Package fuzz :: Module fset
[hide private]
[frames] | no frames]

Source Code for Module fuzz.fset

  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 
14 15 16 -class FuzzyElement(object):
17 """\ 18 Fuzzy element class. 19 """ 20 __slots__ = ['obj', 'mu'] 21
22 - def __init__(self, obj, mu = 1.0):
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
34 - def __repr__(self):
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
45 - def __hash__(self):
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
54 - def __eq__(self, other):
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 #FIXME: is there a better way to compare "almost equal" floats? 68 return self.obj == other.obj and abs( self.mu - other.mu ) < 1e-10
69
70 - def __ne__(self, other):
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
81 82 -class FuzzySet(IndexedSet):
83 """\ 84 Discrete fuzzy set class. 85 """ 86 NORM_STANDARD = 0 87 NORM_ALGEBRAIC = 1 88 NORM_BOUNDED = 2 89 NORM_DRASTIC = 3 90
91 - class FuzzySetIterator(object):
92 """\ 93 Discrete fuzzy set iterator class. 94 """
95 - def __init__(self, fuzzyset):
96 self.setiterator = IndexedSet.__iter__(fuzzyset)
97
98 - def __iter__(self):
99 return self
100
101 - def next(self):
102 while True: 103 element = self.setiterator.next() 104 if element.mu > 0: 105 return element
106
107 - def __init__(self, iterable = set()):
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
116 - def __iter__(self):
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
125 - def __len__(self):
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
134 - def __getitem__(self, key):
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
161 - def add_fuzzy(self, element, mu = 1.0):
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
173 - def update(self, iterable):
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
187 - def keys(self):
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
197 - def mu(self, key):
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
211 - def support(self):
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
221 - def kernel(self):
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
231 - def height(self):
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
241 - def cardinality(self):
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 # Binary fuzzy set operations 250
251 - def __or__(self, other):
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
262 - def __ior__(self, other):
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
308 - def efficient_union(self, other):
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
327 - def __and__(self, other):
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
338 - def __iand__(self, other):
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
350 - def intersection(self, other, norm = 0):
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
383 - def __eq__(self, other):
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
403 - def __ne__(self, other):
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
414 - def issubset(self, other):
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
434 - def issuperset(self, other):
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
457 - def __lt__(self, other):
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
468 - def __gt__(self, other):
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
479 - def overlap(self, other):
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
491 - def _binary_sanity_check(other):
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 # Unary fuzzy set operations 504
505 - def complement(self):
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
517 - def complement_yager(self, w):
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
531 - def alpha(self, alpha):
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
543 - def salpha(self, alpha):
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
555 - def prune(self):
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
564 - def normalize(self):
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
575 - def normal(self):
576 """\ 577 Returns whether the fuzzy set is normal (height = 1). 578 579 @rtype: C{bool} 580 """ 581 return self.height == 1.0
582