# Source code for GPy.util.block_matrices

# Copyright (c) 2014-2015, Alan Saul
import numpy as np

[docs]def get_blocks_3d(A, blocksizes, pagesizes=None):
"""
Given a 3d matrix, make a block matrix, where the first and second dimensions are blocked according
to blocksizes, and the pages are blocked using pagesizes
"""
assert (A.shape[0]==A.shape[1]) and len(A.shape)==3, "can't blockify this non-square matrix, may need to use 2d version"
N = np.sum(blocksizes)
assert A.shape[0] == N, "bad blocksizes"
num_blocks = len(blocksizes)
if pagesizes == None:
#Assume each page of A should be its own dimension
pagesizes = range(A.shape[2])#[0]*A.shape[2]
num_pages = len(pagesizes)
B = np.empty(shape=(num_blocks, num_blocks, num_pages), dtype=np.object)
count_k = 0
#for Bk, k in enumerate(pagesizes):
for Bk in pagesizes:
count_i = 0
for Bi, i in enumerate(blocksizes):
count_j = 0
for Bj, j in enumerate(blocksizes):
#We want to have it count_k:count_k + k but its annoying as it makes a NxNx1 array is page sizes are set to 1
B[Bi, Bj, Bk] = A[count_i:count_i + i, count_j:count_j + j, Bk]
count_j += j
count_i += i
#count_k += k
return B

[docs]def get_blocks(A, blocksizes):
assert (A.shape[0]==A.shape[1]) and len(A.shape)==2, "can't blockify this non-square matrix"
N = np.sum(blocksizes)
assert A.shape[0] == N, "bad blocksizes"
num_blocks = len(blocksizes)
B = np.empty(shape=(num_blocks, num_blocks), dtype=np.object)
count_i = 0
for Bi, i in enumerate(blocksizes):
count_j = 0
for Bj, j in enumerate(blocksizes):
B[Bi, Bj] = A[count_i:count_i + i, count_j : count_j + j]
count_j += j
count_i += i
return B

[docs]def get_block_shapes_3d(B):
assert B.dtype is np.dtype('object'), "Must be a block matrix"
#FIXME: This isn't general AT ALL...
return get_block_shapes(B[:,:,0]), B.shape[2]

[docs]def get_block_shapes(B):
assert B.dtype is np.dtype('object'), "Must be a block matrix"
return [B[b,b].shape[0] for b in range(0, B.shape[0])]

[docs]def unblock(B):
assert B.dtype is np.dtype('object'), "Must be a block matrix"
block_shapes = get_block_shapes(B)
num_elements = np.sum(block_shapes)
A = np.empty(shape=(num_elements, num_elements))
count_i = 0
for Bi, i in enumerate(block_shapes):
count_j = 0
for Bj, j in enumerate(block_shapes):
A[count_i:count_i + i, count_j:count_j + j] = B[Bi, Bj]
count_j += j
count_i += i
return A

[docs]def block_dot(A, B, diagonal=False):
"""
Element wise dot product on block matricies

+------+------+   +------+------+    +-------+-------+
|      |      |   |      |      |    |A11.B11|B12.B12|
| A11  | A12  |   | B11  | B12  |    |       |       |
+------+------+ o +------+------| =  +-------+-------+
|      |      |   |      |      |    |A21.B21|A22.B22|
| A21  | A22  |   | B21  | B22  |    |       |       |
+-------------+   +------+------+    +-------+-------+

..Note
If any block of either (A or B) are stored as 1d vectors then we assume
that it denotes a diagonal matrix efficient dot product using numpy
broadcasting will be used, i.e. A11*B11

If either (A or B) of the diagonal matrices are stored as vectors then a more
efficient dot product using numpy broadcasting will be used, i.e. A11*B11
"""
#Must have same number of blocks and be a block matrix
assert A.dtype is np.dtype('object'), "Must be a block matrix"
assert B.dtype is np.dtype('object'), "Must be a block matrix"
assert A.shape == B.shape
def f(C,D):
"""
C is an element of A, D is the associated element of B
"""
Cshape = C.shape
Dshape = D.shape
if diagonal and (len(Cshape) == 1 or len(Dshape) == 1\
or C.shape[0] != C.shape[1] or D.shape[0] != D.shape[1]):
return C*D
else:
print("Dotting, C: {} C:{}".format(C.shape, D.shape))
return np.dot(C,D)
dot = np.vectorize(f, otypes = [np.object])
return dot(A,B)

if __name__=='__main__':
A = np.zeros((5,5))
B = get_blocks(A,[2,3])
B[0,0] += 7
print(B)

assert np.all(unblock(B) == A)