def __init__(self, layers, domain, ansatz, function):
     self.function = function
     super().__init__(layers, domain, ansatz)
     self.target = self.function(self.domain)
     self.target = 2 * (self.target - np.min(self.target)) / (
         np.max(self.target) - np.min(self.target)) - 1
     self.H = Hamiltonian(1, matrices._Z)
     self.classical = globals()[f"classical_real_{self.ansatz}"]
    def __init__(self, layers, domain, function, ansatz):
        self.function = function
        super().__init__(layers, domain, ansatz)
        self.target = np.array(list(self.function(self.domain)))
        self.target = 2 * (self.target - np.min(self.target)) / (
            np.max(self.target) - np.min(self.target)) - 1

        self.H = Hamiltonian(1, matrices._Z)
        self.classical = classical_real_Weighted_2D
예제 #3
0
def test_trotter_hamiltonian_initialization_errors():
    """Test errors in initialization of ``TrotterHamiltonian``."""
    # Wrong type of terms
    with pytest.raises(TypeError):
        ham = TrotterHamiltonian({(0, 1): "abc"})
    # Wrong type of parts
    with pytest.raises(TypeError):
        ham = TrotterHamiltonian([(0, 1)])
    # Wrong number of target qubits
    with pytest.raises(ValueError):
        ham = TrotterHamiltonian({(0, 1): TFIM(nqubits=3, numpy=True)})
    # Same targets multiple times
    h = TFIM(nqubits=2, numpy=True)
    with pytest.raises(ValueError):
        ham = TrotterHamiltonian({(0, 1): h}, {(0, 1): h})
    # Different term matrix types
    h2 = Hamiltonian(2, np.eye(4, dtype=np.float32), numpy=True)
    with pytest.raises(TypeError):
        ham = TrotterHamiltonian({(0, 1): h, (1, 2): h2})
    # ``from_twoqubit_term`` initialization with nqubits < 0
    with pytest.raises(ValueError):
        ham = TrotterHamiltonian.from_twoqubit_term(-2, h)
    # ``from_twoqubit_term`` initialization with more than 2 targets
    h = TFIM(nqubits=3, numpy=True)
    with pytest.raises(ValueError):
        ham = TrotterHamiltonian.from_twoqubit_term(4, h)
예제 #4
0
파일: qpdf.py 프로젝트: tuliplan/qibo
def qpdf_hamiltonian(nqubits, z_qubit=0):
    """Precomputes Hamiltonian.

    Args:
        nqubits (int): number of qubits.
        z_qubit (int): qubit where the Z measurement is applied, must be z_qubit < nqubits

    Returns:
        An Hamiltonian object.
    """
    eye = matrices.I
    if z_qubit == 0:
        h = matrices.Z
        for _ in range(nqubits - 1):
            h = np.kron(eye, h)

    elif z_qubit == nqubits - 1:
        h = eye
        for _ in range(nqubits - 2):
            h = np.kron(eye, h)
        h = np.kron(matrices.Z, h)
    else:
        h = eye
        for _ in range(nqubits - 1):
            if _ + 1 == z_qubit:
                h = np.kron(matrices.Z, h)
            else:
                h = np.kron(eye, h)
    return Hamiltonian(nqubits, h)
예제 #5
0
    def from_symbolic(cls, symbolic_hamiltonian, symbol_map, ground_state=None):
        """Creates a ``TrotterHamiltonian`` from a symbolic Hamiltonian.

        We refer to the :ref:`How to define custom Hamiltonians using symbols? <symbolicham-example>`
        example for more details.

        Args:
            symbolic_hamiltonian (sympy.Expr): The full Hamiltonian written
                with symbols.
            symbol_map (dict): Dictionary that maps each symbol that appears in
                the Hamiltonian to a pair of (target, matrix).
            ground_state (Callable): Optional callable with no arguments that
                returns the ground state of this ``TrotterHamiltonian``.
                See :class:`qibo.base.hamiltonians.TrotterHamiltonian` for more
                details.

        Returns:
            A :class:`qibo.base.hamiltonians.TrotterHamiltonian` object that
            implements the given symbolic Hamiltonian.
        """
        from qibo.hamiltonians import Hamiltonian
        terms, constant = _SymbolicHamiltonian(
          symbolic_hamiltonian, symbol_map).trotter_terms()
        terms = {k: Hamiltonian(len(k), v, numpy=True)
                 for k, v in terms.items()}
        return cls.from_dictionary(terms, ground_state=ground_state) + constant
