def eval(self): pat = caspy.pattern_match.pat_construct("a*pi", {"a": "const"}, self.parser) pmatch_res, _ = caspy.pattern_match.pmatch(pat, self.arg) if pmatch_res != {}: # Don't check against a_val as 0 == False a_val = pmatch_res["a"] a_val = self.shift_to_2pi(a_val) logger.debug("a_val is {}".format(a_val)) neg = False if a_val > Fraction(1, 1): # If a is between 1 and 2 we can work out -sin((a-1)*pi) # to get the same result neg = True a_val -= 1 fnd = False for (coeff, val) in self.pi_coeffs: if coeff == a_val: fnd = True numeric_val = self.parser.parse(val) break if fnd: if neg: return numeric_val.neg() else: return numeric_val elif self.arg == Numeric(0, "number"): # TODO somehow add handling for case a=0 in pattern matching return Numeric(0, "number") return Numeric(Symbol(self, Fraction(1, 1)), "sym_obj")
def eval(self): if self.arg.is_exclusive_numeric(): logger.debug("Attempting to simplify {} for sqrt".format(self.arg)) if len(self.arg.val) > 1: logger.warning("Argument {} is exclusively numeric but" "contains more than 1 symbol. Not trying" "to simplify").format(self.arg) else: # Make a copy of the argument value in case the fraction # evaluated doesn't give a nice fraction representation sym = copy.deepcopy(self.arg.val[0]) # Attempt to simplify # Removing this breaks sqrt(1/4) for example?!?! sym.simplify() logger.debug("Symbol {}".format(sym)) if sym.is_zero(): return Numeric(0,"number") frac = sym.sym_frac_eval() if frac.is_int_frac(): surd_den = frac.den # Sym is a fraction. Multiply it by den/den Fraction to # ensure you don't end up with a surd on the bottom factors = factoriseNum(frac.num*surd_den) f_out = 1 surd = 1 logger.debug("Factors {}".format(factors)) if factors != [] and int(frac.den) == frac.den: for f in set(factors): # Only looks at each factor once cnt_f = factors.count(f) if cnt_f % 2 == 0: # Even amount of occurrences of factors so factor # them all out f_out *= f ** (cnt_f//2) elif cnt_f > 1: # Must be an odd amount of occurrences of factor f_out *= f ** ((cnt_f - 1)//2) surd *= f else: surd *= f logger.debug("Simplified to {} * sqrt({})".format( f_out,surd )) if surd == 1: # It's just 1 inside the surd so just return the # f_out as a symbol return Numeric(f_out,"number") self.arg = Numeric(surd,"number") new_num = Numeric(Symbol(self, Fraction(1, 1)), "sym_obj") scalar = Symbol(1,Fraction(f_out,surd_den)) new_num.mul(Numeric(scalar,"sym_obj")) # new_num.val.append(Symbol(Fraction(f_out,1),1)) return new_num return Numeric(Symbol(self, Fraction(1, 1)), "sym_obj")
def simplify(self, simp_pows=True): """ Simplifies this symbol by looking for the simple numeric aspects eg [2,1] and [4,-1] in the value class represents 2 * 4^(-1) which can be simplified to 1/2 :return: none """ # logger.debug("Simplifying symbol {}".format(self)) if self.is_zero(): logger.debug("Value is zero so remove all other parts") self.val = [[Fraction(0, 1), 1]] return acc = Fraction(1, 1) to_remove = [] for i in range(0, len(self.val)): # Deal with the case when the power is zero # By removing it we get rid of terms like x^0 if type(self.val[i][1]) == num.Numeric: if type(self.val[i][0]) == Fraction and \ self.val[i][1].is_exclusive_numeric() and simp_pows: try: pow_val = self.val[i][1].frac_eval() if int(pow_val.to_real()) != pow_val.to_real(): continue new_val = copy.deepcopy(self.val[i][0])**int( pow_val.to_real()) # if new_val.is_int_frac(): acc *= new_val to_remove.append(i) except ValueError: pass if self.val[i][1].is_zero(): to_remove.append(i) continue elif isinstance(self.val[i][0], funcs.Function): self.val[i][0].simplify() continue # Deal with numeric 'coefficient terms' if type(self.val[i][0]) == Fraction and self.val[i][1] == 1: # Multiply accumulator by this fraction acc *= self.val[i][0] to_remove.append(i) elif type(self.val[i][0]) == Fraction and self.val[i][1] == -1: # Multiply accumulator by reciprocal of this fraction acc *= self.val[i][0].recip() to_remove.append(i) if to_remove == []: return # Remove the indexes that have been factored into the acc new_val = [] for (j, sym_part) in enumerate(self.val): if j not in to_remove: new_val.append(sym_part) self.val = new_val + [[acc, 1]]
def __init__(self, x, *args): self.pi_coeffs = [[Fraction(1, 2), "1"], [Fraction(1, 3), "sqrt(3)/2"], [Fraction(2, 3), "sqrt(3)/2"], [Fraction(1, 4), "sqrt(1/2)"], [Fraction(3, 4), "sqrt(1/2)"], [Fraction(1, 6), "1/2"], [Fraction(5, 6), "1/2"], [Fraction(1, 1), "0"], [Fraction(0, 1), "0"]] self.arg = x super().__init__(*args)
def np_polyn_to(polyn): new_repr = [] max_pow = len(polyn) - 1 for index, coeff in enumerate(polyn): if coeff != 0: new_repr.append([Fraction(int(coeff), 1), max_pow - index]) return new_repr
def to_frac(self): if self.arg.is_exclusive_numeric(): return Fraction(self.arg.frac_eval().to_real() ** 0.5, 1) else: logger.warning("Argument {} of sqrt isn't exclusive numeric".format( self.arg ))
def sym_frac_eval(self) -> Frac: """ Attempts to get a floating point representation of this symbol. If this is not possible it raises an exception :return: Fraction """ ret = Fraction(1, 1) for (sym_name, sym_pow) in self.val: if type(sym_pow) == num.Numeric: sym_pow_frac = sym_pow.frac_eval() elif type(sym_pow) == Fraction: sym_pow_frac = sym_pow elif type(sym_pow) in (int, float): sym_pow_frac = sym_pow else: raise Exception( "Can't get fraction representation of {}".format(sym_pow)) if type(sym_name) == Fraction: ret *= sym_name**sym_pow_frac elif isinstance(sym_name, funcs.Function): if sym_name.fname != "re": ret *= sym_name.to_frac()**sym_pow_frac else: continue else: raise Exception( "Can't get fraction representation of {}".format(self)) return ret
def eval(self): val = self.arg.frac_eval() if self.float_rep: return val.to_real() else: return caspy.numeric.numeric.Numeric(Fraction(val.to_real(), 1), "number")
def eval(self): pat = caspy.pattern_match.pat_construct("a*pi", {"a": "const"}) pmatch_res, _ = caspy.pattern_match.pmatch(pat, self.arg) if pmatch_res != {}: # Don't check against a_val as 0 == False a_val = pmatch_res["a"] # Try and use sin to evaluate this as # cos(x) = sin(x+pi/2) sin_a_val = a_val + Fraction(1, 2) sin_val = Sin(self.parser.parse( "pi * {}".format(sin_a_val))).eval() if sin_val.is_exclusive_numeric(): return sin_val elif self.arg == Numeric(0, "number"): # TODO somehow add handling for case a=0 in pattern matching return Numeric(1, "number") return Numeric(Symbol(self, Fraction(1, 1)), "sym_obj")
def coeff(self) -> Frac: index = self.get_coeff_index() if index != -1: return self.val[index][0] # This can occur in some cases like 2^2 logger.info("No coefficient index found for object {} so treating " "coeff as 1".format(self)) return Fraction(1, 1)
def neg(self): """Negates this symbol""" coeff_index = self.get_coeff_index() if coeff_index != -1: # Multiply the coefficient index by 1 self.val[coeff_index][0] *= -1 else: logger.warning("No coefficient index found for object {} so " "adding one as -1".format(self)) self.val.append([Fraction(-1, 1), 1])
def add_coeff(self, x: Frac) -> None: logger.debug("Adding coefficient {} to {}".format(x, self)) coeff_index = self.get_coeff_index() if coeff_index != -1: # Increment the coefficient by x self.val[coeff_index][0] += x else: logger.info( "No coefficient index found for object {}".format(self)) # Introduce a coefficient term and add x self.val.append([Fraction(1, 1) + x, 1])
def frac_eval(self) -> Frac: """ Attempts to get a floating point representation of this symbol. If this is not possible it raises an exception :return: float """ ret = Fraction(0, 1) for sym in self.val: x = sym.sym_frac_eval() ret += x return ret
def mul(self, x): """ Multiplies this number by x. If this number or x is made up of a linear combination of terms, eg (1+x) then we don't expand the product :param x: Numeric object :return: self """ if len(self.val) == 1 and len(x.val) > 1: pre_x = copy.deepcopy(x) self.val[0].mul(Symbol(pre_x, Fraction(1, 1))) elif len(self.val) > 1 or len(x.val) > 1: pre_self = copy.copy(self) pre_x = copy.copy(x) # Redefine this numeric object as having just one symbol # that is the product of it's prior self and x self.val = [Symbol(pre_self, Fraction(1, 1))] self.val[0].val.append([pre_x, Numeric(1, "number")]) else: self.val[0].mul(x.val[0]) return self
def __init__(self, val, typ: str = ""): """Initialises a numeric class with a single value""" self.val = [] if typ == "": if type(val) == Symbol: self.val.append(val) elif type(val) == Fraction: self.val.append(Symbol(1, val)) elif type(val) in [float, int]: self.val.append(Symbol(1, Fraction(float(val), 1))) else: if typ == "sym": # val represents the letter of the symbol, such as x,y,.. self.val.append(Symbol(val, Fraction(1, 1))) elif typ == "number": # in this case the 1 represents that this value is just a number if type(val) == Fraction: self.val.append(Symbol(1, val)) else: self.val.append(Symbol(1, Fraction(float(val), 1))) elif typ == "sym_obj": self.val.append(val)
def pow(self, x): """ Raises this number to the power x :param x: Numeric object :return: self """ if len(self.val) == 1: self.val[0] = self.val[0].pow(x) return self else: # Make a copy of this object for later use pre_exp = copy.copy(self) # Redefine this object as a list of length 1 with the previous # info stored as a symbol type and raise that to the power x self.val = [Symbol(pre_exp, Fraction(1, 1))] self.val[0].val[0][1] = x return self
def shift_to_2pi(self, x: Fraction): """ Takes a fraction that represents a coefficient of pi and shifts it so it lies in the range 0 to 2 :param x: Fraction :return: Fraction """ if x.to_real() > 0: if int(x.to_real()) % 2 == 0: x -= int(x.to_real()) else: x = x - int(x.to_real()) + 1 else: if (int(x.to_real()) - 1) % 2 == 0: x -= int(x.to_real()) - 1 else: x = x - (int(x.to_real()) - 1) + 1 return x
def add(self, y): """ Adds y to this number :param y: Another numeric class :return: self """ if self.is_zero(): self.val = y.val return self elif y.is_zero(): return self for sym_y in y.val: sym_y.simplify() lookup = self.sym_and_pow_match(sym_y) if lookup: # A term with the same symbols already exists in this numeric expression # so just add coeffs logger.debug("sym_and_pow match on {} and {}".format( self, sym_y)) # Find coefficient for sym_y sym_y_coeff_index = sym_y.get_coeff_index() if sym_y_coeff_index != -1: lookup.add_coeff(sym_y.val[sym_y_coeff_index][0]) else: logger.debug( "No coefficient for symbol {} so adding '1' as coeff". format(sym_y)) sym_y.val.append([Fraction(1, 1), 1]) lookup.add_coeff(sym_y.val[sym_y_coeff_index][0]) else: self.val.append(sym_y) # Check for unnecessary zeroes new_val = [] if len(self.val) > 1: for sym in self.val: if not sym.is_zero(): new_val.append(sym) self.val = new_val return self
def eval(self): self.wrt = "x" # Store a representation of the polynomial as [[coeff1,power1],...] polyn = [] terms_used = [] for i, sym in enumerate(self.arg.val): pmatch_res = caspy.pattern_match.pmatch_sym( "A1*{}^N1".format(self.wrt), { "N1": "const", "A1": "const" }, sym) if "A1" in pmatch_res.keys() and "N1" in pmatch_res.keys(): if pmatch_res["N1"].to_real() == int(pmatch_res["N1"]) and \ pmatch_res["A1"].is_int_frac(): polyn.append([pmatch_res["A1"], int(pmatch_res["N1"])]) terms_used.append(i) elif sym.is_exclusive_numeric(): try: c = sym.sym_frac_eval() if c.is_int_frac(): polyn.append([c, 0]) terms_used.append(i) except Exception: logger.critical( "Failed exclusive numeric evaluation of symbol") output_val = [] if len(polyn) > 0: empty_polyn = False # Get degree of polynomial n = max(polyn, key=lambda x: x[1])[1] # Convert polynomial so it's stored like # [coeff nth power, coeff n-1st power,...,constant term] polyn_to_factor = [Fraction(0, 1) for i in range(n + 1)] for coeff, power in polyn: polyn_to_factor[n - power] = coeff # print("Factorising {}".format(polyn_to_factor)) cont, m_val, factored = kronecker(polyn_to_factor) factors_str = [] for factor in factored: if factor == [1]: continue parts = [] max_fact_pow = len(factor) - 1 for i, coeff in enumerate(factor): # TODO possibly refactor this so it doesn't make a string # to be parsed? parts.append("{}*{}^{}".format(coeff, self.wrt, max_fact_pow - i)) factors_str.append("({})".format("+".join(parts))) parser = caspy.parsing.parser.Parser() output = parser.parse("{}/{} * {}".format(cont, m_val, "*".join(factors_str))) else: empty_polyn = True output = Numeric(0) for i, sym in enumerate(self.arg.val): if i not in terms_used: # Add on other terms to the output that we didn't try to factorise output_val.append(copy.deepcopy(sym)) if empty_polyn: output.val = output_val else: output.val = output.val + output_val return output
def eval(self): return num.Numeric(caspy.numeric.symbol.Symbol(self, Fraction(1, 1)), "sym_obj")
def pmatch(pat, expr): """ Tries to match a pattern generated in pat_construct to a given expression :param pat: Numeric object with some Placeholder objects :param expr: Numeric object :return: A tuple: (dict with keys as Placeholder names, the matching numeric expression). In effect the second term is just the expr input, but it is there for recursive purposes """ out = {} used_terms = [] remaining_placeholder_name = None # Simplify both expressions pat.simplify() expr.simplify() logger.info("Pattern matching {} with expr {}".format(pat, expr)) attempted_match_sum = caspy.numeric.numeric.Numeric(0, "number") for pat_sym in pat.val: sym_done = False for i, expr_sym in enumerate(expr.val): # Attempt to match this pattern sym with this expression sym # Initialise a blank symbol with value 1 attempted_match_sym = caspy.numeric.symbol.Symbol( 1, Fraction(1, 1)) syms_added_from_pat = caspy.numeric.symbol.Symbol( 1, Fraction(1, 1)) # Make a temporary dict to store the names of coefficients used # and their values in this attempt. If the symbols match then we # transfer these values to the actual out dict tmp_out = {} unused_expression_factors_name = None used_expression_factors = [] logger.debug("Expr sym is {}".format(expr_sym)) for (pat_sym_fact_name, pat_sym_fact_pow) in pat_sym.val: if type(pat_sym_fact_name) == ConstPlaceholder: tmp_out[pat_sym_fact_name.name] = expr_sym.coeff # Multiply the attempted_match_sym by the coeff coeff_tmp_sym = caspy.numeric.symbol.Symbol( 1, expr_sym.coeff) attempted_match_sym = attempted_match_sym * coeff_tmp_sym logger.debug( "Multiply coefficient {} onto match_sym".format( expr_sym.coeff)) used_terms.append(i) used_expression_factors.append(expr_sym.get_coeff_index()) elif type(pat_sym_fact_name) == RemainingPlaceholder: # Set a flag so remaining terms will be included in out dict # once all other terms have been matched remaining_placeholder_name = pat_sym_fact_name.name elif type(pat_sym_fact_name) == CoeffPlaceholder: unused_expression_factors_name = pat_sym_fact_name.name elif type(pat_sym_fact_name) == str: if type(pat_sym_fact_pow) == caspy.numeric.numeric.Numeric: if not does_numeric_contain_placeholders( pat_sym_fact_pow): used_terms.append(i) # Can just multiply this term onto the match_sym val attempted_match_sym.val.append( [pat_sym_fact_name, pat_sym_fact_pow]) # Multiply this term onto syms_added_from_pat # so it won't appear twice if we are using a # CoeffPlaceholder syms_added_from_pat.val.append( [pat_sym_fact_name, pat_sym_fact_pow]) logger.debug("Multiplying term {}^{} onto " "match_sym".format( pat_sym_fact_name, pat_sym_fact_pow)) else: # Find matching term for j, expr_sym_fact in enumerate(expr_sym.val): if expr_sym_fact[0] == pat_sym_fact_name: # Matching term found, therefore turn the # powers into Numeric objects and run pmatch logger.debug( "RECURSING WITH {} AND {}".format( pat_sym_fact_pow, expr_sym_fact[1])) pmatch_res, num_pow = pmatch( pat_sym_fact_pow, expr_sym_fact[1]) logger.debug( "Recursed pmatch result {}".format( pmatch_res)) tmp_out.update(pmatch_res) used_terms.append(i) used_expression_factors.append(j) attempted_match_sym.val.append( [pat_sym_fact_name, num_pow]) break elif isinstance(pat_sym_fact_name, caspy.functions.function.Function): # TODO checking numeric powers if not does_numeric_contain_placeholders( pat_sym_fact_name.arg): used_terms.append(i) # Can just multiply this term onto the match_sym val attempted_match_sym.val.append( [pat_sym_fact_name, pat_sym_fact_pow]) logger.debug("Multiplying term {}^{} onto " "match_sym".format( pat_sym_fact_name, pat_sym_fact_pow)) else: # Find matching term logger.debug("Trying to find matching term") for j, expr_sym_fact in enumerate(expr_sym.val): logger.debug("Matching {} with {}".format( pat_sym_fact_name, expr_sym_fact)) if not isinstance( expr_sym_fact[0], caspy.functions.function.Function): continue if expr_sym_fact[0].fname == pat_sym_fact_name.fname \ and expr_sym_fact[1] == pat_sym_fact_pow: # Matching term found, therefore pmatch the # funtion arguments logger.info("Recursing with {} and {}".format( pat_sym_fact_name.arg, expr_sym_fact[0].arg)) pmatch_res, pmatch_res_arg = pmatch( pat_sym_fact_name.arg, expr_sym_fact[0].arg) logger.info( "Recursed pmatch result {} resulting arg {}" .format(pmatch_res, pmatch_res_arg)) tmp_out.update(pmatch_res) used_terms.append(i) used_expression_factors.append(j) # TODO maybe define fns dict somewhere else if expr_sym_fact[ 0].fname in caspy.parsing.simplify_output.fns: new_fn_obj = caspy.parsing.simplify_output.fns[ expr_sym_fact[0].fname](pmatch_res_arg) attempted_match_sym.val.append( [new_fn_obj, pat_sym_fact_pow]) break # Use up remaining factors of the expression symbol if we have # encountered a CoeffPlaceholder object if unused_expression_factors_name is not None: logger.debug( "syms_added_from_pat {}".format(syms_added_from_pat)) logger.debug( "Attempted match sym {}".format(attempted_match_sym)) unused_factors_prod = caspy.numeric.symbol.Symbol( 1, Fraction(1, 1)) for k, expression_factor in enumerate(expr_sym.val): if k not in used_expression_factors: unused_factors_prod.val.append( copy.deepcopy(expression_factor)) logger.debug( "unused_factors_prod {}".format(unused_factors_prod)) unused_factors_prod = unused_factors_prod / copy.deepcopy( syms_added_from_pat) unused_factors_prod.simplify() logger.debug("Coeff val {}".format(unused_factors_prod)) tmp_out.update({ unused_expression_factors_name: caspy.numeric.numeric.Numeric(unused_factors_prod, "sym_obj") }) attempted_match_sym = attempted_match_sym * unused_factors_prod if attempted_match_sym == expr_sym and \ attempted_match_sym.coeff == expr_sym.coeff: logger.debug("Updating out dict") # Have matched it successfully so can update the out dict # TODO testing for when the same placeholder appears multiple # times in patter, ie stuff like a*x^2 + a*y^2 out.update(tmp_out) # Break out of this loop as the symbol has been matched sym_done = True break else: logger.info( "Can't update out dict " "attempted_match_sym {} == expr_sym {} returns {}".format( attempted_match_sym, expr_sym, attempted_match_sym == expr_sym)) if sym_done: attempted_match_sum = attempted_match_sum + \ caspy.numeric.numeric.Numeric(attempted_match_sym,"sym_obj") if remaining_placeholder_name is not None: remaining_numeric_obj = caspy.numeric.numeric.Numeric(0, "number") for i, sym in enumerate(expr.val): if i not in used_terms: sym_numeric_obj = caspy.numeric.numeric.Numeric(sym, "sym_obj") remaining_numeric_obj += sym_numeric_obj out[remaining_placeholder_name] = remaining_numeric_obj return out, expr if list(sorted(set(used_terms))) != list(range(0, len(expr.val))): return {}, None return out, attempted_match_sum
from datetime import timedelta from caspy.tests.test_symbolic import p, latex_eval from caspy.numeric.fraction import Fraction from hypothesis import given, settings import hypothesis.strategies as st sin_pi_coeffs = [[Fraction(1, 2), "1"], [Fraction(1, 3), "sqrt(3)/2"], [Fraction(2, 3), "sqrt(3)/2"], [Fraction(1, 4), "sqrt(1/2)"], [Fraction(3, 4), "sqrt(1/2)"], [Fraction(1, 6), "1/2"], [Fraction(5, 6), "1/2"], [Fraction(1, 1), "0"], [Fraction(0, 1), "0"]] cos_pi_coeffs = [[Fraction(1, 2), "0"], [Fraction(1, 3), "1/2"], [Fraction(2, 3), "-1/2"], [Fraction(1, 4), "sqrt(2)/2"], [Fraction(3, 4), "-sqrt(2)/2"], [Fraction(1, 6), "sqrt(3)/2"], [Fraction(5, 6), "-sqrt(3)/2"], [Fraction(1, 1), "-1"], [Fraction(0, 1), "1"]] tan_pi_coeffs = [[Fraction(1, 3), "sqrt(3)"], [Fraction(2, 3), "sin(2/3*pi)/cos(2/3*pi)"], [Fraction(1, 4), "1"], [Fraction(3, 4), "-1"], [Fraction(1, 6), "1/sqrt(3)"], [Fraction(5, 6), "-1/sqrt(3)"], [Fraction(1, 1), "0"], [Fraction(0, 1), "0"]] # Default deadline is reached on travis, so increase it @settings(deadline=timedelta(milliseconds=500)) @given(st.integers(min_value=-1000, max_value=1000)) def test_sin_pis(x): """Test sin(n*pi) = 0 for all integers""" assert latex_eval("sin({}*pi)".format(x)) == "0"
def try_replace_numeric_with_var(self, x, y): """ Trys to replace some term (x) in this symbol with another variable. For instance we could try and replace the term x^2 in the symbol sin(x^2) with a variable u. So sin(x^2) would become sin(u) :param x: Numeric object :param y: Numeric object :return: Another numeric object if it has been successful, otherwise None """ MAX_DIVS = 10 MAX_MULTS = 10 if self == x: return y vars_to_remove = x.get_variables_in() sym_args_done = copy.deepcopy(self) for sym in self.val: for (sym_fact_name, sym_fact_pow) in sym.val: if isinstance(sym_fact_name, funcs.Function): # deal with the function argument result = sym_fact_name.arg.try_replace_numeric_with_var( x, copy.deepcopy(y)) if result is not None: sym_fact_name.arg = result if sym_args_done.get_variables_in().intersection( vars_to_remove) == set(): # Don't need to try multiplication or division on this as it is # already done return sym_args_done # Try repeated divs rep_divs = var_replacements.try_replace_numeric_with_var_divs( copy.deepcopy(self), copy.deepcopy(x), copy.deepcopy(y)) if rep_divs is not None: return rep_divs # Try repeated multiplications rep_mults = var_replacements.try_replace_numeric_with_var_mults( copy.deepcopy(self), copy.deepcopy(x), copy.deepcopy(y)) if rep_mults is not None: return rep_mults # Try repeated divs+multiplications symbol by symbol new_sym_list = [] failed_syms = [] for i, sym in enumerate(self.val): sym_try_divs = Numeric(copy.deepcopy(sym)) rep_divs = var_replacements.try_replace_numeric_with_var_divs( sym_try_divs, copy.deepcopy(x), copy.deepcopy(y)) if rep_divs is not None: # rep_divs.val probably just has length 1 but maybe not?!?! [new_sym_list.append(x) for x in rep_divs.val] continue sym_try_mults = Numeric(copy.deepcopy(sym)) rep_mults = var_replacements.try_replace_numeric_with_var_divs( sym_try_mults, copy.deepcopy(x), copy.deepcopy(y)) if rep_mults is not None: # rep_mults.val probably just has length 1 but maybe not?!?! [new_sym_list.append(x) for x in rep_mults.val] else: new_sym_val = [] for (sym_fact, sym_pow) in sym.val: if isinstance(sym_pow, Numeric): if sym_pow.get_variables_in().intersection( vars_to_remove) != set(): tmp_sym_pow = copy.deepcopy(sym_pow) repped = tmp_sym_pow.try_replace_numeric_with_var( copy.deepcopy(x), copy.deepcopy(y)) if repped is not None: new_sym_val.append([sym_fact, repped]) else: # Exhausted ideas so give up return None else: new_sym_val.append([sym_fact, sym_pow]) else: new_sym_val.append([sym_fact, sym_pow]) # Can add this symbol onto new_sym_list tmp = Symbol(1, Fraction(1, 1)) tmp.val = new_sym_val new_sym_list.append(tmp) # Make a numeric object with new_sym_list as val new_val = Numeric(0) new_val.val = new_sym_list return new_val
def eval(self): """ Evaluates the integral. This will be done symbol by symbol, so for instance 2*x^2 + sin(x) will be done by integrating 2x^2 then sin(x) :return: Numeric object """ logger.debug("Attempting to integrate {}".format(self.arg)) parser = caspy.parsing.parser.Parser() tot = caspy.numeric.numeric.Numeric(0, "number") integrated_values = [] for i, sym in enumerate(self.arg.val): # Simplify symbol, this get's rid of issues caused by terms # like x^0 sym.simplify() if sym.is_exclusive_numeric(): # Integrate constant term frac_val = sym.sym_frac_eval() frac_val_num = caspy.numeric.numeric.Numeric( frac_val, "number") wrt_term = caspy.numeric.numeric.Numeric(self.wrt, "sym") tot += wrt_term * frac_val_num integrated_values.append(i) # Go onto next term continue if not sym.has_variable_in(self.wrt): # Integrate term that doesn't contain the variable we're # integrating with respect to sym_numeric_obj = caspy.numeric.numeric.Numeric(sym, "sym_obj") wrt_term = caspy.numeric.numeric.Numeric(self.wrt, "sym") tot += sym_numeric_obj * wrt_term integrated_values.append(i) continue # Try matching x^n pmatch_res = pm.pmatch_sym("a*{}^n".format(self.wrt), { "n": "const", "a": "const" }, sym, self.parser) if pmatch_res != {}: logger.debug( "Integrating polynomial term {}, pmatch result {}".format( sym, pmatch_res)) if pmatch_res["n"] == -1: term_val = self.parser.parse("{} * ln({})".format( copy(pmatch_res["a"]), self.wrt)) else: # TODO maybe refactor Fraction class so it doesn't return self # so we don't need to do a ridiculous amount of copying like this term_val = self.parser.parse("{} * {} ^ ({})".format( copy(pmatch_res["a"]) / (copy(pmatch_res["n"]) + 1), self.wrt, copy(pmatch_res["n"]) + 1)) tot += term_val integrated_values.append(i) # Go onto next term continue # Try integrating exponential terms pmatch_res = pm.pmatch_sym("A1 * e^(A2)".format(self.wrt), { "A1": "const", "A2": "rem" }, sym, self.parser) if pmatch_res != {}: logger.debug( "Integrating exponential term, pmatch result {}".format( pmatch_res)) # Currently pattern matching doesn't quite work correctly with matching # things like e^(a*x+b) so we will have to pattern match the A2 term exp_pow = expand.Expand(pmatch_res["A2"]).eval() pat = pm.pat_construct("B1 * {} + B2".format(self.wrt), { "B1": "const", "B2": "rem" }) pmatch_res_pow, _ = pm.pmatch(pat, exp_pow) logger.debug("Power pmatch result {}".format(pmatch_res_pow)) if "B1" in pmatch_res_pow.keys(): failed_int = False if "B2" in pmatch_res_pow.keys(): if self.wrt in pmatch_res_pow["B2"].get_variables_in(): logger.debug( "Failed integration of exponential term") failed_int = True if not failed_int: term_val = caspy.numeric.numeric.Numeric( copy(pmatch_res["A1"]) / copy(pmatch_res_pow["B1"])) term_val.val[0].val.append( ["e", deepcopy(pmatch_res["A2"])]) tot += term_val integrated_values.append(i) continue # Try integrating exponential terms with u sub pmatch_res = pm.pmatch_sym("B1*e^(A1)", { "A1": "rem", "B1": "coeff" }, sym, self.parser) if pmatch_res != {}: logger.debug( "Integrating e^(...) object with u sub, pmatch_res {}". format(pmatch_res)) numeric_wrapper = caspy.numeric.numeric.Numeric( deepcopy(sym), "sym_obj") u_subbed = self.u_sub_int(pmatch_res["A1"], numeric_wrapper) if u_subbed is not None: logger.warning("U sub integral worked") tot += u_subbed integrated_values.append(i) continue # Try matching simple sin terms like sin(ax+b) pmatch_res = pm.pmatch_sym("a*sin(b*{}+c)".format(self.wrt), { "a": "const", "b": "const", "c": "const" }, sym, self.parser) if pmatch_res != {}: logger.debug("Integrating simple linear sin term") term_val = self.parser.parse("{} * cos({}*{}+{})".format( copy(pmatch_res["a"]) * (-1) / copy(pmatch_res["b"]), pmatch_res["b"], self.wrt, pmatch_res.get("c", 0))) tot += term_val integrated_values.append(i) continue # Try matching simple sin terms like cos(ax+b) pmatch_res = pm.pmatch_sym("a*cos(b*{}+c)".format(self.wrt), { "a": "const", "b": "const", "c": "const" }, sym, self.parser) if pmatch_res != {}: logger.debug("Integrating simple linear cos term") term_val = self.parser.parse("{} * sin({}*{}+{})".format( copy(pmatch_res["a"]) / copy(pmatch_res["b"]), pmatch_res["b"], self.wrt, pmatch_res.get("c", 0))) tot += term_val integrated_values.append(i) continue # Try integrating sin(___) term with a 'u' substitution pmatch_res = pm.pmatch_sym("b*sin(a)", { "a": "rem", "b": "coeff" }, sym, self.parser) if pmatch_res != {}: logger.debug( "Integrating sin object with u sub, pmatch_res {}".format( pmatch_res)) numeric_wrapper = caspy.numeric.numeric.Numeric( deepcopy(sym), "sym_obj") u_subbed = self.u_sub_int(pmatch_res["a"], numeric_wrapper) if u_subbed is not None: logger.warning("U sub integral worked") tot += u_subbed integrated_values.append(i) continue # Try integrating cos(___) term with a 'u' substitution pmatch_res = pm.pmatch_sym("b*cos(a)", { "a": "rem", "b": "coeff" }, sym, self.parser) if pmatch_res != {}: logger.debug( "Integrating cos object with u sub, pmatch_res {}".format( pmatch_res)) numeric_wrapper = caspy.numeric.numeric.Numeric( deepcopy(sym), "sym_obj") u_subbed = self.u_sub_int(pmatch_res["a"], numeric_wrapper) if u_subbed is not None: logger.warning("U sub integral worked") tot += u_subbed integrated_values.append(i) continue if self.root_integral and not self.dont_expand: # Try expanding the symbol then integrating sym_numeric = caspy.numeric.numeric.Numeric( deepcopy(sym), "sym_obj") expand_obj = expand.Expand(sym_numeric) expanded = expand_obj.eval() logger.info("Expanded val: {}".format( ln.latex_numeric_str(expanded))) integ_exp = Integrate(expanded, self.wrt, True, True) new_integral = integ_exp.eval() if integ_exp.fully_integrated: integrated_values.append(i) tot += new_integral continue else: logger.info("Failed expanded int {}".format( ln.latex_numeric_str(new_integral))) if not self.root_integral: continue # Try integrating by parts int_done_by_parts = False for (a, b) in group_list_into_all_poss_pairs(deepcopy(sym.val)): # Make symbols for a and b\ a_sym = caspy.numeric.symbol.Symbol(1, Fraction(1, 1)) a_sym.val = a a_num = caspy.numeric.numeric.Numeric(a_sym, "sym_obj") b_sym = caspy.numeric.symbol.Symbol(1, Fraction(1, 1)) b_sym.val = b b_num = caspy.numeric.numeric.Numeric(b_sym, "sym_obj") logger.debug("PRE Int by parts u_prime {} v {}".format( ln.latex_numeric_str(a_num), ln.latex_numeric_str(b_num))) by_parts_ab = self.int_by_parts(a_num, b_num) if by_parts_ab is not None: logger.debug("Int by parts u_prime {} v {}".format( ln.latex_numeric_str(a_num), ln.latex_numeric_str(b_num))) tot += by_parts_ab integrated_values.append(i) int_done_by_parts = True else: by_part_ba = self.int_by_parts(b_num, a_num) if by_part_ba is not None: logger.debug("Int by parts2 u_prime {} v {}".format( ln.latex_numeric_str(b_num), ln.latex_numeric_str(a_num))) tot += by_part_ba integrated_values.append(i) int_done_by_parts = True if int_done_by_parts: break else: logger.debug("One round of int by parts failed") if int_done_by_parts: continue new_val = [] # Remove integrated values from the arg property for j, term_val in enumerate(self.arg.val): if j not in integrated_values: new_val.append(term_val) if new_val == []: # All terms have been integrated self.fully_integrated = True return tot else: # Make a numeric object to store the non integrated terms new_val_numeric_obj = caspy.numeric.numeric.Numeric(1, "number") new_val_numeric_obj.val = new_val self.arg = new_val_numeric_obj # Return integrated terms and other terms in an integral remaining_int = super().eval() return remaining_int + tot
def test_fraction_simplification(a, b): """Tests that simplifying a fraction doesn't affect it's value""" frac = Fraction(a, b) assert frac.to_real() == a / b