# Q & A Session¶

Questions from Scientific Analysis Session in SPWP 2021

In [1]:

    import numpy as np
    from scipy import optimize

Question: Could you introduce the `lambda` function a bit more?

The Lambda functionality allows to have shorthand defintiions for function according to the schema
lambda arguments : expression

- The experession corresponds the return value
- They basically need to be implementable in on expression (e.g. no `for` or `while` loop)
- They can have multiple arguments
- We can store them in a variable that then holds a python function

In [2]:

    f = lambda x: x**2
    print(type(f))

    <class 'function'>

In [3]:

    f(3)

Out[3]:

    9

In [5]:

    def g(x):
        return x**2
    print(type(g))

    <class 'function'>

In [6]:

    g(3)

Out[6]:

    9

In [7]:

    h = lambda x,y,z: (x**2+y**2+z**2)**0.5 # Cartesian length for 3D-vector
    h(1,2,2)

Out[7]:

    3.0

They are helpful if you want to define function on the fly, e.g. as arguments of another method/function

In [8]:

    a = ["spam","cheese","parrot"]

In [9]:

    sorted(a) # Equivalent sorted(a,key=lambda x:x)

Out[9]:

    ['cheese', 'parrot', 'spam']

Let's define via the `key` variable a sorting that uses the reversed strings.

In [10]:

    sorted(a,key=lambda x:x[-1])

Out[10]:

    ['cheese', 'spam', 'parrot']

Question: A general question: what is the differences using array and matrix?

Most important: `np.matrix` is depreciated
`np.matrix` is a subclass of `np.ndarray` and it has some further property like an Inverse and a Hermitian

In [11]:

    m = np.matrix([[1,0],[1,2]])
    print("Is matrix an instance of ndarray?",isinstance(m,np.ndarray))
    print("Inverse:\n",m.I)
    print("Hermitian:\n",m.H)

    Is matrix an instance of ndarray? True
    Inverse:
     [[ 1.  -0. ]
     [-0.5  0.5]]
    Hermitian:
     [[1 1]
     [0 2]]

But it can only be one- (vector) or two-dimensional (matrix)

In [12]:

    n = np.matrix(np.ones((3,3,3)))

    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-12-b0ecedb62542> in <module>
    ----> 1 n = np.matrix(np.ones((3,3,3)))

    /usr/local/lib/python3.7/site-packages/numpy/matrixlib/defmatrix.py in __new__(subtype, data, dtype, copy)
        135             else:
        136                 intype = N.dtype(dtype)
    --> 137             new = data.view(subtype)
        138             if intype != data.dtype:
        139                 return new.astype(intype)

    /usr/local/lib/python3.7/site-packages/numpy/matrixlib/defmatrix.py in __array_finalize__(self, obj)
        180                 return
        181             elif (ndim > 2):
    --> 182                 raise ValueError("shape too large to be a matrix.")
        183         else:
        184             newshape = self.shape

    ValueError: shape too large to be a matrix.

In [13]:

    v = np.matrix(np.ones(3))
    m = np.matrix(np.ones((3,3)))

In [15]:

    print(v)
    print(m)
    print(v.shape)
    print(m.shape)

    [[1. 1. 1.]]
    [[1. 1. 1.]
     [1. 1. 1.]
     [1. 1. 1.]]
    (1, 3)
    (3, 3)

Question: In the example where quad fails to be accurate, it also gave an estimated error of 0. Can I ever trust the error estimate then? Can we also get error bounds?

The uncertainty that the integration method returns is purely numerical and does not include any information about the convergence behaviour or any potential occurance of systematic issue!

Question: Why do algorithms like `optimize.brentq` provide a separate mean of providing the arguments via `args` instead of telling you to use a `lambda` function?

Because it gives more flexibility and is less cumbersome and comprehensable than solving this via the a `lambda` function

In [16]:

    def f(x,a):
        return x-a

In [17]:

    optimize.brentq(f,-1000,1000,args=(5,))

Out[17]:

    5.0

In [18]:

    optimize.brentq(lambda x:f(x,5),-1000,1000)

Out[18]:

    5.0

Question to "Data structures" lecture: Slide 18. Why `(3,4,1)(8)`is possible, but`(3,2,5)(6)` is not?

Numpy is in the implicit broadcasting matching the arrays starting with the last dimension. Matching a dimension means that the number of entries need to be the same or the number of entries in at least one array needs to be 1. Hence the first case works since the first array has 1 entry along the last dimension and hence matchable to 8. In the second case we try to match arrays that have 5 and 6 entries in the last dimension repecitvely and thus fail.

To match dimensions we can inject new axes as shown below. These can be injected also in the middle of the array dimensions using `:` to indicate a column or `...` (ellipsis) to indicate all remaining column. Thus the ellipsis can only be used once in such an expression

In [53]:

    a=np.ones((3,2,5,4))
    b=np.ones((3,5,4))

In [56]:

    c = a*b[:,np.newaxis,:,:]
    print(c.shape)

    (3, 2, 5, 4)