Source code for vulqano.quantummodels.collapsedquantummodel

# 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.


"""
Here we define functions to map the compilation problem into a quantum many-body
system to simulate with qtealeaves.

In the collapsed mapping, we map the circuit in a q-dimensional qudits lattice.
The coordinate labels the time-step, and the state of each qudit represents
a possible time-step.
"""


import numpy as np
import qtealeaves as qtl
from qtealeaves import modeling
from vulqano.gates.discretegates import GATES_DICTIONARY
from vulqano.rules.abstractdiscreterules import generate_discrete_abstract_rules
from vulqano.quantummodels.localquantumops import TNTimeStepOperators


__all__ = [
    "reverse_time_step",
    "CollapseMap",
    "get_collapsed_quantum_compilation_model",
]


[docs] def reverse_time_step(state): """ Reverse the order of the qubits in a time step. **Arguments** state : list of strings List of gate names that describe the time step. **Returns** reverse : list of strings List of gate names that describe the reversed time step. """ reverse = state.copy() for ii, gate in enumerate(reverse): if gate == "busy": reverse[ii] = reverse[ii - 1] reverse[ii - 1] = "busy" reverse.reverse() return reverse
[docs] class CollapseMap: """ Maps the circuit states and operators from a (1+1)d lattice to a 1d lattice by collasing the qubit axis. Each possible gates configuration of a time step is mapped to a single local state. **Arguments** gates : set of str Gates allowed on the target machine (fictitious gates such as "idle "and "busy" are excluded). circuit_times : int number of time steps allowed for the cicuit. n_t : int number of time steps represented in the lattice. n_q : int Number of qubits of the target machine. reflection_sym : bool, optional. If true, only the supergates that represent time-steps invariant under a reflection on the qubits axis are included. Default is false. WARNING: needs reflection invariant infidelity and transition rules. **Attributes** gates : set of str Gates allowed on the target machine (fictitious gates such as "idle "and "busy" are excluded). circuit_times : int number of time steps allowed for the cicuit. n_t : int number of time steps represented in the lattice. n_q : int Number of qubits of the target machine. collapsed_gates : list A list of each possible time-step configuration, identified by a string "gate_0|gate_1|...|gate_n_q". reflection_sym : bool If true, only the supergates that represent time-steps invariant under a reflection on the qubits axis are included. """ def __init__(self, gates, circuit_times, n_t, n_q, reflection_sym=False): self.gates = sorted(list(set(gates).union({"idle"}))) self.n_q = n_q self.n_t = n_t self.circuit_times = circuit_times for gate in self.gates: if gate not in GATES_DICTIONARY: raise ValueError("Gate " + gate + " not in GATES_DICTIONARY.") states = [[gate] for gate in self.gates] def increase(lst, is_last=False): if GATES_DICTIONARY[lst[-1]]["Connectivity"] == []: for gate in self.gates: if (not is_last) or GATES_DICTIONARY[gate]["Connectivity"] == []: yield lst + [gate] elif GATES_DICTIONARY[lst[-1]]["Connectivity"] == [1]: yield lst + ["busy"] else: raise NotImplementedError( "The connectivity of the gate " + lst[-1] + " can not be managed by CollapseMap" ) for _ in range(1, self.n_q - 1): states = [new_state for state in states for new_state in increase(state)] states = [ new_state for state in states for new_state in increase(state, is_last=True) ] self.reflection_sym = reflection_sym if reflection_sym: symmetric_states = [] for state in states: reverse = reverse_time_step(state) if state == reverse: symmetric_states.append(state) states = symmetric_states collapsed_gates = ["|".join(state) for state in states] self.collapsed_gates = sorted(collapsed_gates)
[docs] def collapse_substate(self, state, position=0): """ Collapse a circuit or a subcircuit. **Arguments** state : matrix of strings Description of the local (sub) circuit to be collapsed. If the number of qubits of the subcircuit is smaller than the number of qubits of the circuit, the remaining qubits are filled with "any" (identity operator) gates. position : int, optional Qubit position of the subcircuit. The default is 0. **Returns** collapsed_state : list of strings Collapsed decsription of the state, encoded as a list of supergates names. """ offset_up = ["any" for ii in range(position)] offset_down = ["any" for ii in range(self.n_q - position - len(state[0]))] collapsed_state = [] for time_step in state: collapsed_state.append(offset_up + list(time_step) + offset_down) return collapsed_state
[docs] def decollapse_state(self, collapsed_state): """ Transform a collapsed a circuit to a non-collapsed circuit. **Arguments** collapsed_state : list of strings Collapsed decsription of the state, encoded as a list of supergates names. **Returns** state : matrix of strings Description of the circuit state in terms of gates at each qubit and time-step. """ state = [] for time_step in range(self.circuit_times): state.append(collapsed_state[time_step].split("|")) return state
[docs] def collapse_rule(self, rule): """ Generates a collapsed version of a transition rule. **Arguments** rule : instance of DiscreteTransformationRule Transition rule to be encoded in a set of collapsed rules. **Yields** List of collapsed rules encoding the applicat of the original rule to different slices of qubits. collapsed_state_a : list of strings or None Collapsed decsription of the state_a of the rule, encoded as a list of supergates names. If the reflection symmetry is enabled, returns None for non symmetric states. collapsed_state_b : list of strings or None Collapsed decsription of the state_b of the rule, encoded as a list of supergates names. If the reflection symmetry is enabled, returns None for non symmetric states. """ if np.any(rule.state_a == "any_rot"): raise NotImplementedError( "Collapsing rules with 'any_rot' gate not implemented." ) for position in range(1 + self.n_q - rule.shape[1]): collapsed_state_a = self.collapse_substate(rule.state_a, position=position) collapsed_state_b = self.collapse_substate(rule.state_b, position=position) yield (collapsed_state_a, collapsed_state_b)
[docs] def collapse_hamiltonian_term(self, operator, prefactor, mask): """ Generate a collapsed version of an Hamiltonian term. **Parameters** operator : np.array of strings Block of local operators, expressed as tensor product of the local operators. prefactor : float Coupling of the term in the Hamiltonian mask : mask The operator is applied on a region A if the mask is true in each site of the region. """ operator = operator.astype("object") for ii, gate in enumerate(operator[0]): if GATES_DICTIONARY[gate]["Connectivity"] == [1]: if ii + 1 < len(operator[0]): operator[0][ii + 1] = "busy" else: operator = np.concatenate((operator, [["busy"]]), axis=1) for qubit in range(mask.shape[1] + 1 - operator.shape[1]): collapsed_operator = self.collapse_substate(operator, position=qubit) if self.reflection_sym and any( (time_step != reverse_time_step(time_step)) for time_step in collapsed_operator ): print( "WARNING: the operator ", collapsed_operator, "<->", collapsed_operator, " is not symmetric. It will not be included in the Infidelity Hamiltonian.", ) yield (None, None, None) else: collapsed_mask = np.full(self.n_t, False) for time in range(mask.shape[0] + 1 - operator.shape[0]): if np.all( mask[ time : time + operator.shape[0], qubit : qubit + operator.shape[1], ] ): collapsed_mask[time] = True yield (collapsed_operator, prefactor, collapsed_mask)
[docs] def build_external_hamiltonian(self, model): """ Build an Hamiltonian that sets as idle all the lattice sites that are external to the ciruit area. **Arguments** model : qtealeaves.modeling.QuantumModel QuantumModel describing the evolution Hamiltonian. **Returns** None. """ all_idle_supergate = "|".join(["idle" for ii in range(self.n_q)]) mask_array = np.full(self.n_t, True) mask_array[: self.circuit_times] = np.full(self.circuit_times, False) def external_mask(parameters, mask_array=mask_array): return mask_array model += modeling.LocalTerm( all_idle_supergate + "->" + all_idle_supergate, mask=external_mask, prefactor=-1 * self.n_q, )
[docs] def build_initial_hamiltonian(self, state, model, strength, position=0): """ Build an Hamiltonian having the initial state as non-degenerate ground state, add this Hamiltonian to the model **Arguments** model : qtealeaves.modeling.QuantumModel QuantumModel describing the evolution Hamiltonian. strength : string Name of the coupling associated to the Hamiltonian. **Returns** None. """ collapsed_state = self.collapse_substate(state, position=position) if self.reflection_sym and any( (time_step != reverse_time_step(time_step)) for time_step in collapsed_state ): raise ValueError("The initial state " + str(state) + " is not symmetric.") for ii, time_step in enumerate(collapsed_state): mask_array = np.full(self.n_t, False) mask_array[ii] = True def initial_mask(parameters, mask_array=mask_array): return mask_array model += modeling.LocalTerm( "|".join(time_step) + "->" + "|".join(time_step), strength=strength, prefactor=-1 * self.n_q, mask=initial_mask, )
[docs] def build_driving_hamiltonian(self, rule, model, strength): """ Build a term in the driving Hamiltonian encoding a transition rule, add this Hamiltonian to the model. **Arguments** rule: instance of DiscreteTransformationRule Transition rule to be encoded as operator. model : qtealeaves.modeling.QuantumModel QuantumModel describing the evolution Hamiltonian. strength : string Name of the coupling associated to the Hamiltonian term. **Returns** None. """ for state_a, state_b in self.collapse_rule(rule): if self.reflection_sym and ( any( (time_step != reverse_time_step(time_step)) for time_step in state_a ) or any( (time_step != reverse_time_step(time_step)) for time_step in state_a ) ): print( "WARNING: the operator ", state_a, "<->", state_b, " is not symmetric. It will not be included in the Driving Hamiltonian.", ) else: operator_name_1 = [ "|".join(time_step_a) + "->" + "|".join(time_step_b) for (time_step_a, time_step_b) in zip(state_a, state_b) ] operator_name_2 = [ "|".join(time_step_b) + "->" + "|".join(time_step_a) for (time_step_a, time_step_b) in zip(state_a, state_b) ] mask_array = np.full(self.n_t, False) mask_array[: 1 + self.circuit_times - len(state_a)] = np.full( 1 + self.circuit_times - len(state_a), True ) def driving_mask(parameters, mask_array=mask_array): return mask_array if len(operator_name_1) == 1: model += modeling.LocalTerm( operator_name_1[0], strength=strength, mask=driving_mask ) model += modeling.LocalTerm( operator_name_2[0], strength=strength, mask=driving_mask ) else: model += modeling.StringTerm1D( operator_name_1, strength=strength, mask=driving_mask, has_obc=False, ) model += modeling.StringTerm1D( operator_name_2, strength=strength, mask=driving_mask, has_obc=False, )
[docs] def build_infidelity_hamiltonian(self, hamiltonian, model, strength): """ Build an Hamiltonian that extimated the circuit infidelity, add this Hamiltonian to the model **Arguments** model : qtealeaves.modeling.QuantumModel QuantumModel describing the evolution Hamiltonian. strength : string Name of the coupling associated to the Hamiltonian. **Returns** None. """ for operator, prefactor, mask in hamiltonian: for ( collapsed_operator, prefactor, collapsed_mask, ) in self.collapse_hamiltonian_term(operator, prefactor, mask): if collapsed_operator is not None: operator_name = [ "|".join(time_step) + "->" + "|".join(time_step) for time_step in collapsed_operator ] def infidelity_mask(parameters, collapsed_mask=collapsed_mask): return collapsed_mask if len(operator_name) == 1: model += modeling.LocalTerm( operator_name[0], strength=strength, prefactor=prefactor, mask=infidelity_mask, ) else: model += modeling.StringTerm1D( operator_name, strength=strength, prefactor=prefactor, mask=infidelity_mask, has_obc=False, )
[docs] def get_collapsed_quantum_compilation_model( input_circuit, machine, qcd_instructions, shape, collapse_map, ): """ Maps the compilation problem into a quantum many-body system to simulate with qtealeaves. In this collapsed mapping, each qudit encodes a time step of the circuit. **Arguments** input_circuit : instance of ``AbstractCircuitState`` Classical circuit state, the initial state of the dynamics. machine : dictionary 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]. gates : set Gates enabled on the machine (virtual gates included). qcd_instructions : dictionary annealing_T : float Annealing time. rules_classes : str or list of ints, optional A list of the ints identifying the rule classes that we want to generate. Default is "all" and generates all the rule classes. generators : "std" or list of generators, optional The list of generators producing the rules to be used. Default is "std", in this case a standard list of rules is used. annealing_schedule : char, optional Pulse schedule for the annealing process. Default is A. max_rules_volume : int, optional Maximum volume allowed for the rules to be included in the driving Hamiltonian. Default is 100. shape : tuple of ints Shape of the lattice where the circuit is encoded, including fictitious sites added to fit in a TTN. collapse_map : instance of ``CollapseMap`` Describes how each possible time step is associated to a qudit state. **Returns** shape : tuple of ints Shape of the lattice where the circuit is encoded, including fictitious sites added to fit in a TTN. my_ops : instance of ``qtealeaves.operators.TNOperators`` Operators that transform a gate state into another gate state. my_obs : instance of ``qtealeaves.observables.TNObservables`` Defines the observables of the simulation. For details, check the corresponding class. model : instance of ``qtealeaves.modeling.QuantumModel`` System Hamiltonian in the form H = H_ext + alpha*H_initial + beta*H_driving + gamma*H_final where: -H_ext = sum_i |idle><idle| is a 1-local Hamiltonian acting on the sites of the lattice that does not correspond to a circuit site. -H_initial is 1-local Hamiltonian having the initial circuit as ground state. -H_driving is the Hamiltonian that drives the evolution by creating superpositions of equivalent circuit. -H_final is the Hamiltonian that encodes the infidelity function. """ if input_circuit.dim != 2: raise NotImplementedError( "Collapsed quantum compilation only implemented for 1d qubits lattices." ) # ---------------- Define observables ---------------- my_obs = qtl.observables.TNObservables() for supergate_name in collapse_map.collapsed_gates: my_obs += qtl.observables.TNObsLocal( "<" + supergate_name + ">", supergate_name + "->" + supergate_name ) # ---------------- Build the model ---------------- model = modeling.QuantumModel(1, "L", name="Collapsed_circuit_optimization_mapping") collapse_map.build_external_hamiltonian(model) collapse_map.build_initial_hamiltonian(input_circuit.vector, model, "alpha") for abstract_rule in generate_discrete_abstract_rules( set(machine["gates"]), 2, rules_classes=qcd_instructions["rules_classes"], generators=qcd_instructions["generators"], ): if ( (abstract_rule.state_a.shape[0] <= input_circuit.times) and (abstract_rule.state_a.shape[1] <= input_circuit.qubits[0]) and ( np.product(abstract_rule.shape) <= qcd_instructions["max_rules_volume"] ) ): collapse_map.build_driving_hamiltonian(abstract_rule, model, "beta") collapse_map.build_infidelity_hamiltonian(machine["hamiltonian"], model, "gamma") time_step_transformations = [ time_step_configuration + "->" + time_step_configuration for time_step_configuration in collapse_map.collapsed_gates ] for elem in model.hterms: for op_tuple in elem.collect_operators(): if op_tuple[0] not in time_step_transformations: time_step_transformations.append(op_tuple[0]) # ---------------- Define operators ---------------- my_ops = TNTimeStepOperators( collapse_map.collapsed_gates, time_step_transformations ) shape = (shape[0],) return (shape, my_ops, my_obs, model)