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

QuantumCircuit to PennyLane template conversion #55

Merged
merged 41 commits into from
Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
91bfd3a
Initial creation of the converter file
antalszava Oct 28, 2019
0a20ca8
Initial outline of the QuantumCircuit converter
antalszava Oct 29, 2019
74f9562
Correcting test cases except for one (BasisState)
antalszava Oct 30, 2019
d119076
Added tests and polished basic version of the load() function
antalszava Oct 30, 2019
35adfe7
Adding qasm loading, fixing tests except for the one reading from file
antalszava Oct 30, 2019
0e524cc
Add file test with tmp_directory fixture
antalszava Oct 30, 2019
7d55e68
Finalizing docstrings and tests
antalszava Oct 30, 2019
088c542
Updating comments
antalszava Oct 30, 2019
035c094
Modifying changelog
antalszava Oct 31, 2019
7bc701d
Update CHANGELOG.md
antalszava Oct 31, 2019
22e5ef7
Update pennylane_qiskit/converter.py
antalszava Oct 31, 2019
ca97645
Update pennylane_qiskit/converter.py
antalszava Oct 31, 2019
cfb00f5
Update tests/test_converter.py
antalszava Oct 31, 2019
3fa3ec1
Update tests/test_converter.py
antalszava Oct 31, 2019
362a174
Update tests/test_converter.py
antalszava Oct 31, 2019
50e3503
Modifications based on comments
antalszava Oct 31, 2019
288da58
Modifications based on comments
antalszava Oct 31, 2019
a20f93b
Adding test for coverage
antalszava Oct 31, 2019
2fca287
Modifying wires argument for load, adding tests
antalszava Nov 1, 2019
80c306f
Remove custom operation recorder
josh146 Nov 3, 2019
ffbfd1f
fix failing 3.5 test
josh146 Nov 3, 2019
025ac51
Parameters can be passed through QNode, added integration tests, modi…
antalszava Nov 4, 2019
e73a494
Organizing qasm conversions into one function, refactoring load, furt…
antalszava Nov 4, 2019
0912585
Modifying docstrings
antalszava Nov 4, 2019
798d26c
Update pennylane_qiskit/converter.py
antalszava Nov 6, 2019
23cbb96
Update pennylane_qiskit/converter.py
antalszava Nov 6, 2019
a3f0d65
Update pennylane_qiskit/converter.py
antalszava Nov 6, 2019
36c8e4f
Update pennylane_qiskit/converter.py
antalszava Nov 6, 2019
87f62a9
Reverting QASM conversion into two parts, corrections in the docstring
antalszava Nov 6, 2019
644a520
Resolving merge conflict
antalszava Nov 6, 2019
328ae38
Update tests/conftest.py
antalszava Nov 11, 2019
3d9c6e9
Update pennylane_qiskit/converter.py
antalszava Nov 11, 2019
31eb089
Update tests/test_converter.py
antalszava Nov 11, 2019
9af6498
Update tests/test_converter.py
antalszava Nov 11, 2019
2e95f37
Change names of tests
antalszava Nov 11, 2019
a1fa5e7
Merge branch 'quanctum_circuit_to_template' of https://github.com/Xan…
antalszava Nov 11, 2019
2a6d991
Update tests/test_converter.py
antalszava Nov 11, 2019
28bc3b5
Adding tests for map_wires, updating tests for U2, U3 and Toffoli
antalszava Nov 11, 2019
4a81f59
Merge branch 'quanctum_circuit_to_template' of https://github.com/Xan…
antalszava Nov 11, 2019
ae5f4df
Adding test on wires provided in a non-standard order
antalszava Nov 11, 2019
4fbd76e
Modifying test on ordering
antalszava Nov 11, 2019
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
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# .coveragerc to control coverage.py
[run]
source = pennylane_qiksit
source = pennylane_qiskit

[report]
# Regexes for lines to exclude from consideration
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Release 0.7.0-dev

