def dsolve_system(eqs, funcs=None, t=None, ics=None, doit=False): 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 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. 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. 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 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 False 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 don't 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] sys_order = _get_func_order(eqs, funcs) # To add higher order to first order reduction later if not all(sys_order[func] == 1 for func in funcs): raise NotImplementedError( filldedent(''' Higher order ODEs aren't solvable by dsolve_system ''')) canon_eqs = canonical_odes(eqs, funcs, t) sols = [] for canon_eq in canon_eqs: sol = _strong_component_solver(canon_eq, funcs, t) if sol is None: sol = _component_solver(canon_eq, funcs, t) sols.append(sol) if sols: final_sols = [] for sol in sols: # To preserve the order corresponding to the # funcs list. sol_dict = {s.lhs: s.rhs for s in sol} sol = [Eq(var, sol_dict[var]) for var in funcs] variables = Tuple(*eqs).free_symbols 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 doit: sol = [s.doit() for s in sol] final_sols.append(sol) sols = final_sols return sols
def test_solve_ics(): # Basic tests that things work from dsolve. assert dsolve(f(x).diff(x) - 1/f(x), f(x), ics={f(1): 2}) == \ Eq(f(x), sqrt(2 * x + 2)) assert dsolve(f(x).diff(x) - f(x), f(x), ics={f(0): 1}) == Eq(f(x), exp(x)) assert dsolve(f(x).diff(x) - f(x), f(x), ics={f(x).diff(x).subs(x, 0): 1}) == Eq(f(x), exp(x)) assert dsolve(f(x).diff(x, x) + f(x), f(x), ics={ f(0): 1, f(x).diff(x).subs(x, 0): 1 }) == Eq(f(x), sin(x) + cos(x)) assert dsolve([f(x).diff(x) - f(x) + g(x), g(x).diff(x) - g(x) - f(x)], [f(x), g(x)], ics={ f(0): 1, g(0): 0 }) == [Eq(f(x), exp(x) * cos(x)), Eq(g(x), exp(x) * sin(x))] # Test cases where dsolve returns two solutions. eq = (x**2 * f(x)**2 - x).diff(x) assert dsolve(eq, f(x), ics={f(1): 0}) == [ Eq(f(x), -sqrt(x - 1) / x), Eq(f(x), sqrt(x - 1) / x) ] assert dsolve(eq, f(x), ics={f(x).diff(x).subs(x, 1): 0}) == [ Eq(f(x), -sqrt(x - S.Half) / x), Eq(f(x), sqrt(x - S.Half) / x) ] eq = cos(f(x)) - (x * sin(f(x)) - f(x)**2) * f(x).diff(x) assert dsolve(eq, f(x), ics={f(0): 1}, hint='1st_exact', simplify=False) == Eq(x * cos(f(x)) + f(x)**3 / 3, Rational(1, 3)) assert dsolve(eq, f(x), ics={f(0): 1}, hint='1st_exact', simplify=True) == Eq(x * cos(f(x)) + f(x)**3 / 3, Rational(1, 3)) assert solve_ics([Eq(f(x), C1 * exp(x))], [f(x)], [C1], {f(0): 1}) == { C1: 1 } assert solve_ics([Eq(f(x), C1 * sin(x) + C2 * cos(x))], [f(x)], [C1, C2], { f(0): 1, f(pi / 2): 1 }) == { C1: 1, C2: 1 } assert solve_ics([Eq(f(x), C1 * sin(x) + C2 * cos(x))], [f(x)], [C1, C2], { f(0): 1, f(x).diff(x).subs(x, 0): 1 }) == { C1: 1, C2: 1 } assert solve_ics([Eq(f(x), C1*sin(x) + C2*cos(x))], [f(x)], [C1, C2], {f(0): 1}) == \ {C2: 1} # Some more complicated tests Refer to PR #16098 assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(0):0, f(x).diff(x).subs(x, 1):0})) == \ {Eq(f(x), 0), Eq(f(x), x ** 3 / 6 - x / 2)} assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(0):0})) == \ {Eq(f(x), 0), Eq(f(x), C2*x + x**3/6)} K, r, f0 = symbols('K r f0') sol = Eq( f(x), K * f0 * exp(r * x) / ((-K + f0) * (f0 * exp(r * x) / (-K + f0) - 1))) assert (dsolve(Eq(f(x).diff(x), r * f(x) * (1 - f(x) / K)), f(x), ics={f(0): f0})) == sol #Order dependent issues Refer to PR #16098 assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(x).diff(x).subs(x,0):0, f(0):0})) == \ {Eq(f(x), 0), Eq(f(x), x ** 3 / 6)} assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(0):0, f(x).diff(x).subs(x,0):0})) == \ {Eq(f(x), 0), Eq(f(x), x ** 3 / 6)} # XXX: Ought to be ValueError raises( ValueError, lambda: solve_ics([Eq(f(x), C1 * sin(x) + C2 * cos(x))], [f(x)], [C1, C2], { f(0): 1, f(pi): 1 })) # Degenerate case. f'(0) is identically 0. raises( ValueError, lambda: solve_ics([Eq(f(x), sqrt(C1 - x**2))], [f(x)], [C1], {f(x).diff(x).subs(x, 0): 0})) EI, q, L = symbols('EI q L') # eq = Eq(EI*diff(f(x), x, 4), q) sols = [ Eq(f(x), C1 + C2 * x + C3 * x**2 + C4 * x**3 + q * x**4 / (24 * EI)) ] funcs = [f(x)] constants = [C1, C2, C3, C4] # Test both cases, Derivative (the default from f(x).diff(x).subs(x, L)), # and Subs ics1 = { f(0): 0, f(x).diff(x).subs(x, 0): 0, f(L).diff(L, 2): 0, f(L).diff(L, 3): 0 } ics2 = { f(0): 0, f(x).diff(x).subs(x, 0): 0, Subs(f(x).diff(x, 2), x, L): 0, Subs(f(x).diff(x, 3), x, L): 0 } solved_constants1 = solve_ics(sols, funcs, constants, ics1) solved_constants2 = solve_ics(sols, funcs, constants, ics2) assert solved_constants1 == solved_constants2 == { C1: 0, C2: 0, C3: L**2 * q / (4 * EI), C4: -L * q / (6 * EI) }
def dsolve_system(eqs, funcs=None, t=None, ics=None, doit=False): 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 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. 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. 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 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 False 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 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. ''')) # Note: This is added to solve a major problem encountered. # Might be best to make a function for this functions # extraction later. if funcs is None: funcs = _extract_funcs(eqs) if len(eqs) != len(funcs): raise ValueError(filldedent(''' Number of equations and number of functions don't 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] match = neq_nth_linear_constant_coeff_match(eqs, funcs, t) sols = [] if match is None or match.get('is_implicit', False): canon_eqs = [eqs] if match is None else match['canon_eqs'] # Note: Assuming a canon_eq has a single # solution. for canon_eq in canon_eqs: sols.append(_component_solver(canon_eq, funcs, t)) # Note: It is advantageous to # divide a system of ODEs since smaller # the matrices, faster the solution # computation. Has to be considered in # future PR when component division function # is added. elif match.get('is_general', False): if match.get('is_linear', False): match['t'] = t sols.append(_linear_ode_solver(match)) if sols: final_sols = [] # This is assuming that all the solutions # have the same funcs. This may have to # be changed when system division is # added. funcs = [s.lhs for s in sols[0]] for sol in sols: sol = _replace_dummies(eqs, sol) if ics: constants = Tuple(*sol).free_symbols - Tuple(*eqs).free_symbols solved_constants = solve_ics(sol, funcs, constants, ics) sol = [s.subs(solved_constants) for s in sol] if doit: sol = [s.doit() for s in sol] final_sols.append(sol) sols = final_sols return sols