1 """\
2 Discrete fuzzy set module. Contains basic fuzzy set and element class
3 definitions.
4
5 @author: Aaron Mavrinac
6 @organization: University of Windsor
7 @contact: mavrin1@uwindsor.ca
8 @license: GPL-3
9 """
10
11 from copy import copy
12
13 from iset import IndexedSet
17 """\
18 Fuzzy element class.
19 """
20 __slots__ = ['obj', 'mu']
21
23 """\
24 Constructor.
25
26 @param obj: The object for this member.
27 @type obj: C{object}
28 @param mu: The membership degree of this member.
29 @type mu: C{float}
30 """
31 self.obj = obj
32 self.mu = mu
33
35 """\
36 Return string representation of a fuzzy element.
37
38 @return: String representation.
39 @rtype: C{string}
40 """
41 return '%s \ %f' % (str(self.obj), self.mu)
42
43 __str__ = __repr__
44
46 """\
47 Return a hash for the fuzzy element which is the hash of its object.
48
49 @return: The hash.
50 @rtype: C{int}
51 """
52 return hash(self.obj)
53
55 """\
56 Equality operator. Note that, because this also checks for equality of
57 membership values, fuzzy sets must verify the uniqueness of obj (this
58 is handled by setting the index attribute of IndexedSet to 'obj').
59
60 @param other: The other fuzzy element.
61 @type other: L{FuzzyElement}
62 @return: True if equal, false otherwise.
63 @rtype: C{bool}
64 """
65 if not isinstance(other, FuzzyElement):
66 return False
67
68 return self.obj == other.obj and abs( self.mu - other.mu ) < 1e-10
69
71 """\
72 Inequality operator.
73
74 @param other: The other fuzzy element.
75 @type other: L{FuzzyElement}
76 @return: True if not equal, false otherwise.
77 @rtype: C{bool}
78 """
79 return not self == other
80
83 """\
84 Discrete fuzzy set class.
85 """
86 NORM_STANDARD = 0
87 NORM_ALGEBRAIC = 1
88 NORM_BOUNDED = 2
89 NORM_DRASTIC = 3
90
92 """\
93 Discrete fuzzy set iterator class.
94 """
97
100
102 while True:
103 element = self.setiterator.next()
104 if element.mu > 0:
105 return element
106
108 """\
109 Construct a fuzzy set from an optional iterable.
110
111 @param iterable: The iterable to construct from (optional).
112 @type iterable: C{object}
113 """
114 IndexedSet.__init__(self, 'obj', iterable)
115
117 """\
118 Return an iterator for this fuzzy set.
119
120 @return: Iterator.
121 @rtype: L{FuzzySet.FuzzySetIterator}
122 """
123 return FuzzySet.FuzzySetIterator(self)
124
126 """\
127 Override the length function.
128
129 @return: Size of this fuzzy set.
130 @rtype: C{int}
131 """
132 return len([element for element in self])
133
135 """\
136 Return a set item indexed by key (including those with a membership
137 degree of zero).
138
139 @param key: The index of the item to get.
140 @type key: C{object}
141 @return: The matching item.
142 @rtype: C{object}
143 """
144 for item in IndexedSet.__iter__(self):
145 if getattr(item, self.index) == key:
146 return item
147 raise KeyError(key)
148
149 - def add(self, element):
150 """\
151 Add an element to the fuzzy set. Overrides the base class add()
152 function to verify that the element is a FuzzyElement.
153
154 @param element: The element to add.
155 @type element: L{FuzzySet}
156 """
157 if not isinstance(element, FuzzyElement):
158 element = FuzzyElement(element)
159 IndexedSet.add(self, element)
160
162 """\
163 Add a fuzzy element to the fuzzy set (without explicitly constructing
164 a FuzzyElement for it). Convenience wrapper for add().
165
166 @param element: The object of the element to add.
167 @type element: C{object}
168 @param mu: The membership degree of the element.
169 @type mu: C{float}
170 """
171 self.add(FuzzyElement(element, mu))
172
174 """\
175 Update the fuzzy set contents from an iterable. Overrides the base
176 class update() function to verify that the iterable contains only
177 FuzzyElement objects.
178
179 @param iterable: The iterable to update from.
180 @type iterable: C{object}
181 """
182 for element in iterable:
183 if not isinstance(element, FuzzyElement):
184 element = FuzzyElement(element)
185 IndexedSet.update(self, iterable)
186
188 """\
189 Return a list of keys in the set (including those with a membership
190 degree of zero).
191
192 @return: List of keys in the set.
193 @rtype: C{list}
194 """
195 return [element.obj for element in IndexedSet.__iter__(self)]
196
198 """\
199 Return the membership degree of the element specified by key. Returns
200 zero for any non-member element.
201
202 @return: The membership degree of the specified element.
203 @rtype: C{float}
204 """
205 try:
206 return self[key].mu
207 except KeyError:
208 return 0.0
209
210 @property
212 """\
213 Support, the crisp set of all elements with non-zero membership in the
214 fuzzy set.
215
216 @rtype: C{set}
217 """
218 return set([element.obj for element in self])
219
220 @property
222 """\
223 Kernel, the crisp set of all elements with membership degree of exactly
224 1.
225
226 @rtype: C{set}
227 """
228 return self.alpha(1.0)
229
230 @property
232 """\
233 Height function. Returns the maximum membership degree of any element
234 in the fuzzy set.
235
236 @rtype: C{float}
237 """
238 return max([element.mu for element in self])
239
240 @property
242 """\
243 Scalar cardinality, the sum of membership degrees of all elements.
244
245 @rtype: C{float}
246 """
247 return sum([element.mu for element in self])
248
249
250
252 """\
253 Return the fuzzy union of two fuzzy sets as a new fuzzy set.
254
255 @param other: The other fuzzy set.
256 @type other: L{FuzzySet}
257 @return: The fuzzy union.
258 @rtype: L{FuzzySet}
259 """
260 return self.efficient_union(other)
261
263 """\
264 In-place fuzzy union.
265
266 @param other: The other fuzzy set.
267 @type other: L{FuzzySet}
268 @return: The fuzzy union (self).
269 @rtype: L{FuzzySet}
270 """
271 self = self.efficient_union(other)
272 return self
273
274 - def union(self, other, norm = 0):
275 """\
276 Return the fuzzy union of two fuzzy sets as a new fuzzy set.
277
278 t-Conorm Types:
279 0 - Standard Union
280 1 - Algebraic Sum
281 2 - Bounded Sum
282 3 - Drastic Union
283
284 @param other: The other fuzzy set.
285 @type other: L{FuzzySet}
286 @param norm: The t-conorm type to use.
287 @type norm: C{int}
288 @return: The fuzzy union.
289 @rtype: L{FuzzySet}
290 """
291 if not norm in range(4):
292 raise ValueError("invalid t-conorm type")
293 self._binary_sanity_check(other)
294 result = self.__class__()
295 bothkeys = set(self.keys()) | set(other.keys())
296 [lambda: result.update([FuzzyElement(key, max(self.mu(key), \
297 other.mu(key))) for key in bothkeys]),
298 lambda: result.update([FuzzyElement(key, self.mu(key) + other.mu(key) \
299 - self.mu(key) * other.mu(key)) for key in bothkeys]),
300 lambda: result.update([FuzzyElement(key, min(1.0, self.mu(key) + \
301 other.mu(key))) for key in bothkeys]),
302 lambda: result.update([FuzzyElement(key, (self.mu(key) == 0.0 and \
303 other.mu(key)) or (other.mu(key) == 0.0 and self.mu(key)) or \
304 1.0) for key in bothkeys])
305 ][norm]()
306 return result
307
309 """\
310 Optimized version of the standard fuzzy union for large fuzzy sets.
311
312 @param other: The other fuzzy set.
313 @type other: L{FuzzySet}
314 @return: The fuzzy union.
315 @rtype: L{FuzzySet}
316 """
317 self._binary_sanity_check(other)
318 result = self.__class__(self)
319 keys = result.keys()
320 for element in other:
321 if element.obj in keys:
322 result[element.obj].mu = max(result[element.obj].mu, element.mu)
323 else:
324 set.add(result, copy(element))
325 return result
326
328 """\
329 Return the fuzzy intersection of two fuzzy sets as a new fuzzy set.
330
331 @param other: The other fuzzy set.
332 @type other: L{FuzzySet}
333 @return: The fuzzy intersection.
334 @rtype: L{FuzzySet}
335 """
336 return self.intersection(other)
337
339 """\
340 In-place fuzzy intersection.
341
342 @param other: The other fuzzy set.
343 @type other: L{FuzzySet}
344 @return: The fuzzy intersection (self).
345 @rtype: L{FuzzySet}
346 """
347 self = self.intersection(other)
348 return self
349
351 """\
352 Return the fuzzy intersection of two fuzzy sets as a new fuzzy set.
353
354 t-Norm Types:
355 0 - Standard Intersection
356 1 - Algebraic Product
357 2 - Bounded Difference
358 3 - Drastic Intersection
359
360 @param other: The other fuzzy set.
361 @type other: L{FuzzySet}
362 @param norm: The t-norm type to use.
363 @type norm: C{int}
364 @return: The fuzzy intersection.
365 @rtype: L{FuzzySet}
366 """
367 if not norm in range(4):
368 raise ValueError("invalid t-norm type")
369 self._binary_sanity_check(other)
370 result = self.__class__()
371 [lambda: result.update([FuzzyElement(key, min(self.mu( key ), \
372 other.mu(key))) for key in self.keys()]),
373 lambda: result.update([FuzzyElement(key, self.mu(key) * \
374 other.mu(key)) for key in self.keys()]),
375 lambda: result.update([FuzzyElement(key, max(0.0, self.mu(key) + \
376 other.mu(key) - 1.0)) for key in self.keys()]),
377 lambda: result.update([FuzzyElement(key, (self.mu(key) == 1.0 and \
378 other.mu(key)) or (other.mu(key) == 1.0 and self.mu(key)) or 0.0) \
379 for key in self.keys()])
380 ][norm]()
381 return result
382
384 """\
385 Compare two fuzzy sets for equality.
386
387 @param other: The other fuzzy set.
388 @type other: L{FuzzySet}
389 @return: True if equal, false otherwise.
390 @rtype: C{bool}
391 """
392 self._binary_sanity_check(other)
393 if len(self) != len(other):
394 return False
395 try:
396 for element in self:
397 if element != other[element.obj]:
398 return False
399 except KeyError:
400 return False
401 return True
402
404 """\
405 Compare two fuzzy sets for inequality.
406
407 @param other: The other fuzzy set.
408 @type other: L{FuzzySet}
409 @return: True if not equal, false otherwise.
410 @rtype: C{bool}
411 """
412 return not self == other
413
415 """\
416 Report whether another fuzzy set contains this fuzzy set.
417
418 @param other: The other fuzzy set.
419 @type other: L{FuzzySet}
420 @return: True if a subset, false otherwise.
421 @rtype: C{bool}
422 """
423 self._binary_sanity_check(other)
424 if len(self) > len(other):
425 return False
426 try:
427 for element in self:
428 if element.mu > other[element.obj].mu:
429 return False
430 except KeyError:
431 return False
432 return True
433
435 """\
436 Report whether this fuzzy set contains another fuzzy set.
437
438 @param other: The other fuzzy set.
439 @type other: L{FuzzySet}
440 @return: True if a superset, false otherwise.
441 @rtype: C{bool}
442 """
443 self._binary_sanity_check(other)
444 if len(self) < len(other):
445 return False
446 try:
447 for element in other:
448 if element.mu > self[element.obj].mu:
449 return False
450 except KeyError:
451 return False
452 return True
453
454 __le__ = issubset
455 __ge__ = issuperset
456
458 """\
459 Report whether another fuzzy set strictly contains this fuzzy set,
460
461 @param other: The other fuzzy set.
462 @type other: L{FuzzySet}
463 @return: True if a strict subset, false otherwise.
464 @rtype: C{bool}
465 """
466 return self.issubset(other) and self != other
467
469 """\
470 Report whether this fuzzy set strictly contains another fuzzy set.
471
472 @param other: The other fuzzy set.
473 @type other: L{FuzzySet}
474 @return: True if a strict superset, false otherwise.
475 @rtype: C{bool}
476 """
477 return self.issuperset(other) and self != other
478
480 """\
481 Return the degree of overlap of this fuzzy set on another fuzzy set.
482
483 @param other: The other fuzzy set.
484 @type other: L{FuzzySet}
485 @return: The overlap in [0, 1] of this set on the other.
486 @rtype: C{float}
487 """
488 return self.intersection(other).cardinality / other.cardinality
489
490 @staticmethod
492 """\
493 Check that the other argument to a binary operation is also a fuzzy
494 set, raising a TypeError otherwise.
495
496 @param other: The other argument.
497 @type other: L{FuzzySet}
498 """
499 if not isinstance(other, FuzzySet):
500 raise TypeError("binary operation only permitted between fuzzy \
501 sets")
502
503
504
506 """\
507 Return the complement of this fuzzy set.
508
509 @return: The complement of this fuzzy set.
510 @rtype: L{FuzzySet}
511 """
512 result = self.__class__()
513 result.update([FuzzyElement(key, 1.0 - self.mu(key)) \
514 for key in self.keys()])
515 return result
516
518 """\
519 Return the Yager complement of this fuzzy set.
520
521 @param w: Yager operator exponent.
522 @type w: C{float}
523 @return: The Yager complement of this fuzzy set.
524 @rtype: L{FuzzySet}
525 """
526 result = self.__class__()
527 result.update([FuzzyElement(key, (1.0 - self.mu(key) ** w) ** \
528 (1.0 / w)) for key in self.keys()])
529 return result
530
532 """\
533 Alpha cut function. Returns the crisp set of members whose membership
534 degrees meet or exceed the alpha value.
535
536 @param alpha: The alpha value for the cut in (0, 1].
537 @type alpha: C{float}
538 @return: The crisp set result of the alpha cut.
539 @rtype: C{set}
540 """
541 return set([element.obj for element in self if element.mu >= alpha])
542
544 """\
545 Strong alpha cut function. Returns the crisp set of members whose
546 membership degrees exceed the alpha value.
547
548 @param alpha: The alpha value for the cut in [0, 1].
549 @type alpha: C{float}
550 @return: The crisp set result of the strong alpha cut.
551 @rtype: C{set}
552 """
553 return set([element.obj for element in self if element.mu > alpha])
554
556 """\
557 Prune the fuzzy set of all elements with zero membership.
558 """
559 prune = [element.obj for element in IndexedSet.__iter__(self) \
560 if element.mu == 0]
561 for key in prune:
562 self.remove(key)
563
565 """\
566 Normalize the fuzzy set by scaling all membership degrees by a factor
567 such that the height equals 1.
568 """
569 if self.height > 0:
570 scale = 1.0 / self.height
571 for element in self:
572 element.mu *= scale
573
574 @property
576 """\
577 Returns whether the fuzzy set is normal (height = 1).
578
579 @rtype: C{bool}
580 """
581 return self.height == 1.0
582