Example #1
0
    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")
Example #2
0
    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")
Example #3
0
    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]]
Example #4
0
 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
Example #6
0
 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
         ))
Example #7
0
    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
Example #8
0
 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")
Example #9
0
 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")
Example #10
0
 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)
Example #11
0
 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])
Example #12
0
 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])
Example #13
0
 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
Example #14
0
 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
Example #15
0
 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)
Example #16
0
 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
Example #17
0
 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
Example #18
0
    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
Example #19
0
    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
Example #20
0
 def eval(self):
     return num.Numeric(caspy.numeric.symbol.Symbol(self, Fraction(1, 1)), "sym_obj")
Example #21
0
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
Example #22
0
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"
Example #23
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
Example #24
0
    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
Example #25
0
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