start to reorganise files

This commit is contained in:
Benjamin Bertrand
2016-01-07 19:34:23 +03:00
parent a0cf4c1f67
commit 881de6dcb1
14 changed files with 96 additions and 86 deletions

View 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

View 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

View 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

View 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

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

View 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

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

View 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