Difference between revisions of "SymPy/Simultaneous Equations"

From PrattWiki
Jump to navigation Jump to search
(Created page with "==Introduction== This page focuses on using SymPy to find both the symbolic and the numeric solutions to equations obtained from electric circuits. == Initialization == To...")
 
(Introduction)
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
==Introduction==
 
==Introduction==
This page focuses on using SymPy to find both the symbolic and the numeric solutions to equations obtained from electric circuits.   
+
This page focuses on using SymPy to find both the symbolic and the numeric solutions to equations obtained from electric circuits.  The examples are available via a Google Drive at [https://drive.google.com/drive/folders/10NuAAl4yWpsapZesm2snnWOgMtBb-tP2?usp=sharing Basic Linear Algebra].  If you have the [https://workspace.google.com/marketplace/app/colaboratory/1014160490159 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 [https://www.anaconda.com/ Anaconda].
  
== Initialization ==
+
== Very Basic Example ==
To use SymPy, you will need to import the moduleThe module comes with the Anaconda distribution of Python, but if you have a different installation, you may need to get the module firstAlso, you will likely want to use the "pretty printing" capabilities of SymPy, which requires initializing printingThese two steps can be done with:
+
The example code below assumes you are either running code in a notebook of some kind or typing it into a consoleThere is a finished example at the end of this section which also includes the commands to display different variables if you are using a script.
 +
Imagine you have the equation $$ax=d$$ and you want to solve for $$x$$You can do this in SymPy as follows:
 +
 
 +
=== Initialization ===
 +
You will need to import <code>sympy</code> to use symbolic calculationsPratt Pundit will generally give that module the nickname <code>sym</code>:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
 
import sympy as sym
 
import sympy as sym
 
sym.init_printing()
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Declaring Symbols ==
+
=== Declare Variables ===
While there are ways to bring in certain "standard" symbols in SymPy, it may be clearest to define them yourself.  You need to declare something as a symbol before using it on the right side of an assignment; as usual, if you create a variable as a combination of symbols, Python will duck type it as a symbol tooThe <code>sym.var()</code> function accepts a space or comma-separated string of variables and will create symbolic entities based on the code in the string. 
+
You will need to let Python know what your variables areThere are several ways to do this; the easiest is to use the <code>sym.var()</code> command with a string containing the variables you want separated by spaces:
For instance:
 
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
sym.var('x, y')
+
sym.var('a x d')
 
</syntaxhighlight>
 
</syntaxhighlight>
will create variables <code>x</code> and <code>y</code>
 
  
== Defining Equations ==
+
=== Define Equations ===
 
When it comes to solving things later, there are two different ways to define an equation:
 
When it comes to solving things later, there are two different ways to define an equation:
* 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.  If $$x+y=0$$ is one of the equations you are looking to solve, you can use:
+
* 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 $$ax=d$$, you can write that in code as:
 +
<syntaxhighlight lang=python>
 +
eqn1 = sym.Eq(a * x, d)
 +
</syntaxhighlight>
 +
* 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:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
eqn1 = x + y
+
eqn1 = a * x - d
 
</syntaxhighlight>
 
</syntaxhighlight>
  
If you have an expression that evaluates to other than zero, you can use sym.Eq(left, right) for that.  If the other equations for which you are trying to solve is $$x-y=3$$, you can write that in code as:
+
=== Solve Equations ===
 +
There are two fundamentally different ways to solve one equation.  If you give the solver the equation, it will return a list with the solution in it:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
eqn2 = sym.Eq(x - y, 3)
+
soln1 = sym.solve(eqn1, x)
 +
soln1
 
</syntaxhighlight>
 
</syntaxhighlight>
Of course, you could also rearrange the expression by moving the right side to the left, in which case:
+
will produce a list that contains $$d/a$$.  On the other hand, if you give the solver a list with an equation in it, it will return a dictionary with the solution defined in it:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
eqn2 = x - y - 3
+
soln2 = sym.solve([eqn1], x)
 +
soln2
 +
</syntaxhighlight>
 +
will produce the dictionary $$\left\{x: \frac{d}{a}\right\}$$.  Whenever you have more than one equation, you will need to collect the equations in a list or a tuple and the solver will return a dictionary.  Given that, it is generally a good idea to always give the solver a list for the equations and a list for the unknowns, even if there is only one of each.
 +
<syntaxhighlight lang=python>
 +
soln = sym.solve([eqn1], [x])
 +
soln
 
</syntaxhighlight>
 
</syntaxhighlight>
would work!
 
  
 +
=== Make Substitutions ===
 +
Now that you have symbolic answers, you can make numerical substitutions for those symbols using the <code>subs</code> command.  You will need to pull the appropriate definition out of the dictionary and then apply the substitutions to it.  For example, to see what $$x$$ is when $$d$$ is 10, you can write:
  
==Solving Equations With SymPy==
 
To solve a system of linear algebra equations, you can use the <code>sym.solve(eqns, vars)</code> command.  The system of equations should be in a list, set, or tuple as should the list of unknowns.  Note that even if you don't specifically "care" about the value of one or more of the unknowns, you must list them; if you want to solve the two equations above to find x, you still need to explicitly list y as a unknown or else SymPy will assume that it is a symbolic, but known, value, thus constraining the system.  Here is an example solving the two equations above:
 
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
soln = sym.solve((eqn1, eqn2), (x, y))
+
soln[x].subs(d, 10)
 
</syntaxhighlight>
 
</syntaxhighlight>
If you run this in the console, <code>soln</code> will print out pretty.  If you put all this in a script, <code>soln</code> will not show up at all...  You can put <code>print(soln)</code> in a script, but it will not be pretty.  To make it pretty requires a little work since the solution set is a dictionary with the variables as keys.  To help, here is a Python function you can add to your code:
+
 
 +
If you want to see multiple substitutions, you can put the substitution pairs in tuples or lists that are contained in a tuple or list:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
def printout(solution, variables, fp=False):
+
soln[x].subs([[a, 3], [d, 10]])
    for var in variables:
 
        sym.pretty_print(var)
 
        if fp:
 
            sym.pretty_print(solution[var].evalf(fp))
 
        else:
 
            sym.pretty_print(solution[var])
 
    print()
 
 
</syntaxhighlight>
 
</syntaxhighlight>
This will go through the solutions and print the variable followed by the solution.  If you have substituted in numbers, set fp to True to do floating point evaluation on the answers.  If you define this function in the console and then type
 
<syntaxhighlight lang=python>
 
printout(soln, (x, y))
 
</syntaxhighlight>
 
you will see the variables and their values. 
 
  
When the solutions are numbers, this does not seem that useful, but imagine that you want to solve:
+
=== Complete Example ===
<center>$$
+
The code below has an additional piece, which is importing IPython.display as disp. This will allow you to display symbols in a formatted fashion within a script.
\begin{align*}
 
ax+by&=c\\
 
dx+ey&=f
 
\end{align*}$$</center>
 
symbolically? You will need to add symbols for a through f first, then redefine the equations to include those symbols; here is a sample code:
 
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
 +
# Import sympy
 
import sympy as sym
 
import sympy as sym
  
 +
# Print pretty
 
sym.init_printing()
 
sym.init_printing()
  
def printout(solution, variables, fp=False):
+
# Use disp.display to use formatted printing
    for var in variables:
+
import IPython.display as disp
        sym.pretty_print(var)
+
show = disp.display
        if fp:
+
 
            sym.pretty_print(solution[var].evalf(fp))
+
# Declare symbols
        else:
+
sym.var('a x d')
            sym.pretty_print(solution[var])
+
 
    print()
+
# Equations
 +
eqn1 = sym.Eq(a * x, d)
 +
 
 +
# Solve
 +
soln1 = sym.solve(eqn1, x)
 +
show(soln1)
 +
 
 +
soln2 = sym.solve([eqn1], x)
 +
show(soln2)
  
x, y = sym.var('x, y')
+
soln = sym.solve([eqn1], [x])
a, b, c, d, e, f = sym.var('a, b, c, d, e, f')
+
show(soln)
  
eqn1 = a*x + b*y - c # shows moving right to left side
+
show(soln[x].subs(d, 10))
eqn2 = sym.Eq(d*x+e*y, f) # Shows using sym.Eq to give left and right
 
  
soln = sym.solve((eqn1, eqn2), (x, y))
+
show(soln[x].subs([[a, 3], [d, 10]]))
print(soln)
 
printout(soln, (x, y))
 
 
</syntaxhighlight>
 
</syntaxhighlight>
The printed version gives:
 
<syntaxhighlight lang=text>
 
{x: (-b*f + c*e)/(a*e - b*d), y: (a*f - c*d)/(a*e - b*d)}
 
</syntaxhighlight>
 
which is a bit hard to interpret; the printout version shows the two fractions as mathematical expressions.
 
  
== Substituting Values ==
+
 
SymPy has a subs method that works on a variable.  For instance, if you have:
 
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
import sympy as sym
 
  
sym.init_printing()
+
</syntaxhighlight>
 +
 
  
sym.var('a, b, c, d, e, f')
 
a = b + c * d + e ** f
 
</syntaxhighlight>
 
You have created $$a=b+cd+e^f$$.  If you want to know what this would be if $$b=2$$, you could write:
 
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
a.subs(b, 2)
+
 
 
</syntaxhighlight>
 
</syntaxhighlight>
to see that - note that this does not change $$a$$If there are multiple substitutions, you can put them in an interable (i.e. list or tuple) of variable-value pairs; the following two lines will produce the same results:
+
 
 +
== Helper Functions ==
 +
As you can see in the example above, there are some processes that look like they might get a little complicated as the number of equations increases.  Given that, here is a set of helper functions that you may want to import into your script or notebookNote: if you are using Google Colab, accessing secondary files takes a bit of work - this will be explained later.  These functions are all in a file called <code>sym_helper.py</code>.
 +
 
 +
=== Imports ===
 +
The <code>sym_helper.py</code> will need to import the sympy module.  It will also attempt to import the IPython.display module.  in order to make <code>sym_helper.py</code> compatible with applications like Trinket (which does not use IPython.display), <code>sym_helper.py</code> will see if it can import the module and then, based on that, decide how it will show things:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
a.subs( ( (b,2),(c,3) ) )
+
import sympy as sym
a.subs( [ [b,2],[c,3] ] )
+
 
 +
try:
 +
  import IPython.display as disp
 +
  show = disp.display
 +
except:
 +
  show = sym.pretty_print
 
</syntaxhighlight>
 
</syntaxhighlight>
If you have an iterable of variables to substitute for and an iterable of values, you can use the zip command to create those pairs for you:
+
 
 +
 
 +
=== Printing Out Solution Dictionary ===
 +
Next, we may want help printing out the solutions to a set of equations.  As long as you give <code>solve</code> a list of equations, you will get back a dictionary.
 +
If you are running your code in a notebook or in the console, typing <code>soln</code> by itself and hitting return will let you see a pretty version of the solution.  If you put all this in a script, however, <code>soln</code> will not show up at all...  You can put <code>print(soln)</code> in a script, but it will not be pretty.  To make it pretty requires a little work since the solution set is a dictionary with the variables as keys.  To help, here is a Python function called <code>printout</code> you can use:
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
varstosub = [b, c]
+
def printout(solution, variables=None, fp=False):
valstosub = [2, 3]
+
  # Default case: print all variables
zipsub = zip(varstosub, valstosub)
+
  if variables==None:
a.subs(zipsub)
+
    variables = tuple(solution.keys())
 +
  # Fixes issue if solving for a single item
 +
  if not isinstance(variables, (list, tuple)):
 +
    variables = [variables]
 +
  for var in variables:
 +
    show(var)
 +
    if fp:
 +
      show(solution[var].evalf(fp))
 +
    else:
 +
      show(solution[var])
 +
    print()
 
</syntaxhighlight>
 
</syntaxhighlight>
Given that, here is another handy function for making substitutions that takes a list of variables and a list of values:
+
This will go through the dictionary and print the variable followed by the solution for that variable.  If you have substituted in numbers, set <code>fp</code> to an integer to do floating point evaluation on the answers.  While a function like this may not seem necessary at the moment (with only one unknown variable, this feels like a lot of overhead), as you start solving problems with more and more unknowns it will be nice to have!
 +
 
 +
=== Making Substitutions ===
 +
As you start working with more equations, you may end up having more and more symbols that you will want to be able to replace with numerical values later.  Having to create a list of lists with the substitutions every time can certainly get old!  Here is a function that will take a solution dictionary, a list of variables for which to substitute, and a list of the values to use for those variables.  It returns a dictionary with the substitutions in place.  Note that if it gets a list of equations rather than a dictionary, it will convert the list to a dictionary first.  This is not something that is needed for linear algebra but will be something that is needed when setting up and solving simultaneous differential equations.
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
 
def makesubs(solution, vartosub, valtosub):
 
def makesubs(solution, vartosub, valtosub):
    subsol = solution.copy()
+
  subsol = solution.copy()
    sublist = list(zip(vartosub, valtosub))
+
  sublist = list(zip(vartosub, valtosub))
     for var in solution:
+
  if type(subsol)==list:
        subsol[var] = subsol[var].subs(sublist)
+
     subsol = makedict(subsol)
     return subsol
+
     
 +
  for var in subsol:
 +
    subsol[var] = subsol[var].subs(sublist)
 +
  return subsol
 +
 
 +
def makedict(sollist):
 +
  d = {}
 +
  for eqn in sollist:
 +
     d[eqn.lhs] = eqn.rhs
 +
  return d
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
=== Complete Example Extended, with Helpers ===
 +
By carefully setting up your symbolic variables and by using the helper functions above, you can organize your code in a way that will be much more flexible for future assignments.  First, instead of creating all of your symbolic variables at once, you can divide them into a collection of unknowns and a collection of knowns -- that will make it easier when you want Python to solve your symbolic equations.  Second, you can create a list of the substitution values that you will be using later. 
  
<!--
+
'''Note:''' the code below assumes that <code>sym_helper.py</code> is accessible.  If you are using Google Colab, please see the section below on how to get that to work.
 +
<syntaxhighlight lang=python>
 +
# Imports
 +
import sympy as sym
 +
from sym_helper import *
  
==Substituting Values==
+
# Print pretty
Now that you have the symbolic answers to the variables ''x'', ''y'', and
+
sym.init_printing()
''z'', you may want to substitute the actual coefficient values to
 
obtain a numerical solution.  One way to do this is to generate a list
 
of the known values, then tell Maple to substitute in the numerical
 
values by using the built-in '''subs''' command. Add the following
 
lines of code:
 
<source lang=text>
 
Vals := a=-1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, k=11, l=12
 
subs(Vals, MySoln)
 
</source>
 
Remember if you cut and paste these into a single group you will need to add a semi-colon after the first line or else you will get a parsing error.
 
The list in '''MySoln''' will now be shown with numerical values
 
instead of symbols.  Note that you have ''not'' made any actual
 
changes to any of the variables - you have just asked Maple to show
 
you what they would look like given the particular substitutions
 
presented in '''Vals'''.  This is a very powerful tool, since you can
 
substitute in a variety of values to see how one or more parameters
 
influence a particular variable or variables.
 
  
'''Note:''' unlike MATLAB, Maple defines <math>\sqrt{-1}</math> as an upper-case <code>I</code> instead of lower case <code>i</code>.  You should therefore avoid re-defining <code>I</code> in Maple; using the lower case version as a variable is perfectly fine.
+
# Declare symbols (unknowns in first group, knowns in second)
 +
# Create a list of future numerical values for knowns
 +
unks = sym.var('x')
 +
knowns = sym.var('a d')
 +
subval = [10, 3]
  
<!--
+
# Equations
===Assigning Representations===
+
eqn1 = sym.Eq(a * x, d)
There will be many times you actually want to assign the solutions
 
found by '''solve''' - that is, you want to take the equations out of
 
the list and have Maple process them as if the = were := so that Maple
 
could use those expressions later. Maple has a command called '''assign''' that does exactly that.  Add the commands:
 
<source lang=text>
 
assign(MySoln)
 
x
 
y
 
z
 
</source>
 
You will see that while the '''assign''' command does not report
 
anything back to you, when you ask Maple to tell you what ''x'', ''y'',
 
and ''z'' are, it responds with the symbolic representation produced in
 
'''MySoln'''.  This is very useful if, for example, the answer you are
 
looking for is some function of the variables ''x'', ''y'', and ''z''.
 
Assuming that you have determined the variable you are looking for,
 
''alpha'', is
 
<center><math>
 
\begin{align}
 
\alpha&=x+y+z
 
\end{align}
 
</math></center>
 
you can now use the symbolic representations in Maple to generate a
 
symbolic representation for ''alpha'':
 
<source lang=text>
 
alpha:=x+y+z
 
</source>
 
Note, among other things, that Maple represents the variable named
 
'''alpha''' as its symbol, <math>\alpha</math>.  If you want a numerical value,
 
you can again use the '''subs''' command and the value list from
 
before:
 
<source lang=text>
 
subs(Vals, alpha)
 
</source>
 
  
 +
# Collect equations
 +
eqns = [eqn1]
  
 +
# Solve and display
 +
sol = sym.solve(eqns, unks)
 +
printout(sol)
  
 +
# Perform substitutions
 +
subsol = makesubs(sol, knowns, subval)
 +
printout(subsol)
 +
</syntaxhighlight>
  
-->
+
== Google Colab and Personal Modules ==
<!--
+
Google Colab runs your files in the cloud; if you have extra modules that you need to access, you will need to make sure they are in the space that Google Colab is looking at.  The following are two different ways to go about this:
  
===Multiple and Dependent Substitution Lists===
+
=== Grab on the fly from GitHub ===
If you have several sets of equations you want to use for substitution
+
If you have a few modules that you need, you can ask Google Colab to see if it already knows what they are and, if not, to grab the text from a GitHub file and write it to a new file in your GitHub space.  For instance, if you need access to the methods in the<code>sym_helper.py</code>, which lives at
- including some which are dependent on values set in other equations,
+
  https://tinyurl.com/mrgdemos/SymPy_Files/sym_helper.py</code>
you can still use <code>subs</code> -- you just need to be careful about the
+
you can get it with the following lines of code:
order of the substitutions. As an example, imagine some variable:
+
<syntaxhighlight lang=python>
<center><math>
+
# Import the helper functions
\begin{align}
+
try:
m&=p+q
+
  from sym_helper import *
\end{align}
+
except: # Get sym_helper.py from GitHub
</math></center>
+
  import requests
where
+
  url = 'https://tinyurl.com/mrgdemos/SymPy_Files/sym_helper.py'
<center><math>
+
  open('sym_helper.py', 'w').write(requests.get(url).text)
\begin{align}
+
  from sym_helper import *
p&=r*s\\
+
</syntaxhighlight>
q&=t-u
 
\end{align}
 
</math></center>
 
and
 
<center><math>
 
\begin{align}
 
r&=1 & t&=3\\
 
s&=2 & u&=4
 
\end{align}
 
</math></center>
 
To get ''m'' in terms of ''r'', ''s'', ''t'', and ''u'', you could write:
 
<source lang=text>
 
MyEqn:=m=p+q;
 
SubsList1:=p=r*s, q=t-u;
 
subs(SubsList1, MyEqn);
 
</source>
 
If you want to get ''m'''s numerical value, you must ''first'' get ''m''
 
in terms of ''r'', ''s'', ''t'', and ''u'', and ''then'' you can substitute
 
in the numbers for those variables. Specifically:
 
<source lang=text>
 
MyEqn:=m=p+q;
 
SubsList1:=p=r*s, q=t-u;
 
SubsList2:=r=1, s=2, t=3, u=4;
 
subs(SubsList1, SubsList2, MyEqn);
 
</source>
 
 
 
Putting the equations in the wrong order will end up yielding an
 
answer that is still in terms of  ''r'', ''s'', ''t'', and ''u''.  The reason
 
is that <code>subs</code> ''only'' makes substitutions into the last entry
 
in the argument list.
 
 
 
Sometimes, you will need to take equations out of a set of brackets to
 
use them. For example, assume that you have some
 
variable you want to calculate called </code>alpha</code>, which has a formula
 
of:
 
<center><math>
 
\begin{align}
 
\alpha&=x+y+z
 
\end{align}
 
</math></center>
 
You can put in the solutions for ''x'', ''y'', and ''z'' to get <code>alpha</code>
 
in terms of those characters. What makes
 
this a bit difficult is that <code>MySoln</code> is given as a
 
single-row matrix and <code>subs</code> just wants the equations
 
themselves. To extract only the equations, you can write:
 
<source lang=text>
 
subs(MySoln[1][], Vals, ThingToSubInFor)
 
</source>
 
Go ahead and add the line
 
<source lang=text>
 
subs(MySoln[1][], Vals, alpha)
 
</source>
 
to the end of your worksheet.
 
  
Again - the order is important - you need to first substitute in the
+
=== Grab from a folder on Google Drive ===
equations for the variables higher in the dependency list, ''then'' give values to the
+
If you have the module you need stored in your Google Drive, you can ask Google Colab to grab it from there.  There are several steps to this:
known quantities, then substitute all that into whatever is in the
+
# Decide where all your files are going to go.  Typically, Google Colab will create a folder called "Colab Notebooks" on your Google Drive.  Best bet is to put everything there in some organized way.
final argument of <code>subs</code>.  
+
# Decide if you want to have a common folder for help files.  For EGR 224 in Spring of 2023, I have a folder called "Helpers" inside my "Colab Notebooks" folder, and I am planning to store all helper files there.
Run the entire script and make sure that <math>\alpha</math> is  
+
# Copy the helper files you need into that folder.  The sym_help.py file will be available on Sakai in the Resources section.  You can download it to your computer and then upload it to your Google Colab/Helpers folder using the web interface or you can install the Google Drive application for you computer so that you have direct access to your Google Drive files from Explorer / Finder.
<math>\frac{1}{3}</math> when everything gets substituted in.
+
# Decide where your notebook is going to be.  The examples on this page are all in a folder I called "BasicLinalg" that is inside the "Colab Notebooks" folder.  Note that whenever Colab creates a new notebook, it puts it in "Colab Notebooks" - you can move it wherever you want it to be later.
 +
# In your notebook, you will need to do three things to get access to your Helper folder, to import functions from the helper file, and to have the notebook save things in the folder for the notebook:
 +
## Connect the drive:<syntaxhighlight lang=python>
 +
from google.colab import drive
 +
drive.mount('/content/drive')</syntaxhighlight>Note that you will generally need to give Google permission to access your drive.
 +
## Change directories and get helper functions:<syntaxhighlight lang=python>
 +
%cd /content/drive/MyDrive/Colab\ Notebooks/Helpers
 +
from sym_helper import *</syntaxhighlight>Note that the % in front of a command issues the command to your operating system; cd means "change directory."  Your Google Drive is mounted at /context/drive/MyDrive, so this changes the directory to the Helpers folder inside of the Colab Notebooks folder.
 +
## Change directories to where your notebook will live:<syntaxhighlight lang=python>
 +
%cd /content/drive/MyDrive/Colab\ Notebooks/BasicLinalg
 +
</syntaxhighlight>Among other things, this will ensure that any files your notebook creates (graphs, for instance) will get saved to the right place in your Google Drive and not to the cloud.
  
==Cleaning Things Up==
 
Many times, Maple will produce an expression that is more complicated
 
than it needs to be.  To get what it considers to be the simplest
 
form, use the '''simplify(expand( ))''' compound function.  The '''expand''' will take the expression and represent it using as many
 
simple terms as necessary while '''simplify''' will recombine them in
 
the most compact form.  Finally, to get a decimal value, use the '''evalf[N]( )''' function, where '''N''' represents the number of
 
decimal digits to use.  For example, 
 
<source lang=text>
 
simplify(expand(alpha))
 
</source>
 
will produce the most symbolically simplified version of <math>\alpha</math> while
 
<source lang=text>
 
evalf[8](subs(Vals, alpha))
 
</source>
 
will produce a floating point result for <math>\alpha</math>.  With practice, you
 
will see how best to combine <code>evalf</code>, <code>simplify</code>, and <code>expand</code> to get the form of answer you want.
 
  
==Memory Issues==
 
A major issue to consider with Maple is its memory.  At the end of the worksheet above,
 
there are several variables that are defined, including ''x'', ''y'', and
 
''z''.  If you go back near the beginning, click in the line where '''eqn1''' is defined, and hit return, you will notice that where ''x'',
 
''y'', and ''z'' were before, their symbolic solutions from much further
 
down the worksheet are being used.  This is why the '''restart'''
 
command is so helpful - if you need to to run a worksheet again, it is
 
best to always start from scratch.  A shortcut for running an entire
 
worksheet is the !!! button at the top of the window.
 
-->
 
 
<!--
 
<!--
 
== Examples ==
 
== Examples ==

Latest revision as of 02:19, 23 January 2023

Introduction

This page focuses on using SymPy to find both the symbolic and the numeric solutions to equations obtained from electric circuits. The examples are available via a Google Drive at Basic Linear Algebra. 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.

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. There is a finished example at the end of this section which also includes the commands to display different variables if you are using a script. Imagine you have the equation $$ax=d$$ and you want to solve for $$x$$. 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

Declare Variables

You will need to let Python know what your variables are. There are several ways to do this; the easiest is to use the sym.var() command with a string containing the variables you want separated by spaces:

sym.var('a x d')

Define Equations

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 $$ax=d$$, you can write that in code as:
eqn1 = sym.Eq(a * x, d)
  • 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 = a * x - d

Solve Equations

There are two fundamentally different ways to solve one equation. If you give the solver the equation, it will return a list with the solution in it:

soln1 = sym.solve(eqn1, x)
soln1

will produce a list that contains $$d/a$$. On the other hand, if you give the solver a list with an equation in it, it will return a dictionary with the solution defined in it:

soln2 = sym.solve([eqn1], x)
soln2

will produce the dictionary $$\left\{x: \frac{d}{a}\right\}$$. Whenever you have more than one equation, you will need to collect the equations in a list or a tuple and the solver will return a dictionary. Given that, it is generally a good idea to always give the solver a list for the equations and a list for the unknowns, even if there is only one of each.

soln = sym.solve([eqn1], [x])
soln

Make Substitutions

Now that you have symbolic answers, you can make numerical substitutions for those symbols using the subs command. You will need to pull the appropriate definition out of the dictionary and then apply the substitutions to it. For example, to see what $$x$$ is when $$d$$ is 10, you can write:

soln[x].subs(d, 10)

If you want to see multiple substitutions, you can put the substitution pairs in tuples or lists that are contained in a tuple or list:

soln[x].subs([[a, 3], [d, 10]])

Complete Example

The code below has an additional piece, which is importing IPython.display as disp. This will allow you to display symbols in a formatted fashion within a script.

# Import sympy
import sympy as sym

# Print pretty
sym.init_printing()

# Use disp.display to use formatted printing
import IPython.display as disp
show = disp.display

# Declare symbols 
sym.var('a x d')

# Equations
eqn1 = sym.Eq(a * x, d)

# Solve
soln1 = sym.solve(eqn1, x)
show(soln1)

soln2 = sym.solve([eqn1], x)
show(soln2)

soln = sym.solve([eqn1], [x])
show(soln)

show(soln[x].subs(d, 10))

show(soln[x].subs([[a, 3], [d, 10]]))



Helper Functions

As you can see in the example above, there are some processes that look like they might get a little complicated as the number of equations increases. Given that, here is a set of helper functions that you may want to import into your script or notebook. Note: if you are using Google Colab, accessing secondary files takes a bit of work - this will be explained later. These functions are all in a file called sym_helper.py.

Imports

The sym_helper.py will need to import the sympy module. It will also attempt to import the IPython.display module. in order to make sym_helper.py compatible with applications like Trinket (which does not use IPython.display), sym_helper.py will see if it can import the module and then, based on that, decide how it will show things:

import sympy as sym

try:
  import IPython.display as disp
  show = disp.display
except:
  show = sym.pretty_print


Printing Out Solution Dictionary

Next, we may want help printing out the solutions to a set of equations. As long as you give solve a list of equations, you will get back a dictionary. If you are running your code in a notebook or in the console, typing soln by itself and hitting return will let you see a pretty version of the solution. If you put all this in a script, however, soln will not show up at all... You can put print(soln) in a script, but it will not be pretty. To make it pretty requires a little work since the solution set is a dictionary with the variables as keys. To help, here is a Python function called printout you can use:

def printout(solution, variables=None, fp=False):
  # Default case: print all variables
  if variables==None:
    variables = tuple(solution.keys())
  # Fixes issue if solving for a single item
  if not isinstance(variables, (list, tuple)):
    variables = [variables]
  for var in variables:
    show(var)
    if fp:
      show(solution[var].evalf(fp))
    else:
      show(solution[var])
    print()

This will go through the dictionary and print the variable followed by the solution for that variable. If you have substituted in numbers, set fp to an integer to do floating point evaluation on the answers. While a function like this may not seem necessary at the moment (with only one unknown variable, this feels like a lot of overhead), as you start solving problems with more and more unknowns it will be nice to have!

Making Substitutions

As you start working with more equations, you may end up having more and more symbols that you will want to be able to replace with numerical values later. Having to create a list of lists with the substitutions every time can certainly get old! Here is a function that will take a solution dictionary, a list of variables for which to substitute, and a list of the values to use for those variables. It returns a dictionary with the substitutions in place. Note that if it gets a list of equations rather than a dictionary, it will convert the list to a dictionary first. This is not something that is needed for linear algebra but will be something that is needed when setting up and solving simultaneous differential equations.

def makesubs(solution, vartosub, valtosub):
  subsol = solution.copy()
  sublist = list(zip(vartosub, valtosub))
  if type(subsol)==list:
    subsol = makedict(subsol)
      
  for var in subsol:
    subsol[var] = subsol[var].subs(sublist)
  return subsol

def makedict(sollist):
  d = {}
  for eqn in sollist:
    d[eqn.lhs] = eqn.rhs
  return d

Complete Example Extended, with Helpers

By carefully setting up your symbolic variables and by using the helper functions above, you can organize your code in a way that will be much more flexible for future assignments. First, instead of creating all of your symbolic variables at once, you can divide them into a collection of unknowns and a collection of knowns -- that will make it easier when you want Python to solve your symbolic equations. Second, you can create a list of the substitution values that you will be using later.

Note: the code below assumes that sym_helper.py is accessible. If you are using Google Colab, please see the section below on how to get that to work.

# Imports
import sympy as sym
from sym_helper import *

# Print pretty
sym.init_printing()

# Declare symbols (unknowns in first group, knowns in second)
# Create a list of future numerical values for knowns
unks = sym.var('x')
knowns = sym.var('a d')
subval = [10, 3]

# Equations
eqn1 = sym.Eq(a * x, d)

# Collect equations
eqns = [eqn1]

# Solve and display
sol = sym.solve(eqns, unks)
printout(sol)

# Perform substitutions
subsol = makesubs(sol, knowns, subval)
printout(subsol)

Google Colab and Personal Modules

Google Colab runs your files in the cloud; if you have extra modules that you need to access, you will need to make sure they are in the space that Google Colab is looking at. The following are two different ways to go about this:

Grab on the fly from GitHub

If you have a few modules that you need, you can ask Google Colab to see if it already knows what they are and, if not, to grab the text from a GitHub file and write it to a new file in your GitHub space. For instance, if you need access to the methods in thesym_helper.py, which lives at

https://tinyurl.com/mrgdemos/SymPy_Files/sym_helper.py

you can get it with the following lines of code:

# Import the helper functions
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 *

Grab from a folder on Google Drive

If you have the module you need stored in your Google Drive, you can ask Google Colab to grab it from there. There are several steps to this:

  1. Decide where all your files are going to go. Typically, Google Colab will create a folder called "Colab Notebooks" on your Google Drive. Best bet is to put everything there in some organized way.
  2. Decide if you want to have a common folder for help files. For EGR 224 in Spring of 2023, I have a folder called "Helpers" inside my "Colab Notebooks" folder, and I am planning to store all helper files there.
  3. Copy the helper files you need into that folder. The sym_help.py file will be available on Sakai in the Resources section. You can download it to your computer and then upload it to your Google Colab/Helpers folder using the web interface or you can install the Google Drive application for you computer so that you have direct access to your Google Drive files from Explorer / Finder.
  4. Decide where your notebook is going to be. The examples on this page are all in a folder I called "BasicLinalg" that is inside the "Colab Notebooks" folder. Note that whenever Colab creates a new notebook, it puts it in "Colab Notebooks" - you can move it wherever you want it to be later.
  5. In your notebook, you will need to do three things to get access to your Helper folder, to import functions from the helper file, and to have the notebook save things in the folder for the notebook:
    1. Connect the drive:
      from google.colab import drive
      drive.mount('/content/drive')
      
      Note that you will generally need to give Google permission to access your drive.
    2. Change directories and get helper functions:
      %cd /content/drive/MyDrive/Colab\ Notebooks/Helpers
      from sym_helper import *
      
      Note that the % in front of a command issues the command to your operating system; cd means "change directory." Your Google Drive is mounted at /context/drive/MyDrive, so this changes the directory to the Helpers folder inside of the Colab Notebooks folder.
    3. Change directories to where your notebook will live:
      %cd /content/drive/MyDrive/Colab\ Notebooks/BasicLinalg
      
      Among other things, this will ensure that any files your notebook creates (graphs, for instance) will get saved to the right place in your Google Drive and not to the cloud.