21 Commits

Author SHA1 Message Date
60ee751e91 Fix: add special case to add typing
Some checks failed
continuous-integration/drone/push Build is failing
Sometimes, typing/add.py tried to build MOpolynomial with only one
coefficient.
2021-10-20 15:33:46 +02:00
d6e3f774fa Feat: fraction can be simplify
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-29 15:34:14 +02:00
1347c30b92 Feat: simplified version for Fraction and MOFraction
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-26 08:58:42 +02:00
bf55470467 Feat: make MOFraction comparable 2021-09-26 08:29:07 +02:00
78ce8f767a Fix: fraction rendering
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-25 18:05:05 +02:00
cbcead48f7 Feat: Init polynomial with coefficients
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-04 10:18:12 +01:00
ff4d8471ef Feat: dirty way to manage () and * in render 2020-12-15 16:01:54 +01:00
460255b151 Fix: Rendering and changing it works 2020-12-15 15:37:27 +01:00
95fd12c430 Feat: test num and denom for fraction 2020-12-15 15:02:39 +01:00
a1608a20d1 Feat: add test on what doesn't work! 2020-12-15 14:36:39 +01:00
48088762be Feat: update tabulate version
Some checks failed
continuous-integration/drone/push Build is failing
2020-12-12 23:25:44 +01:00
2109fc46cb Fix: simplify rendering by expanding tree. still bugs with set_render 2020-12-12 23:14:44 +01:00
1a20e6927d Feat: remove __txt__ and __tex__
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-12 22:10:53 +01:00
7649928be8 Fix: clean __str__ method for token 2020-12-12 22:10:53 +01:00
bb43611f67 Test: separate expression build and showing it
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-15 21:12:26 +01:00
9dc4d08619 Feat: return value for random_list 2020-11-15 21:12:26 +01:00
33c4872ff7 Fix: rename list_generator to random_list 2020-11-15 21:12:26 +01:00
76dad93f03 Tagging: bad version number! 2020-11-15 21:12:26 +01:00
6f7fd416be Tagging: change version to 2.2 2020-11-15 21:12:26 +01:00
8d9294afe1 Feat: random list generator 2020-11-15 21:12:26 +01:00
700665c0a5 Feat: add autopublishing 2020-11-15 21:12:26 +01:00
26 changed files with 517 additions and 252 deletions

View File

@@ -7,3 +7,12 @@ steps:
commands:
- pip install -r requirements.txt
- pytest --doctest-modules ./mapytex/
- name: Publish
image: plugins/pypi
settings:
username:
from_secret: pypi_username
password:
from_secret: pypi_password
when:
event: tag

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# encoding: utf-8
from .calculus import Expression, Integer, Decimal
from .calculus import Expression, Integer, Decimal, random_list, render, Polynomial, Fraction
# Expression.set_render('tex')

View File

@@ -108,26 +108,35 @@ x^7
(6 + 6) * x + 4x^2 + 9
4x^2 + 12x + 9
>>> e = Expression.from_str("(2x-3)(-x+2)")
>>> e_simplified = e.simplify()
>>> e_simplified
<Quadratic - 2x^2 + 7x - 6>
>>> for s in e_simplified.explain():
... print(s)
(2x - 3)(- x + 2)
(2x - 3)(- x + 2)
2x(- x) + 2x * 2 - 3(- x) - 3 * 2
2(- 1) * x^(1 + 1) + 2 * 2 * x - 3(- 1) * x - 6
4x + 3x - 2x^2 - 6
(4 + 3) * x - 2x^2 - 6
- 2x^2 + 7x - 6
"""
from .renders import render
from .expression import Expression
from .tokens.number import Integer, Decimal
from .tokens import Token
from .tokens.polynomial import Polynomial
from .tokens.number import Integer, Decimal, Fraction
if __name__ == "__main__":
e = Expression.from_str("1+2/3/4/5")
et = e._typing()
print("typing")
print(e._tree)
e = et._order()
print("order")
print(e._tree)
e = e._optimize()
print("then optimize")
print(e._tree)
e = et._optimize()
print("optimize without order")
print(e._tree)
e = Expression.from_str("(2x-3)(-x+2)")
e_simplified = e.simplify()
e_simplified
for s in e_simplified.explain():
print(s._tree.map_on_leaf(lambda x: type(x)))
print(s)
# -----------------------------
# Reglages pour 'vim'

View File

@@ -21,7 +21,7 @@ from ..core.random import (
)
from ..core.MO import moify
from .tokens import factory
from .renders import renders
from .renders import render
class Expression(object):
@@ -42,7 +42,6 @@ class Expression(object):
14
"""
RENDER = "txt"
def __init__(self, tree, ancestor=None):
"""
@@ -50,41 +49,6 @@ class Expression(object):
self._tree = tree
self._ancestor = ancestor
@classmethod
def set_render(cls, render):
""" Define default render function
:param render: render name (txt or tex)
:example:
>>> e = Expression.from_str("2+3*4")
>>> print(e)
2 + 3 * 4
>>> e = Expression.from_str("2+3/4")
>>> print(e)
2 + 3 / 4
>>> es = e.simplify()
>>> print(es)
11 / 4
>>> Expression.set_render('tex')
>>> Expression.RENDER
'tex'
>>> e = Expression.from_str("2+3*4")
>>> print(e)
2 + 3 \\times 4
>>> e = Expression.from_str("2+3/4")
>>> print(e)
2 + \\dfrac{3}{4}
>>> es = e.simplify()
>>> print(es)
\\dfrac{11}{4}
>>> Expression.set_render('txt')
"""
from .tokens.token import Token
Token.set_render(render)
cls.RENDER = render
@classmethod
def from_str(cls, string, typing=True):
""" Initiate the expression from a string
@@ -136,9 +100,11 @@ class Expression(object):
:returns: TODO
:example:
>>> Expression.random("{a}/{a*k}") # doctest: +SKIP
>>> e = Expression.random("{a}/{a*k}")
>>> e # doctest: +SKIP
<Exp: -3 / -15>
>>> Expression.random("{a}/{a*k} - 3*{b}", variables_scope={'a':{'min_max':(10, 30)}}) # doctest: +SKIP
>>> e = Expression.random("{a}/{a*k} - 3*{b}", variables_scope={'a':{'min_max':(10, 30)}})
>>> e # doctest: +SKIP
<Exp: 18 / 108 - 3 * 9>
>>> e = Expression.random("{a}*x + {b}*x + 3", ["a>b"], rejected=[0, 1])
>>> ee = e.simplify()
@@ -172,16 +138,16 @@ class Expression(object):
return cls(t)
def __str__(self):
return renders[self.RENDER](self._tree)
return render(self._tree)
def __repr__(self):
return f"<Exp: {renders['txt'](self._tree)}>"
return f"<Exp: {render(self._tree, 'txt')}>"
def _order(self, exclude_nodes=["*", "/", "**"]):
""" Order the expression base on types
:example:
>>> e = Expression.from_str("1 + 2x + 3 + 4x")
>>> print(e)
1 + 2x + 3 + 4x
@@ -202,7 +168,8 @@ class Expression(object):
return type(leaf)
else:
try:
typed_leaf = typing(leaf.node, leaf.left_value, leaf.right_value)
typed_leaf = typing(
leaf.node, leaf.left_value, leaf.right_value)
return typed_leaf.signature
except (AttributeError, NotImplementedError, TypingError):
return type(leaf)
@@ -219,7 +186,7 @@ class Expression(object):
def _optimize(self, exclude_nodes=["/", "**"]):
""" Return a copy of self with an optimize tree
:example:
>>> e = Expression.from_str("2x^2+2x+3x")
>>> print(e._tree)
@@ -260,7 +227,7 @@ class Expression(object):
def _typing(self):
""" Build a copy of self with as much typing as possible
:example:
>>> e = Expression.from_str("2x", typing=False)
>>> print(e._tree.map_on_leaf(lambda x: type(x).__name__))
@@ -305,7 +272,7 @@ class Expression(object):
def _compute(self):
"""" Compute one step of self
"""
try:
return Expression(self._tree.apply_on_last_level(compute))
@@ -342,15 +309,15 @@ class Expression(object):
comp_exp = opt_exp._compute()
if typed_exp == comp_exp:
typed_exp.set_ancestor(self._ancestor)
return typed_exp
else:
if typed_exp != comp_exp:
comp_exp.set_ancestor(self)
return comp_exp._simplify(optimize=optimize)
typed_exp.set_ancestor(self._ancestor)
return typed_exp
def simplify(self, optimize=True):
""" Compute as much as possible the expression
""" Simplify the expression, keep the history and factory child
:param optimize: bool to optimize tree when it's possible
:return: an expression
@@ -370,7 +337,7 @@ class Expression(object):
def explain(self):
""" Yield every calculus step which have lead to self
:example:
>>> e = Expression.from_str("2+3*4")
>>> f = e.simplify()

