start to reorganise files
This commit is contained in:
14
pymath/calculus/__init__.py
Normal file
14
pymath/calculus/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .expression import Expression
|
||||
from .polynom import Polynom
|
||||
from .fraction import Fraction
|
||||
from .random_expression import random_str
|
||||
#from .render import txt,tex
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
601
pymath/calculus/abstract_polynom.py
Normal file
601
pymath/calculus/abstract_polynom.py
Normal file
@@ -0,0 +1,601 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .explicable import Explicable
|
||||
from .expression import Expression
|
||||
from .operator import op
|
||||
from .generic import spe_zip, expand_list, isNumber, transpose_fill, flatten_list, isPolynom, isNumerand
|
||||
from .render import txt,tex
|
||||
from itertools import chain
|
||||
from functools import wraps
|
||||
|
||||
def power_cache(fun):
|
||||
"""Decorator which cache calculated powers of polynoms """
|
||||
cache = {}
|
||||
@wraps(fun)
|
||||
def cached_fun(self, power):
|
||||
#print("cache -> ", cache)
|
||||
if (tuple(self._coef), power) in cache.keys():
|
||||
return cache[(tuple(self._coef), power)]
|
||||
else:
|
||||
poly_powered = fun(self, power)
|
||||
cache[(tuple(self._coef), power)] = poly_powered
|
||||
return poly_powered
|
||||
return cached_fun
|
||||
|
||||
class AbstractPolynom(Explicable):
|
||||
|
||||
"""The mathematic definition of a polynom. It will be the parent class of Polynom (classical polynoms) and later of SquareRoot polynoms"""
|
||||
|
||||
def __init__(self, coefs = [1], letter = "x", name = "P"):
|
||||
"""Initiate the polynom
|
||||
|
||||
:param coef: coefficients of the polynom (ascending degree sorted)
|
||||
3 possibles type of coefficent:
|
||||
- a : simple "number". [1,2] designate 1 + 2x
|
||||
- [a,b,c]: list of coeficient for same degree. [1,[2,3],4] designate 1 + 2x + 3x + 4x^2
|
||||
- a: a Expression. [1, Expression("2+3"), 4] designate 1 + (2+3)x + 4x^2
|
||||
:param letter: the string describing the unknown
|
||||
:param name: Name of the polynom
|
||||
|
||||
>>> P = AbstractPolynom([1, 2, 3])
|
||||
>>> P.mainOp
|
||||
'+'
|
||||
>>> P.name
|
||||
'P'
|
||||
>>> P._letter
|
||||
'x'
|
||||
>>> AbstractPolynom([1]).mainOp
|
||||
'*'
|
||||
>>> AbstractPolynom([0, 0, 3]).mainOp
|
||||
'*'
|
||||
>>> AbstractPolynom([1, 2, 3])._letter
|
||||
'x'
|
||||
>>> AbstractPolynom([1, 2, 3], "y")._letter
|
||||
'y'
|
||||
>>> AbstractPolynom([1, 2, 3], name = "Q").name
|
||||
'Q'
|
||||
"""
|
||||
super(AbstractPolynom, self).__init__()
|
||||
|
||||
try:
|
||||
# Remove 0 at the end of the coefs
|
||||
while coefs[-1] == 0:
|
||||
coefs = coefs[:-1]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if coefs == []:
|
||||
coefs = [0]
|
||||
|
||||
self.feed_coef(coefs)
|
||||
self._letter = letter
|
||||
self.name = name
|
||||
|
||||
if self.is_monom():
|
||||
self.mainOp = op.mul
|
||||
else:
|
||||
self.mainOp = op.add
|
||||
|
||||
self._isPolynom = 1
|
||||
|
||||
def feed_coef(self, l_coef):
|
||||
"""Feed coef of the polynom. Manage differently whether it's a number or an expression
|
||||
|
||||
:l_coef: list of coef
|
||||
"""
|
||||
self._coef = []
|
||||
for coef in l_coef:
|
||||
if type(coef) == list and len(coef)==1:
|
||||
self._coef.append(coef[0])
|
||||
else:
|
||||
self._coef.append(coef)
|
||||
|
||||
@property
|
||||
def degree(self):
|
||||
"""Getting the degree fo the polynom
|
||||
|
||||
:returns: the degree of the polynom
|
||||
|
||||
>>> AbstractPolynom([1, 2, 3]).degree
|
||||
2
|
||||
>>> AbstractPolynom([1]).degree
|
||||
0
|
||||
"""
|
||||
return len(self._coef) - 1
|
||||
|
||||
def is_monom(self):
|
||||
"""is the polynom a monom (only one coefficent)
|
||||
|
||||
:returns: 1 if yes 0 otherwise
|
||||
|
||||
>>> AbstractPolynom([1, 2, 3]).is_monom()
|
||||
0
|
||||
>>> AbstractPolynom([1]).is_monom()
|
||||
1
|
||||
"""
|
||||
if len([i for i in self._coef if i != 0])==1:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def give_name(self, name):
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return str(Expression(self.postfix_tokens))
|
||||
|
||||
def __repr__(self):
|
||||
return "< " + str(self.__class__) + " " + str(self._coef) + ">"
|
||||
|
||||
def __txt__(self):
|
||||
return txt(self.postfix_tokens)
|
||||
|
||||
def __tex__(self):
|
||||
return tex(self.postfix_tokens)
|
||||
|
||||
def coef_postfix(self, a, i):
|
||||
"""Return the postfix display of a coeficient
|
||||
|
||||
:param a: value for the coeficient (/!\ as a postfix list)
|
||||
:param i: power
|
||||
:returns: postfix tokens of coef
|
||||
|
||||
>>> p = AbstractPolynom()
|
||||
>>> p.coef_postfix([3],2)
|
||||
[3, 'x', 2, '^', '*']
|
||||
>>> p.coef_postfix([0],1)
|
||||
[]
|
||||
>>> p.coef_postfix([3],0)
|
||||
[3]
|
||||
>>> p.coef_postfix([3],1)
|
||||
[3, 'x', '*']
|
||||
>>> p.coef_postfix([1],1)
|
||||
['x']
|
||||
>>> p.coef_postfix([1],2)
|
||||
['x', 2, '^']
|
||||
|
||||
"""
|
||||
# TODO: Couille certaine avec txt à qui il fait donner des opérateurs tout beau! |mar. nov. 11 13:08:35 CET 2014
|
||||
ans =[]
|
||||
if a == [0]:
|
||||
pass
|
||||
elif i == 0:
|
||||
ans = a
|
||||
elif i == 1:
|
||||
ans = a * (a!=[1]) + [self._letter] + [op.mul] * (a!=[1])
|
||||
else:
|
||||
ans = a * (a!=[1]) + [self._letter, i, op.pw] + [op.mul] * (a!=[1])
|
||||
|
||||
return ans
|
||||
|
||||
@property
|
||||
def postfix_tokens(self):
|
||||
"""Return the postfix form of the polynom
|
||||
|
||||
:returns: the postfix list of polynom's tokens
|
||||
|
||||
>>> p = AbstractPolynom([1, 2])
|
||||
>>> p.postfix_tokens
|
||||
[2, 'x', '*', 1, '+']
|
||||
>>> p = AbstractPolynom([1, -2])
|
||||
>>> p.postfix_tokens
|
||||
[2, 'x', '*', '-', 1, '+']
|
||||
>>> p = AbstractPolynom([1,2,3])
|
||||
>>> p.postfix_tokens
|
||||
[3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+']
|
||||
>>> p = AbstractPolynom([1])
|
||||
>>> p.postfix_tokens
|
||||
[1]
|
||||
>>> p = AbstractPolynom([0])
|
||||
>>> p.postfix_tokens
|
||||
[0]
|
||||
>>> p = AbstractPolynom([1,[2,3]])
|
||||
>>> p.postfix_tokens
|
||||
[2, 'x', '*', 3, 'x', '*', '+', 1, '+']
|
||||
>>> p = AbstractPolynom([1,[2,-3]])
|
||||
>>> p.postfix_tokens
|
||||
[2, 'x', '*', 3, 'x', '*', '-', 1, '+']
|
||||
>>> p = AbstractPolynom([1,[-2,-3]])
|
||||
>>> p.postfix_tokens
|
||||
[2, 'x', '*', '-', 3, 'x', '*', '-', 1, '+']
|
||||
>>> from pymath.calculus.expression import Expression
|
||||
>>> from pymath.calculus.operator import op
|
||||
>>> e = Expression([2,3,op.add])
|
||||
>>> p = AbstractPolynom([1,e])
|
||||
>>> p.postfix_tokens
|
||||
[2, 3, '+', 'x', '*', 1, '+']
|
||||
|
||||
"""
|
||||
if self == 0:
|
||||
return [0]
|
||||
# TODO: Faudrait factoriser un peu tout ça..! |dim. déc. 21 16:02:34 CET 2014
|
||||
postfix = []
|
||||
for (i,a) in list(enumerate(self._coef))[::-1]:
|
||||
operator = [op.add]
|
||||
operator_sub1 = []
|
||||
if type(a) == Expression:
|
||||
# case coef is an arithmetic expression
|
||||
c = self.coef_postfix(a.postfix_tokens,i)
|
||||
if c != []:
|
||||
postfix.append(c)
|
||||
if len(postfix) > 1:
|
||||
postfix += operator
|
||||
|
||||
elif type(a) == list:
|
||||
# case need to repeat the x^i
|
||||
for b in a:
|
||||
operator = [op.add]
|
||||
operator_sub1 = []
|
||||
if len(postfix) == 0 and isNumber(b) and b < 0:
|
||||
try:
|
||||
b = [(-b)[-1]]
|
||||
except TypeError:
|
||||
b = [-b]
|
||||
operator_sub1 = [op.sub1]
|
||||
elif len(postfix) > 0 and isNumber(b) and b < 0:
|
||||
try:
|
||||
b = [(-b)[-1]]
|
||||
except TypeError:
|
||||
b = [-b]
|
||||
operator = [op.sub]
|
||||
else:
|
||||
b = [b]
|
||||
c = self.coef_postfix(b,i)
|
||||
if c != []:
|
||||
postfix.append(c)
|
||||
if len(postfix) > 1:
|
||||
postfix += operator_sub1
|
||||
postfix += operator
|
||||
postfix += operator_sub1
|
||||
|
||||
elif a != 0:
|
||||
if len(postfix) == 0 and a < 0:
|
||||
try:
|
||||
a = [(-a)[-1]]
|
||||
except TypeError:
|
||||
a = [-a]
|
||||
operator_sub1 = [op.sub1]
|
||||
elif len(postfix) > 0 and a < 0:
|
||||
try:
|
||||
a = [(-a)[-1]]
|
||||
except TypeError:
|
||||
a = [-a]
|
||||
operator = [op.sub]
|
||||
else:
|
||||
a = [a]
|
||||
|
||||
c = self.coef_postfix(a,i)
|
||||
if c != []:
|
||||
postfix.append(c)
|
||||
if len(postfix) > 1:
|
||||
postfix += operator_sub1
|
||||
postfix += operator
|
||||
postfix += operator_sub1
|
||||
|
||||
return flatten_list(postfix)
|
||||
|
||||
def conv2poly(self, other):
|
||||
"""Convert anything number into a polynom
|
||||
|
||||
>>> P = AbstractPolynom([1,2,3])
|
||||
>>> P.conv2poly(1)
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [1]>
|
||||
>>> P.conv2poly(0)
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0]>
|
||||
|
||||
"""
|
||||
if isNumber(other) and not isPolynom(other):
|
||||
return AbstractPolynom([other], letter = self._letter)
|
||||
elif isPolynom(other):
|
||||
return other
|
||||
else:
|
||||
raise ValueError(type(other) + " can't be converted into a polynom")
|
||||
|
||||
def reduce(self):
|
||||
"""Compute coefficients which have same degree
|
||||
|
||||
:returns: new AbstractPolynom with numbers coefficients
|
||||
|
||||
>>> P = AbstractPolynom([1,2,3])
|
||||
>>> Q = P.reduce()
|
||||
>>> Q
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [1, 2, 3]>
|
||||
>>> Q.steps
|
||||
[]
|
||||
>>> P = AbstractPolynom([[1,2], [3,4,5], 6])
|
||||
>>> Q = P.reduce()
|
||||
>>> Q
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [3, 12, 6]>
|
||||
>>> for i in Q.explain():
|
||||
... print(i)
|
||||
6 x^{ 2 } + ( 3 + 4 + 5 ) x + 1 + 2
|
||||
6 x^{ 2 } + ( 7 + 5 ) x + 3
|
||||
6 x^{ 2 } + 12 x + 3
|
||||
>>> Q.steps
|
||||
[< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [< <class 'pymath.calculus.expression.Expression'> [1, 2, '+'] >, < <class 'pymath.calculus.expression.Expression'> [3, 4, '+', 5, '+'] >, 6]>, < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [3, < <class 'pymath.calculus.expression.Expression'> [7, 5, '+'] >, 6]>]
|
||||
"""
|
||||
|
||||
# TODO: It doesn't not compute quick enough |ven. févr. 27 18:04:01 CET 2015
|
||||
|
||||
# gather steps for every coeficients
|
||||
coefs_steps = []
|
||||
for coef in self._coef:
|
||||
coef_steps = []
|
||||
if type(coef) == list:
|
||||
# On converti en postfix avec une addition
|
||||
postfix_add = self.postfix_add([i for i in coef if i!=0])
|
||||
# On converti en Expression
|
||||
coef_exp = Expression(postfix_add)
|
||||
|
||||
with Expression.tmp_render():
|
||||
coef_steps = list(coef_exp.simplify().explain())
|
||||
|
||||
#print('\t 1.coef_steps -> ', coef_steps)
|
||||
|
||||
elif type(coef) == Expression:
|
||||
|
||||
with Expression.tmp_render():
|
||||
coef_steps = list(coef.simplify().explain())
|
||||
|
||||
#print('\t 2.coef_steps -> ', coef_steps)
|
||||
|
||||
else:
|
||||
try:
|
||||
with Expression.tmp_render():
|
||||
coef_steps += coef.simplify().explain()
|
||||
except AttributeError:
|
||||
coef_steps = [coef]
|
||||
|
||||
#print('\t 3.coef_steps -> ', coef_steps)
|
||||
# On ajoute toutes ces étapes
|
||||
coefs_steps.append(coef_steps)
|
||||
|
||||
#print('\t coefs_steps -> ', coefs_steps)
|
||||
|
||||
# On retourne la matrice
|
||||
steps = []
|
||||
for coefs in transpose_fill(coefs_steps):
|
||||
steps.append(AbstractPolynom(coefs, self._letter))
|
||||
|
||||
ans, steps = steps[-1], steps[:-1]
|
||||
ans.steps = steps
|
||||
|
||||
return ans
|
||||
|
||||
def simplify(self):
|
||||
"""Same as reduce """
|
||||
return self.reduce()
|
||||
|
||||
@staticmethod
|
||||
def postfix_add(numbers):
|
||||
"""Convert a list of numbers into a postfix addition
|
||||
|
||||
:numbers: list of numbers
|
||||
:returns: Postfix list of succecive attition of number
|
||||
|
||||
>>> AbstractPolynom.postfix_add([1])
|
||||
[1]
|
||||
>>> AbstractPolynom.postfix_add([1, 2])
|
||||
[1, 2, '+']
|
||||
>>> AbstractPolynom.postfix_add([1, 2, 3])
|
||||
[1, 2, '+', 3, '+']
|
||||
>>> AbstractPolynom.postfix_add(1)
|
||||
[1]
|
||||
>>> AbstractPolynom.postfix_add([])
|
||||
[0]
|
||||
"""
|
||||
if not type(numbers) == list:
|
||||
return [numbers]
|
||||
elif numbers == []:
|
||||
return [0]
|
||||
else:
|
||||
ans = [[a, op.add] if i!=0 else [a] for (i,a) in enumerate(numbers)]
|
||||
return list(chain.from_iterable(ans))
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
o_poly = self.conv2poly(other)
|
||||
return self._coef == o_poly._coef
|
||||
except TypeError:
|
||||
return 0
|
||||
|
||||
def __add__(self, other):
|
||||
""" Overload +
|
||||
|
||||
>>> P = AbstractPolynom([1,2,3])
|
||||
>>> Q = AbstractPolynom([4,5])
|
||||
>>> R = P+Q
|
||||
>>> R
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [5, 7, 3]>
|
||||
>>> R.steps
|
||||
[< <class 'pymath.calculus.expression.Expression'> [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', 5, 'x', '*', 4, '+', '+'] >, < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [< <class 'pymath.calculus.expression.Expression'> [1, 4, '+'] >, < <class 'pymath.calculus.expression.Expression'> [2, 5, '+'] >, 3]>]
|
||||
"""
|
||||
o_poly = self.conv2poly(other)
|
||||
|
||||
n_coef = spe_zip(self._coef, o_poly._coef)
|
||||
p = AbstractPolynom(n_coef, letter = self._letter)
|
||||
|
||||
ini_step = [Expression(self.postfix_tokens + o_poly.postfix_tokens + [op.add])]
|
||||
ans = p.simplify()
|
||||
ans.steps = ini_step + ans.steps
|
||||
return ans
|
||||
|
||||
def __radd__(self, other):
|
||||
o_poly = self.conv2poly(other)
|
||||
return o_poly.__add__(self)
|
||||
|
||||
def __neg__(self):
|
||||
""" overload - (as arity 1 operator)
|
||||
|
||||
>>> P = AbstractPolynom([1,2,3])
|
||||
>>> Q = -P
|
||||
>>> Q
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [-1, -2, -3]>
|
||||
>>> Q.steps
|
||||
[< <class 'pymath.calculus.expression.Expression'> [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', '-'] >]
|
||||
"""
|
||||
ini_step = [Expression(self.postfix_tokens + [op.sub1])]
|
||||
ans = AbstractPolynom([-i for i in self._coef], letter = self._letter).simplify()
|
||||
ans.steps = ini_step + ans.steps
|
||||
return ans
|
||||
|
||||
def __sub__(self, other):
|
||||
""" overload -
|
||||
|
||||
>>> P = AbstractPolynom([1,2,3])
|
||||
>>> Q = AbstractPolynom([4,5,6])
|
||||
>>> R = P - Q
|
||||
>>> R
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [-3, -3, -3]>
|
||||
>>> R.steps
|
||||
[< <class 'pymath.calculus.expression.Expression'> [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', 6, 'x', 2, '^', '*', 5, 'x', '*', '+', 4, '+', '-'] >, < <class 'pymath.calculus.expression.Expression'> [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', 6, 'x', 2, '^', '*', '-', 5, 'x', '*', '-', 4, '-', '+'] >, < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [< <class 'pymath.calculus.expression.Expression'> [1, -4, '+'] >, < <class 'pymath.calculus.expression.Expression'> [2, -5, '+'] >, < <class 'pymath.calculus.expression.Expression'> [3, -6, '+'] >]>]
|
||||
>>> for i in R.explain():
|
||||
... print(i)
|
||||
3 x^{ 2 } + 2 x + 1 - ( 6 x^{ 2 } + 5 x + 4 )
|
||||
3 x^{ 2 } + 2 x + 1 - 6 x^{ 2 } - 5 x - 4
|
||||
( 3 - 6 ) x^{ 2 } + ( 2 - 5 ) x + 1 - 4
|
||||
- 3 x^{ 2 } - 3 x - 3
|
||||
"""
|
||||
o_poly = self.conv2poly(other)
|
||||
ini_step = [Expression(self.postfix_tokens + o_poly.postfix_tokens + [op.sub])]
|
||||
o_poly = -o_poly
|
||||
#ini_step += o_poly.steps
|
||||
|
||||
ans = self + o_poly
|
||||
ans.steps = ini_step + ans.steps
|
||||
|
||||
return ans
|
||||
|
||||
def __rsub__(self, other):
|
||||
o_poly = self.conv2poly(other)
|
||||
|
||||
return o_poly.__sub__(self)
|
||||
|
||||
def __mul__(self, other):
|
||||
""" Overload *
|
||||
|
||||
>>> p = AbstractPolynom([1,2])
|
||||
>>> p*3
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [3, 6]>
|
||||
>>> (p*3).steps
|
||||
[[< <class 'pymath.calculus.expression.Expression'> [2, 'x', '*', 1, '+', 3, '*'] >], < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [3, < <class 'pymath.calculus.expression.Expression'> [2, 3, '*'] >]>]
|
||||
>>> q = AbstractPolynom([0,0,4])
|
||||
>>> q*3
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 0, 12]>
|
||||
>>> (q*3).steps
|
||||
[[< <class 'pymath.calculus.expression.Expression'> [4, 'x', 2, '^', '*', 3, '*'] >], < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 0, < <class 'pymath.calculus.expression.Expression'> [4, 3, '*'] >]>]
|
||||
>>> r = AbstractPolynom([0,1])
|
||||
>>> r*3
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 3]>
|
||||
>>> (r*3).steps
|
||||
[[< <class 'pymath.calculus.expression.Expression'> ['x', 3, '*'] >]]
|
||||
>>> p*q
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 0, 4, 8]>
|
||||
>>> (p*q).steps
|
||||
[[< <class 'pymath.calculus.expression.Expression'> [2, 'x', '*', 1, '+', 4, 'x', 2, '^', '*', '*'] >], < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 0, 4, < <class 'pymath.calculus.expression.Expression'> [2, 4, '*'] >]>]
|
||||
>>> p*r
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 1, 2]>
|
||||
>>> P = AbstractPolynom([1,2,3])
|
||||
>>> Q = AbstractPolynom([4,5,6])
|
||||
>>> P*Q
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [4, 13, 28, 27, 18]>
|
||||
"""
|
||||
# TODO: Je trouve qu'elle grille trop d'étapes... |ven. févr. 27 19:08:44 CET 2015
|
||||
o_poly = self.conv2poly(other)
|
||||
|
||||
coefs = [0]*(self.degree + o_poly.degree + 1)
|
||||
for (i,a) in enumerate(self._coef):
|
||||
for (j,b) in enumerate(o_poly._coef):
|
||||
if a == 0 or b == 0:
|
||||
elem = 0
|
||||
elif a==1:
|
||||
elem = b
|
||||
elif b==1:
|
||||
elem = a
|
||||
else:
|
||||
elem = Expression([a, b, op.mul])
|
||||
|
||||
if coefs[i+j]==0:
|
||||
coefs[i+j] = elem
|
||||
elif elem != 0:
|
||||
if type(coefs[i+j]) == list:
|
||||
coefs[i+j] += [elem]
|
||||
else:
|
||||
coefs[i+j] = [coefs[i+j] , elem]
|
||||
|
||||
p = AbstractPolynom(coefs, letter = self._letter)
|
||||
ini_step = [Expression(self.postfix_tokens + o_poly.postfix_tokens + [op.mul])]
|
||||
ans = p.simplify()
|
||||
|
||||
ans.steps = [ini_step] + ans.steps
|
||||
return ans
|
||||
|
||||
def __rmul__(self, other):
|
||||
o_poly = self.conv2poly(other)
|
||||
|
||||
return o_poly.__mul__(self)
|
||||
|
||||
@power_cache
|
||||
def __pow__(self, power):
|
||||
""" Overload **
|
||||
|
||||
>>> p = AbstractPolynom([0,0,3])
|
||||
>>> p**2
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 0, 0, 0, 9]>
|
||||
>>> (p**2).steps
|
||||
[< <class 'pymath.calculus.expression.Expression'> [3, 'x', 2, '^', '*', 2, '^'] >, < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 0, 0, 0, < <class 'pymath.calculus.expression.Expression'> [3, 2, '^'] >]>]
|
||||
>>> p = AbstractPolynom([1,2])
|
||||
>>> p**2
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [1, 4, 4]>
|
||||
>>> (p**2).steps
|
||||
[< <class 'pymath.calculus.expression.Expression'> [2, 'x', '*', 1, '+', 2, '^'] >, [< <class 'pymath.calculus.expression.Expression'> [2, 'x', '*', 1, '+', 2, 'x', '*', 1, '+', '*'] >], < <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [1, < <class 'pymath.calculus.expression.Expression'> [2, 2, '+'] >, < <class 'pymath.calculus.expression.Expression'> [2, 2, '*'] >]>]
|
||||
>>> p = AbstractPolynom([0,0,1])
|
||||
>>> p**3
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [0, 0, 0, 0, 0, 0, 1]>
|
||||
>>> p = AbstractPolynom([1,2,3])
|
||||
>>> p**2
|
||||
< <class 'pymath.calculus.abstract_polynom.AbstractPolynom'> [1, 4, 10, 12, 9]>
|
||||
|
||||
"""
|
||||
if not type(power):
|
||||
raise ValueError("Can't raise {obj} to {pw} power".format(obj = self.__class__, pw = str(power)))
|
||||
|
||||
ini_step = [Expression(self.postfix_tokens + [power, op.pw])]
|
||||
|
||||
if self.is_monom():
|
||||
if self._coef[self.degree] == 1:
|
||||
coefs = [0]*self.degree*power + [1]
|
||||
p = AbstractPolynom(coefs, letter = self._letter)
|
||||
ans = p
|
||||
else:
|
||||
coefs = [0]*self.degree*power + [Expression([self._coef[self.degree] , power, op.pw])]
|
||||
p = AbstractPolynom(coefs, letter = self._letter)
|
||||
ans = p.simplify()
|
||||
else:
|
||||
if power == 2:
|
||||
ans = self * self
|
||||
else:
|
||||
# TODO: faudrait changer ça c'est pas très sérieux |ven. févr. 27 22:08:00 CET 2015
|
||||
raise AttributeError("__pw__ not implemented yet when power is greatter than 2")
|
||||
|
||||
ans.steps = ini_step + ans.steps
|
||||
return ans
|
||||
|
||||
def __xor__(self, power):
|
||||
return self.__pow__(power)
|
||||
|
||||
if __name__ == '__main__':
|
||||
P = AbstractPolynom([[1,2],[3,4,5],6])
|
||||
Q = P.reduce()
|
||||
for i in Q.explain():
|
||||
print(i)
|
||||
|
||||
#import doctest
|
||||
#doctest.testmod()
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
44
pymath/calculus/arithmetic.py
Normal file
44
pymath/calculus/arithmetic.py
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
__all__ = ['gcd']
|
||||
|
||||
def gcd(a, b):
|
||||
"""Compute gcd(a,b)
|
||||
|
||||
:param a: first number
|
||||
:param b: second number
|
||||
:returns: the gcd
|
||||
|
||||
"""
|
||||
pos_a, _a = (a >= 0), abs(a)
|
||||
pos_b, _b = (b >= 0), abs(b)
|
||||
|
||||
gcd_sgn = (-1 + 2*(pos_a or pos_b))
|
||||
|
||||
if _a > _b:
|
||||
c = _a % _b
|
||||
else:
|
||||
c = _b % _a
|
||||
|
||||
if c == 0:
|
||||
return gcd_sgn * min(_a,_b)
|
||||
elif _a == 1:
|
||||
return gcd_sgn * _b
|
||||
elif _b == 1:
|
||||
return gcd_sgn * _a
|
||||
else:
|
||||
return gcd_sgn * gcd(min(_a,_b), c)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(gcd(3, 15))
|
||||
print(gcd(3, 15))
|
||||
print(gcd(-15, -3))
|
||||
print(gcd(-3, -12))
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
135
pymath/calculus/explicable.py
Normal file
135
pymath/calculus/explicable.py
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .render import txt, tex
|
||||
|
||||
class Renderable(object):
|
||||
"""
|
||||
A Renderable object is an object which can work with Render class. It means that it has to have attribute postfix_tokens.
|
||||
"""
|
||||
STR_RENDER = tex
|
||||
DEFAULT_RENDER = tex
|
||||
|
||||
@classmethod
|
||||
def set_render(cls, render):
|
||||
cls.STR_RENDER = render
|
||||
|
||||
@classmethod
|
||||
def get_render(cls):
|
||||
return cls.STR_RENDER
|
||||
|
||||
@classmethod
|
||||
def set_default_render(cls):
|
||||
cls.set_render(cls.DEFAULT_RENDER)
|
||||
|
||||
@classmethod
|
||||
def tmp_render(cls, render = tex):
|
||||
""" Create a container in which Expression render is temporary modify
|
||||
|
||||
The default temporary render is Expression in order to perform calculus inside numbers
|
||||
|
||||
>>> from .expression import Expression
|
||||
>>> exp = Expression("2*3/5")
|
||||
>>> print(exp)
|
||||
2 \\times \\frac{ 3 }{ 5 }
|
||||
>>> for i in exp.simplify().explain():
|
||||
... print(i)
|
||||
2 \\times \\frac{ 3 }{ 5 }
|
||||
\\frac{ 3 }{ 5 } \\times 2
|
||||
\\frac{ 3 \\times 2 }{ 5 }
|
||||
\\frac{ 6 }{ 5 }
|
||||
>>> with Expression.tmp_render(txt):
|
||||
... for i in exp.simplify().explain():
|
||||
... print(i)
|
||||
2 * 3 / 5
|
||||
3 / 5 * 2
|
||||
( 3 * 2 ) / 5
|
||||
6 / 5
|
||||
>>> for i in exp.simplify().explain():
|
||||
... print(i)
|
||||
2 \\times \\frac{ 3 }{ 5 }
|
||||
\\frac{ 3 }{ 5 } \\times 2
|
||||
\\frac{ 3 \\times 2 }{ 5 }
|
||||
\\frac{ 6 }{ 5 }
|
||||
|
||||
# TODO: essayer de ne pas afficher ce changement de position. |lun. avril 6 17:29:56 CEST 2015
|
||||
|
||||
"""
|
||||
class TmpRenderEnv(object):
|
||||
def __enter__(self):
|
||||
self.old_render = Renderable.get_render()
|
||||
Renderable.set_render(render)
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
Renderable.set_render(self.old_render)
|
||||
return TmpRenderEnv()
|
||||
|
||||
def __eq__(self, other):
|
||||
""" Two Renderable objects are the same if they have same postfix_tokens """
|
||||
try:
|
||||
return self.postfix_tokens == other.postfix_tokens
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
class Explicable(Renderable):
|
||||
|
||||
""" An Explicable object is an object which can be explicable!
|
||||
|
||||
It's a parent class of a more classical Expression, Fraction and Polynom (and later square root)
|
||||
Child class will have the following method
|
||||
* explain: Generator which return steps which leed to himself
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.steps = []
|
||||
|
||||
def explain(self, noself = True):
|
||||
""" Generate and render steps which leed to itself
|
||||
|
||||
:param noself: does explain return self
|
||||
|
||||
"""
|
||||
old_s = ''
|
||||
# les étapes pour l'atteindre
|
||||
try:
|
||||
for s in self.steps:
|
||||
if hasattr(s, 'postfix_tokens'):
|
||||
new_s = self.STR_RENDER(s.postfix_tokens)
|
||||
else:
|
||||
new_s = self.STR_RENDER(s)
|
||||
if not self.is_same_step(new_s, old_s):
|
||||
old_s = new_s
|
||||
yield new_s
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if noself:
|
||||
# Lui même
|
||||
new_s = self.STR_RENDER(self.postfix_tokens)
|
||||
if not self.is_same_step(new_s, old_s):
|
||||
yield new_s
|
||||
|
||||
def is_same_step(self, new, old):
|
||||
"""Return whether the new step is the same than old step
|
||||
"""
|
||||
try:
|
||||
if new.replace(" ", "") == old.replace(" ", ""):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except AttributeError:
|
||||
if new == old:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
399
pymath/calculus/expression.py
Normal file
399
pymath/calculus/expression.py
Normal file
@@ -0,0 +1,399 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
#debuging
|
||||
#from debug.tools import report
|
||||
|
||||
from .generic import Stack, flatten_list, expand_list, isNumber, isOperator, isNumerand
|
||||
from .str2tokens import str2tokens
|
||||
from .operator import op
|
||||
from .explicable import Explicable
|
||||
|
||||
from .random_expression import RdExpression
|
||||
|
||||
__all__ = ['Expression']
|
||||
|
||||
class Fake_int(int, Explicable):
|
||||
isNumber = True
|
||||
def __init__(self, val):
|
||||
super(Fake_int, self).__init__(val)
|
||||
self._val = val
|
||||
self.postfix_tokens = [self]
|
||||
self.steps = []
|
||||
def simplify(self):
|
||||
return Fake_int(self._val)
|
||||
|
||||
|
||||
class Expression(Explicable):
|
||||
"""A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown"""
|
||||
|
||||
@classmethod
|
||||
def random(self, form="", conditions=[], val_min = -10, val_max=10):
|
||||
"""Create a random expression from form and with conditions
|
||||
|
||||
:param form: the form of the expression (/!\ variables need to be in brackets {})
|
||||
:param conditions: condition on variables (/!\ variables need to be in brackets {})
|
||||
:param val_min: min value for generate variables
|
||||
:param val_max: max value for generate variables
|
||||
|
||||
"""
|
||||
random_generator = RdExpression(form, conditions)
|
||||
return Expression(random_generator(val_min, val_max))
|
||||
|
||||
@classmethod
|
||||
def tmp_render(cls, render = lambda _,x:Expression(x)):
|
||||
""" Same ad tmp_render for Renderable but default render is Expression
|
||||
|
||||
>>> exp = Expression("2*3/5")
|
||||
>>> print(exp)
|
||||
2 \\times \\frac{ 3 }{ 5 }
|
||||
>>> for i in exp.simplify().explain():
|
||||
... print(i)
|
||||
2 \\times \\frac{ 3 }{ 5 }
|
||||
\\frac{ 3 }{ 5 } \\times 2
|
||||
\\frac{ 3 \\times 2 }{ 5 }
|
||||
\\frac{ 6 }{ 5 }
|
||||
>>> with Expression.tmp_render():
|
||||
... for i in exp.simplify().explain():
|
||||
... i
|
||||
< <class 'pymath.calculus.expression.Expression'> [2, 3, 5, '/', '*'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [2, < Fraction 3 / 5>, '*'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 5, '/', 2, '*'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 2, '*', 5, '/'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [6, 5, '/'] >
|
||||
>>> from .render import txt
|
||||
>>> with Expression.tmp_render(txt):
|
||||
... for i in exp.simplify().explain():
|
||||
... print(i)
|
||||
2 * 3 / 5
|
||||
3 / 5 * 2
|
||||
( 3 * 2 ) / 5
|
||||
6 / 5
|
||||
>>> for i in exp.simplify().explain():
|
||||
... print(i)
|
||||
2 \\times \\frac{ 3 }{ 5 }
|
||||
\\frac{ 3 }{ 5 } \\times 2
|
||||
\\frac{ 3 \\times 2 }{ 5 }
|
||||
\\frac{ 6 }{ 5 }
|
||||
|
||||
"""
|
||||
return super(Expression, cls).tmp_render(render)
|
||||
|
||||
def __new__(cls, exp):
|
||||
"""Create Expression objects
|
||||
|
||||
:param exp: the expression. It can be a string or a list of postfix tokens.
|
||||
|
||||
"""
|
||||
expression = object.__new__(cls)
|
||||
|
||||
if type(exp) == str:
|
||||
expression.postfix_tokens = str2tokens(exp)
|
||||
|
||||
elif type(exp) == list:
|
||||
# Ici on ne peut convertir les "+" en opérateur que s'ils sont d'arité 2.
|
||||
exp_mod_op = [op.get_op(i) if op.can_be_operator(i) else i for i in exp]
|
||||
expression.postfix_tokens = flatten_list([tok.postfix_tokens if Expression.isExpression(tok) else tok for tok in exp_mod_op])
|
||||
|
||||
elif type(exp) == Expression:
|
||||
return exp
|
||||
|
||||
elif isNumerand(exp):
|
||||
expression.postfix_tokens = [exp]
|
||||
|
||||
else:
|
||||
raise ValueError("Can't build Expression with {} object".format(type(exp)))
|
||||
|
||||
if len(expression.postfix_tokens) == 1:
|
||||
token = expression.postfix_tokens[0]
|
||||
|
||||
if type(token) == Fake_int or type(token) == int:
|
||||
return Fake_int(token)
|
||||
|
||||
elif hasattr(token, 'simplify') and hasattr(token, 'explain'):
|
||||
ans = expression.postfix_tokens[0]
|
||||
return ans
|
||||
|
||||
elif type(token) == str:
|
||||
# TODO: Pourquoi ne pas créer directement un polynom ici? |jeu. févr. 26 18:59:24 CET 2015
|
||||
# On crée un faux str en ajoutant la méthode simplify et simplified et la caractérisique isNumber
|
||||
simplify = lambda x:[x]
|
||||
is_polynom = True
|
||||
methods_attr = {'simplify':simplify, '_isPolynom': is_polynom, 'postfix_tokens': [token]}
|
||||
fake_token = type('fake_str', (str,Explicable, ), methods_attr)(token)
|
||||
return fake_token
|
||||
|
||||
else:
|
||||
raise ValueError("Unknow token type in Expression: {}".format(type(token)))
|
||||
|
||||
else:
|
||||
expression._isExpression = 1
|
||||
return expression
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Overload str
|
||||
|
||||
If you want to changer render use Expression.set_render(...) or use tmp_render context manager.
|
||||
"""
|
||||
return self.STR_RENDER(self.postfix_tokens)
|
||||
|
||||
def __repr__(self):
|
||||
return " ".join(["<", str(self.__class__) , str(self.postfix_tokens), ">"])
|
||||
|
||||
def simplify(self):
|
||||
""" Compute entirely the expression and return the result with .steps attribute """
|
||||
self.compute_exp()
|
||||
|
||||
self.simplified = self.child.simplify()
|
||||
self.simplified.steps = self.child.steps + self.simplified.steps
|
||||
return self.simplified
|
||||
|
||||
def compute_exp(self):
|
||||
""" Create self.child with and stock steps in it """
|
||||
ini_step = Expression(self.postfix_tokens)
|
||||
|
||||
tokenList = self.postfix_tokens.copy()
|
||||
tmpTokenList = []
|
||||
|
||||
while len(tokenList) > 2:
|
||||
# on va chercher les motifs du genre A B +, quand l'operateur est d'arité 2, pour les calculer
|
||||
if isNumerand(tokenList[0]) and isNumerand(tokenList[1]) \
|
||||
and isOperator(tokenList[2]) and tokenList[2].arity == 2 :
|
||||
|
||||
# S'il y a une opération à faire
|
||||
op1 = tokenList[0]
|
||||
op2 = tokenList[1]
|
||||
operator = tokenList[2]
|
||||
|
||||
res = operator(op1, op2)
|
||||
|
||||
tmpTokenList.append(res)
|
||||
|
||||
# Comme on vient de faire le calcul, on peut détruire aussi les deux prochains termes
|
||||
del tokenList[0:3]
|
||||
|
||||
# Et les motifs du gens A -, quand l'operateur est d'arité 1
|
||||
elif isNumerand(tokenList[0]) \
|
||||
and isOperator(tokenList[1]) and tokenList[1].arity == 1:
|
||||
|
||||
# S'il y a une opération à faire
|
||||
op1 = tokenList[0]
|
||||
operator = tokenList[1]
|
||||
|
||||
res = operator(op1)
|
||||
|
||||
tmpTokenList.append(res)
|
||||
|
||||
# Comme on vient de faire le calcul, on peut détruire aussi les deux prochains termes
|
||||
del tokenList[0:2]
|
||||
|
||||
else:
|
||||
tmpTokenList.append(tokenList[0])
|
||||
|
||||
del tokenList[0]
|
||||
|
||||
if len(tokenList) == 2 and isNumerand(tokenList[0]) \
|
||||
and isOperator(tokenList[1]) and tokenList[1].arity == 1:
|
||||
# S'il reste deux éléments dont un operation d'arité 1
|
||||
op1 = tokenList[0]
|
||||
operator = tokenList[1]
|
||||
|
||||
res = operator(op1)
|
||||
|
||||
tmpTokenList.append(res)
|
||||
|
||||
# Comme on vient de faire le calcul, on peut détruire aussi les deux prochains termes
|
||||
del tokenList[0:2]
|
||||
|
||||
tmpTokenList += tokenList
|
||||
self.child = Expression(tmpTokenList)
|
||||
|
||||
steps = self.develop_steps(tmpTokenList)
|
||||
|
||||
if self.child.postfix_tokens == ini_step.postfix_tokens:
|
||||
self.child.steps = steps
|
||||
else:
|
||||
self.child.steps = [ini_step] + steps
|
||||
|
||||
def develop_steps(self, tokenList):
|
||||
""" From a list of tokens, it develops steps of each tokens """
|
||||
# TODO: Attention les étapes sont dans le mauvais sens |lun. avril 20 10:06:03 CEST 2015
|
||||
tmp_steps = []
|
||||
with Expression.tmp_render():
|
||||
for t in tokenList:
|
||||
if hasattr(t, "explain"):
|
||||
tmp_steps.append([i for i in t.explain()])
|
||||
else:
|
||||
tmp_steps.append([t])
|
||||
if max([len(i) for i in tmp_steps]) == 1:
|
||||
# Cas où rien n'a dû être expliqué.
|
||||
return []
|
||||
else:
|
||||
tmp_steps = expand_list(tmp_steps)[:-1]
|
||||
steps = [Expression(s) for s in tmp_steps]
|
||||
return steps
|
||||
|
||||
@classmethod
|
||||
def isExpression(self, other):
|
||||
try:
|
||||
other._isExpression
|
||||
except AttributeError:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
# -----------
|
||||
# Expression act as container from self.postfix_tokens
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.postfix_tokens[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
self.postfix_tokens[index] = value
|
||||
|
||||
# -----------
|
||||
# Some math manipulations
|
||||
|
||||
def operate(self, other, operator):
|
||||
if type(other) == Expression:
|
||||
return Expression(self.postfix_tokens + other.postfix_tokens + [operator])
|
||||
elif type(other) == list:
|
||||
return Expression(self.postfix_tokens + other + [operator])
|
||||
else:
|
||||
return Expression(self.postfix_tokens + [other] + [operator])
|
||||
|
||||
def roperate(self, other, operator):
|
||||
if type(other) == Expression:
|
||||
return Expression(other.postfix_tokens + self.postfix_tokens + [operator])
|
||||
elif type(other) == list:
|
||||
return Expression(other + self.postfix_tokens + [operator])
|
||||
else:
|
||||
return Expression([other] + self.postfix_tokens + [operator])
|
||||
|
||||
def __add__(self, other):
|
||||
""" Overload +
|
||||
|
||||
>>> a = Expression("1+2")
|
||||
>>> print(a.postfix_tokens)
|
||||
[1, 2, '+']
|
||||
>>> b = Expression("3+4")
|
||||
>>> print(b.postfix_tokens)
|
||||
[3, 4, '+']
|
||||
>>> c = a + b
|
||||
>>> print(c.postfix_tokens)
|
||||
[1, 2, '+', 3, 4, '+', '+']
|
||||
"""
|
||||
return self.operate(other, op.add)
|
||||
|
||||
def __radd__(self, other):
|
||||
return self.roperate(other, op.add)
|
||||
|
||||
def __sub__(self, other):
|
||||
""" Overload -
|
||||
|
||||
>>> a = Expression("1+2")
|
||||
>>> print(a.postfix_tokens)
|
||||
[1, 2, '+']
|
||||
>>> b = Expression("3+4")
|
||||
>>> print(b.postfix_tokens)
|
||||
[3, 4, '+']
|
||||
>>> c = a - b
|
||||
>>> print(c.postfix_tokens)
|
||||
[1, 2, '+', 3, 4, '+', '-']
|
||||
"""
|
||||
return self.operate(other, op.sub)
|
||||
|
||||
def __rsub__(self, other):
|
||||
return self.roperate(other, op.sub)
|
||||
|
||||
def __mul__(self, other):
|
||||
""" Overload *
|
||||
|
||||
>>> a = Expression("1+2")
|
||||
>>> print(a.postfix_tokens)
|
||||
[1, 2, '+']
|
||||
>>> b = Expression("3+4")
|
||||
>>> print(b.postfix_tokens)
|
||||
[3, 4, '+']
|
||||
>>> c = a * b
|
||||
>>> print(c.postfix_tokens)
|
||||
[1, 2, '+', 3, 4, '+', '*']
|
||||
"""
|
||||
return self.operate(other, op.mul)
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self.roperate(other, op.mul)
|
||||
|
||||
def __truediv__(self, other):
|
||||
""" Overload /
|
||||
|
||||
>>> a = Expression("1+2")
|
||||
>>> print(a.postfix_tokens)
|
||||
[1, 2, '+']
|
||||
>>> b = Expression("3+4")
|
||||
>>> print(b.postfix_tokens)
|
||||
[3, 4, '+']
|
||||
>>> c = a / b
|
||||
>>> print(c.postfix_tokens)
|
||||
[1, 2, '+', 3, 4, '+', '/']
|
||||
>>>
|
||||
"""
|
||||
return self.operate(other, op.div)
|
||||
|
||||
def __rtruediv__(self, other):
|
||||
return self.roperate(other, op.div)
|
||||
|
||||
def __pow__(self, other):
|
||||
return self.operate(other, op.pw)
|
||||
|
||||
def __xor__(self, other):
|
||||
return self.operate(other, op.pw)
|
||||
|
||||
def __neg__(self):
|
||||
return Expression(self.postfix_tokens + [op.sub1])
|
||||
|
||||
|
||||
def untest(exp):
|
||||
a = Expression(exp)
|
||||
b = a.simplify()
|
||||
|
||||
for i in b.explain():
|
||||
#print(type(i))
|
||||
print(i)
|
||||
|
||||
#print(type(a.simplified()), ":", a.simplified())
|
||||
|
||||
print("\n")
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('\n')
|
||||
A = Expression("( -8 x + 8 ) ( -8 - ( -6 x ) )")
|
||||
Ar = A.simplify()
|
||||
for i in Ar.explain():
|
||||
print(i)
|
||||
#print("------------")
|
||||
#for i in Ar.explain():
|
||||
# print(i)
|
||||
|
||||
#print(type(Ar))
|
||||
|
||||
|
||||
#print('\n-----------')
|
||||
#A = Expression("-6 / 3 + 10 / -5")
|
||||
#Ar = A.simplify()
|
||||
#for i in Ar.explain():
|
||||
# print(i)
|
||||
|
||||
#print('\n-----------')
|
||||
#A = Expression("1/3 + 4/6")
|
||||
#Ar = A.simplify()
|
||||
#for i in Ar.explain():
|
||||
# print(i)
|
||||
|
||||
#import doctest
|
||||
#doctest.testmod()
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
560
pymath/calculus/fraction.py
Normal file
560
pymath/calculus/fraction.py
Normal file
@@ -0,0 +1,560 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .arithmetic import gcd
|
||||
from .generic import isNumber
|
||||
from .operator import op
|
||||
from .expression import Expression
|
||||
from .explicable import Explicable
|
||||
from .render import txt, tex
|
||||
from copy import copy
|
||||
|
||||
|
||||
__all__ = ['Fraction']
|
||||
|
||||
class Fraction(Explicable):
|
||||
"""Fractions!"""
|
||||
|
||||
def __init__(self, num, denom = 1):
|
||||
"""To initiate a fraction we need a numerator and a denominator
|
||||
|
||||
:param num: the numerator
|
||||
:param denom: the denominator
|
||||
|
||||
"""
|
||||
super(Fraction, self).__init__()
|
||||
self._num = num
|
||||
if denom == 0:
|
||||
raise ZeroDivisionError("Can't create Fraction: division by zero")
|
||||
self._denom = denom
|
||||
|
||||
self.isNumber = 1
|
||||
|
||||
def simplify(self):
|
||||
"""Simplify the fraction
|
||||
|
||||
:returns: steps to simplify the fraction or the fraction if there is nothing to do
|
||||
|
||||
>>> f = Fraction(3, 6)
|
||||
>>> f.simplify()
|
||||
< Fraction 1 / 2>
|
||||
>>> for i in f.simplify().explain():
|
||||
... print(i)
|
||||
\\frac{ 3 }{ 6 }
|
||||
\\frac{ 1 \\times 3 }{ 2 \\times 3 }
|
||||
\\frac{ 1 }{ 2 }
|
||||
>>> f = Fraction(6,9)
|
||||
>>> f.simplify()
|
||||
< Fraction 2 / 3>
|
||||
>>> for i in f.simplify().explain():
|
||||
... print(i)
|
||||
\\frac{ 6 }{ 9 }
|
||||
\\frac{ 2 \\times 3 }{ 3 \\times 3 }
|
||||
\\frac{ 2 }{ 3 }
|
||||
>>> f = Fraction(0,3)
|
||||
>>> f.simplify()
|
||||
0
|
||||
|
||||
"""
|
||||
ini_step = [Expression(self.postfix_tokens)]
|
||||
|
||||
if self._num == 0:
|
||||
return Expression([0])
|
||||
|
||||
elif type(self._num) == Fraction or type(self._denom) == Fraction:
|
||||
return self._num / self._denom
|
||||
|
||||
elif self._denom < 0:
|
||||
n_frac = Fraction(-self._num, -self._denom)
|
||||
ans = n_frac.simplify()
|
||||
ans.steps = ini_step + ans.steps
|
||||
return ans
|
||||
|
||||
gcd_ = gcd(abs(self._num), abs(self._denom))
|
||||
if gcd_ == self._denom:
|
||||
n_frac = self._num // gcd_
|
||||
return Expression([n_frac])
|
||||
|
||||
elif gcd_ != 1:
|
||||
n_frac = Fraction(self._num // gcd_ , self._denom // gcd_)
|
||||
ini_step += [Expression([n_frac._num, gcd_, op.mul, n_frac._denom, gcd_, op.mul, op.div ])]
|
||||
|
||||
n_frac.steps = ini_step + n_frac.steps
|
||||
return n_frac
|
||||
|
||||
else:
|
||||
return copy(self)
|
||||
|
||||
@property
|
||||
def postfix_tokens(self):
|
||||
"""Postfix form of the fraction
|
||||
|
||||
>>> f = Fraction(3, 5)
|
||||
>>> f.postfix_tokens
|
||||
[3, 5, '/']
|
||||
|
||||
"""
|
||||
if self._denom == 1:
|
||||
return [self._num]
|
||||
else:
|
||||
return [self._num, self._denom, op.div]
|
||||
|
||||
def __str__(self):
|
||||
return str(Expression(self.postfix_tokens))
|
||||
|
||||
def __repr__(self):
|
||||
return "< Fraction {num} / {denom}>".format(num=self._num, denom = self._denom)
|
||||
|
||||
def __txt__(self):
|
||||
old_render = Expression.get_render()
|
||||
Expression.set_render(txt)
|
||||
_txt = self.__str__()
|
||||
Expression.set_render(old_render)
|
||||
|
||||
return _txt
|
||||
|
||||
def __tex__(self):
|
||||
old_render = Expression.get_render()
|
||||
Expression.set_render(tex)
|
||||
_tex = self.__str__()
|
||||
Expression.set_render(old_render)
|
||||
|
||||
return _tex
|
||||
|
||||
def __float__(self):
|
||||
return self._num / self._denom
|
||||
|
||||
def convert2fraction(self, other):
|
||||
""" Convert a other into a fraction """
|
||||
if type(other) == Fraction:
|
||||
#cool
|
||||
number = other
|
||||
else:
|
||||
number = Fraction(other)
|
||||
|
||||
return number
|
||||
|
||||
def __add__(self, other):
|
||||
""" overload +
|
||||
|
||||
>>> f = Fraction(1, 2)
|
||||
>>> g = Fraction(2, 3)
|
||||
>>> f + g
|
||||
< Fraction 7 / 6>
|
||||
>>> print("\\n".join([repr(i) for i in (f+g).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '/', 2, 3, '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 3, '*', 2, 3, '*', '/', 2, 2, '*', 3, 2, '*', '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 6, '/', 4, 6, '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [< Fraction 3 / 6>, < Fraction 4 / 6>, '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 6, '/', 4, 6, '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 4, '+', 6, '/'] >
|
||||
>>> f + 2
|
||||
< Fraction 5 / 2>
|
||||
>>> print("\\n".join([repr(i) for i in (f+2).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '/', 2, '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 1, '*', 2, 1, '*', '/', 2, 2, '*', 1, 2, '*', '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '/', 4, 2, '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [< Fraction 1 / 2>, < Fraction 4 / 2>, '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '/', 4, 2, '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 4, '+', 2, '/'] >
|
||||
>>> f = Fraction(3, 4)
|
||||
>>> g = Fraction(5, 4)
|
||||
>>> f + g
|
||||
2
|
||||
>>> print("\\n".join([repr(i) for i in (f+g).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 4, '/', 5, 4, '/', '+'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 5, '+', 4, '/'] >
|
||||
>>> f+0
|
||||
< Fraction 3 / 4>
|
||||
>>> (f+0).steps
|
||||
[]
|
||||
|
||||
"""
|
||||
|
||||
if other == 0:
|
||||
return copy(self)
|
||||
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
if self._denom == number._denom:
|
||||
com_denom = self._denom
|
||||
num1 = self._num
|
||||
num2 = number._num
|
||||
|
||||
exp = Expression([num1, num2, op.add, com_denom, op.div])
|
||||
|
||||
else:
|
||||
gcd_denom = gcd(self._denom, number._denom)
|
||||
coef1 = number._denom // gcd_denom
|
||||
coef2 = self._denom // gcd_denom
|
||||
|
||||
exp = Expression([self._num, coef1, op.mul, self._denom, coef1, op.mul, op.div, number._num, coef2, op.mul, number._denom, coef2, op.mul, op.div,op.add])
|
||||
|
||||
ans = exp.simplify()
|
||||
ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.add])
|
||||
ans.steps = [ini_step] + ans.steps
|
||||
return ans
|
||||
|
||||
def __radd__(self, other):
|
||||
if other == 0:
|
||||
return Expression(self.postfix_tokens)
|
||||
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
return number + self
|
||||
|
||||
def __sub__(self, other):
|
||||
""" overload -
|
||||
|
||||
>>> f = Fraction(1, 2)
|
||||
>>> g = Fraction(2, 3)
|
||||
>>> f - g
|
||||
< Fraction -1 / 6>
|
||||
>>> print("\\n".join([repr(i) for i in (f-g).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '/', 2, 3, '/', '-'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 3, '*', 2, 3, '*', '/', 2, 2, '*', 3, 2, '*', '/', '-'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 6, '/', 4, 6, '/', '-'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [< Fraction 3 / 6>, < Fraction 4 / 6>, '-'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 6, '/', 4, 6, '/', '-'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 4, '-', 6, '/'] >
|
||||
>>> f - 0
|
||||
< Fraction 1 / 2>
|
||||
>>> (f-0).steps
|
||||
[]
|
||||
|
||||
"""
|
||||
if other == 0:
|
||||
return copy(self)
|
||||
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
if self._denom == number._denom:
|
||||
com_denom = self._denom
|
||||
num1 = self._num
|
||||
num2 = number._num
|
||||
|
||||
exp = Expression([num1, num2, op.sub, com_denom, op.div])
|
||||
|
||||
else:
|
||||
gcd_denom = gcd(self._denom, number._denom)
|
||||
coef1 = number._denom // gcd_denom
|
||||
coef2 = self._denom // gcd_denom
|
||||
|
||||
exp = Expression([self._num, coef1, op.mul, self._denom, coef1, op.mul, op.div, number._num, coef2, op.mul, number._denom, coef2, op.mul, op.div,op.sub])
|
||||
|
||||
ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.sub])
|
||||
ans = exp.simplify()
|
||||
ans.steps = [ini_step] + ans.steps
|
||||
return ans
|
||||
|
||||
def __rsub__(self, other):
|
||||
if other == 0:
|
||||
return copy(self)
|
||||
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
return number - self
|
||||
|
||||
def __neg__(self):
|
||||
""" overload - (as arity 1 operator)
|
||||
|
||||
>>> f = Fraction(1, 2)
|
||||
>>> -f
|
||||
< Fraction -1 / 2>
|
||||
>>> (-f).steps
|
||||
[]
|
||||
>>> f = Fraction(1, -2)
|
||||
>>> f
|
||||
< Fraction 1 / -2>
|
||||
>>> -f
|
||||
< Fraction 1 / 2>
|
||||
>>> (-f).steps
|
||||
[< <class 'pymath.calculus.expression.Expression'> [-1, -2, '/'] >]
|
||||
|
||||
"""
|
||||
f = Fraction(-self._num, self._denom)
|
||||
ans = f.simplify()
|
||||
|
||||
return ans
|
||||
|
||||
def __mul__(self, other):
|
||||
""" overload *
|
||||
|
||||
>>> f = Fraction(1, 2)
|
||||
>>> g = Fraction(2, 3)
|
||||
>>> f*g
|
||||
< Fraction 1 / 3>
|
||||
>>> print("\\n".join([repr(i) for i in (f*g).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '/', 2, 3, '/', '*'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 1, 2, '*', '*', 1, 2, '*', 3, '*', '/'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '*', 2, 3, '*', '/'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [2, 6, '/'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '*', 3, 2, '*', '/'] >
|
||||
>>> f * 0
|
||||
0
|
||||
>>> (f*0).steps
|
||||
[]
|
||||
>>> f*1
|
||||
< Fraction 1 / 2>
|
||||
>>> (f*1).steps
|
||||
[]
|
||||
>>> f*4
|
||||
2
|
||||
>>> print("\\n".join([repr(i) for i in (f*4).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '/', 4, '*'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [1, 2, '*', 2, '*', 1, 2, '*', '/'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [2, 2, '*', 2, '/'] >
|
||||
|
||||
"""
|
||||
steps = []
|
||||
|
||||
if other == 0:
|
||||
return Expression([0])
|
||||
elif other == 1:
|
||||
return copy(self)
|
||||
|
||||
# TODO: Changer dans le cas où il y a trop de 1 |dim. déc. 28 10:44:10 CET 2014
|
||||
|
||||
elif type(other) == int:
|
||||
gcd1 = gcd(other, self._denom)
|
||||
if gcd1 != 1:
|
||||
num = [self._num, int(other/gcd1), op.mul, gcd1,op.mul]
|
||||
denom = [int(self._denom/gcd1), gcd1, op.mul]
|
||||
else:
|
||||
num = [self._num, other, op.mul]
|
||||
denom = [self._denom]
|
||||
|
||||
exp = Expression(num + denom + [op.div])
|
||||
ini_step = Expression(self.postfix_tokens + [other, op.mul])
|
||||
|
||||
else:
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
gcd1 = gcd(self._num, number._denom)
|
||||
if gcd1 != 1:
|
||||
num1 = [int(self._num/ gcd1), gcd1, op.mul]
|
||||
denom2 = [int(number._denom/ gcd1), gcd1, op.mul]
|
||||
else:
|
||||
num1 = [self._num]
|
||||
denom2 = [number._denom]
|
||||
|
||||
gcd2 = gcd(self._denom, number._num)
|
||||
if gcd2 != 1:
|
||||
num2 = [int(number._num/ gcd2), gcd2, op.mul]
|
||||
denom1 = [int(self._denom/ gcd2), gcd2, op.mul]
|
||||
else:
|
||||
num2 = [number._num]
|
||||
denom1 = [self._denom]
|
||||
|
||||
|
||||
exp = Expression(num1 + num2 + [ op.mul] + denom1 + denom2 + [op.mul, op.div])
|
||||
|
||||
ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.mul])
|
||||
ans = exp.simplify()
|
||||
ans.steps = [ini_step] + ans.steps
|
||||
return ans
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self * other
|
||||
|
||||
def __truediv__(self, other):
|
||||
""" overload /
|
||||
|
||||
>>> f = Fraction(1,2)
|
||||
>>> g = Fraction(3,4)
|
||||
>>> f / 0
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ZeroDivisionError: division by zero
|
||||
>>> f / 1
|
||||
< Fraction 1 / 2>
|
||||
>>> (f/1).steps
|
||||
[]
|
||||
>>> f / g
|
||||
< Fraction 2 / 3>
|
||||
|
||||
"""
|
||||
if other == 0:
|
||||
raise ZeroDivisionError("division by zero")
|
||||
elif other == 1:
|
||||
return copy(self)
|
||||
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.div])
|
||||
|
||||
number = Fraction(number._denom, number._num)
|
||||
ans = self * number
|
||||
|
||||
ans.steps = [ini_step] + ans.steps
|
||||
return ans
|
||||
|
||||
def __rtruediv__(self, other):
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
return number / self
|
||||
|
||||
def __pow__(self, power):
|
||||
""" overload **
|
||||
|
||||
>>> f = Fraction(3, 4)
|
||||
>>> f**0
|
||||
1
|
||||
>>> (f**0).steps
|
||||
[]
|
||||
>>> f**1
|
||||
< Fraction 3 / 4>
|
||||
>>> (f**1).steps
|
||||
[]
|
||||
>>> f**3
|
||||
< Fraction 27 / 64>
|
||||
>>> print("\\n".join([repr(i) for i in (f**3).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 4, '/', 3, '^'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [3, 3, '^', 4, 3, '^', '/'] >
|
||||
>>> f = Fraction(6, 4)
|
||||
>>> f**3
|
||||
< Fraction 27 / 8>
|
||||
>>> print("\\n".join([repr(i) for i in (f**3).steps]))
|
||||
< <class 'pymath.calculus.expression.Expression'> [6, 4, '/', 3, '^'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [6, 3, '^', 4, 3, '^', '/'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [216, 64, '/'] >
|
||||
< <class 'pymath.calculus.expression.Expression'> [27, 8, '*', 8, 8, '*', '/'] >
|
||||
|
||||
"""
|
||||
if not type(power) == int:
|
||||
raise ValueError("Can't raise fraction to power {}".format(str(power)))
|
||||
|
||||
if power == 0:
|
||||
return Expression([1])
|
||||
elif power == 1:
|
||||
return copy(self)
|
||||
else:
|
||||
ini_step = Expression(self.postfix_tokens + [power, op.pw])
|
||||
exp = Expression([self._num, power, op.pw, self._denom, power, op.pw, op.div])
|
||||
|
||||
ans = exp.simplify()
|
||||
ans.steps = [ini_step] + ans.steps
|
||||
return ans
|
||||
|
||||
def __xor__(self, power):
|
||||
""" overload ^
|
||||
|
||||
Work like **
|
||||
|
||||
>>> f = Fraction(3, 4)
|
||||
>>> f^0
|
||||
1
|
||||
>>> f^1
|
||||
< Fraction 3 / 4>
|
||||
>>> f^3
|
||||
< Fraction 27 / 64>
|
||||
"""
|
||||
|
||||
return self.__pow__(power)
|
||||
|
||||
def __abs__(self):
|
||||
return Fraction(abs(self._num), abs(self._denom))
|
||||
|
||||
def __eq__(self, other):
|
||||
""" == """
|
||||
if isNumber(other):
|
||||
number = self.convert2fraction(other)
|
||||
|
||||
return self._num * number._denom == self._denom * number._num
|
||||
else:
|
||||
return 0
|
||||
|
||||
def __lt__(self, other):
|
||||
""" < """
|
||||
return float(self) < float(other)
|
||||
|
||||
def __le__(self, other):
|
||||
""" <= """
|
||||
return float(self) <= float(other)
|
||||
|
||||
def __gt__(self, other):
|
||||
""" > """
|
||||
return float(self) > float(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
""" >= """
|
||||
return float(self) >= float(other)
|
||||
|
||||
def __copy__(self):
|
||||
""" Copying the fraction removing steps where it is from """
|
||||
return Fraction(self._num, self._denom)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
f = Fraction(1, 12)
|
||||
g = Fraction(6, 12)
|
||||
for i in g.simplify().explain():
|
||||
print("g = ",i)
|
||||
h = Fraction(1,-5)
|
||||
t = Fraction(10,3)
|
||||
print("---------")
|
||||
for i in (0 + h).explain():
|
||||
print('0 + h = ',i)
|
||||
#print("---------")
|
||||
#print(str(f) , "+", str(t))
|
||||
#for i in (f + t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(f) , "+", str(g))
|
||||
#for i in (f + g):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(f) , "-", str(g))
|
||||
#for i in (f - g):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(f) , "*", str(g))
|
||||
#for i in (f * g):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "+", str(t))
|
||||
#for i in (h + t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "-", str(t))
|
||||
#for i in (h - t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "*", str(t))
|
||||
#for i in (h * t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print("-", str(h) )
|
||||
#for i in (-h):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "/", str(t))
|
||||
#for i in (h / t):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "+", str(0))
|
||||
#for i in (h + 0):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "*", str(1))
|
||||
#for i in (h * 1):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "*", str(0))
|
||||
#for i in (h * 0):
|
||||
# print(i)
|
||||
#print("---------")
|
||||
#print(str(h) , "*", str(4))
|
||||
#for i in (h * 4):
|
||||
# print(i)
|
||||
|
||||
#print(f.simplify())
|
||||
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
361
pymath/calculus/generic.py
Normal file
361
pymath/calculus/generic.py
Normal file
@@ -0,0 +1,361 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
from itertools import zip_longest
|
||||
|
||||
class Stack(object):
|
||||
"""Docstring for Stack """
|
||||
|
||||
def __init__(self):
|
||||
self.items = []
|
||||
|
||||
def pushFromList(self, list):
|
||||
"""Push the list in the stack
|
||||
|
||||
:param list: a list
|
||||
"""
|
||||
for i in list[::-1]:
|
||||
self.push(i)
|
||||
|
||||
def isEmpty(self):
|
||||
""" Says if the stack is empty
|
||||
|
||||
"""
|
||||
return self.items == []
|
||||
|
||||
def push(self, item):
|
||||
"""Push an item in the stack
|
||||
|
||||
"""
|
||||
self.items.append(item)
|
||||
|
||||
def pop(self):
|
||||
"""Getting the last item and remove it
|
||||
:returns: last item
|
||||
|
||||
"""
|
||||
return self.items.pop()
|
||||
|
||||
def peek(self, posi = 0):
|
||||
"""Getting the last item
|
||||
:param posi: which item to peek 0 (last) 1 (the onebefore the last)...
|
||||
:returns: the item
|
||||
|
||||
"""
|
||||
return self.items[-1 - posi]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.items)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.items) + " -> "
|
||||
|
||||
def __add__(self, addList):
|
||||
return self.items + addList
|
||||
|
||||
def flatten_list(a, result=None):
|
||||
"""Flattens a nested list.
|
||||
|
||||
>>> flatten_list([ [1, 2, [3, 4] ], [5, 6], 7])
|
||||
[1, 2, 3, 4, 5, 6, 7]
|
||||
"""
|
||||
if result is None:
|
||||
result = []
|
||||
|
||||
for x in a:
|
||||
if isinstance(x, list):
|
||||
flatten_list(x, result)
|
||||
else:
|
||||
result.append(x)
|
||||
|
||||
return result
|
||||
|
||||
def first_elem(ll):
|
||||
"""Get the first element in imbricates lists
|
||||
# TODO: Fonction pourrie mais j'ai pas le temps de faire mieux! |mar. janv. 28 22:32:22 CET 2014
|
||||
|
||||
:param list: list of lists of lists...
|
||||
:returns: the first element
|
||||
|
||||
>>> first_elem(1)
|
||||
1
|
||||
>>> first_elem([1,2])
|
||||
1
|
||||
>>> first_elem([["abc"]])
|
||||
'a'
|
||||
>>> first_elem("abc")
|
||||
'a'
|
||||
>>> first_elem([[[1,2],[3,4]], [5,6]])
|
||||
1
|
||||
>>> first_elem([[["ab",2],[3,4]], [5,6]])
|
||||
'a'
|
||||
|
||||
"""
|
||||
if hasattr(ll, '__contains__'):
|
||||
if len(ll) == 1 and type(ll) == str:
|
||||
return ll[0]
|
||||
else:
|
||||
return first_elem(ll[0])
|
||||
else:
|
||||
return ll
|
||||
|
||||
def last_elem(ll):
|
||||
"""Get the last element in imbricates lists
|
||||
# TODO: Fonction pourrie mais j'ai pas le temps de faire mieux! |mar. janv. 28 22:32:22 CET 2014
|
||||
|
||||
:param list: list of lists of lists...
|
||||
:returns: the last element
|
||||
|
||||
>>> last_elem(1)
|
||||
1
|
||||
>>> last_elem([1,2])
|
||||
2
|
||||
>>> last_elem([["abc"]])
|
||||
'c'
|
||||
>>> last_elem("abc")
|
||||
'c'
|
||||
>>> last_elem([[[1,2],[3,4]], [5,6]])
|
||||
6
|
||||
>>> last_elem([[["ab",2],[3,4]], [5,6]])
|
||||
6
|
||||
|
||||
"""
|
||||
if hasattr(ll, '__contains__'):
|
||||
if len(ll) == 1 and type(ll) == str:
|
||||
return ll[-1]
|
||||
else:
|
||||
return last_elem(ll[-1])
|
||||
else:
|
||||
return ll
|
||||
|
||||
|
||||
def expand_list(list_list):
|
||||
"""Expand list of list
|
||||
|
||||
>>> expand_list([1,2,[3,4],5,[6,7,8]])
|
||||
[[1, 2, 3, 5, 6], [1, 2, 4, 5, 7], [1, 2, 4, 5, 8]]
|
||||
>>> expand_list([1,2,4,5,6,7,8])
|
||||
[[1, 2, 4, 5, 6, 7, 8]]
|
||||
|
||||
"""
|
||||
list_in_list = [i for i in list_list if type(i) == list].copy()
|
||||
|
||||
try:
|
||||
nbr_ans_list = max([len(i) for i in list_in_list])
|
||||
|
||||
ans = [list_list.copy() for i in range(nbr_ans_list)]
|
||||
for (i,l) in enumerate(ans):
|
||||
for (j,e) in enumerate(l):
|
||||
if type(e) == list:
|
||||
ans[i][j] = e[min(i,len(e)-1)]
|
||||
# S'il n'y a pas de liste dans la liste (2e exemple)
|
||||
except ValueError:
|
||||
ans = [list_list]
|
||||
|
||||
return ans
|
||||
|
||||
def add_in_dict(dict1, dict2):
|
||||
"""Merge dictionary keys and add the content from dict1 and dict2
|
||||
|
||||
:param dict1: first dictionary
|
||||
:param dict2: second dictionary
|
||||
:returns: merged and added dictionary
|
||||
|
||||
>>> add_in_dict({'a':1, 'b':2}, {'c':3, 'd': 4}) == {'d': 4, 'a': 1, 'c': 3, 'b': 2}
|
||||
True
|
||||
>>> add_in_dict({'a':1, 'b':2}, {'a':3, 'b': 4}) == {'a': 4, 'b': 6}
|
||||
True
|
||||
>>> add_in_dict({'a':1, 'b':2}, {'a':3, 'c': 4}) == {'a': 4, 'b': 2, 'c': 4}
|
||||
True
|
||||
|
||||
"""
|
||||
new_dict = {}
|
||||
new_dict.update(dict1)
|
||||
for (k,v) in dict2.items():
|
||||
if k in new_dict.keys():
|
||||
new_dict[k] += v
|
||||
else:
|
||||
new_dict[k] = v
|
||||
|
||||
return new_dict
|
||||
|
||||
def remove_in_dict(d, value = 0):
|
||||
""" In a dictionary, remove keys which have certain value
|
||||
|
||||
:param d: the dictionary
|
||||
:param value: value to remove
|
||||
:returns: new dictionary whithout unwanted value
|
||||
|
||||
>>> remove_in_dict({'b': 1, 'a': 0}) == {'b': 1}
|
||||
True
|
||||
>>> remove_in_dict({'b': 1, 'a': 0}, 1) == {'a': 0}
|
||||
True
|
||||
"""
|
||||
new_dict = {}
|
||||
for (k,v) in d.items():
|
||||
if v != value:
|
||||
new_dict[k] = v
|
||||
return new_dict
|
||||
|
||||
def convolution_dict(D1, D2, op = lambda x,y:x*y,\
|
||||
op_key = lambda x,y: x + y, \
|
||||
commutative = True, op_twice = lambda x,y: x + y):
|
||||
"""Convolution of two dictionaries
|
||||
|
||||
:param D1: First dictionary
|
||||
:param D2: Second dictionary
|
||||
:param op: Operation of perform in value
|
||||
:param commutative: keys are commutative?
|
||||
:param op_twice: operation on value if the key appear twice
|
||||
|
||||
>>> convolution_dict({"a": 1, "b":3}, {"a":2, "":4}) == {"aa":2, "a": 4, "ba":6, "b":12}
|
||||
True
|
||||
>>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}) == {"aa":2, "ab":10, "bb":12}
|
||||
True
|
||||
>>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}, commutative = False) == {"aa":2, "ab":10, "bb":12}
|
||||
False
|
||||
>>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}, commutative = False) == {"aa":2, "ab":4,"ba":6, "bb":12}
|
||||
True
|
||||
>>> convolution_dict({"a": 1, "b":3}, {"a":2, "b":4}, \
|
||||
op_twice = lambda x,y:[x,y]) == {"aa":2, "ab":[4,6], "bb":12}
|
||||
True
|
||||
|
||||
"""
|
||||
new_dict = {}
|
||||
|
||||
for k1 in sorted(D1.keys()):
|
||||
for k2 in sorted(D2.keys()):
|
||||
if op_key(k1,k2) in new_dict.keys():
|
||||
key = op_key(k1,k2)
|
||||
new_dict[key] = op_twice(new_dict[key], op(D1[k1],D2[k2]))
|
||||
|
||||
elif op_key(k2,k1) in new_dict.keys() and commutative:
|
||||
key = op_key(k2,k1)
|
||||
new_dict[key] = op_twice(new_dict[key], op(D1[k1],D2[k2]))
|
||||
|
||||
else:
|
||||
key = op_key(k1,k2)
|
||||
new_dict[key] = op(D1[k1],D2[k2])
|
||||
|
||||
return new_dict
|
||||
|
||||
def spe_zip(l1,l2):
|
||||
"""Zip two lists, if a list is longer, only it's element are taken
|
||||
|
||||
>>> spe_zip([1,2], [3,4])
|
||||
[[1, 3], [2, 4]]
|
||||
>>> spe_zip([1,2], [3,4,5])
|
||||
[[1, 3], [2, 4], 5]
|
||||
"""
|
||||
tmp = list(zip_longest(l1,l2))
|
||||
ans = []
|
||||
for i in tmp:
|
||||
if None in i:
|
||||
j = [a for a in i if a != None][-1]
|
||||
else:
|
||||
j = list(i)
|
||||
ans.append(j)
|
||||
return ans
|
||||
|
||||
def transpose_fill(list_lists):
|
||||
"""Transpose a list of list and if inside list have not the same length, fill with last token
|
||||
|
||||
:list_lists: a list of list to transpose
|
||||
:returns: generator which generate lines of the transposed matrix
|
||||
|
||||
>>> list(transpose_fill([[1], [2, 3], [4, 5, 6]]))
|
||||
[[1, 2, 4], [1, 3, 5], [1, 3, 6]]
|
||||
"""
|
||||
for i in range(max([len(l) for l in list_lists])):
|
||||
col = []
|
||||
for l in list_lists:
|
||||
try:
|
||||
col.append(l[i])
|
||||
except IndexError:
|
||||
col.append(l[-1])
|
||||
|
||||
yield col
|
||||
|
||||
def isOperator(exp):
|
||||
"""Check if the expression is an opération in "+-*/:^"
|
||||
|
||||
:param exp: an expression
|
||||
:returns: boolean
|
||||
|
||||
"""
|
||||
|
||||
#return (type(exp) == str and exp in "+-*/:^")
|
||||
try:
|
||||
exp.isOperator
|
||||
except AttributeError:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def isNumber(exp):
|
||||
"""Check if the expression can be a number
|
||||
|
||||
:param exp: an expression
|
||||
:returns: True if the expression can be a number and false otherwise
|
||||
|
||||
"""
|
||||
try:
|
||||
exp.isNumber
|
||||
except AttributeError:
|
||||
if type(exp) == int:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def isPolynom(exp):
|
||||
"""Check if the expression can be a polynom
|
||||
|
||||
:param exp: an expression
|
||||
:returns: True if the expression can be a polynom and false otherwise
|
||||
|
||||
>>> from pymath.calculus.polynom import Polynom
|
||||
>>> p = Polynom([1,2])
|
||||
>>> isPolynom(p)
|
||||
1
|
||||
>>> isPolynom(1)
|
||||
0
|
||||
>>> isPolynom("a")
|
||||
0
|
||||
|
||||
"""
|
||||
try:
|
||||
exp._isPolynom
|
||||
except AttributeError:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def isNumerand(exp):
|
||||
"""Check is the expression is something we can compute with
|
||||
|
||||
>>> isNumerand(1)
|
||||
1
|
||||
>>> from pymath.calculus.polynom import Polynom
|
||||
>>> p = Polynom([1,2])
|
||||
>>> isNumerand(p)
|
||||
1
|
||||
>>> from pymath.calculus.fraction import Fraction
|
||||
>>> f = Fraction(12)
|
||||
>>> isNumerand(f)
|
||||
1
|
||||
>>> isNumerand("a")
|
||||
0
|
||||
|
||||
|
||||
"""
|
||||
return isNumber(exp) or isPolynom(exp)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
646
pymath/calculus/operator.py
Normal file
646
pymath/calculus/operator.py
Normal file
@@ -0,0 +1,646 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
#from debug.tools import report
|
||||
|
||||
from .generic import flatten_list, isNumber
|
||||
from functools import wraps
|
||||
import types
|
||||
|
||||
class Operator(str):
|
||||
|
||||
"""The operator class, is a string (representation of the operator) with its arity"""
|
||||
|
||||
def __new__(cls, operator = "", name = "", priority = 0, actions = ("",""), txt = "", tex = "", arity = 2):
|
||||
""" Create an Operator """
|
||||
#def __new__(cls, operator, arity = 2):
|
||||
op = str.__new__(cls, operator)
|
||||
op.operator = operator
|
||||
op.name = name
|
||||
op.arity = arity
|
||||
op.priority = priority
|
||||
op.actions = actions
|
||||
op.txt = txt
|
||||
op.tex = tex
|
||||
|
||||
op.isOperator = 1
|
||||
# TODO: Add self.visibility |sam. nov. 8 17:00:08 CET 2014
|
||||
return op
|
||||
|
||||
def __call__(self, *args):
|
||||
""" Calling this operator performs the rigth calculus """
|
||||
return self._call(*args)
|
||||
|
||||
|
||||
def _call(self, *args):
|
||||
"""Trick to avoid overloading __call__ """
|
||||
if self.arity == 1:
|
||||
return getattr(args[0], self.actions)()
|
||||
|
||||
elif self.arity == 2:
|
||||
#if type(args[1]) == int:
|
||||
if issubclass(type(args[1]),int):
|
||||
return getattr(args[0], self.actions[0])(args[1])
|
||||
else:
|
||||
return getattr(args[1], self.actions[1])(args[0])
|
||||
|
||||
def _render(self, link, *args):
|
||||
"""Global step for __txt__ and __tex__
|
||||
|
||||
:param link: the link between operators
|
||||
:param *args: the operands
|
||||
:returns: the string with operator and operands
|
||||
|
||||
"""
|
||||
if self.arity == 1:
|
||||
op1 = self.l_parenthesis(args[0], True)
|
||||
ans = link.format(op1 = op1)
|
||||
|
||||
elif self.arity == 2:
|
||||
op1 = self.l_parenthesis(args[0], True)
|
||||
op2 = self.r_parenthesis(args[1], True)
|
||||
ans = link.format(op1 = op1, op2 = op2)
|
||||
|
||||
ans = save_mainOp(ans, self)
|
||||
return ans
|
||||
|
||||
def __txt__(self, *args):
|
||||
"""Txt rendering for the operator
|
||||
|
||||
:*args: Operands for this operation
|
||||
:returns: String with operator and his operands
|
||||
|
||||
>>> op.mul.__txt__('1','2')
|
||||
'1 * 2'
|
||||
>>> op.add.__txt__('1','2')
|
||||
'1 + 2'
|
||||
>>> f = save_mainOp('2 + 3',op.add)
|
||||
>>> op.mul.__txt__(f, '4')
|
||||
'( 2 + 3 ) * 4'
|
||||
>>> f = save_mainOp('-3',op.sub1)
|
||||
>>> op.sub1.__txt__(f)
|
||||
'- ( -3 )'
|
||||
>>> op.sub1.__txt__('-3')
|
||||
'- ( -3 )'
|
||||
>>> f = save_mainOp('2 + 3',op.add)
|
||||
>>> op.sub1.__txt__(f)
|
||||
'- ( 2 + 3 )'
|
||||
"""
|
||||
return self._render(self.txt, *args)
|
||||
|
||||
def __tex__(self, *args):
|
||||
"""Tex rendering for the operator
|
||||
|
||||
:*args: Operands for this operation
|
||||
:returns: String with operator and his operands
|
||||
|
||||
>>> op.mul.__tex__('1','2')
|
||||
'1 \\\\times 2'
|
||||
>>> op.add.__tex__('1','2')
|
||||
'1 + 2'
|
||||
>>> f = save_mainOp('2 + 3',op.add)
|
||||
>>> op.mul.__tex__(f, '4')
|
||||
'( 2 + 3 ) \\\\times 4'
|
||||
>>> f = save_mainOp('-3',op.sub1)
|
||||
>>> op.sub1.__tex__(f)
|
||||
'- ( -3 )'
|
||||
>>> op.sub1.__tex__('-3')
|
||||
'- ( -3 )'
|
||||
>>> f = save_mainOp('2 + 3',op.add)
|
||||
>>> op.sub1.__tex__(f)
|
||||
'- ( 2 + 3 )'
|
||||
"""
|
||||
return self._render(self.tex, *args)
|
||||
|
||||
def __p2i__(self, *args):
|
||||
"""Fix list transformation for the operator
|
||||
|
||||
:*args: Operands for this operation
|
||||
:returns: list with the operator surrounded by operands
|
||||
|
||||
>>> op.mul.__p2i__(1,2)
|
||||
[1, '*', 2]
|
||||
>>> f = save_mainOp([2, op.add, 3],op.add)
|
||||
>>> op.mul.__p2i__(f, 4)
|
||||
['(', 2, '+', 3, ')', '*', 4]
|
||||
>>> f = save_mainOp([op.sub1, 3],op.sub1)
|
||||
>>> op.sub1.__p2i__(f)
|
||||
['-', '(', '-', 3, ')']
|
||||
>>> op.sub1.__p2i__(-3)
|
||||
['-', '(', -3, ')']
|
||||
>>> f = save_mainOp([2, op.add, 3],op.add)
|
||||
>>> op.sub1.__p2i__(f)
|
||||
['-', '(', 2, '+', 3, ')']
|
||||
"""
|
||||
if self.arity == 1:
|
||||
op1 = self.l_parenthesis(args[0])
|
||||
ans = flatten_list([self, op1])
|
||||
|
||||
elif self.arity == 2:
|
||||
op1 = self.l_parenthesis(args[0])
|
||||
op2 = self.r_parenthesis(args[1])
|
||||
ans = flatten_list([op1, self, op2])
|
||||
|
||||
ans = save_mainOp(ans, self)
|
||||
return ans
|
||||
|
||||
def l_parenthesis(self, opl, str_join=False):
|
||||
""" Add parenthesis for left operand if necessary """
|
||||
ans = opl
|
||||
try:
|
||||
# TODO: Je pige pas pourquoi quand on enlève .name ça marche plus... |lun. avril 27 19:07:24 CEST 2015
|
||||
if opl.mainOp.name == op.sub1.name:
|
||||
ans = opl
|
||||
elif opl.mainOp.priority < self.priority:
|
||||
ans = flatten_list(["(", opl, ")"])
|
||||
except AttributeError as e:
|
||||
# op has not the attribute priority
|
||||
pass
|
||||
|
||||
ans = flatten_list([ans])
|
||||
if str_join:
|
||||
ans = ' '.join([str(i) for i in ans])
|
||||
return ans
|
||||
|
||||
def r_parenthesis(self, op, str_join=False):
|
||||
""" Add parenthesis for rigth operand if necessary """
|
||||
try:
|
||||
if op.mainOp.priority < self.priority:
|
||||
op = flatten_list(["(", op, ")"])
|
||||
except AttributeError:
|
||||
# op has not the attribute priority
|
||||
try:
|
||||
if int(op) < 0:
|
||||
op = ['(', op, ')']
|
||||
except ValueError:
|
||||
pass
|
||||
ans = flatten_list([op])
|
||||
if str_join:
|
||||
ans = ' '.join([str(i) for i in ans])
|
||||
return ans
|
||||
|
||||
def save_mainOp(obj, mainOp):
|
||||
"""Create a temporary class build over built-in type to stock the main operation of a calculus
|
||||
|
||||
:obj: the object to add the attribute
|
||||
:mainOp: the main operator
|
||||
:returns: the same object with the main operation attribute
|
||||
"""
|
||||
Fake = type('fake_'+str(type(obj)), (type(obj),), {'mainOp': mainOp})
|
||||
|
||||
return Fake(obj)
|
||||
|
||||
def operatorize(fun):
|
||||
"""Transform the answer of the function into an operator
|
||||
|
||||
The returned value of the function has to be a dictionnary with those keys
|
||||
* "operator": the name (Needed!)
|
||||
* "priority": the priority level
|
||||
* "actions": mathematics actions of the operator (list of 1 element if the arity is 1, 2 elements if arity is 2)
|
||||
* "txt": string ready to be formated in txt for with {op1} and/or {op2}
|
||||
* "tex": string ready to be formated in tex for with {op1} and/or {op2}
|
||||
* "arity": arity ie number of operands needed
|
||||
* "_call": action to perform when call the operator
|
||||
* "_render": action use in __txt__ and __tex__
|
||||
* "__txt__": txt rendering
|
||||
* "__tex__": tex rendering
|
||||
* "l_parenthesis": mechanism to add parenthesis for left operande
|
||||
* "r_parenthesis": mechanism to add parenthesis for rigth operande
|
||||
"""
|
||||
@wraps(fun)
|
||||
def mod_fun(self, *args):
|
||||
ans = fun(self, *args)
|
||||
|
||||
def _eq(op1, op2):
|
||||
""" op1 == op2 """
|
||||
return op1.name == op2.name == name and op1.arity == op2.arity
|
||||
|
||||
ans["__eq__"] = _eq
|
||||
|
||||
new_op = Operator(ans["operator"])
|
||||
for (attr, value) in ans.items():
|
||||
if hasattr(value, '__call__'):
|
||||
setattr(new_op, attr, types.MethodType(value, new_op))
|
||||
else:
|
||||
setattr(new_op, attr, value)
|
||||
|
||||
return new_op
|
||||
return mod_fun
|
||||
|
||||
class ClassProperty(object):
|
||||
|
||||
def __init__(self, fget):
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, owner_self, owner_cls):
|
||||
return self.fget(owner_cls)
|
||||
|
||||
class op(object):
|
||||
""" List of admited operations """
|
||||
|
||||
_operators = {("+",2): "add",\
|
||||
("-", 2): "sub",\
|
||||
("-", 1): "sub1",\
|
||||
("*", 2): "mul",\
|
||||
("/", 2): "div",\
|
||||
("^", 2): "pw",\
|
||||
("(", 2): "par",\
|
||||
}
|
||||
|
||||
@ClassProperty
|
||||
@operatorize
|
||||
def add(cls):
|
||||
""" The operator +
|
||||
|
||||
For doctest see test/test_operator.py
|
||||
|
||||
"""
|
||||
|
||||
def _render(self, link, *args):
|
||||
"""Global step for __txt__ and __tex__
|
||||
|
||||
:param link: the link between operators
|
||||
:param *args: the operands
|
||||
:returns: the string with operator and operands
|
||||
|
||||
"""
|
||||
if args[1][0] == "-":
|
||||
op1 = self.l_parenthesis(args[0], True)
|
||||
op2 = self.r_parenthesis(args[1][1:], True)
|
||||
ans = link.replace('+','-').format(op1 = op1, op2 = op2)
|
||||
|
||||
ans = save_mainOp(ans, self)
|
||||
return ans
|
||||
else:
|
||||
op1 = self.l_parenthesis(args[0], True)
|
||||
op2 = self.r_parenthesis(args[1], True)
|
||||
ans = link.format(op1 = op1, op2 = op2)
|
||||
|
||||
ans = save_mainOp(ans, self)
|
||||
return ans
|
||||
|
||||
caract = {
|
||||
"operator" : "+", \
|
||||
"name" : "add",\
|
||||
"priority" : 1, \
|
||||
"arity" : 2, \
|
||||
"actions" : ("__add__","__radd__"), \
|
||||
"txt" : "{op1} + {op2}",\
|
||||
"tex" : "{op1} + {op2}",\
|
||||
"_render": _render,\
|
||||
}
|
||||
|
||||
return caract
|
||||
|
||||
@ClassProperty
|
||||
@operatorize
|
||||
def sub(self):
|
||||
""" The operator -
|
||||
|
||||
>>> sub = op.sub
|
||||
>>> sub
|
||||
'-'
|
||||
>>> sub(1, 2)
|
||||
-1
|
||||
>>> sub.__tex__('1','2')
|
||||
'1 - 2'
|
||||
>>> sub.__txt__('1','2')
|
||||
'1 - 2'
|
||||
>>> sub.__tex__('1','-2')
|
||||
'1 - (-2)'
|
||||
>>> sub.__tex__('-1','2')
|
||||
'-1 - 2'
|
||||
"""
|
||||
def l_parenthesis(self, op, str_join=False):
|
||||
return op
|
||||
|
||||
def r_parenthesis(self, op, str_join=False):
|
||||
try:
|
||||
if op.mainOp.priority <= self.priority:
|
||||
op = flatten_list(["(", op, ")"])
|
||||
elif op[0] == '-':
|
||||
op = flatten_list(["(", op, ")"])
|
||||
except AttributeError:
|
||||
# op has not the attribute priority
|
||||
try:
|
||||
if int(op) < 0:
|
||||
op = ['(', op, ')']
|
||||
except ValueError:
|
||||
pass
|
||||
ans = flatten_list([op])
|
||||
if str_join:
|
||||
ans = ' '.join([str(i) for i in ans])
|
||||
return ans
|
||||
|
||||
caract = {
|
||||
"operator" : "-", \
|
||||
"name" : "sub",\
|
||||
"priority" : 2, \
|
||||
"arity" : 2, \
|
||||
"actions" : ("__sub__","__rsub__"), \
|
||||
"txt" : "{op1} - {op2}",\
|
||||
"tex" : "{op1} - {op2}",\
|
||||
"l_parenthesis": l_parenthesis,\
|
||||
"r_parenthesis": r_parenthesis,\
|
||||
}
|
||||
|
||||
return caract
|
||||
|
||||
@ClassProperty
|
||||
@operatorize
|
||||
def sub1(self):
|
||||
""" The operator -
|
||||
|
||||
>>> sub1 = op.sub1
|
||||
>>> sub1
|
||||
'-'
|
||||
>>> sub1(1)
|
||||
-1
|
||||
>>> sub1.__tex__('1')
|
||||
'- 1'
|
||||
>>> sub1.__txt__('1')
|
||||
'- 1'
|
||||
>>> sub1.__tex__('-1')
|
||||
'- (-1)'
|
||||
"""
|
||||
def l_parenthesis(self, op, str_join=False):
|
||||
""" Add parenthesis if necessary """
|
||||
try:
|
||||
if op.mainOp.priority <= self.priority:
|
||||
op = flatten_list(["("] + [op] + [")"])
|
||||
except AttributeError:
|
||||
# op has not the attribute priority
|
||||
try:
|
||||
if int(op) < 0:
|
||||
op = ['(', op, ')']
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
ans = flatten_list([op])
|
||||
if str_join:
|
||||
ans = ' '.join([str(i) for i in ans])
|
||||
return ans
|
||||
|
||||
caract = {
|
||||
"operator" : "-", \
|
||||
"name" : "sub1",\
|
||||
"priority" : 3, \
|
||||
"arity" : 1, \
|
||||
"actions" : "__neg__",\
|
||||
"txt" : "- {op1}",\
|
||||
"tex" : "- {op1}",\
|
||||
"l_parenthesis": l_parenthesis,\
|
||||
}
|
||||
|
||||
return caract
|
||||
|
||||
@ClassProperty
|
||||
@operatorize
|
||||
def mul(self):
|
||||
""" The operator *
|
||||
|
||||
>>> mul = op.mul
|
||||
>>> mul
|
||||
'*'
|
||||
>>> mul(1, 2)
|
||||
2
|
||||
>>> mul.__tex__('1','2')
|
||||
'1 \\times 2'
|
||||
>>> mul.__tex__('2','a')
|
||||
'2 a'
|
||||
>>> mul.__txt__('1','2')
|
||||
'1 * 2'
|
||||
>>> mul.__txt__('2','a')
|
||||
'2 a'
|
||||
>>> mul.__txt__('a','2')
|
||||
'a * 2'
|
||||
>>> mul.__tex__('1','-2')
|
||||
'1 \\times (-2)'
|
||||
"""
|
||||
# * can not be display in some cases
|
||||
def is_visible(self, op1, op2):
|
||||
""" Tells whether self has to be visible or not
|
||||
|
||||
:param op1: left operande
|
||||
:param op2: rigth operande
|
||||
|
||||
"""
|
||||
# TODO: À finir!!! |lun. mars 9 00:03:40 CET 2015
|
||||
if type(op2) == int:
|
||||
# op2 est maintenant une chaine de caractères
|
||||
return True
|
||||
elif op2.isdecimal():
|
||||
return True
|
||||
elif op2.isalpha():
|
||||
return False
|
||||
elif op2[0].isdecimal():
|
||||
return True
|
||||
elif op2[0] == "(" and not ("+" in op2 or "-" in op2[3:]):
|
||||
return True
|
||||
# Giga bricolage...
|
||||
elif "frac" in op2:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _render(self, link, *args):
|
||||
|
||||
op1 = self.l_parenthesis(args[0], True)
|
||||
op2 = self.r_parenthesis(args[1], True)
|
||||
|
||||
if not self.is_visible(op1, op2):
|
||||
ans = "{op1} {op2}".format(op1 = op1, op2 = op2)
|
||||
else:
|
||||
ans = link.format(op1 = op1, op2 = op2)
|
||||
|
||||
ans = save_mainOp(ans, self)
|
||||
return ans
|
||||
|
||||
caract = {
|
||||
"operator" : "*", \
|
||||
"name" : "mul",\
|
||||
"priority" : 4, \
|
||||
"arity" : 2, \
|
||||
"actions" : ("__mul__","__rmul__"), \
|
||||
"txt" : "{op1} * {op2}",\
|
||||
"tex" : "{op1} \\times {op2}",\
|
||||
"visibility": 1,\
|
||||
"_render": _render,\
|
||||
"is_visible": is_visible,\
|
||||
}
|
||||
|
||||
return caract
|
||||
|
||||
@ClassProperty
|
||||
@operatorize
|
||||
def div(self):
|
||||
""" The operator /
|
||||
|
||||
>>> div = op.div
|
||||
>>> div
|
||||
'/'
|
||||
>>> div(1, 2)
|
||||
< Fraction 1 / 2>
|
||||
>>> div.__tex__('1','2')
|
||||
'\\frac{ 1 }{ 2 }'
|
||||
>>> div.__tex__('1','2')
|
||||
'\\frac{ -1 }{ 2 }'
|
||||
>>> div.__txt__('1','2')
|
||||
'1 / 2'
|
||||
"""
|
||||
from .fraction import Fraction
|
||||
def _call(self, op1, op2):
|
||||
if op2 == 1:
|
||||
return op1
|
||||
else:
|
||||
return Fraction(op1,op2)
|
||||
|
||||
def __tex__(self, *args):
|
||||
# Pas besoin de parenthèses en plus pour \frac
|
||||
replacement = {"op"+str(i+1): op for (i,op) in enumerate(args)}
|
||||
|
||||
ans = self.tex.format(**replacement)
|
||||
ans = save_mainOp(ans, self)
|
||||
return ans
|
||||
|
||||
caract = {
|
||||
"operator" : "/", \
|
||||
"name" : "div",\
|
||||
"priority" : 5, \
|
||||
"arity" : 2, \
|
||||
"txt" : "{op1} / {op2}",\
|
||||
"tex" : "\\frac{{ {op1} }}{{ {op2} }}",\
|
||||
"_call": _call,\
|
||||
"__tex__":__tex__,\
|
||||
}
|
||||
|
||||
return caract
|
||||
|
||||
@ClassProperty
|
||||
@operatorize
|
||||
def pw(self):
|
||||
""" The operator ^
|
||||
|
||||
>>> pw = op.pw
|
||||
>>> pw
|
||||
'^'
|
||||
>>> pw(2, 3)
|
||||
8
|
||||
>>> pw.__tex__('2','3')
|
||||
'2^{ 3 }'
|
||||
>>> pw.__txt__('2','3')
|
||||
'2 ^ 3'
|
||||
>>> pw.__txt__('-2','3')
|
||||
'( -2 ) ^ 3'
|
||||
"""
|
||||
def _call(self, op1, op2):
|
||||
""" Calling this operator performs the rigth calculus """
|
||||
return getattr(op1, "__pow__")(op2)
|
||||
|
||||
def l_parenthesis(self, opl, str_join=False):
|
||||
""" Add parenthesis for left operand if necessary """
|
||||
ans = opl
|
||||
try:
|
||||
if opl.mainOp.priority < self.priority:
|
||||
ans = flatten_list(["(", opl, ")"])
|
||||
except AttributeError as e:
|
||||
# op has not the attribute priority
|
||||
pass
|
||||
try:
|
||||
if int(opl) < 0:
|
||||
ans = ["(", opl, ")"]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
ans = flatten_list([ans])
|
||||
if str_join:
|
||||
ans = ' '.join([str(i) for i in ans])
|
||||
return ans
|
||||
|
||||
caract = {
|
||||
"operator" : "^", \
|
||||
"name" : "pw",\
|
||||
"priority" : 6, \
|
||||
"arity" : 2, \
|
||||
"actions" : ("__pow__",""), \
|
||||
"txt" : "{op1} ^ {op2}",\
|
||||
"tex" : "{op1}^{{ {op2} }}",\
|
||||
"l_parenthesis": l_parenthesis,\
|
||||
"_call":_call,\
|
||||
}
|
||||
|
||||
return caract
|
||||
|
||||
@ClassProperty
|
||||
@operatorize
|
||||
def par(self):
|
||||
""" The operator ( """
|
||||
caract = {
|
||||
"operator" : "(", \
|
||||
"name" : "par",\
|
||||
"priority" : 0, \
|
||||
"arity" : 0, \
|
||||
}
|
||||
return caract
|
||||
|
||||
@classmethod
|
||||
def get_op(cls, op, arity = 2):
|
||||
"""Return the corresponding operator
|
||||
|
||||
:op: symbole of the op
|
||||
:arity: the arity
|
||||
|
||||
>>> op.get_op('+')
|
||||
'+'
|
||||
>>> mul = op.get_op('*')
|
||||
>>> mul.tex
|
||||
'{op1} \\\\times {op2}'
|
||||
>>> mul.txt
|
||||
'{op1} * {op2}'
|
||||
"""
|
||||
try:
|
||||
return getattr(cls, cls._operators[(op, arity)])
|
||||
except KeyError:
|
||||
raise KeyError("{theOp} (arity: {arity}) is not available".format(theOp = op, arity = arity))
|
||||
|
||||
@classmethod
|
||||
def can_be_operator(cls, symbole):
|
||||
""" Tell if the symbole can be an operator """
|
||||
if type(symbole) == str:
|
||||
return symbole in [i[0] for i in cls._operators]
|
||||
else:
|
||||
return False
|
||||
|
||||
if __name__ == '__main__':
|
||||
#print(op.add.__tex__('1','2'))
|
||||
#print(op.mul.__tex__('1','2'))
|
||||
#print(op.sub.__tex__('1','2'))
|
||||
#f = save_mainOp('2 + 3',op.add)
|
||||
#print(op.mul.__txt__(f, '4'))
|
||||
#f = save_mainOp('-3',op.sub1)
|
||||
#print(op.sub1.__txt__(f))
|
||||
#print(op.sub1.__txt__('-3'))
|
||||
#f = save_mainOp('2 + 3',op.add)
|
||||
#print(op.sub1.__txt__(f))
|
||||
|
||||
#from .fraction import Fraction
|
||||
#f = Fraction(1, 2)
|
||||
#print(op.add.__txt__(f.__txt__(),'2'))
|
||||
#print(op.add.__tex__(f.__tex__(),'2'))
|
||||
|
||||
#print("\t op.can_be_operator('+') :" + str(op.can_be_operator('+')))
|
||||
#print("\t op.can_be_operator('t') :" + str(op.can_be_operator('t')))
|
||||
|
||||
print("op.sub.__dict__ -> ", op.sub.__dict__)
|
||||
print(op.sub == op.sub1)
|
||||
#import doctest
|
||||
#doctest.testmod()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
244
pymath/calculus/polynom.py
Normal file
244
pymath/calculus/polynom.py
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
from .expression import Expression
|
||||
from .operator import op
|
||||
from .generic import isNumerand
|
||||
from .random_expression import RdExpression
|
||||
from .abstract_polynom import AbstractPolynom
|
||||
|
||||
from functools import wraps
|
||||
import inspect
|
||||
|
||||
__all__ = ["Polynom"]
|
||||
|
||||
def polynom_factory(func):
|
||||
""" Decorator which specify the type of polynom that the function returns """
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwrds):
|
||||
P = func(*args, **kwrds)
|
||||
if issubclass(type(P),AbstractPolynom) and P.degree == 2:
|
||||
from .polynomDeg2 import Polynom_deg2
|
||||
new_P = Polynom_deg2(poly=P)
|
||||
new_P.steps = P.steps
|
||||
return new_P
|
||||
elif issubclass(type(P),AbstractPolynom):
|
||||
new_P = Polynom(poly=P)
|
||||
new_P.steps = P.steps
|
||||
return new_P
|
||||
else:
|
||||
return P
|
||||
return wrapper
|
||||
|
||||
class Polynom(AbstractPolynom):
|
||||
|
||||
"""Polynom view as a function.
|
||||
|
||||
It can be initiate like a AbstractPolynom
|
||||
# Put example
|
||||
Randomly
|
||||
# Put example
|
||||
It can be evaluate
|
||||
# Put example
|
||||
And derivate
|
||||
# Put example
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def random(self, coefs_form=[], conditions=[], letter = "x", degree = 0, name = "P"):
|
||||
""" Create a random polynom from coefs_form and conditions
|
||||
|
||||
:param coefs_form: list of forms (one by coef) (ascending degree sorted)
|
||||
:param conditions: condition on variables
|
||||
:param letter: the letter for the polynom
|
||||
:param degree: degree of the polynom (can't be used with coefs_form, it will be overwrite) - can't be higher than 26 (number of letters in alphabet)
|
||||
|
||||
/!\ variables need to be in brackets {}
|
||||
|
||||
>>> Polynom.random(["{b}", "{a}"]) # doctest:+ELLIPSIS
|
||||
< <class 'pymath.calculus.polynom.Polynom'> ...
|
||||
>>> Polynom.random(degree = 2) # doctest:+ELLIPSIS
|
||||
< <class 'pymath.calculus.polynomDeg2.Polynom_deg2'> ...
|
||||
>>> Polynom.random(degree = 3) # doctest:+ELLIPSIS
|
||||
< <class 'pymath.calculus.polynom.Polynom'> ...
|
||||
>>> Polynom.random(degree = 2, conditions=["{b**2-4*a*c}>0"]) # Polynom deg 2 with positive Delta (ax^2 + bx + c)
|
||||
< <class 'pymath.calculus.polynomDeg2.Polynom_deg2'> ...
|
||||
>>> Polynom.random(["{c}", "{b}", "{a}"], conditions=["{b**2-4*a*c}>0"]) # Same as above
|
||||
< <class 'pymath.calculus.polynomDeg2.Polynom_deg2'> ...
|
||||
|
||||
"""
|
||||
if (degree > 0 and degree < 26):
|
||||
# Générer assez de lettre pour les coefs
|
||||
coefs_name = map(chr, range(97, 98+degree))
|
||||
coefs_form = ["{" + i + "}" for i in coefs_name][::-1]
|
||||
|
||||
form = str(coefs_form)
|
||||
# On créé les valeurs toutes concaténées dans un string
|
||||
coefs = RdExpression(form, conditions)()
|
||||
# On "parse" ce string pour créer les coefs
|
||||
coefs = [eval(i) if type(i)==str else i for i in eval(coefs)]
|
||||
# Création du polynom
|
||||
return Polynom(coefs = coefs, letter = letter, name = name)
|
||||
|
||||
def __init__(self, coefs = [1], letter = "x", name = "P", poly = 0):
|
||||
"""Initiate the polynom
|
||||
|
||||
:param coef: coefficients of the polynom (ascending degree sorted)
|
||||
3 possibles type of coefficent:
|
||||
- a : simple "number". [1,2] designate 1 + 2x
|
||||
- [a,b,c]: list of coeficient for same degree. [1,[2,3],4] designate 1 + 2x + 3x + 4x^2
|
||||
- a: a Expression. [1, Expression("2+3"), 4] designate 1 + (2+3)x + 4x^2
|
||||
:param letter: the string describing the unknown
|
||||
:param name: Name of the polynom
|
||||
|
||||
>>> P = Polynom([1, 2, 3])
|
||||
>>> P.mainOp
|
||||
'+'
|
||||
>>> P.name
|
||||
'P'
|
||||
>>> P._letter
|
||||
'x'
|
||||
>>> Polynom([1]).mainOp
|
||||
'*'
|
||||
>>> Polynom([0, 0, 3]).mainOp
|
||||
'*'
|
||||
>>> Polynom([1, 2, 3])._letter
|
||||
'x'
|
||||
>>> Polynom([1, 2, 3], "y")._letter
|
||||
'y'
|
||||
>>> Polynom([1, 2, 3], name = "Q").name
|
||||
'Q'
|
||||
"""
|
||||
if poly:
|
||||
coefs = poly._coef
|
||||
letter = poly._letter
|
||||
name = poly.name
|
||||
|
||||
super(Polynom, self).__init__(coefs, letter, name)
|
||||
|
||||
def __call__(self, value):
|
||||
""" Evaluate the polynom in value
|
||||
|
||||
:returns: Expression ready to be simplify
|
||||
|
||||
>>> P = Polynom([1, 2, 3])
|
||||
>>> P(2)
|
||||
17
|
||||
>>> for i in P(2).explain():
|
||||
... print(i)
|
||||
3 \\times 2^{ 2 } + 2 \\times 2 + 1
|
||||
3 \\times 4 + 4 + 1
|
||||
12 + 4 + 1
|
||||
16 + 1
|
||||
17
|
||||
>>> Q = P("1+h")
|
||||
>>> print(Q)
|
||||
3 h^{ 2 } + 8 h + 6
|
||||
>>> R = P(Q)
|
||||
"""
|
||||
#if isNumerand(value) or Expression.isExpression(value):
|
||||
# postfix_exp = [value if i==self._letter else i for i in self.postfix_tokens]
|
||||
#else:
|
||||
postfix_exp = [Expression(value) if i==self._letter else i for i in self.postfix_tokens]
|
||||
|
||||
return Expression(postfix_exp).simplify()
|
||||
|
||||
def derivate(self):
|
||||
""" Return the derivated polynom
|
||||
|
||||
>>> P = Polynom([1, 2, 3])
|
||||
>>> Q = P.derivate()
|
||||
>>> Q
|
||||
< <class 'pymath.calculus.polynom.Polynom'> [2, 6]>
|
||||
>>> print(Q.name)
|
||||
P'
|
||||
>>> for i in Q.explain():
|
||||
... print(i)
|
||||
2 \\times 3 x + 1 \\times 2
|
||||
6 x + 2
|
||||
"""
|
||||
derv_coefs = []
|
||||
for (i,c) in enumerate(self._coef):
|
||||
derv_coefs += [Expression([i, c, op.mul])]
|
||||
|
||||
ans = Polynom(derv_coefs[1:]).simplify()
|
||||
ans.name = self.name + "'"
|
||||
return ans
|
||||
|
||||
# Decorate methods which may return Polynoms
|
||||
methods_list = ["__add__", "__call__", "__mul__", "__neg__", "__pow__",
|
||||
"__radd__", "__rmul__", "__rsub__", "__sub__", "derivate",
|
||||
"reduce", "simplify", "random"]
|
||||
for name, func in inspect.getmembers(Polynom):
|
||||
if name in methods_list:
|
||||
setattr(Polynom, name, polynom_factory(func))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#from .fraction import Fraction
|
||||
#with Expression.tmp_render(txt):
|
||||
# p = Polynom([1, 2, 3])
|
||||
# q = Polynom([4, 5, 6])
|
||||
# for i in (p*q).explain():
|
||||
# print(i)
|
||||
# r = Polynom([0,1])
|
||||
# for i in (r*3).explain():
|
||||
# print(i)
|
||||
# print("q = ", q)
|
||||
# r = q.reduce()
|
||||
# print("r = ", r)
|
||||
# for i in r.explain():
|
||||
# print("q = ", i)
|
||||
# print(p-q)
|
||||
# for i in p-q:
|
||||
# print(i)
|
||||
#Polynom.random(degree = 2, conditions=["{b**2-4*a*c}>0"]) # Polynom deg 2 with positive Delta (ax^2 + bx + c)
|
||||
|
||||
#import doctest
|
||||
#doctest.testmod(optionflags=doctest.ELLIPSIS)
|
||||
|
||||
# while True:
|
||||
# P = Polynom.random(degree = 2)
|
||||
# e = Expression.random("{a}/{b}")
|
||||
# try:
|
||||
# P(e)
|
||||
# except RuntimeError:
|
||||
# print(" P -> " + str(P))
|
||||
# print(" e -> " + str(e))
|
||||
#
|
||||
# import sys
|
||||
# sys.setrecursionlimit(100)
|
||||
|
||||
from .fraction import Fraction
|
||||
from itertools import permutations
|
||||
P = Polynom([-5,6,-4])
|
||||
f = Fraction(2,5)
|
||||
P(f)
|
||||
try:
|
||||
P(f)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
print("-----------------\n")
|
||||
f = Fraction(2,15)
|
||||
print(str(P).replace('x','('+str(f)+')'),"= ", P(f))
|
||||
|
||||
print("-----------------\n")
|
||||
f = Fraction(2,3)
|
||||
print(P(f))
|
||||
#coefs_p = [[(i-2),(j-2)] for i,j in permutations(range(20),2)]
|
||||
#fractions = [Fraction(i,j) for i,j in coefs_p if j!=0]
|
||||
#for f in fractions:
|
||||
# try:
|
||||
# P(f)
|
||||
# #print("ok f -> " + str(f))
|
||||
# except RuntimeError:
|
||||
# print(" f -> " + str(f))
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
250
pymath/calculus/polynomDeg2.py
Normal file
250
pymath/calculus/polynomDeg2.py
Normal file
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .polynom import Polynom
|
||||
from .expression import Expression
|
||||
from .fraction import Fraction
|
||||
from .operator import op
|
||||
from .random_expression import RdExpression
|
||||
|
||||
from sympy import sqrt, latex
|
||||
#from sympy.fractions import Fraction as sp.Fraction
|
||||
|
||||
__all__ = ["Polynom_deg2"]
|
||||
|
||||
class Polynom_deg2(Polynom):
|
||||
|
||||
""" Degree 2 polynoms
|
||||
Child of Polynom with some extra tools
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def random(self, coefs_form = ["{c}", "{b}", "{a}"], conditions = [], letter = "x", name = "P"):
|
||||
""" Create a 2nd degree poly from coefs_form ans conditions
|
||||
|
||||
:param coefs_form: list of forms (one by coef) (ascending degree sorted)
|
||||
:param conditions: condition on variables
|
||||
:param letter: the letter for the polynom
|
||||
|
||||
"""
|
||||
if len(coefs_form) != 3:
|
||||
raise ValueError("Polynom_deg2 have to be degree 2 polynoms, they need 3 coefficients, {} are given".format(len(coefs_form)))
|
||||
|
||||
form = str(coefs_form)
|
||||
# On créé les valeurs toutes concaténées dans un string
|
||||
coefs = RdExpression(form, conditions)()
|
||||
# On "parse" ce string pour créer les coefs
|
||||
coefs = [eval(i) if type(i)==str else i for i in eval(coefs)]
|
||||
# Création du polynom
|
||||
return Polynom_deg2(coefs = coefs, letter = letter, name = name)
|
||||
|
||||
def __init__(self, coefs = [0, 0, 1], letter = "x", name = "P", poly = 0):
|
||||
if poly:
|
||||
coefs = poly._coef
|
||||
letter = poly._letter
|
||||
name = poly.name
|
||||
|
||||
if len(coefs) < 3 or len(coefs) > 4:
|
||||
raise ValueError("Polynom_deg2 have to be degree 2 polynoms, they need 3 coefficients, {} are given".format(len(coefs)))
|
||||
if coefs[2] == 0:
|
||||
raise ValueError("Polynom_deg2 have to be degree 2 polynoms, coefficient of x^2 can't be 0")
|
||||
Polynom.__init__(self, coefs, letter, name = name)
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
return self._coef[2]
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
return self._coef[1]
|
||||
|
||||
@property
|
||||
def c(self):
|
||||
return self._coef[0]
|
||||
|
||||
@property
|
||||
def delta(self):
|
||||
"""Compute the discriminant expression
|
||||
:returns: discriminant expression
|
||||
|
||||
>>> P = Polynom_deg2([1,2,3])
|
||||
>>> P.delta
|
||||
-8
|
||||
>>> for i in P.delta.explain():
|
||||
... print(i)
|
||||
2^{ 2 } - 4 \\times 3 \\times 1
|
||||
4 - 4 \\times 3
|
||||
4 - 12
|
||||
-8
|
||||
"""
|
||||
|
||||
return Expression([self.b, 2, op.pw, 4, self.a, self.c, op.mul, op.mul, op.sub]).simplify()
|
||||
|
||||
@property
|
||||
def alpha(self):
|
||||
""" Compute alpha the abcisse of the extremum
|
||||
|
||||
>>> P = Polynom_deg2([1,2,3])
|
||||
>>> P.alpha
|
||||
< Fraction -1 / 3>
|
||||
>>> for i in P.alpha.explain():
|
||||
... print(i)
|
||||
\\frac{ - 2 }{ 2 \\times 3 }
|
||||
\\frac{ -2 }{ 6 }
|
||||
\\frac{ -1 \\times 2 }{ 3 \\times 2 }
|
||||
\\frac{ -1 }{ 3 }
|
||||
"""
|
||||
return Expression([self.b, op.sub1, 2, self.a, op.mul, op.div]).simplify()
|
||||
|
||||
@property
|
||||
def beta(self):
|
||||
""" Compute beta the extremum of self
|
||||
|
||||
>>> P = Polynom_deg2([1,2,3])
|
||||
>>> P.beta
|
||||
< Fraction 2 / 3>
|
||||
>>> for i in P.beta.explain(): # Ça serait bien que l'on puisse enlever des étapes maintenant...
|
||||
... print(i)
|
||||
3 \\times ( \\frac{ -1 }{ 3 } )^{ 2 } + 2 \\times \\frac{ -1 }{ 3 } + 1
|
||||
3 \\times ( \\frac{ -1 }{ 3 } )^{ 2 } + \\frac{ -1 }{ 3 } \\times 2 + 1
|
||||
3 \\times \\frac{ ( -1 )^{ 2 } }{ 3^{ 2 } } + \\frac{ -1 \\times 2 }{ 3 } + 1
|
||||
3 \\times \\frac{ 1 }{ 9 } + \\frac{ -2 }{ 3 } + 1
|
||||
\\frac{ 1 }{ 9 } \\times 3 + \\frac{ -1 }{ 3 } \\times 2 + 1
|
||||
\\frac{ 1 \\times 1 \\times 3 }{ 3 \\times 3 } + \\frac{ -1 \\times 2 }{ 3 } + 1
|
||||
\\frac{ 1 \\times 3 }{ 9 } + \\frac{ -2 }{ 3 } + 1
|
||||
\\frac{ 3 }{ 9 } + \\frac{ -2 }{ 3 } + 1
|
||||
\\frac{ 1 \\times 3 }{ 3 \\times 3 } + \\frac{ -2 }{ 3 } + 1
|
||||
\\frac{ 1 }{ 3 } + \\frac{ -2 }{ 3 } + 1
|
||||
\\frac{ 1 - 2 }{ 3 } + 1
|
||||
\\frac{ -1 }{ 3 } + 1
|
||||
\\frac{ -1 \\times 1 }{ 3 \\times 1 } + \\frac{ 1 \\times 3 }{ 1 \\times 3 }
|
||||
\\frac{ -1 }{ 3 } + \\frac{ 3 }{ 3 }
|
||||
\\frac{ -1 + 3 }{ 3 }
|
||||
\\frac{ 2 }{ 3 }
|
||||
"""
|
||||
return self(self.alpha)
|
||||
|
||||
def roots(self, after_coma = 2):
|
||||
""" Compute roots of the polynom
|
||||
|
||||
/!\ Can't manage nice rendering because of sqrt.
|
||||
It use sympy to compute roots
|
||||
|
||||
# TODO: Pymath has to know how to compute with sqare root |mar. févr. 24 18:40:04 CET 2015
|
||||
|
||||
>>> P = Polynom_deg2([1, 1, 1])
|
||||
>>> P.roots()
|
||||
[]
|
||||
>>> P = Polynom_deg2([1, 2, 1])
|
||||
>>> P.roots()
|
||||
[-1]
|
||||
>>> P = Polynom_deg2([-1, 0, 1])
|
||||
>>> P.roots()
|
||||
['-1', '1']
|
||||
>>> P = Polynom_deg2([1, 4, 1])
|
||||
>>> P.roots()
|
||||
['-2 - \\\\sqrt{3}', '-2 + \\\\sqrt{3}']
|
||||
"""
|
||||
if self.delta > 0:
|
||||
self._roots = [latex((-self.b - sqrt(self.delta))/(2*self.a)), latex((-self.b + sqrt(self.delta))/(2*self.a))]
|
||||
elif self.delta == 0:
|
||||
self._roots = [Fraction(-self.b,2*self.a).simplify()]
|
||||
else:
|
||||
self._roots = []
|
||||
return self._roots
|
||||
|
||||
def tbl_sgn_header(self):
|
||||
""" Return header of the sign line for tkzTabLine"""
|
||||
if self.delta > 0:
|
||||
return "{$-\\infty$, $" + str(min(self.roots())) + "$ , $" + str( max(self.roots())) + "$ , $+\\infty$}"
|
||||
elif self.delta == 0:
|
||||
return "{$-\\infty$, $" + str(self.roots()[0]) + "$ , $+\\infty$}"
|
||||
else:
|
||||
return "{$-\\infty$, $+\\infty$}"
|
||||
|
||||
def tbl_sgn(self):
|
||||
""" Return the sign line for tkzTabLine
|
||||
|
||||
>>> P = Polynom_deg2([2, 5, 2])
|
||||
>>> print(P.tbl_sgn())
|
||||
\\tkzTabLine{, +, z, -, z , +,}
|
||||
>>> P = Polynom_deg2([2, 1, -2])
|
||||
>>> print(P.tbl_sgn())
|
||||
\\tkzTabLine{, -, z, +, z , -,}
|
||||
>>> P = Polynom_deg2([1, 2, 1])
|
||||
>>> print(P.tbl_sgn())
|
||||
\\tkzTabLine{, +, z, +,}
|
||||
>>> P = Polynom_deg2([0, 0, -2])
|
||||
>>> print(P.tbl_sgn())
|
||||
\\tkzTabLine{, -, z, -,}
|
||||
>>> P = Polynom_deg2([1, 0, 1])
|
||||
>>> print(P.tbl_sgn())
|
||||
\\tkzTabLine{, +,}
|
||||
>>> P = Polynom_deg2([-1, 0, -1])
|
||||
>>> print(P.tbl_sgn())
|
||||
\\tkzTabLine{, -,}
|
||||
"""
|
||||
if self.delta > 0:
|
||||
if self.a > 0:
|
||||
return "\\tkzTabLine{, +, z, -, z , +,}"
|
||||
else:
|
||||
return "\\tkzTabLine{, -, z, +, z , -,}"
|
||||
elif self.delta == 0:
|
||||
if self.a > 0:
|
||||
return "\\tkzTabLine{, +, z, +,}"
|
||||
else:
|
||||
return "\\tkzTabLine{, -, z, -,}"
|
||||
else:
|
||||
if self.a > 0:
|
||||
return "\\tkzTabLine{, +,}"
|
||||
else:
|
||||
return "\\tkzTabLine{, -,}"
|
||||
|
||||
def tbl_variation(self, limits = False):
|
||||
"""Return the variation line for tkzTabVar
|
||||
|
||||
:param limit: Display or not limits in tabular
|
||||
|
||||
>>> P = Polynom_deg2([1,2,3])
|
||||
>>> print(P.tbl_variation())
|
||||
\\tkzTabVar{+/{}, -/{$\\frac{ 2 }{ 3 }$}, +/{}}
|
||||
>>> print(P.tbl_variation(limits = True))
|
||||
\\tkzTabVar{+/{$+\\infty$}, -/{$\\frac{ 2 }{ 3 }$}, +/{$+\\infty$}}
|
||||
|
||||
"""
|
||||
beta = self.beta
|
||||
if limits:
|
||||
if self.a > 0:
|
||||
return "\\tkzTabVar{+/{$+\\infty$}, -/{$" + str(beta) + "$}, +/{$+\\infty$}}"
|
||||
else:
|
||||
return "\\tkzTabVar{-/{$-\\infty$}, +/{$" + str(beta) + "$}, -/{$-\\infty$}}"
|
||||
else:
|
||||
if self.a > 0:
|
||||
return "\\tkzTabVar{+/{}, -/{$" + str(beta) + "$}, +/{}}"
|
||||
else:
|
||||
return "\\tkzTabVar{-/{}, +/{$" + str(beta) + "$}, -/{}}"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#from .render import txt
|
||||
#with Expression.tmp_render(txt):
|
||||
# P = Polynom_deg2([2, 3, 4])
|
||||
# print(P)
|
||||
|
||||
# print("\nDelta")
|
||||
# for i in P.delta.explain():
|
||||
# print(i)
|
||||
# print("\nBeta")
|
||||
# for i in P.beta.explain():
|
||||
# print(i)
|
||||
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
160
pymath/calculus/random_expression.py
Normal file
160
pymath/calculus/random_expression.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from random import randint
|
||||
import re
|
||||
import pyparsing
|
||||
from .generic import flatten_list
|
||||
|
||||
from .arithmetic import gcd
|
||||
|
||||
def random_str(form, conditions = [], val_min = -10, val_max = 10):
|
||||
""" Create a random string using RdExpression class """
|
||||
random_str_generator = RdExpression(form, conditions)
|
||||
return random_str_generator(val_min, val_max)
|
||||
|
||||
class RdExpression(object):
|
||||
"""
|
||||
|
||||
A generator of random expression builder
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, form, conditions = []):
|
||||
"""Initiate the generator
|
||||
|
||||
:param form: the form of the expression (/!\ variables need to be in brackets {})
|
||||
:param conditions: condition on variables (/!\ variables need to be in brackets {})
|
||||
|
||||
"""
|
||||
self._form = form
|
||||
self._conditions = conditions
|
||||
|
||||
self._letters = self.get_letters()
|
||||
self._gene_varia = {}
|
||||
self._gene_2replaced = {}
|
||||
|
||||
def get_2replaced(self):
|
||||
"""Get elements of self._form which will have to be replaced
|
||||
:returns: set for elements which have to be replaced
|
||||
|
||||
"""
|
||||
#pattern = "\{(.*?)\}" #select inside {} non greedy way
|
||||
#varia_form = re.findall(pattern, self._form)
|
||||
|
||||
# TODO: Bug with varia with spaces |dim. nov. 23 10:44:34 CET 2014
|
||||
varia_form = flatten_list([eval(str(i[0])) for i in pyparsing.nestedExpr('{','}').searchString(self._form)])
|
||||
varia_form = set(varia_form)
|
||||
|
||||
varia_cond = set()
|
||||
for c in self._conditions:
|
||||
c_varia_cond = flatten_list([eval(str(i[0])) for i in pyparsing.nestedExpr('{','}').searchString(c)])
|
||||
varia_cond = varia_cond | set(c_varia_cond)
|
||||
|
||||
self._2replaced = varia_cond | varia_form
|
||||
|
||||
return self._2replaced
|
||||
|
||||
def get_letters(self):
|
||||
"""Find letters in the form
|
||||
:returns: list of letters
|
||||
|
||||
"""
|
||||
v2replaced = self.get_2replaced()
|
||||
varia = set()
|
||||
|
||||
pattern = "([a-zA-Z]+)"
|
||||
for v in v2replaced:
|
||||
lvar = set(re.findall(pattern, v))
|
||||
varia = varia | lvar
|
||||
|
||||
return varia
|
||||
|
||||
|
||||
def __call__(self, val_min = -10, val_max = 10):
|
||||
"""RdExpression once it is initiate act like a function which create random expressions.
|
||||
|
||||
:param val_min: minimum value random generation
|
||||
:param val_max: maximum value random generation
|
||||
:returns: an formated random expression
|
||||
|
||||
"""
|
||||
return self.raw_str(val_min, val_max)
|
||||
|
||||
def raw_str(self, val_min = -10, val_max = 10):
|
||||
"""Return raw string (don't use Expression for rendering or parsing)
|
||||
|
||||
:param val_min: minimum value random generation
|
||||
:param val_max: maximum value random generation
|
||||
:returns: an random Expression object
|
||||
|
||||
"""
|
||||
self.gene_varia(val_min, val_max)
|
||||
|
||||
while not(self.val_conditions()):
|
||||
self.gene_varia(val_min, val_max)
|
||||
|
||||
exp = self._form.format(**self._gene_2replaced)
|
||||
|
||||
return exp
|
||||
|
||||
def gene_varia(self, val_min = -10, val_max = 10):
|
||||
"""Randomly generates variables/letters
|
||||
|
||||
Varia can't be equal to 0
|
||||
|
||||
"""
|
||||
for l in self._letters:
|
||||
self._gene_varia[l] = randint(val_min, val_max)
|
||||
while self._gene_varia[l] == 0:
|
||||
self._gene_varia[l] = randint(val_min, val_max)
|
||||
|
||||
|
||||
for e in self._2replaced:
|
||||
self._gene_2replaced[e] = eval(e, globals(), self._gene_varia)
|
||||
|
||||
def val_conditions(self):
|
||||
"""Tells whether or not conditions are validates
|
||||
:returns: boolean
|
||||
|
||||
"""
|
||||
if self._conditions != []:
|
||||
return eval(" and ".join(self._conditions).format(**self._gene_2replaced))
|
||||
else:
|
||||
return True
|
||||
|
||||
def desc_rdExp(rdExp):
|
||||
print("--------------------")
|
||||
print("form: ",rdExp._form)
|
||||
print("Conditions: ",rdExp._conditions)
|
||||
print("Letters: ", rdExp._letters)
|
||||
print("2replaced: ", rdExp._2replaced)
|
||||
print("Call : ", rdExp())
|
||||
print("type: ",type(rdExp()))
|
||||
print("Gene varia: ", rdExp._gene_varia)
|
||||
print("Gene 2replaced: ", rdExp._gene_2replaced)
|
||||
print('')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
form = "({a};{b})"
|
||||
cond = []
|
||||
print(random_str(form, cond))
|
||||
|
||||
form = "{a+a*10}*4 + {a} + 2*{b}"
|
||||
cond = ["{a} + {b} in [1, 2, 3, 4, 5]", "abs({a}) not in [1]", "{b} not in [1]", "gcd({a},{b}) == 1"]
|
||||
print(random_str(form, cond))
|
||||
|
||||
form = "{a+a*10}*4 + {a} + 2*{b}"
|
||||
cond = ["{a-b} + {b} in list(range(20))", "abs({a}) not in [1]", "{b} not in [1]", "gcd({a},{b}) == 1"]
|
||||
print(random_str(form, cond))
|
||||
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
96
pymath/calculus/render.py
Normal file
96
pymath/calculus/render.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .generic import Stack,isOperator
|
||||
|
||||
__all__ = ['txt', 'tex', 'p2i']
|
||||
|
||||
class Render(object):
|
||||
""" Create functions which know how to render postfix tokens lists """
|
||||
|
||||
def __init__(self, render):
|
||||
"""Initiate the render
|
||||
|
||||
:param render: function which take an operator and return a function to render the operator with his operands
|
||||
"""
|
||||
self.render = render
|
||||
|
||||
|
||||
def __call__(self, postfix_tokens):
|
||||
"""Make the object acting like a function
|
||||
|
||||
:param postfix_tokens: the list of postfix tokens to be render
|
||||
:returns: the render string
|
||||
|
||||
"""
|
||||
operandeStack = Stack()
|
||||
for token in postfix_tokens:
|
||||
|
||||
if isOperator(token):
|
||||
if token.arity == 1:
|
||||
|
||||
op1 = operandeStack.pop()
|
||||
|
||||
operandeStack.push(self.render(token)(op1))
|
||||
elif token.arity == 2:
|
||||
op1 = operandeStack.pop()
|
||||
op2 = operandeStack.pop()
|
||||
# Switch op1 and op2 to respect order
|
||||
operandeStack.push(self.render(token)(op2, op1))
|
||||
else:
|
||||
operandeStack.push(self.render(token)())
|
||||
|
||||
if len(operandeStack) > 1:
|
||||
raise ValueError("This postfix_tokens is not a valid expression")
|
||||
else:
|
||||
return operandeStack.pop()
|
||||
|
||||
def txt_render(token):
|
||||
def render(*args):
|
||||
try:
|
||||
return getattr(token, '__txt__')(*args)
|
||||
except AttributeError:
|
||||
return str(token)
|
||||
return render
|
||||
|
||||
txt = Render(txt_render)
|
||||
def tex_render(token):
|
||||
def render(*args):
|
||||
try:
|
||||
return getattr(token, '__tex__')(*args)
|
||||
except AttributeError:
|
||||
return str(token)
|
||||
return render
|
||||
tex = Render(tex_render)
|
||||
|
||||
def p2i_render(token):
|
||||
def render(*args):
|
||||
try:
|
||||
return getattr(token, '__p2i__')(*args)
|
||||
except AttributeError:
|
||||
return token
|
||||
return render
|
||||
p2i = Render(p2i_render)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from .operator import op
|
||||
from itertools import permutations
|
||||
from pymath import Polynom
|
||||
from pymath import Expression
|
||||
from pymath import Fraction
|
||||
coefs_p = [[(i-2),(j-2)] for i,j in permutations(range(5),2)]
|
||||
coefs_q = [[2*(i-2),2*(j-2)] for i,j in permutations(range(5),2)]
|
||||
l_p = [Polynom(i) for i in coefs_p]
|
||||
l_q = [Fraction(i,j) for i,j in coefs_q if j!=0]
|
||||
operations = [Expression([l_p[i],l_q[j],op.mul]) for i,j in permutations(range(len(l_q)),2)]
|
||||
for i in operations:
|
||||
print(i)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
188
pymath/calculus/str2tokens.py
Normal file
188
pymath/calculus/str2tokens.py
Normal file
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .generic import Stack, isOperator, isNumber, isPolynom
|
||||
from .operator import op
|
||||
|
||||
def str2tokens(exp):
|
||||
""" Parse the string into tokens then turn it into postfix form
|
||||
|
||||
>>> str2tokens('2+3*4')
|
||||
[2, 3, 4, '*', '+']
|
||||
>>> str2tokens('2*3+4')
|
||||
[2, 3, '*', 4, '+']
|
||||
>>> str2tokens('2x+4')
|
||||
[2, < <class 'pymath.calculus.polynom.Polynom'> [0, 1]>, '*', 4, '+']
|
||||
"""
|
||||
in_tokens = str2in_tokens(exp)
|
||||
post_tokens = in2post_fix(in_tokens)
|
||||
|
||||
return post_tokens
|
||||
|
||||
def str2in_tokens(exp):
|
||||
""" Parse the expression, ie tranform a string into a list of tokens
|
||||
|
||||
/!\ float are not availiable yet!
|
||||
|
||||
:param exp: The expression (a string)
|
||||
:returns: list of token
|
||||
|
||||
>>> str2in_tokens('2+3*4')
|
||||
[2, '+', 3, '*', 4]
|
||||
>>> str2in_tokens('2*3+4')
|
||||
[2, '*', 3, '+', 4]
|
||||
"""
|
||||
tokens = ['']
|
||||
|
||||
for character in exp:
|
||||
if character.isdigit():
|
||||
# for "big" numbers (like 2345)
|
||||
if type(tokens[-1]) == int:
|
||||
if tokens[-1] > 0:
|
||||
tokens[-1] = tokens[-1]*10 + int(character)
|
||||
else:
|
||||
tokens[-1] = tokens[-1]*10 - int(character)
|
||||
|
||||
|
||||
# Special case for "-" at the begining of an expression or before "("
|
||||
elif tokens[-1] == "-" and \
|
||||
str(tokens[-2]) in " (+-*/:":
|
||||
tokens[-1] = - int(character)
|
||||
else:
|
||||
tokens.append(int(character))
|
||||
|
||||
elif character in "+-*/:^":
|
||||
tokens.append(character)
|
||||
|
||||
elif character == ")":
|
||||
tokens.append(character)
|
||||
|
||||
elif character == "(":
|
||||
# If "3(", ")(", "x("
|
||||
if isNumber(tokens[-1]) \
|
||||
or tokens[-1] == ")" \
|
||||
or isPolynom(tokens[-1]):
|
||||
tokens.append("*")
|
||||
tokens.append(character)
|
||||
|
||||
elif character.isalpha():
|
||||
# If "3x", ")x", "xy"
|
||||
if isNumber(tokens[-1]) \
|
||||
or tokens[-1] == ")" \
|
||||
or isPolynom(tokens[-1]):
|
||||
tokens.append("*")
|
||||
from pymath.calculus.polynom import Polynom
|
||||
tokens.append(Polynom([0,1], letter = character))
|
||||
|
||||
elif character == ".":
|
||||
raise ValueError("No float number please")
|
||||
|
||||
elif character != " ":
|
||||
raise ValueError("{} is an unvalid character".format(character))
|
||||
|
||||
return tokens[1:]
|
||||
|
||||
|
||||
|
||||
def in2post_fix(infix_tokens):
|
||||
""" From the infix_tokens list compute the corresponding postfix_tokens list
|
||||
|
||||
@param infix_tokens: the infix list of tokens to transform into postfix form.
|
||||
@return: the corresponding postfix list of tokens.
|
||||
|
||||
>>> in2post_fix([op.par, 2, op.add, 5, op.sub, 1, ')', op.div, op.par, 3, op.mul, 4, ')'])
|
||||
[2, 5, 1, '-', '+', 3, 4, '*', '/']
|
||||
>>> in2post_fix([op.sub1, op.par, op.sub1, 2, ')'])
|
||||
[2, '-', '-']
|
||||
>>> in2post_fix([op.sub1, op.par, op.sub1, 2, op.add, 3, op.mul, 4, ')'])
|
||||
[2, '-', 3, 4, '*', '+', '-']
|
||||
"""
|
||||
# Stack where operator will be stocked
|
||||
opStack = Stack()
|
||||
# final postfix list of tokens
|
||||
postfix_tokens = []
|
||||
# stack with the nbr of tokens still to compute in postfix_tokens
|
||||
arity_Stack = Stack()
|
||||
arity_Stack.push(0)
|
||||
|
||||
for (pos_token,token) in enumerate(infix_tokens):
|
||||
|
||||
# Pour voir ce qu'il se passe dans cette procédure
|
||||
#print(str(postfix_tokens), " | ", str(opStack), " | ", str(infix_tokens[(pos_token+1):]), " | ", str(arity_Stack))
|
||||
if token == ")":
|
||||
next_op = opStack.pop()
|
||||
while next_op != "(":
|
||||
postfix_tokens.append(next_op)
|
||||
next_op = opStack.pop()
|
||||
|
||||
# Go back to old arity
|
||||
arity_Stack.pop()
|
||||
# Raise the arity
|
||||
arity = arity_Stack.pop()
|
||||
arity_Stack.push(arity + 1)
|
||||
|
||||
elif op.can_be_operator(token):
|
||||
if token == "(":
|
||||
opStack.push(op.get_op(token))
|
||||
# Set next arity counter
|
||||
arity_Stack.push(0)
|
||||
else:
|
||||
arity = arity_Stack.pop()
|
||||
token_op = op.get_op(token, arity + 1)
|
||||
# Reset arity to 0 in case there is other operators (the real operation would be "-op.arity + 1")
|
||||
arity_Stack.push(0)
|
||||
while (not opStack.isEmpty()) and opStack.peek().priority >= token_op.priority:
|
||||
next_op = opStack.pop()
|
||||
postfix_tokens.append(next_op)
|
||||
|
||||
opStack.push(token_op)
|
||||
#print("--", token, " -> ", str(arity + 1))
|
||||
else:
|
||||
postfix_tokens.append(token)
|
||||
arity = arity_Stack.pop()
|
||||
arity_Stack.push(arity + 1)
|
||||
|
||||
## Pour voir ce qu'il se passe dans cette procédure
|
||||
#print(str(postfix_tokens), " | ", str(opStack), " | ", str(infix_tokens[(pos_token+1):]), " | ", str(arity_Stack))
|
||||
|
||||
while not opStack.isEmpty():
|
||||
next_op = opStack.pop()
|
||||
postfix_tokens.append(next_op)
|
||||
|
||||
## Pour voir ce qu'il se passe dans cette procédure
|
||||
#print(str(postfix_tokens), " | ", str(opStack), " | ", str(infix_tokens[(pos_token+1):]), " | ", str(arity_Stack))
|
||||
|
||||
if arity_Stack.peek() != 1:
|
||||
raise ValueError("Unvalid expression. The arity Stack is ", str(arity_Stack))
|
||||
|
||||
return postfix_tokens
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#a, s, m, d, p = Operator("+"), Operator("-"), Operator("*"), Operator("/"), Operator("^")
|
||||
#in_tokens = str2in_tokens("2+3*4")
|
||||
#print("\t in_tokens :" + str(in_tokens))
|
||||
#
|
||||
#print(in2post_fix(in_tokens))
|
||||
|
||||
#print(in2post_fix([op.par, 2, op.add, 5, op.sub, 1, ')', op.div, op.par, 3, op.mul, 4, ')']))
|
||||
#print(in2post_fix([op.sub1, op.par, op.sub1, 2, ')']))
|
||||
#print(in2post_fix([op.sub1, op.par, op.sub1, 2, op.add, 3, op.mul, 4, ')']))
|
||||
|
||||
print(str2tokens('2*3+4'))
|
||||
print("\n------")
|
||||
print(str2tokens('2x+4'))
|
||||
print("\n------")
|
||||
print(str2tokens('xx+4'))
|
||||
print("\n------")
|
||||
print(str2tokens('x(2+1)+4'))
|
||||
print("\n------")
|
||||
#import doctest
|
||||
#doctest.testmod()
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
Reference in New Issue
Block a user