16 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
28 changed files with 670 additions and 708 deletions

View File

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

View File

@@ -12,13 +12,6 @@ Expression
"""
from functools import partial
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 .tokens import factory
from .renders import render
@@ -79,55 +72,6 @@ class Expression(object):
return cls(t)
@classmethod
def random(
cls,
template,
conditions=[],
rejected=[0],
min_max=(-10, 10),
variables_scope={},
shuffle=False,
):
""" Initiate randomly the expression
:param template: the template of the expression
:param conditions: conditions on randomly generate variable
:param rejected: Values to reject for all random variables
:param min_max: Min and max value for all random variables
:param variables_scope: Dictionnary for each random varaibles to fic rejected and min_max
:param shuffle: allowing to shuffle the tree
:returns: TODO
:example:
>>> e = Expression.random("{a}/{a*k}")
>>> e # doctest: +SKIP
<Exp: -3 / -15>
>>> e = Expression.random("{a}/{a*k} - 3*{b}", variables_scope={'a':{'min_max':(10, 30)}})
>>> e # doctest: +SKIP
<Exp: 18 / 108 - 3 * 9>
>>> e = Expression.random("{a}*x + {b}*x + 3", ["a>b"], rejected=[0, 1])
>>> ee = e.simplify()
>>> print(e) # doctest: +SKIP
10x - 6x + 3
>>> print(ee) # doctest: +SKIP
4x + 3
"""
rd_t = Tree.from_str(template, random=True)
leafs = extract_rdleaf(rd_t)
rd_varia = extract_rv(leafs)
generated = random_generator(
rd_varia, conditions, rejected, min_max, variables_scope
)
computed = compute_leafs(leafs, generated)
t = replace_rdleaf(rd_t, computed).map_on_leaf(moify)
if shuffle:
raise NotImplemented("Can't suffle expression yet")
return cls._post_processing(t)
@classmethod
def _post_processing(cls, t):
""" Post process the tree by typing it """

View File

@@ -13,10 +13,8 @@ Tokens representing interger and decimal
from decimal import Decimal as _Decimal
from .token import Token
from ...core.arithmetic import gcd
from ...core.random.int_gene import filter_random
from ...core.MO import MO, MOnumber
from ...core.MO.fraction import MOFraction
from random import random
__all__ = ["Integer", "Decimal", "Fraction"]
@@ -54,23 +52,6 @@ class Integer(Token):
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):
@@ -108,36 +89,6 @@ class Decimal(Token):
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):
@@ -172,69 +123,6 @@ class Fraction(Token):
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
def numerator(self):
""" Get numerator of the fraction

View File

@@ -61,10 +61,6 @@ class Polynomial(Token):
"""
return cls(MOpolynomial(variable_name, coefficients), name)
@classmethod
def random(cls):
raise NotImplementedError
@property
def raw(self):
raise NotImplementedError("Polynomial does not exists in python")
@@ -171,10 +167,6 @@ class Linear(Polynomial):
Polynomial.__init__(self, mo, name, ancestor)
self._mathtype = "affine"
@classmethod
def random(cls):
raise NotImplementedError
@property
@to_be_token
def a(self):
@@ -258,10 +250,6 @@ class Quadratic(Polynomial):
Polynomial.__init__(self, mo, name, ancestor)
self._mathtype = "polynome du 2nd degré"
@classmethod
def random(cls):
raise NotImplementedError
@property
@to_be_token
def a(self):

View File

@@ -24,14 +24,6 @@ class Token(object):
self._mathtype = None
self._ancestor = ancestor
@classmethod
def random(
cls,
family="integer",
**kwds
):
raise NotImplemented
def explain(self):
""" Yield every calculus step which have lead to self

View File

@@ -12,7 +12,6 @@ Make calculus as a student
Expression is the classe wich handle all calculus. It can randomly generate or import calculus, simplify them and explain them as a student would do.
>>> from mapytex.calculus import Expression
>>> render.set_render("txt")
>>> e = Expression.from_str("2x + 6 - 3x")
>>> print(e)
@@ -27,16 +26,23 @@ Expression is the classe wich handle all calculus. It can randomly generate or i
(2 - 3) * 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, Integer, Decimal, render, Polynomial, Fraction
from .core import random_list
from decimal import getcontext
from .API import render, Expression
#from decimal import getcontext
from . import random
#getcontext().prec = 2
__all__ = ["Expression"]
__all__ = ["render", "Expression", "random"]
# -----------------------------

