Skip to content

Compiler

Lowering from a DSL AST to an executable Program.

compiler

quivers.dsl.compiler: AST -> Program compiler.

Re-exports the public surface from the package's submodules.

Compiler

Compiler(module: Module, *, module_name: str | None = None, file_path: str = '<source>', lower_qiec: bool = True)

Bases: _DeclarationsMixin, _ProgramsMixin, _StructuralMixin, _DeductionsMixin, _ResolutionMixin, _ExpressionsMixin

Compile a quivers DSL AST into a Program.

The compiler maintains three environments:

  • objects: name -> SetObject (discrete finite sets)
  • spaces: name -> ContinuousSpace
  • morphisms: name -> Morphism or ContinuousMorphism (any morphism-like)

It processes statements in order and compiles the output expression into a Program wrapping the morphism DAG.

PARAMETER DESCRIPTION
module

The parsed AST.

TYPE: Module

lower_qiec

Whether to lower the checked QIEC projection. Tooling sets this to False only for its classic-compiler recovery pass after QIEC has already produced a diagnostic.

TYPE: bool DEFAULT: True

Source code in src/quivers/dsl/compiler/core.py
def __init__(
    self,
    module: Module,
    *,
    module_name: str | None = None,
    file_path: str = "<source>",
    lower_qiec: bool = True,
) -> None:
    self._qiec_module: QiecModule | None = None
    # The QIEC projection is lowered on first use, after the
    # statements' own checks when `compile` drives it, so a source
    # error the statement compiler names is reported by it.
    self._qiec_source: Module | None = None
    self._qiec_module_name = module_name
    self._qiec_file_path = file_path
    if lower_qiec and has_qiec_surface(module):
        self._qiec_source = module
        module = non_qiec_projection(module)
    self._module = module
    self._algebra: Algebra = PRODUCT_FUZZY
    self._categories: list[str] = []
    self._rules: dict = {}
    self._bundles: dict[str, tuple[str, ...]] = {}
    self._aliases: dict[str, ObjectExpr] = {}
    self._alias_names: set[str] = set()
    # Built-in transformation catalog: singletons looked up by
    # bare name (``expectation``, ``log_prob``, …) and
    # constructors invoked with arguments (``softmax(B)``,
    # ``bayes_invert(prior)``).  Disjoint from
    # `_transformations`, which holds user define-bound
    # transformations defined inside the module.
    self._trans_singletons: dict = _build_default_trans_singletons()
    self._trans_constructors: dict = _build_default_trans_constructors()
    # User-defined transformations bound via ``define t = …``.
    # Disjoint from `_morphisms`: a ``define`` whose RHS
    # resolves to a transformation lands here; a ``define`` whose
    # RHS resolves to a morphism lands in ``_morphisms``.
    self._transformations: dict = {}
    self._objects: dict[str, SetObject] = {}
    self._spaces: dict = {}
    self._morphisms: dict = {}
    self._groups: dict[str, list[str]] = {}
    self._output_expr: Expr | None = None
    # Parametric-program templates: dependent kernels Π(p:P).Kern(dom(p),cod(p))
    # stored as their unsubstituted AST decl. Instantiated at each call
    # site by parameter substitution + α-renaming of internal latents.
    self._program_templates: dict[str, ProgramDecl] = {}
    # Operadic contraction declarations. Each entry is callable
    # from the DSL at define-binding sites; the value records the
    # compiled `EinsumWiring` plus the declared domain /
    # codomain typing for shape-checking at invocation time.
    self._contractions: dict[str, "_CompiledContraction"] = {}

categories property

categories: list[str]

The declared category atoms.

rules property

rules: dict

The compiled rule schemas from rule declarations.

objects property

objects: dict[str, SetObject]

The compiled object environment.

spaces property

spaces: dict

The compiled space environment.

morphisms property

morphisms: dict

The compiled morphism environment.

algebra property

algebra: Algebra

The active algebra.

programs property

programs: dict

Declared parametric program templates (program NAME(...)).

deductions property

deductions: dict

Declared deduction systems (deduction NAME : ...).

signatures property

signatures: dict

Declared signatures (signature NAME).

encoders property

encoders: dict

Declared encoders (encoder NAME : Sig).

decoders property

decoders: dict

Declared decoders (decoder NAME : Sig).

losses property

losses: dict

Declared loss heads (loss NAME : ... [on=...]).

