quivers.formulas.compile

The FormulaToQVRModule lens: Formulaquivers.dsl.ast_nodes.Module.

compile

Bidirectional didactic.api.Lens from Formula to a QVR quivers.dsl.ast_nodes.Module AST.

The compilation from a formula to a QVR program is a panproto-style lens whose complement is the strict subset of Formula fields that are not recoverable from the emitted Module — packaged as FormulaData. The forward direction produces (module, formula_data) where the structural fields of the formula (which columns exist, intercept flag, random effect group / slope structure, response identifier) are encoded as QVR latent / let / observe shape and the un-encodable fields (per-row data, original identifier names lost to _qvr_name's non-alphanumeric → underscore substitution, the term / name presentation labels, the original formula string) ride in FormulaData. The backward direction recovers the structure by calling _decode_module on the target and fuses it with the complement.

The lens never touches strings — the target is a Module that the existing quivers.dsl.compiler.Compiler consumes directly, identical in shape to one produced by quivers.dsl.parser.parse.

Emitted structure (one named scalar coefficient per design-matrix column, matching the brms / lme4 canonical layout in which poly(x, 2) produces two coefficients poly(x, 2)_1 and poly(x, 2)_2):

  • One object Resp : N declaration per response plate.
  • One object G : K declaration per random-effect grouping factor (with K levels).
  • For each fixed-design column c: one scalar latent draw inside the program body, beta_c <- Normal(0, fixed_prior). The per-row covariate values for c flow in as a free variable via the host-data channel (observations[c]).
  • For each random-effect group (slope | g): a HalfNormal scale latent plus a per-level plate draw, with the per-row contribution as a plate-gather alpha_g[g_idx] (or beta_g_slope[g_idx] * slope for a random slope). Multiple random slopes per group are emitted as independent random-effect terms (the uncorrelated / (... || g) semantics in lme4).
  • One observe step closes the program with the family's observation kernel applied to the inverse-link of the linear predictor.

GetPut holds for every Formula f: backward(*forward(f)) == f.

FormulaToQVRModule

FormulaToQVRModule(family: Family, *, fixed_prior: str = 'Normal(0.0, 5.0)', random_scale_prior: str = 'HalfNormal(1.0)', user_priors: Mapping[str, str] | None = None, reparameterize: Literal['centered', 'noncentered'] = 'noncentered')

Bases: Lens[Formula, Module, FormulaData]

Translate a Formula to a QVR Module AST.

A typed didactic.api.Lens whose complement is a FormulaData carrier: just the fields of the source Formula that are not recoverable from the emitted Module. The per-row data arrays, the original (pre- _qvr_name) identifiers, the per-column term / name presentation labels, and the original formula string travel in the complement; everything else (which columns there are, the intercept / random-term structure, the family choice) is decoded back out of the Module by _decode_module.

PARAMETER DESCRIPTION
family

Response family from quivers.formulas.families.

TYPE: Family

fixed_prior

Default prior for fixed-effect coefficients, in the surface form "Family(arg, arg, ...)"; numeric args become floats, identifier args stay as variable references in the emitted program.

TYPE: str DEFAULT: 'Normal(0.0, 5.0)'

random_scale_prior

Default prior for random-effect scale parameters.

TYPE: str DEFAULT: 'HalfNormal(1.0)'

user_priors

Per-name prior overrides keyed by the latent's variable name in the emitted module.

TYPE: Mapping[str, str] DEFAULT: None

Notes

GetPut: backward (forward(f)) = f for every Formula f. PutGet holds on pairs (t, c) for which t is in the image of forward and c is the corresponding FormulaData.

Source code in src/quivers/formulas/compile.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
def __init__(
    self,
    family: Family,
    *,
    fixed_prior: str = "Normal(0.0, 5.0)",
    random_scale_prior: str = "HalfNormal(1.0)",
    user_priors: Mapping[str, str] | None = None,
    reparameterize: Literal["centered", "noncentered"] = "noncentered",
) -> None:
    self._family = family
    self._fixed_prior = fixed_prior
    self._random_scale_prior = random_scale_prior
    self._user_priors: Mapping[str, str] = dict(user_priors or {})
    if reparameterize not in ("centered", "noncentered"):
        raise ValueError(
            f"FormulaToQVRModule: reparameterize must be 'centered' "
            f"or 'noncentered', got {reparameterize!r}"
        )
    self._reparameterize = reparameterize

fixed_column_observations

fixed_column_observations(formula: Formula) -> dict[str, Tensor]

Per-column free-variable bindings for the host-data channel. One entry per non-intercept fixed column, shape (N,).

Source code in src/quivers/formulas/compile.py
473
474
475
476
477
478
479
480
481
482
483
def fixed_column_observations(self, formula: Formula) -> dict[str, torch.Tensor]:
    """Per-column free-variable bindings for the host-data
    channel.  One entry per non-intercept fixed column, shape
    ``(N,)``.
    """
    obs: dict[str, torch.Tensor] = {}
    for col in formula.fixed_columns:
        if col.is_intercept:
            continue
        obs[col.qvr_name] = torch.as_tensor(col.data.copy(), dtype=torch.float32)
    return obs