View File

@@ -132,7 +132,7 @@ class MOstrPower(Molecule):
class MOMonomial(Molecule):
""" Monomial math object : ax^n"""
""" Monomial math object"""
MAINOP = "*"

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,6 @@ from decimal import Decimal, InvalidOperation
from .coroutine import *
from .operator import is_operator
from .MO import moify_cor
from .random.leaf import look_for_rdleaf, RdLeaf
__all__ = ["str2"]
@@ -395,11 +394,7 @@ def missing_times(target):
elif not is_operator(tok) and tok != ")":
target_.send("*")
if (
isinstance(tok, int)
or (isinstance(tok, str) and not is_operator(tok) and not tok == "(")
or (isinstance(tok, RdLeaf))
):
if not ( is_operator(tok) or tok =="(" ):
previous = tok
target_.send(tok)
@@ -814,38 +809,7 @@ def str2(sink, convert_to_mo=True):
return pipeline
def rdstr2(sink):
""" Return a pipeline which parse random expression and with sink as endpoint
:example:
>>> rdstr2list = rdstr2(list_sink)
>>> rdstr2list("{a}+{a*b}-2")
[<RdLeaf a>, '+', <RdLeaf a*b>, '+', <MOnumber -2>]
>>> rdstr2list("{a}({b}x+{c})")
[<RdLeaf a>, '*', [<RdLeaf b>, '*', <MOstr x>, '+', <RdLeaf c>]]
"""
lfop = lookfor(is_operator)
operator_corout = partial(concurent_broadcast, lookfors=[lfop])
def pipeline(expression):
str2_corout = look_for_rdleaf(
lookforNumbers(operator_corout(
missing_times(moify_cor(pparser(sink)))))
)
for i in expression.replace(" ", ""):
str2_corout.send(i)
a = str2_corout.throw(STOOOP)
return a
return pipeline
str2nestedlist = str2(list_sink)
# -----------------------------
# Reglages pour 'vim'
# 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 .coroutine import coroutine, STOOOP
from .str2 import str2, rdstr2
from .str2 import str2
from .operator import OPERATORS, is_operator
__all__ = ["Tree", "MutableTree"]
@@ -51,7 +51,7 @@ class Tree:
self.right_value = right_value
@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
:example:
@@ -77,26 +77,8 @@ class Tree:
> *
| > 3
| > n
>>> t = Tree.from_str("2+{n}x", random=True)
>>> print(t)
+
> 2
> *
| > {n}
| > x
>>> t = Tree.from_str("{a}({b}x+{c})", random=True)
>>> print(t)
*
> {a}
> +
| > *
| | > {b}
| | > x
| > {c}
"""
t = MutableTree.from_str(expression, convert_to_mo, random)
t = MutableTree.from_str(expression, convert_to_mo)
return cls.from_any_tree(t)
@classmethod
@@ -907,7 +889,7 @@ class MutableTree(Tree):
self.right_value = right_value
@classmethod
def from_str(cls, expression, convert_to_mo=True, random=False):
def from_str(cls, expression, convert_to_mo=True):
""" Initiate the MutableTree
:example:
@@ -963,29 +945,9 @@ class MutableTree(Tree):
| | > 8
| | > 3
| > x
>>> t = MutableTree.from_str("{b}*x+{c}", random=True)
>>> print(t)
+
> *
| > {b}
| > x
> {c}
>>> t = MutableTree.from_str("{a}*({b}*x+{c})", random=True)
>>> print(t)
*
> {a}
> +
| > *
| | > {b}
| | > x
| > {c}
"""
if random:
str_2_mut_tree = rdstr2(cls.sink)
return str_2_mut_tree(expression)
else:
str_2_mut_tree = str2(cls.sink, convert_to_mo)
return str_2_mut_tree(expression)
str_2_mut_tree = str2(cls.sink, convert_to_mo)
return str_2_mut_tree(expression)
@classmethod
@coroutine

View File

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

View File

@@ -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,38 +16,38 @@ __all__ = ["reject_random", "filter_random", "FilterRandom"]
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 min_value: minimum value
:param max_value: maximum value
:param rejected: rejected values
:param accept_callbacks: list of function for value rejection
: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 rejection
:example:
>>> a = reject_random()
>>> a not in [0, 1]
True
>>> a >= -10
True
>>> a <= 10
True
>>> a = reject_random(min_value=3, max_value=11, rejected=[5, 7])
>>> a not in [5, 7]
True
>>> a >= 3
True
>>> a <= 11
True
>>> a = reject_random(accept_callbacks=[lambda x: x%2])
>>> a%2
1
>>> random.seed(0)
>>> reject_random()
2
>>> random.seed(1)
>>> reject_random()
-6
:example:
>>> a = reject_random()
>>> a not in [0, 1]
True
>>> a >= -10
True
>>> a <= 10
True
>>> a = reject_random(min_value=3, max_value=11, rejected=[5, 7])
>>> a not in [5, 7]
True
>>> a >= 3
True
>>> a <= 11
True
>>> a = reject_random(accept_callbacks=[lambda x: x%2])
>>> a%2
1
>>> random.seed(0)
>>> reject_random()
2
>>> random.seed(1)
>>> reject_random()
-6
"""
conditions = [lambda x: x not in rejected] + accept_callbacks
@@ -60,38 +60,38 @@ 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=[]):
""" 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 min_value: minimum value
:param max_value: maximum value
:param rejected: rejected values
:param accept_callbacks: list of function for value rejection
: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 rejection
:example:
>>> a = filter_random()
>>> a not in [0, 1]
True
>>> a >= -10
True
>>> a <= 10
True
>>> a = filter_random(min_value=3, max_value=11, rejected=[5, 7])
>>> a not in [5, 7]
True
>>> a >= 3
True
>>> a <= 11
True
>>> a = filter_random(accept_callbacks=[lambda x: x%2])
>>> a%2
1
>>> random.seed(0)
>>> filter_random()
-7
>>> random.seed(1)
>>> filter_random()
6
:example:
>>> a = filter_random()
>>> a not in [0, 1]
True
>>> a >= -10
True
>>> a <= 10
True
>>> a = filter_random(min_value=3, max_value=11, rejected=[5, 7])
>>> a not in [5, 7]
True
>>> a >= 3
True
>>> a <= 11
True
>>> a = filter_random(accept_callbacks=[lambda x: x%2])
>>> a%2
1
>>> random.seed(0)
>>> filter_random()
-7
>>> random.seed(1)
>>> filter_random()
6
"""
candidates = set(range(min_value, max_value + 1))
candidates = {c for c in candidates if c not in rejected}
@@ -111,8 +111,7 @@ def filter_random(min_value=-10, max_value=10, rejected=[0, 1], accept_callbacks
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
@@ -133,7 +132,7 @@ class FilterRandom(object):
}
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:
self._min = low
useless_low = False
@@ -157,11 +156,11 @@ class FilterRandom(object):
)
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)]
def __call__(self, min_value=-10, max_value=10):
""" Randomly choose on candidate """
"""Randomly choose on candidate"""
self.add_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
def look_for_rdleaf(target):
""" Coroutine which look to "{...}" which are RdLeaf
"""Coroutine which look to "{...}" which are RdLeaf
:example:
>>> from ..str2 import list_sink
>>> from ...core.str2 import list_sink
>>> str2list = look_for_rdleaf(list_sink)
>>> for i in "{a}+{a*b}-2":
... str2list.send(i)
@@ -53,9 +53,7 @@ def look_for_rdleaf(target):
class RdLeaf:
""" Random leaf
"""
"""Random leaf"""
def __init__(self, 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

View File

@@ -17,7 +17,7 @@ def test_changing_render():
def test_changing_rending():
e = mapytex.Expression.from_str("2*3")
f = mapytex.Fraction("2/3")
f = mapytex.Expression.from_str("2/3")
assert str(e) == "2 * 3"
assert str(f) == "2 / 3"
mapytex.render.set_render("tex")

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