Add init qubits flag to qobj (#4432)

* Update schemas to include init_qubits config flag with default of true

* Add init_qubits arg to execute and assemble, add tests

* Execute should have init_qubits=None by default

* Turns out default values are only added to the qobj if they are validated. This is not very robust, so I'll remove the default value for init_qubits

* Update test/python/compiler/test_assembler.py

Co-authored-by: Luciano Bello <luciano.bello@ibm.com>

* Update test/python/compiler/test_assembler.py

Co-authored-by: Luciano Bello <luciano.bello@ibm.com>

* Respond to feedback: mention default value in docstring. Let init qubits be True by default. Break up the other tests, and make sure True shows up

* Add a reno note

Co-authored-by: Luciano Bello <luciano.bello@ibm.com>
Co-authored-by: Thomas Alexander <talexander@ibm.com>
This commit is contained in:
Lauren Capelluto 2020-05-15 11:55:57 -04:00 committed by GitHub
parent 948671efd9
commit 6dbf9dd06b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 40 deletions

View File

@ -60,6 +60,7 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule,
rep_time: Optional[float] = None,
parameter_binds: Optional[List[Dict[Parameter, float]]] = None,
parametric_pulses: Optional[List[str]] = None,
init_qubits: bool = True,
**run_config: Dict) -> Qobj:
"""Assemble a list of circuits or pulse schedules into a ``Qobj``.
@ -115,6 +116,8 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule,
Example::
['gaussian', 'constant']
init_qubits: Whether to reset the qubits to the ground state for each shot.
Default: ``True``.
**run_config: Extra arguments used to configure the run (e.g., for Aer configurable
backends). Refer to the backend documentation for details on these
arguments.
@ -130,7 +133,8 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule,
experiments = experiments if isinstance(experiments, list) else [experiments]
qobj_id, qobj_header, run_config_common_dict = _parse_common_args(backend, qobj_id, qobj_header,
shots, memory, max_credits,
seed_simulator, **run_config)
seed_simulator, init_qubits,
**run_config)
# assemble either circuits or schedules
if all(isinstance(exp, QuantumCircuit) for exp in experiments):
@ -164,7 +168,7 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule,
# TODO: rework to return a list of RunConfigs (one for each experiments), and a global one
def _parse_common_args(backend, qobj_id, qobj_header, shots,
memory, max_credits, seed_simulator,
memory, max_credits, seed_simulator, init_qubits,
**run_config):
"""Resolve the various types of args allowed to the assemble() function through
duck typing, overriding args, etc. Refer to the assemble() docstring for details on
@ -221,6 +225,7 @@ def _parse_common_args(backend, qobj_id, qobj_header, shots,
memory=memory,
max_credits=max_credits,
seed_simulator=seed_simulator,
init_qubits=init_qubits,
**run_config)
return qobj_id, qobj_header, run_config_dict

View File

