def __sub__(self, other): "Subtract other objects." # NOTE: We expect expanded objects # symbols, if other is a product, try to let product handle the addition. if self._repr == other._repr: return create_float(0) elif other._prec == 2: # prod if other.get_vrs() == (self,): return create_product([create_float(1.0 - other.val), self]).expand() return create_sum([self, create_product([create_float(-1), other])])
def __sub__(self, other): "Subtract other objects." # NOTE: We expect expanded objects here. if other._prec == 0: # float return create_float(self.val - other.val) # Multiply other by -1 elif self.val == 0.0: return create_product([create_float(-1), other]) # Return a new sum where other is multiplied by -1 return create_sum([self, create_product([create_float(-1), other])])
def __sub__(self, other): "Subtract other objects." # NOTE: We expect expanded objects here. if other._prec == 0: # float return create_float(self.val-other.val) # Multiply other by -1 elif self.val == 0.0: return create_product([create_float(-1), other]) # Return a new sum where other is multiplied by -1 return create_sum([self, create_product([create_float(-1), other])])
def __sub__(self, other): "Subtract other objects." # NOTE: We expect expanded objects # symbols, if other is a product, try to let product handle the addition. if self._repr == other._repr: return create_float(0) elif other._prec == 2: # prod if other.get_vrs() == (self, ): return create_product([create_float(1.0 - other.val), self]).expand() return create_sum([self, create_product([create_float(-1), other])])
def __sub__(self, other): "Subtract other objects." if other._prec == 2 and self.get_vrs() == other.get_vrs(): # Return expanded product, to get rid of 3*x + -2*x -> x, not 1*x. return create_product([create_float(self.val - other.val)] + list(self.get_vrs())).expand() # if self == 2*x and other == x return 3*x. elif other._prec == 1: # sym if self.get_vrs() == (other,): # Return expanded product, to get rid of -x + x -> 0, not product(0). return create_product([create_float(self.val - 1.0), other]).expand() # Return sum return create_sum([self, create_product([FloatValue(-1), other])])
def __div__(self, other): "Division by other objects." # If division is illegal (this should definitely not happen). if other.val == 0.0: error("Division by zero") # TODO: Should we also support division by fraction for generality? # It should not be needed by this module. if other._prec == 4: # frac error("Did not expected to divide by fraction") # If fraction will be zero. if self.val == 0.0: return self # NOTE: We expect expanded objects here i.e., Product([FloatValue]) # should not be present. # Handle types appropriately. if other._prec == 0: # float return create_float(self.val / other.val) # If other is a symbol, return a simple fraction. elif other._prec == 1: # sym return create_fraction(self, other) # Don't handle division by sum. elif other._prec == 3: # sum # TODO: Here we could do: 4 / (2*x + 4*y) -> 2/(x + 2*y). return create_fraction(self, other) # If other is a product, remove any float value to avoid # 4 / (2*x), this will return 2/x. val = 1.0 for v in other.vrs: if v._prec == 0: # float val *= v.val # If we had any floats, create new numerator and only use 'real' variables # from the product in the denominator. if val != 1.0: # Check if we need to create a new denominator. # TODO: Just use other.vrs[1:] instead. if len(other.get_vrs()) > 1: return create_fraction(create_float(self.val / val), create_product(other.get_vrs())) # TODO: Because we expect all products to be expanded we shouldn't need # to check for this case, just use other.vrs[1]. elif len(other.get_vrs()) == 1: return create_fraction(create_float(self.val / val), other.vrs[1]) error("No variables left in denominator") # Nothing left to do. return create_fraction(self, other)
def __init__(self, numerator, denominator): """Initialise a Fraction object, it derives from Expr and contains the additional variables: num - expr, the numerator. denom - expr, the denominator. _expanded - object, an expanded object of self, e.g., self = 'x*y/x'-> self._expanded = y (a symbol). _reduced - object, a reduced object of self, e.g., self = '(2*x + x*y)/z'-> self._reduced = x*(2 + y)/z (a fraction). NOTE: self._prec = 4.""" # Check for illegal division. if denominator.val == 0.0: error("Division by zero.") # Initialise all variables. self.val = numerator.val self.t = min([numerator.t, denominator.t]) self.num = numerator self.denom = denominator self._prec = 4 self._expanded = False self._reduced = False # Only try to eliminate scalar values. # TODO: If we divide by a float, we could add the inverse to the # numerator as a product, but I don't know if this is efficient # since it will involve creating a new object. if denominator._prec == 0 and numerator._prec == 0: # float self.num = create_float(numerator.val / denominator.val) # Remove denominator, such that it will be excluded when printing. self.denom = None # Handle zero. if self.val == 0.0: # Remove denominator, such that it will be excluded when printing self.denom = None # Compute the representation now, such that we can use it directly # in the __eq__ and __ne__ methods (improves performance a bit, but # only when objects are cached). if self.denom: self._repr = "Fraction(%s, %s)" % (self.num._repr, self.denom._repr) else: self._repr = "Fraction(%s, %s)" % (self.num._repr, create_float(1)._repr) # Use repr as hash value. self._hash = hash(self._repr)
def __add__(self, other): "Addition by other objects." # NOTE: Assuming expanded variables. # If two products are equal, add their float values. if other._prec == 2 and self.get_vrs() == other.get_vrs(): # Return expanded product, to get rid of 3*x + -2*x -> x, not 1*x. return create_product([create_float(self.val + other.val)] + list(self.get_vrs())).expand() # if self == 2*x and other == x return 3*x. elif other._prec == 1: # sym if self.get_vrs() == (other,): # Return expanded product, to get rid of -x + x -> 0, not product(0). return create_product([create_float(self.val + 1.0), other]).expand() # Return sum return create_sum([self, other])
def __sub__(self, other): "Subtract other objects." if other._prec == 2 and self.get_vrs() == other.get_vrs(): # Return expanded product, to get rid of 3*x + -2*x -> x, not 1*x. return create_product([create_float(self.val - other.val)] + list(self.get_vrs())).expand() # if self == 2*x and other == x return 3*x. elif other._prec == 1: # sym if self.get_vrs() == (other, ): # Return expanded product, to get rid of -x + x -> 0, not product(0). return create_product([create_float(self.val - 1.0), other]).expand() # Return sum return create_sum([self, create_product([FloatValue(-1), other])])
def __div__(self, other): "Division by other objects." # If division is illegal (this should definitely not happen). if other.val == 0.0: error("Division by zero") # TODO: Should we also support division by fraction for generality? # It should not be needed by this module. if other._prec == 4: # frac error("Did not expected to divide by fraction") # If fraction will be zero. if self.val == 0.0: return self # NOTE: We expect expanded objects here i.e., Product([FloatValue]) # should not be present. # Handle types appropriately. if other._prec == 0: # float return create_float(self.val/other.val) # If other is a symbol, return a simple fraction. elif other._prec == 1: # sym return create_fraction(self, other) # Don't handle division by sum. elif other._prec == 3: # sum # TODO: Here we could do: 4 / (2*x + 4*y) -> 2/(x + 2*y). return create_fraction(self, other) # If other is a product, remove any float value to avoid # 4 / (2*x), this will return 2/x. val = 1.0 for v in other.vrs: if v._prec == 0: # float val *= v.val # If we had any floats, create new numerator and only use 'real' variables # from the product in the denominator. if val != 1.0: # Check if we need to create a new denominator. # TODO: Just use other.vrs[1:] instead. if len(other.get_vrs()) > 1: return create_fraction(create_float(self.val/val), create_product(other.get_vrs())) # TODO: Because we expect all products to be expanded we shouldn't need # to check for this case, just use other.vrs[1]. elif len(other.get_vrs()) == 1: return create_fraction(create_float(self.val/val), other.vrs[1]) error("No variables left in denominator") # Nothing left to do. return create_fraction(self, other)
def __init__(self, numerator, denominator): """Initialise a Fraction object, it derives from Expr and contains the additional variables: num - expr, the numerator. denom - expr, the denominator. _expanded - object, an expanded object of self, e.g., self = 'x*y/x'-> self._expanded = y (a symbol). _reduced - object, a reduced object of self, e.g., self = '(2*x + x*y)/z'-> self._reduced = x*(2 + y)/z (a fraction). NOTE: self._prec = 4.""" # Check for illegal division. if denominator.val == 0.0: error("Division by zero.") # Initialise all variables. self.val = numerator.val self.t = min([numerator.t, denominator.t]) self.num = numerator self.denom = denominator self._prec = 4 self._expanded = False self._reduced = False # Only try to eliminate scalar values. # TODO: If we divide by a float, we could add the inverse to the # numerator as a product, but I don't know if this is efficient # since it will involve creating a new object. if denominator._prec == 0 and numerator._prec == 0: # float self.num = create_float(numerator.val/denominator.val) # Remove denominator, such that it will be excluded when printing. self.denom = None # Handle zero. if self.val == 0.0: # Remove denominator, such that it will be excluded when printing self.denom = None # Compute the representation now, such that we can use it directly # in the __eq__ and __ne__ methods (improves performance a bit, but # only when objects are cached). if self.denom: self._repr = "Fraction(%s, %s)" %(self.num._repr, self.denom._repr) else: self._repr = "Fraction(%s, %s)" %(self.num._repr, create_float(1)._repr) # Use repr as hash value. self._hash = hash(self._repr)
def __add__(self, other): "Addition by other objects." # NOTE: Assuming expanded variables. # If two products are equal, add their float values. if other._prec == 2 and self.get_vrs() == other.get_vrs(): # Return expanded product, to get rid of 3*x + -2*x -> x, not 1*x. return create_product([create_float(self.val + other.val)] + list(self.get_vrs())).expand() # if self == 2*x and other == x return 3*x. elif other._prec == 1: # sym if self.get_vrs() == (other, ): # Return expanded product, to get rid of -x + x -> 0, not product(0). return create_product([create_float(self.val + 1.0), other]).expand() # Return sum return create_sum([self, other])
def reduce_vartype(self, var_type): """Reduce expression with given var_type. It returns a tuple (found, remain), where 'found' is an expression that only has variables of type == var_type. If no variables are found, found=(). The 'remain' part contains the leftover after division by 'found' such that: self = found*remain.""" # Sort variables according to type. found = [] remains = [] for v in self.vrs: if v.t == var_type: found.append(v) continue remains.append(v) # Create appropriate object for found. if len(found) > 1: found = create_product(found) elif found: found = found.pop() # We did not find any variables. else: return [((), self)] # Create appropriate object for remains. if len(remains) > 1: remains = create_product(remains) elif remains: remains = remains.pop() # We don't have anything left. else: return [(self, create_float(1))] # Return whatever we found. return [(found, remains)]
def __mul__(self, other): "Multiplication by other objects." # NOTE: We expect expanded objects here i.e., Product([FloatValue]) # should not be present. # Only handle case where other is a float, else let the other # object handle the multiplication. if other._prec == 0: # float return create_float(self.val*other.val) return other.__mul__(self)
def __mul__(self, other): "Multiplication by other objects." # NOTE: We expect expanded objects here i.e., Product([FloatValue]) # should not be present. # Only handle case where other is a float, else let the other # object handle the multiplication. if other._prec == 0: # float return create_float(self.val * other.val) return other.__mul__(self)
def __add__(self, other): "Addition by other objects." # NOTE: We expect expanded objects # symbols, if other is a product, try to let product handle the addition. # Returns x + x -> 2*x, x + 2*x -> 3*x. if self._repr == other._repr: return create_product([create_float(2), self]) elif other._prec == 2: # prod return other.__add__(self) return create_sum([self, other])
def __add__(self, other): "Addition by other objects." # NOTE: We expect expanded objects here. # This is only well-defined if other is a float or if self.val == 0. if other._prec == 0: # float return create_float(self.val + other.val) elif self.val == 0.0: return other # Return a new sum return create_sum([self, other])
def __add__(self, other): "Addition by other objects." # NOTE: We expect expanded objects here. # This is only well-defined if other is a float or if self.val == 0. if other._prec == 0: # float return create_float(self.val+other.val) elif self.val == 0.0: return other # Return a new sum return create_sum([self, other])
def reduce_vartype(self, var_type): """Reduce expression with given var_type. It returns a tuple (found, remain), where 'found' is an expression that only has variables of type == var_type. If no variables are found, found=(). The 'remain' part contains the leftover after division by 'found' such that: self = found*remain. Works for FloatValue and Symbol.""" if self.t == var_type: return [(self, create_float(1))] return [((), self)]
def __mul__(self, other): "Multiplication by other objects." # NOTE: assuming that we get expanded variables. # If product will be zero. if self.val == 0.0 or other.val == 0.0: return create_float(0) # Create new expanded numerator and denominator and use '/' to reduce. if other._prec != 4: # frac return (self.num*other)/self.denom # If we have a fraction, create new numerator and denominator and use # '/' to reduce expression. return create_product([self.num, other.num]).expand()/create_product([self.denom, other.denom]).expand()
def __mul__(self, other): "Multiplication by other objects." # NOTE: assuming that we get expanded variables. # If product will be zero. if self.val == 0.0 or other.val == 0.0: return create_float(0) # Create new expanded numerator and denominator and use '/' to reduce. if other._prec != 4: # frac return (self.num * other) / self.denom # If we have a fraction, create new numerator and denominator and use # '/' to reduce expression. return create_product([self.num, other.num]).expand() / create_product( [self.denom, other.denom]).expand()
def __mul__(self, other): "Multiplication by other objects." # If product will be zero. if self.val == 0.0 or other.val == 0.0: return create_float(0) # NOTE: We expect expanded sub-expressions with no nested operators. # Create list of new products using the '*' operator # TODO: Is this efficient? new_prods = [v * other for v in self.vrs] # Remove zero valued terms. # TODO: Can this still happen? new_prods = [v for v in new_prods if v.val != 0.0] # Create new sum. if not new_prods: return create_float(0) elif len(new_prods) > 1: # Expand sum to collect terms. return create_sum(new_prods).expand() # TODO: Is it necessary to call expand? return new_prods[0].expand()
def __div__(self, other): "Division by other objects." # If division is illegal (this should definitely not happen). if other.val == 0.0: error("Division by zero.") # If fraction will be zero. if self.val == 0.0: return create_float(0) # NOTE: assuming that we get expanded variables. # If other is a Sum we can only return a fraction. # TODO: We could check for equal sums if Sum.__eq__ could be trusted. # As it is now (2*x + y) == (3*x + y), which works for the other things I do. # NOTE: Expect that other is expanded i.e., x + x -> 2*x which can be handled. # TODO: Fix (1 + y) / (x + x*y) -> 1 / x # Will this be handled when reducing operations on a fraction? if other._prec == 3: # sum return create_fraction(self, other) # NOTE: We expect expanded sub-expressions with no nested operators. # Create list of new products using the '*' operator. # TODO: Is this efficient? new_fracs = [v / other for v in self.vrs] # Remove zero valued terms. # TODO: Can this still happen? new_fracs = [v for v in new_fracs if v.val != 0.0] # Create new sum. # TODO: No need to call expand here, using the '/' operator should have # taken care of this. if not new_fracs: return create_float(0) elif len(new_fracs) > 1: return create_sum(new_fracs) return new_fracs[0]
def __mul__(self, other): "Multiplication by other objects." # If product will be zero. if self.val == 0.0 or other.val == 0.0: return create_float(0) # If other is a Sum or Fraction let them handle it. if other._prec in (3, 4): # sum or frac return other.__mul__(self) # NOTE: We expect expanded sub-expressions with no nested operators. # Create new product adding float or symbol. if other._prec in (0, 1): # float or sym return create_product(self.vrs + [other]) # Create new product adding all variables from other Product. return create_product(self.vrs + other.vrs)
def __mul__(self, other): "Multiplication by other objects." # NOTE: We assume expanded objects. # If product will be zero. if self.val == 0.0 or other.val == 0.0: return create_float(0) # If other is Sum or Fraction let them handle the multiply. if other._prec in (3, 4): # sum or frac return other.__mul__(self) # If other is a float or symbol, create simple product. if other._prec in (0, 1): # float or sym return create_product([self, other]) # Else add variables from product. return create_product([self] + other.vrs)
def __div__(self, other): "Division by other objects." # NOTE: We assume expanded objects. # If division is illegal (this should definitely not happen). if other.val == 0.0: error("Division by zero.") # Return 1 if the two symbols are equal. if self._repr == other._repr: return create_float(1) # If other is a Sum we can only return a fraction. # TODO: Refine this later such that x / (x + x*y) -> 1 / (1 + y)? if other._prec == 3: # sum return create_fraction(self, other) # Handle division by FloatValue, Symbol, Product and Fraction. # Create numerator and list for denominator. num = [self] denom = [] # Add floatvalue, symbol and products to the list of denominators. if other._prec in (0, 1): # float or sym denom = [other] elif other._prec == 2: # prod # Need copies, so can't just do denom = other.vrs. denom += other.vrs # fraction. else: # TODO: Should we also support division by fraction for generality? # It should not be needed by this module. error("Did not expected to divide by fraction.") # Remove one instance of self in numerator and denominator if # present in denominator i.e., x/(x*y) --> 1/y. if self in denom: denom.remove(self) num.remove(self) # Loop entries in denominator and move float value to numerator. for d in denom: # Add the inverse of a float to the numerator, remove it from # the denominator and continue. if d._prec == 0: # float num.append(create_float(1.0/other.val)) denom.remove(d) continue # Create appropriate return value depending on remaining data. # Can only be for x / (2*y*z) -> 0.5*x / (y*z). if len(num) > 1: num = create_product(num) # x / (y*z) -> x/(y*z), elif num: num = num[0] # else x / (x*y) -> 1/y. else: num = create_float(1) # If we have a long denominator, create product and fraction. if len(denom) > 1: return create_fraction(num, create_product(denom)) # If we do have a denominator, but only one variable don't create a # product, just return a fraction using the variable as denominator. elif denom: return create_fraction(num, denom[0]) # If we don't have any donominator left, return the numerator. # x / 2.0 -> 0.5*x. return num.expand()
def reduce_vartype(self, var_type): """Reduce expression with given var_type. It returns a tuple (found, remain), where 'found' is an expression that only has variables of type == var_type. If no variables are found, found=(). The 'remain' part contains the leftover after division by 'found' such that: self = found*remain.""" # Reduce the numerator by the var type. # print "self.num._prec: ", self.num._prec # print "self.num: ", self.num if self.num._prec == 3: foo = self.num.reduce_vartype(var_type) if len(foo) == 1: num_found, num_remain = foo[0] # num_found, num_remain = self.num.reduce_vartype(var_type)[0] else: # meg: I have only a marginal idea of what I'm doing here! # print "here: " new_sum = [] for num_found, num_remain in foo: if num_found == (): new_sum.append(create_fraction(num_remain, self.denom)) else: new_sum.append(create_fraction(create_product([num_found, num_remain]), self.denom)) return create_sum(new_sum).expand().reduce_vartype(var_type) else: # num_found, num_remain = self.num.reduce_vartype(var_type) foo = self.num.reduce_vartype(var_type) if len(foo) != 1: raise RuntimeError("This case is not handled") num_found, num_remain = foo[0] # # TODO: Remove this test later, expansion should have taken care of # # no denominator. # if not self.denom: # error("This fraction should have been expanded.") # If the denominator is not a Sum things are straightforward. denom_found = None denom_remain = None # print "self.denom: ", self.denom # print "self.denom._prec: ", self.denom._prec if self.denom._prec != 3: # sum # denom_found, denom_remain = self.denom.reduce_vartype(var_type) foo = self.denom.reduce_vartype(var_type) if len(foo) != 1: raise RuntimeError("This case is not handled") denom_found, denom_remain = foo[0] # If we have a Sum in the denominator, all terms must be reduced by # the same terms to make sense else: remain = [] for m in self.denom.vrs: # d_found, d_remain = m.reduce_vartype(var_type) foo = m.reduce_vartype(var_type) d_found, d_remain = foo[0] # If we've found a denom, but the new found is different from # the one already found, terminate loop since it wouldn't make # sense to reduce the fraction. # TODO: handle I0/((I0 + I1)/(G0 + G1) + (I1 + I2)/(G1 + G2)) # better than just skipping. # if len(foo) != 1: # raise RuntimeError("This case is not handled") if len(foo) != 1 or (denom_found is not None and repr(d_found) != repr(denom_found)): # If the denominator of the entire sum has a type which is # lower than or equal to the vartype that we are currently # reducing for, we have to move it outside the expression # as well. # TODO: This is quite application specific, but I don't see # how we can do it differently at the moment. if self.denom.t <= var_type: if not num_found: num_found = create_float(1) return [(create_fraction(num_found, self.denom), num_remain)] else: # The remainder is always a fraction return [(num_found, create_fraction(num_remain, self.denom))] # Update denom found and add remainder. denom_found = d_found remain.append(d_remain) # There is always a non-const remainder if denominator was a sum. denom_remain = create_sum(remain) # print "den f: ", denom_found # print "den r: ", denom_remain # If we have found a common denominator, but no found numerator, # create a constant. # TODO: Add more checks to avoid expansion. found = None # There is always a remainder. remain = create_fraction(num_remain, denom_remain).expand() # print "remain: ", repr(remain) if num_found: if denom_found: found = create_fraction(num_found, denom_found) else: found = num_found else: if denom_found: found = create_fraction(create_float(1), denom_found) else: found = () # print "found: ", found # print len((found, remain)) return [(found, remain)]
def __init__(self, variables): """Initialise a Product object, it derives from Expr and contains the additional variables: vrs - a list of variables _expanded - object, an expanded object of self, e.g., self = x*(2+y) -> self._expanded = (2*x + x*y) (a sum), or self = 2*x -> self._expanded = 2*x (self). NOTE: self._prec = 2.""" # Initialise value, list of variables, class. self.val = 1.0 self.vrs = [] self._prec = 2 # Initially set _expanded to True. self._expanded = True # Process variables if we have any. if variables: # Remove nested Products and test for expansion. float_val = 1.0 for var in variables: # If any value is zero the entire product is zero. if var.val == 0.0: self.val = 0.0 self.vrs = [create_float(0.0)] float_val = 0.0 break # Collect floats into one variable if var._prec == 0: # float float_val *= var.val continue # Take care of product such that we don't create nested products. elif var._prec == 2: # prod # if var.vrs[0]._prec == 0: # float_val *= var.vrs[0].val # self.vrs += var.vrs[1:] # continue # self.vrs += var.vrs # continue # If expanded product is a float, just add it. if var._expanded and var._expanded._prec == 0: float_val *= var._expanded.val # If expanded product is symbol, this product is still expanded and add symbol. elif var._expanded and var._expanded._prec == 1: self.vrs.append(var._expanded) # If expanded product is still a product, add the variables. elif var._expanded and var._expanded._prec == 2: # self.vrs.append(var) # Add copies of the variables of other product (collect floats). if var._expanded.vrs[0]._prec == 0: float_val *= var._expanded.vrs[0].val self.vrs += var._expanded.vrs[1:] continue self.vrs += var._expanded.vrs # If expanded product is a sum or fraction, we must expand this product later. elif var._expanded and var._expanded._prec in (3, 4): self._expanded = False self.vrs.append(var._expanded) # Else the product is not expanded, and we must expand this one later else: self._expanded = False # Add copies of the variables of other product (collect floats). if var.vrs[0]._prec == 0: float_val *= var.vrs[0].val self.vrs += var.vrs[1:] continue self.vrs += var.vrs continue # If we have sums or fractions in the variables the product is not expanded. elif var._prec in (3, 4): # sum or frac self._expanded = False # Just add any variable at this point to list of new vars. self.vrs.append(var) # If value is 1 there is no need to include it, unless it is the # only parameter left i.e., 2*0.5 = 1. if float_val and float_val != 1.0: self.val = float_val self.vrs.append(create_float(float_val)) # If we no longer have any variables add the float. elif not self.vrs: self.val = float_val self.vrs = [create_float(float_val)] # If 1.0 is the only value left, add it. elif abs(float_val - 1.0) < format["epsilon"] and not self.vrs: self.val = 1.0 self.vrs = [create_float(1)] # If we don't have any variables the product is zero. else: self.val = 0.0 self.vrs = [create_float(0)] # The type is equal to the lowest variable type. self.t = min([v.t for v in self.vrs]) # Sort the variables such that comparisons work. self.vrs.sort() # Compute the representation now, such that we can use it directly # in the __eq__ and __ne__ methods (improves performance a bit, but # only when objects are cached). self._repr = "Product([%s])" % ", ".join([v._repr for v in self.vrs]) # Use repr as hash value. self._hash = hash(self._repr) # Store self as expanded value, if we did not encounter any sums or fractions. if self._expanded: self._expanded = self
def __init__(self, variables): """Initialise a Sum object, it derives from Expr and contains the additional variables: vrs - list, a list of variables. _expanded - object, an expanded object of self, e.g., self = 'x + x'-> self._expanded = 2*x (a product). _reduced - object, a reduced object of self, e.g., self = '2*x + x*y'-> self._reduced = x*(2 + y) (a product). NOTE: self._prec = 3.""" # Initialise value, list of variables, class, expanded and reduced. self.val = 1.0 self.vrs = [] self._prec = 3 self._expanded = False self._reduced = False # Get epsilon EPS = format["epsilon"] # Process variables if we have any. if variables: # Loop variables and remove nested Sums and collect all floats in # 1 variable. We don't collect [x, x, x] into 3*x to avoid creating # objects, instead we do this when expanding the object. float_val = 0.0 for var in variables: # Skip zero terms. if abs(var.val) < EPS: continue elif var._prec == 0: # float float_val += var.val continue elif var._prec == 3: # sum # Loop and handle variables of nested sum. for v in var.vrs: if abs(v.val) < EPS: continue elif v._prec == 0: # float float_val += v.val continue self.vrs.append(v) continue self.vrs.append(var) # Only create new float if value is different from 0. if abs(float_val) > EPS: self.vrs.append(create_float(float_val)) # If we don't have any variables the sum is zero. else: self.val = 0.0 self.vrs = [create_float(0)] # Handle zero value. if not self.vrs: self.val = 0.0 self.vrs = [create_float(0)] # Type is equal to the smallest type in both lists. self.t = min([v.t for v in self.vrs]) # Sort variables, (for representation). self.vrs.sort() # Compute the representation now, such that we can use it directly # in the __eq__ and __ne__ methods (improves performance a bit, but # only when objects are cached). self._repr = "Sum([%s])" % ", ".join([v._repr for v in self.vrs]) # Use repr as hash value. self._hash = hash(self._repr)
def __div__(self, other): "Division by other objects." # If division is illegal (this should definitely not happen). if other.val == 0.0: error("Division by zero.") # If fraction will be zero. if self.val == 0.0: return self.vrs[0] # If other is a Sum we can only return a fraction. # NOTE: Expect that other is expanded i.e., x + x -> 2*x which can be handled # TODO: Fix x / (x + x*y) -> 1 / (1 + y). # Or should this be handled when reducing a fraction? if other._prec == 3: # sum return create_fraction(self, other) # Handle division by FloatValue, Symbol, Product and Fraction. # NOTE: assuming that we get expanded variables. # Copy numerator, and create list for denominator. num = self.vrs[:] denom = [] # Add floatvalue, symbol and products to the list of denominators. if other._prec in (0, 1): # float or sym denom = [other] elif other._prec == 2: # prod # Get copy. denom = other.vrs[:] # fraction. else: error("Did not expected to divide by fraction.") # Loop entries in denominator and remove from numerator (and denominator). for d in denom[:]: # Add the inverse of a float to the numerator and continue. if d._prec == 0: # float num.append(create_float(1.0/d.val)) denom.remove(d) continue if d in num: num.remove(d) denom.remove(d) # Create appropriate return value depending on remaining data. if len(num) > 1: # TODO: Make this more efficient? # Create product and expand to reduce # Product([5, 0.2]) == Product([1]) -> Float(1). num = create_product(num).expand() elif num: num = num[0] # If all variables in the numerator has been eliminated we need to add '1'. else: num = create_float(1) if len(denom) > 1: return create_fraction(num, create_product(denom)) elif denom: return create_fraction(num, denom[0]) # If we no longer have a denominater, just return the numerator. return num
def __div__(self, other): "Division by other objects." # NOTE: We assume expanded objects. # If division is illegal (this should definitely not happen). if other.val == 0.0: error("Division by zero.") # Return 1 if the two symbols are equal. if self._repr == other._repr: return create_float(1) # If other is a Sum we can only return a fraction. # TODO: Refine this later such that x / (x + x*y) -> 1 / (1 + y)? if other._prec == 3: # sum return create_fraction(self, other) # Handle division by FloatValue, Symbol, Product and Fraction. # Create numerator and list for denominator. num = [self] denom = [] # Add floatvalue, symbol and products to the list of denominators. if other._prec in (0, 1): # float or sym denom = [other] elif other._prec == 2: # prod # Need copies, so can't just do denom = other.vrs. denom += other.vrs # fraction. else: # TODO: Should we also support division by fraction for generality? # It should not be needed by this module. error("Did not expected to divide by fraction.") # Remove one instance of self in numerator and denominator if # present in denominator i.e., x/(x*y) --> 1/y. if self in denom: denom.remove(self) num.remove(self) # Loop entries in denominator and move float value to numerator. for d in denom: # Add the inverse of a float to the numerator, remove it from # the denominator and continue. if d._prec == 0: # float num.append(create_float(1.0 / other.val)) denom.remove(d) continue # Create appropriate return value depending on remaining data. # Can only be for x / (2*y*z) -> 0.5*x / (y*z). if len(num) > 1: num = create_product(num) # x / (y*z) -> x/(y*z), elif num: num = num[0] # else x / (x*y) -> 1/y. else: num = create_float(1) # If we have a long denominator, create product and fraction. if len(denom) > 1: return create_fraction(num, create_product(denom)) # If we do have a denominator, but only one variable don't create a # product, just return a fraction using the variable as denominator. elif denom: return create_fraction(num, denom[0]) # If we don't have any donominator left, return the numerator. # x / 2.0 -> 0.5*x. return num.expand()
def __div__(self, other): "Division by other objects." # If division is illegal (this should definitely not happen). if other.val == 0.0: error("Division by zero.") # If fraction will be zero. if self.val == 0.0: return self.vrs[0] # If other is a Sum we can only return a fraction. # NOTE: Expect that other is expanded i.e., x + x -> 2*x which can be handled # TODO: Fix x / (x + x*y) -> 1 / (1 + y). # Or should this be handled when reducing a fraction? if other._prec == 3: # sum return create_fraction(self, other) # Handle division by FloatValue, Symbol, Product and Fraction. # NOTE: assuming that we get expanded variables. # Copy numerator, and create list for denominator. num = self.vrs[:] denom = [] # Add floatvalue, symbol and products to the list of denominators. if other._prec in (0, 1): # float or sym denom = [other] elif other._prec == 2: # prod # Get copy. denom = other.vrs[:] # fraction. else: error("Did not expected to divide by fraction.") # Loop entries in denominator and remove from numerator (and denominator). for d in denom[:]: # Add the inverse of a float to the numerator and continue. if d._prec == 0: # float num.append(create_float(1.0 / d.val)) denom.remove(d) continue if d in num: num.remove(d) denom.remove(d) # Create appropriate return value depending on remaining data. if len(num) > 1: # TODO: Make this more efficient? # Create product and expand to reduce # Product([5, 0.2]) == Product([1]) -> Float(1). num = create_product(num).expand() elif num: num = num[0] # If all variables in the numerator has been eliminated we need to add '1'. else: num = create_float(1) if len(denom) > 1: return create_fraction(num, create_product(denom)) elif denom: return create_fraction(num, denom[0]) # If we no longer have a denominater, just return the numerator. return num
def reduce_vartype(self, var_type): """Reduce expression with given var_type. It returns a tuple (found, remain), where 'found' is an expression that only has variables of type == var_type. If no variables are found, found=(). The 'remain' part contains the leftover after division by 'found' such that: self = found*remain.""" # Reduce the numerator by the var type. # print "self.num._prec: ", self.num._prec # print "self.num: ", self.num if self.num._prec == 3: foo = self.num.reduce_vartype(var_type) if len(foo) == 1: num_found, num_remain = foo[0] # num_found, num_remain = self.num.reduce_vartype(var_type)[0] else: # meg: I have only a marginal idea of what I'm doing here! # print "here: " new_sum = [] for num_found, num_remain in foo: if num_found == (): new_sum.append(create_fraction(num_remain, self.denom)) else: new_sum.append( create_fraction( create_product([num_found, num_remain]), self.denom)) return create_sum(new_sum).expand().reduce_vartype(var_type) else: # num_found, num_remain = self.num.reduce_vartype(var_type) foo = self.num.reduce_vartype(var_type) if len(foo) != 1: raise RuntimeError("This case is not handled") num_found, num_remain = foo[0] # # TODO: Remove this test later, expansion should have taken care of # # no denominator. # if not self.denom: # error("This fraction should have been expanded.") # If the denominator is not a Sum things are straightforward. denom_found = None denom_remain = None # print "self.denom: ", self.denom # print "self.denom._prec: ", self.denom._prec if self.denom._prec != 3: # sum # denom_found, denom_remain = self.denom.reduce_vartype(var_type) foo = self.denom.reduce_vartype(var_type) if len(foo) != 1: raise RuntimeError("This case is not handled") denom_found, denom_remain = foo[0] # If we have a Sum in the denominator, all terms must be reduced by # the same terms to make sense else: remain = [] for m in self.denom.vrs: # d_found, d_remain = m.reduce_vartype(var_type) foo = m.reduce_vartype(var_type) d_found, d_remain = foo[0] # If we've found a denom, but the new found is different from # the one already found, terminate loop since it wouldn't make # sense to reduce the fraction. # TODO: handle I0/((I0 + I1)/(G0 + G1) + (I1 + I2)/(G1 + G2)) # better than just skipping. # if len(foo) != 1: # raise RuntimeError("This case is not handled") if len(foo) != 1 or (denom_found is not None and repr(d_found) != repr(denom_found)): # If the denominator of the entire sum has a type which is # lower than or equal to the vartype that we are currently # reducing for, we have to move it outside the expression # as well. # TODO: This is quite application specific, but I don't see # how we can do it differently at the moment. if self.denom.t <= var_type: if not num_found: num_found = create_float(1) return [(create_fraction(num_found, self.denom), num_remain)] else: # The remainder is always a fraction return [(num_found, create_fraction(num_remain, self.denom))] # Update denom found and add remainder. denom_found = d_found remain.append(d_remain) # There is always a non-const remainder if denominator was a sum. denom_remain = create_sum(remain) # print "den f: ", denom_found # print "den r: ", denom_remain # If we have found a common denominator, but no found numerator, # create a constant. # TODO: Add more checks to avoid expansion. found = None # There is always a remainder. remain = create_fraction(num_remain, denom_remain).expand() # print "remain: ", repr(remain) if num_found: if denom_found: found = create_fraction(num_found, denom_found) else: found = num_found else: if denom_found: found = create_fraction(create_float(1), denom_found) else: found = () # print "found: ", found # print len((found, remain)) return [(found, remain)]