Skip to content

Logic

Differentiable fuzzy-logic gates — Boolean operations on soft truth values in [0, 1], as functions and as parameter-free nn.Module neurons. The radial-basis activation radbas lives in Ops.

polyweave.logic

Differentiable fuzzy logic — Boolean gates as soft, trainable neurons.

Truth values are tensors in [0, 1]. The gates reduce to exact Boolean truth tables on the corners and interpolate smoothly between, so they are differentiable and compose into trainable logic circuits.

The default product t-norm makes a fuzzy AND a literal product neuron — the multiplicative (Pi) computation at the heart of this library — which is why a single Sigma-Pi / :class:~polyweave.layers.PolyLinear neuron solves XOR (a + b - 2ab). See :func:polyweave.ops.radbas for the radial-basis route to the same non-linearly-separable problem.

Available as functions (fuzzy_and, fuzzy_or, …) and as parameter-free nn.Module gates (FuzzyAnd, FuzzyOr, …) for use in nn.Sequential.

FuzzyAnd

FuzzyAnd(t_norm: str = 'product')

Bases: _BinaryGate

Module form of :func:fuzzy_and.

Source code in polyweave/logic/gates.py
def __init__(self, t_norm: str = "product") -> None:
    super().__init__()
    _check_t_norm(t_norm)
    self.t_norm = t_norm

FuzzyNand

FuzzyNand(t_norm: str = 'product')

Bases: _BinaryGate

Module form of :func:fuzzy_nand.

Source code in polyweave/logic/gates.py
def __init__(self, t_norm: str = "product") -> None:
    super().__init__()
    _check_t_norm(t_norm)
    self.t_norm = t_norm

FuzzyNor

FuzzyNor(t_norm: str = 'product')

Bases: _BinaryGate

Module form of :func:fuzzy_nor.

Source code in polyweave/logic/gates.py
def __init__(self, t_norm: str = "product") -> None:
    super().__init__()
    _check_t_norm(t_norm)
    self.t_norm = t_norm

FuzzyNot

Bases: Module

Module form of :func:fuzzy_not (unary).

FuzzyOr

FuzzyOr(t_norm: str = 'product')

Bases: _BinaryGate

Module form of :func:fuzzy_or.

Source code in polyweave/logic/gates.py
def __init__(self, t_norm: str = "product") -> None:
    super().__init__()
    _check_t_norm(t_norm)
    self.t_norm = t_norm

FuzzyXnor

FuzzyXnor(t_norm: str = 'product')

Bases: _BinaryGate

Module form of :func:fuzzy_xnor.

Source code in polyweave/logic/gates.py
def __init__(self, t_norm: str = "product") -> None:
    super().__init__()
    _check_t_norm(t_norm)
    self.t_norm = t_norm

FuzzyXor

FuzzyXor(t_norm: str = 'product')

Bases: _BinaryGate

Module form of :func:fuzzy_xor.

Source code in polyweave/logic/gates.py
def __init__(self, t_norm: str = "product") -> None:
    super().__init__()
    _check_t_norm(t_norm)
    self.t_norm = t_norm

SoftRuleLayer

SoftRuleLayer(n_features: int, n_rules: int, *, signed: bool = True, eps: float = DEFAULT_EPS)

Bases: Module

n_rules signed-literal conjunctions combined by a probabilistic OR (a soft DNF).

Parameters:

Name Type Description Default
n_features int

input truth-vector width.

required
n_rules int

number of conjunctions (disjuncts) to induce.

required
signed bool

whether premises may be negated (default True).

True
eps float

numerical clamp passed to each literal.

DEFAULT_EPS
Source code in polyweave/logic/literals.py
def __init__(self, n_features: int, n_rules: int, *, signed: bool = True,
             eps: float = DEFAULT_EPS) -> None:
    super().__init__()
    self.rules = nn.ModuleList(
        SoftSignedLiteral(n_features, signed=signed, eps=eps) for _ in range(n_rules)
    )

forward

forward(t: Tensor) -> torch.Tensor

Probabilistic OR over the rules: 1 - prod_r (1 - fire_r) in (0, 1).