예제 #6
0
    def construct_terms(terms):
        """Helper method for `from_symbolic`.

        Constructs the term dictionary by using the same
        :class:`qibo.abstractions.hamiltonians.Hamiltonian` object for terms that
        have equal matrix representation. This is done for efficiency during
        the exponentiation of terms.

        Args:
            terms (dict): Dictionary that maps tuples of targets to the matrix
                          that acts on these on targets.

        Returns:
            terms (dict): Dictionary that maps tuples of targets to the
                          Hamiltonian term that acts on these on targets.
        """
        from qibo.hamiltonians import Hamiltonian
        unique_matrices = []
        hterms = {}
        for targets, matrix in terms.items():
            flag = True
            for m, h in unique_matrices:
                if K.np.array_equal(matrix, m):
                    ham = h
                    flag = False
                    break
            if flag:
                ham = Hamiltonian(len(targets), matrix, numpy=True)
                unique_matrices.append((matrix, ham))
            hterms[targets] = ham
        return hterms
예제 #7
0
def test_from_symbolic_application_hamiltonian():
    """Check ``from_symbolic`` for a specific four-qubit Hamiltonian."""
    import sympy
    z1, z2, z3, z4 = sympy.symbols("z1 z2 z3 z4")
    symmap = {z: (i, matrices.Z) for i, z in enumerate([z1, z2, z3, z4])}
    symham = (z1 * z2 - 0.5 * z1 * z3 + 2 * z2 * z3 + 0.35 * z2 +
              0.25 * z3 * z4 + 0.5 * z3 + z4 - z1)
    # Check that Trotter dense matrix agrees will full Hamiltonian matrix
    fham = Hamiltonian.from_symbolic(symham, symmap)
    tham = TrotterHamiltonian.from_symbolic(symham, symmap)
    np.testing.assert_allclose(tham.dense.matrix, fham.matrix)
    # Check that no one-qubit terms exist in the Trotter Hamiltonian
    # (this means that merging was successful)
    first_targets = set()
    for part in tham.parts:
        for targets, term in part.items():
            first_targets.add(targets[0])
            assert len(targets) == 2
            assert term.nqubits == 2
    assert first_targets == set(range(4))
    # Check making an ``X`` Hamiltonian compatible with ``tham``
    xham = X(nqubits=4, trotter=True)
    cxham = tham.make_compatible(xham)
    assert not tham.is_compatible(xham)
    assert tham.is_compatible(cxham)
    np.testing.assert_allclose(xham.dense.matrix, cxham.dense.matrix)
    def __init__(self, layers, domain, ansatz, real, imag):
        self.f_real = real
        self.f_imag = imag
        # self.function.name = lambda:self.real.name + '_' + self.imag.name
        super().__init__(layers, domain, ansatz)
        self.real = self.f_real(self.domain)
        self.real = 2 * (self.real - np.min(self.real)) / (
            np.max(self.real) - np.min(self.real)) - 1

        self.imag = self.f_imag(self.domain)
        self.imag = 2 * (self.imag - np.min(self.imag)) / (
            np.max(self.imag) - np.min(self.imag)) - 1

        self.target = self.real + 1j * self.imag
        self.target /= np.max(np.abs(self.target))

        self.H = [Hamiltonian(1, matrices._X), Hamiltonian(1, matrices._Y)]
        self.classical = globals()[f"classical_complex_{self.ansatz}"]
예제 #9
0
def test_trotter_hamiltonian_make_compatible_onequbit_terms():
    """Check ``make_compatible`` when the two-qubit Hamiltonian has one-qubit terms."""
    term1 = Hamiltonian(1, matrices.Z, numpy=True)
    term2 = Hamiltonian(2, np.kron(matrices.Z, matrices.Z), numpy=True)
    terms = {
        (0, 1): term2,
        (0, 2): -0.5 * term2,
        (1, 2): 2 * term2,
        (1, ): 0.35 * term1,
        (2, 3): 0.25 * term2,
        (2, ): 0.5 * term1,
        (3, ): term1
    }
    tham = TrotterHamiltonian.from_dictionary(terms) + 1.5
    xham = X(nqubits=4, trotter=True)
    cxham = tham.make_compatible(xham)
    assert not tham.is_compatible(xham)
    assert tham.is_compatible(cxham)
    np.testing.assert_allclose(xham.dense.matrix, cxham.dense.matrix)
