| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824 |
- """
- Module for the ddm_* routines for operating on a matrix in list of lists
- matrix representation.
- These routines are used internally by the DDM class which also provides a
- friendlier interface for them. The idea here is to implement core matrix
- routines in a way that can be applied to any simple list representation
- without the need to use any particular matrix class. For example we can
- compute the RREF of a matrix like:
- >>> from sympy.polys.matrices.dense import ddm_irref
- >>> M = [[1, 2, 3], [4, 5, 6]]
- >>> pivots = ddm_irref(M)
- >>> M
- [[1.0, 0.0, -1.0], [0, 1.0, 2.0]]
- These are lower-level routines that work mostly in place.The routines at this
- level should not need to know what the domain of the elements is but should
- ideally document what operations they will use and what functions they need to
- be provided with.
- The next-level up is the DDM class which uses these routines but wraps them up
- with an interface that handles copying etc and keeps track of the Domain of
- the elements of the matrix:
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.matrices.ddm import DDM
- >>> M = DDM([[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]], (2, 3), QQ)
- >>> M
- [[1, 2, 3], [4, 5, 6]]
- >>> Mrref, pivots = M.rref()
- >>> Mrref
- [[1, 0, -1], [0, 1, 2]]
- """
- from __future__ import annotations
- from operator import mul
- from .exceptions import (
- DMShapeError,
- DMDomainError,
- DMNonInvertibleMatrixError,
- DMNonSquareMatrixError,
- )
- from typing import Sequence, TypeVar
- from sympy.polys.matrices._typing import RingElement
- #: Type variable for the elements of the matrix
- T = TypeVar('T')
- #: Type variable for the elements of the matrix that are in a ring
- R = TypeVar('R', bound=RingElement)
- def ddm_transpose(matrix: Sequence[Sequence[T]]) -> list[list[T]]:
- """matrix transpose"""
- return list(map(list, zip(*matrix)))
- def ddm_iadd(a: list[list[R]], b: Sequence[Sequence[R]]) -> None:
- """a += b"""
- for ai, bi in zip(a, b):
- for j, bij in enumerate(bi):
- ai[j] += bij
- def ddm_isub(a: list[list[R]], b: Sequence[Sequence[R]]) -> None:
- """a -= b"""
- for ai, bi in zip(a, b):
- for j, bij in enumerate(bi):
- ai[j] -= bij
- def ddm_ineg(a: list[list[R]]) -> None:
- """a <-- -a"""
- for ai in a:
- for j, aij in enumerate(ai):
- ai[j] = -aij
- def ddm_imul(a: list[list[R]], b: R) -> None:
- """a <-- a*b"""
- for ai in a:
- for j, aij in enumerate(ai):
- ai[j] = aij * b
- def ddm_irmul(a: list[list[R]], b: R) -> None:
- """a <-- b*a"""
- for ai in a:
- for j, aij in enumerate(ai):
- ai[j] = b * aij
- def ddm_imatmul(
- a: list[list[R]], b: Sequence[Sequence[R]], c: Sequence[Sequence[R]]
- ) -> None:
- """a += b @ c"""
- cT = list(zip(*c))
- for bi, ai in zip(b, a):
- for j, cTj in enumerate(cT):
- ai[j] = sum(map(mul, bi, cTj), ai[j])
- def ddm_irref(a, _partial_pivot=False):
- """In-place reduced row echelon form of a matrix.
- Compute the reduced row echelon form of $a$. Modifies $a$ in place and
- returns a list of the pivot columns.
- Uses naive Gauss-Jordan elimination in the ground domain which must be a
- field.
- This routine is only really suitable for use with simple field domains like
- :ref:`GF(p)`, :ref:`QQ` and :ref:`QQ(a)` although even for :ref:`QQ` with
- larger matrices it is possibly more efficient to use fraction free
- approaches.
- This method is not suitable for use with rational function fields
- (:ref:`K(x)`) because the elements will blowup leading to costly gcd
- operations. In this case clearing denominators and using fraction free
- approaches is likely to be more efficient.
- For inexact numeric domains like :ref:`RR` and :ref:`CC` pass
- ``_partial_pivot=True`` to use partial pivoting to control rounding errors.
- Examples
- ========
- >>> from sympy.polys.matrices.dense import ddm_irref
- >>> from sympy import QQ
- >>> M = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]]
- >>> pivots = ddm_irref(M)
- >>> M
- [[1, 0, -1], [0, 1, 2]]
- >>> pivots
- [0, 1]
- See Also
- ========
- sympy.polys.matrices.domainmatrix.DomainMatrix.rref
- Higher level interface to this routine.
- ddm_irref_den
- The fraction free version of this routine.
- sdm_irref
- A sparse version of this routine.
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Row_echelon_form#Reduced_row_echelon_form
- """
- # We compute aij**-1 below and then use multiplication instead of division
- # in the innermost loop. The domain here is a field so either operation is
- # defined. There are significant performance differences for some domains
- # though. In the case of e.g. QQ or QQ(x) inversion is free but
- # multiplication and division have the same cost so it makes no difference.
- # In cases like GF(p), QQ<sqrt(2)>, RR or CC though multiplication is
- # faster than division so reusing a precomputed inverse for many
- # multiplications can be a lot faster. The biggest win is QQ<a> when
- # deg(minpoly(a)) is large.
- #
- # With domains like QQ(x) this can perform badly for other reasons.
- # Typically the initial matrix has simple denominators and the
- # fraction-free approach with exquo (ddm_irref_den) will preserve that
- # property throughout. The method here causes denominator blowup leading to
- # expensive gcd reductions in the intermediate expressions. With many
- # generators like QQ(x,y,z,...) this is extremely bad.
- #
- # TODO: Use a nontrivial pivoting strategy to control intermediate
- # expression growth. Rearranging rows and/or columns could defer the most
- # complicated elements until the end. If the first pivot is a
- # complicated/large element then the first round of reduction will
- # immediately introduce expression blowup across the whole matrix.
- # a is (m x n)
- m = len(a)
- if not m:
- return []
- n = len(a[0])
- i = 0
- pivots = []
- for j in range(n):
- # Proper pivoting should be used for all domains for performance
- # reasons but it is only strictly needed for RR and CC (and possibly
- # other domains like RR(x)). This path is used by DDM.rref() if the
- # domain is RR or CC. It uses partial (row) pivoting based on the
- # absolute value of the pivot candidates.
- if _partial_pivot:
- ip = max(range(i, m), key=lambda ip: abs(a[ip][j]))
- a[i], a[ip] = a[ip], a[i]
- # pivot
- aij = a[i][j]
- # zero-pivot
- if not aij:
- for ip in range(i+1, m):
- aij = a[ip][j]
- # row-swap
- if aij:
- a[i], a[ip] = a[ip], a[i]
- break
- else:
- # next column
- continue
- # normalise row
- ai = a[i]
- aijinv = aij**-1
- for l in range(j, n):
- ai[l] *= aijinv # ai[j] = one
- # eliminate above and below to the right
- for k, ak in enumerate(a):
- if k == i or not ak[j]:
- continue
- akj = ak[j]
- ak[j] -= akj # ak[j] = zero
- for l in range(j+1, n):
- ak[l] -= akj * ai[l]
- # next row
- pivots.append(j)
- i += 1
- # no more rows?
- if i >= m:
- break
- return pivots
- def ddm_irref_den(a, K):
- """a <-- rref(a); return (den, pivots)
- Compute the fraction-free reduced row echelon form (RREF) of $a$. Modifies
- $a$ in place and returns a tuple containing the denominator of the RREF and
- a list of the pivot columns.
- Explanation
- ===========
- The algorithm used is the fraction-free version of Gauss-Jordan elimination
- described as FFGJ in [1]_. Here it is modified to handle zero or missing
- pivots and to avoid redundant arithmetic.
- The domain $K$ must support exact division (``K.exquo``) but does not need
- to be a field. This method is suitable for most exact rings and fields like
- :ref:`ZZ`, :ref:`QQ` and :ref:`QQ(a)`. In the case of :ref:`QQ` or
- :ref:`K(x)` it might be more efficient to clear denominators and use
- :ref:`ZZ` or :ref:`K[x]` instead.
- For inexact domains like :ref:`RR` and :ref:`CC` use ``ddm_irref`` instead.
- Examples
- ========
- >>> from sympy.polys.matrices.dense import ddm_irref_den
- >>> from sympy import ZZ, Matrix
- >>> M = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)]]
- >>> den, pivots = ddm_irref_den(M, ZZ)
- >>> M
- [[-3, 0, 3], [0, -3, -6]]
- >>> den
- -3
- >>> pivots
- [0, 1]
- >>> Matrix(M).rref()[0]
- Matrix([
- [1, 0, -1],
- [0, 1, 2]])
- See Also
- ========
- ddm_irref
- A version of this routine that uses field division.
- sdm_irref
- A sparse version of :func:`ddm_irref`.
- sdm_rref_den
- A sparse version of :func:`ddm_irref_den`.
- sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
- Higher level interface.
- References
- ==========
- .. [1] Fraction-free algorithms for linear and polynomial equations.
- George C. Nakos , Peter R. Turner , Robert M. Williams.
- https://dl.acm.org/doi/10.1145/271130.271133
- """
- #
- # A simpler presentation of this algorithm is given in [1]:
- #
- # Given an n x n matrix A and n x 1 matrix b:
- #
- # for i in range(n):
- # if i != 0:
- # d = a[i-1][i-1]
- # for j in range(n):
- # if j == i:
- # continue
- # b[j] = a[i][i]*b[j] - a[j][i]*b[i]
- # for k in range(n):
- # a[j][k] = a[i][i]*a[j][k] - a[j][i]*a[i][k]
- # if i != 0:
- # a[j][k] /= d
- #
- # Our version here is a bit more complicated because:
- #
- # 1. We use row-swaps to avoid zero pivots.
- # 2. We allow for some columns to be missing pivots.
- # 3. We avoid a lot of redundant arithmetic.
- #
- # TODO: Use a non-trivial pivoting strategy. Even just row swapping makes a
- # big difference to performance if e.g. the upper-left entry of the matrix
- # is a huge polynomial.
- # a is (m x n)
- m = len(a)
- if not m:
- return K.one, []
- n = len(a[0])
- d = None
- pivots = []
- no_pivots = []
- # i, j will be the row and column indices of the current pivot
- i = 0
- for j in range(n):
- # next pivot?
- aij = a[i][j]
- # swap rows if zero
- if not aij:
- for ip in range(i+1, m):
- aij = a[ip][j]
- # row-swap
- if aij:
- a[i], a[ip] = a[ip], a[i]
- break
- else:
- # go to next column
- no_pivots.append(j)
- continue
- # Now aij is the pivot and i,j are the row and column. We need to clear
- # the column above and below but we also need to keep track of the
- # denominator of the RREF which means also multiplying everything above
- # and to the left by the current pivot aij and dividing by d (which we
- # multiplied everything by in the previous iteration so this is an
- # exact division).
- #
- # First handle the upper left corner which is usually already diagonal
- # with all diagonal entries equal to the current denominator but there
- # can be other non-zero entries in any column that has no pivot.
- # Update previous pivots in the matrix
- if pivots:
- pivot_val = aij * a[0][pivots[0]]
- # Divide out the common factor
- if d is not None:
- pivot_val = K.exquo(pivot_val, d)
- # Could defer this until the end but it is pretty cheap and
- # helps when debugging.
- for ip, jp in enumerate(pivots):
- a[ip][jp] = pivot_val
- # Update columns without pivots
- for jnp in no_pivots:
- for ip in range(i):
- aijp = a[ip][jnp]
- if aijp:
- aijp *= aij
- if d is not None:
- aijp = K.exquo(aijp, d)
- a[ip][jnp] = aijp
- # Eliminate above, below and to the right as in ordinary division free
- # Gauss-Jordan elmination except also dividing out d from every entry.
- for jp, aj in enumerate(a):
- # Skip the current row
- if jp == i:
- continue
- # Eliminate to the right in all rows
- for kp in range(j+1, n):
- ajk = aij * aj[kp] - aj[j] * a[i][kp]
- if d is not None:
- ajk = K.exquo(ajk, d)
- aj[kp] = ajk
- # Set to zero above and below the pivot
- aj[j] = K.zero
- # next row
- pivots.append(j)
- i += 1
- # no more rows left?
- if i >= m:
- break
- if not K.is_one(aij):
- d = aij
- else:
- d = None
- if not pivots:
- denom = K.one
- else:
- denom = a[0][pivots[0]]
- return denom, pivots
- def ddm_idet(a, K):
- """a <-- echelon(a); return det
- Explanation
- ===========
- Compute the determinant of $a$ using the Bareiss fraction-free algorithm.
- The matrix $a$ is modified in place. Its diagonal elements are the
- determinants of the leading principal minors. The determinant of $a$ is
- returned.
- The domain $K$ must support exact division (``K.exquo``). This method is
- suitable for most exact rings and fields like :ref:`ZZ`, :ref:`QQ` and
- :ref:`QQ(a)` but not for inexact domains like :ref:`RR` and :ref:`CC`.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices.ddm import ddm_idet
- >>> a = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]]
- >>> a
- [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
- >>> ddm_idet(a, ZZ)
- 0
- >>> a
- [[1, 2, 3], [4, -3, -6], [7, -6, 0]]
- >>> [a[i][i] for i in range(len(a))]
- [1, -3, 0]
- See Also
- ========
- sympy.polys.matrices.domainmatrix.DomainMatrix.det
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Bareiss_algorithm
- .. [2] https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
- """
- # Bareiss algorithm
- # https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
- # a is (m x n)
- m = len(a)
- if not m:
- return K.one
- n = len(a[0])
- exquo = K.exquo
- # uf keeps track of the sign change from row swaps
- uf = K.one
- for k in range(n-1):
- if not a[k][k]:
- for i in range(k+1, n):
- if a[i][k]:
- a[k], a[i] = a[i], a[k]
- uf = -uf
- break
- else:
- return K.zero
- akkm1 = a[k-1][k-1] if k else K.one
- for i in range(k+1, n):
- for j in range(k+1, n):
- a[i][j] = exquo(a[i][j]*a[k][k] - a[i][k]*a[k][j], akkm1)
- return uf * a[-1][-1]
- def ddm_iinv(ainv, a, K):
- """ainv <-- inv(a)
- Compute the inverse of a matrix $a$ over a field $K$ using Gauss-Jordan
- elimination. The result is stored in $ainv$.
- Uses division in the ground domain which should be an exact field.
- Examples
- ========
- >>> from sympy.polys.matrices.ddm import ddm_iinv, ddm_imatmul
- >>> from sympy import QQ
- >>> a = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
- >>> ainv = [[None, None], [None, None]]
- >>> ddm_iinv(ainv, a, QQ)
- >>> ainv
- [[-2, 1], [3/2, -1/2]]
- >>> result = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
- >>> ddm_imatmul(result, a, ainv)
- >>> result
- [[1, 0], [0, 1]]
- See Also
- ========
- ddm_irref: the underlying routine.
- """
- if not K.is_Field:
- raise DMDomainError('Not a field')
- # a is (m x n)
- m = len(a)
- if not m:
- return
- n = len(a[0])
- if m != n:
- raise DMNonSquareMatrixError
- eye = [[K.one if i==j else K.zero for j in range(n)] for i in range(n)]
- Aaug = [row + eyerow for row, eyerow in zip(a, eye)]
- pivots = ddm_irref(Aaug)
- if pivots != list(range(n)):
- raise DMNonInvertibleMatrixError('Matrix det == 0; not invertible.')
- ainv[:] = [row[n:] for row in Aaug]
- def ddm_ilu_split(L, U, K):
- """L, U <-- LU(U)
- Compute the LU decomposition of a matrix $L$ in place and store the lower
- and upper triangular matrices in $L$ and $U$, respectively. Returns a list
- of row swaps that were performed.
- Uses division in the ground domain which should be an exact field.
- Examples
- ========
- >>> from sympy.polys.matrices.ddm import ddm_ilu_split
- >>> from sympy import QQ
- >>> L = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
- >>> U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
- >>> swaps = ddm_ilu_split(L, U, QQ)
- >>> swaps
- []
- >>> L
- [[0, 0], [3, 0]]
- >>> U
- [[1, 2], [0, -2]]
- See Also
- ========
- ddm_ilu
- ddm_ilu_solve
- """
- m = len(U)
- if not m:
- return []
- n = len(U[0])
- swaps = ddm_ilu(U)
- zeros = [K.zero] * min(m, n)
- for i in range(1, m):
- j = min(i, n)
- L[i][:j] = U[i][:j]
- U[i][:j] = zeros[:j]
- return swaps
- def ddm_ilu(a):
- """a <-- LU(a)
- Computes the LU decomposition of a matrix in place. Returns a list of
- row swaps that were performed.
- Uses division in the ground domain which should be an exact field.
- This is only suitable for domains like :ref:`GF(p)`, :ref:`QQ`, :ref:`QQ_I`
- and :ref:`QQ(a)`. With a rational function field like :ref:`K(x)` it is
- better to clear denominators and use division-free algorithms. Pivoting is
- used to avoid exact zeros but not for floating point accuracy so :ref:`RR`
- and :ref:`CC` are not suitable (use :func:`ddm_irref` instead).
- Examples
- ========
- >>> from sympy.polys.matrices.dense import ddm_ilu
- >>> from sympy import QQ
- >>> a = [[QQ(1, 2), QQ(1, 3)], [QQ(1, 4), QQ(1, 5)]]
- >>> swaps = ddm_ilu(a)
- >>> swaps
- []
- >>> a
- [[1/2, 1/3], [1/2, 1/30]]
- The same example using ``Matrix``:
- >>> from sympy import Matrix, S
- >>> M = Matrix([[S(1)/2, S(1)/3], [S(1)/4, S(1)/5]])
- >>> L, U, swaps = M.LUdecomposition()
- >>> L
- Matrix([
- [ 1, 0],
- [1/2, 1]])
- >>> U
- Matrix([
- [1/2, 1/3],
- [ 0, 1/30]])
- >>> swaps
- []
- See Also
- ========
- ddm_irref
- ddm_ilu_solve
- sympy.matrices.matrixbase.MatrixBase.LUdecomposition
- """
- m = len(a)
- if not m:
- return []
- n = len(a[0])
- swaps = []
- for i in range(min(m, n)):
- if not a[i][i]:
- for ip in range(i+1, m):
- if a[ip][i]:
- swaps.append((i, ip))
- a[i], a[ip] = a[ip], a[i]
- break
- else:
- # M = Matrix([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]])
- continue
- for j in range(i+1, m):
- l_ji = a[j][i] / a[i][i]
- a[j][i] = l_ji
- for k in range(i+1, n):
- a[j][k] -= l_ji * a[i][k]
- return swaps
- def ddm_ilu_solve(x, L, U, swaps, b):
- """x <-- solve(L*U*x = swaps(b))
- Solve a linear system, $A*x = b$, given an LU factorization of $A$.
- Uses division in the ground domain which must be a field.
- Modifies $x$ in place.
- Examples
- ========
- Compute the LU decomposition of $A$ (in place):
- >>> from sympy import QQ
- >>> from sympy.polys.matrices.dense import ddm_ilu, ddm_ilu_solve
- >>> A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
- >>> swaps = ddm_ilu(A)
- >>> A
- [[1, 2], [3, -2]]
- >>> L = U = A
- Solve the linear system:
- >>> b = [[QQ(5)], [QQ(6)]]
- >>> x = [[None], [None]]
- >>> ddm_ilu_solve(x, L, U, swaps, b)
- >>> x
- [[-4], [9/2]]
- See Also
- ========
- ddm_ilu
- Compute the LU decomposition of a matrix in place.
- ddm_ilu_split
- Compute the LU decomposition of a matrix and separate $L$ and $U$.
- sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve
- Higher level interface to this function.
- """
- m = len(U)
- if not m:
- return
- n = len(U[0])
- m2 = len(b)
- if not m2:
- raise DMShapeError("Shape mismtch")
- o = len(b[0])
- if m != m2:
- raise DMShapeError("Shape mismtch")
- if m < n:
- raise NotImplementedError("Underdetermined")
- if swaps:
- b = [row[:] for row in b]
- for i1, i2 in swaps:
- b[i1], b[i2] = b[i2], b[i1]
- # solve Ly = b
- y = [[None] * o for _ in range(m)]
- for k in range(o):
- for i in range(m):
- rhs = b[i][k]
- for j in range(i):
- rhs -= L[i][j] * y[j][k]
- y[i][k] = rhs
- if m > n:
- for i in range(n, m):
- for j in range(o):
- if y[i][j]:
- raise DMNonInvertibleMatrixError
- # Solve Ux = y
- for k in range(o):
- for i in reversed(range(n)):
- if not U[i][i]:
- raise DMNonInvertibleMatrixError
- rhs = y[i][k]
- for j in range(i+1, n):
- rhs -= U[i][j] * x[j][k]
- x[i][k] = rhs / U[i][i]
- def ddm_berk(M, K):
- """
- Berkowitz algorithm for computing the characteristic polynomial.
- Explanation
- ===========
- The Berkowitz algorithm is a division-free algorithm for computing the
- characteristic polynomial of a matrix over any commutative ring using only
- arithmetic in the coefficient ring.
- Examples
- ========
- >>> from sympy import Matrix
- >>> from sympy.polys.matrices.dense import ddm_berk
- >>> from sympy.polys.domains import ZZ
- >>> M = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
- >>> ddm_berk(M, ZZ)
- [[1], [-5], [-2]]
- >>> Matrix(M).charpoly()
- PurePoly(lambda**2 - 5*lambda - 2, lambda, domain='ZZ')
- See Also
- ========
- sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
- The high-level interface to this function.
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Samuelson%E2%80%93Berkowitz_algorithm
- """
- m = len(M)
- if not m:
- return [[K.one]]
- n = len(M[0])
- if m != n:
- raise DMShapeError("Not square")
- if n == 1:
- return [[K.one], [-M[0][0]]]
- a = M[0][0]
- R = [M[0][1:]]
- C = [[row[0]] for row in M[1:]]
- A = [row[1:] for row in M[1:]]
- q = ddm_berk(A, K)
- T = [[K.zero] * n for _ in range(n+1)]
- for i in range(n):
- T[i][i] = K.one
- T[i+1][i] = -a
- for i in range(2, n+1):
- if i == 2:
- AnC = C
- else:
- C = AnC
- AnC = [[K.zero] for row in C]
- ddm_imatmul(AnC, A, C)
- RAnC = [[K.zero]]
- ddm_imatmul(RAnC, R, AnC)
- for j in range(0, n+1-i):
- T[i+j][j] = -RAnC[0][0]
- qout = [[K.zero] for _ in range(n+1)]
- ddm_imatmul(qout, T, q)
- return qout
|