43 Commits

Author SHA1 Message Date
27e7dcba20 Feat: doctest skip
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-20 16:59:40 +02:00
5f398b4c8d Feat: change frac to dfrac for fractions 2020-08-20 16:59:40 +02:00
ec823c85eb Feat: force subtree to be str and tex instead of txt in tree2tex 2020-08-20 16:59:40 +02:00
d72a2be175 Feat: Polynomial are displayed in nicer order! 2020-08-20 16:59:40 +02:00
e596c1af60 Fix: clean __init__ 2020-08-20 16:59:40 +02:00
b84cf047bd Fix: chante value to content in raw 2020-08-20 16:59:40 +02:00
d446139af3 Feat: missing_times works with RdLeaf 2020-08-20 16:59:40 +02:00
975728f8dc Feat: Add doctest to rdstr2 2020-08-20 16:59:40 +02:00
2317296534 Feat: raw methods for tokens 2020-08-20 16:59:40 +02:00
c211ed1591 Feat: remove precision setting for Decimal 2020-08-20 16:59:40 +02:00
0c84c63ad3 Feat: overload pow for tokens 2020-08-20 16:59:40 +02:00
25bfb7699b Feat: add doctest add int and Decimal 2020-08-20 16:59:40 +02:00
0abd80655a Feat: dirty way to get decimal approx of a function 2020-08-20 16:59:40 +02:00
a3f7efca12 Feat: allowing to import Decimal 2020-08-20 16:59:40 +02:00
1a74c54548 Feat: allow import Integer in calculus 2020-08-20 16:59:40 +02:00
1dccaabd86 Feat: dirty sub (repeatition in explain) 2020-08-20 16:59:40 +02:00
510f6a1fa2 Feat: doctest for operation between Token and str 2020-08-20 16:59:40 +02:00
6b353d2dd0 Feat: tokens can operate with builtin int 2020-08-20 16:59:40 +02:00
fbfaeb5a58 Fix: precessing -> processing 2020-08-20 16:59:40 +02:00
1a4e8ffb19 Feat: start using nox 2020-08-20 16:59:40 +02:00
d6bb61dc48 Fix: Black does its job 2020-08-20 16:59:40 +02:00
02214b0f82 Feat: no more round in quadratic roots 2020-08-20 16:59:40 +02:00
e52fec4057 Feat: MOnumber creating with a integer string 2020-08-20 16:59:40 +02:00
219d923ff5 Feat: to_be_token decorator and force token to be Integer when it's
possible
2020-08-20 16:59:40 +02:00
419e5955eb Feat: tokenify for everything! 2020-08-20 16:59:40 +02:00
f471a1efb3 Feat: Test and validate roots (but not elegant) 2020-08-20 16:59:40 +02:00
7600962fe4 Feat: doctest for composing Expressions 2020-08-20 16:59:40 +02:00
3e258b2d41 Feat: Expression call works with tokens 2020-08-20 16:59:40 +02:00
9f492378c8 Feat: Allow pure string rendering 2020-08-20 16:59:40 +02:00
0c3c20262e Fix: move __call__ to expressions 2020-08-20 16:59:40 +02:00
b3ec098b0b Feat: add tree2tex in all import 2020-08-20 16:59:40 +02:00
9d9224fcba Feat: Polynoms can be differentiate!!!! 2020-08-20 16:59:40 +02:00
b53de690d5 Fix: Issue with typing null monomial 2020-08-20 16:59:40 +02:00
beb319f21d Fix: replace notimplemented by NotImplementedError 2020-08-20 16:59:40 +02:00
50f77c4d60 Feat: coefficients, delta and some roots for polynomial, Linear and Quadratic 2020-08-20 16:59:40 +02:00
a83b5ada8d Feat: Polynomial can be evaluated 2020-08-20 16:59:40 +02:00
0aba5eaef6 Feat: Move some import into functions 2020-08-20 16:59:40 +02:00
0faaf481ca Feat: start working on eval for polynoms 2020-08-20 16:59:40 +02:00
b51ac7880d Fix: typing filter for multiply 2020-08-20 16:59:40 +02:00
fdf3b088f2 Mise à jour de 'README.md'
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-18 06:29:21 +00:00
c7bd77e341 Mise à jour de '.drone.yml'
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-18 06:21:50 +00:00
37601be549 Feat: nothing
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-17 20:37:51 +02:00
19cdddf27e Feat: add a drone for testing 2020-07-17 20:37:00 +02:00
38 changed files with 828 additions and 1055 deletions

View File

@@ -7,12 +7,3 @@ steps:
commands: commands:
- pip install -r requirements.txt - pip install -r requirements.txt
- pytest --doctest-modules ./mapytex/ - 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 #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
from .calculus import Expression, render, random from .calculus import Expression, Integer, Decimal
# Expression.set_render('tex') # Expression.set_render('tex')

View File

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

View File

