**Numerical Python Basics**

Pages: 1, **2**, 3

### NumPy and Python

Enough math review -- let's see how it looks in Numerical Python. The NumPy distribution brings to Python the concept of the multi-array. This is the ability to store sets of homogenous numbers in shaped containers. Sounds like a matrix, right? The multi-array (or array) can hold numeric values of any type (integers, floating point numbers, and complex values). The Numeric package also provides extensions to the mathematical functions to cover array arguments as well as scalar. In addition several other modules are provided (LinearAlgebra, FFT, RanLib and Matrix to name a few). We will use the LinearAlgebra module later to create an inverse.

NumPy provides for operations that extend beyond those defined in traditional matrix mathematics like we just reviewed. Although these operations are nonstandard, they make for more efficient computation in certain circumstances.

It is important for you to know that NumPy defines the operation of the binary operators '*' for multiplication and '/' for division to operate element-wise as in addition and subtraction (shape still matters, however). To perform traditional matrix multiplication or to create an inverse you will need to use explicit functions, `matrixmultiply()`

and `inverse()`

.

#### Creation of Matrices / Arrays

To use the features of NumPy you must import it:

`>>> `**from Numeric import *.**

Once the module has been imported, you generate an array with the `array()`

function. The `array()`

function takes two arguments, a tuple or list of values and an optional type code, e.g., Float or Int. The list may be nested to create a multi-dimensional array. The `array()`

function will create an array with the values and type specified. If no type is specified, the type of the array will be dependent on the type of the elements.

`>>> `**a=array((1,2))**
>>> **print a **
[1 2]

Creates an integer array since the values in the tuple were integers.

`>>> `**b=array((1,2),Float)**
>>> **print b**
[ 1. 2.]

creates a floating-point array using integer arguments. The floating-point type overrides the integer arguments.

`>>> `**c=array(((1,2),(3,4)))**
>>> **print c**
[[1 2]
[3 4]]

creates a multi-dimensional array using a multi-dimensional tuple.

#### Shape and Reshape

The shape attribute of an array is expressed as a tuple. To see the shape simply type

`>>> `**a=array((1,2))**
>>> **a.shape**
(2,)
>>> **c=array(((1,2),(3,4)))**
>>> **c.shape**
(2, 2)

Note that `a`

has only a single dimension (the shape tuple has only a single value) and that `c`

is multidimensional (two values in the shape tuple). By default, single dimensional arrays have only a length. The concept of rows and columns is not embodied in the structure of a single dimensional array. The shape of an array, however, can be altered via the `reshape()`

function. The reshape function takes two arguments: an array and a shape tuple.

`>>> `**a=array((1,2))**
>>> **a.shape**
(2,)
>>> **print a**
[1 2]
>>> **ma=reshape(a,(1,2))**
>>> **ma.shape**
(1, 2)
>>> **print ma**
[ [1 2]]
>>> **mb=reshape(a,(2,1))**
>>> **mb.shape**
(2, 1)
>>> **print mb**
[[1]
[2]]

The printout changes when you use reshape to give a single-dimensioned array a shape more like a traditional matrix. In the case of the 1-by-2 matrix `ma`

, the extra space shows the multidimensional nature of the matrix. Contrast the printing of `a`

with `ma`

. See the difference between the original and the reshaped form? The elements are the same, but the shape is different.

Elements are taken row-wise from the source array when we reshape this 2-by-2 array:

`>>> `**c=array(((1,2),(3,4)))**
>>> **c.shape**
(2, 2)
>>> **print c**
[[1 2]
[3 4]]
>>> **d=reshape(c,(1,4))**
>>> **d.shape**
(1, 4)
>>> **print d**
[ [1 2 3 4]]

#### Matrix Multiplication

Addition and subtraction work just like you would expect. Give them a try! But let's take a look at the trickier problem of multiplication. If you weren't paying attention to my earlier warning about multiplication and division, you might wonder what is happening in the next example. We create two arrays with different shapes. Using the default multiplication operation generates results that seem independent of the shape. Remember, in matrix multiplication shape is important. Only when we use the`matrixmultiply()`

do we get the results we expect.
`>>> `**ma**
array([ [1, 2]])
>>> **mb**
array([[1],
[2]])
>>> **ma*mb**
array([[1, 2],
[2, 4]])
>>> **mb*ma**
array([[1, 2],
[2, 4]])
>>> **matrixmultiply(ma,mb)**
array([ [5]])
>>> **matrixmultiply(mb,ma)**
array([[1, 2],
[2, 4]])

The multiplication operation is governed by actions called 'Pseudo Indices.' They really are useful. I will explain them in a later article. For now, stick with the `matrixmultiply()`

function to achieve the expected results.

#### The Matrix Inverse

To generate a matrix inverse, you simply use the `inverse()`

function found in the LinearAlgebra module supplied along with the basic NumPy. To gain access to the function, first perform an import.

`>>> `**From LinearAlgebra import ***

A simple example shows how to use the function

`>>> `**a=array(((3,2),(2,4)),Float)**
>>> **print a**
[[ 3. 2.]
[ 2. 4.]]
>>> **a_inv = inverse(a)**
>>> **print a_inv**
[[ 0.5 -0.25 ]
[-0.25 0.375]]

To check the result, multiply `a_inv`

by `a`

(which should give the identity matrix).

`>>> `**matrixmultiply(a_inv,a)**
array([[ 1.00000000e+000, 1.11022302e-016],
[ 0.00000000e+000, 1.00000000e+000]])