Extending and performance hooks

This page lists the places where code outside the package can plug in new operators, states and spaces. Each entry links to the full docstring.

Most of the methods below are not exported. Add methods to them with import FermionicHilbertSpaces: name or by qualifying the name. They are less stable than the exported API and may change in a minor release.

Custom operators

A new kind of symbolic operator is a subtype of AbstractSym. The tutorials Custom operators: Clock and Custom operators: Floquet build one step by step.

Algebra, from NonCommutativeProducts.jl:

  • @nc MyOp makes + and * build symbolic sums and products of MyOp.
  • mul_effect(a::MyOp, b::MyOp) gives the rule for the product a * b: nothing keeps it, Swap(c) rewrites it as c * b * a, and AddTerms((Swap(c), r, ...)) adds further terms. This is required; without it, products such as X * X throw an ArgumentError.
  • Operators on different groups commute, ordered by group, so no rule is needed for products of MyOp with the operators of the package. Products of different operator types on the same group throw an ArgumentError unless they have their own mul_effect.
  • Operators of the same type on different groups are ordered only by their mul_effect, which should return Swap(1) when they are out of order; otherwise equal products can compare unequal.

Connection to Hilbert spaces:

  • apply_local_operator(op, state, space, precomp) -> (newstate, amplitude) defines how op acts on a basis state. This is required for matrix representations.
  • symbolic_group(op) returns the label of the space op acts on. This is required.
  • Base.adjoint(op) returns the adjoint operator. This is required; transposed spaces, as used for open systems, apply adjoint(op).
  • mat_eltype(::Type{<:MyOp}) gives the element type of the matrix elements. The default is Float64, so operators with complex matrix elements need a method.

For speed:

  • _precomputation_before_operator_application(op, space) computes data once per operator factor, which apply_local_operator then receives as precomp for every state.
  • apply_local_operators(op::NCMul, state, space, precomps; transpose) applies a whole product at once. A method for products of one operator type avoids applying the factors one by one.

Custom basis states and spaces

FermionicHilbertSpaces.groups(H) returns the spaces that line up with the slots of a basis state: factors(H) for a product space, and (H,) for any other space. See Hilbert space and array operations.

The simplest custom space is a GenericHilbertSpace(label, states), which works with any type of state. Its optional third argument state_index can be a function that computes the index of a state, which avoids the default dictionary lookup.

  • AbstractBasisState is the supertype of basis-state types. Subtyping it lets strings name states, and lets constrained spaces return index 0 for states of other types.
  • interpret_state(input, ::Type{MyState}) parses labels such as strings into states, for Kets, basisstate and state_index.
  • internal_rep(state, space, Int) and physical_rep(n, ::Type{MyState}) convert states to integers and back. When all groups of a product space have them, operators on the product space act on integers, which is faster.
  • has_arithmetic_state_index(H) returns true for a space type whose state_index is computed from the state alone. Constrained spaces over such a space then find states with a table instead of a dictionary. The table relies on state_index returning the exact index in 1:dim(H) for each basis state and 0 for every other state of the same type.
  • AbstractSubspace is the supertype of spaces spanned by some basis states of a parent space. A subtype implements parent, subspace_states, subspace_index and rewrap, and gets tensor products, partial traces and matrix representations from its parent. default_subspace_index builds the index.
  • default_subspace_index(constraint, parent, states) builds the lookup that state_index uses on constrained and sector spaces. A method that dispatches on the type of constraint only, not on parent, selects a custom index. It returns default_subspace_index(NoSymmetry(), parent, states) when states don't fit it.
  • state_position(index, state, parent) is the one method a custom index implements: the position of state in the subspace, or 0. It may use parent only through its basis states and state_index. Constructors check the index on the first, middle and last state and throw an ArgumentError on a mismatch.

To restrict a space of custom states, use SectorConstraint or FilterConstraint. Their required argument is the reducer, which is called on each basis state. With maps and spaces together, it is called instead on the values that maps gives for the parts of the state in spaces. See Constrained Hilbert spaces and conserved quantum numbers.

Internally, tensor_product, reshape and partial_trace decompose and recombine basis states through a common mapper protocol. state_mapper(H, Hs) returns a mapper object, split_state(state, mapper) returns a pair (outcomes, weights), where each outcome is a tuple with one substate per target subsystem, and combine_states(substates, mapper) returns a pair (states, weights). The collections can have any concrete type; treat them as iterables.