Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
""" Generic Asymptotically Fast Strassen Algorithms
Sage implements asymptotically fast echelon form and matrix multiplication algorithms. """
#***************************************************************************** # Copyright (C) 2005, 2006 William Stein <wstein@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** from __future__ import print_function, absolute_import
from .matrix_window cimport MatrixWindow
from cysignals.signals cimport sig_on, sig_off
""" Multiplies the submatrices specified by A and B, places result in C. Assumes that A and B have compatible dimensions to be multiplied, and that C is the correct size to receive the product, and that they are all defined over the same ring.
Uses strassen multiplication at high levels and then uses MatrixWindow methods at low levels. EXAMPLES: The following matrix dimensions are chosen especially to exercise the eight possible parity combinations that could occur while subdividing the matrix in the strassen recursion. The base case in both cases will be a (4x5) matrix times a (5x6) matrix.
::
sage: A = MatrixSpace(Integers(2^65), 64, 83).random_element() sage: B = MatrixSpace(Integers(2^65), 83, 101).random_element() sage: A._multiply_classical(B) == A._multiply_strassen(B, 3) #indirect doctest True
AUTHORS:
- David Harvey - Simon King (2011-07): Improve memory efficiency; :trac:`11610` """
cdef strassen_window_multiply_c(MatrixWindow C, MatrixWindow A, MatrixWindow B, Py_ssize_t cutoff): # todo -- I'm not sure how to interpret "cutoff". Should it be... # (a) the minimum side length of the matrices (currently implemented below) # (b) the maximum side length of the matrices # (c) the total number of entries being multiplied # (d) something else entirely?
cdef Py_ssize_t A_nrows, A_ncols, B_ncols
# note: this code is only reached if the TOP level is already beneath # the cutoff. In a typical large multiplication, the base case is # handled directly (see below). C.set_to_prod(A, B) return
# Construct windows for the four quadrants of each matrix. # Note that if the side lengths are odd we're ignoring the # final row/column for the moment.
cdef Py_ssize_t A_sub_nrows, A_sub_ncols, B_sub_ncols
cdef MatrixWindow A00, A01, A10, A11, B00, B01, B10, B11
# Allocate temp space. # S.K: We already have allocated C, so, we should use it for temporary results. # We use the schedule from Douglas-Heroux-Slishman-Smith (see also Boyer-Pernet-Zhou, # "Memory efficient scheduling of Strassen-Winograd's matrix multiplication algorithm", # Table 1).
cdef MatrixWindow S0, S1, S2, S3, T0, T1 ,T2, T3, P0, P1, P2, P3, P4, P5, P6, U0, U1, U2, U3, U4, U5, U6 cdef MatrixWindow X, Y cdef Py_ssize_t tmp_cols, start_row
# 1 S2 = A00-A10 in X
# 2 T2 = B11-B01 in Y
# 3 P6 = S2*T2 in C10 else:
# 4 S0 = A10+A11 in X
# 5 T0 = B01-B00 in Y
# 6 P4 = S0*T0 in C11 else:
# 7 S1 = S0-A00 in X
# 8 T1 = B11-T0 in Y
# 9 P5 = S1*T1 in C01 else:
#10 S3 = A01-S1 in X
#11 P2 = S3*B11 in C00 else:
#12 P0 = A00*B00 in X else:
#13 U1 = P0+P5 in C01
#14 U2 = U1+P6 in C10
#15 U3 = U1+P4 in C01
#16 U6 = U2+P4 in C11 (final)
#17 U4 = U3+P2 in C01 (final)
#18 T3 = T1-B10 in Y
#19 P3 = A11*T3 in C00 else:
#20 U5 = U2-P3 in C10 (final)
#21 P1 = A01*B10 in C00 else:
#22 U0 = P0+P1 in C00 (final)
# Now deal with the leftover row and/or column (if they exist).
cdef MatrixWindow B_last_col, C_last_col, B_bulk, A_last_row, C_last_row, B_last_row, A_last_col, C_bulk
A_last_row = A.matrix_window(A_nrows-1, 0, 1, A_ncols) if B_ncols & 1: B_bulk = B.matrix_window(0, 0, A_ncols, B_ncols-1) C_last_row = C.matrix_window(A_nrows-1, 0, 1, B_ncols-1) else: B_bulk = B C_last_row = C.matrix_window(A_nrows-1, 0, 1, B_ncols) C_last_row.set_to_prod(A_last_row, B_bulk)
cdef subtract_strassen_product(MatrixWindow result, MatrixWindow A, MatrixWindow B, Py_ssize_t cutoff): cdef MatrixWindow to_sub else: to_sub = A.new_empty_window(result.nrows(), result.ncols()) strassen_window_multiply_c(to_sub, A, B, cutoff) result.subtract(to_sub)
""" Compute echelon form, in place. Internal function, call with M.echelonize(algorithm="strassen") Based on work of Robert Bradshaw and David Harvey at MSRI workshop in 2006.
INPUT:
- ``A`` - matrix window
- ``cutoff`` - size at which algorithm reverts to naive Gaussian elimination and multiplication must be at least 1.
OUTPUT: The list of pivot columns
EXAMPLES::
sage: A = matrix(QQ, 7, [5, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 3, 1, 0, -1, 0, 0, -1, 0, 1, 2, -1, 1, 0, -1, 0, 1, 3, -1, 1, 0, 0, -2, 0, 2, 0, 1, 0, 0, -1, 0, 1, 0, 1]) sage: B = A.__copy__(); B._echelon_strassen(1); B [ 1 0 0 0 0 0 0] [ 0 1 0 -1 0 1 0] [ 0 0 1 0 0 0 0] [ 0 0 0 0 1 0 0] [ 0 0 0 0 0 0 1] [ 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0] sage: C = A.__copy__(); C._echelon_strassen(2); C == B True sage: C = A.__copy__(); C._echelon_strassen(4); C == B True
::
sage: n = 32; A = matrix(Integers(389),n,range(n^2)) sage: B = A.__copy__(); B._echelon_in_place_classical() sage: C = A.__copy__(); C._echelon_strassen(2) sage: B == C True
TESTS::
sage: A = matrix(Integers(7), 4, 4, [1,2,0,3,0,0,1,0,0,1,0,0,0,0,0,1]) sage: B = A.__copy__(); B._echelon_in_place_classical() sage: C = A.__copy__(); C._echelon_strassen(2) sage: B == C True
::
sage: A = matrix(Integers(7), 4, 4, [1,0,5,0,2,0,3,6,5,1,2,6,4,6,1,1]) sage: B = A.__copy__(); B._echelon_in_place_classical() sage: C = A.__copy__(); C._echelon_strassen(2) #indirect doctest sage: B == C True
AUTHORS:
- Robert Bradshaw """ raise ValueError("cutoff must be at least 1")
cdef strassen_echelon_c(MatrixWindow A, Py_ssize_t cutoff, Py_ssize_t mul_cutoff): # The following notation will be used in the comments below, which should be understood to give # the general idea of what's going on, as if there were no inconvenient non-pivot columns. # The original matrix is given by [ A B ] # [ C D ] # For compactness, let A' denote the inverse of A # top_left, top_right, bottom_left, and bottom_right loosely correspond to A, B, C, and D respectively, # however, the "cut" between the top and bottom rows need not be the same.
cdef Py_ssize_t nrows, ncols
cdef Py_ssize_t top_h, bottom_cut, bottom_h, bottom_start, top_cut cdef Py_ssize_t prev_pivot_count cdef Py_ssize_t split
cdef MatrixWindow top, bottom, top_left, top_right, bottom_left, bottom_right, clear
# effectively "multiplied" top row by A^{-1} # [ I A'B ] # [ C D ]
# [ 0 0 ] # the whole top is a zero matrix, [ C D ]. Run echelon on the bottom # [ 0 0 ] # we now have [ I C'D ], proceed to sorting
else:
bottom.set_to_zero() # [ I ] # [ 0 ] # proceed to sorting
else: else: # Subtract off C time top from the bottom_right # [ I A'B ] # [ * D - CA'B ]
# Now subtract off C times the top from the bottom_left (pivots -> 0)
else: mul_cutoff) # [ I A'B ] # [ 0 D - CA'B ]
# Now recursively do echelon form on the bottom # [ I A'B ] # [ 0 I F ]
pass # [ I A'B ] # [ 0 0 ] # proceed to sorting
else: # [ I A'B ] = [ I E G ] # let [ 0 I F ] = [ 0 I F ]
# Note: left with respect to leftmost non-zero column of bottom
else:
# subtract off E times bottom from top right
# [ I * G - EF ] # [ 0 I F ]
# Now subtract of E times bottom from top left
else: mul_cutoff)
# [ I 0 G - EF ] # [ 0 I F ] # proceed to sorting
# subrows already sorted...maybe I could do this more efficiently in cases with few pivot columns (e.g. merge sort)
cdef Py_ssize_t i, cur_row
################################ # lots of room for optimization.... # eventually, should I just pass these around rather than lists of ints for pivots? # would need new from_cols r""" Represent a list of integers as a list of integer intervals.
.. NOTE::
Repetitions are not considered.
Useful class for dealing with pivots in the strassen echelon, could have much more general application
INPUT:
It can be one of the following:
- ``indices`` - integer, start of the unique interval - ``range`` - integer, length of the unique interval
OR
- ``indices`` - list of integers, the integers to wrap into intervals
OR
- ``indices`` - None (default), shortcut for an empty list
OUTPUT:
An instance of ``int_range``, i.e. a list of pairs ``(start, length)``.
EXAMPLES:
From a pair of integers::
sage: from sage.matrix.strassen import int_range sage: int_range(2, 4) [(2, 4)]
Default::
sage: int_range() []
From a list of integers::
sage: int_range([1,2,3,4]) [(1, 4)] sage: int_range([1,2,3,4,6,7,8]) [(1, 4), (6, 3)] sage: int_range([1,2,3,4,100,101,102]) [(1, 4), (100, 3)] sage: int_range([1,1000,2,101,3,4,100,102]) [(1, 4), (100, 3), (1000, 1)]
Repetitions are not considered::
sage: int_range([1,2,3]) [(1, 3)] sage: int_range([1,1,1,1,2,2,2,3]) [(1, 3)]
AUTHORS:
- Robert Bradshaw """ r""" See ``sage.matrix.strassen.int_range`` for full documentation.
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: int_range(2, 4) [(2, 4)] """ else:
r""" String representation.
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: int_range([4,5,6,20,21,22,23]) [(4, 3), (20, 4)] sage: int_range([]) [] """
r""" Return the list of intervals.
OUTPUT:
A list of pairs of integers.
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: I = int_range([4,5,6,20,21,22,23]) sage: I.intervals() [(4, 3), (20, 4)] sage: type(I.intervals()) <... 'list'> """
r""" Return the (sorted) list of integers represented by this object.
OUTPUT:
A list of integers.
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: I = int_range([6,20,21,4,5,22,23]) sage: I.to_list() [4, 5, 6, 20, 21, 22, 23]
::
sage: I = int_range(34, 9) sage: I.to_list() [34, 35, 36, 37, 38, 39, 40, 41, 42]
Repetitions are not considered::
sage: I = int_range([1,1,1,1,2,2,2,3]) sage: I.to_list() [1, 2, 3] """
r""" Return an iterator over the intervals.
OUTPUT:
iterator
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: I = int_range([6,20,21,4,5,22,23]) sage: it = iter(I) sage: next(it) (4, 3) sage: next(it) (20, 4) sage: next(it) Traceback (most recent call last): ... StopIteration """
r""" Return the number of integers represented by this object.
OUTPUT:
Python integer
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: I = int_range([6,20,21,4,5,22,23]) sage: len(I) 7
::
sage: I = int_range([1,1,1,1,2,2,2,3]) sage: len(I) 3 """
r""" Return the union of ``self`` and ``right``.
INPUT:
- ``right`` - an instance of ``int_range``
OUTPUT:
An instance of ``int_range``
.. NOTE::
Yes, this two could be a lot faster... Basically, this class is for abstracting away what I was trying to do by hand in several places
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: I = int_range([1,1,1,1,2,2,2,3]) sage: J = int_range([6,20,21,4,5,22,23]) sage: I + J [(1, 6), (20, 4)] """
r""" Return the set difference of ``self`` and ``right``.
INPUT:
- ``right`` - an instance of ``int_range``.
OUTPUT:
An instance of ``int_range``.
.. NOTE::
Yes, this two could be a lot faster... Basically, this class is for abstracting away what I was trying to do by hand in several places
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: I = int_range([1,2,3,4,5]) sage: J = int_range([6,20,21,4,5,22,23]) sage: J - I [(6, 1), (20, 4)] """
r""" Return the intersection of ``self`` and ``right``.
INPUT:
- ``right`` - an instance of ``int_range``.
OUTPUT:
An instance of ``int_range``.
EXAMPLES::
sage: from sage.matrix.strassen import int_range sage: I = int_range([1,2,3,4,5]) sage: J = int_range([6,20,21,4,5,22,23]) sage: J * I [(4, 2)] """
# Useful test code: r""" INPUT:
- ``n`` - integer - ``m`` - integer - ``R`` - ring - ``c`` - integer (optional, default:2)
EXAMPLES::
sage: from sage.matrix.strassen import test sage: for n in range(5): ....: print("{} {}".format(n, test(2*n,n,Frac(QQ['x']),2))) 0 True 1 True 2 True 3 True 4 True """
# This stuff gets tested extensively elsewhere, and the functions # below aren't callable now without using Pyrex.
## todo: doc cutoff parameter as soon as I work out what it really means
## EXAMPLES: ## The following matrix dimensions are chosen especially to exercise the ## eight possible parity combinations that could occur while subdividing ## the matrix in the strassen recursion. The base case in both cases will ## be a (4x5) matrix times a (5x6) matrix.
## TODO -- the doctests below are currently not ## tested/enabled/working -- enable them when linear algebra ## restructing gets going.
## sage: dim1 = 64; dim2 = 83; dim3 = 101 ## sage: R = MatrixSpace(QQ, dim1, dim2) ## sage: S = MatrixSpace(QQ, dim2, dim3) ## sage: T = MatrixSpace(QQ, dim1, dim3)
## sage: A = R.random_element(range(-30, 30)) ## sage: B = S.random_element(range(-30, 30)) ## sage: C = T(0) ## sage: D = T(0)
## sage: A_window = A.matrix_window(0, 0, dim1, dim2) ## sage: B_window = B.matrix_window(0, 0, dim2, dim3) ## sage: C_window = C.matrix_window(0, 0, dim1, dim3) ## sage: D_window = D.matrix_window(0, 0, dim1, dim3)
## sage: from sage.matrix.strassen import strassen_window_multiply ## sage: strassen_window_multiply(C_window, A_window, B_window, 2) # use strassen method ## sage: D_window.set_to_prod(A_window, B_window) # use naive method ## sage: C_window == D_window ## True
## sage: dim1 = 79; dim2 = 83; dim3 = 101 ## sage: R = MatrixSpace(QQ, dim1, dim2) ## sage: S = MatrixSpace(QQ, dim2, dim3) ## sage: T = MatrixSpace(QQ, dim1, dim3)
## sage: A = R.random_element(range(30)) ## sage: B = S.random_element(range(30)) ## sage: C = T(0) ## sage: D = T(0)
## sage: A_window = A.matrix_window(0, 0, dim1, dim2) ## sage: B_window = B.matrix_window(0, 0, dim2, dim3) ## sage: C_window = C.matrix_window(0, 0, dim1, dim3)
## sage: strassen_window_multiply(C, A, B, 2) # use strassen method ## sage: D.set_to_prod(A, B) # use naive method
## sage: C == D ## True
|