I'm reposting this from my opened discussion on Github to reach additional people, due to my question depending on personal preference.
I guess my problem/question is due to sympy being used in python (programming language in general), rather than in a special math-language. I would still like to hear your suggestions.
I have been using sympy for a while now. My goal is to create calculations as reproducible as possible only via the output.
Something that I'm always struggling with, is how to structure my code / storing in variables.
Some different approaches that i came up with on an example calculating the area and volume:
the desired output would be:
import sympy as sp
from sympy.abc import *
display(sp.Eq(A, a*b), sp.Eq(V, A*h), sp.Eq(V, a*b*h))
The first approach is storing them in sp.Eq:
import sympy as sp
from sympy.abc import *
eq_A = sp.Eq(A, a*b)
eq_V = sp.Eq(V, A*h)
display(eq_A, eq_V)
eq_V_subs = sp.Eq(V, A.subs(A, eq_A.rhs))
display(eq_V_subs)
This approach seems to be too tedious and prone to errors because i have to keep resubbing the symbols with its equation-counterpart.
The next approach i took was to overwrite the symbols:
A = a*b
V = A*h
display(sp.Eq(sp.Symbol('A'),A), sp.Eq(sp.Symbol('V'),V))
It feels natural to do calculations in this manner. The problem is, that i overwrite the symbols, which hinders me to get the desired output of sp.Eq(V, A*h)
. Especially when the calculations in the variables are getting bigger, a substitution with a simple Symbol is necessary to keep the overview:
The last approach i had, was to somehow create a store for my expressions:
import sympy as sp
from sympy.abc import *
store={}
store[A] = a*b
store[V] = A*h
display(sp.Eq(A, A.subs(store)), sp.Eq(V, V.subs(store)), sp.Eq(V, V.subs(store).subs(store)))
Which works not too bad, but as it can be seen in the display function: To get the final display of the volume in need to sub V 2 times. With increasing dict-size with equations building on each other, the n-subs increase, which is not feasable anymore by hand (maybe write a function that subs for n times where n is the length of the dict).
Wouldn't it be great to have a class simillar to sp.symbols() where i can store the symbol and the corresponding expression. The class is capable of doing all the operations in sympy on the symbol and the expression. With some defined functions i could then display only with the symbols, with a different function i could render it with the expressions. Some pseudocode to illustrate what i mean:
import sympy as sp
from sympy.abc import *
A = mock_class(sp.Symbol('A'), a*b)
V = mock_class(sp.Symbol('V'), A*h)
mock_display_Symbol(V, V)
moch_display_expression(A, A)
So my question is, how do you handle this? What would you suggest that i can achieve my goal?
There is a nice package that you probably want to explore, Algebra with SymPy.
It implements the concept of "equation" that you might be familiar from school, in which it is possible to apply the same mathematical operation to both sides of an equation, or apply a mathematical operation to two or more equations.
I thinks it is a powerful extension to make SymPy computations feel like more natural. But there is catch: it doesn't perform any check to verify that what you are typing is good or bad. Hence, you have to be careful about what you are trying to achieve.
I'll give you a very brief introduction:
from sympy import *
from algebra_with_sympy import solve, Equation as Eqn, algwsym_config
init_printing()
Eqn
is the class that implements the magic. solve
is a wrapper to SymPy's solve
function, which is able to deal with objects of type Equation
.
# by default, when an equation is shown on the screen, it also shows
# a label with a unique number. Usually, I hide them:
algwsym_config.output.label = False
# by default, the algebra_with_sympy's solve returns objects of type
# FiniteSet. I don't like that behavior: I prefer lists of solutions.
algwsym_config.output.solve_to_list = True
Now to your example:
var("a, b, h, V, A")
eq_V = Eqn(V, A*h)
eq_A = Eqn(A, a*b)
eq_V.subs(eq_A)
# out: V = a*b*h
The above subs
command is equivalent to eq_V.subs(eq_A.lhs, eq_A.rhs)
. Essentially, you drastically reduce the chances of typing errors.