algebra_with_sympy.algebraic_equation

This package uses a special version of sympy which 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).

The intent is to allow using the mathematical tools in SymPy to rearrange equations and perform algebra in a stepwise fashion. In this way more people can successfully perform algebraic rearrangements without stumbling over missed details such as a negative sign. This mimics the capabilities available in SageMath and Maxima.

This package also provides convenient settings for interactive use on the command line, in ipython and Jupyter notebook environments. See the documentation at https://gutow.github.io/Algebra_with_Sympy/.

Explanation

This class defines relations that all high school and college students would recognize as mathematical equations. At present only the "=" relation operator is recognized.

This class is intended to allow using the mathematical tools in SymPy to rearrange equations and perform algebra in a stepwise fashion. 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(...).

Parameters

lhs: sympy expression, class Expr. rhs: sympy expression, class Expr. kwargs:

Examples

NOTE: All the examples below are in vanilla python. You can get human readable eqautions "lhs = rhs" in vanilla python by adjusting the settings in algwsym_config (see it's documentation). Output is human readable by default in IPython and Jupyter environments.

>>> from algebra_with_sympy import *
>>> a, b, c, x = var('a b c x')
>>> Equation(a,b/c)
Equation(a, b/c)
>>> t=Eqn(a,b/c)
>>> t
Equation(a, b/c)
>>> t*c
Equation(a*c, b)
>>> c*t
Equation(a*c, b)
>>> exp(t)
Equation(exp(a), exp(b/c))
>>> exp(log(t))
Equation(a, b/c)

Simplification and Expansion

>>> 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)
>>> f2 = f+a*x**2+b*x +c
>>> f2
Equation(a*x**2 + b*x + c + x**2 - 1, a*x**2 + b*x + 2*c)
>>> collect(f2,x)
Equation(b*x + c + x**2*(a + 1) - 1, a*x**2 + b*x + 2*c)

Apply operation to only one side

>>> poly = Eqn(a*x**2 + b*x + c*x**2, a*x**3 + b*x**3 + c*x)
>>> poly.applyrhs(factor,x)
Equation(a*x**2 + b*x + c*x**2, x*(c + x**2*(a + b)))
>>> poly.applylhs(factor)
Equation(x*(a*x + b + c*x), a*x**3 + b*x**3 + c*x)
>>> poly.applylhs(collect,x)
Equation(b*x + x**2*(a + c), a*x**3 + b*x**3 + c*x)

.apply... also works with user defined python functions

>>> def addsquare(eqn):
...     return eqn+eqn**2
...
>>> t.apply(addsquare)
Equation(a**2 + a, b**2/c**2 + b/c)
>>> t.applyrhs(addsquare)
Equation(a, b**2/c**2 + b/c)
>>> t.apply(addsquare, side = 'rhs')
Equation(a, b**2/c**2 + b/c)
>>> t.applylhs(addsquare)
Equation(a**2 + a, b/c)
>>> addsquare(t)
Equation(a**2 + a, b**2/c**2 + b/c)

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

poly.do.exp() or other sympy math functions will raise an error.

Rearranging an equation (simple example made complicated as illustration)

>>> p, V, n, R, T = var('p V n R T')
>>> eq1=Eqn(p*V,n*R*T)
>>> eq1
Equation(V*p, R*T*n)
>>> eq2 =eq1/V
>>> eq2
Equation(p, R*T*n/V)
>>> eq3 = eq2/R/T
>>> eq3
Equation(p/(R*T), n/V)
>>> eq4 = eq3*R/p
>>> eq4
Equation(1/T, R*n/(V*p))
>>> 1/eq4
Equation(T, V*p/(R*n))
>>> eq5 = 1/eq4 - T
>>> eq5
Equation(0, -T + V*p/(R*n))

Substitution (#'s and units)

>>> L, atm, mol, K = var('L atm mol K', positive=True, real=True) # units
>>> eq2.subs({R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,V:24.0*L})
Equation(p, 0.9334325*atm)
>>> eq2.subs({R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,V:24.0*L}).evalf(4)
Equation(p, 0.9334*atm)

Substituting an equation into another equation:

>>> P, P1, P2, A1, A2, E1, E2 = symbols("P, P1, P2, A1, A2, E1, E2")
>>> eq1 = Eqn(P, P1 + P2)
>>> eq2 = Eqn(P1 / (A1 * E1), P2 / (A2 * E2))
>>> P1_val = (eq1 - P2).swap
>>> P1_val
Equation(P1, P - P2)
>>> eq2 = eq2.subs(P1_val)
>>> eq2
Equation((P - P2)/(A1*E1), P2/(A2*E2))
>>> P2_val = solve(eq2.subs(P1_val), P2).args[0]
>>> P2_val
Equation(P2, A2*E2*P/(A1*E1 + A2*E2))

Combining equations (Math with equations: lhs with lhs and rhs with rhs)

>>> q = Eqn(a*c, b/c**2)
>>> q
Equation(a*c, b/c**2)
>>> t
Equation(a, b/c)
>>> q+t
Equation(a*c + a, b/c + b/c**2)
>>> q/t
Equation(c, 1/c)
>>> t**q
Equation(a**(a*c), (b/c)**(b/c**2))

Utility operations

>>> t.reversed
Equation(b/c, a)
>>> t.swap
Equation(b/c, a)
>>> t.lhs
a
>>> t.rhs
b/c
>>> t.as_Boolean()
Eq(a, b/c)

.check() convenience method for .as_Boolean().simplify()

>>> from sympy import I, pi
>>> Equation(pi*(I+2), pi*I+2*pi).check()
True
>>> Eqn(a,a+1).check()
False

Differentiation Differentiation is applied to both sides if the wrt variable appears on both sides.

>>> q=Eqn(a*c, b/c**2)
>>> q
Equation(a*c, b/c**2)
>>> diff(q,b)
Equation(Derivative(a*c, b), c**(-2))
>>> diff(q,c)
Equation(a, -2*b/c**3)
>>> diff(log(q),b)
Equation(Derivative(log(a*c), b), 1/b)
>>> diff(q,c,2)
Equation(Derivative(a, c), 6*b/c**4)

If you specify multiple differentiation all at once the assumption is order of differentiation matters and the lhs will not be evaluated.

>>> diff(q,c,b)
Equation(Derivative(a*c, b, c), -2/c**3)

To overcome this specify the order of operations.

>>> diff(diff(q,c),b)
Equation(Derivative(a, b), -2/c**3)

But the reverse order returns an unevaulated lhs (a may depend on b).

>>> diff(diff(q,b),c)
Equation(Derivative(a*c, b, c), -2/c**3)

Integration can only be performed on one side at a time.

>>> q=Eqn(a*c,b/c)
>>> integrate(q,b,side='rhs')
b**2/(2*c)
>>> integrate(q,b,side='lhs')
a*b*c

Make a pretty statement of integration from an equation

>>> Eqn(Integral(q.lhs,b),integrate(q,b,side='rhs'))
Equation(Integral(a*c, b), 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))

Automatic solutions using sympy solvers. THIS IS EXPERIMENTAL. Please report issues at https://github.com/gutow/Algebra_with_Sympy/issues.

