Source code for vulqano.quantummodels.quantummodel

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

We map the circuit in a (1+1) dimensional qudits lattice. The first coordinate labels
the time-step, the other coordinates label the qubit where a gate is executed, the state of
each qudit represents the corrisponding executed gate.
"""


from itertools import product
import numpy as np
import qtealeaves as qtl
from qtealeaves import modeling
from vulqano.rules.abstractdiscreterules import generate_discrete_abstract_rules
from vulqano.quantummodels.localquantumops import TNGateOperators


__all__ = [
    "get_quantum_compilation_model",
]


[docs] def get_quantum_compilation_model( input_circuit, machine, qcd_instructions, shape, ): """ Maps the compilation problem into a quantum many-body system to simulate with qtealeaves. **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. **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. """ def generate_op_names_array(arr_a, arr_b): """ Take two matrix of gates [[GATEa_00, GATEa_01...]] and [[GATEb_00, GATEb_01...]] and returns a matrix of operators labels of the form [[GATEa_00->GATEb_00, GATEa_01->GATEb_01...]] **Arguments** arr_a : np.array of strings First matrix of gates. arr_b : np.array of strings Second matrix of gates. **Returns** arr_out : np.array of strings A matrix of operators labels like [[GATEa_00->GATEb_00, GATEa_01->GATEb_01...]] """ arr_out = np.full(arr_a.shape, "any->any", dtype=object) for idx, gate_a in np.ndenumerate(arr_a): arr_out[idx] = gate_a + "->" + arr_b[idx] return arr_out def mask_conversion(mask_in, op_shape): """ Convert a Vulqano operator mask (i.e., the operator can be applied to a rectangular region [x0:x1,y0:y1] iff np.any(mask[x0:x1,y0:y1]) is true) to a QunatumTEA callable operator mask (i.e., the operator can be applied to a rectangular region [x0:x1,y0:y1] iff mask[x0,y0] is true). If the input cirvuit volume is not a power of two, also enlarge the mask by adding false values. **Arguments** mask_in : np.array of bools Vulqano operator mask. op_shape : (times, qubits) Shape of the operator. **Returns** mask_function : callable QunatumTEA operator mask. """ mask_out = np.full(shape, False) for time in range(mask_in.shape[0] + 1 - op_shape[0]): for qubit in range(mask_in.shape[1] + 1 - op_shape[1]): if np.all( mask_in[time : time + op_shape[0], qubit : qubit + op_shape[1]] ): mask_out[time, qubit] = True def mask_function(parameters, mask_out=mask_out): return mask_out return mask_function def build_external_hamiltonian(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. """ external_mask = np.full(shape, True) external_mask[: input_circuit.times, : input_circuit.qubits[0]] = np.full( (input_circuit.times, input_circuit.qubits[0]), False ) def mask(parameters, external_mask=external_mask): return external_mask model += modeling.LocalTerm("idle->idle", mask=mask, prefactor=-1) def build_initial_hamiltonian(model, strength): """ 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. """ mask = np.full(shape, False) for time, qubit in product( range(input_circuit.times), range(input_circuit.qubits[0]) ): mask[time, qubit] = True operator_names = ( input_circuit.vector[time, qubit] + "->" + input_circuit.vector[time, qubit] ) model += modeling.LocalTerm( operator_names, strength=strength, prefactor=-1, mask=mask_conversion(mask, (1, 1)), ) mask[time, qubit] = False def build_driving_hamiltonian(model, strength): """ Build the driving Hamiltonian with the transitions that link equivalent circuits, 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. """ mask_all = np.full((input_circuit.times, input_circuit.qubits[0]), True) 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"] ) ): mask = mask_conversion(mask_all, abstract_rule.state_a.shape) operator_names_1 = generate_op_names_array( abstract_rule.state_a, abstract_rule.state_b ) operator_names_2 = generate_op_names_array( abstract_rule.state_b, abstract_rule.state_a ) if operator_names_1.shape[0] * operator_names_1.shape[1] == 1: model += modeling.LocalTerm( operator_names_1[0, 0], strength=strength, mask=mask ) model += modeling.LocalTerm( operator_names_2[0, 0], strength=strength, mask=mask ) else: model += modeling.BlockTerm2D( operator_names_1, strength=strength, mask=mask, has_obc=False ) model += modeling.BlockTerm2D( operator_names_2, strength=strength, mask=mask, has_obc=False ) def build_infidelity_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, weight, mask in machine["hamiltonian"]: if (operator.shape[0] <= input_circuit.times) and ( operator.shape[1] <= input_circuit.qubits[0] ): operator_names = generate_op_names_array(operator, operator) if operator_names.shape[0] * operator_names.shape[1] == 1: model += modeling.LocalTerm( operator_names[0, 0], strength=strength, prefactor=weight, mask=mask_conversion(mask, operator.shape), ) else: model += modeling.BlockTerm2D( operator_names, strength=strength, prefactor=weight, mask=mask_conversion(mask, operator.shape), has_obc=False, ) # ---------------- Define operators ---------------- my_ops = TNGateOperators(["idle"] + machine["gates"]) # ---------------- Define observables ---------------- my_obs = qtl.observables.TNObservables() for gate in ["idle", "busy"] + machine["gates"]: my_obs += qtl.observables.TNObsLocal("<" + gate + ">", gate + "->" + gate) # ---------------- Build the model ---------------- model = modeling.QuantumModel(2, "L", name="Circuit_optimization_mapping") build_external_hamiltonian(model) build_initial_hamiltonian(model, "alpha") build_driving_hamiltonian(model, "beta") build_infidelity_hamiltonian(model, "gamma") return (shape, my_ops, my_obs, model)