How did numpy implement multi-dimensional broadcasting?
<p>Memory (row major order):</p>

<pre><code>[[A(0,0), A(0,1)]
 [A(1,0), A(1,1)]]

has this memory layout: 
[A(0,0), A(0,1), A(1,0), A(1,1)]
</code></pre>

<p>I guess the algorithm work like this in the following cases.</p>

<p>Broadcasting Dimension is last dimension:</p>

<pre><code>[[0, 1, 2, 3]         [[1]
                  x
 [4, 5, 6, 7]]         [10]]

   A (2 by 4)            B (2 by 1)

Iterate 0th dimensions of A and B simultaneously {
    Iterate last dimension of A{
        multiply;
    } 
}
</code></pre>

<p>Broadcasting dimension is 0th dimension:</p>

<pre><code>[[0, 1, 2, 3]   
                  x    [[1,10,100,1000]]
 [4, 5, 6, 7]]

   A (2 by 4)              B (1 by 4)

Iterate 0th dimension of A{
    Iterate 1st dimensions of A and B simultaneously{
        multiply;
    }
}
</code></pre>

<p>Question:</p>

<ol>
<li><p>How does numpy know which order of multiplication is the best.
(reading memory in order is better than reading memory all over the place. but how did numpy figure that out?)</p></li>
<li><p>What would numpy do if the arrays have more than two dimension</p></li>
<li>What would numpy do if the broadcasting dimension is not the last dimension?</li>
</ol>

<p>2nd guess of what is going on:</p>

<pre><code>#include &lt;iostream&gt;
int main(void){
    const int nA = 12;
    const int nB = 3;
    int A[nA];
    int B[nB];
    for(int i = 0; i != nA; ++i) A[i] = i+1;
    for(int i = 0; i != nB; ++i) B[i] = i+1;
    //dimension
    int dA[] = {2,3,2};
    int dB[] = {1,3,1};

    int* pA = A;
    int* pB = B;
    int* pA_end = A + nA;
    //is it possible to make the compiler
    //generate the iA and sA?
    int iB = 0;
    int iB_max = 2;
    int sB[] = {1,0};

    while(pA != pA_end){
        std::cout &lt;&lt; "*pA, *pB: " &lt;&lt; *pA &lt;&lt; ", " &lt;&lt; *pB &lt;&lt;std::endl;
        std::cout &lt;&lt; "iB: " &lt;&lt; iB &lt;&lt;std::endl;
        *(pA) *= *(pB);
        ++pA;
        pB += sB[iB];
        ++iB;
        if (iB == iB_max) {iB = 0; pB = B;}
    }

    for(pA = A; pA != pA_end; ++pA){
        std::cout &lt;&lt; *(pA) &lt;&lt; ", ";
    }
    std::cout &lt;&lt; std::endl;       
}
</code></pre>

<p>To really get into broadcasting details you need to understand array shape and strides.  But a lot of the work is now implemented in <code>c</code> code using <code>nditer</code>.  You can read about it at <a href="http://docs.scipy.org/doc/numpy/reference/arrays.nditer.html" rel="nofollow">http://docs.scipy.org/doc/numpy/reference/arrays.nditer.html</a>.  <code>np.nditer</code> gives you access to the tool at the Python level, but its real value comes when used with <code>cython</code> or your own <code>c</code> code.</p>

<p><code>np.lib.stride_tricks</code> has functions that let you play with strides.  One of its functions helps visualize how arrays are broadcasted together.  In practice the work is done with <code>nditer</code>, but this function may help understand the action:</p>

<pre><code>In [629]: np.lib.stride_tricks.broadcast_arrays(np.arange(6).reshape(2,3), 
                                  np.array([[1],[2]]))
Out[629]: 
[array([[0, 1, 2],
        [3, 4, 5]]), 
 array([[1, 1, 1],
        [2, 2, 2]])]
</code></pre>

<p>Note that, effectively the 2nd array has been replicated to match the 1st's shape.  But the replication is done with stride tricks, not with actual copies.</p>

<pre><code>In [631]: A,B=np.lib.stride_tricks.broadcast_arrays(np.arange(6).reshape(2,3), 
                                      np.array([[1],[2]]))
In [632]: A.shape
Out[632]: (2, 3)
In [633]: A.strides
Out[633]: (12, 4)
In [634]: B.shape
Out[634]: (2, 3)
In [635]: B.strides
Out[635]: (4, 0)         
</code></pre>