Keyed by entry name; values are the registered :class:LossEntry records. Empty when no loss decl appears in the module.

bundles property

bundles: dict[str, tuple[str, ...]]

Declared bundle aliases (bundle NAME = R1 | R2 | ...).

contractions property

contractions: dict

Declared tensor-network contractions (contraction NAME : ... [wiring=...]).

transformations property

transformations: dict

User-declared transformation constructors / singletons.

qiec_module property

qiec_module: QiecModule | None

The checked QIEC module, or None when the source has no QIEC surface.

RETURNS DESCRIPTION
QiecModule | None

The module, lowered on first access.

RAISES DESCRIPTION
QiecDiagnosticError

If the source's QIEC surface fails to lower, other than by a program gap, which the module records instead.

entry_points

entry_points() -> tuple[EntryPoint, ...]

The executable entry points of the checked module.

RETURNS DESCRIPTION
tuple[EntryPoint, ...]

Every program and define computation, in declaration order; empty when the source has no checked module.

Source code in src/quivers/dsl/compiler/core.py
def entry_points(self) -> tuple[EntryPoint, ...]:
    """The executable entry points of the checked module.

    Returns
    -------
    tuple[EntryPoint, ...]
        Every ``program`` and ``define`` computation, in declaration
        order; empty when the source has no checked module.
    """
    module = self.qiec_module
    if module is None:
        return ()
    return entry_points(module)

entry

entry(name: str) -> EntryPoint

One entry point of the checked module, by name.

PARAMETER DESCRIPTION
name

The entry's source name.

TYPE: str

RETURNS DESCRIPTION
EntryPoint

The entry.

RAISES DESCRIPTION
ExecutionFailure

With code qiec-run-module if the source has no checked module, or qiec-run-computation for an unknown name.

Source code in src/quivers/dsl/compiler/core.py
def entry(self, name: str) -> EntryPoint:
    """One entry point of the checked module, by name.

    Parameters
    ----------
    name : str
        The entry's source name.

    Returns
    -------
    EntryPoint
        The entry.

    Raises
    ------
    ExecutionFailure
        With code ``qiec-run-module`` if the source has no checked
        module, or ``qiec-run-computation`` for an unknown name.
    """
    module = self.qiec_module
    if module is None:
        raise ExecutionFailure(
            ExecutionDiagnostic(
                "qiec-run-module",
                "the source has no checked module and no entry points",
                name,
            )
        )
    if module.gap and not any(point.name == name for point in entry_points(module)):
        raise ExecutionFailure(
            ExecutionDiagnostic(
                "qiec-program-gap",
                f"entry point {name!r} was not lowered: {module.gap}",
                name,
            )
        )
    return entry_point(module, name)

compile

compile() -> Program

Compile the module into a trainable Program.

RETURNS DESCRIPTION
Program

The compiled nn.Module wrapping the morphism DAG.

RAISES DESCRIPTION
CompileError

On semantic errors (undefined names, type mismatches, etc.).

