import numpy as np

a = np.array([0,1,2])
b = np.array([5,5,5])
a + b

a, b

a + 5

M = np.ones((3,3)); M

M + a

a = np.arange(3)
b = np.arange(3)[:, np.newaxis]

print(a); print(b)

a + b

"""
Explanation: Broadcasting Practice
Computation on Arrays: Broadcasting

2019-03-20 19:44:47
End of explanation
"""


M = np.ones((2,3))
a = np.arange(3)

"""
Explanation: Rules of Broadcasting
Broadcasting in NumPy follows a strict set of rules to determine the interaction between the two arrays:


Rule 1: If the two arrays differ in their number of dimensions, the shape of the one with fewer dimensions is padded with ones on its leading (left) side.


Rule 2: If the shape of the two arrays doesn't match in any dimension, the array with shape equal to 1 in that dimension is stretched to match the other shape.


Rule 3: If in any dimension the sizes disagree and neither is equal to 1, an error is raised.


Example 1
End of explanation
"""


M + a

"""
Explanation: M.shape = (2, 3)
`a.shape = (3,)

By Rule 1, a is padded on the left w/ ones:

M.shape → (2, 3)
a.shape → (1, 3)

By Rule 2, the first dimension now disagrees, so its stretched to match:

M.shape → (2, 3)
a.shape → (2, 3)

The shapes now match, and the final shape will be (2, 3).
End of explanation
"""


a = np.arange(3).reshape((3, 1))
b = np.arange(3)

print('a.shape',a.shape); print('b.shape',b.shape)

"""
Explanation: Example 2
End of explanation
"""


a + b

"""
Explanation: Rule 1: pad b with ones:

a.shape -&gt; (3,1)
b.shape -&gt; (1,3)

Rule 2: upgrade each of these ones to match the corresp. size of the other array:

a.shape -&gt; (3,3)
b.shape -&gt; (3,3)

The result matches so the shapes are compatible.
End of explanation
"""


M = np.ones((3,2))
a = np.arange(3)

"""
Explanation: Example 3
End of explanation
"""


M + a

"""
Explanation: Matrix M is transposed from above.

M.shape = (3,2)
a.shape = (3,)

Rule 1: pad the shape of a w/ ones:

M.shape = (3,2)
a.shape = (1,3)

Rule 2: first dim of a stretched to match that of M:

M.shape = (3,2)
a.shape = (3,3)

Rule 3 is triggered: the final shapes don't match so these arrays are incompatible:
End of explanation
"""


a[:, np.newaxis].shape

M + a[:, np.newaxis]

"""
Explanation: You could manually 1-pad a from the right:
End of explanation
"""


np.logaddexp(M, a[:, np.newaxis])

"""
Explanation: These rules apply to any binary ufunc (universal function):
End of explanation
"""


# 10 observations, ea. w/ 3 vals
X = np.random.random((10, 3))

"""
Explanation: logaddexp(a,b) computes log(exp(a) + exp(b)) w/ more precision than the naïve approach. 
See: Computation on NumPy Arrays: Universal Functions
More practice
Centering an array
End of explanation
"""


Xmean = X.mean(0)
Xmean

# center X by subtracting mean (broadcasting op)
X_centered = X - Xmean

# check centered array has ≈ 0 mean
X_centered.mean(0)

"""
Explanation: Calculate the mean of ea. feature using the mean aggregate across the first dimension:
End of explanation
"""


# x and y have 50 steps from 0 to 5
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]

z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)

%matplotlib inline
import matplotlib.pyplot as plt

plt.imshow(z, origin='lower', extent=[0,5,0,5],cmap='viridis')
plt.colorbar();

"""
Explanation: Mean is now zero, to within machine precision.
Plotting a two-dimensional function
End of explanation
"""


