Compiler and execution¶
From DSL to executable operator¶
The DSL builder does not execute operators directly. It first produces a
nkdsl.core.operator.SymbolicOperator, which can then be lowered by the
compiler.
The default flow is:
build symbolic terms into
nkdsl.ir.program.SymbolicOperatorIRvalidate symbol scope and update-program consistency
run DSL linting diagnostics (symbol/index/connectivity quality checks)
normalize and fingerprint the IR
look up a cached compiled artifact
on a cache miss, run analysis and fusion planning
lower to a concrete executable operator target (default:
nkdsl.core.compiled.CompiledOperator)
This structure is visible directly in the source tree under nkdsl.compiler.
Default passes¶
The default pipeline created by
nkdsl.compiler.defaults.default_symbolic_pass_pipeline() contains five
passes.
Pre-cache passes¶
nkdsl.compiler.passes.validation.SymbolicValidationPassnkdsl.compiler.passes.diagnostics.SymbolicDiagnosticsPassnkdsl.compiler.passes.normalization.SymbolicNormalizationPass
Post-cache passes¶
nkdsl.compiler.passes.analysis.SymbolicMaxConnSizeAnalysisPassnkdsl.compiler.passes.fusion.SymbolicFusionPass
Diagnostics policy and strictness¶
Diagnostics are configurable through nkdsl.SymbolicCompilerOptions:
diagnostics_enableddiagnostics_min_severityfail_on_warningsmax_diagnosticslint_state_sample_sizelint_branch_sample_caplint_max_exact_hilbert_states
For full details, see Linting. For the per-code lint catalog with examples and fixes, see Lint Messages.
Caching¶
The compiler can cache compiled artifacts in an in-memory store. The relevant public pieces are:
nkdsl.compiler.SymbolicCompilernkdsl.compiler.SymbolicCompilerOptionsnkdsl.compiler.SymbolicCompilationSignaturenkdsl.compiler.SymbolicCacheKeynkdsl.compiler.defaults.default_symbolic_artifact_store()
Direct compiler usage¶
from nkdsl import SymbolicCompiler
compiler = SymbolicCompiler()
artifact = compiler.compile(symbolic_operator)
compiled = artifact.operator
Convenience compilation from the builder or symbolic operator is also supported:
compiled = SymbolicDiscreteJaxOperator(hi, "hop").for_each_site("i").emit(...).compile()
Compiled operators¶
Compiled objects are normal executable operators. The default target exposes
get_conn_padded and is represented by
nkdsl.core.compiled.CompiledOperator. Custom lowering targets can
bind the generated kernel to a different method name (for example
_get_conn_padded).
By default, compiled connectivity kernels deduplicate connected states with the
same x' target, sum their matrix elements, and drop zero-amplitude entries
before final padding. This is controlled by
deduplicate_connected_components (default: True).
xp, mels = compiled.get_conn_padded(x_batch)
How to read printed IR¶
Every symbolic operator can be printed in a readable textual IR form:
op = (
SymbolicDiscreteJaxOperator(hi, "heisenberg_sym", hermitian=True)
.for_each(("i", "j"), over=edges)
.emit(identity(), matrix_element=site("i").value * site("j").value)
.for_each(("i", "j"), over=edges)
.where(site("i").value * site("j").value < 0)
.emit(swap("i", "j"), matrix_element=2.0)
.build()
)
print(op.to_ir())
Example output:
symbolic.operator @"heisenberg_sym" [dtype=float64, hermitian=true, hilbert_size=8] {
; 2 term(s)
term #0 "0" [kbody, n_iter=8, max_conn_size=8] {
iterate: for (i, j) in [(0, 1), (1, 2), (2, 3), ... +5 more]
where: true
emit #0:
update: identity
amplitude: (x[i] * x[j])
}
term #1 "1" [kbody, n_iter=8, max_conn_size=8] {
iterate: for (i, j) in [(0, 1), (1, 2), (2, 3), ... +5 more]
where: ((x[i] * x[j]) < 0)
emit #0:
update: x'[i], x'[j] = x[j], x[i]
amplitude: 2
}
}
Interpretation guide:
symbolic.operator ...: global header with operator name, dtype, hermiticity, and Hilbert size.term #k: one independent contribution to the operator action.iterate: ...: static iteration domain (the tuples the term runs over).where: ...: predicate gate for each iterator tuple.emit #m: one emitted branch for that term.update: ...: state rewrite rule fromxtox'.amplitude: ...: matrix element assigned to that emitted branch.