SymPy/Simultaneous Equations

From PrattWiki
Revision as of 21:03, 16 January 2023 by DukeEgr93 (talk | contribs)
Jump to navigation Jump to search

Introduction

This page focuses on using SymPy to find both the symbolic and the numeric solutions to equations obtained from electric circuits.

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 general 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]]))




Initialization

To use SymPy, you will need to import the module. The module comes with the Anaconda distribution of Python, but if you have a different installation, you may need to get the module first. Also, you will likely want to use the "pretty printing" capabilities of SymPy, which requires initializing printing. These two steps can be done with:

import sympy as sym

sym.init_printing()

Declaring Symbols

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 too. The sym.var() function accepts a space or comma-separated string of variables and will create symbolic entities based on the code in the string. For instance:

sym.var('x, y')

will create variables x and y

Defining Equations

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:
eqn1 = x + y

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:

eqn2 = sym.Eq(x - y, 3)

Of course, you could also rearrange the expression by moving the right side to the left, in which case:

eqn2 = x - y - 3

would work!


Solving Equations With SymPy

To solve a system of linear algebra equations, you can use the sym.solve(eqns, vars) 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:

soln = sym.solve((eqn1, eqn2), (x, y))

If you run this in the console, soln will print out pretty. If you put all this in a script, 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 you can add to your code:

def printout(solution, variables, fp=False):
    if type(variables)!=tuple: % fixes issue with single symbols
        variables = variables,
    for var in variables:
        sym.pretty_print(var)
        if fp:
            sym.pretty_print(solution[var].evalf(fp))
        else:
            sym.pretty_print(solution[var].simplify())
    print()

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

printout(soln, (x, y))

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:

$$ \begin{align*} ax+by&=c\\ dx+ey&=f \end{align*}$$

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:

import sympy as sym

sym.init_printing()

def printout(solution, variables, fp=False):
    for var in variables:
        sym.pretty_print(var)
        if fp:
            sym.pretty_print(solution[var].evalf(fp))
        else:
            sym.pretty_print(solution[var].simplify())
    print()

x, y = sym.var('x, y')
a, b, c, d, e, f = sym.var('a, b, c, d, e, f')

eqn1 = a*x + b*y - c # shows moving right to left side
eqn2 = sym.Eq(d*x+e*y, f) # Shows using sym.Eq to give left and right

soln = sym.solve((eqn1, eqn2), (x, y))
print(soln)
printout(soln, (x, y))

The printed version gives:

{x: (-b*f + c*e)/(a*e - b*d), y: (a*f - c*d)/(a*e - b*d)}

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:

import sympy as sym

sym.init_printing()

sym.var('a, b, c, d, e, f')
a = b + c * d + e ** f

You have created $$a=b+cd+e^f$$. If you want to know what this would be if $$b=2$$, you could write:

a.subs(b, 2)

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:

a.subs( ( (b,2),(c,3) ) )
a.subs( [ [b,2],[c,3] ] )

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:

varstosub = [b, c]
valstosub = [2, 3]
zipsub = zip(varstosub, valstosub)
a.subs(zipsub)

There will be times when you want to make the same substitions to several symbolic representations at once. Given that, here is another handy function for making substitutions that takes a list of variables and a list of values:

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

Notes

For the printout and makesubs commands, if there is only one variable involved, the "list" of variables will be a single item and not an iterable, you may need to put [ ] around the variable "list" in the function call to get it to work.