Skip to content

Program runtime

Running program entry points on the reference machine.

program_runtime

Running program entry points on the reference machine.

An elaborated program is a named computation whose row names the module's canonical random and score instances. Running it means choosing handlers for the two: this module wraps the entry point in a scoring replay of Random and an accumulating Score, so a program applied to data and to a value for each of its sites yields the value it returns and the log joint density the sites and observations accumulate. The wrapper is an ordinary computation added to a copy of the module, so the kernel checks it like any other before it runs.

ProgramRun dataclass

ProgramRun(value: object, log_joint: float, result: ExecutionResult)

What running a program on the reference machine produced.

PARAMETER DESCRIPTION
value

The value the program returned.

TYPE: object

log_joint

The log joint density: every replayed site's log density under its distribution plus every observation's and score's weight.

TYPE: float

result

The underlying execution result, with its trace.

TYPE: ExecutionResult

DeductionRun dataclass

DeductionRun(weight: object, result: ExecutionResult)

The outcome of running a deduction on the reference machine.

PARAMETER DESCRIPTION
weight

The inside weight of the goal in the deduction's semiring: a log weight, a Boolean, or a count.

TYPE: object

result

The machine's full result, with its trace.

TYPE: ExecutionResult

program_entry

program_entry(module: QiecModule, name: str) -> ProgramEntry

Find a program's entry point in a module.

PARAMETER DESCRIPTION
module

The module.

TYPE: QiecModule

name

The program's name.

TYPE: str

RETURNS DESCRIPTION
ProgramEntry

The entry.

RAISES DESCRIPTION
KeyError

If the module has no program of that name.

Source code in src/quivers/qiec/program_runtime.py
def program_entry(module: QiecModule, name: str) -> ProgramEntry:
    """Find a program's entry point in a module.

    Parameters
    ----------
    module : QiecModule
        The module.
    name : str
        The program's name.

    Returns
    -------
    ProgramEntry
        The entry.

    Raises
    ------
    KeyError
        If the module has no program of that name.
    """
    for entry in module.entries:
        if entry.name == name:
            return entry
    raise KeyError(f"module {module.module!r} has no program {name!r}")

joint_module

joint_module(module: QiecModule, name: str) -> tuple[QiecModule, str]

Extend a module with a computation scoring one program's log joint.

PARAMETER DESCRIPTION
module

The module holding the program.

TYPE: QiecModule

name

The program's name.

TYPE: str

RETURNS DESCRIPTION
tuple[QiecModule, str]

The extended module and the wrapper computation's name. The wrapper takes the program's parameters, handles its score instance with an accumulating handler and its random instance with a replaying one, and returns the program's value paired with the accumulated log weight.

RAISES DESCRIPTION
KeyError

If the module has no such program.

Source code in src/quivers/qiec/program_runtime.py
def joint_module(module: QiecModule, name: str) -> tuple[QiecModule, str]:
    """Extend a module with a computation scoring one program's log joint.

    Parameters
    ----------
    module : QiecModule
        The module holding the program.
    name : str
        The program's name.

    Returns
    -------
    tuple[QiecModule, str]
        The extended module and the wrapper computation's name. The
        wrapper takes the program's parameters, handles its ``score``
        instance with an accumulating handler and its ``random``
        instance with a replaying one, and returns the program's value
        paired with the accumulated log weight.

    Raises
    ------
    KeyError
        If the module has no such program.
    """
    return _wrapped_module(module, name, sampling=False)

sample_module

sample_module(module: QiecModule, name: str) -> tuple[QiecModule, str]

Extend a module with a computation running one program forward.

PARAMETER DESCRIPTION
module

The module holding the program.

TYPE: QiecModule

name

The program's name.

TYPE: str

RETURNS DESCRIPTION
tuple[QiecModule, str]

The extended module and the wrapper computation's name. The wrapper takes the program's parameters, handles its random instance with a conditioning replay of the given sites inside a scoring draw of every other site, its score instance with an accumulating handler, and returns the program's value paired with the accumulated log weight: the log joint of the draws it made and the sites it was given.

RAISES DESCRIPTION
KeyError

If the module has no such program.

