# Source code for nutils.numeric

```# -*- coding: utf8 -*-
#
# Module NUMERIC
#
# Part of Nutils: open source numerical utilities for Python. Jointly developed
# by HvZ Computational Engineering, TU/e Multiscale Engineering Fluid Dynamics,
# and others. More info at http://nutils.org <info@nutils.org>. (c) 2014

"""
The numeric module provides methods that are lacking from the numpy module. An
accompanying extension module _numeric.c should be compiled to benefit from
extra performance, although a Python-only implementation is provided as
fallback. A warning message is printed if the extension module is not found.
"""

import numpy, warnings

try:
from _numeric import _contract, SaneArray
except:
warnings.warn( '''Failed to load _numeric module.
Falling back on equivalent python implementation. THIS
MAY SEVERELY IMPACT PERFORMANCE! Pleace compile the C
extensions by running 'make' in the nutils directory.''', stacklevel=2 )
def _contract( A, B, axes ):
assert A.shape == B.shape and axes > 0
return ((A*B).reshape(A.shape[:-axes]+(-1,))).sum(-1)

[docs]def normdim( ndim, n ):
'check bounds and make positive'

assert isinstance(ndim,int) and ndim >= 0, 'ndim must be positive integer, got %s' % ndim
if n < 0:
n += ndim
assert 0 <= n < ndim, 'argument out of bounds: %s not in [0,%s)' % (n,ndim)
return n

[docs]def align( arr, trans, ndim ):
'''create new array of ndim from arr with axes moved accordin
to trans'''

# as_strided will check validity of trans
arr = numpy.asarray( arr )
trans = numpy.asarray( trans, dtype=int )
assert len(trans) == arr.ndim
strides = numpy.zeros( ndim, dtype=int )
strides[trans] = arr.strides
shape = numpy.ones( ndim, dtype=int )
shape[trans] = arr.shape
return numpy.lib.stride_tricks.as_strided( arr, shape, strides )

[docs]def get( arr, axis, item ):
'take single item from array axis'

arr = numpy.asarray( arr )
axis = normdim( arr.ndim, axis )
return arr[ (slice(None),) * axis + (item,) ]

[docs]def expand( arr, *shape ):
'expand'

newshape = list( arr.shape )
for i, sh in enumerate( shape ):
if sh != None:
if newshape[i-len(shape)] == 1:
newshape[i-len(shape)] = sh
else:
assert newshape[i-len(shape)] == sh
expanded = numpy.empty( newshape )
expanded[:] = arr
return expanded

[docs]def linspace2d( start, stop, steps ):
'linspace & meshgrid combined'

start = tuple(start) if isinstance(start,(list,tuple)) else (start,start)
stop  = tuple(stop ) if isinstance(stop, (list,tuple)) else (stop, stop )
steps = tuple(steps) if isinstance(steps,(list,tuple)) else (steps,steps)
assert len(start) == len(stop) == len(steps) == 2
values = numpy.empty( (2,)+steps )
values[0] = numpy.linspace( start[0], stop[0], steps[0] )[:,numpy.newaxis]
values[1] = numpy.linspace( start[1], stop[1], steps[1] )[numpy.newaxis,:]
return values

[docs]def contract( A, B, axis=-1 ):
'contract'

A = numpy.asarray( A, dtype=float )
B = numpy.asarray( B, dtype=float )

n = B.ndim - A.ndim
if n > 0:
Ashape = list(B.shape[:n]) + list(A.shape)
Astrides = [0]*n + list(A.strides)
Bshape = list(B.shape)
Bstrides = list(B.strides)
elif n < 0:
n = -n
Ashape = list(A.shape)
Astrides = list(A.strides)
Bshape = list(A.shape[:n]) + list(B.shape)
Bstrides = [0]*n + list(B.strides)
else:
Ashape = list(A.shape)
Astrides = list(A.strides)
Bshape = list(B.shape)
Bstrides = list(B.strides)

shape = Ashape
nd = len(Ashape)
for i in range( n, nd ):
if Ashape[i] == 1:
shape[i] = Bshape[i]
Astrides[i] = 0
elif Bshape[i] == 1:
Bstrides[i] = 0
else:
assert Ashape[i] == Bshape[i]

if isinstance( axis, int ):
axis = axis,
axis = sorted( [ ax+nd if ax < 0 else ax for ax in axis ], reverse=True )
for ax in axis:
assert 0 <= ax < nd, 'invalid contraction axis'
shape.append( shape.pop(ax) )
Astrides.append( Astrides.pop(ax) )
Bstrides.append( Bstrides.pop(ax) )

A = numpy.lib.stride_tricks.as_strided( A, shape, Astrides )
B = numpy.lib.stride_tricks.as_strided( B, shape, Bstrides )

if not A.size:
return numpy.zeros( A.shape[:-len(axis)] )

return _contract( A, B, len(axis) )

[docs]def contract_fast( A, B, naxes ):
'contract last n axes'

A = numpy.asarray( A, dtype=float )
B = numpy.asarray( B, dtype=float )

n = B.ndim - A.ndim
if n > 0:
Ashape = list(B.shape[:n]) + list(A.shape)
Astrides = [0]*n + list(A.strides)
Bshape = list(B.shape)
Bstrides = list(B.strides)
elif n < 0:
n = -n
Ashape = list(A.shape)
Astrides = list(A.strides)
Bshape = list(A.shape[:n]) + list(B.shape)
Bstrides = [0]*n + list(B.strides)
else:
Ashape = list(A.shape)
Astrides = list(A.strides)
Bshape = list(B.shape)
Bstrides = list(B.strides)

shape = list(Ashape)
for i in range( len(Ashape) ):
if Ashape[i] == 1:
shape[i] = Bshape[i]
Astrides[i] = 0
elif Bshape[i] == 1:
Bstrides[i] = 0
else:
assert Ashape[i] == Bshape[i]

A = numpy.lib.stride_tricks.as_strided( A, shape, Astrides )
B = numpy.lib.stride_tricks.as_strided( B, shape, Bstrides )

if not A.size:
return numpy.zeros( shape[:-naxes] )

return _contract( A, B, naxes )

[docs]def dot( A, B, axis=-1 ):
'''Transform axis of A by contraction with first axis of B and inserting
remaining axes. Note: with default axis=-1 this leads to multiplication of
vectors and matrices following linear algebra conventions.'''

A = numpy.asarray( A, dtype=float )
B = numpy.asarray( B, dtype=float )

if axis < 0:
axis += A.ndim
assert 0 <= axis < A.ndim

if A.shape[axis] == 1 or B.shape[0] == 1:
return A.sum(axis)[(slice(None),)*axis+(numpy.newaxis,)*(B.ndim-1)] \
* B.sum(0)[(Ellipsis,)+(numpy.newaxis,)*(A.ndim-1-axis)]

assert A.shape[axis] == B.shape[0]

if B.ndim != 1 or axis != A.ndim-1:
shape = A.shape[:axis] + B.shape[1:] + A.shape[axis+1:] + A.shape[axis:axis+1]
Astrides = A.strides[:axis] + (0,) * (B.ndim-1) + A.strides[axis+1:] + A.strides[axis:axis+1]
A = numpy.lib.stride_tricks.as_strided( A, shape, Astrides )

if A.ndim > 1:
Bstrides = (0,) * axis + B.strides[1:] + (0,) * (A.ndim-B.ndim-axis) + B.strides[:1]
B = numpy.lib.stride_tricks.as_strided( B, A.shape, Bstrides )

if not A.size:
return numpy.zeros( A.shape[:-1] )

return _contract( A, B, 1 )

[docs]def fastrepeat( A, nrepeat, axis=-1 ):
'repeat axis by 0stride'

A = numpy.asarray( A )
assert A.shape[axis] == 1
shape = list( A.shape )
shape[axis] = nrepeat
strides = list( A.strides )
strides[axis] = 0
return numpy.lib.stride_tricks.as_strided( A, shape, strides )

[docs]def fastmeshgrid( X, Y ):
'mesh grid based on fastrepeat'

return fastrepeat(X[numpy.newaxis,:],len(Y),axis=0), fastrepeat(Y[:,numpy.newaxis],len(X),axis=1)

[docs]def meshgrid( *args ):
'multi-dimensional meshgrid generalisation'

args = map( numpy.asarray, args )
shape = [ len(args) ] + [ arg.size for arg in args if arg.ndim ]
grid = numpy.empty( shape )
n = len(shape)-1
for i, arg in enumerate( args ):
if arg.ndim:
n -= 1
grid[i] = arg[(slice(None),)+(numpy.newaxis,)*n]
else:
grid[i] = arg
assert n == 0
return grid

[docs]def appendaxes( A, shape ):
'append axes by 0stride'

shape = (shape,) if isinstance(shape,int) else tuple(shape)
A = numpy.asarray( A )
return numpy.lib.stride_tricks.as_strided( A, A.shape + shape, A.strides + (0,)*len(shape) )

def takediag( A ):
if A.shape[-1] == 1:
shape = A.shape[:-1]
strides = A.strides[:-1]
elif A.shape[-2] == 1:
shape = A.shape[:-2] + A.shape[-1:]
strides = A.strides[:-2] + A.strides[-1:]
else:
assert A.shape[-1] == A.shape[-2]
shape = A.shape[:-1]
strides = A.strides[:-2] + (A.strides[-2]+A.strides[-1],)
return numpy.lib.stride_tricks.as_strided( A, shape, strides )

[docs]def inverse( A ):
'linearized inverse'

assert isinstance( A, numpy.ndarray )
assert A.shape[-2] == A.shape[-1]
if A.shape[-1] == 1:
Ainv = numpy.reciprocal( A )
elif A.shape[-1] == 2:
det = A[...,0,0] * A[...,1,1] - A[...,0,1] * A[...,1,0]
Ainv = numpy.empty( A.shape )
numpy.divide( A[...,1,1],  det, Ainv[...,0,0] )
numpy.divide( A[...,0,0],  det, Ainv[...,1,1] )
numpy.divide( A[...,1,0], -det, Ainv[...,1,0] )
numpy.divide( A[...,0,1], -det, Ainv[...,0,1] )
else:
Ainv = numpy.empty( A.shape )
for I in numpy.lib.index_tricks.ndindex( A.shape[:-2] ):
Ainv[I] = numpy.linalg.inv( A[I] )
return Ainv

[docs]def determinant( A ):
'determinant'

assert isinstance( A, numpy.ndarray )
assert A.shape[-2] == A.shape[-1]
if A.shape[-1] == 1:
det = A[...,0,0]
elif A.shape[-1] == 2:
det = A[...,0,0] * A[...,1,1] - A[...,0,1] * A[...,1,0]
else:
det = numpy.empty( A.shape[:-2] )
for I in numpy.lib.index_tricks.ndindex( A.shape[:-2] ):
det[I] = numpy.linalg.det( A[I] )
return det

[docs]def eig( A, sort=False ):
''' eig
Compute the eigenvalues and vectors of a hermitian matrix
sort   -1/0/1 -> descending / unsorted / ascending
'''
assert isinstance( A, numpy.ndarray )
assert A.shape[-2] == A.shape[-1]

sort = int(sort)

eigval = numpy.empty( A.shape[:-1] )
eigvec = numpy.empty( A.shape )

for I in numpy.lib.index_tricks.ndindex( A.shape[:-2] ):
val, vec = numpy.linalg.eig( A[I] )
if sort != 0:
idx = val.argsort()
val = val[idx[::sort]]
vec = vec[:,idx[::sort]]
eigval[I] = val
eigvec[I] = vec

return (eigval, eigvec)

[docs]def eigh( A, sort=False ):
''' eigh
Compute the eigenvalues and vectors of a hermitian matrix
sort   -1/0/1 -> descending / unsorted / ascending
'''
assert isinstance( A, numpy.ndarray )
assert A.shape[-2] == A.shape[-1]

sort = int(sort)

eigval = numpy.empty( A.shape[:-1] )
eigvec = numpy.empty( A.shape )

for I in numpy.lib.index_tricks.ndindex( A.shape[:-2] ):
val, vec = numpy.linalg.eigh( A[I] )
if sort != 0:
idx = val.argsort()
val = val[idx[::sort]]
vec = vec[:,idx[::sort]]
eigval[I] = val
eigvec[I] = vec

return (eigval, eigvec)

[docs]def reshape( A, *shape ):
'more useful reshape'

newshape = []
i = 0
for s in shape:
if isinstance( s, (tuple,list) ):
assert numpy.product( s ) == A.shape[i]
newshape.extend( s )
i += 1
elif s == 1:
newshape.append( A.shape[i] )
i += 1
else:
assert s > 1
newshape.append( numpy.product( A.shape[i:i+s] ) )
i += s
assert i <= A.ndim
newshape.extend( A.shape[i:] )
return A.reshape( newshape )

[docs]def mean( A, weights=None, axis=-1 ):
'generalized mean'

return A.mean( axis ) if weights is None else dot( A, weights / weights.sum(), axis )

[docs]def norm2( A, axis=-1 ):
'L2 norm over specified axis'

return numpy.asarray( numpy.sqrt( contract( A, A, axis ) ) )

[docs]def normalize( A, axis=-1 ):
'devide by normal'

s = [ slice(None) ] * A.ndim
s[axis] = numpy.newaxis
return A / norm2( A, axis )[ tuple(s) ]

[docs]def cross( v1, v2, axis ):
'cross product'

if v1.ndim < v2.ndim:
v1 = v1[ (numpy.newaxis,)*(v2.ndim-v1.ndim) ]
elif v2.ndim < v1.ndim:
v2 = v2[ (numpy.newaxis,)*(v1.ndim-v2.ndim) ]
return numpy.cross( v1, v2, axis=axis )

[docs]def stack( arrays, axis=0 ):
'powerful array stacker with singleton expansion'

arrays = [ numpy.asarray(array,dtype=float) for array in arrays ]
shape = [1] * max( array.ndim for array in arrays )
axis = normdim( len(shape)+1, axis )
for array in arrays:
for i in range(-array.ndim,0):
if shape[i] == 1:
shape[i] = array.shape[i]
else:
assert array.shape[i] in ( shape[i], 1 )
stacked = numpy.empty( shape[:axis]+[len(arrays)]+shape[axis:], dtype=float )
for i, arr in enumerate( arrays ):
stacked[(slice(None),)*axis+(i,)] = arr
return stacked

[docs]def bringforward( arg, axis ):
'bring axis forward'

arg = numpy.asarray(arg)
axis = normdim(arg.ndim,axis)
if axis == 0:
return arg
return arg.transpose( [axis] + range(axis) + range(axis+1,arg.ndim) )

[docs]def diagonalize( arg ):
'append axis, place last axis on diagonal of self and new'

diagonalized = numpy.zeros( arg.shape + (arg.shape[-1],) )
takediag( diagonalized )[:] = arg
return diagonalized

def check_equal_wrapper( f1, f2 ):
def f12( *args ):
v1 = f1( *args )
v2 = f2( *args )
numpy.testing.assert_array_almost_equal( v1, v2 )
return v1
return f12

# vim:shiftwidth=2:foldmethod=indent:foldnestmax=2
```