@ -48,6 +48,7 @@ def execute(experiments, backend,
meas_return=MeasReturnType.AVERAGE,
memory_slots=None, memory_slot_size=100, rep_time=None, parameter_binds=None,
schedule_circuit=False, inst_map=None, meas_map=None, scheduling_method=None,
init_qubits=None,
**run_config):
"""Execute a list of :class:`qiskit.circuit.QuantumCircuit` or
:class:`qiskit.pulse.Schedule` on a backend.
@ -196,6 +197,9 @@ def execute(experiments, backend,
scheduling_method (str or list(str)):
Optionally specify a particular scheduling method.
init_qubits (bool): Whether to reset the qubits to the ground state for each shot.
Default: ``True``.
run_config (dict):
Extra arguments used to configure the run (e.g. for Aer configurable backends).
Refer to the backend documentation for details on these arguments.
@ -275,8 +279,8 @@ def execute(experiments, backend,
rep_time=rep_time,
parameter_binds=parameter_binds,
backend=backend,
**run_config
)
init_qubits=init_qubits,
**run_config)
# executing the circuits on the backend and returning the job
start_time = time()

View File

@ -13,6 +13,7 @@
"meas_level": 2,
"memory_slot_size": 1,
"rep_time": 1000,
"init_qubits": false,
"qubit_lo_freq": [5.2,5.15,5.05],
"meas_lo_freq": [6.9,6.8,6.7],
"pulse_library": [

View File

@ -7,7 +7,8 @@
"backend_name": "ibmqx2"},
"config": {
"shots": 1024,
"memory_slots": 1
"memory_slots": 1,
"init_qubits": true
},
"experiments": [
{

View File

@ -802,6 +802,10 @@
"description": "Total number of qubits used in this experiment.",
"minimum": 1,
"type": "integer"
},
"init_qubits": {
"description": "Whether to initialize the qubits in the ground state before each shot.",
"type": "boolean"
}
},
"title": "Experiment level configuration",

View File

@ -0,0 +1,11 @@
---
features:
- |
Users can request whether the backend inserts any initialization sequences
at the start of each shot via ``assemble`` or ``execute``. By passing
``init_qubits = False``, qubits will be uninitialized at the start of each
experiment and between shots.
The default behavior is ``init_qubits = True``, which replicates the
previously implict behavior: all qubits are assumed to be in the ground
state at the start of each shot.

View File

@ -39,17 +39,18 @@ from qiskit.validation.jsonschema import SchemaValidationError
class TestCircuitAssembler(QiskitTestCase):
"""Tests for assembling circuits to qobj."""
def setUp(self):
qr = QuantumRegister(2, name='q')
cr = ClassicalRegister(2, name='c')
self.circ = QuantumCircuit(qr, cr, name='circ')
self.circ.h(qr[0])
self.circ.cx(qr[0], qr[1])
self.circ.measure(qr, cr)
def test_assemble_single_circuit(self):
"""Test assembling a single circuit.
"""
qr = QuantumRegister(2, name='q')
cr = ClassicalRegister(2, name='c')
circ = QuantumCircuit(qr, cr, name='circ')
circ.h(qr[0])
circ.cx(qr[0], qr[1])
circ.measure(qr, cr)
qobj = assemble(circ, shots=2000, memory=True)
qobj = assemble(self.circ, shots=2000, memory=True)
validate_qobj_against_schema(qobj)
self.assertIsInstance(qobj, QasmQobj)
@ -89,14 +90,7 @@ class TestCircuitAssembler(QiskitTestCase):
def test_assemble_no_run_config(self):
"""Test assembling with no run_config, relying on default.
"""
qr = QuantumRegister(2, name='q')
qc = ClassicalRegister(2, name='c')
circ = QuantumCircuit(qr, qc, name='circ')
circ.h(qr[0])
circ.cx(qr[0], qr[1])
circ.measure(qr, qc)
qobj = assemble(circ)
qobj = assemble(self.circ)
validate_qobj_against_schema(qobj)
self.assertIsInstance(qobj, QasmQobj)
@ -104,28 +98,15 @@ class TestCircuitAssembler(QiskitTestCase):
def test_shots_greater_than_max_shots(self):
"""Test assembling with shots greater than max shots"""
qr = QuantumRegister(2, name='q')
qc = ClassicalRegister(2, name='c')
circ = QuantumCircuit(qr, qc, name='circ')
circ.h(qr[0])
circ.cx(qr[0], qr[1])
circ.measure(qr, qc)
backend = FakeYorktown()
self.assertRaises(QiskitError, assemble, backend, shots=1024000)
def test_default_shots_greater_than_max_shots(self):
"""Test assembling with default shots greater than max shots"""
qr = QuantumRegister(2, name='q')
qc = ClassicalRegister(2, name='c')
circ = QuantumCircuit(qr, qc, name='circ')
circ.h(qr[0])
circ.cx(qr[0], qr[1])
circ.measure(qr, qc)
backend = FakeYorktown()
backend._configuration.max_shots = 5
qobj = assemble(circ, backend)
qobj = assemble(self.circ, backend)
validate_qobj_against_schema(qobj)
@ -345,12 +326,28 @@ class TestCircuitAssembler(QiskitTestCase):
self.assertEqual(_qobj_inst_params(7, 0), [1, 0])
self.assertEqual(_qobj_inst_params(8, 0), [2, 1])
def test_init_qubits_default(self):
"""Check that the init_qubits=None assemble option is passed on to the qobj."""
qobj = assemble(self.circ)
self.assertEqual(qobj.config.init_qubits, True)
def test_init_qubits_true(self):
"""Check that the init_qubits=True assemble option is passed on to the qobj."""
qobj = assemble(self.circ, init_qubits=True)
self.assertEqual(qobj.config.init_qubits, True)
def test_init_qubits_false(self):
"""Check that the init_qubits=False assemble option is passed on to the qobj."""
qobj = assemble(self.circ, init_qubits=False)
self.assertEqual(qobj.config.init_qubits, False)
class TestPulseAssembler(QiskitTestCase):
"""Tests for assembling schedules to qobj."""
def setUp(self):
self.backend_config = FakeOpenPulse2Q().configuration()
self.backend = FakeOpenPulse2Q()
self.backend_config = self.backend.configuration()
test_pulse = pulse.SamplePulse(
samples=np.array([0.02739068, 0.05, 0.05, 0.05, 0.02739068], dtype=np.complex128),
@ -630,15 +627,13 @@ class TestPulseAssembler(QiskitTestCase):
def test_assemble_with_delay(self):
"""Test that delay instruction is ignored in assembly."""
backend = FakeOpenPulse2Q()
orig_schedule = self.schedule
with self.assertWarns(DeprecationWarning):
delay_schedule = orig_schedule + pulse.Delay(10)(self.backend_config.drive(0))
orig_qobj = assemble(orig_schedule, backend)
orig_qobj = assemble(orig_schedule, self.backend)
validate_qobj_against_schema(orig_qobj)
delay_qobj = assemble(delay_schedule, backend)
delay_qobj = assemble(delay_schedule, self.backend)
validate_qobj_against_schema(delay_qobj)
self.assertEqual(orig_qobj.experiments[0].to_dict(),
@ -707,6 +702,21 @@ class TestPulseAssembler(QiskitTestCase):
qobj_insts = qobj.experiments[0].instructions
self.assertFalse(hasattr(qobj_insts[0], 'pulse_shape'))
def test_init_qubits_default(self):
"""Check that the init_qubits=None assemble option is passed on to the qobj."""
qobj = assemble(self.schedule, self.backend)
self.assertEqual(qobj.config.init_qubits, True)
def test_init_qubits_true(self):
"""Check that the init_qubits=True assemble option is passed on to the qobj."""
qobj = assemble(self.schedule, self.backend, init_qubits=True)
self.assertEqual(qobj.config.init_qubits, True)
def test_init_qubits_false(self):
"""Check that the init_qubits=False assemble option is passed on to the qobj."""
qobj = assemble(self.schedule, self.backend, init_qubits=False)
self.assertEqual(qobj.config.init_qubits, False)
class TestPulseAssemblerMissingKwargs(QiskitTestCase):
"""Verify that errors are raised in case backend is not provided and kwargs are missing."""