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