Compare commits

...

79 Commits

Author SHA1 Message Date
5fa3682f73 Feat: Remove references to random in API
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-26 19:20:22 +02:00
86d66e8b1b Fix: fraction test
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-25 15:04:46 +02:00
9919dd77f6 Feat: Few tests for Expression generator
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-10 16:40:50 +02:00
5b0d0e5d1e Fix: log -> cos for domain issues 2021-10-09 20:01:32 +02:00
0f575ae0ae Feat: testing list random generator
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-09 16:18:37 +02:00
b738cf8dd8 Feat: use functions from math module in variable and conditions 2021-10-09 16:09:09 +02:00
32112a4591 Feat: test random import 2021-10-09 15:22:58 +02:00
b43c64fc7e Feat: rename var_list to template 2021-10-09 08:32:00 +02:00
aad2395a3a Fix: format with black 2021-10-09 06:30:38 +02:00
a267acd7b3 Feat: random expression generator is working
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-06 16:17:50 +02:00
5d909a5f81 Fix: list_generator pass doctests 2021-10-06 15:41:35 +02:00
87b6b3ca27 Feat: move to global_config, configs for generator
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-03 16:52:54 +02:00
b2b204c17b Feat: move extract_letters and eval_words in grammar 2021-10-03 15:41:54 +02:00
204c7dffd7 Feat: RandomTree, rename generate function ... 2021-10-03 15:36:35 +02:00
1672530179 Refact: move random function away from core 2021-09-30 14:52:08 +02:00
daed07efa3 Feat: simplify "no * allowed token" 2021-09-29 16:13:02 +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
7e4bfc9ba0 Feat: doctest skip
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-20 16:57:17 +02:00
cdfd3e47b5 Mise à jour de 'README.md' 2020-08-20 16:57:17 +02:00
364f16b872 Mise à jour de '.drone.yml' 2020-08-20 16:57:17 +02:00
cc94f1f50c Feat: nothing 2020-08-20 16:57:17 +02:00
ad6923f8cd Feat: add a drone for testing 2020-08-20 16:57:17 +02:00
fadd93b9a6 Feat: change frac to dfrac for fractions 2019-11-04 09:43:52 +01:00
cb8010c7ac Feat: force subtree to be str and tex instead of txt in tree2tex 2019-11-04 09:39:52 +01:00
f12ec38746 Feat: Polynomial are displayed in nicer order! 2019-10-30 21:12:58 +01:00
f8b24472d1 Fix: clean __init__ 2019-10-30 20:59:09 +01:00
240857a7ad Fix: chante value to content in raw 2019-10-30 20:58:46 +01:00
70ec6933ac Feat: missing_times works with RdLeaf 2019-10-30 18:46:57 +01:00
91779bd945 Feat: Add doctest to rdstr2 2019-10-30 10:49:07 +01:00
69e1e43966 Feat: raw methods for tokens 2019-10-16 22:45:41 +02:00
16a6c4953d Feat: remove precision setting for Decimal 2019-10-15 19:41:09 +02:00
84c71f6b5d Feat: overload pow for tokens 2019-10-15 19:32:19 +02:00
2a74722b19 Feat: add doctest add int and Decimal 2019-10-15 19:15:32 +02:00
43fd46379c Feat: dirty way to get decimal approx of a function 2019-10-14 22:26:51 +02:00
2d296cf1d2 Feat: allowing to import Decimal 2019-10-14 22:22:08 +02:00
12109156d1 Feat: allow import Integer in calculus 2019-10-13 22:38:49 +02:00
2fad004d6d Feat: dirty sub (repeatition in explain) 2019-10-13 22:38:49 +02:00
207dead5d0 Feat: doctest for operation between Token and str 2019-10-13 22:38:49 +02:00
e24bff23db Feat: tokens can operate with builtin int 2019-10-13 22:38:49 +02:00
69c2b3718d Fix: precessing -> processing 2019-10-13 22:38:49 +02:00
9c25142a4b Feat: start using nox 2019-10-13 22:38:49 +02:00
931e55c35e Fix: Black does its job 2019-10-13 22:38:49 +02:00
aba43c9f19 Feat: no more round in quadratic roots 2019-07-17 09:55:32 +02:00
b93f34bc38 Feat: MOnumber creating with a integer string 2019-07-17 09:53:03 +02:00
204fdf755b Feat: to_be_token decorator and force token to be Integer when it's
possible
2019-07-17 09:49:50 +02:00
d75fd4c4cf Feat: tokenify for everything! 2019-07-17 09:25:12 +02:00
a32b684b6b Feat: Test and validate roots (but not elegant) 2019-07-16 16:49:33 +02:00
a5f062a151 Feat: doctest for composing Expressions 2019-07-16 09:31:30 +02:00
f8e1c7f962 Feat: Expression call works with tokens 2019-07-16 09:30:30 +02:00
1685c73051 Feat: Allow pure string rendering 2019-07-16 09:09:39 +02:00
04ac74d17a Fix: move __call__ to expressions 2019-07-15 18:37:16 +02:00
23ab39f7d4 Feat: add tree2tex in all import 2019-07-15 18:36:59 +02:00
41385ec561 Feat: Polynoms can be differentiate!!!! 2019-07-15 17:48:59 +02:00
8389a319f0 Fix: Issue with typing null monomial 2019-07-15 17:27:38 +02:00
6e24756746 Fix: replace notimplemented by NotImplementedError 2019-07-15 17:16:00 +02:00
d45ab560c9 Feat: coefficients, delta and some roots for polynomial, Linear and Quadratic 2019-07-15 11:59:28 +02:00
fa8beb6bb2 Feat: Polynomial can be evaluated 2019-07-15 11:14:48 +02:00
b81281d78a Feat: Move some import into functions 2019-07-15 11:09:59 +02:00
071b8caf1d Feat: start working on eval for polynoms 2019-07-15 10:42:49 +02:00
1ed0b9715a Fix: typing filter for multiply 2019-07-15 10:42:13 +02:00
53 changed files with 2028 additions and 967 deletions

18
.drone.yml Normal file
View File

@ -0,0 +1,18 @@
kind: pipeline
name: default
steps:
- name: testing
image: python
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

2
.gitignore vendored
View File

@ -4,5 +4,7 @@ dist/
build/ build/
*.egg-info/ *.egg-info/
documentation/build/ documentation/build/
documentation/source/_build/
cache/ cache/
venv/ venv/
.nox

View File