Source code in src/quivers/qiec/program_runtime.py
def sample_module(module: QiecModule, name: str) -> tuple[QiecModule, str]:
    """Extend a module with a computation running one program forward.

    Parameters
    ----------
    module : QiecModule
        The module holding the program.
    name : str
        The program's name.

    Returns
    -------
    tuple[QiecModule, str]
        The extended module and the wrapper computation's name. The
        wrapper takes the program's parameters, handles its ``random``
        instance with a conditioning replay of the given sites inside a
        scoring draw of every other site, its ``score`` instance with an
        accumulating handler, and returns the program's value paired
        with the accumulated log weight: the log joint of the draws it
        made and the sites it was given.

    Raises
    ------
    KeyError
        If the module has no such program.
    """
    return _wrapped_module(module, name, sampling=True)

static_arguments_for

static_arguments_for(entry: ProgramEntry, data: Mapping[str, object], given: Sequence[StaticArgument] = ()) -> tuple[StaticArgument, ...]

Read a program's static extents off the data it is applied to.

PARAMETER DESCRIPTION
entry

The program's entry point, whose telescope names the extents.

TYPE: ProgramEntry

data

The host value of each parameter, by name.

TYPE: Mapping[str, object]

given

Static arguments supplied explicitly, one per telescope binder in telescope order, or none. A template's object parameter is an extent no data fixes, so a run of the template supplies it; a binder the data does fix must agree with what is supplied.

TYPE: Sequence[StaticArgument] DEFAULT: ()

RETURNS DESCRIPTION
tuple[StaticArgument, ...]

One index literal per telescope binder, in telescope order: the length of the axis of the first parameter whose tensor type is shaped by the binder, else the supplied argument.

RAISES DESCRIPTION
KeyError

If a binder shapes no parameter and none is supplied, or the parameter is not given or has too few axes, or a supplied argument disagrees with the data, or the supplied arguments do not number the telescope.

Source code in src/quivers/qiec/program_runtime.py
def static_arguments_for(
    entry: ProgramEntry,
    data: Mapping[str, object],
    given: Sequence[StaticArgument] = (),
) -> tuple[StaticArgument, ...]:
    """Read a program's static extents off the data it is applied to.

    Parameters
    ----------
    entry : ProgramEntry
        The program's entry point, whose telescope names the extents.
    data : Mapping[str, object]
        The host value of each parameter, by name.
    given : Sequence[StaticArgument]
        Static arguments supplied explicitly, one per telescope binder
        in telescope order, or none. A template's object parameter is
        an extent no data fixes, so a run of the template supplies it;
        a binder the data does fix must agree with what is supplied.

    Returns
    -------
    tuple[StaticArgument, ...]
        One index literal per telescope binder, in telescope order: the
        length of the axis of the first parameter whose tensor type is
        shaped by the binder, else the supplied argument.

    Raises
    ------
    KeyError
        If a binder shapes no parameter and none is supplied, or the
        parameter is not given or has too few axes, or a supplied
        argument disagrees with the data, or the supplied arguments do
        not number the telescope.
    """
    if given and len(given) != len(entry.telescope):
        raise KeyError(
            f"program {entry.name!r} binds {len(entry.telescope)} extent(s); "
            f"{len(given)} static argument(s) were given"
        )
    arguments: list[StaticArgument] = []
    for position, binder in enumerate(entry.telescope):
        supplied = given[position] if given else None
        found: int | None = None
        for parameter in entry.parameters:
            type_ = parameter.type
            if not isinstance(type_, TypeApplication) or len(type_.arguments) != 2:
                continue
            shape = type_.arguments[1]
            if not isinstance(shape, ShapeIndex):
                continue
            for depth, dimension in enumerate(shape.dimensions):
                if (
                    isinstance(dimension, IndexVariable)
                    and dimension.name == binder.name
                ):
                    try:
                        found = _extent_of(data[parameter.name], depth)
                    except KeyError as error:
                        raise KeyError(
                            f"parameter {parameter.name!r} of program "
                            f"{entry.name!r} does not fix its extent "
                            f"{binder.name!r}: {error.args[0]}"
                        ) from error
                    break
            if found is not None:
                break
        if found is None:
            if supplied is None:
                raise KeyError(
                    f"program {entry.name!r} has an extent {binder.name!r} that "
                    "no parameter fixes; supply it as a static argument"
                )
            arguments.append(supplied)
            continue
        literal = IndexLiteral(found, binder.sort)  # type: ignore[arg-type]
        if supplied is not None and supplied != literal:
            raise KeyError(
                f"static argument {binder.name!r} of program {entry.name!r} is "
                f"given as {render_static(supplied)}, but its data fix it at "
                f"{found}"
            )
        arguments.append(literal)
    return tuple(arguments)

