<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def det_to_src(self, angle, dparam):
 """Direction from a detector location to the source. The direction vector is computed as follows:: dir = rotation_matrix(angle).dot(detector.surface_normal(dparam)) Note that for flat detectors, ``surface_normal`` does not depend on the parameter ``dparam``, hence this function is constant in that variable. Parameters angle : `array-like` or sequence One or several (Euler) angles in radians at which to evaluate. If ``motion_params.ndim >= 2``, a sequence of that length must be provided. dparam : `array-like` or sequence Detector parameter(s) at which to evaluate. If ``det_params.ndim >= 2``, a sequence of that length must be provided. Returns ------- det_to_src : `numpy.ndarray` Vector(s) pointing from a detector point to the source (at infinity). The shape of the returned array is obtained from the (broadcast) shapes of ``angle`` and ``dparam``, and broadcasting is supported within both parameters and between them. The precise definition of the shape is ``broadcast(bcast_angle, bcast_dparam).shape + (ndim,)``, where ``bcast_angle`` is - ``angle`` if `motion_params` is 1D, - ``broadcast(*angle)`` otherwise, and ``bcast_dparam`` defined analogously. Examples -------- The method works with single parameter values, in which case a single vector is returned: array([ 0., -1.]) array([ 0., -1.]) True True Both variables support vectorized calls, i.e., stacks of parameters can be provided. The order of axes in the output (left of the ``ndim`` axis for the vector dimension) corresponds to the order of arguments: array([[ 0., -1.], [ 0., -1.], [ 0., -1.], [ 0., -1.]]) (4, 2) True (3, 2) array([ 0., -1.]) (3, 2) (4, 5, 2) (4, 5, 2) """

        # Always call the downstream methods with vectorized arguments
        # to be able to reliably manipulate the final axes of the result
        if self.motion_params.ndim == 1:
            squeeze_angle = (np.shape(angle) == ())
            angle = np.array(angle, dtype=float, copy=False, ndmin=1)
            matrix = self.rotation_matrix(angle)  # shape (m, ndim, ndim)
        else:
            squeeze_angle = (np.broadcast(*angle).shape == ())
            angle = tuple(np.array(a, dtype=float, copy=False, ndmin=1)
                          for a in angle)
            matrix = self.rotation_matrix(angle)  # shape (m, ndim, ndim)

        if self.det_params.ndim == 1:
            squeeze_dparam = (np.shape(dparam) == ())
            dparam = np.array(dparam, dtype=float, copy=False, ndmin=1)
        else:
            squeeze_dparam = (np.broadcast(*dparam).shape == ())
            dparam = tuple(np.array(p, dtype=float, copy=False, ndmin=1)
                           for p in dparam)

        normal = self.detector.surface_normal(dparam)  # shape (d, ndim)

        # Perform matrix-vector multiplication along the last axis of both
        # `matrix` and `normal` while "zipping" all axes that do not
        # participate in the matrix-vector product. In other words, the axes
        # are labelled
        # [0, 1, ..., r-1, r, r+1] for `matrix` and
        # [0, 1, ..., r-1, r+1] for `normal`, and the output axes are set to
        # [0, 1, ..., r-1, r]. This automatically supports broadcasting
        # along the axes 0, ..., r-1.
        matrix_axes = list(range(matrix.ndim))
        normal_axes = list(range(matrix.ndim - 2)) + [matrix_axes[-1]]
        out_axes = list(range(matrix.ndim - 1))
        det_to_src = np.einsum(matrix, matrix_axes, normal, normal_axes,
                               out_axes)
        if squeeze_angle and squeeze_dparam:
            det_to_src = det_to_src.squeeze()

        return det_to_src