-Lance Larsen
# HG changeset patch
# User Lance Larsen
# Date 1226946818 25200
# Node ID 35c17486e4205095a59d2bb8cd421f48925472a5
# Parent e8ef8227fecd5271cf2680af328525f5510734b7
[mq]: implicit_subs_call_syntax.patch
diff -r e8ef8227fecd -r 35c17486e420 sympy/core/basic.py
--- a/sympy/core/basic.py Fri Nov 14 17:01:51 2008 +0100
+++ b/sympy/core/basic.py Mon Nov 17 11:33:38 2008 -0700
@@ -87,10 +87,13 @@
classnamespace = {}
singleton = {}
- def __init__(cls, *args, **kws):
+ def __init__(cls,*args,**kws):
n = cls.__name__
c = BasicMeta.classnamespace.get(n)
- BasicMeta.classnamespace[n] = cls
+ if c is None:
+ BasicMeta.classnamespace[n] = cls
+ else:
+ print 'Ignoring redefinition of %s: %s defined earlier than %s' % (
n, c, cls)
super(BasicMeta, cls).__init__(cls)
# --- assumptions ---
@@ -896,26 +899,41 @@
"""
return self
- def subs(self, *args):
- """
- Substitutes an expression.
+ def subs(self, *args, **kwargs):
+ """Substitutes an expression.
Calls either _subs_old_new, _subs_dict or _subs_list depending
- if you give it two arguments (old, new), a dictionary or a list.
+ if you give it two arguments (old, new), a dictionary, a list or
+ pass in named parameters. When named parameters are passed in,
+ these are converted to a dictionary and _subs_dict is called.
Examples:
>>> from sympy import *
>>> x,y = symbols('xy')
- >>> (1+x*y).subs(x, pi)
- 1 + pi*y
+ >>> (1+x*y).subs(x=pi, y=2)
+ 1 + 2*pi
>>> (1+x*y).subs({x:pi, y:2})
1 + 2*pi
>>> (1+x*y).subs([(x,pi), (y,2)])
1 + 2*pi
+ >>> (1+x*y).subs(x, pi)
+ 1 + pi*y
+ Subs can be called implicitly as well as shown in the following
+ examples:
+
+ >>> f = 1+x*y
+ >>> f(x=pi, y=2)
+ 1 + 2*pi
+ >>> f({x:pi, y:2})
+ 1 + 2*pi
+ >>> f([(x,pi), (y,2)])
+ 1 + 2*pi
+ >>> f(x, pi)
+ 1 + pi*y
"""
- if len(args) == 1:
+ if len(args) == 1 and len(kwargs) == 0:
sequence = args[0]
if isinstance(sequence, dict):
return self._subs_dict(sequence)
@@ -923,9 +941,11 @@
return self._subs_list(sequence)
else:
raise TypeError("Not an iterable container")
- elif len(args) == 2:
+ elif len(args) == 2 and len(kwargs) == 0:
old, new = args
return self._subs_old_new(old, new)
+ elif len(args) == 0 and len(kwargs) > 0:
+ return self._subs_dict(kwargs)
else:
raise Exception("subs accept either 1 or 2 arguments")
@@ -1932,9 +1952,24 @@
from sympy.integrals import integrate
return integrate(self, *args, **kwargs)
- #XXX fix the removeme
- def __call__(self, *args, **removeme):
- return Function(self[0])(*args)
+ def __call__(self, *args, **kwargs):
+ """Alias for calling the subs function.
+
+ Examples:
+
+ >>> from sympy import *
+ >>> x,y = symbols('xy')
+ >>> f = 1+x*y
+ >>> f(x=pi,y=2)
+ 1 + 2*pi
+ >>> f({x:pi, y:2})
+ 1 + 2*pi
+ >>> f([(x,pi), (y,2)])
+ 1 + 2*pi
+ >>> f(x, pi)
+ 1 + pi*y
+ """
+ return self.subs(*args, **kwargs)
def __float__(self):
result = self.evalf()
diff -r e8ef8227fecd -r 35c17486e420 sympy/core/tests/test_subs.py
--- a/sympy/core/tests/test_subs.py Fri Nov 14 17:01:51 2008 +0100
+++ b/sympy/core/tests/test_subs.py Mon Nov 17 11:33:38 2008 -0700
@@ -169,3 +169,16 @@
assert (f(x,y)).subs(f,sin) == f(x,y)
assert (sin(x)+atan2(x,y)).subs([[atan2,f],[sin,g]]) == f(x,y) + g(x)
assert (g(f(x+y, x))).subs([[f, l], [g, exp]]) == exp(x + sin(x + y))
+
+def test_subs_call_syntax():
+ a, b, x, y, p, q = map(Symbol, "abxypq")
+ f = 2*a*x+3*b*y
+ assert f(x=1, y=2) == 2*a + 6*b
+ assert f({a*x:p,b*y:q}) == 2*p + 3*q
+def test_implicit_subs_call_syntax():
+ a, b, x, y, p, q = map(Symbol, 'abxypq')
+ f = 2*a*x + 3*b*y
+ assert f(x=1, y=2) == 2*a + 6*b
+ assert f({a*x:p,b*y:q}) == 2*p + 3*q
+ assert f([(a*x,p),(b*y,q)]) == 2*p + 3*q
+ assert f(a*x, p) == 2*p + 3*b*y
\ No newline at end of file
Hi Lance,
thanks for the patch.
There is a problem:
In [1]: t = Symbol("theta")
In [2]: f = t*x
In [3]: f(x=3,t=9)
Out[3]: 3⋅θ
Cheers
Riccardo
^^ why are you deleting the whitespace? I suggest to leave it as it was.
> n = cls.__name__
> c = BasicMeta.classnamespace.get(n)
> - BasicMeta.classnamespace[n] = cls
> + if c is None:
> + BasicMeta.classnamespace[n] = cls
> + else:
> + print 'Ignoring redefinition of %s: %s defined earlier than %s' % (
> n, c, cls)
> super(BasicMeta, cls).__init__(cls)
^^^ why are you introducing the "Ignoring redefinition..." thing? We
removed that recently -- maybe it is a leftover from a merge?
Otherwise the patch looks good as it is to me.
However, there seem to be some confusion about what we agreed to
implement, so let's discuss it first, then apply this patch (with the
first 2 hunks removed, as I noted above --- if you agree of course).
Ondrej
>> # HG changeset patch
>> # User Lance Larsen
Please add your name and email address to your .hgrc (or something
similar on windows), see the other patches's authors.
>> # Date 1226946818 25200
>> # Node ID 35c17486e4205095a59d2bb8cd421f48925472a5
>> # Parent e8ef8227fecd5271cf2680af328525f5510734b7
>> [mq]: implicit_subs_call_syntax.patch
Add more meaningful description here.
Hi Lance!
first thanks a lot for the patch and the work you did. It's very good
technically and it will be merged after we discuss this.
I also thought we agree to implement the .subs(x=something, y=sasd)
syntax. Brian -- do you think it is too confusing to put this to subs?
I find it not general enough, e.g. .subs(x=y**2) works, but
.subs(y**2=x) doesn't, so one has to use dictionaries as the general
syntax .subs({x: y**2}) and .subs({y**2:x}), or, in the case of just
one substitution .subs(y**2, x). On the other hand, I think it may
come handy to use .subs(x=y**2) sometimes, so I am +0, which means I
don't mind either way.
as to __call__(), I think we should just suppor the positional
arguments, e.g. f(x=2, y=3), or maybe even f({x:2, y:3}), but not f(x,
y) in the meaning of f(x=y), that would be too confusing. E.g. one
should not just call .subs() from __call__(), but do some checking
first. We can either raise an exception for f(x, y), or implement
Brian's proposal with .bindings.
However Lance's argument, that __call__ and .subs() should behave the
same is also good. But I think f(x, 2) and .subs(x, 2) should not do
the same thing, because I don't know how others (let's discuss this),
but I feel, that f(x, 2) should substitute for positional arguments in
"f", but .subs(x, 2) should rather do .subs({x: 2}). Lance -- so you
propose to drop the .subs(old, new), and only use this syntax to
substitute for positional arguments?
Ondrej
Yes, and because it's the variable IMHO is the one the user should use. Also
in the existing subs routine it works in this way, so it should work also with
that new syntax. i.e: it would be confusing if this works
In [4]: f.subs({x:3,t:9})
Out[4]: 27
and this not:
In [5]: f(x=3,t=9)
Out[5]: 3⋅θ
And again if this works:
In [6]: f(x=3,theta=9)
Out[6]: 27
and this not:
In [7]: f.subs({x:3,theta:9})
---------------------------------------------------------------------------
NameError
> -Lance
Cheers,
Riccardo
> 2. The other option I can think of is to define a new object type
> that has the __call__ logic (instead of putting into Basic). This
> might look like this:
>
> f = x*y
> g = SympyCallable(f, (x,y))
> g(2,3) # becomes f(x=2,y=3)
We already have this:
>>> f = x*y
>>> g = Lambda((x,y), f)
>>> g(2,3)
6
Fredrik
How can you achieve that in Python without preparsing? You can return
some instance on f(x, y) (e.g. set .bindings), but you cannot assign
to it:
In [1]: class A:
...: pass
...:
In [2]: a = A()
In [3]: a = 5
In [4]: A() = 5
------------------------------------------------------------
File "<ipython console>", line 1
SyntaxError: can't assign to function call (<ipython console>, line 1)
>>>> f(2, 3)
> 6
>
> It's the closest way to mathematical notation I can think of.
That is close to what Sage is using, but they use preparsing for that.
Ondrej
Hi Lance,
here is a proof of concept using eval. I just looked at var function code to
see how it obtain the global dict and used the same "hack", but it seems to
work:
def __call__(self, *args, **kwargs):
import inspect
frame = inspect.currentframe().f_back.f_globals
nkwargs = {}
try:
for k, v in kwargs.iteritems():
nkwargs[eval(k, frame)] = v
finally:
del frame
return self._subs_dict(nkwargs)
In [1]: t = Symbol("theta")
In [2]: f = x+t**2
In [3]: f(x=10,t=3)
Out[3]: 19
Cheers,
Riccardo
Wow, nice trick.
But I am worried this is too hacky --- I don't like playing with
frames, maybe with the exception of var().
I think we should rather use just python dictionaries and that's it.
Ondrej
Sorry Ondrej, the problem is the hack or that you're fine with this behaviour?
In [1]: a = Symbol("alpha")
In [2]: f = cos(a) #simple function
In [3]: f(a=pi) #Nothing happens using a
Out[3]: cos(α)
In [3]: f([[a,pi]]) #calling with lists it works with a...
Out[3]: -1
In [4]: f([[alpha,pi]]) #and fail with alpha
---------------------------------------------------------------------------
NameError
In [4]: f(alpha=pi) #It work with alpha
Out[4]: -1
In [5]: f.subs(a,pi) #It works with a
Out[5]: -1
In [6]: f.subs(alpha,pi) #another obvious error
---------------------------------------------------------------------------
NameError
Cheers,
Riccardo
I don't like this behavior. And I don't like that we need to use a
hack to fix it.
I think it's because we are misusing the **kw arguments. The kw
arguments are meant to specify options to functions, not to specify
symbols, because symbol is an instance, but kw arg is a string. So we
need to use hacks to get around it.
And I just realized I have big problems with it. Consider this:
x = Symbol("a")
f = x*y
f(x=3)
and it will fail. Lance --- what is your opinion about this?
So we have 3 options:
1) not allow the f(x=3) syntax
2) only allow the f(x=3), but do not use the hack, thus it will not
work for x = Symbol("a")
3) allow both f(x=3) and use the hack.
Which one do you choose?
Ondrej
So howabout using the proposal 3 in __call__, but leaving .subs() as it is now?
That way the only hack will be in __call__, where it will just
exctract the correct symbols and pass it to .subs() as a regular
Python dictionary.
Ondrej
Lance --- any comments? I am sure we are all thinking about how to fix
this issue. Obviously the current code in __call__ is broken, so it
needs fixing in any case. If we cannot agree on the solution, let's
bring it to our main mailinglist, list all 3 (or more) possibilities
with pros and cons and let discuss this with everyone.
Ondrej
Ok, let's bring it to the main list. I'll try to do it in the afternoon.
Ondrej