123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142 |
- from sympy.core import Add, Mul, S
- from sympy.core.containers import Tuple
- from sympy.core.exprtools import factor_terms
- from sympy.core.numbers import I
- from sympy.core.relational import Eq, Equality
- from sympy.core.sorting import default_sort_key, ordered
- from sympy.core.symbol import Dummy, Symbol
- from sympy.core.function import (expand_mul, expand, Derivative,
- AppliedUndef, Function, Subs)
- from sympy.functions import (exp, im, cos, sin, re, Piecewise,
- piecewise_fold, sqrt, log)
- from sympy.functions.combinatorial.factorials import factorial
- from sympy.matrices import zeros, Matrix, NonSquareMatrixError, MatrixBase, eye
- from sympy.polys import Poly, together
- from sympy.simplify import collect, radsimp, signsimp # type: ignore
- from sympy.simplify.powsimp import powdenest, powsimp
- from sympy.simplify.ratsimp import ratsimp
- from sympy.simplify.simplify import simplify
- from sympy.sets.sets import FiniteSet
- from sympy.solvers.deutils import ode_order
- from sympy.solvers.solveset import NonlinearError, solveset
- from sympy.utilities.iterables import (connected_components, iterable,
- strongly_connected_components)
- from sympy.utilities.misc import filldedent
- from sympy.integrals.integrals import Integral, integrate
- def _get_func_order(eqs, funcs):
- return {func: max(ode_order(eq, func) for eq in eqs) for func in funcs}
- class ODEOrderError(ValueError):
- """Raised by linear_ode_to_matrix if the system has the wrong order"""
- pass
- class ODENonlinearError(NonlinearError):
- """Raised by linear_ode_to_matrix if the system is nonlinear"""
- pass
- def _simpsol(soleq):
- lhs = soleq.lhs
- sol = soleq.rhs
- sol = powsimp(sol)
- gens = list(sol.atoms(exp))
- p = Poly(sol, *gens, expand=False)
- gens = [factor_terms(g) for g in gens]
- if not gens:
- gens = p.gens
- syms = [Symbol('C1'), Symbol('C2')]
- terms = []
- for coeff, monom in zip(p.coeffs(), p.monoms()):
- coeff = piecewise_fold(coeff)
- if isinstance(coeff, Piecewise):
- coeff = Piecewise(*((ratsimp(coef).collect(syms), cond) for coef, cond in coeff.args))
- else:
- coeff = ratsimp(coeff).collect(syms)
- monom = Mul(*(g ** i for g, i in zip(gens, monom)))
- terms.append(coeff * monom)
- return Eq(lhs, Add(*terms))
- def _solsimp(e, t):
- no_t, has_t = powsimp(expand_mul(e)).as_independent(t)
- no_t = ratsimp(no_t)
- has_t = has_t.replace(exp, lambda a: exp(factor_terms(a)))
- return no_t + has_t
- def simpsol(sol, wrt1, wrt2, doit=True):
- """Simplify solutions from dsolve_system."""
- # The parameter sol is the solution as returned by dsolve (list of Eq).
- #
- # The parameters wrt1 and wrt2 are lists of symbols to be collected for
- # with those in wrt1 being collected for first. This allows for collecting
- # on any factors involving the independent variable before collecting on
- # the integration constants or vice versa using e.g.:
- #
- # sol = simpsol(sol, [t], [C1, C2]) # t first, constants after
- # sol = simpsol(sol, [C1, C2], [t]) # constants first, t after
- #
- # If doit=True (default) then simpsol will begin by evaluating any
- # unevaluated integrals. Since many integrals will appear multiple times
- # in the solutions this is done intelligently by computing each integral
- # only once.
- #
- # The strategy is to first perform simple cancellation with factor_terms
- # and then multiply out all brackets with expand_mul. This gives an Add
- # with many terms.
- #
- # We split each term into two multiplicative factors dep and coeff where
- # all factors that involve wrt1 are in dep and any constant factors are in
- # coeff e.g.
- # sqrt(2)*C1*exp(t) -> ( exp(t), sqrt(2)*C1 )
- #
- # The dep factors are simplified using powsimp to combine expanded
- # exponential factors e.g.
- # exp(a*t)*exp(b*t) -> exp(t*(a+b))
- #
- # We then collect coefficients for all terms having the same (simplified)
- # dep. The coefficients are then simplified using together and ratsimp and
- # lastly by recursively applying the same transformation to the
- # coefficients to collect on wrt2.
- #
- # Finally the result is recombined into an Add and signsimp is used to
- # normalise any minus signs.
- def simprhs(rhs, rep, wrt1, wrt2):
- """Simplify the rhs of an ODE solution"""
- if rep:
- rhs = rhs.subs(rep)
- rhs = factor_terms(rhs)
- rhs = simp_coeff_dep(rhs, wrt1, wrt2)
- rhs = signsimp(rhs)
- return rhs
- def simp_coeff_dep(expr, wrt1, wrt2=None):
- """Split rhs into terms, split terms into dep and coeff and collect on dep"""
- add_dep_terms = lambda e: e.is_Add and e.has(*wrt1)
- expandable = lambda e: e.is_Mul and any(map(add_dep_terms, e.args))
- expand_func = lambda e: expand_mul(e, deep=False)
- expand_mul_mod = lambda e: e.replace(expandable, expand_func)
- terms = Add.make_args(expand_mul_mod(expr))
- dc = {}
- for term in terms:
- coeff, dep = term.as_independent(*wrt1, as_Add=False)
- # Collect together the coefficients for terms that have the same
- # dependence on wrt1 (after dep is normalised using simpdep).
- dep = simpdep(dep, wrt1)
- # See if the dependence on t cancels out...
- if dep is not S.One:
- dep2 = factor_terms(dep)
- if not dep2.has(*wrt1):
- coeff *= dep2
- dep = S.One
- if dep not in dc:
- dc[dep] = coeff
- else:
- dc[dep] += coeff
- # Apply the method recursively to the coefficients but this time
- # collecting on wrt2 rather than wrt2.
- termpairs = ((simpcoeff(c, wrt2), d) for d, c in dc.items())
- if wrt2 is not None:
- termpairs = ((simp_coeff_dep(c, wrt2), d) for c, d in termpairs)
- return Add(*(c * d for c, d in termpairs))
- def simpdep(term, wrt1):
- """Normalise factors involving t with powsimp and recombine exp"""
- def canonicalise(a):
- # Using factor_terms here isn't quite right because it leads to things
- # like exp(t*(1+t)) that we don't want. We do want to cancel factors
- # and pull out a common denominator but ideally the numerator would be
- # expressed as a standard form polynomial in t so we expand_mul
- # and collect afterwards.
- a = factor_terms(a)
- num, den = a.as_numer_denom()
- num = expand_mul(num)
- num = collect(num, wrt1)
- return num / den
- term = powsimp(term)
- rep = {e: exp(canonicalise(e.args[0])) for e in term.atoms(exp)}
- term = term.subs(rep)
- return term
- def simpcoeff(coeff, wrt2):
- """Bring to a common fraction and cancel with ratsimp"""
- coeff = together(coeff)
- if coeff.is_polynomial():
- # Calling ratsimp can be expensive. The main reason is to simplify
- # sums of terms with irrational denominators so we limit ourselves
- # to the case where the expression is polynomial in any symbols.
- # Maybe there's a better approach...
- coeff = ratsimp(radsimp(coeff))
- # collect on secondary variables first and any remaining symbols after
- if wrt2 is not None:
- syms = list(wrt2) + list(ordered(coeff.free_symbols - set(wrt2)))
- else:
- syms = list(ordered(coeff.free_symbols))
- coeff = collect(coeff, syms)
- coeff = together(coeff)
- return coeff
- # There are often repeated integrals. Collect unique integrals and
- # evaluate each once and then substitute into the final result to replace
- # all occurrences in each of the solution equations.
- if doit:
- integrals = set().union(*(s.atoms(Integral) for s in sol))
- rep = {i: factor_terms(i).doit() for i in integrals}
- else:
- rep = {}
- sol = [Eq(s.lhs, simprhs(s.rhs, rep, wrt1, wrt2)) for s in sol]
- return sol
- def linodesolve_type(A, t, b=None):
- r"""
- Helper function that determines the type of the system of ODEs for solving with :obj:`sympy.solvers.ode.systems.linodesolve()`
- Explanation
- ===========
- This function takes in the coefficient matrix and/or the non-homogeneous term
- and returns the type of the equation that can be solved by :obj:`sympy.solvers.ode.systems.linodesolve()`.
- If the system is constant coefficient homogeneous, then "type1" is returned
- If the system is constant coefficient non-homogeneous, then "type2" is returned
- If the system is non-constant coefficient homogeneous, then "type3" is returned
- If the system is non-constant coefficient non-homogeneous, then "type4" is returned
- If the system has a non-constant coefficient matrix which can be factorized into constant
- coefficient matrix, then "type5" or "type6" is returned for when the system is homogeneous or
- non-homogeneous respectively.
- Note that, if the system of ODEs is of "type3" or "type4", then along with the type,
- the commutative antiderivative of the coefficient matrix is also returned.
- If the system cannot be solved by :obj:`sympy.solvers.ode.systems.linodesolve()`, then
- NotImplementedError is raised.
- Parameters
- ==========
- A : Matrix
- Coefficient matrix of the system of ODEs
- b : Matrix or None
- Non-homogeneous term of the system. The default value is None.
- If this argument is None, then the system is assumed to be homogeneous.
- Examples
- ========
- >>> from sympy import symbols, Matrix
- >>> from sympy.solvers.ode.systems import linodesolve_type
- >>> t = symbols("t")
- >>> A = Matrix([[1, 1], [2, 3]])
- >>> b = Matrix([t, 1])
- >>> linodesolve_type(A, t)
- {'antiderivative': None, 'type_of_equation': 'type1'}
- >>> linodesolve_type(A, t, b=b)
- {'antiderivative': None, 'type_of_equation': 'type2'}
- >>> A_t = Matrix([[1, t], [-t, 1]])
- >>> linodesolve_type(A_t, t)
- {'antiderivative': Matrix([
- [ t, t**2/2],
- [-t**2/2, t]]), 'type_of_equation': 'type3'}
- >>> linodesolve_type(A_t, t, b=b)
- {'antiderivative': Matrix([
- [ t, t**2/2],
- [-t**2/2, t]]), 'type_of_equation': 'type4'}
- >>> A_non_commutative = Matrix([[1, t], [t, -1]])
- >>> linodesolve_type(A_non_commutative, t)
- Traceback (most recent call last):
- ...
- NotImplementedError:
- The system does not have a commutative antiderivative, it cannot be
- solved by linodesolve.
- Returns
- =======
- Dict
- Raises
- ======
- NotImplementedError
- When the coefficient matrix doesn't have a commutative antiderivative
- See Also
- ========
- linodesolve: Function for which linodesolve_type gets the information
- """
- match = {}
- is_non_constant = not _matrix_is_constant(A, t)
- is_non_homogeneous = not (b is None or b.is_zero_matrix)
- type = "type{}".format(int("{}{}".format(int(is_non_constant), int(is_non_homogeneous)), 2) + 1)
- B = None
- match.update({"type_of_equation": type, "antiderivative": B})
- if is_non_constant:
- B, is_commuting = _is_commutative_anti_derivative(A, t)
- if not is_commuting:
- raise NotImplementedError(filldedent('''
- The system does not have a commutative antiderivative, it cannot be solved
- by linodesolve.
- '''))
- match['antiderivative'] = B
- match.update(_first_order_type5_6_subs(A, t, b=b))
- return match
- def _first_order_type5_6_subs(A, t, b=None):
- match = {}
- factor_terms = _factor_matrix(A, t)
- is_homogeneous = b is None or b.is_zero_matrix
- if factor_terms is not None:
- t_ = Symbol("{}_".format(t))
- F_t = integrate(factor_terms[0], t)
- inverse = solveset(Eq(t_, F_t), t)
- # Note: A simple way to check if a function is invertible
- # or not.
- if isinstance(inverse, FiniteSet) and not inverse.has(Piecewise)\
- and len(inverse) == 1:
- A = factor_terms[1]
- if not is_homogeneous:
- b = b / factor_terms[0]
- b = b.subs(t, list(inverse)[0])
- type = "type{}".format(5 + (not is_homogeneous))
- match.update({'func_coeff': A, 'tau': F_t,
- 't_': t_, 'type_of_equation': type, 'rhs': b})
- return match
- def linear_ode_to_matrix(eqs, funcs, t, order):
- r"""
- Convert a linear system of ODEs to matrix form
- Explanation
- ===========
- Express a system of linear ordinary differential equations as a single
- matrix differential equation [1]. For example the system $x' = x + y + 1$
- and $y' = x - y$ can be represented as
- .. math:: A_1 X' = A_0 X + b
- where $A_1$ and $A_0$ are $2 \times 2$ matrices and $b$, $X$ and $X'$ are
- $2 \times 1$ matrices with $X = [x, y]^T$.
- Higher-order systems are represented with additional matrices e.g. a
- second-order system would look like
- .. math:: A_2 X'' = A_1 X' + A_0 X + b
- Examples
- ========
- >>> from sympy import Function, Symbol, Matrix, Eq
- >>> from sympy.solvers.ode.systems import linear_ode_to_matrix
- >>> t = Symbol('t')
- >>> x = Function('x')
- >>> y = Function('y')
- We can create a system of linear ODEs like
- >>> eqs = [
- ... Eq(x(t).diff(t), x(t) + y(t) + 1),
- ... Eq(y(t).diff(t), x(t) - y(t)),
- ... ]
- >>> funcs = [x(t), y(t)]
- >>> order = 1 # 1st order system
- Now ``linear_ode_to_matrix`` can represent this as a matrix
- differential equation.
- >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, t, order)
- >>> A1
- Matrix([
- [1, 0],
- [0, 1]])
- >>> A0
- Matrix([
- [1, 1],
- [1, -1]])
- >>> b
- Matrix([
- [1],
- [0]])
- The original equations can be recovered from these matrices:
- >>> eqs_mat = Matrix([eq.lhs - eq.rhs for eq in eqs])
- >>> X = Matrix(funcs)
- >>> A1 * X.diff(t) - A0 * X - b == eqs_mat
- True
- If the system of equations has a maximum order greater than the
- order of the system specified, a ODEOrderError exception is raised.
- >>> eqs = [Eq(x(t).diff(t, 2), x(t).diff(t) + x(t)), Eq(y(t).diff(t), y(t) + x(t))]
- >>> linear_ode_to_matrix(eqs, funcs, t, 1)
- Traceback (most recent call last):
- ...
- ODEOrderError: Cannot represent system in 1-order form
- If the system of equations is nonlinear, then ODENonlinearError is
- raised.
- >>> eqs = [Eq(x(t).diff(t), x(t) + y(t)), Eq(y(t).diff(t), y(t)**2 + x(t))]
- >>> linear_ode_to_matrix(eqs, funcs, t, 1)
- Traceback (most recent call last):
- ...
- ODENonlinearError: The system of ODEs is nonlinear.
- Parameters
- ==========
- eqs : list of SymPy expressions or equalities
- The equations as expressions (assumed equal to zero).
- funcs : list of applied functions
- The dependent variables of the system of ODEs.
- t : symbol
- The independent variable.
- order : int
- The order of the system of ODEs.
- Returns
- =======
- The tuple ``(As, b)`` where ``As`` is a tuple of matrices and ``b`` is the
- the matrix representing the rhs of the matrix equation.
- Raises
- ======
- ODEOrderError
- When the system of ODEs have an order greater than what was specified
- ODENonlinearError
- When the system of ODEs is nonlinear
- See Also
- ========
- linear_eq_to_matrix: for systems of linear algebraic equations.
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Matrix_differential_equation
- """
- from sympy.solvers.solveset import linear_eq_to_matrix
- if any(ode_order(eq, func) > order for eq in eqs for func in funcs):
- msg = "Cannot represent system in {}-order form"
- raise ODEOrderError(msg.format(order))
- As = []
- for o in range(order, -1, -1):
- # Work from the highest derivative down
- funcs_deriv = [func.diff(t, o) for func in funcs]
- # linear_eq_to_matrix expects a proper symbol so substitute e.g.
- # Derivative(x(t), t) for a Dummy.
- rep = {func_deriv: Dummy() for func_deriv in funcs_deriv}
- eqs = [eq.subs(rep) for eq in eqs]
- syms = [rep[func_deriv] for func_deriv in funcs_deriv]
- # Ai is the matrix for X(t).diff(t, o)
- # eqs is minus the remainder of the equations.
- try:
- Ai, b = linear_eq_to_matrix(eqs, syms)
- except NonlinearError:
- raise ODENonlinearError("The system of ODEs is nonlinear.")
- Ai = Ai.applyfunc(expand_mul)
- As.append(Ai if o == order else -Ai)
- if o:
- eqs = [-eq for eq in b]
- else:
- rhs = b
- return As, rhs
- def matrix_exp(A, t):
- r"""
- Matrix exponential $\exp(A*t)$ for the matrix ``A`` and scalar ``t``.
- Explanation
- ===========
- This functions returns the $\exp(A*t)$ by doing a simple
- matrix multiplication:
- .. math:: \exp(A*t) = P * expJ * P^{-1}
- where $expJ$ is $\exp(J*t)$. $J$ is the Jordan normal
- form of $A$ and $P$ is matrix such that:
- .. math:: A = P * J * P^{-1}
- The matrix exponential $\exp(A*t)$ appears in the solution of linear
- differential equations. For example if $x$ is a vector and $A$ is a matrix
- then the initial value problem
- .. math:: \frac{dx(t)}{dt} = A \times x(t), x(0) = x0
- has the unique solution
- .. math:: x(t) = \exp(A t) x0
- Examples
- ========
- >>> from sympy import Symbol, Matrix, pprint
- >>> from sympy.solvers.ode.systems import matrix_exp
- >>> t = Symbol('t')
- We will consider a 2x2 matrix for comupting the exponential
- >>> A = Matrix([[2, -5], [2, -4]])
- >>> pprint(A)
- [2 -5]
- [ ]
- [2 -4]
- Now, exp(A*t) is given as follows:
- >>> pprint(matrix_exp(A, t))
- [ -t -t -t ]
- [3*e *sin(t) + e *cos(t) -5*e *sin(t) ]
- [ ]
- [ -t -t -t ]
- [ 2*e *sin(t) - 3*e *sin(t) + e *cos(t)]
- Parameters
- ==========
- A : Matrix
- The matrix $A$ in the expression $\exp(A*t)$
- t : Symbol
- The independent variable
- See Also
- ========
- matrix_exp_jordan_form: For exponential of Jordan normal form
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Jordan_normal_form
- .. [2] https://en.wikipedia.org/wiki/Matrix_exponential
- """
- P, expJ = matrix_exp_jordan_form(A, t)
- return P * expJ * P.inv()
- def matrix_exp_jordan_form(A, t):
- r"""
- Matrix exponential $\exp(A*t)$ for the matrix *A* and scalar *t*.
- Explanation
- ===========
- Returns the Jordan form of the $\exp(A*t)$ along with the matrix $P$ such that:
- .. math::
- \exp(A*t) = P * expJ * P^{-1}
- Examples
- ========
- >>> from sympy import Matrix, Symbol
- >>> from sympy.solvers.ode.systems import matrix_exp, matrix_exp_jordan_form
- >>> t = Symbol('t')
- We will consider a 2x2 defective matrix. This shows that our method
- works even for defective matrices.
- >>> A = Matrix([[1, 1], [0, 1]])
- It can be observed that this function gives us the Jordan normal form
- and the required invertible matrix P.
- >>> P, expJ = matrix_exp_jordan_form(A, t)
- Here, it is shown that P and expJ returned by this function is correct
- as they satisfy the formula: P * expJ * P_inverse = exp(A*t).
- >>> P * expJ * P.inv() == matrix_exp(A, t)
- True
- Parameters
- ==========
- A : Matrix
- The matrix $A$ in the expression $\exp(A*t)$
- t : Symbol
- The independent variable
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Defective_matrix
- .. [2] https://en.wikipedia.org/wiki/Jordan_matrix
- .. [3] https://en.wikipedia.org/wiki/Jordan_normal_form
- """
- N, M = A.shape
- if N != M:
- raise ValueError('Needed square matrix but got shape (%s, %s)' % (N, M))
- elif A.has(t):
- raise ValueError('Matrix A should not depend on t')
- def jordan_chains(A):
- '''Chains from Jordan normal form analogous to M.eigenvects().
- Returns a dict with eignevalues as keys like:
- {e1: [[v111,v112,...], [v121, v122,...]], e2:...}
- where vijk is the kth vector in the jth chain for eigenvalue i.
- '''
- P, blocks = A.jordan_cells()
- basis = [P[:,i] for i in range(P.shape[1])]
- n = 0
- chains = {}
- for b in blocks:
- eigval = b[0, 0]
- size = b.shape[0]
- if eigval not in chains:
- chains[eigval] = []
- chains[eigval].append(basis[n:n+size])
- n += size
- return chains
- eigenchains = jordan_chains(A)
- # Needed for consistency across Python versions
- eigenchains_iter = sorted(eigenchains.items(), key=default_sort_key)
- isreal = not A.has(I)
- blocks = []
- vectors = []
- seen_conjugate = set()
- for e, chains in eigenchains_iter:
- for chain in chains:
- n = len(chain)
- if isreal and e != e.conjugate() and e.conjugate() in eigenchains:
- if e in seen_conjugate:
- continue
- seen_conjugate.add(e.conjugate())
- exprt = exp(re(e) * t)
- imrt = im(e) * t
- imblock = Matrix([[cos(imrt), sin(imrt)],
- [-sin(imrt), cos(imrt)]])
- expJblock2 = Matrix(n, n, lambda i,j:
- imblock * t**(j-i) / factorial(j-i) if j >= i
- else zeros(2, 2))
- expJblock = Matrix(2*n, 2*n, lambda i,j: expJblock2[i//2,j//2][i%2,j%2])
- blocks.append(exprt * expJblock)
- for i in range(n):
- vectors.append(re(chain[i]))
- vectors.append(im(chain[i]))
- else:
- vectors.extend(chain)
- fun = lambda i,j: t**(j-i)/factorial(j-i) if j >= i else 0
- expJblock = Matrix(n, n, fun)
- blocks.append(exp(e * t) * expJblock)
- expJ = Matrix.diag(*blocks)
- P = Matrix(N, N, lambda i,j: vectors[j][i])
- return P, expJ
- # Note: To add a docstring example with tau
- def linodesolve(A, t, b=None, B=None, type="auto", doit=False,
- tau=None):
- r"""
- System of n equations linear first-order differential equations
- Explanation
- ===========
- This solver solves the system of ODEs of the follwing form:
- .. math::
- X'(t) = A(t) X(t) + b(t)
- Here, $A(t)$ is the coefficient matrix, $X(t)$ is the vector of n independent variables,
- $b(t)$ is the non-homogeneous term and $X'(t)$ is the derivative of $X(t)$
- Depending on the properties of $A(t)$ and $b(t)$, this solver evaluates the solution
- differently.
- When $A(t)$ is constant coefficient matrix and $b(t)$ is zero vector i.e. system is homogeneous,
- the system is "type1". The solution is:
- .. math::
- X(t) = \exp(A t) C
- Here, $C$ is a vector of constants and $A$ is the constant coefficient matrix.
- When $A(t)$ is constant coefficient matrix and $b(t)$ is non-zero i.e. system is non-homogeneous,
- the system is "type2". The solution is:
- .. math::
- X(t) = e^{A t} ( \int e^{- A t} b \,dt + C)
- When $A(t)$ is coefficient matrix such that its commutative with its antiderivative $B(t)$ and
- $b(t)$ is a zero vector i.e. system is homogeneous, the system is "type3". The solution is:
- .. math::
- X(t) = \exp(B(t)) C
- When $A(t)$ is commutative with its antiderivative $B(t)$ and $b(t)$ is non-zero i.e. system is
- non-homogeneous, the system is "type4". The solution is:
- .. math::
- X(t) = e^{B(t)} ( \int e^{-B(t)} b(t) \,dt + C)
- When $A(t)$ is a coefficient matrix such that it can be factorized into a scalar and a constant
- coefficient matrix:
- .. math::
- A(t) = f(t) * A
- Where $f(t)$ is a scalar expression in the independent variable $t$ and $A$ is a constant matrix,
- then we can do the following substitutions:
- .. math::
- tau = \int f(t) dt, X(t) = Y(tau), b(t) = b(f^{-1}(tau))
- Here, the substitution for the non-homogeneous term is done only when its non-zero.
- Using these substitutions, our original system becomes:
- .. math::
- Y'(tau) = A * Y(tau) + b(tau)/f(tau)
- The above system can be easily solved using the solution for "type1" or "type2" depending
- on the homogeneity of the system. After we get the solution for $Y(tau)$, we substitute the
- solution for $tau$ as $t$ to get back $X(t)$
- .. math::
- X(t) = Y(tau)
- Systems of "type5" and "type6" have a commutative antiderivative but we use this solution
- because its faster to compute.
- The final solution is the general solution for all the four equations since a constant coefficient
- matrix is always commutative with its antidervative.
- An additional feature of this function is, if someone wants to substitute for value of the independent
- variable, they can pass the substitution `tau` and the solution will have the independent variable
- substituted with the passed expression(`tau`).
- Parameters
- ==========
- A : Matrix
- Coefficient matrix of the system of linear first order ODEs.
- t : Symbol
- Independent variable in the system of ODEs.
- b : Matrix or None
- Non-homogeneous term in the system of ODEs. If None is passed,
- a homogeneous system of ODEs is assumed.
- B : Matrix or None
- Antiderivative of the coefficient matrix. If the antiderivative
- is not passed and the solution requires the term, then the solver
- would compute it internally.
- type : String
- Type of the system of ODEs passed. Depending on the type, the
- solution is evaluated. The type values allowed and the corresponding
- system it solves are: "type1" for constant coefficient homogeneous
- "type2" for constant coefficient non-homogeneous, "type3" for non-constant
- coefficient homogeneous, "type4" for non-constant coefficient non-homogeneous,
- "type5" and "type6" for non-constant coefficient homogeneous and non-homogeneous
- systems respectively where the coefficient matrix can be factorized to a constant
- coefficient matrix.
- The default value is "auto" which will let the solver decide the correct type of
- the system passed.
- doit : Boolean
- Evaluate the solution if True, default value is False
- tau: Expression
- Used to substitute for the value of `t` after we get the solution of the system.
- Examples
- ========
- To solve the system of ODEs using this function directly, several things must be
- done in the right order. Wrong inputs to the function will lead to incorrect results.
- >>> from sympy import symbols, Function, Eq
- >>> from sympy.solvers.ode.systems import canonical_odes, linear_ode_to_matrix, linodesolve, linodesolve_type
- >>> from sympy.solvers.ode.subscheck import checkodesol
- >>> f, g = symbols("f, g", cls=Function)
- >>> x, a = symbols("x, a")
- >>> funcs = [f(x), g(x)]
- >>> eqs = [Eq(f(x).diff(x) - f(x), a*g(x) + 1), Eq(g(x).diff(x) + g(x), a*f(x))]
- Here, it is important to note that before we derive the coefficient matrix, it is
- important to get the system of ODEs into the desired form. For that we will use
- :obj:`sympy.solvers.ode.systems.canonical_odes()`.
- >>> eqs = canonical_odes(eqs, funcs, x)
- >>> eqs
- [[Eq(Derivative(f(x), x), a*g(x) + f(x) + 1), Eq(Derivative(g(x), x), a*f(x) - g(x))]]
- Now, we will use :obj:`sympy.solvers.ode.systems.linear_ode_to_matrix()` to get the coefficient matrix and the
- non-homogeneous term if it is there.
- >>> eqs = eqs[0]
- >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1)
- >>> A = A0
- We have the coefficient matrices and the non-homogeneous term ready. Now, we can use
- :obj:`sympy.solvers.ode.systems.linodesolve_type()` to get the information for the system of ODEs
- to finally pass it to the solver.
- >>> system_info = linodesolve_type(A, x, b=b)
- >>> sol_vector = linodesolve(A, x, b=b, B=system_info['antiderivative'], type=system_info['type_of_equation'])
- Now, we can prove if the solution is correct or not by using :obj:`sympy.solvers.ode.checkodesol()`
- >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)]
- >>> checkodesol(eqs, sol)
- (True, [0, 0])
- We can also use the doit method to evaluate the solutions passed by the function.
- >>> sol_vector_evaluated = linodesolve(A, x, b=b, type="type2", doit=True)
- Now, we will look at a system of ODEs which is non-constant.
- >>> eqs = [Eq(f(x).diff(x), f(x) + x*g(x)), Eq(g(x).diff(x), -x*f(x) + g(x))]
- The system defined above is already in the desired form, so we do not have to convert it.
- >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1)
- >>> A = A0
- A user can also pass the commutative antiderivative required for type3 and type4 system of ODEs.
- Passing an incorrect one will lead to incorrect results. If the coefficient matrix is not commutative
- with its antiderivative, then :obj:`sympy.solvers.ode.systems.linodesolve_type()` raises a NotImplementedError.
- If it does have a commutative antiderivative, then the function just returns the information about the system.
- >>> system_info = linodesolve_type(A, x, b=b)
- Now, we can pass the antiderivative as an argument to get the solution. If the system information is not
- passed, then the solver will compute the required arguments internally.
- >>> sol_vector = linodesolve(A, x, b=b)
- Once again, we can verify the solution obtained.
- >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)]
- >>> checkodesol(eqs, sol)
- (True, [0, 0])
- Returns
- =======
- List
- Raises
- ======
- ValueError
- This error is raised when the coefficient matrix, non-homogeneous term
- or the antiderivative, if passed, aren't a matrix or
- do not have correct dimensions
- NonSquareMatrixError
- When the coefficient matrix or its antiderivative, if passed isn't a square
- matrix
- NotImplementedError
- If the coefficient matrix doesn't have a commutative antiderivative
- See Also
- ========
- linear_ode_to_matrix: Coefficient matrix computation function
- canonical_odes: System of ODEs representation change
- linodesolve_type: Getting information about systems of ODEs to pass in this solver
- """
- if not isinstance(A, MatrixBase):
- raise ValueError(filldedent('''\
- The coefficients of the system of ODEs should be of type Matrix
- '''))
- if not A.is_square:
- raise NonSquareMatrixError(filldedent('''\
- The coefficient matrix must be a square
- '''))
- if b is not None:
- if not isinstance(b, MatrixBase):
- raise ValueError(filldedent('''\
- The non-homogeneous terms of the system of ODEs should be of type Matrix
- '''))
- if A.rows != b.rows:
- raise ValueError(filldedent('''\
- The system of ODEs should have the same number of non-homogeneous terms and the number of
- equations
- '''))
- if B is not None:
- if not isinstance(B, MatrixBase):
- raise ValueError(filldedent('''\
- The antiderivative of coefficients of the system of ODEs should be of type Matrix
- '''))
- if not B.is_square:
- raise NonSquareMatrixError(filldedent('''\
- The antiderivative of the coefficient matrix must be a square
- '''))
- if A.rows != B.rows:
- raise ValueError(filldedent('''\
- The coefficient matrix and its antiderivative should have same dimensions
- '''))
- if not any(type == "type{}".format(i) for i in range(1, 7)) and not type == "auto":
- raise ValueError(filldedent('''\
- The input type should be a valid one
- '''))
- n = A.rows
- # constants = numbered_symbols(prefix='C', cls=Dummy, start=const_idx+1)
- Cvect = Matrix(list(Dummy() for _ in range(n)))
- if any(type == typ for typ in ["type2", "type4", "type6"]) and b is None:
- b = zeros(n, 1)
- is_transformed = tau is not None
- passed_type = type
- if type == "auto":
- system_info = linodesolve_type(A, t, b=b)
- type = system_info["type_of_equation"]
- B = system_info["antiderivative"]
- if type in ("type5", "type6"):
- is_transformed = True
- if passed_type != "auto":
- if tau is None:
- system_info = _first_order_type5_6_subs(A, t, b=b)
- if not system_info:
- raise ValueError(filldedent('''
- The system passed isn't {}.
- '''.format(type)))
- tau = system_info['tau']
- t = system_info['t_']
- A = system_info['A']
- b = system_info['b']
- if type in ("type1", "type2", "type5", "type6"):
- P, J = matrix_exp_jordan_form(A, t)
- P = simplify(P)
- if type in ("type1", "type5"):
- sol_vector = P * (J * Cvect)
- else:
- Jinv = J.subs(t, -t)
- sol_vector = P * J * ((Jinv * P.inv() * b).applyfunc(lambda x: Integral(x, t)) + Cvect)
- else:
- if B is None:
- B, _ = _is_commutative_anti_derivative(A, t)
- if type == "type3":
- sol_vector = B.exp() * Cvect
- else:
- sol_vector = B.exp() * (((-B).exp() * b).applyfunc(lambda x: Integral(x, t)) + Cvect)
- if is_transformed:
- sol_vector = sol_vector.subs(t, tau)
- gens = sol_vector.atoms(exp)
- if type != "type1":
- sol_vector = [expand_mul(s) for s in sol_vector]
- sol_vector = [collect(s, ordered(gens), exact=True) for s in sol_vector]
- if doit:
- sol_vector = [s.doit() for s in sol_vector]
- return sol_vector
- def _matrix_is_constant(M, t):
- """Checks if the matrix M is independent of t or not."""
- return all(coef.as_independent(t, as_Add=True)[1] == 0 for coef in M)
- def canonical_odes(eqs, funcs, t):
- r"""
- Function that solves for highest order derivatives in a system
- Explanation
- ===========
- This function inputs a system of ODEs and based on the system,
- the dependent variables and their highest order, returns the system
- in the following form:
- .. math::
- X'(t) = A(t) X(t) + b(t)
- Here, $X(t)$ is the vector of dependent variables of lower order, $A(t)$ is
- the coefficient matrix, $b(t)$ is the non-homogeneous term and $X'(t)$ is the
- vector of dependent variables in their respective highest order. We use the term
- canonical form to imply the system of ODEs which is of the above form.
- If the system passed has a non-linear term with multiple solutions, then a list of
- systems is returned in its canonical form.
- Parameters
- ==========
- eqs : List
- List of the ODEs
- funcs : List
- List of dependent variables
- t : Symbol
- Independent variable
- Examples
- ========
- >>> from sympy import symbols, Function, Eq, Derivative
- >>> from sympy.solvers.ode.systems import canonical_odes
- >>> f, g = symbols("f g", cls=Function)
- >>> x, y = symbols("x y")
- >>> funcs = [f(x), g(x)]
- >>> eqs = [Eq(f(x).diff(x) - 7*f(x), 12*g(x)), Eq(g(x).diff(x) + g(x), 20*f(x))]
- >>> canonical_eqs = canonical_odes(eqs, funcs, x)
- >>> canonical_eqs
- [[Eq(Derivative(f(x), x), 7*f(x) + 12*g(x)), Eq(Derivative(g(x), x), 20*f(x) - g(x))]]
- >>> system = [Eq(Derivative(f(x), x)**2 - 2*Derivative(f(x), x) + 1, 4), Eq(-y*f(x) + Derivative(g(x), x), 0)]
- >>> canonical_system = canonical_odes(system, funcs, x)
- >>> canonical_system
- [[Eq(Derivative(f(x), x), -1), Eq(Derivative(g(x), x), y*f(x))], [Eq(Derivative(f(x), x), 3), Eq(Derivative(g(x), x), y*f(x))]]
- Returns
- =======
- List
- """
- from sympy.solvers.solvers import solve
- order = _get_func_order(eqs, funcs)
- canon_eqs = solve(eqs, *[func.diff(t, order[func]) for func in funcs], dict=True)
- systems = []
- for eq in canon_eqs:
- system = [Eq(func.diff(t, order[func]), eq[func.diff(t, order[func])]) for func in funcs]
- systems.append(system)
- return systems
- def _is_commutative_anti_derivative(A, t):
- r"""
- Helper function for determining if the Matrix passed is commutative with its antiderivative
- Explanation
- ===========
- This function checks if the Matrix $A$ passed is commutative with its antiderivative with respect
- to the independent variable $t$.
- .. math::
- B(t) = \int A(t) dt
- The function outputs two values, first one being the antiderivative $B(t)$, second one being a
- boolean value, if True, then the matrix $A(t)$ passed is commutative with $B(t)$, else the matrix
- passed isn't commutative with $B(t)$.
- Parameters
- ==========
- A : Matrix
- The matrix which has to be checked
- t : Symbol
- Independent variable
- Examples
- ========
- >>> from sympy import symbols, Matrix
- >>> from sympy.solvers.ode.systems import _is_commutative_anti_derivative
- >>> t = symbols("t")
- >>> A = Matrix([[1, t], [-t, 1]])
- >>> B, is_commuting = _is_commutative_anti_derivative(A, t)
- >>> is_commuting
- True
- Returns
- =======
- Matrix, Boolean
- """
- B = integrate(A, t)
- is_commuting = (B*A - A*B).applyfunc(expand).applyfunc(factor_terms).is_zero_matrix
- is_commuting = False if is_commuting is None else is_commuting
- return B, is_commuting
- def _factor_matrix(A, t):
- term = None
- for element in A:
- temp_term = element.as_independent(t)[1]
- if temp_term.has(t):
- term = temp_term
- break
- if term is not None:
- A_factored = (A/term).applyfunc(ratsimp)
- can_factor = _matrix_is_constant(A_factored, t)
- term = (term, A_factored) if can_factor else None
- return term
- def _is_second_order_type2(A, t):
- term = _factor_matrix(A, t)
- is_type2 = False
- if term is not None:
- term = 1/term[0]
- is_type2 = term.is_polynomial()
- if is_type2:
- poly = Poly(term.expand(), t)
- monoms = poly.monoms()
- if monoms[0][0] in (2, 4):
- cs = _get_poly_coeffs(poly, 4)
- a, b, c, d, e = cs
- a1 = powdenest(sqrt(a), force=True)
- c1 = powdenest(sqrt(e), force=True)
- b1 = powdenest(sqrt(c - 2*a1*c1), force=True)
- is_type2 = (b == 2*a1*b1) and (d == 2*b1*c1)
- term = a1*t**2 + b1*t + c1
- else:
- is_type2 = False
- return is_type2, term
- def _get_poly_coeffs(poly, order):
- cs = [0 for _ in range(order+1)]
- for c, m in zip(poly.coeffs(), poly.monoms()):
- cs[-1-m[0]] = c
- return cs
- def _match_second_order_type(A1, A0, t, b=None):
- r"""
- Works only for second order system in its canonical form.
- Type 0: Constant coefficient matrix, can be simply solved by
- introducing dummy variables.
- Type 1: When the substitution: $U = t*X' - X$ works for reducing
- the second order system to first order system.
- Type 2: When the system is of the form: $poly * X'' = A*X$ where
- $poly$ is square of a quadratic polynomial with respect to
- *t* and $A$ is a constant coefficient matrix.
- """
- match = {"type_of_equation": "type0"}
- n = A1.shape[0]
- if _matrix_is_constant(A1, t) and _matrix_is_constant(A0, t):
- return match
- if (A1 + A0*t).applyfunc(expand_mul).is_zero_matrix:
- match.update({"type_of_equation": "type1", "A1": A1})
- elif A1.is_zero_matrix and (b is None or b.is_zero_matrix):
- is_type2, term = _is_second_order_type2(A0, t)
- if is_type2:
- a, b, c = _get_poly_coeffs(Poly(term, t), 2)
- A = (A0*(term**2).expand()).applyfunc(ratsimp) + (b**2/4 - a*c)*eye(n, n)
- tau = integrate(1/term, t)
- t_ = Symbol("{}_".format(t))
- match.update({"type_of_equation": "type2", "A0": A,
- "g(t)": sqrt(term), "tau": tau, "is_transformed": True,
- "t_": t_})
- return match
- def _second_order_subs_type1(A, b, funcs, t):
- r"""
- For a linear, second order system of ODEs, a particular substitution.
- A system of the below form can be reduced to a linear first order system of
- ODEs:
- .. math::
- X'' = A(t) * (t*X' - X) + b(t)
- By substituting:
- .. math:: U = t*X' - X
- To get the system:
- .. math:: U' = t*(A(t)*U + b(t))
- Where $U$ is the vector of dependent variables, $X$ is the vector of dependent
- variables in `funcs` and $X'$ is the first order derivative of $X$ with respect to
- $t$. It may or may not reduce the system into linear first order system of ODEs.
- Then a check is made to determine if the system passed can be reduced or not, if
- this substitution works, then the system is reduced and its solved for the new
- substitution. After we get the solution for $U$:
- .. math:: U = a(t)
- We substitute and return the reduced system:
- .. math::
- a(t) = t*X' - X
- Parameters
- ==========
- A: Matrix
- Coefficient matrix($A(t)*t$) of the second order system of this form.
- b: Matrix
- Non-homogeneous term($b(t)$) of the system of ODEs.
- funcs: List
- List of dependent variables
- t: Symbol
- Independent variable of the system of ODEs.
- Returns
- =======
- List
- """
- U = Matrix([t*func.diff(t) - func for func in funcs])
- sol = linodesolve(A, t, t*b)
- reduced_eqs = [Eq(u, s) for s, u in zip(sol, U)]
- reduced_eqs = canonical_odes(reduced_eqs, funcs, t)[0]
- return reduced_eqs
- def _second_order_subs_type2(A, funcs, t_):
- r"""
- Returns a second order system based on the coefficient matrix passed.
- Explanation
- ===========
- This function returns a system of second order ODE of the following form:
- .. math::
- X'' = A * X
- Here, $X$ is the vector of dependent variables, but a bit modified, $A$ is the
- coefficient matrix passed.
- Along with returning the second order system, this function also returns the new
- dependent variables with the new independent variable `t_` passed.
- Parameters
- ==========
- A: Matrix
- Coefficient matrix of the system
- funcs: List
- List of old dependent variables
- t_: Symbol
- New independent variable
- Returns
- =======
- List, List
- """
- func_names = [func.func.__name__ for func in funcs]
- new_funcs = [Function(Dummy("{}_".format(name)))(t_) for name in func_names]
- rhss = A * Matrix(new_funcs)
- new_eqs = [Eq(func.diff(t_, 2), rhs) for func, rhs in zip(new_funcs, rhss)]
- return new_eqs, new_funcs
- def _is_euler_system(As, t):
- return all(_matrix_is_constant((A*t**i).applyfunc(ratsimp), t) for i, A in enumerate(As))
- def _classify_linear_system(eqs, funcs, t, is_canon=False):
- r"""
- Returns a dictionary with details of the eqs if the system passed is linear
- and can be classified by this function else returns None
- Explanation
- ===========
- This function takes the eqs, converts it into a form Ax = b where x is a vector of terms
- containing dependent variables and their derivatives till their maximum order. If it is
- possible to convert eqs into Ax = b, then all the equations in eqs are linear otherwise
- they are non-linear.
- To check if the equations are constant coefficient, we need to check if all the terms in
- A obtained above are constant or not.
- To check if the equations are homogeneous or not, we need to check if b is a zero matrix
- or not.
- Parameters
- ==========
- eqs: List
- List of ODEs
- funcs: List
- List of dependent variables
- t: Symbol
- Independent variable of the equations in eqs
- is_canon: Boolean
- If True, then this function will not try to get the
- system in canonical form. Default value is False
- Returns
- =======
- match = {
- 'no_of_equation': len(eqs),
- 'eq': eqs,
- 'func': funcs,
- 'order': order,
- 'is_linear': is_linear,
- 'is_constant': is_constant,
- 'is_homogeneous': is_homogeneous,
- }
- Dict or list of Dicts or None
- Dict with values for keys:
- 1. no_of_equation: Number of equations
- 2. eq: The set of equations
- 3. func: List of dependent variables
- 4. order: A dictionary that gives the order of the
- dependent variable in eqs
- 5. is_linear: Boolean value indicating if the set of
- equations are linear or not.
- 6. is_constant: Boolean value indicating if the set of
- equations have constant coefficients or not.
- 7. is_homogeneous: Boolean value indicating if the set of
- equations are homogeneous or not.
- 8. commutative_antiderivative: Antiderivative of the coefficient
- matrix if the coefficient matrix is non-constant
- and commutative with its antiderivative. This key
- may or may not exist.
- 9. is_general: Boolean value indicating if the system of ODEs is
- solvable using one of the general case solvers or not.
- 10. rhs: rhs of the non-homogeneous system of ODEs in Matrix form. This
- key may or may not exist.
- 11. is_higher_order: True if the system passed has an order greater than 1.
- This key may or may not exist.
- 12. is_second_order: True if the system passed is a second order ODE. This
- key may or may not exist.
- This Dict is the answer returned if the eqs are linear and constant
- coefficient. Otherwise, None is returned.
- """
- # Error for i == 0 can be added but isn't for now
- # Check for len(funcs) == len(eqs)
- if len(funcs) != len(eqs):
- raise ValueError("Number of functions given is not equal to the number of equations %s" % funcs)
- # ValueError when functions have more than one arguments
- for func in funcs:
- if len(func.args) != 1:
- raise ValueError("dsolve() and classify_sysode() work with "
- "functions of one variable only, not %s" % func)
- # Getting the func_dict and order using the helper
- # function
- order = _get_func_order(eqs, funcs)
- system_order = max(order[func] for func in funcs)
- is_higher_order = system_order > 1
- is_second_order = system_order == 2 and all(order[func] == 2 for func in funcs)
- # Not adding the check if the len(func.args) for
- # every func in funcs is 1
- # Linearity check
- try:
- canon_eqs = canonical_odes(eqs, funcs, t) if not is_canon else [eqs]
- if len(canon_eqs) == 1:
- As, b = linear_ode_to_matrix(canon_eqs[0], funcs, t, system_order)
- else:
- match = {
- 'is_implicit': True,
- 'canon_eqs': canon_eqs
- }
- return match
- # When the system of ODEs is non-linear, an ODENonlinearError is raised.
- # This function catches the error and None is returned.
- except ODENonlinearError:
- return None
- is_linear = True
- # Homogeneous check
- is_homogeneous = True if b.is_zero_matrix else False
- # Is general key is used to identify if the system of ODEs can be solved by
- # one of the general case solvers or not.
- match = {
- 'no_of_equation': len(eqs),
- 'eq': eqs,
- 'func': funcs,
- 'order': order,
- 'is_linear': is_linear,
- 'is_homogeneous': is_homogeneous,
- 'is_general': True
- }
- if not is_homogeneous:
- match['rhs'] = b
- is_constant = all(_matrix_is_constant(A_, t) for A_ in As)
- # The match['is_linear'] check will be added in the future when this
- # function becomes ready to deal with non-linear systems of ODEs
- if not is_higher_order:
- A = As[1]
- match['func_coeff'] = A
- # Constant coefficient check
- is_constant = _matrix_is_constant(A, t)
- match['is_constant'] = is_constant
- try:
- system_info = linodesolve_type(A, t, b=b)
- except NotImplementedError:
- return None
- match.update(system_info)
- antiderivative = match.pop("antiderivative")
- if not is_constant:
- match['commutative_antiderivative'] = antiderivative
- return match
- else:
- match['type_of_equation'] = "type0"
- if is_second_order:
- A1, A0 = As[1:]
- match_second_order = _match_second_order_type(A1, A0, t)
- match.update(match_second_order)
- match['is_second_order'] = True
- # If system is constant, then no need to check if its in euler
- # form or not. It will be easier and faster to directly proceed
- # to solve it.
- if match['type_of_equation'] == "type0" and not is_constant:
- is_euler = _is_euler_system(As, t)
- if is_euler:
- t_ = Symbol('{}_'.format(t))
- match.update({'is_transformed': True, 'type_of_equation': 'type1',
- 't_': t_})
- else:
- is_jordan = lambda M: M == Matrix.jordan_block(M.shape[0], M[0, 0])
- terms = _factor_matrix(As[-1], t)
- if all(A.is_zero_matrix for A in As[1:-1]) and terms is not None and not is_jordan(terms[1]):
- P, J = terms[1].jordan_form()
- match.update({'type_of_equation': 'type2', 'J': J,
- 'f(t)': terms[0], 'P': P, 'is_transformed': True})
- if match['type_of_equation'] != 'type0' and is_second_order:
- match.pop('is_second_order', None)
- match['is_higher_order'] = is_higher_order
- return match
- def _preprocess_eqs(eqs):
- processed_eqs = []
- for eq in eqs:
- processed_eqs.append(eq if isinstance(eq, Equality) else Eq(eq, 0))
- return processed_eqs
- def _eqs2dict(eqs, funcs):
- eqsorig = {}
- eqsmap = {}
- funcset = set(funcs)
- for eq in eqs:
- f1, = eq.lhs.atoms(AppliedUndef)
- f2s = (eq.rhs.atoms(AppliedUndef) - {f1}) & funcset
- eqsmap[f1] = f2s
- eqsorig[f1] = eq
- return eqsmap, eqsorig
- def _dict2graph(d):
- nodes = list(d)
- edges = [(f1, f2) for f1, f2s in d.items() for f2 in f2s]
- G = (nodes, edges)
- return G
- def _is_type1(scc, t):
- eqs, funcs = scc
- try:
- (A1, A0), b = linear_ode_to_matrix(eqs, funcs, t, 1)
- except (ODENonlinearError, ODEOrderError):
- return False
- if _matrix_is_constant(A0, t) and b.is_zero_matrix:
- return True
- return False
- def _combine_type1_subsystems(subsystem, funcs, t):
- indices = [i for i, sys in enumerate(zip(subsystem, funcs)) if _is_type1(sys, t)]
- remove = set()
- for ip, i in enumerate(indices):
- for j in indices[ip+1:]:
- if any(eq2.has(funcs[i]) for eq2 in subsystem[j]):
- subsystem[j] = subsystem[i] + subsystem[j]
- remove.add(i)
- subsystem = [sys for i, sys in enumerate(subsystem) if i not in remove]
- return subsystem
- def _component_division(eqs, funcs, t):
- # Assuming that each eq in eqs is in canonical form,
- # that is, [f(x).diff(x) = .., g(x).diff(x) = .., etc]
- # and that the system passed is in its first order
- eqsmap, eqsorig = _eqs2dict(eqs, funcs)
- subsystems = []
- for cc in connected_components(_dict2graph(eqsmap)):
- eqsmap_c = {f: eqsmap[f] for f in cc}
- sccs = strongly_connected_components(_dict2graph(eqsmap_c))
- subsystem = [[eqsorig[f] for f in scc] for scc in sccs]
- subsystem = _combine_type1_subsystems(subsystem, sccs, t)
- subsystems.append(subsystem)
- return subsystems
- # Returns: List of equations
- def _linear_ode_solver(match):
- t = match['t']
- funcs = match['func']
- rhs = match.get('rhs', None)
- tau = match.get('tau', None)
- t = match['t_'] if 't_' in match else t
- A = match['func_coeff']
- # Note: To make B None when the matrix has constant
- # coefficient
- B = match.get('commutative_antiderivative', None)
- type = match['type_of_equation']
- sol_vector = linodesolve(A, t, b=rhs, B=B,
- type=type, tau=tau)
- sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)]
- return sol
- def _select_equations(eqs, funcs, key=lambda x: x):
- eq_dict = {e.lhs: e.rhs for e in eqs}
- return [Eq(f, eq_dict[key(f)]) for f in funcs]
- def _higher_order_ode_solver(match):
- eqs = match["eq"]
- funcs = match["func"]
- t = match["t"]
- sysorder = match['order']
- type = match.get('type_of_equation', "type0")
- is_second_order = match.get('is_second_order', False)
- is_transformed = match.get('is_transformed', False)
- is_euler = is_transformed and type == "type1"
- is_higher_order_type2 = is_transformed and type == "type2" and 'P' in match
- if is_second_order:
- new_eqs, new_funcs = _second_order_to_first_order(eqs, funcs, t,
- A1=match.get("A1", None), A0=match.get("A0", None),
- b=match.get("rhs", None), type=type,
- t_=match.get("t_", None))
- else:
- new_eqs, new_funcs = _higher_order_to_first_order(eqs, sysorder, t, funcs=funcs,
- type=type, J=match.get('J', None),
- f_t=match.get('f(t)', None),
- P=match.get('P', None), b=match.get('rhs', None))
- if is_transformed:
- t = match.get('t_', t)
- if not is_higher_order_type2:
- new_eqs = _select_equations(new_eqs, [f.diff(t) for f in new_funcs])
- sol = None
- # NotImplementedError may be raised when the system may be actually
- # solvable if it can be just divided into sub-systems
- try:
- if not is_higher_order_type2:
- sol = _strong_component_solver(new_eqs, new_funcs, t)
- except NotImplementedError:
- sol = None
- # Dividing the system only when it becomes essential
- if sol is None:
- try:
- sol = _component_solver(new_eqs, new_funcs, t)
- except NotImplementedError:
- sol = None
- if sol is None:
- return sol
- is_second_order_type2 = is_second_order and type == "type2"
- underscores = '__' if is_transformed else '_'
- sol = _select_equations(sol, funcs,
- key=lambda x: Function(Dummy('{}{}0'.format(x.func.__name__, underscores)))(t))
- if match.get("is_transformed", False):
- if is_second_order_type2:
- g_t = match["g(t)"]
- tau = match["tau"]
- sol = [Eq(s.lhs, s.rhs.subs(t, tau) * g_t) for s in sol]
- elif is_euler:
- t = match['t']
- tau = match['t_']
- sol = [s.subs(tau, log(t)) for s in sol]
- elif is_higher_order_type2:
- P = match['P']
- sol_vector = P * Matrix([s.rhs for s in sol])
- sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)]
- return sol
- # Returns: List of equations or None
- # If None is returned by this solver, then the system
- # of ODEs cannot be solved directly by dsolve_system.
- def _strong_component_solver(eqs, funcs, t):
- from sympy.solvers.ode.ode import dsolve, constant_renumber
- match = _classify_linear_system(eqs, funcs, t, is_canon=True)
- sol = None
- # Assuming that we can't get an implicit system
- # since we are already canonical equations from
- # dsolve_system
- if match:
- match['t'] = t
- if match.get('is_higher_order', False):
- sol = _higher_order_ode_solver(match)
- elif match.get('is_linear', False):
- sol = _linear_ode_solver(match)
- # Note: For now, only linear systems are handled by this function
- # hence, the match condition is added. This can be removed later.
- if sol is None and len(eqs) == 1:
- sol = dsolve(eqs[0], func=funcs[0])
- variables = Tuple(eqs[0]).free_symbols
- new_constants = [Dummy() for _ in range(ode_order(eqs[0], funcs[0]))]
- sol = constant_renumber(sol, variables=variables, newconstants=new_constants)
- sol = [sol]
- # To add non-linear case here in future
- return sol
- def _get_funcs_from_canon(eqs):
- return [eq.lhs.args[0] for eq in eqs]
- # Returns: List of Equations(a solution)
- def _weak_component_solver(wcc, t):
- # We will divide the systems into sccs
- # only when the wcc cannot be solved as
- # a whole
- eqs = []
- for scc in wcc:
- eqs += scc
- funcs = _get_funcs_from_canon(eqs)
- sol = _strong_component_solver(eqs, funcs, t)
- if sol:
- return sol
- sol = []
- for j, scc in enumerate(wcc):
- eqs = scc
- funcs = _get_funcs_from_canon(eqs)
- # Substituting solutions for the dependent
- # variables solved in previous SCC, if any solved.
- comp_eqs = [eq.subs({s.lhs: s.rhs for s in sol}) for eq in eqs]
- scc_sol = _strong_component_solver(comp_eqs, funcs, t)
- if scc_sol is None:
- raise NotImplementedError(filldedent('''
- The system of ODEs passed cannot be solved by dsolve_system.
- '''))
- # scc_sol: List of equations
- # scc_sol is a solution
- sol += scc_sol
- return sol
- # Returns: List of Equations(a solution)
- def _component_solver(eqs, funcs, t):
- components = _component_division(eqs, funcs, t)
- sol = []
- for wcc in components:
- # wcc_sol: List of Equations
- sol += _weak_component_solver(wcc, t)
- # sol: List of Equations
- return sol
- def _second_order_to_first_order(eqs, funcs, t, type="auto", A1=None,
- A0=None, b=None, t_=None):
- r"""
- Expects the system to be in second order and in canonical form
- Explanation
- ===========
- Reduces a second order system into a first order one depending on the type of second
- order system.
- 1. "type0": If this is passed, then the system will be reduced to first order by
- introducing dummy variables.
- 2. "type1": If this is passed, then a particular substitution will be used to reduce the
- the system into first order.
- 3. "type2": If this is passed, then the system will be transformed with new dependent
- variables and independent variables. This transformation is a part of solving
- the corresponding system of ODEs.
- `A1` and `A0` are the coefficient matrices from the system and it is assumed that the
- second order system has the form given below:
- .. math::
- A2 * X'' = A1 * X' + A0 * X + b
- Here, $A2$ is the coefficient matrix for the vector $X''$ and $b$ is the non-homogeneous
- term.
- Default value for `b` is None but if `A1` and `A0` are passed and `b` isn't passed, then the
- system will be assumed homogeneous.
- """
- is_a1 = A1 is None
- is_a0 = A0 is None
- if (type == "type1" and is_a1) or (type == "type2" and is_a0)\
- or (type == "auto" and (is_a1 or is_a0)):
- (A2, A1, A0), b = linear_ode_to_matrix(eqs, funcs, t, 2)
- if not A2.is_Identity:
- raise ValueError(filldedent('''
- The system must be in its canonical form.
- '''))
- if type == "auto":
- match = _match_second_order_type(A1, A0, t)
- type = match["type_of_equation"]
- A1 = match.get("A1", None)
- A0 = match.get("A0", None)
- sys_order = {func: 2 for func in funcs}
- if type == "type1":
- if b is None:
- b = zeros(len(eqs))
- eqs = _second_order_subs_type1(A1, b, funcs, t)
- sys_order = {func: 1 for func in funcs}
- if type == "type2":
- if t_ is None:
- t_ = Symbol("{}_".format(t))
- t = t_
- eqs, funcs = _second_order_subs_type2(A0, funcs, t_)
- sys_order = {func: 2 for func in funcs}
- return _higher_order_to_first_order(eqs, sys_order, t, funcs=funcs)
- def _higher_order_type2_to_sub_systems(J, f_t, funcs, t, max_order, b=None, P=None):
- # Note: To add a test for this ValueError
- if J is None or f_t is None or not _matrix_is_constant(J, t):
- raise ValueError(filldedent('''
- Correctly input for args 'A' and 'f_t' for Linear, Higher Order,
- Type 2
- '''))
- if P is None and b is not None and not b.is_zero_matrix:
- raise ValueError(filldedent('''
- Provide the keyword 'P' for matrix P in A = P * J * P-1.
- '''))
- new_funcs = Matrix([Function(Dummy('{}__0'.format(f.func.__name__)))(t) for f in funcs])
- new_eqs = new_funcs.diff(t, max_order) - f_t * J * new_funcs
- if b is not None and not b.is_zero_matrix:
- new_eqs -= P.inv() * b
- new_eqs = canonical_odes(new_eqs, new_funcs, t)[0]
- return new_eqs, new_funcs
- def _higher_order_to_first_order(eqs, sys_order, t, funcs=None, type="type0", **kwargs):
- if funcs is None:
- funcs = sys_order.keys()
- # Standard Cauchy Euler system
- if type == "type1":
- t_ = Symbol('{}_'.format(t))
- new_funcs = [Function(Dummy('{}_'.format(f.func.__name__)))(t_) for f in funcs]
- max_order = max(sys_order[func] for func in funcs)
- subs_dict = {func: new_func for func, new_func in zip(funcs, new_funcs)}
- subs_dict[t] = exp(t_)
- free_function = Function(Dummy())
- def _get_coeffs_from_subs_expression(expr):
- if isinstance(expr, Subs):
- free_symbol = expr.args[1][0]
- term = expr.args[0]
- return {ode_order(term, free_symbol): 1}
- if isinstance(expr, Mul):
- coeff = expr.args[0]
- order = list(_get_coeffs_from_subs_expression(expr.args[1]).keys())[0]
- return {order: coeff}
- if isinstance(expr, Add):
- coeffs = {}
- for arg in expr.args:
- if isinstance(arg, Mul):
- coeffs.update(_get_coeffs_from_subs_expression(arg))
- else:
- order = list(_get_coeffs_from_subs_expression(arg).keys())[0]
- coeffs[order] = 1
- return coeffs
- for o in range(1, max_order + 1):
- expr = free_function(log(t_)).diff(t_, o)*t_**o
- coeff_dict = _get_coeffs_from_subs_expression(expr)
- coeffs = [coeff_dict[order] if order in coeff_dict else 0 for order in range(o + 1)]
- expr_to_subs = sum(free_function(t_).diff(t_, i) * c for i, c in
- enumerate(coeffs)) / t**o
- subs_dict.update({f.diff(t, o): expr_to_subs.subs(free_function(t_), nf)
- for f, nf in zip(funcs, new_funcs)})
- new_eqs = [eq.subs(subs_dict) for eq in eqs]
- new_sys_order = {nf: sys_order[f] for f, nf in zip(funcs, new_funcs)}
- new_eqs = canonical_odes(new_eqs, new_funcs, t_)[0]
- return _higher_order_to_first_order(new_eqs, new_sys_order, t_, funcs=new_funcs)
- # Systems of the form: X(n)(t) = f(t)*A*X + b
- # where X(n)(t) is the nth derivative of the vector of dependent variables
- # with respect to the independent variable and A is a constant matrix.
- if type == "type2":
- J = kwargs.get('J', None)
- f_t = kwargs.get('f_t', None)
- b = kwargs.get('b', None)
- P = kwargs.get('P', None)
- max_order = max(sys_order[func] for func in funcs)
- return _higher_order_type2_to_sub_systems(J, f_t, funcs, t, max_order, P=P, b=b)
- # Note: To be changed to this after doit option is disabled for default cases
- # new_sysorder = _get_func_order(new_eqs, new_funcs)
- #
- # return _higher_order_to_first_order(new_eqs, new_sysorder, t, funcs=new_funcs)
- new_funcs = []
- for prev_func in funcs:
- func_name = prev_func.func.__name__
- func = Function(Dummy('{}_0'.format(func_name)))(t)
- new_funcs.append(func)
- subs_dict = {prev_func: func}
- new_eqs = []
- for i in range(1, sys_order[prev_func]):
- new_func = Function(Dummy('{}_{}'.format(func_name, i)))(t)
- subs_dict[prev_func.diff(t, i)] = new_func
- new_funcs.append(new_func)
- prev_f = subs_dict[prev_func.diff(t, i-1)]
- new_eq = Eq(prev_f.diff(t), new_func)
- new_eqs.append(new_eq)
- eqs = [eq.subs(subs_dict) for eq in eqs] + new_eqs
- return eqs, new_funcs
- def dsolve_system(eqs, funcs=None, t=None, ics=None, doit=False, simplify=True):
- r"""
- Solves any(supported) system of Ordinary Differential Equations
- Explanation
- ===========
- This function takes a system of ODEs as an input, determines if the
- it is solvable by this function, and returns the solution if found any.
- This function can handle:
- 1. Linear, First Order, Constant coefficient homogeneous system of ODEs
- 2. Linear, First Order, Constant coefficient non-homogeneous system of ODEs
- 3. Linear, First Order, non-constant coefficient homogeneous system of ODEs
- 4. Linear, First Order, non-constant coefficient non-homogeneous system of ODEs
- 5. Any implicit system which can be divided into system of ODEs which is of the above 4 forms
- 6. Any higher order linear system of ODEs that can be reduced to one of the 5 forms of systems described above.
- The types of systems described above aren't limited by the number of equations, i.e. this
- function can solve the above types irrespective of the number of equations in the system passed.
- But, the bigger the system, the more time it will take to solve the system.
- This function returns a list of solutions. Each solution is a list of equations where LHS is
- the dependent variable and RHS is an expression in terms of the independent variable.
- Among the non constant coefficient types, not all the systems are solvable by this function. Only
- those which have either a coefficient matrix with a commutative antiderivative or those systems which
- may be divided further so that the divided systems may have coefficient matrix with commutative antiderivative.
- Parameters
- ==========
- eqs : List
- system of ODEs to be solved
- funcs : List or None
- List of dependent variables that make up the system of ODEs
- t : Symbol or None
- Independent variable in the system of ODEs
- ics : Dict or None
- Set of initial boundary/conditions for the system of ODEs
- doit : Boolean
- Evaluate the solutions if True. Default value is True. Can be
- set to false if the integral evaluation takes too much time and/or
- isn't required.
- simplify: Boolean
- Simplify the solutions for the systems. Default value is True.
- Can be set to false if simplification takes too much time and/or
- isn't required.
- Examples
- ========
- >>> from sympy import symbols, Eq, Function
- >>> from sympy.solvers.ode.systems import dsolve_system
- >>> f, g = symbols("f g", cls=Function)
- >>> x = symbols("x")
- >>> eqs = [Eq(f(x).diff(x), g(x)), Eq(g(x).diff(x), f(x))]
- >>> dsolve_system(eqs)
- [[Eq(f(x), -C1*exp(-x) + C2*exp(x)), Eq(g(x), C1*exp(-x) + C2*exp(x))]]
- You can also pass the initial conditions for the system of ODEs:
- >>> dsolve_system(eqs, ics={f(0): 1, g(0): 0})
- [[Eq(f(x), exp(x)/2 + exp(-x)/2), Eq(g(x), exp(x)/2 - exp(-x)/2)]]
- Optionally, you can pass the dependent variables and the independent
- variable for which the system is to be solved:
- >>> funcs = [f(x), g(x)]
- >>> dsolve_system(eqs, funcs=funcs, t=x)
- [[Eq(f(x), -C1*exp(-x) + C2*exp(x)), Eq(g(x), C1*exp(-x) + C2*exp(x))]]
- Lets look at an implicit system of ODEs:
- >>> eqs = [Eq(f(x).diff(x)**2, g(x)**2), Eq(g(x).diff(x), g(x))]
- >>> dsolve_system(eqs)
- [[Eq(f(x), C1 - C2*exp(x)), Eq(g(x), C2*exp(x))], [Eq(f(x), C1 + C2*exp(x)), Eq(g(x), C2*exp(x))]]
- Returns
- =======
- List of List of Equations
- Raises
- ======
- NotImplementedError
- When the system of ODEs is not solvable by this function.
- ValueError
- When the parameters passed aren't in the required form.
- """
- from sympy.solvers.ode.ode import solve_ics, _extract_funcs, constant_renumber
- if not iterable(eqs):
- raise ValueError(filldedent('''
- List of equations should be passed. The input is not valid.
- '''))
- eqs = _preprocess_eqs(eqs)
- if funcs is not None and not isinstance(funcs, list):
- raise ValueError(filldedent('''
- Input to the funcs should be a list of functions.
- '''))
- if funcs is None:
- funcs = _extract_funcs(eqs)
- if any(len(func.args) != 1 for func in funcs):
- raise ValueError(filldedent('''
- dsolve_system can solve a system of ODEs with only one independent
- variable.
- '''))
- if len(eqs) != len(funcs):
- raise ValueError(filldedent('''
- Number of equations and number of functions do not match
- '''))
- if t is not None and not isinstance(t, Symbol):
- raise ValueError(filldedent('''
- The indepedent variable must be of type Symbol
- '''))
- if t is None:
- t = list(list(eqs[0].atoms(Derivative))[0].atoms(Symbol))[0]
- sols = []
- canon_eqs = canonical_odes(eqs, funcs, t)
- for canon_eq in canon_eqs:
- try:
- sol = _strong_component_solver(canon_eq, funcs, t)
- except NotImplementedError:
- sol = None
- if sol is None:
- sol = _component_solver(canon_eq, funcs, t)
- sols.append(sol)
- if sols:
- final_sols = []
- variables = Tuple(*eqs).free_symbols
- for sol in sols:
- sol = _select_equations(sol, funcs)
- sol = constant_renumber(sol, variables=variables)
- if ics:
- constants = Tuple(*sol).free_symbols - variables
- solved_constants = solve_ics(sol, funcs, constants, ics)
- sol = [s.subs(solved_constants) for s in sol]
- if simplify:
- constants = Tuple(*sol).free_symbols - variables
- sol = simpsol(sol, [t], constants, doit=doit)
- final_sols.append(sol)
- sols = final_sols
- return sols
|