|
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. |
@@ -319,48 +321,27 @@ def eval_complex_sign(n: BaseElement) -> Optional[BaseElement]: |
319 | 321 | sign = eval_RealSign(expr) |
320 | 322 | return sign or eval_complex_sign(expr) |
321 | 323 |
|
322 | | - if expr.has_form("Power", 2): |
323 | | - base, exp = expr.elements |
324 | | - if exp.is_zero: |
325 | | - return Integer1 |
326 | | - if isinstance(exp, (Integer, Real, Rational)): |
327 | | - sign = eval_Sign(base) or Expression(SymbolSign, base) |
328 | | - return Expression(SymbolPower, sign, exp) |
329 | | - if isinstance(exp, Complex): |
330 | | - sign = eval_Sign(base) or Expression(SymbolSign, base) |
331 | | - return Expression(SymbolPower, sign, exp.real) |
332 | | - if test_arithmetic_expr(exp): |
333 | | - sign = eval_Sign(base) or Expression(SymbolSign, base) |
334 | | - return Expression(SymbolPower, sign, exp) |
335 | | - return None |
336 | | - if expr.get_head() is SymbolTimes: |
337 | | - abs_value = eval_Abs(eval_multiply_numbers(*expr.elements)) |
338 | | - if abs_value is Integer1: |
339 | | - return expr |
340 | | - if abs_value is None: |
341 | | - return None |
342 | | - criteria = eval_add_numbers(abs_value, IntegerM1) |
343 | | - if test_zero_arithmetic_expr(criteria, numeric=True): |
344 | | - return expr |
345 | | - return None |
346 | | - if expr.get_head() is SymbolPlus: |
347 | | - abs_value = eval_Abs(eval_add_numbers(*expr.elements)) |
348 | | - if abs_value is Integer1: |
349 | | - return expr |
350 | | - if abs_value is None: |
351 | | - return None |
352 | | - criteria = eval_add_numbers(abs_value, IntegerM1) |
353 | | - if test_zero_arithmetic_expr(criteria, numeric=True): |
354 | | - return expr |
355 | | - return None |
356 | 324 |
|
357 | | - if test_arithmetic_expr(expr): |
358 | | - if test_zero_arithmetic_expr(expr): |
359 | | - return Integer0 |
360 | | - if test_positive_arithmetic_expr(expr): |
361 | | - return Integer1 |
362 | | - if test_negative_arithmetic_expr(expr): |
363 | | - return IntegerM1 |
| 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)) |
364 | 345 |
|
365 | 346 |
|
366 | 347 | def eval_mpmath_function( |
@@ -390,6 +371,31 @@ def eval_mpmath_function( |
390 | 371 | return call_mpmath(mpmath_function, tuple(mpmath_args), prec) |
391 | 372 |
|
392 | 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 | + |
393 | 399 | def eval_Plus(*items: BaseElement) -> BaseElement: |
394 | 400 | "evaluate Plus for general elements" |
395 | 401 | numbers, items_tuple = segregate_numbers_from_sorted_list(*items) |
@@ -457,6 +463,13 @@ def append_last(): |
457 | 463 | elements_properties=ElementsProperties(False, False, True), |
458 | 464 | ) |
459 | 465 |
|
| 466 | + elements.sort() |
| 467 | + return Expression( |
| 468 | + SymbolPlus, |
| 469 | + *elements, |
| 470 | + elements_properties=ElementsProperties(False, False, True), |
| 471 | + ) |
| 472 | + |
460 | 473 |
|
461 | 474 | def eval_Power_number(base: Number, exp: Number) -> Optional[Number]: |
462 | 475 | """ |
@@ -688,8 +701,88 @@ def eval_Times(*items: BaseElement) -> BaseElement: |
688 | 701 | ) |
689 | 702 |
|
690 | 703 |
|
| 704 | +# Here I used the convention of calling eval_* to functions that can produce a new expression, or None |
| 705 | +# if the result can not be evaluated, or is trivial. For example, if we call eval_Power_number(Integer2, RationalOneHalf) |
| 706 | +# it returns ``None`` instead of ``Expression(SymbolPower, Integer2, RationalOneHalf)``. |
| 707 | +# The reason is that these functions are written to be part of replacement rules, to be applied during the evaluation process. |
| 708 | +# In that process, a rule is considered applied if produces an expression that is different from the original one, or |
| 709 | +# if the replacement function returns (Python's) ``None``. |
| 710 | +# |
| 711 | +# For example, when the expression ``Power[4, 1/2]`` is evaluated, a (Builtin) rule ``Power[base_, exp_]->eval_repl_rule(base, expr)`` |
| 712 | +# 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])` |
| 713 | +# is False, then no new rules for `Power` are checked, and a new round of evaluation is atempted. |
| 714 | +# |
| 715 | +# 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, |
| 716 | +# 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, |
| 717 | +# and then the expression is kept as it is. |
| 718 | +# The other possibility is to return (Python's) `None`. In that case, the evaluator considers that the rule failed to be applied, |
| 719 | +# and look for another rule associated to ``Power``. To return ``None`` produces then a faster evaluation, since no ``sameQ`` call is needed, |
| 720 | +# and do not prevent that other rules are attempted. |
| 721 | +# |
| 722 | +# 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 |
| 723 | +# the code I call ``eval_Power_number(Integer3, RationalOneHalf)`` I get ``Expression(SymbolPower, Integer3, RationalOneHalf)``. |
| 724 | +# |
| 725 | +# From my point of view, it would make more sense to use the following convention: |
| 726 | +# * if the method has signature ``eval_method(...)->BaseElement:`` then use the prefix ``eval_`` |
| 727 | +# * if the method has the siguature ``apply_method(...)->Optional[BaseElement]`` use the prefix ``apply_`` or maybe ``repl_``. |
| 728 | +# |
| 729 | +# In any case, let's keep the current convention. |
| 730 | +# |
| 731 | +# |
| 732 | + |
| 733 | + |
| 734 | +def associate_powers(expr: BaseElement, power: BaseElement = Integer1) -> BaseElement: |
| 735 | + """ |
| 736 | + base^a^b^c^...^power -> base^(a*b*c*...power) |
| 737 | + provided one of the following cases |
| 738 | + * `a`, `b`, ... `power` are all integer numbers |
| 739 | + * `a`, `b`,... are Rational/Real number with absolute value <=1, |
| 740 | + and the other powers are not integer numbers. |
| 741 | + * `a` is not a Rational/Real number, and b, c, ... power are all |
| 742 | + integer numbers. |
| 743 | + """ |
| 744 | + powers = [] |
| 745 | + base = expr |
| 746 | + if power is not Integer1: |
| 747 | + powers.append(power) |
| 748 | + |
| 749 | + while base.has_form("Power", 2): |
| 750 | + previous_base, outer_power = base, power |
| 751 | + base, power = base.elements |
| 752 | + if len(powers) == 0: |
| 753 | + if power is not Integer1: |
| 754 | + powers.append(power) |
| 755 | + continue |
| 756 | + if power is IntegerM1: |
| 757 | + powers.append(power) |
| 758 | + continue |
| 759 | + if isinstance(power, (Rational, Real)): |
| 760 | + if abs(power.value) < 1: |
| 761 | + powers.append(power) |
| 762 | + continue |
| 763 | + # power is not rational/real and outer_power is integer, |
| 764 | + elif isinstance(outer_power, Integer): |
| 765 | + if power is not Integer1: |
| 766 | + powers.append(power) |
| 767 | + if isinstance(power, Integer): |
| 768 | + continue |
| 769 | + else: |
| 770 | + break |
| 771 | + # in any other case, use the previous base and |
| 772 | + # exit the loop |
| 773 | + base = previous_base |
| 774 | + break |
| 775 | + |
| 776 | + if len(powers) == 0: |
| 777 | + return base |
| 778 | + elif len(powers) == 1: |
| 779 | + return Expression(SymbolPower, base, powers[0]) |
| 780 | + result = Expression(SymbolPower, base, Expression(SymbolTimes, *powers)) |
| 781 | + return result |
| 782 | + |
| 783 | + |
691 | 784 | def eval_add_numbers( |
692 | | - *numbers: Number, |
| 785 | + *numbers: List[Number], |
693 | 786 | ) -> BaseElement: |
694 | 787 | """ |
695 | 788 | Add the elements in ``numbers``. |
@@ -736,7 +829,7 @@ def eval_inverse_number(n: Number) -> Number: |
736 | 829 | return eval_Power_number(n, IntegerM1) |
737 | 830 |
|
738 | 831 |
|
739 | | -def eval_multiply_numbers(*numbers: Number) -> Number: |
| 832 | +def eval_multiply_numbers(*numbers: Number) -> BaseElement: |
740 | 833 | """ |
741 | 834 | Multiply the elements in ``numbers``. |
742 | 835 | """ |
|
0 commit comments