Source code in polyweave/logic/literals.py
def forward(self, t: torch.Tensor) -> torch.Tensor:
    """Probabilistic OR over the rules: ``1 - prod_r (1 - fire_r)`` in ``(0, 1)``."""
    not_fire = torch.cat([1.0 - rule(t) for rule in self.rules], dim=-1)
    return 1.0 - not_fire.prod(dim=-1, keepdim=True)

rules_text

rules_text(feature_names: Optional[List[str]] = None, threshold: float = 0.3) -> List[str]

Read each induced rule as a string, e.g. "bird & not penguin".

Source code in polyweave/logic/literals.py
@torch.no_grad()
def rules_text(self, feature_names: Optional[List[str]] = None,
               threshold: float = 0.3) -> List[str]:
    """Read each induced rule as a string, e.g. ``"bird & not penguin"``."""
    names = feature_names or [f"x{i}" for i in range(self.rules[0].w.numel())]
    texts = []
    for rule in self.rules:
        parts = [(n if r == "required" else f"not {n}")
                 for n, r, _ in rule.literals(names, threshold)]
        texts.append(" & ".join(parts) if parts else "(empty)")
    return texts

SoftSignedLiteral

SoftSignedLiteral(n_features: int, *, signed: bool = True, eps: float = DEFAULT_EPS)

Bases: Module

A single learnable conjunction with signed log-space exponents.

Parameters:

Name Type Description Default
n_features int

size of the input truth vector's last dimension.

required
signed bool

if True (default) each premise can be positive, ignored, or negated; if False only positive literals are possible (a plain monotone product AND that cannot represent an exception).

True
eps float

truth values are clamped to [eps, 1 - eps] for numerical safety.

DEFAULT_EPS
Source code in polyweave/logic/literals.py
def __init__(self, n_features: int, *, signed: bool = True, eps: float = DEFAULT_EPS) -> None:
    super().__init__()
    self.signed = signed
    self.eps = eps
    self.w = nn.Parameter(torch.randn(n_features) * 0.1)

forward

forward(t: Tensor) -> torch.Tensor

t in [0, 1] ([..., n_features]) -> rule firing in (0, 1].

Source code in polyweave/logic/literals.py
def forward(self, t: torch.Tensor) -> torch.Tensor:
    """``t`` in ``[0, 1]`` (``[..., n_features]``) -> rule firing in ``(0, 1]``."""
    t = t.clamp(self.eps, 1.0 - self.eps)
    logc = self.w.clamp(min=0.0) * torch.log(t)
    if self.signed:
        logc = logc + (-self.w).clamp(min=0.0) * torch.log1p(-t)
    return torch.exp(logc.sum(-1, keepdim=True))

exponent_abs_mean

exponent_abs_mean() -> float

Recruitment metric A — mean(|w_i|); ~0 = no rule structure recruited.

Source code in polyweave/logic/literals.py
@torch.no_grad()
def exponent_abs_mean(self) -> float:
    """Recruitment metric A — ``mean(|w_i|)``; ~0 = no rule structure recruited."""
    return self.w.abs().mean().item()

literals

literals(feature_names: Optional[List[str]] = None, threshold: float = 0.3) -> List[Tuple[str, str, float]]

The induced literals as (feature, role, weight) where role is one of "required" / "inhibitory" (premises with |w| <= threshold are treated as ignored and omitted).

Source code in polyweave/logic/literals.py
@torch.no_grad()
def literals(self, feature_names: Optional[List[str]] = None,
             threshold: float = 0.3) -> List[Tuple[str, str, float]]:
    """The induced literals as ``(feature, role, weight)`` where role is one of
    ``"required"`` / ``"inhibitory"`` (premises with ``|w| <= threshold`` are
    treated as ignored and omitted)."""
    names = feature_names or [f"x{i}" for i in range(self.w.numel())]
    out = []
    for name, wv in zip(names, self.w.tolist()):
        if wv > threshold:
            out.append((name, "required", wv))
        elif wv < -threshold:
            out.append((name, "inhibitory", wv))
    return out

fuzzy_and

fuzzy_and(a: Tensor, b: Tensor, t_norm: str = 'product') -> torch.Tensor

