Algebras

Ordered algebraic structures for morphism composition and enriched category theory.

algebras

Algebras: enrichment algebras for V-enriched categories.

A commutative algebra Q = (L, ⊗, ⋁, ⋀, ¬, I, ⊥) provides the algebraic structure that parameterizes composition in a V-enriched category:

(g ∘ f)(a, c) = ⋁_b f(a, b) ⊗ g(b, c)

Different algebras yield different categories of relations:

- BooleanAlgebra:  {0,1} with ∧, ∨         → Rel (crisp relations)
- ProductFuzzyAlgebra:     [0,1] with ×, noisy-OR   → FuzzyRel (product t-norm)

The enrichment determines composition, identity, marginalization, and quantification, all derived from the algebra's operations.

CompositionRule

Bases: ABC

An associative-or-loose binary tensor contraction.

Defines the V-enriched composition kernel (f >> g)[i, k] = ⋁_j f[i, j] ⊗ g[j, k] from the two primitive operations tensor_op (binary ⊗) and join (reduction ⋁). No identity element is required at this level.

The hierarchy below this class is:

  • CompositionRule — the bare composition surface.
  • Semigroupoid — adds the assumption that ⊗ is associative, so composition forms a semigroupoid (a category without identities).
  • Algebra — adds identity (unit / zero), a meet ⋀, a negation, and the full algebra-axiom package. This is the level at which compact-closed operations (identity, cup, cap, dagger, trace) become well-defined.

Operations that need identity check at runtime that the composition rule is at least a Algebra; a clear error is raised if a non-algebra rule is fed in.

name abstractmethod property

name: str

Human-readable name for this composition rule.

tensor_op abstractmethod

tensor_op(a: Tensor, b: Tensor) -> Tensor

Monoidal product ⊗ (elementwise).