>>> tosolv = Eqn(a - b, c/a)
>>> solve(tosolv,a)
FiniteSet(Equation(a, b/2 - sqrt(b**2 + 4*c)/2), Equation(a, b/2 + sqrt(b**2 + 4*c)/2))
>>> solve(tosolv, b)
FiniteSet(Equation(b, (a**2 - c)/a))
>>> solve(tosolv, c)
FiniteSet(Equation(c, a**2 - a*b))
  1"""
  2This package uses a special version of sympy which defines an equation 
  3with a left-hand-side (lhs) and a right-
  4hand-side (rhs) connected by the "=" operator (e.g. `p*V = n*R*T`).
  5
  6The intent is to allow using the mathematical tools in SymPy to rearrange
  7equations and perform algebra in a stepwise fashion. In this way more people
  8can successfully perform algebraic rearrangements without stumbling over
  9missed details such as a negative sign. This mimics the capabilities available
 10in [SageMath](https://www.sagemath.org/) and
 11[Maxima](http://maxima.sourceforge.net/).
 12
 13This package also provides convenient settings for interactive use on the 
 14command line, in ipython and Jupyter notebook environments. See the 
 15documentation at https://gutow.github.io/Algebra_with_Sympy/.
 16
 17Explanation
 18===========
 19This class defines relations that all high school and college students
 20would recognize as mathematical equations. At present only the "=" relation
 21operator is recognized.
 22
 23This class is intended to allow using the mathematical tools in SymPy to
 24rearrange equations and perform algebra in a stepwise fashion. In this
 25way more people can successfully perform algebraic rearrangements without
 26stumbling over missed details such as a negative sign.
 27
 28Create an equation with the call ``Equation(lhs,rhs)``, where ``lhs`` and
 29``rhs`` are any valid Sympy expression. ``Eqn(...)`` is a synonym for
 30``Equation(...)``.
 31
 32Parameters
 33==========
 34lhs: sympy expression, ``class Expr``.
 35rhs: sympy expression, ``class Expr``.
 36kwargs:
 37
 38Examples
 39========
 40NOTE: All the examples below are in vanilla python. You can get human
 41readable eqautions "lhs = rhs" in vanilla python by adjusting the settings
 42in `algwsym_config` (see it's documentation). Output is human readable by
 43default in IPython and Jupyter environments.
 44>>> from algebra_with_sympy import *
 45>>> a, b, c, x = var('a b c x')
 46>>> Equation(a,b/c)
 47Equation(a, b/c)
 48>>> t=Eqn(a,b/c)
 49>>> t
 50Equation(a, b/c)
 51>>> t*c
 52Equation(a*c, b)
 53>>> c*t
 54Equation(a*c, b)
 55>>> exp(t)
 56Equation(exp(a), exp(b/c))
 57>>> exp(log(t))
 58Equation(a, b/c)
 59
 60Simplification and Expansion
 61>>> f = Eqn(x**2 - 1, c)
 62>>> f
 63Equation(x**2 - 1, c)
 64>>> f/(x+1)
 65Equation((x**2 - 1)/(x + 1), c/(x + 1))
 66>>> (f/(x+1)).simplify()
 67Equation(x - 1, c/(x + 1))
 68>>> simplify(f/(x+1))
 69Equation(x - 1, c/(x + 1))
 70>>> (f/(x+1)).expand()
 71Equation(x**2/(x + 1) - 1/(x + 1), c/(x + 1))
 72>>> expand(f/(x+1))
 73Equation(x**2/(x + 1) - 1/(x + 1), c/(x + 1))
 74>>> factor(f)
 75Equation((x - 1)*(x + 1), c)
 76>>> f.factor()
 77Equation((x - 1)*(x + 1), c)
 78>>> f2 = f+a*x**2+b*x +c
 79>>> f2
 80Equation(a*x**2 + b*x + c + x**2 - 1, a*x**2 + b*x + 2*c)
 81>>> collect(f2,x)
 82Equation(b*x + c + x**2*(a + 1) - 1, a*x**2 + b*x + 2*c)
 83
 84Apply operation to only one side
 85>>> poly = Eqn(a*x**2 + b*x + c*x**2, a*x**3 + b*x**3 + c*x)
 86>>> poly.applyrhs(factor,x)
 87Equation(a*x**2 + b*x + c*x**2, x*(c + x**2*(a + b)))
 88>>> poly.applylhs(factor)
 89Equation(x*(a*x + b + c*x), a*x**3 + b*x**3 + c*x)
 90>>> poly.applylhs(collect,x)
 91Equation(b*x + x**2*(a + c), a*x**3 + b*x**3 + c*x)
 92
 93``.apply...`` also works with user defined python functions
 94>>> def addsquare(eqn):
 95...     return eqn+eqn**2
 96...
 97>>> t.apply(addsquare)
 98Equation(a**2 + a, b**2/c**2 + b/c)
 99>>> t.applyrhs(addsquare)
100Equation(a, b**2/c**2 + b/c)
101>>> t.apply(addsquare, side = 'rhs')
102Equation(a, b**2/c**2 + b/c)
103>>> t.applylhs(addsquare)
104Equation(a**2 + a, b/c)
105>>> addsquare(t)
106Equation(a**2 + a, b**2/c**2 + b/c)
107
108Inaddition to ``.apply...`` there is also the less general ``.do``,
109``.dolhs``, ``.dorhs``, which only works for operations defined on the
110``Expr`` class (e.g.``.collect(), .factor(), .expand()``, etc...).
111>>> poly.dolhs.collect(x)
112Equation(b*x + x**2*(a + c), a*x**3 + b*x**3 + c*x)
113>>> poly.dorhs.collect(x)
114Equation(a*x**2 + b*x + c*x**2, c*x + x**3*(a + b))
115>>> poly.do.collect(x)
116Equation(b*x + x**2*(a + c), c*x + x**3*(a + b))
117>>> poly.dorhs.factor()
118Equation(a*x**2 + b*x + c*x**2, x*(a*x**2 + b*x**2 + c))
119
120``poly.do.exp()`` or other sympy math functions will raise an error.
121
122Rearranging an equation (simple example made complicated as illustration)
123>>> p, V, n, R, T = var('p V n R T')
124>>> eq1=Eqn(p*V,n*R*T)
125>>> eq1
126Equation(V*p, R*T*n)
127>>> eq2 =eq1/V
128>>> eq2
129Equation(p, R*T*n/V)
130>>> eq3 = eq2/R/T
131>>> eq3
132Equation(p/(R*T), n/V)
133>>> eq4 = eq3*R/p
134>>> eq4
135Equation(1/T, R*n/(V*p))
136>>> 1/eq4
137Equation(T, V*p/(R*n))
138>>> eq5 = 1/eq4 - T
139>>> eq5
140Equation(0, -T + V*p/(R*n))
141
142Substitution (#'s and units)
143>>> L, atm, mol, K = var('L atm mol K', positive=True, real=True) # units
144>>> eq2.subs({R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,V:24.0*L})
145Equation(p, 0.9334325*atm)
146>>> eq2.subs({R:0.08206*L*atm/mol/K,T:273*K,n:1.00*mol,V:24.0*L}).evalf(4)
147Equation(p, 0.9334*atm)
148
149Substituting an equation into another equation:
150>>> P, P1, P2, A1, A2, E1, E2 = symbols("P, P1, P2, A1, A2, E1, E2")
151>>> eq1 = Eqn(P, P1 + P2)
152>>> eq2 = Eqn(P1 / (A1 * E1), P2 / (A2 * E2))
153>>> P1_val = (eq1 - P2).swap
154>>> P1_val
155Equation(P1, P - P2)
156>>> eq2 = eq2.subs(P1_val)
157>>> eq2
158Equation((P - P2)/(A1*E1), P2/(A2*E2))
159>>> P2_val = solve(eq2.subs(P1_val), P2).args[0]
160>>> P2_val
161Equation(P2, A2*E2*P/(A1*E1 + A2*E2))
162
163Combining equations (Math with equations: lhs with lhs and rhs with rhs)
164>>> q = Eqn(a*c, b/c**2)
165>>> q
166Equation(a*c, b/c**2)
167>>> t
168Equation(a, b/c)
169>>> q+t
170Equation(a*c + a, b/c + b/c**2)
171>>> q/t
172Equation(c, 1/c)
173>>> t**q
174Equation(a**(a*c), (b/c)**(b/c**2))
175
176Utility operations
177>>> t.reversed
178Equation(b/c, a)
179>>> t.swap
180Equation(b/c, a)
181>>> t.lhs
182a
183>>> t.rhs
184b/c
185>>> t.as_Boolean()
186Eq(a, b/c)
187
188`.check()` convenience method for `.as_Boolean().simplify()`
189>>> from sympy import I, pi
190>>> Equation(pi*(I+2), pi*I+2*pi).check()
191True
192>>> Eqn(a,a+1).check()
193False
194
195Differentiation
196Differentiation is applied to both sides if the wrt variable appears on
197both sides.
198>>> q=Eqn(a*c, b/c**2)
199>>> q
200Equation(a*c, b/c**2)
201>>> diff(q,b)
202Equation(Derivative(a*c, b), c**(-2))
203>>> diff(q,c)
204Equation(a, -2*b/c**3)
205>>> diff(log(q),b)
206Equation(Derivative(log(a*c), b), 1/b)
207>>> diff(q,c,2)
208Equation(Derivative(a, c), 6*b/c**4)
209
210If you specify multiple differentiation all at once the assumption
211is order of differentiation matters and the lhs will not be
212evaluated.
213>>> diff(q,c,b)
214Equation(Derivative(a*c, b, c), -2/c**3)
215
216To overcome this specify the order of operations.
217>>> diff(diff(q,c),b)
218Equation(Derivative(a, b), -2/c**3)
219
220But the reverse order returns an unevaulated lhs (a may depend on b).
221>>> diff(diff(q,b),c)
222Equation(Derivative(a*c, b, c), -2/c**3)
223
224Integration can only be performed on one side at a time.
225>>> q=Eqn(a*c,b/c)
226>>> integrate(q,b,side='rhs')
227b**2/(2*c)
228>>> integrate(q,b,side='lhs')
229a*b*c
230
231Make a pretty statement of integration from an equation
232>>> Eqn(Integral(q.lhs,b),integrate(q,b,side='rhs'))
233Equation(Integral(a*c, b), b**2/(2*c))
234
235Integration of each side with respect to different variables
236>>> q.dorhs.integrate(b).dolhs.integrate(a)
237Equation(a**2*c/2, b**2/(2*c))
238
239Automatic solutions using sympy solvers. THIS IS EXPERIMENTAL. Please
240report issues at https://github.com/gutow/Algebra_with_Sympy/issues.
241>>> tosolv = Eqn(a - b, c/a)
242>>> solve(tosolv,a)
243FiniteSet(Equation(a, b/2 - sqrt(b**2 + 4*c)/2), Equation(a, b/2 + sqrt(b**2 + 4*c)/2))
244>>> solve(tosolv, b)
245FiniteSet(Equation(b, (a**2 - c)/a))
246>>> solve(tosolv, c)
247FiniteSet(Equation(c, a**2 - a*b))
248"""
249import sys
250
251import sympy
252from algebra_with_sympy.preparser import integers_as_exact
253from sympy import *
254
255class algwsym_config():
256
257    def __init__(self):
258        """
259        This is a class to hold parameters that control behavior of
260        the algebra_with_sympy package.
261
262        Settings
263        ========
264        Printing
265        --------
266        In interactive environments the default output of an equation is a
267        human readable string with the two sides connected by an equals
268        sign or a typeset equation with the two sides connected by an equals sign.
269        `print(Eqn)` or `str(Eqn)` will return this human readable text version of
270        the equation as well. This is consistent with python standards, but not
271        sympy, where `str()` is supposed to return something that can be
272        copy-pasted into code. If the equation has a declared name as in `eq1 =
273        Eqn(a,b/c)` the name will be displayed to the right of the equation in
274        parentheses (eg. `a = b/c    (eq1)`). Use `print(repr(Eqn))` instead of
275        `print(Eqn)` or `repr(Eqn)` instead of `str(Eqn)` to get a code
276        compatible version of the equation.
277
278        You can adjust this behavior using some flags that impact output:
279        * `algwsym_config.output.show_code` default is `False`.
280        * `algwsym_config.output.human_text` default is `True`.
281        * `algwsym_config.output.label` default is `True`.
282        * `algwsym_config.output.latex_as_equations` default is `False`
283
284        In interactive environments you can get both types of output by setting
285        the `algwsym_config.output.show_code` flag. If this flag is true
286        calls to `latex` and `str` will also print an additional line "code
287        version: `repr(Eqn)`". Thus in Jupyter you will get a line of typeset
288        mathematics output preceded by the code version that can be copy-pasted.
289        Default is `False`.
290
291        A second flag `algwsym_config.output.human_text` is useful in
292        text-based interactive environments such as command line python or
293        ipython. If this flag is true `repr` will return `str`. Thus the human
294        readable text will be printed as the output of a line that is an
295        expression containing an equation.
296        Default is `True`.
297
298        Setting both of these flags to true in a command line or ipython
299        environment will show both the code version and the human readable text.
300        These flags impact the behavior of the `print(Eqn)` statement.
301
302        The third flag `algwsym_config.output.label` has a default value of
303        `True`. Setting this to `False` suppresses the labeling of an equation
304        with its python name off to the right of the equation.
305
306        The fourth flag `algwsym_config.output.latex_as_equations` has
307        a default value of `False`. Setting this to `True` wraps
308        output as LaTex equations wrapping them in `\\begin{equation}...\\end{
309        equation}`.
310        """
311        pass
312
313    class output():
314
315        def __init__(self):
316            """This holds settings that impact output.
317            """
318            pass
319
320        @property
321        def show_code(self):
322            """
323            If `True` code versions of the equation expression will be
324            output in interactive environments. Default = `False`.
325            """
326            return self.show_code
327
328        @property
329        def human_text(self):
330            """
331            If `True` the human readable equation expression will be
332            output in text interactive environments. Default = `False`.
333            """
334            return self.human_text
335
336        @property
337        def solve_to_list(self):
338            """
339            If `True` the results of a call to `solve(...)` will return a
340            Python `list` rather than a Sympy `FiniteSet`. This recovers
341            behavior for versions before 0.11.0.
342
343            Note: setting this `True` means that expressions within the
344            returned solutions will not be pretty-printed in Jupyter and
345            IPython.
346            """
347            return self.solve_to_list
348
349        @property
350        def latex_as_equations(self):
351            """
352            If `True` any output that is returned as LaTex for
353            pretty-printing will be wrapped in the formal Latex for an
354            equation. For example rather than
355            ```
356            $\\frac{a}{b}=c$
357            ```
358            the output will be
359            ```
360            \\begin{equation}\\frac{a}{b}=c\\end{equation}
361            ```
362            """
363            return self.latex_as_equation
364
365    class numerics():
366
367        def __init__(self):
368            """This class holds settings for how numerical computation and
369            inputs are handled.
370            """
371            pass
372
373        def integers_as_exact(self):
374            """**This is a flag for informational purposes and interface
375            consistency. Changing the value will not change the behavior.**
376
377            To change the behavior call:
378            * `unset_integers_as_exact()` to turn this feature off.
379            * `set_integers_as_exact()` to turn this feature on (on by
380            default).
381
382            If set to `True` (the default) and if running in an
383            IPython/Jupyter environment any number input without a decimal
384            will be interpreted as a sympy integer. Thus, fractions and
385            related expressions will not evalute to floating point numbers,
386            but be maintained as exact expressions (e.g. 2/3 -> 2/3 not the
387            float 0.6666...).
388            """
389            return self.integers_as_exact
390
391def __get_sympy_expr_name__(expr):
392    """
393    Tries to find the python string name that refers to a sympy object. In
394    IPython environments (IPython, Jupyter, etc...) looks in the user_ns.
395    If not in an IPython environment looks in __main__.
396    :return: string value if found or empty string.
397    """
398    import __main__ as shell
399    for k in dir(shell):
400        item = getattr(shell, k)
401        if isinstance(item, Basic):
402            if item == expr and not k.startswith('_'):
403                return k
404    return ''
405
406def __latex_override__(expr, *arg):
407    algwsym_config = False
408    ip = False
409    try:
410        from IPython import get_ipython
411        if get_ipython():
412            ip = True
413    except ModuleNotFoundError:
414        pass
415    colab = False
416    try:
417        from google.colab import output
418        colab = True
419    except ModuleNotFoundError:
420        pass
421    show_code = False
422    latex_as_equations = False
423    if ip:
424        algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
425    else:
426        algwsym_config = globals()['algwsym_config']
427    if algwsym_config:
428        show_code = algwsym_config.output.show_code
429        latex_as_equations = algwsym_config.output.latex_as_equations
430    if show_code:
431        print("Code version: " + repr(expr))
432    if latex_as_equations:
433        return r'\begin{equation}'+latex(expr)+r'\end{equation}'
434    else:
435        tempstr = ''
436        namestr = ''
437        if isinstance(expr, Equation):
438            namestr = __get_sympy_expr_name__(expr)
439        if namestr != '' and algwsym_config and algwsym_config.output.label:
440            tempstr += r'$'+latex(expr)
441            # work around for colab's inconsistent handling of mixed latex and
442            # plain strings.
443            if colab:
444                colabname = namestr.replace('_', r'\_')
445                tempstr += r'\,\,\,\,\,\,\,\,\,\,(' + colabname + ')$'
446            else:
447                tempstr += r'\,\,\,\,\,\,\,\,\,\,$(' + namestr + ')'
448            return tempstr
449        else:
450            return '$'+latex(expr) + '$'
451
452def __command_line_printing__(expr, *arg):
453    # print('Entering __command_line_printing__')
454    human_text = True
455    show_code = False
456    if algwsym_config:
457        human_text = algwsym_config.output.human_text
458        show_code = algwsym_config.output.show_code
459    tempstr = ''
460    if show_code:
461        tempstr += "Code version: " + repr(expr) + '\n'
462    if not human_text:
463        return print(tempstr + repr(expr))
464    else:
465        labelstr = ''
466        namestr = ''
467        if isinstance(expr, Equation):
468            namestr = __get_sympy_expr_name__(expr)
469        if namestr != '' and algwsym_config.output.label:
470            labelstr += '          (' + namestr + ')'
471        return print(tempstr + str(expr) + labelstr)
472
473# Now we inject the formatting override(s)
474ip = None
475try:
476    from IPython import get_ipython
477    ip = get_ipython()
478except ModuleNotFoundError:
479    ip = false
480formatter = None
481if ip:
482    # In an environment that can display typeset latex
483    formatter = ip.display_formatter
484    old = formatter.formatters['text/latex'].for_type(Basic,
485                                                      __latex_override__)
486    # print("For type Basic overriding latex formatter = " + str(old))
487
488    # For the terminal based IPython
489    if "text/latex" not in formatter.active_types:
490        old = formatter.formatters['text/plain'].for_type(tuple,
491                                                    __command_line_printing__)
492        # print("For type tuple overriding plain text formatter = " + str(old))
493        for k in sympy.__all__:
494            if k in globals() and not "Printer" in k:
495                if isinstance(globals()[k], type):
496                    old = formatter.formatters['text/plain'].\
497                        for_type(globals()[k], __command_line_printing__)
498                    # print("For type "+str(k)+
499                    # " overriding plain text formatter = " + str(old))
500else:
501    # command line
502    # print("Overriding command line printing of python.")
503    sys.displayhook = __command_line_printing__
504
505# Numerics controls
506def set_integers_as_exact():
507    """This operation causes any number input without a decimal that is
508    part of a Sympy expression to be interpreted as a sympy
509    integer, by using a custom preparser to cast integers within Sympy
510    expressions as Sympy integers (`Integer()`). It also sets the flag
511    `algwsym_config.numerics.integers_as_exact = True` This is the default
512    mode of algebra_with_sympy. To turn this off call
513    `unset_integers_as_exact()`.
514
515    NOTE: `2/3` --> `0.6666...` even when this is set, but  `2*x/3` -->
516    `Integer(2)/Integer(3)*x` if x is a sympy object. If `x` is just a Python
517    object `2*x/3` --> `x*0.6666666666...`.
518    """
519    ip = False
520    try:
521        from IPython import get_ipython
522        ip = True
523    except ModuleNotFoundError:
524        ip = False
525    if ip:
526        if get_ipython():
527            get_ipython().input_transformers_post.append(integers_as_exact)
528            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
529            if algwsym_config:
530                algwsym_config.numerics.integers_as_exact = True
531            else:
532                raise ValueError("The algwsym_config object does not exist.")
533    return
534
535def unset_integers_as_exact():
536    """This operation disables forcing of numbers input without
537    decimals being interpreted as sympy integers. Numbers input without a
538    decimal may be interpreted as floating point if they are part of an
539    expression that undergoes python evaluation (e.g. 2/3 -> 0.6666...). It
540    also sets the flag `algwsym_config.numerics.integers_as_exact = False`.
541    Call `set_integers_as_exact()` to avoid this conversion of rational
542    fractions and related expressions to floating point. Algebra_with_sympy
543    starts with `set_integers_as_exact()` enabled (
544    `algwsym_config.numerics.integers_as_exact = True`).
545    """
546    ip = False
547    try:
548        from IPython import get_ipython
549        ip = True
550    except ModuleNotFoundError:
551        ip = False
552    if ip:
553        if get_ipython():
554            pre = get_ipython().input_transformers_post
555            # The below looks excessively complicated, but more reliably finds the
556            # transformer to remove across varying IPython environments.
557            for k in pre:
558                if "integers_as_exact" in k.__name__:
559                    pre.remove(k)
560            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
561            if algwsym_config:
562                algwsym_config.numerics.integers_as_exact = False
563            else:
564                raise ValueError("The algwsym_config object does not exist.")
565
566    return
567
568Eqn = Equation
569if ip and "text/latex" not in formatter.active_types:
570    old = formatter.formatters['text/plain'].for_type(Eqn,
571                                                __command_line_printing__)
572    # print("For type Equation overriding plain text formatter = " + str(old))
573
574def units(names):
575    """
576    This operation declares the symbols to be positive values, so that sympy
577    will handle them properly when simplifying expressions containing units.
578    Units defined this way are just unit symbols. If you want units that are
579    aware of conversions see sympy.physics.units.
580
581
582    :param string names: a string containing a space separated list of
583    symbols to be treated as units.
584
585    :return string list of defined units: calls `name = symbols(name,
586    positive=True)` in the interactive namespace for each symbol name.
587    """
588    from sympy.core.symbol import symbols
589    #import __main__ as shell
590    user_namespace = None
591    try:
592        from IPython import get_ipython
593        if get_ipython():
594            user_namespace = get_ipython().user_ns
595    except ModuleNotFoundError:
596        pass
597    syms = names.split(' ')
598    retstr = ''
599
600    if user_namespace==None:
601        import sys
602        frame_num = 0
603        frame_name = None
604        while frame_name != '__main__' and frame_num < 50:
605            user_namespace = sys._getframe(frame_num).f_globals
606            frame_num +=1
607            frame_name = user_namespace['__name__']
608    retstr +='('
609    for k in syms:
610        user_namespace[k] = symbols(k, positive = True)
611        retstr += k + ','
612    retstr = retstr[:-1] + ')'
613    return retstr
614
615
616def solve(f, *symbols, **flags):
617    """
618    Override of sympy `solve()`.
619
620    If passed an expression and variable(s) to solve for it behaves
621    almost the same as normal solve with `dict = True`, except that solutions
622    are wrapped in a FiniteSet() to guarantee that the output will be pretty
623    printed in Jupyter like environments.
624
625    If passed an equation or equations it returns solutions as a
626    `FiniteSet()` of solutions, where each solution is represented by an
627    equation or set of equations.
628
629    To get a Python `list` of solutions (pre-0.11.0 behavior) rather than a
630    `FiniteSet` issue the command `algwsym_config.output.solve_to_list = True`.
631    This also prevents pretty-printing in IPython and Jupyter.
632
633    Examples
634    --------
635    >>> a, b, c, x, y = symbols('a b c x y', real = True)
636    >>> import sys
637    >>> sys.displayhook = __command_line_printing__ # set by default on normal initialization.
638    >>> eq1 = Eqn(abs(2*x+y),3)
639    >>> eq2 = Eqn(abs(x + 2*y),3)
640    >>> B = solve((eq1,eq2))
641
642    Default human readable output on command line
643    >>> B
644    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
645
646    To get raw output turn off by setting
647    >>> algwsym_config.output.human_text=False
648    >>> B
649    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)))
650
651    Pre-0.11.0 behavior where a python list of solutions is returned
652    >>> algwsym_config.output.solve_to_list = True
653    >>> solve((eq1,eq2))
654    [[Equation(x, -3), Equation(y, 3)], [Equation(x, -1), Equation(y, -1)], [Equation(x, 1), Equation(y, 1)], [Equation(x, 3), Equation(y, -3)]]
655    >>> algwsym_config.output.solve_to_list = False # reset to default
656
657    `algwsym_config.output.human_text = True` with
658    `algwsym_config.output.how_code=True` shows both.
659    In Jupyter-like environments `show_code=True` yields the Raw output and
660    a typeset version. If `show_code=False` (the default) only the
661    typeset version is shown in Jupyter.
662    >>> algwsym_config.output.show_code=True
663    >>> algwsym_config.output.human_text=True
664    >>> B
665    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)))
666    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
667    """
668    from sympy.solvers.solvers import solve
669    from sympy.sets.sets import FiniteSet
670    newf =[]
671    solns = []
672    displaysolns = []
673    contains_eqn = False
674    if hasattr(f,'__iter__'):
675        for k in f:
676            if isinstance(k, Equation):
677                newf.append(k.lhs-k.rhs)
678                contains_eqn = True
679            else:
680                newf.append(k)
681    else:
682        if isinstance(f, Equation):
683            newf.append(f.lhs - f.rhs)
684            contains_eqn = True
685        else:
686            newf.append(f)
687    flags['dict'] = True
688    result = solve(newf, *symbols, **flags)
689    if len(symbols) == 1 and hasattr(symbols[0], "__iter__"):
690        symbols = symbols[0]
691    if contains_eqn:
692        if len(result[0]) == 1:
693            for k in result:
694                for key in k.keys():
695                    val = k[key]
696                    tempeqn = Eqn(key, val)
697                    solns.append(tempeqn)
698            if len(solns) == len(symbols):
699                # sort according to the user-provided symbols
700                solns = sorted(solns, key=lambda x: symbols.index(x.lhs))
701        else:
702            for k in result:
703                solnset = []
704                for key in k.keys():
705                    val = k[key]
706                    tempeqn = Eqn(key, val)
707                    solnset.append(tempeqn)
708                if not algwsym_config.output.solve_to_list:
709                    solnset = FiniteSet(*solnset)
710                else:
711                    if len(solnset) == len(symbols):
712                        # sort according to the user-provided symbols
713                        solnset = sorted(solnset, key=lambda x: symbols.index(x.lhs))
714                solns.append(solnset)
715    else:
716        solns = result
717    if algwsym_config.output.solve_to_list:
718        if len(solns) == 1 and hasattr(solns[0], "__iter__"):
719            # no need to wrap a list of a single element inside another list
720            return solns[0]
721        return solns
722    else:
723        if len(solns) == 1:
724            # do not wrap a singleton in FiniteSet if it already is
725            for k in solns:
726                if isinstance(k, FiniteSet):
727                    return k
728        return FiniteSet(*solns)
729
730def solveset(f, symbols, domain=sympy.Complexes):
731    """
732    Very experimental override of sympy solveset, which we hope will replace
733    solve. Much is not working. It is not clear how to input a system of
734    equations unless you directly select `linsolve`, etc...
735    """
736    from sympy.solvers import solveset as solve
737    newf = []
738    solns = []
739    displaysolns = []
740    contains_eqn = False
741    if hasattr(f, '__iter__'):
742        for k in f:
743            if isinstance(k, Equation):
744                newf.append(k.lhs - k.rhs)
745                contains_eqn = True
746            else:
747                newf.append(k)
748    else:
749        if isinstance(f, Equation):
750            newf.append(f.lhs - f.rhs)
751            contains_eqn = True
752        else:
753            newf.append(f)
754    result = solve(*newf, symbols, domain=domain)
755    # if contains_eqn:
756    #     if len(result[0]) == 1:
757    #         for k in result:
758    #             for key in k.keys():
759    #                 val = k[key]
760    #                 tempeqn = Eqn(key, val)
761    #                 solns.append(tempeqn)
762    #         display(*solns)
763    #     else:
764    #         for k in result:
765    #             solnset = []
766    #             displayset = []
767    #             for key in k.keys():
768    #                 val = k[key]
769    #                 tempeqn = Eqn(key, val)
770    #                 solnset.append(tempeqn)
771    #                 if algwsym_config.output.show_solve_output:
772    #                     displayset.append(tempeqn)
773    #             if algwsym_config.output.show_solve_output:
774    #                 displayset.append('-----')
775    #             solns.append(solnset)
776    #             if algwsym_config.output.show_solve_output:
777    #                 for k in displayset:
778    #                     displaysolns.append(k)
779    #         if algwsym_config.output.show_solve_output:
780    #             display(*displaysolns)
781    # else:
782    solns = result
783    return solns
784
785class Equality(Equality):
786    """
787    Extension of Equality class to include the ability to convert it to an
788    Equation.
789    """
790    def to_Equation(self):
791        """
792        Return: recasts the Equality as an Equation.
793        """
794        return Equation(self.lhs,self.rhs)
795
796    def to_Eqn(self):
797        """
798        Synonym for to_Equation.
799        Return: recasts the Equality as an Equation.
800        """
801        return self.to_Equation()
802
803Eq = Equality
804
805def __Equation__repr__override__(self):
806    """Override of the default sympy representation to match normal python
807    behavior and allow for a human readable string representation.
808    """
809    return 'Equation(%s, %s)' % (repr(self.lhs), repr(self.rhs))
810
811sympy.core.Equation.__repr__ = __Equation__repr__override__
812
813def __Equation__str__override__(self):
814    """Override of the default sympy representation to match normal python
815    behavior and allow for a human readable string representation.
816    """
817    return '%s = %s' % (repr(self.lhs), repr(self.rhs))
818
819sympy.core.Equation.__str__ = __Equation__str__override__
820
821def __FiniteSet__repr__override__(self):
822    """Override of the `FiniteSet.__repr__(self)` to overcome sympy's
823    inconsistent wrapping of Finite Sets which prevents reliable use of
824    copy and paste of the code representation.
825    """
826    insidestr = ""
827    for k in self.args:
828        insidestr += k.__repr__() +', '
829    insidestr = insidestr[:-2]
830    reprstr = "FiniteSet("+ insidestr + ")"
831    return reprstr
832
833sympy.sets.FiniteSet.__repr__ = __FiniteSet__repr__override__
834
835def __FiniteSet__str__override__(self):
836    """Override of the `FiniteSet.__str__(self)` to overcome sympy's
837    inconsistent wrapping of Finite Sets which prevents reliable use of
838    copy and paste of the code representation.
839    """
840    insidestr = ""
841    for k in self.args:
842        insidestr += str(k) + ', '
843    insidestr = insidestr[:-2]
844    strrep = "{"+ insidestr + "}"
845    return strrep
846
847sympy.sets.FiniteSet.__str__ = __FiniteSet__str__override__
848
849# Redirect python abs() to Abs()
850abs = Abs
class algwsym_config:
256class algwsym_config():
257
258    def __init__(self):
259        """
260        This is a class to hold parameters that control behavior of
261        the algebra_with_sympy package.
262
263        Settings
264        ========
265        Printing
266        --------
267        In interactive environments the default output of an equation is a
268        human readable string with the two sides connected by an equals
269        sign or a typeset equation with the two sides connected by an equals sign.
270        `print(Eqn)` or `str(Eqn)` will return this human readable text version of
271        the equation as well. This is consistent with python standards, but not
272        sympy, where `str()` is supposed to return something that can be
273        copy-pasted into code. If the equation has a declared name as in `eq1 =
274        Eqn(a,b/c)` the name will be displayed to the right of the equation in
275        parentheses (eg. `a = b/c    (eq1)`). Use `print(repr(Eqn))` instead of
276        `print(Eqn)` or `repr(Eqn)` instead of `str(Eqn)` to get a code
277        compatible version of the equation.
278
279        You can adjust this behavior using some flags that impact output:
280        * `algwsym_config.output.show_code` default is `False`.
281        * `algwsym_config.output.human_text` default is `True`.
282        * `algwsym_config.output.label` default is `True`.
283        * `algwsym_config.output.latex_as_equations` default is `False`
284
285        In interactive environments you can get both types of output by setting
286        the `algwsym_config.output.show_code` flag. If this flag is true
287        calls to `latex` and `str` will also print an additional line "code
288        version: `repr(Eqn)`". Thus in Jupyter you will get a line of typeset
289        mathematics output preceded by the code version that can be copy-pasted.
290        Default is `False`.
291
292        A second flag `algwsym_config.output.human_text` is useful in
293        text-based interactive environments such as command line python or
294        ipython. If this flag is true `repr` will return `str`. Thus the human
295        readable text will be printed as the output of a line that is an
296        expression containing an equation.
297        Default is `True`.
298
299        Setting both of these flags to true in a command line or ipython
300        environment will show both the code version and the human readable text.
301        These flags impact the behavior of the `print(Eqn)` statement.
302
303        The third flag `algwsym_config.output.label` has a default value of
304        `True`. Setting this to `False` suppresses the labeling of an equation
305        with its python name off to the right of the equation.
306
307        The fourth flag `algwsym_config.output.latex_as_equations` has
308        a default value of `False`. Setting this to `True` wraps
309        output as LaTex equations wrapping them in `\\begin{equation}...\\end{
310        equation}`.
311        """
312        pass
313
314    class output():
315
316        def __init__(self):
317            """This holds settings that impact output.
318            """
319            pass
320
321        @property
322        def show_code(self):
323            """
324            If `True` code versions of the equation expression will be
325            output in interactive environments. Default = `False`.
326            """
327            return self.show_code
328
329        @property
330        def human_text(self):
331            """
332            If `True` the human readable equation expression will be
333            output in text interactive environments. Default = `False`.
334            """
335            return self.human_text
336
337        @property
338        def solve_to_list(self):
339            """
340            If `True` the results of a call to `solve(...)` will return a
341            Python `list` rather than a Sympy `FiniteSet`. This recovers
342            behavior for versions before 0.11.0.
343
344            Note: setting this `True` means that expressions within the
345            returned solutions will not be pretty-printed in Jupyter and
346            IPython.
347            """
348            return self.solve_to_list
349
350        @property
351        def latex_as_equations(self):
352            """
353            If `True` any output that is returned as LaTex for
354            pretty-printing will be wrapped in the formal Latex for an
355            equation. For example rather than
356            ```
357            $\\frac{a}{b}=c$
358            ```
359            the output will be
360            ```
361            \\begin{equation}\\frac{a}{b}=c\\end{equation}
362            ```
363            """
364            return self.latex_as_equation
365
366    class numerics():
367
368        def __init__(self):
369            """This class holds settings for how numerical computation and
370            inputs are handled.
371            """
372            pass
373
374        def integers_as_exact(self):
375            """**This is a flag for informational purposes and interface
376            consistency. Changing the value will not change the behavior.**
377
378            To change the behavior call:
379            * `unset_integers_as_exact()` to turn this feature off.
380            * `set_integers_as_exact()` to turn this feature on (on by
381            default).
382
383            If set to `True` (the default) and if running in an
384            IPython/Jupyter environment any number input without a decimal
385            will be interpreted as a sympy integer. Thus, fractions and
386            related expressions will not evalute to floating point numbers,
387            but be maintained as exact expressions (e.g. 2/3 -> 2/3 not the
388            float 0.6666...).
389            """
390            return self.integers_as_exact
algwsym_config()
258    def __init__(self):
259        """
260        This is a class to hold parameters that control behavior of
261        the algebra_with_sympy package.
262
263        Settings
264        ========
265        Printing
266        --------
267        In interactive environments the default output of an equation is a
268        human readable string with the two sides connected by an equals
269        sign or a typeset equation with the two sides connected by an equals sign.
270        `print(Eqn)` or `str(Eqn)` will return this human readable text version of
271        the equation as well. This is consistent with python standards, but not
272        sympy, where `str()` is supposed to return something that can be
273        copy-pasted into code. If the equation has a declared name as in `eq1 =
274        Eqn(a,b/c)` the name will be displayed to the right of the equation in
275        parentheses (eg. `a = b/c    (eq1)`). Use `print(repr(Eqn))` instead of
276        `print(Eqn)` or `repr(Eqn)` instead of `str(Eqn)` to get a code
277        compatible version of the equation.
278
279        You can adjust this behavior using some flags that impact output:
280        * `algwsym_config.output.show_code` default is `False`.
281        * `algwsym_config.output.human_text` default is `True`.
282        * `algwsym_config.output.label` default is `True`.
283        * `algwsym_config.output.latex_as_equations` default is `False`
284
285        In interactive environments you can get both types of output by setting
286        the `algwsym_config.output.show_code` flag. If this flag is true
287        calls to `latex` and `str` will also print an additional line "code
288        version: `repr(Eqn)`". Thus in Jupyter you will get a line of typeset
289        mathematics output preceded by the code version that can be copy-pasted.
290        Default is `False`.
291
292        A second flag `algwsym_config.output.human_text` is useful in
293        text-based interactive environments such as command line python or
294        ipython. If this flag is true `repr` will return `str`. Thus the human
295        readable text will be printed as the output of a line that is an
296        expression containing an equation.
297        Default is `True`.
298
299        Setting both of these flags to true in a command line or ipython
300        environment will show both the code version and the human readable text.
301        These flags impact the behavior of the `print(Eqn)` statement.
302
303        The third flag `algwsym_config.output.label` has a default value of
304        `True`. Setting this to `False` suppresses the labeling of an equation
305        with its python name off to the right of the equation.
306
307        The fourth flag `algwsym_config.output.latex_as_equations` has
308        a default value of `False`. Setting this to `True` wraps
309        output as LaTex equations wrapping them in `\\begin{equation}...\\end{
310        equation}`.
311        """
312        pass

