r"""
This module contains :py:meth:`~sympy.solvers.ode.dsolve` and different helper
functions that it uses.
:py:meth:`~sympy.solvers.ode.dsolve` solves ordinary differential equations.
See the docstring on the various functions for their uses. Note that partial
differential equations support is in ``pde.py``. Note that hint functions
have docstrings describing their various methods, but they are intended for
internal use. Use ``dsolve(ode, func, hint=hint)`` to solve an ODE using a
specific hint. See also the docstring on
:py:meth:`~sympy.solvers.ode.dsolve`.
**Functions in this module**
These are the user functions in this module:
- :py:meth:`~sympy.solvers.ode.dsolve` - Solves ODEs.
- :py:meth:`~sympy.solvers.ode.classify_ode` - Classifies ODEs into
possible hints for :py:meth:`~sympy.solvers.ode.dsolve`.
- :py:meth:`~sympy.solvers.ode.checkodesol` - Checks if an equation is the
solution to an ODE.
- :py:meth:`~sympy.solvers.ode.homogeneous_order` - Returns the
homogeneous order of an expression.
- :py:meth:`~sympy.solvers.ode.infinitesimals` - Returns the infinitesimals
of the Lie group of point transformations of an ODE, such that it is
invariant.
- :py:meth:`~sympy.solvers.ode_checkinfsol` - Checks if the given infinitesimals
are the actual infinitesimals of a first order ODE.
These are the non-solver helper functions that are for internal use. The
user should use the various options to
:py:meth:`~sympy.solvers.ode.dsolve` to obtain the functionality provided
by these functions:
- :py:meth:`~sympy.solvers.ode.odesimp` - Does all forms of ODE
simplification.
- :py:meth:`~sympy.solvers.ode.ode_sol_simplicity` - A key function for
comparing solutions by simplicity.
- :py:meth:`~sympy.solvers.ode.constantsimp` - Simplifies arbitrary
constants.
- :py:meth:`~sympy.solvers.ode.constant_renumber` - Renumber arbitrary
constants.
- :py:meth:`~sympy.solvers.ode._handle_Integral` - Evaluate unevaluated
Integrals.
See also the docstrings of these functions.
**Currently implemented solver methods**
The following methods are implemented for solving ordinary differential
equations. See the docstrings of the various hint functions for more
information on each (run ``help(ode)``):
- 1st order separable differential equations.
- 1st order differential equations whose coefficients or `dx` and `dy` are
functions homogeneous of the same order.
- 1st order exact differential equations.
- 1st order linear differential equations.
- 1st order Bernoulli differential equations.
- Power series solutions for first order differential equations.
- Lie Group method of solving first order differential equations.
- 2nd order Liouville differential equations.
- Power series solutions for second order differential equations
at ordinary and regular singular points.
- `n`\th order linear homogeneous differential equation with constant
coefficients.
- `n`\th order linear inhomogeneous differential equation with constant
coefficients using the method of undetermined coefficients.
- `n`\th order linear inhomogeneous differential equation with constant
coefficients using the method of variation of parameters.
**Philosophy behind this module**
This module is designed to make it easy to add new ODE solving methods without
having to mess with the solving code for other methods. The idea is that
there is a :py:meth:`~sympy.solvers.ode.classify_ode` function, which takes in
an ODE and tells you what hints, if any, will solve the ODE. It does this
without attempting to solve the ODE, so it is fast. Each solving method is a
hint, and it has its own function, named ``ode_<hint>``. That function takes
in the ODE and any match expression gathered by
:py:meth:`~sympy.solvers.ode.classify_ode` and returns a solved result. If
this result has any integrals in it, the hint function will return an
unevaluated :py:class:`~sympy.integrals.Integral` class.
:py:meth:`~sympy.solvers.ode.dsolve`, which is the user wrapper function
around all of this, will then call :py:meth:`~sympy.solvers.ode.odesimp` on
the result, which, among other things, will attempt to solve the equation for
the dependent variable (the function we are solving for), simplify the
arbitrary constants in the expression, and evaluate any integrals, if the hint
allows it.
**How to add new solution methods**
If you have an ODE that you want :py:meth:`~sympy.solvers.ode.dsolve` to be
able to solve, try to avoid adding special case code here. Instead, try
finding a general method that will solve your ODE, as well as others. This
way, the :py:mod:`~sympy.solvers.ode` module will become more robust, and
unhindered by special case hacks. WolphramAlpha and Maple's
DETools[odeadvisor] function are two resources you can use to classify a
specific ODE. It is also better for a method to work with an `n`\th order ODE
instead of only with specific orders, if possible.
To add a new method, there are a few things that you need to do. First, you
need a hint name for your method. Try to name your hint so that it is
unambiguous with all other methods, including ones that may not be implemented
yet. If your method uses integrals, also include a ``hint_Integral`` hint.
If there is more than one way to solve ODEs with your method, include a hint
for each one, as well as a ``<hint>_best`` hint. Your ``ode_<hint>_best()``
function should choose the best using min with ``ode_sol_simplicity`` as the
key argument. See
:py:meth:`~sympy.solvers.ode.ode_1st_homogeneous_coeff_best`, for example.
The function that uses your method will be called ``ode_<hint>()``, so the
hint must only use characters that are allowed in a Python function name
(alphanumeric characters and the underscore '``_``' character). Include a
function for every hint, except for ``_Integral`` hints
(:py:meth:`~sympy.solvers.ode.dsolve` takes care of those automatically).
Hint names should be all lowercase, unless a word is commonly capitalized
(such as Integral or Bernoulli). If you have a hint that you do not want to
run with ``all_Integral`` that doesn't have an ``_Integral`` counterpart (such
as a best hint that would defeat the purpose of ``all_Integral``), you will
need to remove it manually in the :py:meth:`~sympy.solvers.ode.dsolve` code.
See also the :py:meth:`~sympy.solvers.ode.classify_ode` docstring for
guidelines on writing a hint name.
Determine *in general* how the solutions returned by your method compare with
other methods that can potentially solve the same ODEs. Then, put your hints
in the :py:data:`~sympy.solvers.ode.allhints` tuple in the order that they
should be called. The ordering of this tuple determines which hints are
default. Note that exceptions are ok, because it is easy for the user to
choose individual hints with :py:meth:`~sympy.solvers.ode.dsolve`. In
general, ``_Integral`` variants should go at the end of the list, and
``_best`` variants should go before the various hints they apply to. For
example, the ``undetermined_coefficients`` hint comes before the
``variation_of_parameters`` hint because, even though variation of parameters
is more general than undetermined coefficients, undetermined coefficients
generally returns cleaner results for the ODEs that it can solve than
variation of parameters does, and it does not require integration, so it is
much faster.
Next, you need to have a match expression or a function that matches the type
of the ODE, which you should put in :py:meth:`~sympy.solvers.ode.classify_ode`
(if the match function is more than just a few lines, like
:py:meth:`~sympy.solvers.ode._undetermined_coefficients_match`, it should go
outside of :py:meth:`~sympy.solvers.ode.classify_ode`). It should match the
ODE without solving for it as much as possible, so that
:py:meth:`~sympy.solvers.ode.classify_ode` remains fast and is not hindered by
bugs in solving code. Be sure to consider corner cases. For example, if your
solution method involves dividing by something, make sure you exclude the case
where that division will be 0.
In most cases, the matching of the ODE will also give you the various parts
that you need to solve it. You should put that in a dictionary (``.match()``
will do this for you), and add that as ``matching_hints['hint'] = matchdict``
in the relevant part of :py:meth:`~sympy.solvers.ode.classify_ode`.
:py:meth:`~sympy.solvers.ode.classify_ode` will then send this to
:py:meth:`~sympy.solvers.ode.dsolve`, which will send it to your function as
the ``match`` argument. Your function should be named ``ode_<hint>(eq, func,
order, match)`. If you need to send more information, put it in the ``match``
dictionary. For example, if you had to substitute in a dummy variable in
:py:meth:`~sympy.solvers.ode.classify_ode` to match the ODE, you will need to
pass it to your function using the `match` dict to access it. You can access
the independent variable using ``func.args[0]``, and the dependent variable
(the function you are trying to solve for) as ``func.func``. If, while trying
to solve the ODE, you find that you cannot, raise ``NotImplementedError``.
:py:meth:`~sympy.solvers.ode.dsolve` will catch this error with the ``all``
meta-hint, rather than causing the whole routine to fail.
Add a docstring to your function that describes the method employed. Like
with anything else in SymPy, you will need to add a doctest to the docstring,
in addition to real tests in ``test_ode.py``. Try to maintain consistency
with the other hint functions' docstrings. Add your method to the list at the
top of this docstring. Also, add your method to ``ode.rst`` in the
``docs/src`` directory, so that the Sphinx docs will pull its docstring into
the main SymPy documentation. Be sure to make the Sphinx documentation by
running ``make html`` from within the doc directory to verify that the
docstring formats correctly.
If your solution method involves integrating, use :py:meth:`Integral()
<sympy.integrals.integrals.Integral>` instead of
:py:meth:`~sympy.core.expr.Expr.integrate`. This allows the user to bypass
hard/slow integration by using the ``_Integral`` variant of your hint. In
most cases, calling :py:meth:`sympy.core.basic.Basic.doit` will integrate your
solution. If this is not the case, you will need to write special code in
:py:meth:`~sympy.solvers.ode._handle_Integral`. Arbitrary constants should be
symbols named ``C1``, ``C2``, and so on. All solution methods should return
an equality instance. If you need an arbitrary number of arbitrary constants,
you can use ``constants = numbered_symbols(prefix='C', cls=Symbol, start=1)``.
If it is possible to solve for the dependent function in a general way, do so.
Otherwise, do as best as you can, but do not call solve in your
``ode_<hint>()`` function. :py:meth:`~sympy.solvers.ode.odesimp` will attempt
to solve the solution for you, so you do not need to do that. Lastly, if your
ODE has a common simplification that can be applied to your solutions, you can
add a special case in :py:meth:`~sympy.solvers.ode.odesimp` for it. For
example, solutions returned from the ``1st_homogeneous_coeff`` hints often
have many :py:meth:`~sympy.functions.log` terms, so
:py:meth:`~sympy.solvers.ode.odesimp` calls
:py:meth:`~sympy.simplify.simplify.logcombine` on them (it also helps to write
the arbitrary constant as ``log(C1)`` instead of ``C1`` in this case). Also
consider common ways that you can rearrange your solution to have
:py:meth:`~sympy.solvers.ode.constantsimp` take better advantage of it. It is
better to put simplification in :py:meth:`~sympy.solvers.ode.odesimp` than in
your method, because it can then be turned off with the simplify flag in
:py:meth:`~sympy.solvers.ode.dsolve`. If you have any extraneous
simplification in your function, be sure to only run it using ``if
match.get('simplify', True):``, especially if it can be slow or if it can
reduce the domain of the solution.
Finally, as with every contribution to SymPy, your method will need to be
tested. Add a test for each method in ``test_ode.py``. Follow the
conventions there, i.e., test the solver using ``dsolve(eq, f(x),
hint=your_hint)``, and also test the solution using
:py:meth:`~sympy.solvers.ode.checkodesol` (you can put these in a separate
tests and skip/XFAIL if it runs too slow/doesn't work). Be sure to call your
hint specifically in :py:meth:`~sympy.solvers.ode.dsolve`, that way the test
won't be broken simply by the introduction of another matching hint. If your
method works for higher order (>1) ODEs, you will need to run ``sol =
constant_renumber(sol, 'C', 1, order)`` for each solution, where ``order`` is
the order of the ODE. This is because ``constant_renumber`` renumbers the
arbitrary constants by printing order, which is platform dependent. Try to
test every corner case of your solver, including a range of orders if it is a
`n`\th order solver, but if your solver is slow, such as if it involves hard
integration, try to keep the test run time down.
Feel free to refactor existing hints to avoid duplicating code or creating
inconsistencies. If you can show that your method exactly duplicates an
existing method, including in the simplicity and speed of obtaining the
solutions, then you can remove the old, less general method. The existing
code is tested extensively in ``test_ode.py``, so if anything is broken, one
of those tests will surely fail.
"""
from __future__ import print_function, division
from collections import defaultdict
from itertools import islice
from sympy.core import Add, S, Mul, Pow, oo
from sympy.core.compatibility import ordered, iterable, is_sequence, range
from sympy.core.containers import Tuple
from sympy.core.exprtools import factor_terms
from sympy.core.expr import AtomicExpr, Expr
from sympy.core.function import (Function, Derivative, AppliedUndef, diff,
expand, expand_mul, Subs, _mexpand)
from sympy.core.multidimensional import vectorize
from sympy.core.numbers import NaN, zoo, I, Number
from sympy.core.relational import Equality, Eq
from sympy.core.symbol import Symbol, Wild, Dummy, symbols
from sympy.core.sympify import sympify
from sympy.logic.boolalg import BooleanAtom
from sympy.functions import cos, exp, im, log, re, sin, tan, sqrt, \
atan2, conjugate
from sympy.functions.combinatorial.factorials import factorial
from sympy.integrals.integrals import Integral, integrate
from sympy.matrices import wronskian, Matrix, eye, zeros
from sympy.polys import (Poly, RootOf, rootof, terms_gcd,
PolynomialError, lcm)
from sympy.polys.polyroots import roots_quartic
from sympy.polys.polytools import cancel, degree, div
from sympy.series import Order
from sympy.series.series import series
from sympy.simplify import collect, logcombine, powsimp, separatevars, \
simplify, trigsimp, denom, posify, cse
from sympy.simplify.powsimp import powdenest
from sympy.simplify.radsimp import collect_const
from sympy.solvers import solve
from sympy.solvers.pde import pdsolve
from sympy.utilities import numbered_symbols, default_sort_key, sift
from sympy.solvers.deutils import _preprocess, ode_order, _desolve
#: This is a list of hints in the order that they should be preferred by
#: :py:meth:`~sympy.solvers.ode.classify_ode`. In general, hints earlier in the
#: list should produce simpler solutions than those later in the list (for
#: ODEs that fit both). For now, the order of this list is based on empirical
#: observations by the developers of SymPy.
#:
#: The hint used by :py:meth:`~sympy.solvers.ode.dsolve` for a specific ODE
#: can be overridden (see the docstring).
#:
#: In general, ``_Integral`` hints are grouped at the end of the list, unless
#: there is a method that returns an unevaluable integral most of the time
#: (which go near the end of the list anyway). ``default``, ``all``,
#: ``best``, and ``all_Integral`` meta-hints should not be included in this
#: list, but ``_best`` and ``_Integral`` hints should be included.
allhints = (
"separable",
"1st_exact",
"1st_linear",
"Bernoulli",
"Riccati_special_minus2",
"1st_homogeneous_coeff_best",
"1st_homogeneous_coeff_subs_indep_div_dep",
"1st_homogeneous_coeff_subs_dep_div_indep",
"almost_linear",
"linear_coefficients",
"separable_reduced",
"1st_power_series",
"lie_group",
"nth_linear_constant_coeff_homogeneous",
"nth_linear_euler_eq_homogeneous",
"nth_linear_constant_coeff_undetermined_coefficients",
"nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients",
"nth_linear_constant_coeff_variation_of_parameters",
"nth_linear_euler_eq_nonhomogeneous_variation_of_parameters",
"Liouville",
"2nd_power_series_ordinary",
"2nd_power_series_regular",
"separable_Integral",
"1st_exact_Integral",
"1st_linear_Integral",
"Bernoulli_Integral",
"1st_homogeneous_coeff_subs_indep_div_dep_Integral",
"1st_homogeneous_coeff_subs_dep_div_indep_Integral",
"almost_linear_Integral",
"linear_coefficients_Integral",
"separable_reduced_Integral",
"nth_linear_constant_coeff_variation_of_parameters_Integral",
"nth_linear_euler_eq_nonhomogeneous_variation_of_parameters_Integral",
"Liouville_Integral",
)
lie_heuristics = (
"abaco1_simple",
"abaco1_product",
"abaco2_similar",
"abaco2_unique_unknown",
"abaco2_unique_general",
"linear",
"function_sum",
"bivariate",
"chi"
)
def sub_func_doit(eq, func, new):
r"""
When replacing the func with something else, we usually want the
derivative evaluated, so this function helps in making that happen.
To keep subs from having to look through all derivatives, we mask them off
with dummy variables, do the func sub, and then replace masked-off
derivatives with their doit values.
Examples
========
>>> from sympy import Derivative, symbols, Function
>>> from sympy.solvers.ode import sub_func_doit
>>> x, z = symbols('x, z')
>>> y = Function('y')
>>> sub_func_doit(3*Derivative(y(x), x) - 1, y(x), x)
2
>>> sub_func_doit(x*Derivative(y(x), x) - y(x)**2 + y(x), y(x),
... 1/(x*(z + 1/x)))
x*(-1/(x**2*(z + 1/x)) + 1/(x**3*(z + 1/x)**2)) + 1/(x*(z + 1/x))
...- 1/(x**2*(z + 1/x)**2)
"""
reps = {}
repu = {}
for d in eq.atoms(Derivative):
u = Dummy('u')
repu[u] = d.subs(func, new).doit()
reps[d] = u
return eq.subs(reps).subs(func, new).subs(repu)
def get_numbered_constants(eq, num=1, start=1, prefix='C'):
"""
Returns a list of constants that do not occur
in eq already.
"""
if isinstance(eq, Expr):
eq = [eq]
elif not iterable(eq):
raise ValueError("Expected Expr or iterable but got %s" % eq)
atom_set = set().union(*[i.free_symbols for i in eq])
ncs = numbered_symbols(start=start, prefix=prefix, exclude=atom_set)
Cs = [next(ncs) for i in range(num)]
return (Cs[0] if num == 1 else tuple(Cs))
[docs]def dsolve(eq, func=None, hint="default", simplify=True,
ics= None, xi=None, eta=None, x0=0, n=6, **kwargs):
r"""
Solves any (supported) kind of ordinary differential equation and
system of ordinary differential equations.
For single ordinary differential equation
=========================================
It is classified under this when number of equation in ``eq`` is one.
**Usage**
``dsolve(eq, f(x), hint)`` -> Solve ordinary differential equation
``eq`` for function ``f(x)``, using method ``hint``.
**Details**
``eq`` can be any supported ordinary differential equation (see the
:py:mod:`~sympy.solvers.ode` docstring for supported methods).
This can either be an :py:class:`~sympy.core.relational.Equality`,
or an expression, which is assumed to be equal to ``0``.
``f(x)`` is a function of one variable whose derivatives in that
variable make up the ordinary differential equation ``eq``. In
many cases it is not necessary to provide this; it will be
autodetected (and an error raised if it couldn't be detected).
``hint`` is the solving method that you want dsolve to use. Use
``classify_ode(eq, f(x))`` to get all of the possible hints for an
ODE. The default hint, ``default``, will use whatever hint is
returned first by :py:meth:`~sympy.solvers.ode.classify_ode`. See
Hints below for more options that you can use for hint.
``simplify`` enables simplification by
:py:meth:`~sympy.solvers.ode.odesimp`. See its docstring for more
information. Turn this off, for example, to disable solving of
solutions for ``func`` or simplification of arbitrary constants.
It will still integrate with this hint. Note that the solution may
contain more arbitrary constants than the order of the ODE with
this option enabled.
``xi`` and ``eta`` are the infinitesimal functions of an ordinary
differential equation. They are the infinitesimals of the Lie group
of point transformations for which the differential equation is
invariant. The user can specify values for the infinitesimals. If
nothing is specified, ``xi`` and ``eta`` are calculated using
:py:meth:`~sympy.solvers.ode.infinitesimals` with the help of various
heuristics.
``ics`` is the set of boundary conditions for the differential equation.
It should be given in the form of ``{f(x0): x1, f(x).diff(x).subs(x, x2):
x3}`` and so on. For now initial conditions are implemented only for
power series solutions of first-order differential equations which should
be given in the form of ``{f(x0): x1}`` (See issue 4720). If nothing is
specified for this case ``f(0)`` is assumed to be ``C0`` and the power
series solution is calculated about 0.
``x0`` is the point about which the power series solution of a differential
equation is to be evaluated.
``n`` gives the exponent of the dependent variable up to which the power series
solution of a differential equation is to be evaluated.
**Hints**
Aside from the various solving methods, there are also some meta-hints
that you can pass to :py:meth:`~sympy.solvers.ode.dsolve`:
``default``:
This uses whatever hint is returned first by
:py:meth:`~sympy.solvers.ode.classify_ode`. This is the
default argument to :py:meth:`~sympy.solvers.ode.dsolve`.
``all``:
To make :py:meth:`~sympy.solvers.ode.dsolve` apply all
relevant classification hints, use ``dsolve(ODE, func,
hint="all")``. This will return a dictionary of
``hint:solution`` terms. If a hint causes dsolve to raise the
``NotImplementedError``, value of that hint's key will be the
exception object raised. The dictionary will also include
some special keys:
- ``order``: The order of the ODE. See also
:py:meth:`~sympy.solvers.deutils.ode_order` in
``deutils.py``.
- ``best``: The simplest hint; what would be returned by
``best`` below.
- ``best_hint``: The hint that would produce the solution
given by ``best``. If more than one hint produces the best
solution, the first one in the tuple returned by
:py:meth:`~sympy.solvers.ode.classify_ode` is chosen.
- ``default``: The solution that would be returned by default.
This is the one produced by the hint that appears first in
the tuple returned by
:py:meth:`~sympy.solvers.ode.classify_ode`.
``all_Integral``:
This is the same as ``all``, except if a hint also has a
corresponding ``_Integral`` hint, it only returns the
``_Integral`` hint. This is useful if ``all`` causes
:py:meth:`~sympy.solvers.ode.dsolve` to hang because of a
difficult or impossible integral. This meta-hint will also be
much faster than ``all``, because
:py:meth:`~sympy.core.expr.Expr.integrate` is an expensive
routine.
``best``:
To have :py:meth:`~sympy.solvers.ode.dsolve` try all methods
and return the simplest one. This takes into account whether
the solution is solvable in the function, whether it contains
any Integral classes (i.e. unevaluatable integrals), and
which one is the shortest in size.
See also the :py:meth:`~sympy.solvers.ode.classify_ode` docstring for
more info on hints, and the :py:mod:`~sympy.solvers.ode` docstring for
a list of all supported hints.
**Tips**
- You can declare the derivative of an unknown function this way:
>>> from sympy import Function, Derivative
>>> from sympy.abc import x # x is the independent variable
>>> f = Function("f")(x) # f is a function of x
>>> # f_ will be the derivative of f with respect to x
>>> f_ = Derivative(f, x)
- See ``test_ode.py`` for many tests, which serves also as a set of
examples for how to use :py:meth:`~sympy.solvers.ode.dsolve`.
- :py:meth:`~sympy.solvers.ode.dsolve` always returns an
:py:class:`~sympy.core.relational.Equality` class (except for the
case when the hint is ``all`` or ``all_Integral``). If possible, it
solves the solution explicitly for the function being solved for.
Otherwise, it returns an implicit solution.
- Arbitrary constants are symbols named ``C1``, ``C2``, and so on.
- Because all solutions should be mathematically equivalent, some
hints may return the exact same result for an ODE. Often, though,
two different hints will return the same solution formatted
differently. The two should be equivalent. Also note that sometimes
the values of the arbitrary constants in two different solutions may
not be the same, because one constant may have "absorbed" other
constants into it.
- Do ``help(ode.ode_<hintname>)`` to get help more information on a
specific hint, where ``<hintname>`` is the name of a hint without
``_Integral``.
For system of ordinary differential equations
=============================================
**Usage**
``dsolve(eq, func)`` -> Solve a system of ordinary differential
equations ``eq`` for ``func`` being list of functions including
`x(t)`, `y(t)`, `z(t)` where number of functions in the list depends
upon the number of equations provided in ``eq``.
**Details**
``eq`` can be any supported system of ordinary differential equations
This can either be an :py:class:`~sympy.core.relational.Equality`,
or an expression, which is assumed to be equal to ``0``.
``func`` holds ``x(t)`` and ``y(t)`` being functions of one variable which
together with some of their derivatives make up the system of ordinary
differential equation ``eq``. It is not necessary to provide this; it
will be autodetected (and an error raised if it couldn't be detected).
**Hints**
The hints are formed by parameters returned by classify_sysode, combining
them give hints name used later for forming method name.
Examples
========
>>> from sympy import Function, dsolve, Eq, Derivative, sin, cos, symbols
>>> from sympy.abc import x
>>> f = Function('f')
>>> dsolve(Derivative(f(x), x, x) + 9*f(x), f(x))
Eq(f(x), C1*sin(3*x) + C2*cos(3*x))
>>> eq = sin(x)*cos(f(x)) + cos(x)*sin(f(x))*f(x).diff(x)
>>> dsolve(eq, hint='1st_exact')
[Eq(f(x), -acos(C1/cos(x)) + 2*pi), Eq(f(x), acos(C1/cos(x)))]
>>> dsolve(eq, hint='almost_linear')
[Eq(f(x), -acos(C1/sqrt(-cos(x)**2)) + 2*pi), Eq(f(x), acos(C1/sqrt(-cos(x)**2)))]
>>> t = symbols('t')
>>> x, y = symbols('x, y', function=True)
>>> eq = (Eq(Derivative(x(t),t), 12*t*x(t) + 8*y(t)), Eq(Derivative(y(t),t), 21*x(t) + 7*t*y(t)))
>>> dsolve(eq)
[Eq(x(t), C1*x0 + C2*x0*Integral(8*exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0**2, t)),
Eq(y(t), C1*y0 + C2(y0*Integral(8*exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0**2, t) +
exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0))]
>>> eq = (Eq(Derivative(x(t),t),x(t)*y(t)*sin(t)), Eq(Derivative(y(t),t),y(t)**2*sin(t)))
>>> dsolve(eq)
set([Eq(x(t), -exp(C1)/(C2*exp(C1) - cos(t))), Eq(y(t), -1/(C1 - cos(t)))])
"""
if iterable(eq):
match = classify_sysode(eq, func)
eq = match['eq']
order = match['order']
func = match['func']
t = list(list(eq[0].atoms(Derivative))[0].atoms(