Changelog

All notable changes to the quivers library are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[0.13.0] - 2026-05-22

Added

  • GHCi-style :type and :kind commands in the REPL. :type accepts an expression (morphism, program, deduction, scoped sample / observe / let / marginalize / return site, contraction, rule, encoder / decoder / loss, bundle, transformation) and prints the GHCi-shaped one-line signature name :: A -> B (or name :: (Real, Real) => Word -> Word for parametric programs, with the Π-bound parameter universes in a Haskell-style constraint context). :kind accepts a type-level binding (object, space, signature, category) or a bare type expression (FinSet 3, A * B) and prints the kind. Each command rejects the wrong sort with a redirect: :type Doc → "Doc is a type, not an expression; use :kind Doc". The bare-expression fallback tries :type first, then :kind, so users can keep typing a name on its own. Aliases :t and :k work as before.
  • User-given type aliases preserved across the REPL. The compiler internally resolves object Item : FinSet 200 to FinSet(name='_FinSet_200', cardinality=200). The REPL now builds an id(obj) → user_name map per session and renders morphism signatures through it, so :type X prints X :: Item -> H_in rather than X :: FinSet 200 -> FinSet 4. The map recurses into product / coproduct components, so a mixed Doc * Topic still surfaces both user names.
  • render_signature(compiler, name) shared helper in quivers.cli.repl_session. Single entry point for the GHCi-style signature line, used by both the REPL's :type / :kind / bare-expression fallback and the LSP's hover panel — so a binding looks the same in the TUI and in an editor.
  • LSP hover leads with a **Type** / **Kind** block. Hover now shows the GHCi signature first (same string :type / :kind would print), then the doc comment, then the verbatim QVR source slice, then the collapsed AST. Editor users get the one-glance signature that REPL users already had.
  • New semantics chapter: docs/semantics/typing.md. Chapter 8 in the semantics development, presenting the QVR type system as a proof calculus: syntactic categories (kinds, type expressions, families, term expressions, statements, programs); contexts (\(\Gamma\), \(\Delta\), \(\Phi\)); judgments (\(\Gamma \vdash \tau : \kappa\), \(\Gamma; \Phi \vdash e : A \rightsquigarrow B\), \(\Gamma; \Phi \vdash F(\bar a) : \mathsf{Kernel}[\Phi, B]\), \(\Gamma; \Phi \vdash s \dashv \Phi'\), \(\Gamma \vdash p : (\Delta) \Rightarrow A \rightsquigarrow B\)); the full inference-rule set; the four structural lemmas (Weakening, Substitution, Type Uniqueness, Inversion); the Soundness theorem against the denotation with proof sketches for the representative Compose / Bind / Marginalize / Prog cases; the Subject-Reduction theorem for parametric instantiation; a Completeness theorem (every phrase with a defined raw denotation has a derivation) with a Decidable-Type-Membership corollary on the non-residuated fragment; the bidirectional algorithm underlying the compiler's typechecker; and a worked example deriving the LDA program's full judgment.

Changed

  • Constraint context in parametric programs drops binder names. A program with typed parameters renders as lda :: (Real, Real) => Word -> Word, not lda :: (alpha : Real, beta : Real) => Word -> Word — Haskell convention. Parameter names are accessible through :info lda.
  • :type of a parametric program no longer curries the parameters with the kernel arrow. Per docs/semantics/programs.md §3a, typed parameters denote a dependent family \(\prod_{p:P} \mathbf{Kern}(\mathrm{dom}, \mathrm{cod})\) — they live in a \(\Pi\), not in the kernel's domain. Rendering them as Real -> Real -> Word -> Word (the previous "GHCi-curried" form) conflates the \(\Pi\)-context with the kernel arrow.
  • Compiler-driven expression typing in :type. The expression-fallback path now drives Compiler._compile_expr directly on the already-compiled session state instead of re-running the full module compile for every :type query.
  • Auto-generated placeholder names stripped from output. The compiler assigns synthetic names like _FinSet_20 and _Real_8 to anonymous FinSets and Euclidean spaces. They no longer leak into :type / :kind output as Doc : _FinSet_20; the stripper renders them as Doc : FinSet 20.
  • Help-text rewrite for :type and :kind. Both commands' inline :help entries and detailed :help :type / :help :kind text now match the GHCi-style semantics.

Documentation

  • docs/semantics/typing.md added as chapter 8; index renumbered.
  • Semantics corpus audit (substantive). After threading typing.md into the development:
  • Cross-link setting.md §6, morphisms.md, and programs.md into the new typing chapter at their natural touch-points.
  • morphisms.md §1.1 gets a "Categorical structure" proposition under the strict-quantale hypothesis, proving \(\mathcal{V}\text{-}\mathbf{Rel}\) is a symmetric monoidal category using the join's universal colimit property, \(\otimes\) associativity, \(\bot\) absorption, and the distributivity law; flags the lax cases of \(\mathcal{V}_{\mathrm{pf}}\) / \(\mathcal{V}_{\mathrm{L}}\) explicitly.
  • programs.md §5 upgrades the monad-law table to a "Monad laws for QVR programs" theorem with proofs derived from the Giry monad's universal property (Giry 1982, Kock 1972, Fritz 2020). The commutativity equation is now stated with an explicit double-strength pair \(\mathrm{dst}_1\) / \(\mathrm{dst}_2\) rather than the previous ambiguous one-line formula, identifying Fubini–Tonelli as its substance.
  • adequacy.md §3.4 upgrades the one-liner "the proofs are straightforward" to a full "Adequacy of @" proposition with term-by-term unfolding; the remaining five tensor combinators get a table whose every row makes the proof template explicit.
  • effects.md §4a adds explicit inference rules for the four chart firings (Base, Lift_T, Handle_{T→S}, Eliminate_T, Swap_{T|U}), turning the previously prose-only joint type-and-effect dispatch into a proof system.
  • Stale class-name references across the corpus updated: TypeExprObjectExpr, TypeProductObjectProduct, TypeEffectApplyObjectEffectApply (the AST class was renamed and the docs had not tracked it).
  • adequacy.md §3.7 references the real method names (_compile_program, not _compile_program_body; dispatch key compose, not compose_deductions).
  • adequacy.md §4: drop references to tests/test_resolution_lenses.py, which no longer exists.
  • British → American spelling sweep across all 14 semantics chapters. marginalisation → marginalization, realise → realize, behaviour → behavior, catalogue → catalog, modelling → modeling, analyse → analyze, etc.

Fixed

  • Compiler.signatures, Compiler.categories now exposed to the REPL's bare-name lookup. Previously, :type SomeSignature returned "unknown name" because signatures and categories were not in either the value-level or type-level buckets the REPL consulted. They are now classified as type-level (a signature denotes a generalised algebraic theory; a category denotes an atom universe) and respond to :kind.
  • Contraction and rule signatures render correctly under :type. Contractions get an operadic c :: (A_1, …, A_k) -> B signature; rules get the r :: prem |- concl schema form.

[0.12.0] - 2026-05-21

Added

  • :: scope paths everywhere a binding name is taken. :info, :type, :doc, :browse, :plate, :graph, :where, :effects, :shape all accept a ::-separated scope path. The leftmost segment is a top-level binding; each subsequent segment looks up a named child in that binding's scope. Coverage across every container kind: program (typed parameters + sample / observe / let / marginalize / return steps; recursing into nested marginalize bodies), deduction (rules / atoms / lexicon entries), signature (sorts / constructors / binders / vertex_kinds / edge_kinds), encoder (op_rules / init_rules / message_rules / update_rules / var_inits), decoder (per-constructor heads), bundle (member rule names), contraction (input parameter names). Examples: lda::theta resolves to the sample site inside the lda program; lda::z::w to the observe site inside the lda program's marginalize block; LF::Term to the Term sort inside the LF signature; CCG::fwd_app to the forward-application rule inside the CCG deduction.
  • quivers.analysis.scope module. New ScopedRef value type, resolve_scoped_path(compiler, path) resolver, scope_children(ref) view, find_all_references(compiler, name) reverse lookup. Foundation for every scope-aware REPL feature.
  • Env-tree click navigation on every nested node. Each env-tree node now carries its full :: path on node.data. Clicking any node (top-level binding, program step, deduction rule, signature sort, bundle member) fires :info PATH against that exact binding. Previously only top-level bindings were clickable; nested children either did nothing or raised "undefined morphism" errors when the click handler tried to dispatch against the rich signature label.
  • Scope-aware tab completion. lda:: completes against the lda program's children; lda::z:: completes against the marginalize block's inner scope; a bare-name prefix like thet surfaces every scoped descendant whose final segment matches (lda::theta).
  • quivers.analysis.plate_graph module. Extractor that walks a compiled QVR program and builds a PlateGraph capturing nodes (latent / observed / marginalized / deterministic), plates (with nested parents), and dependency edges. The grouping plate of a marginalize block propagates correctly to nested observes via the [over=G] axis; the [via=idx] fibration index is correctly treated as a routing label rather than an additional plate. Handles nested marginalize blocks recursively.
  • :plate PROGRAM meta-command with five output formats: default Rich table (kind / variable / family / plates / parents), --mermaid (Mermaid graph TD source with subgraph clusters per plate), --dot (Graphviz DOT with cluster_* subgraphs), --tikz (LaTeX TikZ + bayesnet snippet), --daft (Python script using the daft library), and --open (renders via daft or graphviz to a temp PNG and launches the system default viewer; falls back to Mermaid source if no renderer is installed).
  • :graph PROGRAM meta-command. Vertical step-flow view of a program: one row per sample / observe / let / marginalize / return step with its dependency parents on the side. No 2D edge routing in the in-TUI view so the output is robust on any terminal width. Same --mermaid / --dot / --open flags as :plate.
  • :where NAME meta-command. Lists every scope path in the loaded module whose final segment is NAME, sorted by depth. Useful for finding cross-references between programs, deductions, and other declarations.
  • :effects PROGRAM meta-command. Prints the program's declared [effects=[...]] set alongside the effect set inferred from the body (Sample / Score / Marginal / Pure based on the step kinds present). Flags leak (body uses effects not declared) and unused (declared but not used) divergences.
  • :shape PROGRAM meta-command. Pretty-prints the program's ChainShape: per-step depth, kind, intermediate axis size, source location. Useful for auditing chain depth before fitting.
  • Modal :help dialog. Promotes :help from "write to the output log" to a proper Textual ModalScreen overlay with its own focus, ESC-to-close, and a live filter input. Lists every meta-command grouped by category (loading / inspection / program exploration / live editing / control) plus the full keybinding table. The TUI's F1 binding pushes the same modal.

Changed

  • TUI env-tree builder unified. Replaced the 12 per-kind _children_for_* builders with a single uniform walker that consults scope_children(). Each node's display label comes from a single per-kind type-line renderer; each node's data is its full scope path. Result: every container kind expands to its declared children in a single layered tree without duplicate per-kind logic.
  • Compiler.compile_env() returns every populated bucket. Adds programs, deductions, signatures, encoders, decoders, losses, bundles, contractions, transformations to the union, so consumers that walk the env dict see every declared atom.

Fixed

  • Compiler.losses and compile_env() called reg.entries() (parentheses). LossRegistry.entries is a plain list attribute, not a method. The misplaced call broke every module that declared a loss. The accessors now read the attribute directly.

[0.11.5] - 2026-05-21

Fixed

  • qvr repl rendered every token in the same colour on customised terminal palettes. The STYLE_TABLE used palette-indexed colour names ("bold magenta", "bold blue", "bold cyan", "default"). Terminals whose palettes mapped those four into the same warm hue (common in custom themes) collapsed every span into a single colour, so :info's body looked uniformly red. Switched the table to truecolor #RRGGBB codes (One-Dark inspired) so palette mapping does not apply and the keyword / type / operator / variable distinctions remain visible regardless of the user's terminal theme.
  • Clicking a program / deduction / signature / encoder / decoder node in the env tree raised a "compile error: undefined morphism" or did nothing at all. The click handler read the node's display label (lda(alpha : Real, beta : Real) : Word -> Word) and dispatched :info against that whole string. After 0.11.3 added nested-children expansion, expandable nodes also stopped firing the handler entirely because of an allow_expand gate. The handler now reads the bare binding name from a dedicated node.data slot populated at tree-build time, and the allow_expand gate is gone, so a click on any named declaration fires :info NAME correctly.

Added

  • Comprehensive TUI regression suite at tests/cli/test_repl_tui.py. 20 tests covering: click-target resolution under every node shape (root / category heading / named binding / nested step / non-identifier data); the STYLE_TABLE truecolor invariant; each per-kind nested-children builder; the rendered ANSI for a real :info body (truecolor codes only, distinct codes per token class, keyword / type colour distinct); the full :type and :browse signature lines including typed parameter lists; the completion engine surfacing program names with the correct kind detail; and the TUI module's import-cleanliness on a minimal install.

[0.11.4] - 2026-05-21

Fixed

  • :type on a parametric program dropped its parameter list. Typed program parameters (alpha : Real, beta : Real, G : FinSet, k : Mor[A, B]) live on the AST's type_params slot, not on params (which holds bare names). The REPL session and the TUI env-tree builder were both reading only params, so loading lda.qvr and running :type lda printed program lda : Word -> Word instead of program lda(alpha : Real, beta : Real) : Word -> Word; the env tree's program node had the same problem. Both call sites now walk both slots and render the typed parameter list with its ScalarParam / ObjectParam / MorphismParam annotation.

[0.11.3] - 2026-05-21

Fixed

  • REPL environment was missing eight declaration categories. qvr repl, :browse, and the TUI env browser only surfaced objects / spaces / morphisms / rules — every other top-level declaration (program, composition, deduction, signature, encoder, decoder, loss, bundle, contraction) compiled successfully but was invisible at the user-facing surface. Loading lda.qvr showed three objects and "0 programs" despite the declared program lda. The compiler now exposes public @property accessors (programs, deductions, signatures, encoders, decoders, losses, bundles, contractions, transformations) and compile_env() returns the full set; the REPL session, :browse, the completer, and the TUI status bar / env tree all surface every populated bucket.

Added

  • Nested env-tree rendering. The TUI env pane now expands structural declarations to their internal AST shape on click: a program opens to its sample / observe / let / marginalize / return step list (with marginalize recursively opening to its body); a deduction opens to its rules (each labelled with its premises |- conclusion line) plus semiring / tolerance metadata; a signature opens to its sorts / constructors / binders / vertex_kinds / edge_kinds tables; a bundle opens to its constituent rule names. The plain-text :browse meta-command renders the same hierarchy as indented text so plain-mode users (qvr repl --plain, Jupyter kernel) see the same structure the TUI shows.

[0.11.2] - 2026-05-21

Changed

  • panproto>=0.48.4 and panproto-grammars-all>=0.48.4. Upstream now vendors the homogenized QVR grammar (the unified KIND NAME : SIGNATURE [k = v, ...] [~ INIT] [BODY] surface, the composition NAME as <level> keyword, the [role=...] option-block roles, the indented-body deductions / signatures / encoders / decoders / marginalize blocks). The QVR_USE_LOCAL_GRAMMAR shim is no longer required for the runtime parser: a fresh wheel install parses every gallery example out of the box.

Fixed

  • qvr repl, qvr-lsp, qvr-kernel parse-error on every .qvr load. 0.11.0 / 0.11.1 against panproto-grammars-all 0.48.3 (the latest at release time) used a pre-homogenization grammar whose object_decl node had no init field; the in-tree parser walker raised ParseError: object_decl missing value on the first homogenized declaration. With panproto-grammars-all>=0.48.4 the vendored grammar matches the walker's expectations and every example in docs/examples/source/ loads cleanly.

[0.11.1] - 2026-05-20

Fixed

  • Wheel shipped without grammar.json. _grammar_introspection walks the tree-sitter grammar.json to derive the keyword / punctuation / builtin sets the Pygments lexer, the REPL completer, the TUI highlighter, and the LSP semantic-tokens legend all consume. The canonical file lives at grammars/qvr/src/grammar.json (regenerated by tree-sitter generate), which sits outside src/quivers/ and so was excluded from the wheel build. The 0.11.0 install crashed on first import of quivers.dsl.pygments_lexer with FileNotFoundError: grammars/qvr/src/grammar.json not found above ..., which in turn blocked qvr repl, qvr-lsp, and qvr-kernel. A package-local copy at src/quivers/dsl/_grammar_data/grammar.json is now committed and included in the wheel; _grammar_json_path() prefers the package-local copy and falls back to the in-tree source for editable installs. tools/sync_grammar_data.py keeps the two copies in lockstep, with a --check mode CI can gate on.

[0.11.0] - 2026-05-20

Added

  • Homogenized DSL surface. Every declaration follows the unified KIND NAME : SIGNATURE [k = v, ...] [~ INIT] [BODY] skeleton: morphism, object, composition, program, deduction, signature, encoder, decoder, loss, category, bundle, schema, rule, let, export. Per-role morphism keywords (latent, kernel, observed, embed, discretize) collapse into morphism X : ... [role=ROLE]. Brace-bodied declarations (algebra X { ... }, deduction X : ... { ... }, signature X { sorts { ... } }, marginalize ... in { ... }, atoms { ... }, lexicon { ... }) become indented bodies under the unified header. Effect signatures live in the option block as [effects = [Sample, Score, Marginal, Pure]] rather than ! E1, E2, .... Observation fibrations are [via = idx] and grouping plates are [over = G, reduction = R] in the option block instead of keyword clauses. Morphism replication is [replicate = N]. The pragma surface is #[k = v, ...] (outer, decorates the next declaration) and #![k = v, ...] (inner, decorates the enclosing module); per-line rule and lexicon pragmas (#[learnable], #[bounded], parent = ...) replace the legacy @ learnable marker.
  • composition NAME as <level> keyword. A single surface for algebraic composition rules (algebra / semigroupoid / bilinear_form / rule), with either a registry lookup (composition product_fuzzy as algebra) or an indented inline body (composition my_godel as algebra followed by tensor_op(a, b) = ...).
  • ContinuousSpace family registry. Eleven continuous-space constructors (Real, Simplex, PositiveReals, UnitInterval, Sphere, Ball, Covariance, Correlation, CholeskyFactor, Orthogonal, Stiefel, LowerTriangular, Diagonal) ship as ContinuousSpace subclasses dispatched by the homogenized DiscreteConstructor / ContinuousConstructor AST nodes. Discrete and continuous space initialisers share the same object X : <init> surface.
  • quivers.inference.lifts module. Four general-purpose Bayesian-lift helpers:
  • bayesian_lift_parameters lifts every learnable nn.Parameter into a Normal-prior sample site, returning a MonadicProgram that SVI and NUTS consume uniformly. The new additional_latents argument lifts intermediate program latents as joint NUTS variables via an exact placeholder-cancellation construction; the lifted log-density equals log p(theta) + log p_inner(z, y | x, theta) pointwise, with no Monte-Carlo noise across leapfrog steps. The module docstring includes a methodological-notes section justifying the Normal-prior choice (maximum entropy on R^n; equivalence to L2 regularization; computational fit with NUTS) and the placeholder-cancellation theorem with proof.
  • lift_to_bayesian_program lifts a parameter-only morphism plus any torch.distributions.Distribution observation family into a Bayesian program, with a user-supplied location_fn so the same lift works for rsample(x)-style, tensor-attribute, and prog(x)-forward shapes.
  • lift_from_log_prob lifts a parameter-only model whose forward is already a log_prob(x, y) function (the VAE / encoder-decoder pattern).
  • monte_carlo_log_joint wraps a program so its log_joint MC-draws named intermediate sample sites and returns the conditional data likelihood, with the prior-cancellation correction applied automatically. Documented honestly as a single-sample MC estimator valid for SVI as a stochastic gradient estimator; the function's docstring notes that for rigorous NUTS the joint lift via additional_latents is the correct route.
  • quivers.stochastic.deduction package. Reorganised the deduction-system surface from the previous flat quivers.deduction_fit module into a coherent package with one canonical import path. Subpackages: primitives (the abstract Axiom, Deduction, Goal, Schedule, DeductiveSystem building blocks), fit (point-estimate gradient fitting via adam_fit_deduction), bayes (posterior wrapping via nuts_program_from_deduction), sample (exact length-conditional forward sampling via sample_corpus). The model type DeductionSystem is re-exported from the package root alongside the operations and bayesian_lift_parameters (re-exported from quivers.inference.lifts).
  • ScanMorphism.log_joint accepts a dict signature. The recurrent-cell scan morphism's log_joint now accepts the hidden-state trajectory either positionally as a tensor or via a {state_key: tensor} dict (default state_key = "h"), so the standard inference contract log_joint(x, observations: dict) works without an adapter at the call site.
  • qvr migrate subcommand. Forward-migrates user .qvr source files across the QVR grammar release chain (v0.2.0 through HEAD). The CLI composes the adjacent-pair hop migrators in quivers.cli.migrations automatically; --from VER and --to VER pin the boundary, --dry-run reports without writing, --output DIR writes migrated copies. --check mode validates the migration chain against the panproto VCS schema diff and reports any rule removed in an adjacent grammar revision that the corresponding hop migrator has no converter for.
  • In-tree panproto VCS for the grammar. grammars/qvr/vcs/.panproto/ holds a content-addressed schema chain with one tagged commit per grammar release (v0.2.0 through v0.11.0). grammars/qvr/vcs/build_schemas.py walks tagged grammar revisions, builds a panproto.Schema keyed by rule name, and commits each as a tagged VCS commit. The migration system uses these commits to derive coverage reports and detect uncovered removed-rule gaps in any adjacent-pair migrator.
  • Comprehensive ## Try it sections in every example doc. All 28 gallery examples (regressions, latent-variable models, state-space and time-series, language models, encoder-decoder, weighted deductions) ship a verified three-block runnable Try-it section: synthetic-data generation under known true parameters, an SVI fit, and a Bayesian posterior block. Every block executes in CI via test_gallery_try_it_blocks_execute. Illustrative-budget caveat at the top of each section explicitly notes the SVI / NUTS budgets are sized for tens-of-seconds CI runs, not production convergence.
  • Example walkthrough alignment. All 28 example docs' ## Overview and ## Walkthrough prose audited against the actual .qvr source; the 13 docs with prose-vs-source drift (most severely pcfg.md and montague-nli.md, whose embedded ## QVR Source blocks described entirely different models than the actual source) were rewritten in place.
  • Examples gallery index restructured as a flat grouped list of one-line example descriptions linking to each gallery doc.
  • API reference structure expanded. New pages for api/inference/lifts, api/inference/mcmc, api/inference/elbo, api/continuous/scan, api/continuous/inline, api/stochastic/agenda, api/stochastic/deduction (+ four subpages: primitives, fit, bayes, sample). The qvr migrate, qvr repl, and qvr lsp subcommands are documented in api/cli.md.
  • Denotational semantics extensions. docs/semantics/programs.md gains a §7 "Bayesian lifts" with five propositions and proofs (placeholder-cancellation for the parameter lift, the observation-family lift, the log-prob lift, the single-sample MC bias bound via Jensen, the CRF-vs-PCFG distinction for nuts_program_from_deduction). docs/semantics/adequacy.md gains a §3.7b on adequacy of the lift theorems. docs/semantics/types-and-spaces.md §1 and §4 cover all 13 ContinuousSpace variants with set-builder denotations. docs/semantics/grammar.md rewrites composition_rule_decl EBNF to match the unified surface. docs/semantics/expressions.md §3.4 notes the dict-signature route on ScanMorphism.log_joint.
  • qvr migrate documentation. docs/guides/migration.md covers the --from / --to / --dry-run / --output / --check flag set with worked examples.

Changed

  • quivers.deduction_fit module removed. Imports move to quivers.stochastic.deduction (or quivers.inference.lifts for bayesian_lift_parameters). Pre-1.0 clean break, no compat shim. The 20+ docs and tests that referenced the old path are updated.
  • Docstring cross-references converted to mkdocstrings autorefs. All 1103 Sphinx-style :func:X/ `:class:`X / :mod:X/ `:meth:`X / :attr:X/ `:data:`X / :exc:X`` role markers in source docstrings (132 files) are now either mkdocstrings autorefs (for internal symbols the docs build resolves) or plain inline code (for external / unresolved references). Sphinx role markers no longer render as ugly grey code spans in the API docs.
  • DSL surface drift across the entire test and doc corpus. Every fixture, every embedded source string, every walkthrough prose reference is on the current homogenized surface. mkdocs build --strict produces zero warnings; pytest tests/ reports 2009 passing, 140 skipped, zero failing.

Fixed

  • Grammar parser-recovery bugs. encoder_decl and composition_decl previously used optional(body), $._newline, which let the trailing $._newline swallow the next top-level statement after an indented body. Replaced with choice(body, $._newline) so the $._dedent properly terminates and the following statement parses. parser.c, grammar.json, and node-types.json regenerated.
  • ScanMorphism.log_joint dict-vs-positional adapter unified into the morphism itself so call sites no longer need a DictWrap shim.
  • _TruncNorm distribution shim in quivers.continuous.inline now carries event_shape and batch_shape attributes so the generic family-log_prob path treats it uniformly with stock torch.distributions families.
  • Formula compiler (quivers.formulas.compile) emits object Resp : FinSet N via the homogenized DiscreteConstructor initialiser and accepts both old and new shapes on the reverse-pass decoder.
  • Dataset schema (quivers.data.schema) emits object X : FinSet N in the prelude returned from DatasetSchema.declarations_qvr and the compose wrapper.
  • Compiler template-locals walker (quivers.dsl.compiler.programs._collect_template_local_names) now walks the surface SampleStep / ObserveStep / MarginalizeStep nodes (was only walking the legacy BindStep IR), so locally-bound names in parametric program templates are correctly captured during alpha-renaming.
  • Compiler structural pass (quivers.dsl.compiler.structural._compile_signature) reads dim and vocab from each sort and vertex-kind's options block (the homogenized surface) rather than from removed direct attributes. _compile_loss accepts numeric-literal weight = N in the loss option block.
  • docs/developer/homogenization-plan.md removed. Internal design-tracking doc with phase counts and version anchors; not appropriate for shipped documentation.

Editor packages

  • VS Code extension editors/vscode-qvr bumped to 0.6.0. Syntax highlighting refreshed for the homogenized grammar (the unified declaration keywords, the option-block surface, the new pragma markers, the composition NAME as <level> keyword, the sample / observe / score / marginalize / binders program-step keywords). Tracks the new shared STYLE_TABLE so VS Code, the REPL TUI, the Pygments lexer, and the LSP semantic-tokens legend all paint the same source the same colour.
  • Zed extension editors/zed-extension-qvr bumped to 0.2.0. Tree-sitter highlights.scm regenerated from the homogenized grammar via the shared grammars/qvr/queries/_generate.py script, so the Zed mirror tracks the canonical highlights file automatically.

[0.10.0] - 2026-05-17

Added

  • qvr repl — GHCi-style interactive type explorer. Four-pane Textual TUI (top status bar; input editor with bracket pairing and auto-indent; eval log with live syntax highlighting; environment browser with namespace tree, fuzzy filter, and click-to-:info; auto-hiding watches and diagnostics strips), plus a prompt_toolkit single-line fallback (--plain / non-TTY). Meta-commands :load, :reload, :type, :kind, :info (--python opts in to the didactic AST), :doc, :browse, :dump (--json opts in to model_dump_json), :edit (opens $EDITOR and splices the result back), :trace, :save, :watch, :unwatch, :set, :help, :quit, with the short aliases GHCi users expect (:l, :r, :t, :k, :i, :b, :s, :w, :h, :q). Bare lines compile as appended statements into the live module or fall through to :type. Persistent input history at ~/.config/quivers/history (Ctrl-Up / Ctrl-Down walks it). Tab cycles through env, grammar-keyword, builtin, and path completions. Ctrl-P opens a Textual command palette over every meta-command. Ctrl-G / Ctrl-O / F8 evaluate; the bindings are picked to clear every layer of OS, terminal, XON/XOFF, and TextArea keymap interference on macOS, Linux, and Windows. Install with pip install 'quivers[repl]'.
  • Live syntax highlighting shared with the editor. A single STYLE_TABLE and tree-sitter-driven tokenizer (quivers.cli.repl_highlight) feeds the TUI's RichLog, the prompt_toolkit lexer, the Jupyter kernel's mimetype hint, and the LSP's semanticTokens/full legend. The tokenizer is env-aware: identifiers the grammar can't classify in an ERROR context are upgraded to the env-known kind (type / function / namespace), so the same name reads the same colour across every surface. Adding a keyword to grammars/qvr/grammar.js paints it everywhere automatically.
  • File-watcher auto-reload. The TUI polls the loaded file's mtime once per second and re-runs :reload when it advances, printing the diff. Pinned :watch expressions re-evaluate against the new env. Off via :set autoload_on_save=false.
  • Pinned watches. :watch EXPR adds EXPR to a dedicated panel (auto-hidden when empty) and re-evaluates after every recompile; :unwatch EXPR removes one, :unwatch alone clears all.
  • Click-to-info and click-to-edit. Identifiers in rendered eval-log output (anything the env classified as a type / function / namespace) are clickable links that fire :info NAME. The -- declared at PATH:LINE:COL footer underneath each :info body is also clickable; it launches $EDITOR +LINE PATH.
  • Inline diagnostic markers. Compile, parse, and constraint errors that carry a known location move the input pane's cursor selection to the offending span so the user sees where to look without leaving the editor.
  • Jupyter kernel. qvr-kernel install (or qvr kernel install) registers a quivers kernelspec whose cell evaluator is the same ReplSession driving the REPL. Cells with a leading : dispatch to meta-commands; blank lines split independent statement chunks within a cell. Tab-completion (do_complete) and Shift-Tab inspect (do_inspect:info) share the REPL's completer and renderer. The kernelspec advertises pygments_lexer: qvr so notebook frontends highlight cells via the bundled Pygments lexer.
  • qvr-lsp — Language Server (LSP 3.17). pygls 2.x server with incremental document sync, publishDiagnostics (parser + constraint solver + compiler with source ranges), semanticTokens/full (driven by the shared STYLE_TABLE), hover (QVR declaration sliced verbatim from the original source plus the didactic AST stacked beneath under a collapsed <details>, pretty-printed one field per indented line), definition, references, documentSymbol (Class for objects/spaces, Function for morphisms, Variable otherwise), completion (same engine as the REPL), and formatting (canonical re-emission via module_to_source). Install with pip install 'quivers[lsp]'. New console scripts qvr-lsp and qvr-kernel; new subcommands qvr repl, qvr lsp, qvr kernel.
  • First-party VS Code / Cursor extension. editors/vscode-qvr (v0.5.0) bundles a vscode-languageclient bridge to qvr-lsp plus the existing TextMate grammar. Auto-discovers the server in (1) qvr.lsp.path (with ${workspaceFolder} expansion), (2) <workspace>/.venv/bin/qvr-lsp, (3) <workspace>/.venv/Scripts/qvr-lsp.exe, (4) $VIRTUAL_ENV/bin/qvr-lsp, (5) $PATH. Configurable via qvr.lsp.enabled, qvr.lsp.path, qvr.lsp.args.
  • First-party Zed extension wiring. editors/zed-extension-qvr adds qvr-lsp as a language_server entry; Zed spawns it automatically on .qvr files.
  • :save FILE meta-command. Writes the live module (including any statements appended in the session) back to disk via module_to_source. Defaults to the loaded path when no argument is given.
  • Optional dependency groups. [repl] (Textual, prompt_toolkit, rich, ipykernel, jupyter_client) and [lsp] (pygls, lsprotocol). pip install quivers alone keeps the core dependency list unchanged — none of the interactive tooling is mandatory.
  • Comprehensive interactive guide. New guides/repl-and-lsp.md covers every meta-command, every keybinding (with the cross-platform reasoning), the layout, the file watcher, semantic highlighting, themes, the Jupyter kernel, the LSP capabilities, and per-editor wiring instructions. Cross-linked from the README, docs/index.md, getting-started/installation.md, getting-started/quickstart.md, and getting-started/highlighting.md.

Changed

  • panproto bumped to >=0.47.3 and panproto-grammars-all to >=0.47.3. The current QVR tree-sitter grammar (with the alias, export, program, kernel-decl forms) is now vendored upstream; the local-grammar override (QVR_USE_LOCAL_GRAMMAR=1) is no longer necessary for either the runtime or the CI test suite.

Fixed

  • pygls 2.x API migration. LanguageServer.publish_diagnostics was removed upstream in favour of text_document_publish_diagnostics(PublishDiagnosticsParams(...)). The previous call surfaced as an AttributeError dialog in VS Code on every save.

[0.9.1] - 2026-05-16

Fixed

  • MonadicProgram.log_joint rank-promotes upstream continuous values for single-arg steps. When a draw step's args was a single name, _resolve_input returned the env tensor verbatim, so a 1-D continuous draw (the shape Guide.rsample emits for a 1-D Euclidean codomain) fed nn.Linear as a (B,) row vector and crashed the downstream _NeuralSource matmul with mat1 and mat2 shapes cannot be multiplied (1xB and 1xH). Single-arg continuous inputs are now promoted to (B, 1) via the same rule _stack_tensors already applied for the multi-arg branch, with integer-dtype inputs (which feed nn.Embedding lookups) left untouched. _NeuralSource.forward also unsqueezes a stray rank-1 input as a belt-and-braces guard against custom ContinuousMorphisms regressing the same way. Affects every shipped Conditional* family sitting downstream of a 1-D continuous step; surfaced by direct MonadicProgram(steps=[...]) users on the first batched svi.step.

[0.9.0] - 2026-05-15

Added

  • Comprehensive PyTorch primitive surface for let-expression bodies. The let-expression compiler now resolves 86 named primitives drawn from torch.nn.functional and torch: every standard activation (relu, gelu, silu/swish, mish, elu, selu, leaky_relu, prelu, rrelu, hardtanh, hardshrink, softplus, softshrink, softsign, hardsigmoid, hardswish, sigmoid, logsigmoid, tanh, tanhshrink, threshold, glu), simplex maps (softmax, log_softmax, softmin, normalize), pointwise transcendentals (exp, expm1, log, log1p, log2, log10, sqrt, rsqrt, square, abs, neg, sign, reciprocal, clamp), trig / hyperbolic (sin, cos, tan, asin, acos, atan, sinh, cosh, asinh, acosh, atanh), rounding (floor, ceil, round, trunc), special functions (erf, erfc, erfinv, lgamma, digamma), dim=-1 reductions (sum, mean, var, std, min, max, argmin, argmax, prod, amax, amin, logsumexp, norm), cumulative / ordering ops (cumsum, cumprod, cummax, cummin, flip, sort), and training-mode primitives (dropout, alpha_dropout, layer_norm, rms_norm). Reductions take dim=-1 by convention; reductions over a named axis go through the typed contraction surface.
  • User-defined programs callable from encoder bodies. Encoder / decoder / loss rule bodies (and any let-expression context) can now invoke top-level program declarations, let-bound morphisms, encoders, decoders, and deductions by name. The call form f(arg, ...) resolves first against the primitive table, then against the structural-globals dict, then against the user-declared constructor set. A deterministic program is a Dirac Kleisli arrow embedding Smooth into Kleisli(Giry); calling it from an encoder body composes the two Smooth pieces and stays in Smooth.
  • Compile-time arity checking on let-expression calls. For user-defined callables (Morphism, MonadicProgram, plain Python callables) the compiler verifies the positional argument count at compile time and raises CompileError("call to 'f': expected N positional argument(s), got M"). Variadic callables (*args) skip the check. Morphism is unary; MonadicProgram arity equals len(params) when present, else 1.
  • Runtime tensor-shape errors wrapped at call sites. A RuntimeError or TypeError raised inside a user-defined callable surfaces as CompileError("call to 'f' failed: ..."), so shape mismatches name the call site instead of producing a bare PyTorch trace.
  • DSL [init=auto] annotation on latent declarations. latent f : A -> B [init=auto] overrides the default randn * scale init with the algebra's saturation-free recipe. Through LatentMorphism's sigmoid bijector (for ProductFuzzy / Boolean / Gödel / Łukasiewicz / Probability) the raw parameter is set via logit(value); for other algebras the recipe is applied to the raw parameter directly. The annotation rides on the existing option_block grammar; no new keywords.
  • Encoder factory form: encoder NAME over SIG using FACTORY [k=v, ...]. An alternative to the explicit { ... } body that invokes a shipped builder from quivers.structural.shapes (rnn_encoder, transformer_encoder, bow_encoder, tree_lstm_encoder, gnn_encoder) with optional kwarg overrides. The two forms are mutually exclusive on one declaration and can coexist in the same module. The registry mapping factory name → import path lives in quivers.dsl.compiler.structural._ENCODER_FACTORY_REGISTRY.
  • quivers.analysis package — algebra-guided training tooling. See notes/algebra-guided-training-tooling.md for the broader roadmap.
  • ChainShape.from_module(module) walks a compiled QVR Module and produces a per-step record (StepShape) with bound variable name, source line / col, chain depth (1-indexed at the first stochastic bind), governing algebra, and inferred intermediate axis size.
  • Algebra.init_spec(depth, intermediate_size) -> InitSpec returns the closed-form saturation-free init recipe per algebra (product-fuzzy p ≈ ln(2)/k, Łukasiewicz p ≈ 1/k, log-prob -ln k, Markov logits at 0, tropical/max-plus/real 1/√k, Boolean/Gödel idempotent at 1/2, probability/counting 1/k). recommend_init maps every latent in a module to its InitSpec; apply_init_spec materialises the recipe onto a learnable tensor via torch.nn.init.
  • saturation_warnings(module) returns source-keyed SaturationWarning diagnoses for every latent whose recipe differs materially (> 20% in location or scale) from a default Normal(0, 1).
  • Type-driven contraction wiring. contraction NAME(args) : DOM -> COD blocks no longer require a hand-written einsum string. The compiler infers the einsum from the typed signature: axes that appear in the output propagate; axes that appear in ≥ 2 inputs but not the output get contracted via the rule's join; axes that appear in exactly one input but not the output are flagged with a source-keyed error pointing at the disambiguators. Two opt-in clauses for the cases inference can't derive: share T1, T2, ... keeps the listed axes element-wise even when shared across inputs; wiring "<einsum>" remains as the explicit escape hatch (diagonal extraction, reorderings, etc.).

[0.8.0] - 2026-05-15

Changed

  • BREAKING: fit(..., sampler=...) renamed to fit(..., method=...). SVI is not a sampler (it fits a variational Guide by optimization); the parameter selects an inference algorithm. The literal values ("nuts", "hmc", "svi") are unchanged. Update call sites sampler="..."method="...".
  • BREAKING: random-effect terms emitted in non-centered form by default. (slope | g) formulae now compile to a standard-Normal plate draw z_g_<slope> : g <- Normal(0, 1) plus a deterministic let alpha_g_per_row = sigma_g * z_g_<slope>[g_idx], rather than the textbook centered alpha_g : g <- Normal(0, sigma_g). Same posterior mathematically; the funnel that wrecks centered hierarchical sampling under HMC / NUTS and under-shrinks variance components under VI is gone. Pass fit(..., reparameterize="centered") to recover the previous behaviour.

Added

  • fit(..., guide=...) exposes guide selection for SVI (previously hard-coded to AutoNormalGuide). Accepts any Guide subclass: AutoMultivariateNormalGuide, AutoLowRankMultivariateNormalGuide, AutoLaplaceApproximation, AutoIAFGuide, etc. The class is constructed internally with (program, observed_names=...).
  • fit(..., reparameterize=...) controls the parameterization of random effects. "noncentered" (default; recommended by Stan / brms) or "centered". formula_to_qvr takes the same parameter; BayesianFit.reparameterize records the choice so qvr_source echoes the actual emitted form.
  • Tutorial 8: Analysis pipelines. Step-by-step walkthrough of the formula → fit → diagnostics surface on synthetic data: Gaussian SVI fit, hierarchical SVI, NUTS Bernoulli + PSIS-LOO model comparison, ArviZ posterior-predictive checks, the lens round-trip. Every Python block verified end-to-end against the running runtime.

Fixed

  • NUTS / HMC on hierarchical formula models no longer dies with Sizes of tensors must match except in dimension 1. MonadicProgram._stack_tensors broadcasts size-1 leading dims against the maximum batch in the input list, so a scalar latent (sigma) and a per-row tensor (mu) can be concatenated ahead of an indexed observe.

[0.7.1] - 2026-05-15

Changed

  • FormulaToQVRModule complement is FormulaData, not Formula. The lens was declared as Lens[Formula, Module, Formula] — the lazy function-lens idiom in which the entire source rides as complement and backward is const. The honest setup is Lens[Formula, Module, FormulaData]: the structural fields of the formula (which columns exist, intercept flag, random-effect group / slope structure, family choice, response identifier) are recoverable from the emitted Module via a new _decode_module walker, and only the un-decodable fields ride in the complement (per-row data arrays, original pre-_qvr_name identifier names, term / name presentation labels, the original formula string). backward(module, complement) now performs a real decode + fuse pass. GetPut holds for every formula. New public symbol: FormulaData in quivers.formulas.formula.

[0.7.0] - 2026-05-15

Changed

  • BREAKING: quantalealgebra across DSL, Python API, and docs. The quantale keyword and every Quantale-named symbol overclaimed the algebraic structure: seven of the eleven built-in cases fail strict quantale-distributivity in Kelly's sense. The new term algebra names the structure \((V, \otimes, \oplus, \mathbf{1})\) honestly; the subclass that does satisfy the strict laws keeps the name strict quantale and is identified inline in Algebras §2. Migration:
Old New
quantale X (DSL keyword) algebra X
Quantale (abstract base) Algebra
BooleanQuantale, LukasiewiczQuantale, GodelQuantale, TropicalQuantale, MaxPlusQuantale, LogProbQuantale, RealQuantale, ProbabilityQuantale, CountingQuantale, MarkovQuantale BooleanAlgebra, LukasiewiczAlgebra, GodelAlgebra, TropicalAlgebra, MaxPlusAlgebra, LogProbAlgebra, RealAlgebra, ProbabilityAlgebra, CountingAlgebra, MarkovAlgebra
ProductFuzzy ProductFuzzyAlgebra
DualQuantale, CustomQuantale, QuantaleFromCallables DualAlgebra, CustomAlgebra, AlgebraFromCallables
QuantaleHomomorphism AlgebraHomomorphism
Algebra.compatible_quantales() Algebra.compatible_algebras()
_QUANTALE_REGISTRY, _register_extra_quantales _ALGEBRA_REGISTRY, _register_extra_algebras
compiler env binding __quantale__ __algebra__
QuantaleDecl AST node AlgebraDecl
quivers.core.quantales module quivers.core.algebras
quivers.core.quantale_morphisms module quivers.core.algebra_morphisms
quivers.stochastic.quantale re-export deleted; import MarkovAlgebra, MARKOV from quivers.core.algebras
quivers.Algebra (Eilenberg-Moore monad algebra) quivers.MonadAlgebra (the public re-export; inside quivers.monadic.algebras the class is still named Algebra)

Singletons (PRODUCT_FUZZY, BOOLEAN, LUKASIEWICZ, GODEL, TROPICAL, MAX_PLUS, LOG_PROB, REAL, PROBABILITY, COUNTING, MARKOV, REICHENBACH, MATERIAL_IMPLICATION) keep their names. Registry key strings ("product_fuzzy", "boolean", ...) keep their names.

Added: analysis pipelines (formula → fit → diagnostics)

  • quivers.formulas: brms-style formula frontend. A typed AST lens compiles y ~ x + (1 | g) formulas through the existing QVR DSL without touching source strings: the lens emits a quivers.dsl.ast_nodes.Module directly and the existing Compiler consumes it. R / brms behaviour is faithful: orthogonal polynomials by default (poly(x, k) matches stats::poly), one coefficient per design-matrix column (poly(x, 2) produces beta_poly_x_2_1 / beta_poly_x_2_2), R-style numeric transforms wired in (log, exp, sqrt, ...), and the family registry (gaussian, bernoulli, binomial, categorical, poisson, negative_binomial, gamma, beta, student_t, cumulative) drives the inverse-link + observe step. One-line entry is fit("y ~ x + (1|g)", data=df, family=, sampler=, ...) returning a BayesianFit dx.Model; formula_to_qvr emits the same module as canonical .qvr source. Gated behind the formulas extra.
  • quivers.data: DataFrame schema bridge. DatasetSchema turns a pandas / polars / Narwhals-compatible dataframe into the object cardinalities, observations dict, and plate-index codes a QVR program needs. compose(qvr_body, schema) prepends inferred object X : N declarations to a user-written program body so the user only writes the program body. Four missing-data policies: raise, drop, impute, mask. Gated behind the data extra.
  • quivers.diagnostics: ArviZ adapter. Glue layer between quivers' inference records and the ArviZ 1.x xarray.DataTree surface. to_datatree wraps quivers' per-site sample tensors, log-densities, acceptance rates, and divergences into the canonical ArviZ groups (consumable by plot_trace, plot_forest, plot_ppc, loo, compare, hdi, ...). compare delegates to ArviZ's PSIS-LOO ranking (Yao et al. 2018). posterior_predictive_check computes the canonical posterior-predictive p-value for a user-chosen test statistic. Gated behind the diagnostics extra.
  • quivers.dsl.emit: AST → .qvr source serializer. module_to_source walks a Module AST and produces canonical .qvr source; the round-trip loads(module_to_source(m)) == m is exercised on every formula in the test suite.
  • TransformedMorphism. Morphism.change_base, .dagger, .trace, and .refactor now return a TransformedMorphism whose .tensor is recomputed from the source morphism's tensor on every access, keeping autograd graphs alive across transforms.
  • Posterior-recovery test marker. Ten end-to-end recovery tests under tests/test_formulas.py::TestPosteriorRecovery fit synthetic data with known true coefficients (Gaussian / Bernoulli / Poisson / Gamma, hierarchical random intercepts, multi-predictor regression, log transforms, polynomial terms, interactions, random slopes); marked @pytest.mark.slow and deselected by default (pytest --runslow tests/ to include).

Changed: regression examples

  • Drop the spurious x : Resp <- Normal(0, 1) declarations from every regression example (bayesian_regression, beta_regression, dirichlet_regression, horseshoe_regression, negbin_regression, zip_regression): predictors are exogenous data and flow through the host-data channel as free variables in let expressions, not as latent draws. Gallery markdown explainers updated to match.

[0.6.0] - 2026-05-14

Added

  • Factor expressions: let f = factor v_1 : I_1, ..., v_n : I_n in <body>. A new let-expression form that assembles an indexed tensor of shape (|I_1|, ..., |I_n|, *body_shape) by evaluating <body> once per cell of the Cartesian product of binder indices. The categorical content is the left adjoint of multi-axis indexing: factor is to indexing as a (co)limit cone is to its components. A single-binder pattern-match form factor v : I in { 0 -> e_0, 1 -> e_1, ... } is also supported for cell-structured priors (label coverage and in-range labels are checked at compile time). Closes #19.

[0.5.0] - 2026-05-13

Headline additions

  • Axis-role surface on every distribution clause. Kernel declarations, latent priors, sample steps, and observe steps accept an optional over <axes> [iid over <axes>] post-clause. over names the event axes of the family; remaining axes are iid (categorically a product of independent distributions). Axis names resolve against the named factors of the surrounding morphism's dom/cod; dom/cod are shortcuts when that side is a single unfactored object. The axis count must match the family's declared event_rank (0 / 1 / 2 for scalar / vector / matrix families); mismatch is a compile-time error rather than a silent reinterpretation, preserving the categorical distinction between a dense MVN over dim(A)*dim(B) and a MatrixNormal with Kronecker structure V (X) U.
  • Unified kernel keyword. The continuous and stochastic declaration keywords are removed. A kernel f : A -> B declaration without a ~ clause is a finite-set lookup-table kernel; with ~ Family [options] [axes] it is a parametric kernel whose family parameters come from the input by a neural parameter network at sample time.
  • Latent morphism priors. A latent f : A -> B ~ Family(args) [options] [over <axes>] clause puts a prior on the morphism's representing tensor (factor-analysis / PPCA / BNN idiom).
  • Four new conditional families. ConditionalMatrixNormal (Kronecker covariance, event_rank 2); ConditionalInverseWishart (conjugate covariance prior, event_rank 2); ConditionalGaussianProcess (RBF / Matern 5/2 / linear kernels with learnable length scale and amplitude, event_rank 1); and ConditionalHorseshoe (Carvalho-Polson-Scott global-local shrinkage with a 16-point Gauss-Legendre quadrature giving the exact marginal log-prob, event_rank 1).
  • Example gallery, 36 examples. Regression (Bayesian, beta, Dirichlet, negative-binomial, horseshoe, ZIP); latent-variable (factor analysis, PPCA, LDA, IRT-2PL, PMF, BNN, GMM, VAE); time-series and state-space (HMM discrete, continuous HMM, linear-Gaussian SSM, deep Markov, AR(1), stochastic volatility, changepoint, Weibull survival); sequence architectures (vanilla RNN, LSTM, GRU, bidirectional RNN, transformer; all as language models; one seq2seq with encoder + decoder); formal grammars (PCFG, CCG, Lambek calculus, multimodal TLG, custom rules, Montague NLI, quantifier scope, event-structure latent class). Each example uses at least one distinguishing quivers feature (axis-role priors, marginalize, change_base, encoder block, scan, deduction declarations) and runs end-to-end on synthetic data.

Refactors

  • src/quivers/continuous/bayesian.py renamed to plate.py and further split: _DeterministicMorphism, cumsum, softmax, cholesky_quad_form moved to quivers.continuous.deterministic; CholeskyFactor continuous space moved to quivers.continuous.spaces; LKJCorrelationFactor and Truncated moved to quivers.continuous.families; plate.py now strictly holds the plate / vectorized-observe / grouped-marginalize machinery.
  • src/quivers/dsl/compiler.py (~6300 LOC) split into a package src/quivers/dsl/compiler/ with one module per concern (_prelude, core, declarations, programs, structural, deductions, resolution, expressions). Public import paths unchanged.

A substantive expansion of the inference layer plus the full implementation of scoped grouped marginalize blocks (issue #9), a unified distribution family registry with every torch.distributions family exposed inline, the Tier-1 through Tier-6 benchmark grid emitting docs/developer/inference-benchmarks.md, and the categorical refinements from issues #15-#17 (algebra duality, shape-aware change-of-base, storage-level shape compatibility). Pre-1.0 clean break: no backwards-compatibility shims; user code that relied on the SVI(loss=...) keyword should switch to SVI(objective=...), Predictive(guide=...) should use the positional posterior argument, and imports from quivers.core.extra_algebras move to quivers.core.algebras.

Added: algebra duality and user-defined algebras

  • Algebra.dual() returns a DualAlgebra whose tensor_op and join swap roles under the de-Morgan involution. ProductFuzzy.dual gives the canonical Reichenbach-flavor probabilistic-implication composition (⊗ = noisy-OR, ⋁ = product reduction); Boolean.dual gives (OR, AND); Lukasiewicz.dual gives the bounded-sum t-conorm pair; Godel.dual gives (max, min).
  • Named singletons REICHENBACH, BOOLEAN_DUAL, DUAL_LUKASIEWICZ, DUAL_GODEL exported from quivers.core.algebras and registered with the DSL algebra catalog so users can write algebra reichenbach.
  • CustomAlgebra accepts user-supplied tensor_op / join / unit / zero / negate callables for arbitrary user-defined algebras, with construction-time sanity checks on the identity / absorbing axioms.

Added: functorial change-of-base

  • MorphismTransformation ABC in quivers.core.morphism_transformations: shape-aware change-of-base for transformations that don't factor pointwise through a algebra homomorphism. Concrete subclasses: Softmax(axis_object), L1Normalize(axis_object), L2Normalize(axis_object), BayesInvert(prior).
  • Morphism.change_base now dispatches on either a AlgebraHomomorphism (pointwise) or a MorphismTransformation (shape-aware). The latter may swap the morphism's domain and codomain (used by BayesInvert).

Added: first-class transformations in the DSL

  • Trans values are now full DSL values: a transformation (a AlgebraHomomorphism or MorphismTransformation) can be let-bound, composed with >>>, and passed to change_base. The transformation namespace is disjoint from the morphism namespace; let-binding routes based on the RHS shape.
  • Constructors are bare-verb names (softmax(B), l1_normalize(B), l2_normalize(B), bayes_invert(prior)). The Python-side factory names in quivers.core.morphism_transformations follow the same spelling, softmax, l1_normalize, l2_normalize, bayes_invert, replacing the previous *_over suffixes.
  • Singletons (expectation, log_prob, material_implication, boolean_embedding, probability_clamp, probability_to_real, counting_from_real, counting_to_real, threshold, max_plus) resolve by bare name, both directly inside change_base and as let-bindings (let phi = expectation).
  • t1 >>> t2 composes two transformations; nested chains flatten and each adjacent target / source boundary is type-checked at compile time. Inline or via let:
    let phi = softmax(B) >>> expectation
    let g = f.change_base(phi)
    

Pre-1.0 clean break: the previous mini-surface change_base(softmax_over(B)) is removed. Users on 0.4.x rename softmax_oversoftmax, l1_normalize_overl1_normalize, l2_normalize_overl2_normalize (DSL and Python API both).

Added: multi-observe grouped marginalize blocks

A grouped marginalize block now supports multiple observe steps in the same body, each carrying its own via <idx> clause. The runtime scatter-sums each observe's (N_m, K) per-row per-class log-likelihood into the same (|G|, K) accumulator before the log-sum-exp; categorically the right Kan extension along the coproduct fibration \(\coprod_m r_m : \coprod_m \mathrm{Resp}_m \to G\). The single-observe case is the unary slice; the multi-observe case unblocks hierarchical-Bayes models where multiple heterogeneous response axes share a per-group class indicator (the canonical pattern in the Stan likelihoods that motivated the work).

  • Surface change: the via <idx> clause moves from the marginalize header to each observe step. The marginalize header now declares only the grouping plate (over G or over G * H); each observe inside the body carries its own via <idx> (or via product(idx_a, idx_b)) clause. A grouped body whose observe lacks a via is rejected at compile time with a typed error.
  • Runtime: quivers.continuous.bayesian.marginalize_grouped accepts either a single (N, K) log-likelihood tensor and fibration (single-observe path) or a parallel list of (N_m, K) tensors and fibrations (multi-observe path).
  • Pre-1.0 clean break: the previous marginalize ... over G via idx in { ... } header form is removed. Users on 0.4.x with a grouped marginalize block move the via <idx> clause from the header onto the observe inside the body; a single per-block fibration becomes a single observe carrying that fibration. Multi-block patterns where two marginalize blocks shared a class prior collapse to a single block with two observes (per the canonical pattern in the issue that motivated this).
  • src/quivers/dsl/examples/event_structure.qvr updated to the single-block-with-two-observes form.

Added: documentation overhaul

  • Two-track tutorial structure: a new QVR DSL track (seven chapters, model development to inference, side-by-side with PyMC / NumPyro / Stan) and a refreshed Python API track (two new chapters covering first-class transformations and the composition-rule hierarchy).
  • New denotational-semantics page, docs/semantics/composition-rules.md, formalizing the CompositionRule → Semigroupoid → Algebra hierarchy, operadic n-ary contractions via flat wirings, and the sort Trans[V, W] for first-class transformations.
  • New conceptual guide docs/guides/transformations.md covering the transformation surface and the composition-rule hierarchy end-to-end.
  • Updated docs/semantics/grammar.md and docs/guides/dsl.md EBNF productions to reflect 0.5.0 surface: >>>, change_base, composition-rule keywords, contraction declarations, grouped marginalize, the full compose-operator family.
  • README.md and docs/index.md rewritten with separate framings (probabilistic-programming user vs library developer); architecture and inference-stack diagrams render in Mermaid.
  • New module quivers.core.trans exposing the TransSeq class and compose_trans function as the Python-side surface for the DSL's >>> operator; Morphism.change_base accepts TransSeq natively.
  • QVR syntax highlighting refreshed across all three rendering surfaces: the Pygments lexer (src/quivers/dsl/pygments_lexer.py), the tree-sitter highlights query (grammars/qvr/queries/highlights.scm), and the VSCode TextMate grammar (editors/vscode-qvr/syntaxes/qvr.tmLanguage.json).
  • Restored the MkDocs hook docs/hooks/register_qvr_lexer.py and wired it through mkdocs.yml; Mermaid rendering enabled via pymdownx.superfences custom-fence classifier plus a mermaid-init.js themer.

Added: shape compatibility under product / factored codomains

  • Storage-level init check: observed f : A -> B = from_data("KEY") now matches the init tensor against the declared codomain on numel rather than per-axis shape. A flat init whose total cardinality matches a declared product codomain is accepted and reshaped to the factored layout.
  • Morphism.refactor(domain=..., codomain=...) exposes the reshape as a user-facing method: switch a morphism's view between flat and product factorings of isomorphic objects.

Changed: module consolidation

  • quivers.core.extra_algebras is deleted. All its classes (Lukasiewicz, Godel, Tropical, MaxPlus, LogProb, Real, Probability, Counting) and singletons now live in quivers.core.algebras alongside ProductFuzzy and Boolean. Internal imports updated; user code that imported from extra_algebras must update to import from algebras.

Added: distribution-family wrappers

  • ConditionalMixture wraps a ConditionalX family in a K-component mixture with learnable mixture logits.
  • ConditionalIndependent reinterprets a base distribution's trailing batch dim as an event dim (analogue of torch.distributions.Independent).
  • ConditionalTransformed pushes a base through a chain of bijectors, applying the log-det-Jacobian correction in log_prob.

Fixed: HMC at constrained-support boundaries

  • HMC's potential function now catches torch.distributions' support-validation errors and returns -inf; the kernel reads non-finite log-densities as divergent transitions and rejects them in the Metropolis step. Eliminates the prior ERROR cells in the benchmark matrix at the Gamma / InverseGamma / HalfNormal support boundaries.

Changed: codebase-wide American spelling

  • All British spellings (centered / normalize / parameterize / optimize / reparameterise / recognize / factorize / initialise / specialize / organize / minimize / maximize / discretize and their derived forms) converted to American spellings. eight_schools_centred.qvr renamed to eight_schools_centered.qvr with the program names updated to match.

Added: unified distribution family registry

  • FamilySpec and ParamSpec in quivers.continuous.family_spec, a single source of truth per family. Both the conditional path (_IndependentConditional and the hand-written ConditionalX classes) and the inline path (FixedDistribution / MixedInlineDistribution) read from the same record. Replaces the previous duplication between families.py and inline.py.
  • Inline coverage for 11 previously registry-only families: Cauchy, Laplace, Gumbel, StudentT, Chi2, InverseGamma, Weibull, Pareto, Kumaraswamy, ContinuousBernoulli, FisherSnedecor. Every family declared via _make_family now auto-registers its fixed-inline factory, mixed-inline builder, and support constraint from the spec.
  • New families: ConditionalPoisson, ConditionalGeometric, ConditionalNegativeBinomial, ConditionalVonMises plus their inline factories. Brings the registry to 34 distinct distribution families.
  • _IndependentConditional is shape- and discreteness-aware: continuous-reparameterised, continuous-non-reparameterised (VonMises), and discrete (Poisson / Geometric / Bernoulli / Categorical) all share the same generic conditional class via a discrete flag plus a fall-back to .sample() when the underlying distribution lacks rsample.

Added: benchmark suite expansion

  • Tier 1 conjugate: normal_inverse_gamma (joint mean / variance recovery), gamma_exponential, bayes_linear_regression (well-conditioned).
  • Tier 2 hierarchical: eight_schools_centred and eight_schools_noncentred (Rubin 1981 / Gelman et al. 2013) with cached NUTS-derived posterior moments.
  • Tier 3 hard geometry: neal_funnel (scale-of-scale dependency, Neal 2003), ill_conditioned_mvn (per-dim posteriors with eigenvalues across four decades).
  • Tier 6 constrained support: half_normal_scale (positive support), truncated_normal_recovery (unit interval).
  • Metrics extension: gaussian_kl (closed-form KL between two Gaussians), wasserstein_2_1d (sorted-quantile W₂), total_variation_grid (TV against a quadrature-supplied density), split_r_hat and effective_sample_size (Vehtari et al. 2021).
  • Runner (tests/benchmarks/runner.py) drives an algorithm × problem grid across AutoNormalGuide, AutoMultivariateNormalGuide, AutoLaplaceApproximation, HMCKernel, NUTSKernel and emits docs/developer/inference-benchmarks.md. Capture problems document expected failure modes (mean-field underfit on correlated MVN / funnel posteriors).

Added: earlier 0.5.0 work

Added: inference layer

  • LatentRegistry: per-site introspection helper that every guide and MCMC kernel consumes. Walks the model's _step_specs once at construction and exposes per-site (support, dims, plate vs scalar, bijector, flat-vector offsets) plus flatten / unflatten between site dicts and a single flat unconstrained vector.
  • Transforms / flows: TransformModule cooperative base inheriting both torch.distributions.transforms.Transform and torch.nn.Module. Primitives: AffineCouplingTransform (RealNVP), MaskedAutoregressiveTransform (MAF), InverseAutoregressiveTransform (IAF), NeuralSplineCouplingTransform (NSF rational-quadratic spline coupling), LULinearTransform (Glow), BatchNormTransform, plus MADE and helper masks.
  • Variational objectives: Objective ABC and four implementations: ELBO, IWAEBound (Burda-Grosse-Salakhutdinov 2016), RenyiBound (Li-Turner 2016), VRIWAEBound (Daudel-Douc-Roueff 2023). Each accepts a pluggable GradientEstimator strategy: Reparameterised (default), StickingTheLanding (Roeder-Wu-Duvenaud 2017), DoublyReparameterised (Tucker-Lawson-Gu-Maddison 2019; default for IWAE), ScoreFunction.
  • Guide zoo: AutoMultivariateNormalGuide (full-rank Cholesky Gaussian), AutoLowRankMultivariateNormalGuide (Σ = W W^T + diag(σ²) via Woodbury), AutoNormalizingFlow (user-supplied transform stack), AutoIAFGuide (preconfigured IAF stack with reverse permutations), AutoNeuralSplineGuide (NSF coupling stack), AutoLaplaceApproximation (two-phase: SVI MAP then Hessian-derived Gaussian), AutoMixtureGuide (Gumbel-Softmax mixture of component guides).
  • MCMC: HMCKernel and NUTSKernel operating on the flat unconstrained latent vector via the LatentRegistry. Both support identity / diagonal / dense mass matrices, Nesterov dual-averaging step-size adaptation (Hoffman-Gelman 2014 Alg 6), and Welford-online mass-matrix adaptation. MCMC driver orchestrates parallel chains with warmup-then-sample phases and produces MCMCResult carrying per-site posterior draws + split-R̂ + ESS diagnostics.
  • Hybrid samplers: AutoDAIS (differentiable annealed importance sampling guide; Geffner-Domke 2021 / Zhang et al. 2021) and WarmupThenHMC (SVI warmup then MCMC seeded from the guide's posterior mean).
  • Predictive now accepts either a Guide or an MCMCResult as its posterior; the MCMC overload iterates the recorded posterior draws.

Added: issue #9: scoped grouped marginalize blocks

  • marginalize <v> : K <- F(probs) over G via idx in { ... } with the full feature surface from issue #9:
  • Arbitrary nesting of grouped marginalize blocks. The runtime distinguishes the innermost level (which has a row axis and a fibration to scatter along) from outer levels (which consume already-reduced contributions broadcast over the surviving outer class axes), so a stack of N blocks composes to the correct hierarchical log-mixture. Tested at depths 3-7.
  • Product fibrations via via product(idx_a, idx_b, ...) paired with over G * H product grouping plates.
  • reduction = logsumexp | sum | mean per block.
  • Continuous latents in scope inside the body; the compiler binds the latent name to torch.arange(K) so let-arithmetic referencing the latent broadcasts across the class axis.
  • Body vectorization: the body's terminal observe step is captured by the compiler (GroupedBodyObserveStep IR) and its per-row per-class log-likelihood is stored at the latent's environment slot for the marginalize callable to consume. No user-side let boilerplate.
  • quivers.continuous.bayesian.marginalize_grouped generalized to accept multi-axis broadcast inputs, product fibrations, and the three reduction modes.
  • dsl/examples/event_structure.qvr now uses real observe steps inside the grouped blocks.

Added: synthetic benchmark suite

  • tests/benchmarks/ with Tier-1 conjugate (Beta-Bernoulli, Normal-Normal) and Tier-3 hard-geometry (correlated MVN) benchmarks. Models live in tests/benchmarks/models/*.qvr as canonical didactic models; dataset / reference modules are plain functions returning (model, observations, true_params). The Tier-3 suite includes a capture test for the mean-field failure mode on a correlated posterior.

Added: V-Cat categorical surface

  • Backend-agnostic morphism → nn.Module adapter (quivers.core.morphisms.as_torch_module). Every binding site that calls add_module (MonadicProgram step list, FanOutMorphism, parametric programs) funnels through the adapter so non-Module morphisms (LatentMorphism, ComposedMorphism, ProductMorphism, …) bind without crashing. The wrapper attaches the original categorical morphism on _morphism for runtime recovery; MonadicProgram.rsample detects V-Cat steps and computes them as deterministic tensor applications.
  • Multi-algebra composition with one operator per algebra. >> (ProductFuzzy default), << (reverse), >=> (Kleisli), *> (Markov sum-product), ~> (LogProb log-space), ||> (Gödel min/max + Heyting), ?> (Viterbi max-plus), &&> (Boolean), +> (Łukasiewicz), $> (Real sum-product), %> (Probability sum-product). Each operator carries its algebra; cross-operator chains require explicit .change_base(φ). Five new algebra classes: MaxPlusAlgebra (Viterbi / MAP), LogProbAlgebra (log-space sum-product), RealAlgebra (sum-product on ℝ), ProbabilityAlgebra (sum-product on [0, 1]), CountingAlgebra (sum-product on non-negative integers). The last three mirror the corresponding arcweight weight-set semirings (RealWeight, ProbabilityWeight, IntegerWeight) and round out the built-in algebra catalog to eleven distinct algebras.
  • Algebra homomorphisms (quivers.core.algebra_morphisms) for change-of-base: Expectation, LogProb, MaxPlus, Threshold, MaterialImplication, Embedding, IdentityHom, ProbabilityClamp (Real → Probability), CountingFromReal (Real → Counting via floor), ProbabilityToReal and CountingToReal (sub-algebra inclusions). Module-level singletons (EXPECTATION, LOG_PROB_HOM, MAX_PLUS_HOM, MATERIAL_IMPLICATION, PROBABILITY_CLAMP, COUNTING_FROM_REAL, PROBABILITY_TO_REAL, COUNTING_TO_REAL); factory helpers threshold(tau) / embedding(src, tgt); a HOMOMORPHISM_REGISTRY keyed by (source.name, target.name); and lookup_homomorphism(). Morphisms expose .change_base(phi) to apply a homomorphism; the DSL surface is f.change_base(name) with the catalog wired into the compiler (expectation, log_prob, max_plus, material_implication, threshold, boolean_embedding, probability_clamp, probability_to_real, counting_from_real, counting_to_real).
  • Compact-closed surface on V-Cat morphisms: f.dagger (transpose), f.trace(A) (categorical trace), cup(A) and cap(A) (unit / counit). Each operation is well-defined for every algebra; the semantic interpretation depends on the active algebra (ProductFuzzy: tensor transpose; Markov: Bayes inversion; Viterbi: max-plus reversal; Boolean: relational converse).
  • Data-derived and expression-derived initialisers for morphism declarations. observed f : A -> B = from_data("KEY") binds the morphism's tensor from a runtime-supplied data dictionary (passed via the data= keyword on quivers.dsl.loads / load, or Compiler.bind_data(...)). inner.freeze is a postfix that materialises an expression's tensor with .detach().clone() and wraps the result as a parameter-free ObservedMorphism. Expression-derived initialisers without .freeze propagate gradient lineage; the declaration's type-check accepts shape-compatible inits and re-tags them with the user-declared domain / codomain.

Changed

  • SVI takes an objective: Objective instead of loss: ELBO.
  • Predictive takes a positional posterior: Guide | MCMCResult instead of a guide: keyword.
  • The variational machinery moved off PlateDraw (kl_to_prior removed; the deeper migration of _mean/_log_scale happens in v0.5.0 alongside the rest of the refactor).
  • MonadicProgram binding sites no longer crash on V-Cat morphisms; the runtime applies them as deterministic tensor steps.
  • FanOutMorphism accepts both ContinuousMorphism and V-Cat morphism components; non-continuous components are wrapped in DiscreteAsContinuous so the fan-out loop dispatches uniformly.

Removed

  • PlateDraw.kl_to_prior (no callers; the Guide hierarchy owns variational distributions now).
  • quivers.inference.elbo (re-exported from quivers.inference.objectives as ELBO).

[0.4.1] - 2026-05-12

Three connected bug fixes that unblock hierarchical-Bayesian workflows under the 0.4 surface. No grammar or parser changes; panproto does not need to revendor panproto-grammars-all for this release.

Fixed

  • Variational guides respect constrained supports. quivers.inference.AutoNormalGuide and quivers.inference.AutoDeltaGuide previously sampled in unconstrained real space for every latent and fed the result into the prior's log_prob, which raised ValueError: Expected value to be within the support of the distribution for any constrained family (HalfNormal, Beta, Uniform, Exponential, Gamma, LogNormal, LogitNormal, HalfCauchy, Dirichlet, …). Every ContinuousMorphism now exposes a support: Constraint property; the inline distributions (FixedDistribution, MixedInlineDistribution, DirectBernoulli, DirectTruncatedNormal) and the family-conditional distributions in quivers.continuous.families override it with the correct constraint. The auto-guides sample z ~ Normal(loc, scale) in unconstrained space, push through biject_to(support) to land on the constrained side, and evaluate log_prob with the Jacobian correction (log N(z) + log|det J_{T^{-1}}(v)|). The simplex case routes through StickBreakingTransform and accounts for the d ↔ d-1 dimension reduction. This is the same construction Pyro's AutoNormal uses.

  • condition(model, data) exposes host data to let-expression gather, and plate latents are batch-invariant. Keys in the conditioning data dict that do not match a declared sample / observe site are pre-populated into the trace environment as deterministic values, visible to let-expression evaluation. Free variables in let expressions (variables not bound by any sample / observe / let / lambda step) are no longer rejected at compile time; the runtime resolves them against the conditioning data dict. Together with the batch-invariant plate semantics below, this unlocks the canonical crossed-random- effects idiom:

    program p : Resp -> Resp
        by_subj : Subj <- Normal(0.0, 1.0)
        let mu = sigmoid(by_subj[subj_idx])
        observe r : Resp <- Bernoulli(mu)
        return mu
    
    cond = condition(p.morphism, {"subj_idx": idx, "r": y})
    
    Plate draws (v : A <- F(args)) are now batch-invariant: the latent is a single shared tensor of shape (|A|, *B.shape) : the standard Pyro / NumPyro semantic, instead of being replicated against the program input's leading batch axis. The gather by_subj[subj_idx] along the plate axis then produces a per-row predictor of shape (N_resp,) that broadcasts cleanly against an observed Resp-plate kernel. Scalar-per-row plates (Normal, HalfNormal, …) drop the trailing length-1 axis so the latent has the natural (|A|,) shape. Both :class:AutoNormalGuide and :class:AutoDeltaGuide were updated to advertize the same shape on the variational side: plate latents are stored as (|A|, unconstrained_dim) parameter tensors and sampled batch-invariant so ELBO substitution into the model's log-joint env aligns shape-by-shape with the model's :class:PlateDraw output. (Without this, the SVI step ran into an IndexError from the plate-axis gather even though cond.trace(...) on the same model succeeded.)

  • Inline Dirichlet accepted as a prior with scalar or vector concentration. pc <- Dirichlet(α) and pc <- Dirichlet([α_1, …, α_K]) both compile. For scalar α, the simplex dimension is inferred from the program's declared codomain (dim for a ContinuousSpace, cardinality for a SetObject, or 2 as a minimum); for a per-component vector, the simplex dimension is the number of literals. The make_fixed_dirichlet factory accepts both single-element sequences (treated as symmetric) and per-component sequences. Previous behavior was to raise distribution family 'Dirichlet' is not supported as an inline distribution; declare it as a continuous morphism instead, or to crash on the vector form with TypeError: make_fixed_dirichlet() takes 2 positional arguments but 4 were given.

Internal

  • ContinuousMorphism.support defaults to constraints.real; every constrained-output subclass overrides it. Variational guides consume this through torch.distributions.constraint_registry.biject_to.
  • _FAMILY_SUPPORTS in quivers.continuous.inline maps each inline family to its support, applied by make_inline_distribution when constructing a MixedInlineDistribution. Uniform and TruncatedNormal specialize to the actual interval when both bounds are literal.
  • trace() pre-populates env with the non-site keys of the observations dict before the program's steps run.
  • _validate_let_expr_vars treats unbound names as deferred host references; the eval-time evaluator raises a clear KeyError if the value is missing.
  • PlateDraw.rsample is batch-invariant: returns (|A|, *B.shape) regardless of the program input's batch axis; scalar-per-row plates squeeze the trailing length-1 dimension. PlateDraw.log_prob accepts either the natural plate-latent shape or the legacy flat (batch, |A| · prod(B)) shape for back-compat.
  • _VECTOR_PARAM_FAMILIES in quivers.continuous.inline lists the inline families whose all-literal factory takes a single vector argument rather than splatting positional floats; the parser's flattened literal sequence is re-bundled into a list before the factory call. Currently {"Dirichlet"}; the mechanism is ready for MultivariateNormal, Wishart, and LKJCorrelationFactor as those land.
  • make_fixed_dirichlet treats a single-element concentration sequence as a scalar (symmetric Dirichlet), broadcast to the codomain's simplex dimension; multi-element sequences must match the codomain dimension exactly.

Tests

tests/test_inference_constrained.py (21 cases): every supported constrained family under both auto-guides, host-data passing through condition, the end-to-end Bernoulli hierarchical- regression observation kernel that exercises the plate-gather → observe-plate composition, an SVI-step regression that verifies the guide / model plate shapes align and the ELBO descends with loc_by_subj driven negative by all-zero responses, and inline Dirichlet with both scalar and vector concentrations.

[0.4.0] - 2026-05-12

This release lands three deeply-interconnected bodies of work in a single minor bump, all motivated by the goal of making quivers a unified surface for probabilistic, weighted-deductive, and neural-symbolic programs:

  1. DSL surface homogenization, one Kleisli-bind sigil <-, type-annotated indexed binds, scoped marginalize, and !-prefixed effect signatures.
  2. Agenda-based weighted-deduction framework, a single engine subsuming CKY, Earley, Viterbi, inside-outside, semi-naive Datalog, A* parsing, Knuth's algorithm, and MLTT proof search, declared via deduction { … } blocks with first-class differentiable charts.
  3. Hierarchical-Bayesian + arrow / algebraic-effects substrate , plate draws, vectorized observations, marginalization, LKJ priors, Cholesky factor spaces, the Hughes arrow tower (Arrow, ArrowChoice, ArrowApply, ArrowLoop, ArrowZero, ArrowPlus), stdlib monads / monad transformers, algebraic effects + handlers via FreeMonad.
  4. Structural compression: signatures, encoders, decoders, losses, a uniform algebraic interface for compressing arbitrary structured objects (sequences, trees, graphs, charts, typed lambda terms) to fixed-length vectors and decoding them back under a learned distribution. Realizes transformers, tree-LSTMs, graph-NNs, autoregressive LMs, VAEs, and vector-inside-outside parsers as instances of one F-algebra / F-coalgebra pattern.

Changed (breaking, pre-1.0 clean cut)

Surface DSL homogenization. The program-block surface is reorganized around a single Kleisli-bind sigil <-, type-annotated indexing on the binder, scoped marginalization, and !-prefixed effect signatures. The categorical denotation is unchanged; only the surface forms shift to a Haskell-PPL aesthetic.

  • draw v ~ F(args)v <- F(args): the draw keyword is retired in favor of the unique Kleisli-bind sigil <-. The surface-arrow alternative (the v0.4 do-notation v <- F form) is unified with the new sigil; there is now one and only one way to introduce a random variable.
  • draw v : A -> K ~ F(args)v : A <- F(args): indexed (plate) binds use a type annotation on the binder rather than a separate keyword family. The per-fiber codomain is taken from the family; the : A annotation declares the index set.
  • observe v ~ F(args)observe v <- F(args): scored binds retain the observe prefix; the rest of the line matches the Kleisli-bind shape.
  • observe r[n] ~ F(args) for n in Nobserve r : N <- F(args) : the vectorized-observe for n in N shape collapses into the type-annotated form. Bracket-indexed family arguments theta[N] annotate that an argument is a section of an N-indexed family.
  • marginalize c (trailing) → marginalize c : A <- F(args) in { … } : marginalization is always scoped; the integration target and the scope are visible at the binding site. The categorical pushforward becomes visually local to its binding.
  • posterior name (model) [params] : dom -> codprogram name (params) : dom -> cod ! Pure over model : posterior blocks are encoded as program declarations with a ! Pure effect signature and an over model modifier. The parser routes such programs to the posterior registry and the compiler enforces the determinism constraint.
  • ! effect signature on programs: program P : X -> Y ! Sample, Score declares the body's capability set. Effects: Sample, Score, Marginal, Pure. The compiler verifies the body's actual effects are a subset of the declared set (or rejects any effect when Pure is declared).
  • output Xexport X: module-level exports replace output; multiple export declarations per module are allowed.
  • Drop labeled-tuple return form: the v0.4 return (a: x, b: y) form was purely syntactic rebinding without semantic effect; it is removed as dead surface.

Internal

  • BindStep unifies the four old step shapes (DrawStep, PlateDrawStep, VectorisedObserveStep, MarginalizeStep) at the AST level; the old shapes remain as compiler-internal IR consumed by the runtime step-builder. The parser emits only BindStep and LetStep; the compiler's _expand_bind_steps pass translates to the internal IR.
  • ProgramDecl gains effects: frozenset[str] | None and over_model: str | None fields.
  • MonadicProgram gains an effect_set: frozenset[str] | None field for introspection by downstream inference / dispatch code.
  • ExportDecl replaces OutputDecl. The first declared export is the module's primary output; subsequent exports are accessible as additional module bindings.
  • The DSL's <- arrow now binds with prec.right, eliminating the previous GLR ambiguity between draw-args and following steps.

Migration guide

For most programs the mechanical translation is:

pre-0.4 0.4
draw v ~ F(args) v <- F(args)
draw v : A -> K ~ F(args) v : A <- F(args)
observe v ~ F(args) observe v <- F(args)
observe r[n] ~ F(args) for n in N observe r : N <- F(args)
marginalize c marginalize c <- F(args) in { … }
posterior P (M) [v] : … body program P(v) : … ! Pure over M body
output E export E

Tests

  • All example .qvr files migrated to the 0.4 surface.
  • Tree-sitter corpus covers the new surface shapes (20/20 parses).

Agenda-based weighted-deduction framework

0.4 also lands the full agenda-engine substrate underneath a declarative deduction { … } block. The framework subsumes CKY, Earley, Viterbi, inside-outside, semi-naive Datalog evaluation, A* parsing, Knuth's algorithm, depth-first MLTT proof search, and edit-distance dynamic programming as parameter settings on a single engine.

  • Surface form:

    deduction CG : Atom -> Atom [semiring=LogProb, start=S, depth=4]
        atoms NP, S, VP
        rule fwd_app : X/Y, Y |- X
        rule bwd_app : Y, Y\X |- X
    
    Single-uppercase-letter pattern names (X, Y) bind as wildcards; non-wildcard atoms match literally. The block declares the seven irreducible parameters of an agenda-based deduction (item algebra via atoms, rule set, semiring, axiom source, goal predicate, start symbol, depth bound). Concrete parsing strategies are selected by the compiler from these parameters.

  • Charts as first-class differentiable values: each deduction's runtime view exposes chart.weight(item), chart.enumerate(pattern), chart.derivations(item), chart.goal_weight(), all returning torch.Tensor values whose gradients flow back through the agenda's semiring operations to any requires_grad=True axiom / rule weight, enabling end-to-end gradient-based learning over deduction systems (the Goodman 1999 semiring framework lifted to PyTorch tensors).

  • Pre-registered stdlib deductions (quivers.stochastic.stdlib): CCG, Lambek, STLC, MLTT, Datalog, Dijkstra, HMM, ViterbiHMM, EditDistance. Users import and run them directly:

    from quivers.stochastic.stdlib import Datalog
    view = Datalog(edge_axioms)
    reaches = view.enumerate(("reach", source, Wildcard("Y")))
    

  • Agenda strategies: cky_agenda(), earley_agenda(), viterbi_agenda(priority_fn), astar_agenda(g_plus_h), knuth_agenda(), depth_first_agenda(), semi_naive_agenda(). Strategy independence (Goodman 1999 §3) is verified in test_agenda.py: under idempotent semirings, chart values agree across all strategies.

  • panproto integration: QVR_DEDUCTION_PROTOCOL and extract_deduction_schema(compiler) make deduction systems first-class panproto schemas. Schema morphisms over the protocol correspond to deduction-system specializations (e.g., CCG ⊂ Lambek ⊂ MultimodalLambek).

Structural compression: signatures, encoders, decoders, losses

The release lands a uniform algebraic interface for compressing arbitrary structured objects to fixed-length vectors and decoding them back under a learned distribution. Categorically: every constructor algebra a user declares is the initial Σ-algebra T_Σ of a multi-sorted signature; a encoder is a Σ-algebra homomorphism T_Σ → Vec_D, and a decoder is a Kleisli coalgebra Vec_D → Kern(T_Σ). The recursion / corecursion is supplied by the framework; the analyst supplies only the per-operation parametric functions. This single abstraction subsumes RNN / transformer / tree-LSTM / graph-NN encoders, autoregressive LM and variational decoders, and the proposal's vector inside-outside parser (the chart's item signature is Σ, the parser's combine / split^L,R are the per-operation encoder functions, the attention-weighted aggregation lives entirely inside the encoder, outside the chart's role, so the semiring abstraction is not broken).

  • Signature blocks declare sorts, constructors, binders, and (for graph signatures) vertex / edge kinds:

signature LF
    sorts
        Term : object [dim=64]
        Type : object [dim=32]
        Name : data   [dim=32, vocab=["dog", "cat", "every"]]
    constructors
        Const : Name      -> Term
        App   : Term, Term -> Term
    binders
        Lam : binds (x : Term : ty : Type) in (body : Term) -> Term
Three sort kinds: object (recursively decoded), data (opaque raw values; data sorts may declare a closed vocabulary via vocab { … } of string / integer / float literals), index (de-Bruijn slots). The reserved BoundVar op is a built-in de-Bruijn reference; binders thread a typed context Γ carrying (var_sort, embedding, type_term) per scope entry. Binder variables may carry an annotation sort via binds (x : Term : ty : Type), the variable's type is structurally tracked through the de-Bruijn context. - Encoder blocks declare an F-algebra homomorphism T_Σ → Vec_D. Per-constructor bodies are user-supplied or scaffolded as 2-layer MLPs by the compiler with correct per-arg dimensions. Sequence sugar, Cons(head, tail) recurrent state |-> body for left-folds and Cons(head, tail) attention prefix |-> body for iterative outside-in walks that thread a running prefix list, handles RNN / transformer-shaped encoders uniformly. Graph signatures use a message_passing-shaped body (init[V], message[E], update[V], readout, iterations N). - Decoder blocks are Kleisli coalgebras Vec_D → Kern(T_Σ) with sample(vec) -> Term and log_prob(term, vec) -> Tensor. Per-sort structure / primitive / factor / binder_select heads are scaffolded as learnable neural networks; the corecursion (structure choice, factor split, recursive descent with extended Γ at binders, BoundVar fallback to in-scope variables, depth-bounded termination) is supplied by the framework. No silent type coercion or sentinel value: an observed term whose shape doesn't match the canonical form raises with a typed diagnostic. - var_init per (var_sort, annot_sort) pair: multiple var_init Term from Type as ty |-> body declarations per encoder, one per pair of sorts the signature's binders introduce; the compiler scaffolds defaults for omitted pairs. - Stdlib shapes (quivers.structural.shapes): Seq[A] with rnn_encoder, transformer_encoder, bow_encoder, ar_decoder; Tree[L, B] with tree_lstm_encoder and tree_decoder; Graph[V, E] with graph_signature and gnn_encoder (per-edge-kind message MLP, per-vertex-kind GRU update, mean / sum / max readout). - Deduction integration: a deduction block may declare an item signature and attach a encoder; the chart's embedding(item) query returns a differentiable vector computed by the attached encoder's algebra-homomorphism recursion over the chart-item term. - Loss attachments (loss <name> [weight ...] [on <site>] { body }): attachable at global, program <name>, deduction <name>, encoder <name>, decoder <name>, rule <name> in <D>, and chart of <D> sites. Rule-attached losses fire on every rule application during chart construction (the agenda's _fire path invokes a registered rule_callback with the full antecedent list); chart-attached losses fire once on the completed chart. LossRegistry.evaluate_on(kind, target, env) returns the weighted partial sum for a given attachment site; ChartView.attached_loss exposes the accumulated rule + chart losses fired during a deduction's run. - Optional export: a module with only signatures / encoders / decoders / losses (no top-level morphism) now compiles into a Program(None) container; the artifacts are reachable through prog.signatures / prog.encoders / prog.decoders / prog.losses. The previous no export declaration found hard error is replaced by a precise diagnostic on forward(). - Strict declaration discipline: every sort referenced in a constructor's domain or a binder's variables / scoped arguments / codomain must be declared in the signature's sorts { … } block, no silent auto-registration. Every sort with no inline dim must have its dim supplied by every encoder / decoder over the signature. Reserved op names (BoundVar, Data) are rejected as user-declared constructors or binders. vocab clauses are only valid on data sorts; duplicate vocabulary entries are rejected. - Public surface: quivers.structural exports Signature, Sort, Constructor, Binder, BinderVarSpec, BinderArgSpec, VertexKind, EdgeKind, Term, Context, EMPTY_CONTEXT, DataLeaf, Encoder, Decoder, LossEntry, LossRegistry, bound_var, make_term. quivers.structural.shapes exports the canonical sequence / tree / graph factories. - 27 new tests in tests/test_structural.py cover every surface form, every strict-rule diagnostic, the typed-binder discipline, end-to-end compression and decoding for sequences / trees / graphs, rule-attached and chart-attached loss firing, per-pair var_init overrides, recurrent / attention modes, and the data-sort vocabulary pipeline.

Hierarchical-Bayesian primitives and arrow / effects tower

Added

  • Hierarchical-Bayesian modeling primitives in quivers.continuous.bayesian, each carrying its categorical denotation in Kern:
  • PlateDraw(index_size, family, domain), finite-domain-indexed draw realized as a Kern-morphism A → B by the natural isomorphism Kern(1, B^A) ≅ Kern(A, B); subclass of ContinuousMorphism so it threads through the existing MonadicProgram step machinery.
  • VectorisedObserve(family, response), batched-observation kernel Φ → G_{≤1}(Φ) with score ∏_n p_F(r_obs(n); θ(n, φ)).
  • marginalize_categorical(log_probs), program-level pushforward through π_{Φ\C} realized as log_sum_exp over the class axis.
  • LKJCorrelationFactor(K, eta), LKJ prior on CholeskyFactor(K) via the Lewandowski-Kurowicka-Joe onion method; analytic log_prob matches Stan's lkj_corr_cholesky_lpdf.
  • Truncated(base, lower, upper), generic interval-truncation combinator (rejection sampling with Monte-Carlo truncation-mass estimation).
  • cumsum(K), softmax(K), deterministic morphisms for monotone splines and simplex projection.
  • cholesky_quad_form(K), covariance reconstruction Σ = diag(s) L L^T diag(s).
  • CholeskyFactor(K) ContinuousSpace, manifold of K×K lower-triangular factors of correlation matrices.
  • Surface syntax for hierarchical-Bayesian models in .qvr:
  • draw v : A -> K ~ Family(args), finite-domain-indexed plate draw.
  • observe r[n] ~ Family(args) for n in N, vectorized observation.
  • marginalize c, program-level discrete-latent marginalization.
  • arr[idx], Kleisli pullback gather expression inside let-bodies.
  • posterior name (model) : domain -> codomain { steps return ... }, deterministic post-conditioning block whose body consumes posterior latents.
  • Parametric programs: program name (G : FinSet, scale : Real, prior : Mor[A, B]) : dom -> cod ..., programs polymorphic over objects, scalars, and morphisms; denote dependent kernels Π(p:P).Kern(dom(p), cod(p)). Instantiated at each call site draw v ~ name(args) by parameter substitution + α-renaming, so each call contributes fresh latent factors to the caller's joint kernel. Supports the random-effects reuse story without tying latents across call sites.
  • Let-expression builtins: cumsum, softmax, cholesky_quad_form join the existing sigmoid / exp / log / abs / softplus.
  • AST nodes in quivers.dsl.ast_nodes: PlateDrawStep, VectorisedObserveStep, MarginalizeStep, LetExprIndex, PosteriorDecl; each docstring carries the Kern denotation.
  • Stan-model port at src/quivers/dsl/examples/event_structure.qvr, a faithful translation of the four-class telicity × durativity latent-class model from ~/Projects/supertelicity/analysis/event-structure-induction/models/event-structure-model.stan, demonstrating crossed random effects, ordinal monotone splines, vectorized observations, and marginalize over the discrete latent class.
  • tests/test_bayesian.py: 15 tests covering every new primitive and every new AST node's parse / compile round-trip, plus a compile-time smoke test on the Stan-model port.

Changed

  • _walk_program_step return type widened from DrawStep | LetStep to the ProgramStep union root.
  • Tree-sitter grammar regenerated (grammars/qvr/src/parser.c, grammar.json, node-types.json) to recognize the new program steps and top-level declarations.

Added

  • Pattern-polymorphic schema declarations: schema r[X, Y : Cat] : (X/Y) * Y -> X. Subsumes rule with explicit parameter types and a unified domain/codomain shape.
  • New SetObject variants: EnumSet(name, elements) for named-element finite sets, FreeResiduated(generators, depth, ops) for residuated category universes. New surface syntax object Atoms = {NP, S, VP} and object Cat = FreeResiduated(Atoms, depth=4, ops=[slash]).
  • FreeMonoid surface form: object Free = FreeMonoid(X, max_length=4).
  • TypeSlash and TypeEffectApply TypeExpr variants, residuated patterns and effect-typed types are first-class. The CatPattern AST family is removed (folded into TypeExpr).
  • chart_fold(lex=, binary=, unary=, start=, depth=, effect_depth=, handlers=) primitive expression, desugared form of parser(rules=...). unary= is wired through the inside algorithm's reflexive-transitive unary-rule closure. handlers= post-composes effect handlers on the parser output as log-space transition morphisms.
  • .curry_right / .curry_left postfix combinators witnessing the residuation isomorphisms; backed by quivers.core.morphisms.CurriedMorphism.
  • Typeclass tower in quivers.monadic.typeclasses: Functor, Applicative, Monad, Alternative, MonadPlus, Foldable, Traversable, MonadTrans. Concrete monads (FuzzyPowersetMonad, FreeMonoidMonad, GiryMonad) subclass Monad directly.
  • Stdlib effect instances in quivers.monadic.instances: Identity, Maybe, Alternative_, Continuation, State, Reader, Writer, List. All operations (pure, fmap, apply, join, bind, lift_a2, empty, alt, foldr, traverse) are concrete V-relation realizations; function-space-dependent operations encode [A → B] as a finite FinSet of cardinality |B|^|A|. Monad transformers in quivers.monadic.transformers: StateT, ReaderT, MaybeT, ContT, WriterT.
  • Algebraic effects + handlers in quivers.monadic.algebraic: Operation, EffectSignature, Handler, FreeMonad. FreeMonad carrier is the bounded-depth signature-tree set realized as a flat FinSet with structural decomposition via _decompose_carrier_index / _compose_carrier_index; pure, fmap, join, bind, lift_a2 satisfy the monad laws up to truncation. Handler.run is the post-order tree fold interpreting each leaf through return_clause and each operation node through operation_clauses. EffectSignature.to_theory() and Handler.as_theory_morphism() realize the panproto-side theory and theory morphism.
  • quivers.monadic.bridges: Kleisli, ArrowMonad, CoKleisli, kleisli, arrow_monad, cokleisli connecting the monad and arrow towers. Kleisli.compose is fmap-then-join with structural recovery of the underlying B; Kleisli.first is realized via the canonical monad strength σ = (pure × id) >> lift_a2(id_{A⊗B}); Kleisli.app routes through the Applicative apply. ArrowMonad provides fmap/pure/apply/join/bind/lift_a2 via the underlying arrow's arr/id_arr/app/compose. CoKleisli is registered as Category_; promoting to Arrow requires an explicit comonad costrength supplied via first_via_costrength(f, C, costrength).
  • quivers.arrows package, Hughes-style arrow hierarchy (Category_, Arrow, ArrowChoice, ArrowApply, ArrowLoop, ArrowZero, ArrowPlus) with panproto-theory mirrors. New quivers.arrows.instances with VRel, Function, Stochastic arrow instances; loop_arr realized via the V-quantale iterative trace (Joyal-Street-Verity 1996, §3).
  • quivers.stochastic.effect_lifts.class_directed_lifts: class-driven schema lifting for effect-typed parsers. make_swap_schema / swap_rule_set emit swap_TU schemas from registered DistributiveLaw instances for commutation firings.
  • quivers.core._factories module, concrete morphism constructors inj, case, pi, pair, parallel, terminal, constant, distrib_right, coproduct_map. The algebra on which the stdlib monads, arrows, and algebraic-effects layer are built.
  • New tree-sitter grammar at grammars/qvr/ with regenerated parser; the unified _type_expr family subsumes the prior _cat_pattern productions.
  • Local-grammar override at quivers.dsl._dev_grammar (activated by QVR_USE_LOCAL_GRAMMAR=1) using panproto 0.47's first-class AstParserRegistry.override_grammar() API. Compiles the in-tree grammar and installs it into the standard registry when the upstream panproto-grammars-all bundle hasn't yet vendored the latest grammar source.
  • docs/guides/effects.md and docs/semantics/effects.md, user guide and formal denotational layer for the typeclass + algebraic-effects framework.
  • quantifier_scope.qvr example demonstrating Charlow-style scope-taking via Continuation.

Changed

  • The Monad ABC is the typeclass-tower one (quivers.monadic.typeclasses.Monad); the previous parallel ABC in quivers.monadic.monads is removed. Concrete monads provide both the typeclass operations (pure, apply, join) and the Eilenberg–Moore aliases (unit, multiply).
  • RuleDecl premises and conclusion are typed at TypeExpr (previously CatPattern).
  • ObjectDecl admits both : type_expr and = initializer forms.
  • parser(...) infers category atoms from a uniquely-declared FreeResiduated in scope when no categories= argument is supplied.
  • QVR_PROGRAM_PROTOCOL extended with enum_set, free_residuated, schema_decl vertex kinds.
  • InsideAlgorithm accepts an optional unary morphism; the chart fills with reflexive-transitive unary-rule closure at each cell.
  • chart_fold(effect_depth>0) no longer raises CompileError; the parameter flows through as informational metadata and the caller-supplied binary morphism (typically built via lift_rule_set over declared effects) provides the lifted firings. handlers= are post-composed via _ChartHandlerComposite log-space transitions.
  • Denotational-semantics docs: corrected marginalization formulas (proper handling of residual input Y), Kleisli composition ordering in programs.md (s_1 ⋄ ⋯ ⋄ s_n ⋄ ret, not the reverse), scan formula typing in expressions.md, profunctor typing in grammar.md, the row-stochastic/column-stochastic distinction in morphisms.md, the arrow_monad ∘ kleisli ≅ id natural isomorphism in effects.md.

Fixed

  • FreeMonad carrier no longer collapses under CoproductSet auto-flattening when the leaf type is itself a coproduct; the flat-FinSet representation preserves the recursive leaf-vs-operation structure.
  • FreeMonad.lift_a2 is the correct free-monad applicative recursion (bi-depth (d_a, d_b, d_c) tracking with proper continuation re-encoding), replacing a prior block-identity rule that misrepresented op-summand handling.
  • FreeMonad.join splices outer trees correctly through _carrier_op_offset, replacing a prior block-identity that mapped op-summand indices without accounting for the differing inner/outer continuation cardinalities.
  • CoKleisli.first was type-incorrect (W(A) × C → B × C vs the required W(A × C) → B × C); now registered as Category_ only, with first_via_costrength for promotion to Arrow when a comonad costrength is supplied.
  • List.fmap_obj accepts non-FinSet inputs by re-encoding via cardinality; List(List(A)) now type-checks and the monad laws hold on its own image.
  • TypeCoproduct was already correctly handled at resolution.py:119; documented as such.
  • Bridge round-trip claim in effects.md §7 corrected from = to (Hughes 2000 proves natural isomorphism via the 1 ⊗ A ≅ A unitor, not equality).

Upstream

  • panproto/panproto#89 closed and shipped in panproto 0.47.0, first-class runtime grammar override via AstParserRegistry.override_grammar().
  • panproto/didactic#38 closed and shipped in didactic 0.7.0, tuple[Model, ...] field types accept any dx.Model element directly (the workaround tuple-of-TaggedUnion-roots is no longer needed).
  • panproto/didactic#39 closed and shipped in didactic 0.7.0, dx.field(opaque=True) for fields typed at typeclass ABCs (Monad, ArrowApply, etc.); opaque fields preserve in-process identity through with_ but drop to None on JSON round-trip.

Dependencies

  • panproto >= 0.47.0 (was >= 0.45.0).
  • panproto-grammars-all >= 0.47.0 (was >= 0.45.0).
  • didactic >= 0.7.1 (was >= 0.6.0).

[0.3.0] - 2026-04-12

This release provides effects integration: a typeclass + algebraic-effects substrate underneath the DSL, joint type-and-effect schema lifting in the chart parser, and a substantially expanded surface for categorial grammars driven by it.

Surface

  • Unified type-expression family. The categorial-pattern sublanguage (CatPattern, CatPatternSlash, CatPatternProduct) is folded into TypeExpr. Slash patterns (X/Y, X\Y) parse as TypeSlash; effect-typed applications (T(X), Cont_S(NP)) parse as TypeEffectApply. Rule and schema premises / conclusions are typed at TypeExpr uniformly.
  • schema declarations. Pattern-polymorphic morphism schemas with explicit parameter types and a unified domain / codomain shape: schema forward_app[X, Y : Cat] : (X/Y) * Y -> X. Arity is derived from the domain shape, a 2-component TypeProduct produces a binary chart-rule, otherwise unary.
  • EnumSet and FreeResiduated object initializers. object Atoms = {NP, S, VP} declares an EnumSet; object Cat = FreeResiduated(Atoms, depth=2, ops=[slash]) declares the residuated category universe over an EnumSet of generators, closed under the listed connectives up to a bounded nesting depth.
  • FreeMonoid object surface. object Strings = FreeMonoid(X, max_length=4) parses, walks, and compiles to a runtime FreeMonoid carrier.
  • chart_fold(...) primitive. chart_fold(lex=…, binary=…, unary=…, start=…, depth=…, effect_depth=…) exposes the inside algorithm as a first-class morphism expression. Accepts unary binaries via the reflexive-transitive closure of unary chart cells.
  • Residuation-witness combinators. .curry_right and .curry_left postfix methods realize the right / left residuation isomorphism on a binary morphism. Forward / backward application become theorems derivable from identity + curry once joint type-and-effect dispatch fires.
  • alias declarations. Object-shaped aliases (alias Pair = X * Y) bind a resolved SetObject in the compiler environment; residuated aliases (alias VP = S \ NP) are stored for syntactic substitution at schema-pattern use sites. Duplicate-declaration and shadowing diagnostics included.
  • bundle declarations. bundle CCG = [forward_app, backward_app, harmonic_composition] names a tuple of rule references that parser(rules=…) and chart_fold(binary=…) splice into the rule list. Bundles can reference other bundles; the expander detects cycles.
  • Doc comments. Lines starting with ## attach to the next declaration that carries a docs field (object, morphism, schema, program, alias, bundle). Plain # line comments continue to be dropped at parse time.

Categorial-effects substrate

  • Typeclass tower (quivers.monadic.typeclasses). Functor / Applicative / Monad / Alternative / MonadPlus / Foldable / Traversable / MonadTrans. Each ABC documents its laws; the runtime law-check scaffold lives in laws.py.
  • Arrow tower (quivers.arrows). Category_ / Arrow / ArrowChoice / ArrowApply / ArrowLoop / ArrowZero / ArrowPlus (Hughes 2000 §3). ArrowApply is bridged to Monad; ArrowLoop is the denotational target of chart_fold's loop.
  • Stdlib effect instances (quivers.monadic.instances). Identity, Maybe, Alternative_ (Hamblin powerset), Continuation(answer), State(state), Reader(env), Writer(monoid), List(max_length). Each is a dx.Model registered against its appropriate ABC via ABC.register(...), with concrete V-relation realizations of pure / fmap / apply / join / bind / lift_a2.
  • Monad transformers (quivers.monadic.transformers). StateT(state), ReaderT(env), MaybeT, ContT(answer), WriterT(monoid).
  • Algebraic effects + handlers (quivers.monadic.algebraic). Operation, EffectSignature, Handler, FreeMonad(signature) with a bounded-depth flat-FinSet carrier that preserves the recursive leaf-vs-operation structure. Handler.run is a post-order tree fold; a handler is a panproto theory morphism from sig.to_theory() into the target monad's theory.
  • Bridges (quivers.monadic.bridges). Kleisli(monad) wraps a Monad as Arrow / ArrowApply with concrete strength and app; ArrowMonad(arrow) wraps an ArrowApply as a Monad. The Joyal–Street–Verity iterative trace realizes loop_arr.

Chart parser

  • Class-directed lifts (quivers.stochastic.effect_lifts.class_directed_lifts). Given a base SchemaDecl and an effect, returns the tuple of lifted schemas keyed by the effect's typeclass interface: Applicativepure_T / apply_T; Monad → adds bind_T (Charlow's scope-extruding lift); Alternative → adds alt_T; MonadPlus is their union. Dispatch is on the typeclass, never the effect's identity, so adding a new effect or a new typeclass extends the lifting machinery automatically.
  • lift_rule_set(base, effects) applies the dispatch matrix pairwise across a rule-set and an effect-stack, returning the union of base + lifted schemas; consumed uniformly by the chart-fold runtime.
  • chart_fold end-to-end. The compiler path drops the previous effect_depth > 0 guard, composes handlers as log-space transitions on the parser output, and emits swap_TU schemas from registered DistributiveLaw instances.

Tooling

  • qvr check CLI (quivers.cli.{__init__,check}). Parse + constraint-solver + compile pipeline over a list of .qvr files; structured JSON output via --json; exit codes 0 / 1 / 2 for clean / error / usage. Registered as the qvr console script.
  • Constraint solver (quivers.dsl.constraints.check_constraints). Walks the parsed AST and reports residuated_constraint, effect_constraint, and bundle_unknown_member violations without invoking the full compiler. Surfaced via qvr check.
  • Pygments lexer (quivers.dsl.pygments_lexer). Driven by the in-tree tree-sitter parser via the _dev_grammar shim, so the highlighter always reflects the authoritative grammar.
  • Highlight queries (grammars/qvr/queries/highlights.scm) refreshed for schema_decl, type_slash, type_effect_apply, chart_fold, curry_right / curry_left, EnumSet / FreeResiduated / FreeMonoid, alias_decl, bundle_decl.
  • GitHub linguist (.gitattributes). .qvr classified as detectable; tree-sitter generated artefacts marked vendored / generated.

Examples + documentation

  • quantifier_scope.qvr: Charlow-style scope-taking grammar using the Continuation effect, exercising the new surface (EnumSet, FreeResiduated, schema, Cont_S(X)) end-to-end.
  • docs/guides/effects.md: typeclass + algebraic-effects framework: monad and arrow towers, stdlib effect instances, class-driven schema lifting, joint type-and-effect dispatch, bridges between the two towers.
  • docs/semantics/effects.md: denotational layer: panproto- theory mirrors of each typeclass, effect-typed schema denotations as natural transformations, joint type-and-effect chart dispatch, lifting-adequacy theorem, conservativity over the bare-grammar fragment.
  • docs/getting-started/architecture.md: quivers/arrows/ section added; quivers/monadic/ rewritten to cover the typeclass spine, transformers, algebraic effects, bridges, theories, laws.
  • mkdocstrings stubs for every new public module: quivers.monadic.{typeclasses,instances,transformers,algebraic,bridges,theories,laws}, quivers.arrows.{typeclasses,theories}, quivers.stochastic.effect_lifts.

Internal

  • Program theory (quivers.dsl.program_theory). Vertex kinds extended with enum_set, free_residuated, schema_decl. write_set_object branches for EnumSet (name + element constraints) and FreeResiduated (depth + op constraints + generators edge to an enum_set).
  • Local-grammar override (quivers.dsl._dev_grammar). Adopts panproto 0.47's first-class AstParserRegistry.override_grammar API, replacing the previous ctypes-pinned-buffer shim. Activated by QVR_USE_LOCAL_GRAMMAR=1 until panproto-grammars-all vendors the new QVR grammar.
  • quivers.core._factories: the concrete morphism alphabet (coproduct injection / case eliminator, product projection / pairing, parallel pair, distributivity, terminal, constant, coproduct functorial action) on which the typeclass realizations are built.
  • Legacy Monad ABC removed. quivers.monadic.monads no longer ships a parallel Monad; FuzzyPowersetMonad, FreeMonoidMonad, and GiryMonad subclass the typeclass Monad directly. The Eilenberg–Moore aliases (unit, multiply, kleisli_compose) are kept as convenience methods. FreeMonoidMonad.join now does the full word-concatenation tensor with truncation beyond max_length.

Dependencies

  • panproto >= 0.47.0 (was >= 0.45.0).
  • panproto-grammars-all >= 0.47.0 (was >= 0.45.0).
  • didactic >= 0.7.0 (was >= 0.6.0).

Tests

  • 986 tests pass under QVR_USE_LOCAL_GRAMMAR=1. New surface coverage: 32 cases in tests/test_dsl_extensions.py (aliases, bundles, doc comments, FreeMonoid surface, constraint solver, qvr check CLI, highlight-query parity, Pygments lexer round-trips, grammar-shape parity across every example).
  • Monad unit laws hold on Identity, Maybe, Alternative_, State, Reader, Writer; FreeMonad left / right unit laws hold up to truncation; trace yanking and identity verified on VRel.

Key citations

[0.2.0] - 2026-05-06

Changed

  • Every record-shaped value type (AST nodes, FinSet, ProductSet, CoproductSet, ContinuousSpace variants, Category variants, RuleSystem) is now a didactic.api.Model. Recursive sums are dx.TaggedUnion roots discriminated by a kind: Literal[...] field. JSON round-trips via model_dump_json / model_validate_json are available on every value type.
  • Resolution from TypeExpr / SpaceExpr AST trees to runtime SetObject / ContinuousSpace values is expressed as a dx.Lens family in quivers.dsl.resolution.
  • Variadic constructors ProductSet(A, B, C) and CoproductSet(A, B, C) are replaced by keyword form ProductSet(components=(A, B, C)) and CoproductSet(components=(A, B, C)). The flattening converter preserves the previous flattening behavior.
  • Continuous spaces (Euclidean, Simplex, PositiveReals, ProductSpace) expose public name and dim fields (no longer private with property accessors).
  • Minimum supported Python is now 3.14.

Added

  • A tree-sitter grammar for the QVR DSL at grammars/qvr/, registered with panproto's panproto-grammars-all distribution.
  • quivers.dsl.parser delegates parsing to panproto via the qvr tree-sitter grammar.
  • quivers.dsl.program_theory defines QVR_PROGRAM_PROTOCOL and extract_program_schema, lifting every compiled program to a panproto Schema for use with panproto schema diff, panproto lens generate, and related tooling.
  • A Denotational Semantics documentation section giving a formal, compositional semantics for the DSL across the discrete, stochastic, and continuous strata, plus an adequacy theorem connecting the compiler implementation to the denotation.
  • RuleSystem carries cross-field axioms (__axioms__) ensuring binary_weights/unary_weights lengths match binary_rules/unary_rules when supplied.
  • .github/workflows/release.yml builds an sdist + wheel on tag push and publishes to PyPI via the FACTSlab/quivers OIDC trusted publisher.
  • Pull request template under .github/PULL_REQUEST_TEMPLATE.md and issue templates under .github/ISSUE_TEMPLATE/.

Removed

  • quivers.dsl.lexer and quivers.dsl.tokens: the hand-written lexer is replaced by panproto's tree-sitter–integrated lexing.
  • LexError: lexical errors now surface as ParseError.

[0.1.0] - 2026-03-26

Added

Core Categorical Algebra

  • Fundamental category types and morphisms
  • Object declarations and morphism composition
  • Support for latent and observed morphisms
  • Basic categorical operations and abstractions

Stochastic Morphisms

  • Stochastic morphism declarations and semantics
  • Integration with probability theory
  • Support for morphism composition in stochastic settings

Continuous Distributions (30+ Families)

  • Normal distribution and variants (LogitNormal, TruncatedNormal)
  • Beta, Dirichlet for probability simplices
  • Exponential family: Exponential, Gamma, Chi2
  • Heavy-tailed: Cauchy, StudentT, Pareto
  • Bounded: Uniform, Kumaraswamy
  • Half-variants: HalfCauchy, HalfNormal
  • Transformed: LogNormal, Gumbel, Laplace, Weibull
  • Multivariate: MultivariateNormal, LowRankMVN, Wishart
  • Bernoulli variants: Bernoulli, ContinuousBernoulli, RelaxedBernoulli
  • Advanced: RelaxedOneHotCategorical, FisherSnedecor
  • Normalized flows: Flow
  • Categorical and discrete approximations

Monadic Programs

  • Draw statements for sampling from morphisms
  • Observe statements for conditioning and likelihood
  • Return statements with optional labeled outputs
  • Variable binding and destructuring in patterns
  • Program parameters and composition

QVR DSL

  • Complete lexer with token recognition for all language constructs
  • Recursive descent parser with full grammar support
  • Abstract syntax tree (AST) node definitions
  • Program block execution with proper scoping
  • Let bindings for expression computation
  • Built-in let functions: sigmoid, exp, log, abs, softplus
  • Comment support (#)
  • Type expressions: products (*), coproducts (+)
  • Expression operators: composition (>>), tensor product (@), marginalization
  • Indentation-aware program body parsing
  • Specialized handling for draw/observe arguments

Variational Inference Layer

  • Inference interface for probabilistic programs
  • Support for approximate posterior computation
  • Integration with continuous distribution families