diff --git a/nifty5/__init__.py b/nifty5/__init__.py
index 9374f4503ae0a91d09d5748600ea99bf685c9005..8b9670ab0a88ab21b9ca8365c3c0e3a1d53c3fb0 100644
--- a/nifty5/__init__.py
+++ b/nifty5/__init__.py
@@ -46,15 +46,16 @@ from .operators.outer_product_operator import OuterProduct
 from .operators.simple_linear_operators import (
     VdotOperator, ConjugationOperator, Realizer,
     FieldAdapter, ducktape, GeometryRemover, NullOperator,
-    MatrixProductOperator)
+    MatrixProductOperator, PartialExtractor)
 from .operators.value_inserter import ValueInserter
 from .operators.energy_operators import (
     EnergyOperator, GaussianEnergy, PoissonianEnergy, InverseGammaLikelihood,
-    BernoulliEnergy, StandardHamiltonian, AveragedEnergy)
+    BernoulliEnergy, StandardHamiltonian, AveragedEnergy, QuadraticFormOperator,
+    SquaredNormOperator)
 from .operators.convolution_operators import FuncConvolutionOperator
 
 from .probing import probe_with_posterior_samples, probe_diagonal, \
-    StatCalculator
+    StatCalculator, approximation2endo
 
 from .minimization.line_search import LineSearch
 from .minimization.iteration_controllers import (
@@ -97,6 +98,8 @@ from .logger import logger
 
 from .linearization import Linearization
 
+from .operator_spectrum import operator_spectrum
+
 from . import internal_config
 _scheme = internal_config.parallelization_scheme()
 if _scheme == "Samples":
diff --git a/nifty5/operator_spectrum.py b/nifty5/operator_spectrum.py
new file mode 100644
index 0000000000000000000000000000000000000000..6eb3a37dd968d0726699d0684621a3ef8b13a707
--- /dev/null
+++ b/nifty5/operator_spectrum.py
@@ -0,0 +1,143 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright(C) 2013-2019 Max-Planck-Society
+
+import numpy as np
+import scipy.sparse.linalg as ssl
+
+from .domain_tuple import DomainTuple
+from .domains.unstructured_domain import UnstructuredDomain
+from .field import Field
+from .multi_domain import MultiDomain
+from .multi_field import MultiField
+from .operators.linear_operator import LinearOperator
+from .operators.sandwich_operator import SandwichOperator
+from .sugar import from_global_data, makeDomain
+
+
+class _DomRemover(LinearOperator):
+    """Operator which transforms between a structured MultiDomain
+    and an unstructured domain.
+
+    Parameters
+    ----------
+    domain: MultiDomain
+        the full input domain of the operator.
+
+    Notes
+    -----
+    The operator converts the full domain of its input domain to an
+    UnstructuredDomain
+    """
+
+    def __init__(self, domain):
+        self._domain = makeDomain(domain)
+        if isinstance(self._domain, MultiDomain):
+            self._size_array = np.array([0] +
+                                        [d.size for d in domain.values()])
+        else:
+            self._size_array = np.array([0, domain.size])
+        np.cumsum(self._size_array, out=self._size_array)
+        target = UnstructuredDomain(self._size_array[-1])
+        self._target = makeDomain(target)
+        self._capability = self.TIMES | self.ADJOINT_TIMES
+
+    def apply(self, x, mode):
+        self._check_input(x, mode)
+        self._check_float_dtype(x)
+        x = x.to_global_data()
+        if isinstance(self._domain, DomainTuple):
+            res = x.ravel() if mode == self.TIMES else x.reshape(
+                self._domain.shape)
+        else:
+            res = np.empty(self.target.shape) if mode == self.TIMES else {}
+            for ii, (kk, dd) in enumerate(self.domain.items()):
+                i0, i1 = self._size_array[ii:ii + 2]
+                if mode == self.TIMES:
+                    res[i0:i1] = x[kk].ravel()
+                else:
+                    res[kk] = x[i0:i1].reshape(dd.shape)
+        return from_global_data(self._tgt(mode), res)
+
+    @staticmethod
+    def _check_float_dtype(fld):
+        if isinstance(fld, MultiField):
+            dts = [ff.local_data.dtype for ff in fld.values()]
+        elif isinstance(fld, Field):
+            dts = [fld.local_data.dtype]
+        else:
+            raise TypeError
+        for dt in dts:
+            if not np.issubdtype(dt, np.float64):
+                raise TypeError('Operator supports only floating point dtypes')
+
+
+def operator_spectrum(A, k, hermitian, which='LM', tol=0):
+    '''
+    Find k eigenvalues and eigenvectors of the endomorphism A.
+
+    Parameters
+    ----------
+    A : LinearOperator
+        Operator of which eigenvalues shall be computed.
+    k : int
+        The number of eigenvalues and eigenvectors desired. `k` must be
+        smaller than N-1. It is not possible to compute all eigenvectors of a
+        matrix.
+    hermitian: bool
+        Specifies whether A is hermitian or not.
+    which : str, ['LM' | 'SM' | 'LR' | 'SR' | 'LI' | 'SI'], optional
+        Which `k` eigenvectors and eigenvalues to find:
+
+            'LM' : largest magnitude
+
+            'SM' : smallest magnitude
+
+            'LR' : largest real part
+
+            'SR' : smallest real part
+
+            'LI' : largest imaginary part
+
+            'SI' : smallest imaginary part
+
+    tol : float, optional
+        Relative accuracy for eigenvalues (stopping criterion)
+        The default value of 0 implies machine precision.
+
+    Returns
+    -------
+    w : ndarray
+        Array of k eigenvalues.
+
+    Raises
+    ------
+    ArpackNoConvergence
+        When the requested convergence is not obtained.
+        The currently converged eigenvalues and eigenvectors can be found
+        as ``eigenvalues`` and ``eigenvectors`` attributes of the exception
+        object.
+    '''
+    if not isinstance(A, LinearOperator):
+        raise TypeError('Operator needs to be linear.')
+    if A.domain is not A.target:
+        raise TypeError('Operator needs to be endomorphism.')
+    size = A.domain.size
+    Ar = SandwichOperator.make(_DomRemover(A.domain).adjoint, A)
+    M = ssl.LinearOperator(
+        shape=2*(size,),
+        matvec=lambda x: Ar(from_global_data(Ar.domain, x)).to_global_data())
+    f = ssl.eigsh if hermitian else ssl.eigs
+    eigs = f(M, k=k, tol=tol, return_eigenvectors=False, which=which)
+    return np.flip(np.sort(eigs), axis=0)
diff --git a/nifty5/operators/energy_operators.py b/nifty5/operators/energy_operators.py
index cbbcc854069dc09b1bd581c6fd90fb1e6db4cf75..2f03e4d2a1d66bae1e3c5e97b871516f407aa6de 100644
--- a/nifty5/operators/energy_operators.py
+++ b/nifty5/operators/energy_operators.py
@@ -319,9 +319,11 @@ class StandardHamiltonian(EnergyOperator):
     `<https://arxiv.org/abs/1812.04403>`_
     """
 
-    def __init__(self, lh, ic_samp=None):
+    def __init__(self, lh, ic_samp=None, _c_inp=None):
         self._lh = lh
         self._prior = GaussianEnergy(domain=lh.domain)
+        if _c_inp is not None:
+            _, self._prior = self._prior.simplify_for_constant_input(_c_inp)
         self._ic_samp = ic_samp
         self._domain = lh.domain
 
@@ -338,9 +340,13 @@ class StandardHamiltonian(EnergyOperator):
 
     def __repr__(self):
         subs = 'Likelihood:\n{}'.format(utilities.indent(self._lh.__repr__()))
-        subs += '\nPrior: Quadratic{}'.format(self._lh.domain.keys())
+        subs += '\nPrior:\n{}'.format(self._prior)
         return 'StandardHamiltonian:\n' + utilities.indent(subs)
 
+    def _simplify_for_constant_input_nontrivial(self, c_inp):
+        out, lh1 = self._lh.simplify_for_constant_input(c_inp)
+        return out, StandardHamiltonian(lh1, self._ic_samp, _c_inp=c_inp)
+
 
 class AveragedEnergy(EnergyOperator):
     """Averages an energy over samples.
diff --git a/nifty5/operators/inversion_enabler.py b/nifty5/operators/inversion_enabler.py
index ea107ba775fad5884da7bc67fa035046f2e18826..3999f1fc42d070053930b308999aca5f95e65a78 100644
--- a/nifty5/operators/inversion_enabler.py
+++ b/nifty5/operators/inversion_enabler.py
@@ -23,6 +23,7 @@ from ..minimization.iteration_controllers import IterationController
 from ..minimization.quadratic_energy import QuadraticEnergy
 from ..sugar import full
 from .endomorphic_operator import EndomorphicOperator
+from .linear_operator import LinearOperator
 
 
 class InversionEnabler(EndomorphicOperator):
@@ -47,6 +48,12 @@ class InversionEnabler(EndomorphicOperator):
     """
 
     def __init__(self, op, iteration_controller, approximation=None):
+        # isinstance(op, EndomorphicOperator) does not suffice since op can be
+        # a ChainOperator
+        if not isinstance(op, LinearOperator):
+            raise TypeError('Operator needs to be linear.')
+        if op.domain is not op.target:
+            raise TypeError('Operator needs to be endomorphic.')
         self._op = op
         self._ic = iteration_controller
         self._approximation = approximation
diff --git a/nifty5/operators/sampling_enabler.py b/nifty5/operators/sampling_enabler.py
index 512ad076e8ed7e0477626df6d1b81b639f859fc5..883b8f3f6428beb38e963fa140d9e857e6bb241e 100644
--- a/nifty5/operators/sampling_enabler.py
+++ b/nifty5/operators/sampling_enabler.py
@@ -42,15 +42,20 @@ class SamplingEnabler(EndomorphicOperator):
         operator, which supports the operation modes that the operator doesn't
         have. It is used as a preconditioner during the iterative inversion,
         to accelerate convergence.
+    start_from_zero : boolean
+        If true, the conjugate gradient algorithm starts from a field filled
+        with zeros. Otherwise, it starts from a prior samples. Default is
+        False.
     """
 
     def __init__(self, likelihood, prior, iteration_controller,
-                 approximation=None):
-        self._op = likelihood + prior
+                 approximation=None, start_from_zero=False):
         self._likelihood = likelihood
         self._prior = prior
         self._ic = iteration_controller
         self._approximation = approximation