View File

@@ -12,34 +12,59 @@ Expression
"""
from ..core import tree2txt, tree2tex
class Render(object):
""" Object which render Expression or token """
def _txt(mo_tree):
""" txt render for MOs or Trees"""
try:
return tree2txt(mo_tree)
except ValueError:
pass
def __init__(self, default="txt"):
self._default = default
self._render = default
self.renders = {}
try:
return mo_tree.__txt__
except AttributeError:
return str(mo_tree)
def register_render(self, name, func, attribute):
""" register a render """
try:
self.renders[name]
except KeyError:
self.renders[name] = {"name": name, "func": func, "attribute": attribute}
else:
raise ValueError("This render name already exists")
@property
def render_name(self):
return self._render
@property
def render(self):
return self.renders[self._render]
def __call__(self, mo_tree, tmp_render=''):
if tmp_render:
r = self.renders[tmp_render]
else:
r = self.render
try:
return r["func"](mo_tree)
except ValueError:
pass
try:
return getattr(mo_tree, r["attribute"])
except AttributeError:
return str(mo_tree)
def set_render(self, render):
""" Define the render """
if render in self.renders.keys():
self._render = render
else:
raise ValueError("This render does not exists")
def _tex(mo_tree):
""" Tex render for MOs or Trees"""
try:
return tree2tex(mo_tree)
except ValueError:
pass
try:
return mo_tree.__tex__
except AttributeError:
return str(mo_tree)
renders = {"txt": _txt, "tex": _tex}
render = Render()
render.register_render("txt", tree2txt, "__txt__")
render.register_render("tex", tree2tex, "__tex__")
# -----------------------------
# Reglages pour 'vim'

View File

@@ -18,7 +18,7 @@ from ...core.MO import MO, MOnumber
from ...core.MO.fraction import MOFraction
from random import random
__all__ = ["Integer", "Decimal"]
__all__ = ["Integer", "Decimal", "Fraction"]
class Integer(Token):
@@ -141,7 +141,7 @@ class Decimal(Token):
class Fraction(Token):
""" Token representing a fraction
""" Token representing a fraction of numbers
:example:
>>> Fraction("3/4")
@@ -237,11 +237,25 @@ class Fraction(Token):
@property
def numerator(self):
return self._mo.numerator
""" Get numerator of the fraction
:example:
>>> a = Fraction("3/4")
>>> a.numerator
<Integer 3>
"""
return Integer(self._mo.numerator)
@property
def denominator(self):
return self._mo.denominator
""" Get denominator of the fraction
:example:
>>> a = Fraction("3/4")
>>> a.denominator
<Integer 4>
"""
return Integer(self._mo.denominator)
@property
def decimal(self):
@@ -255,7 +269,58 @@ class Fraction(Token):
>>> f.decimal
<Decimal 0.3333333333333333333333333333>
"""
return Decimal(_Decimal(self._mo.numerator._value) / _Decimal(self._mo.denominator._value))
return Decimal(self._mo._value)
@property
def simplified(self):
""" Get the irreductible version of self
:example:
>>> f = Fraction("3/4")
>>> f.simplified
<Fraction 3 / 4>
>>> f = Fraction("12/9")
>>> f.simplified
<Fraction 4 / 3>
>>> f = Fraction("12/4")
>>> f.simplified
<Integer 3>
"""
simplified = self._mo.simplified()
if isinstance(simplified, MOnumber):
return Integer(simplified)
return Fraction(simplified)
def simplify(self):
""" Itself or its simplified version
:example:
>>> f = Fraction("12/8")
>>> fs = f.simplify()
>>> for i in fs.explain():
... print(i)
12 / 8
3 / 2
>>> f = Fraction("5/8")
>>> fs = f.simplify()
>>> for i in fs.explain():
... print(i)
5 / 8
"""
simplified = self.simplified
try:
if self.numerator == simplified.numerator:
return self
except AttributeError:
pass
simplified._ancestor = self
return simplified
# -----------------------------