@ -1,4 +1,5 @@
# Mapytex # Mapytex
[![Build Status](https://drone.opytex.org/api/badges/lafrite/Mapytex/status.svg)](https://drone.opytex.org/lafrite/Mapytex)
Formal calculus with explanation python module and exercises creation tools. Formal calculus with explanation python module and exercises creation tools.

View File

@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
from .calculus import Expression#, Polynom, Fraction, random_str, txt, Equation from .calculus import Expression, render, random
#Expression.set_render('tex') # Expression.set_render('tex')
from .stat import Dataset, WeightedDataset from .stat import Dataset, WeightedDataset
from .geometry import random_pythagore from .geometry import random_pythagore

View File

@ -84,19 +84,20 @@ x^7
>>> e = Expression.from_str("1+2x^2+3x+4+5x") >>> e = Expression.from_str("1+2x^2+3x+4+5x")
>>> e_simplified = e.simplify() >>> e_simplified = e.simplify()
>>> e_simplified >>> e_simplified
<Quadratic 5 + 2x^2 + 8x> <Quadratic 2x^2 + 8x + 5>
>>> for s in e_simplified.explain(): >>> for s in e_simplified.explain():
... print(s) ... print(s)
1 + 2x^2 + 3x + 4 + 5x 1 + 2x^2 + 3x + 4 + 5x
1 + 2x^2 + 3x + 4 + 5x 2x^2 + 3x + 1 + 4 + 5x
1 + 4 + 2x^2 + 3x + 5x 2x^2 + 3x + 5x + 1 + 4
5 + 2x^2 + (3 + 5) * x 2x^2 + (3 + 5) * x + 5
5 + 2x^2 + 8x 2x^2 + 8x + 5
>>> e = Expression.from_str("(2x+3)^2") >>> e = Expression.from_str("(2x+3)^2")
>>> e_simplified = e.simplify() >>> e_simplified = e.simplify()
>>> e_simplified >>> e_simplified
<Quadratic 12x + 4x^2 + 9> <Quadratic 4x^2 + 12x + 9>
>>> for s in e_simplified.explain(): >>> for s in e_simplified.explain():
... print(s) ... print(s)
(2x + 3)^2 (2x + 3)^2
@ -105,26 +106,37 @@ x^7
2 * 2 * x^(1 + 1) + 3 * 2 * x + 3 * 2 * x + 9 2 * 2 * x^(1 + 1) + 3 * 2 * x + 3 * 2 * x + 9
6x + 6x + 4x^2 + 9 6x + 6x + 4x^2 + 9
(6 + 6) * x + 4x^2 + 9 (6 + 6) * x + 4x^2 + 9
12x + 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 .expression import Expression
from .tokens import Token
from .tokens.polynomial import Polynomial
from .tokens.number import Integer, Decimal, Fraction
if __name__ == "__main__": if __name__ == "__main__":
e = Expression.from_str("1+2/3/4/5") e = Expression.from_str("(2x-3)(-x+2)")
et = e._typing() e_simplified = e.simplify()
print("typing") e_simplified
print(e._tree) for s in e_simplified.explain():
e = et._order() print(s._tree.map_on_leaf(lambda x: type(x)))
print("order") print(s)
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

@ -10,11 +10,11 @@
Expression Expression
""" """
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, Token from .tokens import factory
from .renders import renders from .renders import render
class Expression(object): class Expression(object):
@ -35,7 +35,6 @@ class Expression(object):
14 14
""" """
RENDER = "txt"
def __init__(self, tree, ancestor=None): def __init__(self, tree, ancestor=None):
""" """
@ -43,39 +42,6 @@ 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 + \\frac{3}{4}
>>> es = e.simplify()
>>> print(es)
\\frac{11}{4}
>>> Expression.set_render('txt')
"""
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
@ -95,67 +61,19 @@ class Expression(object):
<Linear 2x + 1> <Linear 2x + 1>
>>> e = Expression.from_str("2x + 1 + 5x^2") >>> e = Expression.from_str("2x + 1 + 5x^2")
>>> e >>> e
<Quadratic 2x + 1 + 5x^2> <Quadratic 5x^2 + 2x + 1>
>>> e = Expression.from_str("2x + 1 + 5x") >>> e = Expression.from_str("2x + 1 + 5x")
>>> e >>> e
<Exp: 2x + 1 + 5x> <Exp: 2x + 1 + 5x>
""" """
t = Tree.from_str(string) t = Tree.from_str(string)
if typing: if typing:
return cls._post_precessing(t) return cls._post_processing(t)
return cls(t) return cls(t)
@classmethod @classmethod
def random( def _post_processing(cls, t):
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_precessing(t)
@classmethod
def _post_precessing(cls, t):
""" Post process the tree by typing it """ """ Post process the tree by typing it """
tt = cls(t)._typing() tt = cls(t)._typing()
try: try:
@ -164,10 +82,10 @@ class Expression(object):
return cls(t) return cls(t)
def __str__(self): def __str__(self):
return renders[self.RENDER](self._tree) return render(self._tree)
def __repr__(self): def __repr__(self):
return f"<Exp: {renders['txt'](self._tree)}>" return f"<Exp: {render(self._tree, 'txt')}>"
def _order(self, exclude_nodes=["*", "/", "**"]): def _order(self, exclude_nodes=["*", "/", "**"]):
""" Order the expression base on types """ Order the expression base on types
@ -194,7 +112,8 @@ class Expression(object):
return type(leaf) return type(leaf)
else: else:
try: 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 return typed_leaf.signature
except (AttributeError, NotImplementedError, TypingError): except (AttributeError, NotImplementedError, TypingError):
return type(leaf) return type(leaf)
@ -334,15 +253,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):
""" 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 :param optimize: bool to optimize tree when it's possible
:return: an expression :return: an expression
@ -437,6 +356,65 @@ class Expression(object):
else: else:
yield self yield self
def __call__(self, value):
""" Call a Expression to evaluate itself on value
:param value: evaluate the Expression with this value
:return: Expression simplified if the value is not a string with a length greater than 1.
:examples:
>>> f = Expression.from_str("3*x^2 + 2x + 1")
>>> for s in f(2).explain():
... print(s)
3 * 2^2 + 2 * 2 + 1
3 * 4 + 4 + 1
12 + 5
17
>>> f(f(2))
<Integer 902>
>>> f(17)
<Integer 902>
>>> f("n")
<Quadratic 3n^2 + 2n + 1>
>>> f("u_n")
<Exp: 3u_n^2 + 2u_n + 1>
>>> f(f)
<Polynomial 27x^4 + 36x^3 + 36x^2 + 16x + 6>
"""
tree = self._tree
variable = (set(tree.get_leafs(extract_variable)) - {None}).pop()
try:
dest = value._mo
except AttributeError:
dest = moify(value)
replace_var = partial(replace, origin=variable, dest=dest)
tree = tree.map_on_leaf(replace_var)
if isinstance(value, str) and len(value) > 1:
return Expression(tree)
return Expression(tree).simplify()
def extract_variable(leaf):
try:
return leaf.variable
except AttributeError:
return None
def replace(leaf, origin, dest):
""" Recursively replace origin to dest in leaf """
try:
leaf.tree
except AttributeError:
if leaf == origin:
return dest
return leaf
replace_var = partial(replace, origin=origin, dest=dest)
return leaf.tree.map_on_leaf(replace_var)
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'

View File

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

@ -10,22 +10,163 @@
Tokens represents MathObject at API level Tokens represents MathObject at API level
""" """
from ...core.MO import MO, MOnumber, MOstr from ...core.MO import MO, MOnumber, MOstr, moify
from ...core.MO.fraction import MOFraction from ...core.MO.fraction import MOFraction
from ...core.MO.monomial import MOstrPower, MOMonomial from ...core.MO.monomial import MOstrPower, MOMonomial
from ...core.MO.polynomial import MOpolynomial from ...core.MO.polynomial import MOpolynomial
from decimal import Decimal as _Decimal from decimal import Decimal as _Decimal
from functools import wraps
from .number import Integer, Decimal, Fraction
from .polynomial import Polynomial, Linear, Quadratic
from .token import Token from .token import Token
__all__ = ["factory"] __all__ = ["factory"]
def tokenify(mo, name="", ancestor=None):
""" Transform a MO or a python builtin to the appropriate token
:param mo: the thing to turn into a Token
:param name: a virtual name of the toke
:param ancestor: its ancestor
:example:
>>> a = MOnumber(2)
>>> tokenify(a)
<Integer 2>
>>> tokenify(2)
<Integer 2>
>>> tokenify("x")
<Linear x>
>>> tokenify(_Decimal("2.2"))
<Decimal 2.2>
>>> tokenify("2.2")
<Decimal 2.2>
>>> tokenify(2.2)
<Decimal 2.20000000000000017763568394002504646778106689453125>
tokenify is idempotent on "mo" parameter
>>> a = MOnumber(2)
>>> ta = tokenify(a)
>>> ta == tokenify(ta)
True
"""
if isinstance(mo, MO):
return _tokenify(mo, name, ancestor)
if isinstance(mo, Token):
if name == "":
_name = mo.name
else:
_name = name
if ancestor is None:
_ancestor = mo._ancestor
else:
_ancestor = ancestor
return _tokenify(mo._mo, _name, _ancestor)
return _tokenify(moify(mo), name, ancestor)
def to_be_token(func):
""" Decorator to ensure that the return value is a Token """
@wraps(func)
def wrapped(*args, **kwds):
ans = func(*args, **kwds)
try:
return [tokenify(t) for t in ans]
except TypeError:
return tokenify(ans)
return wrapped
def _tokenify(mo, name="", ancestor=None):
""" Transform a MO (from core) to the appropriate token (from API)
:example:
>>> a = MOnumber(2)
>>> _tokenify(a)
<Integer 2>
>>> a = MOnumber(2.5)
>>> _tokenify(a)
<Decimal 2.5>
>>> a = MOFraction(2, 5)
>>> _tokenify(a)
<Fraction 2 / 5>
>>> a = MOstr('x')
>>> _tokenify(a)
<Linear x>
>>> a = MOstrPower('x', 2)
>>> _tokenify(a)
<Quadratic x^2>
>>> a = MOstrPower('x', 3)
>>> _tokenify(a)
<Polynomial x^3>
>>> a = MOMonomial(3, 'x', 1)
>>> _tokenify(a)
<Linear 3x>
>>> a = MOMonomial(3, 'x', 2)
>>> _tokenify(a)
<Quadratic 3x^2>
>>> a = MOMonomial(3, 'x', 3)
>>> _tokenify(a)
<Polynomial 3x^3>
"""
if isinstance(mo, MOnumber):
if isinstance(mo.value, int):
from .number import Integer
return Integer.from_mo(mo, name, ancestor)
elif isinstance(mo.value, _Decimal):
from .number import Decimal
return Decimal.from_mo(mo, name, ancestor)
raise TypeError(f"Can't build from MOnumber ({mo}) neither int nor decimal")
if isinstance(mo, MOFraction):
if isinstance(mo._denominator, MOnumber) and isinstance(
mo._numerator, MOnumber
):
from .number import Fraction
return Fraction.from_mo(mo, name, ancestor)
raise TypeError(
f"Can't build from MOFraction ({mo}) numerator and denominator are not MOnumber"
)
if isinstance(mo, (MOstr, MOstrPower, MOMonomial, MOpolynomial)):
if not isinstance(mo._variable, (MOstr, str)):
raise TypeError(
f"Can't build Polynom over something else than a letter (got {mo._variable})"
)
if (
isinstance(mo, MOstr)
or (isinstance(mo, MOMonomial) and mo.power.value == 1)
or (isinstance(mo, MOpolynomial) and mo.power.value == 1)
):
from .polynomial import Linear
return Linear.from_mo(mo, name, ancestor)
elif (
(isinstance(mo, MOstrPower) and mo.power.value == 2)
or (isinstance(mo, MOMonomial) and mo.power.value == 2)
or (isinstance(mo, MOpolynomial) and mo.power.value == 2)
):
from .polynomial import Quadratic
return Quadratic.from_mo(mo, name, ancestor)
else:
from .polynomial import Polynomial
return Polynomial.from_mo(mo, name, ancestor)
raise TypeError(f"{type(mo)} is unknown MathObject")
def factory(exp, name="", ancestor=None): def factory(exp, name="", ancestor=None):
""" Transform a Expression with on MathObject (from core) to a appropriate token (from API) """ Transform a Expression with on single MathObject (from core) to a appropriate token (from API)
:example: :example:
>>> from ..expression import Expression >>> from ..expression import Expression
@ -61,46 +202,7 @@ def factory(exp, name="", ancestor=None):
if not isinstance(mo, MO): if not isinstance(mo, MO):
raise TypeError(f"Can't build Token from not computed Expression (got {mo})") raise TypeError(f"Can't build Token from not computed Expression (got {mo})")
if isinstance(mo, MOnumber): return _tokenify(mo, name, ancestor)
if isinstance(mo.value, int):
return Integer.from_mo(mo, name, ancestor)
elif isinstance(mo.value, _Decimal):
return Decimal.from_mo(mo, name, ancestor)
raise TypeError(f"Can't build from MOnumber ({mo}) neither int nor decimal")
elif isinstance(mo, MOFraction):
if isinstance(mo._denominator, MOnumber) and isinstance(
mo._numerator, MOnumber
):
return Fraction.from_mo(mo, name, ancestor)
raise TypeError(
f"Can't build from MOFraction ({mo}) numerator and denominator are not MOnumber"
)
elif isinstance(mo, (MOstr, MOstrPower, MOMonomial, MOpolynomial)):
if not isinstance(mo._variable, (MOstr, str)):
raise TypeError(
f"Can't build Polynom over something else than a letter (got {mo._variable})"
)
if (
isinstance(mo, MOstr)
or (isinstance(mo, MOMonomial) and mo.power.value == 1)
or (isinstance(mo, MOpolynomial) and mo.power.value == 1)
):
return Linear.from_mo(mo, name, ancestor)
elif (
(isinstance(mo, MOstrPower) and mo.power.value == 2)
or (isinstance(mo, MOMonomial) and mo.power.value == 2)
or (isinstance(mo, MOpolynomial) and mo.power.value == 2)
):
return Quadratic.from_mo(mo, name, ancestor)
else:
return Polynomial.from_mo(mo, name, ancestor)
else:
raise TypeError(f"{type(mo)} is unknown MathObject")
# ----------------------------- # -----------------------------

View File

@ -13,11 +13,10 @@ 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
__all__ = ["Integer", "Decimal"] __all__ = ["Integer", "Decimal", "Fraction"]
class Integer(Token): class Integer(Token):
@ -53,23 +52,6 @@ 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):
@ -107,40 +89,10 @@ 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 """ Token representing a fraction of numbers
:example: :example:
>>> Fraction("3/4") >>> Fraction("3/4")
@ -171,76 +123,92 @@ 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):
return self._mo.numerator """ Get numerator of the fraction
:example:
>>> a = Fraction("3/4")
>>> a.numerator
<Integer 3>
"""
return Integer(self._mo.numerator)
@property @property
def denominator(self): 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):
""" return decimal approximation of the fraction
:example:
>>> f = Fraction("3/4")
>>> f.decimal
<Decimal 0.75>
>>> f = Fraction("1/3")
>>> f.decimal
<Decimal 0.3333333333333333333333333333>
"""
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

@ -10,26 +10,33 @@
Tokens representing polynomials functions Tokens representing polynomials functions
""" """
from ..expression import Expression
from .token import Token from .token import 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.polynomial import MOpolynomial
__all__ = ["Polynomial", "Quadratic", "Linear"] __all__ = ["Polynomial", "Quadratic", "Linear"]
class Polynomial(Token): class Polynomial(Token):
""" Token representing a polynomial """ """ Token representing a polynomial
:examples:
>>> from ...core.MO.polynomial import MOpolynomial
>>> P = Polynomial(MOpolynomial('x', [1, 2, 3]))
>>> P
<Polynomial 3x^2 + 2x + 1>
"""
def __init__(self, a, name="", ancestor=None): def __init__(self, a, name="", ancestor=None):
""" Initiate Polynomial with a MO"""
if not isinstance(a, MO): if not isinstance(a, MO):
if isinstance(a, str):
raise TypeError raise TypeError
else:
raise TypeError
else:
mo = a
Token.__init__(self, mo, name, ancestor) Token.__init__(self, a, name, ancestor)
self._mathtype = "polynome" self._mathtype = "polynome"
@classmethod @classmethod
@ -38,48 +45,271 @@ class Polynomial(Token):
return cls(mo, name, ancestor) return cls(mo, name, ancestor)
@classmethod @classmethod
def random(cls): def from_coefficients(cls, coefficients, variable_name="x", name=""):
raise NotImplemented """ 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)
@property
def raw(self):
raise NotImplementedError("Polynomial does not exists in python")
def __setitem__(self, key, item): def __setitem__(self, key, item):
""" Use Polynomial like if they were a dictionnary to set coefficients """ """ Use Polynomial like if they were a dictionnary to set coefficients """
pass raise NotImplementedError("Can't set coefficient of a polynomial")
@to_be_token
def __getitem__(self, key): def __getitem__(self, key):
""" Use Polynomial like if they were a dictionnary to get coefficients """ """ Use Polynomial like if they were a dictionnary to get coefficients
pass
:examples:
>>> from ...core.MO.polynomial import MOpolynomial
>>> P = Polynomial(MOpolynomial('x', [1, 2, 3]))
>>> P[0]
<Integer 1>
>>> P[1]
<Integer 2>
>>> P[2]
<Integer 3>
>>> P[3]
Traceback (most recent call last):
...
KeyError: 3
"""
return self._mo.coefficients[key]
def __call__(self, value): def __call__(self, value):
""" Call a Polynomial to evaluate itself on value """ """ Call a Polynomial to evaluate itself on value
pass
:examples:
>>> from ...core.MO.polynomial import MOpolynomial
>>> P = Polynomial(MOpolynomial('x', [1, 2, 3]))
>>> for s in P(2).explain():
... print(s)
3 * 2^2 + 2 * 2 + 1
3 * 4 + 4 + 1
12 + 5
17
"""
return Expression(self._mo.tree)(value)
def differentiate(self):
""" Differentiate a polynome
:example:
>>> from ...core.MO.polynomial import MOpolynomial
>>> P = Polynomial(MOpolynomial('x', [1, 2, 3]))
>>> P
<Polynomial 3x^2 + 2x + 1>
>>> P.differentiate()
<Linear 6x + 2>
>>> for s in P.differentiate().explain():
... print(s)
0 + 2 + 3 * 2x
2 + 3 * 2 * x
6x + 2
"""
return Expression(self._mo.differentiate()).simplify()
@property
def roots(self):
""" Get roots of the Polynomial """
raise NotImplementedError("Can't compute roots not specific polynomial")
class Linear(Polynomial): class Linear(Polynomial):
""" Token representing a linear """ """ Token representing a linear ax + b
:examples:
>>> from ...core.MO.polynomial import MOpolynomial, MOMonomial
>>> P = Linear(MOpolynomial('x', [1, 2]))
>>> P
<Linear 2x + 1>
>>> P.a
<Integer 2>
>>> P.b
<Integer 1>
>>> P.differentiate()
<Integer 2>
>>> P.roots
[<Integer - 2>]
>>> for i in P.roots[0].explain():
... print(i)
- 2 / 1
- 2
"""
def __init__(self, mo, name="", ancestor=None): def __init__(self, mo, name="", ancestor=None):
""" Initiate Linear with MO
:examples:
>>> from ...core.MO.polynomial import MOpolynomial, MOMonomial
>>> P = Linear(MOpolynomial('x', [1, 2]))
>>> P
<Linear 2x + 1>
>>> Q = Linear(MOMonomial(3, 'x', 1))
>>> Q
<Linear 3x>
"""
Polynomial.__init__(self, mo, name, ancestor) Polynomial.__init__(self, mo, name, ancestor)
self._mathtype = "affine" self._mathtype = "affine"
@classmethod @property
def random(cls): @to_be_token
raise NotImplemented def a(self):
return self[1]
@property
@to_be_token
def b(self):
return self[0]
@property
@to_be_token
def roots(self):
""" Get the root of the polynomial
:examples:
>>> from ...core.MO.polynomial import MOpolynomial, MOMonomial
>>> 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:
return [Expression.from_str(f"-{self.a}/{self.b}").simplify()]
except AttributeError:
return [Expression.from_str(f"-{self.a}/{self.b}")]
class Quadratic(Polynomial): class Quadratic(Polynomial):
""" Token representing a quadratic """ """ Token representing a quadratic ax^2 + bx + c
:examples:
>>> from ...core.MO.polynomial import MOpolynomial
>>> P = Quadratic(MOpolynomial('x', [1, 2, 3]))
>>> P
<Quadratic 3x^2 + 2x + 1>
>>> P.a
<Integer 3>
>>> P.b
<Integer 2>
>>> P.c
<Integer 1>
>>> P.delta
<Integer - 8>
>>> for s in P.delta.explain():
... print(s)
2^2 - 4 * 3 * 1
4 - 12 * 1
4 - 12
- 8
>>> P.differentiate()
<Linear 6x + 2>
>>> P.roots
[]
"""
def __init__(self, mo, name="", ancestor=None): def __init__(self, mo, name="", ancestor=None):
""" Initiate Quadratic from MO
>>> from ...core.MO.polynomial import MOpolynomial
>>> P = Quadratic(MOpolynomial('x', [1, 2, 3]))
>>> P
<Quadratic 3x^2 + 2x + 1>
"""
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 @property
def random(cls): @to_be_token
raise NotImplemented def a(self):
try:
return self[2]
except KeyError:
return 0
@property
@to_be_token
def b(self):
try:
return self[1]
except KeyError:
return 0
@property
@to_be_token
def c(self):
try:
return self[0]
except KeyError:
return 0
@property
@to_be_token
def delta(self):
return Expression.from_str(f"{self.b}^2-4*{self.a}*{self.c}").simplify()
@property
@to_be_token
def roots(self):
""" Roots of the polynom
:example:
>>> from ...core.MO.polynomial import MOpolynomial
>>> P = Quadratic(MOpolynomial('x', [1, 0, 1]))
>>> P.roots
[]
>>> P = Quadratic(MOpolynomial('x', [4, -4, 1]))
>>> P.roots
[<Integer 2>]
>>> P = Quadratic(MOpolynomial('x', [1, 0, -1]))
>>> P.roots
[<Integer - 1>, <Integer 1>]
"""
if self.delta._mo < 0:
return []
elif self.delta._mo == 0:
# return [Expression.from_str(f"-{self.b}/(2*{self.a})").simplify()]
return [round(eval(f"-{self.b}/(2*{self.a})"), 2)]
else:
from math import sqrt
roots = [
str(eval(f"(-{self.b}-sqrt({self.delta}))/(2*{self.a})")),
str(eval(f"(-{self.b}+sqrt({self.delta}))/(2*{self.a})")),
]
roots.sort()
return roots
# ----------------------------- # -----------------------------

View File

@ -10,33 +10,20 @@
Tokens: practical envelop of math object Tokens: practical envelop of math object
""" """
from ..renders import renders from ..renders import render
from ...core.MO.atoms import moify
class Token(object): 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):
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
@ -59,39 +46,39 @@ class Token(object):
yield self yield self
def __repr__(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): def __str__(self):
if self.RENDER == "tex": try:
return self.__tex__ return render(self._mo._tree)
elif self.RENDER == "txt": except AttributeError:
return self.__txt__ return render(self._mo)
else:
raise ValueError(f"Unknow render {self.RENDER}")
#return renders[self.RENDER](self._mo)
@property @property
def __txt__(self): def raw(self):
return self._mo.__txt__ """ Get python's raw forme of the token """
return self._mo.content
@property
def __tex__(self):
return self._mo.__tex__
def _operate(self, other, operation): def _operate(self, other, operation):
""" Make a operation between 2 Tokens, """ Make a operation between 2 Tokens """
a Token and en Expression ora
a Token an a builtin type
"""
from ..expression import Expression from ..expression import Expression
from ...core import Tree from ...core import Tree
from . import factory from . import factory
if not isinstance(other, Token): if not isinstance(other, Token):
try:
_other = factory(other) _other = factory(other)
except AttributeError:
_other = factory(Expression(moify(other)))
else: else:
_other = other _other = other
if operation == '-':
tree = Tree("+", self._mo, Tree("-", None, _other._mo))
else:
tree = Tree(operation, self._mo, _other._mo) tree = Tree(operation, self._mo, _other._mo)
return Expression(tree).simplify() return Expression(tree).simplify()
@ -109,6 +96,18 @@ class Token(object):
... print(i) ... print(i)
3 + 7 3 + 7
10 10
>>> a = Integer(3)
>>> c = a + 7
>>> c
<Integer 10>
>>> for i in c.explain():
... print(i)
3 + 7
10
>>> a = Integer(3)
>>> c = a + "x"
>>> c
<Linear x + 3>
>>> from .number import Fraction >>> from .number import Fraction
>>> a = Fraction("4/3") >>> a = Fraction("4/3")
>>> b = Integer(7) >>> b = Integer(7)
@ -126,6 +125,32 @@ class Token(object):
""" """
return self._operate(other, "+") return self._operate(other, "+")
def __sub__(self, other):
""" Subing 2 Tokens or a Token and a Expression
:example:
>>> from .number import Integer
>>> a = Integer(3)
>>> b = Integer(7)
>>> c = a - b
>>> c
<Integer - 4>
>>> for i in c.explain():
... print(i)
3 - 7
3 - 7
- 4
>>> a = Integer(3)
>>> c = a - 7
>>> c
<Integer - 4>
>>> a = Integer(3)
>>> c = a - "x"
>>> c
<Linear - x + 3>
"""
return self._operate(other, "-")
def __mul__(self, other): def __mul__(self, other):
""" Multiply 2 Tokens or a Token and a Expression """ Multiply 2 Tokens or a Token and a Expression
@ -140,6 +165,12 @@ class Token(object):
... print(i) ... print(i)
3 * 7 3 * 7
21 21
>>> c = a * 7
>>> c
<Integer 21>
>>> c = a * "x"
>>> c
<Linear 3x>
>>> from .number import Fraction >>> from .number import Fraction
>>> a = Fraction("4/3") >>> a = Fraction("4/3")
>>> b = Integer(7) >>> b = Integer(7)
@ -167,6 +198,9 @@ class Token(object):
>>> for i in c.explain(): >>> for i in c.explain():
... print(i) ... print(i)
3 / 7 3 / 7
>>> c = a / 7
>>> c
<Fraction 3 / 7>
>>> from .number import Fraction >>> from .number import Fraction
>>> a = Fraction("4/3") >>> a = Fraction("4/3")
>>> b = Integer(7) >>> b = Integer(7)
@ -182,6 +216,128 @@ class Token(object):
""" """
return self._operate(other, "/") return self._operate(other, "/")
def __pow__(self, other):
""" Token powered by an other
:example:
>>> from .number import Integer
>>> a = Integer(3)
>>> b = Integer(7)
>>> c = a ** b
>>> c
<Integer 2187>
>>> c = a ** 7
>>> c
<Integer 2187>
>>> from .number import Decimal
>>> a = Decimal('2.3')
>>> 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
from ...core import Tree
from . import factory
if not isinstance(other, Token):
try:
_other = factory(other)
except AttributeError:
_other = factory(Expression(moify(other)))
else:
_other = other
if operation == '-':
tree = Tree("+", _other._mo, Tree("-", None, self._mo))
else:
tree = Tree(operation, _other._mo, self._mo)
return Expression(tree).simplify()
def __radd__(self, other):
""" Adding 2 Tokens or a Token and a Expression
:example:
>>> from .number import Integer
>>> a = Integer(3)
>>> c = 7 + a
>>> c
<Integer 10>
>>> c = "x" + a
>>> c
<Linear x + 3>
"""
return self._roperate(other, "+")
def __rsub__(self, other):
""" Subing 2 Tokens or a Token and a Expression
:example:
>>> from .number import Integer
>>> a = Integer(3)
>>> c = 7 - a
>>> c
<Integer 4>
>>> a = Integer(3)
>>> c = "x" - a
>>> c
<Linear x - 3>
"""
return self._roperate(other, "-")
def __rmul__(self, other):
""" Multiply 2 Tokens or a Token and a Expression
:example:
>>> from .number import Integer
>>> a = Integer(3)
>>> c = 7 * a
>>> c
<Integer 21>
>>> c = "x" * a
>>> c
<Linear 3x>
"""
return self._roperate(other, "*")
def __rtruediv__(self, other):
""" Divising 2 Tokens or a Token and a Expression
:example:
>>> from .number import Integer
>>> a = Integer(3)
>>> c = 7 / a
>>> c
<Fraction 7 / 3>
"""
return self._roperate(other, "/")
def _get_soul(self, other=None):
""" Get the builtin soul of self or other """
if isinstance(other, Token):
return other._mo._value
elif not other is None:
return other
return self._mo._value
def __eq__(self, other):
return self._get_soul() == self._get_soul(other)
def __gt__(self, other):
return self._get_soul() > self._get_soul(other)
def __lt__(self, other):
return self._get_soul() < self._get_soul(other)
def __ge__(self, other):
return self._get_soul() >= self._get_soul(other)
def __le__(self, other):
return self._get_soul() <= self._get_soul(other)
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'

View File

@ -12,8 +12,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. 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 >>> render.set_render("txt")
>>> 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
@ -27,12 +26,23 @@ 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 Expression from .API import render, Expression
#from decimal import getcontext
from . import random
__all__ = ["Expression"] #getcontext().prec = 2
__all__ = ["render", "Expression", "random"]
# ----------------------------- # -----------------------------

View File

@ -26,6 +26,7 @@ def moify(token):
except MOError: except MOError:
return token return token
@coroutine @coroutine
def moify_cor(target): def moify_cor(target):
""" Coroutine which try to convert a parsed token into an MO """ Coroutine which try to convert a parsed token into an MO
@ -36,7 +37,7 @@ def moify_cor(target):
>>> for i in [-2, "+", "x", "*", Decimal("3.3")]: >>> for i in [-2, "+", "x", "*", Decimal("3.3")]:
... list2molist.send(i) ... list2molist.send(i)
>>> list2molist.throw(STOOOP) >>> list2molist.throw(STOOOP)
[<MOnumber - 2>, '+', <MOstr x>, '*', <MOnumber 3.3>] [<MOnumber -2>, '+', <MOstr x>, '*', <MOnumber 3.3>]
""" """
try: try:
@ -77,7 +78,7 @@ class MOnumber(Atom):
>>> MOnumber(23) >>> MOnumber(23)
<MOnumber 23> <MOnumber 23>
>>> MOnumber(-23) >>> MOnumber(-23)
<MOnumber - 23> <MOnumber -23>
As expected there will be trouble with float As expected there will be trouble with float
@ -89,7 +90,13 @@ class MOnumber(Atom):
>>> MOnumber(Decimal("23.3")) >>> MOnumber(Decimal("23.3"))
<MOnumber 23.3> <MOnumber 23.3>
>>> MOnumber(Decimal("-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 initialisation is idempotent MOnumber initialisation is idempotent
@ -100,7 +107,7 @@ class MOnumber(Atom):
>>> MOnumber("a") >>> MOnumber("a")
Traceback (most recent call last): Traceback (most recent call last):
... ...
mapytex.calculus.core.MO.exceptions.MOError: ('The value of an MOnumber need to be a int, a float or a Decimal', "(got <class 'str'>)") mapytex.calculus.core.MO.exceptions.MOError: ('The value of an MOnumber need to be a int, a float, a Decimal or a decimal string', "(got <class 'str'>)")
Atoms specific property and methods Atoms specific property and methods
@ -117,15 +124,26 @@ class MOnumber(Atom):
""" """
if isinstance(value, Atom) and isinstance(value.value, (int, Decimal, float)): if isinstance(value, Atom) and isinstance(value.value, (int, Decimal, float)):
Atom.__init__(self, value.value) Atom.__init__(self, value.value)
elif isinstance(value, (int, Decimal)): elif isinstance(value, (float, Decimal)):
Atom.__init__(self, value) if int(value) == value:
elif isinstance(value, float): Atom.__init__(self, int(value))
Atom.__init__(self, Decimal(value))
else: else:
Atom.__init__(self, Decimal(value))
elif isinstance(value, int):
Atom.__init__(self, value)
else:
try:
v = float(value)
except (ValueError, TypeError):
raise MOError( raise MOError(
"The value of an MOnumber need to be a int, a float or a Decimal", "The value of an MOnumber need to be a int, a float, a Decimal or a decimal string",
f"(got {type(value)})", f"(got {type(value)})",
) )
else:
if int(v) == v:
Atom.__init__(self, int(v))
else:
Atom.__init__(self, Decimal(value))
self._signature = "scalar" self._signature = "scalar"
@ -154,7 +172,7 @@ class MOnumber(Atom):
>>> MOnumber(-3).__tex__ >>> MOnumber(-3).__tex__
'- 3' '- 3'
""" """
if self.value > 0: if self.value >= 0:
return str(self.value) return str(self.value)
return f"- {abs(self.value)}" return f"- {abs(self.value)}"
@ -166,6 +184,16 @@ class MOnumber(Atom):
except AttributeError: except AttributeError:
return self.value < other return self.value < other
def differentiate(self):
""" differentiate a number and get 0
:example:
>>> a = MOnumber(3)
>>> a.differentiate()
<MOnumber 0>
"""
return MOnumber(0)
class MOstr(Atom): class MOstr(Atom):
@ -231,7 +259,8 @@ 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(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) Atom.__init__(self, val)
@ -260,6 +289,16 @@ class MOstr(Atom):
def degree(self): def degree(self):
return 1 return 1
def differentiate(self):
""" differentiate a variable and get 1
:example:
>>> a = MOstr("x")
>>> a.differentiate()
<MOnumber 1>
"""
return MOnumber(1)
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'

View File

@ -8,6 +8,9 @@
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 decimal import Decimal
from ..arithmetic import gcd
__all__ = ["MOFraction"] __all__ = ["MOFraction"]
@ -31,10 +34,10 @@ class MOFraction(Molecule):
>>> f = MOFraction(2, 3) >>> f = MOFraction(2, 3)
>>> f >>> f
<MOFraction 2 / 3> <MOFraction 2 / 3>
>>> print(f.__txt__) >>> print(f.tree)
2 / 3 /
>>> print(f.__tex__) > 2
\\frac{2}{3} > 3
>>> print(f) >>> print(f)
2 / 3 2 / 3
>>> f = MOFraction(2, 3, negative = True) >>> f = MOFraction(2, 3, negative = True)
@ -43,11 +46,13 @@ 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
@ -67,10 +72,61 @@ 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)
def differentiate(self):
""" differentiate a fraction and get something!
:example:
>>> a = MOFraction(2, 3)
>>> a.differentiate()
<MOnumber 0>
"""
d_num = self.numerator.differentiate()
d_denom = self.denominator.differentiate()
if d_num == 0 and d_denom == 0:
return MOnumber(0)
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' # Reglages pour 'vim'

View File

@ -57,20 +57,12 @@ class MO(ABC):
pass pass
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.__txt__}>" return f"<{self.__class__.__name__} {self.__str__()}>"
@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__()
@ -97,6 +89,9 @@ class MO(ABC):
""" """
return self._signature return self._signature
def differentiate(self):
raise NotImplementedError
class Atom(MO): class Atom(MO):
@ -158,21 +153,21 @@ class Molecule(MO):
It is a wrapping of tree 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 MAINOP = None
def __init__(self, value): def __init__(self, tree):
""" Initiate the MO """ Initiate the MO
It should be idempotent. It should be idempotent.
""" """
try: try:
self._tree = value._tree self._tree = tree._tree
except AttributeError: except AttributeError:
self._tree = value self._tree = tree
self.is_scalar = True self.is_scalar = True
self._signature = None self._signature = None
@ -183,18 +178,10 @@ class Molecule(MO):
@property @property
def content(self): def content(self):
return self._tree return self.tree
def __str__(self): def __str__(self):
return str(self.__txt__) return tree2txt(self.tree)
@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.__txt__) >>> print(s.tree)
x^2 ^
>>> print(s.__tex__) > x
x^{2} > 2
>>> MOstrPower(3, 1) >>> MOstrPower(3, 1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -59,7 +59,8 @@ class MOstrPower(Molecule):
""" """
_variable = MO.factory(variable) _variable = MO.factory(variable)
if not isinstance(_variable, MOstr): 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 self._variable = _variable
_power = MO.factory(power) _power = MO.factory(power)
@ -111,6 +112,23 @@ class MOstrPower(Molecule):
""" """
return f"monome{self.power}" return f"monome{self.power}"
def differentiate(self):
""" differentiate a MOstrPower and get a tree
:example:
>>> a = MOstrPower('x', 3)
>>> print(a.differentiate())
*
> 3
> x^2
"""
if self._power > 2:
return Tree(
"*", self.power, MOstrPower(self.variable,
self._power._value - 1)
)
return Tree("*", self.power, MOstr(self.variable))
class MOMonomial(Molecule): class MOMonomial(Molecule):
@ -131,22 +149,20 @@ class MOMonomial(Molecule):
<MOMonomial 4x> <MOMonomial 4x>
>>> print(m) >>> print(m)
4x 4x
>>> print(m.__txt__) >>> print(m.tree)
4x *
>>> print(m.__tex__) > 4
4x > x
>>> x = MOstrPower('x', 2) >>> x = MOstrPower('x', 2)
>>> MOMonomial(4, x) >>> MOMonomial(4, x)
<MOMonomial 4x^2> <MOMonomial 4x^2>
>>> m = MOMonomial(4, 'x') >>> m = MOMonomial(-1, 'x')
>>> m >>> m
<MOMonomial 4x> <MOMonomial - x>
>>> print(m) >>> print(m.tree)
4x -
>>> print(m.__txt__) > None
4x > x
>>> print(m.__tex__)
4x
>>> MOMonomial(4, 'x', 1) >>> MOMonomial(4, 'x', 1)
<MOMonomial 4x> <MOMonomial 4x>
>>> MOMonomial(4, 'x', 2) >>> MOMonomial(4, 'x', 2)
@ -154,6 +170,13 @@ 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):
... ...
@ -183,34 +206,21 @@ class MOMonomial(Molecule):
self._power = _power self._power = _power
try: try:
if self._coefficient.value != 1: if self.coefficient.value == 1:
_tree = Tree("*", self._coefficient, self.strpower)
else:
_tree = self.strpower _tree = self.strpower
else:
_tree = Tree("*", self.coefficient, 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)
def __str__(self):
if self._coefficient != -1:
return super(MOMonomial, self).__str__()
else:
return "- " + self.strpower.__str__()
@property @property
def __txt__(self): def tree(self):
if self._coefficient != -1: if self._coefficient == -1:
return super(MOMonomial, self).__txt__ return Tree("-", None, self.strpower)
else:
return "- " + self.strpower.__txt__
@property return Tree("*", self.coefficient, self.strpower)
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):
@ -258,6 +268,31 @@ class MOMonomial(Molecule):
""" """
return f"monome{self.power}" return f"monome{self.power}"
def differentiate(self):
""" Differentiate a MOMonomial and get a tree
:example:
>>> x = MOstr('x')
>>> m = MOMonomial(4, x)
>>> m
<MOMonomial 4x>
>>> print(m.differentiate())
4
>>> m = MOMonomial(4, 'x', 2)
>>> m
<MOMonomial 4x^2>
>>> print(m.differentiate())
*
> 4
> *
| > 2
| > x
"""
if self.power == 1:
return self.coefficient
return Tree("*", self.coefficient, self.strpower.differentiate())
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'

View File

@ -6,6 +6,7 @@
# #
# Distributed under terms of the MIT license. # Distributed under terms of the MIT license.
from collections import OrderedDict
from mapytex.calculus.core.tree import Tree from mapytex.calculus.core.tree import Tree
from . import MO, MOstr from . import MO, MOstr
from .mo import Molecule from .mo import Molecule
@ -57,8 +58,9 @@ class MOpolynomial(Molecule):
raise TypeError("Coefs needs to be a dictionnary or a list") raise TypeError("Coefs needs to be a dictionnary or a list")
self._coefs = _coefs self._coefs = _coefs
monomials = {} monomials = OrderedDict()
for deg, coef in self._coefs.items(): for deg in sorted(self._coefs.keys()):
coef = self._coefs[deg]
if deg == 0: if deg == 0:
monomials[deg] = coef monomials[deg] = coef
elif deg == 1 and coef == 1: elif deg == 1 and coef == 1:
@ -120,12 +122,34 @@ class MOpolynomial(Molecule):
:example: :example:
>>> p = MOpolynomial('x', [1, 2, 3]) >>> p = MOpolynomial('x', [1, 2, 3])
>>> p.monomials >>> p.monomials
{<MOnumber 0>: <MOnumber 1>, <MOnumber 1>: <MOMonomial 2x>, <MOnumber 2>: <MOMonomial 3x^2>} OrderedDict([(<MOnumber 0>, <MOnumber 1>), (<MOnumber 1>, <MOMonomial 2x>), (<MOnumber 2>, <MOMonomial 3x^2>)])
>>> p.monomials.values() >>> p.monomials.values()
dict_values([<MOnumber 1>, <MOMonomial 2x>, <MOMonomial 3x^2>]) odict_values([<MOnumber 1>, <MOMonomial 2x>, <MOMonomial 3x^2>])
""" """
return self._monomials return self._monomials
def differentiate(self):
""" Differentiate a MOMonomial and get a tree
:example:
>>> p = MOpolynomial('x', [1, 2, 3])
>>> print(p)
3x^2 + 2x + 1
>>> print(p.differentiate())
+
> 0
> +
| > 2
| > *
| | > 3
| | > *
| | | > 2
| | | > x
"""
monomials_d = [m.differentiate() for m in self.monomials.values()]
return Tree.from_list("+", monomials_d)
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'

View File

@ -40,7 +40,7 @@ def compute(node, left_v, right_v):
>>> compute("+", MOnumber(1), MOnumber(2)) >>> compute("+", MOnumber(1), MOnumber(2))
<MOnumber 3> <MOnumber 3>
>>> compute("-", None, MOnumber(2)) >>> compute("-", None, MOnumber(2))
<MOnumber - 2> <MOnumber -2>
>>> compute("*", MOnumber(1), MOnumber(2)) >>> compute("*", MOnumber(1), MOnumber(2))
<MOnumber 2> <MOnumber 2>
>>> compute("~", MOnumber(1), MOnumber(2)) >>> compute("~", MOnumber(1), MOnumber(2))
@ -70,7 +70,8 @@ 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__] + [(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 return lines

View File

@ -70,6 +70,9 @@ def monumber_monumber(left, right):
>>> b = MOnumber(6) >>> b = MOnumber(6)
>>> add(a, b) >>> add(a, b)
<MOnumber 10> <MOnumber 10>
>>> b = MOnumber('2.3')
>>> add(a, b)
<MOnumber 6.3>
""" """
return MO.factory(left.value + right.value) return MO.factory(left.value + right.value)

View File

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

View File

@ -1,246 +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 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

@ -10,7 +10,7 @@
Tree renders Tree renders
""" """
__all__ = ["tree2txt"] __all__ = ["tree2txt", "tree2tex"]
from .tree2txt import tree2txt from .tree2txt import tree2txt
from .tree2tex import tree2tex from .tree2tex import tree2tex

View File

@ -91,9 +91,11 @@ 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, "*") right_ = render_with_parenthesis(right, "*", is_at_right=True)
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_[
@ -117,17 +119,17 @@ def div2tex(left, right):
>>> from ..MO import MO >>> from ..MO import MO
>>> div2tex(MO.factory(2), MO.factory(3)) >>> div2tex(MO.factory(2), MO.factory(3))
'\\frac{2}{3}' '\\dfrac{2}{3}'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1/2") >>> t = Tree.from_str("1/2")
>>> div2tex(t, MO.factory(3)) >>> div2tex(t, MO.factory(3))
'\\frac{\\frac{1}{2}}{3}' '\\dfrac{\\dfrac{1}{2}}{3}'
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> div2tex(t, MO.factory(3)) >>> div2tex(t, MO.factory(3))
'\\frac{1 + 2}{3}' '\\dfrac{1 + 2}{3}'
>>> t = Tree.from_str("1*2") >>> t = Tree.from_str("1*2")
>>> div2tex(MO.factory(3), t) >>> div2tex(MO.factory(3), t)
'\\frac{3}{1 \\times 2}' '\\dfrac{3}{1 \\times 2}'
""" """
try: try:
left_ = tree2tex(left) left_ = tree2tex(left)
@ -138,7 +140,7 @@ def div2tex(left, right):
except (AttributeError, ValueError): except (AttributeError, ValueError):
right_ = right.__tex__ right_ = right.__tex__
return "\\frac{" + left_ + "}{" + right_ + "}" return "\\dfrac{" + left_ + "}{" + right_ + "}"
def pow2tex(left, right): def pow2tex(left, right):
@ -183,20 +185,17 @@ def pow2tex(left, right):
return f"{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 subtree_need_parenthesis = False
try: try:
subtree.node subtree.node
except AttributeError: except AttributeError:
try: try:
if (
OPERATORS[subtree.MAINOP]["precedence"]
< OPERATORS[operator]["precedence"]
):
subtree_need_parenthesis = True
except (AttributeError, KeyError):
pass
subtree_ = subtree.__tex__ 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: 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
@ -207,7 +206,8 @@ def render_with_parenthesis(subtree, operator):
return subtree_ return subtree_
OPERATOR2TEX = {"+": plus2tex, "-": minus2tex, "*": mul2tex, "/": div2tex, "^": pow2tex} OPERATOR2TEX = {"+": plus2tex, "-": minus2tex,
"*": mul2tex, "/": div2tex, "^": pow2tex}
def tree2tex(tree): def tree2tex(tree):
@ -227,8 +227,17 @@ def tree2tex(tree):
from ..tree import Tree from ..tree import Tree
if not isinstance(tree, Tree): if not isinstance(tree, Tree):
raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})") raise ValueError(
return OPERATOR2TEX[tree.node](tree.left_value, tree.right_value) 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' '- 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, "*") right_ = render_with_parenthesis(right, "*", is_at_right=True)
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
@ -187,20 +189,22 @@ def pow2txt(left, right):
return f"{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 subtree_need_parenthesis = False
try: try:
subtree.node subtree.node
except AttributeError: except AttributeError:
try: try:
if (
OPERATORS[subtree.MAINOP]["precedence"]
< OPERATORS[operator]["precedence"]
):
subtree_need_parenthesis = True
except (AttributeError, KeyError):
pass
subtree_ = subtree.__txt__ 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: 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
@ -211,7 +215,8 @@ def render_with_parenthesis(subtree, operator):
return subtree_ return subtree_
OPERATOR2TXT = {"+": plus2txt, "-": minus2txt, "*": mul2txt, "/": div2txt, "^": pow2txt} OPERATOR2TXT = {"+": plus2txt, "-": minus2txt,
"*": mul2txt, "/": div2txt, "^": pow2txt}
def tree2txt(tree): def tree2txt(tree):
@ -231,8 +236,17 @@ def tree2txt(tree):
from ..tree import Tree from ..tree import Tree
if not isinstance(tree, Tree): if not isinstance(tree, Tree):
raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})") raise ValueError(
return OPERATOR2TXT[tree.node](tree.left_value, tree.right_value) 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

@ -15,7 +15,6 @@ 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
__all__ = ["str2"] __all__ = ["str2"]
@ -395,9 +394,7 @@ def missing_times(target):
elif not is_operator(tok) and tok != ")": elif not is_operator(tok) and tok != ")":
target_.send("*") target_.send("*")
if isinstance(tok, int) or ( if not ( is_operator(tok) or tok =="(" ):
isinstance(tok, str) and not is_operator(tok) and not tok == "("
):
previous = tok previous = tok
target_.send(tok) target_.send(tok)
@ -482,7 +479,8 @@ def lookforNumbers(target):
if current.replace("-", "", 1).isdigit(): if current.replace("-", "", 1).isdigit():
current += tok current += tok
else: else:
raise ParsingError(f"Can't build decimal with '{current}'") raise ParsingError(
f"Can't build decimal with '{current}'")
elif tok == "-": elif tok == "-":
if current == "": if current == "":
current = tok current = tok
@ -800,7 +798,8 @@ 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(operator_corout(missing_times(pparser(sink)))) str2_corout = lookforNumbers(
operator_corout(missing_times(pparser(sink))))
for i in expression.replace(" ", ""): for i in expression.replace(" ", ""):
str2_corout.send(i) str2_corout.send(i)
@ -810,60 +809,7 @@ 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>]
"""
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
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>]
"""
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, rdstr2 from .str2 import str2
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, random=False): def from_str(cls, expression, convert_to_mo=True):
""" Initiate a tree from an string expression """ Initiate a tree from an string expression
:example: :example:
@ -77,17 +77,8 @@ class Tree:
> * > *
| > 3 | > 3
| > n | > n
>>> t = Tree.from_str("2+{n}*x", random=True)
>>> print(t)
+
> 2
> *
| > {n}
| > x
""" """
t = MutableTree.from_str(expression, convert_to_mo, random) t = MutableTree.from_str(expression, convert_to_mo)
return cls.from_any_tree(t) return cls.from_any_tree(t)
@classmethod @classmethod
@ -898,7 +889,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, random=False): def from_str(cls, expression, convert_to_mo=True):
""" Initiate the MutableTree """ Initiate the MutableTree
:example: :example:
@ -954,12 +945,7 @@ class MutableTree(Tree):
| | > 8 | | > 8
| | > 3 | | > 3
| > x | > x
""" """
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

@ -95,10 +95,10 @@ def moscalar_momonomial(left, right):
>>> a = MOnumber(2) >>> a = MOnumber(2)
>>> b = MOMonomial(3, 'x', 4) >>> b = MOMonomial(3, 'x', 4)
>>> add(a, b) >>> add(a, b)
<MOpolynomial 2 + 3x^4> <MOpolynomial 3x^4 + 2>
>>> a = MOFraction(1, 5) >>> a = MOFraction(1, 5)
>>> add(a, b) >>> add(a, b)
<MOpolynomial 1 / 5 + 3x^4> <MOpolynomial 3x^4 + 1 / 5>
""" """
return MOpolynomial(right.variable, {right.power: right.coefficient, 0: left}) return MOpolynomial(right.variable, {right.power: right.coefficient, 0: left})
@ -126,10 +126,10 @@ def moscalar_mopolynomial(left, right):
>>> a = MOnumber(2) >>> a = MOnumber(2)
>>> b = MOpolynomial('x', [0, 2, 3]) >>> b = MOpolynomial('x', [0, 2, 3])
>>> add(a, b) >>> add(a, b)
<MOpolynomial 2 + 3x^2 + 2x> <MOpolynomial 3x^2 + 2x + 2>
>>> a = MOFraction(1, 5) >>> a = MOFraction(1, 5)
>>> add(a, b) >>> add(a, b)
<MOpolynomial 1 / 5 + 3x^2 + 2x> <MOpolynomial 3x^2 + 2x + 1 / 5>
""" """
if 0 in right.coefficients.keys(): if 0 in right.coefficients.keys():
raise NotImplementedError( raise NotImplementedError(
@ -237,7 +237,7 @@ def mostr_mopolynomial(left, right):
>>> a = MOstr("x") >>> a = MOstr("x")
>>> b = MOpolynomial('x', [1, 0, 3]) >>> b = MOpolynomial('x', [1, 0, 3])
>>> add(a, b) >>> add(a, b)
<MOpolynomial x + 3x^2 + 1> <MOpolynomial 3x^2 + x + 1>
""" """
if 1 in right.coefficients.keys(): if 1 in right.coefficients.keys():
raise NotImplementedError("Polynomial with no constant, calculus to do") raise NotImplementedError("Polynomial with no constant, calculus to do")
@ -255,7 +255,7 @@ def mopolynomial_mostr(left, right):
>>> a = MOpolynomial('x', [1, 0, 3]) >>> a = MOpolynomial('x', [1, 0, 3])
>>> b = MOstr("x") >>> b = MOstr("x")
>>> add(a, b) >>> add(a, b)
<MOpolynomial 3x^2 + 1 + x> <MOpolynomial 3x^2 + x + 1>
""" """
if 1 in left.coefficients.keys(): if 1 in left.coefficients.keys():
raise NotImplementedError("Polynomial with no constant, calculus to do") raise NotImplementedError("Polynomial with no constant, calculus to do")
@ -272,7 +272,7 @@ def mostrpower_mopolynomial(left, right):
>>> a = MOstrPower("x", 2) >>> a = MOstrPower("x", 2)
>>> b = MOpolynomial('x', [1, 2, 0, 4]) >>> b = MOpolynomial('x', [1, 2, 0, 4])
>>> add(a, b) >>> add(a, b)
<MOpolynomial x^2 + 4x^3 + 2x + 1> <MOpolynomial 4x^3 + x^2 + 2x + 1>
""" """
if left.power in right.coefficients.keys(): if left.power in right.coefficients.keys():
raise NotImplementedError("Degree in common, need to compute") raise NotImplementedError("Degree in common, need to compute")
@ -290,7 +290,7 @@ def mopolynomial_mostrpower(left, right):
>>> a = MOpolynomial('x', [1, 2, 0, 4]) >>> a = MOpolynomial('x', [1, 2, 0, 4])
>>> b = MOstrPower("x", 2) >>> b = MOstrPower("x", 2)
>>> add(a, b) >>> add(a, b)
<MOpolynomial 4x^3 + 2x + 1 + x^2> <MOpolynomial 4x^3 + x^2 + 2x + 1>
""" """
if right.power in left.coefficients.keys(): if right.power in left.coefficients.keys():
raise NotImplementedError("Degree in common, need to compute") raise NotImplementedError("Degree in common, need to compute")
@ -307,7 +307,7 @@ def momonomial_mopolynomial(left, right):
>>> a = MOMonomial(3, "x", 2) >>> a = MOMonomial(3, "x", 2)
>>> b = MOpolynomial('x', [1, 2, 0, 4]) >>> b = MOpolynomial('x', [1, 2, 0, 4])
>>> add(a, b) >>> add(a, b)
<MOpolynomial 3x^2 + 4x^3 + 2x + 1> <MOpolynomial 4x^3 + 3x^2 + 2x + 1>
""" """
if left.power in right.coefficients.keys(): if left.power in right.coefficients.keys():
raise NotImplementedError("Degree in common, need to compute") raise NotImplementedError("Degree in common, need to compute")
@ -325,7 +325,7 @@ def mopolynomial_momonomial(left, right):
>>> a = MOpolynomial('x', [1, 2, 0, 4]) >>> a = MOpolynomial('x', [1, 2, 0, 4])
>>> b = MOMonomial(3, "x", 2) >>> b = MOMonomial(3, "x", 2)
>>> add(a, b) >>> add(a, b)
<MOpolynomial 4x^3 + 2x + 1 + 3x^2> <MOpolynomial 4x^3 + 3x^2 + 2x + 1>
""" """
if right.power in left.coefficients.keys(): if right.power in left.coefficients.keys():
raise NotImplementedError("Degree in common, need to compute") raise NotImplementedError("Degree in common, need to compute")
@ -342,9 +342,9 @@ def mopolynomial_mopolynomial(left, right):
>>> a = MOpolynomial('x', [1, 0, 3]) >>> a = MOpolynomial('x', [1, 0, 3])
>>> b = MOpolynomial('x', [0, 2, 0, 4]) >>> b = MOpolynomial('x', [0, 2, 0, 4])
>>> add(a, b) >>> add(a, b)
<MOpolynomial 3x^2 + 1 + 4x^3 + 2x> <MOpolynomial 4x^3 + 3x^2 + 2x + 1>
>>> add(b, a) >>> add(b, a)
<MOpolynomial 4x^3 + 2x + 3x^2 + 1> <MOpolynomial 4x^3 + 3x^2 + 2x + 1>
""" """
common_degree = set(left.monomials.keys()).intersection(right.monomials.keys()) common_degree = set(left.monomials.keys()).intersection(right.monomials.keys())
if common_degree: if common_degree:
@ -361,7 +361,7 @@ def mostr_monomial(left, right):
>>> a = MOstr('x') >>> a = MOstr('x')
>>> b = MOMonomial(3, 'x', 4) >>> b = MOMonomial(3, 'x', 4)
>>> add(a, b) >>> add(a, b)
<MOpolynomial x + 3x^4> <MOpolynomial 3x^4 + x>
""" """
if right.power == 1: if right.power == 1:
raise NotImplementedError("Monomial is deg 1, need to compute") raise NotImplementedError("Monomial is deg 1, need to compute")
@ -391,7 +391,7 @@ def mostrpower_monomial(left, right):
>>> a = MOstrPower('x', 2) >>> a = MOstrPower('x', 2)
>>> b = MOMonomial(3, 'x', 4) >>> b = MOMonomial(3, 'x', 4)
>>> add(a, b) >>> add(a, b)
<MOpolynomial x^2 + 3x^4> <MOpolynomial 3x^4 + x^2>
""" """
if left.power == right.power: if left.power == right.power:
raise NotImplementedError( raise NotImplementedError(

View File

@ -15,6 +15,7 @@ from ..tree import Tree
from ..MO import MO, MOnumber, MOstr from ..MO import MO, MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial
from ..compute.filters import special_case
multiply_doc = """ Multiply MOs multiply_doc = """ Multiply MOs
@ -27,7 +28,38 @@ multiply_doc = """ Multiply MOs
multiply = Dispatcher("multiply", doc=multiply_doc) multiply = Dispatcher("multiply", doc=multiply_doc)
def multiply_filter(left, right):
""" Automatic multiply on MO
:param left: MO
:param right: MO
:returns: MO if it is a special case, nothing other wise
"""
try:
if left == 0:
return left
except TypeError:
pass
try:
if right == 0:
return right
except TypeError:
pass
try:
if left == 1:
return right
except TypeError:
pass
try:
if right == 1:
return left
except TypeError:
pass
@multiply.register((MOnumber, MOFraction), MOstr) @multiply.register((MOnumber, MOFraction), MOstr)
@special_case(multiply_filter)
def moscalar_mostr(left, right): def moscalar_mostr(left, right):
""" Multiply a scalar with a letter to create a MOMonomial """ Multiply a scalar with a letter to create a MOMonomial
@ -43,6 +75,7 @@ def moscalar_mostr(left, right):
@multiply.register(MOstr, (MOnumber, MOFraction)) @multiply.register(MOstr, (MOnumber, MOFraction))
@special_case(multiply_filter)
def mostr_moscalar(left, right): def mostr_moscalar(left, right):
""" Multiply a scalar with a letter to create a MOMonomial """ Multiply a scalar with a letter to create a MOMonomial
@ -58,6 +91,7 @@ def mostr_moscalar(left, right):
@multiply.register((MOnumber, MOFraction), MOstrPower) @multiply.register((MOnumber, MOFraction), MOstrPower)
@special_case(multiply_filter)
def moscalar_mostrpower(left, right): def moscalar_mostrpower(left, right):
""" Multiply a scalar with a MOstrPower """ Multiply a scalar with a MOstrPower
@ -65,12 +99,18 @@ def moscalar_mostrpower(left, right):
>>> x = MOstrPower('x', 4) >>> x = MOstrPower('x', 4)
>>> multiply(a, x) >>> multiply(a, x)
<MOMonomial 4x^4> <MOMonomial 4x^4>
>>> a = MOnumber(1)
>>> x = MOstrPower('x', 4)
>>> multiply(a, x)
<MOstrPower x^4>
""" """
# if left == 1:
# return right
return MOMonomial(left, right) return MOMonomial(left, right)
@multiply.register(MOstrPower, (MOnumber, MOFraction)) @multiply.register(MOstrPower, (MOnumber, MOFraction))
@special_case(multiply_filter)
def mostrpower_moscalar(left, right): def mostrpower_moscalar(left, right):
""" Multiply a MOstrPower with a scalar """ Multiply a MOstrPower with a scalar

