def evalf_expression(expression, n=15, evaluate=None): """ Customized version of evalf. Attempts to truncate expressions to n digits in a way to get identical results for two expressions that agree to n digits. To accomplish, the method performs the following steps 1. Traverses expression tree and attempts to call evalf with max(30,n+5) precision on every element. The first pass puts expression in a consistent form. 2. Traverses expression tree, converting each number to a float with the precision specified by n. For consistency, converts expression to string and back to float so that expression loses memory of original value and two expression will be the same if their conversion to string is the same. """ expression = sympify(expression) initial_n_digits = max(30, n+5) # first convert every number to a float with initial_n_digits # for consistency if evaluate is not False: expression = bottom_up( expression, lambda w: _initial_evalf(w,initial_n_digits), atoms=True, evaluate=evaluate) expression = bottom_up( expression, lambda w: w if not w.is_Number else Float(str(w.evalf(n))), atoms=True, evaluate=evaluate) return expression
def test_substitute_list_tuple(self): x=Symbol('x') y=Symbol('y') z=Symbol('z') expr = bottom_up((x,y,z), lambda w: w if not w==x else Tuple(x,y), atoms=True) self.assertEqual(expr, ((x,y),y,z)) expr = bottom_up((x,y,z), lambda w: w if not w==x else [x,y], atoms=True) self.assertEqual(expr, ([x,y],y,z)) self.assertRaises(AttributeError, bottom_up, x**2, lambda w: w if not w==x else Tuple(x,y), atoms=True)
def round_expression(expression, n=0, initial_n_digits=100, evaluate=None): """ Customized version of round Attempts to round expressions to n decimal places in a way to get identical results for two expressions that agree to n decimal places. To accomplish, the method performs the following steps 1. Traverses expression tree and attempts to call evalf with initial_n_digits precision on every element. The first pass puts expression in a consistent form. 2. Traverses expression tree, converting each number to a float with the number of decimal places specified by n n. For consistency, converts expression to string and back to float so that expression loses memory of original value and two expression will be the same if their conversion to string is the same. If n is 0 or smaller, then converts to integer rather than float. """ expression = sympify(expression) # first convert every number to a float with initial_n_digits # for consistency if evaluate is not False: expression = bottom_up( expression, lambda w: _initial_evalf(w,initial_n_digits), atoms=True, evaluate=evaluate) # next, round numbers if n <= 0: from sympy import Integer expression = bottom_up( expression, lambda w: w if not w.is_Number else Integer(modified_round(w,n)), atoms=True, evaluate=evaluate) else: expression = bottom_up( expression, lambda w: w if not w.is_Number else Float(str(modified_round(w,n))), atoms=True, evaluate=evaluate) return expression
def normalize_floats(expression, n_digits=14, evaluate=None): """ To ensure consistency of expression with floats in presence of machine precision errors, round all floats to n_digits, converting expression to string and back to lose memory of original value and ensure two expression will be the same if their conversion to string is the same """ expression = sympify(expression) expression = bottom_up( expression, lambda w: w if not w.is_Float else Float(str(w.evalf(n_digits))), atoms=True, evaluate=evaluate) return expression
def test_rounds_numbers_to_integers(self): expr = bottom_up(sympify("2.0*x"), lambda w: w if not w.is_Number else Integer(w.round()), atoms=True) self.assertEqual(str(expr), "2*x")
def test_evalf_accepted_each_atom(self): expr = bottom_up(sympify("sin(x/3)"), lambda w: w if not w.is_Number else w.evalf(4), atoms=True) self.assertEqual(str(expr), "sin(0.3333*x)")
def try_normalize_expr(expr): """ Attempt to normalize expression. If relational, subtract rhs from both sides. Convert any subclass of ImmutableMatrix to ImmutableMatrix. Convert any subclass of Derivative to Derivative Convert customized log commands to sympy log Use doit, expand, then ratsimp to simplify rationals, then expand again """ def _remove_one_coefficient(expr): # remove a coefficent of a Mul that is just 1.0 from sympy import Mul if expr.is_Mul and expr.args[0] == 1: if len(expr.args[1:]) == 1: return expr.args[1] else: return Mul(*expr.args[1:]) else: return expr from .user_commands import log, ln from sympy import log as sympy_log def replace_logs_to_sympy(w): if w.func == ln or w.func == log: return sympy_log(*w.args) else: return w def normalize_transformations(w): # same as # lambda w: w.doit().expand().ratsimp().expand() # except catch Polynomial error that could be triggered by ratsimp() # and catch attribute error for objects like Interval from sympy import PolynomialError w = w.doit() try: w = w.expand() except (AttributeError, TypeError): pass if w.has(sympy_log): from sympy import logcombine try: w = logcombine(w) except TypeError: pass try: w = w.ratsimp().expand() except (AttributeError, PolynomialError, UnicodeEncodeError, TypeError): pass return w expr = bottom_up( expr, lambda w: w if not isinstance(w, ImmutableMatrix) else ImmutableMatrix(*w.args), nonbasic=True ) from sympy import Derivative expr = bottom_up(expr, lambda w: w if not isinstance(w, Derivative) else Derivative(*w.args)) try: if expr.is_Relational: from sympy import StrictLessThan, LessThan, Equality, Unequality # in attempt to normalize relational # 1. subtract sides so rhs is zero (lhs for less than inequalities) # 2. try to find coefficient of first term and divide by it lmr = (expr.lhs - expr.rhs).expand() if lmr.is_Add: term = lmr.args[0] else: term = lmr coeff = term.as_coeff_Mul()[0] if not (isinstance(expr, Equality) or isinstance(expr, Unequality)): coeff = Abs(coeff) if coeff: lmr = lmr / coeff if isinstance(expr, StrictLessThan) or isinstance(expr, LessThan): expr = expr.func(0, -lmr) else: expr = expr.func(lmr, 0) except AttributeError: pass # replace logs with sympy log expr = bottom_up(expr, replace_logs_to_sympy) # transformations to try to normalize expr = bottom_up(expr, normalize_transformations) # remove any cofficients of 1.0 # expr=bottom_up(expr, _remove_one_coefficient) return expr
def compare_with_expression_sub(self, new_expr, additional_rounding=None): """ Compare expression of object with new_expression. Returns: 1 if expressions are considered equal. If normalize_on_compare is set, then expressions are considered equal if their normalized expressions to the same. Otherwise, expressions themselves must be the same. -1 if normalize_on compare is not set, the expressions themselves are not the same, but their normalized expressions are the same. 0 if the expressions not the same and the normalized expressions are not the same p number p, 0 < p < 1, if expressions are partially equal, where p indicates the fraction of expressions that are equal -p number p, 0 < p < 1, if normalize_on compare is not set, the expressions themselves are not the same, but their normalized expressions are partially equal, then p indicates the fraction of normalized expressions that are equal In all determinations of equality, expressions are rounded to the precision determined by round_on_compare and round_absolute, if set, minus any additional rounding specified. """ from mitesting.sympy_customized import EVALUATE_NONE expression = self._expression evaluate_level = self.return_evaluate_level() if evaluate_level != EVALUATE_NONE: comparison_evaluate = None else: comparison_evaluate = False # replace Symbol('lamda') with Symbol('lambda') def replace_lamda(w): if w == Symbol("lamda"): return Symbol("lambda") elif w == Symbol("lamda", real=True): return Symbol("lambda", real=True) else: return w expression = bottom_up(expression, replace_lamda, evaluate=comparison_evaluate, atoms=True) new_expr = bottom_up(new_expr, replace_lamda, evaluate=comparison_evaluate, atoms=True) # As long as evaluate is not False # convert customized ln command to customized log command # also convert sympy log to customized log command if evaluate_level != EVALUATE_NONE: from .user_commands import log, ln from sympy import log as sympy_log def replace_logs(w): if w.func == ln or w.func == sympy_log: return log(*w.args) else: return w expression = bottom_up(expression, replace_logs) new_expr = bottom_up(new_expr, replace_logs) # Calculate the normalized expressions for both expressions, # rounded to precision as specified by # round_on_compare and round_absolute (with additional rounding) new_expr_normalize = self.eval_to_comparison_precision( try_normalize_expr(new_expr), additional_rounding=additional_rounding ) expression_normalize = self.eval_to_comparison_precision( try_normalize_expr(expression), additional_rounding=additional_rounding ) # As long as evaluate is not False # evaluate both expressions to precision as specified by # round_on_compare and round_absolute (with additional rounding) new_expr = self.eval_to_comparison_precision( new_expr, additional_rounding=additional_rounding, evaluate=comparison_evaluate ) expression = self.eval_to_comparison_precision( expression, additional_rounding=additional_rounding, evaluate=comparison_evaluate ) tuple_is_unordered = self._parameters.get("tuple_is_unordered", False) match_partial_on_compare = self._parameters.get("match_partial_on_compare", False) expressions_equal = 0 equal_if_normalize = 0 if self._parameters.get("normalize_on_compare"): expressions_equal = check_equality( expression_normalize, new_expr_normalize, tuple_is_unordered=tuple_is_unordered, partial_matches=match_partial_on_compare, ) else: expressions_equal = check_equality( expression, new_expr, tuple_is_unordered=tuple_is_unordered, partial_matches=match_partial_on_compare ) if expressions_equal == 0: equal_if_normalize = check_equality( expression_normalize, new_expr_normalize, tuple_is_unordered=tuple_is_unordered, partial_matches=match_partial_on_compare, ) if expressions_equal: return expressions_equal if equal_if_normalize: return -1 * equal_if_normalize return 0