View File

@@ -15,6 +15,7 @@ from .token import Token
from . import to_be_token
from ...core.MO import MO
from ...core.MO.atoms import moify
from ...core.MO.polynomial import MOpolynomial
__all__ = ["Polynomial", "Quadratic", "Linear"]
@@ -44,9 +45,21 @@ class Polynomial(Token):
return cls(mo, name, ancestor)
@classmethod
def from_coefficients(cls, coefficients):
""" Initiate polynomial from list of coefficients """
pass
def from_coefficients(cls, coefficients, variable_name="x", name=""):
""" Initiate polynomial from list of coefficients
:examples:
>>> P = Polynomial.from_coefficients([1, 2, 3])
>>> P
<Polynomial 3x^2 + 2x + 1>
>>> P = Polynomial.from_coefficients([1, 2, -3])
>>> P
<Polynomial - 3x^2 + 2x + 1>
>>> P = Polynomial.from_coefficients([1, 2, 3], "y")
>>> P
<Polynomial 3y^2 + 2y + 1>
"""
return cls(MOpolynomial(variable_name, coefficients), name)
@classmethod
def random(cls):
@@ -135,7 +148,11 @@ class Linear(Polynomial):
>>> P.differentiate()
<Integer 2>
>>> P.roots
[<Fraction - 2 / 1>]
[<Integer - 2>]
>>> for i in P.roots[0].explain():
... print(i)
- 2 / 1
- 2
"""
@@ -177,9 +194,20 @@ class Linear(Polynomial):
>>> from ...core.MO.polynomial import MOpolynomial, MOMonomial
>>> P = Linear(MOpolynomial('x', [1, 2]))
>>> P.roots
[<Fraction - 2 / 1>]
>>> #P = Linear(MOpolynomial('x', [1, -2]))
>>> #P.roots
[<Integer - 2>]
>>> P = Linear(MOpolynomial('x', [2, 1]))
>>> P.roots
[<Fraction - 1 / 2>]
>>> for i in P.roots[0].explain():
... print(i)
- 1 / 2
>>> P = Linear(MOpolynomial('x', [10, 6]))
>>> P.roots
[<Fraction - 3 / 5>]
>>> for i in P.roots[0].explain():
... print(i)
- 6 / 10
- 3 / 5
"""
try:

View File

@@ -10,7 +10,7 @@
Tokens: practical envelop of math object
"""
from ..renders import renders
from ..renders import render
from ...core.MO.atoms import moify
@@ -18,8 +18,6 @@ class Token(object):
""" Token: practical envelop of an math object """
RENDER = "txt"
def __init__(self, mo, name="", ancestor=None):
self._mo = mo
self.name = name
@@ -34,17 +32,9 @@ class Token(object):
):
raise NotImplemented
@classmethod
def set_render(cls, render):
""" Define default render function
:param render: render name (txt or tex)
"""
cls.RENDER = render
def explain(self):
""" Yield every calculus step which have lead to self
:example:
>>> from mapytex.calculus.API import Expression
>>> e = Expression.from_str("2+3*4")
@@ -64,25 +54,16 @@ class Token(object):
yield self
def __repr__(self):
return f"<{self.__class__.__name__} {self.__txt__}>"
try:
return f"<{self.__class__.__name__} {render(self._mo._tree, 'txt')}>"
except AttributeError:
return f"<{self.__class__.__name__} {render(self._mo, 'txt')}>"
def __str__(self):
if self.RENDER == "tex":
return self.__tex__
elif self.RENDER == "txt":
return self.__txt__
else:
raise ValueError(f"Unknow render {self.RENDER}")
# return renders[self.RENDER](self._mo)
@property
def __txt__(self):
return self._mo.__txt__
@property
def __tex__(self):
return self._mo.__tex__
try:
return render(self._mo._tree)
except AttributeError:
return render(self._mo)
@property
def raw(self):
@@ -261,11 +242,10 @@ class Token(object):
>>> c = a ** 2
>>> c
<Decimal 5.29>
"""
"""
return self._operate(other, "^")
def _roperate(self, other, operation):
""" Make a operation between 2 Tokens """
from ..expression import Expression
@@ -315,6 +295,7 @@ class Token(object):
<Linear x - 3>
"""
return self._roperate(other, "-")
def __rmul__(self, other):
""" Multiply 2 Tokens or a Token and a Expression
@@ -342,7 +323,6 @@ class Token(object):
"""
return self._roperate(other, "/")
def _get_soul(self, other=None):
""" Get the builtin soul of self or other """
if isinstance(other, Token):

View File

@@ -13,7 +13,7 @@ Make calculus as a student
Expression is the classe wich handle all calculus. It can randomly generate or import calculus, simplify them and explain them as a student would do.
>>> from mapytex.calculus import Expression
>>> Expression.set_render("txt")
>>> render.set_render("txt")
>>> e = Expression.from_str("2x + 6 - 3x")
>>> print(e)
2x + 6 - 3x
@@ -30,7 +30,8 @@ Expression is the classe wich handle all calculus. It can randomly generate or i
"""
from .API import Expression, Integer, Decimal
from .API import Expression, Integer, Decimal, render, Polynomial, Fraction
from .core import random_list
from decimal import getcontext
#getcontext().prec = 2

View File

