import numpy as np

"""
Explanation: Understanding NumPy Internals
We can achieve significant performance speed enhancement with NumPy over native Python code, particularly when our computations follow the Single Instruction, Multiple Data (SIMD) paradigm.
End of explanation
"""


def aid(x):
    # This function returns the memory
    # block address of an array.
    return x.__array_interface__['data'][0]

"""
Explanation: Copy and "deep copy"
To achieve high performance, assignments in Python usually do not copy the underlaying objects. 
This is important for example when objects are passed between functions, to avoid an excessive amount of memory copying when it is not necessary (techincal term: pass by reference).
<img src="images/reference.png" />
First, we need a way to check whether two arrays share the same underlying data buffer in memory. 
Let's define a function aid() that returns the memory location of the underlying data buffer:
End of explanation
"""


A = np.array([[1, 2], [3, 4]])

A

# now B is referring to the same array data as A 
B = A 

aid(A) == aid(B)

# changing B affects A
B[0,0] = 10

B

A

"""
Explanation: Two arrays with the same data location (as returned by aid()) share the same underlying data buffer. 
However, the opposite is true only if the arrays have the same offset (meaning that they have the same first element).
End of explanation
"""


B = np.copy(A)

# now, if we modify B, A is not affected
B[0,0] = -5

B

A

aid(A) == aid(B)

"""
Explanation: If we want to avoid this behavior, so that when we get a new completely independent object B copied from A, then we need to do a so-called deep copy using the function np.copy:
End of explanation
"""


