code: def adjoint(self):
          
          # Lazy import to improve `import odl` time
          import scipy.sparse
  
          adjoint_ops = [op.adjoint for op in self.ops.data]
          data = np.empty(len(adjoint_ops), dtype=object)
          data[:] = adjoint_ops
          indices = [self.ops.col, self.ops.row]  # Swap col/row -> transpose
          shape = (self.ops.shape[1], self.ops.shape[0])
          adj_matrix = scipy.sparse.coo_matrix((data, indices), shape)
          return ProductSpaceOperator(adj_matrix, self.range, self.domain)
docs: Adjoint of this operator.
  
          The adjoint is given by taking the transpose of the matrix
          and the adjoint of each component operator.
  
          In weighted product spaces, the adjoint needs to take the
          weightings into account. This is currently not supported.
  
          Returns
          -------
          adjoint : `ProductSpaceOperator`
              The adjoint
  
          Examples
          --------
          >>> r3 = odl.rn(3)
          >>> pspace = odl.ProductSpace(r3, r3)
          >>> I = odl.IdentityOperator(r3)
          >>> x = pspace.element([[1, 2, 3],
          ...                     [4, 5, 6]])
  
          Matrix is transposed:
  
          >>> prod_op = ProductSpaceOperator([[0, I], [0, 0]],
          ...                                domain=pspace, range=pspace)
          >>> prod_op(x)
          ProductSpace(rn(3), 2).element([
              [ 4.,  5.,  6.],
              [ 0.,  0.,  0.]
          ])
          >>> prod_op.adjoint(x)
          ProductSpace(rn(3), 2).element([
              [ 0.,  0.,  0.],
              [ 1.,  2.,  3.]
          ])