Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Pass transpilation options #108

Merged
merged 17 commits into from
Oct 24, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions pennylane_qiskit/qiskit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,27 @@ def __init__(self, wires, provider, backend, shots=1024, **kwargs):
else:
raise ValueError("Backend {} does not support noisy simulations".format(backend))

# set transpile_args
self.transpile_args = {}
transpile_sig = inspect.signature(transpile).parameters
self.set_args(kwargs, transpile_sig, self.transpile_args)

if "backend_options" in s.parameters:
self.run_args["backend_options"] = kwargs

@staticmethod
def set_args(source, signature, destination):
"""This static method is for fetching all signature matching parameters from source dict and appending them to destination dict.

Args:
source (Dict): The source dictionary
signature (Iterable): The iterable of params to filter
destination (Dict): The destination dictionary
"""
for arg in signature:
if arg in source:
destination[arg] = source[arg]

@property
def backend(self):
"""The Qiskit simulation backend object"""
Expand All @@ -178,7 +196,14 @@ def reset(self):
self._current_job = None
self._state = None # statevector of a simulator backend

def apply(self, operations, **kwargs):
def apply(self, operations, persist_transpile_options=False, **kwargs):
"""This method applies the given set of operations on the qiskit ciruit.

Args:
operations (List[PennyLane.operation]): operations to be applied
persist_transpile_options (bool, optional): If true, transpile options
will be used for furture runs. Defaults to False.
"""
rotations = kwargs.get("rotations", [])

applied_operations = self.apply_operations(operations)
Expand All @@ -196,7 +221,14 @@ def apply(self, operations, **kwargs):
measure(self._circuit, qr, cr)

# These operations need to run for all devices
qobj = self.compile()
temp_transpile_args = {}
transpile_sig = inspect.signature(transpile).parameters
self.set_args(
kwargs,
transpile_sig,
(self.transpile_args if persist_transpile_options else temp_transpile_args),
)
qobj = self.compile(temp_transpile_args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to note for apply is that it is being called in the QubitDevice.execute method by passing pre-defined arguments. This means that one would not be able to explicitly pass the persist_transpile_options option to apply.

There could be two ways to handle this behaviour:

  1. We could simply resort to the transpile arguments being defined on device initialization
  2. We could have a setter method for the transpile arguments (one that can update dev.transpile_args)

The second option would be nice to have, but it is also already nice to be able to define transpile options on device initialization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a setter for transpilation options. This will not merge the transpile parameters, but will override them.

self.run(qobj)

def apply_operations(self, operations):
Expand Down Expand Up @@ -265,12 +297,21 @@ def qubit_unitary_check(operation, par, wires):
2**wires)."
)

def compile(self):
"""Compile the quantum circuit to target
the provided compile_backend. If compile_backend is None,
then the target is simply the backend."""
def compile(self, temp_transpile_args=None):
"""Compile the quantum circuit to target the provided compile_backend. If compile_backend is None,then the target is simply the backend.

Args:
temp_transpile_args ([Dict], optional): This dictionary will be used to override/add transpile parameters for current transpilation only. Defaults to None.

Returns:
A ``Qobj`` that can be run on a backend. Depending on the type of input, this will be either a ``QasmQobj`` or a ``PulseQobj``.
"""
temp_transpile_args = {} if temp_transpile_args is None else temp_transpile_args
compile_backend = self.compile_backend or self.backend
compiled_circuits = transpile(self._circuit, backend=compile_backend)
t_args = {**self.transpile_args, **temp_transpile_args}
t_args.pop("circuits", None)
t_args.pop("backend", None)
compiled_circuits = transpile(self._circuit, backend=compile_backend, **t_args)

# Specify to have a memory for hw/hw simulators
memory = str(compile_backend) not in self._state_backends
Expand Down
35 changes: 35 additions & 0 deletions tests/test_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,46 @@
qml.T
]

test_transpile_options = [
{},
{'optimization_level':2},
{'optimization_level':2, 'seed_transpiler':22}
]

test_device_options = [
{},
{'optimization_level':3},
{'optimization_level':1}
]

single_qubit_operations_param = [qml.PhaseShift, qml.RX, qml.RY, qml.RZ]
two_qubit = [qml.CNOT, qml.SWAP, qml.CZ]
two_qubit_param = [qml.CRZ]
three_qubit = [qml.Toffoli, qml.CSWAP]

@pytest.mark.parametrize("qiskit_device", [AerDevice, BasicAerDevice])
@pytest.mark.parametrize("wires", [1,2])
@pytest.mark.parametrize("transpile_options", test_transpile_options)
@pytest.mark.parametrize("device_options", test_device_options)
@pytest.mark.parametrize("persist", [True, False])
@pytest.mark.transpile_args_test
class TestTranspileOptionsUpdations:
"""Test the updation of transpilation options passed while applying operations"""

def test_transpilation_options(self, init_state, qiskit_device, wires,
transpile_options, device_options, persist):
"""test that the transpilation options are updated as
expected during device init"""
dev = qiskit_device(wires=wires, **device_options)
assert dev.transpile_args == device_options
state = init_state(wires)
dev.apply([qml.QubitStateVector(state, wires=range(wires))],
persist_transpile_options=persist, **transpile_options)
if persist:
assert dev.transpile_args == {**device_options, **transpile_options}
else:
assert dev.transpile_args == device_options

@pytest.mark.parametrize("analytic", [True])
@pytest.mark.parametrize("shots", [8192])
@pytest.mark.usefixtures("skip_unitary")
Expand Down
17 changes: 16 additions & 1 deletion tests/test_qiskit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest

import pennylane as qml
from pennylane_qiskit import AerDevice
from pennylane_qiskit import AerDevice, BasicAerDevice


class TestProbabilities:
Expand All @@ -14,6 +14,21 @@ def test_probability_no_results(self):
dev = AerDevice(backend="statevector_simulator", wires=1, analytic=True)
assert dev.probability() is None

@pytest.mark.parametrize("qiskit_device", [AerDevice, BasicAerDevice])
@pytest.mark.parametrize("wires", [1,2])
@pytest.mark.transpile_args_test
class TestTranspilationOptionInitialization:
"""Tests for passing the transpilation options to qiskit at time of device init"""

def test_no_transpilation_options(self, qiskit_device, wires):
"""Test that the transpilation options must me {} if not provided."""
dev = qiskit_device(wires=wires)
assert dev.transpile_args == {}

def test_with_transpilation_options(self, qiskit_device, wires):
"""test that the transpilation options are set as expected during device init"""
dev = qiskit_device(wires=wires, abc=123, optimization_level=2)
assert dev.transpile_args == {'optimization_level':2}

class TestAnalyticWarningHWSimulator:
"""Tests the warnings for when the analytic attribute of a device is set to true"""
Expand Down