This is a class to hold parameters that control behavior of the algebra_with_sympy package.

Settings

Printing

In interactive environments the default output of an equation is a human readable string with the two sides connected by an equals sign or a typeset equation with the two sides connected by an equals sign. print(Eqn) or str(Eqn) will return this human readable text version of the equation as well. This is consistent with python standards, but not sympy, where str() is supposed to return something that can be copy-pasted into code. If the equation has a declared name as in eq1 = Eqn(a,b/c) the name will be displayed to the right of the equation in parentheses (eg. a = b/c (eq1)). Use print(repr(Eqn)) instead of print(Eqn) or repr(Eqn) instead of str(Eqn) to get a code compatible version of the equation.

You can adjust this behavior using some flags that impact output:

In interactive environments you can get both types of output by setting the algwsym_config.output.show_code flag. If this flag is true calls to latex and str will also print an additional line "code version: repr(Eqn)". Thus in Jupyter you will get a line of typeset mathematics output preceded by the code version that can be copy-pasted. Default is False.

A second flag algwsym_config.output.human_text is useful in text-based interactive environments such as command line python or ipython. If this flag is true repr will return str. Thus the human readable text will be printed as the output of a line that is an expression containing an equation. Default is True.

Setting both of these flags to true in a command line or ipython environment will show both the code version and the human readable text. These flags impact the behavior of the print(Eqn) statement.