@@ -12,9 +12,16 @@ Expression
""" """
from functools import partial from functools import partial
from ..core import AssocialTree, Tree, compute, typing, TypingError from ..core import AssocialTree, Tree, compute, typing, TypingError
from ..core.random import (
extract_rdleaf,
extract_rv,
random_generator,
compute_leafs,
replace_rdleaf,
)
from ..core.MO import moify from ..core.MO import moify
from .tokens import factory from .tokens import factory
from .renders import render from .renders import renders
class Expression(object): class Expression(object):
@@ -35,6 +42,7 @@ class Expression(object):
14 14
""" """
RENDER = "txt"
def __init__(self, tree, ancestor=None): def __init__(self, tree, ancestor=None):
""" """
@@ -42,6 +50,41 @@ class Expression(object):
self._tree = tree self._tree = tree
self._ancestor = ancestor 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 @classmethod
def from_str(cls, string, typing=True): def from_str(cls, string, typing=True):
""" Initiate the expression from a string """ Initiate the expression from a string
@@ -72,6 +115,53 @@ class Expression(object):
return cls(t) return cls(t)
@classmethod
def random(
cls,
template,
conditions=[],
rejected=[0],
min_max=(-10, 10),
variables_scope={},
shuffle=False,
):
""" Initiate randomly the expression
:param template: the template of the expression
:param conditions: conditions on randomly generate variable
:param rejected: Values to reject for all random variables
:param min_max: Min and max value for all random variables
:param variables_scope: Dictionnary for each random varaibles to fic rejected and min_max
:param shuffle: allowing to shuffle the tree
:returns: TODO
:example:
>>> Expression.random("{a}/{a*k}") # doctest: +SKIP
<Exp: -3 / -15>
>>> Expression.random("{a}/{a*k} - 3*{b}", variables_scope={'a':{'min_max':(10, 30)}}) # doctest: +SKIP
<Exp: 18 / 108 - 3 * 9>
>>> e = Expression.random("{a}*x + {b}*x + 3", ["a>b"], rejected=[0, 1])
>>> ee = e.simplify()
>>> print(e) # doctest: +SKIP
10x - 6x + 3
>>> print(ee) # doctest: +SKIP
4x + 3
"""
rd_t = Tree.from_str(template, random=True)
leafs = extract_rdleaf(rd_t)
rd_varia = extract_rv(leafs)
generated = random_generator(
rd_varia, conditions, rejected, min_max, variables_scope
)
computed = compute_leafs(leafs, generated)
t = replace_rdleaf(rd_t, computed).map_on_leaf(moify)
if shuffle:
raise NotImplemented("Can't suffle expression yet")
return cls._post_processing(t)
@classmethod @classmethod
def _post_processing(cls, t): def _post_processing(cls, t):
""" Post process the tree by typing it """ """ Post process the tree by typing it """
@@ -82,10 +172,10 @@ class Expression(object):
return cls(t) return cls(t)
def __str__(self): def __str__(self):
return render(self._tree) return renders[self.RENDER](self._tree)
def __repr__(self): def __repr__(self):
return f"<Exp: {render(self._tree, 'txt')}>" return f"<Exp: {renders['txt'](self._tree)}>"
def _order(self, exclude_nodes=["*", "/", "**"]): def _order(self, exclude_nodes=["*", "/", "**"]):
""" Order the expression base on types """ Order the expression base on types
@@ -112,8 +202,7 @@ class Expression(object):
return type(leaf) return type(leaf)
else: else:
try: try:
typed_leaf = typing( typed_leaf = typing(leaf.node, leaf.left_value, leaf.right_value)
leaf.node, leaf.left_value, leaf.right_value)
return typed_leaf.signature return typed_leaf.signature
except (AttributeError, NotImplementedError, TypingError): except (AttributeError, NotImplementedError, TypingError):
return type(leaf) return type(leaf)
@@ -253,15 +342,15 @@ class Expression(object):
comp_exp = opt_exp._compute() comp_exp = opt_exp._compute()
if typed_exp != comp_exp: if typed_exp == comp_exp:
typed_exp.set_ancestor(self._ancestor)
return typed_exp
else:
comp_exp.set_ancestor(self) comp_exp.set_ancestor(self)
return comp_exp._simplify(optimize=optimize) return comp_exp._simplify(optimize=optimize)
typed_exp.set_ancestor(self._ancestor)
return typed_exp
def simplify(self, optimize=True): def simplify(self, optimize=True):
""" Simplify the expression, keep the history and factory child """ Compute as much as possible the expression
:param optimize: bool to optimize tree when it's possible :param optimize: bool to optimize tree when it's possible
:return: an expression :return: an expression

View File

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

View File