예제 #10
0
def test_trotter_hamiltonian_three_qubit_term(backend):
    """Test creating ``TrotterHamiltonian`` with three qubit term."""
    import qibo
    from scipy.linalg import expm
    original_backend = qibo.get_backend()
    qibo.set_backend(backend)
    m1 = utils.random_numpy_hermitian(3)
    m2 = utils.random_numpy_hermitian(2)
    m3 = utils.random_numpy_hermitian(1)

    term1 = Hamiltonian(3, m1, numpy=True)
    term2 = Hamiltonian(2, m2, numpy=True)
    term3 = Hamiltonian(1, m3, numpy=True)
    parts = [{(0, 1, 2): term1}, {(2, 3): term2, (1, ): term3}]
    trotter_h = TrotterHamiltonian(*parts)

    # Test that the `TrotterHamiltonian` dense matrix is correct
    eye = np.eye(2, dtype=m1.dtype)
    mm1 = np.kron(m1, eye)
    mm2 = np.kron(np.kron(eye, eye), m2)
    mm3 = np.kron(np.kron(eye, m3), np.kron(eye, eye))
    target_h = Hamiltonian(4, mm1 + mm2 + mm3)
    np.testing.assert_allclose(trotter_h.dense.matrix, target_h.matrix)

    dt = 1e-2
    initial_state = utils.random_numpy_state(4)
    if backend == "custom":
        with pytest.raises(NotImplementedError):
            circuit = trotter_h.circuit(dt=dt)
    else:
        circuit = trotter_h.circuit(dt=dt)
        final_state = circuit(np.copy(initial_state))

        u = [expm(-0.5j * dt * m) for m in [mm1, mm2, mm3]]
        target_state = u[2].dot(u[1].dot(u[0])).dot(initial_state)
        target_state = u[0].dot(u[1].dot(u[2])).dot(target_state)
        np.testing.assert_allclose(final_state, target_state)

    qibo.set_backend(original_backend)
예제 #11
0
def test_hamiltonian_initialization():
    """Testing hamiltonian initialization errors."""
    import tensorflow as tf
    dtype = K.dtypes('DTYPECPX')
    with pytest.raises(TypeError):
        H = Hamiltonian(2, "test")
    H1 = Hamiltonian(2, np.eye(4))
    H1 = Hamiltonian(2, np.eye(4), numpy=True)
    H1 = Hamiltonian(2, tf.eye(4, dtype=dtype))
    H1 = Hamiltonian(2, tf.eye(4, dtype=dtype), numpy=True)
    with pytest.raises(ValueError):
        H1 = Hamiltonian(-2, np.eye(4))
    with pytest.raises(RuntimeError):
        H2 = Hamiltonian(np.eye(2), np.eye(4))
    with pytest.raises(ValueError):
        H3 = Hamiltonian(4, np.eye(10))
예제 #12
0
def test_x_hamiltonian_from_symbols(nqubits, trotter):
    """Check creating sum(X) Hamiltonian using sympy."""
    import sympy
    x_symbols = sympy.symbols(" ".join((f"X{i}" for i in range(nqubits))))
    symham = -sum(x_symbols)
    symmap = {x: (i, matrices.X) for i, x in enumerate(x_symbols)}

    target_matrix = X(nqubits).matrix
    if trotter:
        trotter_ham = TrotterHamiltonian.from_symbolic(symham, symmap)
        final_matrix = trotter_ham.dense.matrix
    else:
        full_ham = Hamiltonian.from_symbolic(symham, symmap)
        final_matrix = full_ham.matrix
    np.testing.assert_allclose(final_matrix, target_matrix)
예제 #13
0
def test_tfim_hamiltonian_from_symbols(nqubits, trotter):
    """Check creating TFIM Hamiltonian using sympy."""
    import sympy
    h = 0.5
    z_symbols = sympy.symbols(" ".join((f"Z{i}" for i in range(nqubits))))
    x_symbols = sympy.symbols(" ".join((f"X{i}" for i in range(nqubits))))

    symham = sum(z_symbols[i] * z_symbols[i + 1] for i in range(nqubits - 1))
    symham += z_symbols[0] * z_symbols[-1]
    symham += h * sum(x_symbols)
    symmap = {z: (i, matrices.Z) for i, z in enumerate(z_symbols)}
    symmap.update({x: (i, matrices.X) for i, x in enumerate(x_symbols)})

    target_matrix = TFIM(nqubits, h=h).matrix
    if trotter:
        trotter_ham = TrotterHamiltonian.from_symbolic(-symham, symmap)
        final_matrix = trotter_ham.dense.matrix
    else:
        full_ham = Hamiltonian.from_symbolic(-symham, symmap)
        final_matrix = full_ham.matrix
    np.testing.assert_allclose(final_matrix, target_matrix)