The third flag algwsym_config.output.label has a default value of True. Setting this to False suppresses the labeling of an equation with its python name off to the right of the equation.

The fourth flag algwsym_config.output.latex_as_equations has a default value of False. Setting this to True wraps output as LaTex equations wrapping them in \begin{equation}...\end{ equation}.

class algwsym_config.output:
314    class output():
315
316        def __init__(self):
317            """This holds settings that impact output.
318            """
319            pass
320
321        @property
322        def show_code(self):
323            """
324            If `True` code versions of the equation expression will be
325            output in interactive environments. Default = `False`.
326            """
327            return self.show_code
328
329        @property
330        def human_text(self):
331            """
332            If `True` the human readable equation expression will be
333            output in text interactive environments. Default = `False`.
334            """
335            return self.human_text
336
337        @property
338        def solve_to_list(self):
339            """
340            If `True` the results of a call to `solve(...)` will return a
341            Python `list` rather than a Sympy `FiniteSet`. This recovers
342            behavior for versions before 0.11.0.
343
344            Note: setting this `True` means that expressions within the
345            returned solutions will not be pretty-printed in Jupyter and
346            IPython.
347            """
348            return self.solve_to_list
349
350        @property
351        def latex_as_equations(self):
352            """
353            If `True` any output that is returned as LaTex for
354            pretty-printing will be wrapped in the formal Latex for an
355            equation. For example rather than
356            ```
357            $\\frac{a}{b}=c$
358            ```
359            the output will be
360            ```
361            \\begin{equation}\\frac{a}{b}=c\\end{equation}
362            ```
363            """
364            return self.latex_as_equation
algwsym_config.output()
316        def __init__(self):
317            """This holds settings that impact output.
318            """
319            pass

