Skip to content

Measures & relations

Two modules cover the parts of classical fuzzy set theory that the inference engines hide behind an efficient shortcut.

Measures on fuzzy sets

fuzzytool.measures works on sampled membership degrees — a 1-D array over a shared universe, which is what variable.terms[name](variable.universe) gives you. sample() accepts a membership function, an array, or an interval type-2 set (which collapses to the midpoint of its footprint).

import numpy as np, fuzzytool as fz
from fuzzytool import measures as ms

x = np.linspace(0, 10, 501)
a = ms.sample(fz.tri(2, 4, 6), x)
b = ms.sample(fz.tri(3, 5, 7), x)

Descriptors — height, is_normal, support, core, alpha_cut, cardinality, relative_cardinality:

ms.support(x, a)          # (2.0, 6.0)
ms.core(x, a)             # (4.0, 4.0)
ms.alpha_cut(x, a, 0.5)   # (3.0, 5.0)

Distances — hamming, euclidean, minkowski, each with a normalized option so the value does not depend on how finely you sampled the universe.

Similarity and inclusion — jaccard, dice, subsethood (Kosko's degree to which A ⊆ B) and consistency (max min(a, b): can these two sets hold at once?). consistency == 0 between two antecedent terms means the rules using them can never fire together.

Fuzziness — fuzzy_entropy (De Luca-Termini, normalized to [0, 1]) and index_of_fuzziness. Both are 0 for a crisp set and 1 when every degree is 0.5.

These are what audit uses to decide when two terms say the same thing.

Fuzzy relations

A fuzzy relation on X × Y is a fuzzy set of pairs: a matrix R[i, j] grading how strongly x_i relates to y_j. This is what a fuzzy rule is, before any engine optimizes it away.

from fuzzytool import relations as rel

a = fz.tri(2, 4, 6)(x)
b = fz.tri(5, 7, 9)(x)

r = rel.cartesian(a, b)        # the relation "IF x is A THEN y is B"
out = rel.cri(a, r)            # compositional rule of inference
np.allclose(out, b)            # True — fuzzy modus ponens

cri is the compositional rule of inference: B' = A' ∘ R. Feed in the antecedent you built the relation from and you get the consequent back; feed in a different observation and you get the partial match a real inference produces.

Other operations:

Function What it does
compose(r, s) sup-t composition R ∘ S (max-min by default)
projection(r, axis) the shadow a relation casts on one universe
cylindrical_extension(a, n) lift a set to a relation that ignores the other axis
inverse, union, intersection the obvious pointwise operations
implication_relation(a, b, kind) Mamdani, Larsen, Łukasiewicz, Gödel, Kleene-Dienes, Zadeh
transitive_closure(r) max-min closure; turns a proximity into an equivalence
is_reflexive / is_symmetric / is_transitive the relation's properties

Every operation takes its connectives by name, so composing with the product t-norm is one argument away:

rel.compose(r, s, tnorm="prod", snorm="probor")

Why the implication kind matters

implication_relation shows something the Mamdani engine papers over. Mamdani and Larsen are conjunctive readings (min and product); the rest are genuine implications. They agree where the antecedent fires strongly and differ sharply where it does not — a Łukasiewicz implication approaches 1 when the antecedent is near 0, whereas Mamdani goes to 0. If you have ever wondered why "IF x is A THEN y is B" behaves oddly outside A's support, this is where to look.

Relational clustering in three lines

The transitive closure of a fuzzy proximity relation is a fuzzy equivalence relation, and its alpha-cuts are a hierarchy of crisp partitions:

similarity = np.array([[1.0, 0.8, 0.2],
                       [0.8, 1.0, 0.6],
                       [0.2, 0.6, 1.0]])
equivalence = rel.transitive_closure(similarity)
groups = equivalence >= 0.6        # the partition at level 0.6

See also: Auditing a rule base, Fuzzy clustering.