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

Adding is_zero_initialized to transpile and extending HighLevelSynthesis with ancilla qubits #12729

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f190001
experimenting
alexanderivrii Jul 7, 2024
830763a
experimenting
alexanderivrii Jul 7, 2024
fae4f44
some bug fixes and formatting
alexanderivrii Jul 7, 2024
c91f597
Merge branch 'main' into hls-with-ancillas
alexanderivrii Jul 7, 2024
56d045e
removing print
alexanderivrii Jul 7, 2024
9d5d308
bug fix
alexanderivrii Jul 7, 2024
cba1425
bug fix
alexanderivrii Jul 8, 2024
6795769
including identity in basis_gates for tests: the transpilation does n…
alexanderivrii Jul 8, 2024
bd6f9ac
pylint fixes
alexanderivrii Jul 8, 2024
bb72261
adding is_zero_initialized across the flow and refactoring HLS pass
alexanderivrii Jul 18, 2024
260d21d
merge with main
alexanderivrii Jul 18, 2024
7eb4a02
updating mcx synthesis calls
alexanderivrii Jul 18, 2024
976ac86
correction to when we can use ancilla qubits
alexanderivrii Jul 18, 2024
7051ebf
fix
alexanderivrii Jul 18, 2024
0fd4e5b
test to passmanager __str__ method
alexanderivrii Jul 18, 2024
afa1af3
removing MCX synthesis functions and plugins from this PR
alexanderivrii Jul 18, 2024
15024f7
fixes
alexanderivrii Jul 18, 2024
cd869f4
release notes
alexanderivrii Jul 18, 2024
389b33d
implement review comments
Cryoris Jul 19, 2024
db9f935
rename to ``qubits_initially_zero``
Cryoris Jul 19, 2024
06d27eb
Merge pull request #77 from Cryoris/hls-with-ancillas-tests
alexanderivrii Jul 21, 2024
8b6866d
minor changes to comments
alexanderivrii Jul 21, 2024
7d8e1a2
only computing dirty auxiliary qubits when needed
alexanderivrii Jul 21, 2024
26307ea
changing _num to num in release notes
alexanderivrii Jul 21, 2024
64c52ab
chaning clean_ancillas to set
alexanderivrii Jul 21, 2024
62c09e0
docstring
alexanderivrii Jul 21, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def _mcsu2_real_diagonal(
circuit.h(target)

if use_basis_gates:
circuit = transpile(circuit, basis_gates=["p", "u", "cx"])
circuit = transpile(circuit, basis_gates=["p", "u", "cx"], qubits_initially_zero=False)

return circuit

Expand Down
3 changes: 2 additions & 1 deletion qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ def _define(self):
q_target = self.num_ctrl_qubits
new_target = q_target
for k in range(self.num_ctrl_qubits):
qc.mcrz(lam / (2**k), q_controls, new_target, use_basis_gates=True)
# Note: it's better *not* to run transpile recursively
qc.mcrz(lam / (2**k), q_controls, new_target, use_basis_gates=False)
new_target = q_controls.pop()
qc.p(lam / (2**self.num_ctrl_qubits), new_target)
else: # in this case type(lam) is ParameterValueType
Expand Down
3 changes: 3 additions & 0 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def transpile( # pylint: disable=too-many-return-statements
optimization_method: Optional[str] = None,
ignore_backend_supplied_default_methods: bool = False,
num_processes: Optional[int] = None,
qubits_initially_zero: bool = True,
) -> _CircuitT:
"""Transpile one or more circuits, according to some desired transpilation targets.

Expand Down Expand Up @@ -285,6 +286,7 @@ def callback_func(**kwargs):
``num_processes`` in the user configuration file, and the ``QISKIT_NUM_PROCS``
environment variable. If set to ``None`` the system default or local user configuration
will be used.
qubits_initially_zero: Indicates whether the input circuit is zero-initialized.

Returns:
The transpiled circuit(s).
Expand Down Expand Up @@ -372,6 +374,7 @@ def callback_func(**kwargs):
init_method=init_method,
optimization_method=optimization_method,
dt=dt,
qubits_initially_zero=qubits_initially_zero,
)

out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes)
Expand Down
4 changes: 3 additions & 1 deletion qiskit/synthesis/unitary/qsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ def _apply_a2(circ):
from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate

decomposer = two_qubit_decompose.TwoQubitDecomposeUpToDiagonal()
ccirc = transpile(circ, basis_gates=["u", "cx", "qsd2q"], optimization_level=0)
ccirc = transpile(
circ, basis_gates=["u", "cx", "qsd2q"], optimization_level=0, qubits_initially_zero=False
)
ind2q = []
# collect 2q instrs
for i, instruction in enumerate(ccirc.data):
Expand Down
227 changes: 189 additions & 38 deletions qiskit/transpiler/passes/synthesis/high_level_synthesis.py

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions qiskit/transpiler/passmanager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __init__(
hls_config=None,
init_method=None,
optimization_method=None,
qubits_initially_zero=True,
):
"""Initialize a PassManagerConfig object

Expand Down Expand Up @@ -84,6 +85,8 @@ def __init__(
init_method (str): The plugin name for the init stage plugin to use
optimization_method (str): The plugin name for the optimization stage plugin
to use.
qubits_initially_zero (bool): Indicates whether the input circuit is
zero-initialized.
"""
self.initial_layout = initial_layout
self.basis_gates = basis_gates
Expand All @@ -104,6 +107,7 @@ def __init__(
self.unitary_synthesis_plugin_config = unitary_synthesis_plugin_config
self.target = target
self.hls_config = hls_config
self.qubits_initially_zero = qubits_initially_zero

@classmethod
def from_backend(cls, backend, _skip_target=False, **pass_manager_options):
Expand Down Expand Up @@ -189,5 +193,6 @@ def __str__(self):
f"\ttiming_constraints: {self.timing_constraints}\n"
f"\tunitary_synthesis_method: {self.unitary_synthesis_method}\n"
f"\tunitary_synthesis_plugin_config: {self.unitary_synthesis_plugin_config}\n"
f"\tqubits_initially_zero: {self.qubits_initially_zero}\n"
f"\ttarget: {str(self.target).replace(newline, newline_tab)}\n"
)
4 changes: 4 additions & 0 deletions qiskit/transpiler/preset_passmanagers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def generate_preset_pass_manager(
init_method=None,
optimization_method=None,
dt=None,
qubits_initially_zero=True,
*,
_skip_target=False,
):
Expand Down Expand Up @@ -274,6 +275,8 @@ def generate_preset_pass_manager(
plugin is not used. You can see a list of installed plugins by
using :func:`~.list_stage_plugins` with ``"optimization"`` for the
``stage_name`` argument.
qubits_initially_zero (bool): Indicates whether the input circuit is
zero-initialized.

Returns:
StagedPassManager: The preset pass manager for the given options
Expand Down Expand Up @@ -385,6 +388,7 @@ def generate_preset_pass_manager(
"hls_config": hls_config,
"init_method": init_method,
"optimization_method": optimization_method,
"qubits_initially_zero": qubits_initially_zero,
}

if backend is not None:
Expand Down
5 changes: 5 additions & 0 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
pass_manager_config.unitary_synthesis_method,
pass_manager_config.unitary_synthesis_plugin_config,
pass_manager_config.hls_config,
pass_manager_config.qubits_initially_zero,
)
elif optimization_level == 1:
init = PassManager()
Expand All @@ -104,6 +105,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
pass_manager_config.unitary_synthesis_method,
pass_manager_config.unitary_synthesis_plugin_config,
pass_manager_config.hls_config,
pass_manager_config.qubits_initially_zero,
)
init.append(
InverseCancellation(
Expand Down Expand Up @@ -132,6 +134,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
pass_manager_config.unitary_synthesis_method,
pass_manager_config.unitary_synthesis_plugin_config,
pass_manager_config.hls_config,
pass_manager_config.qubits_initially_zero,
)
init.append(ElidePermutations())
init.append(RemoveDiagonalGatesBeforeMeasure())
Expand Down Expand Up @@ -173,6 +176,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
unitary_synthesis_method=pass_manager_config.unitary_synthesis_method,
unitary_synthesis_plugin_config=pass_manager_config.unitary_synthesis_plugin_config,
hls_config=pass_manager_config.hls_config,
qubits_initially_zero=pass_manager_config.qubits_initially_zero,
)


Expand All @@ -190,6 +194,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
unitary_synthesis_method=pass_manager_config.unitary_synthesis_method,
unitary_synthesis_plugin_config=pass_manager_config.unitary_synthesis_plugin_config,
hls_config=pass_manager_config.hls_config,
qubits_initially_zero=pass_manager_config.qubits_initially_zero,
)


Expand Down
14 changes: 12 additions & 2 deletions qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def generate_unroll_3q(
unitary_synthesis_method="default",
unitary_synthesis_plugin_config=None,
hls_config=None,
qubits_initially_zero=True,
):
"""Generate an unroll >3q :class:`~qiskit.transpiler.PassManager`

Expand All @@ -202,8 +203,10 @@ def generate_unroll_3q(
configuration, this is plugin specific refer to the specified plugin's
documentation for how to use.
hls_config (HLSConfig): An optional configuration class to use for
:class:`~qiskit.transpiler.passes.HighLevelSynthesis` pass.
Specifies how to synthesize various high-level objects.
:class:`~qiskit.transpiler.passes.HighLevelSynthesis` pass.
Specifies how to synthesize various high-level objects.
qubits_initially_zero (bool): Indicates whether the input circuit is
zero-initialized.

Returns:
PassManager: The unroll 3q or more pass manager
Expand All @@ -228,6 +231,7 @@ def generate_unroll_3q(
equivalence_library=sel,
basis_gates=basis_gates,
min_qubits=3,
qubits_initially_zero=qubits_initially_zero,
)
)
# If there are no target instructions revert to using unroll3qormore so
Expand Down Expand Up @@ -417,6 +421,7 @@ def generate_translation_passmanager(
unitary_synthesis_method="default",
unitary_synthesis_plugin_config=None,
hls_config=None,
qubits_initially_zero=True,
):
"""Generate a basis translation :class:`~qiskit.transpiler.PassManager`

Expand All @@ -442,6 +447,8 @@ def generate_translation_passmanager(
hls_config (HLSConfig): An optional configuration class to use for
:class:`~qiskit.transpiler.passes.HighLevelSynthesis` pass.
Specifies how to synthesize various high-level objects.
qubits_initially_zero (bool): Indicates whether the input circuit is
zero-initialized.

Returns:
PassManager: The basis translation pass manager
Expand Down Expand Up @@ -469,6 +476,7 @@ def generate_translation_passmanager(
use_qubit_indices=True,
equivalence_library=sel,
basis_gates=basis_gates,
qubits_initially_zero=qubits_initially_zero,
),
BasisTranslator(sel, basis_gates, target),
]
Expand All @@ -493,6 +501,7 @@ def generate_translation_passmanager(
use_qubit_indices=True,
basis_gates=basis_gates,
min_qubits=3,
qubits_initially_zero=qubits_initially_zero,
),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
Collect2qBlocks(),
Expand All @@ -515,6 +524,7 @@ def generate_translation_passmanager(
target=target,
use_qubit_indices=True,
basis_gates=basis_gates,
qubits_initially_zero=qubits_initially_zero,
),
]
else:
Expand Down
18 changes: 18 additions & 0 deletions releasenotes/notes/hls-with-ancillas-d6792b41dfcf4aac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
features_transpiler:
- |
A new argument ``qubits_initially_zero`` has been added to :func:`qiskit.compiler.transpile`,
:func:`.generate_preset_pass_manager`, and to :class:`~.PassManagerConfig`.
If set to ``True``, the qubits are assumed to be initially in the state :math:`|0\rangle`,
potentially allowing additional optimization opportunities for individual transpiler passes.
- |
The constructor for :class:`.HighLevelSynthesis` transpiler pass now accepts an
additional argument ``qubits_initially_zero``. If set to ``True``, the pass assumes that the
qubits are initially in the state :math:`|0\rangle`. In addition, the pass keeps track of
clean and dirty auxiliary qubits throughout the run, and passes this information to plugins
via kwargs ``num_clean_ancillas`` and ``num_dirty_ancillas``.
upgrade_transpiler:
- |
The :func:`~qiskit.compiler.transpile` now assumes that the qubits are initially in the state
:math:`|0\rangle`. To avoid this assumption, one can set the argument ``qubits_initially_zero``
to ``False``.
116 changes: 115 additions & 1 deletion test/python/transpiler/test_high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
Parameter,
Operation,
EquivalenceLibrary,
Delay,
)
from qiskit.circuit.classical import expr, types
from qiskit.circuit.library import (
Expand All @@ -42,6 +43,7 @@
CU3Gate,
CU1Gate,
QFTGate,
IGate,
)
from qiskit.circuit.library.generalized_gates import LinearFunction
from qiskit.quantum_info import Clifford
Expand Down Expand Up @@ -220,6 +222,31 @@ def method(self, op_name, method_name):
return self.plugins[plugin_name]()


class MockHLS(HighLevelSynthesisPlugin):
"""A mock HLS using auxiliary qubits."""

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run a mock synthesis for high_level_object being anything with a num_qubits property.

Replaces the high_level_objects by a layer of X gates, applies S gates on clean
ancillas and T gates on dirty ancillas.
"""

num_action_qubits = high_level_object.num_qubits
num_clean = options["num_clean_ancillas"]
num_dirty = options["num_dirty_ancillas"]
num_qubits = num_action_qubits + num_clean + num_dirty
decomposition = QuantumCircuit(num_qubits)
decomposition.x(range(num_action_qubits))
if num_clean > 0:
decomposition.s(range(num_action_qubits, num_action_qubits + num_clean))
if num_dirty > 0:
decomposition.t(range(num_action_qubits + num_clean, num_qubits))

return decomposition


@ddt
class TestHighLevelSynthesisInterface(QiskitTestCase):
"""Tests for the synthesis plugin interface."""

Expand Down Expand Up @@ -481,7 +508,7 @@ def test_target_gets_passed_to_plugins(self):
[
HighLevelSynthesis(
hls_config=hls_config,
target=GenericBackendV2(num_qubits=5, basis_gates=["u", "cx"]).target,
target=GenericBackendV2(num_qubits=5, basis_gates=["u", "cx", "id"]).target,
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
)
]
)
Expand Down Expand Up @@ -514,6 +541,93 @@ def test_qubits_get_passed_to_plugins(self):
# plugin should see qubits and complete without errors.
pm_use_qubits_true.run(qc)

def test_ancilla_arguments(self):
"""Test ancillas are correctly labelled."""
gate = Gate(name="duckling", num_qubits=5, params=[])
hls_config = HLSConfig(duckling=[MockHLS()])

qc = QuantumCircuit(10)
qc.h([0, 8, 9]) # the two last H gates yield two dirty ancillas
qc.barrier()
qc.append(gate, range(gate.num_qubits))

pm = PassManager([HighLevelSynthesis(hls_config=hls_config)])

synthesized = pm.run(qc)

count = synthesized.count_ops()
self.assertEqual(count.get("x", 0), gate.num_qubits) # gate qubits
self.assertEqual(count.get("s", 0), qc.num_qubits - gate.num_qubits - 2) # clean
self.assertEqual(count.get("t", 0), 2) # dirty

def test_ancilla_noop(self):
"""Test ancillas states are not affected by no-ops."""
gate = Gate(name="duckling", num_qubits=1, params=[])
hls_config = HLSConfig(duckling=[MockHLS()])
pm = PassManager([HighLevelSynthesis(hls_config)])

noops = [Delay(100), IGate()]
for noop in noops:
qc = QuantumCircuit(2)
qc.append(noop, [1]) # this noop should still yield a clean ancilla
qc.barrier()
qc.append(gate, [0])

synthesized = pm.run(qc)
count = synthesized.count_ops()
with self.subTest(noop=noop):
self.assertEqual(count.get("x", 0), gate.num_qubits) # gate qubits
self.assertEqual(count.get("s", 0), 1) # clean ancilla
self.assertEqual(count.get("t", 0), 0) # dirty ancilla

@data(True, False)
def test_ancilla_reset(self, reset):
"""Test ancillas are correctly freed after a reset operation."""
gate = Gate(name="duckling", num_qubits=1, params=[])
hls_config = HLSConfig(duckling=[MockHLS()])
pm = PassManager([HighLevelSynthesis(hls_config)])

qc = QuantumCircuit(2)
qc.h(1)
if reset:
qc.reset(1) # the reset frees the ancilla qubit again
qc.barrier()
qc.append(gate, [0])

synthesized = pm.run(qc)
count = synthesized.count_ops()

expected_clean = 1 if reset else 0
expected_dirty = 1 - expected_clean

self.assertEqual(count.get("x", 0), gate.num_qubits) # gate qubits
self.assertEqual(count.get("s", 0), expected_clean) # clean ancilla
self.assertEqual(count.get("t", 0), expected_dirty) # clean ancilla

def test_ancilla_state_maintained(self):
"""Test ancillas states are still dirty/clean after they've been used."""
gate = Gate(name="duckling", num_qubits=1, params=[])
hls_config = HLSConfig(duckling=[MockHLS()])
pm = PassManager([HighLevelSynthesis(hls_config)])

qc = QuantumCircuit(3)
qc.h(2) # the final ancilla is dirty
qc.barrier()
qc.append(gate, [0])
qc.append(gate, [0])

# the ancilla states should be unchanged after the synthesis, i.e. qubit 1 is always
# clean (S gate) and qubit 2 is always dirty (T gate)
ref = QuantumCircuit(3)
ref.h(2)
ref.barrier()
for _ in range(2):
ref.x(0)
ref.s(1)
ref.t(2)

self.assertEqual(ref, pm.run(qc))


class TestPMHSynthesisLinearFunctionPlugin(QiskitTestCase):
"""Tests for the PMHSynthesisLinearFunction plugin for synthesizing linear functions."""
Expand Down
1 change: 1 addition & 0 deletions test/python/transpiler/test_passmanager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def test_str(self):
\ttiming_constraints: None
\tunitary_synthesis_method: default
\tunitary_synthesis_plugin_config: None
\tqubits_initially_zero: True
\ttarget: Target: Basic Target
\tNumber of qubits: None
\tInstructions:
Expand Down
Loading