This holds settings that impact output.

show_code = False

If True code versions of the equation expression will be output in interactive environments. Default = False.

human_text = True

If True the human readable equation expression will be output in text interactive environments. Default = False.

solve_to_list = False

If True the results of a call to solve(...) will return a Python list rather than a Sympy FiniteSet. This recovers behavior for versions before 0.11.0.

Note: setting this True means that expressions within the returned solutions will not be pretty-printed in Jupyter and IPython.

latex_as_equations = 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}
label = True
class algwsym_config.numerics:
366    class numerics():
367
368        def __init__(self):
369            """This class holds settings for how numerical computation and
370            inputs are handled.
371            """
372            pass
373
374        def integers_as_exact(self):
375            """**This is a flag for informational purposes and interface
376            consistency. Changing the value will not change the behavior.**
377
378            To change the behavior call:
379            * `unset_integers_as_exact()` to turn this feature off.
380            * `set_integers_as_exact()` to turn this feature on (on by
381            default).
382
383            If set to `True` (the default) and if running in an
384            IPython/Jupyter environment any number input without a decimal
385            will be interpreted as a sympy integer. Thus, fractions and
386            related expressions will not evalute to floating point numbers,
387            but be maintained as exact expressions (e.g. 2/3 -> 2/3 not the
388            float 0.6666...).
389            """
390            return self.integers_as_exact
algwsym_config.numerics()
368        def __init__(self):
369            """This class holds settings for how numerical computation and
370            inputs are handled.
371            """
372            pass