run_program

run_program(module: QiecModule, name: str, *, data: Mapping[str, object], sites: Mapping[str, object], parameters: Mapping[str, object] | None = None, static_arguments: Sequence[StaticArgument] = (), fuel: int | None = None, observer: TraceObserver | None = None, providers: tuple[RuntimeProvider, ...] = ()) -> ProgramRun

Run a program with every site replayed and score its log joint.

PARAMETER DESCRIPTION
module

The module holding the program.

TYPE: QiecModule

name

The program's name.

TYPE: str

data

A host value for each of the program's parameters, by name: its data, observations, fibrations, and map numbers.

TYPE: Mapping[str, object]

sites

A host value for each sampled site, by label, or by label and occurrence as "<label>@<n>" for a site a recurrence reaches at every position. Every site the program reaches must be given, and every given site reached.

TYPE: Mapping[str, object]

parameters

The learned weights a deduction the program calls reads, by name; an absent weight reads as zero.

TYPE: Mapping[str, object] | None DEFAULT: None

static_arguments

The program's static extents, one per telescope binder, for an extent no data fixes (a template's object parameter); empty when the data fix every extent.

TYPE: Sequence[StaticArgument] DEFAULT: ()

fuel

A step budget for the run.

TYPE: int | None DEFAULT: None

observer

A callback receiving every trace event as it is emitted.

TYPE: TraceObserver | None DEFAULT: None

providers

Additional typed providers used by computations the program calls.

TYPE: tuple[RuntimeProvider, ...] DEFAULT: ()

RETURNS DESCRIPTION
ProgramRun

The returned value and the log joint.

RAISES DESCRIPTION
KeyError

If the module has no such program, a parameter is not given, or an open extent cannot be read off the data.

ExecutionFailure

If the run fails, a site is missing, or a value does not inhabit its type.

Source code in src/quivers/qiec/program_runtime.py
def run_program(
    module: QiecModule,
    name: str,
    *,
    data: Mapping[str, object],
    sites: Mapping[str, object],
    parameters: Mapping[str, object] | None = None,
    static_arguments: Sequence[StaticArgument] = (),
    fuel: int | None = None,
    observer: TraceObserver | None = None,
    providers: tuple[RuntimeProvider, ...] = (),
) -> ProgramRun:
    """Run a program with every site replayed and score its log joint.

    Parameters
    ----------
    module : QiecModule
        The module holding the program.
    name : str
        The program's name.
    data : Mapping[str, object]
        A host value for each of the program's parameters, by name:
        its data, observations, fibrations, and map numbers.
    sites : Mapping[str, object]
        A host value for each sampled site, by label, or by label and
        occurrence as ``"<label>@<n>"`` for a site a recurrence reaches
        at every position. Every site the program reaches must be
        given, and every given site reached.
    parameters : Mapping[str, object] | None
        The learned weights a deduction the program calls reads, by
        name; an absent weight reads as zero.
    static_arguments : Sequence[StaticArgument]
        The program's static extents, one per telescope binder, for an
        extent no data fixes (a template's object parameter); empty
        when the data fix every extent.
    fuel : int | None
        A step budget for the run.
    observer : TraceObserver | None
        A callback receiving every trace event as it is emitted.
    providers : tuple[RuntimeProvider, ...]
        Additional typed providers used by computations the program calls.

    Returns
    -------
    ProgramRun
        The returned value and the log joint.

    Raises
    ------
    KeyError
        If the module has no such program, a parameter is not given, or
        an open extent cannot be read off the data.
    ExecutionFailure
        If the run fails, a site is missing, or a value does not inhabit
        its type.
    """
    entry = program_entry(module, name)
    extended, wrapper_name = joint_module(module, name)
    configured: dict[str, object] = {
        REPLAY_HANDLER: {
            "kind": "replay",
            "values": dict(sites),
            "score_instance": entry.score_instance,
        },
        ACCUMULATE_HANDLER: {"kind": "score"},
    }
    return _run_wrapped(
        module,
        extended,
        wrapper_name,
        entry,
        configured,
        data=data,
        parameters=parameters,
        static_arguments=static_arguments,
        fuel=fuel,
        observer=observer,
        providers=providers,
    )

