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

Source Code for Module fuzz.fnumber

  1  """\ 
  2  Fuzzy number module. Contains basic fuzzy number class definitions. 
  3   
  4  @author: Aaron Mavrinac 
  5  @organization: University of Windsor 
  6  @contact: mavrin1@uwindsor.ca 
  7  @license: GPL-3 
  8  """ 
  9   
 10  from math import e, sqrt, log 
 11  from numbers import Number 
 12   
 13  from fset import FuzzySet 
14 15 16 -class RealRange(tuple):
17 """\ 18 Real range class. 19 """
20 - def __new__(cls, arg = (0.0, 0.0)):
21 """\ 22 Instatiation method. Verifies the validity of the range argument 23 before returning the range object. 24 """ 25 if not len(arg) == 2: 26 raise ValueError("range must consist of two values") 27 if not isinstance(arg[0], Number) \ 28 or not isinstance(arg[1], Number): 29 raise TypeError("range values must be numeric") 30 if arg[0] > arg[1]: 31 raise ValueError("range may not have negative size") 32 return tuple.__new__(cls, arg)
33 34 @property
35 - def size(self):
36 """\ 37 Return the size of the range. 38 39 @rtype: C{float} 40 """ 41 return float(self[1] - self[0])
42
43 - def __add__(self, other):
44 """\ 45 Addition operation. 46 47 @param other: The other operand. 48 @type other: L{RealRange} 49 @return: Sum of ranges. 50 @rtype: L{RealRange} 51 """ 52 return RealRange((self[0] + other[0], self[1] + other[1]))
53
54 - def __sub__(self, other):
55 """\ 56 Subtraction operation. 57 58 @param other: The other operand. 59 @type other: L{RealRange} 60 @return: Difference of ranges. 61 @rtype: L{RealRange} 62 """ 63 return RealRange((self[0] - other[1], self[1] - other[0]))
64
65 - def __contains__(self, value):
66 """\ 67 Report whether a given value is within this range. 68 69 @param value: The value. 70 @type value: C{float} 71 @return: True if within the range, false otherwise. 72 @rtype: C{bool} 73 """ 74 return value >= self[0] and value <= self[1]
75
76 - def issubset(self, other):
77 """\ 78 Report whether another range contains this range. 79 80 @param other: The other range. 81 @type other: L{RealRange} 82 @return: True if a subset, false otherwise. 83 @rtype: C{bool} 84 """ 85 if not isinstance(other, RealRange): 86 raise TypeError("argument must be a RealRange") 87 if other[0] <= self[0] and other[1] >= self[1]: 88 return True 89 return False
90
91 - def issuperset(self, other):
92 """\ 93 Report whether this range contains another range. 94 95 @param other: The other range. 96 @type other: L{RealRange} 97 @return: True if a superset, false otherwise. 98 @rtype: C{bool} 99 """ 100 if not isinstance(other, RealRange): 101 raise TypeError("argument must be a RealRange") 102 if self[0] <= other[0] and self[1] >= other[1]: 103 return True 104 return False
105 106 __le__ = issubset 107 __ge__ = issuperset 108
109 - def __lt__(self, other):
110 """\ 111 Report whether another range strictly contains this range. 112 113 @param other: The other range. 114 @type other: L{RealRange} 115 @return: True if a strict subset, false otherwise. 116 @rtype: C{bool} 117 """ 118 return self.issubset(other) and not self == other
119
120 - def __gt__(self, other):
121 """\ 122 Report whether this range strictly contains another range. 123 124 @param other: The other range. 125 @type other: L{RealRange} 126 @return: True if a strict superset, false otherwise. 127 @rtype: C{bool} 128 """ 129 return self.issuperset(other) and not self == other
130
131 132 -class FuzzyNumber(object):
133 """\ 134 Fuzzy number class (abstract base class for all fuzzy numbers). 135 """
136 - def __init__(self):
137 """\ 138 Constructor. Not to be instantiated directly. 139 """ 140 if self.__class__ is FuzzyNumber: 141 raise NotImplementedError("please use one of the subclasses")
142
143 - def __repr__(self):
144 """\ 145 Return string representation of a trapezoidal fuzzy number. 146 147 @return: String representation. 148 @rtype: C{string} 149 """ 150 return '%s: kernel %s, support %s' % \ 151 (self.__class__.__name__, str(self.kernel), str(self.support))
152 153 __str__ = __repr__ 154
155 - def mu(self, value):
156 """\ 157 Return the membership level of a value in the universal set domain of 158 the fuzzy number. 159 160 @param value: A value in the universal set. 161 @type value: C{float} 162 """ 163 raise NotImplementedError("mu method must be overridden")
164
165 - def normalize(self):
166 """\ 167 Normalize this fuzzy number, so that its height is equal to 1.0. 168 """ 169 if not self.height == 1.0: 170 raise NotImplementedError("normalize method must be overridden")
171 172 kernel = None 173 support = None 174 height = None
175
176 177 -class PolygonalFuzzyNumber(FuzzyNumber):
178 """\ 179 Polygonal fuzzy number class. 180 """
181 - def __init__(self, points):
182 """\ 183 Constructor. 184 185 @param points: A set of points from which to generate the polygon. 186 @type points: C{list} of C{tuple} 187 """ 188 if not points[0][1] == 0.0 or not points[-1][1] == 0.0: 189 raise ValueError("points must start and end with mu = 0") 190 for i in range(1, len(points)): 191 if not points[i][0] > points[i - 1][0]: 192 raise ValueError("points must be in increasing order") 193 self.points = points 194 FuzzyNumber.__init__(self)
195 196 @staticmethod
197 - def _binary_sanity_check(other):
198 """\ 199 Check that the other argument to a binary operation is also a 200 polygonal fuzzy number, raising a TypeError otherwise. 201 202 @param other: The other argument. 203 @type other: L{PolygonalFuzzyNumber} 204 """ 205 if not isinstance(other, PolygonalFuzzyNumber): 206 raise TypeError("binary operation only permitted between \ 207 polygonal fuzzy numbers")
208
209 - def mu(self, value):
210 """\ 211 Return the membership level of a value in the universal set domain of 212 the fuzzy number. 213 214 @param value: A value in the universal set. 215 @type value: C{float} 216 """ 217 if not True in [value in subrange for subrange in self.support]: 218 return 0.0 219 for i in range(1, len(self.points)): 220 if self.points[i][0] > value: 221 return ((value - self.points[i - 1][0]) / (self.points[i][0] \ 222 - self.points[i - 1][0])) * (self.points[i][1] - \ 223 self.points[i - 1][1]) + self.points[i - 1][1] 224 return 0.0
225 226 @property
227 - def kernel(self):
228 """\ 229 Return the kernel of the fuzzy number (range of values in the 230 universal set where membership degree is equal to one). 231 232 @rtype: C{list} of L{RealRange} 233 """ 234 kernel = [] 235 start = None 236 for i in range(1, len(self.points)): 237 if start is None and self.points[i][1] == 1.0: 238 start = i 239 elif start is not None and self.points[i][1] < 1.0: 240 kernel.append(RealRange((self.points[start][0], 241 self.points[i - 1][0]))) 242 start = None 243 return kernel
244 245 @property
246 - def support(self):
247 """\ 248 Return the support of the fuzzy number (range of values in the 249 universal set where membership degree is nonzero). 250 251 @rtype: C{list} of L{RealRange} 252 """ 253 support = [] 254 start = None 255 for i in range(1, len(self.points)): 256 if start is None and self.points[i][1] > 0.0: 257 start = i - 1 258 elif start is not None and self.points[i][1] == 0.0: 259 support.append(RealRange((self.points[start][0], 260 self.points[i][0]))) 261 start = None 262 return support
263 264 @property
265 - def height(self):
266 """\ 267 Return the height of the fuzzy number (maximum membership degree 268 value). 269 270 @rtype: C{float} 271 """ 272 return max([point[1] for point in self.points])
273 274 @staticmethod
275 - def _line_intersection(p, q, r, s):
276 """\ 277 Return the point of intersection of line segments pq and rs. Helper 278 function for union and intersection. 279 280 @return: The point of intersection. 281 @rtype: C{tuple} of C{float} 282 """ 283 ua = ((s[0] - r[0]) * (p[1] - r[1]) - (s[1] - r[1]) * (p[0] - r[0])) / \ 284 ((s[1] - r[1]) * (q[0] - p[0]) - (s[0] - r[0]) * (q[1] - p[1])) 285 return(p[0] + ua * (q[0] - p[0]), p[1] + ua * (q[1] - p[1]))
286
287 - def __or__(self, other):
288 """\ 289 Return the standard fuzzy union of two polygonal fuzzy numbers as a new 290 polygonal fuzzy number. 291 292 @param other: The other fuzzy number. 293 @type other: L{PolygonalFuzzyNumber} 294 @return: The fuzzy union. 295 @rtype: L{PolygonalFuzzyNumber} 296 """ 297 return self.union(other)
298
299 - def __ior__(self, other):
300 """\ 301 In-place standard fuzzy union. 302 303 @param other: The other fuzzy number. 304 @type other: L{PolygonalFuzzyNumber} 305 @return: The fuzzy union (self). 306 @rtype: L{PolygonalFuzzyNumber} 307 """ 308 self = self.union(other) 309 return self
310
311 - def union(self, other):
312 """\ 313 Return the standard fuzzy union of two polygonal fuzzy numbers as a new 314 polygonal fuzzy number. 315 316 @param other: The other fuzzy number. 317 @type other: L{PolygonalFuzzyNumber} 318 @return: The fuzzy union. 319 @rtype: L{PolygonalFuzzyNumber} 320 """ 321 self._binary_sanity_check(other) 322 points = [] 323 for i in range(len(self.points)): 324 if self.points[i][1] >= other.mu(self.points[i][0]): 325 points.append((self.points[i], self, i)) 326 for i in range(len(other.points)): 327 if other.points[i][1] >= self.mu(other.points[i][0]): 328 points.append((other.points[i], other, i)) 329 points.sort() 330 i = 0 331 while(True): 332 try: 333 if points[i][0][0] == points[i + 1][0][0]: 334 del points[i] 335 continue 336 if points[i][1] is not points[i + 1][1]: 337 points.insert(i + 1, (self._line_intersection(points[i][0], 338 points[i][1].points[points[i][2] + 1], points[i + 1][0], 339 points[i + 1][1].points[points[i + 1][2] - 1]), 340 None, None)) 341 if points[i + 1][0][0] == points[i + 2][0][0]: 342 del points[i + 1] 343 else: 344 i += 1 345 i += 1 346 except IndexError: 347 break 348 return PolygonalFuzzyNumber([point[0] for point in points])
349
350 - def __and__(self, other):
351 """\ 352 Return the standard fuzzy intersection of two polygonal fuzzy numbers 353 as a new polygonal fuzzy number. 354 355 @param other: The other fuzzy number. 356 @type other: L{PolygonalFuzzyNumber} 357 @return: The fuzzy intersection. 358 @rtype: L{PolygonalFuzzyNumber} 359 """ 360 return self.intersection(other)
361
362 - def __iand__(self, other):
363 """\ 364 In-place standard fuzzy intersection. 365 366 @param other: The other fuzzy number. 367 @type other: L{PolygonalFuzzyNumber} 368 @return: The fuzzy intersection (self). 369 @rtype: L{PolygonalFuzzyNumber} 370 """ 371 self = self.intersection(other) 372 return self
373
374 - def intersection(self, other):
375 """\ 376 Return the standard fuzzy intersection of two polygonal fuzzy numbers 377 as a new polygonal fuzzy number. 378 379 @param other: The other fuzzy number. 380 @type other: L{PolygonalFuzzyNumber} 381 @return: The fuzzy intersection. 382 @rtype: L{PolygonalFuzzyNumber} 383 """ 384 self._binary_sanity_check(other) 385 points = [] 386 for i in range(len(self.points)): 387 if self.points[i][1] <= other.mu(self.points[i][0]): 388 points.append((self.points[i], self, i)) 389 for i in range(len(other.points)): 390 if other.points[i][1] <= self.mu(other.points[i][0]): 391 points.append((other.points[i], other, i)) 392 points.sort() 393 i = 0 394 while(True): 395 try: 396 if points[i][0][0] == points[i + 1][0][0] \ 397 or points[i][0][1] == 0.0 and points[i + 1][0][1] == 0.0: 398 del points[i] 399 continue 400 if points[i][1] is not points[i + 1][1]: 401 points.insert(i + 1, (self._line_intersection(points[i][0], 402 points[i][1].points[points[i][2] + 1], points[i + 1][0], 403 points[i + 1][1].points[points[i + 1][2] - 1]), 404 None, None)) 405 i += 1 406 i += 1 407 except IndexError: 408 break 409 return PolygonalFuzzyNumber([point[0] for point in points])
410
411 - def normalize(self):
412 """\ 413 Normalize this fuzzy number, so that its height is equal to 1.0. 414 """ 415 self.points = [(point[0], point[1] * (1.0 / self.height)) \ 416 for point in self.points]
417
418 - def to_fuzzy_set(self, samplepoints = None):
419 """\ 420 Convert this polygonal fuzzy number to a discrete fuzzy set at the 421 specified sample points. If no sample points are specified, the 422 vertices of the polygonal fuzzy number will be used. 423 424 @param samplepoints: Set of points at which to sample the number. 425 @type samplepoints: C{set} of C{float} 426 @return: Result fuzzy set. 427 @rtype: L{fset.FuzzySet} 428 """ 429 if samplepoints is None: 430 samplepoints = [point[0] for point in self.points] 431 F = FuzzySet() 432 for point in samplepoints: 433 F.add_fuzzy(point, self.mu(point)) 434 return F
435
436 437 -class TrapezoidalFuzzyNumber(FuzzyNumber):
438 """\ 439 Trapezoidal fuzzy number class. 440 """
441 - def __init__(self, kernel = (0.0, 0.0), support = (0.0, 0.0)):
442 """\ 443 Constructor. 444 445 @param kernel: The kernel of the fuzzy number. 446 @type kernel: C{tuple} 447 @param support: The support of the fuzzy number. 448 @type support: C{tuple} 449 """ 450 if not (isinstance(kernel, tuple) and len(kernel) == 2) \ 451 or not (isinstance(support, tuple) and len(support) == 2): 452 raise TypeError("kernel and support must be 2-tuples") 453 self.kernel = RealRange(kernel) 454 self.support = RealRange(support) 455 if not self.kernel <= self.support: 456 raise ValueError("kernel range must be within support range") 457 self.height = 1.0 458 FuzzyNumber.__init__(self)
459 460 @property
461 - def triangular(self):
462 """\ 463 Report if this is a triangular fuzzy number (kernel has zero size). 464 465 @rtype: C{bool} 466 """ 467 return self.kernel.size == 0
468 469 @staticmethod
470 - def _binary_sanity_check(other):
471 """\ 472 Check that the other argument to a binary operation is also a 473 trapezoidal fuzzy number, raising a TypeError otherwise. 474 475 @param other: The other argument. 476 @type other: L{TrapezoidalFuzzyNumber} 477 """ 478 if not isinstance(other, TrapezoidalFuzzyNumber): 479 raise TypeError("binary operation only permitted between \ 480 trapezoidal fuzzy numbers")
481
482 - def __add__(self, other):
483 """\ 484 Addition operation. 485 486 @param other: The other trapezoidal fuzzy number. 487 @type other: L{TrapezoidalFuzzyNumber} 488 @return: Sum of the trapezoidal fuzzy numbers. 489 @rtype: L{TrapezoidalFuzzyNumber} 490 """ 491 self._binary_sanity_check(other) 492 return self.__class__(self.kernel + other.kernel, 493 self.support + other.support)
494
495 - def __sub__(self, other):
496 """\ 497 Subtraction operation. 498 499 @param other: The other trapezoidal fuzzy number. 500 @type other: L{TrapezoidalFuzzyNumber} 501 @return: Difference of the trapezoidal fuzzy numbers. 502 @rtype: L{TrapezoidalFuzzyNumber} 503 """ 504 self._binary_sanity_check(other) 505 return self.__class__(self.kernel - other.kernel, 506 self.support - other.support)
507
508 - def mu(self, value):
509 """\ 510 Return the membership level of a value in the universal set domain of 511 the fuzzy number. 512 513 @param value: A value in the universal set. 514 @type value: C{float} 515 """ 516 if value in self.kernel: 517 return 1. 518 elif value > self.support[0] and value < self.kernel[0]: 519 return (value - self.support[0]) / \ 520 (self.kernel[0] - self.support[0]) 521 elif value < self.support[1] and value > self.kernel[1]: 522 return (self.support[1] - value) / \ 523 (self.support[1] - self.kernel[1]) 524 else: 525 return 0.
526
527 - def alpha(self, alpha):
528 """\ 529 Alpha cut function. Returns the interval within the fuzzy number whose 530 membership levels meet or exceed the alpha value. 531 532 @param alpha: The alpha value for the cut in [0, 1]. 533 @type alpha: C{float} 534 @return: The alpha cut interval. 535 @rtype: L{RealRange} 536 """ 537 return RealRange(((self.kernel[0] - self.support[0]) * alpha \ 538 + self.support[0], self.support[1] - \ 539 (self.support[1] - self.kernel[1]) * alpha))
540
541 - def to_polygonal(self):
542 """\ 543 Convert this trapezoidal fuzzy number into a polygonal fuzzy number. 544 545 @return: Result polygonal fuzzy number. 546 @rtype: L{PolygonalFuzzyNumber} 547 """ 548 points = [(self.support[0], 0.0), 549 (self.kernel[0], 1.0), 550 (self.kernel[1], 1.0), 551 (self.support[1], 0.0)] 552 return PolygonalFuzzyNumber(points)
553
554 555 -class TriangularFuzzyNumber(TrapezoidalFuzzyNumber):
556 """\ 557 Triangular fuzzy number class (special case of trapezoidal fuzzy number). 558 """
559 - def __init__(self, kernel = 0.0, support = (0.0, 0.0)):
560 """\ 561 Constructor. 562 563 @param kernel: The kernel value of the fuzzy number. 564 @type kernel: C{float} 565 @param support: The support of the fuzzy number. 566 @type support: C{tuple} 567 """ 568 TrapezoidalFuzzyNumber.__init__((kernel, kernel), support)
569
570 571 -class GaussianFuzzyNumber(FuzzyNumber):
572 """\ 573 Gaussian fuzzy number class. 574 """
575 - def __init__(self, mean, stddev):
576 """\ 577 Constructor. 578 579 @param mean: The mean (central value) of the Gaussian. 580 @type mean: C{float} 581 @param stddev: The standard deviation of the Gaussian. 582 @type stddev: C{float} 583 """ 584 self.mean = mean 585 self.stddev = stddev 586 self.height = 1.0 587 FuzzyNumber.__init__(self)
588
589 - def mu(self, value):
590 """\ 591 Return the membership level of a value in the universal set domain of 592 the fuzzy number. 593 594 @param value: A value in the universal set. 595 @type value: C{float} 596 """ 597 if value not in self.support: 598 return 0.0 599 return e ** -((value - self.mean) ** 2 / (2.0 * self.stddev ** 2))
600 601 @property
602 - def kernel(self):
603 """\ 604 Return the kernel of the fuzzy number (range of values in the 605 universal set where membership degree is equal to one). 606 607 @rtype: L{RealRange} 608 """ 609 return RealRange((self.mean, self.mean))
610 611 @property
612 - def support(self):
613 """\ 614 Return the support of the fuzzy number (range of values in the 615 universal set where membership degree is nonzero). 616 617 @rtype: L{RealRange} 618 """ 619 return self.alpha(1e-10)
620
621 - def alpha(self, alpha):
622 """\ 623 Alpha cut function. Returns the interval within the fuzzy number whose 624 membership levels meet or exceed the alpha value. 625 626 @param alpha: The alpha value for the cut in [0, 1]. 627 @type alpha: C{float} 628 @return: The alpha cut interval. 629 @rtype: L{RealRange} 630 """ 631 if alpha < 1e-10: 632 alpha = 1e-10 633 edge = sqrt(-2.0 * (self.stddev ** 2) * log(alpha)) 634 return RealRange((self.mean - edge, self.mean + edge))
635
636 - def to_polygonal(self, np):
637 """\ 638 Convert this Gaussian fuzzy number into a polygonal fuzzy number 639 (approximate). 640 641 @param np: The number of points to interpolate per side. 642 @type np: C{int} 643 @return: Result polygonal fuzzy number. 644 @rtype: L{PolygonalFuzzyNumber} 645 """ 646 if np < 0: 647 raise ValueError("number of points must be positive") 648 points = [] 649 start, end = self.support 650 increment = (self.mean - start) / float(np + 1) 651 points.append((start, 0.0)) 652 for i in range(1, np + 1): 653 value = start + i * increment 654 points.append((value, self.mu(value))) 655 points.append((self.mean, 1.0)) 656 for i in range(1, np + 1): 657 value = self.mean + i * increment 658 points.append((value, self.mu(value))) 659 points.append((end, 0.0)) 660 print points 661 return PolygonalFuzzyNumber(points)
662