def _sqrtdenest34(expr): """Helper that denests the square root of three or four surds. Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrtdenest34 >>> _sqrtdenest34(sqrt(-72*sqrt(2) + 158*sqrt(5) + 498)) -sqrt(10) + sqrt(2) + 9 + 9*sqrt(5) >>> _sqrtdenest34(sqrt(12 + 2*sqrt(6) + 2*sqrt(14) + 2*sqrt(21))) sqrt(2) + sqrt(3) + sqrt(7) References ========== - D. J. Jeffrey and A. D. Rich, 'Symplifying Square Roots of Square Roots by Denesting' """ from sympy.simplify.simplify import radsimp if expr.base < 0: return sqrt(-1)*_sqrtdenest34(sqrt(-expr.base)) # a should be > b so we sort; this also makes the process canonical args = sorted(expr.base.args, reverse=True) a = Add._from_args(args[:2]) b = Add._from_args(args[2:]) c = _sqrtdenest1(sqrt(_mexpand(a**2 - b**2))) if sqrt_depth(c) > 1: return expr d = _sqrtdenest1(sqrt(a + c)) if sqrt_depth(d) > 1: return expr return _mexpand(radsimp((d/sqrt(2) + b/(sqrt(2)*d))))
def _sqrtdenest34(expr): """Helper that denests the square root of three or four surds. Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrtdenest34 >>> _sqrtdenest34(sqrt(-72*sqrt(2) + 158*sqrt(5) + 498)) -sqrt(10) + sqrt(2) + 9 + 9*sqrt(5) >>> _sqrtdenest34(sqrt(12 + 2*sqrt(6) + 2*sqrt(14) + 2*sqrt(21))) sqrt(2) + sqrt(3) + sqrt(7) References ========== - D. J. Jeffrey and A. D. Rich, 'Symplifying Square Roots of Square Roots by Denesting' """ from sympy.simplify.simplify import radsimp if expr.base < 0: return sqrt(-1) * _sqrtdenest34(sqrt(-expr.base)) # a should be > b so we sort; this also makes the process canonical args = sorted(expr.base.args, reverse=True) a = Add._from_args(args[:2]) b = Add._from_args(args[2:]) c = _sqrtdenest1(sqrt(_mexpand(a**2 - b**2))) if sqrt_depth(c) > 1: return expr d = _sqrtdenest1(sqrt(a + c)) if sqrt_depth(d) > 1: return expr return _mexpand(radsimp((d / sqrt(2) + b / (sqrt(2) * d))))
def _sqrtdenest_rec(expr): """Helper that denests the square root of three or more surds. It returns the denested expression; if it cannot be denested it throws SqrtdenestStopIteration Algorithm: expr.base is in the extension Q_m = Q(sqrt(r_1),..,sqrt(r_k)); split expr.base = a + b*sqrt(r_k), where `a` and `b` are on Q_(m-1) = Q(sqrt(r_1),..,sqrt(r_(k-1))); then a**2 - b**2*r_k is on Q_(m-1); denest sqrt(a**2 - b**2*r_k) and so on. See [1], section 6. Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrtdenest_rec >>> _sqrtdenest_rec(sqrt(-72*sqrt(2) + 158*sqrt(5) + 498)) -sqrt(10) + sqrt(2) + 9 + 9*sqrt(5) >>> w=-6*sqrt(55)-6*sqrt(35)-2*sqrt(22)-2*sqrt(14)+2*sqrt(77)+6*sqrt(10)+65 >>> _sqrtdenest_rec(sqrt(w)) -sqrt(11) - sqrt(7) + sqrt(2) + 3*sqrt(5) """ from sympy.simplify.simplify import radsimp, split_surds, rad_rationalize if not expr.is_Pow: return sqrtdenest(expr) if expr.base < 0: return sqrt(-1)*_sqrtdenest_rec(sqrt(-expr.base)) g, a, b = split_surds(expr.base) a = a*sqrt(g) if a < b: a, b = b, a c2 = _mexpand(a**2 - b**2) if len(c2.args) > 2: g, a1, b1 = split_surds(c2) a1 = a1*sqrt(g) if a1 < b1: a1, b1 = b1, a1 c2_1 = _mexpand(a1**2 - b1**2) c_1 = _sqrtdenest_rec(sqrt(c2_1)) d_1 = _sqrtdenest_rec(sqrt(a1 + c_1)) num, den = rad_rationalize(b1, d_1) c = _mexpand(d_1/sqrt(2) + num/(den*sqrt(2))) else: c = _sqrtdenest1(sqrt(c2)) if sqrt_depth(c) > 1: raise SqrtdenestStopIteration ac = a + c if len(ac.args) >= len(expr.args): if count_ops(ac) >= count_ops(expr.base): raise SqrtdenestStopIteration d = sqrtdenest(sqrt(ac)) if sqrt_depth(d) > 1: raise SqrtdenestStopIteration num, den = rad_rationalize(b, d) r = d/sqrt(2) + num/(den*sqrt(2)) r = radsimp(r) return _mexpand(r)
def _sqrtdenest1(expr, denester=True): """Return denested expr after denesting with simpler methods or, that failing, using the denester.""" from sympy.simplify.simplify import radsimp if not is_sqrt(expr): return expr a = expr.base if a.is_Atom: return expr val = _sqrt_match(a) if not val: return expr a, b, r = val # try a quick numeric denesting d2 = _mexpand(a**2 - b**2 * r) if d2.is_Rational: if d2.is_positive: z = _sqrt_numeric_denest(a, b, r, d2) if z is not None: return z else: # fourth root case # sqrtdenest(sqrt(3 + 2*sqrt(3))) = # sqrt(2)*3**(1/4)/2 + sqrt(2)*3**(3/4)/2 dr2 = _mexpand(-d2 * r) dr = sqrt(dr2) if dr.is_Rational: z = _sqrt_numeric_denest(_mexpand(b * r), a, r, dr2) if z is not None: return z / root(r, 4) else: z = _sqrt_symbolic_denest(a, b, r) if z is not None: return z if not denester or not is_algebraic(expr): return expr res = sqrt_biquadratic_denest(expr, a, b, r, d2) if res: return res # now call to the denester av0 = [a, b, r, d2] z = _denester([radsimp(expr**2)], av0, 0, sqrt_depth(expr))[0] if av0[1] is None: return expr if z is not None: if sqrt_depth(z) == sqrt_depth( expr) and count_ops(z) > count_ops(expr): return expr return z return expr
def _sqrtdenest1(expr, denester=True): """Return denested expr after denesting with simpler methods or, that failing, using the denester.""" from sympy.simplify.simplify import radsimp if not is_sqrt(expr): return expr a = expr.base if a.is_Atom: return expr val = _sqrt_match(a) if not val: return expr a, b, r = val # try a quick numeric denesting d2 = _mexpand(a**2 - b**2*r) if d2.is_Rational: if d2.is_positive: z = _sqrt_numeric_denest(a, b, r, d2) if z is not None: return z else: # fourth root case # sqrtdenest(sqrt(3 + 2*sqrt(3))) = # sqrt(2)*3**(1/4)/2 + sqrt(2)*3**(3/4)/2 dr2 = _mexpand(-d2*r) dr = sqrt(dr2) if dr.is_Rational: z = _sqrt_numeric_denest(_mexpand(b*r), a, r, dr2) if z is not None: return z/root(r, 4) else: z = _sqrt_symbolic_denest(a, b, r) if z is not None: return z if not denester or not is_algebraic(expr): return expr res = sqrt_biquadratic_denest(expr, a, b, r, d2) if res: return res # now call to the denester av0 = [a, b, r, d2] z = _denester([radsimp(expr**2)], av0, 0, sqrt_depth(expr))[0] if av0[1] is None: return expr if z is not None: if sqrt_depth(z) == sqrt_depth(expr) and count_ops(z) > count_ops(expr): return expr return z return expr
def _sqrt_numeric_denest(a, b, r, d2): """Helper that denest expr = a + b*sqrt(r), with d2 = a**2 - b**2*r > 0 or returns None if not denested. """ from sympy.simplify.simplify import radsimp depthr = sqrt_depth(r) d = sqrt(d2) vad = a + d # sqrt_depth(res) <= sqrt_depth(vad) + 1 # sqrt_depth(expr) = depthr + 2 # there is denesting if sqrt_depth(vad)+1 < depthr + 2 # if vad**2 is Number there is a fourth root if sqrt_depth(vad) < depthr + 1 or (vad**2).is_Rational: vad1 = radsimp(1/vad) return (sqrt(vad/2) + sign(b)*sqrt((b**2*r*vad1/2).expand())).expand()
def _sqrtdenest34(expr): """Helper that denests the square root of three or four surds. Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrtdenest34 >>> _sqrtdenest34(sqrt(-72*sqrt(2) + 158*sqrt(5) + 498)) -sqrt(10) + sqrt(2) + 9 + 9*sqrt(5) >>> _sqrtdenest34(sqrt(12 + 2*sqrt(6) + 2*sqrt(14) + 2*sqrt(21))) sqrt(2) + sqrt(3) + sqrt(7) References ========== - D. J. Jeffrey and A. D. Rich, 'Symplifying Square Roots of Square Roots by Denesting' """ from sympy.simplify.simplify import radsimp if not is_sqrt(expr): return expr a_plus_b = expr.base if not (a_plus_b.is_Add and len(a_plus_b.args) > 2): return expr if a_plus_b < 0: return sqrt(-1)*_sqrtdenest34(sqrt(-a_plus_b)) args = a_plus_b.args a = Add._from_args(args[:2]) b = Add._from_args(args[2:]) if a < b: # we want a > b; since a + b > 0 then a**2 > b**2, too a, b = b, a c = _sqrtdenest1(sqrt(_mexpand(a**2 - b**2))) if sqrt_depth(c) > 1: return expr d = _sqrtdenest1(sqrt(a + c)) if sqrt_depth(d) > 1: return expr return _mexpand(radsimp((d/sqrt(2) + b/(sqrt(2)*d))))
def sqrt_biquadratic_denest(expr, a, b, r, d2): """denest expr = sqrt(a + b*sqrt(r)) where a, b, r are linear combinations of square roots of positive rationals on the rationals (SQRR) and r > 0, b != 0, d2 = a**2 - b**2*r > 0 If it cannot denest it returns None. ALGORITHM Search for a solution A of type SQRR of the biquadratic equation 4*A**4 - 4*a*A**2 + b**2*r = 0 (1) sqd = sqrt(a**2 - b**2*r) Choosing the sqrt to be positive, the possible solutions are A = sqrt(a/2 +/- sqd/2) Since a, b, r are SQRR, then a**2 - b**2*r is a SQRR, so if sqd can be denested, it is done by _sqrtdenest_rec, and the result is a SQRR. Similarly for A. Examples of solutions (in both cases a and sqd are positive): Example of expr with solution sqrt(a/2 + sqd/2) but not solution sqrt(a/2 - sqd/2): expr = sqrt(-sqrt(15) - sqrt(2)*sqrt(-sqrt(5) + 5) - sqrt(3) + 8) a = -sqrt(15) - sqrt(3) + 8; sqd = -2*sqrt(5) - 2 + 4*sqrt(3) Example of expr with solution sqrt(a/2 - sqd/2) but not solution sqrt(a/2 + sqd/2): w = 2 + r2 + r3 + (1 + r3)*sqrt(2 + r2 + 5*r3) expr = sqrt((w**2).expand()) a = 4*sqrt(6) + 8*sqrt(2) + 47 + 28*sqrt(3) sqd = 29 + 20*sqrt(3) Define B = b/2*A; eq.(1) implies a = A**2 + B**2*r; then expr**2 = a + b*sqrt(r) = (A + B*sqrt(r))**2 Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrt_match, sqrt_biquadratic_denest >>> z = sqrt((2*sqrt(2) + 4)*sqrt(2 + sqrt(2)) + 5*sqrt(2) + 8) >>> a, b, r = _sqrt_match(z**2) >>> d2 = a**2 - b**2*r >>> sqrt_biquadratic_denest(z, a, b, r, d2) sqrt(2) + sqrt(sqrt(2) + 2) + 2 """ from sympy.simplify.simplify import radsimp, rad_rationalize if r <= 0 or d2 < 0 or not b or sqrt_depth(expr.base) < 2: return None for x in (a, b, r): for y in x.args: y2 = y**2 if not y2.is_Integer or not y2.is_positive: return None sqd = _mexpand(sqrtdenest(sqrt(radsimp(d2)))) if sqrt_depth(sqd) > 1: return None x1, x2 = [a/2 + sqd/2, a/2 - sqd/2] # look for a solution A with depth 1 for x in (x1, x2): A = sqrtdenest(sqrt(x)) if sqrt_depth(A) > 1: continue Bn, Bd = rad_rationalize(b, _mexpand(2*A)) B = Bn/Bd z = A + B*sqrt(r) if z < 0: z = -z return _mexpand(z) return None
def _denester(nested, av0, h, max_depth_level): """Denests a list of expressions that contain nested square roots. Algorithm based on <http://www.almaden.ibm.com/cs/people/fagin/symb85.pdf>. It is assumed that all of the elements of 'nested' share the same bottom-level radicand. (This is stated in the paper, on page 177, in the paragraph immediately preceding the algorithm.) When evaluating all of the arguments in parallel, the bottom-level radicand only needs to be denested once. This means that calling _denester with x arguments results in a recursive invocation with x+1 arguments; hence _denester has polynomial complexity. However, if the arguments were evaluated separately, each call would result in two recursive invocations, and the algorithm would have exponential complexity. This is discussed in the paper in the middle paragraph of page 179. """ from sympy.simplify.simplify import radsimp if h > max_depth_level: return None, None if av0[1] is None: return None, None if (av0[0] is None and all(n.is_Number for n in nested)): # no arguments are nested for f in _subsets(len(nested)): # test subset 'f' of nested p = _mexpand(Mul(*[nested[i] for i in range(len(f)) if f[i]])) if f.count(1) > 1 and f[-1]: p = -p sqp = sqrt(p) if sqp.is_Rational: return sqp, f # got a perfect square so return its square root. # Otherwise, return the radicand from the previous invocation. return sqrt(nested[-1]), [0]*len(nested) else: R = None if av0[0] is not None: values = [av0[:2]] R = av0[2] nested2 = [av0[3], R] av0[0] = None else: values = list(filter(None, [_sqrt_match(expr) for expr in nested])) for v in values: if v[2]: # Since if b=0, r is not defined if R is not None: if R != v[2]: av0[1] = None return None, None else: R = v[2] if R is None: # return the radicand from the previous invocation return sqrt(nested[-1]), [0]*len(nested) nested2 = [_mexpand(v[0]**2) - _mexpand(R*v[1]**2) for v in values] + [R] d, f = _denester(nested2, av0, h + 1, max_depth_level) #d = sqrtdenest(d) if not f: return None, None if not any(f[i] for i in range(len(nested))): v = values[-1] return sqrt(v[0] + _mexpand(v[1]*d)), f else: p = Mul(*[nested[i] for i in range(len(nested)) if f[i]]) v = _sqrt_match(p) if 1 in f and f.index(1) < len(nested) - 1 and f[len(nested) - 1]: v[0] = -v[0] v[1] = -v[1] if not f[len(nested)]: # Solution denests with square roots vad = _mexpand(v[0] + d) if vad <= 0: # return the radicand from the previous invocation. return sqrt(nested[-1]), [0]*len(nested) if not(sqrt_depth(vad) <= sqrt_depth(R) + 1 or (vad**2).is_Number): av0[1] = None return None, None sqvad = _sqrtdenest1(sqrt(vad), denester=False) if not (sqrt_depth(sqvad) <= sqrt_depth(R) + 1): av0[1] = None return None, None sqvad1 = radsimp(1/sqvad) res = _mexpand(sqvad/sqrt(2) + (v[1]*sqrt(R)*sqvad1/sqrt(2))) return res, f # sign(v[1])*sqrt(_mexpand(v[1]**2*R*vad1/2))), f else: # Solution requires a fourth root s2 = _mexpand(v[1]*R) + d if s2 <= 0: return sqrt(nested[-1]), [0]*len(nested) FR, s = root(_mexpand(R), 4), sqrt(s2) return _mexpand(s/(sqrt(2)*FR) + v[0]*FR/(sqrt(2)*s)), f
def test_H(): "Hazony problem 5.3.a" s, k = symbols('s k') w = symbols('w', real=True) pprint("test_H") Z = (s**3 + 4 * s**2 + 5 * s + 8) / (2 * s**3 + 2 * s**2 + 20 * s + 9) pprint(f"Z: {Z}") #plot_real_part( sympy.lambdify(s, Z, "numpy")) real_part = cancel(sympy.re(Z.subs({s: sympy.I * w}))) print(f"real_part: {real_part}") roots = sympy.solveset(real_part, w) print(f"roots for w: {roots}") #plot( sympy.lambdify(w, real_part, "numpy")) w0 = sympy.sqrt(6) target0 = radsimp(Z.subs({s: sympy.I * w0}) / (sympy.I * w0)) print(f"target: {target0}") target1 = radsimp(Z.subs({s: sympy.I * w0}) * (sympy.I * w0)) print(f"target: {target1}") assert target0 > 0 eq = sympy.Eq(Z.subs({s: k}) / k, target0) #eq = sympy.Eq( Z.subs({s:k})*k, target1) print(f"eq: {eq}") roots = sympy.solveset(eq, k) print(f"roots for k: {roots}") k0 = Rational(1, 4) + sympy.sqrt(33) / 4 Z_k0 = Z.subs({s: k0}) print(k0, Z_k0) print(k0.evalf(), Z_k0.evalf()) return f = s**2 + 6 den = cancel((k0 * Z_k0 - s * Z) / f) print(f"den factored: {sympy.factor(den)}") num = cancel((k0 * Z - s * Z_k0) / f) print(f"num factored: {sympy.factor(num).evalf()}") print(sympy.factor(cancel(den / num))) return eta = cancel(((k0 * Z - s * Z_k0) / (k0 * Z_k0 - s * Z)).evalf()) print(k0, Z_k0, eta) print(k0, Z_k0, eta.evalf()) print("normal") Z0 = eta * Z_k0 print(f"Z0: {Z0}") Y1 = cancel(1 / Z0 - 4) print(f"Y1: {Y1}") C = Cascade.Shunt(4) Z2 = cancel(1 / Y1 - s / 6 - 1 / (3 * s)) print(f"Z2: {Z2}") C = C.hit(Cascade.Series(s / 6)) C = C.hit(Cascade.Series(1 / (3 * s))) eta_Z_k0 = cancel(C.terminate(0)) print(f"eta_Z_k0: {eta_Z_k0}") assert sympy.Eq(cancel(eta_Z_k0 - Z0), 0) print("recip") Z0 = cancel(Z_k0 / eta) print(f"Z0: {Z0}") Z1 = cancel(Z0 - 1) print(f"Z1: {Z1}") C = Cascade.Series(1) Y2 = cancel(1 / Z1 - 2 * s / 3 - 4 / (3 * s)) print(f"Y2: {Y2}") C = C.hit(Cascade.Shunt(2 * s / 3)) C = C.hit(Cascade.Shunt(4 / (3 * s))) eta_over_Z_k0 = cancel(1 / C.terminate_with_admittance(0)) print(f"eta_over_Z_k0: {eta_over_Z_k0}") assert sympy.Eq(cancel(eta_over_Z_k0 - Z0), 0) def p(a, b): return 1 / (1 / a + 1 / b) constructed_Z = cancel( p(eta_Z_k0, (k0 * Z_k0) / s) + p(eta_over_Z_k0, (Z_k0 * s) / k0)) print(f"constructed_Z: {constructed_Z}") assert sympy.Eq(cancel(constructed_Z - Z), 0)
def test_J(): "Second problem in Guillemin" s, k = symbols('s k') w = symbols('w', real=True) pprint("test_I") Z = (s**2 + s + 8) / (s**2 + 2 * s + 2) pprint(f"Z: {Z}") Y = 1 / Z #plot_real_part( sympy.lambdify(s, Y, "numpy")) real_part = cancel(sympy.re(Y.subs({s: sympy.I * w}))) print(f"real_part: {real_part}") roots = sympy.solveset(real_part, w) print(f"roots for w: {roots}") #plot( sympy.lambdify(w, real_part, "numpy")) w0 = 2 target0 = radsimp(Y.subs({s: sympy.I * w0}) / (sympy.I * w0)) print(f"target: {target0.evalf()}") target0 = Rational(1, 2) target1 = radsimp(Y.subs({s: sympy.I * w0}) * (sympy.I * w0)) print(f"target: {target1.evalf()}") target1 = Rational(2, 1) assert target0 > 0 eq = sympy.Eq(Y.subs({s: k}) / k, target0) #assert target1 > 0 #eq = sympy.Eq( Z.subs({s:k})*k, target1) roots = sympy.solveset(eq, k) print(f"roots for k: {roots}") k0 = Rational(1, 1) Y_k0 = Y.subs({s: k0}) print(k0, Y_k0) print(k0.evalf(), Y_k0.evalf()) den = cancel((k0 * Y_k0 - s * Y)) print(f"den factored: {sympy.factor(den)}") num = cancel((k0 * Y - s * Y_k0)) print(f"num factored: {sympy.factor(num)}") eta = cancel(num / den) print(k0, Y_k0, eta) print("normal") Y0 = eta * Y_k0 print(f"Y0: {Y0}") Z1 = ratsimp(1 / Y0 - 4) print(f"Z1: {Z1}") C = Cascade.Series(4) Y2 = ratsimp(1 / Z1) print(f"Y2: {Y2}") C = C.hit(Cascade.Shunt(s / 10)) C = C.hit(Cascade.Shunt(2 / (5 * s))) eta_Y_k0 = cancel(C.terminate_with_admittance(0)) print(f"eta_Y_k0: {eta_Y_k0}") assert sympy.Eq(cancel(eta_Y_k0 - Y0), 0) print("recip") Y0 = ratsimp(Y_k0 / eta) print(f"Y0: {Y0}") Y1 = ratsimp(Y0 - 1) print(f"Y1: {Y1}") C = Cascade.Shunt(1) Z2 = ratsimp(1 / Y1 - 2 * s / 5 - 8 / (5 * s)) print(f"Z2: {Z2}") C = C.hit(Cascade.Series(2 * s / 5)) C = C.hit(Cascade.Series(8 / (5 * s))) eta_over_Y_k0 = cancel(1 / C.terminate(0)) print(f"eta_over_Y_k0: {eta_over_Y_k0}") assert sympy.Eq(cancel(eta_over_Y_k0 - Y0), 0) def p(a, b): return a * b / (a + b) constructed_Y = cancel( p(eta_Y_k0, (k0 * Y_k0) / s) + p(eta_over_Y_k0, (Y_k0 * s) / k0)) print(f"constructed_Y: {constructed_Y}") assert sympy.Eq(cancel(constructed_Y - Y), 0)
def test_I(): "Hazony problem 5.3.a" s, k = symbols('s k') w = symbols('w', real=True) pprint("test_I") Z = (s**3 + 3 * s**2 + s + 1) / (s**3 + s**2 + 3 * s + 1) pprint(f"Z: {Z}") #plot_real_part( sympy.lambdify(s, Z, "numpy")) real_part = cancel(sympy.re(Z.subs({s: sympy.I * w}))) print(f"real_part: {real_part}") roots = sympy.solveset(real_part, w) print(f"roots for w: {roots}") #plot( sympy.lambdify(w, real_part, "numpy")) w0 = 1 target0 = radsimp(Z.subs({s: sympy.I * w0}) / (sympy.I * w0)) print(f"target: {target0}") target1 = radsimp(Z.subs({s: sympy.I * w0}) * (sympy.I * w0)) print(f"target: {target1}") assert target0 > 0 eq = sympy.Eq(Z.subs({s: k}) / k, target0) #assert target1 > 0 #eq = sympy.Eq( Z.subs({s:k})*k, target1) roots = sympy.solveset(eq, k) print(f"roots for k: {roots}") k0 = Rational(1, 1) Z_k0 = Z.subs({s: k0}) print(k0, Z_k0) print(k0.evalf(), Z_k0.evalf()) den = cancel((k0 * Z_k0 - s * Z)) print(f"den factored: {sympy.factor(den)}") num = cancel((k0 * Z - s * Z_k0)) print(f"num factored: {sympy.factor(num)}") eta = cancel(num / den) print(k0, Z_k0, eta) print("normal") Z0 = eta * Z_k0 print(f"Z0: {Z0}") Y1 = ratsimp(1 / Z0 - 1) print(f"Y1: {Y1}") C = Cascade.Shunt(1) Z2 = ratsimp(1 / Y1 - s / 2 - 1 / (2 * s)) print(f"Z2: {Z2}") C = C.hit(Cascade.Series(s / 2)) C = C.hit(Cascade.Series(1 / (2 * s))) eta_Z_k0 = cancel(C.terminate(0)) print(f"eta_Z_k0: {eta_Z_k0}") assert sympy.Eq(cancel(eta_Z_k0 - Z0), 0) print("recip") Z0 = cancel(Z_k0 / eta) print(f"Z0: {Z0}") Z1 = ratsimp(Z0 - 1) print(f"Z1: {Z1}") C = Cascade.Series(1) Y2 = ratsimp(1 / Z1 - s / 2 - 1 / (2 * s)) print(f"Y2: {Y2}") C = C.hit(Cascade.Shunt(s / 2)) C = C.hit(Cascade.Shunt(1 / (2 * s))) eta_over_Z_k0 = cancel(1 / C.terminate_with_admittance(0)) print(f"eta_over_Z_k0: {eta_over_Z_k0}") assert sympy.Eq(cancel(eta_over_Z_k0 - Z0), 0) def p(a, b): return a * b / (a + b) constructed_Z = cancel( p(eta_Z_k0, (k0 * Z_k0) / s) + p(eta_over_Z_k0, (Z_k0 * s) / k0)) print(f"constructed_Z: {constructed_Z}") assert sympy.Eq(cancel(constructed_Z - Z), 0)
def _denester(nested, av0, h, max_depth_level): """Denests a list of expressions that contain nested square roots. Algorithm based on <http://www.almaden.ibm.com/cs/people/fagin/symb85.pdf>. It is assumed that all of the elements of 'nested' share the same bottom-level radicand. (This is stated in the paper, on page 177, in the paragraph immediately preceding the algorithm.) When evaluating all of the arguments in parallel, the bottom-level radicand only needs to be denested once. This means that calling _denester with x arguments results in a recursive invocation with x+1 arguments; hence _denester has polynomial complexity. However, if the arguments were evaluated separately, each call would result in two recursive invocations, and the algorithm would have exponential complexity. This is discussed in the paper in the middle paragraph of page 179. """ from sympy.simplify.simplify import radsimp if h > max_depth_level: return None, None if av0[1] is None: return None, None if (av0[0] is None and all(n.is_Number for n in nested)): # no arguments are nested for f in subsets(len(nested)): # test subset 'f' of nested p = _mexpand(Mul(*[nested[i] for i in range(len(f)) if f[i]])) if f.count(1) > 1 and f[-1]: p = -p sqp = sqrt(p) if sqp.is_Rational: return sqp, f # got a perfect square so return its square root. # Otherwise, return the radicand from the previous invocation. return sqrt(nested[-1]), [0]*len(nested) else: R = None if av0[0] is not None: values = [av0[:2]] R = av0[2] nested2 = [av0[3], R] av0[0] = None else: values = filter(None, [_sqrt_match(expr) for expr in nested]) for v in values: if v[2]: #Since if b=0, r is not defined if R is not None: if R != v[2]: av0[1] = None return None, None else: R = v[2] if R is None: # return the radicand from the previous invocation return sqrt(nested[-1]), [0]*len(nested) nested2 = [_mexpand(v[0]**2) - _mexpand(R*v[1]**2) for v in values] + [R] d, f = _denester(nested2, av0, h + 1, max_depth_level) if not f: return None, None if not any(f[i] for i in range(len(nested))): v = values[-1] return sqrt(v[0] + v[1]*d), f else: p = Mul(*[nested[i] for i in range(len(nested)) if f[i]]) v = _sqrt_match(p) if 1 in f and f.index(1) < len(nested) - 1 and f[len(nested) - 1]: v[0] = -v[0] v[1] = -v[1] if not f[len(nested)]: #Solution denests with square roots vad = _mexpand(v[0] + d) if vad <= 0: # return the radicand from the previous invocation. return sqrt(nested[-1]), [0]*len(nested) if not(sqrt_depth(vad) < sqrt_depth(R) + 1 or (vad**2).is_Number): av0[1] = None return None, None vad1 = radsimp(1/vad) return _mexpand(sqrt(vad/2) + sign(v[1])*sqrt(_mexpand(v[1]**2*R*vad1/2))), f else: #Solution requires a fourth root s2 = _mexpand(v[1]*R) + d if s2 <= 0: return sqrt(nested[-1]), [0]*len(nested) FR, s = root(_mexpand(R), 4), sqrt(s2) return _mexpand(s/(sqrt(2)*FR) + v[0]*FR/(sqrt(2)*s)), f
def test_G(): "Hazony example 5.2.2" s, k = symbols('s k') pprint("test_G") Z = (s**2 + s + 1) / (s**2 + s + 4) pprint(f"Z: {Z}") #plot_real_part( sympy.lambdify(s, Z, "numpy")) w0 = sympy.sqrt(2) target = radsimp(Z.subs({s: sympy.I * w0}) / (sympy.I * w0)) print(f"target: {target}") eq = sympy.Eq(Z.subs({s: k}) / k, target) roots = sympy.solveset(eq, k) if True: for k0 in roots: Z_k0 = Z.subs({s: k0}) eta = cancel((k0 * Z - s * Z_k0) / (k0 * Z_k0 - s * Z)) print(k0, Z_k0, eta) #plot_real_part( sympy.lambdify(s, eta, "numpy")) k0 = 1 Z_k0 = Z.subs({s: k0}) eta = cancel((k0 * Z - s * Z_k0) / (k0 * Z_k0 - s * Z)) print(k0, Z_k0, eta) print("normal") Z0 = eta * Z_k0 print(f"Z0: {Z0}") Y1 = cancel(1 / Z0 - 4) print(f"Y1: {Y1}") C = Cascade.Shunt(4) Z2 = cancel(1 / Y1 - s / 6 - 1 / (3 * s)) print(f"Z2: {Z2}") C = C.hit(Cascade.Series(s / 6)) C = C.hit(Cascade.Series(1 / (3 * s))) eta_Z_k0 = cancel(C.terminate(0)) print(f"eta_Z_k0: {eta_Z_k0}") assert sympy.Eq(cancel(eta_Z_k0 - Z0), 0) print("recip") Z0 = cancel(Z_k0 / eta) print(f"Z0: {Z0}") Z1 = cancel(Z0 - 1) print(f"Z1: {Z1}") C = Cascade.Series(1) Y2 = cancel(1 / Z1 - 2 * s / 3 - 4 / (3 * s)) print(f"Y2: {Y2}") C = C.hit(Cascade.Shunt(2 * s / 3)) C = C.hit(Cascade.Shunt(4 / (3 * s))) eta_over_Z_k0 = cancel(1 / C.terminate_with_admittance(0)) print(f"eta_over_Z_k0: {eta_over_Z_k0}") assert sympy.Eq(cancel(eta_over_Z_k0 - Z0), 0) def p(a, b): return 1 / (1 / a + 1 / b) constructed_Z = cancel( p(eta_Z_k0, (k0 * Z_k0) / s) + p(eta_over_Z_k0, (Z_k0 * s) / k0)) print(f"constructed_Z: {constructed_Z}") assert sympy.Eq(cancel(constructed_Z - Z), 0)