Extending fuzzytool¶
The core principle (shared with the sibling project turboswarm): the inference loop knows nothing about concrete variants. Everything that varies lives behind a small Protocol, so extending the library means adding a callable — never editing the engine.
A new membership function¶
Any callable x -> degree (vectorized over NumPy arrays) works directly:
import numpy as np, fuzzytool as fz
def asym_gauss(c, sl, sr):
def mf(x):
x = np.asarray(x, float)
s = np.where(x < c, sl, sr)
return np.exp(-0.5 * ((x - c) / s) ** 2)
return mf
v = fz.Variable("v", (0, 10))
v["mid"] = asym_gauss(5, 1.0, 3.0)
To make a custom shape survive fz.save / fz.load, write it as a class and
register the constructor arguments that rebuild it:
class AsymGauss:
def __init__(self, c, sl, sr):
self.c, self.sl, self.sr = float(c), float(sl), float(sr)
def __call__(self, x):
x = np.asarray(x, float)
s = np.where(x < self.c, self.sl, self.sr)
return np.exp(-0.5 * ((x - self.c) / s) ** 2)
fz.membership.register("asym_gauss", AsymGauss, ("c", "sl", "sr"))
A new connective (t-norm / s-norm / complement)¶
Register a vectorized (a, b) -> result:
from fuzzytool import norms
def t_soft(a, b):
return (a * b) ** 0.5
norms._TNORMS["soft"] = t_soft
sys = fz.Mamdani(tnorm="soft")
Or pass the callable straight to the engine: fz.Mamdani(tnorm=t_soft).
The complement (NOT) is pluggable the same way — norms._COMPLEMENTS for a
name, or fz.Mamdani(complement=my_callable) directly. Non-standard complements
(Sugeno, Yager) ship with the library.
A new defuzzifier¶
A callable (x, y) -> float:
If it has a vectorized form, register it so batch predict uses it:
A new type reducer (interval type-2)¶
A callable (points, lower, upper) -> (y_l, y_r), registered in
fuzzytool.type2.reduction._REDUCERS or passed directly:
A new inference engine¶
Mix in RuleBase and implement
__call__(**inputs). The mixin supplies firing, explain, summary,
input_variables and rule_from_text to any class exposing rules, tnorm,
snorm and complement, so a new engine gets the whole introspection and
auditing story for free.
Evaluate antecedents with
antecedent.eval(inputs, tnorm, snorm, complement=..., cache=...), passing one
fresh cache dict per call — that is what stops a repeated proposition from
being evaluated once per rule. self._eval_kwargs() builds those arguments for
you. See fuzzytool/inference/mamdani.py and tsk.py as templates.