11
2+ """
3+ Domain and Set classes for fuzzy logic.
4+
5+ Primary abstractions for recursive functions for better handling.
6+ """
7+
28import matplotlib .pyplot as plt
39from numpy import arange , fromiter , array_equal , less_equal , greater_equal , less , greater
410import numpy as np
915from .combinators import MAX , MIN , product , bounded_sum , simple_disjoint_sum
1016
1117class FuzzyWarning (UserWarning ):
18+ """Extra Exception so that user code can filter exceptions specific to this lib."""
19+
1220 pass
1321
1422
@@ -48,9 +56,11 @@ class Domain:
4856 >>> temp(3) == {"hot": 0, "cold": 1}
4957 True
5058 """
59+
5160 _allowed_attrs = ['name' , 'low' , 'high' , 'res' , '_sets' ]
5261
5362 def __init__ (self , name , low , high , * , res = 1 , sets :dict = None ):
63+ """Define a domain."""
5464 assert low < high , "higher bound must be greater than lower."
5565 assert res > 0 , "resolution can't be negative or zero"
5666 self .name = name
@@ -62,32 +72,38 @@ def __init__(self, name, low, high, *, res=1, sets:dict=None):
6272
6373
6474 def __call__ (self , x ):
75+ """Pass a value to all sets of the domain and return a dict with results."""
6576 if not (self .low <= x <= self .high ):
6677 warn (f"{ x } is outside of domain!" )
6778 memberships = {name : s .func (x ) for
6879 name , s , in self ._sets .items ()}
6980 return memberships
7081
7182 def __str__ (self ):
83+ """Return a string to print()."""
7284 return self .name
7385
7486 def __repr__ (self ):
87+ """Return a string so that eval(repr(Domain)) == Domain."""
7588 return f"Domain('{ self .name } ', { self .low } , { self .high } , res={ self .res } , sets={ self ._sets } )"
7689
7790 def __eq__ (self , other ):
91+ """Test equality of two domains."""
7892 return all ([self .name == other .name ,
7993 self .low == other .low ,
8094 self .high == other .high ,
8195 self .res == other .res ,
8296 self ._sets == other ._sets ])
8397
8498 def __getattr__ (self , name ):
99+ """Get the value of an attribute."""
85100 if name in self ._sets :
86101 return self ._sets [name ]
87102 else :
88103 raise AttributeError (f"{ name } is not a set or attribute" )
89104
90105 def __setattr__ (self , name , value ):
106+ """Define a set within a domain or assign a value to a domain attribute."""
91107 # it's a domain attr
92108 if name in self ._allowed_attrs :
93109 object .__setattr__ (self , name , value )
@@ -100,6 +116,7 @@ def __setattr__(self, name, value):
100116 value .name = name
101117
102118 def __delattr__ (self , name ):
119+ """Delete a fuzzy set from the domain."""
103120 if name in self ._sets :
104121 del self ._sets [name ]
105122 else :
@@ -123,13 +140,13 @@ def min(self, x):
123140 return min (f (x ) for f in self ._sets .values ())
124141
125142 def max (self , x ):
126- """Standard way to get the max over all membership funcs.
127- """
143+ """Standard way to get the max over all membership funcs."""
128144 return max (f (x ) for f in self ._sets .values ())
129145
130146class Set :
131147 """
132148 A fuzzyset defines a 'region' within a domain.
149+
133150 The associated membership function defines 'how much' a given value is
134151 inside this region - how 'true' the value is.
135152
@@ -142,10 +159,12 @@ class Set:
142159 Note that most checks are merely assertions that can be optimized away.
143160 DO NOT RELY on these checks and use tests to make sure that only valid calls are made.
144161 """
162+
145163 _domain = None
146164 _name = None
147165
148166 def __init__ (self , func :callable , * , domain = None , name = None ):
167+ """Initialize the set."""
149168 assert callable (func ) or isinstance (func , str )
150169 # if func is a str, we've got a pickled function via repr
151170
@@ -197,27 +216,35 @@ def fset(self, value):
197216 del domain_
198217
199218 def __call__ (self , x ):
219+ """Call the function of the set (which can be of any arbitrary complexity)."""
200220 return self .func (x )
201221
202222 def __invert__ (self ):
223+ """Return a new set with modified function."""
203224 return Set (inv (self .func ))
204225
205226 def __and__ (self , other ):
227+ """Return a new set with modified function."""
206228 return Set (MIN (self .func , other .func ))
207229
208230 def __or__ (self , other ):
231+ """Return a new set with modified function."""
209232 return Set (MAX (self .func , other .func ))
210233
211234 def __mul__ (self , other ):
235+ """Return a new set with modified function."""
212236 return Set (product (self .func , other .func ))
213237
214238 def __add__ (self , other ):
239+ """Return a new set with modified function."""
215240 return Set (bounded_sum (self .func , other .func ))
216241
217242 def __xor__ (self , other ):
243+ """Return a new set with modified function."""
218244 return Set (simple_disjoint_sum (self .func , other .func ))
219245
220246 def __pow__ (self , power ):
247+ """Return a new set with modified function."""
221248 #FYI: pow is used with hedges
222249 return Set (lambda x : pow (self .func (x ), power ))
223250
@@ -259,16 +286,19 @@ def __gt__(self, other):
259286 return all (greater (self .array (), other .array ()))
260287
261288 def __len__ (self ):
289+ """Number of membership values in the set, defined by bounds and resolution of domain."""
262290 if self .domain is None :
263291 raise FuzzyWarning ("No domain." )
264292 return len (self .array ())
265293
266294 def cardinality (self ):
295+ """The sum of all values in the set."""
267296 if self .domain is None :
268297 raise FuzzyWarning ("No domain." )
269298 return sum (self .array ())
270299
271300 def relative_cardinality (self ):
301+ """Relative cardinality is the sum of all membership values by number of all values."""
272302 if self .domain is None :
273303 raise FuzzyWarning ("No domain." )
274304 if len (self ) == 0 :
@@ -278,6 +308,8 @@ def relative_cardinality(self):
278308
279309 def concentrated (self ):
280310 """
311+ Alternative to hedge "very".
312+
281313 Returns an new array that has a reduced amount of values the set includes and to dampen the
282314 membership of many values.
283315 TODO: implement this as a new set?
@@ -288,8 +320,11 @@ def concentrated(self):
288320
289321 def intensified (self ):
290322 """
291- Returns a new array where the membershi of values are increased that
323+ Alternative to using hedges.
324+
325+ Returns a new array where the membership of values are increased that
292326 already strongly belong to the set and dampened the rest.
327+
293328 TODO: implement this as a new set?
294329 """
295330 return NotImplemented
@@ -298,30 +333,32 @@ def intensified(self):
298333 else :
299334 return 1 - 2 (1 - x ** 2 )
300335
301- def dilatated (self ):
302- """Expands the set with more values and already included values are enhanced.
303- TODO: implement this as a new set?"""
336+ def dilated (self ):
337+ """Expand the set with more values and already included values are enhanced.
338+
339+ TODO: implement this as a new set?
340+ """
304341 return NotImplemented
305342 return x ** 1. / 2.
306343
307- def multiplication (self , n ):
308- """Set is multiplied with a constant factor, which changes all membership values.
309- TODO: implement this as a new set?"""
344+ def multiplied (self , n ):
345+ """Multiply with a constant factor, changing all membership values.
346+
347+ TODO: implement this as a new set?
348+ """
310349 return NotImplemented
311350 return x * n
312351
313352 def plot (self ):
314- """Graph the set.
315- Use the bounds and resolution of the domain to display the set
316- unless specified otherwise.
317- """
353+ """Graph the set in the given domain."""
318354 if self .domain is None :
319355 raise FuzzyWarning ("No domain assigned, cannot plot." )
320356 R = self .domain .range ()
321357 V = [self .func (x ) for x in R ]
322358 plt .plot (R , V )
323359
324360 def array (self ):
361+ """Return an array of all values for this set within the given domain."""
325362 if self .domain is None :
326363 raise FuzzyWarning ("No domain assigned." )
327364 return fromiter ((self .func (x ) for x in self .domain .range ()),
@@ -350,13 +387,14 @@ def create_function_closure():
350387 return f"Set({ self .func } , domain={ self .domain } , name={ self .name } )"
351388
352389 def __str__ (self ):
390+ """Return a string for print()."""
353391 if self .name is None and self .domain is None :
354392 return f"dangling Set({ self .func } )"
355393 else :
356394 return f"{ self .domain } .{ self .name } "
357395
358396 def normalized (self ):
359- """Returns a set *in this domain* whose max value is 1."""
397+ """Return a set *in this domain* whose max value is 1."""
360398 if self .domain is None :
361399 raise FuzzyWarning ("Can't normalize without domain." )
362400 else :
0 commit comments