Package gPy :: Module Variables
[hide private]
[frames] | no frames]

Source Code for Module gPy.Variables

  1  """For manipulation of (random) variables 
  2   
  3  @var _version: Version of this module 
  4  @type _version: String 
  5  """ 
  6   
  7  _version = '$Id: Variables.py,v 1.3 2008/10/07 09:14:46 jc Exp $' 
  8   
9 -class VariableError(Exception):
10 """Base class for errors in the Variables module""" 11 pass
12 13 import operator 14
15 -def extdiv(x,y):
16 """Return x/y where 0/0 = 0 17 18 @param x: LHS of division 19 @type x: Numeric 20 @param y: LHS of division 21 @type y: Numeric 22 @return: C{x/y} where 0/0 = 0 23 @rtype: Numeric 24 @raise ZeroDivisionError: If C{y==0} and C{x!=0}. 25 """ 26 try: 27 return x / y 28 except ZeroDivisionError: 29 if x == 0: return 0 30 else: raise ZeroDivisionError
31
32 -class Domain(object):
33 """A domain is a set of random variables, implicitly representing all functions 34 from joint instantiations of the variables 35 36 @ivar _domain: A dictionary mapping each variable to its set of possible values. 37 Each key is a variable name. Each value is a frozenset. 38 @type _domain: Dictionary 39 @ivar _numvals: A dictionary mapping each variable to the number of values it can have. 40 Each key is a variable name. Each value is a positive integer 41 @type _numvals: Dictionary 42 @ivar _instd: Set of instantiated variables 43 @type _instd: Set 44 """ 45
46 - def __init__(self,domain=None,new_domain_variables=None,must_be_new=False):
47 """Construct a domain 48 49 @param domain: An existing domain to use. If None the object will be 50 independent of any existing domains. Otherwise C{self} and C{domain} will have identical 51 attributes. 52 @type domain: L{Domain} or None 53 @param new_domain_variables: A dictionary containing a mapping from any new 54 variables to their values. 55 @type new_domain_variables: Dict or None 56 @param must_be_new: Whether domain variables in C{new_domain_variables} have 57 to be new 58 @type must_be_new: Boolean 59 @raise VariableError: If a variable in C{new_domain_variables} 60 already exists with values different from 61 its values in C{new_domain_variables}; 62 Or if C{must_be_new} is set and the variable already exists. 63 """ 64 if domain is None: 65 self._domain = {} 66 self._numvals = {} 67 self._instd = set() 68 else: 69 self._domain = domain._domain 70 self._numvals = domain._numvals 71 self._instd = domain._instd 72 if new_domain_variables: 73 self.add_domain_variables(new_domain_variables,must_be_new)
74
75 - def __repr__(self):
76 return 'Domain(None,%s)' % self._domain
77
78 - def __str__(self):
79 return str(self._domain)
80
81 - def add_domain_variable(self,variable,values,must_be_new=False):
82 """Add a variable and its associated values 83 84 If C{variable} already exists then a check is done to ensure that C{values} 85 is correct. 86 @param variable: The new variable 87 @type variable: Immutable 88 @param values: The values of the new variable 89 @type values: Iterable 90 @param must_be_new: If the variable should be a new variable 91 @type must_be_new: Boolean 92 @raise VariableError: If C{variable} already exists with values different from 93 C{values}; Or if C{must_be_new} is set and the variable already exists. 94 """ 95 values = frozenset(values) 96 if variable in self._domain: 97 if must_be_new: 98 raise VariableError("%s already exists" % variable) 99 if values != self._domain[variable]: 100 raise VariableError("Conflicting values for %s\n new: %s\n old: %s" % 101 (variable, values, self._domain[variable])) 102 else: 103 self._domain[variable] = values 104 lvals = len(values) 105 self._numvals[variable] = lvals 106 if lvals == 1: 107 self._instd.add(variable)
108
109 - def add_domain_variables(self,new_domain_variables,must_be_new=False):
110 """Add variables from C{new_domain_variables} 111 112 @param new_domain_variables: A dictionary mapping variables to values 113 @type new_domain_variables: Dictionary 114 @param must_be_new: Whether domain variables in C{new_domain_variables} have 115 to be new 116 @type must_be_new: Boolean 117 @raise VariableError: If a variable in C{new_domain_variables} 118 already exists with values different from 119 its values in C{new_domain_variables}; 120 Or if C{must_be_new} is set and the variable already exists. 121 """ 122 for variable, values in new_domain_variables.items(): 123 self.add_domain_variable(variable,values,must_be_new)
124
125 - def add_domain_variables_from_rawdata(self,rawdata,must_be_new=False):
126 """Add variables from C{rawdata} 127 128 @param rawdata: A tuple like that returned by L{IO.read_csv}. 129 @type rawdata: Tuple 130 @param must_be_new: Whether domain variables from C{rawdata} have 131 to be new 132 @type must_be_new: Boolean 133 @raise VariableError: If a variable in C{rawdata} 134 already exists with values different from 135 its values in C{rawdata}; 136 Or if C{must_be_new} is set and the variable already exists. 137 """ 138 self.add_domain_variables(rawdata[1],must_be_new)
139
140 - def change_domain_variable(self,variable,values):
141 """Change the values associated with a domain variable 142 143 @param variable: The variable 144 @type variable: Immutable 145 @param values: The new values of the C{variable} 146 @type values: Iterable 147 @raise KeyError: If C{variable} is not in the domain 148 """ 149 values = frozenset(values) 150 self._domain[variable] = values 151 lvals = len(values) 152 self._numvals[variable] = lvals 153 if lvals == 1: 154 self._instd.add(variable) 155 else: 156 self._instd.discard(variable)
157
158 - def change_domain_variables(self,new_values):
159 """Change the values associated with a domain variables 160 161 @param new_values: The new values of the C{variable} 162 @type new_values: Iterable 163 @raise KeyError: If C{new_values} contains a variable not in the domain 164 """ 165 for variable, values in new_values.items(): 166 self.change_domain_variable(variable,values)
167
168 - def common_domain(self,other):
169 """Make the domain for C{other} identical to that of C{self} 170 171 The domain for self is updated to include any extra variables in C{other} 172 @param other: Domain 173 @type other: L{Domain} or subclass 174 @raise VariableError: If C{self} and C{other} use a variable with different values 175 in each one's domain. 176 """ 177 if self._domain is not other._domain: 178 self.add_domain_variables(other._domain) 179 other._domain = self._domain 180 other._numvals = self._numvals 181 other._instd = self._instd
182
183 - def copy(self):
184 """Return a (deep) copy of a domain 185 186 @return: The copy 187 @rtype: L{Domain} 188 """ 189 return Domain(new_domain_variables=self._domain)
190
191 - def known_variable(self,variable):
192 """Whether C{self} knows C{variable} (and thus its vales) 193 194 If, as is common, C{self} uses the internal default domain to 195 keep track of variables, this amounts to checking whether the 196 variable is in the internal default domain. 197 198 @param variable: A variable 199 @type variable: Immutable (usually string) 200 """ 201 return variable in self._domain
202
203 - def numvals(self,variable):
204 """Return the number of values associated with a variable 205 206 @param variable: Variable whose number of values is sought. 207 @type variable: String 208 @return: The number of values associated with a variable 209 @rtype: Int 210 @raise KeyError: If C{variable} is not in the domain 211 """ 212 return self._numvals[variable]
213
214 - def values(self,variable):
215 """Return the values associated with a variable 216 217 @param variable: Variable whose number of values is sought. 218 @type variable: String 219 @return: The values associated with a variable 220 @rtype: Frozenset 221 @raise KeyError: If C{variable} is not in the domain 222 """ 223 return self._domain[variable]
224
225 - def variables(self):
226 """Return the object's variables 227 228 @return: The object's variables 229 @rtype: Frozenset 230 """ 231 return frozenset(self._domain)
232
233 - def varvalues(self):
234 """Return the dictionary mapping the object's variables 235 to their set of possible values 236 237 @return: Map from variables to values 238 @rtype: Dictionary 239 """ 240 return self._domain.copy()
241 242 _default_domain = Domain() 243 """Default domain 244 245 Initially empty. IGNORE any value given in epydoc documentation. If there 246 is one this is just an unwanted by product of the way the documentation is produced. 247 """ 248
249 -def clear_default_domain():
250 """Reset the internal default domain to be empty""" 251 global _default_domain 252 _default_domain = Domain()
253 259
260 -def set_default_domain(dict):
261 """Set the internal default domain 262 263 Any previous values will be deleted! 264 265 @param dict: Mapping from each variable to the values it can have 266 @type dict: Dictionary 267 """ 268 global _default_domain 269 _default_domain = Domain(new_domain_variables=dict)
270
271 -def declare_variable(variable,values,must_be_new=False):
272 """Add a variable and its associated values to the default domain 273 274 If C{variable} already exists then a check is done to ensure that C{values} 275 is correct. 276 @param variable: The new variable 277 @type variable: Immutable 278 @param values: The values of the new variable 279 @type values: Iterable 280 @param must_be_new: If the variable should be a new variable 281 @type must_be_new: Boolean 282 @raise VariableError: If C{variable} already exists with values different from 283 C{values}; Or if C{must_be_new} is set and the variable already exists. 284 """ 285 _default_domain.add_domain_variable(variable,values,must_be_new=False)
286
287 -def change_variable(self,variable,values):
288 """Change the values associated with a domain variable 289 290 @param variable: The variable 291 @type variable: Immutable 292 @param values: The new values of the C{variable} 293 @type values: Iterable 294 @raise KeyError: If C{variable} is not in the domain 295 """ 296 _default_domain.change_domain_variable(variable,values)
297
298 -class SubDomain(Domain):
299 """A subdomain is a domain together with a specified subset of the domain variables. 300 301 A subdomain implicitly represents all functions from joint 302 instantiations of the domain variables whose values depend only on 303 the specified subset of the domain variables. As such they can represent data-less 304 L{Parameters.Factor} object. 305 306 @ivar _variables: The specified subset of domain variables. 307 @type _variables: frozenset 308 """ 309
310 - def __init__(self,variables=(),domain=None,new_domain_variables=None, 311 must_be_new=False,check=False):
312 """Construct a L{SubDomain} object 313 314 @param variables: The subset of domain variables for the object. 315 @type variables: Iterable 316 @param domain: A domain for the model. 317 If None the internal default domain is used. 318 If the string 'new', a new empty domain is used. 319 @type domain: L{Domain} or None 320 @param new_domain_variables: A dictionary containing a mapping from any new 321 variables to their values. C{domain} is updated with these values 322 @type new_domain_variables: Dict or None 323 @param must_be_new: Whether domain variables in C{new_domain_variables} have 324 to be new 325 @type must_be_new: Boolean 326 @param check: Whether to check that all variables exist in C{domain} 327 @type check: Boolean 328 @raise VariableError: If a variable in C{new_domain_variables} 329 already exists with values different from 330 its values in C{new_domain_variables}; 331 Or if C{must_be_new} is set and the variable already exists. 332 Or if C{check} is set and a variable in C{variables} is not in the domain 333 """ 334 if domain is None: 335 domain = _default_domain 336 elif domain == 'new': 337 domain = Domain() 338 Domain.__init__(self,domain,new_domain_variables,must_be_new) 339 variables = frozenset(variables) 340 if check and not variables.issubset(self._domain): 341 raise VariableError("Variables %s not a subset of %s " % 342 (variables,frozenset(self._domain))) 343 self._variables = variables
344
345 - def __add__(self, other):
346 """Factor addition 347 348 ('Factors' can be L{SubDomain} objects.) 349 @param other: Factor or scalar on the RHS of the addition 350 @type other: Typically L{Parameters.Factor} or float object 351 @return: Result of factor addition 352 @rtype: Same as C{self} 353 @raise VariableError: If C{self} and C{other} use a variable with different values 354 in each one's domain. 355 """ 356 return self.copy()._pointwise_op(other,operator.add)
357
358 - def __div__(self,other):
359 """Division, typically of factors 360 361 0/0 is defined to equal 1 362 ('Factors' can be L{SubDomain} objects.) 363 @param other: Factor or scalar on the RHS of the division 364 @type other: Typically L{Parameters.Factor} object or float object 365 @return: Result of factor division 366 @rtype: Same as C{self} 367 @raise VariableError: If C{self} and C{other} use a variable with different values 368 in each one's domain. 369 """ 370 return self.copy()._pointwise_op(other,extdiv)
371
372 - def __iadd__(self,other):
373 """Does in-place addition 374 375 ('Factors' can be L{SubDomain} objects.) 376 @param other: Factor or scalar on the RHS of the addition 377 @type other: Typically L{Parameters.Factor} or float object 378 @return: C{self} after being added 379 @rtype: Same as C{self} 380 @raise VariableError: If C{self} and C{other} use a variable with different values 381 in each one's domain. 382 """ 383 return self._pointwise_op(other, operator.add)
384
385 - def __idiv__(self,other):
386 """Does in-place division 387 388 ('Factors' can be L{SubDomain} objects.) 389 @param other: Factor or scalar on the RHS of the division 390 @type other: Typically L{Parameters.Factor} or float object 391 @return: C{self} after being divided 392 @rtype: Same as C{self} 393 @raise VariableError: If C{self} and C{other} use a variable with different values 394 in each one's domain. 395 """ 396 return self._pointwise_op(other,extdiv)
397
398 - def __imul__(self,other):
399 """Does in-place multiplication 400 401 ('Factors' can be L{SubDomain} objects.) 402 @param other: Factor or scalar on the RHS of the multiplication 403 @type other: Typically L{Parameters.Factor} or float object 404 @return: C{self} after being multiplied 405 @rtype: Same as C{self} 406 @raise VariableError: If C{self} and C{other} use a variable with different values 407 in each one's domain. 408 """ 409 return self._pointwise_op(other,operator.mul)
410
411 - def __isub__(self,other):
412 """Does in-place subtraction 413 414 ('Factors' can be L{SubDomain} objects.) 415 @param other: Factor or scalar on the RHS of the subtraction 416 @type other: Typically L{Parameters.Factor} or float object 417 @return: C{self} after being subtracted 418 @rtype: Same as C{self} 419 @raise VariableError: If C{self} and C{other} use a variable with different values 420 in each one's domain. 421 """ 422 return self._pointwise_op(other, operator.sub)
423
424 - def __mul__(self,other):
425 """Factor multiplication 426 427 ('Factors' can be L{SubDomain} objects.) 428 @param other: Factor or scalar on the RHS of the multiplication 429 @type other: Typically L{Parameters.Factor} or float object 430 @return: Result of factor multiplication 431 @rtype: Same as C{self} 432 @raise VariableError: If C{self} and C{other} use a variable with different values 433 in each one's domain. 434 """ 435 return self.copy()._pointwise_op(other,operator.mul)
436
437 - def __rdiv__(self,other):
438 """Only called when evaluating other / self, where 439 other is not a Factor, but self is 440 441 ('Factors' can be L{SubDomain} objects.) 442 @param other: Number 443 @type other: Numeric 444 @return: C{other} / C{self} 445 @rtype: Same as C{self} 446 @raise TypeError: If other is not a number 447 """ 448 return self.copy()._pointwise_op(other,extdiv,swapped=True)
449
450 - def __sub__(self, other):
451 """Factor subtraction 452 453 ('Factors' can be L{SubDomain} objects.) 454 @param other: Factor or scalar on the RHS of the subtraction 455 @type other: Typically L{Parameters.Factor} or float object 456 @return: Result of factor subtraction 457 @rtype: Same as C{self} 458 @raise VariableError: If C{self} and C{other} use a variable with different values 459 in each one's domain. 460 """ 461 return self.copy()._pointwise_op(other,operator.sub)
462
463 - def __repr__(self):
464 return 'SubDomain(%s,%s)' % (self._variables,Domain.__repr__(self))
465
466 - def __str__(self):
467 return str(dict((v,self._domain[v]) for v in self._variables))
468
469 - def __rmul__(self,other):
470 """Only called when evaluating other*self, where 471 other is not a Factor, but self is 472 473 ('Factors' can be L{SubDomain} objects.) 474 @param other: Number 475 @type other: Numeric 476 @return: C{other} * C{self} 477 @rtype: Same as C{self} 478 @raise TypeError: If other is not a number 479 480 """ 481 return self.copy()._pointwise_op(other,operator.mul,swapped=True)
482
483 - def copy(self,copy_domain=False):
484 """Return a 'copy' of a subdomain 485 486 @param copy_domain: If true C{self}'s domain is copied, otherwise the copy 487 shares C{self}'s domain 488 @type copy_domain: Boolean 489 @return: The copy 490 @rtype: L{SubDomain} 491 """ 492 if copy_domain: 493 domain = Domain.copy(self) 494 else: 495 domain = self 496 return SubDomain(self._variables,domain)
497 498
499 - def insts(self,variables=None):
500 """Return an iterator over joint instantiations of C{variables} 501 502 Each instantiation is a tuple of values, the order of the values 503 corresponding to the ordering of variables in C{variables}. The instantiations 504 themselves follow the standard ordering. 505 @param variables: Which variables to include. If None, all the table/factor's 506 variables are included in order. 507 @type variables: Sequence or None 508 @return: An iterator over joint instantiations 509 @rtype: Iterator 510 @raise KeyError: If a variable in C{variables} is not defined. 511 """ 512 if variables is None: 513 variables = sorted(self._variables) 514 return self._insts(variables)
515
516 - def insts_indices(self,variables=None):
517 """Return an iterator over the indices of joint instantiations of C{variables} 518 519 Each instantiation is a tuple of integers, the order of the values 520 corresponding to the ordering of variables in C{variables}. The instantiations 521 themselves follow the standard ordering. 522 @param variables: Which variables to include. If None, all the table/factor's 523 variables are included in order. 524 @type variables: Sequence or None 525 @return: An iterator over joint instantiations 526 @rtype: Iterator 527 @raise KeyError: If a variable in C{variables} is not defined. 528 """ 529 if variables is None: 530 variables = sorted(self._variables) 531 return self._insts_indices(variables)
532
533 - def inst2index(self,inst):
534 """Return the index associated with the instantiation C{inst}. 535 536 The first time this is called a lookup table (dictionary) is created 537 to enable subsequent calls to be returned by lookup. 538 @param inst: A tuple of ordered values, one for each variable 539 @type inst: Tuple 540 @return: The index associated with the instantiation C{inst} 541 @rtype: Int 542 @raise KeyError: If there is no joint instantiation C{inst}. 543 """ 544 try: 545 return self._inst2index[inst] 546 except AttributeError: 547 self._inst2index = {} 548 for i, tmp_inst in enumerate(self.insts()): 549 self._inst2index[tmp_inst] = i 550 return self._inst2index[inst]
551
552 - def drop_variable(self,variable):
553 """Alter self by dropping C{variable} 554 555 @param variable: Variable to drop 556 @type variable: Immutable 557 @return: The altered C{self} 558 @rtype: Class of C{self} 559 """ 560 return self.drop_variables((variable,))
561 562
563 - def drop_variables(self,variables):
564 """Alter self by dropping C{variables} 565 566 C{variables} is not altered. Variables in C{variables} 567 which are not in C{self}'s variables are ignored. 568 @param variables: Variables to drop 569 @type variables: Sequence 570 @return: The altered C{self} 571 @rtype: Class of C{self} 572 """ 573 self._variables = self._variables.difference(variables) 574 return self
575 576
577 - def marginalise_onto(self,variables):
578 """Alter self by marginalising onto the intersection of 579 C{variables} and C{self}'s variables 580 581 @param variables: Variables to keep 582 @type variables: Sequence 583 """ 584 return self.marginalise_away(self._variables.difference(variables))
585 586
587 - def table_size(self,variables=None):
588 """Return the number of joint instantiations of C{variables} in C{self} 589 590 @param variables: Variables for which we want number of joint instantiations. 591 If None, all variables are considered. 592 @type variables: Iterable or None 593 @return: The number of joint instantiations of variables in C{self} 594 @rtype: Integer 595 """ 596 if variables is None: 597 variables = self._variables 598 return reduce(operator.mul,[self._numvals[v] for v in variables],1)
599
600 - def sumout(self,vars_togo):
601 """Summing out (marginalising) the variables vars_togo from the factor 602 603 Does not alter C{self} 604 @param vars_togo: Variables to sum out 605 @type vars_togo: Iterable, e.g. list, tuple, set 606 @return: New factor with vars_togo summed out 607 @rtype: L{Parameters.Factor} object 608 """ 609 return self.copy().marginalise_away(vars_togo)
610
611 - def uses_default_domain(self):
612 """Whether C{self} uses the internal default domain 613 614 @return: Whether C{self} uses the internal default domain 615 @rtype: Boolean 616 """ 617 return self._domain is _default_domain._domain
618
619 - def variables(self):
620 return self._variables
621
622 - def varvalues(self):
623 return dict([[k,self._domain[k]] for k in self._variables])
624 625
626 - def _decode_inst(self,inst):
627 if isinstance(inst,(tuple,list)): 628 return self.inst2index(tuple(inst)) 629 elif isinstance(inst,dict): 630 vals = [] 631 for name in sorted(self._variables): 632 val = inst[name] 633 if val not in self._domain[name]: 634 raise KeyError("%s has no value called %s" % (name,val)) 635 else: 636 vals.append(val) 637 return self.inst2index(tuple(vals)) 638 else: 639 return inst
640 641
642 - def _insts(self,variables):
643 if variables: 644 for value in sorted(self._domain[variables[0]]): 645 for inst in self._insts(variables[1:]): 646 yield ((value),) + inst 647 else: 648 yield ()
649
650 - def _insts_indices(self,variables):
651 if variables: 652 for i in range(self._numvals[variables[0]]): 653 for inst in self._insts_indices(variables[1:]): 654 yield ((i),) + inst 655 else: 656 yield ()
657 658
659 - def _pointwise_op(self,other,op,swapped=False):
660 """ 661 @raise VariableError: If C{self} and C{other} use a variable with different values 662 in each one's values dictionary. 663 """ 664 if not isinstance(other,(float,int)): 665 self._variables = self._get_result_variables(other) 666 return self
667
668 - def _get_result_variables(self,other):
669 """ 670 @raise VariableError: If C{self} and C{other} use a variable with different values 671 in each one's domain. 672 """ 673 self.common_domain(other) 674 return self._variables | other._variables
675