Spaces:
Sleeping
Sleeping
File size: 34,551 Bytes
6a86ad5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 |
from types import FunctionType
from sympy.core.cache import cacheit
from sympy.core.numbers import Float, Integer
from sympy.core.singleton import S
from sympy.core.symbol import uniquely_named_symbol
from sympy.core.mul import Mul
from sympy.polys import PurePoly, cancel
from sympy.functions.combinatorial.numbers import nC
from sympy.polys.matrices.domainmatrix import DomainMatrix
from sympy.polys.matrices.ddm import DDM
from .exceptions import NonSquareMatrixError
from .utilities import (
_get_intermediate_simp, _get_intermediate_simp_bool,
_iszero, _is_zero_after_expand_mul, _dotprodsimp, _simplify)
def _find_reasonable_pivot(col, iszerofunc=_iszero, simpfunc=_simplify):
""" Find the lowest index of an item in ``col`` that is
suitable for a pivot. If ``col`` consists only of
Floats, the pivot with the largest norm is returned.
Otherwise, the first element where ``iszerofunc`` returns
False is used. If ``iszerofunc`` does not return false,
items are simplified and retested until a suitable
pivot is found.
Returns a 4-tuple
(pivot_offset, pivot_val, assumed_nonzero, newly_determined)
where pivot_offset is the index of the pivot, pivot_val is
the (possibly simplified) value of the pivot, assumed_nonzero
is True if an assumption that the pivot was non-zero
was made without being proved, and newly_determined are
elements that were simplified during the process of pivot
finding."""
newly_determined = []
col = list(col)
# a column that contains a mix of floats and integers
# but at least one float is considered a numerical
# column, and so we do partial pivoting
if all(isinstance(x, (Float, Integer)) for x in col) and any(
isinstance(x, Float) for x in col):
col_abs = [abs(x) for x in col]
max_value = max(col_abs)
if iszerofunc(max_value):
# just because iszerofunc returned True, doesn't
# mean the value is numerically zero. Make sure
# to replace all entries with numerical zeros
if max_value != 0:
newly_determined = [(i, 0) for i, x in enumerate(col) if x != 0]
return (None, None, False, newly_determined)
index = col_abs.index(max_value)
return (index, col[index], False, newly_determined)
# PASS 1 (iszerofunc directly)
possible_zeros = []
for i, x in enumerate(col):
is_zero = iszerofunc(x)
# is someone wrote a custom iszerofunc, it may return
# BooleanFalse or BooleanTrue instead of True or False,
# so use == for comparison instead of `is`
if is_zero == False:
# we found something that is definitely not zero
return (i, x, False, newly_determined)
possible_zeros.append(is_zero)
# by this point, we've found no certain non-zeros
if all(possible_zeros):
# if everything is definitely zero, we have
# no pivot
return (None, None, False, newly_determined)
# PASS 2 (iszerofunc after simplify)
# we haven't found any for-sure non-zeros, so
# go through the elements iszerofunc couldn't
# make a determination about and opportunistically
# simplify to see if we find something
for i, x in enumerate(col):
if possible_zeros[i] is not None:
continue
simped = simpfunc(x)
is_zero = iszerofunc(simped)
if is_zero in (True, False):
newly_determined.append((i, simped))
if is_zero == False:
return (i, simped, False, newly_determined)
possible_zeros[i] = is_zero
# after simplifying, some things that were recognized
# as zeros might be zeros
if all(possible_zeros):
# if everything is definitely zero, we have
# no pivot
return (None, None, False, newly_determined)
# PASS 3 (.equals(0))
# some expressions fail to simplify to zero, but
# ``.equals(0)`` evaluates to True. As a last-ditch
# attempt, apply ``.equals`` to these expressions
for i, x in enumerate(col):
if possible_zeros[i] is not None:
continue
if x.equals(S.Zero):
# ``.iszero`` may return False with
# an implicit assumption (e.g., ``x.equals(0)``
# when ``x`` is a symbol), so only treat it
# as proved when ``.equals(0)`` returns True
possible_zeros[i] = True
newly_determined.append((i, S.Zero))
if all(possible_zeros):
return (None, None, False, newly_determined)
# at this point there is nothing that could definitely
# be a pivot. To maintain compatibility with existing
# behavior, we'll assume that an illdetermined thing is
# non-zero. We should probably raise a warning in this case
i = possible_zeros.index(None)
return (i, col[i], True, newly_determined)
def _find_reasonable_pivot_naive(col, iszerofunc=_iszero, simpfunc=None):
"""
Helper that computes the pivot value and location from a
sequence of contiguous matrix column elements. As a side effect
of the pivot search, this function may simplify some of the elements
of the input column. A list of these simplified entries and their
indices are also returned.
This function mimics the behavior of _find_reasonable_pivot(),
but does less work trying to determine if an indeterminate candidate
pivot simplifies to zero. This more naive approach can be much faster,
with the trade-off that it may erroneously return a pivot that is zero.
``col`` is a sequence of contiguous column entries to be searched for
a suitable pivot.
``iszerofunc`` is a callable that returns a Boolean that indicates
if its input is zero, or None if no such determination can be made.
``simpfunc`` is a callable that simplifies its input. It must return
its input if it does not simplify its input. Passing in
``simpfunc=None`` indicates that the pivot search should not attempt
to simplify any candidate pivots.
Returns a 4-tuple:
(pivot_offset, pivot_val, assumed_nonzero, newly_determined)
``pivot_offset`` is the sequence index of the pivot.
``pivot_val`` is the value of the pivot.
pivot_val and col[pivot_index] are equivalent, but will be different
when col[pivot_index] was simplified during the pivot search.
``assumed_nonzero`` is a boolean indicating if the pivot cannot be
guaranteed to be zero. If assumed_nonzero is true, then the pivot
may or may not be non-zero. If assumed_nonzero is false, then
the pivot is non-zero.
``newly_determined`` is a list of index-value pairs of pivot candidates
that were simplified during the pivot search.
"""
# indeterminates holds the index-value pairs of each pivot candidate
# that is neither zero or non-zero, as determined by iszerofunc().
# If iszerofunc() indicates that a candidate pivot is guaranteed
# non-zero, or that every candidate pivot is zero then the contents
# of indeterminates are unused.
# Otherwise, the only viable candidate pivots are symbolic.
# In this case, indeterminates will have at least one entry,
# and all but the first entry are ignored when simpfunc is None.
indeterminates = []
for i, col_val in enumerate(col):
col_val_is_zero = iszerofunc(col_val)
if col_val_is_zero == False:
# This pivot candidate is non-zero.
return i, col_val, False, []
elif col_val_is_zero is None:
# The candidate pivot's comparison with zero
# is indeterminate.
indeterminates.append((i, col_val))
if len(indeterminates) == 0:
# All candidate pivots are guaranteed to be zero, i.e. there is
# no pivot.
return None, None, False, []
if simpfunc is None:
# Caller did not pass in a simplification function that might
# determine if an indeterminate pivot candidate is guaranteed
# to be nonzero, so assume the first indeterminate candidate
# is non-zero.
return indeterminates[0][0], indeterminates[0][1], True, []
# newly_determined holds index-value pairs of candidate pivots
# that were simplified during the search for a non-zero pivot.
newly_determined = []
for i, col_val in indeterminates:
tmp_col_val = simpfunc(col_val)
if id(col_val) != id(tmp_col_val):
# simpfunc() simplified this candidate pivot.
newly_determined.append((i, tmp_col_val))
if iszerofunc(tmp_col_val) == False:
# Candidate pivot simplified to a guaranteed non-zero value.
return i, tmp_col_val, False, newly_determined
return indeterminates[0][0], indeterminates[0][1], True, newly_determined
# This functions is a candidate for caching if it gets implemented for matrices.
def _berkowitz_toeplitz_matrix(M):
"""Return (A,T) where T the Toeplitz matrix used in the Berkowitz algorithm
corresponding to ``M`` and A is the first principal submatrix.
"""
# the 0 x 0 case is trivial
if M.rows == 0 and M.cols == 0:
return M._new(1,1, [M.one])
#
# Partition M = [ a_11 R ]
# [ C A ]
#
a, R = M[0,0], M[0, 1:]
C, A = M[1:, 0], M[1:,1:]
#
# The Toeplitz matrix looks like
#
# [ 1 ]
# [ -a 1 ]
# [ -RC -a 1 ]
# [ -RAC -RC -a 1 ]
# [ -RA**2C -RAC -RC -a 1 ]
# etc.
# Compute the diagonal entries.
# Because multiplying matrix times vector is so much
# more efficient than matrix times matrix, recursively
# compute -R * A**n * C.
diags = [C]
for i in range(M.rows - 2):
diags.append(A.multiply(diags[i], dotprodsimp=None))
diags = [(-R).multiply(d, dotprodsimp=None)[0, 0] for d in diags]
diags = [M.one, -a] + diags
def entry(i,j):
if j > i:
return M.zero
return diags[i - j]
toeplitz = M._new(M.cols + 1, M.rows, entry)
return (A, toeplitz)
# This functions is a candidate for caching if it gets implemented for matrices.
def _berkowitz_vector(M):
""" Run the Berkowitz algorithm and return a vector whose entries
are the coefficients of the characteristic polynomial of ``M``.
Given N x N matrix, efficiently compute
coefficients of characteristic polynomials of ``M``
without division in the ground domain.
This method is particularly useful for computing determinant,
principal minors and characteristic polynomial when ``M``
has complicated coefficients e.g. polynomials. Semi-direct
usage of this algorithm is also important in computing
efficiently sub-resultant PRS.
Assuming that M is a square matrix of dimension N x N and
I is N x N identity matrix, then the Berkowitz vector is
an N x 1 vector whose entries are coefficients of the
polynomial
charpoly(M) = det(t*I - M)
As a consequence, all polynomials generated by Berkowitz
algorithm are monic.
For more information on the implemented algorithm refer to:
[1] S.J. Berkowitz, On computing the determinant in small
parallel time using a small number of processors, ACM,
Information Processing Letters 18, 1984, pp. 147-150
[2] M. Keber, Division-Free computation of sub-resultants
using Bezout matrices, Tech. Report MPI-I-2006-1-006,
Saarbrucken, 2006
"""
# handle the trivial cases
if M.rows == 0 and M.cols == 0:
return M._new(1, 1, [M.one])
elif M.rows == 1 and M.cols == 1:
return M._new(2, 1, [M.one, -M[0,0]])
submat, toeplitz = _berkowitz_toeplitz_matrix(M)
return toeplitz.multiply(_berkowitz_vector(submat), dotprodsimp=None)
def _adjugate(M, method="berkowitz"):
"""Returns the adjugate, or classical adjoint, of
a matrix. That is, the transpose of the matrix of cofactors.
https://en.wikipedia.org/wiki/Adjugate
Parameters
==========
method : string, optional
Method to use to find the cofactors, can be "bareiss", "berkowitz",
"bird", "laplace" or "lu".
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[1, 2], [3, 4]])
>>> M.adjugate()
Matrix([
[ 4, -2],
[-3, 1]])
See Also
========
cofactor_matrix
sympy.matrices.matrixbase.MatrixBase.transpose
"""
return M.cofactor_matrix(method=method).transpose()
# This functions is a candidate for caching if it gets implemented for matrices.
def _charpoly(M, x='lambda', simplify=_simplify):
"""Computes characteristic polynomial det(x*I - M) where I is
the identity matrix.
A PurePoly is returned, so using different variables for ``x`` does
not affect the comparison or the polynomials:
Parameters
==========
x : string, optional
Name for the "lambda" variable, defaults to "lambda".
simplify : function, optional
Simplification function to use on the characteristic polynomial
calculated. Defaults to ``simplify``.
Examples
========
>>> from sympy import Matrix
>>> from sympy.abc import x, y
>>> M = Matrix([[1, 3], [2, 0]])
>>> M.charpoly()
PurePoly(lambda**2 - lambda - 6, lambda, domain='ZZ')
>>> M.charpoly(x) == M.charpoly(y)
True
>>> M.charpoly(x) == M.charpoly(y)
True
Specifying ``x`` is optional; a symbol named ``lambda`` is used by
default (which looks good when pretty-printed in unicode):
>>> M.charpoly().as_expr()
lambda**2 - lambda - 6
And if ``x`` clashes with an existing symbol, underscores will
be prepended to the name to make it unique:
>>> M = Matrix([[1, 2], [x, 0]])
>>> M.charpoly(x).as_expr()
_x**2 - _x - 2*x
Whether you pass a symbol or not, the generator can be obtained
with the gen attribute since it may not be the same as the symbol
that was passed:
>>> M.charpoly(x).gen
_x
>>> M.charpoly(x).gen == x
False
Notes
=====
The Samuelson-Berkowitz algorithm is used to compute
the characteristic polynomial efficiently and without any
division operations. Thus the characteristic polynomial over any
commutative ring without zero divisors can be computed.
If the determinant det(x*I - M) can be found out easily as
in the case of an upper or a lower triangular matrix, then
instead of Samuelson-Berkowitz algorithm, eigenvalues are computed
and the characteristic polynomial with their help.
See Also
========
det
"""
if not M.is_square:
raise NonSquareMatrixError()
# Use DomainMatrix. We are already going to convert this to a Poly so there
# is no need to worry about expanding powers etc. Also since this algorithm
# does not require division or zero detection it is fine to use EX.
#
# M.to_DM() will fall back on EXRAW rather than EX. EXRAW is a lot faster
# for elementary arithmetic because it does not call cancel for each
# operation but it generates large unsimplified results that are slow in
# the subsequent call to simplify. Using EX instead is faster overall
# but at least in some cases EXRAW+simplify gives a simpler result so we
# preserve that existing behaviour of charpoly for now...
dM = M.to_DM()
K = dM.domain
cp = dM.charpoly()
x = uniquely_named_symbol(x, [M], modify=lambda s: '_' + s)
if K.is_EXRAW or simplify is not _simplify:
# XXX: Converting back to Expr is expensive. We only do it if the
# caller supplied a custom simplify function for backwards
# compatibility or otherwise if the domain was EX. For any other domain
# there should be no benefit in simplifying at this stage because Poly
# will put everything into canonical form anyway.
berk_vector = [K.to_sympy(c) for c in cp]
berk_vector = [simplify(a) for a in berk_vector]
p = PurePoly(berk_vector, x)
else:
# Convert from the list of domain elements directly to Poly.
p = PurePoly(cp, x, domain=K)
return p
def _cofactor(M, i, j, method="berkowitz"):
"""Calculate the cofactor of an element.
Parameters
==========
method : string, optional
Method to use to find the cofactors, can be "bareiss", "berkowitz",
"bird", "laplace" or "lu".
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[1, 2], [3, 4]])
>>> M.cofactor(0, 1)
-3
See Also
========
cofactor_matrix
minor
minor_submatrix
"""
if not M.is_square or M.rows < 1:
raise NonSquareMatrixError()
return S.NegativeOne**((i + j) % 2) * M.minor(i, j, method)
def _cofactor_matrix(M, method="berkowitz"):
"""Return a matrix containing the cofactor of each element.
Parameters
==========
method : string, optional
Method to use to find the cofactors, can be "bareiss", "berkowitz",
"bird", "laplace" or "lu".
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[1, 2], [3, 4]])
>>> M.cofactor_matrix()
Matrix([
[ 4, -3],
[-2, 1]])
See Also
========
cofactor
minor
minor_submatrix
"""
if not M.is_square:
raise NonSquareMatrixError()
return M._new(M.rows, M.cols,
lambda i, j: M.cofactor(i, j, method))
def _per(M):
"""Returns the permanent of a matrix. Unlike determinant,
permanent is defined for both square and non-square matrices.
For an m x n matrix, with m less than or equal to n,
it is given as the sum over the permutations s of size
less than or equal to m on [1, 2, . . . n] of the product
from i = 1 to m of M[i, s[i]]. Taking the transpose will
not affect the value of the permanent.
In the case of a square matrix, this is the same as the permutation
definition of the determinant, but it does not take the sign of the
permutation into account. Computing the permanent with this definition
is quite inefficient, so here the Ryser formula is used.
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> M.per()
450
>>> M = Matrix([1, 5, 7])
>>> M.per()
13
References
==========
.. [1] Prof. Frank Ben's notes: https://math.berkeley.edu/~bernd/ban275.pdf
.. [2] Wikipedia article on Permanent: https://en.wikipedia.org/wiki/Permanent_%28mathematics%29
.. [3] https://reference.wolfram.com/language/ref/Permanent.html
.. [4] Permanent of a rectangular matrix : https://arxiv.org/pdf/0904.3251.pdf
"""
import itertools
m, n = M.shape
if m > n:
M = M.T
m, n = n, m
s = list(range(n))
subsets = []
for i in range(1, m + 1):
subsets += list(map(list, itertools.combinations(s, i)))
perm = 0
for subset in subsets:
prod = 1
sub_len = len(subset)
for i in range(m):
prod *= sum(M[i, j] for j in subset)
perm += prod * S.NegativeOne**sub_len * nC(n - sub_len, m - sub_len)
perm *= S.NegativeOne**m
return perm.simplify()
def _det_DOM(M):
DOM = DomainMatrix.from_Matrix(M, field=True, extension=True)
K = DOM.domain
return K.to_sympy(DOM.det())
# This functions is a candidate for caching if it gets implemented for matrices.
def _det(M, method="bareiss", iszerofunc=None):
"""Computes the determinant of a matrix if ``M`` is a concrete matrix object
otherwise return an expressions ``Determinant(M)`` if ``M`` is a
``MatrixSymbol`` or other expression.
Parameters
==========
method : string, optional
Specifies the algorithm used for computing the matrix determinant.
If the matrix is at most 3x3, a hard-coded formula is used and the
specified method is ignored. Otherwise, it defaults to
``'bareiss'``.
Also, if the matrix is an upper or a lower triangular matrix, determinant
is computed by simple multiplication of diagonal elements, and the
specified method is ignored.
If it is set to ``'domain-ge'``, then Gaussian elimination method will
be used via using DomainMatrix.
If it is set to ``'bareiss'``, Bareiss' fraction-free algorithm will
be used.
If it is set to ``'berkowitz'``, Berkowitz' algorithm will be used.
If it is set to ``'bird'``, Bird's algorithm will be used [1]_.
If it is set to ``'laplace'``, Laplace's algorithm will be used.
Otherwise, if it is set to ``'lu'``, LU decomposition will be used.
.. note::
For backward compatibility, legacy keys like "bareis" and
"det_lu" can still be used to indicate the corresponding
methods.
And the keys are also case-insensitive for now. However, it is
suggested to use the precise keys for specifying the method.
iszerofunc : FunctionType or None, optional
If it is set to ``None``, it will be defaulted to ``_iszero`` if the
method is set to ``'bareiss'``, and ``_is_zero_after_expand_mul`` if
the method is set to ``'lu'``.
It can also accept any user-specified zero testing function, if it
is formatted as a function which accepts a single symbolic argument
and returns ``True`` if it is tested as zero and ``False`` if it
tested as non-zero, and also ``None`` if it is undecidable.
Returns
=======
det : Basic
Result of determinant.
Raises
======
ValueError
If unrecognized keys are given for ``method`` or ``iszerofunc``.
NonSquareMatrixError
If attempted to calculate determinant from a non-square matrix.
Examples
========
>>> from sympy import Matrix, eye, det
>>> I3 = eye(3)
>>> det(I3)
1
>>> M = Matrix([[1, 2], [3, 4]])
>>> det(M)
-2
>>> det(M) == M.det()
True
>>> M.det(method="domain-ge")
-2
References
==========
.. [1] Bird, R. S. (2011). A simple division-free algorithm for computing
determinants. Inf. Process. Lett., 111(21), 1072-1074. doi:
10.1016/j.ipl.2011.08.006
"""
# sanitize `method`
method = method.lower()
if method == "bareis":
method = "bareiss"
elif method == "det_lu":
method = "lu"
if method not in ("bareiss", "berkowitz", "lu", "domain-ge", "bird",
"laplace"):
raise ValueError("Determinant method '%s' unrecognized" % method)
if iszerofunc is None:
if method == "bareiss":
iszerofunc = _is_zero_after_expand_mul
elif method == "lu":
iszerofunc = _iszero
elif not isinstance(iszerofunc, FunctionType):
raise ValueError("Zero testing method '%s' unrecognized" % iszerofunc)
n = M.rows
if n == M.cols: # square check is done in individual method functions
if n == 0:
return M.one
elif n == 1:
return M[0, 0]
elif n == 2:
m = M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0]
return _get_intermediate_simp(_dotprodsimp)(m)
elif n == 3:
m = (M[0, 0] * M[1, 1] * M[2, 2]
+ M[0, 1] * M[1, 2] * M[2, 0]
+ M[0, 2] * M[1, 0] * M[2, 1]
- M[0, 2] * M[1, 1] * M[2, 0]
- M[0, 0] * M[1, 2] * M[2, 1]
- M[0, 1] * M[1, 0] * M[2, 2])
return _get_intermediate_simp(_dotprodsimp)(m)
dets = []
for b in M.strongly_connected_components():
if method == "domain-ge": # uses DomainMatrix to evaluate determinant
det = _det_DOM(M[b, b])
elif method == "bareiss":
det = M[b, b]._eval_det_bareiss(iszerofunc=iszerofunc)
elif method == "berkowitz":
det = M[b, b]._eval_det_berkowitz()
elif method == "lu":
det = M[b, b]._eval_det_lu(iszerofunc=iszerofunc)
elif method == "bird":
det = M[b, b]._eval_det_bird()
elif method == "laplace":
det = M[b, b]._eval_det_laplace()
dets.append(det)
return Mul(*dets)
# This functions is a candidate for caching if it gets implemented for matrices.
def _det_bareiss(M, iszerofunc=_is_zero_after_expand_mul):
"""Compute matrix determinant using Bareiss' fraction-free
algorithm which is an extension of the well known Gaussian
elimination method. This approach is best suited for dense
symbolic matrices and will result in a determinant with
minimal number of fractions. It means that less term
rewriting is needed on resulting formulae.
Parameters
==========
iszerofunc : function, optional
The function to use to determine zeros when doing an LU decomposition.
Defaults to ``lambda x: x.is_zero``.
TODO: Implement algorithm for sparse matrices (SFF),
http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps.
"""
# Recursively implemented Bareiss' algorithm as per Deanna Richelle Leggett's
# thesis http://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
def bareiss(mat, cumm=1):
if mat.rows == 0:
return mat.one
elif mat.rows == 1:
return mat[0, 0]
# find a pivot and extract the remaining matrix
# With the default iszerofunc, _find_reasonable_pivot slows down
# the computation by the factor of 2.5 in one test.
# Relevant issues: #10279 and #13877.
pivot_pos, pivot_val, _, _ = _find_reasonable_pivot(mat[:, 0], iszerofunc=iszerofunc)
if pivot_pos is None:
return mat.zero
# if we have a valid pivot, we'll do a "row swap", so keep the
# sign of the det
sign = (-1) ** (pivot_pos % 2)
# we want every row but the pivot row and every column
rows = [i for i in range(mat.rows) if i != pivot_pos]
cols = list(range(mat.cols))
tmp_mat = mat.extract(rows, cols)
def entry(i, j):
ret = (pivot_val*tmp_mat[i, j + 1] - mat[pivot_pos, j + 1]*tmp_mat[i, 0]) / cumm
if _get_intermediate_simp_bool(True):
return _dotprodsimp(ret)
elif not ret.is_Atom:
return cancel(ret)
return ret
return sign*bareiss(M._new(mat.rows - 1, mat.cols - 1, entry), pivot_val)
if not M.is_square:
raise NonSquareMatrixError()
if M.rows == 0:
return M.one
# sympy/matrices/tests/test_matrices.py contains a test that
# suggests that the determinant of a 0 x 0 matrix is one, by
# convention.
return bareiss(M)
def _det_berkowitz(M):
""" Use the Berkowitz algorithm to compute the determinant."""
if not M.is_square:
raise NonSquareMatrixError()
if M.rows == 0:
return M.one
# sympy/matrices/tests/test_matrices.py contains a test that
# suggests that the determinant of a 0 x 0 matrix is one, by
# convention.
berk_vector = _berkowitz_vector(M)
return (-1)**(len(berk_vector) - 1) * berk_vector[-1]
# This functions is a candidate for caching if it gets implemented for matrices.
def _det_LU(M, iszerofunc=_iszero, simpfunc=None):
""" Computes the determinant of a matrix from its LU decomposition.
This function uses the LU decomposition computed by
LUDecomposition_Simple().
The keyword arguments iszerofunc and simpfunc are passed to
LUDecomposition_Simple().
iszerofunc is a callable that returns a boolean indicating if its
input is zero, or None if it cannot make the determination.
simpfunc is a callable that simplifies its input.
The default is simpfunc=None, which indicate that the pivot search
algorithm should not attempt to simplify any candidate pivots.
If simpfunc fails to simplify its input, then it must return its input
instead of a copy.
Parameters
==========
iszerofunc : function, optional
The function to use to determine zeros when doing an LU decomposition.
Defaults to ``lambda x: x.is_zero``.
simpfunc : function, optional
The simplification function to use when looking for zeros for pivots.
"""
if not M.is_square:
raise NonSquareMatrixError()
if M.rows == 0:
return M.one
# sympy/matrices/tests/test_matrices.py contains a test that
# suggests that the determinant of a 0 x 0 matrix is one, by
# convention.
lu, row_swaps = M.LUdecomposition_Simple(iszerofunc=iszerofunc,
simpfunc=simpfunc)
# P*A = L*U => det(A) = det(L)*det(U)/det(P) = det(P)*det(U).
# Lower triangular factor L encoded in lu has unit diagonal => det(L) = 1.
# P is a permutation matrix => det(P) in {-1, 1} => 1/det(P) = det(P).
# LUdecomposition_Simple() returns a list of row exchange index pairs, rather
# than a permutation matrix, but det(P) = (-1)**len(row_swaps).
# Avoid forming the potentially time consuming product of U's diagonal entries
# if the product is zero.
# Bottom right entry of U is 0 => det(A) = 0.
# It may be impossible to determine if this entry of U is zero when it is symbolic.
if iszerofunc(lu[lu.rows-1, lu.rows-1]):
return M.zero
# Compute det(P)
det = -M.one if len(row_swaps)%2 else M.one
# Compute det(U) by calculating the product of U's diagonal entries.
# The upper triangular portion of lu is the upper triangular portion of the
# U factor in the LU decomposition.
for k in range(lu.rows):
det *= lu[k, k]
# return det(P)*det(U)
return det
@cacheit
def __det_laplace(M):
"""Compute the determinant of a matrix using Laplace expansion.
This is a recursive function, and it should not be called directly.
Use _det_laplace() instead. The reason for splitting this function
into two is to allow caching of determinants of submatrices. While
one could also define this function inside _det_laplace(), that
would remove the advantage of using caching in Cramer Solve.
"""
n = M.shape[0]
if n == 1:
return M[0]
elif n == 2:
return M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0]
else:
return sum((-1) ** i * M[0, i] *
__det_laplace(M.minor_submatrix(0, i)) for i in range(n))
def _det_laplace(M):
"""Compute the determinant of a matrix using Laplace expansion.
While Laplace expansion is not the most efficient method of computing
a determinant, it is a simple one, and it has the advantage of
being division free. To improve efficiency, this function uses
caching to avoid recomputing determinants of submatrices.
"""
if not M.is_square:
raise NonSquareMatrixError()
if M.shape[0] == 0:
return M.one
# sympy/matrices/tests/test_matrices.py contains a test that
# suggests that the determinant of a 0 x 0 matrix is one, by
# convention.
return __det_laplace(M.as_immutable())
def _det_bird(M):
r"""Compute the determinant of a matrix using Bird's algorithm.
Bird's algorithm is a simple division-free algorithm for computing, which
is of lower order than the Laplace's algorithm. It is described in [1]_.
References
==========
.. [1] Bird, R. S. (2011). A simple division-free algorithm for computing
determinants. Inf. Process. Lett., 111(21), 1072-1074. doi:
10.1016/j.ipl.2011.08.006
"""
def mu(X):
n = X.shape[0]
zero = X.domain.zero
total = zero
diag_sums = [zero]
for i in reversed(range(1, n)):
total -= X[i][i]
diag_sums.append(total)
diag_sums = diag_sums[::-1]
elems = [[zero] * i + [diag_sums[i]] + X_i[i + 1:] for i, X_i in
enumerate(X)]
return DDM(elems, X.shape, X.domain)
Mddm = M._rep.to_ddm()
n = M.shape[0]
if n == 0:
return M.one
# sympy/matrices/tests/test_matrices.py contains a test that
# suggests that the determinant of a 0 x 0 matrix is one, by
# convention.
Fn1 = Mddm
for _ in range(n - 1):
Fn1 = mu(Fn1).matmul(Mddm)
detA = Fn1[0][0]
if n % 2 == 0:
detA = -detA
return Mddm.domain.to_sympy(detA)
def _minor(M, i, j, method="berkowitz"):
"""Return the (i,j) minor of ``M``. That is,
return the determinant of the matrix obtained by deleting
the `i`th row and `j`th column from ``M``.
Parameters
==========
i, j : int
The row and column to exclude to obtain the submatrix.
method : string, optional
Method to use to find the determinant of the submatrix, can be
"bareiss", "berkowitz", "bird", "laplace" or "lu".
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> M.minor(1, 1)
-12
See Also
========
minor_submatrix
cofactor
det
"""
if not M.is_square:
raise NonSquareMatrixError()
return M.minor_submatrix(i, j).det(method=method)
def _minor_submatrix(M, i, j):
"""Return the submatrix obtained by removing the `i`th row
and `j`th column from ``M`` (works with Pythonic negative indices).
Parameters
==========
i, j : int
The row and column to exclude to obtain the submatrix.
Examples
========
>>> from sympy import Matrix
>>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> M.minor_submatrix(1, 1)
Matrix([
[1, 3],
[7, 9]])
See Also
========
minor
cofactor
"""
if i < 0:
i += M.rows
if j < 0:
j += M.cols
if not 0 <= i < M.rows or not 0 <= j < M.cols:
raise ValueError("`i` and `j` must satisfy 0 <= i < ``M.rows`` "
"(%d)" % M.rows + "and 0 <= j < ``M.cols`` (%d)." % M.cols)
rows = [a for a in range(M.rows) if a != i]
cols = [a for a in range(M.cols) if a != j]
return M.extract(rows, cols)
|