[PYTHON MATRIX-SIG] ArrayTutorial.py

Hinsen Konrad hinsenk@ere.umontreal.ca
Tue, 26 Sep 1995 21:39:35 -0400


# This file illustrates the use of the the Array class.
#
# Send comments to Konrad Hinsen <hinsenk@ere.umontreal.ca>
#

from Array import *

######################################################################
# Arrays are constructed from (nested) lists:

a = Array(range(10))
b = Array([ [2,3,7], [9,8,2] ])
c = Array([ [ [4,5,6], [0,4,5] ], [ [1,6,5], [8,5,2] ] ])

# Scalars make arrays of rank 0:

s = Array(42)

# Array elements can be anything:

text_array = Array(['Hello', 'world'])

# Arrays can be printed:

print 'a:\n', a
print 'b:\n', b
print 'c:\n', c
print 's:\n', s

# shape() returns an array containing the dimensions of another array:

print 'shape(a):\n', shape(a)
print 'shape(b):\n', shape(b)
print 'shape(c):\n', shape(c)
print 'shape(s):\n', shape(s)


# Scalar functions act on each element of an array:

print 'sqrt(b):\n',sqrt(b)


# Binary operators likewise work elementwise:

print 'c+c:\n',c+c

# But you can also add a scalar:

print 'c+s:\n',c+s

# To understand the general rule for combining arrays of different
# shapes in a function, we need some more technical terms:
# The length of the shape vector of an array is called its rank.
# The elements of an array along the first axis are called items.
# The items of an array of rank N have rank N-1. More generally,
# the shape vector is divided into frames and cells. Frames and
# cells are not properties of an array, but describe ways of looking
# at an array. For example, a rank-3 array can be regarded as
# as just that - a single rank-3 array. It can also be regarded
# as a rank-1 frame of rank-2 cells, or as a rank-2 frame of
# rank-1 cells. Or even as a rank-3 array of rank-0 cells, i.e.
# scalar cells.
#
# When two arrays are to be added (or multiplied, or...), their
# shapes need not equal, but the lower-rank array must match
# an equal-rank cell of the higher-rank array. The lower-rank
# array will then be combined with each corresponding cell, and
# the result will have the shape of the higher-rank array.

print 'b+c:\n',b+c

# The addition of a scalar is just a special case: it has rank 0
# and therefore matches the rank-0 cells (scalar elements) of any array!

print 'b+s:\n',b+s


# All operators are also available as normal binary function,
# e.g. addition can be written as

print 'sum(a,s):\n',sum(a,s)

# You'll need this form to perform reductions, e.g. a sum
# of all items of an array:

print 'sum.over(a):\n',sum.over(a)

# Let's do it with a higher-rank array:

print 'product.over(b):\n',product.over(b)

# But how do you get the product along the second axis
# of b? Easy:

print 'product.over[1](b):\n',product.over[1](b)

# The [1] after the function name product.over modifies
# the functions rank. Function ranks are related to array
# ranks, in that a function of rank N operates on the
# N-cells of its argument. If the argument has a higher
# rank, the function is applied to each N-cell and the
# result is combined with the frame of the argument.
# In the last example, product.over will be
# called for each of the 1-cells of b, returning a
# rank-0 result for each, and the results will be
# collected in the 1-frame of b, producing as a net
# result an array of rank 1.
#
# All functions have ranks; if no rank is explicitly
# given, the default rank will be used. The default
# rank of all reductions is 'unbounded', which means
# that the function will operate on the highest-level
# cells possible. Many functions have unbounded rank,
# for example shape():

print 'shape(c):\n',shape(c)

# But of course you can modify the rank of shape():

print 'shape[1](c):\n',shape[1](c)
print 'shape[2](c):\n',shape[2](c)

# Functions with more than one argument can have a different
# rank for each. The rank is applied to each argument,
# defining its frames and cells for this purpose. The frames
# must match in the same way as indicated above for
# addition of two arrays. The function is then applied
# to the cells, and if appropriate, the same matching
# procedure is applied once again. This may seem confusing
# at first, but it is really just the application of a
# single principle everywhere!
#
# For example, let's take a (our rank-1 array) and add
# b (our rank-2 array) to each of a's 0-cells:

print 'sum[[0,None]](a,b):\n',sum[[0,None]](a,b)

# 'None' stands for 'unbounded'. Since the rank of sum is
# 0 for its first argument, a is divided into 1-frames
# and 0-cells. For b the rank is unbounded, so it is
# treated as a 0-frame with 2-cells. b's 0-frame matches
# a's 1-frame (a 0-frame matches everything!), and
# the result gets the 1-frame. The cells of the result
# are sums of a rank-0 array (element of a) and a rank-2
# array (b), i.e. rank-2 arrays by the matching rules
# given above. So the net total is a rank-3 array,
# as you have seen.


# From now on we will specify the default rank of each function.
# It should be noted that specifying a higher rank than the
# default rank has no effect on the function's behaviour. Only
# lower ranks make a difference.
#
# All the scalar mathematical functions (sqrt, sin, ...) have
# rank 0. The binary arithmetic functions (sum, product, ...)
# have unbounded rank for both argument. For structural functions
# (i.e. those that modify an array's shape rather than its
# elements), the rank varies. As we have seen, shape() is
# unbounded. The other structural functions are yet to be
# introduced:

# ravel() produces a rank-1 array containing all elements
# of its argument. It has unbounded rank:

print 'ravel(c):\n',ravel(c)

# reshape() allows you to change the shape of an array.
# It first obtains a rank-1 list of elements of its first
# argument (like ravel()) and then reassembles the
# elements into an array with the shape given by the
# second argument:

print 'reshape(a,[2,2]):\n',reshape(a,[2,2])
print 'reshape(b,[10]):\n',reshape(b,[10])

# As you have seen in the second case, reshape() reuses
# the elements of its arguments all over if they get
# exhausted.

# You may have noticed that in some examples, a nested list
# has been used instead of an array as a function argument.
# In general, all array functions will convert its arguments
# to arrays if necessary.

# Now we need a way to select parts of an array. You can
# use standard indexing and slicing to obtain items
# (i.e. N-1 cells for a rank-N array):

print 'c[1]:\n',c[1]
print 'a[3:7]:\n',a[3:7]

# You can also specify an array as the index and obtain an
# array of the corresponding items:

print 'a[[2,6,1]]:\n',a[[2,6,1]]

# The function take() does exactly the same:

print 'take(c,1):\n',take(c,1)

# You will have to use take() if you want to modify its
# ranks, which are [None,0] by default. There is little
# point in changing the second rank (it wouldn't make
# any difference), but changing the first rank lets you
# select along other dimensions than the first:

print 'take[1](c,0):\n',take[1](c,0)
print 'take[2](c,1):\n',take[2](c,1)

# Isn't there something wrong here? take() takes
# two arguments and therefore needs two ranks. But for
# convenience, only one rank must be given if all ranks
# are to be the same.

# And that's the end of this introduction. Stay tuned for
# updates of the array package that will provide some
# important still missing in this version. In the meantime,
# play round with what there is to get a feeling for
# how things work.


=================
MATRIX-SIG  - SIG on Matrix Math for Python

send messages to: matrix-sig@python.org
administrivia to: matrix-sig-request@python.org
=================