### New features since last release
* Added the ability to automatically convert Qiskit `QuantumCircuits`
or QASM circuits directly into PennyLane templates. The loaded
operations can be used directly inside PennyLane circuits.
[#55](https://github.com/XanaduAI/pennylane-qiskit/pull/55)


### Breaking changes

Expand All @@ -14,6 +19,8 @@

This release contains contributions from (in alphabetical order):

Antal Száva

---

# Release 0.6.0
Expand Down
3 changes: 1 addition & 2 deletions pennylane_qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
"""Top level PennyLane-qiskit module"""

from ._version import __version__

from .aer import AerDevice
from .basic_aer import BasicAerDevice
from .converter import load, load_qasm, load_qasm_from_file
from .ibmq import IBMQDevice

from .ops import *
196 changes: 196 additions & 0 deletions pennylane_qiskit/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Copyright 2019 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
QuanctumCircuit converter module
================================

.. currentmodule:: pennylane_qiskit.load

This module contains functions for converting Qiskit QuantumCircuit objects
into PennyLane circuit templates.
"""
import warnings

import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.exceptions import QiskitError

import pennylane as qml
import pennylane.ops.qubit as pennylane_ops
from pennylane_qiskit.qiskit_device import QISKIT_OPERATION_MAP

# pylint: disable=too-many-instance-attributes

inv_map = {v.__name__: k for k, v in QISKIT_OPERATION_MAP.items()}


def _check_parameter_bound(param):
"""Utility function determining if a certain parameter in a QuantumCircuit has
been bound.

Args:
param: the parameter to be checked

Returns:
param: the parameter after the check
"""
if isinstance(param, Parameter):
raise ValueError("The parameter {} was not bound correctly.".format(param))
return param


def _check_circuit_and_bind_parameters(quantum_circuit: QuantumCircuit, params: dict) -> QuantumCircuit:
"""Utility function for checking for a valid quantum circuit and then binding parameters.

Args:
quantum_circuit (QuantumCircuit): the quantum circuit to check and bind the parameters for
params (dict): dictionary of the parameters in the circuit

Returns:
qc (QuantumCircuit): quantum circuit with bound parameters
"""
if not isinstance(quantum_circuit, QuantumCircuit):
raise ValueError("The circuit {} is not a valid Qiskit QuantumCircuit.".format(quantum_circuit))

if params is None:
return quantum_circuit

for k, v in params.items():
if isinstance(v, qml.variable.Variable):
params.update({k: v.val})

return quantum_circuit.bind_parameters(params)


def map_wires(wires: list, qc_wires: list) -> dict:
antalszava marked this conversation as resolved.
Show resolved Hide resolved
"""Utility function mapping the wires specified in a quantum circuit with the wires
specified by the user for the template.

Args:
wires (list): wires specified for the template
qc_wires (list): wires from the converted quantum circuit

Returns:
wire_map (dict): map from quantum circuit wires to the user defined wires
"""
if wires is None:
return dict(zip(qc_wires, range(len(qc_wires))))

if len(qc_wires) == len(wires):
return dict(zip(qc_wires, wires))

raise qml.QuantumFunctionError("The specified number of wires - {} - does not match "
"the number of wires the loaded quantum circuit acts on.".format(len(wires)))


def execute_supported_operation(operation_name: str, parameters: list, wires: list):
"""Utility function that executes an operation that is natively supported by PennyLane.

Args:
operation_name (str): wires specified for the template
parameters (str): parameters of the operation that will be executed
wires (list): wires of the operation
"""
operation = getattr(pennylane_ops, operation_name)

parameters = [_check_parameter_bound(param) for param in parameters]

if not parameters:
operation(wires=wires)
elif operation_name == 'QubitStateVector':
operation(np.array(parameters), wires=wires)
elif operation_name == 'QubitUnitary':
operation(*parameters, wires=wires)
else:
float_params = [float(param) for param in parameters]
operation(*float_params, wires=wires)


def load(quantum_circuit: QuantumCircuit):
"""Loads a PennyLane template from a Qiskit QuantumCircuit.
Warnings are created for each of the QuantumCircuit instructions that were
not incorporated in the PennyLane template.
antalszava marked this conversation as resolved.
Show resolved Hide resolved

Args:
quantum_circuit (qiskit.QuantumCircuit): the QuantumCircuit to be converted
antalszava marked this conversation as resolved.
Show resolved Hide resolved

Returns:
function: the resulting PennyLane template
"""

def _function(params: dict = None, wires: list = None):
"""Returns a PennyLane template created based on the input QuantumCircuit.
Warnings are created for each of the QuantumCircuit instructions that were
not incorporated in the PennyLane template.

Args:
params (dict): specifies the parameters that need to be bound in the QuantumCircuit
wires (Sequence[int] or int): The wires the converted template acts on.
Note that if the original QuantumCircuit acted on :math:`N` qubits,
then this must be a list of length :math:`N`.

Returns:
function: the new PennyLane template
"""

qc = _check_circuit_and_bind_parameters(quantum_circuit, params)

# Wires from a qiskit circuit are unique w.r.t. a register name and a qubit index
antalszava marked this conversation as resolved.
Show resolved Hide resolved
qc_wires = [(q.register.name, q.index) for q in quantum_circuit.qubits]

wire_map = map_wires(wires, qc_wires)

# Processing the dictionary of parameters passed
for op in qc.data:

instruction_name = op[0].__class__.__name__

operation_wires = [wire_map[(qubit.register.name, qubit.index)] for qubit in op[1]]

if instruction_name in inv_map and inv_map[instruction_name] in pennylane_ops.ops:

execute_supported_operation(inv_map[instruction_name], op[0].params, operation_wires)

else:
try:
operation_matrix = op[0].to_matrix()
pennylane_ops.QubitUnitary(operation_matrix, wires=operation_wires)
except (AttributeError, QiskitError):
warnings.warn(__name__ + ": The {} instruction is not supported by PennyLane,"
" and has not been added to the template.".
format(instruction_name),
UserWarning)

return _function


def load_qasm(qasm_string: str):
"""Loads a PennyLane template from a QASM string.
Args:
qasm_string (str): the name of the QASM string
Returns:
function: the new PennyLane template
"""
return load(QuantumCircuit.from_qasm_str(qasm_string))


def load_qasm_from_file(file: str):
"""Loads a PennyLane template from a QASM file.
Args:
file (str): the name of the QASM file
Returns:
function: the new PennyLane template
"""
return load(QuantumCircuit.from_qasm_file(file))
12 changes: 12 additions & 0 deletions pennylane_qiskit/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@
# pylint: disable=too-few-public-methods
from pennylane.operation import Operation

_ops = {
antalszava marked this conversation as resolved.
Show resolved Hide resolved
"S",
"Sdg",
"T",
"Tdg",
"U1",
"U2",
"U3",
"CSWAP",
"Toffoli",
}


class S(Operation):
r"""S(wires)
Expand Down
14 changes: 10 additions & 4 deletions pennylane_qiskit/qiskit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,28 @@ def pauli_eigs(n):
"RX": ex.RXGate,
"RY": ex.RYGate,
"RZ": ex.RZGate,
"S": ex.SGate,
"T": ex.TGate,
"CSWAP": ex.FredkinGate,
"CRZ": ex.CrzGate,
"PhaseShift": ex.U1Gate,
"QubitStateVector": ex.Initialize,

# TODO: add the CRY gate, once the U3Gate is a part of PennyLane
# "CRY": U3Gate,
# operations not natively implemented in Qiskit but provided in gates.py
"Rot": Rot,
"BasisState": BasisState,
"QubitUnitary": ex.UnitaryGate,
# additional operations not native to PennyLane but present in Qiskit
"S": ex.SGate,
# operations not natively implemented in Qiskit but provided in gates.py

# TODO: once inverse is added to PennyLane-Qiskit, test the following gates
"Sdg": ex.SdgGate,
"T": ex.TGate,
"Tdg": ex.TdgGate,
"U1": ex.U1Gate,

"U2": ex.U2Gate,
"U3": ex.U3Gate,
"CSWAP": ex.FredkinGate,
"Toffoli": ex.ToffoliGate,
}

