Implicit subs syntax like f(x=1, y=2)

10 views
Skip to first unread message

Lance Larsen

unread,
Nov 17, 2008, 2:04:25 PM11/17/08
to sympy-...@googlegroups.com
Here is the patch for the implicit subs call syntax (i.e. f(x=1, y=2)
vs. f.subs({x:1,y:2}) ). I changed the behavior of the __call__ method
in the Basic class, but this did not seem to affect any test cases. If
this causes a problem, the __call__ can be modified to behave as
before if a *arg value is passed in, but use subs if **kwargs are
passed in. I think it is more straight forward to just make __call__
an alias for subs so that is what I did. However, there may be use
cases that require the old __call__ code that I am not aware of (there
didn't appear to be in the test cases). I had trouble setting up the
smtp server for patchbomb on my machine, so I am sending this manually
via gmail using the patchbomb output. Hope it works. :)

-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

Brian Granger

unread,
Nov 17, 2008, 2:32:56 PM11/17/08
to sympy-...@googlegroups.com
This patch implements something quite different from what we had discussed:

* We never discussed changing the implementation of subs. I think
that is a separate discussion and it outside of the scope of changing
__call__. Personally, I think the syntax for subs is fine.

* I thought we decided that __call__(*args, **kwargs) should not
simply be subs(*args, **kwargs). The reason for this is that calling
a function is a different thing conceptually from calling a function.

The following will be very confusing to users that are used to
python's call syntax:

>>> f = x*y
>>> f(x,2)

* I thought we had also decided to implement an entirely different
approach to handling the positional part of args.

Do we need to have further discussion about what we all want to be
implemented, or do you just want to have a go at implementing the
original proposal.

Just for reference, here is how I picture the positional syntax looking:

>>> f = x*y
>>> f.bindings = (x,y) # This tells Basic how to order the *args part of __call__
>>> f(2,3)
6

>>> f.bindings = (y,x)
>>> f(2, z)
z*2

>>> f.bindings
(y, x)

>>> f.bindings = None
>>> f(2, 3)
this should raise an exception saying that symbols have to first be
bound to the positional arguments.

The name "bindings" could be something else, I just made that up. But
bindings would just be a property that the implementation of __call__
uses when it gets a positional argument.

I think this syntax is non-magic and explicit and also give people
what they expect.

Cheers,

Brian

Riccardo Gori

unread,
Nov 17, 2008, 4:34:19 PM11/17/08
to sympy-...@googlegroups.com

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

nnms

unread,
Nov 17, 2008, 5:25:51 PM11/17/08
to sympy-patches
> There is a problem:
>
> In [1]: t = Symbol("theta")
>
> In [2]: f = t*x
>
> In [3]: f(x=3,t=9)
> Out[3]: 3⋅θ

Not sure if this is a problem. Sympy knows about 'theta' but not about
t. If you print f you get:

>>> f
theta*x
>>> f(x=3, theta=9)
27

't' is the python variable name assigned, but 'theta' is the symbol
name.

-Lance

nnms

unread,
Nov 17, 2008, 6:00:17 PM11/17/08
to sympy-patches
On Nov 17, 12:32 pm, "Brian Granger" <ellisonbg....@gmail.com> wrote:
> This patch implements something quite different from what we had discussed:

Hi Brian,

The implementation I submitted was the implementation I suggested in
my original post.

> * We never discussed changing the implementation of subs.  I think
> that is a separate discussion and it outside of the scope of changing
> __call__.  Personally, I think the syntax for subs is fine.

You can still use subs the same way that you did before. All I did was
add an additional syntax:

f.subs(x=1,y=2)

It seems like this is a reasonable thing to do.

> * I thought we decided that __call__(*args, **kwargs) should not
> simply be subs(*args, **kwargs).  The reason for this is that calling
> a function is a different thing conceptually from calling a function.

I am not understanding what you are saying here (typo maybe). Could
you clarify.

> The following will be very confusing to users that are used to
> python's call syntax:
>
> >>> f = x*y
> >>> f(x,2)

