def get_nullspace(n,q,eigv): M = matrix_cache.get_reduced_matrix(n,q, True) M.m = M.m._new(M.m.rows, M.m.cols,[nsimplify(v, rational=True) for v in M.m]) # N = M - lambdaI N = M.m - sympy.Matrix.eye(M.get_size())* eigv # rationalize entries N = N._new(N.rows, N.cols,[nsimplify(v, rational=True) for v in N]) # find eigenspace - Kernel(M-lambdaI) = Kernel(N) EigenSpaceBase = N.nullspace() # orthogonal space = Kernel(TransposedEigenVectors) TransposedEigenSpaceBase = [list(v) for v in EigenSpaceBase] EigenSpaceTransposeMatrix = (sympy.Matrix(numpy.matrix(TransposedEigenSpaceBase))) OrthogonalSubspaceBase = EigenSpaceTransposeMatrix.nullspace() # need to transpose to put the vectors in columns, and get a matrix for the orthogonal operator OrthogonalSubspaceMatrix = (sympy.Matrix(numpy.matrix(OrthogonalSubspaceBase))).transpose() # multiply M * Ortohogonal, to get the reduction of M on the orthogonal subspace OrthogonalSubspaceMatrix = OrthogonalSubspaceMatrix._new(OrthogonalSubspaceMatrix.rows, OrthogonalSubspaceMatrix.cols, [nsimplify(v, rational=True) for v in OrthogonalSubspaceMatrix]) EigenValueReducedMatrix = M.m * OrthogonalSubspaceMatrix EigenValueReducedMatrix = EigenValueReducedMatrix.rref()
def random_complex_number(a=2, b=-1, c=3, d=1, rational=False): """ Return a random complex number. To reduce chance of hitting branch cuts or anything, we guarantee b <= Im z <= d, a <= Re z <= c """ A, B = uniform(a, c), uniform(b, d) if not rational: return A + I*B return nsimplify(A, rational=True) + I*nsimplify(B, rational=True)
def eval(cls, p, q): from sympy.simplify.simplify import nsimplify if q.is_Number: float = not q.is_Rational pnew = expand_mul(p) if pnew.is_Number: float = float or not pnew.is_Rational if not float: return pnew % q return Float(nsimplify(pnew) % nsimplify(q)) elif pnew.is_Add and pnew.args[0].is_Number: r, p = pnew.as_two_terms() p += Mod(r, q) return Mod(p, q, evaluate=False)
def random_complex_number(a=2, b=-1, c=3, d=1, rational=False, tolerance=None): """ Return a random complex number. To reduce chance of hitting branch cuts or anything, we guarantee b <= Im z <= d, a <= Re z <= c When rational is True, a rational approximation to a random number is obtained within specified tolerance, if any. """ A, B = uniform(a, c), uniform(b, d) if not rational: return A + I*B return (nsimplify(A, rational=True, tolerance=tolerance) + I*nsimplify(B, rational=True, tolerance=tolerance))
def test_minpoly_issue_7113(): # see discussion in https://github.com/sympy/sympy/pull/2234 from sympy.simplify.simplify import nsimplify r = nsimplify(pi, tolerance=0.000000001) mp = minimal_polynomial(r, x) assert mp == 1768292677839237920489538677417507171630859375*x**109 - \ 2734577732179183863586489182929671773182898498218854181690460140337930774573792597743853652058046464
def _pow_float(inter, power): """Evaluates an interval raised to a floating point.""" power_rational = nsimplify(power) num, denom = power_rational.as_numer_denom() if num % 2 == 0: start = abs(inter.start)**power end = abs(inter.end)**power if start < 0: ret = interval(0, max(start, end)) else: ret = interval(start, end) return ret elif denom % 2 == 0: if inter.end < 0: return interval(-float('inf'), float('inf'), is_valid=False) elif inter.start < 0: return interval(0, inter.end**power, is_valid=None) else: return interval(inter.start**power, inter.end**power) else: if inter.start < 0: start = -abs(inter.start)**power else: start = inter.start**power if inter.end < 0: end = -abs(inter.end)**power else: end = inter.end**power return interval(start, end, is_valid=inter.is_valid)
def test_issue_2877(): f = Float(2.0) assert (x + f).subs({f: 2}) == x + 2 def r(a, b, c): return factor(a * x**2 + b * x + c) e = r(5.0 / 6, 10, 5) assert nsimplify(e) == 5 * x**2 / 6 + 10 * x + 5
def test_minpoly_issue_7113(): # see discussion in https://github.com/sympy/sympy/pull/2234 from sympy.simplify.simplify import nsimplify r = nsimplify(pi, tolerance=0.000000001) mp = minimal_polynomial(r, x) assert ( mp == 1768292677839237920489538677417507171630859375 * x**109 - 2734577732179183863586489182929671773182898498218854181690460140337930774573792597743853652058046464 )
def _eval_sum_hyper(f, i, a): """ Returns (res, cond). Sums from a to oo. """ from sympy.functions import hyper from sympy.simplify import hyperexpand, hypersimp, fraction, simplify from sympy.polys.polytools import Poly, factor from sympy.core.numbers import Float if a != 0: return _eval_sum_hyper(f.subs(i, i + a), i, 0) if f.subs(i, 0) == 0: if simplify(f.subs(i, Dummy("i", integer=True, positive=True))) == 0: return S.Zero, True return _eval_sum_hyper(f.subs(i, i + 1), i, 0) hs = hypersimp(f, i) if hs is None: return None if isinstance(hs, Float): from sympy.simplify.simplify import nsimplify hs = nsimplify(hs) numer, denom = fraction(factor(hs)) top, topl = numer.as_coeff_mul(i) bot, botl = denom.as_coeff_mul(i) ab = [top, bot] factors = [topl, botl] params = [[], []] for k in range(2): for fac in factors[k]: mul = 1 if fac.is_Pow: mul = fac.exp fac = fac.base if not mul.is_Integer: return None p = Poly(fac, i) if p.degree() != 1: return None m, n = p.all_coeffs() ab[k] *= m ** mul params[k] += [n / m] * mul # Add "1" to numerator parameters, to account for implicit n! in # hypergeometric series. ap = params[0] + [1] bq = params[1] x = ab[0] / ab[1] h = hyper(ap, bq, x) f = combsimp(f) return f.subs(i, 0) * hyperexpand(h), h.convergence_statement
def _eval_sum_hyper(f, i, a): """ Returns (res, cond). Sums from a to oo. """ from sympy.functions import hyper from sympy.simplify import hyperexpand, hypersimp, fraction, simplify from sympy.polys.polytools import Poly, factor from sympy.core.numbers import Float if a != 0: return _eval_sum_hyper(f.subs(i, i + a), i, 0) if f.subs(i, 0) == 0: if simplify(f.subs(i, Dummy('i', integer=True, positive=True))) == 0: return S(0), True return _eval_sum_hyper(f.subs(i, i + 1), i, 0) hs = hypersimp(f, i) if hs is None: return None if isinstance(hs, Float): from sympy.simplify.simplify import nsimplify hs = nsimplify(hs) numer, denom = fraction(factor(hs)) top, topl = numer.as_coeff_mul(i) bot, botl = denom.as_coeff_mul(i) ab = [top, bot] factors = [topl, botl] params = [[], []] for k in range(2): for fac in factors[k]: mul = 1 if fac.is_Pow: mul = fac.exp fac = fac.base if not mul.is_Integer: return None p = Poly(fac, i) if p.degree() != 1: return None m, n = p.all_coeffs() ab[k] *= m**mul params[k] += [n/m]*mul # Add "1" to numerator parameters, to account for implicit n! in # hypergeometric series. ap = params[0] + [1] bq = params[1] x = ab[0]/ab[1] h = hyper(ap, bq, x) return f.subs(i, 0)*hyperexpand(h), h.convergence_statement
def sparse_sympy_matrix(self, triqs_operator_expression): Hsp = self.sparse_matrix(triqs_operator_expression) from sympy.matrices import SparseMatrix from sympy.simplify.simplify import nsimplify d = dict([((i, j), nsimplify(val)) for (i, j), val in Hsp.todok().items()]) H = SparseMatrix(Hsp.shape[0], Hsp.shape[1], d) return H
def __new__(cls, *args, **kwargs): if iterable(args[0]): coords = Tuple(*args[0]) elif isinstance(args[0], Point): coords = args[0].args else: coords = Tuple(*args) if len(coords) != 2: raise NotImplementedError("Only two dimensional points currently supported") if kwargs.get('evaluate', True): coords = [nsimplify(c) for c in coords] return GeometryEntity.__new__(cls, *coords)
def __new__(cls, *args, **kwargs): if iterable(args[0]): coords = Tuple(*args[0]) elif isinstance(args[0], Point): coords = args[0].args else: coords = Tuple(*args) if len(coords) != 2: raise NotImplementedError( "Only two dimensional points currently supported") if kwargs.get('evaluate', True): coords = [nsimplify(c) for c in coords] return GeometryEntity.__new__(cls, *coords)
def custom_charpoly(self, **flags): """ custom charpoly """ if (self.isSymbolic == True): self.m = self.m._new(self.m.rows, self.m.cols,[nsimplify(v, rational=True) for v in self.m]) max_denom = 0; for i in range (0,self.m.rows): for j in range (0,self.m.cols): if self.m[i,j] > max_denom: max_denom = self.m[i,j].q print max_denom self.m *= max_denom flags.pop('simplify', None) # pop unsupported flag return self.m.berkowitz_charpoly(Dummy('x')) else: numpy.rint(self.m) return numpy.rint(numpy.poly(self.m))
def eval(cls, n, p, t): # ... if not 0 <= p: raise ValueError("must have 0 <= p") if not 0 <= n: raise ValueError("must have 0 <= n") # ... # ... r = Symbol('r') pp = 2 * p + 1 N = pp + 1 L = list(range(0, N + pp + 1)) b0 = bspline_basis(pp, L, 0, r) b0_r = diff(b0, r) b0_rr = diff(b0_r, r) b0_rrr = diff(b0_rr, r) b0_rrrr = diff(b0_rrr, r) bsp = lambdify(r, b0_rrrr) # ... # ... we use nsimplify to get the rational number phi = [] for i in range(0, p + 1): y = bsp(p + 1 - i) y = nsimplify(y, tolerance=TOLERANCE, rational=True) phi.append(y) # ... # ... m = phi[0] * cos(S.Zero) for i in range(1, p + 1): m += 2 * phi[i] * cos(i * t) # ... # ... scaling m *= n**3 # ... return m
def eval(cls, p, t): if p is S.Infinity: raise NotImplementedError('Add symbol limit for p -> oo') elif isinstance(p, Symbol): return Bilaplacian(p, t, evaluate=False) elif isinstance(p, int): # ... r = Symbol('r') pp = 2*p + 1 N = pp + 1 L = list(range(0, N + pp + 1)) b0 = bspline_basis(pp, L, 0, r) b0_r = diff(b0, r) b0_rr = diff(b0_r, r) b0_rrr = diff(b0_rr, r) b0_rrrr = diff(b0_rrr, r) bsp = lambdify(r, b0_rrrr) # ... # ... we use nsimplify to get the rational number phi = [] for i in range(0, p+1): y = bsp(p+1-i) y = nsimplify(y, tolerance=TOLERANCE, rational=True) phi.append(y) # ... # ... m = phi[0] * cos(S.Zero) for i in range(1, p+1): m += 2 * phi[i] * cos(i * t) # ... return m
def __rpow__(self, other): if isinstance(other, (float, int)): if not self.is_valid: #Don't do anything return self elif other < 0: if self.width > 0: return interval(-float('inf'), float('inf'), is_valid=False) else: power_rational = nsimplify(self.start) num, denom = power_rational.as_numer_denom() if denom % 2 == 0: return interval(-float('inf'), float('inf'), is_valid=False) else: start = -abs(other)**self.start end = start return interval(start, end) else: return interval(other**self.start, other**self.end) elif isinstance(other, interval): return other.__pow__(self) else: return NotImplemented
def polytope_integrate(poly, expr=None, **kwargs): """Integrates polynomials over 2/3-Polytopes. This function accepts the polytope in `poly` and the function in `expr` (uni/bi/trivariate polynomials are implemented) and returns the exact integral of `expr` over `poly`. Parameters ========== poly : The input Polygon. expr : The input polynomial. clockwise : Binary value to sort input points of 2-Polytope clockwise.(Optional) max_degree : The maximum degree of any monomial of the input polynomial.(Optional) Examples ======== >>> from sympy.abc import x, y >>> from sympy.geometry.polygon import Polygon >>> from sympy.geometry.point import Point >>> from sympy.integrals.intpoly import polytope_integrate >>> polygon = Polygon(Point(0,0), Point(0,1), Point(1,1), Point(1,0)) >>> polys = [1, x, y, x*y, x**2*y, x*y**2] >>> expr = x*y >>> polytope_integrate(polygon, expr) 1/4 >>> polytope_integrate(polygon, polys, max_degree=3) {1: 1, x: 1/2, y: 1/2, x*y: 1/4, x*y**2: 1/6, x**2*y: 1/6} """ clockwise = kwargs.get('clockwise', False) max_degree = kwargs.get('max_degree', None) if clockwise is True: if isinstance(poly, Polygon): poly = point_sort(poly) else: raise TypeError("clockwise=True works for only 2-Polytope" "V-representation input") if isinstance(poly, Polygon): # For Vertex Representation(2D case) hp_params = hyperplane_parameters(poly) facets = poly.sides elif len(poly[0]) == 2: # For Hyperplane Representation(2D case) plen = len(poly) if len(poly[0][0]) == 2: intersections = [intersection(poly[(i - 1) % plen], poly[i], "plane2D") for i in range(0, plen)] hp_params = poly lints = len(intersections) facets = [Segment2D(intersections[i], intersections[(i + 1) % lints]) for i in range(0, lints)] else: raise NotImplementedError("Integration for H-representation 3D" "case not implemented yet.") else: # For Vertex Representation(3D case) vertices = poly[0] facets = poly[1:] hp_params = hyperplane_parameters(facets, vertices) if max_degree is None: if expr is None: raise TypeError('Input expression be must' 'be a valid SymPy expression') return main_integrate3d(expr, facets, vertices, hp_params) if max_degree is not None: result = {} if not isinstance(expr, list) and expr is not None: raise TypeError('Input polynomials must be list of expressions') if len(hp_params[0][0]) == 3: result_dict = main_integrate3d(0, facets, vertices, hp_params, max_degree) else: result_dict = main_integrate(0, facets, hp_params, max_degree) if expr is None: return result_dict for poly in expr: if poly not in result: if poly is S.Zero: result[S.Zero] = S.Zero continue integral_value = S.Zero monoms = decompose(poly, separate=True) for monom in monoms: monom = nsimplify(monom) coeff, m = strip(monom) integral_value += result_dict[m] * coeff result[poly] = integral_value return result if expr is None: raise TypeError('Input expression be must' 'be a valid SymPy expression') return main_integrate(expr, facets, hp_params)
def polytope_integrate(poly, expr=None, **kwargs): """Integrates polynomials over 2/3-Polytopes. This function accepts the polytope in `poly` and the function in `expr` (uni/bi/trivariate polynomials are implemented) and returns the exact integral of `expr` over `poly`. Parameters ========== poly : The input Polygon. expr : The input polynomial. clockwise : Binary value to sort input points of 2-Polytope clockwise.(Optional) max_degree : The maximum degree of any monomial of the input polynomial.(Optional) Examples ======== >>> from sympy.abc import x, y >>> from sympy.geometry.polygon import Polygon >>> from sympy.geometry.point import Point >>> from sympy.integrals.intpoly import polytope_integrate >>> polygon = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) >>> polys = [1, x, y, x*y, x**2*y, x*y**2] >>> expr = x*y >>> polytope_integrate(polygon, expr) 1/4 >>> polytope_integrate(polygon, polys, max_degree=3) {1: 1, x: 1/2, y: 1/2, x*y: 1/4, x*y**2: 1/6, x**2*y: 1/6} """ clockwise = kwargs.get('clockwise', False) max_degree = kwargs.get('max_degree', None) if clockwise: if isinstance(poly, Polygon): poly = Polygon(*point_sort(poly.vertices), evaluate=False) else: raise TypeError("clockwise=True works for only 2-Polytope" "V-representation input") if isinstance(poly, Polygon): # For Vertex Representation(2D case) hp_params = hyperplane_parameters(poly) facets = poly.sides elif len(poly[0]) == 2: # For Hyperplane Representation(2D case) plen = len(poly) if len(poly[0][0]) == 2: intersections = [ intersection(poly[(i - 1) % plen], poly[i], "plane2D") for i in range(0, plen) ] hp_params = poly lints = len(intersections) facets = [ Segment2D(intersections[i], intersections[(i + 1) % lints]) for i in range(0, lints) ] else: raise NotImplementedError("Integration for H-representation 3D" "case not implemented yet.") else: # For Vertex Representation(3D case) vertices = poly[0] facets = poly[1:] hp_params = hyperplane_parameters(facets, vertices) if max_degree is None: if expr is None: raise TypeError('Input expression be must' 'be a valid SymPy expression') return main_integrate3d(expr, facets, vertices, hp_params) if max_degree is not None: result = {} if not isinstance(expr, list) and expr is not None: raise TypeError('Input polynomials must be list of expressions') if len(hp_params[0][0]) == 3: result_dict = main_integrate3d(0, facets, vertices, hp_params, max_degree) else: result_dict = main_integrate(0, facets, hp_params, max_degree) if expr is None: return result_dict for poly in expr: if poly not in result: if poly is S.Zero: result[S.Zero] = S.Zero continue integral_value = S.Zero monoms = decompose(poly, separate=True) for monom in monoms: monom = nsimplify(monom) coeff, m = strip(monom) integral_value += result_dict[m] * coeff result[poly] = integral_value return result if expr is None: raise TypeError('Input expression be must' 'be a valid SymPy expression') return main_integrate(expr, facets, hp_params)
def doit(self, **hints): """Evaluates the limit. Parameters ========== deep : bool, optional (default: True) Invoke the ``doit`` method of the expressions involved before taking the limit. hints : optional keyword arguments To be passed to ``doit`` methods; only used if deep is True. """ e, z, z0, dir = self.args if z0 is S.ComplexInfinity: raise NotImplementedError("Limits at complex " "infinity are not implemented") if hints.get('deep', True): e = e.doit(**hints) z = z.doit(**hints) z0 = z0.doit(**hints) if e == z: return z0 if not e.has(z): return e if z0 is S.NaN: return S.NaN if e.has(*_illegal): return self if e.is_Order: return Order(limit(e.expr, z, z0), *e.args[1:]) cdir = 0 if str(dir) == "+": cdir = 1 elif str(dir) == "-": cdir = -1 def set_signs(expr): if not expr.args: return expr newargs = tuple(set_signs(arg) for arg in expr.args) if newargs != expr.args: expr = expr.func(*newargs) abs_flag = isinstance(expr, Abs) sign_flag = isinstance(expr, sign) if abs_flag or sign_flag: sig = limit(expr.args[0], z, z0, dir) if sig.is_zero: sig = limit(1 / expr.args[0], z, z0, dir) if sig.is_extended_real: if (sig < 0) == True: return -expr.args[0] if abs_flag else S.NegativeOne elif (sig > 0) == True: return expr.args[0] if abs_flag else S.One return expr if e.has(Float): # Convert floats like 0.5 to exact SymPy numbers like S.Half, to # prevent rounding errors which can lead to unexpected execution # of conditional blocks that work on comparisons # Also see comments in https://github.com/sympy/sympy/issues/19453 from sympy.simplify.simplify import nsimplify e = nsimplify(e) e = set_signs(e) if e.is_meromorphic(z, z0): if abs(z0) is S.Infinity: newe = e.subs(z, 1 / z) # cdir changes sign as oo- should become 0+ cdir = -cdir else: newe = e.subs(z, z + z0) try: coeff, ex = newe.leadterm(z, cdir=cdir) except ValueError: pass else: if ex > 0: return S.Zero elif ex == 0: return coeff if cdir == 1 or not (int(ex) & 1): return S.Infinity * sign(coeff) elif cdir == -1: return S.NegativeInfinity * sign(coeff) else: return S.ComplexInfinity if abs(z0) is S.Infinity: if e.is_Mul: e = factor_terms(e) newe = e.subs(z, 1 / z) # cdir changes sign as oo- should become 0+ cdir = -cdir else: newe = e.subs(z, z + z0) try: coeff, ex = newe.leadterm(z, cdir=cdir) except (ValueError, NotImplementedError, PoleError): # The NotImplementedError catching is for custom functions from sympy.simplify.powsimp import powsimp e = powsimp(e) if e.is_Pow: r = self.pow_heuristics(e) if r is not None: return r else: if isinstance(coeff, AccumBounds) and ex == S.Zero: return coeff if coeff.has(S.Infinity, S.NegativeInfinity, S.ComplexInfinity, S.NaN): return self if not coeff.has(z): if ex.is_positive: return S.Zero elif ex == 0: return coeff elif ex.is_negative: if ex.is_integer: if cdir == 1 or ex.is_even: return S.Infinity * sign(coeff) elif cdir == -1: return S.NegativeInfinity * sign(coeff) else: return S.ComplexInfinity else: if cdir == 1: return S.Infinity * sign(coeff) elif cdir == -1: return S.Infinity * sign(coeff) * S.NegativeOne**ex else: return S.ComplexInfinity # gruntz fails on factorials but works with the gamma function # If no factorial term is present, e should remain unchanged. # factorial is defined to be zero for negative inputs (which # differs from gamma) so only rewrite for positive z0. if z0.is_extended_positive: e = e.rewrite(factorial, gamma) l = None try: if str(dir) == '+-': r = gruntz(e, z, z0, '+') l = gruntz(e, z, z0, '-') if l != r: raise ValueError( "The limit does not exist since " "left hand limit = %s and right hand limit = %s" % (l, r)) else: r = gruntz(e, z, z0, dir) if r is S.NaN or l is S.NaN: raise PoleError() except (PoleError, ValueError): if l is not None: raise r = heuristics(e, z, z0, dir) if r is None: return self return r
def handle(expr): # Handle first reduces to the case # expr = 1/d, where d is an add, or d is base**p/2. # We do this by recursively calling handle on each piece. from sympy.simplify.simplify import nsimplify n, d = fraction(expr) if expr.is_Atom or (d.is_Atom and n.is_Atom): return expr elif not n.is_Atom: n = n.func(*[handle(a) for a in n.args]) return _unevaluated_Mul(n, handle(1/d)) elif n is not S.One: return _unevaluated_Mul(n, handle(1/d)) elif d.is_Mul: return _unevaluated_Mul(*[handle(1/d) for d in d.args]) # By this step, expr is 1/d, and d is not a mul. if not symbolic and d.free_symbols: return expr if ispow2(d): d2 = sqrtdenest(sqrt(d.base))**numer(d.exp) if d2 != d: return handle(1/d2) elif d.is_Pow and (d.exp.is_integer or d.base.is_positive): # (1/d**i) = (1/d)**i return handle(1/d.base)**d.exp if not (d.is_Add or ispow2(d)): return 1/d.func(*[handle(a) for a in d.args]) # handle 1/d treating d as an Add (though it may not be) keep = True # keep changes that are made # flatten it and collect radicals after checking for special # conditions d = _mexpand(d) # did it change? if d.is_Atom: return 1/d # is it a number that might be handled easily? if d.is_number: _d = nsimplify(d) if _d.is_Number and _d.equals(d): return 1/_d while True: # collect similar terms collected = defaultdict(list) for m in Add.make_args(d): # d might have become non-Add p2 = [] other = [] for i in Mul.make_args(m): if ispow2(i, log2=True): p2.append(i.base if i.exp is S.Half else i.base**(2*i.exp)) elif i is S.ImaginaryUnit: p2.append(S.NegativeOne) else: other.append(i) collected[tuple(ordered(p2))].append(Mul(*other)) rterms = list(ordered(list(collected.items()))) rterms = [(Mul(*i), Add(*j)) for i, j in rterms] nrad = len(rterms) - (1 if rterms[0][0] is S.One else 0) if nrad < 1: break elif nrad > max_terms: # there may have been invalid operations leading to this point # so don't keep changes, e.g. this expression is troublesome # in collecting terms so as not to raise the issue of 2834: # r = sqrt(sqrt(5) + 5) # eq = 1/(sqrt(5)*r + 2*sqrt(5)*sqrt(-sqrt(5) + 5) + 5*r) keep = False break if len(rterms) > 4: # in general, only 4 terms can be removed with repeated squaring # but other considerations can guide selection of radical terms # so that radicals are removed if all([x.is_Integer and (y**2).is_Rational for x, y in rterms]): nd, d = rad_rationalize(S.One, Add._from_args( [sqrt(x)*y for x, y in rterms])) n *= nd else: # is there anything else that might be attempted? keep = False break from sympy.simplify.powsimp import powsimp, powdenest num = powsimp(_num(rterms)) n *= num d *= num d = powdenest(_mexpand(d), force=symbolic) if d.is_Atom: break if not keep: return expr return _unevaluated_Mul(n, 1/d)