@@ -13,10 +13,12 @@ Tokens representing interger and decimal
from decimal import Decimal as _Decimal from decimal import Decimal as _Decimal
from .token import Token from .token import Token
from ...core.arithmetic import gcd from ...core.arithmetic import gcd
from ...core.random.int_gene import filter_random
from ...core.MO import MO, MOnumber from ...core.MO import MO, MOnumber
from ...core.MO.fraction import MOFraction from ...core.MO.fraction import MOFraction
from random import random
__all__ = ["Integer", "Decimal", "Fraction"] __all__ = ["Integer", "Decimal"]
class Integer(Token): class Integer(Token):
@@ -52,6 +54,23 @@ class Integer(Token):
return cls(mo, name, ancestor) return cls(mo, name, ancestor)
@classmethod
def random(
cls, name="", min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks=[]
):
""" Generate a random Integer
:param name: name of the Integer
:param min_value: minimum value
:param max_value: maximum value
:param rejected: rejected values
:param accept_callbacks: list of function for value acceptation
"""
candidate = filter_random(min_value, max_value, rejected, accept_callbacks)
return Integer(candidate, name)
class Decimal(Token): class Decimal(Token):
@@ -89,10 +108,40 @@ class Decimal(Token):
return cls(mo, name, ancestor) return cls(mo, name, ancestor)
@classmethod
def random(
cls,
name="",
min_value=-10,
max_value=10,
digits=2,
rejected=[0, 1],
reject_callbacks=[],
):
""" Generate a random Decimal
:param name: name of the Integer
:param min_value: minimum value
:param max_value: maximum value
:param digits: digits after comas
:param rejected: rejected values
:param reject_callbacks: list of function for value rejection
"""
conditions = [lambda x: x in rejected] + reject_callbacks
float_cand = (max_value - min_value) * random() + min_value
candidate = _Decimal(f"{float_cand:.{digits}f}")
while any(c(candidate) for c in conditions):
float_cand = (max_value - min_value) * random() + min_value
candidate = _Decimal(f"{float_cand:.{digits}f}")
return Decimal(candidate, name)
class Fraction(Token): class Fraction(Token):
""" Token representing a fraction of numbers """ Token representing a fraction
:example: :example:
>>> Fraction("3/4") >>> Fraction("3/4")
@@ -123,27 +172,76 @@ class Fraction(Token):
return cls(mo, name, ancestor) return cls(mo, name, ancestor)
@classmethod
def random(
cls,
name="",
fix_num="",
min_num=-10,
max_num=10,
rejected_num=[0],
accept_num_callbacks=[],
fix_denom="",
min_denom=-10,
max_denom=10,
rejected_denom=[0, 1, -1],
accept_denom_callbacks=[],
irreductible=False,
not_integer=True,
):
""" Generate a random Fraction
:param name: Name of the fraction
:param fix_num: if set, the numerator will get this value
:param min_num: minimum value for the numerator
:param max_num: maximum value for the numerator
:param rejected_num: rejected values for the numerator
:param accept_num_callbacks: list of function for numerator rejection
:param fix_denom: if set, the denomerator will get this value
:param min_denom: minimum value for the denominator
:param max_denom: maximum value for the denominator
:param rejected_denom: rejected values for the denominator
:param accept_denom_callbacks: list of function for denomerator rejection
:param irreductible: is the generated fraction necessary irreductible
:param not_integer: can the generated fraction be egal to an interger
"""
if fix_num == "":
num = filter_random(min_num, max_num, rejected_num, accept_num_callbacks)
else:
num = fix_num
if fix_denom == "":
accept_callbacks = accept_denom_callbacks
if irreductible:
def prime_with_num(denom):
return gcd(num, denom) == 1
accept_callbacks.append(prime_with_num)
if not_integer:
def not_divise_num(denom):
return num % denom != 0
accept_callbacks.append(not_divise_num)
denom = filter_random(
min_denom, max_denom, rejected_denom, accept_callbacks
)
else:
denom = fix_denom
frac = MOFraction(num, denom)
return cls(frac, name)
@property @property
def numerator(self): def numerator(self):
""" Get numerator of the fraction return self._mo.numerator
:example:
>>> a = Fraction("3/4")
>>> a.numerator
<Integer 3>
"""
return Integer(self._mo.numerator)
@property @property
def denominator(self): def denominator(self):
""" Get denominator of the fraction return self._mo.denominator
:example:
>>> a = Fraction("3/4")
>>> a.denominator
<Integer 4>
"""
return Integer(self._mo.denominator)
@property @property
def decimal(self): def decimal(self):
@@ -157,58 +255,7 @@ class Fraction(Token):
>>> f.decimal >>> f.decimal
<Decimal 0.3333333333333333333333333333> <Decimal 0.3333333333333333333333333333>
""" """
return Decimal(self._mo._value) return Decimal(_Decimal(self._mo.numerator._value) / _Decimal(self._mo.denominator._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,7 +15,6 @@ from .token import Token
from . import to_be_token from . import to_be_token
from ...core.MO import MO from ...core.MO import MO
from ...core.MO.atoms import moify from ...core.MO.atoms import moify
from ...core.MO.polynomial import MOpolynomial
__all__ = ["Polynomial", "Quadratic", "Linear"] __all__ = ["Polynomial", "Quadratic", "Linear"]
@@ -45,21 +44,13 @@ class Polynomial(Token):
return cls(mo, name, ancestor) return cls(mo, name, ancestor)
@classmethod @classmethod
def from_coefficients(cls, coefficients, variable_name="x", name=""): def from_coefficients(cls, coefficients):
""" Initiate polynomial from list of coefficients """ Initiate polynomial from list of coefficients """
pass
:examples: @classmethod
>>> P = Polynomial.from_coefficients([1, 2, 3]) def random(cls):
>>> P raise NotImplementedError
<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)
@property @property
def raw(self): def raw(self):
@@ -144,11 +135,7 @@ class Linear(Polynomial):
>>> P.differentiate() >>> P.differentiate()
<Integer 2> <Integer 2>
>>> P.roots >>> P.roots
[<Integer - 2>] [<Fraction - 2 / 1>]
>>> for i in P.roots[0].explain():
... print(i)
- 2 / 1
- 2
""" """
@@ -167,6 +154,10 @@ class Linear(Polynomial):
Polynomial.__init__(self, mo, name, ancestor) Polynomial.__init__(self, mo, name, ancestor)
self._mathtype = "affine" self._mathtype = "affine"
@classmethod
def random(cls):
raise NotImplementedError
@property @property
@to_be_token @to_be_token
def a(self): def a(self):
@@ -186,20 +177,9 @@ class Linear(Polynomial):
>>> from ...core.MO.polynomial import MOpolynomial, MOMonomial >>> from ...core.MO.polynomial import MOpolynomial, MOMonomial
>>> P = Linear(MOpolynomial('x', [1, 2])) >>> P = Linear(MOpolynomial('x', [1, 2]))
>>> P.roots >>> P.roots
[<Integer - 2>] [<Fraction - 2 / 1>]
>>> P = Linear(MOpolynomial('x', [2, 1])) >>> #P = Linear(MOpolynomial('x', [1, -2]))
>>> P.roots >>> #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: try:
@@ -250,6 +230,10 @@ class Quadratic(Polynomial):
Polynomial.__init__(self, mo, name, ancestor) Polynomial.__init__(self, mo, name, ancestor)
self._mathtype = "polynome du 2nd degré" self._mathtype = "polynome du 2nd degré"
@classmethod
def random(cls):
raise NotImplementedError
@property @property
@to_be_token @to_be_token
def a(self): def a(self):

View File

@@ -10,7 +10,7 @@
Tokens: practical envelop of math object Tokens: practical envelop of math object
""" """
from ..renders import render from ..renders import renders
from ...core.MO.atoms import moify from ...core.MO.atoms import moify
@@ -18,12 +18,30 @@ class Token(object):
""" Token: practical envelop of an math object """ """ Token: practical envelop of an math object """
RENDER = "txt"
def __init__(self, mo, name="", ancestor=None): def __init__(self, mo, name="", ancestor=None):
self._mo = mo self._mo = mo
self.name = name self.name = name
self._mathtype = None self._mathtype = None
self._ancestor = ancestor self._ancestor = ancestor
@classmethod
def random(
cls,
family="integer",
**kwds
):
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): def explain(self):
""" Yield every calculus step which have lead to self """ Yield every calculus step which have lead to self
@@ -46,16 +64,25 @@ class Token(object):
yield self yield self
def __repr__(self): def __repr__(self):
try: return f"<{self.__class__.__name__} {self.__txt__}>"
return f"<{self.__class__.__name__} {render(self._mo._tree, 'txt')}>"
except AttributeError:
return f"<{self.__class__.__name__} {render(self._mo, 'txt')}>"
def __str__(self): def __str__(self):
try: if self.RENDER == "tex":
return render(self._mo._tree) return self.__tex__
except AttributeError: elif self.RENDER == "txt":
return render(self._mo) 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__
@property @property
def raw(self): def raw(self):
@@ -234,10 +261,11 @@ class Token(object):
>>> c = a ** 2 >>> c = a ** 2
>>> c >>> c
<Decimal 5.29> <Decimal 5.29>
"""
"""
return self._operate(other, "^") return self._operate(other, "^")
def _roperate(self, other, operation): def _roperate(self, other, operation):
""" Make a operation between 2 Tokens """ """ Make a operation between 2 Tokens """
from ..expression import Expression from ..expression import Expression
@@ -287,7 +315,6 @@ class Token(object):
<Linear x - 3> <Linear x - 3>
""" """
return self._roperate(other, "-") return self._roperate(other, "-")
def __rmul__(self, other): def __rmul__(self, other):
""" Multiply 2 Tokens or a Token and a Expression """ Multiply 2 Tokens or a Token and a Expression
@@ -315,6 +342,7 @@ class Token(object):
""" """
return self._roperate(other, "/") return self._roperate(other, "/")
def _get_soul(self, other=None): def _get_soul(self, other=None):
""" Get the builtin soul of self or other """ """ Get the builtin soul of self or other """
if isinstance(other, Token): if isinstance(other, Token):

View File

@@ -12,7 +12,8 @@ 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. 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.
>>> render.set_render("txt") >>> from mapytex.calculus import Expression
>>> Expression.set_render("txt")
>>> e = Expression.from_str("2x + 6 - 3x") >>> e = Expression.from_str("2x + 6 - 3x")
>>> print(e) >>> print(e)
2x + 6 - 3x 2x + 6 - 3x
@@ -26,23 +27,15 @@ Expression is the classe wich handle all calculus. It can randomly generate or i
(2 - 3) * x + 6 (2 - 3) * x + 6
- x + 6 - x + 6
Create random Expression
========================
>>> e = random.expression("{a} / {b} + {c} / {d}")
>>> print(e) # doctest: +SKIP
- 3 / - 10 + 3 / 5
""" """
from .API import render, Expression from .API import Expression, Integer, Decimal
#from decimal import getcontext from decimal import getcontext
from . import random
#getcontext().prec = 2 #getcontext().prec = 2
__all__ = ["render", "Expression", "random"] __all__ = ["Expression"]
# ----------------------------- # -----------------------------

View File

@@ -259,8 +259,7 @@ class MOstr(Atom):
f"An MOstr should be initiate with a single caracter string, got {val}" f"An MOstr should be initiate with a single caracter string, got {val}"
) )
if not val.isalpha(): if not val.isalpha():
raise MOError( raise MOError(f"An MOstr should be initiate with a alpha string, got {val}")
f"An MOstr should be initiate with a alpha string, got {val}")
Atom.__init__(self, val) Atom.__init__(self, val)

View File

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

View File

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

View File

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

View File

@@ -70,8 +70,7 @@ def compute_capacities(node):
op = OPERATIONS[node] op = OPERATIONS[node]
lines = [[node] + [mo.__name__ for mo in MOS]] lines = [[node] + [mo.__name__ for mo in MOS]]
for left_mo in MOS: for left_mo in MOS:
lines.append([left_mo.__name__] + lines.append([left_mo.__name__] + [(left_mo, i) in op.funcs for i in MOS])
[(left_mo, i) in op.funcs for i in MOS])
return lines return lines

View File

@@ -0,0 +1,246 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2017 lafrite <lafrite@Poivre>
#
# Distributed under terms of the MIT license.
"""
Tools to extract random leafs, random variables, generate random values and
fill new trees
Flow
----
Tree with RdLeaf
|
| Extract rdLeaf
|
List of leafs to generate
|
| extract_rv
|
List random variables to generate
|
| Generate
|
Dictionnary of generated random variables
|
| Compute leafs
|
Dictionnary of computed leafs
|
| Replace
|
Tree with RdLeaf replaced by generated values
:example:
>>> from ..tree import Tree
>>> rd_t = Tree("/", RdLeaf("a"), RdLeaf("a*k"))
>>> print(rd_t)
/
> {a}
> {a*k}
>>> leafs = extract_rdleaf(rd_t)
>>> leafs
['a', 'a*k']
>>> rd_varia = extract_rv(leafs)
>>> rd_varia # doctest: +SKIP
{'a', 'k'}
>>> generated = random_generator(rd_varia, conditions=['a%2+1'])
>>> generated # doctest: +SKIP
{'a': 7, 'k': 4}
>>> computed = compute_leafs(leafs, generated)
>>> computed # doctest: +SKIP
{'a': 7, 'a*k': 28}
>>> replaced = replace_rdleaf(rd_t, computed)
>>> print(replaced) # doctest: +SKIP
/
> 7
> 28
"""
from random import choice
from functools import reduce
from .leaf import RdLeaf
def extract_rdleaf(tree):
""" Extract rdLeaf in a Tree
:example:
>>> from ..tree import Tree
>>> rd_t = Tree("+", RdLeaf("a"), RdLeaf("a*k"))
>>> extract_rdleaf(rd_t)
['a', 'a*k']
>>> rd_t = Tree("+", RdLeaf("a"), 2)
>>> extract_rdleaf(rd_t)
['a']
"""
rd_leafs = []
for leaf in tree.get_leafs():
try:
leaf.rdleaf
except AttributeError:
pass
else:
rd_leafs.append(leaf.name)
return rd_leafs
def extract_rv(leafs):
""" Extract the set of random values from the leaf list
:param leafs: list of leafs
:return: set of random values
:example:
>>> leafs = ["a", "a*k"]
>>> extract_rv(leafs) == {'a', 'k'}
True
"""
rd_values = set()
for leaf in leafs:
for c in leaf:
if c.isalpha():
rd_values.add(c)
return rd_values
def compute_leafs(leafs, generated_values):
""" Compute leafs from generated random values
:param generated_values: Dictionnary of name:generated value
:param leafs: list of leafs
:return: Dictionnary of evaluated leafs from generated values
:example:
>>> leafs = ["a", "a*k"]
>>> generated_values = {"a":2, "k":3}
>>> compute_leafs(leafs, generated_values)
{'a': 2, 'a*k': 6}
"""
return {leaf: eval(leaf, generated_values) for leaf in leafs}
def replace_rdleaf(tree, computed_leafs):
""" Replace RdLeaf by the corresponding computed value
>>> from ..tree import Tree
>>> rd_t = Tree("+", RdLeaf("a"), RdLeaf("a*k"))
>>> computed_leafs = {'a': 2, 'a*k': 6}
>>> print(replace_rdleaf(rd_t, computed_leafs))
+
> 2
> 6
"""
def replace(leaf):
try:
return leaf.replace(computed_leafs)
except AttributeError:
return leaf
return tree.map_on_leaf(replace)
def random_generator(
rd_variables, conditions=[], rejected=[0], min_max=(-10, 10), variables_scope={}
):
""" Generate random variables
:param rd_variables: list of random variables to generate
: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
:return: dictionnary of generated variables
:example:
>>> gene = random_generator(["a", "b"],
... ["a > 0"],
... [0], (-10, 10),
... {"a": {"rejected": [0, 1]},
... "b": {"min_max": (-5, 0)}})
>>> gene["a"] > 0
True
>>> gene["a"] != 0
True
>>> gene["b"] < 0
True
>>> gene = random_generator(["a", "b"],
... ["a % b == 0"],
... [0, 1], (-10, 10))
>>> gene["a"] not in [0, 1]
True
>>> gene["b"] in list(range(-10, 11))
True
>>> gene["a"] % gene["b"]
0
"""
complete_scope = build_variable_scope(
rd_variables, rejected, min_max, variables_scope
)
choices_list = {
v: list(
set(
range(
complete_scope[v]["min_max"][0], complete_scope[v]["min_max"][1] + 1
)
).difference(complete_scope[v]["rejected"])
)
for v in rd_variables
}
# quantity_choices = reduce(lambda x,y : x*y,
# [len(choices_list[v]) for v in choices_list])
# TODO: améliorer la méthode de rejet avec un cache |dim. mai 12 17:04:11 CEST 2019
generate_variable = {v: choice(choices_list[v]) for v in rd_variables}
while not all([eval(c, __builtins__, generate_variable) for c in conditions]):
generate_variable = {v: choice(choices_list[v]) for v in rd_variables}
return generate_variable
def build_variable_scope(rd_variables, rejected, min_max, variables_scope):
""" Build variables scope from incomplete one
:param rd_variables: list of random variables to generate
:param rejected: Rejected values for the generator
:param min_max: (min, max) limits in between variables will be generated
:param variables_scope: rejected and min_max define for individual variables
:return: complete variable scope
:example:
>>> completed = build_variable_scope(["a", "b", "c", "d"], [0], (-10, 10),
... {"a": {"rejected": [0, 1]},
... "b": {"min_max": (-5, 0)},
... "c": {"rejected": [2], "min_max": (0, 5)}})
>>> complete = {'a': {'rejected': [0, 1], 'min_max': (-10, 10)},
... 'b': {'rejected': [0], 'min_max': (-5, 0)},
... 'c': {'rejected': [2], 'min_max': (0, 5)},
... 'd': {'rejected': [0], 'min_max': (-10, 10)}}
>>> completed == complete
True
"""
complete_scope = variables_scope.copy()
for v in rd_variables:
try:
complete_scope[v]
except KeyError:
complete_scope[v] = {"rejected": rejected, "min_max": min_max}
else:
try:
complete_scope[v]["rejected"]
except KeyError:
complete_scope[v]["rejected"] = rejected
try:
complete_scope[v]["min_max"]
except KeyError:
complete_scope[v]["min_max"] = min_max
return complete_scope

View File

@@ -111,7 +111,8 @@ def filter_random(min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks
class FilterRandom(object): class FilterRandom(object):
"""Integer random generator which filter then choose candidate""" """ Integer random generator which filter then choose candidate
"""
# TODO: Faire un cache pour éviter de reconstruire les listes à chaque fois |ven. déc. 21 19:07:42 CET 2018 # TODO: Faire un cache pour éviter de reconstruire les listes à chaque fois |ven. déc. 21 19:07:42 CET 2018

View File

@@ -9,7 +9,7 @@
""" """
""" """
from ...core.coroutine import coroutine, STOOOP from ..coroutine import *
@coroutine @coroutine
@@ -17,7 +17,7 @@ def look_for_rdleaf(target):
""" Coroutine which look to "{...}" which are RdLeaf """ Coroutine which look to "{...}" which are RdLeaf
:example: :example:
>>> from ...core.str2 import list_sink >>> from ..str2 import list_sink
>>> str2list = look_for_rdleaf(list_sink) >>> str2list = look_for_rdleaf(list_sink)
>>> for i in "{a}+{a*b}-2": >>> for i in "{a}+{a*b}-2":
... str2list.send(i) ... str2list.send(i)
@@ -53,7 +53,9 @@ def look_for_rdleaf(target):
class RdLeaf: class RdLeaf:
"""Random leaf""" """ Random leaf
"""
def __init__(self, name): def __init__(self, name):
self._name = name self._name = name

View File

@@ -91,11 +91,9 @@ def mul2tex(left, right):
'- 3x' '- 3x'
>>> mul2tex(a, a) >>> mul2tex(a, a)
'x \\times x' 'x \\times x'
>>> mul2tex(a, MO.factory(-3))
'x(- 3)'
""" """
left_ = render_with_parenthesis(left, "*") left_ = render_with_parenthesis(left, "*")
right_ = render_with_parenthesis(right, "*", is_at_right=True) right_ = render_with_parenthesis(right, "*")
display_time = True display_time = True
# if (right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal())) or right_[ # if (right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal())) or right_[
@@ -185,17 +183,23 @@ def pow2tex(left, right):
return f"{left_}^{{{right_}}}" return f"{left_}^{{{right_}}}"
def render_with_parenthesis(subtree, operator, is_at_right=False): def render_with_parenthesis(subtree, operator):
subtree_need_parenthesis = False subtree_need_parenthesis = False
try: try:
subtree.node subtree.node
except AttributeError: except AttributeError:
try:
if (
OPERATORS[subtree.MAINOP]["precedence"]
< OPERATORS[operator]["precedence"]
):
subtree_need_parenthesis = True
except (AttributeError, KeyError):
pass
try: try:
subtree_ = subtree.__tex__ subtree_ = subtree.__tex__
except AttributeError: except AttributeError:
subtree_ = str(subtree) subtree_ = str(subtree)
if subtree_.startswith("-") and OPERATORS["-"]["precedence"] < OPERATORS[operator]["precedence"] and is_at_right:
subtree_need_parenthesis = True
else: else:
if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]: if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]:
subtree_need_parenthesis = True subtree_need_parenthesis = True
@@ -206,8 +210,7 @@ def render_with_parenthesis(subtree, operator, is_at_right=False):
return subtree_ return subtree_
OPERATOR2TEX = {"+": plus2tex, "-": minus2tex, OPERATOR2TEX = {"+": plus2tex, "-": minus2tex, "*": mul2tex, "/": div2tex, "^": pow2tex}
"*": mul2tex, "/": div2tex, "^": pow2tex}
def tree2tex(tree): def tree2tex(tree):
@@ -227,17 +230,8 @@ def tree2tex(tree):
from ..tree import Tree from ..tree import Tree
if not isinstance(tree, Tree): if not isinstance(tree, Tree):
raise ValueError( raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})")
f"Can only render a Tree (got {type(tree).__name__}: {tree})") return OPERATOR2TEX[tree.node](tree.left_value, tree.right_value)
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,13 +91,11 @@ def mul2txt(left, right):
'- 3x' '- 3x'
>>> mul2txt(a, a) >>> mul2txt(a, a)
'x * x' 'x * x'
>>> mul2txt(a, MO.factory(-3))
'x(- 3)'
""" """
display_time = True display_time = True
left_ = render_with_parenthesis(left, "*") left_ = render_with_parenthesis(left, "*")
right_ = render_with_parenthesis(right, "*", is_at_right=True) right_ = render_with_parenthesis(right, "*")
if right_[0].isalpha(): if right_[0].isalpha():
# TODO: C'est bien beurk en dessous... |ven. déc. 21 12:03:07 CET 2018 # TODO: C'est bien beurk en dessous... |ven. déc. 21 12:03:07 CET 2018
@@ -189,22 +187,23 @@ def pow2txt(left, right):
return f"{left_}^{right_}" return f"{left_}^{right_}"
def tree_with_parenthesis(subtree, operator): def render_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 subtree_need_parenthesis = False
try: try:
subtree.node subtree.node
except AttributeError: except AttributeError:
try:
if (
OPERATORS[subtree.MAINOP]["precedence"]
< OPERATORS[operator]["precedence"]
):
subtree_need_parenthesis = True
except (AttributeError, KeyError):
pass
try: try:
subtree_ = subtree.__txt__ subtree_ = subtree.__txt__
except AttributeError: except AttributeError:
subtree_ = str(subtree) subtree_ = str(subtree)
if subtree_.startswith("-") and OPERATORS["-"]["precedence"] < OPERATORS[operator]["precedence"] and is_at_right:
subtree_need_parenthesis = True
else: else:
if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]: if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]:
subtree_need_parenthesis = True subtree_need_parenthesis = True
@@ -215,8 +214,7 @@ def render_with_parenthesis(subtree, operator, is_at_right=False):
return subtree_ return subtree_
OPERATOR2TXT = {"+": plus2txt, "-": minus2txt, OPERATOR2TXT = {"+": plus2txt, "-": minus2txt, "*": mul2txt, "/": div2txt, "^": pow2txt}
"*": mul2txt, "/": div2txt, "^": pow2txt}
def tree2txt(tree): def tree2txt(tree):
@@ -236,17 +234,8 @@ def tree2txt(tree):
from ..tree import Tree from ..tree import Tree
if not isinstance(tree, Tree): if not isinstance(tree, Tree):
raise ValueError( raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})")
f"Can only render a Tree (got {type(tree).__name__}: {tree})") return OPERATOR2TXT[tree.node](tree.left_value, tree.right_value)
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

@@ -15,6 +15,7 @@ from decimal import Decimal, InvalidOperation
from .coroutine import * from .coroutine import *
from .operator import is_operator from .operator import is_operator
from .MO import moify_cor from .MO import moify_cor
from .random.leaf import look_for_rdleaf, RdLeaf
__all__ = ["str2"] __all__ = ["str2"]
@@ -394,7 +395,11 @@ def missing_times(target):
elif not is_operator(tok) and tok != ")": elif not is_operator(tok) and tok != ")":
target_.send("*") target_.send("*")
if not ( is_operator(tok) or tok =="(" ): if (
isinstance(tok, int)
or (isinstance(tok, str) and not is_operator(tok) and not tok == "(")
or (isinstance(tok, RdLeaf))
):
previous = tok previous = tok
target_.send(tok) target_.send(tok)
@@ -479,8 +484,7 @@ def lookforNumbers(target):
if current.replace("-", "", 1).isdigit(): if current.replace("-", "", 1).isdigit():
current += tok current += tok
else: else:
raise ParsingError( raise ParsingError(f"Can't build decimal with '{current}'")
f"Can't build decimal with '{current}'")
elif tok == "-": elif tok == "-":
if current == "": if current == "":
current = tok current = tok
@@ -798,8 +802,7 @@ def str2(sink, convert_to_mo=True):
operator_corout(missing_times(moify_cor(pparser(sink)))) operator_corout(missing_times(moify_cor(pparser(sink))))
) )
else: else:
str2_corout = lookforNumbers( str2_corout = lookforNumbers(operator_corout(missing_times(pparser(sink))))
operator_corout(missing_times(pparser(sink))))
for i in expression.replace(" ", ""): for i in expression.replace(" ", ""):
str2_corout.send(i) str2_corout.send(i)
@@ -809,7 +812,37 @@ def str2(sink, convert_to_mo=True):
return pipeline return pipeline
def rdstr2(sink):
""" Return a pipeline which parse random expression and with sink as endpoint
:example:
>>> rdstr2list = rdstr2(list_sink)
>>> rdstr2list("{a}+{a*b}-2")
[<RdLeaf a>, '+', <RdLeaf a*b>, '+', <MOnumber - 2>]
>>> rdstr2list("{a}({b}x+{c})")
[<RdLeaf a>, '*', [<RdLeaf b>, '*', <MOstr x>, '+', <RdLeaf c>]]
"""
lfop = lookfor(is_operator)
operator_corout = partial(concurent_broadcast, lookfors=[lfop])
def pipeline(expression):
str2_corout = look_for_rdleaf(
lookforNumbers(operator_corout(missing_times(moify_cor(pparser(sink)))))
)
for i in expression.replace(" ", ""):
str2_corout.send(i)
a = str2_corout.throw(STOOOP)
return a
return pipeline
str2nestedlist = str2(list_sink) str2nestedlist = str2(list_sink)
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4: # vim:set autoindent expandtab tabstop=4 shiftwidth=4:

View File

@@ -11,7 +11,7 @@ Tree class
from .tree_tools import to_nested_parenthesis, postfix_concatenate, show_tree from .tree_tools import to_nested_parenthesis, postfix_concatenate, show_tree
from .coroutine import coroutine, STOOOP from .coroutine import coroutine, STOOOP
from .str2 import str2 from .str2 import str2, rdstr2
from .operator import OPERATORS, is_operator from .operator import OPERATORS, is_operator
__all__ = ["Tree", "MutableTree"] __all__ = ["Tree", "MutableTree"]
@@ -51,7 +51,7 @@ class Tree:
self.right_value = right_value self.right_value = right_value
@classmethod @classmethod
def from_str(cls, expression, convert_to_mo=True): def from_str(cls, expression, convert_to_mo=True, random=False):
""" Initiate a tree from an string expression """ Initiate a tree from an string expression
:example: :example:
@@ -77,8 +77,26 @@ class Tree:
> * > *
| > 3 | > 3
| > n | > n
>>> t = Tree.from_str("2+{n}x", random=True)
>>> print(t)
+
> 2
> *
| > {n}
| > x
>>> t = Tree.from_str("{a}({b}x+{c})", random=True)
>>> print(t)
*
> {a}
> +
| > *
| | > {b}
| | > x
| > {c}
""" """
t = MutableTree.from_str(expression, convert_to_mo) t = MutableTree.from_str(expression, convert_to_mo, random)
return cls.from_any_tree(t) return cls.from_any_tree(t)
@classmethod @classmethod
@@ -889,7 +907,7 @@ class MutableTree(Tree):
self.right_value = right_value self.right_value = right_value
@classmethod @classmethod
def from_str(cls, expression, convert_to_mo=True): def from_str(cls, expression, convert_to_mo=True, random=False):
""" Initiate the MutableTree """ Initiate the MutableTree
:example: :example:
@@ -945,7 +963,27 @@ class MutableTree(Tree):
| | > 8 | | > 8
| | > 3 | | > 3
| > x | > x
>>> t = MutableTree.from_str("{b}*x+{c}", random=True)
>>> print(t)
+
> *
| > {b}
| > x
> {c}
>>> t = MutableTree.from_str("{a}*({b}*x+{c})", random=True)
>>> print(t)
*
> {a}
> +
| > *
| | > {b}
| | > x
| > {c}
""" """
if random:
str_2_mut_tree = rdstr2(cls.sink)
return str_2_mut_tree(expression)
else:
str_2_mut_tree = str2(cls.sink, convert_to_mo) str_2_mut_tree = str2(cls.sink, convert_to_mo)
return str_2_mut_tree(expression) return str_2_mut_tree(expression)

View File

@@ -1,19 +0,0 @@
from .list import list_generator as list
from .expression import expression_generator as expression
__all__ = ["list", "expression"]
"""
Generate random stuffs
======================
list_generator
==============
Generate random lists
expression_generator
====================
Generate random Expression
"""

View File

@@ -1,65 +0,0 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2017 lafrite <lafrite@Poivre>
#
# Distributed under terms of the MIT license.
"""
Tools to extract random leaves, random variables, generate random values and
fill new trees
Flow
----
Tree with RdLeaf
|
| Extract rdLeaf
|
List of leaves to generate
|
| extract_rv
|
List random variables to generate
|
| Generate
|
Dictionnary of generated random variables
|
| Compute leaves
|
Dictionnary of computed leaves
|
| Replace
|
Tree with RdLeaf replaced by generated values
:example:
>>> from .random_tree import RandomTree
>>> from .leaf import RdLeaf
>>> from .generate import random_generator
>>> from .grammar import extract_letters, eval_words
>>> rd_t = RandomTree("/", RdLeaf("a"), RdLeaf("a*k"))
>>> print(rd_t)
/
> {a}
> {a*k}
>>> leaves = rd_t.random_leaves
>>> leaves = ['a', 'a*k']
>>> rd_varia = extract_letters(leaves)
>>> sorted(list(rd_varia))
['a', 'k']
>>> generated = random_generator(rd_varia, conditions=['a%2+1'], global_config={"min_max": (-10, 10), "rejected":[0, 1]})
>>> generated # doctest: +SKIP
{'a': 7, 'k': 4}
>>> computed = eval_words(leaves, generated)
>>> computed # doctest: +SKIP
{'a': 7, 'a*k': 28}
>>> replaced = rd_t.eval_random_leaves(computed)
>>> print(replaced) # doctest: +SKIP
/
> 7
> 28
"""

View File

@@ -1,116 +0,0 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2017 lafrite <lafrite@Poivre>
#
# Distributed under terms of the MIT license.
from random import choice
import math
EVAL_FUN = {**math.__dict__}
def complete_variable_configs(
variables, global_config: dict = {}, configs: dict = {}
) -> dict:
"""Completes variables configurations with the global configuration
:param variables: list of random variables to generate
:param global_config: global parameters
:param configs: global parameters
:return: complete variable scope
:example:
>>> completed = complete_variable_configs(["a", "b", "c", "d"],
... global_config={"rejected": [], "min_max": (-10, 10)},
... configs={
... "a": {"rejected": [0, 1]},
... "b": {"min_max": (-5, 0)},
... "c": {"rejected": [2], "min_max": (0, 5)}
... })
>>> completed["a"] == {'rejected': [0, 1], 'min_max': (-10, 10)}
True
>>> completed["b"] == {'rejected': [], 'min_max': (-5, 0)}
True
>>> completed['c'] == {'rejected': [2], 'min_max': (0, 5)}
True
>>> completed['d'] == {'rejected': [], 'min_max': (-10, 10)}
True
"""
complete_configs = configs.copy()
for variable in variables:
try:
complete_configs[variable]
except KeyError:
complete_configs[variable] = global_config
else:
complete_configs[variable] = dict(global_config, **configs[variable])
return complete_configs
def random_generator(
variables: list[str],
conditions: list[str] = [],
global_config: dict = {},
configs: dict = {},
) -> dict[str, int]:
"""Generate random variables
:param variables: list of random variables to generate
:param conditions: condition over variables
:param global_config: global parameters
:param configs: global parameters
:return: dictionnary of generated variables
In variables and configurations, you have access to all math module functions
:example:
>>> gene = random_generator(["a", "b"],
... ["a > 0"],
... {"rejected": [0], "min_max":(-10, 10)},
... {"a": {"rejected": [0, 1]},
... "b": {"min_max": (-5, 0)},
... })
>>> gene["a"] > 0
True
>>> gene["a"] != 0
True
>>> gene["b"] < 0
True
>>> gene = random_generator(["a", "b"],
... ["a % b == 0"],
... {"rejected": [0, 1], "min_max":(-10, 10)}
... )
>>> gene["a"] not in [0, 1]
True
>>> gene["b"] in list(range(-10, 11))
True
>>> gene["a"] % gene["b"]
0
"""
complete_scope = complete_variable_configs(variables, global_config, configs)
choices_list = {
v: list(
set(
range(
complete_scope[v]["min_max"][0], complete_scope[v]["min_max"][1] + 1
)
).difference(complete_scope[v]["rejected"])
)
for v in variables
}
# quantity_choices = reduce(lambda x,y : x*y,
# [len(choices_list[v]) for v in choices_list])
# TODO: améliorer la méthode de rejet avec un cache |dim. mai 12 17:04:11 CEST 2019
generate_variable = {v: choice(choices_list[v]) for v in variables}
while not all([eval(c, EVAL_FUN, generate_variable) for c in conditions]):
generate_variable = {v: choice(choices_list[v]) for v in variables}
return generate_variable

View File

@@ -1,43 +0,0 @@
import math
EVAL_FUN = {**math.__dict__}
def extract_letters(words: list[str]) -> set[str]:
"""Extracts unique letters from a list of words
:param words: list of leafs
:return: set of letters
:example:
>>> leafs = ["a", "a*k"]
>>> extract_letters(leafs) == {'a', 'k'}
True
"""
letters = set()
for word in words:
for c in word:
if c.isalpha():
letters.add(c)
return letters
def eval_words(words: list[str], values: dict[str, int]) -> dict[str, int]:
"""Evaluate words replacing letters with values
:param words: list of words
:param values: Dictionary of letters:value
:return: Dictionary of evaluated words from generated values
In words, you have access to all math module functions
:example:
>>> leafs = ["a", "a*k"]
>>> generated_values = {"a":2, "k":3}
>>> eval_words(leafs, generated_values)
{'a': 2, 'a*k': 6}
>>> leafs = ["exp(a)", "gcd(a, k)"]
>>> generated_values = {"a":2, "k":3}
>>> eval_words(leafs, generated_values)
{'exp(a)': 7.38905609893065, 'gcd(a, k)': 1}
"""
return {word: eval(word, EVAL_FUN, values) for word in words}

View File

@@ -1,118 +0,0 @@
from ...core.tree import MutableTree, Tree
from ...core.MO import moify
from .grammar import extract_letters, eval_words
from .generate import random_generator
from .str2 import rdstr2
class RandomTree(MutableTree):
"""MutableTree that accept {a} syntax for random generation
:example:
>>> t = RandomTree()
>>> type(t)
<class 'mapytex.calculus.random.core.random_tree.RandomTree'>
"""
@classmethod
def from_str(cls, expression):
"""Initiate a random tree from a string that need to be parsed
:exemple:
>>> t = RandomTree.from_str("{b}*x+{c}")
>>> print(t)
+
> *
| > {b}
| > x
> {c}
>>> t = RandomTree.from_str("{a}*({b}*x+{c})")
>>> print(t)
*
> {a}
> +
| > *
| | > {b}
| | > x
| > {c}
"""
str_2_mut_tree = rdstr2(cls.sink)
return str_2_mut_tree(expression)
@property
def random_leaves(self) -> list[str]:
"""Get list of random leaves
:example:
>>> from .leaf import RdLeaf
>>> random_tree = RandomTree("+", RdLeaf("a"), RdLeaf("a*k"))
>>> random_tree.random_leaves
['a', 'a*k']
>>> random_tree = RandomTree("+", RdLeaf("a"), 2)
>>> random_tree.random_leaves
['a']
"""
rd_leafs = []
for leaf in self.get_leafs():
try:
leaf.rdleaf
except AttributeError:
pass
else:
rd_leafs.append(leaf.name)
return rd_leafs
@property
def random_value(self) -> set[str]:
"""Get set of random values to generate
:example:
>>> from .leaf import RdLeaf
>>> random_tree = RandomTree("+", RdLeaf("a"), RdLeaf("a*k"))
>>> random_tree.random_value == {'a', 'k'}
True
>>> random_tree = RandomTree("+", RdLeaf("a"), 2)
>>> random_tree.random_value
{'a'}
"""
return extract_letters(self.random_leaves)
def eval_random_leaves(self, leaves_value: dict[str, int]):
"""Given random leaves value get the tree
:example:
>>> from .leaf import RdLeaf
>>> rd_t = RandomTree("+", RdLeaf("a"), RdLeaf("a*k"))
>>> leaves_values = {'a': 2, 'a*k': 6}
>>> t = rd_t.eval_random_leaves(leaves_values)
>>> type(t)
<class 'mapytex.calculus.core.tree.Tree'>
>>> print(t)
+
> 2
> 6
"""
def replace(leaf):
try:
return leaf.replace(leaves_value)
except AttributeError:
return leaf
return self.map_on_leaf(replace).map_on_leaf(moify)
def generate(
self, conditions: list[str] = [], global_config: dict = {}, configs: dict = {}
) -> Tree:
"""Generate a random version of self
:param conditions: list of conditions
:param config: global configuration for generated values
:param configs: specific configuration for each generated values
"""
generated_values = random_generator(
self.random_value, conditions, global_config, configs
)
leaves = eval_words(self.random_leaves, generated_values)
return self.eval_random_leaves(leaves)

View File

@@ -1,40 +0,0 @@
from ...core.operator import is_operator
from functools import partial
from ...core.str2 import (
concurent_broadcast,
lookforNumbers,
pparser,
missing_times,
lookfor,
)
from ...core.coroutine import STOOOP
from ...core.MO import moify_cor
from .leaf import look_for_rdleaf
def rdstr2(sink):
"""Return a pipeline which parse random expression and with sink as endpoint
:example:
>>> from ...core.str2 import list_sink
>>> rdstr2list = rdstr2(list_sink)
>>> rdstr2list("{a}+{a*b}-2")
[<RdLeaf a>, '+', <RdLeaf a*b>, '+', <MOnumber -2>]
>>> rdstr2list("{a}({b}x+{c})")
[<RdLeaf a>, '*', [<RdLeaf b>, '*', <MOstr x>, '+', <RdLeaf c>]]
"""
lfop = lookfor(is_operator)
operator_corout = partial(concurent_broadcast, lookfors=[lfop])
def pipeline(expression):
str2_corout = look_for_rdleaf(
lookforNumbers(operator_corout(missing_times(moify_cor(pparser(sink)))))
)
for i in expression.replace(" ", ""):
str2_corout.send(i)
a = str2_corout.throw(STOOOP)
return a
return pipeline

View File

@@ -1,43 +0,0 @@
from ..API.expression import Expression
from .core.random_tree import RandomTree
DEFAUTL_CONFIG = {
"rejected": [0, 1],
"min_max": (-10, 10),
}
def expression_generator(
template: str,
conditions: list[str] = [],
global_config: dict = {},
configs: dict = {},
):
"""Generate a random expression
:param template: the template of the expression
:param conditions: conditions on randomly generate variable
:param global_config: configuration for all variables
:param configs: configuration for each variables
:return: Expression or Token generated
:example:
>>> e = expression_generator("{a}/{a*k}")
>>> e # doctest: +SKIP
<Exp: -3 / -15>
>>> e = expression_generator("{a}/{a*k} - 3*{b}", configs={'a':{'min_max':(10, 30)}})
>>> e # doctest: +SKIP
<Exp: 18 / 108 - 3 * 9>
>>> e = expression_generator("{a}*x + {b}*x + 3", conditions=["a>b"], global_config={"rejected":[0, 1]})
>>> print(e) # doctest: +SKIP
10x - 6x + 3
>>> ee = e.simplify()
>>> print(ee) # doctest: +SKIP
4x + 3
"""
rd_tree = RandomTree.from_str(template)
generated_tree = rd_tree.generate(
conditions, dict(DEFAUTL_CONFIG, **global_config), configs
)
return Expression._post_processing(generated_tree)

View File

@@ -1,64 +0,0 @@
"""
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}
"""
from .core.generate import random_generator
from .core.grammar import extract_letters, eval_words
DEFAUTL_CONFIG = {
"rejected": [0],
"min_max": (-10, 10),
}
def list_generator(
template: list[str],
conditions: list[str] = [],
global_config: dict = {},
configs: dict = {},
) -> list[int]:
"""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 global_config: configuration for all variables
:param configs: configuration for each variables
:return: list 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
>>> a, ab, b, c = list_generator(["a", "a*b", "b", "c"], conditions=["a-b==0"])
>>> a - b == 0
True
>>> a, ab, b, c = list_generator(["a", "a*b", "b", "c"], global_config={"rejected": [2, 3, 5, 7]})
>>> a not in [2, 3, 5, 7]
True
>>> b not in [2, 3, 5, 7]
True
>>> c not in [2, 3, 5, 7]
True
>>> a, ab, b, c = list_generator(["a", "a*b", "b", "c"], configs={"a": {"rejected": [2, 3, 5, 7]}})
>>> a not in [2, 3, 5, 7]
True
"""
rv = extract_letters(template)
rv_gen = random_generator(
rv, conditions, dict(DEFAUTL_CONFIG, **global_config), configs
)
generated = eval_words(template, rv_gen)
return [generated[v] for v in template]

View File

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

View File

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

View File

View File

@@ -1,31 +0,0 @@
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.Expression.from_str("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"

View File

@@ -1,15 +0,0 @@
import mapytex
def test_generate_expression():
random_expression = mapytex.random.expression("{a}+{b}")
assert type(random_expression).__name__ == "Expression"
random_expression = mapytex.random.expression("{a}/{b}")
assert type(random_expression).__name__ == "Fraction"
def test_generate_expression_calculus():
random_expression = mapytex.random.expression("{a}+{a*b}")
assert type(random_expression).__name__ == "Expression"
random_expression = mapytex.random.expression("{a}/{a*b}", global_config={"min_max": (2, 10)})
assert type(random_expression).__name__ == "Fraction"
assert random_expression.numerator / random_expression.denominator <= 1

View File

@@ -1,55 +0,0 @@
import mapytex
def test_generate_list():
random_list = mapytex.random.list(["a", "b"])
assert len(random_list) == 2
random_list = mapytex.random.list(["a", "b", "c"])
assert len(random_list) == 3
random_list = mapytex.random.list(["a", "b", "a", "b"])
assert random_list[0] == random_list[2]
assert random_list[1] == random_list[3]
def test_generate_list_calculus():
random_list = mapytex.random.list(["a", "b", "a+b"])
assert random_list[0] + random_list[1] == random_list[2]
random_list = mapytex.random.list(["a", "b", "a-b"])
assert random_list[0] - random_list[1] == random_list[2]
random_list = mapytex.random.list(["a", "b", "a*b"])
assert random_list[0] * random_list[1] == random_list[2]
random_list = mapytex.random.list(["a", "b", "a/b"])
assert random_list[0] / random_list[1] == random_list[2]
def test_generate_list_calculus_math():
import math
a, b, gcd = mapytex.random.list(["a", "b", "gcd(a, b)"])
assert math.gcd(a, b) == gcd
a, b, exp, cos = mapytex.random.list(["a", "b", "exp(a)", "cos(b)"])
assert math.exp(a) == exp
assert math.cos(b) == cos
def test_generate_list_conditions():
a, b = mapytex.random.list(["a", "b"], conditions=["a + b == 10"])
assert a + b == 10
a, b = mapytex.random.list(["a", "b"], conditions=["a * b > 0", "a + b == 10"])
assert a + b == 10
assert a * b > 0
def test_generate_list_conditions_math():
import math
a, b = mapytex.random.list(["a", "b"], conditions=["gcd(a, b) == 3"])
assert math.gcd(a, b) == 3
def test_generate_list_global_config():
global_config = {"rejected": [0, 1, 2, 3]}
a, = mapytex.random.list(["a"], global_config=global_config)
assert a not in global_config["rejected"]
global_config = {"min_max": (20, 30)}
a, = mapytex.random.list(["a"], global_config=global_config)
assert a >= 20
assert a <= 30