sample_program

sample_program(module: QiecModule, name: str, *, data: Mapping[str, object], sites: Mapping[str, object] | None = None, parameters: Mapping[str, object] | None = None, static_arguments: Sequence[StaticArgument] = (), fuel: int | None = None, observer: TraceObserver | None = None, providers: tuple[RuntimeProvider, ...] = ()) -> ProgramRun

Run a program forward, drawing every site it is not given.

PARAMETER DESCRIPTION
module

The module holding the program.

TYPE: QiecModule

name

The program's name.

TYPE: str

data

A host value for each of the program's parameters, by name: its data, observations, fibrations, and map numbers.

TYPE: Mapping[str, object]

sites

A host value for each conditioned site, by label, or by label and occurrence as "<label>@<n>"; every other site is drawn from its distribution. A given site the program never reaches is an error.

TYPE: Mapping[str, object] | None DEFAULT: None

parameters

The learned weights a deduction the program calls reads, by name; an absent weight reads as zero.

TYPE: Mapping[str, object] | None DEFAULT: None

static_arguments

The program's static extents, one per telescope binder, for an extent no data fixes; empty when the data fix every extent.

TYPE: Sequence[StaticArgument] DEFAULT: ()

fuel

A step budget for the run.

TYPE: int | None DEFAULT: None

observer

A callback receiving every trace event as it is emitted.

TYPE: TraceObserver | None DEFAULT: None

providers

Additional typed providers used by computations the program calls.

TYPE: tuple[RuntimeProvider, ...] DEFAULT: ()

RETURNS DESCRIPTION
ProgramRun

The returned value and the log joint of the drawn and given sites together with the program's observations and scores.

RAISES DESCRIPTION
KeyError

If the module has no such program, a parameter is not given, or an open extent cannot be read off the data.

ExecutionFailure

If the run fails or a value does not inhabit its type.

Source code in src/quivers/qiec/program_runtime.py
def sample_program(
    module: QiecModule,
    name: str,
    *,
    data: Mapping[str, object],
    sites: Mapping[str, object] | None = None,
    parameters: Mapping[str, object] | None = None,
    static_arguments: Sequence[StaticArgument] = (),
    fuel: int | None = None,
    observer: TraceObserver | None = None,
    providers: tuple[RuntimeProvider, ...] = (),
) -> ProgramRun:
    """Run a program forward, drawing every site it is not given.

    Parameters
    ----------
    module : QiecModule
        The module holding the program.
    name : str
        The program's name.
    data : Mapping[str, object]
        A host value for each of the program's parameters, by name:
        its data, observations, fibrations, and map numbers.
    sites : Mapping[str, object] | None
        A host value for each conditioned site, by label, or by label
        and occurrence as ``"<label>@<n>"``; every other site is drawn
        from its distribution. A given site the program never reaches
        is an error.
    parameters : Mapping[str, object] | None
        The learned weights a deduction the program calls reads, by
        name; an absent weight reads as zero.
    static_arguments : Sequence[StaticArgument]
        The program's static extents, one per telescope binder, for an
        extent no data fixes; empty when the data fix every extent.
    fuel : int | None
        A step budget for the run.
    observer : TraceObserver | None
        A callback receiving every trace event as it is emitted.
    providers : tuple[RuntimeProvider, ...]
        Additional typed providers used by computations the program calls.

    Returns
    -------
    ProgramRun
        The returned value and the log joint of the drawn and given
        sites together with the program's observations and scores.

    Raises
    ------
    KeyError
        If the module has no such program, a parameter is not given, or
        an open extent cannot be read off the data.
    ExecutionFailure
        If the run fails or a value does not inhabit its type.
    """
    entry = program_entry(module, name)
    extended, wrapper_name = sample_module(module, name)
    configured: dict[str, object] = {
        CONDITION_HANDLER: {
            "kind": "replay",
            "values": dict(sites or {}),
            "score_instance": entry.score_instance,
        },
        DRAW_HANDLER: {"kind": "draw-scoring", "score_instance": entry.score_instance},
        ACCUMULATE_HANDLER: {"kind": "score"},
    }
    return _run_wrapped(
        module,
        extended,
        wrapper_name,
        entry,
        configured,
        data=data,
        parameters=parameters,
        static_arguments=static_arguments,
        fuel=fuel,
        observer=observer,
        providers=providers,
    )