It is possible that only allowing the syntax f(x=1,y=2) is preferable
rather than all things possible with subs. Having the shorthand f(...)
support the full capability of subs was just a preference I had in
implementing it. I am not too tied to the idea, but I think it could
be handy at times. Maybe requiring an explicit subs is better. What do
others think?

> * I thought we had also decided to implement an entirely different
> approach to handling the positional part of args.
>
> Do we need to have further discussion about what we all want to be
> implemented, or do you just want to have a go at implementing the
> original proposal.
>
> Just for reference, here is how I picture the positional syntax looking:
>
> >>> f = x*y
> >>> f.bindings = (x,y)  # This tells Basic how to order the *args part of __call__
> >>> f(2,3)
>
> 6
>
> >>> f.bindings = (y,x)
> >>> f(2, z)
>
> z*2
>
> >>> f.bindings
>
> (y, x)
>
> >>> f.bindings = None
> >>> f(2, 3)
>
> this should raise an exception saying that symbols have to first be
> bound to the positional arguments.
>
> The name "bindings" could be something else, I just made that up.  But
> bindings would just be a property that the implementation of __call__
> uses when it gets a positional argument.

I did note that you gave some suggestions of how one might implement
positional args and it seems like a reasonable method, but from the
thread it seemed like there were various objections to including an
implicit args implementation. Since it was easy to add explicit args,
and implicit args required more careful thought (on my part), I did
not include this. I think the patch I submitted handles explicit
substitution (i.e. f(x=1, y=2) ) as discussed on the thread.

If we do decide to support positional args, I would suggest making
this a part of the 'subs' method and forwarding from the __call__
method as done in the path. This makes for a consistent substitution
syntax. There may be a good reason for f(...) to behave differently
from f.subs(...), but I like the consistency of them behaving the same
way. However, I would like to hear more from those that had objections
to an implicit substitution syntax, particularly in regard to
including a 'bindings' method as suggested.

-Lance

Ondrej Certik

unread,
Nov 17, 2008, 6:14:17 PM11/17/08
to sympy-...@googlegroups.com

^^ 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

Ondrej Certik

unread,
Nov 17, 2008, 6:16:43 PM11/17/08
to sympy-...@googlegroups.com
Also I forgot (feel free to fix it after we discuss the other things):

>> # 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.

Ondrej Certik

unread,
Nov 17, 2008, 6:25:37 PM11/17/08
to sympy-...@googlegroups.com

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

Ondrej Certik

unread,
Nov 17, 2008, 6:29:31 PM11/17/08
to sympy-...@googlegroups.com

Riccardo Gori

unread,
Nov 17, 2008, 6:29:31 PM11/17/08
to sympy-...@googlegroups.com

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

nnms

unread,
Nov 17, 2008, 9:47:48 PM11/17/08
to sympy-patches
> ^^ why are you deleting the whitespace? I suggest to leave it as it was.