@@ -37,7 +37,7 @@ def moify_cor(target):
>>> for i in [-2, "+", "x", "*", Decimal("3.3")]:
... list2molist.send(i)
>>> list2molist.throw(STOOOP)
[<MOnumber - 2>, '+', <MOstr x>, '*', <MOnumber 3.3>]
[<MOnumber -2>, '+', <MOstr x>, '*', <MOnumber 3.3>]
"""
try:
@@ -78,7 +78,7 @@ class MOnumber(Atom):
>>> MOnumber(23)
<MOnumber 23>
>>> MOnumber(-23)
<MOnumber - 23>
<MOnumber -23>
As expected there will be trouble with float
@@ -90,13 +90,13 @@ class MOnumber(Atom):
>>> MOnumber(Decimal("23.3"))
<MOnumber 23.3>
>>> MOnumber(Decimal("-23.3"))
<MOnumber - 23.3>
<MOnumber -23.3>
Or directly passe a decimal string
>>> MOnumber("23.3")
<MOnumber 23.3>
>>> MOnumber("-23.3")
<MOnumber - 23.3>
<MOnumber -23.3>
MOnumber initialisation is idempotent
@@ -259,7 +259,8 @@ class MOstr(Atom):
f"An MOstr should be initiate with a single caracter string, got {val}"
)
if not val.isalpha():
raise MOError(f"An MOstr should be initiate with a alpha string, got {val}")
raise MOError(
f"An MOstr should be initiate with a alpha string, got {val}")
Atom.__init__(self, val)

View File

@@ -9,6 +9,8 @@
from mapytex.calculus.core.tree import Tree
from .mo import Molecule, MO
from .atoms import MOnumber
from decimal import Decimal
from ..arithmetic import gcd
__all__ = ["MOFraction"]
@@ -32,10 +34,10 @@ class MOFraction(Molecule):
>>> f = MOFraction(2, 3)
>>> f
<MOFraction 2 / 3>
>>> print(f.__txt__)
2 / 3
>>> print(f.__tex__)
\\dfrac{2}{3}
>>> print(f.tree)
/
> 2
> 3
>>> print(f)
2 / 3
>>> f = MOFraction(2, 3, negative = True)
@@ -44,11 +46,13 @@ class MOFraction(Molecule):
"""
_numerator = MO.factory(numerator)
_denominator = MO.factory(denominator)
base_tree = Tree("/", _numerator, _denominator)
if negative:
tree = Tree("-", None, base_tree)
else:
tree = base_tree
Molecule.__init__(self, tree)
self._numerator = _numerator
@@ -68,6 +72,10 @@ class MOFraction(Molecule):
def denominator(self):
return self._denominator
@property
def _value(self):
return Decimal(self._numerator._value) / Decimal(self._denominator._value)
def inverse(self):
""" return the inverse fraction """
return MOFraction(self._denominator, self._numerator, self.negative)
@@ -88,6 +96,37 @@ class MOFraction(Molecule):
else:
raise NotImplementedError
def simplified(self):
""" Simplified version of self
:examplex
>>> f = MOFraction(2, 3)
>>> f
<MOFraction 2 / 3>
>>> f.simplified()
<MOFraction 2 / 3>
>>> f = MOFraction(2, 6)
>>> f
<MOFraction 2 / 6>
>>> f.simplified()
<MOFraction 1 / 3>
>>> f = MOFraction(32, 24)
>>> f.simplified()
<MOFraction 4 / 3>
>>> f = MOFraction(32, 8)
>>> f.simplified()
<MOnumber 4>
"""
frac_gcd = gcd(self.numerator._value, self.denominator._value)
new_num = self.numerator._value / frac_gcd
new_denom = self.denominator._value / frac_gcd
if new_denom == 1:
return MOnumber(new_num)
return MOFraction(new_num, new_denom)
# -----------------------------
# Reglages pour 'vim'

View File