This class holds settings for how numerical computation and inputs are handled.

def integers_as_exact(self):
374        def integers_as_exact(self):
375            """**This is a flag for informational purposes and interface
376            consistency. Changing the value will not change the behavior.**
377
378            To change the behavior call:
379            * `unset_integers_as_exact()` to turn this feature off.
380            * `set_integers_as_exact()` to turn this feature on (on by
381            default).
382
383            If set to `True` (the default) and if running in an
384            IPython/Jupyter environment any number input without a decimal
385            will be interpreted as a sympy integer. Thus, fractions and
386            related expressions will not evalute to floating point numbers,
387            but be maintained as exact expressions (e.g. 2/3 -> 2/3 not the
388            float 0.6666...).
389            """
390            return self.integers_as_exact

This is a flag for informational purposes and interface consistency. Changing the value will not change the behavior.

To change the behavior call:

If set to True (the default) and if running in an IPython/Jupyter environment any number input without a decimal will be interpreted as a sympy integer. Thus, fractions and related expressions will not evalute to floating point numbers, but be maintained as exact expressions (e.g. 2/3 -> 2/3 not the float 0.6666...).

ip = None
formatter = None
def set_integers_as_exact():
507def set_integers_as_exact():
508    """This operation causes any number input without a decimal that is
509    part of a Sympy expression to be interpreted as a sympy
510    integer, by using a custom preparser to cast integers within Sympy
511    expressions as Sympy integers (`Integer()`). It also sets the flag
512    `algwsym_config.numerics.integers_as_exact = True` This is the default
513    mode of algebra_with_sympy. To turn this off call
514    `unset_integers_as_exact()`.
515
516    NOTE: `2/3` --> `0.6666...` even when this is set, but  `2*x/3` -->
517    `Integer(2)/Integer(3)*x` if x is a sympy object. If `x` is just a Python
518    object `2*x/3` --> `x*0.6666666666...`.
519    """
520    ip = False
521    try:
522        from IPython import get_ipython
523        ip = True
524    except ModuleNotFoundError:
525        ip = False
526    if ip:
527        if get_ipython():
528            get_ipython().input_transformers_post.append(integers_as_exact)
529            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
530            if algwsym_config:
531                algwsym_config.numerics.integers_as_exact = True
532            else:
533                raise ValueError("The algwsym_config object does not exist.")
534    return

