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 __latex_override__(expr, *arg):
392    algwsym_config = False
393    ip = False
394    try:
395        from IPython import get_ipython
396        if get_ipython():
397            ip = True
398    except ModuleNotFoundError:
399        pass
400    colab = False
401    try:
402        from google.colab import output
403        colab = True
404    except ModuleNotFoundError:
405        pass
406    show_code = False
407    latex_as_equations = False
408    if ip:
409        algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
410    else:
411        algwsym_config = globals()['algwsym_config']
412    if algwsym_config:
413        show_code = algwsym_config.output.show_code
414        latex_as_equations = algwsym_config.output.latex_as_equations
415    if show_code:
416        print("Code version: " + repr(expr))
417    if latex_as_equations:
418        return r'\begin{equation}'+latex(expr)+'\end{equation}'
419    else:
420        tempstr = ''
421        namestr = ''
422        if isinstance(expr, Equation):
423            namestr = expr._get_eqn_name()
424        if namestr != '' and algwsym_config and algwsym_config.output.label:
425            tempstr += r'$'+latex(expr)
426            # work around for colab's inconsistent handling of mixed latex and
427            # plain strings.
428            if colab:
429                colabname = namestr.replace('_', '\_')
430                tempstr += r'\,\,\,\,\,\,\,\,\,\,(' + colabname + ')$'
431            else:
432                tempstr += r'\,\,\,\,\,\,\,\,\,\,$(' + namestr + ')'
433            return tempstr
434        else:
435            return '$'+latex(expr) + '$'
436
437def __command_line_printing__(expr, *arg):
438    # print('Entering __command_line_printing__')
439    human_text = True
440    show_code = False
441    if algwsym_config:
442        human_text = algwsym_config.output.human_text
443        show_code = algwsym_config.output.show_code
444    tempstr = ''
445    if show_code:
446        tempstr += "Code version: " + repr(expr) + '\n'
447    if not human_text:
448        return print(tempstr + repr(expr))
449    else:
450        labelstr = ''
451        namestr = ''
452        if isinstance(expr, Equation):
453            namestr = expr._get_eqn_name()
454        if namestr != '' and algwsym_config.output.label:
455            labelstr += '          (' + namestr + ')'
456        return print(tempstr + str(expr) + labelstr)
457
458# Now we inject the formatting override(s)
459ip = None
460try:
461    from IPython import get_ipython
462    ip = get_ipython()
463except ModuleNotFoundError:
464    ip = false
465formatter = None
466if ip:
467    # In an environment that can display typeset latex
468    formatter = ip.display_formatter
469    old = formatter.formatters['text/latex'].for_type(Basic,
470                                                      __latex_override__)
471    # print("For type Basic overriding latex formatter = " + str(old))
472
473    # For the terminal based IPython
474    if "text/latex" not in formatter.active_types:
475        old = formatter.formatters['text/plain'].for_type(tuple,
476                                                    __command_line_printing__)
477        # print("For type tuple overriding plain text formatter = " + str(old))
478        for k in sympy.__all__:
479            if k in globals() and not "Printer" in k:
480                if isinstance(globals()[k], type):
481                    old = formatter.formatters['text/plain'].\
482                        for_type(globals()[k], __command_line_printing__)
483                    # print("For type "+str(k)+
484                    # " overriding plain text formatter = " + str(old))
485else:
486    # command line
487    # print("Overriding command line printing of python.")
488    sys.displayhook = __command_line_printing__
489
490# Numerics controls
491def set_integers_as_exact():
492    """This operation causes any number input without a decimal that is
493    part of a Sympy expression to be interpreted as a sympy
494    integer, by using a custom preparser to cast integers within Sympy
495    expressions as Sympy integers (`Integer()`). It also sets the flag
496    `algwsym_config.numerics.integers_as_exact = True` This is the default
497    mode of algebra_with_sympy. To turn this off call
498    `unset_integers_as_exact()`.
499
500    NOTE: `2/3` --> `0.6666...` even when this is set, but  `2*x/3` -->
501    `Integer(2)/Integer(3)*x` if x is a sympy object. If `x` is just a Python
502    object `2*x/3` --> `x*0.6666666666...`.
503    """
504    ip = False
505    try:
506        from IPython import get_ipython
507        ip = True
508    except ModuleNotFoundError:
509        ip = False
510    if ip:
511        if get_ipython():
512            get_ipython().input_transformers_post.append(integers_as_exact)
513            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
514            if algwsym_config:
515                algwsym_config.numerics.integers_as_exact = True
516            else:
517                raise ValueError("The algwsym_config object does not exist.")
518    return
519
520def unset_integers_as_exact():
521    """This operation disables forcing of numbers input without
522    decimals being interpreted as sympy integers. Numbers input without a
523    decimal may be interpreted as floating point if they are part of an
524    expression that undergoes python evaluation (e.g. 2/3 -> 0.6666...). It
525    also sets the flag `algwsym_config.numerics.integers_as_exact = False`.
526    Call `set_integers_as_exact()` to avoid this conversion of rational
527    fractions and related expressions to floating point. Algebra_with_sympy
528    starts with `set_integers_as_exact()` enabled (
529    `algwsym_config.numerics.integers_as_exact = True`).
530    """
531    ip = False
532    try:
533        from IPython import get_ipython
534        ip = True
535    except ModuleNotFoundError:
536        ip = False
537    if ip:
538        if get_ipython():
539            pre = get_ipython().input_transformers_post
540            # The below looks excessively complicated, but more reliably finds the
541            # transformer to remove across varying IPython environments.
542            for k in pre:
543                if "integers_as_exact" in k.__name__:
544                    pre.remove(k)
545            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
546            if algwsym_config:
547                algwsym_config.numerics.integers_as_exact = False
548            else:
549                raise ValueError("The algwsym_config object does not exist.")
550
551    return
552
553Eqn = Equation
554if ip and "text/latex" not in formatter.active_types:
555    old = formatter.formatters['text/plain'].for_type(Eqn,
556                                                __command_line_printing__)
557    # print("For type Equation overriding plain text formatter = " + str(old))
558
559def units(names):
560    """
561    This operation declares the symbols to be positive values, so that sympy
562    will handle them properly when simplifying expressions containing units.
563    Units defined this way are just unit symbols. If you want units that are
564    aware of conversions see sympy.physics.units.
565
566
567    :param string names: a string containing a space separated list of
568    symbols to be treated as units.
569
570    :return string list of defined units: calls `name = symbols(name,
571    positive=True)` in the interactive namespace for each symbol name.
572    """
573    from sympy.core.symbol import symbols
574    #import __main__ as shell
575    user_namespace = None
576    try:
577        from IPython import get_ipython
578        if get_ipython():
579            user_namespace = get_ipython().user_ns
580    except ModuleNotFoundError:
581        pass
582    syms = names.split(' ')
583    retstr = ''
584
585    if user_namespace==None:
586        import sys
587        frame_num = 0
588        frame_name = None
589        while frame_name != '__main__' and frame_num < 50:
590            user_namespace = sys._getframe(frame_num).f_globals
591            frame_num +=1
592            frame_name = user_namespace['__name__']
593    retstr +='('
594    for k in syms:
595        user_namespace[k] = symbols(k, positive = True)
596        retstr += k + ','
597    retstr = retstr[:-1] + ')'
598    return retstr
599
600
601def solve(f, *symbols, **flags):
602    """
603    Override of sympy `solve()`.
604
605    If passed an expression and variable(s) to solve for it behaves
606    almost the same as normal solve with `dict = True`, except that solutions
607    are wrapped in a FiniteSet() to guarantee that the output will be pretty
608    printed in Jupyter like environments.
609
610    If passed an equation or equations it returns solutions as a
611    `FiniteSet()` of solutions, where each solution is represented by an
612    equation or set of equations.
613
614    To get a Python `list` of solutions (pre-0.11.0 behavior) rather than a
615    `FiniteSet` issue the command `algwsym_config.output.solve_to_list = True`.
616    This also prevents pretty-printing in IPython and Jupyter.
617
618    Examples
619    --------
620    >>> a, b, c, x, y = symbols('a b c x y', real = True)
621    >>> import sys
622    >>> sys.displayhook = __command_line_printing__ # set by default on normal initialization.
623    >>> eq1 = Eqn(abs(2*x+y),3)
624    >>> eq2 = Eqn(abs(x + 2*y),3)
625    >>> B = solve((eq1,eq2))
626
627    Default human readable output on command line
628    >>> B
629    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
630
631    To get raw output turn off by setting
632    >>> algwsym_config.output.human_text=False
633    >>> B
634    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)))
635
636    Pre-0.11.0 behavior where a python list of solutions is returned
637    >>> algwsym_config.output.solve_to_list = True
638    >>> solve((eq1,eq2))
639    [[Equation(x, -3), Equation(y, 3)], [Equation(x, -1), Equation(y, -1)], [Equation(x, 1), Equation(y, 1)], [Equation(x, 3), Equation(y, -3)]]
640    >>> algwsym_config.output.solve_to_list = False # reset to default
641
642    `algwsym_config.output.human_text = True` with
643    `algwsym_config.output.how_code=True` shows both.
644    In Jupyter-like environments `show_code=True` yields the Raw output and
645    a typeset version. If `show_code=False` (the default) only the
646    typeset version is shown in Jupyter.
647    >>> algwsym_config.output.show_code=True
648    >>> algwsym_config.output.human_text=True
649    >>> B
650    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)))
651    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
652    """
653    from sympy.solvers.solvers import solve
654    from sympy.sets.sets import FiniteSet
655    newf =[]
656    solns = []
657    displaysolns = []
658    contains_eqn = False
659    if hasattr(f,'__iter__'):
660        for k in f:
661            if isinstance(k, Equation):
662                newf.append(k.lhs-k.rhs)
663                contains_eqn = True
664            else:
665                newf.append(k)
666    else:
667        if isinstance(f, Equation):
668            newf.append(f.lhs - f.rhs)
669            contains_eqn = True
670        else:
671            newf.append(f)
672    flags['dict'] = True
673    result = solve(newf, *symbols, **flags)
674    if len(symbols) == 1 and hasattr(symbols[0], "__iter__"):
675        symbols = symbols[0]
676    if contains_eqn:
677        if len(result[0]) == 1:
678            for k in result:
679                for key in k.keys():
680                    val = k[key]
681                    tempeqn = Eqn(key, val)
682                    solns.append(tempeqn)
683            if len(solns) == len(symbols):
684                # sort according to the user-provided symbols
685                solns = sorted(solns, key=lambda x: symbols.index(x.lhs))
686        else:
687            for k in result:
688                solnset = []
689                for key in k.keys():
690                    val = k[key]
691                    tempeqn = Eqn(key, val)
692                    solnset.append(tempeqn)
693                if not algwsym_config.output.solve_to_list:
694                    solnset = FiniteSet(*solnset)
695                else:
696                    if len(solnset) == len(symbols):
697                        # sort according to the user-provided symbols
698                        solnset = sorted(solnset, key=lambda x: symbols.index(x.lhs))
699                solns.append(solnset)
700    else:
701        solns = result
702    if algwsym_config.output.solve_to_list:
703        if len(solns) == 1 and hasattr(solns[0], "__iter__"):
704            # no need to wrap a list of a single element inside another list
705            return solns[0]
706        return solns
707    else:
708        if len(solns) == 1:
709            # do not wrap a singleton in FiniteSet if it already is
710            for k in solns:
711                if isinstance(k, FiniteSet):
712                    return k
713        return FiniteSet(*solns)
714
715def solveset(f, symbols, domain=sympy.Complexes):
716    """
717    Very experimental override of sympy solveset, which we hope will replace
718    solve. Much is not working. It is not clear how to input a system of
719    equations unless you directly select `linsolve`, etc...
720    """
721    from sympy.solvers import solveset as solve
722    newf = []
723    solns = []
724    displaysolns = []
725    contains_eqn = False
726    if hasattr(f, '__iter__'):
727        for k in f:
728            if isinstance(k, Equation):
729                newf.append(k.lhs - k.rhs)
730                contains_eqn = True
731            else:
732                newf.append(k)
733    else:
734        if isinstance(f, Equation):
735            newf.append(f.lhs - f.rhs)
736            contains_eqn = True
737        else:
738            newf.append(f)
739    result = solve(*newf, symbols, domain=domain)
740    # if contains_eqn:
741    #     if len(result[0]) == 1:
742    #         for k in result:
743    #             for key in k.keys():
744    #                 val = k[key]
745    #                 tempeqn = Eqn(key, val)
746    #                 solns.append(tempeqn)
747    #         display(*solns)
748    #     else:
749    #         for k in result:
750    #             solnset = []
751    #             displayset = []
752    #             for key in k.keys():
753    #                 val = k[key]
754    #                 tempeqn = Eqn(key, val)
755    #                 solnset.append(tempeqn)
756    #                 if algwsym_config.output.show_solve_output:
757    #                     displayset.append(tempeqn)
758    #             if algwsym_config.output.show_solve_output:
759    #                 displayset.append('-----')
760    #             solns.append(solnset)
761    #             if algwsym_config.output.show_solve_output:
762    #                 for k in displayset:
763    #                     displaysolns.append(k)
764    #         if algwsym_config.output.show_solve_output:
765    #             display(*displaysolns)
766    # else:
767    solns = result
768    return solns
769
770
771class Equality(Equality):
772    """
773    Extension of Equality class to include the ability to convert it to an
774    Equation.
775    """
776    def to_Equation(self):
777        """
778        Return: recasts the Equality as an Equation.
779        """
780        return Equation(self.lhs,self.rhs)
781
782    def to_Eqn(self):
783        """
784        Synonym for to_Equation.
785        Return: recasts the Equality as an Equation.
786        """
787        return self.to_Equation()
788
789Eq = Equality
790
791def __FiniteSet__repr__override__(self):
792    """Override of the `FiniteSet.__repr__(self)` to overcome sympy's
793    inconsistent wrapping of Finite Sets which prevents reliable use of
794    copy and paste of the code representation.
795    """
796    insidestr = ""
797    for k in self.args:
798        insidestr += k.__repr__() +', '
799    insidestr = insidestr[:-2]
800    reprstr = "FiniteSet("+ insidestr + ")"
801    return reprstr
802
803sympy.sets.FiniteSet.__repr__ = __FiniteSet__repr__override__
804
805def __FiniteSet__str__override__(self):
806    """Override of the `FiniteSet.__str__(self)` to overcome sympy's
807    inconsistent wrapping of Finite Sets which prevents reliable use of
808    copy and paste of the code representation.
809    """
810    insidestr = ""
811    for k in self.args:
812        insidestr += str(k) + ', '
813    insidestr = insidestr[:-2]
814    strrep = "{"+ insidestr + "}"
815    return strrep
816
817sympy.sets.FiniteSet.__str__ = __FiniteSet__str__override__
818
819# Redirect python abs() to Abs()
820abs = 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():
492def set_integers_as_exact():
493    """This operation causes any number input without a decimal that is
494    part of a Sympy expression to be interpreted as a sympy
495    integer, by using a custom preparser to cast integers within Sympy
496    expressions as Sympy integers (`Integer()`). It also sets the flag
497    `algwsym_config.numerics.integers_as_exact = True` This is the default
498    mode of algebra_with_sympy. To turn this off call
499    `unset_integers_as_exact()`.
500
501    NOTE: `2/3` --> `0.6666...` even when this is set, but  `2*x/3` -->
502    `Integer(2)/Integer(3)*x` if x is a sympy object. If `x` is just a Python
503    object `2*x/3` --> `x*0.6666666666...`.
504    """
505    ip = False
506    try:
507        from IPython import get_ipython
508        ip = True
509    except ModuleNotFoundError:
510        ip = False
511    if ip:
512        if get_ipython():
513            get_ipython().input_transformers_post.append(integers_as_exact)
514            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
515            if algwsym_config:
516                algwsym_config.numerics.integers_as_exact = True
517            else:
518                raise ValueError("The algwsym_config object does not exist.")
519    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():
521def unset_integers_as_exact():
522    """This operation disables forcing of numbers input without
523    decimals being interpreted as sympy integers. Numbers input without a
524    decimal may be interpreted as floating point if they are part of an
525    expression that undergoes python evaluation (e.g. 2/3 -> 0.6666...). It
526    also sets the flag `algwsym_config.numerics.integers_as_exact = False`.
527    Call `set_integers_as_exact()` to avoid this conversion of rational
528    fractions and related expressions to floating point. Algebra_with_sympy
529    starts with `set_integers_as_exact()` enabled (
530    `algwsym_config.numerics.integers_as_exact = True`).
531    """
532    ip = False
533    try:
534        from IPython import get_ipython
535        ip = True
536    except ModuleNotFoundError:
537        ip = False
538    if ip:
539        if get_ipython():
540            pre = get_ipython().input_transformers_post
541            # The below looks excessively complicated, but more reliably finds the
542            # transformer to remove across varying IPython environments.
543            for k in pre:
544                if "integers_as_exact" in k.__name__:
545                    pre.remove(k)
546            algwsym_config = get_ipython().user_ns.get("algwsym_config", False)
547            if algwsym_config:
548                algwsym_config.numerics.integers_as_exact = False
549            else:
550                raise ValueError("The algwsym_config object does not exist.")
551
552    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):
560def units(names):
561    """
562    This operation declares the symbols to be positive values, so that sympy
563    will handle them properly when simplifying expressions containing units.
564    Units defined this way are just unit symbols. If you want units that are
565    aware of conversions see sympy.physics.units.
566
567
568    :param string names: a string containing a space separated list of
569    symbols to be treated as units.
570
571    :return string list of defined units: calls `name = symbols(name,
572    positive=True)` in the interactive namespace for each symbol name.
573    """
574    from sympy.core.symbol import symbols
575    #import __main__ as shell
576    user_namespace = None
577    try:
578        from IPython import get_ipython
579        if get_ipython():
580            user_namespace = get_ipython().user_ns
581    except ModuleNotFoundError:
582        pass
583    syms = names.split(' ')
584    retstr = ''
585
586    if user_namespace==None:
587        import sys
588        frame_num = 0
589        frame_name = None
590        while frame_name != '__main__' and frame_num < 50:
591            user_namespace = sys._getframe(frame_num).f_globals
592            frame_num +=1
593            frame_name = user_namespace['__name__']
594    retstr +='('
595    for k in syms:
596        user_namespace[k] = symbols(k, positive = True)
597        retstr += k + ','
598    retstr = retstr[:-1] + ')'
599    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):
602def solve(f, *symbols, **flags):
603    """
604    Override of sympy `solve()`.
605
606    If passed an expression and variable(s) to solve for it behaves
607    almost the same as normal solve with `dict = True`, except that solutions
608    are wrapped in a FiniteSet() to guarantee that the output will be pretty
609    printed in Jupyter like environments.
610
611    If passed an equation or equations it returns solutions as a
612    `FiniteSet()` of solutions, where each solution is represented by an
613    equation or set of equations.
614
615    To get a Python `list` of solutions (pre-0.11.0 behavior) rather than a
616    `FiniteSet` issue the command `algwsym_config.output.solve_to_list = True`.
617    This also prevents pretty-printing in IPython and Jupyter.
618
619    Examples
620    --------
621    >>> a, b, c, x, y = symbols('a b c x y', real = True)
622    >>> import sys
623    >>> sys.displayhook = __command_line_printing__ # set by default on normal initialization.
624    >>> eq1 = Eqn(abs(2*x+y),3)
625    >>> eq2 = Eqn(abs(x + 2*y),3)
626    >>> B = solve((eq1,eq2))
627
628    Default human readable output on command line
629    >>> B
630    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
631
632    To get raw output turn off by setting
633    >>> algwsym_config.output.human_text=False
634    >>> B
635    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)))
636
637    Pre-0.11.0 behavior where a python list of solutions is returned
638    >>> algwsym_config.output.solve_to_list = True
639    >>> solve((eq1,eq2))
640    [[Equation(x, -3), Equation(y, 3)], [Equation(x, -1), Equation(y, -1)], [Equation(x, 1), Equation(y, 1)], [Equation(x, 3), Equation(y, -3)]]
641    >>> algwsym_config.output.solve_to_list = False # reset to default
642
643    `algwsym_config.output.human_text = True` with
644    `algwsym_config.output.how_code=True` shows both.
645    In Jupyter-like environments `show_code=True` yields the Raw output and
646    a typeset version. If `show_code=False` (the default) only the
647    typeset version is shown in Jupyter.
648    >>> algwsym_config.output.show_code=True
649    >>> algwsym_config.output.human_text=True
650    >>> B
651    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)))
652    {{x = -3, y = 3}, {x = -1, y = -1}, {x = 1, y = 1}, {x = 3, y = -3}}
653    """
654    from sympy.solvers.solvers import solve
655    from sympy.sets.sets import FiniteSet
656    newf =[]
657    solns = []
658    displaysolns = []
659    contains_eqn = False
660    if hasattr(f,'__iter__'):
661        for k in f:
662            if isinstance(k, Equation):
663                newf.append(k.lhs-k.rhs)
664                contains_eqn = True
665            else:
666                newf.append(k)
667    else:
668        if isinstance(f, Equation):
669            newf.append(f.lhs - f.rhs)
670            contains_eqn = True
671        else:
672            newf.append(f)
673    flags['dict'] = True
674    result = solve(newf, *symbols, **flags)
675    if len(symbols) == 1 and hasattr(symbols[0], "__iter__"):
676        symbols = symbols[0]
677    if contains_eqn:
678        if len(result[0]) == 1:
679            for k in result:
680                for key in k.keys():
681                    val = k[key]
682                    tempeqn = Eqn(key, val)
683                    solns.append(tempeqn)
684            if len(solns) == len(symbols):
685                # sort according to the user-provided symbols
686                solns = sorted(solns, key=lambda x: symbols.index(x.lhs))
687        else:
688            for k in result:
689                solnset = []
690                for key in k.keys():
691                    val = k[key]
692                    tempeqn = Eqn(key, val)
693                    solnset.append(tempeqn)
694                if not algwsym_config.output.solve_to_list:
695                    solnset = FiniteSet(*solnset)
696                else:
697                    if len(solnset) == len(symbols):
698                        # sort according to the user-provided symbols
699                        solnset = sorted(solnset, key=lambda x: symbols.index(x.lhs))
700                solns.append(solnset)
701    else:
702        solns = result
703    if algwsym_config.output.solve_to_list:
704        if len(solns) == 1 and hasattr(solns[0], "__iter__"):
705            # no need to wrap a list of a single element inside another list
706            return solns[0]
707        return solns
708    else:
709        if len(solns) == 1:
710            # do not wrap a singleton in FiniteSet if it already is
711            for k in solns:
712                if isinstance(k, FiniteSet):
713                    return k
714        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):
716def solveset(f, symbols, domain=sympy.Complexes):
717    """
718    Very experimental override of sympy solveset, which we hope will replace
719    solve. Much is not working. It is not clear how to input a system of
720    equations unless you directly select `linsolve`, etc...
721    """
722    from sympy.solvers import solveset as solve
723    newf = []
724    solns = []
725    displaysolns = []
726    contains_eqn = False
727    if hasattr(f, '__iter__'):
728        for k in f:
729            if isinstance(k, Equation):
730                newf.append(k.lhs - k.rhs)
731                contains_eqn = True
732            else:
733                newf.append(k)
734    else:
735        if isinstance(f, Equation):
736            newf.append(f.lhs - f.rhs)
737            contains_eqn = True
738        else:
739            newf.append(f)
740    result = solve(*newf, symbols, domain=domain)
741    # if contains_eqn:
742    #     if len(result[0]) == 1:
743    #         for k in result:
744    #             for key in k.keys():
745    #                 val = k[key]
746    #                 tempeqn = Eqn(key, val)
747    #                 solns.append(tempeqn)
748    #         display(*solns)
749    #     else:
750    #         for k in result:
751    #             solnset = []
752    #             displayset = []
753    #             for key in k.keys():
754    #                 val = k[key]
755    #                 tempeqn = Eqn(key, val)
756    #                 solnset.append(tempeqn)
757    #                 if algwsym_config.output.show_solve_output:
758    #                     displayset.append(tempeqn)
759    #             if algwsym_config.output.show_solve_output:
760    #                 displayset.append('-----')
761    #             solns.append(solnset)
762    #             if algwsym_config.output.show_solve_output:
763    #                 for k in displayset:
764    #                     displaysolns.append(k)
765    #         if algwsym_config.output.show_solve_output:
766    #             display(*displaysolns)
767    # else:
768    solns = result
769    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):
772class Equality(Equality):
773    """
774    Extension of Equality class to include the ability to convert it to an
775    Equation.
776    """
777    def to_Equation(self):
778        """
779        Return: recasts the Equality as an Equation.
780        """
781        return Equation(self.lhs,self.rhs)
782
783    def to_Eqn(self):
784        """
785        Synonym for to_Equation.
786        Return: recasts the Equality as an Equation.
787        """
788        return self.to_Equation()

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

