def from_backend(cls, backend: BaseBackend): """Construct an :class:`InstructionDurations` object from the backend. Args: backend: backend from which durations (gate lengths) and dt are extracted. Returns: InstructionDurations: The InstructionDurations constructed from backend. Raises: TranspilerError: If dt and dtm is different in the backend. """ # All durations in seconds in gate_length instruction_durations = [] for gate, insts in backend.properties()._gates.items(): for qubits, props in insts.items(): if 'gate_length' in props: gate_length = props['gate_length'][ 0] # Throw away datetime at index 1 instruction_durations.append( (gate, qubits, gate_length, 's')) for q, props in backend.properties()._qubits.items(): if 'readout_length' in props: readout_length = props['readout_length'][ 0] # Throw away datetime at index 1 instruction_durations.append( ('measure', [q], readout_length, 's')) try: dt = backend.configuration().dt # pylint: disable=invalid-name except AttributeError: dt = None return InstructionDurations(instruction_durations, dt=dt)
def create_from_backend(cls, backend: BaseBackend): """Initialize a class with backend information provided by provider. Args: backend: Backend object. Returns: OpenPulseBackendInfo: New configured instance. """ configuration = backend.configuration() defaults = backend.defaults() # load name name = backend.name() # load cycle time dt = configuration.dt # load frequencies chan_freqs = dict() chan_freqs.update({ pulse.DriveChannel(qind): freq for qind, freq in enumerate(defaults.qubit_freq_est) }) chan_freqs.update({ pulse.MeasureChannel(qind): freq for qind, freq in enumerate(defaults.meas_freq_est) }) for qind, u_lo_mappers in enumerate(configuration.u_channel_lo): temp_val = .0 + .0j for u_lo_mapper in u_lo_mappers: temp_val += defaults.qubit_freq_est[ u_lo_mapper.q] * u_lo_mapper.scale chan_freqs[pulse.ControlChannel(qind)] = temp_val.real # load qubit channel mapping qubit_channel_map = defaultdict(list) for qind in range(configuration.n_qubits): qubit_channel_map[qind].append(configuration.drive(qubit=qind)) qubit_channel_map[qind].append(configuration.measure(qubit=qind)) for tind in range(configuration.n_qubits): try: qubit_channel_map[qind].extend( configuration.control(qubits=(qind, tind))) except BackendConfigurationError: pass return OpenPulseBackendInfo(name=name, dt=dt, channel_frequency_map=chan_freqs, qubit_channel_map=qubit_channel_map)
def from_backend(cls, backend: BaseBackend, **kwargs): """Automatically loads a QuaC noise model given a backend of type BaseBackend. Primarily for speeding up definition of IBMQ hardware noise models in QuaC :param backend: an object of type BaseBackend :param kwargs: an optional dictionary mapping strings to booleans stating which types of noise to include (keys are t1, t2, meas, and zz) :return: a QuacNoiseModel object """ n_qubits = len(backend.properties().qubits) qubits = list(range(n_qubits)) # Set up defaults t1_times = [float('inf') for _ in range(n_qubits)] t2_times = [float('inf') for _ in range(n_qubits)] meas_matrices = None zz = None # Adjust defaults as appropriate if kwargs.get("t1"): t1_times = [ backend.properties().t1(qubit) * 1e9 for qubit in qubits ] if kwargs.get("t2"): t2_times = [ backend.properties().t2(qubit) * 1e9 for qubit in qubits ] if kwargs.get("meas"): meas_matrices = [] # Construct probability matrix for measurement error adjustments for qubit in range(n_qubits): # Not all backends have measurement errors added try: prob_meas0_prep1 = backend.properties().qubit_property( qubit, "prob_meas0_prep1")[0] prob_meas1_prep0 = backend.properties().qubit_property( qubit, "prob_meas1_prep0")[0] except BackendPropertyError: warnings.warn( "Measurement error simulation not supported on this backend" ) break qubit_measurement_error_matrix = np.array( [[1 - prob_meas1_prep0, prob_meas0_prep1], [prob_meas1_prep0, 1 - prob_meas0_prep1]]) meas_matrices.append(qubit_measurement_error_matrix) if kwargs.get("zz"): warnings.warn("ZZ coupling not supported in automatic loading") return QuacNoiseModel(t1_times, t2_times, meas_matrices, zz)
def optimize_noise_model_ng( guess_noise_model: QuacNoiseModel, circuits: List[QuantumCircuit], backend: BaseBackend, reference_result: Result, loss_function: Callable[[np.array], float]) -> QuacNoiseModel: """Refines T1 and T2 noise parameters until the divergence between the probability distributions of a provided list of circuits simulated with the plugin and run with the hardware is minimized using the Nevergrad TwoPointsDE optimizer :param guess_noise_model: a QuacNoiseModel object as an initial ideal guess :param circuits: a list of QuantumCircuit objects for loss computation :param backend: the backend simulator on which circuits should be run :param reference_result: the Qiskit Result object from running circuits on real hardware :param loss_function: the loss function that should be used for optimization (i.e., kl_objective_function) :return: an optimized QuacNoiseModel object """ arr = ng.p.Array(init=guess_noise_model.to_array()) arr.set_bounds(0, float('inf')) param = ng.p.Instrumentation(arr, circuits, backend, reference_result) optimizer = ng.optimizers.TwoPointsDE(parametrization=param, budget=100, num_workers=5) print(optimizer.num_workers) with futures.ThreadPoolExecutor( max_workers=optimizer.num_workers) as executor: result = optimizer.minimize(loss_function, executor=executor, batch_mode=True, verbosity=2) print(result[0][0].value) return QuacNoiseModel.from_array(result[0][0].value, backend.configuration().n_qubits)
def __init__(self, backend: BaseBackend, optimisation_level: int = 1): """Identifies a Qiskit backend and provides the corresponding default compilation pass from pytket as a :py:class:`qiskit.transpiler.TransformationPass`. :param backend: The Qiskit backend to target. Accepts Aer or IBMQ backends. :type backend: BaseBackend :param optimisation_level: The level of optimisation to perform during compilation. Level 0 just solves the device constraints without optimising. Level 1 additionally performs some light optimisations. Level 2 adds more intensive optimisations that can increase compilation time for large circuits. Defaults to 1. :type optimisation_level: int, optional """ if not isinstance(backend, BaseBackend): raise ValueError("Requires BaseBackend instance") if isinstance(backend._provider, AerProvider): tk_backend = self._aer_backend_map[type(backend).__name__]() elif isinstance(backend._provider, AccountProvider): tk_backend = IBMQBackend(backend.name()) else: raise NotImplementedError( "This backend provider is not supported.") super().__init__( tk_backend.default_compilation_pass(optimisation_level))
def quac_time_qasm_transpiler(circuit: QuantumCircuit, backend: BaseBackend) -> str: """Converts a circuit of type QuantumCircuit to a string of TIMEQASM specification :param circuit: a QuantumCircuit (need not be transpiled) :param backend: a specific backend to generate the QASM for (for tranpsilation) :return: a string containing necessary QASM with times for each gate """ # Get original QASM transpiled_circuit = transpile(circuit, backend) original_qasm = transpiled_circuit.qasm() # Get body of original QASM start = 2 + len(circuit.qregs) + len(circuit.cregs) original_qasm_body = original_qasm.splitlines()[start:] # Formulate header qasm_modified = "TIMEQASM 1.0;\n" qasm_modified += "\n".join(original_qasm.splitlines()[1:start]) qasm_modified += "\n" # Schedule circuit qobj = assemble(transpiled_circuit, backend) qexp = qobj.experiments[0] qschedule = list_schedule_experiment(qexp, backend.properties()) # Formulate body for instruction, time in qschedule: # Note: ID was custom injected by the scheduler in this plugin qasm_modified += f"{original_qasm_body[instruction.id][:-1]} @{time};\n" return qasm_modified
def __validate_number_of_clbits(self, experiment: QasmQobjExperiment) -> None: """ Checks whether the number of classical bits has a value cQASM can support. 1. When number of classical bits is less than 1 an error is raised. 2. When binary controlled gates are used and the number of classical registers > number of classical registers an error is raised. When using binary controlled gates in Qiskit, we can have something like: q = QuantumRegister(2) c = ClassicalRegister(4) circuit = QuantumCircuit(q, c) circuit.h(q[0]).c_if(c, 15) Because cQASM has the same number of classical registers as qubits (2 in this case), this circuit cannot be translated to valid cQASM. Args: experiment: The experiment with gate operations and header. Raises: QisKitBackendError: When the value is not correct. """ number_of_clbits = experiment.header.memory_slots if number_of_clbits < 1: raise QisKitBackendError("Invalid amount of classical bits ({})!".format(number_of_clbits)) if BaseBackend.configuration(self).conditional: number_of_qubits = experiment.header.n_qubits if number_of_clbits > number_of_qubits: # no problem when there are no conditional gate operations for instruction in experiment.instructions: if hasattr(instruction, 'conditional'): raise QisKitBackendError("Number of classical bits must be less than or equal to the" " number of qubits when using conditional gate operations")
def from_backend(cls, backend: BaseBackend): """Construct an :class:`InstructionDurations` object from the backend. Args: backend: backend from which durations (gate lengths) and dt are extracted. Returns: InstructionDurations: The InstructionDurations constructed from backend. Raises: TranspilerError: If dt and dtm is different in the backend. """ # All durations in seconds in gate_length instruction_durations = [] for gate, insts in backend.properties()._gates.items(): for qubits, props in insts.items(): if 'gate_length' in props: gate_length = props['gate_length'][ 0] # Throw away datetime at index 1 instruction_durations.append( (gate, qubits, gate_length, 's')) try: dt = backend.configuration().dt # pylint: disable=invalid-name except AttributeError: dt = None # TODO: backend.properties() should tell us durations of measurements # TODO: Remove the following lines after that try: dtm = backend.configuration().dtm if dtm != dt: raise TranspilerError("dtm != dt case is not supported.") inst_map = backend.defaults().instruction_schedule_map all_qubits = tuple(range(backend.configuration().num_qubits)) meas_duration = inst_map.get('measure', all_qubits).duration for q in all_qubits: instruction_durations.append( ('measure', [q], meas_duration, 'dt')) except AttributeError: pass return InstructionDurations(instruction_durations, dt=dt)
def _tk_gate_set(backend: BaseBackend) -> Set[OpType]: """ Set of tket gate types supported by the qiskit backend """ config = backend.configuration() if config.simulator: return { _gate_str_2_optype[gate_str] for gate_str in config.basis_gates if gate_str in _gate_str_2_optype }.union({OpType.Measure, OpType.Reset, OpType.Barrier}) else: return { _gate_str_2_optype[gate_str] for gate_str in config.supported_instructions if gate_str in _gate_str_2_optype }
def create_rb_experiment(rb_seed_circs: List[QuantumCircuit], control: int, target: int, backend: BaseBackend, cnot_sched_control_target: Schedule = None, cnot_sched_target_control: Schedule = None, shots: int = 1024)\ -> Tuple[List[PulseQobj], List[List[QuantumCircuit]]]: """ Create randomized benchmark qobj. Args: rb_seed_circs: RB circuits. control: index of control qubit. target: index of target qubit. backend: Target quantum system. cnot_sched_control_target: Schedule of CX(control, target) cnot_sched_target_control: Schedule of CX(target, control) shots: Number of shots. """ back_defaults = backend.defaults() rb_inst_map = deepcopy(back_defaults.instruction_schedule_map) # update circuit instruction map if cnot_sched_control_target is not None and cnot_sched_target_control is not None: rb_inst_map.add('cx', qubits=(control, target), schedule=cnot_sched_control_target) rb_inst_map.add('cx', qubits=(target, control), schedule=cnot_sched_target_control) pulse_qobjs = [] transpiled_circs = [] for rb_seed_circ in rb_seed_circs: # transpile rb_seed_circ_transpiled = qiskit.transpile(rb_seed_circ, backend, optimization_level=0) transpiled_circs.append(rb_seed_circ_transpiled) # schedule rb_seed_sched = qiskit.schedule(rb_seed_circ_transpiled, backend, inst_map=rb_inst_map) # create pulse qobj pulse_qobjs.append( qiskit.assemble(rb_seed_sched, backend, meas_level=2, shots=shots)) return pulse_qobjs, transpiled_circs
def create_qpt_experiment(target_circuits: List[QuantumCircuit], control: int, target: int, backend: BaseBackend, mit_readout: bool = True, inst_map: InstructionScheduleMap = None, basis_gate: List[str] = None, sanity_check: bool = False, shots: int = 2048, return_schedule=False)\ -> Tuple[Union[PulseQobj, Schedule], List[List[QuantumCircuit]], List[str]]: """ Create circuits and schedules for QPT. Args: target_circuits: List of target circuits for QPT experiment. control: index of control qubit. target: index of target qubit. backend: Target quantum system. mit_readout: If use readout mitigation. inst_map: instruction mapping object. basis_gate: basis gates. sanity_check: check memory slot mapping of generated qobj. shots: Number of shots. return_schedule: set ``True`` when return schedule object instead of qobj. Returns: Qobj, Schedules, Quantum circuits, Measurement labels Additional Information: Bit ordering is little endian as a convention of computational science community, as the rest of qiskit does. When you measure the CR process tomography of q0 and q1, you will observe XZ (ZX) interaction when q0 (q1) is control qubit. """ qubits = sorted([control, target]) back_config = backend.configuration() back_defaults = backend.defaults() if inst_map is None: inst_map = back_defaults.circuit_instruction_map if basis_gate is None: basis_gate = back_config.basis_gates if isinstance(target_circuits, QuantumCircuit): target_circuits = [target_circuits] exp_circs = [] # create the measurement circuits for error mitigation, optional qr = target_circuits[0].qregs[0] if mit_readout: meas_circs, meas_labels = mit.complete_meas_cal(qubit_list=qubits, qr=qr, circlabel='mcal') exp_circs.extend(meas_circs) else: meas_labels = [] # create qpt circuit qpt_qcs_list = [] for target_circuit in target_circuits: # extract quantum registers from target circuit qr = target_circuit.qregs[0] qr0 = qr[qubits[0]] qr1 = qr[qubits[1]] qpt_qcs = tomo.process_tomography_circuits(target_circuit, measured_qubits=[qr0, qr1]) qpt_qcs_list.append(qpt_qcs) exp_circs.extend(qpt_qcs) # transpile exp_circs = qiskit.transpile(exp_circs, backend, basis_gates=basis_gate) # schedule with measure alignment exp_scheds = align_measures(qiskit.schedule(exp_circs, backend=backend, inst_map=inst_map), inst_map=inst_map) if return_schedule: return exp_scheds, qpt_qcs_list, meas_labels # assemble pulse qobj qobj = qiskit.assemble(exp_scheds, backend=backend, meas_level=2, shots=shots) # sanity check if sanity_check: for experiment in qobj.experiments: for inst in experiment.instructions: if inst.name == 'acquire': memory_slot_map = inst.memory_slot if memory_slot_map[qubits[0]] != __reserved_registers[0] or \ memory_slot_map[qubits[0]] != __reserved_registers[1]: warnings.warn('Wrong memory slots are assigned. ' 'QPT fitter may return invalid result.') assert len(qobj.experiments) <= back_config.max_experiments return qobj, qpt_qcs_list, meas_labels
def from_calibration_results(cls, backend: BaseBackend, t1_result: Tuple[np.array, Result], t2_result: Tuple[np.array, Result], meas_result: Result, zz_results: Dict[Tuple[int, int], Tuple[np.array, float, Result]]): """Takes results from running calibration circuits on hardware and constructs a QuacNoiseModel object :param backend: the backend on which the circuits were run (a BaseBackend object) :param t1_result: a tuple with a list of delay times (in ns) as the 0th element and the T1 calibration Result object as the 1st element :param t2_result: a tuple with a list of delay times (in ns) as the 0th element and the T2 calibration Result object as the 1st element :param meas_result: a Result object from running measurement calibration circuits :param zz_results: a dictionary mapping tuples of qubit indices to a ZZ coupling calibration circuit Result object :return: a QuacNoiseModel object """ n_qubits = len(backend.properties().qubits) qubits = list(range(n_qubits)) # Set up defaults t1_times = [float('inf') for _ in range(n_qubits)] t2_times = [float('inf') for _ in range(n_qubits)] meas_matrices = None zz = None # Adjust defaults as appropriate if t1_result: t1_fit = T1Fitter(t1_result[1], t1_result[0], qubits, fit_p0=[1, 1e5, 0], fit_bounds=([0, 0, -1], [2, 1e10, 1]), time_unit="nano-seconds") t1_times = t1_fit.time() if t2_result: t2_fit = T2Fitter(t2_result[1], t2_result[0], qubits, fit_p0=[1, 1e4, 0], fit_bounds=([0, 0, -1], [2, 1e10, 1]), time_unit="nano-seconds") t2_times = t2_fit.time() if meas_result: meas_fit = TensoredMeasFitter(meas_result, [[qubit] for qubit in qubits]) meas_matrices = meas_fit.cal_matrices if zz_results: zz = {} for qubit1 in qubits: for qubit2 in qubits: if qubit1 < qubit2: zz_information = zz_results[(qubit1, qubit2)] xdata, osc_freq, zz_result = zz_information zz_fit = ZZFitter( zz_result, xdata, [qubit1], [qubit2], fit_p0=[1, osc_freq, -np.pi / 20, 0], fit_bounds=([-0.5, 0, -np.pi, -0.5], [1.5, 1e10, np.pi, 1.5]), ) zz[(qubit1, qubit2)] = zz_fit.ZZ_rate()[0] return QuacNoiseModel(t1_times, t2_times, meas_matrices, zz)
def __init__( self, backend: BaseBackend, # run config shots: int = 1024, seed_simulator: Optional[int] = None, max_credits: int = 10, # backend properties basis_gates: Optional[List[str]] = None, coupling_map: Optional[Union[CouplingMap, List[List]]] = None, # transpile initial_layout: Optional[Union[Layout, Dict, List]] = None, pass_manager: Optional[PassManager] = None, seed_transpiler: Optional[int] = None, optimization_level: Optional[int] = None, # simulation backend_options: Optional[Dict] = None, noise_model: Optional['NoiseModel'] = None, # job timeout: Optional[float] = None, wait: float = 5., # others skip_qobj_validation: bool = True, measurement_error_mitigation_cls: Optional[Callable] = None, cals_matrix_refresh_period: int = 30, measurement_error_mitigation_shots: Optional[int] = None, job_callback: Optional[Callable] = None) -> None: """ Quantum Instance holds a Qiskit Terra backend as well as configuration for circuit transpilation and execution. When provided to an Aqua algorithm the algorithm will execute the circuits it needs to run using the instance. Args: backend: Instance of selected backend shots: Number of repetitions of each circuit, for sampling seed_simulator: Random seed for simulators max_credits: Maximum credits to use basis_gates: List of basis gate names supported by the target. Defaults to basis gates of the backend. coupling_map: Coupling map (perhaps custom) to target in mapping initial_layout: Initial layout of qubits in mapping pass_manager: Pass manager to handle how to compile the circuits seed_transpiler: The random seed for circuit mapper optimization_level: How much optimization to perform on the circuits. Higher levels generate more optimized circuits, at the expense of longer transpilation time. backend_options: All running options for backend, please refer to the provider of the backend for information as to what options it supports. noise_model: noise model for simulator timeout: Seconds to wait for job. If None, wait indefinitely. wait: Seconds between queries for job result skip_qobj_validation: Bypass Qobj validation to decrease circuit processing time during submission to backend. measurement_error_mitigation_cls: The approach to mitigate measurement errors. Qiskit Ignis provides fitter classes for this functionality and CompleteMeasFitter from qiskit.ignis.mitigation.measurement module can be used here. (TensoredMeasFitter is not supported). cals_matrix_refresh_period: How often to refresh the calibration matrix in measurement mitigation. in minutes measurement_error_mitigation_shots: The number of shots number for building calibration matrix. If None, the main `shots` parameter value is used. job_callback: Optional user supplied callback which can be used to monitor job progress as jobs are submitted for processing by an Aqua algorithm. The callback is provided the following arguments: `job_id, job_status, queue_position, job` Raises: AquaError: the shots exceeds the maximum number of shots AquaError: set noise model but the backend does not support that AquaError: set backend_options but the backend does not support that """ self._backend = backend self._pass_manager = pass_manager # setup run config if shots is not None: if self.is_statevector and shots != 1: logger.info( "statevector backend only works with shot=1, changing " "shots from %s to 1.", shots) shots = 1 max_shots = self._backend.configuration().max_shots if max_shots is not None and shots > max_shots: raise AquaError( 'The maximum shots supported by the selected backend is {} ' 'but you specified {}'.format(max_shots, shots)) run_config = RunConfig(shots=shots, max_credits=max_credits) if seed_simulator: run_config.seed_simulator = seed_simulator self._run_config = run_config # setup backend config basis_gates = basis_gates or backend.configuration().basis_gates coupling_map = coupling_map or getattr(backend.configuration(), 'coupling_map', None) self._backend_config = { 'basis_gates': basis_gates, 'coupling_map': coupling_map } # setup compile config self._compile_config = { 'initial_layout': initial_layout, 'seed_transpiler': seed_transpiler, 'optimization_level': optimization_level } # setup job config self._qjob_config = {'timeout': timeout} if self.is_local \ else {'timeout': timeout, 'wait': wait} # setup noise config self._noise_config = {} if noise_model is not None: if is_simulator_backend( self._backend) and not is_basicaer_provider(self._backend): self._noise_config = {'noise_model': noise_model} else: raise AquaError( "The noise model is not supported on the selected backend {} ({}) " "only certain backends, such as Aer qasm simulator " "support noise.".format(self.backend_name, self._backend.provider())) # setup backend options for run self._backend_options = {} if backend_options is not None: if support_backend_options(self._backend): self._backend_options = {'backend_options': backend_options} else: raise AquaError( "backend_options can not used with the backends in IBMQ provider." ) # setup measurement error mitigation self._meas_error_mitigation_cls = None if self.is_statevector: if measurement_error_mitigation_cls is not None: raise AquaError("Measurement error mitigation does not work " "with the statevector simulation.") else: self._meas_error_mitigation_cls = measurement_error_mitigation_cls self._meas_error_mitigation_fitters: Dict = {} # TODO: support different fitting method in error mitigation? self._meas_error_mitigation_method = 'least_squares' self._cals_matrix_refresh_period = cals_matrix_refresh_period self._meas_error_mitigation_shots = measurement_error_mitigation_shots if self._meas_error_mitigation_cls is not None: logger.info( "The measurement error mitigation is enabled. " "It will automatically submit an additional job to help " "calibrate the result of other jobs. " "The current approach will submit a job with 2^N circuits " "to build the calibration matrix, " "where N is the number of measured qubits. " "Furthermore, Aqua will re-use the calibration matrix for %s minutes " "and re-build it after that.", self._cals_matrix_refresh_period) # setup others if is_ibmq_provider(self._backend): if skip_qobj_validation: logger.info( "skip_qobj_validation was set True but this setting is not " "supported by IBMQ provider and has been ignored.") skip_qobj_validation = False self._skip_qobj_validation = skip_qobj_validation self._circuit_summary = False self._job_callback = job_callback logger.info(self)
def process_characterisation(backend: BaseBackend) -> Dict[str, Any]: """Convert a :py:class:`qiskit.BaseBackend` to a dictionary containing device Characteristics :param backend: A backend to be converted :type backend: BaseBackend :return: A dictionary containing device characteristics :rtype: dict """ gate_set = _tk_gate_set(backend) assert OpType.CX in gate_set # TODO explicitly check for and separate 1 and 2 qubit gates properties = cast("BackendProperties", backend.properties()) def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any]: try: first_found = next(filter(lambda item: item.name == name, iterator)) except StopIteration: return None if hasattr(first_found, "value"): return first_found.value return None config = backend.configuration() coupling_map = config.coupling_map n_qubits = config.n_qubits if coupling_map is None: # Assume full connectivity arc = FullyConnected(n_qubits) link_ers_dict = {} else: arc = Architecture(coupling_map) link_ers_dict = { tuple(pair): QubitErrorContainer({OpType.CX}) for pair in coupling_map } node_ers_dict = {} supported_single_optypes = gate_set.difference({OpType.CX}) t1_times_dict = {} t2_times_dict = {} frequencies_dict = {} gate_times_dict = {} if properties is not None: for index, qubit_info in enumerate(properties.qubits): error_cont = QubitErrorContainer(supported_single_optypes) error_cont.add_readout( return_value_if_found(qubit_info, "readout_error")) t1_times_dict[index] = return_value_if_found(qubit_info, "T1") t2_times_dict[index] = return_value_if_found(qubit_info, "T2") frequencies_dict[index] = return_value_if_found( qubit_info, "frequency") node_ers_dict[index] = error_cont for gate in properties.gates: name = gate.gate if name in _gate_str_2_optype: optype = _gate_str_2_optype[name] qubits = gate.qubits gate_error = return_value_if_found(gate.parameters, "gate_error") gate_error = gate_error if gate_error else 0.0 gate_length = return_value_if_found(gate.parameters, "gate_length") gate_length = gate_length if gate_length else 0.0 gate_times_dict[(optype, tuple(qubits))] = gate_length # add gate fidelities to their relevant lists if len(qubits) == 1: node_ers_dict[qubits[0]].add_error((optype, gate_error)) elif len(qubits) == 2: link_ers_dict[tuple(qubits)].add_error( (optype, gate_error)) opposite_link = tuple(qubits[::-1]) if opposite_link not in coupling_map: # to simulate a worse reverse direction square the fidelity link_ers_dict[opposite_link] = QubitErrorContainer( {OpType.CX}) link_ers_dict[opposite_link].add_error( (optype, 2 * gate_error)) # convert qubits to architecture Nodes node_ers_dict = { Node(q_index): ers for q_index, ers in node_ers_dict.items() } link_ers_dict = {(Node(q_indices[0]), Node(q_indices[1])): ers for q_indices, ers in link_ers_dict.items()} characterisation: Dict[str, Any] = dict() characterisation["NodeErrors"] = node_ers_dict characterisation["EdgeErrors"] = link_ers_dict characterisation["Architecture"] = arc characterisation["t1times"] = t1_times_dict characterisation["t2times"] = t2_times_dict characterisation["Frequencies"] = frequencies_dict characterisation["GateTimes"] = gate_times_dict return characterisation