@@ -57,20 +57,12 @@ class MO(ABC):
pass
def __repr__(self):
return f"<{self.__class__.__name__} {self.__txt__}>"
return f"<{self.__class__.__name__} {self.__str__()}>"
@abstractmethod
def __str__(self):
pass
@abstractmethod
def __txt__(self):
pass
@abstractmethod
def __tex__(self):
pass
def __hash__(self):
try:
return self._tree.__hash__()
@@ -161,21 +153,21 @@ class Molecule(MO):
It is a wrapping of tree
Its wrapping tree can be access throw .tree property
Its wrapping tree can be access through .tree property
"""
MAINOP = None
def __init__(self, value):
def __init__(self, tree):
""" Initiate the MO
It should be idempotent.
"""
try:
self._tree = value._tree
self._tree = tree._tree
except AttributeError:
self._tree = value
self._tree = tree
self.is_scalar = True
self._signature = None
@@ -186,18 +178,10 @@ class Molecule(MO):
@property
def content(self):
return self._tree
return self.tree
def __str__(self):
return str(self.__txt__)
@property
def __txt__(self):
return tree2txt(self._tree)
@property
def __tex__(self):
return tree2tex(self._tree)
return tree2txt(self.tree)
# -----------------------------

View File

@@ -31,10 +31,10 @@ class MOstrPower(Molecule):
<MOstrPower x^2>
>>> print(s)
x^2
>>> print(s.__txt__)
x^2
>>> print(s.__tex__)
x^{2}
>>> print(s.tree)
^
> x
> 2
>>> MOstrPower(3, 1)
Traceback (most recent call last):
...
@@ -59,7 +59,8 @@ class MOstrPower(Molecule):
"""
_variable = MO.factory(variable)
if not isinstance(_variable, MOstr):
raise MOError("The variable of a monomial should be convertible into MOstr")
raise MOError(
"The variable of a monomial should be convertible into MOstr")
self._variable = _variable
_power = MO.factory(power)
@@ -102,7 +103,7 @@ class MOstrPower(Molecule):
@property
def signature(self):
""" Name of the mo in the API
:example:
>>> MOstrPower("x", 3).signature
'monome3'
@@ -123,14 +124,15 @@ class MOstrPower(Molecule):
"""
if self._power > 2:
return Tree(
"*", self.power, MOstrPower(self.variable, self._power._value - 1)
"*", self.power, MOstrPower(self.variable,
self._power._value - 1)
)
return Tree("*", self.power, MOstr(self.variable))
class MOMonomial(Molecule):
""" Monomial math object"""
""" Monomial math object : ax^n"""
MAINOP = "*"
@@ -147,22 +149,20 @@ class MOMonomial(Molecule):
<MOMonomial 4x>
>>> print(m)
4x
>>> print(m.__txt__)
4x
>>> print(m.__tex__)
4x
>>> print(m.tree)
*
> 4
> x
>>> x = MOstrPower('x', 2)
>>> MOMonomial(4, x)
<MOMonomial 4x^2>
>>> m = MOMonomial(4, 'x')
>>> m = MOMonomial(-1, 'x')
>>> m
<MOMonomial 4x>
>>> print(m)
4x
>>> print(m.__txt__)
4x
>>> print(m.__tex__)
4x
<MOMonomial - x>
>>> print(m.tree)
-
> None
> x
>>> MOMonomial(4, 'x', 1)
<MOMonomial 4x>
>>> MOMonomial(4, 'x', 2)
@@ -170,6 +170,13 @@ class MOMonomial(Molecule):
>>> x2 = MOstrPower('x', 2)
>>> MOMonomial(4, x2, 3)
<MOMonomial 4x^6>
>>> m = MOMonomial(-1, 'x', 2)
>>> m
<MOMonomial - x^2>
>>> print(m.tree)
-
> None
> x^2
>>> MOMonomial(0, x)
Traceback (most recent call last):
...
@@ -199,34 +206,21 @@ class MOMonomial(Molecule):
self._power = _power
try:
if self._coefficient.value != 1:
_tree = Tree("*", self._coefficient, self.strpower)
else:
if self.coefficient.value == 1:
_tree = self.strpower
else:
_tree = Tree("*", self.coefficient, self.strpower)
except AttributeError:
_tree = Tree("*", self._coefficient, self.strpower)
_tree = Tree("*", self.coefficient, self.strpower)
Molecule.__init__(self, _tree)
def __str__(self):
if self._coefficient != -1:
return super(MOMonomial, self).__str__()
else:
return "- " + self.strpower.__str__()
@property
def __txt__(self):
if self._coefficient != -1:
return super(MOMonomial, self).__txt__
else:
return "- " + self.strpower.__txt__
def tree(self):
if self._coefficient == -1:
return Tree("-", None, self.strpower)
@property
def __tex__(self):
if self._coefficient != -1:
return super(MOMonomial, self).__tex__
else:
return "- " + self.strpower.__tex__
return Tree("*", self.coefficient, self.strpower)
@property
def coefficient(self):
@@ -265,7 +259,7 @@ class MOMonomial(Molecule):
@property
def signature(self):
""" Name of the mo in the API
:example:
>>> MOMonomial(2, "x").signature
'monome1'

View File

@@ -18,7 +18,7 @@ __all__ = ["MOpolynomial"]
class MOpolynomial(Molecule):
""" MO polynomial"""
""" MO polynomial: ax^n + ... + z (can't be a monomial)"""
MAINOP = "+"
@@ -39,6 +39,14 @@ class MOpolynomial(Molecule):
<MOpolynomial 4x^3 + 1>
>>> MOpolynomial('x', {0: 1, 3: 1})
<MOpolynomial x^3 + 1>
>>> MOpolynomial('x', [0, 0, 3])
Traceback (most recent call last):
...
TypeError: A MOpolynomial can't be monomial it has to have more than one coefficient.
>>> MOpolynomial('x', {3: 1})
Traceback (most recent call last):
...
TypeError: A MOpolynomial can't be monomial it has to have more than one coefficient.
"""
_variable = MO.factory(variable)
@@ -58,6 +66,9 @@ class MOpolynomial(Molecule):
raise TypeError("Coefs needs to be a dictionnary or a list")
self._coefs = _coefs
if len(self._coefs) == 1:
raise TypeError("A MOpolynomial can't be monomial it has to have more than one coefficient.")
monomials = OrderedDict()
for deg in sorted(self._coefs.keys()):
coef = self._coefs[deg]

View File

@@ -66,6 +66,7 @@ from .tree import Tree, AssocialTree
from .compute import compute
from .typing import typing, TypingError
from .renders import tree2txt, tree2tex
from .random import list_generator as random_list
# -----------------------------

View File

@@ -40,7 +40,7 @@ def compute(node, left_v, right_v):
>>> compute("+", MOnumber(1), MOnumber(2))
<MOnumber 3>
>>> compute("-", None, MOnumber(2))
<MOnumber - 2>
<MOnumber -2>
>>> compute("*", MOnumber(1), MOnumber(2))
<MOnumber 2>
>>> compute("~", MOnumber(1), MOnumber(2))
@@ -70,7 +70,8 @@ def compute_capacities(node):
op = OPERATIONS[node]
lines = [[node] + [mo.__name__ for mo in MOS]]
for left_mo in MOS:
lines.append([left_mo.__name__] + [(left_mo, i) in op.funcs for i in MOS])
lines.append([left_mo.__name__] +
[(left_mo, i) in op.funcs for i in MOS])
return lines

View File

@@ -35,7 +35,7 @@ def monumber(_, right):
>>> a = MOnumber(4)
>>> minus(None, a)
<MOnumber - 4>
<MOnumber -4>
"""
return MO.factory(-right.value)

View File

@@ -47,8 +47,8 @@ Tree with RdLeaf replaced by generated values
>>> leafs
['a', 'a*k']
>>> rd_varia = extract_rv(leafs)
>>> rd_varia # doctest: +SKIP
{'a', 'k'}
>>> sorted(list(rd_varia))
['a', 'k']
>>> generated = random_generator(rd_varia, conditions=['a%2+1'])
>>> generated # doctest: +SKIP
{'a': 7, 'k': 4}
@@ -61,8 +61,18 @@ Tree with RdLeaf replaced by generated values
> 7
> 28
List generator
--------------
This function ignores tree structure and works with lists
>>> values = list_generator(["a", "a*b", "b", "c"], conditions=["b%c==1"])
>>> values # doctest: +SKIP
{'a': -8, 'a*b': -40, 'b': 5, 'c': 4}
"""
__all__ = ["list_generator"]
from random import choice
from functools import reduce
from .leaf import RdLeaf
@@ -244,3 +254,35 @@ def build_variable_scope(rd_variables, rejected, min_max, variables_scope):
except KeyError:
complete_scope[v]["min_max"] = min_max
return complete_scope
def list_generator(var_list, conditions=[], rejected=[0], min_max=(-10, 10), variables_scope={}, dictionnary=False):
""" Generate random computed values from the list
:param rd_variables: list of random variables to generate (can be computed value - "a*b")
:param conditions: condition over variables
:param rejected: Rejected values for the generator (default [0])
:param min_max: (min, max) limits in between variables will be generated
:param variables_scope: rejected and min_max define for individual variables
:param dictionnary: the return value will be a dictionnary with var_list as keys (default False)
:return: dictionnary of generated variables
:example:
>>> a, ab, b, c = list_generator(["a", "a*b", "b", "c"])
>>> a, ab, b, c # doctest: +SKIP
(5, -20, -4, -3)
>>> a * b == ab
True
>>> ab # doctest: +SKIP
-20
>>> a, b # doctest: +SKIP
5, -4
>>> list_generator(["a", "a*b", "b", "c"], dictionnary=True) # doctest: +SKIP
{'a': -3, 'a*b': 18, 'b': -6, 'c': -4}
"""
rv = extract_rv(var_list)
rv_gen = random_generator(rv, conditions, rejected, min_max, variables_scope)
generated = compute_leafs(var_list, rv_gen)
if dictionnary:
return generated
return [generated[v] for v in var_list]

View File

@@ -91,9 +91,11 @@ def mul2tex(left, right):
'- 3x'
>>> mul2tex(a, a)
'x \\times x'
>>> mul2tex(a, MO.factory(-3))
'x(- 3)'
"""
left_ = render_with_parenthesis(left, "*")
right_ = render_with_parenthesis(right, "*")
right_ = render_with_parenthesis(right, "*", is_at_right=True)
display_time = True
# if (right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal())) or right_[
@@ -183,23 +185,17 @@ def pow2tex(left, right):
return f"{left_}^{{{right_}}}"
def render_with_parenthesis(subtree, operator):
def render_with_parenthesis(subtree, operator, is_at_right=False):
subtree_need_parenthesis = False
try:
subtree.node
except AttributeError:
try:
if (
OPERATORS[subtree.MAINOP]["precedence"]
< OPERATORS[operator]["precedence"]
):
subtree_need_parenthesis = True
except (AttributeError, KeyError):
pass
try:
subtree_ = subtree.__tex__
except AttributeError:
subtree_ = str(subtree)
if subtree_.startswith("-") and OPERATORS["-"]["precedence"] < OPERATORS[operator]["precedence"] and is_at_right:
subtree_need_parenthesis = True
else:
if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]:
subtree_need_parenthesis = True
@@ -210,7 +206,8 @@ def render_with_parenthesis(subtree, operator):
return subtree_
OPERATOR2TEX = {"+": plus2tex, "-": minus2tex, "*": mul2tex, "/": div2tex, "^": pow2tex}
OPERATOR2TEX = {"+": plus2tex, "-": minus2tex,
"*": mul2tex, "/": div2tex, "^": pow2tex}
def tree2tex(tree):
@@ -230,8 +227,17 @@ def tree2tex(tree):
from ..tree import Tree
if not isinstance(tree, Tree):
raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})")
return OPERATOR2TEX[tree.node](tree.left_value, tree.right_value)
raise ValueError(
f"Can only render a Tree (got {type(tree).__name__}: {tree})")
def expand(leaf):
try:
return leaf.tree
except AttributeError:
return leaf
expanded_tree = tree.map_on_leaf(expand)
return OPERATOR2TEX[expanded_tree.node](expanded_tree.left_value, expanded_tree.right_value)
# -----------------------------