Source code in src/quivers/dsl/compiler/core.py
def compile(self) -> Program:
    """Compile the module into a trainable Program.

    Returns
    -------
    Program
        The compiled nn.Module wrapping the morphism DAG.

    Raises
    ------
    CompileError
        On semantic errors (undefined names, type mismatches, etc.).
    """
    for stmt in self._module.statements:
        self._compile_statement(stmt)
    qiec_module = self.qiec_module
    if self._output_expr is None:
        # A module may declare only structural artifacts
        # (signatures, encoders, decoders, losses) with no
        # exported morphism; the returned Program is a container
        # carrying those artifacts.
        program = Program(None)
    elif isinstance(
        self._output_expr, ExprIdent
    ) and self._output_expr.name in getattr(self, "_program_templates", {}):
        # The export names a parametric program template. A
        # template has no root morphism on its own (a function
        # ``Pi (p : P). Kern(dom(p), cod(p))`` rather than a
        # single Kleisli arrow); the returned Program is a
        # container holding the template, which the caller
        # instantiates by attribute access.
        tmpl_name = self._output_expr.name
        program = Program(None)
        invoker = self._make_template_invoker(tmpl_name)
        program.templates = {tmpl_name: invoker}
        # Convenience: ``program.<name>(...)`` works directly.
        object.__setattr__(program, tmpl_name, invoker)
    else:
        root_morphism = self._compile_expr(self._output_expr)
        program = Program(root_morphism)
    # Exports after the first become named accessors on the compiled
    # Program: ``export a`` then ``export b`` makes ``a`` the primary
    # morphism (``program.morphism`` / ``program(...)``) and
    # ``program.b`` the compiled form of the second.
    self._attach_secondary_exports(program)
    # Attach the compiler's deduction and posterior registries to
    # the Program so downstream callers can reach them after
    # `quivers.dsl.load(...)`.
    program.deductions = getattr(self, "_deductions", {})
    program.posteriors = getattr(self, "_posteriors", {})
    program.signatures = getattr(self, "_signatures", {})
    program.encoders = getattr(self, "_encoders", {})
    program.decoders = getattr(self, "_decoders", {})
    program.losses = getattr(self, "_loss_registry", None)
    # Structural components are ordinary PyTorch modules. Register them
    # under stable source-derived names as well as exposing the ergonomic
    # dictionaries above, so ``Program.parameters()``, ``state_dict()``,
    # optimizers, and checkpointing see every learned tensor.
    for role, components in (
        ("encoder", program.encoders),
        ("decoder", program.decoders),
    ):
        for name, component in components.items():
            if isinstance(component, nn.Module):
                program.add_module(f"_qvr_{role}_{name}", component)
    # Preserve the checked projection on the compiled container.  A
    # QIEC-only source thus becomes an inspection/evaluation container;
    # its computation graph is never misrepresented as a PyTorch morphism.
    object.__setattr__(program, "qiec", qiec_module)
    # Wire the loss registry into every compiled deduction so the
    # agenda's rule-firing and chart-completion paths can fire
    # rule-attached and chart-attached losses automatically.
    if program.losses is not None:
        for name, system in program.deductions.items():
            system._loss_registry = program.losses  # type: ignore[attr-defined]
            system._deduction_name = name  # type: ignore[attr-defined]
    return program

compile_env

compile_env() -> dict

Compile all statements and return the full environment.

Useful for inspection without requiring an output declaration.

RETURNS DESCRIPTION
dict

Combined environment of every declared atom: the active algebra, objects, spaces, morphisms, rules, programs (parametric templates), deductions, signatures, encoders, decoders, losses, bundles, contractions, transformations.

Source code in src/quivers/dsl/compiler/core.py
def compile_env(self) -> dict:
    """Compile all statements and return the full environment.

    Useful for inspection without requiring an output declaration.

    Returns
    -------
    dict
        Combined environment of every declared atom: the active
        algebra, objects, spaces, morphisms, rules, programs
        (parametric templates), deductions, signatures, encoders,
        decoders, losses, bundles, contractions, transformations.
    """
    for stmt in self._module.statements:
        self._compile_statement(stmt)
    env: dict = {}
    env["__algebra__"] = self._algebra
    qiec_module = self.qiec_module
    if qiec_module is not None:
        env["__qiec__"] = qiec_module
    for name, obj in self._objects.items():
        env[name] = obj
    for name, space in self._spaces.items():
        env[name] = space
    for name, morph in self._morphisms.items():
        env[name] = morph
    for name, rule in self._rules.items():
        env[name] = rule
    for name, tmpl in getattr(self, "_program_templates", {}).items():
        env[name] = tmpl
    for name, system in getattr(self, "_deductions", {}).items():
        env[name] = system
    for name, sig in getattr(self, "_signatures", {}).items():
        env[name] = sig
    for name, enc in getattr(self, "_encoders", {}).items():
        env[name] = enc
    for name, dec in getattr(self, "_decoders", {}).items():
        env[name] = dec
    reg = getattr(self, "_loss_registry", None)
    if reg is not None:
        for entry in reg.entries:
            env[entry.name] = entry
    for name, bundle in self._bundles.items():
        env[name] = bundle
    for name, contr in getattr(self, "_contractions", {}).items():
        env[name] = contr
    return env

CompileError

CompileError(message: str, line: int = 0, col: int = 0)

Bases: Exception

Raised when the compiler encounters a semantic error.

PARAMETER DESCRIPTION
message

Error description.

TYPE: str

line

Source line number (0 if unknown).

TYPE: int DEFAULT: 0

col

Source column number (0 if unknown).

TYPE: int DEFAULT: 0

Source code in src/quivers/dsl/compiler/_prelude.py
def __init__(self, message: str, line: int = 0, col: int = 0) -> None:
    self.line = line
    self.col = col
    loc = f"line {line}, col {col}: " if line else ""
    super().__init__(f"{loc}{message}")