def eval(self, e): if isinstance(e, str): e = parser.parse_expr(e) if e.ty != expr.INTEGRAL: return e rec = Linearity().eval if e.body.is_plus(): return rec(expr.Integral(e.var, e.lower, e.upper, e.body.args[0])) + \ rec(expr.Integral(e.var, e.lower, e.upper, e.body.args[1])) elif e.body.is_uminus(): return -rec(expr.Integral(e.var, e.lower, e.upper, e.body.args[0])) elif e.body.is_minus(): return rec(expr.Integral(e.var, e.lower, e.upper, e.body.args[0])) - \ rec(expr.Integral(e.var, e.lower, e.upper, e.body.args[1])) elif e.body.is_times(): factors = decompose_expr_factor(e.body) if factors[0].is_constant(): return factors[0] * rec(expr.Integral(e.var, e.lower, e.upper, functools.reduce(lambda x, y: x * y, factors[2:], factors[1]))) else: return e elif e.body.is_constant() and e.body != Const(1): return e.body * expr.Integral(e.var, e.lower, e.upper, Const(1)) else: return e
def eval(self, e): """Eliminate the lhs's integral in rhs by solving equation.""" if isinstance(e, str): e = parser.parse_expr(e) norm_e = e.normalize() rhs_var = None def get_coeff(t): nonlocal rhs_var if t.ty == INTEGRAL: if t == self.lhs: rhs_var = t.var return 1 else: return 0 elif t.is_plus(): return get_coeff(t.args[0]) + get_coeff(t.args[1]) elif t.is_minus(): return get_coeff(t.args[0]) - get_coeff(t.args[1]) elif t.is_uminus(): return -get_coeff(t.args[0]) elif t.is_times() and t.args[0].ty == CONST: return t.args[0].val * get_coeff(t.args[1]) else: return 0 coeff = get_coeff(norm_e) if coeff == 0: return e new_rhs = (norm_e + (Const(-coeff)*self.lhs.alpha_convert(rhs_var))).normalize() self.coeff = (-Const(coeff)).normalize() return (new_rhs/(Const(1-coeff))).normalize()
def testPriority(self): x = parse_expr("x") test_data = [ (Const(1) + (x ^ Const(2)), "1 + x^2"), ] for s, s2 in test_data: self.assertEqual(s, parse_expr(s2))
def testParseTerm3(self): test_data = [ ("$sin(x)^2$*sin(x)", Op("*", Op("^",Fun("sin",Var("x")),Const(2)), Fun("sin",Var("x")))), ("x + $x + y$", Op("+", Var("x"), Op("+", Var("x"), Var("y")))), ] for s, e in test_data: self.assertEqual(parse_expr(s), e) self.assertTrue(Op("^",Fun("sin",Var("x")),Const(2)) in trig_identity)
def testParseTerm2(self): test_data = [ ("-x", -Var("x")), ("-2", Const(-2)), ("1/2", Const(Fraction(1) / 2)), ("-1/2", Const(Fraction(-1) / 2)), ("0.5", Const(Decimal("0.5"))), ("pi", Fun("pi")), ("-x^2", Op("-", Op("^", Var("x"), Const(2)))) ] for s, e, in test_data: self.assertEqual(parse_expr(s), e)
def eval(self, e): if e.ty != LIMIT: return e bd = e.body subst_poly = bd.replace_trig(Var(e.var), e.lim).to_poly() if subst_poly.T != poly.UNKNOWN: return e inf_part, zero_part, const_part = [], [], [] bd_poly = bd.to_poly() if len(bd_poly) != 1: raise NotImplementedError m = bd_poly[0] factors = m.factors for i, j in factors: mono = poly.Polynomial([poly.Monomial(1, ((i, j),))]) norm_m = expr.from_poly(mono) subst_m = norm_m.replace_trig(Var(e.var), e.lim).to_poly() if subst_m.T == poly.ZERO: zero_part.append((Const(1) / norm_m).normalize()) elif subst_m.T in (poly.POS_INF, poly.NEG_INF): inf_part.append(norm_m) elif subst_m.T == poly.NON_ZERO: const_part.append(norm_m) else: raise NotImplementedError(str(mono)) assert inf_part and zero_part inf_expr = functools.reduce(operator.mul, inf_part[1:], inf_part[0]) zero_expr = functools.reduce(operator.mul, zero_part[1:], zero_part[0]) nm_trace = [inf_expr] denom_trace = [zero_expr] while True: nm, denom = nm_trace[-1], denom_trace[-1] nm_deriv, denom_deriv = expr.deriv(e.var, nm), expr.deriv(e.var, denom) nm_subst, denom_subst = nm_deriv.replace_trig(Var(e.var), e.lim), denom_deriv.replace_trig(Var(e.var), e.lim) nm_poly, denom_poly = nm_subst.to_poly(), denom_subst.to_poly() if nm_poly.T in (poly.POS_INF, poly.NEG_INF) and denom_poly.T in (poly.POS_INF, poly.NEG_INF): continue elif nm_poly.T in (poly.ZERO, poly.NON_ZERO) and denom_poly.T in (poly.POS_INF, poly.NEG_INF): return Const(0) elif nm_poly.T in (poly.POS_INF, poly.NEG_INF) and denom_poly.T in (poly.ZERO, poly.NON_ZERO): return expr.from_poly(nm_poly) elif nm_poly.T == poly.NON_ZERO and denom_poly.T == poly.NON_ZERO: return (nm_subst / denom_subst).normalize() else: raise NotImplementedError
def testSubstitution(self): e = parse_expr("INT x:[0,1]. (3 * x + 1) ^ (-2)") e = rules.Substitution1("u", parse_expr("3 * x + 1")).eval(e) e = rules.Linearity().eval(e) e = rules.OnSubterm(rules.CommonIntegral()).eval(e) e = rules.Simplify().eval(e) self.assertEqual(e, Const(Fraction("1/4")))
def testConstantMonomial(self): test_data = [ (1, [], "1"), (1, [(2, "1/2")], "2^(1/2)"), (1, [(2, "3/2")], "2 * 2^(1/2)"), (1, [(2, "-1/2")], "1/2 * 2^(1/2)"), (1, [(6, "1/2")], "2^(1/2) * 3^(1/2)"), (1, [(pi, "1/2")], "pi^(1/2)"), (1, [(E, "1/2")], "e^(1/2)"), (1, [(9, "1/2")], "3"), (1, [(2, 2)], "4"), (1, [(8, "1/2"), (6, "1/2"), (9, "1/3")], "12 * 3^(1/6)"), (1, [(expr.sin(Const(1)), 2)], "sin(1)^2"), (1, [(expr.sin(Const(1)) + expr.sin(Const(2)), 2)], "(sin(1) + sin(2))^2"), ] for coeff, factors, res in test_data: factors = [(n, Fraction(e)) for n, e in factors] mono = ConstantMonomial(coeff, factors) self.assertEqual(str(mono), res)
def eval(self, e, a=Const(0)): if isinstance(e, str): e = parser.parse_expr(e) def gen_lim_expr(var, lim, lower, upper): return expr.Limit(new_var, lim, expr.Integral(e.var, lower, upper, e.body)) if e.ty != expr.INTEGRAL: return e upper, lower = e.upper, e.lower if upper != expr.inf and lower != expr.neg_inf: return e new_var = "s" if e.var == "t" else "t" if upper == expr.inf and lower != expr.neg_inf: return gen_lim_expr(new_var, expr.inf, lower, Var(new_var)) elif upper != expr.inf and lower == expr.neg_inf: return gen_lim_expr(new_var, expr.neg_inf, Var(new_var), upper) elif upper == expr.inf and lower == expr.neg_inf: assert a is not None, "No split point provided" return gen_lim_expr(new_var, expr.neg_inf, Var(new_var), a) + \ gen_lim_expr(new_var, expr.inf, a, Var(new_var)) else: raise NotImplementedError
def testCompareExpr(self): x, y, z = Var("x"), Var("y"), Var("z") test_data = [ (x, y), (x, Const(3)), (Const(3), Const(4)), (Const(4), x * y), (x * y, x + y), (x * y, x * z), (x * y, z * y), (sin(x), x * y), (x * y, sin(sin(x))), (sin(x), Deriv("x", x)), (Deriv("x", x), Integral("x", Const(1), Const(2), x)), ] for s, t in test_data: self.assertTrue(s <= t) self.assertTrue(s < t) self.assertFalse(t <= s) self.assertFalse(t < s)
def convert_expr(e, mode="large"): if e.ty == expr.VAR: return e.name elif e.ty == expr.CONST: if isinstance(e.val, (int, Decimal)): if e.val == Decimal("inf"): return "\\infty" elif e.val == Decimal("-inf"): return "-\\infty" else: return str(e.val) elif isinstance(e.val, Fraction): if e.val.denominator == 1: return "%d" % e.val.numerator elif mode == 'large': if e.val.numerator > 0: return "\\frac{%d}{%d}" % (e.val.numerator, e.val.denominator) elif e.val.numerator < 0: return "-\\frac{%d}{%d}" % (-e.val.numerator, e.val.denominator) else: return "%d/%d" % (e.val.numerator, e.val.denominator) else: raise NotImplementedError elif e.ty == expr.INF: return "\\infty" elif e.ty == expr.OP: if len(e.args) == 1: a, = e.args sa = convert_expr(a, mode) if a.priority() < 70: sa = "(%s)" % sa return "%s%s" % (e.op, sa) elif len(e.args) == 2: x, y = e.args sx = convert_expr(x, mode) sy = convert_expr(y, mode) if e.op in ("+", "-", "^"): if e.op == "^": # Still can improve if y.ty == expr.CONST and isinstance(y.val, Fraction): if y.val.numerator == 1: if y.val.denominator == 2: return "\\sqrt{%s}" % sx else: return "\\sqrt[%s]{%s}" % (y.val.denominator, sx) if x.ty == OP: return "(%s) ^ {%s}" % (sx, sy) return "%s ^ {%s}" % (sx, sy) if y.ty == expr.CONST and y.val < 0: if y.val != -1: new_expr = expr.Op( "/", expr.Const(1), expr.Op("^", x, expr.Const(-y.val))) else: new_expr = expr.Op("/", expr.Const(1), x) return convert_expr(new_expr) elif isinstance(x, expr.Fun) and len( x.args) > 0 and x.func_name != "abs": return "\%s^{%s}(%s)" % (x.func_name, sy, convert_expr(x.args[0])) if x.priority() < expr.op_priority[e.op]: sx = "(%s)" % sx if y.priority() < expr.op_priority[e.op]: sy = "{(%s)}" % sy if e.op == "^" and len(sy) > 1: sy = "{%s}" % sy if y.ty == expr.OP and y.op == e.op and y.op in ("-", "/"): sy = "{(%s)}" % sy return "%s %s %s" % (sx, e.op, sy) elif e.op == "*": if not x.is_constant() and not y.is_constant() and not ( y.ty == OP and y.op == "^" and y.args[1].ty == CONST and y.args[1].val < 0) or x == expr.Fun( "pi") or y == expr.Fun("pi"): if x.ty == expr.OP and (x.op not in ( "^", "*")) and not len(x.args) == 1: sx = "(" + sx + ")" if y.ty == expr.OP and y.op != "^": sy = "(" + sy + ")" return "%s %s" % (sx, sy) elif x.is_constant() and y.is_constant( ) and y.ty != CONST and not (y.ty == OP and y.op in ("+", "-") or y.ty == CONST and isinstance(y.val, Fraction)): if x == Const(-1): return "-%s" % sy if x.ty == expr.OP and x.op != "^" and len(x.args) != 1: sx = "(" + sx + ")" if y.ty == expr.OP and not ( len(y.args) == 2 and y.op == "^" or y.args[1].ty == CONST and y.args[1].val == Fraction(1 / 2)): sy = "(" + sy + ")" if x.ty == expr.CONST and isinstance( x.val, Fraction) and mode == "short": sx = "(" + sx + ")" return "%s %s" % (sx, sy) elif x.is_constant() and y.ty == CONST and isinstance( y.val, Fraction ) and y.val.numerator == 1 and y.val.denominator != 1: return "\\frac{%s}{%s}" % ( sx, convert_expr(expr.Const(y.val.denominator))) elif y.ty == OP and y.op == "^" and y.args[ 1].ty == CONST and y.args[1].val < 0: if y.args[1].val == -1: return "\\frac{%s}{%s}" % (sx, convert_expr(y.args[0])) else: new_denom = Op("^", y.args[0], Const(-y.args[1].val)) return "\\frac{%s}{%s}" % ( sx, convert_expr(new_denom, mode)) elif x.ty == expr.CONST: if x.val == -1: if y.ty == OP: return "-(%s)" % sy else: return "-%s" % sy elif x.val == 1 and not y.ty == OP: if y.ty == OP: return "(%s)" % sy else: return "%s" % sy elif isinstance( x.val, Fraction ) and x.val.numerator == 1 and y.ty not in (expr.INTEGRAL, expr.OP, expr.EVAL_AT): return "\\frac{%s}{%s}" % ( sy, convert_expr(expr.Const(x.val.denominator))) elif y.ty in (expr.VAR, expr.FUN): if isinstance(x.val, Fraction): return "(%s) %s" % (sx, sy) else: return "%s %s" % (sx, sy) elif not y.is_constant(): if y.ty == OP and y.op != '^': return "%s (%s)" % (sx, sy) elif y.ty == EVAL_AT: return "%s \\times (%s)" % (sx, sy) return "%s %s" % (sx, sy) elif y.ty != CONST and y.is_constant() and not ( y.ty == OP and y.op in ('+', '-')): return "%s %s" % (sx, sy) elif y.ty == OP and y.op == "^" and not y.args[ 0].is_constant(): return "%s %s" % (sx, sy) else: if x.priority() < expr.op_priority[e.op]: sx = "(%s)" % sx if y.priority() < expr.op_priority[e.op]: sy = "(%s)" % sy return "%s %s %s" % (sx, e.op, sy) else: if x.priority() < expr.op_priority[e.op]: sx = "(%s)" % sx if y.priority() < expr.op_priority[e.op]: sy = "(%s)" % sy return "%s %s %s" % (sx, e.op, sy) elif e.op == "/": if mode == 'large': if sy == "1": return "%s" % sx elif x.ty == expr.OP: if x.op == "-" and len(x.args) == 1: # (-x) / y => - (x/y) sxx = convert_expr(x.args[0]) return "-\\frac{%s}{%s}" % (sxx, sy) elif y.ty == expr.CONST: return "\\frac{1}{%s} \\times %s" % (sy, sx) else: return "\\frac{%s}{%s}" % (sx, sy) else: return "\\frac{%s}{%s}" % (sx, sy) else: return "%s/%s" % (sx, sy) else: raise NotImplementedError else: raise NotImplementedError elif e.ty == expr.FUN: if len(e.args) == 0: return "\\%s" % e.func_name elif len(e.args) == 1: x, = e.args sx = convert_expr(x, mode) if e.func_name == "exp": if e.args[0] == expr.Const(1): return "e" else: return "e^{%s}" % sx elif e.func_name == "sqrt": return "\\sqrt{%s}" % sx elif e.func_name == "abs": return "\\left| %s \\right|" % sx elif e.func_name == "atan": return "\\arctan{%s}" % sx elif e.func_name == "asin": return "\\arcsin{%s}" % sx elif e.func_name == "acos": return "\\arccos{%s}" % sx return "\\%s{(%s)}" % (e.func_name, sx) else: raise NotImplementedError elif e.ty == expr.INTEGRAL: lower = convert_expr(e.lower, mode='short') upper = convert_expr(e.upper, mode='short') body = convert_expr(e.body, mode) return "\\int_{%s}^{%s} %s \\,d%s" % (lower, upper, body, e.var) elif e.ty == expr.EVAL_AT: lower = convert_expr(e.lower, mode='short') upper = convert_expr(e.upper, mode='short') body = convert_expr(e.body, mode) return "\\left. %s \\right\\vert_{%s=%s}^{%s}" % (body, e.var, lower, upper) elif e.ty == expr.LIMIT: lim = convert_expr(e.lim, mode="short") body = convert_expr(e.body, mode) if e.body.ty == expr.OP and len(e.body.args) > 1: return "\\lim\\limits_{%s\\to %s} (\,%s\,)" % (e.var, lim, body) else: return "\\lim\\limits_{%s\\to %s} %s" % (e.var, lim, body) else: raise NotImplementedError
def eval(self, e): if isinstance(e, str): e = parser.parse_expr(e) if e.ty != expr.INTEGRAL: return e if e.body.is_constant() and e.body != Const(1): return EvalAt(e.var, e.lower, e.upper, e.body * Var(e.var)) x = Var(e.var) c = Symbol('c', [CONST]) rules = [ (Const(1), None, Var(e.var)), (c, None, c * Var(e.var)), (x, None, (x ^ 2) / 2), (x ^ c, lambda m: m[c].val != -1, lambda m: (x ^ Const(m[c].val + 1)) / (Const(m[c].val + 1))), (Const(1) / x ^ c, lambda m: m[c].val != 1, (-c) / (x ^ (c + 1))), (expr.sqrt(x), None, Fraction(2,3) * (x ^ Fraction(3,2))), (sin(x), None, -cos(x)), (cos(x), None, sin(x)), (expr.exp(x), None, expr.exp(x)), (Const(1) / x, None, expr.log(expr.Fun('abs', x))), (x ^ Const(-1), None, expr.log(expr.Fun('abs', x))), ((1 + (x ^ Const(2))) ^ Const(-1), None, expr.arctan(x)), (expr.sec(x) ^ Const(2), None, expr.tan(x)), (expr.csc(x) ^ Const(2), None, -expr.cot(x)), ] for pat, cond, pat_res in rules: mapping = expr.match(e.body, pat) if mapping is not None and (cond is None or cond(mapping)): if isinstance(pat_res, expr.Expr): integral = pat_res.inst_pat(mapping) else: integral = pat_res(mapping) return EvalAt(e.var, e.lower, e.upper, integral) return e
def testMatching(self): a = Symbol('a', [CONST]) b = Symbol('b', [CONST]) x = Symbol('x', [VAR]) y = Symbol('y', [VAR, OP, FUN]) test_data = [ ('x - 1', x - a, {x: Var('x'), a: Const(1)}), ('x - 2', x - Const(2), {x: Var('x')}), ('x + 3', x - b, None), ('x', x + a, None), ('3*x', a * x, {a: Const(3), x: Var('x')}), ('3 * x + 5', a * x + b, {a: Const(3), x: Var('x'), b: Const(5)}), ('2 * x + 3', a * x + a, None), ('x ^ 2', x ^ Const(2), {x: Var('x')}), ('x ^ 3 - 2', x ^ Const(3), None), ('1 - x ^ 2', a - (x ^ Const(2)), {a: Const(1), x: Var('x')}), ('cos(x) ^ 2', cos(x) ^ Const(2), {x: Var('x')}), ('(1 - x ^ 2) ^ (1/2)', (Const(1) - (x ^ Const(2)))^(Const(Fraction(1/2))), {x: Var('x')}), ('(1 - x ^ 3) ^ (1/2)', (Const(1) - (x ^ Const(2)))^(Const(Fraction(1/2))), None), ('(1 - 2 * sin(x) ^ 2) ^ (1/2)', (b - a * (sin(x) ^ Const(2)))^Const(Fraction(1/2)), {b: Const(1), a: Const(2), x: Var('x')}), ('sin(x) ^ 2 + cos(y)^2', (sin(x)^Const(2))+(cos(x)^Const(2)), None), ('sin(2*x+1)^2 + cos(2*x+1) ^ 2', (sin(y)^Const(2))+(cos(y)^Const(2)), {y: Op("+",Op("*",Const(2),Var('x')),Const(1))}), ('2*pi', a * pi, {a: Const(2)}), ] for r1, r2, r3 in test_data: self.assertEqual(match(parse_expr(r1), r2), r3)
def testPrintExpr(self): x, y, z = Var("x"), Var("y"), Var("z") test_data = [ (x, "x"), (Const(1), "1"), (Const(Decimal("1.1")), "11/10"), (Const((-1)), "-1"), (x + y, "x + y"), (x - y, "x - y"), (-x, "-x"), (x * y, "x * y"), (x / y, "x / y"), (x ^ y, "x ^ y"), ((x + y) * z, "(x + y) * z"), (x + (y * z), "x + y * z"), (x * y + z, "x * y + z"), (x * (y + z), "x * (y + z)"), (x + y + z, "x + y + z"), (x + (y + z), "x + (y + z)"), (x * (y ^ Const(2)), "x * y ^ 2"), ((x * y) ^ Const(2), "(x * y) ^ 2"), (-(x + y), "-(x + y)"), (x ^ Const(Fraction("1/2")), "x ^ (1/2)"), (-x + y, "-x + y"), (x - (x - y), "x - (x - y)"), (x - (x + y), "x - (x + y)"), (x / (x / y), "x / (x / y)"), (x / (x * y), "x / (x * y)"), (sin(x), "sin(x)"), (cos(x), "cos(x)"), (log(x), "log(x)"), (exp(x), "exp(x)"), (-x * exp(x), "-x * exp(x)"), (-(x * exp(x)), "-(x * exp(x))"), (sin(x ^ Const(2)), "sin(x ^ 2)"), (sin(x) * cos(x), "sin(x) * cos(x)"), (Deriv("x", Const(3) * x), "D x. 3 * x"), (Integral("x", Const(1), Const(2), Const(3) * x), "INT x:[1,2]. 3 * x"), (Deriv("x", Const(3) * x) * x, "(D x. 3 * x) * x"), (Integral("x", Const(1), Const(2), Const(3) * x) * x, "(INT x:[1,2]. 3 * x) * x"), (EvalAt("x", Const(1), Const(2), Const(3) * x), "[3 * x]_x=1,2"), (EvalAt("x", Const(1), Const(2), Const(3) * x) * x, "([3 * x]_x=1,2) * x"), ] for e, s in test_data: self.assertEqual(str(e), s)
def testIntegral1(self): e = parse_expr("INT x:[2,3]. 2 * x + x ^ 2") e = rules.Linearity().eval(e) e = rules.OnSubterm(rules.CommonIntegral()).eval(e) e = rules.Simplify().eval(e) self.assertEqual(e, Const(Fraction(34, 3)))