def simplify_n_monic(tt): num, den = sp.fraction(sp.simplify(tt)) num = sp.poly(num, s) den = sp.poly(den, s) lcnum = sp.LC(num) lcden = sp.LC(den) return (sp.simplify(lcnum / lcden) * (sp.monic(num) / sp.monic(den)))
def remove_leading_coeff(P): coeff = sympy.LC(P) if np.abs(coeff) < 1.e-7: print(f"P (before removing leading term): {P} {coeff}") P = P - coeff * sympy.LM(P) print(f"P (after removing leading term): {P}") return P
def rearrange(e, n): """'e' is assumed to be a list of exponential polynomial terms""" pos, neg = [], [] for t in e: poly = sp.poly(t[0], gens=n) lc = sp.LC(poly, gens=n) if lc > 0: pos.append(t) elif lc < 0: neg.append(t) return pos, neg
def _find_realization(self, G, s): """ Represenatation [A, B, C, D] of the state space model Returns the representation in state space of a given transfer sp.Function Parameters ========== G: sp.Matrix sp.Matrix valued transfer sp.Function G(s) in laplace space s: sp.Symbol variable s, where G is dependent from See Also ======== Utils : some quick tools for sp.Matrix polynomials References ========== Joao P. Hespanha, Linear Systems Theory. 2009. """ A, B, C, D = 4 * [None] try: m, k = G.shape except AttributeError: raise TypeError("G must be a sp.Matrix") # test if G is proper if not is_proper(G, s, strict=False): raise ValueError("G must be proper!") # define D as the limit of G for s to infinity D = G.limit(s, sp.oo) # define G_sp as the (stricly proper) difference of G and D G_sp = sp.simplify(G - D) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # get the coefficients of the monic least common denominator of all entries of G_sp # compute a least common denominator using utl and sp.lcm fl = fraction_list(G_sp, only_denoms=True) lcd = sp.lcm(list(fl)) # make it monic lcd = sp.simplify(lcd / sp.LC(lcd, s)) # and get a coefficient list of its monic. The [1:] cuts the sp.LC away (thats a one) lcd_coeff = sp.Poly(lcd, s).all_coeffs()[1:] # get the sp.degree of the lcd lcd_deg = sp.degree(lcd, s) # get the sp.Matrix Valued Coeffs of G_sp in G_sp = 1/lcd * (N_1 * s**(n-1) + N_2 * s**(n-2) .. +N_n) G_sp_coeff = matrix_coeff(sp.simplify(G_sp * lcd), s) G_sp_coeff = [sp.zeros(m, k) ] * (lcd_deg - len(G_sp_coeff)) + G_sp_coeff # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # now store A, B, C, D in terms of the coefficients of lcd and G_sp # define A A = (-1) * lcd_coeff[0] * sp.eye(k) for alpha in lcd_coeff[1:]: A = A.row_join((-1) * alpha * sp.eye(k)) for i in range(lcd_deg - 1): if i == 0: tmp = sp.eye(k) else: tmp = sp.zeros(k) for j in range(lcd_deg)[1:]: if j == i: tmp = tmp.row_join(sp.eye(k)) else: tmp = tmp.row_join(sp.zeros(k)) if tmp is not None: A = A.col_join(tmp) # define B B = sp.eye(k) for i in range(lcd_deg - 1): B = B.col_join(sp.zeros(k)) # define C C = G_sp_coeff[0] for i in range(lcd_deg)[1:]: C = C.row_join(G_sp_coeff[i]) # return the state space representation return [sp.simplify(A), sp.simplify(B), sp.simplify(C), sp.simplify(D)]
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
#Compute trace according to ChaosBook v14 eq. 20.13 for i in range(Ncycle): ni = rpos[i][0] Ti = rpos[i][1] Lambda = rpos[i][2] Sum = 0 r = 1 while ni*r <= Nexpansion: Sum = Sum + (z**(ni*r))/np.abs(1-Lambda**r) r += 1 Trace = Trace + Ti * Sum #Extract coefficients of the expansion: C = [] for i in range(Nexpansion): C.insert(0, sympy.LC(Trace)) Trace = Trace - sympy.LT(Trace) #Compute Coefficients of the determinant expansion according to ChaosBook v14 eq. 20.15 Q = [C[0]] SpectDetPoly = np.array([-Q[0], 1], float) F00 = [1 - sum(Q)] for n in range(2, Nexpansion+1): Qn = C[n-1] for k in range(1,n): #print n,k Qn = Qn - C[n - k - 1]*Q[k - 1] Qn = Qn / float(n) Q.append(Qn) F00.append(1 - sum(Q)) SpectDetPoly = np.insert(SpectDetPoly, 0, -Qn)
def ep_ge_0(e, n, strict=False): transformed = to_exponential_polynomial(e) inf_bnd = 9999999999999999999999999999999 e = my_simplify(e, n) if isinstance(e, int) or isinstance(e, float) or e.is_number: res = e > 0 if strict else e >= 0 if res: return res, -1 else: return res, 0 p = e.as_poly(n) if p is not None: if p.degree() <= 4: if strict: solution = sp.solveset(e > 0, domain=sp.S.Reals) else: solution = sp.solveset(e >= 0, domain=sp.S.Reals) non_negative = sp.Interval(0, sp.oo, left_open=False) rounded_solution = round_interval(solution) intersect = rounded_solution.intersection(non_negative) # print(intersect) if intersect.is_empty: return False, 0 if intersect == non_negative: return True, -1 elif sp.LC(p) > 0: j = intersect.left + 1 if intersect.left_open else intersect.left return False, 0 else: j = intersect.right if intersect.right_open else intersect.right + 1 return False, j if any(t[1] < 0 for t in transformed): even = ep_ge_0(e.subs(n, 2*n), n) odd = ep_ge_0(e.subs(n, 2*n + 1), n) if not even[0] and not odd[0]: return False, min(2*even[0], 2*odd[0] + 1) if not even[0]: return even if not odd[0]: return odd return True, -1 # return ep_ge_0(e.subs(n, sp.Integer(2)*n), n) and ep_ge_0(e.subs(n, sp.Integer(2)*n + sp.Integer(1)), n) if any(t[1] < 1 for t in transformed): return ep_ge_0(10**n*e, n) if len(transformed) == 0: return True, -1 pos, neg = rearrange(transformed, n) if len(pos) == 0 and len(neg) > 0: return ep_ge_0_finite(e, n, inf_bnd, strict=strict) u = max(cauchy_bnd(t[0], n) for t in transformed) if len(pos) > 0 and len(neg) == 0: return ep_ge_0_finite(e, n, u, strict=strict) b_pos = max(t[1] for t in pos) pos_index = [t[1] for t in pos].index(b_pos) b_neg = max(t[1] for t in neg) if b_pos < b_neg: return ep_ge_0_finite(e, n, inf_bnd, strict=strict) r = sum(sp.Poly(t[0], gens=n) for t in neg).degree() if r is sp.S.NegativeInfinity: r = 0 mac = sp.series((b_pos/b_neg)**n, x=n, x0=0, n=r+2) mac = mac.removeO() u_p = cauchy_bnd(pos[pos_index][0].subs(n, u)*mac + len(neg)*sum(t[0] for t in neg), n) return ep_ge_0_finite(e, n, max(u, u_p), strict=strict)