Expand Down
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
'qiskit.basicaer = pennylane_qiskit:BasicAerDevice',
'qiskit.ibmq = pennylane_qiskit:IBMQDevice',
],
'pennylane.io': [
'qiskit = pennylane_qiskit:load',
'qasm = pennylane_qiskit:load_qasm',
'qasm_file = pennylane_qiskit:load_qasm_from_file',
],
},
'description': 'PennyLane plugin for qiskit-terra',
'long_description': open('README.rst').read(),
Expand Down
30 changes: 30 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import pytest
import numpy as np

import pennylane as qml
from pennylane_qiskit import AerDevice, BasicAerDevice

import contextlib
import io


np.random.seed(42)

Expand Down Expand Up @@ -53,3 +57,29 @@ def _device(n):
return request.param(wires=n, backend=backend, shots=shots, analytic=analytic)

return _device


@pytest.fixture(scope="function")
def mock_device(monkeypatch):
"""A mock instance of the abstract Device class"""

with monkeypatch.context() as m:
dev = qml.Device
m.setattr(dev, '__abstractmethods__', frozenset())
yield qml.Device()


@pytest.fixture(scope="function")
def recorder():
return qml.utils.OperationRecorder()


@pytest.fixture(scope="function")
def qubit_device_single_wire():
return qml.device('default.qubit', wires=1)


@pytest.fixture(scope="function")
def qubit_device_2_wires():
return qml.device('default.qubit', wires=2)

Loading