Skip to content

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
@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
@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
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
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
@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
@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
@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
@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
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
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
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
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 the built-in 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
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
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
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
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 instance 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 because the operation may be non-associative.

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
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
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
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
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
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
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
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). It supports 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.

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), smoke-tests tensor_op for associativity on a fixed sample.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
CustomSemigroupoid

The composition rule built from the callables.

RAISES DESCRIPTION
ValueError

If name is empty, or if verify_associative is set and tensor_op fails the associativity check.

Source code in src/quivers/core/algebras.py
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`.

    Parameters
    ----------
    name : str
        Human-readable name.
    tensor_op : Callable
        Binary monoidal product.
    join : Callable
        Reduction along an axis.
    verify_associative : bool
        When ``True`` (default), smoke-tests ``tensor_op`` for
        associativity on a fixed sample.

    Returns
    -------
    CustomSemigroupoid
        The composition rule built from the callables.

    Raises
    ------
    ValueError
        If ``name`` is empty, or if ``verify_associative`` is set and
        ``tensor_op`` fails the associativity check.
    """
    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.

PARAMETER DESCRIPTION
name

Human-readable name.

TYPE: str

tensor_op

Binary product, which need not be associative.

TYPE: Callable

join

Reduction along an axis.

TYPE: Callable

RETURNS DESCRIPTION
CustomBilinearForm

The composition rule built from the callables.

Source code in src/quivers/core/algebras.py
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`.

    Parameters
    ----------
    name : str
        Human-readable name.
    tensor_op : Callable
        Binary product, which need not be associative.
    join : Callable
        Reduction along an axis.

    Returns
    -------
    CustomBilinearForm
        The composition rule built from the callables.
    """
    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.

RETURNS DESCRIPTION
CustomSemigroupoid

The material-implication composition rule.

Source code in src/quivers/core/algebras.py
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.

    Returns
    -------
    CustomSemigroupoid
        The material-implication composition rule.
    """

    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
    )

The algebra singletons, such as PRODUCT_FUZZY, are documented on the core overview.