API reference¶
Generated from docstrings with mkdocstrings.
Membership functions¶
fuzzytool.membership ¶
Membership functions.
Every membership function (MF) is a callable mapping a crisp value (scalar or
NumPy array) to a membership degree in [0, 1]. The :class:MembershipFunction
Protocol is the only contract the rest of the library relies on: the inference
engine never inspects a concrete MF type, so a new shape = a new callable, with
no changes to the core (mirroring the "one variant = one impl" design of the
sibling project turboswarm).
The lowercase factory functions (:func:tri, :func:trap, :func:gauss,
:func:gauss2, :func:gbell, :func:sigmoid, :func:smf, :func:zmf,
:func:pimf, :func:singleton, :func:ramp_up, :func:ramp_down) are the
public API; they return small classes so the parameters stay introspectable for
visualization and serialization.
Shapes that are monotonic (:func:sigmoid, :func:smf, :func:zmf,
:func:ramp_up, :func:ramp_down) also expose inverse(degree), which makes
them usable as Tsukamoto consequents. Custom shapes join the serialization
round trip through :func:register.
MembershipFunction ¶
Bases: Protocol
A callable x -> degree with membership degrees in [0, 1].
Triangular ¶
Triangular MF with feet at a/c and peak at b.
Trapezoidal ¶
Trapezoidal MF; flat top between b and c.
Gaussian ¶
Gaussian MF centered at c with spread sigma.
GeneralizedBell ¶
Generalized bell MF: 1 / (1 + |(x - c) / a|^(2b)).
Sigmoid ¶
Sigmoidal MF: 1 / (1 + exp(-a (x - c))). Monotonic (invertible).
SShaped ¶
S-shaped (spline) MF: smoothly 0 at a rising to 1 at b. Monotonic.
ZShaped ¶
Z-shaped (spline) MF: smoothly 1 at a falling to 0 at b. Monotonic.
PiShaped ¶
Pi-shaped MF: an S-shaped rise a..b and a Z-shaped fall c..d.
Gaussian2 ¶
Two-sided Gaussian: left shoulder (c1, sigma1), right (c2, sigma2).
Membership is 1 on the plateau [c1, c2]; outside it, each side decays
with its own Gaussian. With c1 == c2 it reduces to a plain Gaussian with
asymmetric spreads.
Singleton ¶
Crisp singleton: 1 at value (within tol), 0 elsewhere.
Useful as a Mamdani consequent (which then behaves like a zero-order TSK rule) and for fuzzifying already-crisp measurements.
RampUp ¶
Monotonically increasing ramp: 0 below a, 1 above b.
RampDown ¶
Monotonically decreasing ramp: 1 below a, 0 above b.
trap ¶
Trapezoidal MF: shoulders a/d, flat top b..c.
gbell ¶
Generalized bell MF: width a, slope b, center c.
pimf ¶
Pi-shaped MF: S-shaped rise a..b, plateau, Z-shaped fall c..d.
gauss2 ¶
Two-sided Gaussian MF with a plateau between c1 and c2.
singleton ¶
Crisp singleton MF: 1 at value, 0 elsewhere.
ramp_up ¶
Increasing ramp MF from 0 at a to 1 at b (monotonic).
ramp_down ¶
Decreasing ramp MF from 1 at a to 0 at b (monotonic).
register ¶
Make a custom membership-function class serializable under tag.
params names the constructor arguments, in order, as attributes of the
instance — so cls(*[getattr(obj, p) for p in params]) rebuilds it. This
is the extension point that lets :func:fuzzytool.save / :func:fuzzytool.load
round-trip shapes that are not built in.
import numpy as np, fuzzytool as fz class Cosine: ... def init(self, c, w): ... self.c, self.w = float(c), float(w) ... def call(self, x): ... d = np.abs(np.asarray(x, dtype=float) - self.c) / self.w ... return np.where(d <= 1, 0.5 * (1 + np.cos(np.pi * d)), 0.0) fz.membership.register("cosine", Cosine, ("c", "w")) fz.membership.to_dict(Cosine(2, 1))
register_factory ¶
Register a factory that rebuilds a shape from its serialized parameters.
Used for membership functions produced by a function rather than a class —
the objects mark themselves with a _serial_spec attribute naming this
tag, and :func:from_dict calls the factory to rebuild them.
Connectives (t-norms / s-norms)¶
fuzzytool.norms ¶
Fuzzy connectives: t-norms (AND), s-norms / t-conorms (OR), complements (NOT).
Connectives are plain vectorized callables (a, b) -> result operating
elementwise on membership degrees. They are pluggable: the inference engines
look them up by name through :func:get_tnorm / :func:get_snorm /
:func:get_complement, so adding a connective means registering one function —
the engine never changes.
Besides the named connectives there are parametric families — Yager, Dombi and Frank — exposed as factories that return a callable, which every engine accepts in place of a name::
>>> import fuzzytool as fz
>>> sys = fz.Mamdani(tnorm=fz.norms.yager_tnorm(2.0))
Norm ¶
Bases: Protocol
A binary connective on membership degrees.
yager_tnorm ¶
Yager t-norm: max(0, 1 - ((1-a)^p + (1-b)^p)^(1/p)) (p > 0).
dombi_tnorm ¶
Dombi t-norm with parameter p > 0 (0 whenever either degree is 0).
dombi_snorm ¶
Dombi s-norm with parameter p > 0 (1 whenever either degree is 1).
frank_snorm ¶
Frank s-norm, the De Morgan dual of :func:frank_tnorm.
get_tnorm ¶
Resolve a t-norm by name (or pass a callable through unchanged).
get_snorm ¶
Resolve an s-norm by name (or pass a callable through unchanged).
sugeno_complement ¶
Sugeno complement (1 - a) / (1 + lam a) (lam > -1).
lam = 0 recovers the standard complement.
yager_complement ¶
Yager complement (1 - a^w)^(1/w) (w > 0).
w = 1 recovers the standard complement.
get_complement ¶
Resolve a complement by name (or pass a callable through unchanged).
import fuzzytool as fz float(fz.norms.get_complement("standard")(0.25)) 0.75
Sets, variables, and rule antecedents¶
fuzzytool.sets ¶
Fuzzy sets, linguistic variables, and the rule-antecedent expression tree.
A :class:Variable is a linguistic variable: a named universe of discourse plus
a dictionary of terms (named membership functions). Indexing a variable with a
term name (score["good"]) returns a :class:Proposition, the atom of a
rule antecedent. Propositions compose with Python operators:
score["poor"] | dti["high"] # OR (s-norm)
score["good"] & dti["low"] # AND (t-norm)
~dti["high"] # NOT (complement)
The result is a small expression tree that the inference engine evaluates
against crisp inputs to get a firing strength. The same atom doubles as a rule
consequent (premium["high"]), so there is a single concept to learn.
Antecedent ¶
Base node of a rule-antecedent expression tree.
eval ¶
eval(inputs: Mapping[str, float], tnorm: Callable, snorm: Callable, *, complement: Callable | None = None, cache: dict | None = None) -> np.ndarray
Return the (type-1) firing strength given crisp inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Mapping[str, float]
|
crisp values per variable name (scalars or arrays). |
required |
tnorm
|
Callable
|
connective used by :class: |
required |
snorm
|
Callable
|
connective used by :class: |
required |
complement
|
Callable | None
|
connective used by :class: |
None
|
cache
|
dict | None
|
optional dict memoizing |
None
|
eval_interval ¶
eval_interval(inputs: Mapping[str, float], tnorm: Callable, snorm: Callable, *, complement: Callable | None = None, cache: dict | None = None)
Return the firing interval (lower, upper) for IT2 inference.
Type-1 terms collapse to a degenerate interval (d, d), so type-1 and
IT2 terms may be mixed freely in the same antecedent. Takes the same
complement / cache options as :meth:eval.
propositions ¶
Every :class:Proposition atom in this subtree, in reading order.
Proposition ¶
Variable ¶
A linguistic variable: a named universe with named fuzzy-set terms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
identifier; rule inputs are matched to variables by this name. |
required |
universe
|
tuple[float, float]
|
|
required |
terms
|
Iterable[str] | Mapping[str, MembershipFunction] | None
|
optional list of term names to auto-generate evenly across the
universe, or a mapping |
None
|
kind
|
str
|
shape used by auto-generation ( |
'triangular'
|
resolution
|
int
|
number of samples used to discretize the universe for Mamdani defuzzification. |
501
|
shoulders
|
bool
|
saturate the outermost auto-generated terms at the edges of
the universe (see :meth: |
False
|
auto_terms ¶
Generate evenly-spaced terms named names across the universe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
names
|
list[str]
|
term names, laid out left to right. |
required |
kind
|
str
|
|
'triangular'
|
shoulders
|
bool
|
make the first and last term shouldered — saturated at 1 all the way to the edge of the universe instead of decaying past their center. This is the usual practice for a complete partition: without it, membership drops toward the extremes and inference near the edges is driven by a single weak rule. |
False
|
v = Variable("v", (0, 10), terms=["lo", "hi"], shoulders=True) float(v.fuzzify(0.0)["lo"]), float(v.fuzzify(10.0)["hi"]) (1.0, 1.0)
fuzzify ¶
Membership degree of x in every term, as {term: degree}.
IT2 terms collapse to the midpoint of their membership interval, so the result is always a plain number (or array) per term.
v = Variable("v", (0, 10), terms=["lo", "hi"]) {k: round(float(d), 2) for k, d in v.fuzzify(2.5).items()}
to_dict ¶
Serialize this variable (universe + terms) to a JSON-ready dict.
Only built-in membership functions can be serialized (see
:func:fuzzytool.membership.to_dict).
from_dict
classmethod
¶
Rebuild a variable from :meth:to_dict output.
antecedent_from_dict ¶
Rebuild an antecedent tree from :meth:Antecedent.to_dict output.
variables maps variable names to the :class:Variable instances the
propositions should reference.
Rules¶
fuzzytool.rules ¶
Fuzzy rules shared by the inference engines.
A :class:Rule pairs an antecedent expression with a consequent and an optional
weight. The consequent is interpreted by the engine: Mamdani expects a
:class:~fuzzytool.sets.Proposition (output is term); TSK expects a
constant or a linear coefficient vector.
Rule
dataclass
¶
IF antecedent THEN consequent with an optional firing weight.
Defuzzification¶
fuzzytool.defuzz ¶
Defuzzification: collapse an aggregated output set into a crisp value.
Each defuzzifier takes the discretized universe x and the aggregated
membership y (same shape) and returns a scalar. They are looked up by name
through :func:get_defuzzifier, so adding a method = registering a function.
centroid ¶
Center of gravity of the area under y (the most common choice).
bisector ¶
Abscissa that splits the area under y into two equal halves.
weighted_average ¶
Height / weighted-average defuzzification: Σ x·y / Σ y.
Unlike :func:centroid this treats the samples as discrete weighted points
rather than integrating the area, so it is cheaper and insensitive to the
universe's resolution being non-uniform.
coa_largest ¶
Center of the largest contiguous area (COA / CLA).
When aggregation leaves several disjoint humps, the centroid can land in the valley between them — a value no rule ever supported. This method keeps only the widest connected region of positive membership and takes its centroid.
centroid_batch ¶
Vectorized :func:centroid over the rows of Y.
bisector_batch ¶
Vectorized :func:bisector over the rows of Y.
weighted_average_batch ¶
Vectorized :func:weighted_average over the rows of Y.
get_batch_defuzzifier ¶
Return the vectorized form of a defuzzifier, or None if it has none.
get_defuzzifier ¶
Resolve a defuzzifier by name (or pass a callable through unchanged).
Inference — Mamdani¶
fuzzytool.inference.mamdani ¶
Mamdani fuzzy inference.
The engine knows nothing about specific membership functions, connectives, or
defuzzifiers: t-norm, s-norm, implication, aggregation and defuzzification are
all resolved by name (or supplied as callables) and applied uniformly. Adding a
behavior means registering a function in :mod:fuzzytool.norms or
:mod:fuzzytool.defuzz, never editing this loop.
Pipeline per call: evaluate each rule's antecedent to a firing strength → shape its consequent term over the output universe via the implication operator → aggregate the shaped sets per output variable → defuzzify.
Mamdani ¶
Bases: RuleBase
A Mamdani inference system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tnorm
|
str | Callable
|
t-norm for AND in antecedents (default |
'min'
|
snorm
|
str | Callable
|
s-norm for OR in antecedents (default |
'max'
|
implication
|
str
|
how a firing strength shapes its consequent set —
|
'min'
|
aggregation
|
str | Callable
|
s-norm combining shaped sets per output (default |
'max'
|
defuzz
|
str | Callable
|
defuzzification method (default |
'centroid'
|
on_no_rule
|
str
|
crisp value for an output when no rule fires — |
'mid'
|
complement
|
str | Callable
|
connective for NOT in antecedents (default |
'standard'
|
rule ¶
Add IF antecedent THEN output is term and return self.
__call__ ¶
Run inference. Returns a float for one output, else a dict by name.
predict ¶
Vectorized inference over array-valued inputs.
Each keyword is an array of n samples. Returns an array of length
n for a single output, else a dict of arrays. Equivalent to calling
the system once per sample, but evaluated in batch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunk_size
|
int | None
|
number of samples processed per block. Mamdani inference
materializes an |
4096
|
**inputs
|
ndarray
|
one array per input variable. |
{}
|
Inference — Takagi-Sugeno (TSK)¶
fuzzytool.inference.tsk ¶
Takagi-Sugeno-Kang (TSK) fuzzy inference.
Consequents are crisp functions of the inputs rather than fuzzy sets, so there is no defuzzification: the output is the firing-weighted average of the rule consequents. A consequent may be
- a number — zero-order (Sugeno constant), e.g.
5.0; - a mapping
{"const": b0, "x": b1, ...}— first-order linear in the inputs; - any callable
f(**inputs) -> float— arbitrary.
TSK ¶
Bases: RuleBase
A (zero- or first-order) Takagi-Sugeno inference system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tnorm
|
str | Callable
|
t-norm for AND in antecedents (default |
'min'
|
snorm
|
str | Callable
|
s-norm for OR in antecedents (default |
'max'
|
on_no_rule
|
str
|
what to return when no rule fires — |
'nan'
|
complement
|
str | Callable
|
connective for NOT in antecedents (default |
'standard'
|
rule ¶
Add IF antecedent THEN output = consequent and return self.
__call__ ¶
Run inference, returning the firing-weighted average output.
predict ¶
Vectorized inference over array-valued inputs (n per keyword).
Returns an array of length n. Samples where no rule fires follow
on_no_rule: nan (default) or a raised ValueError.
Inference — Tsukamoto¶
fuzzytool.inference.tsukamoto ¶
Tsukamoto fuzzy inference.
In a Tsukamoto system every rule's consequent is a monotonic membership
function. A rule firing with strength w produces the crisp value at which
its consequent reaches w (the inverse of the monotonic MF). The system
output is the firing-weighted average of those crisp values — so, like TSK,
there is no defuzzification step.
Consequents must expose inverse(degree) -> value; the built-in
:func:~fuzzytool.membership.ramp_up, :func:~fuzzytool.membership.ramp_down
and :func:~fuzzytool.membership.sigmoid do.
Tsukamoto ¶
Bases: RuleBase
A Tsukamoto inference system (monotonic consequents).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tnorm
|
str | Callable
|
t-norm for AND in antecedents (default |
'min'
|
snorm
|
str | Callable
|
s-norm for OR in antecedents (default |
'max'
|
on_no_rule
|
str
|
what to return when no rule fires — |
'nan'
|
complement
|
str | Callable
|
connective for NOT in antecedents (default |
'standard'
|
rule ¶
Add a rule; consequent is a monotonic MF with an inverse.
__call__ ¶
Run inference, returning the firing-weighted average crisp output.
predict ¶
Vectorized inference over array-valued inputs (n per keyword).
Returns an array of length n. The consequent inverses are scalar
functions, so they are applied per sample; the antecedents are still
evaluated in batch.
Inference — shared rule-base behavior¶
fuzzytool.inference._common ¶
Shared helpers and the common rule-base behavior of the inference engines.
RuleBase ¶
Behavior every engine shares: introspection, firing strengths, explanation.
Engines mixing this in must expose rules, tnorm, snorm and
complement. Nothing here knows how a consequent is interpreted, so the
same code serves Mamdani, TSK, Tsukamoto and the type-2 engines.
input_variables ¶
Input linguistic variables referenced by the antecedents, by name.
output_variables ¶
Output linguistic variables, by name (empty for engines without them).
rule_from_text ¶
Add a rule written in the text syntax of :mod:fuzzytool.dsl.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
e.g. |
required |
variables
|
object
|
the variables the rule may reference. Defaults to the ones this system already knows, so only the first rule of a fresh system needs to pass them. |
None
|
Returns self, so calls chain like :meth:rule.
firing ¶
Weighted firing strength of every rule, in rule order.
Evaluates the whole rule base with one shared memo, so each membership
function is applied at most once even when many rules mention it.
Accepts scalars (returns shape (n_rules,)) or arrays (returns shape
(n_rules, n_samples)).
explain ¶
Run the system on inputs and report why it answered that.
Returns the crisp output plus fired_rules: every rule with a
positive firing strength, strongest first, each carrying its index, its
text, its firing strength and its share of the total firing. This is
the payload a human (or an LLM agent) needs to trust the answer.
from fuzzytool import datasets sys, *_ = datasets.credit_risk() report = sys.explain(score=520, dti=42) max(r["share"] for r in report["fired_rules"]) > 0.5 True
check_on_no_rule ¶
Validate an on_no_rule policy, returning it unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
the policy string to check. |
required |
allow_mid
|
bool
|
whether |
required |
The uniform policy across engines answers "what should a call return when no
rule fires for an output?": "nan" (a quiet, composable sentinel),
"raise" (fail loudly), or "mid" (Mamdani only: the midpoint of the
output universe).
Fuzzy classification¶
fuzzytool.classify ¶
Fuzzy rule-based classification.
The Mamdani/TSK engines answer with a number. A classifier answers with a label, and the natural fuzzy form of that is a rule base whose consequents are class labels, each rule carrying a certainty factor — how reliably that antecedent implied that class in the data it came from:
IF score is poor AND dti is high THEN class = default (cf 0.82)
Inference is a vote: every rule adds its firing strength times its certainty to
its class, and the strongest class wins. Because the vote is transparent, the
classifier explains itself the same way the other engines do — see
:meth:~fuzzytool.inference._common.RuleBase.explain.
>>> import numpy as np, fuzzytool as fz
>>> x = fz.Variable("x", (0, 10), terms=["lo", "hi"])
>>> clf = fz.FuzzyClassifier()
>>> _ = clf.rule(x["lo"], "a").rule(x["hi"], "b")
>>> clf(x=1.0), clf(x=9.0)
('a', 'b')
>>> clf.predict(x=np.array([1.0, 9.0])).tolist()
['a', 'b']
FuzzyClassifier ¶
Bases: RuleBase
A fuzzy rule-based classifier (class-label consequents + certainty factors).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tnorm
|
str | Callable
|
t-norm for AND in antecedents (default |
'min'
|
snorm
|
str | Callable
|
s-norm for OR in antecedents (default |
'max'
|
aggregation
|
str
|
how a class collects the votes of its rules — |
'max'
|
on_no_rule
|
str
|
what to predict when nothing fires — |
'majority'
|
complement
|
str | Callable
|
connective for NOT in antecedents (default |
'standard'
|
Rule text syntax (DSL)¶
fuzzytool.dsl ¶
A small text syntax for fuzzy rules.
Operator overloading (score["poor"] | dti["high"]) is the natural way to
write a rule in Python. It is not the natural way to write one in a config
file, a spreadsheet column, a web form, or an LLM's reply. This module parses
the form everybody already writes on paper:
IF score IS poor OR dti IS high THEN premium IS high
IF x IS small AND NOT (y IS large) THEN out = 3.5 WITH 0.8
IF x IS small THEN out = 1.5 + 2*x - 0.5*y
Grammar, in short: IF THEN WITH <variable> IS [NOT] <term> atoms with AND,
OR, NOT and parentheses (&, |, ~ also work); keywords are
case-insensitive and term names may be quoted if they contain spaces. The
consequent is either <variable> IS <term> (Mamdani), <name> = <number>
or <name> = <affine expression> (TSK), or <name> = <label> (classifier).
Every engine exposes this as :meth:~fuzzytool.inference._common.RuleBase.rule_from_text.
>>> import fuzzytool as fz
>>> score = fz.Variable("score", (300, 850), terms=["poor", "good"])
>>> premium = fz.Variable("premium", (0, 12), terms=["low", "high"])
>>> sys = fz.Mamdani()
>>> _ = sys.rule_from_text("IF score IS poor THEN premium IS high",
... [score, premium])
>>> sys.rules[0]
IF score is poor THEN premium is high
parse_rule ¶
Parse one rule into (antecedent, consequent, weight).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
the rule, e.g. |
required |
variables
|
object
|
the :class: |
required |
Returns:
| Type | Description |
|---|---|
tuple
|
A tuple ready to splat into any engine's |
import fuzzytool as fz from fuzzytool.dsl import parse_rule x = fz.Variable("x", (0, 10), terms=["small", "large"]) ant, cons, w = parse_rule("IF NOT x IS small THEN out = 2 + 3*x WITH 0.5", [x]) ant, cons, w ((not x is small), {'const': 2.0, 'x': 3.0}, 0.5)
Rule learning (Wang-Mendel, Chi, Chiu, c-means)¶
fuzzytool.learn ¶
Learning fuzzy rule bases from data.
Two families live here, matching the two ways a rule base is usually obtained:
Grid partition — the input space is carved up in advance into linguistic terms, and the data decides which consequent each cell gets.
- :func:
wang_mendel(1992) turns each training sample into one Mamdani rule, picking the highest-membership term per variable and resolving conflicts by rule degree. - :func:
chi(1996) is its classification counterpart: same antecedents, class labels as consequents, and a certainty factor per rule.
Scatter partition — clustering finds where the data actually is, and each cluster becomes one rule, so the rule count does not explode with the number of inputs.
- :func:
subtractive_clustering(Chiu, 1994) locates cluster centers without being told how many there are. - :func:
chiu_tskbuilds a Takagi-Sugeno system from those centers, fitting the consequents by least squares. - :func:
cmeans_tskdoes the same starting from a fuzzy c-means partition.
wang_mendel ¶
wang_mendel(X: ndarray, y: ndarray, inputs: list[Variable], output: Variable, use_weights: bool = False) -> Mamdani
Generate a Mamdani rule base from data with the Wang-Mendel method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
inputs, shape |
required |
y
|
ndarray
|
targets, shape |
required |
inputs
|
list[Variable]
|
the input linguistic variables (each pre-populated with terms);
column |
required |
output
|
Variable
|
the output linguistic variable (pre-populated with terms). |
required |
use_weights
|
bool
|
give each rule a weight equal to its (normalized) degree, so rules backed by strongly-matching samples dominate rules scraped from the edge of a partition. Off by default, matching the original method. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
Mamdani
|
class: |
Mamdani
|
antecedent (conflicts resolved by rule degree). |
chi ¶
Generate a fuzzy classifier from labelled data (Chi, Yuen & Pedrycz, 1996).
Each sample votes, with degree equal to the product of its best-matching
memberships, for the pair (antecedent cell, class). A cell's rule predicts
the class with the most votes and carries the standard certainty factor
(votes_winner - votes_rest) / votes_total — near 1 where a cell is pure,
near 0 where classes overlap, so ambiguous regions cannot outvote clean ones.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
inputs, shape |
required |
y
|
ndarray
|
class labels, shape |
required |
inputs
|
list[Variable]
|
input linguistic variables, pre-populated with terms. |
required |
min_certainty
|
float
|
drop rules whose certainty factor falls below this. |
0.0
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
FuzzyClassifier
|
class: |
import numpy as np, fuzzytool as fz from fuzzytool.learn import chi rng = np.random.default_rng(0) X = rng.uniform(0, 10, size=(200, 1)) y = np.where(X[:, 0] < 5, "low", "high") v = fz.Variable("x", (0, 10), terms=["a", "b", "c", "d"]) clf = chi(X, y, [v]) str(clf(x=1.0)), str(clf(x=9.0)) ('low', 'high')
subtractive_clustering ¶
subtractive_clustering(X: ndarray, radii: float | ndarray = 0.5, squash: float = 1.25, accept_ratio: float = 0.5, reject_ratio: float = 0.15, max_clusters: int = 50) -> np.ndarray
Chiu's subtractive clustering: find cluster centers without fixing their number.
Every data point is a candidate center, scored by how many neighbours fall
within radii. The best-scoring point becomes a center, its neighbourhood
is subtracted from the scores, and the process repeats until no candidate is
dense enough. Unlike c-means it needs no c and no initialization, which
is why it is the usual front end for building a Takagi-Sugeno rule base.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
data, shape |
required |
radii
|
float | ndarray
|
neighbourhood radius per feature, as a fraction of that feature's range (scalar or one value per feature). Smaller radii -> more, tighter clusters. |
0.5
|
squash
|
float
|
the subtraction radius as a multiple of |
1.25
|
accept_ratio
|
float
|
potentials above this fraction of the first center's are accepted outright. |
0.5
|
reject_ratio
|
float
|
potentials below this fraction are rejected outright; in between, a center is accepted only if it is far enough away. |
0.15
|
max_clusters
|
int
|
hard cap on the number of centers. |
50
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Centers in the original scale, shape |
import numpy as np from fuzzytool.learn import subtractive_clustering X = np.vstack([np.zeros((20, 2)) + 1.0, np.zeros((20, 2)) + 9.0]) subtractive_clustering(X, radii=0.3).shape (2, 2)
chiu_tsk ¶
chiu_tsk(X: ndarray, y: ndarray, radii: float | ndarray = 0.5, names: list[str] | None = None, order: int = 1, ridge: float = 0.0, **kwargs: object) -> TSK
Build a Takagi-Sugeno system from subtractive clustering (Chiu, 1994).
One rule per cluster: the antecedent is a Gaussian around the center in each
input, the consequent is fitted by least squares. The rule count follows the
structure of the data rather than the number of inputs, so this scales where
a grid partition (n_mf ** p rules) does not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
inputs, shape |
required |
y
|
ndarray
|
targets, shape |
required |
radii
|
float | ndarray
|
neighbourhood radius per feature (see :func: |
0.5
|
names
|
list[str] | None
|
input variable names (default |
None
|
order
|
int
|
|
1
|
ridge
|
float
|
Tikhonov regularization for the least-squares fit; use a small value when clusters overlap and the design matrix is ill-conditioned. |
0.0
|
**kwargs
|
object
|
forwarded to :func: |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
TSK
|
class: |
TSK
|
available as |
import numpy as np from fuzzytool.learn import chiu_tsk X = np.linspace(0, 10, 100).reshape(-1, 1) y = np.sin(X[:, 0]) sys = chiu_tsk(X, y, radii=0.25, names=["x"]) bool(abs(sys(x=3.0) - np.sin(3.0)) < 0.15) True
cmeans_tsk ¶
cmeans_tsk(X: ndarray, y: ndarray, c: int, m: float = 2.0, names: list[str] | None = None, order: int = 1, ridge: float = 0.0, seed: int | None = 0) -> TSK
Build a Takagi-Sugeno system from a fuzzy c-means partition.
The same scatter-partition idea as :func:chiu_tsk, but the clusters come
from :func:~fuzzytool.cluster.fuzzy_cmeans — so you choose the number of
rules directly, and each rule's spread is the fuzzy standard deviation of
its cluster along each input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
inputs, shape |
required |
y
|
ndarray
|
targets, shape |
required |
c
|
int
|
number of clusters, i.e. number of rules. |
required |
m
|
float
|
fuzziness exponent for c-means. |
2.0
|
names
|
list[str] | None
|
input variable names (default |
None
|
order
|
int
|
|
1
|
ridge
|
float
|
Tikhonov regularization for the least-squares fit. |
0.0
|
seed
|
int | None
|
RNG seed for the c-means initialization. |
0
|
import numpy as np from fuzzytool.learn import cmeans_tsk X = np.linspace(0, 10, 120).reshape(-1, 1) sys = cmeans_tsk(X, np.sin(X[:, 0]), c=6, names=["x"]) bool(abs(sys(x=3.0) - np.sin(3.0)) < 0.15) True
Rule-base auditing and pruning¶
fuzzytool.audit ¶
Auditing, pruning and interpretability metrics for a rule base.
A fuzzy system is supposed to be readable, but nothing stops a hand-written (or machine-learned) rule base from contradicting itself, repeating rules, leaving holes where nothing fires, or carrying terms so similar that no human can tell them apart. Those defects are invisible at the call site: the system still returns a number.
:func:audit looks for them and reports what it finds; :func:prune removes
the rules that carry no information; :func:interpretability reduces the rule
base to the handful of numbers usually quoted when comparing accuracy against
readability.
Run against the credit-risk demo, it immediately finds something a reader would not: the rule base leaves a hole for mid-range scores with low leverage, and one term is never used.
>>> from fuzzytool import datasets
>>> from fuzzytool.audit import audit
>>> system, *_ = datasets.credit_risk()
>>> report = audit(system)
>>> report.contradictions, report.duplicates
([], [])
>>> report.coverage < 1.0 and report.unused_terms
[('dti', 'low')]
>>> next(iter(report.uncovered))
{'score': 630.0, 'dti': 0.0}
AuditReport
dataclass
¶
What :func:audit found in a rule base.
Attributes:
| Name | Type | Description |
|---|---|---|
n_rules |
int
|
number of rules. |
contradictions |
list[tuple[int, int]]
|
pairs |
duplicates |
list[tuple[int, int]]
|
pairs |
never_fired |
list[int]
|
indices of rules whose firing strength stayed at 0 over the whole sampled input space — dead rules. |
unused_terms |
list[tuple[str, str]]
|
|
indistinguishable |
list[tuple[str, str, str, float]]
|
|
uncovered |
list[dict]
|
sampled input points where no rule fires at all. |
coverage |
float
|
fraction of sampled input points where some rule fires. |
partition_gaps |
list[tuple[str, float]]
|
|
audit ¶
audit(system: object, n_per_axis: int = 11, max_points: int = 4096, similarity_threshold: float = 0.8, eps: float = 1e-09) -> AuditReport
Inspect a rule base for contradictions, redundancy and coverage holes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
object
|
any engine exposing |
required |
n_per_axis
|
int
|
samples per input variable for the coverage sweep. |
11
|
max_points
|
int
|
cap on the total number of sampled input points. |
4096
|
similarity_threshold
|
float
|
Jaccard similarity above which two terms of the same variable are reported as indistinguishable. |
0.8
|
eps
|
float
|
firing strength below which a rule counts as not firing. |
1e-09
|
Returns:
| Name | Type | Description |
|---|---|---|
An |
AuditReport
|
class: |
interpretability ¶
Readability metrics for a rule base.
Returns n_rules, n_conditions (total antecedent atoms),
mean_rule_length, max_rule_length, n_input_variables,
n_terms (summed over the input variables) and coverage. Fewer,
shorter rules over fewer terms read better; report these next to accuracy
when comparing a learned rule base against a hand-written one.
from fuzzytool import datasets from fuzzytool.audit import interpretability system, *_ = datasets.credit_risk() interpretability(system)["n_rules"] 3
prune ¶
prune(system: object, drop_duplicates: bool = True, drop_never_fired: bool = False, min_weight: float = 0.0, **audit_kwargs: object) -> object
Return a copy of system without the rules that carry no information.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
object
|
the engine to prune (Mamdani, TSK, Tsukamoto or an IT2 engine). |
required |
drop_duplicates
|
bool
|
remove rules repeating an earlier rule verbatim. |
True
|
drop_never_fired
|
bool
|
remove rules that never fire anywhere on the sampled input space. Off by default: with a coarse sweep a narrow but valid rule can look dead, so switch it on deliberately. |
False
|
min_weight
|
float
|
drop rules whose weight is at or below this value. |
0.0
|
**audit_kwargs
|
object
|
forwarded to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
A new system of the same class, configured identically, holding the |
object
|
surviving rules in their original order. |
import fuzzytool as fz from fuzzytool.audit import prune x = fz.Variable("x", (0, 10), terms=["lo", "hi"]) y = fz.Variable("y", (0, 10), terms=["lo", "hi"]) sys = fz.Mamdani().rule(x["lo"], y["hi"]).rule(x["lo"], y["hi"]) len(prune(sys).rules) 1
Measures on fuzzy sets¶
fuzzytool.measures ¶
Measures on fuzzy sets: descriptors, distances, similarity, entropy.
Everything here works on sampled membership degrees — a 1-D array of values in
[0, 1] over a shared universe, which is exactly what
variable.terms[name](variable.universe) gives you (or :func:sample, which
accepts either a membership function or an array).
These measures are the building blocks for comparing fuzzy sets, and the rule
base auditing in :mod:fuzzytool.audit uses them to decide when two terms or
two rules say the same thing.
>>> import numpy as np, fuzzytool as fz
>>> from fuzzytool import measures as ms
>>> x = np.linspace(0, 10, 501)
>>> a, b = ms.sample(fz.tri(2, 4, 6), x), ms.sample(fz.tri(3, 5, 7), x)
>>> round(ms.jaccard(a, b), 3)
0.391
sample ¶
Membership degrees as an array, from a callable MF or an array.
IT2 sets collapse to the midpoint of their footprint, so every measure here applies to type-1 and interval type-2 terms alike.
is_normal ¶
Whether the set reaches membership 1 somewhere (is normal).
support ¶
Bounds (lo, hi) of the support — where membership is strictly positive.
Returns (nan, nan) for the empty set.
core ¶
Bounds (lo, hi) of the core — where membership equals 1.
alpha_cut ¶
Bounds (lo, hi) of the alpha-cut {x : mu(x) >= alpha}.
import numpy as np, fuzzytool as fz from fuzzytool import measures as ms x = np.linspace(0, 10, 1001) lo, hi = ms.alpha_cut(x, ms.sample(fz.tri(2, 4, 6), x), 0.5) round(lo, 2), round(hi, 2) (3.0, 5.0)
relative_cardinality ¶
Sigma-count divided by the number of samples (a value in [0, 1]).
hamming ¶
Hamming distance Σ |a - b| (mean absolute difference if normalized).
euclidean ¶
Euclidean distance sqrt(Σ (a - b)^2) (RMS difference if normalized).
minkowski ¶
Minkowski distance of order p (p = 1 Hamming, p = 2 Euclidean).
jaccard ¶
Jaccard similarity |A ∩ B| / |A ∪ B| with min/max intersection-union.
1 for identical sets, 0 for sets with disjoint supports.
subsethood ¶
Kosko's subsethood: the degree to which A is contained in B.
|A ∩ B| / |A|, so it is 1 exactly when a <= b everywhere.
consistency ¶
Consistency (possibility) of two sets: max_x min(a(x), b(x)).
0 means the two sets cannot both hold — the basis for detecting rules whose antecedents can never fire together.
fuzzy_entropy ¶
De Luca-Termini entropy, normalized to [0, 1].
0 for a crisp set (every degree 0 or 1), 1 when every degree is 0.5 — the point of maximum vagueness.
index_of_fuzziness ¶
Kaufmann's index of fuzziness: normalized distance to the nearest crisp set.
kind is "linear" (Hamming) or "quadratic" (Euclidean). 0 for a
crisp set, 1 when every degree is 0.5.
Fuzzy relations¶
fuzzytool.relations ¶
Fuzzy relations and the compositional rule of inference.
A fuzzy relation on X x Y is a fuzzy set of pairs: a matrix R where
R[i, j] grades how strongly x_i relates to y_j. Relations are the
other half of classical fuzzy logic — the half that the rule-based engines in
:mod:fuzzytool.inference hide behind an efficient shortcut.
This module makes that half explicit and lets you work with it directly:
- build a relation from two fuzzy sets (:func:
cartesian) or from an implication (:func:implication_relation); - combine relations (:func:
compose,max-minby default); - apply one to a fuzzy set — the compositional rule of inference
(:func:
cri), which is what "IF x is A THEN y is B" means before any engine optimizes it away; - project, extend, invert and close relations.
Everything is plain NumPy, and every operation takes the connectives by name, so a relation composed with the product t-norm is one argument away.
>>> import numpy as np, fuzzytool as fz
>>> from fuzzytool import relations as rel
>>> x = np.linspace(0, 10, 101)
>>> a, b = fz.tri(2, 4, 6)(x), fz.tri(5, 7, 9)(x)
>>> r = rel.cartesian(a, b) # "IF x is A THEN y is B"
>>> out = rel.cri(a, r) # feeding A back in recovers B
>>> bool(np.allclose(out, b))
True
cartesian ¶
Cartesian product A x B: the relation R[i, j] = T(a_i, b_j).
This is Mamdani's reading of a fuzzy rule — the relation induced by "IF x is A THEN y is B".
compose ¶
Sup-t composition of relations R ∘ S (max-min by default).
R is (n, m) on X x Y and S is (m, k) on Y x Z; the
result is (n, k) on X x Z, aggregating over the shared Y.
cri ¶
Compositional rule of inference: B' = A' ∘ R.
Given an observed fuzzy input A' and a relation R, produce the
induced fuzzy output. With R = cartesian(A, B) and A' = A this
returns B — the fuzzy-relational statement of modus ponens.
projection ¶
Project a relation onto one of its universes (axis=1 keeps X).
The projection of R onto X is sup_y R(x, y): the shadow the
relation casts on that axis.
cylindrical_extension ¶
Extend a fuzzy set to a relation that ignores the other universe.
With axis=1 the set indexes the rows and is repeated across n
columns. This is the inverse of :func:projection and the standard way to
make two sets defined on different universes comparable.
union ¶
Elementwise union of two relations on the same universes.
intersection ¶
Elementwise intersection of two relations on the same universes.
implication_relation ¶
The relation induced by "IF x is A THEN y is B" under a given implication.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
ndarray
|
antecedent membership over |
required |
b
|
ndarray
|
consequent membership over |
required |
kind
|
str
|
|
'mamdani'
|
The first two are the conjunctive readings the Mamdani engine uses; the rest are genuine implications, which behave differently when the antecedent does not fire.
transitive_closure ¶
Max-min (or sup-t) transitive closure of a square relation.
Repeatedly unions R with R ∘ R until nothing changes. Applied to a
fuzzy proximity relation (reflexive and symmetric) this yields a fuzzy
equivalence relation, whose alpha-cuts are a hierarchy of crisp
partitions — relational fuzzy clustering in three lines.
is_transitive ¶
Whether R ∘ R <= R (sup-t transitivity).
Fuzzy numbers¶
fuzzytool.fuzzynum ¶
Fuzzy numbers and their arithmetic.
A fuzzy number is a convex, normal fuzzy set on the reals. This module provides
the two most used shapes — triangular (TFN) and trapezoidal (TrFN) — with
standard fuzzy arithmetic (+ - * / and scalar scaling), alpha-cuts, a crisp
(centroid) value, and a vertex distance used by fuzzy MCDM (see
:mod:fuzzytool.mcdm).
Multiplication and division use the common positive-support approximation
(operate on the ordered defining points), which is exact for +/- and a
good approximation for *// when supports are positive.
FuzzyNumber ¶
Base class for fuzzy numbers defined by ordered points p.
distance ¶
Vertex distance to another fuzzy number of the same shape.
TriangularFuzzyNumber ¶
TrapezoidalFuzzyNumber ¶
tfn ¶
Shortcut for :class:TriangularFuzzyNumber.
trfn ¶
Shortcut for :class:TrapezoidalFuzzyNumber.
rank ¶
Indices that sort numbers from largest to smallest by centroid.
Fuzzy MCDM¶
fuzzytool.mcdm ¶
Fuzzy multi-criteria decision making (MCDM).
- :func:
fuzzy_topsis— Chen's (2000) fuzzy TOPSIS: rank alternatives by their closeness to the fuzzy positive-ideal solution. - :func:
fuzzy_ahp— Chang's (1996) extent-analysis AHP: derive crisp criterion weights from a triangular-fuzzy pairwise-comparison matrix.
Inputs are triangular fuzzy numbers (:class:~fuzzytool.fuzzynum.TriangularFuzzyNumber).
TopsisResult
dataclass
¶
Result of :func:fuzzy_topsis.
Attributes:
| Name | Type | Description |
|---|---|---|
closeness |
ndarray
|
closeness coefficient |
ranking |
list[int]
|
alternative indices ordered best-to-worst. |
d_plus |
ndarray
|
distance to the fuzzy positive-ideal solution per alternative. |
d_minus |
ndarray
|
distance to the fuzzy negative-ideal solution per alternative. |
fuzzy_topsis ¶
Rank alternatives with Chen's fuzzy TOPSIS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
matrix
|
list
|
|
required |
weights
|
list
|
|
required |
benefit
|
list
|
length- |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
TopsisResult
|
class: |
fuzzy_ahp ¶
Crisp criterion weights from a fuzzy pairwise matrix (Chang's method).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
matrix
|
list
|
|
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
A length- |
Serialization¶
fuzzytool.serialize ¶
Save and load fuzzy inference systems as JSON.
Every rule-based engine round-trips: :class:~fuzzytool.inference.Mamdani,
:class:~fuzzytool.inference.TSK, :class:~fuzzytool.inference.Tsukamoto,
:class:~fuzzytool.classify.FuzzyClassifier and the interval type-2 engines.
The requirements are that the connectives/defuzzifier were given by name
(a bare callable has no portable representation) and that every membership
function is registered — the built-ins are, and custom shapes join them
through :func:fuzzytool.membership.register.
fz.save(system, "system.json")
system = fz.load("system.json")
The file records a format_version; a file written by a newer, incompatible
version of fuzzytool is rejected with a clear error rather than silently
misread.
Interval type-2 — sets¶
fuzzytool.type2.sets ¶
Interval type-2 (IT2) membership functions.
An :class:IntervalType2MF carries a lower (LMF) and an upper (UMF) type-1
membership function. Calling it returns the membership interval
(lower, upper); the engines also call .lower(x) / .upper(x) directly.
The constructors cover the standard ways of building an FOU from a type-1 set:
- :func:
it2— explicit LMF/UMF; - :func:
it2_scale— height uncertainty (LMF is a scaled-down UMF); - :func:
it2_gauss_uncertain_mean— a Gaussian with mean in[c1, c2]; - :func:
it2_gauss_uncertain_std— a Gaussian with uncertain spread.
IntervalType2MF ¶
An IT2 set defined by a lower (LMF) and an upper (UMF) type-1 MF.
The UMF must dominate the LMF everywhere (LMF(x) <= UMF(x)); this is not
enforced at construction (it would require sampling the universe) but holds
for every set built through the constructors below.
it2 ¶
An IT2 set from explicit lower and upper type-1 membership functions.
it2_scale ¶
Height-uncertainty FOU: UMF is mf, LMF is scale * mf (0 < scale ≤ 1).
it2_gauss_uncertain_mean ¶
Gaussian with an uncertain mean spanning [c1, c2] (c1 < c2).
The UMF is the upper envelope of all Gaussians with mean in [c1, c2]
(flat-topped at 1 between the means); the LMF is their lower envelope.
it2_gauss_uncertain_std ¶
Gaussian with an uncertain spread: UMF uses the wider σ, LMF the narrower.
Interval type-2 — inference¶
fuzzytool.type2.inference ¶
Interval type-2 inference engines.
Both engines evaluate each rule's antecedent to a firing interval
[f_low, f_high] (via :meth:Antecedent.eval_interval) and then collapse the
rule base with type reduction, returning the midpoint of the type-reduced
interval [y_l, y_r].
- :class:
IT2Mamdaniuses center-of-sets type reduction: each consequent IT2 set is summarized by its centroid interval (computed once and cached), and the reducer combines those centroids weighted by the firing intervals. - :class:
IT2TSKhas crisp consequents (numbers, coefficient mappings, or callables) and type-reduces them directly.
The reducer is pluggable (reducer="km" | "eiasc" | "nie-tan", or any
callable) exactly like the type-1 defuzzifiers, and both engines share the
type-1 engines' predict / on_no_rule / explain contract.
IT2Mamdani ¶
Bases: _IT2Base
Interval type-2 Mamdani inference (center-of-sets type reduction).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tnorm
|
str | Callable
|
t-norm for AND in antecedents (default |
'min'
|
snorm
|
str | Callable
|
s-norm for OR in antecedents (default |
'max'
|
reducer
|
str | Callable
|
type reducer — |
'km'
|
on_no_rule
|
str
|
what to return when no rule fires — |
'mid'
|
complement
|
str | Callable
|
connective for NOT in antecedents (default |
'standard'
|
rule ¶
Add IF antecedent THEN output is term and return self.
__call__ ¶
Run inference. Returns a float for one output, else a dict by name.
predict ¶
Vectorized inference over array-valued inputs (n per keyword).
The antecedents are evaluated in batch; type reduction is inherently per-sample, so it still loops — but each membership function is touched once for the whole batch instead of once per sample.
IT2TSK ¶
Bases: _IT2Base
Interval type-2 Takagi-Sugeno inference (type reduction over crisp consequents).
Consequents follow the same convention as the type-1 :class:~fuzzytool.inference.tsk.TSK
engine: a number, a coefficient mapping {"const": b0, "x": b1, ...}, or a
callable f(**inputs) -> float.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tnorm
|
str | Callable
|
t-norm for AND in antecedents (default |
'min'
|
snorm
|
str | Callable
|
s-norm for OR in antecedents (default |
'max'
|
reducer
|
str | Callable
|
type reducer — |
'km'
|
on_no_rule
|
str
|
|
'nan'
|
complement
|
str | Callable
|
connective for NOT in antecedents (default |
'standard'
|
Interval type-2 — type reduction¶
fuzzytool.type2.reduction ¶
Karnik-Mendel type reduction.
Type reduction turns the interval-valued output of an IT2 system into a crisp
interval [y_l, y_r] (typically defuzzified as its midpoint). The
Karnik-Mendel (KM) algorithm finds each endpoint by locating the switch point
where the per-point weight flips between its lower and upper bound.
The single primitive :func:km_endpoint is reused everywhere: for an IT2 set's
centroid (points = universe samples, weights = the FOU) and for an IT2 rule base
(points = consequent centroids, weights = rule firing intervals).
km_endpoint ¶
km_endpoint(points: ndarray, lower: ndarray, upper: ndarray, side: str = 'l', max_iter: int = 100) -> float
One endpoint of the type-reduced interval via Karnik-Mendel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
ndarray
|
the y-values (e.g. consequent centroids or universe samples). |
required |
lower
|
ndarray
|
per-point weight lower bounds (same shape as |
required |
upper
|
ndarray
|
per-point weight upper bounds (same shape as |
required |
side
|
str
|
|
'l'
|
max_iter
|
int
|
safeguard on the Karnik-Mendel iteration count. |
100
|
Left endpoint uses the upper weights to the left of the switch and the lower weights to its right; the right endpoint mirrors this.
karnik_mendel ¶
Type-reduce to the interval (y_l, y_r) over a shared set of points.
eiasc ¶
Type-reduce with EIASC (Wu & Nie, 2011).
The Enhanced Iterative Algorithm with Stop Condition computes the very same
interval as :func:karnik_mendel but walks the sorted points from one end
and stops at the switch point, so it needs no repeated full re-averaging.
In practice it is the fastest exact type reducer for rule bases of the size
fuzzy systems actually use.
import numpy as np from fuzzytool.type2 import eiasc, karnik_mendel pts, lo, hi = np.array([1., 3., 6.]), np.array([.2, .4, .1]), np.array([.6, .9, .5]) np.allclose(eiasc(pts, lo, hi), karnik_mendel(pts, lo, hi)) True
nie_tan ¶
Nie-Tan type reduction: the centroid of the FOU's average membership.
A closed-form approximation — one pass, no iteration — that returns a
degenerate interval (y, y). Accurate enough for control loops where the
exact switch points do not matter, and typically an order of magnitude
cheaper than Karnik-Mendel.
get_type_reducer ¶
Resolve a type reducer by name (or pass a callable through unchanged).
A reducer maps (points, lower, upper) to the interval (y_l, y_r).
centroid_it2 ¶
Centroid interval (c_l, c_r) of an IT2 set over universe.
A plain type-1 membership function collapses to a degenerate footprint
(lower == upper), so type-1 and IT2 consequents mix freely in the same
rule base — just as they already do in antecedents.
General type-2 (zSlices)¶
fuzzytool.type2.general ¶
General type-2 (GT2) fuzzy sets via the zSlices / alpha-plane representation.
An interval type-2 set treats every point inside its footprint of uncertainty (FOU) as equally possible. A general type-2 set adds a secondary membership that weights those possibilities — the third dimension IT2 throws away.
A practical, exact way to handle that third dimension is the zSlices (alpha
-plane) decomposition (Wagner & Hagras): slice the secondary domain at levels
z and, at each level, the GT2 set reduces to an ordinary IT2 set whose FOU is
the z-cut. Inference and type reduction then run the existing IT2 machinery
on each slice and combine the results, weighted by z.
Here a GT2 set is built from an IT2 footprint plus a triangular secondary
peaking at the FOU's principal (mid) MF: at z -> 0 a slice is the full FOU,
at z = 1 it collapses to the principal MF.
- :class:
GeneralType2MF— a stack of IT2 z-slices (also exposes the overall FOU vialower/upper, so it degrades gracefully to IT2). - :func:
gt2_from_it2, :func:gt2_gauss_uncertain_mean, :func:gt2_scale— constructors. - :class:
GeneralType2Mamdani— z-weighted stack of IT2 Mamdani inferences. - :func:
centroid_gt2— zSlices centroid of a single GT2 set.
GeneralType2MF ¶
A general type-2 set represented as a stack of IT2 z-slices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slices
|
list[IntervalType2MF]
|
IT2 sets, one per |
required |
zlevels
|
ndarray
|
the secondary levels (in |
required |
footprint
|
IntervalType2MF
|
the overall FOU as an IT2 set (used for |
required |
GeneralType2Mamdani ¶
General type-2 Mamdani inference via the zSlices decomposition.
At each z level the GT2 terms collapse to their IT2 z-slice and the
existing :class:~fuzzytool.type2.inference.IT2Mamdani engine
(center-of-sets type reduction) produces a crisp output y(z). The final
result is the z-weighted average Σ z·y(z) / Σ z — the centroid of the
type-reduced general type-2 output.
Variables may mix GT2 and IT2/type-1 terms; only the GT2 terms are sliced.
All GT2 terms must share the same z levels (same n_slices).
gt2_from_it2 ¶
Build a GT2 set from an IT2 FOU with a triangular secondary MF.
The secondary membership peaks at the principal MF (the midline of the FOU)
and falls linearly to 0 at the LMF/UMF. The z-slice at level z is the
IT2 set whose FOU is narrowed from the full footprint toward that midline by
a factor (1 - z).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
footprint
|
IntervalType2MF
|
the IT2 set describing the full footprint of uncertainty. |
required |
n_slices
|
int
|
number of |
5
|
gt2_gauss_uncertain_mean ¶
GT2 Gaussian with an uncertain mean in [c1, c2] (triangular secondary).
gt2_scale ¶
GT2 set from a height-uncertainty FOU (UMF mf, LMF scale * mf).
centroid_gt2 ¶
zSlices centroid of a GT2 set: z-weighted mean of per-slice IT2 centroids.
Fuzzy clustering¶
fuzzytool.cluster ¶
Fuzzy clustering.
Unlike crisp k-means, fuzzy clustering lets each sample belong to several
clusters with graded membership. This module provides three algorithms and a
small set of validity metrics, all on plain NumPy arrays of shape
(n_samples, n_features):
- :func:
fuzzy_cmeans— Bezdek's FCM (spherical clusters, Euclidean norm). - :func:
gustafson_kessel— GK, an adaptive norm per cluster that captures ellipsoidal shapes. - :func:
possibilistic_cmeans— PCM, which drops the "memberships sum to 1" constraint so outliers get low typicality in every cluster.
Each returns a :class:ClusterResult. Every algorithm accepts a seed for
reproducibility.
ClusterResult
dataclass
¶
Outcome of a fuzzy clustering run.
Attributes:
| Name | Type | Description |
|---|---|---|
centers |
ndarray
|
cluster prototypes, shape |
u |
ndarray
|
membership/typicality matrix, shape |
n_iter |
int
|
iterations run until convergence. |
objective |
float
|
final value of the algorithm's objective function. |
labels |
ndarray
|
hard assignment |
fuzzy_cmeans ¶
fuzzy_cmeans(X: ndarray, c: int, m: float = 2.0, max_iter: int = 150, tol: float = 1e-05, seed: int | None = None) -> ClusterResult
Bezdek's fuzzy c-means.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
data, shape |
required |
c
|
int
|
number of clusters. |
required |
m
|
float
|
fuzziness exponent ( |
2.0
|
max_iter
|
int
|
maximum iterations. |
150
|
tol
|
float
|
stop when the membership matrix changes by less than this (max-norm). |
1e-05
|
seed
|
int | None
|
RNG seed for the membership initialization. |
None
|
gustafson_kessel ¶
gustafson_kessel(X: ndarray, c: int, m: float = 2.0, max_iter: int = 150, tol: float = 1e-05, seed: int | None = None, reg: float = 1e-10) -> ClusterResult
Gustafson-Kessel clustering (adaptive per-cluster Mahalanobis norm).
Each cluster learns a covariance-shaped, unit-determinant norm, so GK fits ellipsoidal clusters that FCM (fixed spherical norm) cannot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
data, shape |
required |
c
|
int
|
number of clusters. |
required |
m
|
float
|
fuzziness exponent ( |
2.0
|
max_iter
|
int
|
maximum iterations. |
150
|
tol
|
float
|
convergence threshold on the membership matrix (max-norm). |
1e-05
|
seed
|
int | None
|
RNG seed for initialization. |
None
|
reg
|
float
|
ridge added to each fuzzy covariance for numerical stability. |
1e-10
|
possibilistic_cmeans ¶
possibilistic_cmeans(X: ndarray, c: int, m: float = 2.0, max_iter: int = 150, tol: float = 1e-05, seed: int | None = None, k: float = 1.0) -> ClusterResult
Possibilistic c-means (Krishnapuram-Keller).
PCM drops the probabilistic constraint that memberships sum to 1: each value
is a typicality in [0, 1], so noise points score low everywhere. It is
initialized from an FCM run, which also fixes each cluster's scale eta.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
data, shape |
required |
c
|
int
|
number of clusters. |
required |
m
|
float
|
fuzziness exponent ( |
2.0
|
max_iter
|
int
|
maximum iterations. |
150
|
tol
|
float
|
convergence threshold on the typicality matrix (max-norm). |
1e-05
|
seed
|
int | None
|
RNG seed (passed to the initializing FCM). |
None
|
k
|
float
|
scale multiplier for the bandwidth |
1.0
|
partition_coefficient ¶
Bezdek's partition coefficient in (1/c, 1]; higher = crisper.
partition_entropy ¶
Partition entropy in [0, log c); lower = crisper.
xie_beni ¶
Xie-Beni index (compactness / separation); lower is better.
ANFIS¶
fuzzytool.anfis ¶
ANFIS — an adaptive network-based fuzzy inference system (Jang, 1993).
ANFIS is a first-order Takagi-Sugeno system whose parameters are learned from data. Each rule fires with the product of its inputs' Gaussian memberships and emits an affine function of the inputs.
Training uses Jang's hybrid scheme, one pass per epoch:
- with the premise (Gaussian) parameters fixed, the consequent (affine) parameters are solved in closed form by least squares — the output is linear in them;
- with the consequents fixed, the premise centers and widths take a gradient-descent step on the mean squared error.
The rules come from one of two partitions:
partition="grid"— the classic layout:n_mfmembership functions per input, one rule per combination, son_mf ** prules. Readable, but only for a handful of inputs.partition="cluster"— one rule per fuzzy c-means cluster, each with its own Gaussian per input. The rule count is chosen directly (n_rules) and does not grow with the number of inputs, which is what makes ANFIS usable beyond three or four features.
Pure NumPy.
ANFIS ¶
A trainable first-order Sugeno system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_inputs
|
int
|
number of input features |
required |
n_mf
|
int
|
Gaussian membership functions per input, for the grid partition
(rules = |
3
|
learning_rate
|
float
|
step size for the premise gradient updates. |
0.05
|
partition
|
str
|
|
'grid'
|
n_rules
|
int
|
number of rules when |
8
|
ridge
|
float | None
|
Tikhonov regularization added to the consequent least-squares
solve. The design matrix has |
None
|
tol
|
float
|
stop early when the training RMSE improves by less than this for
|
0.0
|
patience
|
int
|
how many stagnant epochs to tolerate before stopping. |
5
|
max_rules
|
int
|
guard against an accidentally enormous grid partition. |
20000
|
seed
|
int | None
|
RNG seed for the clustering initialization. |
0
|
to_tsk ¶
Export the trained model as a plain :class:~fuzzytool.inference.TSK.
The result is an ordinary fuzzy system — inspectable, serializable, explainable — rather than a fitted estimator, so a trained ANFIS can be audited or saved like any hand-written rule base.
import numpy as np from fuzzytool import ANFIS X = np.linspace(0, 6, 60).reshape(-1, 1) model = ANFIS(1, n_mf=3).fit(X, np.sin(X[:, 0]), epochs=20) sys = model.to_tsk(["x"]) direct = model.predict(np.full((1, 1), 2.0)).item() bool(abs(sys(x=2.0) - direct) < 1e-9) True
get_params ¶
Hyperparameters, for scikit-learn compatibility (Pipeline/GridSearch).
set_params ¶
Set hyperparameters (resets trained state), returning self.
F-transform¶
fuzzytool.ftransform ¶
F-transform — the fuzzy transform of Perfilieva.
The F-transform projects a function onto a fuzzy partition of its domain. Over
a uniform partition by triangular basis functions A_1..A_n that satisfy the
Ruspini condition (they sum to 1 everywhere on [a, b]):
- the direct transform reduces the data to
ncomponents, each a membership-weighted average of the samples falling under one basis function; - the inverse transform reconstructs an approximation
f̂(x) = Σ_k F_k · A_k(x).
With few components the round trip smooths/denoises a signal; with many it approximates it closely. Pure NumPy.
FTransform ¶
A triangular F-transform over a uniform fuzzy partition of [a, b].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
float
|
left end of the domain. |
required |
b
|
float
|
right end of the domain ( |
required |
n_basis
|
int
|
number of basis functions / components ( |
required |
Visualization¶
fuzzytool.viz ¶
Matplotlib visualization helpers.
Importing this module requires matplotlib (an optional dependency; install
with pip install fuzzytool[viz]). Visualization is a first-class priority of
the project, mirroring its sibling turboswarm.
plot_variable ¶
Plot every term's membership function over the variable's universe.
plot_it2_variable ¶
Plot an IT2 variable: each term's lower/upper MF with a shaded FOU.
Type-1 terms (if any are mixed in) are drawn as a single line.
control_surface ¶
Plot a system's output as a surface over two input variables.
system must be callable as system(**{x_var.name: x, y_var.name: y})
and return a scalar (single-output Mamdani or TSK).
plot_clusters ¶
Scatter 2-D data colored by hard label, with cluster centers marked.
result is a :class:~fuzzytool.cluster.ClusterResult. Point opacity
encodes the top membership, so fuzzy/boundary points appear fainter.
Integrations — pandas¶
fuzzytool.integrations.pandas ¶
pandas integration: DataFrame in/out and tabular views of fuzzy objects.
Install with pip install fuzzytool[pandas]. Nothing here imports pandas at
module load beyond a guarded check, so the rest of the toolkit stays dependency
-free.
- :func:
predict_df— batch inference straight from a DataFrame. - :func:
rules_dataframe— a readable table of a system's rule base. - :func:
memberships_dataframe— a clustering result's membership matrix. - :func:
components_dataframe— an F-transform's components.
predict_df ¶
predict_df(system: Mamdani | TSK, df: DataFrame, columns: list[str] | None = None) -> pd.Series | pd.DataFrame
Run vectorized inference over a DataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
Mamdani | TSK
|
a :class: |
required |
df
|
DataFrame
|
a DataFrame whose columns include the system's input variables. |
required |
columns
|
list[str] | None
|
input column names to use, in order. Defaults to the variables
the system's rules reference (see
:func: |
None
|
Returns:
| Type | Description |
|---|---|
Series | DataFrame
|
A |
Series | DataFrame
|
|
rules_dataframe ¶
Return the system's rule base as a DataFrame (one row per rule).
Columns: antecedent (the IF part as text), consequent (the
THEN part) and weight.
memberships_dataframe ¶
Return a clustering result's membership matrix as a DataFrame.
One row per sample, one column {prefix}_{k} per cluster, plus a label
column holding the hard (argmax) assignment.
components_dataframe ¶
Return an :class:~fuzzytool.ftransform.FTransform's components as a DataFrame.
Columns: node (the basis-function center) and component (its value).
Integrations — scikit-learn¶
fuzzytool.integrations.sklearn ¶
scikit-learn integration: fuzzy systems as estimators and transformers.
These objects follow the scikit-learn estimator protocol (fit returns
self, plus predict/transform, get_params/set_params and a
score/fit_transform), so they slot into a Pipeline, GridSearchCV
or cross_val_score — yet, like :class:~fuzzytool.anfis.ANFIS, they do not
import scikit-learn. The [sklearn] extra simply installs scikit-learn so
you have those tools available:
pip install fuzzytool[sklearn]
- :class:
Fuzzifier— a transformer turning crisp features into fuzzy membership-degree features (one column per term). - :class:
WangMendelRegressor— learns a Mamdani rule base from data (Wang-Mendel) and predicts with it. - :class:
FuzzySystemRegressor— wraps an already-built Mamdani/TSK system so it can be scored or pipelined. - :class:
ChiClassifier— learns an interpretable fuzzy rule-based classifier (Chi et al.), withpredict_proba. - :class:
FuzzySystemClassifier— wraps an already-built :class:~fuzzytool.classify.FuzzyClassifier.
Fuzzifier ¶
Bases: _Estimator
Expand crisp features into fuzzy membership-degree features.
Column j of X is interpreted through variables[j]: each of that
variable's terms becomes one output column holding the membership degree.
The result is a soft, interpretable encoding that downstream linear or tree
models can consume.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variables
|
list[Variable]
|
one :class: |
required |
WangMendelRegressor ¶
Bases: _Estimator
A regressor that learns a Mamdani rule base from data (Wang-Mendel).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
list[Variable]
|
input linguistic variables (pre-populated with terms), one per
column of |
required |
output
|
Variable
|
the output linguistic variable (pre-populated with terms). |
required |
FuzzySystemRegressor ¶
Bases: _Estimator
Wrap an already-built single-output fuzzy system as an sklearn regressor.
fit does not change the system (it is already defined); it only records
the input column order. Use this to cross-validate, score, or pipeline a
hand-written Mamdani/TSK system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
Mamdani | TSK
|
a single-output Mamdani or TSK system. |
required |
columns
|
list[str] | None
|
input variable names matching |
None
|
ChiClassifier ¶
Bases: _Estimator
A fuzzy rule-based classifier learned from data (Chi et al.).
Fits an interpretable rule base — one rule per occupied cell of the input
partition, each with a certainty factor — and classifies with it. Unlike a
black-box classifier, self.system_.summary() prints the model, and
self.system_.explain(...) says which rules decided a given prediction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
list[Variable]
|
input linguistic variables (pre-populated with terms), one per
column of |
required |
min_certainty
|
float
|
drop learned rules below this certainty factor. |
0.0
|
aggregation
|
str
|
|
'max'
|
FuzzySystemClassifier ¶
Bases: _Estimator
Wrap an already-built :class:~fuzzytool.classify.FuzzyClassifier as an estimator.
fit does not change the rule base; it only records the input column
order, so a hand-written classifier can be cross-validated or pipelined.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
object
|
the classifier to wrap. |
required |
columns
|
list[str] | None
|
input variable names matching |
None
|
Integrations — SciPy¶
fuzzytool.integrations.scipy ¶
SciPy integration: tune a fuzzy system's membership functions to data.
Install with pip install fuzzytool[scipy]. :func:tune adjusts the
parameters of a system's built-in membership functions so its output fits a
dataset, using :func:scipy.optimize.least_squares. It mutates the system in
place and returns SciPy's OptimizeResult (.x, .cost, .nfev,
.success …) — a drop-in way to refine a hand-written Mamdani/TSK system.
Only built-in membership shapes (tri, trap, gauss, gbell,
sigmoid, ramp_up, ramp_down) are tunable; custom callables are left
untouched. Shape validity is preserved every iteration (breakpoints are kept
ordered, widths kept positive), so the optimizer explores freely without
producing degenerate sets.
tune ¶
tune(system: Mamdani | TSK, X: ndarray, y: ndarray, columns: list[str] | None = None, tune_outputs: bool = True, **least_squares_kwargs) -> OptimizeResult
Fit a system's membership-function parameters to data.
Any extra keyword arguments are forwarded to
:func:scipy.optimize.least_squares (e.g. max_nfev).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
Mamdani | TSK
|
a Mamdani or TSK system (mutated in place with the best params). |
required |
X
|
ndarray
|
inputs, shape |
required |
y
|
ndarray
|
targets, shape |
required |
columns
|
list[str] | None
|
input variable names matching |
None
|
tune_outputs
|
bool
|
also tune the output variables' MFs (Mamdani only). |
True
|
Returns:
| Type | Description |
|---|---|
OptimizeResult
|
The SciPy |
Integrations — turboswarm (PSO)¶
fuzzytool.integrations.turboswarm ¶
turboswarm integration: tune a fuzzy system with Particle Swarm Optimization.
Install with pip install fuzzytool[turboswarm]. :func:tune fits the
parameters of a system's built-in membership functions to data with
turboswarm <https://pypi.org/project/turboswarm/>_'s PSO — a gradient-free,
global optimizer. It is the metaheuristic sibling of
:func:fuzzytool.integrations.scipy.tune (local least-squares): slower, but it
escapes poor local optima and needs no derivatives, which suits the rugged,
non-smooth error surfaces fuzzy rule bases often produce.
Each parameter is searched within a box of ±margin × universe around its
current value; shape validity (ordered breakpoints, positive widths) is enforced
every evaluation, so the swarm explores freely. The system is mutated in place
with the best parameters found and the turboswarm PsoResult is returned.
tune ¶
tune(system: Mamdani | TSK, X: ndarray, y: ndarray, columns: list[str] | None = None, tune_outputs: bool = True, margin: float = 0.5, n_particles: int = 30, max_iter: int = 100, seed: int | None = None, **minimize_kwargs) -> PsoResult
Fit a system's membership-function parameters to data with PSO.
Extra keyword arguments are forwarded to :func:turboswarm.minimize
(e.g. topology, patience, max_time).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
Mamdani | TSK
|
a Mamdani or TSK system (mutated in place with the best params). |
required |
X
|
ndarray
|
inputs, shape |
required |
y
|
ndarray
|
targets, shape |
required |
columns
|
list[str] | None
|
input variable names matching |
None
|
tune_outputs
|
bool
|
also tune the output variables' MFs (Mamdani only). |
True
|
margin
|
float
|
search half-width per parameter as a fraction of its variable's
universe span ( |
0.5
|
n_particles
|
int
|
swarm size. |
30
|
max_iter
|
int
|
PSO iterations. |
100
|
seed
|
int | None
|
PSO seed for reproducibility. |
None
|
Returns:
| Type | Description |
|---|---|
PsoResult
|
The turboswarm |
Integrations — Optuna¶
fuzzytool.integrations.optuna ¶
Optuna integration: search fuzzy-system structure and hyperparameters.
Install with pip install fuzzytool[optuna]. Fuzzy systems have plenty of
discrete/continuous knobs — which t-norm, which defuzzifier, how many membership
functions, what learning rate — that are awkward to grid-search by hand. These
helpers turn an Optuna trial into a configured system, plus a ready-made
:func:tune_anfis study.
- :func:
suggest_inference_spec— sample the connectives/implication/defuzz of a Mamdani system from a trial. - :func:
suggest_anfis— build an :class:~fuzzytool.anfis.ANFISwith a trial-suggestedn_mfandlearning_rate. - :func:
tune_anfis— run a full study that minimizes training RMSE and returns the best (refit) model.
suggest_inference_spec ¶
Sample a Mamdani spec {tnorm, snorm, implication, defuzz} from a trial.
prefix namespaces the parameter names so several specs can coexist in one
study. Pass the result straight to :class:~fuzzytool.inference.Mamdani.
suggest_anfis ¶
suggest_anfis(trial, n_inputs: int, n_mf_range: tuple[int, int] = (2, 5), lr_range: tuple[float, float] = (0.001, 0.2)) -> ANFIS
Build an ANFIS with a trial-suggested n_mf and learning_rate.
tune_anfis ¶
tune_anfis(X: ndarray, y: ndarray, n_trials: int = 20, n_mf_range: tuple[int, int] = (2, 5), lr_range: tuple[float, float] = (0.001, 0.2), epochs: int = 100, seed: int | None = None) -> tuple
Tune an ANFIS's n_mf/learning_rate with an Optuna study.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
inputs, shape |
required |
y
|
ndarray
|
targets, shape |
required |
n_trials
|
int
|
number of Optuna trials. |
20
|
n_mf_range
|
tuple[int, int]
|
|
(2, 5)
|
lr_range
|
tuple[float, float]
|
|
(0.001, 0.2)
|
epochs
|
int
|
training epochs per trial. |
100
|
seed
|
int | None
|
seed for Optuna's sampler (reproducible search). |
None
|
Returns:
| Type | Description |
|---|---|
tuple
|
A |
tuple
|
hyperparameters, and the Optuna study. |
Integrations — Joblib / Dask¶
fuzzytool.integrations.parallel ¶
Parallel execution helpers (Joblib and Dask).
Install with pip install fuzzytool[parallel] (Joblib) or
fuzzytool[dask] (Dask). These spread embarrassingly-parallel fuzzy workloads
across cores or a Dask cluster:
- :func:
parallel_predict— chunked batch inference with Joblib. - :func:
multi_start_cmeans— run fuzzy c-means from many seeds in parallel and keep the best (FCM is sensitive to initialization). - :func:
dask_predict— the same chunked inference on a Dask scheduler.
.. note::
The process backends pickle the system. A hand-written Mamdani/TSK built from
the built-in shapes pickles fine; a TSK with lambda consequents does not —
pass backend="threading" for those.
parallel_predict ¶
parallel_predict(system: Mamdani | TSK, X: ndarray, columns: list[str] | None = None, n_jobs: int = -1, n_chunks: int | None = None, backend: str = 'loky') -> np.ndarray | dict
Run batch inference over X in parallel chunks with Joblib.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
Mamdani | TSK
|
a Mamdani or TSK system. |
required |
X
|
ndarray
|
inputs, shape |
required |
columns
|
list[str] | None
|
input variable names matching |
None
|
n_jobs
|
int
|
Joblib worker count ( |
-1
|
n_chunks
|
int | None
|
number of row chunks (defaults to |
None
|
backend
|
str
|
Joblib backend ( |
'loky'
|
Returns:
| Type | Description |
|---|---|
ndarray | dict
|
The stacked predictions — an array (single output) or a dict of arrays. |
multi_start_cmeans ¶
multi_start_cmeans(X: ndarray, c: int, n_starts: int = 8, n_jobs: int = -1, backend: str = 'loky', **kwargs) -> ClusterResult
Run :func:~fuzzytool.cluster.fuzzy_cmeans from many seeds; keep the best.
Fuzzy c-means converges to a local optimum that depends on initialization;
running several seeds and keeping the lowest-objective result is a standard
safeguard. The runs are independent, so they parallelize cleanly. Extra
keyword arguments are forwarded to :func:~fuzzytool.cluster.fuzzy_cmeans.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
data, shape |
required |
c
|
int
|
number of clusters. |
required |
n_starts
|
int
|
number of random restarts (seeds |
8
|
n_jobs
|
int
|
Joblib worker count ( |
-1
|
backend
|
str
|
Joblib backend. |
'loky'
|
Returns:
| Type | Description |
|---|---|
ClusterResult
|
The best :class: |
dask_predict ¶
dask_predict(system: Mamdani | TSK, X: ndarray, columns: list[str] | None = None, n_chunks: int | None = None) -> np.ndarray | dict
Run chunked batch inference on a Dask scheduler.
Mirrors :func:parallel_predict but builds a Dask graph (dask.delayed)
and computes it, so it scales to a distributed cluster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
Mamdani | TSK
|
a Mamdani or TSK system. |
required |
X
|
ndarray
|
inputs, shape |
required |
columns
|
list[str] | None
|
input variable names matching |
None
|
n_chunks
|
int | None
|
number of row chunks (default 8). |
None
|
Returns:
| Type | Description |
|---|---|
ndarray | dict
|
The stacked predictions — an array or a dict of arrays. |
Integrations — LLM agents¶
fuzzytool.integrations.agents ¶
LLM-agent integration: expose a fuzzy system as an explainable tool.
Install with pip install fuzzytool[agents]. A fuzzy inference system is an
unusually good citizen for tool-using LLMs: it is deterministic, bounded, and —
unlike a black-box model — it can say why it produced an answer by reporting
which rules fired and how strongly.
- :func:
explain— run a system and return its crisp output plus the fired rules (no third-party dependency; useful on its own). - :func:
inference_tool— wrap a system as a LangChainStructuredToolan agent can call.
explain ¶
Run system on inputs and explain the result.
Returns a dict with the crisp output (a float, or a dict for a
multi-output Mamdani) and fired_rules: the rules whose firing strength is
positive, each {"index", "rule", "firing", "share"}, sorted
strongest-first. This is the payload an agent (or a human) needs to trust
the answer.
Thin wrapper over :meth:~fuzzytool.inference._common.RuleBase.explain,
which every engine provides; kept here so agent code has one obvious import.
inference_tool ¶
inference_tool(system: Mamdani | TSK, columns: list[str] | None = None, name: str = 'fuzzy_inference', description: str | None = None) -> StructuredTool
Wrap a fuzzy system as a LangChain StructuredTool.
The tool takes one float argument per input variable and returns the crisp output together with the rules that fired — so the agent can both use and explain the system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
Mamdani | TSK
|
a Mamdani or TSK system. |
required |
columns
|
list[str] | None
|
input variable names (the tool's arguments). Defaults to the variables the rules reference. |
None
|
name
|
str
|
tool name exposed to the agent. |
'fuzzy_inference'
|
description
|
str | None
|
tool description; a sensible default is generated. |
None
|
Returns:
| Type | Description |
|---|---|
StructuredTool
|
A |
Datasets¶
fuzzytool.datasets ¶
Ready-made example systems.
credit_risk ¶
A credit-risk-premium Mamdani system.
Given a borrower's credit score (300-850) and dti (debt-to-income
ratio, 0-50%), recommend the premium (risk points, 0-12) a lender should
add on top of its base interest rate. Returns (system, score, dti,
premium) so callers can inspect the variables (e.g. for plotting).
sys, score, dti, premium = credit_risk() safe = sys(score=800, dti=10) # great score, low leverage risky = sys(score=520, dti=42) # poor score, high leverage safe < risky True
credit_risk_it2 ¶
An interval type-2 version of :func:credit_risk.
The score and premium terms carry a footprint of uncertainty (uncertain
Gaussian means), modeling vagueness in how a "good" score or a "low" premium
is defined. Returns (system, score, dti, premium).
sys, score, dti, premium = credit_risk_it2() sys(score=800, dti=10) < sys(score=520, dti=42) True
make_blobs ¶
make_blobs(centers=((0.0, 0.0), (6.0, 6.0), (0.0, 6.0)), n_per: int = 60, spread: float = 0.7, seed: int | None = 0) -> np.ndarray
Synthetic isotropic Gaussian blobs for clustering demos and tests.
Returns the stacked data X of shape (len(centers) * n_per, n_features).
X = make_blobs(seed=0) X.shape (180, 2)