bulanik.inference¶
Rule bases and the three type-1 inference systems. The package re-exports its public names, but they are defined in four submodules, which are rendered one after another below: rules (rule syntax and explanations), Mamdani, Sugeno and Tsukamoto.
inference ¶
Fuzzy rules and inference systems (type-1).
- Rules: write them as text (see
parse_rulefor the syntax) or build them fromTerm,And,OrandRule;firing_strengthevaluates an antecedent. - Systems:
MamdaniFIS(fuzzy output sets, defuzzified),SugenoFIS(constant orLinearconsequents) andTsukamotoFIS(monotonic consequents inverted at the firing strength). All three offerevaluatefor the crisp outputs andexplainfor anExplanation. - Interval type-2 versions live in
bulanik.type2; the grid-partition ANFIS inbulanik.anfis.
Rules¶
rules ¶
Fuzzy rules: antecedent objects, the rule grammar and firing strength.
Everything here is independent of the inference style, so Mamdani, Sugeno,
Tsukamoto and interval type-2 systems share it. A rule is written as text and
parsed by parse_rule (the inference
systems do this for you when you pass strings), or built from the objects
Term, And,
Or and Rule.
Rule syntax¶
A rule has an antecedent (the condition), one or more consequents and an optional weight:
IF <antecedent> THEN <var> IS <term> [AND <var> IS <term> ...] [WITH <weight>]
- Keywords are
IF,THEN,IS,NOT,AND,ORandWITH. They are case-insensitive (if,IfandIFare the same) and ASCII only. A keyword cannot be used as a variable or term name. - Names (variables and terms) start with a letter or an underscore and
continue with letters, digits or underscores; there are no spaces, so
very_highandx2are names butvery highis two. Letters outside ASCII are allowed. - Atoms are
variable IS termandvariable IS NOT term.NOTnegates the membership degree, \(1 - \mu\), and is only allowed directly afterISin an antecedent; it cannot appear in a consequent. - Operators:
ANDbinds tighter thanOR, and parentheses group, soa IS x OR b IS y AND c IS zmeansa IS x OR (b IS y AND c IS z). Chains such asp AND q AND rare n-ary and are combined from left to right. - Consequents follow
THENand are joined withAND, one per output variable (naming an output twice in one rule is an error). A rule may leave an output variable out; it then does not contribute to that output. - Weight:
WITH wmultiplies the rule's firing strength by a number \(w \in [0, 1]\) and defaults to 1. A weight outside \([0, 1]\) raisesValueError.
The firing strength of an antecedent is computed from the membership degrees
of the crisp inputs. For atoms \(\mu\) is the degree (or \(1 - \mu\) if negated);
AND is a t-norm (\(\min\) or the product, and_method of the system) and OR
a t-conorm (\(\max\) or the probabilistic sum \(a + b - ab\), or_method); see
t_norm and t_conorm. The
activation of a rule is its weight times that strength.
Parsing and printing round-trip: str(rule) is canonical upper-case rule text
that parse_rule reads back to an equal rule.
Examples:
>>> from bulanik.inference import parse_rule
>>> rule = parse_rule("if food is not rancid and (service is good or service is ok) then tip is high with 0.8")
>>> print(rule)
IF food IS NOT rancid AND (service IS good OR service IS ok) THEN tip IS high WITH 0.8
>>> rule.weight, dict(rule.consequents)
(0.8, {'tip': 'high'})
>>> parse_rule(str(rule)) == rule
True
>>> parse_rule("IF a IS b THEN c IS NOT d")
Traceback (most recent call last):
...
ValueError: NOT is not allowed in a consequent (offset 20) in rule 'IF a IS b THEN c IS NOT d'
Antecedent
module-attribute
¶
Antecedent = Union[Term, And, Or]
NoRuleFiredError ¶
NoRuleFiredError(message: str, *, output: str | None = None, explanation: Explanation | None = None)
Bases: ValueError
No rule fired for an output and no default value was given.
A ValueError raised by evaluate / explain of the inference systems.
Give the system a default to return a fixed value instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The error message. |
required |
output
|
str | None
|
Name of the output variable no rule fired for. |
None
|
explanation
|
Explanation | None
|
The partial |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
output |
The output variable name, or |
|
explanation |
The input |
Examples:
>>> from bulanik.sets import LinguisticVariable, triangular
>>> from bulanik.inference import MamdaniFIS, NoRuleFiredError
>>> x = LinguisticVariable("x", (0, 10), {"lo": triangular(0, 0, 3)})
>>> y = LinguisticVariable("y", (0, 10), {"low": triangular(0, 0, 10)})
>>> fis = MamdaniFIS([x], [y], ["IF x IS lo THEN y IS low"])
>>> try:
... fis.evaluate({"x": 8})
... except NoRuleFiredError as err:
... print(err.output, err.explanation.activations[0].strength)
y 0.0
Term
dataclass
¶
Term(variable: str, term: str, negated: bool = False)
The atom variable IS term, or variable IS NOT term when negated.
The operators &, | and ~ build antecedents: a & b is
And, a | b is
Or and ~a is the negated term.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variable
|
str
|
Name of an input variable. |
required |
term
|
str
|
Name of one of its terms. |
required |
negated
|
bool
|
Whether the membership degree is negated, \(1 - \mu\). |
False
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a name is not a string, or |
ValueError
|
If a name is not a valid rule name or is a rule keyword (see the module description for the syntax). |
Attributes:
| Name | Type | Description |
|---|---|---|
variable |
str
|
The variable name. |
term |
str
|
The term name. |
negated |
bool
|
Whether the term is negated. |
Examples:
>>> from bulanik.inference import Term
>>> food, service = Term("food", "good"), Term("service", "poor")
>>> print(food & ~service)
food IS good AND service IS NOT poor
>>> print((food | service) & Term("price", "low"))
(food IS good OR service IS poor) AND price IS low
And
dataclass
¶
And(parts: tuple[Antecedent, ...])
Conjunction of antecedents (AND); nested conjunctions are flattened.
Evaluated with the system's t-norm, folded from left to right.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parts
|
tuple[Antecedent, ...]
|
Two or more antecedents. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two parts remain after flattening. |
Attributes:
| Name | Type | Description |
|---|---|---|
parts |
tuple[Antecedent, ...]
|
The flattened parts. |
Examples:
>>> from bulanik.inference import And, Term
>>> a, b, c = Term("x", "a"), Term("x", "b"), Term("y", "c")
>>> And((And((a, b)), c)).parts == (a, b, c)
True
>>> print(a & b & c)
x IS a AND x IS b AND y IS c
Or
dataclass
¶
Or(parts: tuple[Antecedent, ...])
Disjunction of antecedents (OR); nested disjunctions are flattened.
Evaluated with the system's t-conorm, folded from left to right.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parts
|
tuple[Antecedent, ...]
|
Two or more antecedents. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two parts remain after flattening. |
Attributes:
| Name | Type | Description |
|---|---|---|
parts |
tuple[Antecedent, ...]
|
The flattened parts. |
Examples:
>>> from bulanik.inference import Or, Term
>>> a, b = Term("x", "a"), Term("y", "b")
>>> print(Or((a, b)))
x IS a OR y IS b
>>> Or((a,))
Traceback (most recent call last):
...
ValueError: Or needs at least two parts
Rule
dataclass
¶
Rule(antecedent: Antecedent, consequents: Mapping[str, str], weight: float = 1.0)
IF antecedent THEN consequents [WITH weight].
A rule is immutable and hashable; str(rule) is canonical rule text that
parse_rule reads back. Rules given
to an inference system are checked against its variables and terms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
antecedent
|
Antecedent
|
required | |
consequents
|
Mapping[str, str]
|
Mapping from output variable name to term name (at least one). |
required |
weight
|
float
|
Rule weight in \([0, 1]\); it multiplies the firing strength. |
1.0
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If there are no consequents, a name is invalid or a rule
keyword, or |
Attributes:
| Name | Type | Description |
|---|---|---|
antecedent |
Antecedent
|
The condition. |
consequents |
Mapping[str, str]
|
A read-only mapping of output variable to term; mutating
it raises |
weight |
float
|
The rule weight as a float. |
Examples:
>>> from bulanik.inference import Rule, Term
>>> rule = Rule(Term("service", "good") | Term("food", "good"), {"tip": "high"}, 0.5)
>>> print(rule)
IF service IS good OR food IS good THEN tip IS high WITH 0.5
>>> Rule(Term("a", "b"), {"c": "d"}, 1.5)
Traceback (most recent call last):
...
ValueError: rule weight 1.5 must be in [0, 1]
RuleActivation
dataclass
¶
RuleActivation(rule: str, strength: float | tuple[float, float])
A rule's firing level: weight times antecedent degree.
Interval type-2 systems store a (lower, upper) pair instead of a float.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rule
|
str
|
The rule text, as |
required |
strength
|
float | tuple[float, float]
|
The activation, a float in \([0, 1]\) (or a pair for interval type-2 systems). |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
rule |
str
|
The rule text. |
strength |
float | tuple[float, float]
|
The activation. |
Examples:
>>> from bulanik.inference import RuleActivation
>>> RuleActivation("IF x IS a THEN y IS b", 0.25).strength
0.25
Explanation
dataclass
¶
Explanation(degrees: dict[str, dict[str, float | tuple[float, float]]], activations: tuple[RuleActivation, ...], outputs: dict[str, float] = dict())
Everything behind one evaluation: degrees, rule activations and outputs.
Returned by the explain method of every inference system; its outputs
are what evaluate returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
degrees
|
dict[str, dict[str, float | tuple[float, float]]]
|
Membership degree of each input in each of its terms, as
|
required |
activations
|
tuple[RuleActivation, ...]
|
One |
required |
outputs
|
dict[str, float]
|
The crisp value of each output variable. |
dict()
|
Attributes:
| Name | Type | Description |
|---|---|---|
degrees |
dict[str, dict[str, float | tuple[float, float]]]
|
The input membership degrees. |
activations |
tuple[RuleActivation, ...]
|
The rule activations. |
outputs |
dict[str, float]
|
The crisp outputs. |
Examples:
>>> from bulanik.sets import LinguisticVariable, triangular
>>> from bulanik.inference import MamdaniFIS
>>> x = LinguisticVariable("x", (0, 10), {"lo": triangular(0, 0, 10), "hi": triangular(0, 10, 10)})
>>> y = LinguisticVariable("y", (0, 10), {"lo": triangular(0, 0, 10), "hi": triangular(0, 10, 10)})
>>> fis = MamdaniFIS([x], [y], ["IF x IS lo THEN y IS lo", "IF x IS hi THEN y IS hi WITH 0.5"])
>>> info = fis.explain({"x": 2.5})
>>> info.degrees
{'x': {'lo': 0.75, 'hi': 0.25}}
>>> [a.strength for a in info.activations]
[0.75, 0.125]
>>> sorted(info.outputs)
['y']
rule_terms ¶
rule_terms(antecedent: Antecedent) -> list[Term]
All Term leaves of an antecedent, left to right.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
antecedent
|
Antecedent
|
A |
required |
Returns:
| Type | Description |
|---|---|
list[Term]
|
The terms in the order they appear in the rule text. |
Examples:
>>> from bulanik.inference import Term
>>> from bulanik.inference.rules import rule_terms
>>> [str(t) for t in rule_terms((Term("a", "x") | Term("b", "y")) & ~Term("c", "z"))]
['a IS x', 'b IS y', 'c IS NOT z']
firing_strength ¶
firing_strength(antecedent: Antecedent, degrees: Mapping[str, Mapping[str, float]], and_method: str = 'min', or_method: str = 'max') -> float
Degree to which an antecedent holds.
A term has its membership degree \(\mu\), or \(1 - \mu\) when negated.
And folds the parts with the t-norm and_method and Or with the
t-conorm or_method, from left to right (see
t_norm and t_conorm).
The rule weight is not applied here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
antecedent
|
Antecedent
|
A |
required |
degrees
|
Mapping[str, Mapping[str, float]]
|
Membership degrees as |
required |
and_method
|
str
|
|
'min'
|
or_method
|
str
|
|
'max'
|
Returns:
| Type | Description |
|---|---|
float
|
The firing strength in \([0, 1]\). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a method name is unknown, or a term of the antecedent
is missing from |
Examples:
>>> from bulanik.inference import firing_strength, parse_rule
>>> rule = parse_rule("IF x IS a AND y IS b THEN z IS c")
>>> degrees = {"x": {"a": 0.6}, "y": {"b": 0.5}}
>>> firing_strength(rule.antecedent, degrees)
0.5
>>> firing_strength(rule.antecedent, degrees, and_method="prod")
0.3
parse_rule ¶
parse_rule(text: str) -> Rule
Parse rule text IF ... THEN ... [WITH w] into a Rule.
The grammar is described in the module documentation: keywords are
case-insensitive, AND binds tighter than OR, parentheses group, and
NOT only follows IS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The rule text. |
required |
Returns:
| Type | Description |
|---|---|
Rule
|
The parsed rule. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If the text is not a valid rule (the message gives the offset), a keyword is used as a name, a consequent is negated or repeated, or the weight is outside \([0, 1]\). |
Examples:
>>> from bulanik.inference import parse_rule
>>> rule = parse_rule("IF temp IS hot OR NOT humidity IS low THEN fan IS fast")
Traceback (most recent call last):
...
ValueError: keyword 'NOT' used as a name at offset 18 in rule 'IF temp IS hot OR NOT humidity IS low THEN fan IS fast'
>>> rule = parse_rule("IF temp IS hot OR humidity IS NOT low THEN fan IS fast WITH 0.7")
>>> rule.weight, str(rule.antecedent)
(0.7, 'temp IS hot OR humidity IS NOT low')
Mamdani¶
mamdani ¶
Mamdani fuzzy inference over sampled output universes.
MamdaniFIS fuzzifies crisp inputs,
fires the rules (see parse_rule for
the rule syntax), clips or scales each consequent set by its rule's firing
strength, aggregates the clipped sets over a sampled output universe and
defuzzifies the result with
defuzzify_samples.
MamdaniFIS ¶
MamdaniFIS(inputs: Sequence[LinguisticVariable], outputs: Sequence[LinguisticVariable], rules: Sequence[Rule | str], *, and_method: str = 'min', or_method: str = 'max', implication: str = 'min', aggregation: str = 'max', defuzzification: str = 'centroid', resolution: int = 1001, default: Mapping[str, float] | None = None)
Mamdani fuzzy inference system (Mamdani and Assilian 1975).
For crisp inputs the system computes each rule's firing strength
\(\alpha_k\) (rule weight times the antecedent degree, with the t-norm
and_method and t-conorm or_method). Every output universe is sampled at
resolution evenly spaced points \(x_i\), endpoints included, and the
aggregated set is
where \(B_k\) is the consequent term of rule \(k\). implication is "min"
(clip, \(\min(\alpha, \mu)\)) or "prod" (scale, \(\alpha\mu\)); aggregation
is "max", "sum" (not clipped to 1, as in MATLAB) or "probor"
(\(a + b - ab\)). The crisp output is the defuzzification of the aggregated
samples, see defuzzify_samples.
If no rule fires for an output (all aggregated samples are 0) the value from
default is returned, or NoRuleFiredError
is raised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Sequence[LinguisticVariable]
|
Input |
required |
outputs
|
Sequence[LinguisticVariable]
|
Output linguistic variables (at least one; names distinct from the inputs). |
required |
rules
|
Sequence[Rule | str]
|
|
required |
and_method
|
str
|
|
'min'
|
or_method
|
str
|
|
'max'
|
implication
|
str
|
|
'min'
|
aggregation
|
str
|
|
'max'
|
defuzzification
|
str
|
|
'centroid'
|
resolution
|
int
|
Samples per output universe, an integer of at least 2. |
1001
|
default
|
Mapping[str, float] | None
|
Output value per output variable, used when no rule fires
(inside the universe). |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a variable is not a |
ValueError
|
If an option name is unknown, |
Attributes:
| Name | Type | Description |
|---|---|---|
inputs |
The input variables (read-only). |
|
outputs |
The output variables (read-only). |
|
rules |
The parsed rules (read-only). |
|
and_method |
The t-norm name. |
|
or_method |
The t-conorm name. |
|
implication |
The implication name. |
|
aggregation |
The aggregation name. |
|
defuzzification |
The defuzzification name. |
|
resolution |
Samples per output universe. |
|
default |
A copy of the no-fire defaults, or |
Examples:
>>> from bulanik.sets import LinguisticVariable, triangular
>>> from bulanik.inference import MamdaniFIS
>>> service = LinguisticVariable("service", (0, 10), {
... "poor": triangular(0, 0, 5), "good": triangular(5, 10, 10)})
>>> tip = LinguisticVariable("tip", (0, 20), {
... "low": triangular(0, 0, 10), "high": triangular(10, 20, 20)})
>>> fis = MamdaniFIS([service], [tip], [
... "IF service IS poor THEN tip IS low",
... "IF service IS good THEN tip IS high"])
>>> fis.evaluate({"service": 7.5})
{'tip': 16.116...}
>>> fis.evaluate({"service": 2})
{'tip': 3.708...}
References
Mamdani and Assilian (1975): "An experiment in linguistic synthesis with a fuzzy logic controller", International Journal of Man-Machine Studies.
explain ¶
explain(inputs: Mapping[str, float]) -> Explanation
Evaluate and report degrees, rule activations and crisp outputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Mapping[str, float]
|
Crisp value of every input variable, by name. The keys must be exactly the input names and each value must lie inside its variable's universe (values are never clamped). |
required |
Returns:
| Type | Description |
|---|---|
Explanation
|
An |
Explanation
|
input membership degrees, one activation per rule and the crisp |
Explanation
|
outputs. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a value is a |
ValueError
|
If an input is missing, unexpected, not finite or outside its universe. |
NoRuleFiredError
|
If no rule fires for an output and there is no
|
Examples:
>>> from bulanik.sets import LinguisticVariable, triangular
>>> from bulanik.inference import MamdaniFIS
>>> x = LinguisticVariable("x", (0, 10), {"lo": triangular(0, 0, 10), "hi": triangular(0, 10, 10)})
>>> y = LinguisticVariable("y", (0, 10), {"lo": triangular(0, 0, 10), "hi": triangular(0, 10, 10)})
>>> fis = MamdaniFIS([x], [y], ["IF x IS lo THEN y IS lo", "IF x IS hi THEN y IS hi"])
>>> info = fis.explain({"x": 2.5})
>>> info.degrees
{'x': {'lo': 0.75, 'hi': 0.25}}
>>> info.activations[0]
RuleActivation(rule='IF x IS lo THEN y IS lo', strength=0.75)
>>> fis.explain({"x": 11})
Traceback (most recent call last):
...
ValueError: 'x' input 11 is outside its universe [0, 10]
evaluate ¶
evaluate(inputs: Mapping[str, float]) -> dict[str, float]
Crisp outputs for crisp inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Mapping[str, float]
|
Crisp value of every input variable, by name. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dict from output variable name to its crisp value. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a value is a |
ValueError
|
If an input is missing, unexpected, not finite or outside its universe. |
NoRuleFiredError
|
If no rule fires for an output and there is no
|
Examples:
>>> from bulanik.sets import LinguisticVariable, triangular
>>> from bulanik.inference import MamdaniFIS
>>> x = LinguisticVariable("x", (0, 10), {"lo": triangular(0, 0, 3), "hi": triangular(7, 10, 10)})
>>> y = LinguisticVariable("y", (0, 20), {"low": triangular(0, 0, 10), "high": triangular(10, 20, 20)})
>>> rules = ["IF x IS lo THEN y IS low", "IF x IS hi THEN y IS high"]
>>> MamdaniFIS([x], [y], rules, default={"y": 10}).evaluate({"x": 5})
{'y': 10.0}
>>> MamdaniFIS([x], [y], rules).evaluate({"x": 5})
Traceback (most recent call last):
...
bulanik.inference.rules.NoRuleFiredError: no rule fired for output 'y' and no default was given; the error's .explanation holds the input degrees and rule activations
defuzzify_samples ¶
defuzzify_samples(xs: Sequence[float], mus: Sequence[float], method: str) -> float
Crisp value of a sampled fuzzy set.
With samples \((x_i, \mu_i)\) the methods are:
"centroid": \(\sum_i x_i \mu_i / \sum_i \mu_i\)."bisector": the first \(x_i\) where the running sum of \(\mu\) reaches half of the total."mom","som","lom": the mean, smallest and largest \(x_i\) among the samples whose \(\mu_i\) is within \(10^{-12}\) of the maximum.
Memberships may exceed 1 ("sum" aggregation). Tiny negative values within
the library tolerance are treated as 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xs
|
Sequence[float]
|
Sample points (finite). |
required |
mus
|
Sequence[float]
|
Membership at each sample, same length as |
required |
method
|
str
|
|
required |
Returns:
| Type | Description |
|---|---|
float
|
The crisp value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from bulanik.inference import defuzzify_samples
>>> xs, mus = [0, 1, 2, 3], [0, 1, 1, 0]
>>> [defuzzify_samples(xs, mus, m) for m in ("centroid", "bisector", "mom", "som", "lom")]
[1.5, 1, 1.5, 1, 2]
>>> defuzzify_samples(xs, [0, 0, 0, 0], "centroid")
Traceback (most recent call last):
...
ValueError: cannot defuzzify a set whose memberships are all zero
Sugeno¶
sugeno ¶
Takagi-Sugeno-Kang fuzzy inference: constant or linear rule consequents.
A Sugeno system has no output membership functions. Each output variable
(SugenoOutput) maps its term names to
a constant (zero-order) or a Linear function
of the inputs (first-order). SugenoFIS
combines the fired rules' values by a weighted average.
Linear ¶
Linear(coefficients: Mapping[str, float], constant: float = 0.0)
First-order Sugeno consequent \(c_0 + \sum_i c_i x_i\) over named inputs.
Calling the object evaluates it on a mapping of crisp inputs; extra inputs are ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coefficients
|
Mapping[str, float]
|
Mapping from input variable name to its coefficient \(c_i\). |
required |
constant
|
float
|
The constant term \(c_0\). |
0.0
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a coefficient or |
ValueError
|
If a coefficient or |
Attributes:
| Name | Type | Description |
|---|---|---|
coefficients |
A copy of the input name to coefficient mapping. |
|
constant |
The constant term. |
Examples:
>>> from bulanik.inference import Linear
>>> f = Linear({"x": 2.0, "y": -1.0}, 0.5)
>>> f({"x": 3, "y": 1})
5.5
>>> f
Linear({'x': 2.0, 'y': -1.0}, 0.5)
>>> f({"x": 3})
Traceback (most recent call last):
...
ValueError: missing input 'y' for Linear
SugenoOutput ¶
SugenoOutput(name: str, functions: Mapping[str, float | Linear])
Output variable whose terms are constants (zero-order) or Linear (first-order).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The output name, used in rule consequents. |
required |
functions
|
Mapping[str, float | Linear]
|
Mapping from term name to a number or a
|
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
If a term name is not a string, or a constant is a |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
The output name. |
|
functions |
A copy of the term to constant-or- |
|
term_names |
tuple[str, ...]
|
The term names, in insertion order. |
Examples:
>>> from bulanik.inference import Linear, SugenoOutput
>>> speed = SugenoOutput("speed", {"slow": 10, "fast": Linear({"load": -2.0}, 80)})
>>> speed.term_names
('slow', 'fast')
>>> speed
SugenoOutput('speed', {'slow': 10.0, 'fast': Linear({'load': -2.0}, 80.0)})
SugenoFIS ¶
SugenoFIS(inputs: Sequence[LinguisticVariable], outputs: Sequence[SugenoOutput], rules: Sequence[Rule | str], *, and_method: str = 'min', or_method: str = 'max', output_method: str = 'weighted_average', default: Mapping[str, float] | None = None)
Sugeno fuzzy inference system (Takagi and Sugeno 1985).
Each fired rule \(k\) has firing strength \(\alpha_k\) (rule weight times the
antecedent degree) and a crisp value \(z_k\): the consequent constant, or the
Linear function evaluated at the crisp
inputs. The output is
for output_method="weighted_average" and \(y = \sum_k \alpha_k z_k\) for
"weighted_sum".
When no rule fires for an output (\(\sum_k \alpha_k = 0\)) the value from
default is returned, or
NoRuleFiredError is raised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Sequence[LinguisticVariable]
|
Input |
required |
outputs
|
Sequence[SugenoOutput]
|
|
required |
rules
|
Sequence[Rule | str]
|
|
required |
and_method
|
str
|
|
'min'
|
or_method
|
str
|
|
'max'
|
output_method
|
str
|
|
'weighted_average'
|
default
|
Mapping[str, float] | None
|
Output value per output variable, used when no rule fires. |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a variable has the wrong class or a rule is neither a
|
ValueError
|
If an option name is unknown, there are no inputs, outputs
or rules, a variable name is duplicated, a rule names an unknown
variable or term, a |
Attributes:
| Name | Type | Description |
|---|---|---|
inputs |
The input variables (read-only). |
|
outputs |
The output variables (read-only). |
|
rules |
The parsed rules (read-only). |
|
and_method |
The t-norm name. |
|
or_method |
The t-conorm name. |
|
output_method |
The output combination name. |
|
default |
A copy of the no-fire defaults, or |
Examples:
>>> from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing
>>> from bulanik.inference import Linear, SugenoFIS, SugenoOutput
>>> x = LinguisticVariable("x", (0, 10), {
... "lo": linear_decreasing(0, 10), "hi": linear_increasing(0, 10)})
>>> y = SugenoOutput("y", {"a": 1.0, "b": Linear({"x": 2.0}, 1.0)})
>>> rules = ["IF x IS lo THEN y IS a", "IF x IS hi THEN y IS b"]
>>> SugenoFIS([x], [y], rules).evaluate({"x": 2.5})
{'y': 2.25}
>>> weighted = ["IF x IS lo THEN y IS a WITH 0.5", "IF x IS hi THEN y IS b"]
>>> SugenoFIS([x], [y], weighted).evaluate({"x": 2.5})
{'y': 3.0}
>>> SugenoFIS([x], [y], weighted, output_method="weighted_sum").evaluate({"x": 2.5})
{'y': 1.875}
References
Takagi and Sugeno (1985): "Fuzzy identification of systems and its applications to modeling and control", IEEE Transactions on Systems, Man, and Cybernetics.
explain ¶
explain(inputs: Mapping[str, float]) -> Explanation
Evaluate and report degrees, rule activations and crisp outputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Mapping[str, float]
|
Crisp value of every input variable, by name. The keys must be exactly the input names and each value must lie inside its variable's universe. |
required |
Returns:
| Type | Description |
|---|---|
Explanation
|
An |
Explanation
|
input membership degrees, one activation per rule and the crisp |
Explanation
|
outputs. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a value is a |
ValueError
|
If an input is missing, unexpected, not finite or outside its universe. |
NoRuleFiredError
|
If no rule fires for an output and there is no
|
Examples:
>>> from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing
>>> from bulanik.inference import SugenoFIS, SugenoOutput
>>> x = LinguisticVariable("x", (0, 10), {
... "lo": linear_decreasing(0, 10), "hi": linear_increasing(0, 10)})
>>> y = SugenoOutput("y", {"a": 0.0, "b": 100.0})
>>> fis = SugenoFIS([x], [y], ["IF x IS lo THEN y IS a", "IF x IS hi THEN y IS b"])
>>> info = fis.explain({"x": 2.5})
>>> [a.strength for a in info.activations], info.outputs
([0.75, 0.25], {'y': 25.0})
evaluate ¶
evaluate(inputs: Mapping[str, float]) -> dict[str, float]
Crisp outputs for crisp inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Mapping[str, float]
|
Crisp value of every input variable, by name. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dict from output variable name to its crisp value. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a value is a |
ValueError
|
If an input is missing, unexpected, not finite or outside its universe. |
NoRuleFiredError
|
If no rule fires for an output and there is no
|
Examples:
>>> from bulanik.sets import LinguisticVariable, triangular
>>> from bulanik.inference import SugenoFIS, SugenoOutput
>>> x = LinguisticVariable("x", (0, 10), {"lo": triangular(0, 0, 3)})
>>> y = SugenoOutput("y", {"a": 5})
>>> SugenoFIS([x], [y], ["IF x IS lo THEN y IS a"], default={"y": -1}).evaluate({"x": 8})
{'y': -1.0}
Tsukamoto¶
tsukamoto ¶
Tsukamoto fuzzy inference: monotonic consequents inverted at the firing strength.
TsukamotoFIS needs output terms
with a monotonic membership function; each fired rule reads its crisp value off
the term at the rule's firing strength, and the output is the weighted average
of those values.
TsukamotoFIS ¶
TsukamotoFIS(inputs: Sequence[LinguisticVariable], outputs: Sequence[LinguisticVariable], rules: Sequence[Rule | str], *, and_method: str = 'min', or_method: str = 'max', default: Mapping[str, float] | None = None)
Tsukamoto fuzzy inference system (Tsukamoto 1979).
Every output term must be monotonic (linear_increasing,
linear_decreasing or sigmoid). A fired rule \(k\) with firing strength
\(\alpha_k\) (rule weight times the antecedent degree) gives the crisp value
\(z_k = \mu_{B_k}^{-1}(\alpha_k)\), the point where its consequent term
reaches \(\alpha_k\), and the output is
over the rules with \(\alpha_k > 0\). Each \(z_k\) is clamped into the output's
universe (the inverse of a sigmoid or of a linear ramp can fall outside
it), and so is the final average (rounding can put it one ulp outside);
inputs are never clamped. If no rule fires for an output the value from
default is returned, or
NoRuleFiredError is raised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Sequence[LinguisticVariable]
|
Input |
required |
outputs
|
Sequence[LinguisticVariable]
|
Output linguistic variables whose terms are all monotonic. |
required |
rules
|
Sequence[Rule | str]
|
|
required |
and_method
|
str
|
|
'min'
|
or_method
|
str
|
|
'max'
|
default
|
Mapping[str, float] | None
|
Output value per output variable, used when no rule fires. |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a variable is not a |
ValueError
|
If an option name is unknown, an output term is not
monotonic, there are no inputs, outputs or rules, a variable name
is duplicated, a rule names an unknown variable or term, or
|
Attributes:
| Name | Type | Description |
|---|---|---|
inputs |
The input variables (read-only). |
|
outputs |
The output variables (read-only). |
|
rules |
The parsed rules (read-only). |
|
and_method |
The t-norm name. |
|
or_method |
The t-conorm name. |
|
default |
A copy of the no-fire defaults, or |
Examples:
>>> from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing
>>> from bulanik.inference import TsukamotoFIS
>>> x = LinguisticVariable("x", (0, 10), {
... "lo": linear_decreasing(0, 10), "hi": linear_increasing(0, 10)})
>>> z = LinguisticVariable("z", (0, 20), {
... "low": linear_decreasing(0, 10), "high": linear_increasing(0, 20)})
>>> fis = TsukamotoFIS([x], [z], ["IF x IS lo THEN z IS low", "IF x IS hi THEN z IS high"])
>>> fis.evaluate({"x": 2.5})
{'z': 3.125}
References
Tsukamoto (1979): "An approach to fuzzy reasoning method", in Advances in Fuzzy Set Theory and Applications (North-Holland, Amsterdam).
explain ¶
explain(inputs: Mapping[str, float]) -> Explanation
Evaluate and report degrees, rule activations and crisp outputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Mapping[str, float]
|
Crisp value of every input variable, by name. The keys must be exactly the input names and each value must lie inside its variable's universe. |
required |
Returns:
| Type | Description |
|---|---|
Explanation
|
An |
Explanation
|
input membership degrees, one activation per rule and the crisp |
Explanation
|
outputs. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a value is a |
ValueError
|
If an input is missing, unexpected, not finite or outside its universe. |
NoRuleFiredError
|
If no rule fires for an output and there is no
|
Examples:
>>> from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing
>>> from bulanik.inference import TsukamotoFIS
>>> x = LinguisticVariable("x", (0, 10), {"hi": linear_increasing(0, 10)})
>>> z = LinguisticVariable("z", (0, 20), {"high": linear_increasing(0, 20)})
>>> info = TsukamotoFIS([x], [z], ["IF x IS hi THEN z IS high"]).explain({"x": 5})
>>> info.activations[0].strength, info.outputs
(0.5, {'z': 10.0})
evaluate ¶
evaluate(inputs: Mapping[str, float]) -> dict[str, float]
Crisp outputs for crisp inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Mapping[str, float]
|
Crisp value of every input variable, by name. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dict from output variable name to its crisp value. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a value is a |
ValueError
|
If an input is missing, unexpected, not finite or outside its universe. |
NoRuleFiredError
|
If no rule fires for an output and there is no
|
Examples:
>>> from bulanik.sets import LinguisticVariable, linear_increasing
>>> from bulanik.inference import TsukamotoFIS
>>> x = LinguisticVariable("x", (0, 10), {"hi": linear_increasing(0, 10)})
>>> z = LinguisticVariable("z", (0, 20), {"high": linear_increasing(0, 20)})
>>> TsukamotoFIS([x], [z], ["IF x IS hi THEN z IS high"]).evaluate({"x": 10})
{'z': 20.0}