View File

@@ -91,11 +91,13 @@ def mul2txt(left, right):
'- 3x'
>>> mul2txt(a, a)
'x * x'
>>> mul2txt(a, MO.factory(-3))
'x(- 3)'
"""
display_time = True
left_ = render_with_parenthesis(left, "*")
right_ = render_with_parenthesis(right, "*")
right_ = render_with_parenthesis(right, "*", is_at_right=True)
if right_[0].isalpha():
# TODO: C'est bien beurk en dessous... |ven. déc. 21 12:03:07 CET 2018
@@ -187,23 +189,22 @@ def pow2txt(left, right):
return f"{left_}^{right_}"
def render_with_parenthesis(subtree, operator):
def tree_with_parenthesis(subtree, operator):
""" Assuming the subtree is a tree, then have .node """
pass
def render_with_parenthesis(subtree, operator, is_at_right=False):
subtree_need_parenthesis = False
try:
subtree.node
except AttributeError:
try:
if (
OPERATORS[subtree.MAINOP]["precedence"]
< OPERATORS[operator]["precedence"]
):
subtree_need_parenthesis = True
except (AttributeError, KeyError):
pass
try:
subtree_ = subtree.__txt__
except AttributeError:
subtree_ = str(subtree)
if subtree_.startswith("-") and OPERATORS["-"]["precedence"] < OPERATORS[operator]["precedence"] and is_at_right:
subtree_need_parenthesis = True
else:
if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]:
subtree_need_parenthesis = True
@@ -214,7 +215,8 @@ def render_with_parenthesis(subtree, operator):
return subtree_
OPERATOR2TXT = {"+": plus2txt, "-": minus2txt, "*": mul2txt, "/": div2txt, "^": pow2txt}
OPERATOR2TXT = {"+": plus2txt, "-": minus2txt,
"*": mul2txt, "/": div2txt, "^": pow2txt}
def tree2txt(tree):
@@ -234,8 +236,17 @@ def tree2txt(tree):
from ..tree import Tree
if not isinstance(tree, Tree):
raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})")
return OPERATOR2TXT[tree.node](tree.left_value, tree.right_value)
raise ValueError(
f"Can only render a Tree (got {type(tree).__name__}: {tree})")
def expand(leaf):
try:
return leaf.tree
except AttributeError:
return leaf
expanded_tree = tree.map_on_leaf(expand)
return OPERATOR2TXT[expanded_tree.node](expanded_tree.left_value, expanded_tree.right_value)
# -----------------------------

View File

@@ -271,7 +271,7 @@ def concurent_broadcast(target, lookfors=[]):
>>> a = searcher.throw(STOOOP)
>>> print(a)
['az', 'ABC', 'a', 'b', 'az', 'b']
>>> lfop = lookfor(something_in("+-*/()"), lambda x: f"op{x}")
>>> searcher = concurent_broadcast(list_sink, [lfop])
>>> for i in '12+3+234':
@@ -484,7 +484,8 @@ def lookforNumbers(target):
if current.replace("-", "", 1).isdigit():
current += tok
else:
raise ParsingError(f"Can't build decimal with '{current}'")
raise ParsingError(
f"Can't build decimal with '{current}'")
elif tok == "-":
if current == "":
current = tok
@@ -802,7 +803,8 @@ def str2(sink, convert_to_mo=True):
operator_corout(missing_times(moify_cor(pparser(sink))))
)
else:
str2_corout = lookforNumbers(operator_corout(missing_times(pparser(sink))))
str2_corout = lookforNumbers(
operator_corout(missing_times(pparser(sink))))
for i in expression.replace(" ", ""):
str2_corout.send(i)
@@ -819,7 +821,7 @@ def rdstr2(sink):
:example:
>>> rdstr2list = rdstr2(list_sink)
>>> rdstr2list("{a}+{a*b}-2")
[<RdLeaf a>, '+', <RdLeaf a*b>, '+', <MOnumber - 2>]
[<RdLeaf a>, '+', <RdLeaf a*b>, '+', <MOnumber -2>]
>>> rdstr2list("{a}({b}x+{c})")
[<RdLeaf a>, '*', [<RdLeaf b>, '*', <MOstr x>, '+', <RdLeaf c>]]
"""
@@ -828,7 +830,8 @@ def rdstr2(sink):
def pipeline(expression):
str2_corout = look_for_rdleaf(
lookforNumbers(operator_corout(missing_times(moify_cor(pparser(sink)))))
lookforNumbers(operator_corout(
missing_times(moify_cor(pparser(sink)))))
)
for i in expression.replace(" ", ""):

