Skip to content

Commit db2c45c

Browse files
amogorkondependabot[bot]
and
dependabot[bot]
committed
feat: Refactor step function to handle limit and return values
The step function in `fuzzylogic/functions.py` has been refactored to handle the limit and return values more accurately. It now returns the left argument when coming from the left, the average of the left and right arguments at the limit, and the right argument when coming from the right. This change improves the functionality and clarity of the step function. Note: The `njit` import has been commented out due to unresolved issues. Co-authored-by: dependabot[bot] <support@github.com>
1 parent 7ce45ed commit db2c45c

File tree

3 files changed

+66
-51
lines changed

3 files changed

+66
-51
lines changed

src/fuzzylogic/classes.py

+47-44
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
adding logical operaitons for easier handling.
66
"""
77

8-
from logging import warn
98
from typing import Callable
109

1110
import matplotlib.pyplot as plt
@@ -17,7 +16,6 @@
1716

1817
class FuzzyWarning(UserWarning):
1918
"""Extra Exception so that user code can filter exceptions specific to this lib."""
20-
2119
pass
2220

2321

@@ -71,19 +69,11 @@ def __init__(
7169
self._res = res
7270
self._sets = {} if sets is None else sets # Name: Set(Function())
7371

74-
def __call__(self, X):
72+
def __call__(self, x):
7573
"""Pass a value to all sets of the domain and return a dict with results."""
76-
if isinstance(X, np.ndarray):
77-
if any(not (self._low <= x <= self._high) for x in X):
78-
raise FuzzyWarning("Value in array is outside of defined range!")
79-
res = {}
80-
for s in self._sets.values():
81-
vector = np.vectorize(s.func, otypes=[float])
82-
res[s] = vector(X)
83-
return res
84-
if not (self._low <= X <= self._high):
85-
warn(f"{X} is outside of domain!")
86-
return {s: s.func(X) for name, s in self._sets.items()}
74+
if not (self._low <= x <= self._high):
75+
raise FuzzyWarning(f"{x} is outside of domain!")
76+
return {name: s.func(x) for name, s in self._sets.items()}
8777

8878
def __str__(self):
8979
"""Return a string to print()."""
@@ -195,7 +185,7 @@ class Set:
195185
name = None # these are set on assignment to the domain! DO NOT MODIFY
196186
domain = None
197187

198-
def __init__(self, func: Callable, *, name=None, domain=None):
188+
def __init__(self, func: Callable, *, name:str|None=None, domain:Domain|None=None):
199189
self.func = func
200190
self.domain = domain
201191
self.name = name
@@ -334,7 +324,7 @@ def dilated(self):
334324

335325
def multiplied(self, n):
336326
"""Multiply with a constant factor, changing all membership values."""
337-
return Set(lambda x: self.func(x) * n, domain=self)
327+
return Set(lambda x: self.func(x) * n, domain=self.domain)
338328

339329
def plot(self):
340330
"""Graph the set in the given domain."""
@@ -350,19 +340,18 @@ def array(self):
350340
raise FuzzyWarning("No domain assigned.")
351341
return np.fromiter((self.func(x) for x in self.domain.range), float)
352342

353-
@property
354343
def center_of_gravity(self):
355344
"""Return the center of gravity for this distribution, within the given domain."""
356-
if self.__center_of_gravity is not None:
357-
return self.__center_of_gravity
358345

346+
assert self.domain is not None, "No center of gravity with no domain."
359347
weights = self.array()
360348
if sum(weights) == 0:
361349
return 0
362-
cog = np.average(np.arange(len(weights)), weights=weights)
350+
cog = np.average(self.domain.range, weights=weights)
363351
self.__center_of_gravity = cog
364352
return cog
365353

354+
366355
def __repr__(self):
367356
"""
368357
Return a string representation of the Set that reconstructs the set with eval().
@@ -410,6 +399,7 @@ class Rule:
410399
"""
411400

412401
def __init__(self, conditions, func=None):
402+
print("ohalala")
413403
self.conditions = {frozenset(C): oth for C, oth, in conditions.items()}
414404
self.func = func
415405

@@ -444,30 +434,43 @@ def __call__(self, args: "dict[Domain, float]", method="cog"):
444434
assert isinstance(
445435
args, dict
446436
), "Please make sure to pass in the values as a dictionary."
447-
if method == "cog":
448-
assert (
449-
len({C.domain for C in self.conditions.values()}) == 1
450-
), "For CoG, all conditions must have the same target domain."
451-
actual_values = {
452-
f: f(args[f.domain]) for S in self.conditions.keys() for f in S
453-
}
454-
455-
weights = []
456-
for K, v in self.conditions.items():
457-
x = min((actual_values[k] for k in K if k in actual_values), default=0)
458-
if x > 0:
459-
weights.append((v, x))
460-
461-
if not weights:
462-
return None
463-
target_domain = list(self.conditions.values())[0].domain
464-
index = sum(v.center_of_gravity * x for v, x in weights) / sum(
465-
x for v, x in weights
466-
)
467-
return (target_domain._high - target_domain._low) / len(
468-
target_domain.range
469-
) * index + target_domain._low
470-
437+
match method:
438+
case "cog":
439+
assert (
440+
len({C.domain for C in self.conditions.values()}) == 1
441+
), "For CoG, all conditions must have the same target domain."
442+
actual_values = {
443+
f: f(args[f.domain]) for S in self.conditions.keys() for f in S
444+
}
445+
446+
weights = []
447+
for K, v in self.conditions.items():
448+
x = min((actual_values[k] for k in K if k in actual_values), default=0)
449+
if x > 0:
450+
weights.append((v, x))
451+
452+
if not weights:
453+
return None
454+
target_domain = list(self.conditions.values())[0].domain
455+
index = sum(v.center_of_gravity * x for v, x in weights) / sum(
456+
x for v, x in weights
457+
)
458+
return (target_domain._high - target_domain._low) / len(
459+
target_domain.range
460+
) * index + target_domain._low
461+
462+
case "centroid":
463+
raise NotImplementedError("Centroid method not implemented yet.")
464+
case "bisector":
465+
raise NotImplementedError("Bisector method not implemented yet.")
466+
case "mom":
467+
raise NotImplementedError("Middle of max method not implemented yet.")
468+
case "som":
469+
raise NotImplementedError("Smallest of max method not implemented yet.")
470+
case "lom":
471+
raise NotImplementedError("Largest of max method not implemented yet.")
472+
case _:
473+
raise ValueError("Invalid method.")
471474

472475
def rule_from_table(table: str, references: dict):
473476
"""Turn a (2D) string table into a Rule of fuzzy sets.

