Source code for vulqano.hamiltonians.mchamiltonians

# This code is part of vulqano.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.


"""
Define a class for Markov chain infidelity Hamiltonians.
"""

import itertools
import numpy as np
from numpy import ma
from vulqano.gates.discretegates import (
    gate_labels_to_ints as discrete_gate_labels_to_ints,
)
from vulqano.gates.continuousgates import (
    gate_labels_to_ints as continuous_gate_labels_to_ints,
)
from vulqano.states.abstractcircuitstate import (
    AbstractCircuitState,
    compact_hamiltonian,
)
from vulqano.states.mcstates import DiscreteMCState, ContinuousMCState


__all__ = [
    "MCHamiltonian",
]


[docs] class MCHamiltonian: """ Class for continuous and discrete MC Hamiltonians encoding infidelity. The dictionary in input define the Hamiltonian. While in the input Hamiltonian the gates are encoded as strings, in Markov chain Hamiltonian the gates are labeled by integers. This different encoding allows to more quickly count a subciruit with a given cost appears in the circuit state. **Arguments** hamiltonian_operator : list of (np.array of strings, float, mask) Abstract description of the Hamiltonian. The energy is obtained by counting how many times each subcircuit hamiltonian_operator[i][0] appears on a region A of the circuit suck that that hamiltonian_operator[i][2] is True for all (t,q) in A. The counted number is multiplied by the weight hamiltonian_operator[i][1]. dim : int Number of dimension of the circuit state on which the Hamiltonian acts, i.e. 1 + number of dimensions of the qubits lattice. is_continuous : bool If true the Hamiltonian acts on continuous states. If false it acts on discrete states. **Attributes** hamiltonian_operator : list of (np.array of ints, np.array of floats) Abstract description of the Hamiltonian. The energy is obtained as E = sum_i sum_{t,q} IS(hamiltonian_operator[i][0](t,q))* hamiltonian_operator[i][1](t,q) Where IS(hamiltonian_operator[i][0](t,q)) checks if the subcircuit hamiltonian_operator[i][0] is appears on the site (t,q) of the circuit state. dim : int Number of dimension of the circuit state on which the Hamiltonian acts, i.e. 1 + number of dimensions of the qubits lattice. interactions_range : (int,int) A tuple with two ints, respectively corresponding to the interaction range (the maximum distance between interacting gates of the time-qubits lattice) in the time and qubits directions. is_continuous : bool If true the Hamiltonian acts on continuous states. If false it acts on discrete states. """ def __init__(self, hamiltonian_operator, dim, is_continuous): self.hamiltonian_operator = compact_hamiltonian(hamiltonian_operator) self.dim = dim self.is_continuous = is_continuous if is_continuous: gate_labels_to_ints = continuous_gate_labels_to_ints else: gate_labels_to_ints = discrete_gate_labels_to_ints tmp_ham = [] for operator, coupling in self.hamiltonian_operator: if len(operator.shape) != dim: raise ValueError( "The Hamiltonian operators and state must have the same number of dimensions." ) tmp_ham.append( ( ma.masked_equal( gate_labels_to_ints(operator), gate_labels_to_ints("any"), ), coupling, ) ) self.hamiltonian_operator = tmp_ham self.interactions_range = [ max(elem[0].shape[ii] for elem in self.hamiltonian_operator) - 1 for ii in range(self.dim) ]
[docs] def get_energy(self, state_vector, position=None): """ Returns the energy associated by the Hamiltonian to a (sub)circuit state. **Arguments** state_vector : numpy.array of ints A vector representing the circuit (or subcircuit) state. position : tuple ints or None, optional Position of the first gate of the subcircuit in the global circuit state. If None the position is (0,...,0). Default is None. **Returns** energy : float The expectation value of the Hamiltonian (infidelity cost). """ if position is None: position = [0 for ii in range(self.dim)] energy = 0 vector_shape = np.array(state_vector.shape) for operator, coupling in self.hamiltonian_operator: op_shape = np.array(operator.shape) for site in itertools.product( *[range(s) for s in vector_shape - op_shape + 1] ): if np.ma.allequal( operator, state_vector[ tuple( slice(site[i], (site + op_shape)[i]) for i in range(self.dim) ) ], ): energy += coupling[ tuple(position[ii] + site[ii] for ii in range(self.dim)) ] return energy
[docs] def get_energy_diff(self, state_vector, transition): """ Returns the energy difference generated in the circuit by appliyng a rule that repleces a subcircuit in a given region. **Arguments** state_vector : np.array of strings A vector representing the circuit state. transition : (window, (rule, direction)) Description of the transition to be applied-> window : touple of ints Region of the circuit where the transition rule applies: (t_min,q1_min,q2_min,...,t_max,q1_max,q2_max,... ) rule : DiscreteMCRule Transformation rule to apply. direction : bool Direction of the transformation rule: True for state_a->state_b, False for state_b->state_a. **Returns** energy_diff : float Energy after the transition - energy before the transition. transition_instructions : (np array of ints, mask) Matrix representing the new subcircuit. Where the mask is true, the gate is not raplaced. """ if self.is_continuous: mask = np.logical_not(transition[1][0].any_mask) else: mask = np.logical_not(transition[1][0].masks[0] | transition[1][0].masks[1]) if transition[1][1]: # From A to B new_local = transition[1][0].state_b else: # From B to A new_local = transition[1][0].state_a outer_window = [ max(transition[0][ii] - self.interactions_range[ii], 0) for ii in range(self.dim) ] + [ min( transition[0][ii] + transition[0][ii + self.dim] + self.interactions_range[ii], state_vector.shape[ii], ) for ii in range(self.dim) ] local_state_old = state_vector[ tuple( slice(outer_window[ii], outer_window[ii + self.dim] + 1) for ii in range(self.dim) ) ] local_state_new = np.copy(local_state_old) np.putmask( local_state_new[ tuple( slice( transition[0][ii] - outer_window[ii], transition[0][ii] + transition[0][ii + self.dim] - outer_window[ii] + 1, ) for ii in range(self.dim) ) ], mask, new_local, ) energy_diff = self.get_energy( local_state_new, position=outer_window[: self.dim] ) - self.get_energy(local_state_old, position=outer_window[: self.dim]) transition_instructions = (new_local, mask) return energy_diff, transition_instructions
def unit_test_discrete(): """ Compares the energy calculated with a MCHamiltonian on an MCState with the energy calculated with an abstract Hamiltonian on a AbstractCircuitState. Returns ------- bool True if the difference is zero. """ vector = np.array( [ [ "idle", "Z", "idle", "Z", "idle", "Z", "idle", "Z", "idle", "Z", "idle", "Z", "idle", "Z", "idle", "Z", ], [ "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", ], [ "CZ", "busy", "CZ", "busy", "CZ", "busy", "CZ", "busy", "CZ", "busy", "CZ", "busy", "CZ", "busy", "CZ", "busy", ], [ "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", "idle", "H", ], [ "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", ], ] ) circuit_state = AbstractCircuitState(vector, "") swap_left = 1 swap_right = 2 circuit_state.add_swap_area(swap_left, swap_right) circuit_area_mask = np.concatenate( ( np.full(np.append(swap_left, circuit_state.qubits), False), np.full( np.append( circuit_state.times - swap_right - swap_left, circuit_state.qubits ), True, ), np.full(np.append(swap_right, circuit_state.qubits), False), ) ) swap_area_mask = np.logical_not(circuit_area_mask) hamiltonian = ( (np.array([["Z"]]), 0.001, circuit_area_mask), (np.array([["H"]]), 0.001, circuit_area_mask), (np.array([["idle"]]), 0.001, circuit_area_mask), (np.array([["CZ"]]), 0.005, circuit_area_mask), (np.array([["SWAP"]]), 0.5, circuit_area_mask), (np.array([["CZ", "any", "CZ"]]), 0.05, circuit_area_mask), ( np.array([["CZ", "any", "any", "CZ"]]), 0.005, circuit_area_mask, ), ( np.array([["CZ", "any", "any", "any", "CZ"]]), 0.0005, circuit_area_mask, ), ( np.full(np.append(1, circuit_state.qubits), "idle"), -np.prod(circuit_state.qubits) * 0.001, circuit_area_mask, ), (np.array([["SWAP"]]), 0, swap_area_mask), (np.array([["Z"]]), 1, swap_area_mask), (np.array([["H"]]), 1, swap_area_mask), (np.array([["CZ"]]), 1, swap_area_mask), ) energy_diff = np.abs( circuit_state.get_energy(hamiltonian) - MCHamiltonian(hamiltonian, circuit_state.dim, False).get_energy( DiscreteMCState(circuit_state).vector ) ) return energy_diff < 10 ** (-15) def unit_test_continuous(): """ Compares the energy calculated with a MCHamiltonian on an MCState with the energy calculated with an abstract Hamiltonian on a AbstractCircuitState. Returns ------- bool True if the difference is zero. """ vector = np.array( [ [ "idle", "RZ", "idle", "RZ", "idle", "RZ", "idle", "RZ", "idle", "RZ", "idle", "RZ", "idle", "RZ", "idle", "RZ", ], [ "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", ], [ "CP", "busy", "CP", "busy", "CP", "busy", "CP", "busy", "CP", "busy", "CP", "busy", "CP", "busy", "CP", "busy", ], [ "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", "idle", "RX", ], [ "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", "SWAP", "busy", ], ] ) rot_amplitudes_array = np.array( [ [ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, ], [ 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, ], [ 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, ], [ 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], ] ) circuit_state = AbstractCircuitState( vector, "", rot_amplitudes_array=rot_amplitudes_array ) swap_left = 1 swap_right = 2 circuit_state.add_swap_area(swap_left, swap_right) circuit_area_mask = np.concatenate( ( np.full(np.append(swap_left, circuit_state.qubits), False), np.full( np.append( circuit_state.times - swap_right - swap_left, circuit_state.qubits ), True, ), np.full(np.append(swap_right, circuit_state.qubits), False), ) ) swap_area_mask = np.logical_not(circuit_area_mask) hamiltonian = ( (np.array([["RZ"]]), 0.001, circuit_area_mask), (np.array([["RX"]]), 0.001, circuit_area_mask), (np.array([["idle"]]), 0.001, circuit_area_mask), (np.array([["CP"]]), 0.005, circuit_area_mask), (np.array([["SWAP"]]), 0.5, circuit_area_mask), (np.array([["CP", "any", "CP"]]), 0.05, circuit_area_mask), ( np.array([["CP", "any", "any", "CP"]]), 0.005, circuit_area_mask, ), ( np.array([["CP", "any", "any", "any", "CP"]]), 0.0005, circuit_area_mask, ), ( np.full(np.append(1, circuit_state.qubits), "idle"), -np.prod(circuit_state.qubits) * 0.001, circuit_area_mask, ), (np.array([["SWAP"]]), 0, swap_area_mask), (np.array([["RZ"]]), 1, swap_area_mask), (np.array([["RX"]]), 1, swap_area_mask), (np.array([["CP"]]), 1, swap_area_mask), ) energy_diff = np.abs( circuit_state.get_energy(hamiltonian) - MCHamiltonian(hamiltonian, circuit_state.dim, True).get_energy( ContinuousMCState(circuit_state).vector ) ) return energy_diff < 10 ** (-15) if __name__ == "__main__": print(unit_test_discrete()) print(unit_test_continuous())