def to_Equation(self):
777    def to_Equation(self):
778        """
779        Return: recasts the Equality as an Equation.
780        """
781        return Equation(self.lhs,self.rhs)

Return: recasts the Equality as an Equation.

def to_Eqn(self):
783    def to_Eqn(self):
784        """
785        Synonym for to_Equation.
786        Return: recasts the Equality as an Equation.
787        """
788        return self.to_Equation()

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

default_assumptions = {}
Inherited Members
sympy.core.relational.Equality
rel_op
is_Equality
binary_symbols
integrate
as_poly
sympy.core.relational.Relational
ValidRelationOperator
is_Relational
lhs
rhs
reversed
reversedsign
negated
weak
strict
canonical
equals
expand
sympy.logic.boolalg.Boolean
kind
to_nnf
as_set
sympy.core.basic.Basic
is_number
is_Atom
is_Symbol
is_symbol
is_Indexed
is_Dummy
is_Wild
is_Function
is_Add
is_Mul
is_Pow
is_Number
is_Float
is_Rational
is_Integer
is_NumberSymbol
is_Order
is_Derivative
is_Piecewise
is_Poly
is_AlgebraicNumber
is_Boolean
is_Not
is_Matrix
is_Vector
is_Point
is_MatAdd
is_MatMul
is_real
is_extended_real
is_zero
is_negative
is_commutative
copy
assumptions0
compare
fromiter
class_key
sort_key
dummy_eq
atoms
free_symbols
expr_free_symbols
as_dummy
canonical_variables
rcall
is_hypergeometric
is_comparable
func
args
as_content_primitive
subs
xreplace
has
has_xfree
has_free
replace
find
count
matches
match
count_ops
doit
simplify
refine
rewrite
could_extract_minus_sign
is_irrational
is_nonnegative
is_extended_negative
is_finite
is_composite
is_hermitian
is_extended_positive
is_noninteger
is_integer
is_imaginary
is_even
is_algebraic
is_odd
is_transcendental
is_infinite
is_extended_nonnegative
is_polar
is_complex
is_nonzero
is_extended_nonpositive
is_positive
is_nonpositive
is_extended_nonzero
is_rational
is_antihermitian
is_prime
sympy.core.evalf.EvalfMixin
evalf
n
Eq = <class 'Equality'>
abs = Abs