diff --git a/README.md b/README.md index 6500d5e..bb9b151 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ pip install git+https://github.com/idanpa/calcpy * `?` prefix provides some basic analysis of expression (similar to [WolframAlpha](https://www.wolframalpha.com/)) `?((1,2),(3,4))`, `?x**2+1`, `?234` * Automatic symbolic variables, anything like `x` `y_1` is a sympy symbol +* Symbolic variables assumptions are uniform, `symbols(x, real=True)` would change all occurencase of `x` to be real * Implicit multiplication (`2x`, `(x+1)(x-1)` are valid) * Nested tuples are matrices `((1,2),(3,4))**2` * All variables and functions are restored between sessions (delete using `del`) diff --git a/calcpy/__init__.py b/calcpy/__init__.py index 635b731..fa525e3 100644 --- a/calcpy/__init__.py +++ b/calcpy/__init__.py @@ -46,6 +46,7 @@ class CalcPy(IPython.core.magic.Magics): auto_permutation = traitlets.Bool(False, config=True, help="convert '(0 1)(3 4)' to 'Permutation(0, 1)(3, 4)'") auto_latex = traitlets.Bool(True, config=True, help="convert $1+1$ to parse_latex('1+1')") auto_latex_sub = traitlets.Bool(True, config=True, help="substitute local variables in parsed latex") + uniform_assumptions = traitlets.Bool(True, config=True, help="uniform assumption per name for symbolic variables") previewer = traitlets.Bool(True, config=True, help="enable previewer") bitwidth = traitlets.Int(0, config=True, help="bitwidth of displayed binary integers, if 0 adjusted accordingly") chop = traitlets.Bool(True, config=True, help="replace small numbers with zero") diff --git a/calcpy/tests/test_autorestore.py b/calcpy/tests/test_autorestore.py index 59b7a1d..5425a7f 100644 --- a/calcpy/tests/test_autorestore.py +++ b/calcpy/tests/test_autorestore.py @@ -1,3 +1,5 @@ +from sympy.abc import x + def test_store_restore_var(ip): ip.run_cell('test_var = 100') assert 'test_var' in ip.user_ns @@ -24,3 +26,15 @@ def test_store_restore_func(ip): ip.run_cell('calcpy.auto_store = True') assert ip.run_cell('test_func()').result == 100 +def test_store_autovars(ip): + ip.run_cell('x + 1') # generate autovar + ip.run_cell('x=1') # set it to something else + ip.run_cell('calcpy.auto_store = False') + ip.run_cell('calcpy.auto_store = True') + assert ip.run_cell('x').result == 1 + ip.run_cell('del x') # set it back to be autovar + ip.run_cell('x + 1') + ip.run_cell('calcpy.auto_store = False') + ip.run_cell('calcpy.auto_store = True') + assert ip.run_cell('x').result == x + diff --git a/calcpy/tests/test_transformers.py b/calcpy/tests/test_transformers.py index dd062ca..3ba2555 100644 --- a/calcpy/tests/test_transformers.py +++ b/calcpy/tests/test_transformers.py @@ -6,6 +6,26 @@ def test_auto_date(ip): assert dt.days == 1 or 24*60*60-1 <= dt.seconds <= 24*60*60 assert ip.run_cell('d\'1 January 1970\'').result == datetime(1970, 1, 1) +def test_auto_symbols(ip): + assert ip.run_cell('x').result == symbols('x') + ip.run_cell('symbols("x", real=True)') + assert ip.run_cell('x.is_real').result == True + ip.run_cell('del x') + assert ip.run_cell('x.is_real').result == None + + ip.run_cell('symbols("x", real=True)') + ip.run_cell('f = x**2 + 1') + assert ip.run_cell('solve(x**2 + 1)').result == [] + ip.run_cell('symbols("x", complex=True, real=None)') + assert ip.run_cell('solve(f)').result == [-I, I] + ip.run_cell('f = $a + b + c$') + assert ip.run_cell('f.is_integer').result == None + ip.run_cell('symbols("a b c", integer=True)') + assert ip.run_cell('f.is_integer').result == True + + ip.run_cell('symbols("y", real=True)') + assert ip.run_cell('y.is_real').result == True + def test_auto_product(ip): assert ip.run_cell('2(1+1)').result == 4 assert ip.run_cell('(1+1)2').result == 4 diff --git a/calcpy/transformers.py b/calcpy/transformers.py index 9064103..f7a9bba 100644 --- a/calcpy/transformers.py +++ b/calcpy/transformers.py @@ -29,11 +29,16 @@ def dateparse(datetime_string): def parse_latex(s): ip = IPython.get_ipython() - expr = sympy.parsing.latex.parse_latex(s) + try: + # don't take the default assumptions of symbols created by parser: + sympy.Symbol._ignore_assumptions = True + expr = sympy.parsing.latex.parse_latex(s) + finally: + sympy.Symbol._ignore_assumptions = False expr = expr.subs({'i': sympy.I}) if not ip.calcpy.auto_latex_sub: return expr - subs = {sym.name : ip.user_ns.get(sym.name,sympy.symbols(sym.name)) for sym in expr.free_symbols} + subs = {sym.name : ip.user_ns.get(sym.name, sym) for sym in expr.free_symbols} return expr.subs(subs) def is_auto_symbol(var_name): @@ -214,7 +219,7 @@ class AutoSymbols(AstNodeTransformer): def visit_Name(self, node): if self.ip.calcpy.auto_symbols: if node.id not in self.ip.user_ns and is_auto_symbol(node.id): - self.ip.calcpy.push({node.id: sympy.symbols(node.id)}, interactive=False) + self.ip.calcpy.push({node.id: sympy.symbols(node.id, ignore_assumptions=True)}, interactive=False) return self.generic_visit(node) class AutoProduct(AstNodeTransformer): @@ -274,3 +279,23 @@ def sympy_float_array(self, dtype=np.dtype(float)): except (ModuleNotFoundError, ImportError): pass + # unified assumptions for symbol name + sympy.Symbol._all_symbols = {} + sympy.Symbol._ignore_assumptions = False + sympy_symbol_xnew = sympy.Symbol.__xnew__ + def unified_sympy_symbol_xnew(cls, name, ignore_assumptions=False, **assumptions): + new_symbol = sympy_symbol_xnew(cls, name, **assumptions) + if not ip.calcpy.uniform_assumptions or cls != sympy.Symbol: + return new_symbol + if name not in sympy.Symbol._all_symbols: + sympy.Symbol._all_symbols[name] = new_symbol + elif not ignore_assumptions and not sympy.Symbol._ignore_assumptions: + sympy.Symbol._all_symbols[name]._assumptions = new_symbol._assumptions + sympy.Symbol._all_symbols[name]._assumptions0 = new_symbol._assumptions0 + sympy.Symbol._all_symbols[name]._assumptions_orig = new_symbol._assumptions_orig + + return sympy.Symbol._all_symbols[name] + sympy.Symbol.__xnew__ = unified_sympy_symbol_xnew + # disable cache so assumptions could be changed multiple times + sympy.Symbol._Symbol__xnew_cached_ = unified_sympy_symbol_xnew +