Skip to content

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:

sys = fz.Mamdani(defuzz=lambda x, y: float((x * y).sum() / y.sum()))

If it has a vectorized form, register it so batch predict uses it:

from fuzzytool import defuzz
defuzz._BATCH[my_defuzzifier] = my_batch_defuzzifier

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:

fz.IT2Mamdani(reducer=my_reducer)

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.