foreign_handler_configuration

foreign_handler_configuration(module: QiecModule) -> dict[str, object]

The runtime configuration of a module's elaborated foreign handlers.

PARAMETER DESCRIPTION
module

The module.

TYPE: QiecModule

RETURNS DESCRIPTION
dict[str, object]

By handler name: the enumeration handlers of marginalization blocks, the collecting handlers of those blocks and of deductions, and the search handlers of deductions, each with the options its name records.

Source code in src/quivers/qiec/program_runtime.py
def foreign_handler_configuration(module: QiecModule) -> dict[str, object]:
    """The runtime configuration of a module's elaborated foreign handlers.

    Parameters
    ----------
    module : QiecModule
        The module.

    Returns
    -------
    dict[str, object]
        By handler name: the enumeration handlers of marginalization
        blocks, the collecting handlers of those blocks and of
        deductions, and the search handlers of deductions, each with
        the options its name records.
    """
    configured: dict[str, object] = {}
    for handler in module.handlers:
        if handler.implementation != "foreign":
            continue
        marginal = _marginal_handler(handler.name)
        deduction = _deduction_handler(handler.name)
        if handler.effect.name == "Random" and marginal is not None:
            grouped, reduction = marginal
            configured[handler.name] = {
                "kind": "enumerate",
                "grouped": grouped,
                "reduction": reduction,
            }
        elif handler.effect.name == "Weight" and handler.name.endswith(
            "collect_marginal"
        ):
            configured[handler.name] = {"kind": "collect"}
        elif deduction is not None:
            kind, semiring = deduction
            if kind == "search":
                configured[handler.name] = {
                    "kind": "search",
                    "reduction": SEARCH_REDUCTION_OF[semiring],
                }
            else:
                configured[handler.name] = {
                    "kind": "collect",
                    "combine": COLLECT_COMBINE_OF[semiring],
                    "forkable": True,
                }
    return configured

run_deduction

run_deduction(module: QiecModule, name: str, *, tokens: Sequence[str] | None = None, axioms: Sequence[object] | None = None, axiom_weights: Sequence[object] | None = None, parameters: Mapping[str, object] | None = None, fuel: int | None = None) -> DeductionRun

Run a deduction's entry on the reference machine.

PARAMETER DESCRIPTION
module

The module holding the deduction.

TYPE: QiecModule

name

The deduction's name.

TYPE: str

tokens

The sentence, for a deduction with a lexicon.

TYPE: Sequence[str] | None DEFAULT: None

axioms

The axiom items, for a deduction without one; runtime constructor values of its item family.

TYPE: Sequence[object] | None DEFAULT: None

axiom_weights

One weight per axiom in the semiring's carrier.

TYPE: Sequence[object] | None DEFAULT: None

parameters

The learned weights by name; an absent weight reads as zero.

TYPE: Mapping[str, object] | None DEFAULT: None

fuel

A step budget for the run.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
DeductionRun

The goal's inside weight.

RAISES DESCRIPTION
KeyError

If the module has no such deduction.

ValueError

If the input does not fit the deduction: tokens for one that takes axioms, or the reverse.

ExecutionFailure

If the run fails.