예제 #14
0
def test_three_qubit_term_hamiltonian_from_symbols(trotter):
    """Check creating Hamiltonian with three-qubit interaction using sympy."""
    import sympy
    from qibo import matrices
    x_symbols = sympy.symbols(" ".join((f"X{i}" for i in range(4))))
    y_symbols = sympy.symbols(" ".join((f"Y{i}" for i in range(4))))
    z_symbols = sympy.symbols(" ".join((f"Z{i}" for i in range(4))))
    symmap = {x: (i, matrices.X) for i, x in enumerate(x_symbols)}
    symmap.update({x: (i, matrices.Y) for i, x in enumerate(y_symbols)})
    symmap.update({x: (i, matrices.Z) for i, x in enumerate(z_symbols)})

    symham = x_symbols[0] * y_symbols[1] * z_symbols[2]
    symham += 0.5 * y_symbols[0] * z_symbols[1] * x_symbols[3]
    symham += z_symbols[0] * x_symbols[2]
    symham += -3 * x_symbols[1] * y_symbols[3]
    symham += y_symbols[2]
    symham += 1.5 * z_symbols[1]
    symham -= 2

    target_matrix = np.kron(np.kron(matrices.X, matrices.Y),
                            np.kron(matrices.Z, matrices.I))
    target_matrix += 0.5 * np.kron(np.kron(matrices.Y, matrices.Z),
                                   np.kron(matrices.I, matrices.X))
    target_matrix += np.kron(np.kron(matrices.Z, matrices.I),
                             np.kron(matrices.X, matrices.I))
    target_matrix += -3 * np.kron(np.kron(matrices.I, matrices.X),
                                  np.kron(matrices.I, matrices.Y))
    target_matrix += np.kron(np.kron(matrices.I, matrices.I),
                             np.kron(matrices.Y, matrices.I))
    target_matrix += 1.5 * np.kron(np.kron(matrices.I, matrices.Z),
                                   np.kron(matrices.I, matrices.I))
    target_matrix -= 2 * np.eye(2**4, dtype=target_matrix.dtype)
    if trotter:
        trotter_ham = TrotterHamiltonian.from_symbolic(symham, symmap)
        final_matrix = trotter_ham.dense.matrix
    else:
        full_ham = Hamiltonian.from_symbolic(symham, symmap)
        final_matrix = full_ham.matrix
    np.testing.assert_allclose(final_matrix, target_matrix)
예제 #15
0
def test_from_symbolic_with_power(trotter):
    """Check ``from_symbolic`` when the expression contains powers."""
    import sympy
    z = sympy.symbols(" ".join((f"Z{i}" for i in range(3))))
    symham = z[0]**2 - z[1]**2 + 3 * z[1] - 2 * z[0] * z[2] + +1
    matrix = utils.random_numpy_hermitian(1)
    symmap = {x: (i, matrix) for i, x in enumerate(z)}
    if trotter:
        ham = TrotterHamiltonian.from_symbolic(symham, symmap)
        final_matrix = ham.dense.matrix
    else:
        ham = Hamiltonian.from_symbolic(symham, symmap)
        final_matrix = ham.matrix

    matrix2 = matrix.dot(matrix)
    eye = np.eye(2, dtype=matrix.dtype)
    target_matrix = np.kron(np.kron(matrix2, eye), eye)
    target_matrix -= np.kron(np.kron(eye, matrix2), eye)
    target_matrix += 3 * np.kron(np.kron(eye, matrix), eye)
    target_matrix -= 2 * np.kron(np.kron(matrix, eye), matrix)
    target_matrix += np.eye(8, dtype=matrix.dtype)
    np.testing.assert_allclose(final_matrix, target_matrix)
예제 #16
0
 def expectation(self, state, normalize=False):
     return Hamiltonian.expectation(self, state, normalize)