<p>It's this <code>(4,0)</code> strides that does the replication without copy.</p>

<p>=================</p>

<p>Using the python level <code>nditer</code>, here's what it does during broadcasting.</p>

<pre><code>In [1]: A=np.arange(6).reshape(2,3)
In [2]: B=np.array([[1],[2]])
</code></pre>

<p>The plain nditer feeds elements one set at a time
<a href="http://docs.scipy.org/doc/numpy/reference/arrays.nditer.html#using-an-external-loop" rel="nofollow">http://docs.scipy.org/doc/numpy/reference/arrays.nditer.html#using-an-external-loop</a></p>

<pre><code>In [5]: it =np.nditer((A,B))
In [6]: for a,b in it:
   ...:     print(a,b)

0 1
1 1
2 1
3 2
4 2
5 2
</code></pre>

<p>But when I turn <code>extenal_loop</code> on, it iterates in chunks, here respective rows of the broadcasted arrays:</p>

<pre><code>In [7]: it =np.nditer((A,B), flags=['external_loop'])
In [8]: for a,b in it:
   ...:     print(a,b)

[0 1 2] [1 1 1]
[3 4 5] [2 2 2]
</code></pre>

<p>With a more complex broadcasting the <code>external_loop</code> still produces 1d arrays that allow simple <code>c</code> style iteration:</p>

<pre><code>In [13]: A1=np.arange(24).reshape(3,2,4)
In [18]: it =np.nditer((A1,np.arange(3)[:,None,None]), flags=['external_loop'])
In [19]: while not it.finished:
    ...:     print(it[:])
    ...:     it.iternext()
    ...:     
(array([0, 1, 2, 3, 4, 5, 6, 7]), array([0, 0, 0, 0, 0, 0, 0, 0]))
(array([ 8,  9, 10, 11, 12, 13, 14, 15]), array([1, 1, 1, 1, 1, 1, 1, 1]))
(array([16, 17, 18, 19, 20, 21, 22, 23]), array([2, 2, 2, 2, 2, 2, 2, 2]))
</code></pre>

<p>Note that while <code>A1</code> is (3,2,4), the nditer loop yields 3 steps (1st axis) with 2*4 length elements.  </p>

<p>I found in another <code>cython/nditer</code> SO question that the first approach did not produce much of a speed improvement, but the 2nd helped a lot.  In <code>c</code> or <code>cython</code> the <code>external_loop</code> case would do simple low level iteration.</p>

<p>===============</p>

<p>If I broadcast on the 1 and 3rd axis, the iterator takes 2*3 steps (effectively flattening the 1st 2 axes, and feeding the 3rd):</p>

<pre><code>In [20]: it =np.nditer((A1,np.arange(2)[None,:,None]), flags=['external_loop'])
In [21]: while not it.finished:
    ...:     print(it[:])
    ...:     it.iternext()
    ...:     
(array([0, 1, 2, 3]), array([0, 0, 0, 0]))
(array([4, 5, 6, 7]), array([1, 1, 1, 1]))
(array([ 8,  9, 10, 11]), array([0, 0, 0, 0]))
(array([12, 13, 14, 15]), array([1, 1, 1, 1]))
(array([16, 17, 18, 19]), array([0, 0, 0, 0]))
(array([20, 21, 22, 23]), array([1, 1, 1, 1]))
</code></pre>

<p>But with <code>buffered</code>, it iterates once, feeding me 2 1d arrays:</p>

<pre><code>In [22]: it =np.nditer((A1,np.arange(2)[None,:,None]), flags=['external_loop','buffered'])
In [23]: while not it.finished:
    ...:     print(it[:])
    ...:     it.iternext()
    ...:     
(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]), 
 array([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]))
</code></pre>

<p><a href="http://stackoverflow.com/questions/39058641/does-cython-offer-any-reasonably-easy-and-efficient-way-to-iterate-numpy-arrays">Does Cython offer any reasonably easy and efficient way to iterate Numpy arrays as if they were flat?</a>
has some speed tests, showing that buffered external loop is fastest</p>

<p><code>cython</code> translates this into fast simple <code>c</code> iteration:</p>

<pre><code>for xarr in it:
   x = xarr
   size = x.shape[0]
   for i in range(size):
       x[i] = x[i]+1.0
</code></pre>

python c numpy