def _futrig(e, **kwargs): """Helper for futrig.""" from sympy.simplify.fu import ( TR1, TR2, TR3, TR2i, TR10, L, TR10i, TR8, TR6, TR15, TR16, TR111, TR5, TRmorrie, TR11, TR14, TR22, TR12) from sympy.core.compatibility import _nodes if not e.has(TrigonometricFunction): return e if e.is_Mul: coeff, e = e.as_independent(TrigonometricFunction) else: coeff = S.One Lops = lambda x: (L(x), x.count_ops(), _nodes(x), len(x.args), x.is_Add) trigs = lambda x: x.has(TrigonometricFunction) tree = [identity, ( TR3, # canonical angles TR1, # sec-csc -> cos-sin TR12, # expand tan of sum lambda x: _eapply(factor, x, trigs), TR2, # tan-cot -> sin-cos [identity, lambda x: _eapply(_mexpand, x, trigs)], TR2i, # sin-cos ratio -> tan lambda x: _eapply(lambda i: factor(i.normal()), x, trigs), TR14, # factored identities TR5, # sin-pow -> cos_pow TR10, # sin-cos of sums -> sin-cos prod TR11, TR6, # reduce double angles and rewrite cos pows lambda x: _eapply(factor, x, trigs), TR14, # factored powers of identities [identity, lambda x: _eapply(_mexpand, x, trigs)], TRmorrie, TR10i, # sin-cos products > sin-cos of sums [identity, TR8], # sin-cos products -> sin-cos of sums [identity, lambda x: TR2i(TR2(x))], # tan -> sin-cos -> tan [ lambda x: _eapply(expand_mul, TR5(x), trigs), lambda x: _eapply( expand_mul, TR15(x), trigs)], # pos/neg powers of sin [ lambda x: _eapply(expand_mul, TR6(x), trigs), lambda x: _eapply( expand_mul, TR16(x), trigs)], # pos/neg powers of cos TR111, # tan, sin, cos to neg power -> cot, csc, sec [identity, TR2i], # sin-cos ratio to tan [identity, lambda x: _eapply( expand_mul, TR22(x), trigs)], # tan-cot to sec-csc TR1, TR2, TR2i, [identity, lambda x: _eapply( factor_terms, TR12(x), trigs)], # expand tan of sum )] e = greedy(tree, objective=Lops)(e) return coeff*e
def test_TR6(): assert TR6(cos(x)**2) == -sin(x)**2 + 1 assert TR6(cos(x)**-2) == cos(x)**(-2) assert TR6(cos(x)**4) == (-sin(x)**2 + 1)**2
def ratfun(self, expr, z, n, **kwargs): expr = expr / z # Handle special case 1 / (z**m * (z - 1)) since this becomes u[n - m] # The default method produces u[n] - delta[n] for u[n-1]. This is correct # but can be simplified. # In general, 1 / (z**m * (z - a)) becomes a**n * u[n - m] if (len(expr.args) == 2 and expr.args[1].is_Pow and expr.args[1].args[0].is_Add and expr.args[1].args[0].args[0] == -1 and expr.args[1].args[0].args[1] == z): delay = None if expr.args[0] == z: delay = 1 elif expr.args[0].is_Pow and expr.args[0].args[0] == z: a = expr.args[0].args[1] if a.is_positive: warn('Dodgy z-transform 1. Have advance of unit step.') elif not a.is_negative: warn( 'Dodgy z-transform 2. May have advance of unit step.') delay = -a elif (expr.args[0].is_Pow and expr.args[0].args[0].is_Pow and expr.args[0].args[0].args[0] == z and expr.args[0].args[0].args[1] == -1): a = expr.args[0].args[1] if a.is_negative: warn('Dodgy z-transform 3. Have advance of unit step.') elif not a.is_positive: warn( 'Dodgy z-transform 4. May have advance of unit step.') delay = a if delay is not None: return UnitStep(n - delay), sym.S.Zero zexpr = Ratfun(expr, z) Q, M, D, delay, undef = zexpr.as_QMA() cresult = sym.S.Zero uresult = sym.S.Zero if Q: Qpoly = sym.Poly(Q, z) C = Qpoly.all_coeffs() for m, c in enumerate(C): cresult += c * UnitImpulse(n - len(C) + m + 1) # There is problem with determining residues if # have 1/(z*(-a/z + 1)) instead of 1/(-a + z). Hopefully, # simplify will fix things... expr = (M / D).simplify() # M and D may contain common factors before simplification, so redefine M and D M = sym.numer(expr) D = sym.denom(expr) for factor in expr.as_ordered_factors(): if factor == sym.oo: return factor, factor zexpr = Ratfun(expr, z, **kwargs) poles = zexpr.poles(damping=kwargs.get('damping', None)) poles_dict = {} for pole in poles: # Replace cos()**2-1 by sin()**2 pole.expr = TR6(sym.expand(pole.expr)) pole.expr = sym.simplify(pole.expr) # Remove abs value from sin() pole.expr = pole.expr.subs(sym.Abs, sym.Id) poles_dict[pole.expr] = pole.n # Juergen Weizenecker HsKa # Make two dictionaries in order to handle them differently and make # pretty expressions if kwargs.get('pairs', True): pole_pair_dict, pole_single_dict = pair_conjugates(poles_dict) else: pole_pair_dict, pole_single_dict = {}, poles_dict # Make n (=number of poles) different denominators to speed up # calculation and avoid sym.limit. The different denominators are # due to shortening of poles after multiplying with (z-z1)**o if not (M.is_polynomial(z) and D.is_polynomial(z)): print("Numerator or denominator may contain 1/z terms: ", M, D) n_poles = len(poles) # Leading coefficient of denominator polynom a_0 = sym.LC(D, z) # The canceled denominator (for each (z-p)**o) shorten_denom = {} for i in range(n_poles): shorten_term = sym.prod([(z - poles[j].expr)**(poles[j].n) for j in range(n_poles) if j != i], a_0) shorten_denom[poles[i].expr] = shorten_term # Run through single poles real or complex, order 1 or higher for pole in pole_single_dict: p = pole # Number of occurrences of the pole. o = pole_single_dict[pole] # X(z)/z*(z-p)**o after shortening. expr2 = M / shorten_denom[p] if o == 0: continue if o == 1: r = sym.simplify(sym.expand(expr2.subs(z, p))) if p == 0: cresult += r * UnitImpulse(n) else: uresult += r * p**n continue # Handle repeated poles. all_derivatives = [expr2] for i in range(1, o): all_derivatives += [sym.diff(all_derivatives[i - 1], z)] bino = 1 sum_p = 0 for i in range(1, o + 1): m = o - i derivative = all_derivatives[m] # Derivative at z=p derivative = sym.expand(derivative.subs(z, p)) r = sym.simplify(derivative) / sym.factorial(m) if p == 0: cresult += r * UnitImpulse(n - i + 1) else: sum_p += r * bino * p**(1 - i) / sym.factorial(i - 1) bino *= n - i + 1 uresult += sym.simplify(sum_p * p**n) # Run through complex pole pairs for pole in pole_pair_dict: p1 = pole[0] p2 = pole[1] # Number of occurrences of the pole pair o1 = pole_pair_dict[pole] # X(z)/z*(z-p)**o after shortening expr_1 = M / shorten_denom[p1] expr_2 = M / shorten_denom[p2] # Oscillation parameter lam = sym.sqrt(sym.simplify(p1 * p2)) p1_n = sym.simplify(p1 / lam) # term is of form exp(j*arg()) if len(p1_n.args ) == 1 and p1_n.is_Function and p1_n.func == sym.exp: omega_0 = sym.im(p1_n.args[0]) # term is of form cos() + j sin() elif p1_n.is_Add and sym.re(p1_n).is_Function and sym.re( p1_n).func == sym.cos: p1_n = p1_n.rewrite(sym.exp) omega_0 = sym.im(p1_n.args[0]) # general form else: omega_0 = sym.simplify(sym.arg(p1_n)) if o1 == 1: r1 = expr_1.subs(z, p1) r2 = expr_2.subs(z, p2) r1_re = sym.re(r1).simplify() r1_im = sym.im(r1).simplify() # if pole pairs is selected, r1=r2* # Handle real part uresult += 2 * TR9(r1_re) * lam**n * sym.cos(omega_0 * n) uresult -= 2 * TR9(r1_im) * lam**n * sym.sin(omega_0 * n) else: bino = 1 sum_b = 0 # Compute first all derivatives needed all_derivatives_1 = [expr_1] for i in range(1, o1): all_derivatives_1 += [ sym.diff(all_derivatives_1[i - 1], z) ] # Loop through the binomial series for i in range(1, o1 + 1): m = o1 - i # m th derivative at z=p1 derivative = all_derivatives_1[m] r1 = derivative.subs(z, p1) / sym.factorial(m) # prefactors prefac = bino * lam**(1 - i) / sym.factorial(i - 1) # simplify r1 r1 = r1.rewrite(sym.exp).simplify() # sum sum_b += prefac * r1 * sym.exp(sym.I * omega_0 * (1 - i)) # binomial coefficient bino *= n - i + 1 # take result = lam**n * (sum_b*sum_b*exp(j*omega_0*n) + cc) aa = sym.simplify(sym.re(sum_b)) bb = sym.simplify(sym.im(sum_b)) uresult += 2 * (aa * sym.cos(omega_0 * n) - bb * sym.sin(omega_0 * n)) * lam**n # cresult is a sum of Dirac deltas and its derivatives so is known # to be causal. return cresult, uresult