Expression Manipulation
- class sympy_equation.algebraic_equation.Equation(lhs, rhs, **kwargs)
This class defines an equation with a left-hand-side (lhs) and a right- hand-side (rhs) connected by the “=” operator (e.g. p*V = n*R*T), which supports mathematical operations like addition, subtraction, multiplication, divison and exponentiation.
In particular, this class is intended to allow using the mathematical tools in SymPy to rearrange equations and perform algebra in a stepwise fashion.
Create an equation with the call
Equation(lhs,rhs), wherelhsandrhsare any valid Sympy expression.Eqn(...)is a synonym forEquation(...).- Parameters:
- lhs
Expr - rhs
Expr
- lhs
- Attributes:
lhsExprReturns the lhs of the equation.
rhsExprReturns the rhs of the equation.
swapEquationSwaps the lhs and the rhs.
Methods
apply(func, *args)
Apply a function
funcwith arguments*argsto both sides of the equation.applylhs(func, *args)
Apply a function
funcwith arguments*argsto the lhs of the equation.applyrhs(func, *args)
Apply a function
funcwith arguments*argsto the rhs of the equation.as_Boolean()
Convert the
Equationto anEquality.as_expr()
Convert the
Equationto a symbolic expression of the form ‘lhs - rhs’.check(**kwargs)
Forces simplification and casts as
Equalityto check validity.to_expr()
Alias of
as_expr().to_lhs()
Return a new equation with the form
LHS - RHS = 0.to_rhs()
Return a new equation with the form
0 = RHS - LHS.cross_multiply()
Given and equation
Equation(a/b, c/d), cross-multiply the members in order to get a newEquation(a*d, b*c).Examples
>>> from sympy import * >>> from sympy_equation import Eqn, Equation >>> a, b, c, d, e, x = symbols('a, b, c, d, e, x')
Mathematical operations between an equation and a scalar value:
>>> eq = Eqn(a, b/c) >>> eq Equation(a, b/c) >>> eq + d Equation(a + d, b/c + d) >>> eq - d Equation(a - d, b/c - d) >>> eq * c Equation(a*c, b) >>> c * eq Equation(a*c, b) >>> eq / c Equation(a/c, b/c**2) >>> c / eq Equation(c/a, c**2/b) >>> eq ** d Equation(a**d, (b/c)**d) >>> d ** eq Equation(d**a, d**(b/c))
Mathematical operations between two equations:
>>> e1 = Eqn(a, b + c) >>> e2 = Eqn(e, d - a) >>> e1 + e2 Equation(a + e, -a + b + c + d) >>> e1 - e2 Equation(a - e, a + b + c - d) >>> e1 * e2 Equation(a*e, (-a + d)*(b + c)) >>> e1 / e2 Equation(a/e, (b + c)/(-a + d)) >>> e1 ** e2 Equation(a**e, (b + c)**(-a + d))
Apply mathematical functions to the equation:
>>> eq.apply(exp) Equation(exp(a), exp(b/c)) >>> def add_square(eqn): ... return eqn+eqn**2 ... >>> add_square(eq) Equation(a**2 + a, b**2/c**2 + b/c) >>> eq.apply(add_square) Equation(a**2 + a, b**2/c**2 + b/c) >>> eq.applylhs(add_square) Equation(a**2 + a, b/c) >>> eq.applyrhs(add_square) Equation(a, b**2/c**2 + b/c)
Expression manipulation:
>>> f = Eqn(x**2 - 1, c) >>> f Equation(x**2 - 1, c) >>> f/(x+1) Equation((x**2 - 1)/(x + 1), c/(x + 1)) >>> (f/(x+1)).simplify() Equation(x - 1, c/(x + 1)) >>> simplify(f/(x+1)) Equation(x - 1, c/(x + 1)) >>> (f/(x+1)).expand() Equation(x**2/(x + 1) - 1/(x + 1), c/(x + 1)) >>> expand(f/(x+1)) Equation(x**2/(x + 1) - 1/(x + 1), c/(x + 1)) >>> factor(f) Equation((x - 1)*(x + 1), c) >>> f.factor() Equation((x - 1)*(x + 1), c) >>> eq3 = Eqn(2 * b + 2 * c, d - b) >>> eq3 Equation(2*b + 2*c, -b + d) >>> eq3.applylhs(collect_const, 2) Equation(2*(b + c), -b + d)
In addition to
.apply...there is also the less general.do,.dolhs,.dorhs, which only works for operations defined on theExprclass (e.g.``.collect(), .factor(), .expand()``, etc…):>>> poly = Eqn(a*x**2 + b*x + c*x**2, a*x**3 + b*x**3 + c*x) >>> poly.dolhs.collect(x) Equation(b*x + x**2*(a + c), a*x**3 + b*x**3 + c*x) >>> poly.dorhs.collect(x) Equation(a*x**2 + b*x + c*x**2, c*x + x**3*(a + b)) >>> poly.do.collect(x) Equation(b*x + x**2*(a + c), c*x + x**3*(a + b)) >>> poly.dorhs.factor() Equation(a*x**2 + b*x + c*x**2, x*(a*x**2 + b*x**2 + c))
Substitutions and numerical evaluation:
>>> p, V, n, R, T = var('p V n R T') >>> L, atm, mol, K = var('L atm mol K', positive=True, real=True) # units >>> ideal_gas_law = Eqn(p * V, n * R * T) >>> pressure = ideal_gas_law / V >>> pressure.subs({R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,V:24.0*L}) Equation(p, 0.9334325*atm)
Evaluate up to n-digits:
>>> pressure.evalf(subs={R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,V:24.0*L}, n=2) Equation(p, 0.93*atm)
Substituting an equation into another equation:
>>> e3 = Eqn(a/b, c/d) >>> e4 = Eqn(d, (a + b) / c) >>> e3.subs(e4) Equation(a/b, c**2/(a + b))
Utility operations:
>>> eq Equation(a, b/c) >>> eq.reversed # or t.swap Equation(b/c, a) >>> eq.lhs a >>> eq.rhs b/c >>> eq.as_Boolean() Eq(a, b/c)
.check()to verify if the lhs is equal to the rhs. It is a convenience method for.as_Boolean().simplify():>>> Equation(pi*(I+2), pi*I+2*pi).check() True >>> Eqn(a,a+1).check() False
Convert an Equation to an expression of the form
lhs - rhs:>>> eq.to_expr() a - b/c
Cross multiply members of an equation:
>>> e5 = Eqn(a/b, c/d) >>> e5.cross_multiply() Equation(a*d, b*c) >>> e6 = Eqn(a/(b+c), 1) >>> e6.cross_multiply() Equation(a, b + c)
Differentiation is applied to both sides:
>>> q=Eqn(a*b, b**2/c**2) >>> q Equation(a*b, b**2/c**2) >>> diff(q,b) Equation(a, 2*b/c**2) >>> q.diff(c) Equation(0, -2*b**2/c**3)
Integration is applied to both sides:
>>> q=Eqn(a*c,b/c) >>> integrate(q,b) Equation(a*b*c, b**2/(2*c))
Integration of each side with respect to different variables:
>>> q.dorhs.integrate(b).dolhs.integrate(a) Equation(a**2*c/2, b**2/(2*c))
Solving equations:
>>> from sympy_equation import solve >>> eq = Eqn(a - b, c/a) >>> solve(eq,a) [Equation(a, b/2 - sqrt(b**2 + 4*c)/2), Equation(a, b/2 + sqrt(b**2 + 4*c)/2)] >>> solve(eq, b) [Equation(b, (a**2 - c)/a)] >>> solve(eq, c) [Equation(c, a**2 - a*b)]
- class sympy_equation.algebraic_equation._equation_config(*, human_text, integers_as_exact, latex_as_equations, show_label, solve_to_list, name)
This class implements the configuration options for the module.
Do not instantiate it directly, instead import the following:
from sympy_equation import equation_config
Then, set the appropriate attribute to the intended value, for example:
equation_config.integers_as_exact = True
- Attributes:
- show_labelBoolean
- default:
False
If True a label with the name of the equation in the python environment will be shown on the screen. Default to False.
- human_textBoolean
- default:
False
For text-based interactive environments, if the last line of a cell is the name of some equation, its execution will show the textual representation of the equation in the output. If
human_text=Truethe equation will be shown as “lhs = rhs”. IfFalse, it will be shown asEquation(lhs, rhs).
- solve_to_listBoolean
- default:
True
If
True, the results of a call tosolve([e1, e2, ...], v1, v2, ...)will return a Pythonlist, otherwise it returns a Sympy’sFiniteSet.Note: setting this True means that expressions within the returned solutions might not be pretty-printed in Jupyter and IPython.
- latex_as_equationsBoolean
- default:
False
If True any output that is returned as LaTex for pretty-printing will be wrapped in the formal Latex for an equation. For example rather than:
` \frac{a}{b}=c `the output will be:
` \begin{equation}\frac{a}{b}=c\end{equation} `In an interactive environment like Jupyter notebook, this effectively moves the equation horizontally to the center of the screen.
- integers_as_exactBoolean
- default:
False
If running in an IPython/Jupyter environment, preparse the content of a code line in order to convert integer numbers to sympy’s Integer. This can be handy when writing expressions containing rational number. For example, by settings this to
Truewe can write 2/3 which will be automatically converted to Integer(2)/Integer(3) which than SymPy converts to Rational(2, 3). IfFalse, no preparsing is done, and Python evaluates 2/3 to 0.6666667, which will then be converted by SymPy to a Float.Note: it is reccommended to set this options to
Trueonly when executing purely symbolic computations, not when using other numerical libraries, such as Numpy, because it will create hard to debug situations. Consider executing this:np.cos(np.pi / 4). Ifintegers_as_exact = True, this will raise an error because 4 is first replaced with sympy’s Integer(4), thennp.pi / Integer(4)becomes a symbolic expression andnp.cosis unable to evaluate it.
- class sympy_equation.utils.table_of_expressions(expr, **params)
Nicely print the arguments of a symbolic expression as a table with two columns: an index, and the argument itself. The index can later be used to retrive the argument we are interested in, without having to resort to pattern matching operations.
There are three modes of operation:
t = table_of_expressions(list/set of expressions): sort the list/set of expressions in a deterministic way and visualize the table on the screen. This is useful when dealing with results of pattern matching operations.t = table_of_expressions(expr, mode="args"): visualize the arguments of a symbolic expressions. These arguments are not sorted, which meanst[idx]returns the same expression asexpr.args[idx].t = table_of_expressions(expr, mode="nodes"): visualize the unique nodes of the expression tree, sorted in a deterministic way. This mode usessympy.postorder_traversal()in order to retrieve all nodes of the expression tree. The larger the expression, the greater the number of nodes. For large expressions there are two disadvantages to be aware of:screen space: if
auto_show=Truethe table will be automatically visualized on the screen. The larger the expression tree, the more time to show it on the screen and the more space will be used. This can be mitigated by filtering the table with theselectkeyword arguments (see examples below).memory usage.
- Attributes:
- exprParameter
- allow_None:
True
The symbolic expression whose arguments or nodes are to be shown.
- modeSelector
- default:
‘args’
- options:
[‘args’, ‘nodes’]
What to extract from the symbolic expression.
- selectList
- default:
[]
- item_type:
(sympy.core.expr.Expr, numbers.Number)
List of targets used to filter the table. The table is constructed by looping over
expressions. If an expression contains any of the targets, it will be shown on the table.
- use_latexBoolean
- default:
True
If True, a Markdown table containing latex expressions will be shown. Otherwise, a plain-text table will be shown.
- latex_printerCallable
- allow_None:
True
A function similar to sympy’s
latexthat generates the appropriate Latex representation for symbolic expressions whenuse_latex=True. If not provided, sympy’slatexwill be used.
- auto_showBoolean
- default:
True
If True, the table will be shown on the screen automatically after instantiation, or after editing the expr and has attributes. Otherwise, the
show()method must be executed manually in order to visualize the table.
- column_labelsList
- length:
2
- default:
[‘idx’, ‘exprs’]
Labels to be shown on the header of the table.
- expressionsList
- constant:
True
- default:
[]
- item_type:
sympy.core.basic.Basic
- readonly:
True
List of sub-expressions composing expr.
- selected_idxList
- constant:
True
- default:
[]
- item_type:
int
- readonly:
True
Get the indices of the expressions that were filtered by
select.
Examples
There are situations where we might be dealing with relatively complex and large expressions. Suppose the following expression is the result of a symbolic integration:
>>> from sympy import symbols, Pow >>> from sympy_equation import table_of_expressions >>> L, mdot, q, T_in, c_p, n, xi = symbols("L, mdot, q, T_in, cp, n, xi") >>> expr = L**2*mdot**2*q**2*(L*q + T_in*mdot*c_p)**(n*xi) + 2*L*T_in*mdot**3*c_p*q*(L*q + T_in*mdot*c_p)**(n*xi) + T_in**2*mdot**4*c_p**2*(L*q + T_in*mdot*c_p)**(n*xi) - mdot**(n*xi + 4)*(T_in*c_p)**(n*xi + 2)
This addition is composed of 4 terms. 3 of them share a common term that can be collected,
(L*q + T_in*mdot*c_p)**(n*xi).Let’s explore the first mode of operation of this class. Instead of typing that term directly and risk inserting typing errors, we can extract it with a pattern matching operation. For example, let’s find all powers:
>>> ton = table_of_expressions(expr.find(Pow), use_latex=False) idx | exprs ------|----------------------------- 0 | L**2 1 | T_in**2 2 | cp**2 3 | mdot**2 4 | mdot**3 5 | mdot**4 6 | q**2 7 | mdot**(n*xi + 4) 8 | (T_in*cp)**(n*xi + 2) 9 | (L*q + T_in*cp*mdot)**(n*xi)
The above output shows sub-expressions that are powers. The interested terms is located at index 9:
>>> expr.collect(ton[9]) -mdot**(n*xi + 4)*(T_in*cp)**(n*xi + 2) + (L*q + T_in*cp*mdot)**(n*xi)*(L**2*mdot**2*q**2 + 2*L*T_in*cp*mdot**3*q + T_in**2*cp**2*mdot**4)
This is now an addition of 2 terms.
Let’s explore the second mode of operation, which shows the arguments of a symbolic expression:
>>> t = table_of_expressions(expr, mode="args", use_latex=False) idx | args ------|--------------------------------------------------- 0 | -mdot**(n*xi + 4)*(T_in*cp)**(n*xi + 2) 1 | L**2*mdot**2*q**2*(L*q + T_in*cp*mdot)**(n*xi) 2 | T_in**2*cp**2*mdot**4*(L*q + T_in*cp*mdot)**(n*xi) 3 | 2*L*T_in*cp*mdot**3*q*(L*q + T_in*cp*mdot)**(n*xi)
The above output shows the 4 sub-expressions that composes
expr. Again, the table can be indexed in order to retrieve the interested term, for example:>>> t[2] T_in**2*cp**2*mdot**4*(L*q + T_in*cp*mdot)**(n*xi)
The third mode of operation shows a the unique nodes that are contained in the expression tree.
>>> table_of_expressions(expr, mode="nodes", use_latex=False)
This mode of operation usually creates large tables. The can be filtered by terms contained in the nodes, using the
selectkeyword argument. For example, let’s visualize the node containing the termL*q:>>> toe = table_of_expressions(expr, select=[L*q], mode="nodes", use_latex=False) idx | nodes ------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 12 | L*q 26 | L*q + T_in*cp*mdot 27 | (L*q + T_in*cp*mdot)**(n*xi) 29 | L**2*mdot**2*q**2*(L*q + T_in*cp*mdot)**(n*xi) 30 | T_in**2*cp**2*mdot**4*(L*q + T_in*cp*mdot)**(n*xi) 31 | 2*L*T_in*cp*mdot**3*q*(L*q + T_in*cp*mdot)**(n*xi) 32 | L**2*mdot**2*q**2*(L*q + T_in*cp*mdot)**(n*xi) + 2*L*T_in*cp*mdot**3*q*(L*q + T_in*cp*mdot)**(n*xi) + T_in**2*cp**2*mdot**4*(L*q + T_in*cp*mdot)**(n*xi) - mdot**(n*xi + 4)*(T_in*cp)**(n*xi + 2)
- get_selected_expressions()
Returns the expressions filtered by
select.
- show()
Show the table on the screen.
- sympy_equation.utils.process_arguments_of_add(expr, indices_groups, func, check=True)
Given an addition composed of several terms, this function performs the following:
select a group of terms.
add them together to create.
apply func to the result of step 2, which will compute a new expression.
replace the addition of step 2 with the new expression.
the function
table_of_expressionscan be used to select the appropriate terms to be modified.- Parameters:
- exprAdd
The addition to modify.
- indices_groupslist
A list of lists of integer numbers. Each list contains indices of arguments to be selected in step 1. In practice, each list represent a group of terms.
- funccallable
A callable requiring one argument, the expression created at step 2, and returning a new expression.
- checkboolean, optional
If True, verify that the new expression is mathematically equivalent to expr. If their are not, or the equivalency could not be established, a warning will be shown, but the function will returned the modified expression.
- Returns:
- new_exprAdd
See also
Examples
Consider the following addition. Modify it in order to collect terms containing ratios.
>>> from sympy import symbols, factor >>> from sympy_equation import process_arguments_of_add, table_of_expressions >>> gamma, v1, v2, p1, p2 = symbols("gamma, v1, v2, p1, p2") >>> expr = gamma - gamma*v2/v1 + gamma*p2/p1 + 1 + v2/v1 - p2/p1 >>> toe = table_of_expressions(expr, use_latex=False) idx | args ------|------------- 0 | 1 1 | gamma 2 | v2/v1 3 | -p2/p1 4 | gamma*p2/p1 5 | -gamma*v2/v1
From the above table, we can see that terms 2 and 5 contains v2/v1, while terms 3 and 4 contains p2/p1. Sympy’s factor() can be used for this task:
>>> new_expr = process_arguments_of_add(expr, [[2, 5], [3, 4]], factor) >>> new_expr gamma + 1 - v2*(gamma - 1)/v1 + p2*(gamma - 1)/p1
- sympy_equation.utils.divide_term_by_term(expr, denominator=None)
Consider a symbolic expression having the form numerator / denominator, where numerator is an addition. This function will divide each term of numerator by denominator.
- Parameters:
- exprAdd or Mul
The symbolic expression to be modified. If denominator=None, then expr must be a fraction, where the numerator is an addition. If denominator is provided then expr must be an addition.
- denominatorExpr or None
If None, the denominator will be extracted using sympy’s fraction(). If an expression is provided, then all arguments of expr will be divided by denominator.
- Returns:
- new_exprAdd
Examples
Consider an expression with the form numerator/denominator:
>>> from sympy import symbols >>> from sympy_equation import divide_term_by_term >>> gamma, v1, v2, p1, p2 = symbols("gamma, v1, v2, p1, p2") >>> expr = (gamma + 1 - v2*(gamma - 1)/v1 + p2*(gamma - 1)/p1)/(gamma - 1)
Note the denominator on the right (gamma - 1). Let’s divide term by term:
>>> new_expr = divide_term_by_term(expr) >>> new_expr gamma/(gamma - 1) + 1/(gamma - 1) - v2/v1 + p2/p1
Now, consider an addition of terms. We would like to divide all terms by the same denominator:
>>> a, b, c, d, e = symbols("a:e") >>> expr = a + b - c / d >>> den = 2*a - e >>> new_expr = divide_term_by_term(expr, denominator=den) >>> new_expr a/(2*a - e) + b/(2*a - e) - c/(d*(2*a - e))
- sympy_equation.utils.collect_reciprocal(expr, term_to_collect, check=True)
Given an addition, collect the specified term from the addends. This is different from sympy’s
collect, in fact it doesn’t use it at all. While’scollectrequires the term to be collected to be contained by some two or more terms of an addition, this function requires at does not. See examples below to understand the goal of this function.- Parameters:
- exprExpr
The addition to modify.
- term_to_collectExpr
- checkboolean, optional
If True, verify that the new expression is mathematically equivalent to expr. If their are not, or the equivalency could not be established, a warning will be shown, but the function will returned the modified expression.
Examples
>>> from sympy import symbols >>> from sympy_equation import collect_reciprocal >>> a = symbols("a") >>> expr = a + 1 >>> collect_reciprocal(expr, a) a*(1 + 1/a)
>>> v1, v2, gamma = symbols("v1, v2, gamma") >>> expr = -1 + v2*(gamma + 1)/(v1*(gamma - 1)) >>> collect_reciprocal(expr, v2/v1) v2*(-v1/v2 + (gamma + 1)/(gamma - 1))/v1
- sympy_equation.utils.split_two_terms_add(eq)
Consider an equation having one of the following forms:
a + b = 0: LHS is an addition of two terms.0 = a + b: RHS is an addition of two terms.
This function splits the addition and places the terms on the two sides of the equation:
a = -b.If the equation doesn’t have the expected form, the function returns it unmodified.
- Parameters:
- eqExpr, Equation, Equality
- Returns:
- new_eqEquation, Equality