I looked at other comments and tried to make this fit the same pattern
(text starts immediately after """), but it doesn't really matter to
me how the whitespace is arranged.

> ^^^ why are you introducing the "Ignoring redefinition..." thing? We
> removed that recently -- maybe it is a leftover from a merge?

I'm actually not sure where the "Ignoring redefinion" came from. I
will review the patch again. I have not used the queueing stuff before
and was running into some problems, so I copied my code from one
repository to a freshly checked out one and something may have gotten
carried along I didn't expect.

> 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).

I was having trouble getting the username and email set correctly, so
I will have to play mercurial a bit more to figure out the appropriate
syntax. I'll review everyone's comments and see what I need to change.

-Lance

nnms

unread,
Nov 17, 2008, 10:01:11 PM11/17/08
to sympy-patches
> 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})

OK. I see better what you are saying. The fact that f({x:3, t:2})
behaves differently from f(x=3,t=2) is a little awkward. Note that
there is the same problem with f.subs({x:3, t:2}) and f.subs({'x':
3,'t':2}). These two do not behave the same. Unfortunately, I can't
think of a fix for this particular issue. The syntax f(x=2,t=3) always
passes the keys as strings. I'm not sure how you would get the
original object t. Same issue with f.subs({'x':3,'t':2}). I can't
think of a way for sympy to retrieve what t is in the original
function. However, I do think the issue is more one of a person
understanding python rather than a problem with the syntax.

-Lance

nnms

unread,
Nov 17, 2008, 10:24:00 PM11/17/08
to sympy-patches
"Lance -- so you propose to drop the .subs(old, new), and only use
this
syntax to substitute for positional arguments? -Ondrej"

Yes. My vote would be to drop the .subs(old new) syntax and have f
(...) behave the same as f.subs(...). That being said, I think this
really depends on how widely the syntax f.subs(old,new) was being
used. If it appears this was used frequently, changing this may be
more trouble than it is worth. In that case, f(...) may need to behave
a little differently from subs. I still like the idea of maintaining
compatibility as closely as possible.

I like Brian's explicit bindings idea. If people object to modifying
subs, bindings could just be supported in __call__, but as noted I
would prefer to keep the two types of calls compatible. I think it
would be more maintainable in the long run.

-Lance

Brian Granger

unread,
Nov 18, 2008, 1:20:25 AM11/18/08
to sympy-...@googlegroups.com
> 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.

I am +1 on subs(x=foo, y=bar) working in addition to the current
calling syntax. I had just missed that changing subs was also on the
table. The only reason to not support this is if we thought that subs
might later need to grow keyword arguments that control how subs
itself works. We used to use **kwargs in this exact manner in
IPython's parallel stuff. It ended up *killing* us because we
eventually needed to have keyword arguments other than the users ones.

Thus, if there is any chance at all that subs will need keyword
arguments eventually, we should not pursue this change. But, if that
is not an issue (I can't think of any keyword arguments off hand),
then I think it is a good choice and makes sense.

> as to __call__(), I think we should just support 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?

I need to give a little more background about my thinking on this issue...

For decades I was an avid Mathematica user. Mathematica treats
functions (their definition and calling syntax) *very* differently
from substitutions. From my experience with Mathematica, I have
concluded a few things about this:

* Function calls syntax should always do what people expect as the
notion of a function is strongly tied to standard mathematical
notation. Mathematica is great at this.

* Function calls and substitution *are* very different things and some
effort should be made to keep them separated.

* However, in Mathematica, I always get frustrated that functions and
substitutions are *so* different. I think it is important to have a
simple and fast way of going between using an expression as a function
and doing subs on it and Mathematica is a pain in this area.

Summary: a careful balance is needed. On the one hand, we should
keep substitutions and function calls separate. However, on the other
hand, we don't want them to be too different.

I _really_ like how subs works in Sympy and I think adding the
.subs(x=10,y=30) syntax will improve it even more. I also like having
a __call__ method that can be used by passing symbol names
explicitely, as in f(x=10, y=30). This big question is how to handle
the positional case. I can think of a few different approaches:

1. Have a simple way that you can tell a Basic instance how positional
arguments to __call__ should be interpreted. This is my .bindings
model:

>>> f = x*y
>>> f.bindings = (x, y)
>>> f(2,3) # f.bindings determines the order so this is f(x=2,y=3)

This is really nice as it is explicit and allows you take complex
expressions and quickly define an unambiguoug calling syntax for them:

>>> f=x*y*z*w
>>> f.bindings = (x,)
>>> f(10)
10*y*z*w

We don't have to use the name "bindings" and we could make it a function:

f.set_bindings
f.get_bindings

But, using a property seems more pythonic and lightweight to me.

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)

This is more like Mathematica's approach. I don't like this as much
as the resulting object is not merely a Basic object and you have to
keep track of both f and g. For me this solution has the pitfall of
Mathematica in this area.

What do people think?

Brian

> Ondrej
>
> >
>

Fredrik Johansson

unread,
Nov 18, 2008, 5:21:41 AM11/18/08
to sympy-...@googlegroups.com
On Tue, Nov 18, 2008 at 7:20 AM, Brian Granger <elliso...@gmail.com> wrote:

> 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

Vinzent Steinberg

unread,
Nov 18, 2008, 9:50:51 AM11/18/08
to sympy-patches


On 18 Nov., 07:20, "Brian Granger" <ellisonbg....@gmail.com> wrote:
[...]
>
> >>> f = x*y
> >>> f.bindings = (x, y)
> >>> f(2,3)    # f.bindings determines the order so this is f(x=2,y=3)
>
> This is really nice as it is explicit and allows you take complex
> expressions and quickly define an unambiguoug calling syntax for them:
>
> >>> f=x*y*z*w
> >>> f.bindings = (x,)
> >>> f(10)
>
> 10*y*z*w
>
> We don't have to use the name "bindings" and we could make it a function:
>
> f.set_bindings
> f.get_bindings
>
> But, using a property seems more pythonic and lightweight to me.
>
> 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)
[...]

What about this syntax?

>>> f(x, y) = x*y
>>> f(2, 3)
6

It's the closest way to mathematical notation I can think of.

Vinzent

Ondrej Certik

unread,
Nov 18, 2008, 9:56:47 AM11/18/08
to sympy-...@googlegroups.com

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

Lance Larsen

unread,
Nov 18, 2008, 10:44:51 AM11/18/08
to sympy-patches
> The only reason to not support this is if we thought that subs
> might later need to grow keyword arguments that control how subs
> itself works. We used to use **kwargs in this exact manner in
> IPython's parallel stuff. It ended up *killing* us because we
> eventually needed to have keyword arguments other than the users ones.

One to pass in options (if it is needed) is to make a special keyword
arg (like 'subs_options') that allows you to pass in options. Or for
each option you can name it ('subs_***'). Anyway, there are at least
some ways to get around this that don't seem too limiting if it
becomes necessary.

> 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)
>
> This is more like Mathematica's approach. I don't like this as much
> as the resulting object is not merely a Basic object and you have to
> keep track of both f and g. For me this solution has the pitfall of
> Mathematica in this area.
>
> What do people think?

Fredrik noted that the second syntax suggested by Brian is already
supported. Should we pursue a bindings method for the convenience
factor, or the the current Lambda notation good enough?

I was not aware of the Lambda class, and this does seem to satisfy the
need to specify positional arguments to me. It is slightly more
cumbersome than the bindings approach, but not too much so. Right
given the discussion so far I am thinking bindings may not add much
given what Lambda already provides.

-Lance

Lance Larsen

unread,
Nov 18, 2008, 11:21:08 PM11/18/08
to sympy-patches
A new patch was submitted which includes suggestions from this
thread.

http://groups.google.com/group/sympy-patches/browse_thread/thread/83c33c0aa8560475

Brian Granger

unread,
Nov 19, 2008, 12:29:01 AM11/19/08
to sympy-...@googlegroups.com
Sorry I have been so slow to follow this stuff up. This week has been
crazy with personal stuff...

> Fredrik noted that the second syntax suggested by Brian is already
> supported. Should we pursue a bindings method for the convenience
> factor, or the the current Lambda notation good enough?

> I was not aware of the Lambda class, and this does seem to satisfy the
> need to specify positional arguments to me. It is slightly more
> cumbersome than the bindings approach, but not too much so. Right
> given the discussion so far I am thinking bindings may not add much
> given what Lambda already provides.

I too was not aware of the Lambda stuff in sympy. While I think it is
good to have this, I do think the bindings approach has a number of
advantages, that make it a compelling addition:

* The bindings approach is much lighter weight (less cumbersome)
* You don't have to create two separate objects (the original
expression and the Lambda).
* With bindings, the type of object remains the same. For
implementing some types of algorithms, this could be important. For
instance, maybe I just wrote a function that does something with a
polynomial. But, if the polynomial arises from a Lambda, my method
won't see that the Lambda is actually just a polynomial. This is
exactly what I mean when I talk about functions and expressions being
*too* different in Mathematica.
* Bindings can also be created and used recursively:

>>> f = x*y*z
>>> f.bindings = (x,)
>>> g = f(10)
>>> g
10*y*z
>>> g.bindings = (y, z)
>>> g(2, 3)
60

and you still get a reasonable type at the end:

>>> type(g)
Mul

Even if you did make Lambdas work recursively in this manner, you
would end up with a nasty nested Lambda, rather than just a Mul. In
my experience with CASs this way of working where I start with a
multivariate expression and gradually evaluate different symbols in it
is a common usage pattern - and sometimes it makes more sense
conceptually to do this using a function call syntax.

Cheers,

Brian


>
> -Lance
> >
>

Vinzent Steinberg

unread,
Nov 19, 2008, 6:16:54 AM11/19/08
to sympy-patches
Yeah, that's true. We could use uglier alternatives:

>>> f(x, y) == x*y

or

>>> f[x,y] = x*y

But that's probably not a good idea, because it's basically abuse.


Vinzent

Riccardo Gori

unread,
Nov 19, 2008, 10:14:49 AM11/19/08
to sympy-...@googlegroups.com

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

Ondrej Certik

unread,
Nov 19, 2008, 8:21:06 PM11/19/08
to sympy-...@googlegroups.com

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

Riccardo Gori

unread,
Nov 20, 2008, 5:38:59 AM11/20/08
to sympy-...@googlegroups.com

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

Ondrej Certik

unread,
Nov 20, 2008, 6:27:28 AM11/20/08
to sympy-...@googlegroups.com

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

Lance Larsen

unread,
Nov 20, 2008, 11:00:48 AM11/20/08
to sympy-patches
I haven't used frames before, and I think Riccardo's trick is pretty
cool. I didn't have a problem with the simpler behavior of treating
the value as a string, but I do agree that it could lead to confusion
for people. I think good documentation and examples explaining the
originally suggested behavior would make a significant difference, but
it is better if things just behave intuitively so you don't have to
explain the unexpected. I would vote in favor or Riccardo's proposal.

-Lance

Ondrej Certik

unread,
Nov 20, 2008, 1:10:10 PM11/20/08
to sympy-...@googlegroups.com

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

Brian Granger

unread,
Nov 20, 2008, 3:03:26 PM11/20/08
to sympy-...@googlegroups.com
I am off to teach lab right now, but I will look this over later
today. Looks like a complication...

Brian

Brian Granger

unread,
Nov 21, 2008, 11:36:59 AM11/21/08
to sympy-patches
Ahhh, I didn't realize this problem was there.

> I don't like this behavior. And I don't like that we need to use a
> hack to fix it.

Me neither, playing with the frames is messy stuff
> 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

This is definitely the safest option and least magic.

> 2) only allow the f(x=3), but do not use the hack, thus it will not
> work for x = Symbol("a")

But this

> 3) allow both f(x=3) and use the hack.

> Which one do you choose?

Whew, this is a tough choice. My initial intuition says to go with
1. But, if we go that way, I would definitely want to implement
the .bindings approach as well. But, I am open to other options as
well.

Brian

> Ondrej

Brian Granger

unread,
Nov 21, 2008, 11:40:44 AM11/21/08
to sympy-patches
> 2) only allow the f(x=3), but do not use the hack, thus it will not
> work for x = Symbol("a")

But this...will confuse users. I often use variable names that differ
from the symbol.

> 3) allow both f(x=3) and use the hack.

The hack is...well, a hack so this isn't very appealing.

Ondrej Certik

unread,
Nov 25, 2008, 8:30:25 AM11/25/08
to sympy-...@googlegroups.com

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

Lance Larsen

unread,
Nov 25, 2008, 7:06:39 PM11/25/08
to sympy-patches
I think it worth sending to the main mailing list one way or another.
There has been a lot of discussion on the sympy-patches list which
probably doesn't have the visibility of the sympy list. I think the
problems with the different approaches have been covered here, so it
is worth hearing what other developers or users of sympy think. I am
still OK with the original idea of using parameters as strings. I like
the convenience of using f(x=1) to evaluate an expression. I believe
that reasonable documentation would help alleviate some of the
confusion. I don't have experience with using the kind of hack
suggested by Riccardo, so I don't know the pitfalls, but it was a
pretty cool way to get around the problem raise by Riccardo. However,
it is probably a good idea to get a better picture of what a more
general audience thinks about how to handle this.

-Lance

Ondrej Certik

unread,
Nov 26, 2008, 4:00:05 AM11/26/08
to sympy-...@googlegroups.com

Ok, let's bring it to the main list. I'll try to do it in the afternoon.

Ondrej

Ondrej Certik

unread,
Nov 28, 2008, 9:08:21 AM11/28/08
to sympy-...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages