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

Overloaded get_compiled_circuit/s #423

Merged
merged 21 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 20 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
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Add a boolean `perm_warning` argument to {py:func}`tk_to_qiskit` indicating whether to give a warning if the input {py:class}`Circuit` has an implicit qubit permutation.
- Add new level 3 optimisation that uses `GreedyPauliSimp`
- Fix `get_results()` for Aer backends when circuits with different numbers of qubits or postprocessing circuits submitted together.
- Add `timeout` parameter to `get_compiled_circuit` and `get_compiled_circuits`.

## 0.59.0 (November 2024)

Expand Down
126 changes: 124 additions & 2 deletions pytket/extensions/qiskit/backends/aer.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,35 @@ def _arch_dependent_default_compilation_pass(
[
DecomposeBoxes(),
RemoveBarriers(),
AutoRebase({OpType.CX, OpType.H, OpType.Rz}),
AutoRebase(
{
OpType.Z,
OpType.X,
OpType.Y,
OpType.S,
OpType.Sdg,
OpType.V,
OpType.Vdg,
OpType.H,
OpType.CX,
OpType.CY,
OpType.CZ,
OpType.SWAP,
OpType.Rz,
OpType.Rx,
OpType.Ry,
OpType.T,
OpType.Tdg,
OpType.ZZMax,
OpType.ZZPhase,
OpType.XXPhase,
OpType.YYPhase,
OpType.PhasedX,
}
),
GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10),
*arch_specific_passes,
self.rebase_pass(),
SynthesiseTket(),
],
)
Expand All @@ -232,8 +258,35 @@ def _arch_independent_default_compilation_pass(
[
DecomposeBoxes(),
RemoveBarriers(),
AutoRebase({OpType.CX, OpType.H, OpType.Rz}),
AutoRebase(
{
OpType.Z,
OpType.X,
OpType.Y,
OpType.S,
OpType.Sdg,
OpType.V,
OpType.Vdg,
OpType.H,
OpType.CX,
OpType.CY,
OpType.CZ,
OpType.SWAP,
OpType.Rz,
OpType.Rx,
OpType.Ry,
OpType.T,
OpType.Tdg,
OpType.ZZMax,
OpType.ZZPhase,
OpType.XXPhase,
OpType.YYPhase,
OpType.PhasedX,
}
),
GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10),
self.rebase_pass(),
SynthesiseTket(),
],
)

Expand All @@ -256,6 +309,75 @@ def default_compilation_pass(
optimisation_level, timeout
)

def get_compiled_circuit(
self, circuit: Circuit, optimisation_level: int = 2, timeout: int = 300
) -> Circuit:
"""
Return a single circuit compiled with :py:meth:`default_compilation_pass`.

:param optimisation_level: Allows values of 0, 1, 2 or 3, with higher values
prompting more computationally heavy optimising compilation that
can lead to reduced gate count in circuits.
:type optimisation_level: int, optional
:param timeout: Only valid for optimisation level 3, gives a maximimum time
for running a single thread of the pass `GreedyPauliSimp`. Increase for
optimising larger circuits.
:type timeout: int, optional

:return: An optimised quantum circuit
:rtype: Circuit
"""
return_circuit = circuit.copy()
if optimisation_level == 3 and circuit.n_gates_of_type(OpType.Barrier) > 0:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this check not in the other function get_compiled_circuits ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

get_compiled_circuits calls this method so the check is done implicitly

warnings.warn(
"Barrier operations in this circuit will be removed when using "
"optimisation level 3."
)
self.default_compilation_pass(optimisation_level, timeout).apply(return_circuit)
return return_circuit

def get_compiled_circuits(
self,
circuits: Sequence[Circuit],
optimisation_level: int = 2,
timeout: int = 300,
) -> list[Circuit]:
"""Compile a sequence of circuits with :py:meth:`default_compilation_pass`
and return the list of compiled circuits (does not act in place).

As well as applying a degree of optimisation (controlled by the
`optimisation_level` parameter), this method tries to ensure that the circuits
can be run on the backend (i.e. successfully passed to
:py:meth:`process_circuits`), for example by rebasing to the supported gate set,
or routing to match the connectivity of the device. However, this is not always
possible, for example if the circuit contains classical operations that are not
supported by the backend. You may use :py:meth:`valid_circuit` to check whether
the circuit meets the backend's requirements after compilation. This validity
check is included in :py:meth:`process_circuits` by default, before any circuits
are submitted to the backend.

If the validity check fails, you can obtain more information about the failure
by iterating through the predicates in the `required_predicates` property of the
backend, and running the :py:meth:`verify` method on each in turn with your
circuit.

:param circuits: The circuits to compile.
:type circuit: Sequence[Circuit]
:param optimisation_level: The level of optimisation to perform during
compilation. See :py:meth:`default_compilation_pass` for a description of
the different levels (0, 1, 2 or 3). Defaults to 2.
:type optimisation_level: int, optional
:param timeout: Only valid for optimisation level 3, gives a maximimum time
for running a single thread of the pass `GreedyPauliSimp`. Increase for
optimising larger circuits.
:type timeout: int, optional
:return: Compiled circuits.
:rtype: List[Circuit]
"""
return [
self.get_compiled_circuit(c, optimisation_level, timeout) for c in circuits
]

