|
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 |
|
3 | 3 | """ |
4 | | -arithmetic-related evaluation functions. |
| 4 | +helper functions for arithmetic evaluation, which do not |
| 5 | +depends on the evaluation context. Conversions to Sympy are |
| 6 | +used just as a last resource. |
5 | 7 |
|
6 | 8 | Many of these do do depend on the evaluation context. Conversions to Sympy are |
7 | 9 | used just as a last resource. |
@@ -320,6 +322,28 @@ def eval_complex_sign(n: BaseElement) -> Optional[BaseElement]: |
320 | 322 | return sign or eval_complex_sign(expr) |
321 | 323 |
|
322 | 324 |
|
| 325 | +def eval_Sign_number(n: Number) -> Number: |
| 326 | + """ |
| 327 | + Evals the absolute value of a number. |
| 328 | + """ |
| 329 | + if n.is_zero: |
| 330 | + return Integer0 |
| 331 | + if isinstance(n, (Integer, Rational, Real)): |
| 332 | + return Integer1 if n.value > 0 else IntegerM1 |
| 333 | + if isinstance(n, Complex): |
| 334 | + abs_sq = eval_add_numbers( |
| 335 | + *(eval_multiply_numbers(x, x) for x in (n.real, n.imag)) |
| 336 | + ) |
| 337 | + criteria = eval_add_numbers(abs_sq, IntegerM1) |
| 338 | + if test_zero_arithmetic_expr(criteria): |
| 339 | + return n |
| 340 | + if n.is_inexact(): |
| 341 | + return eval_multiply_numbers(n, eval_Power_number(abs_sq, RealM0p5)) |
| 342 | + if test_zero_arithmetic_expr(criteria, numeric=True): |
| 343 | + return n |
| 344 | + return eval_multiply_numbers(n, eval_Power_number(abs_sq, RationalMOneHalf)) |
| 345 | + |
| 346 | + |
323 | 347 | def eval_mpmath_function( |
324 | 348 | mpmath_function: Callable, *args: Number, prec: Optional[int] = None |
325 | 349 | ) -> Optional[Number]: |
@@ -347,6 +371,31 @@ def eval_mpmath_function( |
347 | 371 | return call_mpmath(mpmath_function, tuple(mpmath_args), prec) |
348 | 372 |
|
349 | 373 |
|
| 374 | +def eval_Exponential(exp: BaseElement) -> BaseElement: |
| 375 | + """ |
| 376 | + Eval E^exp |
| 377 | + """ |
| 378 | + # If both base and exponent are exact quantities, |
| 379 | + # use sympy. |
| 380 | + |
| 381 | + if not exp.is_inexact(): |
| 382 | + exp_sp = exp.to_sympy() |
| 383 | + if exp_sp is None: |
| 384 | + return None |
| 385 | + return from_sympy(sympy.Exp(exp_sp)) |
| 386 | + |
| 387 | + prec = exp.get_precision() |
| 388 | + if prec is not None: |
| 389 | + if exp.is_machine_precision(): |
| 390 | + number = mpmath.exp(exp.to_mpmath()) |
| 391 | + result = from_mpmath(number) |
| 392 | + return result |
| 393 | + else: |
| 394 | + with mpmath.workprec(prec): |
| 395 | + number = mpmath.exp(exp.to_mpmath()) |
| 396 | + return from_mpmath(number, prec) |
| 397 | + |
| 398 | + |
350 | 399 | def eval_Plus(*items: BaseElement) -> BaseElement: |
351 | 400 | "evaluate Plus for general elements" |
352 | 401 | numbers, items_tuple = segregate_numbers_from_sorted_list(*items) |
@@ -645,8 +694,88 @@ def eval_Times(*items: BaseElement) -> BaseElement: |
645 | 694 | ) |
646 | 695 |
|
647 | 696 |
|
| 697 | +# Here I used the convention of calling eval_* to functions that can produce a new expression, or None |
| 698 | +# if the result can not be evaluated, or is trivial. For example, if we call eval_Power_number(Integer2, RationalOneHalf) |
| 699 | +# it returns ``None`` instead of ``Expression(SymbolPower, Integer2, RationalOneHalf)``. |
| 700 | +# The reason is that these functions are written to be part of replacement rules, to be applied during the evaluation process. |
| 701 | +# In that process, a rule is considered applied if produces an expression that is different from the original one, or |
| 702 | +# if the replacement function returns (Python's) ``None``. |
| 703 | +# |
| 704 | +# For example, when the expression ``Power[4, 1/2]`` is evaluated, a (Builtin) rule ``Power[base_, exp_]->eval_repl_rule(base, expr)`` |
| 705 | +# is applied. If the rule matches, `repl_rule` is called with arguments ``(4, 1/2)`` and produces `2`. As `Integer2.sameQ(Power[4, 1/2])` |
| 706 | +# is False, then no new rules for `Power` are checked, and a new round of evaluation is atempted. |
| 707 | +# |
| 708 | +# On the other hand, if ``Power[3, 1/2]``, ``repl_rule`` can do two possible things: one is return ``Power[3, 1/2]``. If it does, |
| 709 | +# the rule is considered applied. Then, the evaluation method checks if `Power[3, 1/2].sameQ(Power[3, 1/2])`. In this case it is true, |
| 710 | +# and then the expression is kept as it is. |
| 711 | +# The other possibility is to return (Python's) `None`. In that case, the evaluator considers that the rule failed to be applied, |
| 712 | +# and look for another rule associated to ``Power``. To return ``None`` produces then a faster evaluation, since no ``sameQ`` call is needed, |
| 713 | +# and do not prevent that other rules are attempted. |
| 714 | +# |
| 715 | +# The bad part of using ``None`` as a return is that I would expect that ``eval`` produces always a valid Expression, so if at some point of |
| 716 | +# the code I call ``eval_Power_number(Integer3, RationalOneHalf)`` I get ``Expression(SymbolPower, Integer3, RationalOneHalf)``. |
| 717 | +# |
| 718 | +# From my point of view, it would make more sense to use the following convention: |
| 719 | +# * if the method has signature ``eval_method(...)->BaseElement:`` then use the prefix ``eval_`` |
| 720 | +# * if the method has the siguature ``apply_method(...)->Optional[BaseElement]`` use the prefix ``apply_`` or maybe ``repl_``. |
| 721 | +# |
| 722 | +# In any case, let's keep the current convention. |
| 723 | +# |
| 724 | +# |
| 725 | + |
| 726 | + |
| 727 | +def associate_powers(expr: BaseElement, power: BaseElement = Integer1) -> BaseElement: |
| 728 | + """ |
| 729 | + base^a^b^c^...^power -> base^(a*b*c*...power) |
| 730 | + provided one of the following cases |
| 731 | + * `a`, `b`, ... `power` are all integer numbers |
| 732 | + * `a`, `b`,... are Rational/Real number with absolute value <=1, |
| 733 | + and the other powers are not integer numbers. |
| 734 | + * `a` is not a Rational/Real number, and b, c, ... power are all |
| 735 | + integer numbers. |
| 736 | + """ |
| 737 | + powers = [] |
| 738 | + base = expr |
| 739 | + if power is not Integer1: |
| 740 | + powers.append(power) |
| 741 | + |
| 742 | + while base.has_form("Power", 2): |
| 743 | + previous_base, outer_power = base, power |
| 744 | + base, power = base.elements |
| 745 | + if len(powers) == 0: |
| 746 | + if power is not Integer1: |
| 747 | + powers.append(power) |
| 748 | + continue |
| 749 | + if power is IntegerM1: |
| 750 | + powers.append(power) |
| 751 | + continue |
| 752 | + if isinstance(power, (Rational, Real)): |
| 753 | + if abs(power.value) < 1: |
| 754 | + powers.append(power) |
| 755 | + continue |
| 756 | + # power is not rational/real and outer_power is integer, |
| 757 | + elif isinstance(outer_power, Integer): |
| 758 | + if power is not Integer1: |
| 759 | + powers.append(power) |
| 760 | + if isinstance(power, Integer): |
| 761 | + continue |
| 762 | + else: |
| 763 | + break |
| 764 | + # in any other case, use the previous base and |
| 765 | + # exit the loop |
| 766 | + base = previous_base |
| 767 | + break |
| 768 | + |
| 769 | + if len(powers) == 0: |
| 770 | + return base |
| 771 | + elif len(powers) == 1: |
| 772 | + return Expression(SymbolPower, base, powers[0]) |
| 773 | + result = Expression(SymbolPower, base, Expression(SymbolTimes, *powers)) |
| 774 | + return result |
| 775 | + |
| 776 | + |
648 | 777 | def eval_add_numbers( |
649 | | - *numbers: Number, |
| 778 | + *numbers: List[Number], |
650 | 779 | ) -> BaseElement: |
651 | 780 | """ |
652 | 781 | Add the elements in ``numbers``. |
@@ -693,7 +822,7 @@ def eval_inverse_number(n: Number) -> Number: |
693 | 822 | return eval_Power_number(n, IntegerM1) |
694 | 823 |
|
695 | 824 |
|
696 | | -def eval_multiply_numbers(*numbers: Number) -> Number: |
| 825 | +def eval_multiply_numbers(*numbers: Number) -> BaseElement: |
697 | 826 | """ |
698 | 827 | Multiply the elements in ``numbers``. |
699 | 828 | """ |
|
0 commit comments