Fuzzy conjunction. product: a*b (a Pi neuron); min: min(a, b).

Source code in polyweave/logic/gates.py
def fuzzy_and(a: torch.Tensor, b: torch.Tensor, t_norm: str = "product") -> torch.Tensor:
    """Fuzzy conjunction. ``product``: ``a*b`` (a Pi neuron); ``min``: ``min(a, b)``."""
    _check_t_norm(t_norm)
    return a * b if t_norm == "product" else torch.minimum(a, b)

fuzzy_nand

fuzzy_nand(a: Tensor, b: Tensor, t_norm: str = 'product') -> torch.Tensor

Fuzzy NAND not(and(a, b)).

Source code in polyweave/logic/gates.py
def fuzzy_nand(a: torch.Tensor, b: torch.Tensor, t_norm: str = "product") -> torch.Tensor:
    """Fuzzy NAND ``not(and(a, b))``."""
    return fuzzy_not(fuzzy_and(a, b, t_norm))

fuzzy_nor

fuzzy_nor(a: Tensor, b: Tensor, t_norm: str = 'product') -> torch.Tensor

Fuzzy NOR not(or(a, b)).

Source code in polyweave/logic/gates.py
def fuzzy_nor(a: torch.Tensor, b: torch.Tensor, t_norm: str = "product") -> torch.Tensor:
    """Fuzzy NOR ``not(or(a, b))``."""
    return fuzzy_not(fuzzy_or(a, b, t_norm))

fuzzy_not

fuzzy_not(a: Tensor) -> torch.Tensor

Fuzzy negation 1 - a (the standard strong/complement negation).

Source code in polyweave/logic/gates.py
def fuzzy_not(a: torch.Tensor) -> torch.Tensor:
    """Fuzzy negation ``1 - a`` (the standard strong/complement negation)."""
    return 1.0 - a

fuzzy_or

fuzzy_or(a: Tensor, b: Tensor, t_norm: str = 'product') -> torch.Tensor

Fuzzy disjunction (t-conorm dual of :func:fuzzy_and).

product: a + b - a*b (probabilistic sum); min: max(a, b).

Source code in polyweave/logic/gates.py
def fuzzy_or(a: torch.Tensor, b: torch.Tensor, t_norm: str = "product") -> torch.Tensor:
    """Fuzzy disjunction (t-conorm dual of :func:`fuzzy_and`).

    ``product``: ``a + b - a*b`` (probabilistic sum); ``min``: ``max(a, b)``.
    """
    _check_t_norm(t_norm)
    return a + b - a * b if t_norm == "product" else torch.maximum(a, b)

fuzzy_xnor

fuzzy_xnor(a: Tensor, b: Tensor, t_norm: str = 'product') -> torch.Tensor

Fuzzy equivalence not(xor(a, b)) — soft equality of two truth values.

Source code in polyweave/logic/gates.py
def fuzzy_xnor(a: torch.Tensor, b: torch.Tensor, t_norm: str = "product") -> torch.Tensor:
    """Fuzzy equivalence ``not(xor(a, b))`` — soft equality of two truth values."""
    return fuzzy_not(fuzzy_xor(a, b, t_norm))

fuzzy_xor

fuzzy_xor(a: Tensor, b: Tensor, t_norm: str = 'product') -> torch.Tensor

Fuzzy exclusive-or or(a, b) - and(a, b).

For the product t-norm this is a + b - 2ab — a linear term plus a bilinear product, i.e. exactly a single Sigma-Pi / degree-2 neuron. For min it is |a - b|. Both are correct on the Boolean corners.

Source code in polyweave/logic/gates.py
def fuzzy_xor(a: torch.Tensor, b: torch.Tensor, t_norm: str = "product") -> torch.Tensor:
    """Fuzzy exclusive-or ``or(a, b) - and(a, b)``.

    For the product t-norm this is ``a + b - 2ab`` — a linear term plus a bilinear
    product, i.e. exactly a single Sigma-Pi / degree-2 neuron. For ``min`` it is
    ``|a - b|``. Both are correct on the Boolean corners.
    """
    return fuzzy_or(a, b, t_norm) - fuzzy_and(a, b, t_norm)