Algebraic Equations with SymPy (using the Equation
class)¶
This class defines relations that all high school and college students would recognize as mathematical equations. They consist of a left hand side (lhs) and a right hand side (rhs) connected by the relation operator "=". In addition it sets some convenient defaults and provides some useful controls of output formatting that may be useful even if you do not use the Equation
class (see examples below).
This class should not be confused with the Boolean class Equality
(abbreviated Eq
) which evaluates to True
or False
if it can determine the validity of the equality. See the last example in Utility operations.
This class is intended to allow using the mathematical tools in SymPy to rearrange equations and perform algebra in a stepwise fashion using as close to standard mathematical notation as possible. In this way more people can successfully perform algebraic rearrangements without stumbling over missed details such as a negative sign.
Create an equation with the call Equation(lhs,rhs)
, where lhs
and rhs
are any valid Sympy expression. Eqn(...)
is a synonym for Equation(...)
.
In Jupyter/IPython there is also a shorthand eq_name =@ lhs = rhs
.
from algebra_with_sympy import * # Automatically imports sympy
print("This notebook is running Algebra_with_Sympy version " + str(algwsym_version)+".")
This notebook is running Algebra_with_Sympy version 1.1.1.
Index:¶
General Examples | Rearranging an equation | Simplification and Expansion | Apply operations to only one side | Substituting in numbers and units | Multistep rearrangement | Differentiation | Integration | Combining Equations | Output options | Control Interpretation of Integers | Utility operations | Errors tested for | Automatic Solutions | Check version
General Examples¶
# declare some sympy symbolic variables
var('a b c')
(a, b, c)
# Create a simple equation.
Eqn(a,b/c)
# Shorthand available in Jupyter and IPython
=@ a = b/c
# Apply a SymPy function to an equation.
log(Eqn(a,b/c))
# Using shorthand
eq1 =@ a = b/c
log(eq1)
# Give an equation a name and manipulate it.
t=Eqn(a,b/c)
exp(t)
sin(t)
exp(log(t))
# Reverse order does not simplify, because t could be non-real numbers.
log(exp(t))
a*log(t)
log(t)*a
c*t
t*c
t/b
t*c/a
c*t/a
c/a*t
t-a
a-t
t%c
c%t
sqrt(t)
root(t,3)
root(t,3,1)
root(t,3,1).subs({b:8,c:27})
root(t,3,0)
root(t,3,2)
r=Eqn(b**2,a/c**2)
r
sqrt(r)
t**(1/2)
(r**(1/2)).subs({a:2,c:4})
# .evalf() also works
(r**(1/2)).evalf(4,{a:2,c:4})
sqrt(r).subs({a:2,c:4})
var('mat')
mattst=Eqn(mat,Matrix([[2,6],[3,5]]))
mattst
mattst.apply(transpose)
trsp = transpose(mattst)
trsp
# respects exponentiation of matrix
exp(mattst)
mattst*trsp
Simplification and Expansion¶
var('x')
f = Eqn(x**2 - 1, c)
f
f/(x+1)
(f/(x+1)).simplify()
simplify(f/(x+1))
(f/(x+1)).expand()
expand(f/(x+1))
# .factor() works
f.factor()
factor(f)
f2 = f+a*x**2+b*x +c + a*x
f2
# .collect() works
f2.collect(x)
f2.collect(a)
collect(f2,a)
#Arbitrary functions can operate on both sides of an equation using
# .apply(funcname, *args, **kwargs)
def addsquare(expr):
return expr+expr**2
f2.apply(addsquare)
# or using normal Python call syntax
addsquare(f2)
# expansion
addsquare(f2).expand().collect(x)
expand(addsquare(f2)).collect(x)
Apply operations to only one side¶
poly = Eqn(a*x**2 + b*x + c*x**2, a*x**3 + b*x**3 + c*x)
poly
poly.applyrhs(factor,x)
poly.applylhs(factor)
poly.applylhs(collect,x)
# also works with user defined python functions
t.applyrhs(addsquare)
t.apply(addsquare, side = 'rhs')
t.applylhs(addsquare)
# Inaddition to `.apply...` there is also the less general `.do`,
# `.dolhs`, `.dorhs`, which only works for operations defined on the
# `Expr` class (e.g.``.collect(), .factor(), .expand()``, etc...).
poly.dolhs.collect(x)
poly.dorhs.collect(x)
poly.do.collect(x)
poly.dorhs.factor()
# Errors are raised if your try to call a function this way
try:
poly.do.exp()
except AttributeError as e:
print(e)
Expressions in the equation have no attribute `exp`. Try `.apply(exp, *args)` or pass the equation as a parameter to `exp()`.
Rearranging an equation¶
# Ideal Gas Law
var('p V n R T')
eq1=Eqn(p*V,n*R*T)
eq1
eq2 =eq1/V
eq2
eq3 = eq1/p
eq3
eq4 = eq1/n/R
eq4
eq4.swap
Substituting in numbers and units¶
Algebra_with_Sympy has a convenience operation units(...)
which takes a string of space
separated symbols to use as units. This simply declares the symbols
to be positive, making them behave as units. This does not create units
that know about conversions, prefixes or systems of units. This lack
is on purpose to provide units that require the user to worry about
conversions (ideal in a teaching situation). To get units with built-in
conversions see sympy.physics.units
.
units('L atm mol K') # Liters, atmospheres, moles and Kelvin
eq2.subs({R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,V:24.0*L})
eq3.subs({R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,p:3.00*atm}).n(3)
Multistep rearrangement¶
# Nernst Equation
var('E Eo z F Q')
N1=Eqn(E,Eo-(R*T/z/F)*ln(Q))
N1
N2 = N1+(R*T/z/F)*ln(Q)
N2
N3 = (N2 -E)*F*z
N3
N4 = N3/T/R
N4
N5=exp(N4)
N5
Differentiation¶
q=Eqn(a*c, b/c**2)
q
# Notice the assumption is that symbols on the lhs depend on the rhs
diff(q,b)
# unless one of them matches the symbol of differentiation
diff(q,c)
diff(q,c,2)
# If you specify all at once it has to assume order of differentiation matters.
diff(q,c,b)
# If you specify the order explicitly it works as expected.
diff(diff(q,c),b)
diff(diff(q,b),c)
diff(log(q),b)
# Using `.apply` or `.do` with differentiation always assumes all symbols are independent.
q.apply(diff,b)
q.do.diff(b)
dt2 = Eqn(a,b*sin(c))
diff(dt2,c)
Integration¶
integrate(q,b,side='rhs')
integrate(q,b,side='lhs')
# Make a pretty statement of integration from an equation
Eqn(Integral(q.lhs,b),integrate(q,b,side='rhs'))
q.apply(integrate, b)
q.do.integrate(b)
# Integration of each side with respect to different variables
q.dorhs.integrate(b).dolhs.integrate(a)
Combining Equations (math with equations)¶
This code operates/combines lhs
with lhs
and rhs
with rhs
.
q
t
q+t
q/t
t/q
q%t
t%q
t**q
q**t
# Example that might be used to solve an Ideal Gas law problem
var('p_1 p_2 T_1 T_2')
IdG1 = eq1.subs({p:p_1,T:T_1})
IdG1
IdG2 = eq1.subs({p:p_2,T:T_2})
IdG1/IdG2
IdG1/IdG2*p_2
# Alternative route
soln1 = (IdG1/R/T_1).swap
soln1
# Note that subs will accept equations as parameters
soln2 = IdG2.subs(soln1)
soln2
(soln2*T_1/T_2/V).swap
Output options¶
# Equation Labels Default
algwsym_config.output.label
True
# Turn off
algwsym_config.output.label = False
IdG1
# Turn on
algwsym_config.output.label = True
IdG1
# Show Code Default
algwsym_config.output.show_code
False
# Turn on
algwsym_config.output.show_code = True
IdG1
Code version: Equation(V*p_1, R*T_1*n)
# This also shows code for Sympy expressions
(cos(a)+I*sin(a)).simplify()
Code version: exp(I*a)
# Turn off
algwsym_config.output.show_code = False
IdG1
# Display equation wrapped as a Latex equation in `\begin{equation}...\end{equation}`
algwsym_config.output.latex_as_equations = True
IdG1
# Turn it back off
algwsym_config.output.latex_as_equations = False
IdG1
Control Interpretation of Integers¶
# Default behavior. NOTICE THAT IT DOES NOTHING TO INTEGERS THAT ARE NOT PART OF A SYMPY EXPRESSION.
print("Integers as exact flag: " + str(algwsym_config.numerics.integers_as_exact))
2/3
Integers as exact flag: True
0.6666666666666666
# HOWEVER, IN SYMPY EXPRESSIONS INTEGERS ARE SPECIAL.
var('x')
quad =@ x**2 + 3/2*x + 1 = 0
quad
solve(quad,x)
# Turn off treating integers as exact
unset_integers_as_exact()
# AGAIN THIS SETTING HAS NO IMPACT ON INTEGERS OUTSIDE OF SYMPY EXPRESSIONS
print("Integers as exact flag: " + str(algwsym_config.numerics.integers_as_exact))
2/3
Integers as exact flag: False
0.6666666666666666
# INTEGERS ARE NOT EXACT EVEN IN SYMPY EXPRESSIONS WHEN `unset_integers_as_exact()` has been called.
quad =@ x**2 + 3/2*x + 1 = 0
quad
solve(quad,x)
# Turn treating integers as exact on again
set_integers_as_exact()
print("Integers as exact flag: " + str(algwsym_config.numerics.integers_as_exact))
2/3
Integers as exact flag: True
0.6666666666666666
quad =@ x**2 + 3/2*x + 1 = 0
quad
Utility operations¶
t
t.reversed
t.swap
t.lhs
t.rhs
t.check()
t.subs({a:1/2,b:1,c:2}).check()
TF = t.as_Boolean()
TF
TF.subs({a:1,b:2,c:3})
# Getting everything on one side
t - t.rhs
t - t.lhs
t.rewrite(Add)
Errors tested for¶
# Test for errors
try:
Max(Eqn(a,b/c), 2) # This is just an example of an error raised by an incompatible
# operation.
except ValueError as e:
print(e)
print('----')
try:
integrate(Eqn(a,b/c),b)
except ValueError as e:
print(e)
print('----')
try:
integrate(Eqn(a,b/c),b,side='right')
except AttributeError as e:
print(e)
print('----')
try:
Eqn(FiniteSet(a), FiniteSet(b, c))
except TypeError as e:
print(e)
print('----')
try:
Eqn(a,b/c).do.log() # trying to apply a function as an attribute.
except AttributeError as e:
print(e)
print('----')
# Use check to find simple logic/typographical errors
print(Eqn(2.0,3.0).check())
Eqn(2,2.0).check()
The argument 'a = b/c' is not comparable. ---- You must specify `side="lhs"` or `side="rhs"` when integrating an Equation ---- `side` must equal "lhs" or "rhs". ---- lhs and rhs must be valid sympy expressions. ---- Expressions in the equation have no attribute `log`. Try `.apply(log, *args)` or pass the equation as a parameter to `log()`. ---- False
Equation(pi*(I+2), pi*I+2*pi).check()
Eqn(a,a+1).check()
Equation(pi*(I+2), pi*I+2*pi+I).check()
Eqn(cos(a)+I*sin(a),exp(I*a)).check()
_.simplify()
Eqn(cos(pi)+I*sin(pi),exp(I*pi))
_.check()
Atomatic solutions (sympy.solve)¶
eqtosolve =@ a - b = c/a
eqtosolve
solve(eqtosolve,a)
solve(eqtosolve.lhs-eqtosolve.rhs, a)
solve(eqtosolve,c)
solve(eqtosolve,b)
solve(N1,Q)
# A system of equations with multiple solutions
var ('x y', real = True)
eq1 =@ abs(2*x + y) = 3
eq2 = Eqn(abs(x + 2*y), 3)
solve([eq1,eq2],x,y)
# Very experimental use of solveset. It has the nice property that multiple solutions pretty print well.
# Unfortunately, I cannot get systems of equations to work.
solveset(eq1,x,domain=S.Reals)
Controling what outputs of solve look like¶
algwsym_config.output.show_code = True
solve([eq1,eq2],x,y)
Code version: FiniteSet(FiniteSet(Equation(x, -3), Equation(y, 3)), FiniteSet(Equation(x, -1), Equation(y, -1)), FiniteSet(Equation(x, 1), Equation(y, 1)), FiniteSet(Equation(x, 3), Equation(y, -3)))
algwsym_config.output.show_code = False
solve([eq1,eq2],x,y)
algwsym_config.output.show_code = True
solns = solve(eqtosolve,a)
solns
Code version: FiniteSet(Equation(a, b/2 - sqrt(b**2 + 4*c)/2), Equation(a, b/2 + sqrt(b**2 + 4*c)/2))
solve(eqtosolve.lhs-eqtosolve.rhs, a)
Code version: FiniteSet({a: b/2 - sqrt(b**2 + 4*c)/2}, {a: b/2 + sqrt(b**2 + 4*c)/2})
list(solns)[0]
Code version: Equation(a, b/2 - sqrt(b**2 + 4*c)/2)
algwsym_config.output.show_code = False
list(solns)[1]
Setting solve output to Python lists instead of Sympy FiniteSets¶
Note this prevents pretty-printed output.
algwsym_config.output.solve_to_list = True
solve(eqtosolve, a)
[Equation(a, b/2 - sqrt(b**2 + 4*c)/2), Equation(a, b/2 + sqrt(b**2 + 4*c)/2)]
solve(eqtosolve.lhs-eqtosolve.rhs, a)
[{a: b/2 - sqrt(b**2 + 4*c)/2}, {a: b/2 + sqrt(b**2 + 4*c)/2}]
solve([eq1,eq2],x,y)
[[Equation(x, -3), Equation(y, 3)], [Equation(x, -1), Equation(y, -1)], [Equation(x, 1), Equation(y, 1)], [Equation(x, 3), Equation(y, -3)]]
Checking version of Algebra with Sympy¶
algwsym_version
'1.1.1'
from algebra_with_sympy.version import __version__ as spa_version
spa_version
'1.1.1'