1006 lines
32 KiB
Python
1006 lines
32 KiB
Python
from ..libmp.backend import xrange
|
|
import warnings
|
|
|
|
# TODO: interpret list as vectors (for multiplication)
|
|
|
|
rowsep = '\n'
|
|
colsep = ' '
|
|
|
|
class _matrix(object):
|
|
"""
|
|
Numerical matrix.
|
|
|
|
Specify the dimensions or the data as a nested list.
|
|
Elements default to zero.
|
|
Use a flat list to create a column vector easily.
|
|
|
|
The datatype of the context (mpf for mp, mpi for iv, and float for fp) is used to store the data.
|
|
|
|
Creating matrices
|
|
-----------------
|
|
|
|
Matrices in mpmath are implemented using dictionaries. Only non-zero values
|
|
are stored, so it is cheap to represent sparse matrices.
|
|
|
|
The most basic way to create one is to use the ``matrix`` class directly.
|
|
You can create an empty matrix specifying the dimensions:
|
|
|
|
>>> from mpmath import *
|
|
>>> mp.dps = 15
|
|
>>> matrix(2)
|
|
matrix(
|
|
[['0.0', '0.0'],
|
|
['0.0', '0.0']])
|
|
>>> matrix(2, 3)
|
|
matrix(
|
|
[['0.0', '0.0', '0.0'],
|
|
['0.0', '0.0', '0.0']])
|
|
|
|
Calling ``matrix`` with one dimension will create a square matrix.
|
|
|
|
To access the dimensions of a matrix, use the ``rows`` or ``cols`` keyword:
|
|
|
|
>>> A = matrix(3, 2)
|
|
>>> A
|
|
matrix(
|
|
[['0.0', '0.0'],
|
|
['0.0', '0.0'],
|
|
['0.0', '0.0']])
|
|
>>> A.rows
|
|
3
|
|
>>> A.cols
|
|
2
|
|
|
|
You can also change the dimension of an existing matrix. This will set the
|
|
new elements to 0. If the new dimension is smaller than before, the
|
|
concerning elements are discarded:
|
|
|
|
>>> A.rows = 2
|
|
>>> A
|
|
matrix(
|
|
[['0.0', '0.0'],
|
|
['0.0', '0.0']])
|
|
|
|
Internally ``mpmathify`` is used every time an element is set. This
|
|
is done using the syntax A[row,column], counting from 0:
|
|
|
|
>>> A = matrix(2)
|
|
>>> A[1,1] = 1 + 1j
|
|
>>> A
|
|
matrix(
|
|
[['0.0', '0.0'],
|
|
['0.0', mpc(real='1.0', imag='1.0')]])
|
|
|
|
A more comfortable way to create a matrix lets you use nested lists:
|
|
|
|
>>> matrix([[1, 2], [3, 4]])
|
|
matrix(
|
|
[['1.0', '2.0'],
|
|
['3.0', '4.0']])
|
|
|
|
Convenient advanced functions are available for creating various standard
|
|
matrices, see ``zeros``, ``ones``, ``diag``, ``eye``, ``randmatrix`` and
|
|
``hilbert``.
|
|
|
|
Vectors
|
|
.......
|
|
|
|
Vectors may also be represented by the ``matrix`` class (with rows = 1 or cols = 1).
|
|
For vectors there are some things which make life easier. A column vector can
|
|
be created using a flat list, a row vectors using an almost flat nested list::
|
|
|
|
>>> matrix([1, 2, 3])
|
|
matrix(
|
|
[['1.0'],
|
|
['2.0'],
|
|
['3.0']])
|
|
>>> matrix([[1, 2, 3]])
|
|
matrix(
|
|
[['1.0', '2.0', '3.0']])
|
|
|
|
Optionally vectors can be accessed like lists, using only a single index::
|
|
|
|
>>> x = matrix([1, 2, 3])
|
|
>>> x[1]
|
|
mpf('2.0')
|
|
>>> x[1,0]
|
|
mpf('2.0')
|
|
|
|
Other
|
|
.....
|
|
|
|
Like you probably expected, matrices can be printed::
|
|
|
|
>>> print randmatrix(3) # doctest:+SKIP
|
|
[ 0.782963853573023 0.802057689719883 0.427895717335467]
|
|
[0.0541876859348597 0.708243266653103 0.615134039977379]
|
|
[ 0.856151514955773 0.544759264818486 0.686210904770947]
|
|
|
|
Use ``nstr`` or ``nprint`` to specify the number of digits to print::
|
|
|
|
>>> nprint(randmatrix(5), 3) # doctest:+SKIP
|
|
[2.07e-1 1.66e-1 5.06e-1 1.89e-1 8.29e-1]
|
|
[6.62e-1 6.55e-1 4.47e-1 4.82e-1 2.06e-2]
|
|
[4.33e-1 7.75e-1 6.93e-2 2.86e-1 5.71e-1]
|
|
[1.01e-1 2.53e-1 6.13e-1 3.32e-1 2.59e-1]
|
|
[1.56e-1 7.27e-2 6.05e-1 6.67e-2 2.79e-1]
|
|
|
|
As matrices are mutable, you will need to copy them sometimes::
|
|
|
|
>>> A = matrix(2)
|
|
>>> A
|
|
matrix(
|
|
[['0.0', '0.0'],
|
|
['0.0', '0.0']])
|
|
>>> B = A.copy()
|
|
>>> B[0,0] = 1
|
|
>>> B
|
|
matrix(
|
|
[['1.0', '0.0'],
|
|
['0.0', '0.0']])
|
|
>>> A
|
|
matrix(
|
|
[['0.0', '0.0'],
|
|
['0.0', '0.0']])
|
|
|
|
Finally, it is possible to convert a matrix to a nested list. This is very useful,
|
|
as most Python libraries involving matrices or arrays (namely NumPy or SymPy)
|
|
support this format::
|
|
|
|
>>> B.tolist()
|
|
[[mpf('1.0'), mpf('0.0')], [mpf('0.0'), mpf('0.0')]]
|
|
|
|
|
|
Matrix operations
|
|
-----------------
|
|
|
|
You can add and subtract matrices of compatible dimensions::
|
|
|
|
>>> A = matrix([[1, 2], [3, 4]])
|
|
>>> B = matrix([[-2, 4], [5, 9]])
|
|
>>> A + B
|
|
matrix(
|
|
[['-1.0', '6.0'],
|
|
['8.0', '13.0']])
|
|
>>> A - B
|
|
matrix(
|
|
[['3.0', '-2.0'],
|
|
['-2.0', '-5.0']])
|
|
>>> A + ones(3) # doctest:+ELLIPSIS
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: incompatible dimensions for addition
|
|
|
|
It is possible to multiply or add matrices and scalars. In the latter case the
|
|
operation will be done element-wise::
|
|
|
|
>>> A * 2
|
|
matrix(
|
|
[['2.0', '4.0'],
|
|
['6.0', '8.0']])
|
|
>>> A / 4
|
|
matrix(
|
|
[['0.25', '0.5'],
|
|
['0.75', '1.0']])
|
|
>>> A - 1
|
|
matrix(
|
|
[['0.0', '1.0'],
|
|
['2.0', '3.0']])
|
|
|
|
Of course you can perform matrix multiplication, if the dimensions are
|
|
compatible, using ``@`` (for Python >= 3.5) or ``*``. For clarity, ``@`` is
|
|
recommended (`PEP 465 <https://www.python.org/dev/peps/pep-0465/>`), because
|
|
the meaning of ``*`` is different in many other Python libraries such as NumPy.
|
|
|
|
>>> A @ B # doctest:+SKIP
|
|
matrix(
|
|
[['8.0', '22.0'],
|
|
['14.0', '48.0']])
|
|
>>> A * B # same as A @ B
|
|
matrix(
|
|
[['8.0', '22.0'],
|
|
['14.0', '48.0']])
|
|
>>> matrix([[1, 2, 3]]) * matrix([[-6], [7], [-2]])
|
|
matrix(
|
|
[['2.0']])
|
|
|
|
..
|
|
COMMENT: TODO: the above "doctest:+SKIP" may be removed as soon as we
|
|
have dropped support for Python 3.5 and below.
|
|
|
|
You can raise powers of square matrices::
|
|
|
|
>>> A**2
|
|
matrix(
|
|
[['7.0', '10.0'],
|
|
['15.0', '22.0']])
|
|
|
|
Negative powers will calculate the inverse::
|
|
|
|
>>> A**-1
|
|
matrix(
|
|
[['-2.0', '1.0'],
|
|
['1.5', '-0.5']])
|
|
>>> A * A**-1
|
|
matrix(
|
|
[['1.0', '1.0842021724855e-19'],
|
|
['-2.16840434497101e-19', '1.0']])
|
|
|
|
|
|
|
|
Matrix transposition is straightforward::
|
|
|
|
>>> A = ones(2, 3)
|
|
>>> A
|
|
matrix(
|
|
[['1.0', '1.0', '1.0'],
|
|
['1.0', '1.0', '1.0']])
|
|
>>> A.T
|
|
matrix(
|
|
[['1.0', '1.0'],
|
|
['1.0', '1.0'],
|
|
['1.0', '1.0']])
|
|
|
|
Norms
|
|
.....
|
|
|
|
Sometimes you need to know how "large" a matrix or vector is. Due to their
|
|
multidimensional nature it's not possible to compare them, but there are
|
|
several functions to map a matrix or a vector to a positive real number, the
|
|
so called norms.
|
|
|
|
For vectors the p-norm is intended, usually the 1-, the 2- and the oo-norm are
|
|
used.
|
|
|
|
>>> x = matrix([-10, 2, 100])
|
|
>>> norm(x, 1)
|
|
mpf('112.0')
|
|
>>> norm(x, 2)
|
|
mpf('100.5186549850325')
|
|
>>> norm(x, inf)
|
|
mpf('100.0')
|
|
|
|
Please note that the 2-norm is the most used one, though it is more expensive
|
|
to calculate than the 1- or oo-norm.
|
|
|
|
It is possible to generalize some vector norms to matrix norm::
|
|
|
|
>>> A = matrix([[1, -1000], [100, 50]])
|
|
>>> mnorm(A, 1)
|
|
mpf('1050.0')
|
|
>>> mnorm(A, inf)
|
|
mpf('1001.0')
|
|
>>> mnorm(A, 'F')
|
|
mpf('1006.2310867787777')
|
|
|
|
The last norm (the "Frobenius-norm") is an approximation for the 2-norm, which
|
|
is hard to calculate and not available. The Frobenius-norm lacks some
|
|
mathematical properties you might expect from a norm.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.__data = {}
|
|
# LU decompostion cache, this is useful when solving the same system
|
|
# multiple times, when calculating the inverse and when calculating the
|
|
# determinant
|
|
self._LU = None
|
|
if "force_type" in kwargs:
|
|
warnings.warn("The force_type argument was removed, it did not work"
|
|
" properly anyway. If you want to force floating-point or"
|
|
" interval computations, use the respective methods from `fp`"
|
|
" or `mp` instead, e.g., `fp.matrix()` or `iv.matrix()`."
|
|
" If you want to truncate values to integer, use .apply(int) instead.")
|
|
if isinstance(args[0], (list, tuple)):
|
|
if isinstance(args[0][0], (list, tuple)):
|
|
# interpret nested list as matrix
|
|
A = args[0]
|
|
self.__rows = len(A)
|
|
self.__cols = len(A[0])
|
|
for i, row in enumerate(A):
|
|
for j, a in enumerate(row):
|
|
# note: this will call __setitem__ which will call self.ctx.convert() to convert the datatype.
|
|
self[i, j] = a
|
|
else:
|
|
# interpret list as row vector
|
|
v = args[0]
|
|
self.__rows = len(v)
|
|
self.__cols = 1
|
|
for i, e in enumerate(v):
|
|
self[i, 0] = e
|
|
elif isinstance(args[0], int):
|
|
# create empty matrix of given dimensions
|
|
if len(args) == 1:
|
|
self.__rows = self.__cols = args[0]
|
|
else:
|
|
if not isinstance(args[1], int):
|
|
raise TypeError("expected int")
|
|
self.__rows = args[0]
|
|
self.__cols = args[1]
|
|
elif isinstance(args[0], _matrix):
|
|
A = args[0]
|
|
self.__rows = A._matrix__rows
|
|
self.__cols = A._matrix__cols
|
|
for i in xrange(A.__rows):
|
|
for j in xrange(A.__cols):
|
|
self[i, j] = A[i, j]
|
|
elif hasattr(args[0], 'tolist'):
|
|
A = self.ctx.matrix(args[0].tolist())
|
|
self.__data = A._matrix__data
|
|
self.__rows = A._matrix__rows
|
|
self.__cols = A._matrix__cols
|
|
else:
|
|
raise TypeError('could not interpret given arguments')
|
|
|
|
def apply(self, f):
|
|
"""
|
|
Return a copy of self with the function `f` applied elementwise.
|
|
"""
|
|
new = self.ctx.matrix(self.__rows, self.__cols)
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(self.__cols):
|
|
new[i,j] = f(self[i,j])
|
|
return new
|
|
|
|
def __nstr__(self, n=None, **kwargs):
|
|
# Build table of string representations of the elements
|
|
res = []
|
|
# Track per-column max lengths for pretty alignment
|
|
maxlen = [0] * self.cols
|
|
for i in range(self.rows):
|
|
res.append([])
|
|
for j in range(self.cols):
|
|
if n:
|
|
string = self.ctx.nstr(self[i,j], n, **kwargs)
|
|
else:
|
|
string = str(self[i,j])
|
|
res[-1].append(string)
|
|
maxlen[j] = max(len(string), maxlen[j])
|
|
# Patch strings together
|
|
for i, row in enumerate(res):
|
|
for j, elem in enumerate(row):
|
|
# Pad each element up to maxlen so the columns line up
|
|
row[j] = elem.rjust(maxlen[j])
|
|
res[i] = "[" + colsep.join(row) + "]"
|
|
return rowsep.join(res)
|
|
|
|
def __str__(self):
|
|
return self.__nstr__()
|
|
|
|
def _toliststr(self, avoid_type=False):
|
|
"""
|
|
Create a list string from a matrix.
|
|
|
|
If avoid_type: avoid multiple 'mpf's.
|
|
"""
|
|
# XXX: should be something like self.ctx._types
|
|
typ = self.ctx.mpf
|
|
s = '['
|
|
for i in xrange(self.__rows):
|
|
s += '['
|
|
for j in xrange(self.__cols):
|
|
if not avoid_type or not isinstance(self[i,j], typ):
|
|
a = repr(self[i,j])
|
|
else:
|
|
a = "'" + str(self[i,j]) + "'"
|
|
s += a + ', '
|
|
s = s[:-2]
|
|
s += '],\n '
|
|
s = s[:-3]
|
|
s += ']'
|
|
return s
|
|
|
|
def tolist(self):
|
|
"""
|
|
Convert the matrix to a nested list.
|
|
"""
|
|
return [[self[i,j] for j in range(self.__cols)] for i in range(self.__rows)]
|
|
|
|
def __repr__(self):
|
|
if self.ctx.pretty:
|
|
return self.__str__()
|
|
s = 'matrix(\n'
|
|
s += self._toliststr(avoid_type=True) + ')'
|
|
return s
|
|
|
|
def __get_element(self, key):
|
|
'''
|
|
Fast extraction of the i,j element from the matrix
|
|
This function is for private use only because is unsafe:
|
|
1. Does not check on the value of key it expects key to be a integer tuple (i,j)
|
|
2. Does not check bounds
|
|
'''
|
|
if key in self.__data:
|
|
return self.__data[key]
|
|
else:
|
|
return self.ctx.zero
|
|
|
|
def __set_element(self, key, value):
|
|
'''
|
|
Fast assignment of the i,j element in the matrix
|
|
This function is unsafe:
|
|
1. Does not check on the value of key it expects key to be a integer tuple (i,j)
|
|
2. Does not check bounds
|
|
3. Does not check the value type
|
|
4. Does not reset the LU cache
|
|
'''
|
|
if value: # only store non-zeros
|
|
self.__data[key] = value
|
|
elif key in self.__data:
|
|
del self.__data[key]
|
|
|
|
|
|
def __getitem__(self, key):
|
|
'''
|
|
Getitem function for mp matrix class with slice index enabled
|
|
it allows the following assingments
|
|
scalar to a slice of the matrix
|
|
B = A[:,2:6]
|
|
'''
|
|
# Convert vector to matrix indexing
|
|
if isinstance(key, int) or isinstance(key,slice):
|
|
# only sufficent for vectors
|
|
if self.__rows == 1:
|
|
key = (0, key)
|
|
elif self.__cols == 1:
|
|
key = (key, 0)
|
|
else:
|
|
raise IndexError('insufficient indices for matrix')
|
|
|
|
if isinstance(key[0],slice) or isinstance(key[1],slice):
|
|
|
|
#Rows
|
|
if isinstance(key[0],slice):
|
|
#Check bounds
|
|
if (key[0].start is None or key[0].start >= 0) and \
|
|
(key[0].stop is None or key[0].stop <= self.__rows+1):
|
|
# Generate indices
|
|
rows = xrange(*key[0].indices(self.__rows))
|
|
else:
|
|
raise IndexError('Row index out of bounds')
|
|
else:
|
|
# Single row
|
|
rows = [key[0]]
|
|
|
|
# Columns
|
|
if isinstance(key[1],slice):
|
|
# Check bounds
|
|
if (key[1].start is None or key[1].start >= 0) and \
|
|
(key[1].stop is None or key[1].stop <= self.__cols+1):
|
|
# Generate indices
|
|
columns = xrange(*key[1].indices(self.__cols))
|
|
else:
|
|
raise IndexError('Column index out of bounds')
|
|
|
|
else:
|
|
# Single column
|
|
columns = [key[1]]
|
|
|
|
# Create matrix slice
|
|
m = self.ctx.matrix(len(rows),len(columns))
|
|
|
|
# Assign elements to the output matrix
|
|
for i,x in enumerate(rows):
|
|
for j,y in enumerate(columns):
|
|
m.__set_element((i,j),self.__get_element((x,y)))
|
|
|
|
return m
|
|
|
|
else:
|
|
# single element extraction
|
|
if key[0] >= self.__rows or key[1] >= self.__cols:
|
|
raise IndexError('matrix index out of range')
|
|
if key in self.__data:
|
|
return self.__data[key]
|
|
else:
|
|
return self.ctx.zero
|
|
|
|
def __setitem__(self, key, value):
|
|
# setitem function for mp matrix class with slice index enabled
|
|
# it allows the following assingments
|
|
# scalar to a slice of the matrix
|
|
# A[:,2:6] = 2.5
|
|
# submatrix to matrix (the value matrix should be the same size as the slice size)
|
|
# A[3,:] = B where A is n x m and B is n x 1
|
|
# Convert vector to matrix indexing
|
|
if isinstance(key, int) or isinstance(key,slice):
|
|
# only sufficent for vectors
|
|
if self.__rows == 1:
|
|
key = (0, key)
|
|
elif self.__cols == 1:
|
|
key = (key, 0)
|
|
else:
|
|
raise IndexError('insufficient indices for matrix')
|
|
# Slice indexing
|
|
if isinstance(key[0],slice) or isinstance(key[1],slice):
|
|
# Rows
|
|
if isinstance(key[0],slice):
|
|
# Check bounds
|
|
if (key[0].start is None or key[0].start >= 0) and \
|
|
(key[0].stop is None or key[0].stop <= self.__rows+1):
|
|
# generate row indices
|
|
rows = xrange(*key[0].indices(self.__rows))
|
|
else:
|
|
raise IndexError('Row index out of bounds')
|
|
else:
|
|
# Single row
|
|
rows = [key[0]]
|
|
# Columns
|
|
if isinstance(key[1],slice):
|
|
# Check bounds
|
|
if (key[1].start is None or key[1].start >= 0) and \
|
|
(key[1].stop is None or key[1].stop <= self.__cols+1):
|
|
# Generate column indices
|
|
columns = xrange(*key[1].indices(self.__cols))
|
|
else:
|
|
raise IndexError('Column index out of bounds')
|
|
else:
|
|
# Single column
|
|
columns = [key[1]]
|
|
# Assign slice with a scalar
|
|
if isinstance(value,self.ctx.matrix):
|
|
# Assign elements to matrix if input and output dimensions match
|
|
if len(rows) == value.rows and len(columns) == value.cols:
|
|
for i,x in enumerate(rows):
|
|
for j,y in enumerate(columns):
|
|
self.__set_element((x,y), value.__get_element((i,j)))
|
|
else:
|
|
raise ValueError('Dimensions do not match')
|
|
else:
|
|
# Assign slice with scalars
|
|
value = self.ctx.convert(value)
|
|
for i in rows:
|
|
for j in columns:
|
|
self.__set_element((i,j), value)
|
|
else:
|
|
# Single element assingment
|
|
# Check bounds
|
|
if key[0] >= self.__rows or key[1] >= self.__cols:
|
|
raise IndexError('matrix index out of range')
|
|
# Convert and store value
|
|
value = self.ctx.convert(value)
|
|
if value: # only store non-zeros
|
|
self.__data[key] = value
|
|
elif key in self.__data:
|
|
del self.__data[key]
|
|
|
|
if self._LU:
|
|
self._LU = None
|
|
return
|
|
|
|
def __iter__(self):
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(self.__cols):
|
|
yield self[i,j]
|
|
|
|
def __mul__(self, other):
|
|
if isinstance(other, self.ctx.matrix):
|
|
# dot multiplication
|
|
if self.__cols != other.__rows:
|
|
raise ValueError('dimensions not compatible for multiplication')
|
|
new = self.ctx.matrix(self.__rows, other.__cols)
|
|
self_zero = self.ctx.zero
|
|
self_get = self.__data.get
|
|
other_zero = other.ctx.zero
|
|
other_get = other.__data.get
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(other.__cols):
|
|
new[i, j] = self.ctx.fdot((self_get((i,k), self_zero), other_get((k,j), other_zero))
|
|
for k in xrange(other.__rows))
|
|
return new
|
|
else:
|
|
# try scalar multiplication
|
|
new = self.ctx.matrix(self.__rows, self.__cols)
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(self.__cols):
|
|
new[i, j] = other * self[i, j]
|
|
return new
|
|
|
|
def __matmul__(self, other):
|
|
return self.__mul__(other)
|
|
|
|
def __rmul__(self, other):
|
|
# assume other is scalar and thus commutative
|
|
if isinstance(other, self.ctx.matrix):
|
|
raise TypeError("other should not be type of ctx.matrix")
|
|
return self.__mul__(other)
|
|
|
|
def __pow__(self, other):
|
|
# avoid cyclic import problems
|
|
#from linalg import inverse
|
|
if not isinstance(other, int):
|
|
raise ValueError('only integer exponents are supported')
|
|
if not self.__rows == self.__cols:
|
|
raise ValueError('only powers of square matrices are defined')
|
|
n = other
|
|
if n == 0:
|
|
return self.ctx.eye(self.__rows)
|
|
if n < 0:
|
|
n = -n
|
|
neg = True
|
|
else:
|
|
neg = False
|
|
i = n
|
|
y = 1
|
|
z = self.copy()
|
|
while i != 0:
|
|
if i % 2 == 1:
|
|
y = y * z
|
|
z = z*z
|
|
i = i // 2
|
|
if neg:
|
|
y = self.ctx.inverse(y)
|
|
return y
|
|
|
|
def __div__(self, other):
|
|
# assume other is scalar and do element-wise divison
|
|
assert not isinstance(other, self.ctx.matrix)
|
|
new = self.ctx.matrix(self.__rows, self.__cols)
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(self.__cols):
|
|
new[i,j] = self[i,j] / other
|
|
return new
|
|
|
|
__truediv__ = __div__
|
|
|
|
def __add__(self, other):
|
|
if isinstance(other, self.ctx.matrix):
|
|
if not (self.__rows == other.__rows and self.__cols == other.__cols):
|
|
raise ValueError('incompatible dimensions for addition')
|
|
new = self.ctx.matrix(self.__rows, self.__cols)
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(self.__cols):
|
|
new[i,j] = self[i,j] + other[i,j]
|
|
return new
|
|
else:
|
|
# assume other is scalar and add element-wise
|
|
new = self.ctx.matrix(self.__rows, self.__cols)
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(self.__cols):
|
|
new[i,j] += self[i,j] + other
|
|
return new
|
|
|
|
def __radd__(self, other):
|
|
return self.__add__(other)
|
|
|
|
def __sub__(self, other):
|
|
if isinstance(other, self.ctx.matrix) and not (self.__rows == other.__rows
|
|
and self.__cols == other.__cols):
|
|
raise ValueError('incompatible dimensions for subtraction')
|
|
return self.__add__(other * (-1))
|
|
|
|
def __pos__(self):
|
|
"""
|
|
+M returns a copy of M, rounded to current working precision.
|
|
"""
|
|
return (+1) * self
|
|
|
|
def __neg__(self):
|
|
return (-1) * self
|
|
|
|
def __rsub__(self, other):
|
|
return -self + other
|
|
|
|
def __eq__(self, other):
|
|
return self.__rows == other.__rows and self.__cols == other.__cols \
|
|
and self.__data == other.__data
|
|
|
|
def __len__(self):
|
|
if self.rows == 1:
|
|
return self.cols
|
|
elif self.cols == 1:
|
|
return self.rows
|
|
else:
|
|
return self.rows # do it like numpy
|
|
|
|
def __getrows(self):
|
|
return self.__rows
|
|
|
|
def __setrows(self, value):
|
|
for key in self.__data.copy():
|
|
if key[0] >= value:
|
|
del self.__data[key]
|
|
self.__rows = value
|
|
|
|
rows = property(__getrows, __setrows, doc='number of rows')
|
|
|
|
def __getcols(self):
|
|
return self.__cols
|
|
|
|
def __setcols(self, value):
|
|
for key in self.__data.copy():
|
|
if key[1] >= value:
|
|
del self.__data[key]
|
|
self.__cols = value
|
|
|
|
cols = property(__getcols, __setcols, doc='number of columns')
|
|
|
|
def transpose(self):
|
|
new = self.ctx.matrix(self.__cols, self.__rows)
|
|
for i in xrange(self.__rows):
|
|
for j in xrange(self.__cols):
|
|
new[j,i] = self[i,j]
|
|
return new
|
|
|
|
T = property(transpose)
|
|
|
|
def conjugate(self):
|
|
return self.apply(self.ctx.conj)
|
|
|
|
def transpose_conj(self):
|
|
return self.conjugate().transpose()
|
|
|
|
H = property(transpose_conj)
|
|
|
|
def copy(self):
|
|
new = self.ctx.matrix(self.__rows, self.__cols)
|
|
new.__data = self.__data.copy()
|
|
return new
|
|
|
|
__copy__ = copy
|
|
|
|
def column(self, n):
|
|
m = self.ctx.matrix(self.rows, 1)
|
|
for i in range(self.rows):
|
|
m[i] = self[i,n]
|
|
return m
|
|
|
|
class MatrixMethods(object):
|
|
|
|
def __init__(ctx):
|
|
# XXX: subclass
|
|
ctx.matrix = type('matrix', (_matrix,), {})
|
|
ctx.matrix.ctx = ctx
|
|
ctx.matrix.convert = ctx.convert
|
|
|
|
def eye(ctx, n, **kwargs):
|
|
"""
|
|
Create square identity matrix n x n.
|
|
"""
|
|
A = ctx.matrix(n, **kwargs)
|
|
for i in xrange(n):
|
|
A[i,i] = 1
|
|
return A
|
|
|
|
def diag(ctx, diagonal, **kwargs):
|
|
"""
|
|
Create square diagonal matrix using given list.
|
|
|
|
Example:
|
|
>>> from mpmath import diag, mp
|
|
>>> mp.pretty = False
|
|
>>> diag([1, 2, 3])
|
|
matrix(
|
|
[['1.0', '0.0', '0.0'],
|
|
['0.0', '2.0', '0.0'],
|
|
['0.0', '0.0', '3.0']])
|
|
"""
|
|
A = ctx.matrix(len(diagonal), **kwargs)
|
|
for i in xrange(len(diagonal)):
|
|
A[i,i] = diagonal[i]
|
|
return A
|
|
|
|
def zeros(ctx, *args, **kwargs):
|
|
"""
|
|
Create matrix m x n filled with zeros.
|
|
One given dimension will create square matrix n x n.
|
|
|
|
Example:
|
|
>>> from mpmath import zeros, mp
|
|
>>> mp.pretty = False
|
|
>>> zeros(2)
|
|
matrix(
|
|
[['0.0', '0.0'],
|
|
['0.0', '0.0']])
|
|
"""
|
|
if len(args) == 1:
|
|
m = n = args[0]
|
|
elif len(args) == 2:
|
|
m = args[0]
|
|
n = args[1]
|
|
else:
|
|
raise TypeError('zeros expected at most 2 arguments, got %i' % len(args))
|
|
A = ctx.matrix(m, n, **kwargs)
|
|
for i in xrange(m):
|
|
for j in xrange(n):
|
|
A[i,j] = 0
|
|
return A
|
|
|
|
def ones(ctx, *args, **kwargs):
|
|
"""
|
|
Create matrix m x n filled with ones.
|
|
One given dimension will create square matrix n x n.
|
|
|
|
Example:
|
|
>>> from mpmath import ones, mp
|
|
>>> mp.pretty = False
|
|
>>> ones(2)
|
|
matrix(
|
|
[['1.0', '1.0'],
|
|
['1.0', '1.0']])
|
|
"""
|
|
if len(args) == 1:
|
|
m = n = args[0]
|
|
elif len(args) == 2:
|
|
m = args[0]
|
|
n = args[1]
|
|
else:
|
|
raise TypeError('ones expected at most 2 arguments, got %i' % len(args))
|
|
A = ctx.matrix(m, n, **kwargs)
|
|
for i in xrange(m):
|
|
for j in xrange(n):
|
|
A[i,j] = 1
|
|
return A
|
|
|
|
def hilbert(ctx, m, n=None):
|
|
"""
|
|
Create (pseudo) hilbert matrix m x n.
|
|
One given dimension will create hilbert matrix n x n.
|
|
|
|
The matrix is very ill-conditioned and symmetric, positive definite if
|
|
square.
|
|
"""
|
|
if n is None:
|
|
n = m
|
|
A = ctx.matrix(m, n)
|
|
for i in xrange(m):
|
|
for j in xrange(n):
|
|
A[i,j] = ctx.one / (i + j + 1)
|
|
return A
|
|
|
|
def randmatrix(ctx, m, n=None, min=0, max=1, **kwargs):
|
|
"""
|
|
Create a random m x n matrix.
|
|
|
|
All values are >= min and <max.
|
|
n defaults to m.
|
|
|
|
Example:
|
|
>>> from mpmath import randmatrix
|
|
>>> randmatrix(2) # doctest:+SKIP
|
|
matrix(
|
|
[['0.53491598236191806', '0.57195669543302752'],
|
|
['0.85589992269513615', '0.82444367501382143']])
|
|
"""
|
|
if not n:
|
|
n = m
|
|
A = ctx.matrix(m, n, **kwargs)
|
|
for i in xrange(m):
|
|
for j in xrange(n):
|
|
A[i,j] = ctx.rand() * (max - min) + min
|
|
return A
|
|
|
|
def swap_row(ctx, A, i, j):
|
|
"""
|
|
Swap row i with row j.
|
|
"""
|
|
if i == j:
|
|
return
|
|
if isinstance(A, ctx.matrix):
|
|
for k in xrange(A.cols):
|
|
A[i,k], A[j,k] = A[j,k], A[i,k]
|
|
elif isinstance(A, list):
|
|
A[i], A[j] = A[j], A[i]
|
|
else:
|
|
raise TypeError('could not interpret type')
|
|
|
|
def extend(ctx, A, b):
|
|
"""
|
|
Extend matrix A with column b and return result.
|
|
"""
|
|
if not isinstance(A, ctx.matrix):
|
|
raise TypeError("A should be a type of ctx.matrix")
|
|
if A.rows != len(b):
|
|
raise ValueError("Value should be equal to len(b)")
|
|
A = A.copy()
|
|
A.cols += 1
|
|
for i in xrange(A.rows):
|
|
A[i, A.cols-1] = b[i]
|
|
return A
|
|
|
|
def norm(ctx, x, p=2):
|
|
r"""
|
|
Gives the entrywise `p`-norm of an iterable *x*, i.e. the vector norm
|
|
`\left(\sum_k |x_k|^p\right)^{1/p}`, for any given `1 \le p \le \infty`.
|
|
|
|
Special cases:
|
|
|
|
If *x* is not iterable, this just returns ``absmax(x)``.
|
|
|
|
``p=1`` gives the sum of absolute values.
|
|
|
|
``p=2`` is the standard Euclidean vector norm.
|
|
|
|
``p=inf`` gives the magnitude of the largest element.
|
|
|
|
For *x* a matrix, ``p=2`` is the Frobenius norm.
|
|
For operator matrix norms, use :func:`~mpmath.mnorm` instead.
|
|
|
|
You can use the string 'inf' as well as float('inf') or mpf('inf')
|
|
to specify the infinity norm.
|
|
|
|
**Examples**
|
|
|
|
>>> from mpmath import *
|
|
>>> mp.dps = 15; mp.pretty = False
|
|
>>> x = matrix([-10, 2, 100])
|
|
>>> norm(x, 1)
|
|
mpf('112.0')
|
|
>>> norm(x, 2)
|
|
mpf('100.5186549850325')
|
|
>>> norm(x, inf)
|
|
mpf('100.0')
|
|
|
|
"""
|
|
try:
|
|
iter(x)
|
|
except TypeError:
|
|
return ctx.absmax(x)
|
|
if type(p) is not int:
|
|
p = ctx.convert(p)
|
|
if p == ctx.inf:
|
|
return max(ctx.absmax(i) for i in x)
|
|
elif p == 1:
|
|
return ctx.fsum(x, absolute=1)
|
|
elif p == 2:
|
|
return ctx.sqrt(ctx.fsum(x, absolute=1, squared=1))
|
|
elif p > 1:
|
|
return ctx.nthroot(ctx.fsum(abs(i)**p for i in x), p)
|
|
else:
|
|
raise ValueError('p has to be >= 1')
|
|
|
|
def mnorm(ctx, A, p=1):
|
|
r"""
|
|
Gives the matrix (operator) `p`-norm of A. Currently ``p=1`` and ``p=inf``
|
|
are supported:
|
|
|
|
``p=1`` gives the 1-norm (maximal column sum)
|
|
|
|
``p=inf`` gives the `\infty`-norm (maximal row sum).
|
|
You can use the string 'inf' as well as float('inf') or mpf('inf')
|
|
|
|
``p=2`` (not implemented) for a square matrix is the usual spectral
|
|
matrix norm, i.e. the largest singular value.
|
|
|
|
``p='f'`` (or 'F', 'fro', 'Frobenius, 'frobenius') gives the
|
|
Frobenius norm, which is the elementwise 2-norm. The Frobenius norm is an
|
|
approximation of the spectral norm and satisfies
|
|
|
|
.. math ::
|
|
|
|
\frac{1}{\sqrt{\mathrm{rank}(A)}} \|A\|_F \le \|A\|_2 \le \|A\|_F
|
|
|
|
The Frobenius norm lacks some mathematical properties that might
|
|
be expected of a norm.
|
|
|
|
For general elementwise `p`-norms, use :func:`~mpmath.norm` instead.
|
|
|
|
**Examples**
|
|
|
|
>>> from mpmath import *
|
|
>>> mp.dps = 15; mp.pretty = False
|
|
>>> A = matrix([[1, -1000], [100, 50]])
|
|
>>> mnorm(A, 1)
|
|
mpf('1050.0')
|
|
>>> mnorm(A, inf)
|
|
mpf('1001.0')
|
|
>>> mnorm(A, 'F')
|
|
mpf('1006.2310867787777')
|
|
|
|
"""
|
|
A = ctx.matrix(A)
|
|
if type(p) is not int:
|
|
if type(p) is str and 'frobenius'.startswith(p.lower()):
|
|
return ctx.norm(A, 2)
|
|
p = ctx.convert(p)
|
|
m, n = A.rows, A.cols
|
|
if p == 1:
|
|
return max(ctx.fsum((A[i,j] for i in xrange(m)), absolute=1) for j in xrange(n))
|
|
elif p == ctx.inf:
|
|
return max(ctx.fsum((A[i,j] for j in xrange(n)), absolute=1) for i in xrange(m))
|
|
else:
|
|
raise NotImplementedError("matrix p-norm for arbitrary p")
|
|
|
|
if __name__ == '__main__':
|
|
import doctest
|
|
doctest.testmod()
|