4

I need to get the diagonal "stripe" of a matrix. Say I have a matrix of size KxN (K>N):

[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]

From it I need to extract a diagonal stripe, in this case, a matrix MxV size that is created by truncating the original one:

[[ 0  x  x]
 [ 3  4  x]
 [ x  7  8]
 [ x  x  11]]

So the result matrix is:

[[ 0  4  8]
 [ 3  7  11]]

I could define a bolean mask like so:

import numpy as np

X=np.arange(12).reshape(4,3)
mask=np.asarray([
  [ True,  False,  False],
  [ True,  True,  False], 
  [ False, True,  True], 
  [ False, False,  True]
])

>>> X
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

>>> X.T[mask.T].reshape(3,2).T
array([[ 0,  4,  8],
       [ 3,  7, 11]])

But I don't see how such a mask could be automatically generated to an arbitrary KxN matrix (e.g. 39x9, 360x96)

Is there a function that does this automatically either in numpy, scipy or pytorch?


Additional question: is it possible to get a "reverse stripe" instead? i.e.

[[ x   x   2]
 [ x   4   5]
 [ 6   7   x]
 [ 9   x   x]]
iacob
  • 20,084
  • 6
  • 92
  • 119
Ivan Bilan
  • 2,379
  • 5
  • 38
  • 58

3 Answers3

3

stride_tricks do the trick:

>>> import numpy as np
>>> 
>>> def stripe(a):
...    a = np.asanyarray(a)
...    *sh, i, j = a.shape
...    assert i >= j
...    *st, k, m = a.strides
...    return np.lib.stride_tricks.as_strided(a, (*sh, i-j+1, j), (*st, k, k+m))
... 
>>> a = np.arange(24).reshape(6, 4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19],
       [20, 21, 22, 23]])
>>> stripe(a)
array([[ 0,  5, 10, 15],
       [ 4,  9, 14, 19],
       [ 8, 13, 18, 23]])

If a is an array this creates a writable view, meaning that if you feel so inclined you can do things like

>>> stripe(a)[...] *= 10
>>> a
array([[  0,   1,   2,   3],
       [ 40,  50,   6,   7],
       [ 80,  90, 100,  11],
       [ 12, 130, 140, 150],
       [ 16,  17, 180, 190],
       [ 20,  21,  22, 230]])

UPDATE: bottom-left to top-right stripes can be obtained in the same spirit. Only minor complication: It is not based at the same address as the original array.

>>> def reverse_stripe(a):
...     a = np.asanyarray(a)
...     *sh, i, j = a.shape
...     assert i >= j
...     *st, k, m = a.strides
...     return np.lib.stride_tricks.as_strided(a[..., j-1:, :], (*sh, i-j+1, j), (*st, k, m-k))
... 
>>> a = np.arange(24).reshape(6, 4)
>>> reverse_stripe(a)
array([[12,  9,  6,  3],
       [16, 13, 10,  7],
       [20, 17, 14, 11]])