This operation causes any number input without a decimal that is part of a Sympy expression to be interpreted as a sympy integer, by using a custom preparser to cast integers within Sympy expressions as Sympy integers (Integer()). It also sets the flag algwsym_config.numerics.integers_as_exact = True This is the default mode of algebra_with_sympy. To turn this off call unset_integers_as_exact().

NOTE: 2/3 --> 0.6666... even when this is set, but 2*x/3 --> Integer(2)/Integer(3)*x if x is a sympy object. If x is just a Python object 2*x/3 --> x*0.6666666666....

def unset_integers_as_exact():
536def unset_integers_as_exact():
537    """This operation disables forcing of numbers input without
538    decimals being interpreted as sympy integers. Numbers input without a
539    decimal may be interpreted as floating point if they are part of an
540    expression that undergoes python evaluation (e.g. 2/3 -> 0.6666...). It
541    also sets the flag `algwsym_config.numerics.integers_as_exact = False`.
542    Call `set_integers_as_exact()` to avoid this conversion of rational
543    fractions and related expressions to floating point. Algebra_with_sympy
544    starts with `set_integers_as_exact()` enabled (
545    `algwsym_config.numerics.integers_as_exact = True`).
546    """
547    ip = False
548    try:
549        from IPython import get_ipython
550        ip = True
551    except ModuleNotFoundError:
552        ip = False
553    if ip:
554        if get_ipython():
555            pre = get_ipython().input_transformers_post
556            # The below looks excessively complicated, but more reliably finds the
557            # transformer to remove across varying IPython environments.
558            for k in pre:
559                if "integers_as_exact" in k.__name__:
560                    pre.remove(k)
561            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
562            if algwsym_config:
563                algwsym_config.numerics.integers_as_exact = False
564            else:
565                raise ValueError("The algwsym_config object does not exist.")
566
567    return

This operation disables forcing of numbers input without decimals being interpreted as sympy integers. Numbers input without a decimal may be interpreted as floating point if they are part of an expression that undergoes python evaluation (e.g. 2/3 -> 0.6666...). It also sets the flag algwsym_config.numerics.integers_as_exact = False. Call set_integers_as_exact() to avoid this conversion of rational fractions and related expressions to floating point. Algebra_with_sympy starts with set_integers_as_exact() enabled ( algwsym_config.numerics.integers_as_exact = True).

Eqn = <class 'sympy.core.equation.Equation'>
def units(names):
575def units(names):
576    """
577    This operation declares the symbols to be positive values, so that sympy
578    will handle them properly when simplifying expressions containing units.
579    Units defined this way are just unit symbols. If you want units that are
580    aware of conversions see sympy.physics.units.
581
582
583    :param string names: a string containing a space separated list of
584    symbols to be treated as units.
585
586    :return string list of defined units: calls `name = symbols(name,
587    positive=True)` in the interactive namespace for each symbol name.
588    """
589    from sympy.core.symbol import symbols
590    #import __main__ as shell
591    user_namespace = None
592    try:
593        from IPython import get_ipython
594        if get_ipython():
595            user_namespace = get_ipython().user_ns
596    except ModuleNotFoundError:
597        pass
598    syms = names.split(' ')
599    retstr = ''
600
601    if user_namespace==None:
602        import sys
603        frame_num = 0
604        frame_name = None
605        while frame_name != '__main__' and frame_num < 50:
606            user_namespace = sys._getframe(frame_num).f_globals
607            frame_num +=1
608            frame_name = user_namespace['__name__']
609    retstr +='('
610    for k in syms:
611        user_namespace[k] = symbols(k, positive = True)
612        retstr += k + ','
613    retstr = retstr[:-1] + ')'
614    return retstr

This operation declares the symbols to be positive values, so that sympy will handle them properly when simplifying expressions containing units. Units defined this way are just unit symbols. If you want units that are aware of conversions see sympy.physics.units.

Parameters
  • string names: a string containing a space separated list of symbols to be treated as units.
Returns

calls name = symbols(name, positive=True) in the interactive namespace for each symbol name.

