Skip to content

Commit

Permalink
add uniform assumptions on symbolic vars
Browse files Browse the repository at this point in the history
  • Loading branch information
idanpa committed Mar 29, 2024
1 parent f2ad297 commit ba3f3e2
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
1 change: 1 addition & 0 deletions calcpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
14 changes: 14 additions & 0 deletions calcpy/tests/test_autorestore.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

20 changes: 20 additions & 0 deletions calcpy/tests/test_transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 28 additions & 3 deletions calcpy/transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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

0 comments on commit ba3f3e2

Please sign in to comment.