class Approximant_real_2D(Approximant):
    def __init__(self, layers, domain, function, ansatz):
        self.function = function
        super().__init__(layers, domain, ansatz)
        self.target = np.array(list(self.function(self.domain)))
        self.target = 2 * (self.target - np.min(self.target)) / (
            np.max(self.target) - np.min(self.target)) - 1

        self.H = Hamiltonian(1, matrices._Z)
        self.classical = classical_real_Weighted_2D

    def name_folder(self, quantum=True):
        folder = self.ansatz + '/' + self.function.name + '/%s_layers' % (
            self.layers)
        if quantum:
            folder = 'quantum/' + folder
        else:
            folder = 'classical/' + folder
        folder = 'results/' + folder
        import os
        try:
            l = os.listdir(folder)
            l = [int(_) for _ in l]
            l.sort()
            trial = int(l[-1]) + 1
        except:
            trial = 0
            os.makedirs(folder)
        fold_name = folder + '/%s' % (trial)
        os.makedirs(fold_name)
        return fold_name, trial

    def cf_one_point(self, x, f):
        state = self.get_state(x)
        o = self.H.expectation(state)
        cf = (o - f)**2
        return cf

    def paint_representation_2D(self, name):
        fig = plt.figure()
        axs = fig.gca(projection='3d')

        axs.plot_trisurf(self.domain[:, 0],
                         self.domain[:, 1],
                         self.target,
                         color='black',
                         label='Target Function',
                         alpha=0.5)
        outcomes = np.zeros_like(self.target)
        for j, x in enumerate(self.domain):
            state = self.get_state(x)
            outcomes[j] = self.H.expectation(state)

        axs.scatter(self.domain[:, 0],
                    self.domain[:, 1],
                    outcomes,
                    color='C1',
                    label='Quantum ' + self.ansatz + ' model')

        fig.savefig(name)
        plt.close(fig)

    def paint_representation_2D_classical(self, prediction, name):
        fig = plt.figure()
        axs = fig.gca(projection='3d')

        prediction = np.array(list(prediction))

        axs.plot_trisurf(self.domain[:, 0],
                         self.domain[:, 1],
                         self.target,
                         color='black',
                         label='Target Function',
                         alpha=0.5)
        axs.scatter(self.domain[:, 0],
                    self.domain[:, 1],
                    prediction,
                    color='C0',
                    label='Classical ' + self.ansatz + ' model')

        fig.savefig(name)
        #plt.show()
        plt.close(fig)
