Quickstart¶
This guide walks through the core concepts of quivers with concrete examples: creating objects, building morphisms, composing them, and running them as differentiable programs.
1. Basic Morphisms¶
A morphism is a \(\mathcal{V}\)-enriched relation: a function from a pair of objects to an algebra (lattice of truth values). In quivers, morphisms are tensors.
Create finite sets:
from quivers import FinSet, morphism, observed
import torch
X = FinSet(name="X", cardinality=3)
Y = FinSet(name="Y", cardinality=4)
Create a latent (learnable) morphism from X to Y. Its tensor entries are parameters:
f = morphism(X, Y)
print(f.tensor.shape) # torch.Size([3, 4])
print(f.domain.name, "->", f.codomain.name) # X -> Y
Create an observed (fixed) morphism with a fixed tensor:
data = torch.tensor([
[1.0, 0.5, 0.0, 0.2],
[0.0, 1.0, 0.8, 0.1],
[0.3, 0.2, 1.0, 0.9],
])
g = observed(X, Y, data)
print(g.tensor) # your data tensor
Get the underlying nn.Module for use in training:
mod = f.module()
params = list(mod.parameters())
print(len(params)) # 1 parameter matrix
2. Composition¶
Compose morphisms with the >> operator. The domain of the second must match the codomain of the first:
Z = FinSet(name="Z", cardinality=2)
h = morphism(Y, Z)
# Compose f: X -> Y and h: Y -> Z to get X -> Z
composed = f >> h
print(composed.tensor.shape) # torch.Size([3, 2])
Composition is lazy: it builds a computation graph. The final tensor is only materialized on evaluation.
3. Programs¶
Wrap a morphism as a differentiable nn.Module with Program:
from quivers import FinSet, morphism, Program
X = FinSet(name="X", cardinality=3)
Y = FinSet(name="Y", cardinality=4)
Z = FinSet(name="Z", cardinality=2)
composed = morphism(X, Y) >> morphism(Y, Z)
program = Program(composed)
output = program()
print(output.shape) # torch.Size([3, 2])
Integrate with a training loop:
import torch.optim as optim
optimizer = optim.Adam(program.parameters(), lr=0.01)
for epoch in range(10):
optimizer.zero_grad()
output = program()
# Define a loss and backpropagate
loss = output.sum()
loss.backward()
optimizer.step()
4. Monadic Programs (Continuous)¶
For probabilistic computations with continuous and discrete variables, use monadic programs. A program is a tuple (domain, codomain, steps, return_vars) where each step binds a sample, observation, or let-expression into a named variable; the final return_vars names the variables returned in the program's output tuple.
from quivers import FinSet
from quivers.continuous.spaces import Euclidean
from quivers.continuous.families import ConditionalNormal
from quivers.continuous.programs import MonadicProgram
import torch
# Define spaces.
X = FinSet(name="context", cardinality=3)
R = Euclidean(name="response", dim=2)
# Conditional normal family: each input x ∈ X yields a 2D normal on R.
family = ConditionalNormal(X, R, hidden_dim=16)
# A one-step program: bind y ∼ family(x), return y.
program = MonadicProgram(
domain=X,
codomain=R,
steps=[(("y",), family, None)],
return_vars=("y",),
)
x = torch.tensor([0, 1, 2])
output = program.rsample(x) # reparameterized samples, shape (3, 2)
5. The DSL¶
Write categorical programs declaratively in .qvr files:
composition product_fuzzy as algebra
object X : FinSet 3
object Y : FinSet 4
object Z : FinSet 2
morphism f : X -> Y [role=latent]
morphism g : Y -> Z [role=latent]
let composed = f >> g
export composed
Load and run:
from quivers.dsl import load
prog = load("docs/examples/source/bayesian_regression.qvr")
Or use loads for inline strings:
from quivers.dsl import loads
source = """
composition product_fuzzy as algebra
object X : FinSet 3
object Y : FinSet 4
object Z : FinSet 2
morphism f : X -> Y [role=latent]
morphism g : Y -> Z [role=latent]
let composed = f >> g
export composed
"""
prog = loads(source)
output = prog()
print(output.shape) # torch.Size([3, 2])
Supported DSL operators:
| Operator | Meaning | Example |
|---|---|---|
>> |
composition | f >> g |
>>> |
transformation composition | softmax(B) >>> expectation |
@ |
tensor product | f @ g |
.marginalize(X) |
marginalize over object | f.marginalize(X) |
.change_base(t) |
change of base under transformation t |
f.change_base(softmax(B)) |
identity(X) |
identity morphism | morphism id : X -> X [role=observed] ~ identity(X) |
The DSL also has surface for monadic probabilistic programs (program ... [effects = [Sample, Score]]), composition rules at four algebraic levels via composition NAME as <algebra | semigroupoid | bilinear_form | rule>, and operadic contractions (contraction op : ... [rule = R, wiring = "..."]). The QVR tutorial walks through the full API.
6. Stochastic Morphisms¶
The FinStoch category models Markov kernels on finite sets:
from quivers import (
stochastic, FinSet, DiscretizedNormal,
condition, ConditionedMorphism
)
import torch
X = FinSet(name="X", cardinality=3)
Y = FinSet(name="Y", cardinality=4)
# Create a stochastic morphism (Markov kernel)
kern = stochastic(X, Y)
print(kern.tensor.shape) # [3, 4], rows sum to 1
print(kern.tensor.sum(dim=-1)) # all 1.0
# Condition on an observation
obs_data = torch.tensor([0.1, 0.8, 0.05, 0.05]) # P(Y)
conditioned = condition(kern, obs_data)
print(conditioned.tensor.shape) # [3, 4]
7. Enriched Structures¶
Work with more abstract categorical structures:
from quivers import (
FuzzyPowersetMonad, KleisliCategory, FinSet, morphism,
)
# Create a monad
monad = FuzzyPowersetMonad()
# Work in its Kleisli category
kleisli = KleisliCategory(monad)
X = FinSet(name="X", cardinality=3)
Y = FinSet(name="Y", cardinality=4)
f = morphism(X, Y)
g = morphism(Y, X)
# Kleisli composition
comp = kleisli.compose(f, g)
print(comp.tensor.shape) # [3, 3]
8. Interactive exploration¶
pip install 'quivers[repl,lsp]' adds two interactive surfaces over
the same elaborator the library uses internally.
The REPL¶
qvr repl docs/examples/source/seq2seq.qvr
A four-pane Textual TUI: input editor (top-left), output log (mid-left),
environment browser (right, click any leaf to :info it), watches and
diagnostics strips at the bottom (auto-hidden when empty), plus a
status bar showing the loaded file, active algebra, and binding
counts.
GHCi-shaped meta-commands:
qvr> :type seq2seq
morphism seq2seq : Source * Target -> Target [role=latent]
qvr> :info backbone
let backbone = (encoder @ decoder) >> cross
-- declared at docs/examples/source/seq2seq.qvr:59:0
qvr> :watch backbone # pinned re-eval after every recompile
qvr> :save my_edits.qvr # write the live module to disk
Ctrl-G (or Ctrl-O, F8) evaluates; Tab cycles completions;
Ctrl-P opens a fuzzy command palette; the loaded file auto-reloads
on save. The full reference is in
Interactive surface.
The language server¶
pip install 'quivers[lsp]' # provides `qvr-lsp` on your PATH
The
vscode-qvr
and
zed-extension-qvr
extensions auto-discover it. Capabilities: hover (QVR declaration plus
collapsible AST), go-to-definition, references, document symbols,
semantic highlighting, completion, formatting, live diagnostics. The
same STYLE_TABLE drives the REPL and the LSP so the colours never
disagree.
The Jupyter kernel¶
qvr-kernel install --user
jupyter console --kernel quivers
Cell semantics match the REPL: leading-: lines run meta-commands,
other lines append statements to the live module.
Next Steps¶
- Interactive surface: REPL, language server, and Jupyter kernel reference.
- Architecture: learn the package structure and design principles.
- API Reference: detailed documentation of all classes and functions.
- Guides: core types, morphisms, categorical structures, stochastic and continuous morphisms, the DSL, and variational inference.
- Tutorials: end-to-end examples including probabilistic programs and inference.