View File

@ -0,0 +1,19 @@
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

@ -0,0 +1,65 @@
#! /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

@ -0,0 +1,116 @@
#! /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

@ -0,0 +1,43 @@
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

@ -16,7 +16,7 @@ __all__ = ["reject_random", "filter_random", "FilterRandom"]
def reject_random(min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks=[]): def reject_random(min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks=[]):
""" Generate a random integer with the rejection method """Generate a random integer with the rejection method
:param name: name of the Integer :param name: name of the Integer
:param min_value: minimum value :param min_value: minimum value
@ -60,7 +60,7 @@ def reject_random(min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks
def filter_random(min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks=[]): def filter_random(min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks=[]):
""" Generate a random integer by filtering then choosing a candidate """Generate a random integer by filtering then choosing a candidate
:param name: name of the Integer :param name: name of the Integer
:param min_value: minimum value :param min_value: minimum value
@ -111,8 +111,7 @@ 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
@ -133,7 +132,7 @@ class FilterRandom(object):
} }
def add_candidates(self, low, high): def add_candidates(self, low, high):
""" Add candidates between low and high to _candidates """ """Add candidates between low and high to _candidates"""
if low < self._min: if low < self._min:
self._min = low self._min = low
useless_low = False useless_low = False
@ -157,11 +156,11 @@ class FilterRandom(object):
) )
def candidates(self, min_value=-10, max_value=10): def candidates(self, min_value=-10, max_value=10):
""" Return candidates between min_value and max_value """ """Return candidates between min_value and max_value"""
return [c for c in self._candidates if (c > min_value and c < max_value)] return [c for c in self._candidates if (c > min_value and c < max_value)]
def __call__(self, min_value=-10, max_value=10): def __call__(self, min_value=-10, max_value=10):
""" Randomly choose on candidate """ """Randomly choose on candidate"""
self.add_candidates(min_value, max_value) self.add_candidates(min_value, max_value)
return random.choice(self.candidates(min_value, max_value)) return random.choice(self.candidates(min_value, max_value))

