[PEP draft 2] Adding new math operators

Tim Hochberg tim.hochberg at ieee.org
Wed Aug 9 21:26:19 EDT 2000


hzhu at localhost.localdomain (Huaiyu Zhu) writes:

> On Wed, 09 Aug 2000 23:01:45 GMT, Tim Hochberg <tim.hochberg at ieee.org> wrote:
> >
> >def elementandmatrixwise_multiply2(a, b, c):
> >   a, b, c = asarray(a, Float), asarray(b, Float), asarray(c, Float)
> >   return (a.M*b.M) * c
> 
> What if this happens in the middle of code?  Like
> 
> x = vector from matrix computaion
> y = vector from matrix computaion
> z = x ~+ y.T  # generating a 2d array for plotting
> mesh(z)
> 
> Presumably it would be
> z = asarray(x) + asarray(y.T)

Why wouldn't it just be:

z = (x.E + y.E).M

given that you already know that x and y are vectors of matrix type.

> It appears that this just admits that conversion by method call like .E is
> not generally feasible given Python's current type/class dichotomy. Or you
> have to assume anywhere you want this it's not going to be a scalar.

I don't think it has anything to do with the type/class split. It
would be perfectly possible to add E and M methods to scalars. It's
just unlikely that they are going to grow such methods to suit
us. Heck even I don't think that would be a good idea.

Values can be scalars, just not arbitrary scalars. I consider rank 0
arrays scalars and they would work fine. It's core python numeric
types that would not work (ints, floats and complexes). This means
that it's at least possible to design functions that always return
matrix/array type objects, returning rank(0) arrays when they want to
return a scalar. Objects returned from these functions would then 
play nicely with above notation.
 
> >Yes, but if you have calls from outside the module all bets are off. I
> >suppose it would be OK to assume all objects are of a given type in a
> >function if that function is only called from within the module, but
> >modules that are called from outside should generally have their
> >arguments checked and adjusted. However, given that you know what the
> >input types are going to be inside a module, the differences between
> >the .E and the ~* notation only come up in the scalar case discussed
> >above.
> 
> No, they come up whenver you want to do a different type of operation, and
> you have to decide whether you're going to leave the result in current
> flavor or cast it back to what's prevalent surrounding it (while you don't
> have enough information to decide).

What do you mean here? I think I always know whether I want my results
to be an array or a matrix. It seems the issue is whether casting
back to the original type becomes unwieldy and/or error prone.

> >The ~* notation does appear to have an advantage in this
> >case. For me, the advantages of checking things at the module
> >boundaries outweigh the extra line that's required, so I don't see
> >this as a big problem.
> 
> The issue at module or function boundaries is not important compared with
> what's inside (and what they return).  You might have to do things like
> 
> x = asarray(f(a)) +  asarray(f(b))  asarray(f(c))
> 
> because f happens to return the wrong flavor.  And if f can return numbers
> you can't use the f(a).E notation.

There are two issues here. One is the one involving scalars. This is a
genuine issue; I don't think it's as serious as you do given that f
can be designed to return its scalars as rank-0 arrays. Problems would
arise only when dealing with old or unfriendly code. It's a real issue
though.

The second issue, which I believe you've been referring to is what if
f returns an array when your in a matrix environment or vice-versa. In
this case, both notations are equally vulnerable (actually the .E
notation is slightly less vulerable, but not enough to really matter).

a * f()
a ~* f()

will both fail if f() doesn't have the prevailing type. Given that *
and ~* have opposite effects on arrays and matrices, mixed operations
had better not succeed.

a * f()
a.E * f().E
a.M * f().M

Of these only the first will fail, but that's essentially as bad as
the previous case. The point being that in either case you better have
a pretty good idea of what type a function will return when your calling
it or you should be using asarray in either case!

> >
> >In addition I still really don't like the fact that ~* and * could mean
> >opposite things depending on whether an object was an array or a
> >matrix. While this may rarely be a problem, when it is a problem it
> >could be confusing and hard to track down.
> 
> This would be a problem only if objects change flavor frequently.  Given the
> convenience of using ~op there's really no need to do that, at least no more
> than what is already the case now between NumPy and MatPy interfaces. 

This is also an issue if you don't know what type (array or matrix)
get's returned by function. I see this as very similar to the scalar issue.
 
> >> On the other hand, if the difference is in operands, you can't be sure the
> >> flavor of the objects in any big chunk of code if both operations exist,
> >> unless you set up a convention to always cast back to a given flavor after
> >> each operation. That's what I think was the reason someone came up with the
> >> "non-persistent-type" or "shadow" classes approach.  This is 40% of the
> >> issue (IMO).
> >
> >I understand the urge for shadow type operands, but I think they
> >introduce more problems than they solve. The rules for straightforward
> >conversions are, well, straightforward. I suspect that shadow type
> >operands would be either confusing, hard to implement fully, or
> >both. I addition, I think that disallowing mixed type operations would
> >catch the vast majority of the errors that could creep in through
> >when forgetting to cast back and forth.
> 
> In a lot of places, the operands are of very similar types.  If you forget
> to cast one of them, it's likely the you'll forget the other.  This is
> especially so if they are returned by functions.

If they're returned by functions both proposals fail equally! The ~
notation does _not_ help here. What the tilda notation does depends on
the types of the objects, and if they are both wrong, the ~operator
will happily do the wrong operation. Of course if both MatPy and NumPy
used the tilda operators to mean the same things, this would be a
different story, but as I understand the proposal, this is not the
case.

> The shadow type does introduce a lot of additional problems, but it at least
> ensures that you know the flavors of _all_ objects in a piece of code.  

As long as you check _all_ the objects at the boundaries and check
_all_ values returned by functions this is true.

> >The test here is looking at code. People need to start coughing up
> >real code that uses mixed type operations and start seeing whether
> >real functions look like in both notations. The only matrix operation
> >that I use with any regularity is dot, so mine would be pretty
> >boring. Anyone have any good examples?
> 
> Well, this would only be possible if you could specify where to use
> asarray(a) and a.E and so on.  That's the 55% of the issue. :-)

You don't need to write it up. Just supply the code and how you would
write it up in ~* notation. I'll write it up in .E notation and point
out the relevant pitfalls in the ~* version and then you can return
the favor.

-tim



More information about the Python-list mailing list