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
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
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:
algwsym_config.output.show_code
default isFalse
.algwsym_config.output.human_text
default isTrue
.algwsym_config.output.label
default isTrue
.algwsym_config.output.latex_as_equations
default isFalse
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}
.
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
If True
code versions of the equation expression will be
output in interactive environments. Default = False
.
If True
the human readable equation expression will be
output in text interactive environments. Default = 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.
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
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.
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:
unset_integers_as_exact()
to turn this feature off.set_integers_as_exact()
to turn this feature on (on by default).
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...).
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...
.
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
).
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.
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}}
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...
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.
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.