Distributive Laws

Distributive laws between monads and comonads.

distributive_laws

Distributive laws for composing monads.

A distributive law λ: S ∘ T ⇒ T ∘ S between monads S and T allows their composition T ∘ S to form a new monad. The component λ_A: S(T(A)) → T(S(A)) "pushes S past T."

This module provides:

DistributiveLaw (abstract)
└── FreeMonoidPowersetLaw — λ: Free(P(A)) → P(Free(A))

DistributiveLaw

Bases: ABC

Abstract distributive law λ: S ∘ T ⇒ T ∘ S.

Subclasses must implement outer_monad, inner_monad, and distribute.

outer_monad abstractmethod property

outer_monad: Monad

The outer monad S (applied second in S ∘ T).

inner_monad abstractmethod property

inner_monad: Monad

The inner monad T (applied first in S ∘ T).

distribute abstractmethod

distribute(obj: SetObject) -> Morphism

The component λ_A: S(T(A)) → T(S(A)).

PARAMETER DESCRIPTION
obj

The object A.

TYPE: SetObject

RETURNS DESCRIPTION
Morphism

The distributive morphism λ_A.

Source code in src/quivers/monadic/distributive_laws.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@abstractmethod
def distribute(self, obj: SetObject) -> Morphism:
    """The component λ_A: S(T(A)) → T(S(A)).

    Parameters
    ----------
    obj : SetObject
        The object A.

    Returns
    -------
    Morphism
        The distributive morphism λ_A.
    """
    ...

FreeMonoidPowersetLaw

FreeMonoidPowersetLaw(max_length: int, quantale: Quantale | None = None)

Bases: DistributiveLaw

Distributive law λ: Free(P(A)) → P(Free(A)).

A word of fuzzy sets (S₁, ..., Sₖ) in Free(P(A)) maps to a fuzzy set of words in P(Free(A)) with membership:

λ(S₁,...,Sₖ)(a₁,...,aₖ) = ⊗ᵢ Sᵢ(aᵢ)

This is exactly the componentwise lift (the functorial action of FreeMonoid on morphisms). The distributive law witnesses the fact that "a word of fuzzy sets" can be reinterpreted as "a fuzzy set of words" via the product t-norm.

PARAMETER DESCRIPTION
max_length

Maximum string length for the free monoid.

TYPE: int

quantale

The enrichment algebra. Defaults to PRODUCT_FUZZY.

TYPE: Quantale or None DEFAULT: None

Source code in src/quivers/monadic/distributive_laws.py
82
83
84
85
86
87
88
89
90
def __init__(
    self,
    max_length: int,
    quantale: Quantale | None = None,
) -> None:
    self._max_length = max_length
    self._quantale = quantale if quantale is not None else PRODUCT_FUZZY
    self._powerset = FuzzyPowersetMonad(quantale=self._quantale)
    self._free_monoid = FreeMonoidMonad(max_length)

outer_monad property

outer_monad: FreeMonoidMonad

The free monoid monad (applied to P(A)).

inner_monad property

inner_monad: FuzzyPowersetMonad

The fuzzy powerset monad (applied to A).

distribute

distribute(obj: SetObject) -> Morphism

λ_A: Free(P(A)) → P(Free(A)) = Free(A) (at set level).

Since P is identity at set level, Free(P(A)) = Free(A) = A. And P(Free(A)) = Free(A) = A (also identity at set level). So λ_A: A → A is an endomorphism.

The tensor is the block-diagonal matrix built from componentwise_lift — which is exactly the FreeMonoidFunctor's action on the identity morphism A → A.

PARAMETER DESCRIPTION
obj

Must be a FinSet.

TYPE: SetObject

RETURNS DESCRIPTION
ObservedMorphism

The distributive law component λ_A.

Source code in src/quivers/monadic/distributive_laws.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def distribute(self, obj: SetObject) -> Morphism:
    """λ_A: Free(P(A)) → P(Free(A)) = Free(A) (at set level).

    Since P is identity at set level, Free(P(A)) = Free(A) = A*.
    And P(Free(A)) = Free(A) = A* (also identity at set level).
    So λ_A: A* → A* is an endomorphism.

    The tensor is the block-diagonal matrix built from
    componentwise_lift — which is exactly the FreeMonoidFunctor's
    action on the identity morphism A → A.

    Parameters
    ----------
    obj : SetObject
        Must be a FinSet.

    Returns
    -------
    ObservedMorphism
        The distributive law component λ_A.
    """
    if not isinstance(obj, FinSet):
        raise TypeError(
            f"FreeMonoidPowersetLaw requires FinSet, got {type(obj).__name__}"
        )

    fm = FreeMonoid(generators=obj, max_length=self._max_length)

    # the distributive law at the tensor level is the block-diagonal
    # identity: for each stratum k, the k-fold componentwise lift
    # of id_A is id_{A^k}
    n = obj.cardinality
    id_tensor = torch.eye(n)

    blocks: list[torch.Tensor] = []

    for k in range(self._max_length + 1):
        lifted = componentwise_lift(id_tensor, k, quantale=self._quantale)
        rows = n**k if k > 0 else 1
        cols = n**k if k > 0 else 1
        blocks.append(lifted.reshape(rows, cols))

    data = torch.block_diag(*blocks)

    return observed(fm, fm, data, quantale=self._quantale)