class Approximant_real(Approximant):
    def __init__(self, layers, domain, ansatz, function):
        self.function = function
        super().__init__(layers, domain, ansatz)
        self.target = self.function(self.domain)
        self.target = 2 * (self.target - np.min(self.target)) / (
            np.max(self.target) - np.min(self.target)) - 1
        self.H = Hamiltonian(1, matrices._Z)
        self.classical = globals()[f"classical_real_{self.ansatz}"]

    def name_folder(self, quantum=True):
        folder = self.ansatz + '/' + self.function.name + '/%s_layers' % (
            self.layers)
        if quantum:
            folder = 'quantum/' + folder
        else:
            folder = 'classical/' + folder
        folder = 'results/' + folder
        import os
        try:
            l = os.listdir(folder)
            l = [int(_) for _ in l]
            l.sort()
            trial = int(l[-1]) + 1
        except:
            trial = 0
            os.makedirs(folder)
        fold_name = folder + '/%s' % (trial)
        os.makedirs(fold_name)
        return fold_name, trial

    def cf_one_point(self, x, f):
        state = self.get_state(x)
        o = self.H.expectation(state)
        cf = (o - f)**2
        return cf

    def derivative_cf_one_point(self, x, f):
        self.theta_with_x(x)
        derivatives = np.zeros_like(self.params)
        index = 0
        ch_index = 0
        for l in range(self.layers - 1):
            cir_params_ = self.cir_params.copy()
            delta = np.zeros_like(cir_params_)
            delta[ch_index] = np.pi / 2
            self.C.set_parameters(cir_params_ + delta)
            state = self.C()
            z1 = self.H.expectation(state)
            self.C.set_parameters(cir_params_ - delta)
            state = self.C()
            z2 = self.H.expectation(state)
            derivatives[index + 1] = 0.5 * ((z1**2 - z2**2) - 2 * f *
                                            (z1 - z2))
            derivatives[index] = x * derivatives[index + 1]
            index += 2
            ch_index += 1

            cir_params_ = self.cir_params.copy()
            delta = np.zeros_like(cir_params_)
            delta[ch_index] = np.pi / 2
            self.C.set_parameters(cir_params_ + delta)
            state = self.C()
            z1 = self.H.expectation(state)
            self.C.set_parameters(cir_params_ - delta)
            state = self.C()
            z2 = self.H.expectation(state)
            derivatives[index] = 0.5 * ((z1**2 - z2**2) - 2 * f * (z1 - z2))
            index += 1
            ch_index += 1

        cir_params_ = self.cir_params.copy()
        delta = np.zeros_like(cir_params_)
        delta[ch_index] = np.pi / 2
        self.C.set_parameters(cir_params_ + delta)
        state = self.C()
        z1 = self.H.expectation(state)
        self.C.set_parameters(cir_params_ - delta)
        state = self.C()
        z2 = self.H.expectation(state)
        derivatives[index + 1] = 0.5 * ((z1**2 - z2**2) - 2 * f * (z1 - z2))
        derivatives[index] = x * derivatives[index + 1]
        index += 2
        ch_index += 1

        return derivatives

    def derivative_cf(self, params):
        try:
            params = params.flatten()
        except:
            pass
        self.set_parameters(params)
        derivatives = np.zeros_like(params)
        for x, t in zip(self.domain, self.target):
            derivatives += self.derivative_cf_one_point(x, t)
        derivatives /= len(self.domain)

        return derivatives

    def paint_representation_1D(self, name):
        fig, axs = plt.subplots()

        axs.plot(self.domain,
                 self.target,
                 color='black',
                 label='Target Function')
        outcomes = np.zeros_like(self.domain)
        for j, x in enumerate(self.domain):
            state = self.get_state(x)
            outcomes[j] = self.H.expectation(state)

        axs.plot(self.domain,
                 outcomes,
                 color='C1',
                 label='Quantum ' + self.ansatz + ' model')

        axs.legend()

        fig.savefig(name)
        plt.close(fig)

    def paint_representation_1D_classical(self, prediction, name):
        fig, axs = plt.subplots()

        axs.plot(self.domain,
                 self.target,
                 color='black',
                 label='Target Function')
        axs.plot(self.domain,
                 prediction,
                 color='C0',
                 label='Classical ' + self.ansatz + ' model')
        axs.legend()

        fig.savefig(name)
        plt.close(fig)

    def paint_representation_2D(self):
        from mpl_toolkits.mplot3d import Axes3D
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')

        if self.num_functions == 1:
            ax = fig.gca(projection='3d')
            print('shape', self.target[0].shape)
            ax.plot_trisurf(self.domain[:, 0],
                            self.domain[:, 1],
                            self.target[:, 0],
                            label='Target Function',
                            linewidth=0.2,
                            antialiased=True)
            outcomes = np.zeros_like(self.domain)
            for j, x in enumerate(self.domain):
                C = self.circuit(x)
                state = C.execute()
                outcomes[j] = self.hamiltonian[0].expectation(state)

            ax.plot_trisurf(self.domain[:, 0],
                            self.domain[:, 1],
                            outcomes[:, 0] + 0.1,
                            label='Approximation',
                            linewidth=0.2,
                            antialiased=True)
            #ax.legend()
        else:
            for i in range(self.num_functions):
                ax = fig.add_subplot(1, 1, i + 1, projection='3d')
                ax.plot(self.domain,
                        self.functions[i](self.domain).flatten(),
                        color='black')
                outcomes = np.zeros_like(self.domain)
                for j, x in enumerate(self.domain):
                    C = self.circuit(x)
                    state = C.execute()
                    outcomes[j] = self.hamiltonian[i].expectation(state)

                ax.scatter(self.domain[:, 0],
                           self.domain[:, 1],
                           outcomes,
                           color='C0',
                           label=self.measurements[i])
                ax.legend()

        fig.savefig(name)
        plt.close(fig)

    def paint_historical(self, name):
        import matplotlib.pyplot as plt
        fig, axs = plt.subplots(nrows=2)
        axs[0].plot(np.arange(len(self.hist_chi)), self.hist_chi)
        axs[0].set(yscale='log', ylabel=r'$\chi^2$')
        hist_params = np.array(self.hist_params)
        for i in range(len(self.params)):
            axs[1].plot(np.arange(len(self.hist_chi)), hist_params[:, i],
                        'C%s' % i)

        axs[1].set(ylabel='Parameter', xlabel='Function evaluation')
        fig.suptitle('Historical behaviour', fontsize=16)
        fig.savefig(name)