Greg's latest revision.

This commit is contained in:
Barry Warsaw 2000-11-22 22:01:47 +00:00
parent 6efa7422c1
commit 8daf85c43a
1 changed files with 141 additions and 168 deletions

View File

@ -11,73 +11,81 @@ Post-History:
Introduction Introduction
This PEP describes a proposal to add linear algebra operators to This PEP describes a conservative proposal to add linear algebra
Python 2.0. It discusses why such operators are desirable, and operators to Python 2.0. It discusses why such operators are
alternatives that have been considered and discarded. This PEP desirable, and why a minimalist approach should be adopted at this
summarizes discussions held in mailing list forums, and provides point. This PEP summarizes discussions held in mailing list
URLs for further information, where appropriate. The CVS revision forums, and provides URLs for further information, where
history of this file contains the definitive historical record. appropriate. The CVS revision history of this file contains the
definitive historical record.
Proposal Proposal
Add a single new infix binary operator '@' ("across"), and Add a single new infix binary operator '@' ("across"), and
corresponding special methods "__across__()" and "__racross__()". corresponding special methods "__across__()", "__racross__()", and
This operator will perform mathematical matrix multiplication on "__iacross__()". This operator will perform mathematical matrix
NumPy arrays, and generate cross-products when applied to built-in multiplication on NumPy arrays, and generate cross-products when
sequence types. No existing operator definitions will be changed. applied to built-in sequence types. No existing operator
definitions will be changed.
Background Background
Computers were invented to do arithmetic, as was the first The first high-level programming language, Fortran, was invented
high-level programming language, Fortran. While Fortran was a to do arithmetic. While this is now just a small part of
great advance on its machine-level predecessors, there was still a computing, there are still many programmers who need to express
very large gap between its syntax and the notation used by complex mathematical operations in code.
mathematicians. The most influential effort to close this gap was
APL [1]:
The language [APL] was invented by Kenneth E. Iverson while at The most influential of Fortran's successors was APL [1]. Its
Harvard University. The language, originally titled "Iverson author, Kenneth Iverson, designed the language as a notation for
Notation", was designed to overcome the inherent ambiguities expressing matrix algebra, and received the 1980 Turing Award for
and points of confusion found when dealing with standard his work.
mathematical notation. It was later described in 1962 in a
book simply titled "A Programming Language" (hence APL).
Towards the end of the sixties, largely through the efforts of
IBM, the computer community gained its first exposure to
APL. Iverson received the Turing Award in 1980 for this work.
APL's operators supported both familiar algebraic operations, such APL's operators supported both familiar algebraic operations, such
as vector dot product and matrix multiplication, and a wide range as vector dot product and matrix multiplication, and a wide range
of structural operations, such as stitching vectors together to of structural operations, such as stitching vectors together to
create arrays. Its notation was exceptionally cryptic: many of create arrays. Even by programming's standards, APL is
its symbols did not exist on standard keyboards, and expressions exceptionally cryptic: many of its symbols did not exist on
had to be read right to left. standard keyboards, and expressions have to be read right to left.
Most subsequent work on numerical languages, such as Fortran-90, Most subsequent work numerical languages, such as Fortran-90,
MATLAB, and Mathematica, has tried to provide the power of APL MATLAB, and Mathematica, have tried to provide the power of APL
without the obscurity. Python's NumPy [2] has most of the without the obscurity. Python's NumPy [2] has most of the
features that users of such languages expect, but these are features that users of such languages expect, but these are
provided through named functions and methods, rather than provided through named functions and methods, rather than
overloaded operators. This makes NumPy clumsier than its overloaded operators. This makes NumPy clumsier than most
competitors. alternatives.
One way to make NumPy more competitive is to provide greater The author of this PEP therefore consulted the developers of GNU
syntactic support in Python itself for linear algebra. This Octave [3], an open source clone of MATLAB. When asked how
proposal therefore examines the requirements that new linear important it was to have infix operators for matrix solution,
algebra operators in Python must satisfy, and proposes a syntax Prof. James Rawlings replied [4]:
and semantics for those operators.
I DON'T think it's a must have, and I do a lot of matrix
inversion. I cannot remember if its A\b or b\A so I always
write inv(A)*b instead. I recommend dropping \.
Rawlings' feedback on other operators was similar. It is worth
noting in this context that notations such as "/" and "\" for
matrix solution were invented by programmers, not mathematicians,
and have not been adopted by the latter.
Based on this discussion, and feedback from classes at the US
national laboratories and elsewhere, we recommend only adding a
matrix multiplication operator to Python at this time. If there
is significant user demand for syntactic support for other
operations, these can be added in a later release.
Requirements Requirements
The most important requirement is that there be minimal impact on The most important requirement is minimal impact on existing
the existing definition of Python. The proposal must not break Python programs and users: the proposal must not break existing
existing programs, except possibly those that use NumPy. code (except possibly NumPy).
The second most important requirement is to be able to do both The second most important requirement is the ability to handle all
elementwise and mathematical matrix multiplication using infix common cases cleanly and clearly. There are nine such cases:
notation. The nine cases that must be handled are:
|5 6| * 9 = |45 54| MS: matrix-scalar multiplication |5 6| * 9 = |45 54| MS: matrix-scalar multiplication
|7 8| |63 72| |7 8| |63 72|
@ -108,60 +116,23 @@ Requirements
Note that 1-dimensional vectors are treated as rows in VM, as Note that 1-dimensional vectors are treated as rows in VM, as
columns in MV, and as both in VD and VO. Both are special cases columns in MV, and as both in VD and VO. Both are special cases
of 2-dimensional matrices (Nx1 and 1xN respectively). It may of 2-dimensional matrices (Nx1 and 1xN respectively). We will
therefore be reasonable to define the new operator only for therefore define the new operator only for 2-dimensional arrays,
2-dimensional arrays, and provide an easy (and efficient) way for and provide an easy (and efficient) way for users to treat
users to convert 1-dimensional structures to 2-dimensional. 1-dimensional structures as 2-dimensional.
Behavior of a new multiplication operator for built-in types may
then:
(a) be a parsing error (possible only if a constant is one of the Third, we must avoid confusion between Python's notation and those
arguments, since names are untyped in Python); of MATLAB and Fortran-90. In particular, mathematical matrix
multiplication (case MM) should not be represented as '.*', since:
(b) generate a runtime error; or
(c) be derived by plausible extension from its behavior in the
two-dimensional case.
Third, syntactic support should be considered for three other
operations:
T
(a) transposition: A => A[j, i] for A[i, j]
-1
(b) inverse: A => A' such that A' * A = I (the identity matrix)
(c) solution: A/b => x such that A * x = b
A\b => x such that x * A = b
With regard to (c), it is worth noting that the two syntaxes used
were invented by programmers, not mathematicians. Mathematicians
do not have a standard, widely-used notation for matrix solution.
It is also worth noting that dozens of matrix inversion and
solution algorithms are widely used. MATLAB and its kin bind
their inversion and/or solution operators to one which is
reasonably robust in most cases, and require users to call
functions or methods to access others.
Fourth, confusion between Python's notation and those of MATLAB
and Fortran-90 should be avoided. In particular, mathematical
matrix multiplication (case MM) should not be represented as '.*',
since:
(a) MATLAB uses prefix-'.' forms to mean 'elementwise', and raw (a) MATLAB uses prefix-'.' forms to mean 'elementwise', and raw
forms to mean "mathematical" [4]; and forms to mean "mathematical"; and
(b) even if the Python parser can be taught how to handle dotted (b) even if the Python parser can be taught how to handle dotted
forms, '1.*A' will still be visually ambiguous [4]. forms, '1.*A' will still be visually ambiguous.
One anti-requirement is that new operators are not needed for
addition, subtraction, bitwise operations, and so on, since
mathematicians already treat them elementwise.
Proposal: Proposal
The meanings of all existing operators will be unchanged. In The meanings of all existing operators will be unchanged. In
particular, 'A*B' will continue to be interpreted elementwise. particular, 'A*B' will continue to be interpreted elementwise.
@ -169,14 +140,13 @@ Proposal:
minimal impact on existing programs. minimal impact on existing programs.
A new operator '@' (pronounced "across") will be added to Python, A new operator '@' (pronounced "across") will be added to Python,
along with two special methods, "__across__()" and along with special methods "__across__()", "__racross__()", and
"__racross__()", with the usual semantics. "__iacross__()", with the usual semantics.
NumPy will overload "@" to perform mathematical multiplication of NumPy will overload "@" to perform mathematical multiplication of
arrays where shapes permit, and to throw an exception otherwise. arrays where shapes permit, and to throw an exception otherwise.
The matrix class's implementation of "@" will treat built-in Its implementation of "@" will treat built-in sequence types as if
sequence types as if they were column vectors. This takes care of they were column vectors. This takes care of the cases MM and MV.
the cases MM and MV.
An attribute "T" will be added to the NumPy array type, such that An attribute "T" will be added to the NumPy array type, such that
"m.T" is: "m.T" is:
@ -204,22 +174,22 @@ Proposal:
operator, such that "A ** inv" is the inverse of "A". This is operator, such that "A ** inv" is the inverse of "A". This is
similar in spirit to NumPy's existing "newaxis" value. similar in spirit to NumPy's existing "newaxis" value.
(Optional) When applied to sequences, the operator will return a (Optional) When applied to sequences, the "@" operator will return
list of tuples containing the cross-product of their elements in a tuple of tuples containing the cross-product of their elements
left-to-right order: in left-to-right order:
>>> [1, 2] @ (3, 4) >>> [1, 2] @ (3, 4)
[(1, 3), (1, 4), (2, 3), (2, 4)] ((1, 3), (1, 4), (2, 3), (2, 4))
>>> [1, 2] @ (3, 4) @ (5, 6) >>> [1, 2] @ (3, 4) @ (5, 6)
[(1, 3, 5), (1, 3, 6), ((1, 3, 5), (1, 3, 6),
(1, 4, 5), (1, 4, 6), (1, 4, 5), (1, 4, 6),
(2, 3, 5), (2, 3, 6), (2, 3, 5), (2, 3, 6),
(2, 4, 5), (2, 4, 6)] (2, 4, 5), (2, 4, 6))
This will require the same kind of special support from the parser This will require the same kind of special support from the parser
as chained comparisons (such as "a<b<c<=d"). However, it would as chained comparisons (such as "a<b<c<=d"). However, it will
permit the following: permit:
>>> for (i, j) in [1, 2] @ [3, 4]: >>> for (i, j) in [1, 2] @ [3, 4]:
>>> print i, j >>> print i, j
@ -240,44 +210,48 @@ Proposal:
as implementing single-stage nesting). as implementing single-stage nesting).
Alternatives: Alternatives
01. Don't add new operators --- stick to functions and methods. 01. Don't add new operators.
Python is not primarily a numerical language. It is not worth Python is not primarily a numerical language; it may not be worth
complexifying the language for this special case --- NumPy's complexifying it for this special case. NumPy's success is proof
success is proof that users can and will use functions and methods that users can and will use functions and methods for linear
for linear algebra. algebra. However, support for real matrix multiplication is
frequently requested, as:
On the positive side, this maintains Python's simplicity. Its * functional forms are cumbersome for lengthy formulas, and do not
weakness is that support for real matrix multiplication (and, to a respect the operator precedence rules of conventional mathematics;
lesser extent, other linear algebra operations) is frequently and
requested, as functional forms are cumbersome for lengthy
formulas, and do not respect the operator precedence rules of
conventional mathematics. In addition, the method form is
asymmetric in its operands.
02. Introduce prefixed forms of existing operators, such as "@*" * method forms are asymmetric in their operands.
or "~*", or used boxed forms, such as "[*]" or "%*%".
There are (at least) three objections to this. First, either form What's more, the proposed semantics for "@" for built-in sequence
seems to imply that all operators exist in both forms. This is types would simplify expression of a very common idiom (nested
more new entities than the problem merits, and would require the loops). User testing during discussion of 'lockstep loops'
addition of many new overloadable methods, such as __at_mul__. indicated that both new and experienced users would understand
this immediately.
Second, while it is certainly possible to invent semantics for 02. Introduce prefixed forms of all existing operators, such as
these new operators for built-in types, this would be a case of "~*" and "~+", as proposed in PEP 0225 [7].
the tail wagging the dog, i.e. of letting the existence of a
feature "create" a need for it.
Finally, the boxed forms make human parsing more complex, e.g.: This proposal would duplicate all built-in mathematical operators
with matrix equivalents, as in numerical languages such as
MATLAB. Our objections to this are:
A[*] = B vs. A[:] = B * Python is not primarily a numerical programming language. While
the (self-selected) participants in the discussions that led to
PEP 0225 may want all of these new operators, the majority of
Python users would be indifferent. The extra complexity they
would introduce into the language therefore does not seem
merited. (See also Rawlings' comments, quoted in the Background
section, about these operators not being essential.)
03. (From Moshe Zadka [7], and also considered by Huaiyu Zhou [8] * The proposed syntax is difficult to read (i.e. passes the "low
in his proposal [9]) Retain the existing meaning of all toner" readability test).
operators, but create a behavioral accessor for arrays, such
that: 03. Retain the existing meaning of all operators, but create a
behavioral accessor for arrays, such that:
A * B A * B
@ -289,42 +263,23 @@ Alternatives:
return an object that aliased A's memory (for efficiency), but return an object that aliased A's memory (for efficiency), but
which had a different implementation of __mul__(). which had a different implementation of __mul__().
The advantage of this method is that it has no effect on the This proposal was made by Moshe Zadka, and is also considered by
PEP 0225 [7]. Its advantage is that it has no effect on the
existing implementation of Python: changes are localized in the existing implementation of Python: changes are localized in the
Numeric module. The disadvantages are: Numeric module. The disadvantages are
(a) The semantics of "A.m() * B", "A + B.m()", and so on would * The semantics of "A.m() * B", "A + B.m()", and so on would have
have to be defined, and there is no "obvious" choice for them. to be defined, and there is no "obvious" choice for them.
(b) Aliasing objects to trigger different operator behavior feels * Aliasing objects to trigger different operator behavior feels
less Pythonic than either calling methods (as in the existing less Pythonic than either calling methods (as in the existing
Numeric module) or using a different operator. This PEP is Numeric module) or using a different operator. This PEP is
primarily about look and feel, and about making Python more primarily about look and feel, and about making Python more
attractive to people who are not already using it. attractive to people who are not already using it.
04. (From a proposal [9] by Huaiyu Zhou [8]) Introduce a "delayed
inverse" attribute, similar to the "transpose" attribute
advocated in the third part of this proposal. The expression
"a.I" would be a delayed handle on the inverse of the matrix
"a", which would be evaluated in context as required. For
example, "a.I * b" and "b * a.I" would solve sets of linear
equations, without actually calculating the inverse.
The main drawback of this proposal it is reliance on lazy
evaluation, and even more on "smart" lazy evaluation (i.e. the
operation performed depends on the context in which the evaluation
is done). The BDFL has so far resisted introducing LE into
Python.
Related Proposals Related Proposals
0203 : Augmented Assignments
If new operators for linear algebra are introduced, it may
make sense to introduce augmented assignment forms for
them.
0207 : Rich Comparisons 0207 : Rich Comparisons
It may become possible to overload comparison operators It may become possible to overload comparison operators
@ -336,24 +291,42 @@ Related Proposals
Multidimensional arrays are currently an extension to Multidimensional arrays are currently an extension to
Python, rather than a built-in type. Python, rather than a built-in type.
0225 : Elementwise/Objectwise Operators
Acknowledgments: A (much) larger proposal that addresses the same subject.
Acknowledgments
I am grateful to Huaiyu Zhu [8] for initiating this discussion, I am grateful to Huaiyu Zhu [8] for initiating this discussion,
and for some of the ideas and terminology included below. and for some of the ideas and terminology included below.
References: References
[1] http://www.acm.org/sigapl/whyapl.htm [1] http://www.acm.org/sigapl/whyapl.htm
[2] http://numpy.sourceforge.net [2] http://numpy.sourceforge.net
[3] PEP-0203.txt "Augmented Assignments". [3] http://bevo.che.wisc.edu/octave/
[4] http://bevo.che.wisc.edu/octave/doc/octave_9.html#SEC69 [4] http://www.egroups.com/message/python-numeric/4
[5] http://www.python.org/pipermail/python-dev/2000-July/013139.html [5] http://www.python.org/pipermail/python-dev/2000-July/013139.html
[6] PEP-0201.txt "Lockstep Iteration" [6] PEP-0201.txt "Lockstep Iteration"
[7] Moshe Zadka is 'moshez@math.huji.ac.il'. [7] http://www.python.org/pipermail/python-list/2000-August/112529.html
[8] Huaiyu Zhu is 'hzhu@users.sourceforge.net'
[9] http://www.python.org/pipermail/python-list/2000-August/112529.html
Appendix: Other Operations
We considered syntactic support for three other operations:
T
(a) transposition: A => A[j, i] for A[i, j]
-1
(b) inverse: A => A' such that A' * A = I (the identity matrix)
(c) solution: A/b => x such that A * x = b
A\b => x such that x * A = b
Local Variables: Local Variables: