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)
|