The Symbolic Operator

The main entry point is nkdsl.SymbolicDiscreteJaxOperator, also exported as nkdsl.SymbolicDiscreteJaxOperator.

A builder accumulates terms. Each term has:

  • an iterator

  • an optional predicate

  • one or more emissions

A minimal shape looks like this:

from nkdsl import SymbolicDiscreteJaxOperator, shift, site

op = (
    SymbolicDiscreteJaxOperator(hi, "raise_one")
    .for_each_site("i")
    .where(site("i") < 2)
    .emit(shift("i", +1), matrix_element=1.0)
    .build()
)

Important builder rules

Iterator methods open terms

Every call to globally or for_each* seals the current term and starts a new one. That means where and emit always attach to the most recently opened term.

build() returns a symbolic object

build() returns nkdsl.core.operator.SymbolicOperator. This object holds typed IR terms and can later be compiled.

compile() is a convenience shortcut

Calling compile() on the builder is equivalent to build().compile().

Connectivity semantics

By default, compile() enables connected-state deduplication:

  • duplicate emitted targets x' are merged

  • matrix elements are summed across merged entries

  • zero-amplitude components are dropped before padding

Pass deduplicate_connected_components=False to keep raw per-branch connectivity output.

Term metadata

Two builder helpers matter when definitions become larger.

named(name)

gives the current term a readable identifier for IR dumps and compiler output

max_conn_size(hint)

sets a manual static upper bound for the term fanout when you know a tighter bound than the generic iterator-size times emission-count estimate

Hermiticity and dtype

The builder constructor accepts dtype and hermitian. These become part of both the symbolic IR and the compiled artifact metadata.

Warning

You are responsible to ensure that the hermiticity of the operator is correct! If you set hermitian=True for a non-Hermitian operator, gradients will be incorrectly computed.