def solve(f, *symbols, **flags):
617def solve(f, *symbols, **flags):
618    """
619    Override of sympy `solve()`.
620
621    If passed an expression and variable(s) to solve for it behaves
622    almost the same as normal solve with `dict = True`, except that solutions
623    are wrapped in a FiniteSet() to guarantee that the output will be pretty
624    printed in Jupyter like environments.
625
626    If passed an equation or equations it returns solutions as a
627    `FiniteSet()` of solutions, where each solution is represented by an
628    equation or set of equations.
629
630    To get a Python `list` of solutions (pre-0.11.0 behavior) rather than a
631    `FiniteSet` issue the command `algwsym_config.output.solve_to_list = True`.
632    This also prevents pretty-printing in IPython and Jupyter.
633
634    Examples
635    --------
636    >>> a, b, c, x, y = symbols('a b c x y', real = True)
637    >>> import sys
638    >>> sys.displayhook = __command_line_printing__ # set by default on normal initialization.
639    >>> eq1 = Eqn(abs(2*x+y),3)
640    >>> eq2 = Eqn(abs(x + 2*y),3)
641    >>> B = solve((eq1,eq2))
642
643    Default human readable output on command line
644    >>> B
645    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
646
647    To get raw output turn off by setting
648    >>> algwsym_config.output.human_text=False
649    >>> B
650    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)))
651
652    Pre-0.11.0 behavior where a python list of solutions is returned
653    >>> algwsym_config.output.solve_to_list = True
654    >>> solve((eq1,eq2))
655    [[Equation(x, -3), Equation(y, 3)], [Equation(x, -1), Equation(y, -1)], [Equation(x, 1), Equation(y, 1)], [Equation(x, 3), Equation(y, -3)]]
656    >>> algwsym_config.output.solve_to_list = False # reset to default
657
658    `algwsym_config.output.human_text = True` with
659    `algwsym_config.output.how_code=True` shows both.
660    In Jupyter-like environments `show_code=True` yields the Raw output and
661    a typeset version. If `show_code=False` (the default) only the
662    typeset version is shown in Jupyter.
663    >>> algwsym_config.output.show_code=True
664    >>> algwsym_config.output.human_text=True
665    >>> B
666    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)))
667    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
668    """
669    from sympy.solvers.solvers import solve
670    from sympy.sets.sets import FiniteSet
671    newf =[]
672    solns = []
673    displaysolns = []
674    contains_eqn = False
675    if hasattr(f,'__iter__'):
676        for k in f:
677            if isinstance(k, Equation):
678                newf.append(k.lhs-k.rhs)
679                contains_eqn = True
680            else:
681                newf.append(k)
682    else:
683        if isinstance(f, Equation):
684            newf.append(f.lhs - f.rhs)
685            contains_eqn = True
686        else:
687            newf.append(f)
688    flags['dict'] = True
689    result = solve(newf, *symbols, **flags)
690    if len(symbols) == 1 and hasattr(symbols[0], "__iter__"):
691        symbols = symbols[0]
692    if contains_eqn:
693        if len(result[0]) == 1:
694            for k in result:
695                for key in k.keys():
696                    val = k[key]
697                    tempeqn = Eqn(key, val)
698                    solns.append(tempeqn)
699            if len(solns) == len(symbols):
700                # sort according to the user-provided symbols
701                solns = sorted(solns, key=lambda x: symbols.index(x.lhs))
702        else:
703            for k in result:
704                solnset = []
705                for key in k.keys():
706                    val = k[key]
707                    tempeqn = Eqn(key, val)
708                    solnset.append(tempeqn)
709                if not algwsym_config.output.solve_to_list:
710                    solnset = FiniteSet(*solnset)
711                else:
712                    if len(solnset) == len(symbols):
713                        # sort according to the user-provided symbols
714                        solnset = sorted(solnset, key=lambda x: symbols.index(x.lhs))
715                solns.append(solnset)
716    else:
717        solns = result
718    if algwsym_config.output.solve_to_list:
719        if len(solns) == 1 and hasattr(solns[0], "__iter__"):
720            # no need to wrap a list of a single element inside another list
721            return solns[0]
722        return solns
723    else:
724        if len(solns) == 1:
725            # do not wrap a singleton in FiniteSet if it already is
726            for k in solns:
727                if isinstance(k, FiniteSet):
728                    return k
729        return FiniteSet(*solns)

Override of sympy solve().

If passed an expression and variable(s) to solve for it behaves almost the same as normal solve with dict = True, except that solutions are wrapped in a FiniteSet() to guarantee that the output will be pretty printed in Jupyter like environments.

If passed an equation or equations it returns solutions as a FiniteSet() of solutions, where each solution is represented by an equation or set of equations.

To get a Python list of solutions (pre-0.11.0 behavior) rather than a FiniteSet issue the command algwsym_config.output.solve_to_list = True. This also prevents pretty-printing in IPython and Jupyter.

Examples

>>> a, b, c, x, y = symbols('a b c x y', real = True)
>>> import sys
>>> sys.displayhook = __command_line_printing__ # set by default on normal initialization.
>>> eq1 = Eqn(abs(2*x+y),3)
>>> eq2 = Eqn(abs(x + 2*y),3)
>>> B = solve((eq1,eq2))

Default human readable output on command line

>>> B
{{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}

To get raw output turn off by setting

>>> algwsym_config.output.human_text=False
>>> B
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)))

Pre-0.11.0 behavior where a python list of solutions is returned

>>> algwsym_config.output.solve_to_list = True
>>> solve((eq1,eq2))
[[Equation(x, -3), Equation(y, 3)], [Equation(x, -1), Equation(y, -1)], [Equation(x, 1), Equation(y, 1)], [Equation(x, 3), Equation(y, -3)]]
>>> algwsym_config.output.solve_to_list = False # reset to default

algwsym_config.output.human_text = True with algwsym_config.output.how_code=True shows both. In Jupyter-like environments show_code=True yields the Raw output and a typeset version. If show_code=False (the default) only the typeset version is shown in Jupyter.

>>> algwsym_config.output.show_code=True
>>> algwsym_config.output.human_text=True
>>> B
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)))
{{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
def solveset(f, symbols, domain=Complexes):
731def solveset(f, symbols, domain=sympy.Complexes):
732    """
733    Very experimental override of sympy solveset, which we hope will replace
734    solve. Much is not working. It is not clear how to input a system of
735    equations unless you directly select `linsolve`, etc...
736    """
737    from sympy.solvers import solveset as solve
738    newf = []
739    solns = []
740    displaysolns = []
741    contains_eqn = False
742    if hasattr(f, '__iter__'):
743        for k in f:
744            if isinstance(k, Equation):
745                newf.append(k.lhs - k.rhs)
746                contains_eqn = True
747            else:
748                newf.append(k)
749    else:
750        if isinstance(f, Equation):
751            newf.append(f.lhs - f.rhs)
752            contains_eqn = True
753        else:
754            newf.append(f)
755    result = solve(*newf, symbols, domain=domain)
756    # if contains_eqn:
757    #     if len(result[0]) == 1:
758    #         for k in result:
759    #             for key in k.keys():
760    #                 val = k[key]
761    #                 tempeqn = Eqn(key, val)
762    #                 solns.append(tempeqn)
763    #         display(*solns)
764    #     else:
765    #         for k in result:
766    #             solnset = []
767    #             displayset = []
768    #             for key in k.keys():
769    #                 val = k[key]
770    #                 tempeqn = Eqn(key, val)
771    #                 solnset.append(tempeqn)
772    #                 if algwsym_config.output.show_solve_output:
773    #                     displayset.append(tempeqn)
774    #             if algwsym_config.output.show_solve_output:
775    #                 displayset.append('-----')
776    #             solns.append(solnset)
777    #             if algwsym_config.output.show_solve_output:
778    #                 for k in displayset:
779    #                     displaysolns.append(k)
780    #         if algwsym_config.output.show_solve_output:
781    #             display(*displaysolns)
782    # else:
783    solns = result
784    return solns

Very experimental override of sympy solveset, which we hope will replace solve. Much is not working. It is not clear how to input a system of equations unless you directly select linsolve, etc...

class Equality(sympy.core.relational.Equality):
786class Equality(Equality):
787    """
788    Extension of Equality class to include the ability to convert it to an
789    Equation.
790    """
791    def to_Equation(self):
792        """
793        Return: recasts the Equality as an Equation.
794        """
795        return Equation(self.lhs,self.rhs)
796
797    def to_Eqn(self):
798        """
799        Synonym for to_Equation.
800        Return: recasts the Equality as an Equation.
801        """
802        return self.to_Equation()

Extension of Equality class to include the ability to convert it to an Equation.

def to_Equation(self):
791    def to_Equation(self):
792        """
793        Return: recasts the Equality as an Equation.
794        """
795        return Equation(self.lhs,self.rhs)

Return: recasts the Equality as an Equation.

def to_Eqn(self):
797    def to_Eqn(self):
798        """
799        Synonym for to_Equation.
800        Return: recasts the Equality as an Equation.
801        """
802        return self.to_Equation()

Synonym for to_Equation. Return: recasts the Equality as an Equation.

default_assumptions = {}
Eq = <class 'Equality'>
abs = Abs