Paul Panzer
  • 51,835
  • 3
  • 54
  • 99
  • 1
    amazing, thank you. Any insights on whether this can be duplicated in native PyTorch? – Ivan Bilan Apr 23 '18 at 19:26
  • 1
    @ivan_bilan No idea, sorry, completely not familiar with PyTorch. – Paul Panzer Apr 23 '18 at 19:28
  • hey, I ended up using your solution. I have another question, how would I select a reverse stripe, so not the one going from top left to bottom right, but with one going from bottom left to top right? (see my new edit for an example). Thank you! – Ivan Bilan May 16 '18 at 20:23
  • Thanks, just to clarify, I am not using the version with +1 since it adds one element that is too many. I always get `i>j` anyway. In your new update, does the `a[j-1:]` have anything to do with the `i-j+1`, in other words, if I do `i-j` instead, do I need the -1 part? – Ivan Bilan May 17 '18 at 20:35
  • 1
    @ivan_bilan If you are using `i-j` you are missing one diagonal. If you use it together wtih `a[j:]` the first diagonal will be skipped, if with `a[j-1:]` the last diagonal. – Paul Panzer May 17 '18 at 21:52
  • 1
    thank you so much! I am having some trouble replicating this in PyTorch due to matrix `a` being modified in this way `a[j-1:]`. Any other ideas on how to do this without this part? – Ivan Bilan May 18 '18 at 09:01
  • 1
    @ivan_bilan since I don't know PyTorch it is difficult to say anything wise here. You could try using the original top-left to bottom right version like so `stripe(a[::-1])[::-1]`. – Paul Panzer May 18 '18 at 20:20
  • Ok, thanks. See I have a third dimension for number of batches, so `a` is actually of size `[x, y, z]`, where matrix `y, z` is the matrix I want to get the stripes from. In your comment above, how would I do that with the batch dimension included? – Ivan Bilan May 21 '18 at 21:07
  • 1
    @ivan_bilan I've updated the functions (untested, though). `stripe(a[..., ::-1, :])[..., ::-1, :]`. – Paul Panzer May 22 '18 at 07:45
  • thank you, looks like PyTorch doesn't support negative strides. I need a solution that doesn't have negative strides. – Ivan Bilan May 22 '18 at 11:54
  • Hi, sorry for bothering again. I think I found a solution using `stripe(a[..., ::-1, :])[..., ::-1, :]`, can you explain why you have to flip it the second time after applying stripe? – Ivan Bilan May 23 '18 at 19:17
  • @ivan_bilan you are flipping the matrix, then taking the stripe (to get the reversed stripe), but then you need to flip the matrix back to its original shape. – iacob Mar 30 '21 at 08:57
1

Yes, you can do this in NumPy by providing an offset value to its built in numpy.diagonal():

a = np.array([[0, 1, 2],
              [3, 4, 5],
              [6, 7, 8],
              [9,10,11]])

stripe = np.array([a.diagonal(), 
                   a.diagonal(-1)])
>>> stripe
array([[ 0,  4,  8],
       [ 3,  7, 11]])

For an arbitrary KxN matrix you could get the variable width stripe with:

stripe = [a.diagonal(i) for i in range(K,N,-1)]

PyTorch's torch.diagonal() has the exact same functionality.

iacob
  • 20,084
  • 6
  • 92
  • 119
0

Extending Paul's answer. You can do the same in PyTorch using diag multiple times (I do not think there is any direct function to do strides in PyTorch)

import torch

def stripe(a):
    i, j = a.size()
    assert(i>=j)
    out = torch.zeros((i-j+1, j))
    for diag in range(0, i-j+1):
        out[diag] = torch.diag(a, -diag)
    return out
 

a = torch.randn((6, 3))
>>> a
 0.7669  0.6808 -0.6102
-1.0624 -1.2016 -0.7308
 1.4054 -1.0621  0.2618
-0.9505 -0.9322 -0.4321
-0.0134 -1.3684  0.1883
-0.8499  0.2533 -0.3976
[torch.FloatTensor of size 6x3]
>>> stripe(a)
 0.7669 -1.2016  0.2618
-1.0624 -1.0621 -0.4321
 1.4054 -0.9322  0.1883
-0.9505 -1.3684 -0.3976
[torch.FloatTensor of size 4x3]
iacob
  • 20,084
  • 6
  • 92
  • 119
layog
  • 4,661
  • 1
  • 28
  • 30
  • 1
    thank you, I have a follow up question, I actually have 3 dimensions and not two, how do I skip the first one? Also, the input I have is a `torch.autograd.variable.Variable`, so I am getting this error: `RuntimeError: invalid argument 1: expected a matrix or a vector at` at line `out[diag] = torch.diag(a, -diag)`. I made a follow-up question, maybe be you can help there: https://stackoverflow.com/questions/50090821/how-to-apply-a-custom-function-to-specific-columns-in-a-matrix-in-pytorch . Thank you! – Ivan Bilan Apr 29 '18 at 20:17