src/fuzzylogic/combinators.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ def F(z):
3838
from collections.abc import Callable
3939
from functools import reduce
4040

41+
from fuzzylogic.functions import noop # noqa
4142
from numpy import multiply
4243

4344
try:
44-
from numba import njit
45+
raise ImportError
46+
# from numba import njit # still not ready for prime time :(
4547
except ImportError:
4648

4749
def njit(func):

src/fuzzylogic/functions.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
from math import exp, isinf, isnan, log
3333
from typing import Optional
3434

35-
36-
3735
try:
38-
from numba import njit
36+
raise ImportError
37+
# from numba import njit # still not ready for prime time :(
38+
3939
except ImportError:
4040

4141
def njit(func):
@@ -206,19 +206,29 @@ def f(x) -> float:
206206
return f
207207

208208

209-
def step(limit, /, *, left=0, right=1):
209+
def step(limit:float|int, /, *, left:float|int=0, right:float|int=1, at_lmt:None|float|int=None) -> Callable:
210210
"""A step function.
211211
212+
Coming from left, the function returns the *left* argument.
213+
At the limit, it returns *at_lmt* or the average of left and right.
214+
After the limit, it returns the *right* argument.
212215
>>> f = step(2)
213216
>>> f(1)
214217
0
215218
>>> f(2)
219+
0.5
220+
>>> f(3)
216221
1
217222
"""
218223
assert 0 <= left <= 1 and 0 <= right <= 1
219224

220-
def f(x):
221-
return left if x < limit else right
225+
def f(x:float|int) -> float|int:
226+
if x < limit:
227+
return left
228+
elif x > limit:
229+
return right
230+
else:
231+
return at_lmt if at_lmt is not None else (left + right)/2
222232

223233
return f
224234

0 commit comments

Comments
 (0)