Mask indexing when dim(mask)!=dim(source)

IZugec: <p>I am having a system of atoms from which I want to create a graph. Let’s also say for clarity that I am only interested in distances between atoms as feature at the moment.</p>
<p>Moreover, let’s assume we have a function which from input coordinates spits out distances for <strong>all</strong> atoms to all other atoms within <strong>constant radius</strong> (in other words it takes only neighboring atoms which are within a sphere of this radius). This is very important because it means that it is very likely that different atoms will have <strong>different</strong> number of neighbors</p>
<p>Now… that’s when I encountered a problem<br>
So, as I just mentioned I have a function which returns me a 1-D tensor consisting of distances for all atoms (i.e. torch.Size([<span class="hashtag">#DistancesFromAtomsToAllOtherAtoms</span>]), from which I want to build 2-D tensor of the dimensions torch.Size([<span class="hashtag">#NumAtoms</span>, <span class="hashtag">#Neighbors</span>] but the second dimensions “Neighbors” has to be <strong>padded</strong>.</p>
<p>For example let’s say we are looking at three atoms and the first has 20 neighbors, second 23 and third 21, then 1-D tensor will have the shape of torch.Size([64]), while the 2-D tensor needs to have shape of torch.Size([3,23]) which asks for 69 elements and I get error when I just try to index new tensor with some mask (i.e output[mask] = distances), where mask is boolean tensor with the dimensions torch.Size([3,23])</p>
<p>For this specific example Pytorch error would be:</p>
<p>RuntimeError: The size of tensor a (23) must match the size of tensor b (64) at non-singleton dimension 1</p>
<p>Finally, it’s worth noting that the same process with analogous functions, and with arrays with same dimensions as tensors above, work perfectly in numpy. It just puts zero in resulting tensor whenever the value of mask array is False, and takes value from source array when the value of mask array is True.</p>
<p>Hope someone could suggest me a way to do the same indexing process in Pytorch, while <strong>retaining</strong> the possibility to do <strong>backprop pass</strong> on this operation.</p>
<p>Thanks in advance,<br>
Ivan</p>

ptrblck: <p>Could you post an example (using random values) for the working numpy code, please?</p>

IZugec: <p>The problem is that it can not be random. It works for a specific case when the number of True values in a mask array is equal to the number of elements in source array. But I will give you an example for my code. Hope it’s not too cumbersome, and the benefit is it’s also physical and easier to understand. I will continue with the example of <strong>3</strong> atoms</p>
<p>Okay four arrays are important here. First one is <strong>n_nbh</strong>. This array tells me how many neighbors each of the atoms have. It has the dimensions of (number_atoms, max_number_of_neighbors)</p>
<pre><code class="lang-auto">array([[16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16],
       [15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15],
       [ 9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9]])
</code></pre>
<p>And here is one example of such array in which first atom has 16, second 15, and third 9 neighbors. Why is it repeating? Well, so that I can make my mask array. However, to create condition I need second array <strong>nbh_range</strong></p>
<pre><code class="lang-auto">array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15]],
      dtype=int32)
</code></pre>
<p>This one just gives me range until number of maximum neighbors so that now I can create mask</p>
<pre><code class="lang-auto">idx=np.arange(n_atoms) # in our case number of atoms is 3
mask = np.zeros((n_atoms, n_max_nbh), dtype=np.bool_) #n_max_nbh = 16 in our case
mask[idx, :] = nbh_range &lt; n_nbh
mask
array([[ True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True, False],
       [ True,  True,  True,  True,  True,  True,  True,  True,  True,
        False, False, False, False, False, False, False]])
</code></pre>
<p>Furthermore, we have our distances array which is of shape (40,) which comes from 16+15+9 as it signifies distances to all other atoms within a cutoff radius for each atom</p>
<pre><code class="lang-auto">dist
array([2.14767316, 2.        , 1.18848643, 2.91247318, 1.        ,
       0.46097722, 2.71661554, 2.66129668, 1.00623059, 2.64196896,
       2.77173231, 1.        , 1.95256242, 2.9291637 , 2.        ,
       2.93470612, 2.93470612, 2.        , 1.95256242, 1.        ,
       2.77173231, 1.00623059, 2.86818758, 2.66129668, 0.46097722,
       1.        , 2.83310783, 2.91247318, 1.18848643, 2.        ,
       2.14767316, 2.        , 2.9291637 , 2.83310783, 1.        ,
       2.64196896, 2.86818758, 2.71661554, 1.        , 2.        ])
</code></pre>
<p>Finally I can create an array of shape (3,16) by executing</p>
<pre><code class="lang-auto">distances = np.zeros((n_atoms, n_max_nbh), dtype=np.float32)
distances[mask] = dist
distances
array([[2.1476731 , 2.        , 1.1884865 , 2.9124732 , 1.        ,
        0.46097723, 2.7166154 , 2.6612966 , 1.0062306 , 2.641969  ,
        2.7717323 , 1.        , 1.9525625 , 2.9291637 , 2.        ,
        2.9347062 ],
       [2.9347062 , 2.        , 1.9525625 , 1.        , 2.7717323 ,
        1.0062306 , 2.8681877 , 2.6612966 , 0.46097723, 1.        ,
        2.833108  , 2.9124732 , 1.1884865 , 2.        , 2.1476731 ,
        0.        ],
       [2.        , 2.9291637 , 2.833108  , 1.        , 2.641969  ,
        2.8681877 , 2.7166154 , 1.        , 2.        , 0.        ,
        0.        , 0.        , 0.        , 0.        , 0.        ,
        0.        ]], dtype=float32)
distances.shape
(3,16)
</code></pre>

ptrblck: <p>Thanks for the code snippet. I don’t see any numpy-specific operation which wouldn’t work in PyTorch, too. Based on your previous post:</p>
<aside class="quote no-group" data-username="IZugec" data-post="1" data-topic="166522">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/izugec/48/51049_2.png" class="avatar"> IZugec:</div>
<blockquote>
<p>Finally, it’s worth noting that the same process with analogous functions, and with arrays with same dimensions as tensors above, work perfectly in numpy. It just puts zero in resulting tensor whenever the value of mask array is False, and takes value from source array when the value of mask array is True.</p>
</blockquote>
</aside>
<p>it sounded as if the approach works fine in numpy but is not supported in PyTorch. Is this indeed the case (and I’m missing something)?</p>