def process_circuits(
self,
circuits: Sequence[Circuit],
Expand Down
98 changes: 97 additions & 1 deletion pytket/extensions/qiskit/backends/ibm.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,34 @@ def default_compilation_pass_offline(
passlist.append(FullPeepholeOptimise())
elif optimisation_level == 3:
passlist.append(RemoveBarriers())
passlist.append(AutoRebase({OpType.CX, OpType.H, OpType.Rz}))
passlist.append(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this a relevant change, that should be in the changelog, too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think we don't need to include this - all I've done is update the set of gates we AutoRebase to so that it exactly matches what GreedyPauliSimp accepts. We don't mention this rebase for the GreedyPauliSimp line of the changelog so I don't think it's worth explicitly pointing out.

AutoRebase(
{
OpType.Z,
OpType.X,
OpType.Y,
OpType.S,
OpType.Sdg,
OpType.V,
OpType.Vdg,
OpType.H,
OpType.CX,
OpType.CY,
OpType.CZ,
OpType.SWAP,
OpType.Rz,
OpType.Rx,
OpType.Ry,
OpType.T,
OpType.Tdg,
OpType.ZZMax,
OpType.ZZPhase,
OpType.XXPhase,
OpType.YYPhase,
OpType.PhasedX,
}
),
)
passlist.append(
GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10)
)
Expand Down Expand Up @@ -441,6 +468,75 @@ def default_compilation_pass_offline(
)
return SequencePass(passlist)

def get_compiled_circuit(
self, circuit: Circuit, optimisation_level: int = 2, timeout: int = 300
) -> Circuit:
"""
Return a single circuit compiled with :py:meth:`default_compilation_pass`.

:param optimisation_level: Allows values of 0, 1, 2 or 3, with higher values
prompting more computationally heavy optimising compilation that
can lead to reduced gate count in circuits.
:type optimisation_level: int, optional
:param timeout: Only valid for optimisation level 3, gives a maximimum time
for running a single thread of the pass `GreedyPauliSimp`. Increase for
optimising larger circuits.
:type timeout: int, optional

:return: An optimised quantum circuit
:rtype: Circuit
"""
return_circuit = circuit.copy()
if optimisation_level == 3 and circuit.n_gates_of_type(OpType.Barrier) > 0:
Copy link
Collaborator

Choose a reason for hiding this comment

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

See above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same answer as for AerBackend

warn(
"Barrier operations in this circuit will be removed when using "
"optimisation level 3."
)
self.default_compilation_pass(optimisation_level, timeout).apply(return_circuit)
return return_circuit

def get_compiled_circuits(
self,
circuits: Sequence[Circuit],
optimisation_level: int = 2,
timeout: int = 300,
) -> list[Circuit]:
"""Compile a sequence of circuits with :py:meth:`default_compilation_pass`
and return the list of compiled circuits (does not act in place).

As well as applying a degree of optimisation (controlled by the
`optimisation_level` parameter), this method tries to ensure that the circuits
can be run on the backend (i.e. successfully passed to
:py:meth:`process_circuits`), for example by rebasing to the supported gate set,
or routing to match the connectivity of the device. However, this is not always
possible, for example if the circuit contains classical operations that are not
supported by the backend. You may use :py:meth:`valid_circuit` to check whether
the circuit meets the backend's requirements after compilation. This validity
check is included in :py:meth:`process_circuits` by default, before any circuits
are submitted to the backend.

If the validity check fails, you can obtain more information about the failure
by iterating through the predicates in the `required_predicates` property of the
backend, and running the :py:meth:`verify` method on each in turn with your
circuit.

:param circuits: The circuits to compile.
:type circuit: Sequence[Circuit]
:param optimisation_level: The level of optimisation to perform during
compilation. See :py:meth:`default_compilation_pass` for a description of
the different levels (0, 1, 2 or 3). Defaults to 2.
:type optimisation_level: int, optional
:param timeout: Only valid for optimisation level 3, gives a maximimum time
for running a single thread of the pass `GreedyPauliSimp`. Increase for
optimising larger circuits.
:type timeout: int, optional
:return: Compiled circuits.
:rtype: List[Circuit]
"""
return [
self.get_compiled_circuit(c, optimisation_level, timeout) for c in circuits
]

@property
def _result_id_type(self) -> _ResultIdTuple:
# IBMQ job ID, index, number of bits, post-processing circuit
Expand Down
4 changes: 4 additions & 0 deletions tests/backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1516,13 +1516,17 @@ def test_optimisation_level_3_compilation() -> None:

compiled_2 = b.get_compiled_circuit(c, 2)
compiled_3 = b.get_compiled_circuit(c, 3)
compiled_3_timeout = b.get_compiled_circuit(c, 3, timeout=0)

assert compiled_2.n_2qb_gates() == 78
assert compiled_2.n_gates == 205
assert compiled_2.depth() == 147
assert compiled_3.n_2qb_gates() == 61
assert compiled_3.n_gates == 164
assert compiled_3.depth() == 114
assert compiled_3_timeout.n_2qb_gates() == 69
assert compiled_3_timeout.n_gates == 171
assert compiled_3_timeout.depth() == 125


def test_optimisation_level_3_serialisation() -> None:
Expand Down