Source code in src/quivers/qiec/program_runtime.py
def run_deduction(
    module: QiecModule,
    name: str,
    *,
    tokens: Sequence[str] | None = None,
    axioms: Sequence[object] | None = None,
    axiom_weights: Sequence[object] | None = None,
    parameters: Mapping[str, object] | None = None,
    fuel: int | None = None,
) -> DeductionRun:
    """Run a deduction's entry on the reference machine.

    Parameters
    ----------
    module : QiecModule
        The module holding the deduction.
    name : str
        The deduction's name.
    tokens : Sequence[str] | None
        The sentence, for a deduction with a lexicon.
    axioms : Sequence[object] | None
        The axiom items, for a deduction without one; runtime
        constructor values of its item family.
    axiom_weights : Sequence[object] | None
        One weight per axiom in the semiring's carrier.
    parameters : Mapping[str, object] | None
        The learned weights by name; an absent weight reads as zero.
    fuel : int | None
        A step budget for the run.

    Returns
    -------
    DeductionRun
        The goal's inside weight.

    Raises
    ------
    KeyError
        If the module has no such deduction.
    ValueError
        If the input does not fit the deduction: tokens for one that
        takes axioms, or the reverse.
    ExecutionFailure
        If the run fails.
    """
    entry_name = f"{name}__derive"
    if not any(item.name == entry_name for item in module.computations):
        raise KeyError(f"module {module.module!r} has no deduction {name!r}")
    computation = next(
        item for item in module.computations if item.name == f"{name}__run"
    )
    takes_tokens = len(computation.parameters) == 1
    if takes_tokens and (tokens is None or axioms is not None):
        raise ValueError(f"deduction {name!r} takes a sentence of tokens")
    if not takes_tokens and (
        axioms is None or axiom_weights is None or tokens is not None
    ):
        raise ValueError(f"deduction {name!r} takes its axioms and their weights")
    extended, wrapper_name = _deduction_wrapper(module, computation)
    if takes_tokens:
        assert tokens is not None
        arguments: tuple[object, ...] = (tuple(tokens),)
        extent = len(tokens)
    else:
        assert axioms is not None and axiom_weights is not None
        arguments = (tuple(axioms), tuple(axiom_weights))
        extent = len(axioms)
    binder = computation.telescope[0]
    assert isinstance(binder, IndexBinder)
    configured: dict[str, object] = {}
    if wrapper_name != computation.name:
        configured[PARAMS_HANDLER] = {"kind": "param", "values": dict(parameters or {})}
    configured.update(foreign_handler_configuration(module))
    result = run_named(
        extended,
        wrapper_name,
        arguments,
        static_arguments=(IndexLiteral(extent, binder.sort),),  # type: ignore[arg-type]
        runtime=RuntimeConfiguration(
            (RuntimeSelection("core", {"handlers": configured}),)
        ),
        fuel=fuel,
    )
    return DeductionRun(result.value, result)

deduction_item

deduction_item(module: QiecModule, name: str, symbol: str, *fields: object) -> RuntimeConstructor

Build an item of a deduction's family as a host value.

PARAMETER DESCRIPTION
module

The module holding the deduction.

TYPE: QiecModule

name

The deduction's name.

TYPE: str

symbol

The atom or constructor symbol, as the deduction spells it.

TYPE: str

*fields

The constructor's fields: integers for position slots, items for the rest.

TYPE: object DEFAULT: ()

RETURNS DESCRIPTION
RuntimeConstructor

The item, which run_deduction takes among the axioms of a deduction without a lexicon.

RAISES DESCRIPTION
KeyError

If the deduction declares no such symbol.

ValueError

If the field count differs from the constructor's arity.

Source code in src/quivers/qiec/program_runtime.py
def deduction_item(
    module: QiecModule, name: str, symbol: str, *fields: object
) -> RuntimeConstructor:
    """Build an item of a deduction's family as a host value.

    Parameters
    ----------
    module : QiecModule
        The module holding the deduction.
    name : str
        The deduction's name.
    symbol : str
        The atom or constructor symbol, as the deduction spells it.
    *fields : object
        The constructor's fields: integers for position slots, items for
        the rest.

    Returns
    -------
    RuntimeConstructor
        The item, which ``run_deduction`` takes among the axioms of a
        deduction without a lexicon.

    Raises
    ------
    KeyError
        If the deduction declares no such symbol.
    ValueError
        If the field count differs from the constructor's arity.
    """
    constructor_name = f"{name}__{symbol}"
    constructor = next(
        (item for item in module.constructors if item.name == constructor_name), None
    )
    if constructor is None:
        raise KeyError(f"deduction {name!r} declares no symbol {symbol!r}")
    if len(fields) != len(constructor.fields):
        raise ValueError(
            f"{symbol!r} of deduction {name!r} takes {len(constructor.fields)} "
            f"field(s), not {len(fields)}"
        )
    family = next(item for item in module.families if item.id == constructor.family)
    return RuntimeConstructor(
        constructor.id, (), tuple(fields), TypeApplication(family.type_constructor, ())
    )