Quellcode für qiskit.assembler.assemble_schedules

# This code is part of Qiskit.
# (C) Copyright IBM 2017, 2019.
# 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.

"""Assemble function for converting a list of circuits into a qobj."""
import hashlib
from collections import defaultdict
from typing import Any, Dict, List, Tuple, Union

from qiskit import qobj, pulse
from qiskit.assembler.run_config import RunConfig
from qiskit.exceptions import QiskitError
from qiskit.pulse import instructions, transforms, library, schedule, channels
from qiskit.qobj import utils as qobj_utils, converters
from qiskit.qobj.converters.pulse_instruction import ParametricPulseShapes

[Doku]def assemble_schedules( schedules: List[ Union[ schedule.ScheduleBlock, schedule.ScheduleComponent, Tuple[int, schedule.ScheduleComponent], ] ], qobj_id: int, qobj_header: qobj.QobjHeader, run_config: RunConfig, ) -> qobj.PulseQobj: """Assembles a list of schedules into a qobj that can be run on the backend. Args: schedules: Schedules to assemble. qobj_id: Identifier for the generated qobj. qobj_header: Header to pass to the results. run_config: Configuration of the runtime environment. Returns: The Qobj to be run on the backends. Raises: QiskitError: when frequency settings are not supplied. Examples: .. code-block:: python from qiskit import pulse from qiskit.assembler import assemble_schedules from qiskit.assembler.run_config import RunConfig # Construct a Qobj header for the output Qobj header = {"backend_name": "FakeOpenPulse2Q", "backend_version": "0.0.0"} # Build a configuration object for the output Qobj config = RunConfig(shots=1024, memory=False, meas_level=1, meas_return='avg', memory_slot_size=100, parametric_pulses=[], init_qubits=True, qubit_lo_freq=[4900000000.0, 5000000000.0], meas_lo_freq=[6500000000.0, 6600000000.0], schedule_los=[]) # Build a Pulse schedule to assemble into a Qobj schedule = pulse.Schedule() schedule += pulse.Play(pulse.Waveform([0.1] * 16, name="test0"), pulse.DriveChannel(0), name="test1") schedule += pulse.Play(pulse.Waveform([0.1] * 16, name="test1"), pulse.DriveChannel(0), name="test2") schedule += pulse.Play(pulse.Waveform([0.5] * 16, name="test0"), pulse.DriveChannel(0), name="test1") # Assemble a Qobj from the schedule. pulseQobj = assemble_schedules(schedules=[schedule], qobj_id="custom-id", qobj_header=header, run_config=config) """ if not hasattr(run_config, "qubit_lo_freq"): raise QiskitError("qubit_lo_freq must be supplied.") if not hasattr(run_config, "meas_lo_freq"): raise QiskitError("meas_lo_freq must be supplied.") lo_converter = converters.LoConfigConverter( qobj.PulseQobjExperimentConfig, **run_config.to_dict() ) experiments, experiment_config = _assemble_experiments(schedules, lo_converter, run_config) qobj_config = _assemble_config(lo_converter, experiment_config, run_config) return qobj.PulseQobj( experiments=experiments, qobj_id=qobj_id, header=qobj_header, config=qobj_config )
def _assemble_experiments( schedules: List[Union[schedule.ScheduleComponent, Tuple[int, schedule.ScheduleComponent]]], lo_converter: converters.LoConfigConverter, run_config: RunConfig, ) -> Tuple[List[qobj.PulseQobjExperiment], Dict[str, Any]]: """Assembles a list of schedules into PulseQobjExperiments, and returns related metadata that will be assembled into the Qobj configuration. Args: schedules: Schedules to assemble. lo_converter: The configured frequency converter and validator. run_config: Configuration of the runtime environment. Returns: The list of assembled experiments, and the dictionary of related experiment config. Raises: QiskitError: when frequency settings are not compatible with the experiments. """ freq_configs = [lo_converter(lo_dict) for lo_dict in getattr(run_config, "schedule_los", [])] if len(schedules) > 1 and len(freq_configs) not in [0, 1, len(schedules)]: raise QiskitError( "Invalid 'schedule_los' setting specified. If specified, it should be " "either have a single entry to apply the same LOs for each schedule or " "have length equal to the number of schedules." ) instruction_converter = getattr( run_config, "instruction_converter", converters.InstructionToQobjConverter ) instruction_converter = instruction_converter(qobj.PulseQobjInstruction, **run_config.to_dict()) formatted_schedules = [transforms.target_qobj_transform(sched) for sched in schedules] compressed_schedules = transforms.compress_pulses(formatted_schedules) user_pulselib = {} experiments = [] for idx, sched in enumerate(compressed_schedules): qobj_instructions, max_memory_slot = _assemble_instructions( sched, instruction_converter, run_config, user_pulselib ) metadata = sched.metadata if metadata is None: metadata = {} # TODO: add other experimental header items (see circuit assembler) qobj_experiment_header = qobj.QobjExperimentHeader( memory_slots=max_memory_slot + 1, # Memory slots are 0 indexed name=sched.name or "Experiment-%d" % idx, metadata=metadata, ) experiment = qobj.PulseQobjExperiment( header=qobj_experiment_header, instructions=qobj_instructions ) if freq_configs: # This handles the cases where one frequency setting applies to all experiments and # where each experiment has a different frequency freq_idx = idx if len(freq_configs) != 1 else 0 experiment.config = freq_configs[freq_idx] experiments.append(experiment) # Frequency sweep if freq_configs and len(experiments) == 1: experiment = experiments[0] experiments = [] for freq_config in freq_configs: experiments.append( qobj.PulseQobjExperiment( header=experiment.header, instructions=experiment.instructions, config=freq_config, ) ) # Top level Qobj configuration experiment_config = { "pulse_library": [ qobj.PulseLibraryItem(name=name, samples=samples) for name, samples in user_pulselib.items() ], "memory_slots": max(exp.header.memory_slots for exp in experiments), } return experiments, experiment_config def _assemble_instructions( sched: Union[pulse.Schedule, pulse.ScheduleBlock], instruction_converter: converters.InstructionToQobjConverter, run_config: RunConfig, user_pulselib: Dict[str, List[complex]], ) -> Tuple[List[qobj.PulseQobjInstruction], int]: """Assembles the instructions in a schedule into a list of PulseQobjInstructions and returns related metadata that will be assembled into the Qobj configuration. Lookup table for pulses defined in all experiments are registered in ``user_pulselib``. This object should be mutable python dictionary so that items are properly updated after each instruction assemble. The dictionary is not returned to avoid redundancy. Args: sched: Schedule to assemble. instruction_converter: A converter instance which can convert PulseInstructions to PulseQobjInstructions. run_config: Configuration of the runtime environment. user_pulselib: User pulse library from previous schedule. Returns: A list of converted instructions, the user pulse library dictionary (from pulse name to pulse samples), and the maximum number of readout memory slots used by this Schedule. """ sched = transforms.target_qobj_transform(sched) max_memory_slot = 0 qobj_instructions = [] acquire_instruction_map = defaultdict(list) for time, instruction in sched.instructions: if isinstance(instruction, instructions.Play): if isinstance(instruction.pulse, (library.ParametricPulse, library.SymbolicPulse)): is_backend_supported = True try: pulse_shape = ParametricPulseShapes.from_instance(instruction.pulse).name if pulse_shape not in run_config.parametric_pulses: is_backend_supported = False except ValueError: # Custom pulse class, or bare SymbolicPulse object. is_backend_supported = False if not is_backend_supported: instruction = instructions.Play( instruction.pulse.get_waveform(), instruction.channel, name=instruction.name ) if isinstance(instruction.pulse, library.Waveform): name = hashlib.sha256(instruction.pulse.samples).hexdigest() instruction = instructions.Play( library.Waveform(name=name, samples=instruction.pulse.samples), channel=instruction.channel, name=name, ) user_pulselib[name] = instruction.pulse.samples # ignore explicit delay instrs on acq channels as they are invalid on IBMQ backends; # timing of other instrs will still be shifted appropriately if isinstance(instruction, instructions.Delay) and isinstance( instruction.channel, channels.AcquireChannel ): continue if isinstance(instruction, instructions.Acquire): if instruction.mem_slot: max_memory_slot = max(max_memory_slot, instruction.mem_slot.index) # Acquires have a single AcquireChannel per inst, but we have to bundle them # together into the Qobj as one instruction with many channels acquire_instruction_map[(time, instruction.duration)].append(instruction) continue qobj_instructions.append(instruction_converter(time, instruction)) if acquire_instruction_map: if hasattr(run_config, "meas_map"): _validate_meas_map(acquire_instruction_map, run_config.meas_map) for (time, _), instruction_bundle in acquire_instruction_map.items(): qobj_instructions.append( instruction_converter(time, instruction_bundle), ) return qobj_instructions, max_memory_slot def _validate_meas_map( instruction_map: Dict[Tuple[int, instructions.Acquire], List[instructions.Acquire]], meas_map: List[List[int]], ) -> None: """Validate all qubits tied in ``meas_map`` are to be acquired. Args: instruction_map: A dictionary grouping Acquire instructions according to their start time and duration. meas_map: List of groups of qubits that must be acquired together. Raises: QiskitError: If the instructions do not satisfy the measurement map. """ sorted_inst_map = sorted(instruction_map.items(), key=lambda item: item[0]) meas_map_sets = [set(m) for m in meas_map] # error if there is time overlap between qubits in the same meas_map for idx, inst in enumerate(sorted_inst_map[:-1]): inst_end_time = inst[0][0] + inst[0][1] next_inst = sorted_inst_map[idx + 1] next_inst_time = next_inst[0][0] if next_inst_time < inst_end_time: inst_qubits = {inst.channel.index for inst in inst[1]} next_inst_qubits = {inst.channel.index for inst in next_inst[1]} for meas_set in meas_map_sets: common_instr_qubits = inst_qubits.intersection(meas_set) common_next = next_inst_qubits.intersection(meas_set) if common_instr_qubits and common_next: raise QiskitError( "Qubits {} and {} are in the same measurement grouping: {}. " "They must either be acquired at the same time, or disjointly" ". Instead, they were acquired at times: {}-{} and " "{}-{}".format( common_instr_qubits, common_next, meas_map, inst[0][0], inst_end_time, next_inst_time, next_inst_time + next_inst[0][1], ) ) def _assemble_config( lo_converter: converters.LoConfigConverter, experiment_config: Dict[str, Any], run_config: RunConfig, ) -> qobj.PulseQobjConfig: """Assembles the QobjConfiguration from experimental config and runtime config. Args: lo_converter: The configured frequency converter and validator. experiment_config: Schedules to assemble. run_config: Configuration of the runtime environment. Returns: The assembled PulseQobjConfig. """ qobj_config = run_config.to_dict() qobj_config.update(experiment_config) # Run config not needed in qobj config qobj_config.pop("meas_map", None) qobj_config.pop("qubit_lo_range", None) qobj_config.pop("meas_lo_range", None) # convert enums to serialized values meas_return = qobj_config.get("meas_return", "avg") if isinstance(meas_return, qobj_utils.MeasReturnType): qobj_config["meas_return"] = meas_return.value meas_level = qobj_config.get("meas_level", 2) if isinstance(meas_level, qobj_utils.MeasLevel): qobj_config["meas_level"] = meas_level.value # convert LO frequencies to GHz qobj_config["qubit_lo_freq"] = [freq / 1e9 for freq in qobj_config["qubit_lo_freq"]] qobj_config["meas_lo_freq"] = [freq / 1e9 for freq in qobj_config["meas_lo_freq"]] # override defaults if single entry for ``schedule_los`` schedule_los = qobj_config.pop("schedule_los", []) if len(schedule_los) == 1: lo_dict = schedule_los[0] q_los = lo_converter.get_qubit_los(lo_dict) # Hz -> GHz if q_los: qobj_config["qubit_lo_freq"] = [freq / 1e9 for freq in q_los] m_los = lo_converter.get_meas_los(lo_dict) if m_los: qobj_config["meas_lo_freq"] = [freq / 1e9 for freq in m_los] return qobj.PulseQobjConfig(**qobj_config)