diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0e9800fb5557..ebfee3f7f37d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -91,6 +91,7 @@ Changed rest of spec-defined entities. (#1909). - The rzz gate is now represented as a line when printed in text (#1957). - Text drawer has support for multi-q gates (#1939). +- Separate ``Qobj`` into ``PulseQobj`` and ``QasmQobj`` (#1969). Deprecated ---------- diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py index 8d9f776b4782..ad69561cad1c 100644 --- a/qiskit/compiler/assembler.py +++ b/qiskit/compiler/assembler.py @@ -7,14 +7,15 @@ """Assemble function for converting a list of circuits into a qobj""" import uuid -import sympy + import numpy +import sympy from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.qobj import Qobj, QobjConfig, QobjExperiment, QobjInstruction, QobjHeader -from qiskit.qobj import QobjExperimentConfig, QobjExperimentHeader, QobjConditional from qiskit.compiler.run_config import RunConfig -from qiskit.qobj.utils import QobjType +from qiskit.qobj import (QasmQobj, QobjExperimentHeader, QobjHeader, + QasmQobjInstruction, QasmQobjExperimentConfig, QasmQobjExperiment, + QasmQobjConfig, QobjConditional) def assemble_circuits(circuits, run_config=None, qobj_header=None, qobj_id=None): @@ -27,14 +28,14 @@ def assemble_circuits(circuits, run_config=None, qobj_header=None, qobj_id=None) qobj_id (int): identifier for the generated qobj Returns: - Qobj: the Qobj to be run on the backends + QasmQobj: the Qobj to be run on the backends """ qobj_header = qobj_header or QobjHeader() run_config = run_config or RunConfig() if isinstance(circuits, QuantumCircuit): circuits = [circuits] - userconfig = QobjConfig(**run_config.to_dict()) + userconfig = QasmQobjConfig(**run_config.to_dict()) experiments = [] max_n_qubits = 0 max_memory_slots = 0 @@ -68,11 +69,11 @@ def assemble_circuits(circuits, run_config=None, qobj_header=None, qobj_id=None) creg_sizes=creg_sizes, name=circuit.name) # TODO: why do we need n_qubits and memory_slots in both the header and the config - experimentconfig = QobjExperimentConfig(n_qubits=n_qubits, memory_slots=memory_slots) + experimentconfig = QasmQobjExperimentConfig(n_qubits=n_qubits, memory_slots=memory_slots) instructions = [] for opt in circuit.data: - current_instruction = QobjInstruction(name=opt.name) + current_instruction = QasmQobjInstruction(name=opt.name) if opt.qargs: qubit_indices = [qubit_labels.index([qubit[0].name, qubit[1]]) for qubit in opt.qargs] @@ -107,8 +108,8 @@ def assemble_circuits(circuits, run_config=None, qobj_header=None, qobj_id=None) val="0x%X" % opt.control[1]) instructions.append(current_instruction) - experiments.append(QobjExperiment(instructions=instructions, header=experimentheader, - config=experimentconfig)) + experiments.append(QasmQobjExperiment(instructions=instructions, header=experimentheader, + config=experimentconfig)) if n_qubits > max_n_qubits: max_n_qubits = n_qubits if memory_slots > max_memory_slots: @@ -117,6 +118,5 @@ def assemble_circuits(circuits, run_config=None, qobj_header=None, qobj_id=None) userconfig.memory_slots = max_memory_slots userconfig.n_qubits = max_n_qubits - return Qobj(qobj_id=qobj_id or str(uuid.uuid4()), config=userconfig, - experiments=experiments, header=qobj_header, - type=QobjType.QASM.value) + return QasmQobj(qobj_id=qobj_id or str(uuid.uuid4()), config=userconfig, + experiments=experiments, header=qobj_header) diff --git a/qiskit/qobj/__init__.py b/qiskit/qobj/__init__.py index aaed5b6fe57b..23e60ec4ea33 100644 --- a/qiskit/qobj/__init__.py +++ b/qiskit/qobj/__init__.py @@ -7,9 +7,19 @@ """Module for the Qobj structure.""" -from .qobj import Qobj -from .models import (QobjConfig, QobjExperiment, QobjInstruction, QobjHeader, - QobjExperimentHeader, QobjConditional, QobjExperimentConfig) -from .exceptions import QobjValidationError +from qiskit.qobj.models.base import (QobjInstruction, QobjExperimentHeader, QobjExperimentConfig, + QobjExperiment, QobjConfig, QobjHeader) + +from qiskit.qobj.models.pulse import (PulseQobjInstruction, PulseQobjExperimentConfig, + PulseQobjExperiment, PulseQobjConfig, + QobjMeasurementOption, QobjPulseLibrary) + +from qiskit.qobj.models.qasm import (QasmQobjInstruction, QasmQobjExperimentConfig, + QasmQobjExperiment, QasmQobjConfig, + QobjConditional) from ._validation import validate_qobj_against_schema + +from .exceptions import QobjValidationError + +from .qobj import Qobj, QasmQobj, PulseQobj diff --git a/qiskit/qobj/models.py b/qiskit/qobj/models/base.py similarity index 59% rename from qiskit/qobj/models.py rename to qiskit/qobj/models/base.py index c2b5e6500155..a84102199b35 100644 --- a/qiskit/qobj/models.py +++ b/qiskit/qobj/models/base.py @@ -5,56 +5,33 @@ # This source code is licensed under the Apache License, Version 2.0 found in # the LICENSE.txt file in the root directory of this source tree. -"""Models for Qobj and its related components.""" +"""The generic qobj models.""" -from marshmallow.validate import Length, Range, Regexp +from marshmallow.validate import Length, Range -from qiskit.validation.base import BaseModel, BaseSchema, bind_schema -from qiskit.validation.fields import (Integer, List, Nested, String, - InstructionParameter) - - -class QobjConditionalSchema(BaseSchema): - """Schema for QobjConditional.""" - - # Required properties. - mask = String(required=True, validate=Regexp('^0x([0-9A-Fa-f])+$')) - type = String(required=True) - val = String(required=True, validate=Regexp('^0x([0-9A-Fa-f])+$')) +from qiskit.validation import BaseSchema, bind_schema, BaseModel +from qiskit.validation.fields import String, Nested, Integer class QobjInstructionSchema(BaseSchema): - """Schema for QobjInstruction.""" + """Base Schema for QobjInstruction.""" - # Required properties. + # Required properties name = String(required=True) - # Optional properties. - qubits = List(Integer(validate=Range(min=0)), - validate=Length(min=1)) - params = List(InstructionParameter()) - memory = List(Integer(validate=Range(min=0)), - validate=Length(min=1)) - conditional = Nested(QobjConditionalSchema) - class QobjExperimentHeaderSchema(BaseSchema): - """Schema for QobjExperimentHeader.""" + """Base Schema for QobjExperimentHeader.""" pass class QobjExperimentConfigSchema(BaseSchema): - """Schema for QobjExperimentConfig.""" - - # Required properties. - - # Optional properties. - memory_slots = Integer(validate=Range(min=0)) - n_qubits = Integer(validate=Range(min=1)) + """Base Schema for QobjExperimentConfig.""" + pass class QobjExperimentSchema(BaseSchema): - """Schema for QobjExperiment.""" + """Base Schema for QobjExperiment.""" # Required properties. instructions = Nested(QobjInstructionSchema, required=True, many=True, @@ -66,49 +43,23 @@ class QobjExperimentSchema(BaseSchema): class QobjConfigSchema(BaseSchema): - """Schema for QobjConfig.""" - - # Required properties. + """Base Schema for QobjConfig.""" # Optional properties. max_credits = Integer() seed = Integer() memory_slots = Integer(validate=Range(min=0)) - n_qubits = Integer(validate=Range(min=1)) shots = Integer(validate=Range(min=1)) class QobjHeaderSchema(BaseSchema): - """Schema for QobjHeader.""" - - # Required properties. + """Base Schema for QobjHeader.""" # Optional properties. backend_name = String() backend_version = String() -@bind_schema(QobjConditionalSchema) -class QobjConditional(BaseModel): - """Model for QobjConditional. - - Please note that this class only describes the required fields. For the - full description of the model, please check ``QobjConditionalSchema``. - - Attributes: - mask (str): hexadecimal mask of the conditional - type (str): type of the conditional - val (str): hexadecimal value of the conditional - """ - def __init__(self, mask, type, val, **kwargs): - # pylint: disable=redefined-builtin - self.mask = mask - self.type = type - self.val = val - - super().__init__(**kwargs) - - @bind_schema(QobjInstructionSchema) class QobjInstruction(BaseModel): """Model for QobjInstruction. diff --git a/qiskit/qobj/models/pulse.py b/qiskit/qobj/models/pulse.py new file mode 100644 index 000000000000..2fbcd239b708 --- /dev/null +++ b/qiskit/qobj/models/pulse.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""The pulse qobj models.""" + +from marshmallow.validate import Range, Regexp, Length, OneOf + +from .base import (QobjInstructionSchema, QobjExperimentConfigSchema, QobjExperimentSchema, + QobjConfigSchema, QobjInstruction, QobjExperimentConfig, + QobjExperiment, QobjConfig) +from qiskit.qobj.utils import MeasReturnType +from qiskit.validation import bind_schema, BaseSchema, BaseModel +from qiskit.validation.fields import (Integer, String, Number, Complex, + List, Nested, MeasurementParameter) + + +class QobjMeasurementOptionSchema(BaseSchema): + """Schema for QobjMeasOptiton.""" + + # Required properties. + name = String(required=True) + params = MeasurementParameter(required=True) + + +class QobjPulseLibrarySchema(BaseSchema): + """Schema for QobjPulseLibrary.""" + + # Required properties. + name = String(required=True) + samples = List(Complex(), required=True, validate=Length(min=1)) + + +class PulseQobjInstructionSchema(QobjInstructionSchema): + """Schema for PulseQobjInstruction.""" + # pylint: disable=invalid-name + + # Required properties + t0 = Integer(required=True, validate=Range(min=0)) + + # Optional properties. + ch = String(validate=Regexp('[dum]([0-9])+')) + conditional = Integer(validate=Range(min=0)) + phase = Number() + val = Complex() + duration = Integer(validate=Range(min=1)) + qubits = List(Integer(validate=Range(min=0)), validate=Length(min=1)) + memory_slot = List(Integer(validate=Range(min=0)), validate=Length(min=1)) + register_slot = List(Integer(validate=Range(min=0)), validate=Length(min=1)) + kernels = Nested(QobjMeasurementOptionSchema, many=True) + discriminators = Nested(QobjMeasurementOptionSchema, many=True) + label = String() + type = String() + + +class PulseQobjExperimentConfigSchema(QobjExperimentConfigSchema): + """Schema for PulseQobjExperimentConfig.""" + + # Optional properties. + qubit_lo_freq = List(Number()) + meas_lo_freq = List(Number()) + + +class PulseQobjExperimentSchema(QobjExperimentSchema): + """Schema for PulseQobjExperiment.""" + + # Required properties. + instructions = Nested(PulseQobjInstructionSchema, required=True, many=True, + validate=Length(min=1)) + + # Optional properties. + config = Nested(PulseQobjExperimentConfigSchema) + + +class PulseQobjConfigSchema(QobjConfigSchema): + """Schema for PulseQobjConfig.""" + + # Required properties. + # TODO : check if they are always required by backend + meas_level = Integer(required=True, validate=Range(min=0, max=2)) + memory_slot_size = Integer(required=True) + pulse_library = Nested(QobjPulseLibrarySchema, many=True, required=True) + qubit_lo_freq = List(Number(), required=True) + meas_lo_freq = List(Number(), required=True) + rep_time = Integer(required=True) + meas_return = String(validate=OneOf(choices=(MeasReturnType.AVERAGE, + MeasReturnType.SINGLE))) + + +@bind_schema(QobjMeasurementOptionSchema) +class QobjMeasurementOption(BaseModel): + """Model for QobjMeasurementOption. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QobjMeasurementOptionSchema``. + + Attributes: + name (str): name of option specified in the backend + params (dict): measurement parameter + """ + def __init__(self, name, params, **kwargs): + self.name = name + self.params = params + + super().__init__(**kwargs) + + +@bind_schema(QobjPulseLibrarySchema) +class QobjPulseLibrary(BaseModel): + """Model for QobjPulseLibrary. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QobjPulseLibrarySchema``. + + Attributes: + name (str): name of pulse + samples (list[complex]]): list of complex values defining pulse shape + """ + + def __init__(self, name, samples, **kwargs): + self.name = name + self.samples = samples + + super().__init__(**kwargs) + + +@bind_schema(PulseQobjInstructionSchema) +class PulseQobjInstruction(QobjInstruction): + """Model for PulseQobjInstruction inherit from QobjInstruction. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``PulseQobjInstructionSchema``. + + Attributes: + name (str): name of the instruction + t0 (int): timing of executing the instruction + """ + def __init__(self, name, t0, **kwargs): + # pylint: disable=invalid-name + self.t0 = t0 + + super().__init__(name=name, + t0=t0, + **kwargs) + + +@bind_schema(PulseQobjExperimentConfigSchema) +class PulseQobjExperimentConfig(QobjExperimentConfig): + """Model for PulseQobjExperimentConfig inherit from QobjExperimentConfig. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``PulseQobjExperimentConfigSchema``. + """ + pass + + +@bind_schema(PulseQobjExperimentSchema) +class PulseQobjExperiment(QobjExperiment): + """Model for PulseQobjExperiment inherit from QobjExperiment. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``PulseQobjExperimentSchema``. + + Attributes: + instructions (list[PulseQobjInstruction]): list of instructions. + """ + def __init__(self, instructions, **kwargs): + + super().__init__(instructions=instructions, + **kwargs) + + +@bind_schema(PulseQobjConfigSchema) +class PulseQobjConfig(QobjConfig): + """Model for PulseQobjConfig inherit from QobjConfig. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``PulseQobjConfigSchema``. + + Attributes: + meas_level (int): a value represents the level of measurement. + memory_slot_size (int): size of memory slot + meas_return (str): a level of measurement information. + pulse_library (list[QobjPulseLibrary]): a pulse library. + qubit_lo_freq (list): the list of frequencies for qubit drive LO's in GHz. + meas_lo_freq (list): the list of frequencies for measurement drive LO's in GHz. + rep_time (int): the value of repetition time of experiment in us. + """ + def __init__(self, meas_level, memory_slot_size, meas_return, + pulse_library, qubit_lo_freq, meas_lo_freq, rep_time, + **kwargs): + self.meas_level = meas_level + self.memory_slot_size = memory_slot_size + self.meas_return = meas_return + self.pulse_library = pulse_library + self.qubit_lo_freq = qubit_lo_freq + self.meas_lo_freq = meas_lo_freq + self.rep_time = rep_time + + super().__init__(meas_level=meas_level, + memory_slot_size=memory_slot_size, + meas_return=meas_return, + pulse_library=pulse_library, + qubit_lo_freq=qubit_lo_freq, + meas_lo_freq=meas_lo_freq, + rep_time=rep_time, + **kwargs) diff --git a/qiskit/qobj/models/qasm.py b/qiskit/qobj/models/qasm.py new file mode 100644 index 000000000000..19c3440e3b3d --- /dev/null +++ b/qiskit/qobj/models/qasm.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""The qasm qobj models.""" + +from marshmallow.validate import Range, Length, Regexp + +from .base import (QobjInstructionSchema, QobjExperimentConfigSchema, QobjExperimentSchema, + QobjConfigSchema, QobjInstruction, QobjExperimentConfig, + QobjExperiment, QobjConfig) +from qiskit.validation import bind_schema, BaseSchema, BaseModel +from qiskit.validation.fields import List, Integer, InstructionParameter, Nested, String + + +class QobjConditionalSchema(BaseSchema): + """Schema for QobjConditional.""" + + # Required properties. + mask = String(required=True, validate=Regexp('^0x([0-9A-Fa-f])+$')) + type = String(required=True) + val = String(required=True, validate=Regexp('^0x([0-9A-Fa-f])+$')) + + +class QasmQobjInstructionSchema(QobjInstructionSchema): + """Schema for QasmQobjInstruction.""" + + # Optional properties. + qubits = List(Integer(validate=Range(min=0)), + validate=Length(min=1)) + params = List(InstructionParameter()) + memory = List(Integer(validate=Range(min=0)), + validate=Length(min=1)) + conditional = Nested(QobjConditionalSchema) + + +class QasmQobjExperimentConfigSchema(QobjExperimentConfigSchema): + """Schema for QasmQobjExperimentConfig.""" + + # Optional properties. + memory_slots = Integer(validate=Range(min=0)) + n_qubits = Integer(validate=Range(min=1)) + + +class QasmQobjExperimentSchema(QobjExperimentSchema): + """Schema for QasmQobjExperiment.""" + + # Required properties. + instructions = Nested(QasmQobjInstructionSchema, required=True, many=True, + validate=Length(min=1)) + + # Optional properties. + config = Nested(QasmQobjExperimentConfigSchema) + + +class QasmQobjConfigSchema(QobjConfigSchema): + """Schema for QasmQobjConfig.""" + + # Optional properties. + n_qubits = Integer(validate=Range(min=1)) + + +@bind_schema(QobjConditionalSchema) +class QobjConditional(BaseModel): + """Model for QobjConditional. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QobjConditionalSchema``. + + Attributes: + mask (str): hexadecimal mask of the conditional + type (str): type of the conditional + val (str): hexadecimal value of the conditional + """ + def __init__(self, mask, type, val, **kwargs): + # pylint: disable=redefined-builtin + self.mask = mask + self.type = type + self.val = val + + super().__init__(**kwargs) + + +@bind_schema(QasmQobjInstructionSchema) +class QasmQobjInstruction(QobjInstruction): + """Model for QasmQobjInstruction inherit from QobjInstruction. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QasmQobjInstructionSchema``. + + Attributes: + name (str): name of the instruction + """ + def __init__(self, name, **kwargs): + super().__init__(name=name, + **kwargs) + + +@bind_schema(QasmQobjExperimentConfigSchema) +class QasmQobjExperimentConfig(QobjExperimentConfig): + """Model for QasmQobjExperimentConfig inherit from QobjExperimentConfig. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QasmQobjExperimentConfigSchema``. + """ + pass + + +@bind_schema(QasmQobjExperimentSchema) +class QasmQobjExperiment(QobjExperiment): + """Model for QasmQobjExperiment inherit from QobjExperiment. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QasmQobjExperimentSchema``. + + Attributes: + instructions (list[QasmQobjInstruction]): list of instructions. + """ + def __init__(self, instructions, **kwargs): + super().__init__(instructions=instructions, + **kwargs) + + +@bind_schema(QasmQobjConfigSchema) +class QasmQobjConfig(QobjConfig): + """Model for QasmQobjConfig inherit from QobjConfig. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QasmQobjConfigSchema``. + """ + pass diff --git a/qiskit/qobj/qobj.py b/qiskit/qobj/qobj.py index bc9639e66fd4..a030558b2037 100644 --- a/qiskit/qobj/qobj.py +++ b/qiskit/qobj/qobj.py @@ -7,16 +7,17 @@ """Model for Qobj.""" -from marshmallow.validate import OneOf +from marshmallow.validate import Equal, OneOf +from qiskit.qobj.models.base import QobjExperimentSchema, QobjConfigSchema, QobjHeaderSchema +from qiskit.qobj.models.pulse import PulseQobjExperimentSchema, PulseQobjConfigSchema +from qiskit.qobj.models.qasm import QasmQobjExperimentSchema, QasmQobjConfigSchema from qiskit.validation.base import BaseModel, BaseSchema, bind_schema from qiskit.validation.fields import Nested, String - -from .models import QobjConfigSchema, QobjExperimentSchema, QobjHeaderSchema from .utils import QobjType - QOBJ_VERSION = '1.1.0' + """Current version of the Qobj schema. Qobj schema versions: @@ -28,16 +29,37 @@ class QobjSchema(BaseSchema): """Schema for Qobj.""" - # Required properties. qobj_id = String(required=True) + schema_version = String(required=True, missing=QOBJ_VERSION) + + # Required properties depend on Qobj type. config = Nested(QobjConfigSchema, required=True) experiments = Nested(QobjExperimentSchema, required=True, many=True) header = Nested(QobjHeaderSchema, required=True) - type = String(required=True, - validate=OneOf(QobjType.QASM.value, - QobjType.PULSE.value)) - schema_version = String(required=True, missing=QOBJ_VERSION) + type = String(required=True, validate=OneOf(choices=(QobjType.QASM, QobjType.PULSE))) + + +class QasmQobjSchema(QobjSchema): + """Schema for QasmQobj.""" + + # Required properties. + config = Nested(QasmQobjConfigSchema, required=True) + experiments = Nested(QasmQobjExperimentSchema, required=True, many=True) + + type = String(required=True, validate=Equal(QobjType.QASM), + missing=QobjType.QASM) + + +class PulseQobjSchema(QobjSchema): + """Schema for PulseQobj.""" + + # Required properties. + config = Nested(PulseQobjConfigSchema, required=True) + experiments = Nested(PulseQobjExperimentSchema, required=True, many=True) + + type = String(required=True, validate=Equal(QobjType.PULSE), + missing=QobjType.PULSE) @bind_schema(QobjSchema) @@ -52,8 +74,7 @@ class Qobj(BaseModel): config (QobjConfig): config settings for the Qobj. experiments (list[QobjExperiment]): list of experiments. header (QobjHeader): headers. - type (str): experiment type (QASM/PULSE). - schema_version (str): Qobj version. + type (str): Qobj type. """ def __init__(self, qobj_id, config, experiments, header, type, **kwargs): # pylint: disable=redefined-builtin @@ -62,7 +83,58 @@ def __init__(self, qobj_id, config, experiments, header, type, **kwargs): self.experiments = experiments self.header = header self.type = type - self.schema_version = QOBJ_VERSION super().__init__(**kwargs) + + +@bind_schema(QasmQobjSchema) +class QasmQobj(Qobj): + """Model for QasmQobj inherit from Qobj. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``QasmQobjSchema``. + + Attributes: + qobj_id (str): Qobj identifier. + config (QASMQobjConfig): config settings for the Qobj. + experiments (list[QASMQobjExperiment]): list of experiments. + header (QobjHeader): headers. + """ + def __init__(self, qobj_id, config, experiments, header, **kwargs): + + # to avoid specifying 'type' here within from_dict() + kwargs.pop('type', None) + + super().__init__(qobj_id=qobj_id, + config=config, + experiments=experiments, + header=header, + type=QobjType.QASM.value, + **kwargs) + + +@bind_schema(PulseQobjSchema) +class PulseQobj(Qobj): + """Model for PulseQobj inherit from Qobj. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``PulseQobjSchema``. + + Attributes: + qobj_id (str): Qobj identifier. + config (PulseQobjConfig): config settings for the Qobj. + experiments (list[PulseQobjExperiment]): list of experiments. + header (QobjHeader): headers. + """ + def __init__(self, qobj_id, config, experiments, header, **kwargs): + + # to avoid specifying 'type' here within from_dict() + kwargs.pop('type', None) + + super().__init__(qobj_id=qobj_id, + config=config, + experiments=experiments, + header=header, + type=QobjType.PULSE.value, + **kwargs) diff --git a/qiskit/qobj/utils.py b/qiskit/qobj/utils.py index a0bd32a567b0..77a7b4dc282d 100644 --- a/qiskit/qobj/utils.py +++ b/qiskit/qobj/utils.py @@ -14,3 +14,9 @@ class QobjType(str, Enum): """Qobj.type allowed values.""" QASM = 'QASM' PULSE = 'PULSE' + + +class MeasReturnType(str, Enum): + """PulseQobjConfig meas_return allowed values.""" + AVERAGE = 'avg' + SINGLE = 'single' diff --git a/qiskit/test/mock.py b/qiskit/test/mock.py index d35a6a434b91..6ae9b69fb0d2 100644 --- a/qiskit/test/mock.py +++ b/qiskit/test/mock.py @@ -22,14 +22,13 @@ from concurrent import futures import time -from qiskit.qobj.utils import QobjType from qiskit.result import Result from qiskit.providers import BaseBackend, BaseJob from qiskit.providers.models import BackendProperties, BackendConfiguration from qiskit.providers.models.backendconfiguration import GateConfig -from qiskit.qobj import (Qobj, QobjConfig, QobjHeader, QobjInstruction, - QobjExperiment, QobjExperimentHeader, - QobjExperimentConfig) +from qiskit.qobj import (QasmQobj, QobjExperimentHeader, QobjHeader, + QasmQobjInstruction, QasmQobjExperimentConfig, + QasmQobjExperiment, QasmQobjConfig) from qiskit.providers.jobstatus import JobStatus from qiskit.providers.baseprovider import BaseProvider from qiskit.providers.exceptions import QiskitBackendNotFoundError @@ -348,16 +347,15 @@ def _error(self): def new_fake_qobj(): """Create fake `Qobj` and backend instances.""" backend = FakeQasmSimulator() - return Qobj( + return QasmQobj( qobj_id='test-id', - config=QobjConfig(shots=1024, memory_slots=1, max_credits=100), + config=QasmQobjConfig(shots=1024, memory_slots=1, max_credits=100), header=QobjHeader(backend_name=backend.name()), - experiments=[QobjExperiment( + experiments=[QasmQobjExperiment( instructions=[ - QobjInstruction(name='barrier', qubits=[1]) + QasmQobjInstruction(name='barrier', qubits=[1]) ], header=QobjExperimentHeader(), - config=QobjExperimentConfig(seed=123456) - )], - type=QobjType.QASM.value + config=QasmQobjExperimentConfig(seed=123456) + )] ) diff --git a/qiskit/validation/fields/__init__.py b/qiskit/validation/fields/__init__.py index 3318a924109a..635095e68a77 100644 --- a/qiskit/validation/fields/__init__.py +++ b/qiskit/validation/fields/__init__.py @@ -33,7 +33,7 @@ class Boolean(marshmallow.fields.Boolean, ModelTypeValidator): from qiskit.validation.fields.polymorphic import ByAttribute, ByType, TryFrom from qiskit.validation.fields.containers import Nested, List -from .custom import Complex, InstructionParameter +from .custom import Complex, InstructionParameter, MeasurementParameter class String(_fields.String, ModelTypeValidator): diff --git a/qiskit/validation/fields/custom.py b/qiskit/validation/fields/custom.py index 668b10db2e8a..6ff1d45c0bf6 100644 --- a/qiskit/validation/fields/custom.py +++ b/qiskit/validation/fields/custom.py @@ -11,6 +11,8 @@ import sympy from marshmallow.utils import is_collection +from marshmallow.exceptions import ValidationError +from marshmallow.compat import Mapping, Iterable from qiskit.validation import ModelTypeValidator @@ -120,3 +122,66 @@ def check_type(self, value, attr, data): for item in value] return root_value + + +class MeasurementParameter(ModelTypeValidator): + """Field for objects used in measurement kernel and discriminator parameters. + """ + default_error_messages = { + 'invalid': 'Not a valid mapping type.', + 'invalid_sub': 'Not a valid value.' + } + + valid_types = (int, float, str, bool, Iterable, Mapping, type(None)) + + def check_type(self, value, attr, data): + if value is None: + return None + + _check_type = super().check_type + + errors = [] + if isinstance(value, Mapping): + for v in value.values(): + try: + _check_type(v, None, value) + except ValidationError as err: + errors.append(err.messages) + else: + errors.append('Not a valid mapping type.') + + if errors: + raise ValidationError(errors) + + return value + + def _serialize_sub(self, value): + # pylint: disable=too-many-return-statements + if value is None: + return None + if isinstance(value, (int, float, str, bool)): + return value + if isinstance(value, Iterable): + return [self._serialize_sub(each) for each in value] + if isinstance(value, Mapping): + return {str(k): self._serialize_sub(v) for k, v in value.items()} + + return self.fail('invalid_sub', input=value) + + def _serialize(self, value, attr, obj): + # pylint: disable=too-many-return-statements + if value is None: + return None + if isinstance(value, Mapping): + return {str(k): self._serialize_sub(v) for k, v in value.items()} + + return self.fail('invalid') + + def _deserialize(self, value, attr, data): + # pylint: disable=too-many-return-statements + if value is None: + return None + if isinstance(value, Mapping): + return value + + return self.fail('invalid') diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py index 375f99fb10eb..d16cf236b805 100644 --- a/test/python/compiler/test_assembler.py +++ b/test/python/compiler/test_assembler.py @@ -13,7 +13,7 @@ from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.compiler import assemble_circuits from qiskit.compiler import RunConfig -from qiskit.qobj import Qobj +from qiskit.qobj import QasmQobj from qiskit.test import QiskitTestCase @@ -32,7 +32,7 @@ def test_assemble_single_circuit(self): run_config = RunConfig(shots=2000, memory=True) qobj = assemble_circuits(circ, run_config=run_config) - self.assertIsInstance(qobj, Qobj) + self.assertIsInstance(qobj, QasmQobj) self.assertEqual(qobj.config.shots, 2000) self.assertEqual(qobj.config.memory, True) self.assertEqual(len(qobj.experiments), 1) @@ -58,7 +58,7 @@ def test_assemble_multiple_circuits(self): run_config = RunConfig(shots=100, memory=False, seed=6) qobj = assemble_circuits([circ0, circ1], run_config=run_config) - self.assertIsInstance(qobj, Qobj) + self.assertIsInstance(qobj, QasmQobj) self.assertEqual(qobj.config.seed, 6) self.assertEqual(len(qobj.experiments), 2) self.assertEqual(qobj.experiments[1].config.n_qubits, 3) @@ -76,7 +76,7 @@ def test_assemble_no_run_config(self): circ.measure(q, c) qobj = assemble_circuits(circ) - self.assertIsInstance(qobj, Qobj) + self.assertIsInstance(qobj, QasmQobj) self.assertIsNone(getattr(qobj.config, 'shots', None)) def test_assemble_initialize(self): @@ -87,7 +87,7 @@ def test_assemble_initialize(self): circ.initialize([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)], q[:]) qobj = assemble_circuits(circ) - self.assertIsInstance(qobj, Qobj) + self.assertIsInstance(qobj, QasmQobj) self.assertEqual(qobj.experiments[0].instructions[0].name, 'init') np.testing.assert_almost_equal(qobj.experiments[0].instructions[0].params, [0.7071067811865, 0, 0, 0.707106781186]) diff --git a/test/python/compiler/test_compiler.py b/test/python/compiler/test_compiler.py index 0cf1c158f298..93ea010534ac 100644 --- a/test/python/compiler/test_compiler.py +++ b/test/python/compiler/test_compiler.py @@ -18,7 +18,7 @@ from qiskit import compile, execute from qiskit.test import QiskitTestCase, Path from qiskit.test.mock import FakeRueschlikon, FakeTenerife -from qiskit.qobj import Qobj +from qiskit.qobj import QasmQobj from qiskit.converters import circuit_to_dag from qiskit.tools.qi.qi import random_unitary_matrix from qiskit.mapper.compiling import two_qubit_kak @@ -576,7 +576,7 @@ def test_yzy_zyz_cases(self): circ1.rz(0.7, qr[1]) circ1.rx(1.570796, qr[1]) qobj1 = compile(circ1, backend) - self.assertIsInstance(qobj1, Qobj) + self.assertIsInstance(qobj1, QasmQobj) circ2 = QuantumCircuit(qr) circ2.y(qr[0]) @@ -584,7 +584,7 @@ def test_yzy_zyz_cases(self): circ2.s(qr[0]) circ2.h(qr[0]) qobj2 = compile(circ2, backend) - self.assertIsInstance(qobj2, Qobj) + self.assertIsInstance(qobj2, QasmQobj) def test_move_measurements(self): """Measurements applied AFTER swap mapping. diff --git a/test/python/converters/test_qobj_to_circuits.py b/test/python/converters/test_qobj_to_circuits.py index b66c62484375..34d3b1e25cbd 100644 --- a/test/python/converters/test_qobj_to_circuits.py +++ b/test/python/converters/test_qobj_to_circuits.py @@ -16,7 +16,7 @@ from qiskit.compiler import assemble_circuits from qiskit.converters import qobj_to_circuits -from qiskit.qobj import Qobj, QobjConfig, QobjHeader +from qiskit.qobj import QasmQobj, QasmQobjConfig, QobjHeader from qiskit.transpiler import PassManager from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase @@ -87,11 +87,10 @@ def test_qobj_to_circuit_with_sim_instructions(self): def test_qobj_to_circuits_with_nothing(self): """Verify that qobj_to_circuits returns None without any data.""" - qobj = Qobj(qobj_id='abc123', - config=QobjConfig(), - header=QobjHeader(), - experiments=[], - type='QASM') + qobj = QasmQobj(qobj_id='abc123', + config=QasmQobjConfig(), + header=QobjHeader(), + experiments=[]) self.assertIsNone(qobj_to_circuits(qobj)) def test_qobj_to_circuits_single_no_qasm(self): diff --git a/test/python/qobj/test_qobj.py b/test/python/qobj/test_qobj.py index 11234a8607a4..3f3c3f081f5b 100644 --- a/test/python/qobj/test_qobj.py +++ b/test/python/qobj/test_qobj.py @@ -8,39 +8,40 @@ """Qobj tests.""" -import uuid import copy +import uuid + import jsonschema -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit import BasicAer +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.compiler import assemble_circuits, RunConfig - -from qiskit.qobj.exceptions import SchemaValidationError -from qiskit.qobj import Qobj, QobjConfig, QobjExperiment, QobjInstruction -from qiskit.qobj import QobjHeader, validate_qobj_against_schema from qiskit.providers.basicaer import basicaerjob - -from qiskit.qobj.utils import QobjType +from qiskit.qobj import (QasmQobj, PulseQobj, QobjHeader, + PulseQobjInstruction, PulseQobjExperiment, + PulseQobjConfig, QobjMeasurementOption, + QobjPulseLibrary, QasmQobjInstruction, + QasmQobjExperiment, QasmQobjConfig) +from qiskit.qobj import validate_qobj_against_schema +from qiskit.qobj.exceptions import SchemaValidationError from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeRueschlikon -class TestQobj(QiskitTestCase): - """Tests for Qobj.""" +class TestQASMQobj(QiskitTestCase): + """Tests for QasmQobj.""" def setUp(self): - self.valid_qobj = Qobj( + self.valid_qobj = QasmQobj( qobj_id='12345', header=QobjHeader(), - config=QobjConfig(shots=1024, memory_slots=2, max_credits=10), + config=QasmQobjConfig(shots=1024, memory_slots=2, max_credits=10), experiments=[ - QobjExperiment(instructions=[ - QobjInstruction(name='u1', qubits=[1], params=[0.4]), - QobjInstruction(name='u2', qubits=[1], params=[0.4, 0.2]) + QasmQobjExperiment(instructions=[ + QasmQobjInstruction(name='u1', qubits=[1], params=[0.4]), + QasmQobjInstruction(name='u2', qubits=[1], params=[0.4, 0.2]) ]) - ], - type=QobjType.QASM.value + ] ) self.valid_dict = { @@ -70,21 +71,21 @@ def test_as_dict_against_schema(self): def test_from_dict_per_class(self): """Test Qobj and its subclass representations given a dictionary.""" test_parameters = { - Qobj: ( + QasmQobj: ( self.valid_qobj, self.valid_dict ), - QobjConfig: ( - QobjConfig(shots=1, memory_slots=2), + QasmQobjConfig: ( + QasmQobjConfig(shots=1, memory_slots=2), {'shots': 1, 'memory_slots': 2} ), - QobjExperiment: ( - QobjExperiment( - instructions=[QobjInstruction(name='u1', qubits=[1], params=[0.4])]), + QasmQobjExperiment: ( + QasmQobjExperiment( + instructions=[QasmQobjInstruction(name='u1', qubits=[1], params=[0.4])]), {'instructions': [{'name': 'u1', 'qubits': [1], 'params': [0.4]}]} ), - QobjInstruction: ( - QobjInstruction(name='u1', qubits=[1], params=[0.4]), + QasmQobjInstruction: ( + QasmQobjInstruction(name='u1', qubits=[1], params=[0.4]), {'name': 'u1', 'qubits': [1], 'params': [0.4]} ) } @@ -126,5 +127,125 @@ def test_change_qobj_after_compile(self): self.assertTrue(qobj1.config.shots == 1024) +class TestPulseQobj(QiskitTestCase): + """Tests for PulseQobj.""" + + def setUp(self): + self.valid_qobj = PulseQobj( + qobj_id='12345', + header=QobjHeader(), + config=PulseQobjConfig(shots=1024, memory_slots=2, max_credits=10, + meas_level=1, + memory_slot_size=8192, + meas_return='avg', + pulse_library=[ + QobjPulseLibrary(name='pulse0', + samples=[0.0 + 0.0j, + 0.5 + 0.0j, + 0.0 + 0.0j]) + ], + qubit_lo_freq=[4.9], + meas_lo_freq=[6.9], + rep_time=1000), + experiments=[ + PulseQobjExperiment(instructions=[ + PulseQobjInstruction(name='pulse0', t0=0, ch='d0'), + PulseQobjInstruction(name='fc', t0=5, ch='d0', phase=1.57), + PulseQobjInstruction(name='pv', t0=10, ch='d0', val=0.1 + 0.0j), + PulseQobjInstruction(name='acquire', t0=15, duration=5, + qubits=[0], memory_slot=[0], + kernels=[ + QobjMeasurementOption(name='boxcar', + params={"start_window": 0, + "stop_window": 5}) + ]) + ]) + ] + ) + self.valid_dict = { + 'qobj_id': '12345', + 'type': 'PULSE', + 'schema_version': '1.1.0', + 'header': {}, + 'config': {'max_credits': 10, 'memory_slots': 2, 'shots': 1024, + 'meas_level': 1, + 'memory_slot_size': 8192, + 'meas_return': 'avg', + 'pulse_library': [{'name': 'pulse0', + 'samples': [[0.0, 0.0], + [0.5, 0.0], + [0.0, 0.0]]} + ], + 'qubit_lo_freq': [4.9], + 'meas_lo_freq': [6.9], + 'rep_time': 1000 + }, + 'experiments': [ + {'instructions': [ + {'name': 'pulse0', 't0': 0, 'ch': 'd0'}, + {'name': 'fc', 't0': 5, 'ch': 'd0', 'phase': 1.57}, + {'name': 'pv', 't0': 10, 'ch': 'd0', 'val': [0.1, 0.0]}, + {'name': 'acquire', 't0': 15, 'duration': 5, + 'qubits': [0], 'memory_slot': [0], + 'kernels': [{'name': 'boxcar', + 'params': {'start_window': 0, + 'stop_window': 5}} + ] + } + ]} + ] + } + + def test_as_dict_against_schema(self): + """Test dictionary representation of Qobj against its schema.""" + try: + validate_qobj_against_schema(self.valid_qobj) + except jsonschema.ValidationError as validation_error: + self.fail(str(validation_error)) + + def test_from_dict_per_class(self): + """Test Qobj and its subclass representations given a dictionary.""" + test_parameters = { + PulseQobj: ( + self.valid_qobj, + self.valid_dict + ), + PulseQobjConfig: ( + PulseQobjConfig(meas_level=1, + memory_slot_size=8192, + meas_return='avg', + pulse_library=[ + QobjPulseLibrary(name='pulse0', samples=[0.1 + 0.0j]) + ], + qubit_lo_freq=[4.9], meas_lo_freq=[6.9], + rep_time=1000), + {'meas_level': 1, + 'memory_slot_size': 8192, + 'meas_return': 'avg', + 'pulse_library': [{'name': 'pulse0', 'samples': [[0.1, 0.0]]}], + 'qubit_lo_freq': [4.9], + 'meas_lo_freq': [6.9], + 'rep_time': 1000} + ), + QobjPulseLibrary: ( + QobjPulseLibrary(name='pulse0', samples=[0.1 + 0.0j]), + {'name': 'pulse0', 'samples': [[0.1, 0.0]]} + ), + PulseQobjExperiment: ( + PulseQobjExperiment( + instructions=[PulseQobjInstruction(name='pulse0', t0=0, ch='d0')]), + {'instructions': [{'name': 'pulse0', 't0': 0, 'ch': 'd0'}]} + ), + PulseQobjInstruction: ( + PulseQobjInstruction(name='pulse0', t0=0, ch='d0'), + {'name': 'pulse0', 't0': 0, 'ch': 'd0'} + ) + } + + for qobj_class, (qobj_item, expected_dict) in test_parameters.items(): + with self.subTest(msg=str(qobj_class)): + self.assertEqual(qobj_item, qobj_class.from_dict(expected_dict)) + + def _nop(): pass