View File

@@ -11,11 +11,11 @@ Add MO with typing
"""
from multipledispatch import Dispatcher
from ..tree import Tree
from ..MO import MO, MOnumber, MOstr
from ..MO import MOnumber, MOstr
from ..MO.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial
from ..MO.fraction import MOFraction
from ..compute.filters import special_case
add_doc = """ Add MOs
@@ -27,8 +27,26 @@ add_doc = """ Add MOs
add = Dispatcher("add", doc=add_doc)
def add_filter(left, right):
""" Special cases for add MO (adding 0)
:param left: MO
:param right: MO
:returns: MO if it is a special case, nothing other wise
"""
try:
if left == 0:
return right
except TypeError:
pass
try:
if right == 0:
return left
except TypeError:
pass
@add.register((MOnumber, MOFraction), MOstr)
@special_case(add_filter)
def moscalar_mostr(left, right):
""" add a scalar with a letter to create a MOpolynomial
@@ -39,11 +57,15 @@ def moscalar_mostr(left, right):
>>> a = MOFraction(1, 5)
>>> add(a, b)
<MOpolynomial x + 1 / 5>
>>> a = MOnumber(0)
>>> add(a, b)
<MOstr x>
"""
return MOpolynomial(right, [left, 1])
@add.register(MOstr, (MOnumber, MOFraction))
@special_case(add_filter)
def mostr_moscalar(left, right):
""" add a scalar with a letter to create a MOpolynomial
@@ -54,11 +76,15 @@ def mostr_moscalar(left, right):
>>> b = MOFraction(1, 5)
>>> add(a, b)
<MOpolynomial x + 1 / 5>
>>> b = MOnumber(0)
>>> add(a, b)
<MOstr x>
"""
return MOpolynomial(left, [right, 1])
@add.register((MOnumber, MOFraction), MOstrPower)
@special_case(add_filter)
def moscalar_mostrpower(left, right):
""" add a scalar with a letter to create a MOpolynomial
@@ -74,6 +100,7 @@ def moscalar_mostrpower(left, right):
@add.register(MOstrPower, (MOnumber, MOFraction))
@special_case(add_filter)
def mostrpower_moscalar(left, right):
""" add a scalar with a letter to create a MOpolynomial
@@ -89,6 +116,7 @@ def mostrpower_moscalar(left, right):
@add.register((MOnumber, MOFraction), MOMonomial)
@special_case(add_filter)
def moscalar_momonomial(left, right):
""" add a scalar with a MOMonomial to create a MOpolynomial
@@ -99,11 +127,17 @@ def moscalar_momonomial(left, right):
>>> a = MOFraction(1, 5)
>>> add(a, b)
<MOpolynomial 3x^4 + 1 / 5>
>>> a = MOnumber(0)
>>> b = MOMonomial(2, 'x', 4)
>>> add(a, b)
<MOMonomial 2x^4>
"""
return MOpolynomial(right.variable, {right.power: right.coefficient, 0: left})
@add.register(MOMonomial, (MOnumber, MOFraction))
@special_case(add_filter)
def momonial_moscalar(left, right):
""" add a scalar with a letter to create a MOpolynomial
@@ -115,11 +149,17 @@ def momonial_moscalar(left, right):
>>> add(a, b)
<MOpolynomial 3x^4 + 1 / 5>
>>> a = MOMonomial(2, 'x', 4)
>>> b = MOnumber(0)
>>> add(a, b)
<MOMonomial 2x^4>
"""
return MOpolynomial(left.variable, {0: right, left.power: left.coefficient})
@add.register((MOnumber, MOFraction), MOpolynomial)
@special_case(add_filter)
def moscalar_mopolynomial(left, right):
""" add a scalar with a MOpolynomial to create a MOpolynomial
@@ -143,6 +183,7 @@ def moscalar_mopolynomial(left, right):
@add.register(MOpolynomial, (MOnumber, MOFraction))
@special_case(add_filter)
def mopolynomial_moscalar(left, right):
""" add a scalar with a MOpolynomial to create a MOpolynomial
@@ -164,6 +205,7 @@ def mopolynomial_moscalar(left, right):
@add.register(MOstr, MOstr)
@special_case(add_filter)
def mostr_mostr(left, right):
""" add 2 mostr
@@ -178,6 +220,7 @@ def mostr_mostr(left, right):
@add.register(MOstr, MOstrPower)
@special_case(add_filter)
def mostr_mostrpower(left, right):
""" add a scalar with a letter to create a MOpolynomial
@@ -195,6 +238,7 @@ def mostr_mostrpower(left, right):
@add.register(MOstrPower, MOstr)
@special_case(add_filter)
def mostrpower_mostr(left, right):
""" add a scalar with a letter to create a MOpolynomial
@@ -212,6 +256,7 @@ def mostrpower_mostr(left, right):
@add.register(MOstrPower, MOstrPower)
@special_case(add_filter)
def mostrpower_mostrpower(left, right):
""" add 2 mostrpower
@@ -231,6 +276,7 @@ def mostrpower_mostrpower(left, right):
@add.register(MOstr, MOpolynomial)
@special_case(add_filter)
def mostr_mopolynomial(left, right):
""" add a str with a MOpolynomial to create a MOpolynomial
@@ -249,6 +295,7 @@ def mostr_mopolynomial(left, right):
@add.register(MOpolynomial, MOstr)
@special_case(add_filter)
def mopolynomial_mostr(left, right):
""" add a str with a MOpolynomial to create a MOpolynomial
@@ -266,6 +313,7 @@ def mopolynomial_mostr(left, right):
@add.register(MOstrPower, MOpolynomial)
@special_case(add_filter)
def mostrpower_mopolynomial(left, right):
""" add a strPower with a MOpolynomial to create a MOpolynomial
@@ -284,6 +332,7 @@ def mostrpower_mopolynomial(left, right):
@add.register(MOpolynomial, MOstrPower)
@special_case(add_filter)
def mopolynomial_mostrpower(left, right):
""" add a strPower with a MOpolynomial to create a MOpolynomial
@@ -301,6 +350,7 @@ def mopolynomial_mostrpower(left, right):
@add.register(MOMonomial, MOpolynomial)
@special_case(add_filter)
def momonomial_mopolynomial(left, right):
""" add a Monomial with a MOpolynomial to create a MOpolynomial
@@ -319,6 +369,7 @@ def momonomial_mopolynomial(left, right):
@add.register(MOpolynomial, MOMonomial)
@special_case(add_filter)
def mopolynomial_momonomial(left, right):
""" add a Monomial with a MOpolynomial to create a MOpolynomial
@@ -336,6 +387,7 @@ def mopolynomial_momonomial(left, right):
@add.register(MOpolynomial, MOpolynomial)
@special_case(add_filter)
def mopolynomial_mopolynomial(left, right):
""" add a polynomial with a MOpolynomial to create a MOpolynomial
@@ -355,6 +407,7 @@ def mopolynomial_mopolynomial(left, right):
@add.register(MOstr, MOMonomial)
@special_case(add_filter)
def mostr_monomial(left, right):
""" add a mostr with a MOMonomial to create a MOpolynomial
@@ -370,6 +423,7 @@ def mostr_monomial(left, right):
@add.register(MOMonomial, MOstr)
@special_case(add_filter)
def monomial_mostr(left, right):
""" add a mostr with a MOMonomial to create a MOpolynomial
@@ -385,6 +439,7 @@ def monomial_mostr(left, right):
@add.register(MOstrPower, MOMonomial)
@special_case(add_filter)
def mostrpower_monomial(left, right):
""" add a mostrPower with a MOMonomial to create a MOpolynomial
@@ -402,6 +457,7 @@ def mostrpower_monomial(left, right):
@add.register(MOMonomial, MOstrPower)
@special_case(add_filter)
def monomial_mostrpower(left, right):
""" add a mostrPower with a MOMonomial to create a MOpolynomial
@@ -419,6 +475,7 @@ def monomial_mostrpower(left, right):
@add.register(MOMonomial, MOMonomial)
@special_case(add_filter)
def monomial_momonomial(left, right):
""" add a moMonomial with a MOMonomial to create a MOpolynomial

View File

@@ -21,6 +21,6 @@ pyparsing==2.3.0
pytest==3.10.1
simplegeneric==0.8.1
six==1.11.0
tabulate==0.8.2
tabulate==0.8.7
traitlets==4.3.2
wcwidth==0.1.7

View File

@@ -7,7 +7,7 @@ except ImportError:
setup(
name="mapytex",
version="2.1",
version="2.3.2",
description="Computing like a student",
author="Benjamin Bertrand",
author_email="programming@opytex.org",

0
test/__init__.py Normal file
View File

View File

@@ -0,0 +1,31 @@
import mapytex
def test_default_render():
assert mapytex.render.render_name == "txt"
def test_default_rending():
e = mapytex.Expression("2*3")
assert str(e) == "2*3"
def test_changing_render():
assert mapytex.render.render_name == "txt"
mapytex.render.set_render("tex")
assert mapytex.render.render_name == "tex"
mapytex.render.set_render("txt")
assert mapytex.render.render_name == "txt"
def test_changing_rending():
e = mapytex.Expression.from_str("2*3")
f = mapytex.Fraction("2/3")
assert str(e) == "2 * 3"
assert str(f) == "2 / 3"
mapytex.render.set_render("tex")
assert str(e) == "2 \\times 3"
assert str(f) == "\\dfrac{2}{3}"
mapytex.render.set_render("txt")
assert str(e) == "2 * 3"
assert str(f) == "2 / 3"