View File

@ -9,15 +9,15 @@
""" """
""" """
from ..coroutine import * from ...core.coroutine import coroutine, STOOOP
@coroutine @coroutine
def look_for_rdleaf(target): def look_for_rdleaf(target):
""" Coroutine which look to "{...}" which are RdLeaf """Coroutine which look to "{...}" which are RdLeaf
:example: :example:
>>> from ..str2 import list_sink >>> from ...core.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,9 +53,7 @@ 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

@ -0,0 +1,118 @@
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

@ -0,0 +1,40 @@
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

@ -0,0 +1,43 @@
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

@ -0,0 +1,64 @@
"""
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

@ -5,7 +5,7 @@
from random import randint from random import randint
def random_pythagore(v_min = 1, v_max = 10, nbr_format = lambda x : x) : def random_pythagore(v_min=1, v_max=10, nbr_format=lambda x: x):
""" Generate a pythagore triplet """ Generate a pythagore triplet
:returns: (a,b,c) such that a^2 = b^2 + c^2 :returns: (a,b,c) such that a^2 = b^2 + c^2
@ -14,10 +14,11 @@ def random_pythagore(v_min = 1, v_max = 10, nbr_format = lambda x : x) :
while u == v: while u == v:
u, v = randint(v_min, v_max), randint(v_min, v_max) u, v = randint(v_min, v_max), randint(v_min, v_max)
u, v = max(u, v), min(u, v) u, v = max(u, v), min(u, v)
triplet = (u**2+v**2, 2*u*v, u**2-v**2) triplet = (u ** 2 + v ** 2, 2 * u * v, u ** 2 - v ** 2)
formated_triplet = [nbr_format(i) for i in triplet] formated_triplet = [nbr_format(i) for i in triplet]
return formated_triplet return formated_triplet
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4: # vim:set autoindent expandtab tabstop=4 shiftwidth=4:

View File

@ -1,4 +1,4 @@
#/usr/bin/env python # /usr/bin/env python
# -*- coding:Utf-8 -*- # -*- coding:Utf-8 -*-
# #
@ -32,11 +32,17 @@ class Dataset(list):
""" """
@classmethod @classmethod
def random(cls, length, data_name="Valeurs", def random(
distrib="gauss", rd_args=(0, 1), cls,
length,
data_name="Valeurs",
distrib="gauss",
rd_args=(0, 1),
nbr_format=lambda x: round(x, 2), nbr_format=lambda x: round(x, 2),
v_min=None, v_max=None, v_min=None,
exact_mean=None): v_max=None,
exact_mean=None,
):
""" Generate a random list of value """ Generate a random list of value
:param length: length of the dataset :param length: length of the dataset
@ -47,11 +53,9 @@ class Dataset(list):
:param v_max: maximum accepted value :param v_max: maximum accepted value
:param exact_mean: if set, the last generated number will be create in order that the computed mean is exacly equal to "exact_mean" :param exact_mean: if set, the last generated number will be create in order that the computed mean is exacly equal to "exact_mean"
""" """
data = random_generator(length, data = random_generator(
distrib, rd_args, length, distrib, rd_args, nbr_format, v_min, v_max, exact_mean
nbr_format, )
v_min, v_max,
exact_mean)
return cls(data, data_name=data_name) return cls(data, data_name=data_name)
@ -94,7 +98,7 @@ class Dataset(list):
def deviation(self): def deviation(self):
""" Compute the deviation (not normalized) """ """ Compute the deviation (not normalized) """
mean = self.mean() mean = self.mean()
return sum([(x - mean)**2 for x in self]) return sum([(x - mean) ** 2 for x in self])
@number_factory @number_factory
def variance(self): def variance(self):
@ -120,7 +124,8 @@ class Dataset(list):
self.quartile(1), self.quartile(1),
self.quartile(2), self.quartile(2),
self.quartile(3), self.quartile(3),
max(self)) max(self),
)
@number_factory @number_factory
def quartile(self, quartile=1): def quartile(self, quartile=1):
@ -173,18 +178,21 @@ class Dataset(list):
""" Latex code to display dataset as a tabular """ """ Latex code to display dataset as a tabular """
d_per_line = self.effectif_total() // nbr_lines d_per_line = self.effectif_total() // nbr_lines
d_last_line = self.effectif_total() % d_per_line d_last_line = self.effectif_total() % d_per_line
splited_data = [self[x:x + d_per_line] splited_data = [
for x in range(0, self.effectif_total(), d_per_line)] self[x : x + d_per_line]
for x in range(0, self.effectif_total(), d_per_line)
]
# On ajoute les éléments manquant pour la dernière line # On ajoute les éléments manquant pour la dernière line
if d_last_line: if d_last_line:
splited_data[-1] += [' '] * (d_per_line - d_last_line) splited_data[-1] += [" "] * (d_per_line - d_last_line)
# Construction du tableau # Construction du tableau
latex = "\\begin{{tabular}}{{|c|*{{{nbr_col}}}{{c|}}}} \n".format( latex = "\\begin{{tabular}}{{|c|*{{{nbr_col}}}{{c|}}}} \n".format(
nbr_col=d_per_line) nbr_col=d_per_line
)
latex += "\t\t \hline \n" latex += "\t\t \hline \n"
d_lines = [' & '.join(map(str, l)) for l in splited_data] d_lines = [" & ".join(map(str, l)) for l in splited_data]
latex += " \\\\ \n \\hline \n".join(d_lines) latex += " \\\\ \n \\hline \n".join(d_lines)
latex += " \\\\ \n \\hline \n" latex += " \\\\ \n \\hline \n"

View File

@ -1,4 +1,4 @@
#/usr/bin/env python # /usr/bin/env python
# -*- coding:Utf-8 -*- # -*- coding:Utf-8 -*-
from functools import wraps from functools import wraps
@ -6,6 +6,7 @@ from functools import wraps
def number_factory(fun): def number_factory(fun):
""" Decorator which format returned value """ """ Decorator which format returned value """
@wraps(fun) @wraps(fun)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
ans = fun(*args, **kwargs) ans = fun(*args, **kwargs)
@ -16,6 +17,7 @@ def number_factory(fun):
return round(ans, 2) return round(ans, 2)
except AttributeError: except AttributeError:
return ans return ans
return wrapper return wrapper

View File

@ -1,14 +1,18 @@
#/usr/bin/env python # /usr/bin/env python
# -*- coding:Utf-8 -*- # -*- coding:Utf-8 -*-
from random import randint, uniform, gauss, choice from random import randint, uniform, gauss, choice
def random_generator(length, def random_generator(
distrib=gauss, rd_args=(0, 1), length,
distrib=gauss,
rd_args=(0, 1),
nbr_format=lambda x: round(x, 2), nbr_format=lambda x: round(x, 2),
v_min=None, v_max=None, v_min=None,
exact_mean=None): v_max=None,
exact_mean=None,
):
""" Generate a random list of value """ Generate a random list of value
:param length: length of the dataset :param length: length of the dataset
@ -19,12 +23,18 @@ def random_generator(length,
:param v_max: maximum accepted value :param v_max: maximum accepted value
:param exact_mean: if set, the last generated number will be create in order that the computed mean is exacly equal to "exact_mean" :param exact_mean: if set, the last generated number will be create in order that the computed mean is exacly equal to "exact_mean"
>>> random_generator(10) >>> random_generator(10) # doctest: +SKIP
>>> random_generator(10, distrib = uniform, rd_args = (5, 10)) [-0.76, 0.46, 0.19, 0.08, -1.13, -0.5, 0.47, -2.11, 0.16, -1.05]
>>> random_generator(10, distrib = "uniform", rd_args = (5, 10)) >>> random_generator(10, distrib = uniform, rd_args = (5, 10)) # doctest: +SKIP
>>> random_generator(10, v_min = 0) [9.01, 5.32, 5.59, 8.8, 7.36, 6.9, 6.05, 7.44, 9.47, 6.95]
>>> random_generator(10, exact_mean = 0) >>> random_generator(10, distrib = "uniform", rd_args = (5, 10)) # doctest: +SKIP
>>> random_generator(10, distrib = gauss, rd_args = (50,20), nbr_format = int) [7.85, 9.01, 5.32, 5.59, 8.8, 7.36, 6.9, 6.05, 7.44, 9.47]
>>> random_generator(10, v_min = 0) # doctest: +SKIP
[0.46, 0.19, 0.08, 0.47, 0.16, 0.87, 0.17, 1.79, 0.19, 1.12]
>>> random_generator(10, exact_mean = 0) # doctest: +SKIP
[-0.76, 0.46, 0.19, 0.08, -1.13, -0.5, 0.47, -2.11, 0.16, 3.14]
>>> random_generator(10, distrib = gauss, rd_args = (50,20), nbr_format = int) # doctest: +SKIP
[34, 59, 53, 51, 27, 40, 59, 7, 53, 28]
""" """
# if exact_mean is set, we create automaticaly only length-1 value # if exact_mean is set, we create automaticaly only length-1 value
@ -47,7 +57,8 @@ def random_generator(length,
"gauss": gauss, "gauss": gauss,
"uniform": uniform, "uniform": uniform,
"randint": randint, "randint": randint,
"choice": choice} "choice": choice,
}
try: try:
distrib(*rd_args) distrib(*rd_args)
except TypeError: except TypeError:
@ -67,11 +78,13 @@ def random_generator(length,
last_v = nbr_format((length + 1) * exact_mean - sum(data)) last_v = nbr_format((length + 1) * exact_mean - sum(data))
if not validate(last_v): if not validate(last_v):
raise ValueError( raise ValueError(
"Can't build the last value. Conflict between v_min/v_max and exact_mean") "Can't build the last value. Conflict between v_min/v_max and exact_mean"
)
data.append(last_v) data.append(last_v)
return data return data
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'
# vim:set autoindent expandtab tabstop=4 shiftwidth=4: # vim:set autoindent expandtab tabstop=4 shiftwidth=4:

View File

@ -1,4 +1,4 @@
#/usr/bin/env python # /usr/bin/env python
# -*- coding:Utf-8 -*- # -*- coding:Utf-8 -*-
""" """
@ -12,9 +12,11 @@ from .dataset import Dataset
from itertools import chain from itertools import chain
from .number_tools import number_factory from .number_tools import number_factory
def flatten_list(l): def flatten_list(l):
return list(chain(*l)) return list(chain(*l))
class WeightedDataset(dict): class WeightedDataset(dict):
""" A weighted dataset with statistics and latex rendering methods """ A weighted dataset with statistics and latex rendering methods
@ -37,11 +39,8 @@ class WeightedDataset(dict):
""" """
def __init__( def __init__(
self, self, datas=[], data_name="Valeurs", weights=[], weight_name="Effectifs"
datas=[], ):
data_name="Valeurs",
weights=[],
weight_name="Effectifs"):
""" """
Initiate the WeightedDataset Initiate the WeightedDataset
""" """
@ -84,7 +83,7 @@ class WeightedDataset(dict):
def deviation(self): def deviation(self):
""" Compute the deviation (not normalized) """ """ Compute the deviation (not normalized) """
mean = self.mean() mean = self.mean()
return sum([v * (k - mean)**2 for (k, v) in self.items()]) return sum([v * (k - mean) ** 2 for (k, v) in self.items()])
@number_factory @number_factory
def variance(self): def variance(self):
@ -109,11 +108,13 @@ class WeightedDataset(dict):
(1, 3, 4, 5, 5) (1, 3, 4, 5, 5)
""" """
return (min(self.keys()), return (
min(self.keys()),
self.quartile(1), self.quartile(1),
self.quartile(2), self.quartile(2),
self.quartile(3), self.quartile(3),
max(self.keys())) max(self.keys()),
)
@number_factory @number_factory
def quartile(self, quartile=1): def quartile(self, quartile=1):
@ -146,8 +147,9 @@ class WeightedDataset(dict):
position = self.posi_quartile(quartile) - 1 position = self.posi_quartile(quartile) - 1
expanded_values = flatten_list([v * [k] for (k, v) in self.items()]) expanded_values = flatten_list([v * [k] for (k, v) in self.items()])
if position.is_integer(): if position.is_integer():
return (expanded_values[int(position)] + return (
expanded_values[int(position) + 1]) / 2 expanded_values[int(position)] + expanded_values[int(position) + 1]
) / 2
else: else:
return expanded_values[ceil(position)] return expanded_values[ceil(position)]
@ -167,7 +169,8 @@ class WeightedDataset(dict):
def tabular_latex(self): def tabular_latex(self):
""" Latex code to display dataset as a tabular """ """ Latex code to display dataset as a tabular """
latex = "\\begin{{tabular}}{{|c|*{{{nbr_col}}}{{c|}}}} \n".format( latex = "\\begin{{tabular}}{{|c|*{{{nbr_col}}}{{c|}}}} \n".format(
nbr_col=len(self.keys())) nbr_col=len(self.keys())
)
latex += "\t \hline \n" latex += "\t \hline \n"
data_line = "\t {data_name} ".format(data_name=self.data_name) data_line = "\t {data_name} ".format(data_name=self.data_name)
weight_line = "\t {weight_name} ".format(weight_name=self.weight_name) weight_line = "\t {weight_name} ".format(weight_name=self.weight_name)

35
noxfile.py Normal file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python
import nox
@nox.session
def lint(session):
session.install("black")
session.run("black", "mapytex", "noxfile.py", "setup.py")
@nox.session
def test(session):
session.install("-r", "requirements.txt")
session.install("pytest")
session.run("pytest", "mapytex")
@nox.session
def docs(session):
"""Build the documentation."""
session.run("rm", "-rf", "documentation/_build", external=True)
session.install("sphinx", "sphinx-autobuild", "sphinx_rtd_theme")
session.install(".")
session.cd("documentation/source")
sphinx_args = ["-b", "html", "-W", "-d", "_build/doctrees", ".", "_build/html"]
if not session.interactive:
sphinx_cmd = "sphinx-build"
else:
sphinx_cmd = "sphinx-autobuild"
sphinx_args.insert(0, "--open-browser")
session.run(sphinx_cmd, *sphinx_args)

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.2 tabulate==0.8.7
traitlets==4.3.2 traitlets==4.3.2
wcwidth==0.1.7 wcwidth==0.1.7

View File

@ -6,17 +6,14 @@ except ImportError:
from distutils.core import setup from distutils.core import setup
setup( setup(
name='mapytex', name="mapytex",
version='2.1', version="2.3.2",
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",
url='http://git.opytex.org/lafrite/Mapytex', url="http://git.opytex.org/lafrite/Mapytex",
#packages=['mapytex'], # packages=['mapytex'],
packages=find_packages(), packages=find_packages(),
include_package_data = True, include_package_data=True,
install_requires=[ install_requires=["multipledispatch", "tabulate"],
'multipledispatch', )
'tabulate',
],
)

0
test/__init__.py Normal file
View 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.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

View File

@ -0,0 +1,15 @@
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

@ -0,0 +1,55 @@
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