Difference between revisions of "SymPy/Differential Equations"
(→Initial Conditions) |
|||
(7 intermediate revisions by the same user not shown) | |||
Line 43: | Line 43: | ||
There are two fundamentally different ways to solve one differential equation. If you give the solver the equation and the function to solve for, it will return an equation with the solution in it: | There are two fundamentally different ways to solve one differential equation. If you give the solver the equation and the function to solve for, it will return an equation with the solution in it: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
− | soln1 = sym. | + | soln1 = sym.dsolve(eqn1, y(t)) |
soln1 | soln1 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
will produce an equation that contains $$y(t)=\frac{C_1e^{-\frac{3t}{2}}}{3}+\frac{4}{3}$$. On the other hand, if you give the solver a list with an equation in it and a list with the function in it, it will return a list with the equation in it: | will produce an equation that contains $$y(t)=\frac{C_1e^{-\frac{3t}{2}}}{3}+\frac{4}{3}$$. On the other hand, if you give the solver a list with an equation in it and a list with the function in it, it will return a list with the equation in it: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
− | soln2 = sym. | + | soln2 = sym.dsolve([eqn1], [y(t)]) |
soln2 | soln2 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 55: | Line 55: | ||
=== Initial Conditions === | === Initial Conditions === | ||
− | To incorporate initial conditions, you will give the <code>dsolve</code> command a dictionary of function values at particular times. For example, to solve our sample equation with $$y(0)=5$$, you will include the initial condition by adding <code>ics={y(0) | + | To incorporate initial conditions, you will give the <code>dsolve</code> command a dictionary of function values at particular times. For example, to solve our sample equation with $$y(0)=5$$, you will include the initial condition by adding <code>ics={y(0):5}</code> to the arguments. |
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
soln3 = sym.solve([eqn1], [y(t)], ics = {y(0): 5}) | soln3 = sym.solve([eqn1], [y(t)], ics = {y(0): 5}) | ||
Line 68: | Line 68: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
will produce $$\left[ y(t)=\frac{4}{3} + \frac{17e^9e^{-\frac{3t}{2}}}{3} \right]$$ | will produce $$\left[ y(t)=\frac{4}{3} + \frac{17e^9e^{-\frac{3t}{2}}}{3} \right]$$ | ||
+ | |||
+ | == Basic Symbolic Example == | ||
+ | For some simple systems of differential equations, SymPy can actually solve with symbolic coefficients and undefined forcing functions. The result will be an unevaluated integral, but still - pretty impressive! Here is an example that solves $$a\,\frac{dy(t)}{dt} + b\,y(t)=x(t)$$ assuming $$y(t_0)=y_0$$. If you want to follow along, the code is in the <code>dsolve_2</code> notebook in the [https://drive.google.com/drive/folders/10Y1kzpu-DTTRwm1mYtdgTwpXF2zKpmoL?usp=sharing Basic Dsolve] Google Drive folder. | ||
+ | <syntaxhighlight lang=python> | ||
+ | import sympy as sym | ||
+ | sym.init_printing(use_latex=True) | ||
+ | a, b, t, y0, t0 = sym.var('a, b, t, y0, t0') | ||
+ | x = sym.Function('x') | ||
+ | y = sym.Function('y') | ||
+ | eqn1 = sym.Eq(a*sym.Derivative(y(t), t) + b * y(t), x(t)) | ||
+ | eqns = [eqn1] | ||
+ | soln = sym.dsolve(eqns, [y(t)], ics = {y(t0): y0}) | ||
+ | soln | ||
+ | </syntaxhighlight> | ||
+ | The solution is: | ||
+ | <center> | ||
+ | $$ | ||
+ | \left[ y{\left(t \right)} = \left(y_{0} e^{\frac{b t_{0}}{a}} - \frac{\int x{\left(t_{0} \right)} e^{\frac{b t_{0}}{a}}\, dt_{0}}{a}\right) e^{- \frac{b t}{a}} + \frac{e^{- \frac{b t}{a}} \int x{\left(t \right)} e^{\frac{b t}{a}}\, dt}{a}\right] | ||
+ | $$ | ||
+ | </center> | ||
+ | === Making Substitutions === | ||
+ | Making substitutions into lists of equations is a little complicated - you need to go into each element in the list and make the substitutions there. Given that, there is a helper function in <code>sym_helper</code> called <code>makesubseqnlist(EQN LIST, VARS TO SUB, VALS TO SUB)</code> that will take a list of equations, a list of variables (or functions), and a list of values (or functions). The code for <code>makesubseqnlist(EQN LIST, VARS TO SUB, VALS TO SUB)</code> is | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | def makesubseqnlist(equations, vartosub, valtosub): | ||
+ | sublist = list(zip(vartosub, valtosub)) | ||
+ | esub = [] | ||
+ | for eqn in equations: | ||
+ | esub.append(eqn.subs(sublist)) | ||
+ | return esub | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | and you can get by by adding the following to near the top of your notebook or script: | ||
+ | <syntaxhighlight lang=python> | ||
+ | try: | ||
+ | from sym_helper import * | ||
+ | except: # Get sym_helper.py from GitHub | ||
+ | import requests | ||
+ | url = 'https://tinyurl.com/mrgdemos/SymPy_Files/sym_helper.py' | ||
+ | open('sym_helper.py', 'w').write(requests.get(url).text) | ||
+ | from sym_helper import * | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | If we specifically want to set $$a=2$$, $$b=3$$, and $$x(t)=4\,\cos(5t)$$, and then solve that where $$y(0)=6$$, we can: | ||
+ | <syntaxhighlight lang=python> | ||
+ | eqns2 = makesubseqnlist(eqns, [a, b, x(t)], [2, 3, 4*sym.cos(5*t)]) | ||
+ | soln2 = sym.dsolve(eqns2, [y(t)], ics = {y(0): 6}) | ||
+ | </syntaxhighlight> | ||
+ | yields: | ||
+ | <center> | ||
+ | $$ | ||
+ | \left[ y{\left(t \right)} = \frac{40 \sin{\left(5 t \right)}}{109} + \frac{12 \cos{\left(5 t \right)}}{109} + \frac{642 e^{- \frac{3 t}{2}}}{109}\right] | ||
+ | $$ | ||
+ | </center> | ||
+ | |||
+ | == Making a Dictionary == | ||
+ | For linear algebra, the results came in a dictionary that had each of the unknowns as keys and their solutions as variables. Since <code>dsolve</code> produces a list, versus a ductionary, it will be useful to convert that list to a dictionary for making substitutions later. <code>sym_helper</code> has a <code>make_dict(LIST)</code> command that will turn a list of equations into a dictionary. The code for <code>make_dict</code> is: | ||
+ | <syntaxhighlight lang=python> | ||
+ | def makedict(sollist): | ||
+ | d = {} | ||
+ | for eqn in sollist: | ||
+ | d[eqn.lhs] = eqn.rhs | ||
+ | return d | ||
+ | </syntaxhighlight> | ||
+ | and so the code you can add to your script is: | ||
+ | <syntaxhighlight lang=python> | ||
+ | def makedict(sollist): | ||
+ | soln = makedict(solnlist) | ||
+ | </syntaxhighlight> | ||
+ | and, as long as <code>sym_helper</code> has been imported, you can run it by writing code like: | ||
+ | <syntaxhighlight lang=python> | ||
+ | solndict = makedict(soln2) | ||
+ | </syntaxhighlight> | ||
+ | which yields: | ||
+ | <center> | ||
+ | $$ | ||
+ | \left\{ y{\left(t \right)} : \frac{40 \sin{\left(5 t \right)}}{109} + \frac{12 \cos{\left(5 t \right)}}{109} + \frac{642 e^{- \frac{3 t}{2}}}{109}\right\} | ||
+ | |||
+ | $$ | ||
+ | </center> | ||
+ | |||
+ | == Plotting Results == | ||
+ | Plotting will require using <code>lambdify</code> to turn one or more functions into a lambda function and then setting up your plot. Here is the code for this example that does all those things (making a plot that runs from $$t$$=0 to 10 sec); note the additional required imports: | ||
+ | <syntaxhighlight lang=python> | ||
+ | import matplotlib.pyplot as plt | ||
+ | import numpy as np | ||
+ | yfun = sym.lambdify(t, solndict[y(t)]) | ||
+ | fig = plt.figure(num=1, clear=True) | ||
+ | ax = fig.add_subplot(1, 1, 1) | ||
+ | tn = np.linspace(0, 10, 1000) | ||
+ | ax.plot(tn, yfun(tn)) | ||
+ | </syntaxhighlight> | ||
== Lessons Learned == | == Lessons Learned == | ||
* dsolve is good for up to two coupled ODEs. Once there are three or more...not so much (even if all the coefficients are ints or floats). Once there are three or more (linear) coupled differential equations, consider going the Laplace route. | * dsolve is good for up to two coupled ODEs. Once there are three or more...not so much (even if all the coefficients are ints or floats). Once there are three or more (linear) coupled differential equations, consider going the Laplace route. | ||
* If your equations are a combination of differential equations a non-differential equations, dsolve expects all the differential equations to come first. | * If your equations are a combination of differential equations a non-differential equations, dsolve expects all the differential equations to come first. | ||
+ | * If you are using certain functions from SymPy that aren't exactly the same in NumPy, when you lambdify things you will need to provide the translation | ||
+ | == Examples == | ||
+ | * See [[SymPy/Differential Equations/RC Example]] for a complete example; this will reference IVP_RCDemo in the Google Drive folder. | ||
+ | * See [[SymPy/CAD]] for a list of SymPy examples from a free textbook. The ones most relevant to this page are from Chapter 5. | ||
+ | * There are several other files in the [https://drive.google.com/drive/folders/10Y1kzpu-DTTRwm1mYtdgTwpXF2zKpmoL?usp=sharing Basic Dsolve] Google Drive folder: | ||
− | + | ** Coupled ODEs (just to see how SymPy handles those!) | |
− | + | *** Coupled2 | |
+ | *** Coupled3 | ||
+ | *** deq_coupled: symbolically solve two coupled first-order ODEs; substitute numbers later to make graph. Needs some editing - does not completely follow the general template. | ||
+ | *** deq_coupled_num: symbolically solve two coupled first-order ODEs with numbers substituted in before solving. Needs some editing - does not completely follow the general template. | ||
+ | ** First-order differential equations: | ||
+ | *** IVP_demo1st: symbolic solution to 1st order ODE with a constant forcing function; substitutes numbers later to make graph | ||
+ | *** IVP_demo1stfun: symbolic solution to 1st order ODE with a non-constant forcing function; substitutes numbers later to make graph (including forcing function on graph) | ||
+ | *** IVP_demo1st_pulse: symbolic solution to 1st order ODE with a non-constant forcing function comprised of step and ramp functions; substitutes numbers later to make graph (including forcing function on graph) | ||
+ | **** This one shows extra code needed to translate step and ramp functions into a lambdify function | ||
+ | ** Second-order differential equations: | ||
+ | *** IVP_demo2ndcon: symbolic solution to 2nd order ODE with a constant forcing function; substitutes numbers later to make graph. Needs some editing - does not completely follow the general template. | ||
+ | *** IVP_demo2ndfun: symbolic solution to 2nd order ODE with a non-constant forcing function; substitutes numbers later to make graph. Needs some editing - does not completely follow the general template. |
Latest revision as of 17:30, 21 February 2023
Contents
Introduction
This page focuses on using SymPy to find both the symbolic and the numeric solutions to differential equations obtained from electric circuits. The examples are available via a Google Drive at Basic Dsolve. If you have the Google Colaboratory app installed in your browser, you should be able to run the notebooks in your browser. If not, you can either install it or install a local program that can read notebook files such as Jupyter Notebooks, available with Anaconda. This page is based on SymPy/Simultaneous Equations, which is likely a good place to start before going through this page!
Very Basic Example
The example code below assumes you are either running code in a notebook of some kind or typing it into a console. If you want to follow along, the code is in the dsolve_1
notebook in the Basic Dsolve Google Drive folder.
Imagine you have the equation $$2\frac{dy(t)}{dt} + 3 y(t) = 4$$ with the initial condition $$y(0)=5$$, and you want to solve for $$y(t)$$. You can do this in SymPy as follows:
Initialization
You will need to import sympy to use symbolic calculations. Pratt Pundit will generally give that module the nickname sym
:
import sympy as sym
You will likely want your notebook or script to try to use $$\LaTeX$$ to render the results, so add:
sym.init_printing(use_latex=True)
as well.
Declare Variables and Functions
You will need to let Python know what your variables and functions are. For the example equation above, the unknown variable is $$t$$ and the unknown function is $$y$$; you can declare them with the following lines of code:
t = sym.var('t')
y = sym.Function('y')
Note that the Function
command starts with a capital F.
Define Equations
To define a derivative in SymPy, use sym.Derivative(FUN, VAR)
, where FUN is a function (with an argument, usually VAR) and VAR is the variable you are taking the derivative with respect to. For instance, sym.Derivative(y(t), t)
represents $$\frac{dy(t)}{dt}$$. If you need to take a higher-order derivative, you can (a) append the independent variable to the argument list, (b) append the order of the derivative to the argument list, or (c) give a tuple with the independent argument and the order. For example, to get $$\frac{d^2y(t)}{dt^2}$$, you can use sym.Derivative(y(t), t, t)
, sym.Derivative(y(t), t, 2)
, or sym.Derivative(y(t), (t, 2))
. See Defining Derivatives at SymPy for further examples.
When it comes to solving things later, there are two different ways to define an equation:
- If you have an expression that evaluates to other than zero, you can use sym.Eq(left, right) for that. Given that we are trying to solve $$2\frac{dy(t)}{dt} + 3 y(t) = 4$$, you can write that in code as:
eqn1 = sym.Eq(2 * sym.Derivative(y(t), t) + 3 * y(t), 4)
- If the equation is equal to zero, you do not explicitly have to include the "=0" part; you can simply define a variable to hold on to the expression. For instance, in this case, we can re-write the equation we are trying to solve as $$ax-d=0$$ and just define a variable to hold on to the left side:
eqn1 = 2 * sym.Derivative(y(t), t) + 3 * y(t) - 4
Solve Equations
There are two fundamentally different ways to solve one differential equation. If you give the solver the equation and the function to solve for, it will return an equation with the solution in it:
soln1 = sym.dsolve(eqn1, y(t))
soln1
will produce an equation that contains $$y(t)=\frac{C_1e^{-\frac{3t}{2}}}{3}+\frac{4}{3}$$. On the other hand, if you give the solver a list with an equation in it and a list with the function in it, it will return a list with the equation in it:
soln2 = sym.dsolve([eqn1], [y(t)])
soln2
will produce the list $$\left[ y(t)=\frac{C_1e^{-\frac{3t}{2}}}{3}+\frac{4}{3} \right]$$.
This latter version is going to be much more useful if we are going to do anything else with the solution, so you should generally make sure to give dsolve
two lists. Note that unlike solving linear algebra equations, if you give a list of differential equations, you must also give a list with the functions.
Initial Conditions
To incorporate initial conditions, you will give the dsolve
command a dictionary of function values at particular times. For example, to solve our sample equation with $$y(0)=5$$, you will include the initial condition by adding ics={y(0):5}
to the arguments.
soln3 = sym.solve([eqn1], [y(t)], ics = {y(0): 5})
soln3
will produce $$\left[ y(t)=\frac{4}{3} + \frac{11e^{-\frac{3t}{2}}}{3} \right]$$
Note that the initial condition does not have to be at time 0; if you know that $$y(6)=7$$, you can use that as well:
soln4 = sym.solve([eqn1], [y(t)], ics = {y(6): 7})
soln4
will produce $$\left[ y(t)=\frac{4}{3} + \frac{17e^9e^{-\frac{3t}{2}}}{3} \right]$$
Basic Symbolic Example
For some simple systems of differential equations, SymPy can actually solve with symbolic coefficients and undefined forcing functions. The result will be an unevaluated integral, but still - pretty impressive! Here is an example that solves $$a\,\frac{dy(t)}{dt} + b\,y(t)=x(t)$$ assuming $$y(t_0)=y_0$$. If you want to follow along, the code is in the dsolve_2
notebook in the Basic Dsolve Google Drive folder.
import sympy as sym
sym.init_printing(use_latex=True)
a, b, t, y0, t0 = sym.var('a, b, t, y0, t0')
x = sym.Function('x')
y = sym.Function('y')
eqn1 = sym.Eq(a*sym.Derivative(y(t), t) + b * y(t), x(t))
eqns = [eqn1]
soln = sym.dsolve(eqns, [y(t)], ics = {y(t0): y0})
soln
The solution is:
$$ \left[ y{\left(t \right)} = \left(y_{0} e^{\frac{b t_{0}}{a}} - \frac{\int x{\left(t_{0} \right)} e^{\frac{b t_{0}}{a}}\, dt_{0}}{a}\right) e^{- \frac{b t}{a}} + \frac{e^{- \frac{b t}{a}} \int x{\left(t \right)} e^{\frac{b t}{a}}\, dt}{a}\right] $$
Making Substitutions
Making substitutions into lists of equations is a little complicated - you need to go into each element in the list and make the substitutions there. Given that, there is a helper function in sym_helper
called makesubseqnlist(EQN LIST, VARS TO SUB, VALS TO SUB)
that will take a list of equations, a list of variables (or functions), and a list of values (or functions). The code for makesubseqnlist(EQN LIST, VARS TO SUB, VALS TO SUB)
is
def makesubseqnlist(equations, vartosub, valtosub):
sublist = list(zip(vartosub, valtosub))
esub = []
for eqn in equations:
esub.append(eqn.subs(sublist))
return esub
and you can get by by adding the following to near the top of your notebook or script:
try:
from sym_helper import *
except: # Get sym_helper.py from GitHub
import requests
url = 'https://tinyurl.com/mrgdemos/SymPy_Files/sym_helper.py'
open('sym_helper.py', 'w').write(requests.get(url).text)
from sym_helper import *
If we specifically want to set $$a=2$$, $$b=3$$, and $$x(t)=4\,\cos(5t)$$, and then solve that where $$y(0)=6$$, we can:
eqns2 = makesubseqnlist(eqns, [a, b, x(t)], [2, 3, 4*sym.cos(5*t)])
soln2 = sym.dsolve(eqns2, [y(t)], ics = {y(0): 6})
yields:
$$ \left[ y{\left(t \right)} = \frac{40 \sin{\left(5 t \right)}}{109} + \frac{12 \cos{\left(5 t \right)}}{109} + \frac{642 e^{- \frac{3 t}{2}}}{109}\right] $$
Making a Dictionary
For linear algebra, the results came in a dictionary that had each of the unknowns as keys and their solutions as variables. Since dsolve
produces a list, versus a ductionary, it will be useful to convert that list to a dictionary for making substitutions later. sym_helper
has a make_dict(LIST)
command that will turn a list of equations into a dictionary. The code for make_dict
is:
def makedict(sollist):
d = {}
for eqn in sollist:
d[eqn.lhs] = eqn.rhs
return d
and so the code you can add to your script is:
def makedict(sollist):
soln = makedict(solnlist)
and, as long as sym_helper
has been imported, you can run it by writing code like:
solndict = makedict(soln2)
which yields:
$$ \left\{ y{\left(t \right)} : \frac{40 \sin{\left(5 t \right)}}{109} + \frac{12 \cos{\left(5 t \right)}}{109} + \frac{642 e^{- \frac{3 t}{2}}}{109}\right\} $$
Plotting Results
Plotting will require using lambdify
to turn one or more functions into a lambda function and then setting up your plot. Here is the code for this example that does all those things (making a plot that runs from $$t$$=0 to 10 sec); note the additional required imports:
import matplotlib.pyplot as plt
import numpy as np
yfun = sym.lambdify(t, solndict[y(t)])
fig = plt.figure(num=1, clear=True)
ax = fig.add_subplot(1, 1, 1)
tn = np.linspace(0, 10, 1000)
ax.plot(tn, yfun(tn))
Lessons Learned
- dsolve is good for up to two coupled ODEs. Once there are three or more...not so much (even if all the coefficients are ints or floats). Once there are three or more (linear) coupled differential equations, consider going the Laplace route.
- If your equations are a combination of differential equations a non-differential equations, dsolve expects all the differential equations to come first.
- If you are using certain functions from SymPy that aren't exactly the same in NumPy, when you lambdify things you will need to provide the translation
Examples
- See SymPy/Differential Equations/RC Example for a complete example; this will reference IVP_RCDemo in the Google Drive folder.
- See SymPy/CAD for a list of SymPy examples from a free textbook. The ones most relevant to this page are from Chapter 5.
- There are several other files in the Basic Dsolve Google Drive folder:
- Coupled ODEs (just to see how SymPy handles those!)
- Coupled2
- Coupled3
- deq_coupled: symbolically solve two coupled first-order ODEs; substitute numbers later to make graph. Needs some editing - does not completely follow the general template.
- deq_coupled_num: symbolically solve two coupled first-order ODEs with numbers substituted in before solving. Needs some editing - does not completely follow the general template.
- First-order differential equations:
- IVP_demo1st: symbolic solution to 1st order ODE with a constant forcing function; substitutes numbers later to make graph
- IVP_demo1stfun: symbolic solution to 1st order ODE with a non-constant forcing function; substitutes numbers later to make graph (including forcing function on graph)
- IVP_demo1st_pulse: symbolic solution to 1st order ODE with a non-constant forcing function comprised of step and ramp functions; substitutes numbers later to make graph (including forcing function on graph)
- This one shows extra code needed to translate step and ramp functions into a lambdify function
- Second-order differential equations:
- IVP_demo2ndcon: symbolic solution to 2nd order ODE with a constant forcing function; substitutes numbers later to make graph. Needs some editing - does not completely follow the general template.
- IVP_demo2ndfun: symbolic solution to 2nd order ODE with a non-constant forcing function; substitutes numbers later to make graph. Needs some editing - does not completely follow the general template.
- Coupled ODEs (just to see how SymPy handles those!)