|
from ..libmp.backend import xrange |
|
import warnings |
|
|
|
|
|
|
|
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 = {} |
|
|
|
|
|
|
|
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)): |
|
|
|
A = args[0] |
|
self.__rows = len(A) |
|
self.__cols = len(A[0]) |
|
for i, row in enumerate(A): |
|
for j, a in enumerate(row): |
|
|
|
self[i, j] = a |
|
else: |
|
|
|
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): |
|
|
|
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): |
|
|
|
res = [] |
|
|
|
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]) |
|
|
|
for i, row in enumerate(res): |
|
for j, elem in enumerate(row): |
|
|
|
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. |
|
""" |
|
|
|
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: |
|
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] |
|
''' |
|
|
|
if isinstance(key, int) or isinstance(key,slice): |
|
|
|
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): |
|
|
|
|
|
if isinstance(key[0],slice): |
|
|
|
if (key[0].start is None or key[0].start >= 0) and \ |
|
(key[0].stop is None or key[0].stop <= self.__rows+1): |
|
|
|
rows = xrange(*key[0].indices(self.__rows)) |
|
else: |
|
raise IndexError('Row index out of bounds') |
|
else: |
|
|
|
rows = [key[0]] |
|
|
|
|
|
if isinstance(key[1],slice): |
|
|
|
if (key[1].start is None or key[1].start >= 0) and \ |
|
(key[1].stop is None or key[1].stop <= self.__cols+1): |
|
|
|
columns = xrange(*key[1].indices(self.__cols)) |
|
else: |
|
raise IndexError('Column index out of bounds') |
|
|
|
else: |
|
|
|
columns = [key[1]] |
|
|
|
|
|
m = self.ctx.matrix(len(rows),len(columns)) |
|
|
|
|
|
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: |
|
|
|
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): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(key, int) or isinstance(key,slice): |
|
|
|
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): |
|
|
|
if isinstance(key[0],slice): |
|
|
|
if (key[0].start is None or key[0].start >= 0) and \ |
|
(key[0].stop is None or key[0].stop <= self.__rows+1): |
|
|
|
rows = xrange(*key[0].indices(self.__rows)) |
|
else: |
|
raise IndexError('Row index out of bounds') |
|
else: |
|
|
|
rows = [key[0]] |
|
|
|
if isinstance(key[1],slice): |
|
|
|
if (key[1].start is None or key[1].start >= 0) and \ |
|
(key[1].stop is None or key[1].stop <= self.__cols+1): |
|
|
|
columns = xrange(*key[1].indices(self.__cols)) |
|
else: |
|
raise IndexError('Column index out of bounds') |
|
else: |
|
|
|
columns = [key[1]] |
|
|
|
if isinstance(value,self.ctx.matrix): |
|
|
|
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: |
|
|
|
value = self.ctx.convert(value) |
|
for i in rows: |
|
for j in columns: |
|
self.__set_element((i,j), value) |
|
else: |
|
|
|
|
|
if key[0] >= self.__rows or key[1] >= self.__cols: |
|
raise IndexError('matrix index out of range') |
|
|
|
value = self.ctx.convert(value) |
|
if value: |
|
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): |
|
|
|
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: |
|
|
|
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): |
|
|
|
if isinstance(other, self.ctx.matrix): |
|
raise TypeError("other should not be type of ctx.matrix") |
|
return self.__mul__(other) |
|
|
|
def __pow__(self, other): |
|
|
|
|
|
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): |
|
|
|
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: |
|
|
|
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 |
|
|
|
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): |
|
|
|
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() |
|
|