Skip to content

Commit 2045806

Browse files
fuzzy_logic: descriptive parameter names + drop __future__ annotations
Address review on #15166: - rename single-letter params (x->grid, a/b->membership_a/membership_b, fuzzy_complement arg -> membership) per algorithms-keeper + cclauss - drop 'from __future__ import annotations' (repo is Python 3.14t-only)
1 parent e8ff08f commit 2045806

1 file changed

Lines changed: 0 additions & 184 deletions

File tree

Lines changed: 0 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,184 +0,0 @@
1-
"""
2-
Zadeh's fuzzy-set operators on membership vectors.
3-
4-
A fuzzy set over a universe of discourse ``X`` is described by a *membership
5-
function* ``mu: X -> [0, 1]``. Once the universe is sampled on a grid, that
6-
function becomes a NumPy vector of membership degrees and the classic set
7-
operations reduce to element-wise arithmetic.
8-
9-
This module implements the standard (Zadeh) operators plus a few common
10-
alternatives. Unlike ``fuzzy_operations.FuzzySet`` -- which stores a *triangular
11-
fuzzy number* by its three defining points -- the functions here work on the
12-
sampled membership vectors directly, so they apply to *any* membership shape
13-
(triangular, trapezoidal, Gaussian, ...).
14-
15-
References:
16-
- https://en.wikipedia.org/wiki/Fuzzy_set#Fuzzy_set_operations
17-
- https://en.wikipedia.org/wiki/Fuzzy_logic
18-
- https://en.wikipedia.org/wiki/T-norm
19-
20-
Requirements:
21-
- numpy
22-
23-
Originally contributed as a ``scikit-fuzzy`` demo by Jigyasa Gandhi; rewritten
24-
here to be dependency-free (NumPy only) and covered by doctests.
25-
"""
26-
27-
from __future__ import annotations
28-
29-
import numpy as np
30-
from numpy.typing import NDArray
31-
32-
33-
def triangular_membership(
34-
x: NDArray[np.float64], left: float, peak: float, right: float
35-
) -> NDArray[np.float64]:
36-
"""
37-
Sample a triangular membership function on the grid ``x``.
38-
39-
The membership rises linearly from 0 at ``left`` to 1 at ``peak`` and falls
40-
back to 0 at ``right``.
41-
42-
>>> x = np.array([0.0, 25.0, 50.0])
43-
>>> triangular_membership(x, 0, 25, 50)
44-
array([0., 1., 0.])
45-
>>> triangular_membership(np.array([10.0, 12.5]), 0, 25, 50)
46-
array([0.4, 0.5])
47-
"""
48-
if not left <= peak <= right:
49-
msg = f"Expected left <= peak <= right, got {left}, {peak}, {right}"
50-
raise ValueError(msg)
51-
left_slope = (x - left) / (peak - left) if peak > left else np.where(x < peak, 0, 1)
52-
right_slope = (
53-
(right - x) / (right - peak) if right > peak else np.where(x > peak, 0, 1)
54-
)
55-
return np.clip(np.minimum(left_slope, right_slope), 0.0, 1.0)
56-
57-
58-
def fuzzy_union(a: NDArray[np.float64], b: NDArray[np.float64]) -> NDArray[np.float64]:
59-
"""
60-
Union (logical OR): ``max(mu_A(x), mu_B(x))``.
61-
62-
>>> fuzzy_union(np.array([0.2, 0.7]), np.array([0.5, 0.1]))
63-
array([0.5, 0.7])
64-
"""
65-
return np.maximum(a, b)
66-
67-
68-
def fuzzy_intersection(
69-
a: NDArray[np.float64], b: NDArray[np.float64]
70-
) -> NDArray[np.float64]:
71-
"""
72-
Intersection (logical AND): ``min(mu_A(x), mu_B(x))``.
73-
74-
>>> fuzzy_intersection(np.array([0.2, 0.7]), np.array([0.5, 0.1]))
75-
array([0.2, 0.1])
76-
"""
77-
return np.minimum(a, b)
78-
79-
80-
def fuzzy_complement(a: NDArray[np.float64]) -> NDArray[np.float64]:
81-
"""
82-
Complement (logical NOT): ``1 - mu_A(x)``.
83-
84-
>>> fuzzy_complement(np.array([0.0, 0.3, 1.0]))
85-
array([1. , 0.7, 0. ])
86-
"""
87-
return 1.0 - a
88-
89-
90-
def fuzzy_difference(
91-
a: NDArray[np.float64], b: NDArray[np.float64]
92-
) -> NDArray[np.float64]:
93-
"""
94-
Difference ``A / B``: ``min(mu_A(x), 1 - mu_B(x))``.
95-
96-
>>> fuzzy_difference(np.array([0.6, 0.4]), np.array([0.2, 0.9]))
97-
array([0.6, 0.1])
98-
"""
99-
return np.minimum(a, 1.0 - b)
100-
101-
102-
def algebraic_sum(
103-
a: NDArray[np.float64], b: NDArray[np.float64]
104-
) -> NDArray[np.float64]:
105-
"""
106-
Algebraic (probabilistic) sum: ``mu_A + mu_B - mu_A * mu_B``.
107-
108-
>>> algebraic_sum(np.array([0.5, 1.0]), np.array([0.5, 0.2]))
109-
array([0.75, 1. ])
110-
"""
111-
return a + b - a * b
112-
113-
114-
def algebraic_product(
115-
a: NDArray[np.float64], b: NDArray[np.float64]
116-
) -> NDArray[np.float64]:
117-
"""
118-
Algebraic product: ``mu_A * mu_B``.
119-
120-
>>> algebraic_product(np.array([0.5, 1.0]), np.array([0.5, 0.2]))
121-
array([0.25, 0.2 ])
122-
"""
123-
return a * b
124-
125-
126-
def bounded_sum(a: NDArray[np.float64], b: NDArray[np.float64]) -> NDArray[np.float64]:
127-
"""
128-
Bounded sum (Lukasiewicz t-conorm): ``min(1, mu_A + mu_B)``.
129-
130-
>>> bounded_sum(np.array([0.5, 0.8]), np.array([0.2, 0.7]))
131-
array([0.7, 1. ])
132-
"""
133-
return np.minimum(1.0, a + b)
134-
135-
136-
def bounded_difference(
137-
a: NDArray[np.float64], b: NDArray[np.float64]
138-
) -> NDArray[np.float64]:
139-
"""
140-
Bounded difference (Lukasiewicz t-norm): ``max(0, mu_A + mu_B - 1)``.
141-
142-
>>> bounded_difference(np.array([0.5, 0.8]), np.array([0.2, 0.7]))
143-
array([0. , 0.5])
144-
"""
145-
return np.maximum(0.0, a + b - 1.0)
146-
147-
148-
if __name__ == "__main__":
149-
from doctest import testmod
150-
151-
testmod()
152-
153-
# Reproduce the original "young vs. middle-aged" demo, dependency-free.
154-
universe = np.linspace(start=0, stop=75, num=75)
155-
young = triangular_membership(universe, 0, 25, 50)
156-
middle_aged = triangular_membership(universe, 25, 50, 75)
157-
158-
operations = {
159-
"young": young,
160-
"middle_aged": middle_aged,
161-
"union": fuzzy_union(young, middle_aged),
162-
"intersection": fuzzy_intersection(young, middle_aged),
163-
"complement(young)": fuzzy_complement(young),
164-
"difference young/middle": fuzzy_difference(young, middle_aged),
165-
"algebraic_sum": algebraic_sum(young, middle_aged),
166-
"algebraic_product": algebraic_product(young, middle_aged),
167-
"bounded_sum": bounded_sum(young, middle_aged),
168-
"bounded_difference": bounded_difference(young, middle_aged),
169-
}
170-
171-
try:
172-
import matplotlib.pyplot as plt
173-
174-
plt.figure()
175-
for index, (title, values) in enumerate(operations.items(), start=1):
176-
plt.subplot(4, 3, index)
177-
plt.plot(universe, values)
178-
plt.title(title)
179-
plt.grid(True)
180-
plt.subplots_adjust(hspace=0.5)
181-
plt.show()
182-
except ImportError:
183-
for title, values in operations.items():
184-
print(f"{title}: peak membership = {values.max():.3f}")

0 commit comments

Comments
 (0)