code: def surface_normal(self, param):
          
          # Checking is done by `surface_deriv`
          if self.ndim == 1 and self.space_ndim == 2:
              return -perpendicular_vector(self.surface_deriv(param))
          elif self.ndim == 2 and self.space_ndim == 3:
              deriv = self.surface_deriv(param)
              if deriv.ndim > 2:
                  # Vectorized, need to reshape (N, 2, 3) to (2, N, 3)
                  deriv = moveaxis(deriv, -2, 0)
              normal = np.cross(*deriv, axis=-1)
              normal /= np.linalg.norm(normal, axis=-1, keepdims=True)
              return normal
          else:
              raise NotImplementedError(
                  'no default implementation of `surface_normal` available '
                  'for `ndim = {}` and `space_ndim = {}`'
                  ''.format(self.ndim, self.space_ndim))
docs: Unit vector perpendicular to the detector surface at ``param``.
  
          The orientation is chosen as follows:
  
              - In 2D, the system ``(normal, tangent)`` should be
                right-handed.
              - In 3D, the system ``(tangent[0], tangent[1], normal)``
                should be right-handed.
  
          Here, ``tangent`` is the return value of `surface_deriv` at
          ``param``.
  
          Parameters
          ----------
          param : `array-like` or sequence
              Parameter value(s) at which to evaluate.  If ``ndim >= 2``,
              a sequence of length `ndim` must be provided.
  
          Returns
          -------
          normal : `numpy.ndarray`
              Unit vector(s) perpendicular to the detector surface at
              ``param``.
              If ``param`` is a single parameter, an array of shape
              ``(space_ndim,)`` representing a single vector is returned.
              Otherwise the shape of the returned array is
  
              - ``param.shape + (space_ndim,)`` if `ndim` is 1,
              - ``param.shape[:-1] + (space_ndim,)`` otherwise.