docs: def convert_to_qutip(expr, full_space=None, mapping=None):
      """Convert a QNET expression to a qutip object
  
      Args:
          expr: a QNET expression
          full_space (HilbertSpace): The
              Hilbert space in which `expr` is defined. If not given,
              ``expr.space`` is used. The Hilbert space must have a well-defined
              basis.
          mapping (dict): A mapping of any (sub-)expression to either a
              `quip.Qobj` directly, or to a callable that will convert the
              expression into a `qutip.Qobj`. Useful for e.g. supplying objects
              for symbols
      Raises:
          ValueError: if `expr` is not in `full_space`, or if `expr` cannot be
              converted.
      """
      if full_space is None:
          full_space = expr.space
      if not expr.space.is_tensor_factor_of(full_space):
          raise ValueError(
              "expr '%s' must be in full_space %s" % (expr, full_space))
      if full_space == TrivialSpace:
          raise AlgebraError(
              "Cannot convert object in TrivialSpace to qutip. "
              "You may pass a non-trivial `full_space`")
      if mapping is not None:
          if expr in mapping:
              ret = mapping[expr]
              if isinstance(ret, qutip.Qobj):
                  return ret
              else:
                  assert callable(ret)
                  return ret(expr)
      if expr is IdentityOperator:
          local_spaces = full_space.local_factors
          if len(local_spaces) == 0:
              raise ValueError("full_space %s does not have local factors"
                               % full_space)
          else:
              return qutip.tensor(*[qutip.qeye(s.dimension)
                                    for s in local_spaces])
      elif expr is ZeroOperator:
          return qutip.tensor(
              *[qutip.Qobj(csr_matrix((s.dimension, s.dimension)))
                for s in full_space.local_factors]
          )
      elif isinstance(expr, LocalOperator):
          return _convert_local_operator_to_qutip(expr, full_space, mapping)
      elif (isinstance(expr, Operator) and isinstance(expr, Operation)):
          return _convert_operator_operation_to_qutip(expr, full_space, mapping)
      elif isinstance(expr, OperatorTrace):
          raise NotImplementedError('Cannot convert OperatorTrace to '
                                    'qutip')
          # actually, this is perfectly doable in principle, but requires a bit
          # of work
      elif isinstance(expr, State):
          return _convert_ket_to_qutip(expr, full_space, mapping)
      elif isinstance(expr, SuperOperator):
          return _convert_superoperator_to_qutip(expr, full_space, mapping)
      elif isinstance(expr, Operation):
          # This is assumed to be an Operation on states, as we have handled all
          # other Operations above. Eventually, a StateOperation should be
          # defined as a common superclass for the Operations in the state
          # algebra
          return _convert_state_operation_to_qutip(expr, full_space, mapping)
      elif isinstance(expr, SLH):
          # SLH object cannot be converted to a single qutip object, only to a
          # nested list of qutip object. That's why a separate routine
          # SLH_to_qutip exists
          raise ValueError("SLH objects can only be converted using "
                           "SLH_to_qutip routine")
      else:
          raise ValueError("Cannot convert '%s' of type %s"
                           % (str(expr), type(expr)))