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 a quantale (lattice of truth values). In quivers, morphisms are tensors.

Create finite sets:

from quivers import FinSet, morphism, observed
import torch

X = FinSet("X", 3)
Y = FinSet("Y", 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, f.codomain)  # 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("Z", 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 Program

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:

from quivers import (
    Euclidean, ConditionalNormal, MonadicProgram
)
import torch

# Define spaces
X = FinSet("X", 3)
R = Euclidean("position", 2)

# Create a conditional distribution: X -> Normal on R
mu_net = torch.nn.Linear(3, 2)
sigma_net = torch.nn.Sequential(
    torch.nn.Linear(3, 2),
    torch.nn.Softplus()
)

family = ConditionalNormal(mu_net, sigma_net)

# Create a monadic program (probabilistic computation)
prog = MonadicProgram(family)

# Sample from the program
x = torch.randn(1, 3)  # batch of inputs
sample = prog.rsample(x)  # reparameterized sample
log_p = prog.log_prob(x, sample)  # log probability

5. The DSL

Write categorical programs declaratively in .qvr files:

object X : 3
object Y : 4
object Z : 2

latent f : X -> Y
latent g : Y -> Z
output f >> g

Load and run:

from quivers import dsl_load, Program

prog = dsl_load("program.qvr")
output = prog()

Or use loads for inline strings:

from quivers import dsl_loads

source = """
object X : 3
object Y : 4
object Z : 2
latent f : X -> Y
latent g : Y -> Z
output f >> g
"""

prog = dsl_loads(source)
output = prog()
print(output.shape)  # torch.Size([3, 2])

Supported DSL operators:

Operator Meaning Example
>> composition f >> g
@ tensor product f @ g
.marginalize(X) marginalize over object f.marginalize(X)
identity(X) identity morphism observed id : X -> X = identity(X)

6. Stochastic Morphisms

The FinStoch category models Markov kernels on finite sets:

from quivers import (
    stochastic, FinSet, DiscretizedNormal,
    condition, ConditionedMorphism
)
import torch

X = FinSet("X", 3)
Y = FinSet("Y", 4)

# Create a stochastic morphism (Markov kernel)
kern = stochastic(X, Y)
print(kern.tensor.shape)  # [3, 4], columns 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
)

# Create a monad
monad = FuzzyPowersetMonad()

# Work in its Kleisli category
kleisli = KleisliCategory(monad)

X = FinSet("X", 3)
Y = FinSet("Y", 4)

f = morphism(X, Y)
g = morphism(Y, X)

# Kleisli composition
comp = kleisli.compose(f, g)
print(comp.tensor.shape)  # [3, 3]

Next Steps