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