+        self._start_from_zero = bool(start_from_zero)
+        self._op = likelihood + prior
         self._domain = self._op.domain
         self._capability = self._op.capability
 
@@ -60,11 +65,15 @@ class SamplingEnabler(EndomorphicOperator):
         except NotImplementedError:
             if not from_inverse:
                 raise ValueError("from_inverse must be True here")
-            s = self._prior.draw_sample(from_inverse=True)
-            sp = self._prior(s)
-            nj = self._likelihood.draw_sample()
-            energy = QuadraticEnergy(s, self._op, sp + nj,
-                                     _grad=self._likelihood(s) - nj)
+            if self._start_from_zero:
+                b = self._op.draw_sample()
+                energy = QuadraticEnergy(0*b, self._op, b)
+            else:
+                s = self._prior.draw_sample(from_inverse=True)
+                sp = self._prior(s)
+                nj = self._likelihood.draw_sample()
+                energy = QuadraticEnergy(s, self._op, sp + nj,
+                                         _grad=self._likelihood(s) - nj)
             inverter = ConjugateGradient(self._ic)
             if self._approximation is not None:
                 energy, convergence = inverter(
diff --git a/nifty5/plot.py b/nifty5/plot.py
index e7fc65b10901bad683127a7b4d55ff75361320d0..7a2d1600860c52d9735af66cf5dc25cd864aa206 100644
--- a/nifty5/plot.py
+++ b/nifty5/plot.py
@@ -347,8 +347,12 @@ def _plot2D(f, ax, **kwargs):
     if len(dom) == 2:
         if (not isinstance(dom[1], RGSpace)) or len(dom[1].shape) != 1:
             raise TypeError("need 1D RGSpace as second domain")
-        rgb = _rgb_data(f.to_global_data())
-        have_rgb = True
+        if dom[1].shape[0] == 1:
+            from .sugar import from_global_data
+            f = from_global_data(f.domain[0], f.to_global_data()[..., 0])
+        else:
+            rgb = _rgb_data(f.to_global_data())
+            have_rgb = True
 
     foo = kwargs.pop("norm", None)
     norm = {} if foo is None else {'norm': foo}
@@ -477,6 +481,17 @@ class Plot(object):
         alpha: float or list of floats
             Transparency value.
         """
+        from .multi_field import MultiField
+        if isinstance(f, MultiField):
+            for kk in f.domain.keys():
+                self._plots.append(f[kk])
+                mykwargs = kwargs.copy()
+                if 'title' in kwargs:
+                    mykwargs['title'] = "{} {}".format(kk, kwargs['title'])
+                else:
+                    mykwargs['title'] = "{}".format(kk)
+                self._kwargs.append(mykwargs)
+            return
         self._plots.append(f)
         self._kwargs.append(kwargs)