Source code in src/quivers/core/algebras.py
59
60
61
62
@abstractmethod
def tensor_op(self, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
    """Monoidal product ⊗ (elementwise)."""
    ...

join abstractmethod

join(t: Tensor, dim: int | tuple[int, ...]) -> Tensor

Join ⋁ — reduction for composition.

Source code in src/quivers/core/algebras.py
64
65
66
67
@abstractmethod
def join(self, t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
    """Join ⋁ — reduction for composition."""
    ...

compose

compose(m: Tensor, n: Tensor, n_contract: int) -> Tensor

V-enriched composition.

Computes: result[d..., c...] = ⋁_{s...} m[d..., s...] ⊗ n[s..., c...]

Source code in src/quivers/core/algebras.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def compose(
    self,
    m: torch.Tensor,
    n: torch.Tensor,
    n_contract: int,
) -> torch.Tensor:
    """V-enriched composition.

    Computes: result[d..., c...] = ⋁_{s...} m[d..., s...] ⊗ n[s..., c...]
    """
    if n_contract < 1:
        raise ValueError(f"n_contract must be >= 1, got {n_contract}")
    shared_m = m.shape[-n_contract:]
    shared_n = n.shape[:n_contract]
    if shared_m != shared_n:
        raise ValueError(
            f"shared dimensions do not match: "
            f"m trailing {shared_m} != n leading {shared_n}"
        )
    n_domain = m.ndim - n_contract
    n_codomain = n.ndim - n_contract
    m_expanded = m.reshape(*m.shape, *([1] * n_codomain))
    n_expanded = n.reshape(*([1] * n_domain), *n.shape)
    product = self.tensor_op(m_expanded, n_expanded)
    contract_dims = tuple(range(n_domain, n_domain + n_contract))
    return self.join(product, dim=contract_dims)

is_compatible

is_compatible(other: CompositionRule) -> bool

Two composition rules compose if they're the same type or carry the same name (the latter catches custom-built instances of the same rule constructed independently).

Source code in src/quivers/core/algebras.py
 96
 97
 98
 99
100
101
102
103
def is_compatible(self, other: CompositionRule) -> bool:
    """Two composition rules compose if they're the same type
    or carry the same name (the latter catches custom-built
    instances of the same rule constructed independently).
    """
    if type(self) is type(other):
        return True
    return getattr(self, "name", None) == getattr(other, "name", None)

Semigroupoid

Bases: CompositionRule

A composition rule whose tensor_op is associative.

Semantically a CompositionRule with the marker promise of associativity. No identity, no compact-closed structure, no negation — those need Algebra.

Material implication composition (a ⊗ b = 1 - a + a*b, ⋁ = product) is the canonical example: associative under its ⊗, but no tensor satisfies f >> id == f for all f.

BilinearForm

Bases: CompositionRule

A composition rule with no associativity guarantee.

(f >> g) is still well-defined as a binary tensor contraction, but (f >> g) >> h may differ from f >> (g >> h). Callers fix an association order explicitly — the type system records that the operation isn't associative so downstream optimizations can't reorder composition chains.

Examples include signed-dot-product compositions (sign flipping breaks associativity), top-k truncating compositions (early truncation isn't commutative with later contractions), and attention-style softmax-then-multiply rules.

Sibling of Semigroupoid under CompositionRule: BilinearForm opts out of the associativity promise that Semigroupoid carries; a rule that's actually associative should be declared as Semigroupoid instead.

Algebra

Bases: Semigroupoid

Abstract commutative algebra for V-enriched categories.

Subclasses must implement the six primitive operations. Composition and identity are derived but overridable.

name abstractmethod property

name: str

Human-readable name for this algebra.

unit abstractmethod property

unit: float

Unit element I of the monoidal product ⊗.

zero abstractmethod property

zero: float

Bottom element ⊥ (identity for ⋁).

tensor_op abstractmethod

tensor_op(a: Tensor, b: Tensor) -> Tensor

Monoidal product ⊗ (elementwise).

PARAMETER DESCRIPTION
a

Left operand.

TYPE: Tensor

b

Right operand (broadcastable with a).

TYPE: Tensor

RETURNS DESCRIPTION
Tensor

a ⊗ b, elementwise.

Source code in src/quivers/core/algebras.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@abstractmethod
def tensor_op(self, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
    """Monoidal product ⊗ (elementwise).

    Parameters
    ----------
    a : torch.Tensor
        Left operand.
    b : torch.Tensor
        Right operand (broadcastable with a).

    Returns
    -------
    torch.Tensor
        a ⊗ b, elementwise.
    """
    ...

join abstractmethod

join(t: Tensor, dim: int | tuple[int, ...]) -> Tensor

Join ⋁ — reduction for composition and existential (∃).

PARAMETER DESCRIPTION
t

Input tensor with values in L.

TYPE: Tensor

dim

Dimension(s) to reduce.

TYPE: int or tuple[int, ...]

RETURNS DESCRIPTION
Tensor

Reduced tensor.

Source code in src/quivers/core/algebras.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
@abstractmethod
def join(self, t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
    """Join ⋁ — reduction for composition and existential (∃).

    Parameters
    ----------
    t : torch.Tensor
        Input tensor with values in L.
    dim : int or tuple[int, ...]
        Dimension(s) to reduce.

    Returns
    -------
    torch.Tensor
        Reduced tensor.
    """
    ...

meet abstractmethod

meet(t: Tensor, dim: int | tuple[int, ...]) -> Tensor

Meet ⋀ — reduction for universal quantification (∀).

PARAMETER DESCRIPTION
t

Input tensor with values in L.

TYPE: Tensor

dim

Dimension(s) to reduce.

TYPE: int or tuple[int, ...]

RETURNS DESCRIPTION
Tensor

Reduced tensor.

Source code in src/quivers/core/algebras.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
@abstractmethod
def meet(self, t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
    """Meet ⋀ — reduction for universal quantification (∀).

    Parameters
    ----------
    t : torch.Tensor
        Input tensor with values in L.
    dim : int or tuple[int, ...]
        Dimension(s) to reduce.

    Returns
    -------
    torch.Tensor
        Reduced tensor.
    """
    ...

negate abstractmethod

negate(t: Tensor) -> Tensor

Complement / negation ¬.

PARAMETER DESCRIPTION
t

Input tensor.

TYPE: Tensor

RETURNS DESCRIPTION
Tensor

¬t, elementwise.

Source code in src/quivers/core/algebras.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@abstractmethod
def negate(self, t: torch.Tensor) -> torch.Tensor:
    """Complement / negation ¬.

    Parameters
    ----------
    t : torch.Tensor
        Input tensor.

    Returns
    -------
    torch.Tensor
        ¬t, elementwise.
    """
    ...

compose

compose(m: Tensor, n: Tensor, n_contract: int) -> Tensor

V-enriched composition.

Computes: result[d..., c...] = ⋁_{s...} m[d..., s...] ⊗ n[s..., c...]

Override for numerical stability in specific algebras.

PARAMETER DESCRIPTION
m

Left tensor of shape (domain, shared).

TYPE: Tensor

n

Right tensor of shape (shared, codomain).

TYPE: Tensor

n_contract

Number of shared dimensions to contract.

TYPE: int

RETURNS DESCRIPTION
Tensor

Composed tensor of shape (domain, codomain).

Source code in src/quivers/core/algebras.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def compose(
    self,
    m: torch.Tensor,
    n: torch.Tensor,
    n_contract: int,
) -> torch.Tensor:
    """V-enriched composition.

    Computes: result[d..., c...] = ⋁_{s...} m[d..., s...] ⊗ n[s..., c...]

    Override for numerical stability in specific algebras.

    Parameters
    ----------
    m : torch.Tensor
        Left tensor of shape (*domain, *shared).
    n : torch.Tensor
        Right tensor of shape (*shared, *codomain).
    n_contract : int
        Number of shared dimensions to contract.

    Returns
    -------
    torch.Tensor
        Composed tensor of shape (*domain, *codomain).
    """
    if n_contract < 1:
        raise ValueError(f"n_contract must be >= 1, got {n_contract}")

    # validate shared dimensions
    shared_m = m.shape[-n_contract:]
    shared_n = n.shape[:n_contract]

    if shared_m != shared_n:
        raise ValueError(
            f"shared dimensions do not match: "
            f"m trailing {shared_m} != n leading {shared_n}"
        )

    n_domain = m.ndim - n_contract
    n_codomain = n.ndim - n_contract

    # broadcast for element-wise tensor_op
    m_expanded = m.reshape(*m.shape, *([1] * n_codomain))
    n_expanded = n.reshape(*([1] * n_domain), *n.shape)

    product = self.tensor_op(m_expanded, n_expanded)

    # join over shared dims
    contract_dims = tuple(range(n_domain, n_domain + n_contract))
    return self.join(product, dim=contract_dims)

identity_tensor

identity_tensor(obj_shape: tuple[int, ...]) -> Tensor

Identity morphism tensor for an object with given shape.

Returns a tensor of shape (obj_shape, obj_shape) with the unit value on the diagonal and zero elsewhere.

PARAMETER DESCRIPTION
obj_shape

Shape of the object (e.g., (n,) for FinSet(n)).

TYPE: tuple[int, ...]

RETURNS DESCRIPTION
Tensor

Identity tensor.

Source code in src/quivers/core/algebras.py
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
def identity_tensor(self, obj_shape: tuple[int, ...]) -> torch.Tensor:
    """Identity morphism tensor for an object with given shape.

    Returns a tensor of shape (*obj_shape, *obj_shape) with
    the unit value on the diagonal and zero elsewhere.

    Parameters
    ----------
    obj_shape : tuple[int, ...]
        Shape of the object (e.g., (n,) for FinSet(n)).

    Returns
    -------
    torch.Tensor
        Identity tensor.
    """
    full_shape = obj_shape + obj_shape
    result = torch.full(full_shape, self.zero)
    ndim = len(obj_shape)

    if ndim == 1:
        # simple case: (n, n) matrix
        n = obj_shape[0]

        for i in range(n):
            result[i, i] = self.unit

    else:
        # multi-dimensional: iterate over all index tuples
        for idx in itertools.product(*(range(s) for s in obj_shape)):
            result[idx + idx] = self.unit

    return result

is_compatible

is_compatible(other: Algebra) -> bool

Check if two algebras are compatible for composition.

PARAMETER DESCRIPTION
other

The other algebra.

TYPE: Algebra

RETURNS DESCRIPTION
bool

True if morphisms from these algebras can compose.

Source code in src/quivers/core/algebras.py
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
def is_compatible(self, other: Algebra) -> bool:
    """Check if two algebras are compatible for composition.

    Parameters
    ----------
    other : Algebra
        The other algebra.

    Returns
    -------
    bool
        True if morphisms from these algebras can compose.
    """
    if type(self) is type(other):
        return True
    # Two ``DualAlgebra`` instances over the same base are
    # compatible; ditto for any custom algebra that overrides
    # ``name`` to match.
    return getattr(self, "name", None) == getattr(other, "name", None)

dual

dual() -> Algebra

The dual algebra under de Morgan negation.

For a commutative algebra with involution N (negate), the dual carries

tensor_op^op(a, b) = N(N(a) ⋁ N(b))
join^op(a_i)       = N(⋀_i N(a_i)) = N(⊗_i N(a_i))   (finite I)

Unit and zero swap:

1^op = 0,    0^op = 1.

For ProductFuzzyAlgebra this yields the role-swapped pair (⊗ = noisy-OR, ⋁ = product reduction), which is the canonical Reichenbach-flavour probabilistic-implication composition rule.

The default implementation requires negate to be a true involution; subclasses with non-involutive lattices should override.

Source code in src/quivers/core/algebras.py
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
def dual(self) -> Algebra:
    """The dual algebra under de Morgan negation.

    For a commutative algebra with involution ``N`` (``negate``),
    the dual carries

        tensor_op^op(a, b) = N(N(a) ⋁ N(b))
        join^op(a_i)       = N(⋀_i N(a_i)) = N(⊗_i N(a_i))   (finite I)

    Unit and zero swap:

        1^op = 0,    0^op = 1.

    For ProductFuzzyAlgebra this yields the role-swapped pair
    ``(⊗ = noisy-OR, ⋁ = product reduction)``, which is the
    canonical Reichenbach-flavour probabilistic-implication
    composition rule.

    The default implementation requires `negate` to be a
    true involution; subclasses with non-involutive lattices
    should override.
    """
    return DualAlgebra(self)

DualAlgebra

DualAlgebra(base: Algebra)

Bases: Algebra

The de-Morgan dual of an involutive commutative algebra.

For a t-norm / t-conorm pair (T, S) related by the strong negation N (S(a, b) = N(T(N(a), N(b)))), the dual algebra carries the role-swapped pair:

tensor_op^op = base.join         (reducing as a binary op)
join^op      = base.tensor_op    (reducing as a fold)
meet^op      = base.join         (along whatever axis)
unit^op      = base.zero
zero^op      = base.unit
negate^op    = base.negate       (involution self-dualizes)

Concretely for shipped pairs:

  • ProductFuzzyAlgebra.dual: ⊗ = noisy-OR (a + b - ab), ⋁ = product (∏ a_i).
  • Lukasiewicz.dual: ⊗ = bounded sum (min(1, a + b)), ⋁ = bounded difference / repeated Łukasiewicz t-norm.
  • Godel.dual: ⊗ = max, ⋁ = min.
  • Boolean.dual: ⊗ = OR, ⋁ = AND.

Returned by Algebra.dual. Subclasses with non-involutive negation (CountingAlgebra, …) should override Algebra.dual to raise rather than allow dual construction that breaks the de-Morgan equations.

Source code in src/quivers/core/algebras.py
411
412
413
def __init__(self, base: Algebra) -> None:
    self._base = base
    self._name = f"Dual({base.name})"

base property

base: Algebra

The underlying algebra this is the dual of.

dual

dual() -> Algebra

Dual of dual is the base (involution).

Source code in src/quivers/core/algebras.py
451
452
453
def dual(self) -> Algebra:
    """Dual of dual is the base (involution)."""
    return self._base

CustomAlgebra

CustomAlgebra(name: str, tensor_op: Callable[[Tensor, Tensor], Tensor], join: Callable[[Tensor, int | tuple[int, ...]], Tensor], unit: float, zero: float, negate: Callable[[Tensor], Tensor] | None = None, meet: Callable[[Tensor, int | tuple[int, ...]], Tensor] | None = None, verify: bool = True)

Bases: Algebra

User-defined algebra built from callable operations.

Construct a fresh algebra by supplying the primitive operations as Python functions, rather than subclassing Algebra for each variant.

The constructor only stores the operations; the user is responsible for ensuring they satisfy the algebra axioms (associativity, identity, distributivity of ⊗ over ⋁, de-Morgan duality between ⊗ and ⋁ via negate for involutive lattices). Basic structural axioms are sanity-checked at construction time against a handful of fixed sample inputs; serious deployments should write their own targeted unit tests.

The DSL surface algebra name { tensor_op(a, b) = …; join(t) = …; unit = …; zero = …; } compiles to this class under the hood, with verify=False (user expressions are arithmetic and routinely violate one of the canned samples used by _sanity_check).

Source code in src/quivers/core/algebras.py
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
def __init__(
    self,
    name: str,
    tensor_op: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    join: Callable[[torch.Tensor, int | tuple[int, ...]], torch.Tensor],
    unit: float,
    zero: float,
    negate: Callable[[torch.Tensor], torch.Tensor] | None = None,
    meet: (
        Callable[[torch.Tensor, int | tuple[int, ...]], torch.Tensor] | None
    ) = None,
    verify: bool = True,
) -> None:
    if not name:
        raise ValueError("CustomAlgebra: name must be non-empty")
    self._name = str(name)
    self._tensor_op = tensor_op
    self._join = join
    self._unit = float(unit)
    self._zero = float(zero)
    self._negate = negate
    self._meet = meet
    if verify:
        self._sanity_check()

CustomSemigroupoid

CustomSemigroupoid(name: str, tensor_op: Callable[[Tensor, Tensor], Tensor], join: Callable[[Tensor, int | tuple[int, ...]], Tensor], verify_associative: bool = True)

Bases: Semigroupoid

User-defined Semigroupoid built from callable operations.

Use for composition rules that are associative under their tensor_op but lack an identity element — Reichenbach-style material implication composition, weighted shortest-path on a non-pointed lattice, etc. Callers who want a full algebra (with identity, dagger, compact-closed structure) should use CustomAlgebra instead.

PARAMETER DESCRIPTION
name

Human-readable name.

TYPE: str

tensor_op

Binary monoidal product.

TYPE: Callable

join

Reduction along an axis.

TYPE: Callable

verify_associative

When True (default) checks tensor_op is associative on a fixed pseudo-random sample at construction time. Set False to skip the smoke test.

TYPE: bool DEFAULT: True

Source code in src/quivers/core/algebras.py
644
645
646
647
648
649
650
651
652
653
654
655
656
657
def __init__(
    self,
    name: str,
    tensor_op: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    join: Callable[[torch.Tensor, int | tuple[int, ...]], torch.Tensor],
    verify_associative: bool = True,
) -> None:
    if not name:
        raise ValueError("CustomSemigroupoid: name must be non-empty")
    self._name = str(name)
    self._tensor_op = tensor_op
    self._join = join
    if verify_associative:
        self._check_associativity()

CustomBilinearForm

CustomBilinearForm(name: str, tensor_op: Callable[[Tensor, Tensor], Tensor], join: Callable[[Tensor, int | tuple[int, ...]], Tensor])

Bases: BilinearForm

User-defined BilinearForm built from callable operations.

Use for composition rules whose tensor_op is not associative — for example signed-dot-product or top-k truncating rules. Callers must pin an association order explicitly when chaining; the runtime doesn't promise that (f >> g) >> h == f >> (g >> h).

No associativity smoke test runs (the construction is honest about non-associativity); a non-associative op would just fail the check anyway.

PARAMETER DESCRIPTION
name

Human-readable name.

TYPE: str

tensor_op

Binary product (need not be associative).

TYPE: Callable

join

Reduction along an axis.

TYPE: Callable

Source code in src/quivers/core/algebras.py
730
731
732
733
734
735
736
737
738
739
740
def __init__(
    self,
    name: str,
    tensor_op: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    join: Callable[[torch.Tensor, int | tuple[int, ...]], torch.Tensor],
) -> None:
    if not name:
        raise ValueError("CustomBilinearForm: name must be non-empty")
    self._name = str(name)
    self._tensor_op = tensor_op
    self._join = join

ProductFuzzyAlgebra

Bases: Algebra

[0,1] with product t-norm and probabilistic sum (noisy-OR).

This is the enrichment for the Kleisli category of the fuzzy powerset monad with the product t-norm:

⊗ = product:      a ⊗ b = a * b
⋁ = noisy-OR:     ⋁_i x_i = 1 - ∏_i (1 - x_i)
⋀ = product:      ⋀_i x_i = ∏_i x_i
¬ = complement:    ¬a = 1 - a
I = 1.0
⊥ = 0.0

Composition uses log-space for numerical stability.

join

join(t: Tensor, dim: int | tuple[int, ...]) -> Tensor

Noisy-OR in log-space: 1 - exp(∑ log(1 - t)).

Source code in src/quivers/core/algebras.py
819
820
821
822
823
824
825
826
827
828
def join(self, t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
    """Noisy-OR in log-space: 1 - exp(∑ log(1 - t))."""
    if isinstance(dim, int):
        dim = (dim,)

    t_clamped = clamp_probs(t)
    log_complement = torch.log1p(-t_clamped)
    sum_log = log_complement.sum(dim=dim)

    return -torch.expm1(sum_log)

meet

meet(t: Tensor, dim: int | tuple[int, ...]) -> Tensor

Product (fuzzy AND): ∏_i t_i.

Source code in src/quivers/core/algebras.py
830
831
832
833
834
835
836
837
838
839
840
def meet(self, t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
    """Product (fuzzy AND): ∏_i t_i."""
    if isinstance(dim, int):
        dim = (dim,)

    result = t

    for d in sorted(dim, reverse=True):
        result = result.prod(dim=d)

    return result

compose

compose(m: Tensor, n: Tensor, n_contract: int) -> Tensor

Override for log-space numerical stability.

Computes noisy-OR contraction matching the existing noisy_or_contract implementation exactly.

Source code in src/quivers/core/algebras.py
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
def compose(
    self,
    m: torch.Tensor,
    n: torch.Tensor,
    n_contract: int,
) -> torch.Tensor:
    """Override for log-space numerical stability.

    Computes noisy-OR contraction matching the existing
    noisy_or_contract implementation exactly.
    """
    if n_contract < 1:
        raise ValueError(f"n_contract must be >= 1, got {n_contract}")

    shared_m = m.shape[-n_contract:]
    shared_n = n.shape[:n_contract]

    if shared_m != shared_n:
        raise ValueError(
            f"shared dimensions do not match: "
            f"m trailing {shared_m} != n leading {shared_n}"
        )

    n_domain = m.ndim - n_contract
    n_codomain = n.ndim - n_contract
    n_shared = n_contract

    m_expanded = m.reshape(*m.shape, *([1] * n_codomain))
    n_expanded = n.reshape(*([1] * n_domain), *n.shape)

    product = m_expanded * n_expanded

    # log-space noisy-OR for stability
    product_clamped = clamp_probs(product)
    log_complement = torch.log1p(-product_clamped)

    contract_dims = tuple(range(n_domain, n_domain + n_shared))
    sum_log = log_complement.sum(dim=contract_dims)

    return -torch.expm1(sum_log)

BooleanAlgebra

Bases: Algebra

{0, 1} with logical AND and OR.

The enrichment for the category Rel of crisp binary relations:

⊗ = AND:     a ⊗ b = a ∧ b
⋁ = OR:      ⋁_i x_i = max_i x_i
⋀ = AND:     ⋀_i x_i = min_i x_i
¬ = NOT:     ¬a = 1 - a
I = 1.0
⊥ = 0.0

Works on float tensors with values in {0.0, 1.0}. Intermediate fuzzy values are rounded.

tensor_op

tensor_op(a: Tensor, b: Tensor) -> Tensor

Logical AND via product (exact for {0,1} inputs).

Source code in src/quivers/core/algebras.py
915
916
917
def tensor_op(self, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
    """Logical AND via product (exact for {0,1} inputs)."""
    return a * b

join

join(t: Tensor, dim: int | tuple[int, ...]) -> Tensor

Logical OR via iterated max.

Source code in src/quivers/core/algebras.py
919
920
921
922
923
924
925
926
927
928
929
def join(self, t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
    """Logical OR via iterated max."""
    if isinstance(dim, int):
        dim = (dim,)

    result = t

    for d in sorted(dim, reverse=True):
        result = result.max(dim=d).values

    return result

meet

meet(t: Tensor, dim: int | tuple[int, ...]) -> Tensor

Logical AND via iterated min.

Source code in src/quivers/core/algebras.py
931
932
933
934
935
936
937
938
939
940
941
def meet(self, t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
    """Logical AND via iterated min."""
    if isinstance(dim, int):
        dim = (dim,)

    result = t

    for d in sorted(dim, reverse=True):
        result = result.min(dim=d).values

    return result

LukasiewiczAlgebra

Bases: Algebra

[0,1] with Łukasiewicz t-norm and bounded sum.

The Łukasiewicz t-norm is the strongest continuous t-norm:

⊗ = Łukasiewicz:   a ⊗ b = max(a + b - 1, 0)
⋁ = bounded sum:   ⋁_i x_i = min(1, ∑_i x_i)
⋀ = min:           ⋀_i x_i = min_i x_i
¬ = strong neg:    ¬a = 1 - a
I = 1.0
⊥ = 0.0

GodelAlgebra

Bases: Algebra

[0,1] with Gödel (min) t-norm.

⊗ = min, ⋁ = max, ⋀ = min, ¬ = Gödel neg (1 if a == 0 else 0), I = 1.0, ⊥ = 0.0.

TropicalAlgebra

Bases: Algebra

[0, ∞] with (+, min) — Lawvere metric spaces.

⊗ = addition (distances compose),
⋁ = infimum (shortest path),
⋀ = supremum (longest path),
I = 0.0, ⊥ = ∞.

Composition is the tropical / (min, +) matrix product. Negation is undefined.

MaxPlusAlgebra

Bases: Algebra

Max-plus (Viterbi) semiring on (-∞, ∞].

Distinct from TropicalAlgebra (which is min-plus, suited to shortest-path aggregations): the join here is max and the tensor is +. The canonical algebra for MAP decoding in HMMs, CRFs, and weighted automata.

⊗ = +,   ⋁ = max,   ⋀ = min,
I = 0, ⊥ = -∞.

LogProbAlgebra

Bases: Algebra

Log-space sum-product semiring on (-∞, 0].

Tensor is real addition (probability multiplication in log- space) and join is torch.logsumexp (probability summation in log-space). Pairs naturally with float32 numerics for hierarchical-Bayes log-likelihood pipelines.

⊗ = +,   ⋁ = logsumexp,   ⋀ = min,
I = 0 (log 1), ⊥ = -∞ (log 0).

RealAlgebra

Bases: Algebra

Sum-product semiring on the real numbers (ℝ, +, ·).

The canonical numeric semiring: addition is the lattice join, multiplication the monoidal tensor. Use when entries are unbounded real weights with no probability interpretation.

ProbabilityAlgebra

Bases: Algebra

Sum-product semiring on [0, 1] with explicit clamp.

Same operations as RealAlgebra but restricted to the unit interval.

CountingAlgebra

Bases: Algebra

Sum-product semiring on the non-negative integers (ℕ, +, ·).

Counting algebra: composition counts the number of distinct paths through a structure. The underlying tensor is float-typed (PyTorch's autograd requires it) but operations are integer-respecting.

MarkovAlgebra

Bases: Algebra

Sum-product composition for stochastic matrices.

Implements the composition rule of FinStoch:

(g ∘ f)(a, c) = Σ_b f(a, b) · g(b, c)

standard matrix multiplication on row-stochastic matrices. Formally:

⊗ = product,   ⋁ = sum,   ⋀ = product,
¬ = complement (1 - p),
I = 1.0, ⊥ = 0.0.

Not a true algebra in the lattice-theoretic sense (Σ is not idempotent), but the composition formula matches the algebra interface and composition of row-stochastic matrices yields row-stochastic matrices.

semigroupoid

semigroupoid(name: str, tensor_op: Callable[[Tensor, Tensor], Tensor], join: Callable[[Tensor, int | tuple[int, ...]], Tensor], *, verify_associative: bool = True) -> CustomSemigroupoid

Convenience constructor for CustomSemigroupoid.

Source code in src/quivers/core/algebras.py
693
694
695
696
697
698
699
700
701
702
703
def semigroupoid(
    name: str,
    tensor_op: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    join: Callable[[torch.Tensor, int | tuple[int, ...]], torch.Tensor],
    *,
    verify_associative: bool = True,
) -> CustomSemigroupoid:
    """Convenience constructor for `CustomSemigroupoid`."""
    return CustomSemigroupoid(
        name, tensor_op, join, verify_associative=verify_associative
    )

bilinear_form

bilinear_form(name: str, tensor_op: Callable[[Tensor, Tensor], Tensor], join: Callable[[Tensor, int | tuple[int, ...]], Tensor]) -> CustomBilinearForm

Convenience constructor for CustomBilinearForm.

Source code in src/quivers/core/algebras.py
756
757
758
759
760
761
762
def bilinear_form(
    name: str,
    tensor_op: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
    join: Callable[[torch.Tensor, int | tuple[int, ...]], torch.Tensor],
) -> CustomBilinearForm:
    """Convenience constructor for `CustomBilinearForm`."""
    return CustomBilinearForm(name, tensor_op, join)

material_implication

material_implication() -> CustomSemigroupoid

Reichenbach material implication composition as a Semigroupoid.

Tensor product is the probabilistic implication a → b = 1 - a + a*b; join is the product reduction. Associative but lacks an identity, so it's a semigroupoid, not an algebra.

Source code in src/quivers/core/algebras.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
def material_implication() -> CustomSemigroupoid:
    """Reichenbach material implication composition as a Semigroupoid.

    Tensor product is the probabilistic implication
    ``a → b = 1 - a + a*b``; join is the product reduction.
    Associative but lacks an identity, so it's a semigroupoid,
    not an algebra.
    """

    def _impl(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
        return 1.0 - a + a * b

    def _prod(t: torch.Tensor, dim: int | tuple[int, ...]) -> torch.Tensor:
        if isinstance(dim, int):
            dim = (dim,)
        result = t
        for d in sorted(dim, reverse=True):
            result = result.prod(dim=d)
        return result

    # Material implication isn't actually associative — `(a→b)→c`
    # ≠ `a→(b→c)` — but its *composition* under join=product is
    # the well-defined Reichenbach S-implication composition. We
    # skip the associativity smoke test because the binary ⊗
    # itself isn't associative; what matters is the composition
    # algorithm is consistent.
    return CustomSemigroupoid(
        "MaterialImplication", _impl, _prod, verify_associative=False
    )