def __add__(self, other): "Addition by other objects." # Add two fractions if their denominators are equal by creating # (expanded) sum of their numerators. if other._prec == 4 and self.denom == other.denom: # frac return create_fraction(create_sum([self.num, other.num]).expand(), self.denom) return create_sum([self, other])
def __sub__(self, other): "Subtract other objects." # Return a new sum if other._prec == 4 and self.denom == other.denom: # frac num = create_sum([self.num, create_product([FloatValue(-1), other.num])]).expand() return create_fraction(num, self.denom) return create_sum([self, create_product([FloatValue(-1), other])])
def _group_fractions(expr): "Group Fractions in a Sum: 2/x + y/x -> (2 + y)/x." if expr._prec != 3: # sum return expr # Loop variables and group those with common denominator. not_frac = [] fracs = {} for v in expr.vrs: if v._prec == 4: # frac if v.denom in fracs: fracs[v.denom][1].append(v.num) fracs[v.denom][0] += 1 else: fracs[v.denom] = [1, [v.num], v] continue not_frac.append(v) if not fracs: return expr # Loop all fractions and create new ones using an appropriate numerator. for k, v in sorted(fracs.iteritems()): if v[0] > 1: # TODO: Is it possible to avoid expanding the Sum? # I think we have to because x/a + 2*x/a -> 3*x/a. not_frac.append(create_fraction(create_sum(v[1]).expand(), k)) else: not_frac.append(v[2]) # Create return value. if len(not_frac) > 1: return create_sum(not_frac) return not_frac[0]
def __add__(self, other): "Addition by other objects." # Add two fractions if their denominators are equal by creating # (expanded) sum of their numerators. if other._prec == 4 and self.denom == other.denom: # frac return create_fraction( create_sum([self.num, other.num]).expand(), self.denom) return create_sum([self, other])
def __sub__(self, other): "Subtract other objects." # Return a new sum if other._prec == 4 and self.denom == other.denom: # frac num = create_sum( [self.num, create_product([FloatValue(-1), other.num])]).expand() return create_fraction(num, self.denom) return create_sum([self, create_product([FloatValue(-1), other])])
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 __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 __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 __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 __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 __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 __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 __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 __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 __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 reduce_vartype(self, var_type): """Reduce expression with given var_type. It returns a list of tuples [(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 = Sum([f*r for f,r in self.reduce_vartype(Type)]).""" found = {} # print "\nself: ", self # Loop members and reduce them by vartype. for v in self.vrs: # print "v: ", v # print "red: ", v.reduce_vartype(var_type) # red = v.reduce_vartype(var_type) # f, r = v.reduce_vartype(var_type) # print "len red: ", len(red) # print "red: ", red # if len(red) == 2: # f, r = red # else: # raise RuntimeError for f, r in v.reduce_vartype(var_type): if f in found: found[f].append(r) else: found[f] = [r] # Create the return value. returns = [] for f, r in found.iteritems(): if len(r) > 1: # Use expand to group expressions. # r = create_sum(r).expand() r = create_sum(r) elif r: r = r.pop() returns.append((f, r)) return returns
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 __add__(self, other): "Addition by other objects." # 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.""" # 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 reduce_ops(self): "Reduce the number of operations needed to evaluate the sum." # global ind # ind += " " # print "\n%sreduce_ops, start" % ind if self._reduced: return self._reduced # NOTE: Assuming that sum has already been expanded. # TODO: Add test for this and handle case if it is not. # TODO: The entire function looks expensive, can it be optimised? # TODO: It is not necessary to create a new Sum if we do not have more # than one Fraction. # First group all fractions in the sum. new_sum = _group_fractions(self) if new_sum._prec != 3: # sum self._reduced = new_sum.reduce_ops() return self._reduced # Loop all variables of the sum and collect the number of common # variables that can be factored out. common_vars = {} for var in new_sum.vrs: # Get dictonary of occurrences and add the variable and the number # of occurrences to common dictionary. for k, v in var.get_var_occurrences().iteritems(): # print # print ind + "var: ", var # print ind + "k: ", k # print ind + "v: ", v if k in common_vars: common_vars[k].append((v, var)) else: common_vars[k] = [(v, var)] # print # print "common vars: " # for k,v in common_vars.items(): # print "k: ", k # print "v: ", v # print # Determine the maximum reduction for each variable # sorted as: {(x*x*y, x*y*z, 2*y):[2, [y]]}. terms_reductions = {} for k, v in sorted(common_vars.iteritems()): # print # print ind + "k: ", k # print ind + "v: ", v # If the number of expressions that can be reduced is only one # there is nothing to be done. if len(v) > 1: # TODO: Is there a better way to compute the reduction gain # and the number of occurrences we should remove? # Get the list of number of occurences of 'k' in expressions # in 'v'. occurrences = [t[0] for t in v] # Determine the favorable number of occurences and an estimate # of the maximum reduction for current variable. fav_occur = 0 reduc = 0 for i in set(occurrences): # Get number of terms that has a number of occcurences equal # to or higher than the current number. num_terms = len([o for o in occurrences if o >= i]) # An estimate of the reduction in operations is: # (number_of_terms - 1) * number_occurrences. new_reduc = (num_terms - 1) * i if new_reduc > reduc: reduc = new_reduc fav_occur = i # Extract the terms of v where the number of occurrences is # equal to or higher than the most favorable number of occurrences. terms = sorted([t[1] for t in v if t[0] >= fav_occur]) # We need to reduce the expression with the favorable number of # occurrences of the current variable. red_vars = [k] * fav_occur # If the list of terms is already present in the dictionary, # add the reduction count and the variables. if tuple(terms) in terms_reductions: terms_reductions[tuple(terms)][0] += reduc terms_reductions[tuple(terms)][1] += red_vars else: terms_reductions[tuple(terms)] = [reduc, red_vars] # print "\nterms_reductions: " # for k,v in terms_reductions.items(): # print "k: ", create_sum(k) # print "v: ", v # print "red: self: ", self if terms_reductions: # Invert dictionary of terms. reductions_terms = dict([((v[0], tuple(v[1])), k) for k, v in terms_reductions.iteritems()]) # Create a sorted list of those variables that give the highest # reduction. sorted_reduc_var = [k for k, v in reductions_terms.iteritems()] # print # print ind + "raw" # for k in sorted_reduc_var: # print ind, k[0], k[1] sorted_reduc_var.sort() # sorted_reduc_var.sort(lambda x, y: cmp(x[0], y[0])) sorted_reduc_var.reverse() # print ind + "sorted" # for k in sorted_reduc_var: # print ind, k[0], k[1] # Create a new dictionary of terms that should be reduced, if some # terms overlap, only pick the one which give the highest reduction to # ensure that a*x*x + b*x*x + x*x*y + 2*y -> x*x*(a + b + y) + 2*y NOT # x*x*(a + b) + y*(2 + x*x). reduction_vars = {} rejections = {} for var in sorted_reduc_var: terms = reductions_terms[var] if _overlap(terms, reduction_vars) or _overlap( terms, rejections): rejections[var[1]] = terms else: reduction_vars[var[1]] = terms # print "\nreduction_vars: " # for k,v in reduction_vars.items(): # print "k: ", k # print "v: ", v # Reduce each set of terms with appropriate variables. all_reduced_terms = [] reduced_expressions = [] for reduc_var, terms in sorted(reduction_vars.iteritems()): # Add current terms to list of all variables that have been reduced. all_reduced_terms += list(terms) # Create variable that we will use to reduce the terms. reduction_var = None if len(reduc_var) > 1: reduction_var = create_product(list(reduc_var)) else: reduction_var = reduc_var[0] # Reduce all terms that need to be reduced. reduced_terms = [t.reduce_var(reduction_var) for t in terms] # Create reduced expression. reduced_expr = None if len(reduced_terms) > 1: # Try to reduce the reduced terms further. reduced_expr = create_product([ reduction_var, create_sum(reduced_terms).reduce_ops() ]) else: reduced_expr = create_product(reduction_var, reduced_terms[0]) # Add reduced expression to list of reduced expressions. reduced_expressions.append(reduced_expr) # Create list of terms that should not be reduced. dont_reduce_terms = [] for v in new_sum.vrs: if not v in all_reduced_terms: dont_reduce_terms.append(v) # Create expression from terms that was not reduced. not_reduced_expr = None if dont_reduce_terms and len(dont_reduce_terms) > 1: # Try to reduce the remaining terms that were not reduced at first. not_reduced_expr = create_sum(dont_reduce_terms).reduce_ops() elif dont_reduce_terms: not_reduced_expr = dont_reduce_terms[0] # Create return expression. if not_reduced_expr: self._reduced = create_sum(reduced_expressions + [not_reduced_expr]) elif len(reduced_expressions) > 1: self._reduced = create_sum(reduced_expressions) else: self._reduced = reduced_expressions[0] # # NOTE: Only switch on for debugging. # if not self._reduced.expand() == self.expand(): # print reduced_expressions[0] # print reduced_expressions[0].expand() # print "self: ", self # print "red: ", repr(self._reduced) # print "self.exp: ", self.expand() # print "red.exp: ", self._reduced.expand() # error("Reduced expression is not equal to original expression.") return self._reduced # Return self if we don't have any variables for which we can reduce # the sum. self._reduced = self return self._reduced
def expand(self): "Expand all members of the sum." # If sum is already expanded, simply return the expansion. if self._expanded: return self._expanded # TODO: This function might need some optimisation. # Sort variables into symbols, products and fractions (add floats # directly to new list, will be handled later). Add fractions if # possible else add to list. new_variables = [] syms = [] prods = [] frac_groups = {} # TODO: Rather than using '+', would it be more efficient to collect # the terms first? for var in self.vrs: exp = var.expand() # TODO: Should we also group fractions, or put this in a separate function? if exp._prec in (0, 4): # float or frac new_variables.append(exp) elif exp._prec == 1: # sym syms.append(exp) elif exp._prec == 2: # prod prods.append(exp) elif exp._prec == 3: # sum for v in exp.vrs: if v._prec in (0, 4): # float or frac new_variables.append(v) elif v._prec == 1: # sym syms.append(v) elif v._prec == 2: # prod prods.append(v) # Sort all variables in groups: [2*x, -7*x], [(x + y), (2*x + 4*y)] etc. # First handle product in order to add symbols if possible. prod_groups = {} for v in prods: if v.get_vrs() in prod_groups: prod_groups[v.get_vrs()] += v else: prod_groups[v.get_vrs()] = v sym_groups = {} # Loop symbols and add to appropriate groups. for v in syms: # First try to add to a product group. if (v, ) in prod_groups: prod_groups[(v, )] += v # Then to other symbols. elif v in sym_groups: sym_groups[v] += v # Create a new entry in the symbols group. else: sym_groups[v] = v # Loop groups and add to new variable list. for k, v in sym_groups.iteritems(): new_variables.append(v) for k, v in prod_groups.iteritems(): new_variables.append(v) # for k,v in frac_groups.iteritems(): # new_variables.append(v) # append(v) if len(new_variables) > 1: # Return new sum (will remove multiple instances of floats during construction). self._expanded = create_sum(sorted(new_variables)) return self._expanded elif new_variables: # If we just have one variable left, return it since it is already expanded. self._expanded = new_variables[0] return self._expanded error("Where did the variables go?")
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 __sub__(self, other): "Subtract other objects." # Return a new sum return create_sum([self, create_product([FloatValue(-1), other])])