From 6e65ad883ced32a2628d1b97e9bd66203572df9c Mon Sep 17 00:00:00 2001 From: Renyi Chen Date: Tue, 19 Mar 2024 21:00:31 -0700 Subject: [PATCH 1/4] Impl dynamical decoupling transfomer. --- cirq-core/cirq/__init__.py | 2 + cirq-core/cirq/json_resolver_cache.py | 1 + .../DynamicalDecouplingModel.json | 21 +++ .../DynamicalDecouplingModel.repr | 2 + cirq-core/cirq/transformers/__init__.py | 5 + .../cirq/transformers/dynamical_decoupling.py | 146 ++++++++++++++++++ .../transformers/dynamical_decoupling_test.py | 121 +++++++++++++++ 7 files changed, 298 insertions(+) create mode 100644 cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json create mode 100644 cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr create mode 100644 cirq-core/cirq/transformers/dynamical_decoupling.py create mode 100644 cirq-core/cirq/transformers/dynamical_decoupling_test.py diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 2dc2034600a..e5df04d7b36 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -336,6 +336,8 @@ from cirq.transformers import ( AbstractInitialMapper, + add_dynamical_decoupling, + DynamicalDecouplingModel, align_left, align_right, CompilationTargetGateset, diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 4880046618a..2ab84df6dcf 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -102,6 +102,7 @@ def _symmetricalqidpair(qids): import sympy return { + 'DynamicalDecouplingModel': cirq.DynamicalDecouplingModel, 'AmplitudeDampingChannel': cirq.AmplitudeDampingChannel, 'AnyIntegerPowerGateFamily': cirq.AnyIntegerPowerGateFamily, 'AnyUnitaryGateFamily': cirq.AnyUnitaryGateFamily, diff --git a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json new file mode 100644 index 00000000000..183726ac703 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json @@ -0,0 +1,21 @@ +[ + { + "cirq_type": "DynamicalDecouplingModel", + "schema": "XX_PAIR" + }, + { + "cirq_type": "DynamicalDecouplingModel", + "base_dd_sequence": [ + { + "cirq_type": "XPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + { + "cirq_type": "XPowGate", + "exponent": 1.0, + "global_shift": 0.0 + } + ] + } +] \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr new file mode 100644 index 00000000000..93e994447b5 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr @@ -0,0 +1,2 @@ +[cirq.DynamicalDecouplingModel.from_schema("XX_PAIR"), +cirq.DynamicalDecouplingModel(base_dd_sequence=[cirq.XPowGate(), cirq.XPowGate()])] \ No newline at end of file diff --git a/cirq-core/cirq/transformers/__init__.py b/cirq-core/cirq/transformers/__init__.py index 803d22c5e38..7d03bb4e3ca 100644 --- a/cirq-core/cirq/transformers/__init__.py +++ b/cirq-core/cirq/transformers/__init__.py @@ -78,6 +78,11 @@ from cirq.transformers.drop_negligible_operations import drop_negligible_operations +from cirq.transformers.dynamical_decoupling import ( + add_dynamical_decoupling, + DynamicalDecouplingModel, +) + from cirq.transformers.eject_z import eject_z from cirq.transformers.measurement_transformers import ( diff --git a/cirq-core/cirq/transformers/dynamical_decoupling.py b/cirq-core/cirq/transformers/dynamical_decoupling.py new file mode 100644 index 00000000000..6af91bf0107 --- /dev/null +++ b/cirq-core/cirq/transformers/dynamical_decoupling.py @@ -0,0 +1,146 @@ +# Copyright 2024 The Cirq Developers +# +# 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 +# +# https://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. + +"""Transformer pass that adds dynamical decoupling moments to a circuit.""" + +import enum +from functools import reduce +from typing import Any, Dict, Optional, Tuple + +import cirq +from cirq import value +import numpy as np + + +@enum.unique +class _DynamicalDecouplingSchema(enum.Enum): + """Supported schemes of dynamical decoupling.""" + + XX_PAIR = 'XX_PAIR' + YY_PAIR = 'YY_PAIR' + + +def _repeat_sequence(base_sequence: list['cirq.Gate'], num_idle_moments: int): + repeat_times = num_idle_moments // len(base_sequence) + return base_sequence * repeat_times + + +def _generate_dd_sequence_from_schema( + schema: _DynamicalDecouplingSchema, num_idle_moments: int = 2 +) -> list['cirq.Gate']: + match schema: + case _DynamicalDecouplingSchema.XX_PAIR: + return _repeat_sequence([cirq.XPowGate(), cirq.XPowGate()], num_idle_moments) + case _DynamicalDecouplingSchema.YY_PAIR: + return _repeat_sequence([cirq.YPowGate(), cirq.YPowGate()], num_idle_moments) + + +def _validate_dd_sequence(dd_sequence: list['cirq.Gate']) -> None: + if len(dd_sequence) < 2: + raise ValueError('Invalid dynamical decoupling sequence. Expect more than one gates.') + matrices = [cirq.unitary(gate) for gate in dd_sequence] + product = reduce(np.matmul, matrices) + if not np.array_equal(product, np.eye(2)): + raise ValueError( + "Invalid dynamical decoupling sequence, sequence product doesn't equal" ' identity.' + ) + + +@value.value_equality +class DynamicalDecouplingModel: + """Dynamical decoupling model that generates dynamical decoupling gate sequences.""" + + def __init__( + self, + schema: Optional[_DynamicalDecouplingSchema] = None, + base_dd_sequence: Optional[list['cirq.Gate']] = None, + ): + if not schema and not base_dd_sequence: + raise ValueError( + 'Specify either schema or base_dd_sequence to construct a valid' + ' DynamicalDecouplingModel.' + ) + self.schema = schema + self.base_dd_sequence = base_dd_sequence + if base_dd_sequence: + _validate_dd_sequence(base_dd_sequence) + + def generate_dd_sequence(self, num_idle_moments: int = 2) -> list['cirq.Gate']: + """Returns the longest possible dynamical decoupling sequence.""" + if num_idle_moments <= 0: + return [] + if self.schema: + return _generate_dd_sequence_from_schema(self.schema, num_idle_moments) + if self.base_dd_sequence: + return _repeat_sequence(self.base_dd_sequence, num_idle_moments) + return [] + + @classmethod + def from_schema(cls, schema: str): + """Create dynamical decoupling model according to a given schema.""" + if not schema in _DynamicalDecouplingSchema.__members__: + raise ValueError("Invalid schema name.") + return cls(schema=_DynamicalDecouplingSchema[schema]) + + @classmethod + def from_base_dd_sequence(cls, base_dd_sequence: list['cirq.Gate']): + """Create dynamical decoupling model according to a base sequence.""" + return cls(base_dd_sequence=base_dd_sequence) + + def _json_dict_(self) -> Dict[str, Any]: + d: Dict[str, Any] = {} + if self.schema: + d['schema'] = self.schema.name + if self.base_dd_sequence: + d['base_dd_sequence'] = self.base_dd_sequence + return d + + @classmethod + def _from_json_dict_(cls, schema=None, base_dd_sequence=None, **kwargs): + if schema: + return cls(schema=_DynamicalDecouplingSchema[schema]) + if base_dd_sequence: + return cls(base_dd_sequence=base_dd_sequence) + + def _value_equality_values_(self) -> Any: + return self.schema, self.base_dd_sequence + + +def add_dynamical_decoupling( + circuit: 'cirq.AbstractCircuit', dd_model: DynamicalDecouplingModel +) -> 'cirq.Circuit': + """Add dynamical decoupling gates in a given circuit. + + Args: + circuit: Input circuit to transform. + dd_model: Dynamical decoupling model that defines the schema to generate + dynamical decoupling sequences. + + Return: + A circuit with dynamical decoupling operations. + """ + last_busy_moment_by_qubits: Dict['cirq.Qid', int] = {q: 0 for q in circuit.all_qubits()} + insert_into: list[Tuple[int, 'cirq.OP_TREE']] = [] + for moment_id, moment in enumerate(circuit): + for q in moment.qubits: + insert_gates = dd_model.generate_dd_sequence( + num_idle_moments=moment_id - last_busy_moment_by_qubits[q] - 1 + ) + for idx, gate in enumerate(insert_gates): + insert_into.append((last_busy_moment_by_qubits[q] + idx + 1, gate.on(q))) + last_busy_moment_by_qubits[q] = moment_id + + circuit.batch_insert_into(insert_into) + + return circuit diff --git a/cirq-core/cirq/transformers/dynamical_decoupling_test.py b/cirq-core/cirq/transformers/dynamical_decoupling_test.py new file mode 100644 index 00000000000..310822650df --- /dev/null +++ b/cirq-core/cirq/transformers/dynamical_decoupling_test.py @@ -0,0 +1,121 @@ +# Copyright 2024 The Cirq Developers +# +# 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 +# +# https://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. + +import cirq +from cirq import DynamicalDecouplingModel, add_dynamical_decoupling +import pytest + + +def assert_dd( + input_circuit: cirq.Circuit, expected_circuit: cirq.Circuit, dd_model: DynamicalDecouplingModel +): + updated_circuit = add_dynamical_decoupling(input_circuit, dd_model=dd_model) + cirq.testing.assert_same_circuits(updated_circuit, expected_circuit) + + +def test_insert_provided_schema(): + a = cirq.NamedQubit("a") + b = cirq.NamedQubit("b") + c = cirq.NamedQubit("c") + + # No insertion as there is no room for a dd sequence. + assert_dd( + input_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), cirq.Moment(cirq.CNOT(a, b)), cirq.Moment(cirq.H(b)) + ), + expected_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), cirq.Moment(cirq.CNOT(a, b)), cirq.Moment(cirq.H(b)) + ), + dd_model=DynamicalDecouplingModel.from_schema("XX_PAIR"), + ) + + # Insert one XX_PAIR dynamical decoupling sequence in idle moments. + assert_dd( + input_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.measure_each(a, b, c)), + ), + expected_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), + cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), + cirq.Moment(cirq.measure_each(a, b, c)), + ), + dd_model=DynamicalDecouplingModel.from_schema("XX_PAIR"), + ) + + # Insert one XX_PAIR dynamical decoupling sequence in idle moments. + assert_dd( + input_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.measure_each(a, b, c)), + ), + expected_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)), + cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)), + cirq.Moment(cirq.measure_each(a, b, c)), + ), + dd_model=DynamicalDecouplingModel.from_schema("YY_PAIR"), + ) + + +def test_insert_by_customized_dd_sequence(): + a = cirq.NamedQubit("a") + b = cirq.NamedQubit("b") + c = cirq.NamedQubit("c") + + assert_dd( + input_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.measure_each(a, b, c)), + ), + expected_circuit=cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), + cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), + cirq.Moment(cirq.measure_each(a, b, c)), + ), + dd_model=DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate(), cirq.XPowGate()]), + ) + + +def test_dd_model_constructor(): + # Succeed + DynamicalDecouplingModel.from_schema("XX_PAIR") + DynamicalDecouplingModel.from_schema("YY_PAIR") + DynamicalDecouplingModel.from_base_dd_sequence( + [cirq.XPowGate(), cirq.XPowGate(), cirq.YPowGate(), cirq.YPowGate()] + ) + # Fail + with pytest.raises(ValueError, match="Specify either schema or base_dd_sequence"): + DynamicalDecouplingModel() + with pytest.raises(ValueError, match="Invalid schema name."): + DynamicalDecouplingModel.from_schema("unimplemented_schema") + with pytest.raises(ValueError, match="Invalid dynamical decoupling sequence. Expect more than one gates."): + DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate()]) + with pytest.raises(ValueError, match="Invalid dynamical decoupling sequence"): + DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate(), cirq.YPowGate()]) From 3a6a9b4decdf6e9675e66175c6cb5e61975f76ae Mon Sep 17 00:00:00 2001 From: Renyi Date: Tue, 23 Apr 2024 03:47:01 -0700 Subject: [PATCH 2/4] Address comments. --- .../DynamicalDecouplingModel.json | 4 +- .../DynamicalDecouplingModel.repr | 2 +- .../cirq/transformers/dynamical_decoupling.py | 51 +++++++---- .../transformers/dynamical_decoupling_test.py | 86 +++++++++---------- 4 files changed, 79 insertions(+), 64 deletions(-) diff --git a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json index 183726ac703..e605e9d0061 100644 --- a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json +++ b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json @@ -7,12 +7,12 @@ "cirq_type": "DynamicalDecouplingModel", "base_dd_sequence": [ { - "cirq_type": "XPowGate", + "cirq_type": "_PauliX", "exponent": 1.0, "global_shift": 0.0 }, { - "cirq_type": "XPowGate", + "cirq_type": "_PauliX", "exponent": 1.0, "global_shift": 0.0 } diff --git a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr index 93e994447b5..a4502182882 100644 --- a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr +++ b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr @@ -1,2 +1,2 @@ [cirq.DynamicalDecouplingModel.from_schema("XX_PAIR"), -cirq.DynamicalDecouplingModel(base_dd_sequence=[cirq.XPowGate(), cirq.XPowGate()])] \ No newline at end of file +cirq.DynamicalDecouplingModel.from_base_dd_sequence(base_dd_sequence=[cirq.X, cirq.X])] \ No newline at end of file diff --git a/cirq-core/cirq/transformers/dynamical_decoupling.py b/cirq-core/cirq/transformers/dynamical_decoupling.py index 6af91bf0107..403500a91cb 100644 --- a/cirq-core/cirq/transformers/dynamical_decoupling.py +++ b/cirq-core/cirq/transformers/dynamical_decoupling.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Transformer pass that adds dynamical decoupling moments to a circuit.""" +"""Transformer pass that adds dynamical decoupling operations to a circuit.""" import enum from functools import reduce from typing import Any, Dict, Optional, Tuple +from cirq.transformers import transformer_api import cirq from cirq import value import numpy as np @@ -28,7 +29,9 @@ class _DynamicalDecouplingSchema(enum.Enum): """Supported schemes of dynamical decoupling.""" XX_PAIR = 'XX_PAIR' + X_XINV = 'X_XINV' YY_PAIR = 'YY_PAIR' + Y_YINV = 'Y_YINV' def _repeat_sequence(base_sequence: list['cirq.Gate'], num_idle_moments: int): @@ -41,9 +44,13 @@ def _generate_dd_sequence_from_schema( ) -> list['cirq.Gate']: match schema: case _DynamicalDecouplingSchema.XX_PAIR: - return _repeat_sequence([cirq.XPowGate(), cirq.XPowGate()], num_idle_moments) + return _repeat_sequence([cirq.X, cirq.X], num_idle_moments) + case _DynamicalDecouplingSchema.X_XINV: + return _repeat_sequence([cirq.X, cirq.X**-1], num_idle_moments) case _DynamicalDecouplingSchema.YY_PAIR: - return _repeat_sequence([cirq.YPowGate(), cirq.YPowGate()], num_idle_moments) + return _repeat_sequence([cirq.Y, cirq.Y], num_idle_moments) + case _DynamicalDecouplingSchema.Y_YINV: + return _repeat_sequence([cirq.Y, cirq.Y**-1], num_idle_moments) def _validate_dd_sequence(dd_sequence: list['cirq.Gate']) -> None: @@ -51,15 +58,17 @@ def _validate_dd_sequence(dd_sequence: list['cirq.Gate']) -> None: raise ValueError('Invalid dynamical decoupling sequence. Expect more than one gates.') matrices = [cirq.unitary(gate) for gate in dd_sequence] product = reduce(np.matmul, matrices) - if not np.array_equal(product, np.eye(2)): + + if not cirq.equal_up_to_global_phase(product, np.eye(2)): raise ValueError( - "Invalid dynamical decoupling sequence, sequence product doesn't equal" ' identity.' + "Invalid dynamical decoupling sequence. Expect sequence production equals identity" + f" up to a global phase, got {product}.".replace('\n', ' ') ) @value.value_equality class DynamicalDecouplingModel: - """Dynamical decoupling model that generates dynamical decoupling gate sequences.""" + """Dynamical decoupling model that generates dynamical decoupling operation sequences.""" def __init__( self, @@ -81,10 +90,10 @@ def generate_dd_sequence(self, num_idle_moments: int = 2) -> list['cirq.Gate']: if num_idle_moments <= 0: return [] if self.schema: - return _generate_dd_sequence_from_schema(self.schema, num_idle_moments) - if self.base_dd_sequence: - return _repeat_sequence(self.base_dd_sequence, num_idle_moments) - return [] + dd_sequence = _generate_dd_sequence_from_schema(self.schema, num_idle_moments) + elif self.base_dd_sequence: + dd_sequence = _repeat_sequence(self.base_dd_sequence, num_idle_moments) + return dd_sequence @classmethod def from_schema(cls, schema: str): @@ -117,21 +126,27 @@ def _value_equality_values_(self) -> Any: return self.schema, self.base_dd_sequence +@transformer_api.transformer def add_dynamical_decoupling( - circuit: 'cirq.AbstractCircuit', dd_model: DynamicalDecouplingModel + circuit: 'cirq.AbstractCircuit', + *, + context: Optional['cirq.TransformerContext'] = None, + dd_model: DynamicalDecouplingModel = DynamicalDecouplingModel.from_schema("X_XINV"), ) -> 'cirq.Circuit': - """Add dynamical decoupling gates in a given circuit. + """Add dynamical decoupling gate operations to a given circuit. Args: circuit: Input circuit to transform. - dd_model: Dynamical decoupling model that defines the schema to generate - dynamical decoupling sequences. + context: `cirq.TransformerContext` storing common configurable options for transformers. + dd_model: Dynamical decoupling model that defines the schema to generate dynamical + decoupling sequences. Return: - A circuit with dynamical decoupling operations. + A copy of the input circuit with dynamical decoupling operations. """ last_busy_moment_by_qubits: Dict['cirq.Qid', int] = {q: 0 for q in circuit.all_qubits()} insert_into: list[Tuple[int, 'cirq.OP_TREE']] = [] + for moment_id, moment in enumerate(circuit): for q in moment.qubits: insert_gates = dd_model.generate_dd_sequence( @@ -141,6 +156,6 @@ def add_dynamical_decoupling( insert_into.append((last_busy_moment_by_qubits[q] + idx + 1, gate.on(q))) last_busy_moment_by_qubits[q] = moment_id - circuit.batch_insert_into(insert_into) - - return circuit + updated_circuit = circuit.unfreeze(copy=True) + updated_circuit.batch_insert_into(insert_into) + return updated_circuit diff --git a/cirq-core/cirq/transformers/dynamical_decoupling_test.py b/cirq-core/cirq/transformers/dynamical_decoupling_test.py index 310822650df..59d37bbd3fe 100644 --- a/cirq-core/cirq/transformers/dynamical_decoupling_test.py +++ b/cirq-core/cirq/transformers/dynamical_decoupling_test.py @@ -24,10 +24,9 @@ def assert_dd( cirq.testing.assert_same_circuits(updated_circuit, expected_circuit) -def test_insert_provided_schema(): +def test_no_insert_due_to_no_consecutive_moments(): a = cirq.NamedQubit("a") b = cirq.NamedQubit("b") - c = cirq.NamedQubit("c") # No insertion as there is no room for a dd sequence. assert_dd( @@ -40,42 +39,39 @@ def test_insert_provided_schema(): dd_model=DynamicalDecouplingModel.from_schema("XX_PAIR"), ) - # Insert one XX_PAIR dynamical decoupling sequence in idle moments. - assert_dd( - input_circuit=cirq.Circuit( - cirq.Moment(cirq.H(a)), - cirq.Moment(cirq.CNOT(a, b)), - cirq.Moment(cirq.CNOT(b, c)), - cirq.Moment(cirq.CNOT(b, c)), - cirq.Moment(cirq.measure_each(a, b, c)), - ), - expected_circuit=cirq.Circuit( - cirq.Moment(cirq.H(a)), - cirq.Moment(cirq.CNOT(a, b)), - cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), - cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), - cirq.Moment(cirq.measure_each(a, b, c)), - ), - dd_model=DynamicalDecouplingModel.from_schema("XX_PAIR"), + +@pytest.mark.parametrize( + 'schema,inserted_gates', + [ + ("XX_PAIR", [cirq.X, cirq.X]), + ("X_XINV", [cirq.X, cirq.X**-1]), + ("YY_PAIR", [cirq.Y, cirq.Y]), + ("Y_YINV", [cirq.Y, cirq.Y**-1]), + ], +) +def test_insert_provided_schema(schema: str, inserted_gates: list['cirq.Gate']): + a = cirq.NamedQubit("a") + b = cirq.NamedQubit("b") + c = cirq.NamedQubit("c") + + input_circuit = cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.measure_each(a, b, c)), + ) + expected_circuit = cirq.Circuit( + cirq.Moment(cirq.H(a)), + cirq.Moment(cirq.CNOT(a, b)), + cirq.Moment(cirq.CNOT(b, c), inserted_gates[0](a)), + cirq.Moment(cirq.CNOT(b, c), inserted_gates[1](a)), + cirq.Moment(cirq.measure_each(a, b, c)), ) - # Insert one XX_PAIR dynamical decoupling sequence in idle moments. + # Insert one dynamical decoupling sequence in idle moments. assert_dd( - input_circuit=cirq.Circuit( - cirq.Moment(cirq.H(a)), - cirq.Moment(cirq.CNOT(a, b)), - cirq.Moment(cirq.CNOT(b, c)), - cirq.Moment(cirq.CNOT(b, c)), - cirq.Moment(cirq.measure_each(a, b, c)), - ), - expected_circuit=cirq.Circuit( - cirq.Moment(cirq.H(a)), - cirq.Moment(cirq.CNOT(a, b)), - cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)), - cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)), - cirq.Moment(cirq.measure_each(a, b, c)), - ), - dd_model=DynamicalDecouplingModel.from_schema("YY_PAIR"), + input_circuit, expected_circuit, dd_model=DynamicalDecouplingModel.from_schema(schema) ) @@ -99,7 +95,7 @@ def test_insert_by_customized_dd_sequence(): cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), cirq.Moment(cirq.measure_each(a, b, c)), ), - dd_model=DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate(), cirq.XPowGate()]), + dd_model=DynamicalDecouplingModel.from_base_dd_sequence([cirq.X, cirq.X]), ) @@ -107,15 +103,19 @@ def test_dd_model_constructor(): # Succeed DynamicalDecouplingModel.from_schema("XX_PAIR") DynamicalDecouplingModel.from_schema("YY_PAIR") - DynamicalDecouplingModel.from_base_dd_sequence( - [cirq.XPowGate(), cirq.XPowGate(), cirq.YPowGate(), cirq.YPowGate()] - ) + DynamicalDecouplingModel.from_base_dd_sequence([cirq.X, cirq.X, cirq.Y, cirq.Y]) # Fail with pytest.raises(ValueError, match="Specify either schema or base_dd_sequence"): DynamicalDecouplingModel() with pytest.raises(ValueError, match="Invalid schema name."): DynamicalDecouplingModel.from_schema("unimplemented_schema") - with pytest.raises(ValueError, match="Invalid dynamical decoupling sequence. Expect more than one gates."): - DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate()]) - with pytest.raises(ValueError, match="Invalid dynamical decoupling sequence"): - DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate(), cirq.YPowGate()]) + with pytest.raises( + ValueError, match="Invalid dynamical decoupling sequence. Expect more than one gates." + ): + DynamicalDecouplingModel.from_base_dd_sequence([cirq.X]) + with pytest.raises( + ValueError, + match="Invalid dynamical decoupling sequence. Expect sequence production equals identity" + " up to a global phase, got", + ): + DynamicalDecouplingModel.from_base_dd_sequence([cirq.X, cirq.H]) From 3ae49b122257a20f68c5cc25f8406bc2601fd977 Mon Sep 17 00:00:00 2001 From: Renyi Chen Date: Wed, 1 May 2024 16:09:18 -0700 Subject: [PATCH 3/4] Apply comments of removing dynamical decoupling model class. --- cirq-core/cirq/__init__.py | 1 - cirq-core/cirq/json_resolver_cache.py | 1 - .../DynamicalDecouplingModel.json | 21 --- .../DynamicalDecouplingModel.repr | 2 - cirq-core/cirq/transformers/__init__.py | 5 +- .../cirq/transformers/dynamical_decoupling.py | 158 +++++++----------- .../transformers/dynamical_decoupling_test.py | 84 +++++----- 7 files changed, 104 insertions(+), 168 deletions(-) delete mode 100644 cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json delete mode 100644 cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index e5df04d7b36..a663923f3f5 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -337,7 +337,6 @@ from cirq.transformers import ( AbstractInitialMapper, add_dynamical_decoupling, - DynamicalDecouplingModel, align_left, align_right, CompilationTargetGateset, diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 2ab84df6dcf..4880046618a 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -102,7 +102,6 @@ def _symmetricalqidpair(qids): import sympy return { - 'DynamicalDecouplingModel': cirq.DynamicalDecouplingModel, 'AmplitudeDampingChannel': cirq.AmplitudeDampingChannel, 'AnyIntegerPowerGateFamily': cirq.AnyIntegerPowerGateFamily, 'AnyUnitaryGateFamily': cirq.AnyUnitaryGateFamily, diff --git a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json deleted file mode 100644 index e605e9d0061..00000000000 --- a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "cirq_type": "DynamicalDecouplingModel", - "schema": "XX_PAIR" - }, - { - "cirq_type": "DynamicalDecouplingModel", - "base_dd_sequence": [ - { - "cirq_type": "_PauliX", - "exponent": 1.0, - "global_shift": 0.0 - }, - { - "cirq_type": "_PauliX", - "exponent": 1.0, - "global_shift": 0.0 - } - ] - } -] \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr b/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr deleted file mode 100644 index a4502182882..00000000000 --- a/cirq-core/cirq/protocols/json_test_data/DynamicalDecouplingModel.repr +++ /dev/null @@ -1,2 +0,0 @@ -[cirq.DynamicalDecouplingModel.from_schema("XX_PAIR"), -cirq.DynamicalDecouplingModel.from_base_dd_sequence(base_dd_sequence=[cirq.X, cirq.X])] \ No newline at end of file diff --git a/cirq-core/cirq/transformers/__init__.py b/cirq-core/cirq/transformers/__init__.py index 7d03bb4e3ca..5a98df78a1a 100644 --- a/cirq-core/cirq/transformers/__init__.py +++ b/cirq-core/cirq/transformers/__init__.py @@ -78,10 +78,7 @@ from cirq.transformers.drop_negligible_operations import drop_negligible_operations -from cirq.transformers.dynamical_decoupling import ( - add_dynamical_decoupling, - DynamicalDecouplingModel, -) +from cirq.transformers.dynamical_decoupling import add_dynamical_decoupling from cirq.transformers.eject_z import eject_z diff --git a/cirq-core/cirq/transformers/dynamical_decoupling.py b/cirq-core/cirq/transformers/dynamical_decoupling.py index 403500a91cb..a84cc7786b2 100644 --- a/cirq-core/cirq/transformers/dynamical_decoupling.py +++ b/cirq-core/cirq/transformers/dynamical_decoupling.py @@ -14,116 +14,61 @@ """Transformer pass that adds dynamical decoupling operations to a circuit.""" -import enum from functools import reduce -from typing import Any, Dict, Optional, Tuple +from typing import Dict, Optional, Sequence, Tuple, Union from cirq.transformers import transformer_api import cirq -from cirq import value import numpy as np -@enum.unique -class _DynamicalDecouplingSchema(enum.Enum): - """Supported schemes of dynamical decoupling.""" - - XX_PAIR = 'XX_PAIR' - X_XINV = 'X_XINV' - YY_PAIR = 'YY_PAIR' - Y_YINV = 'Y_YINV' - - -def _repeat_sequence(base_sequence: list['cirq.Gate'], num_idle_moments: int): +def _repeat_sequence( + base_sequence: Sequence['cirq.Gate'], num_idle_moments: int +) -> Sequence['cirq.Gate']: + """Returns the longest possible dynamical decoupling sequence.""" repeat_times = num_idle_moments // len(base_sequence) - return base_sequence * repeat_times + return list(base_sequence) * repeat_times -def _generate_dd_sequence_from_schema( - schema: _DynamicalDecouplingSchema, num_idle_moments: int = 2 -) -> list['cirq.Gate']: +def _get_dd_sequence_from_schema_name(schema: str) -> Sequence['cirq.Gate']: + """Gets dynamical decoupling sequence from a schema name.""" + dd_sequence: Sequence['cirq.Gate'] match schema: - case _DynamicalDecouplingSchema.XX_PAIR: - return _repeat_sequence([cirq.X, cirq.X], num_idle_moments) - case _DynamicalDecouplingSchema.X_XINV: - return _repeat_sequence([cirq.X, cirq.X**-1], num_idle_moments) - case _DynamicalDecouplingSchema.YY_PAIR: - return _repeat_sequence([cirq.Y, cirq.Y], num_idle_moments) - case _DynamicalDecouplingSchema.Y_YINV: - return _repeat_sequence([cirq.Y, cirq.Y**-1], num_idle_moments) + case 'XX_PAIR': + dd_sequence = (cirq.X, cirq.X) + case 'X_XINV': + dd_sequence = (cirq.X, cirq.X**-1) + case 'YY_PAIR': + dd_sequence = (cirq.Y, cirq.Y) + case 'Y_YINV': + dd_sequence = (cirq.Y, cirq.Y**-1) + case _: + raise ValueError('Invalid schema name.') + return dd_sequence + + +def _validate_dd_sequence(dd_sequence: Sequence['cirq.Gate']) -> Tuple[bool, Optional[str]]: + """Validates a given dynamical decoupling sequence. + Args: + dd_sequence: Input dynamical sequence to be validated. -def _validate_dd_sequence(dd_sequence: list['cirq.Gate']) -> None: + Returns: + A tuple containing: + - is_valid (bool): True if the dd sequence is valid, False otherwise. + - error_message (str): An error message if the dd sequence is invalid, else None. + """ if len(dd_sequence) < 2: - raise ValueError('Invalid dynamical decoupling sequence. Expect more than one gates.') + return False, 'Invalid dynamical decoupling sequence. Expect more than one gates.' matrices = [cirq.unitary(gate) for gate in dd_sequence] product = reduce(np.matmul, matrices) if not cirq.equal_up_to_global_phase(product, np.eye(2)): - raise ValueError( - "Invalid dynamical decoupling sequence. Expect sequence production equals identity" - f" up to a global phase, got {product}.".replace('\n', ' ') + return False, ( + 'Invalid dynamical decoupling sequence. Expect sequence production equals' + f' identity up to a global phase, got {product}.'.replace('\n', ' ') ) - - -@value.value_equality -class DynamicalDecouplingModel: - """Dynamical decoupling model that generates dynamical decoupling operation sequences.""" - - def __init__( - self, - schema: Optional[_DynamicalDecouplingSchema] = None, - base_dd_sequence: Optional[list['cirq.Gate']] = None, - ): - if not schema and not base_dd_sequence: - raise ValueError( - 'Specify either schema or base_dd_sequence to construct a valid' - ' DynamicalDecouplingModel.' - ) - self.schema = schema - self.base_dd_sequence = base_dd_sequence - if base_dd_sequence: - _validate_dd_sequence(base_dd_sequence) - - def generate_dd_sequence(self, num_idle_moments: int = 2) -> list['cirq.Gate']: - """Returns the longest possible dynamical decoupling sequence.""" - if num_idle_moments <= 0: - return [] - if self.schema: - dd_sequence = _generate_dd_sequence_from_schema(self.schema, num_idle_moments) - elif self.base_dd_sequence: - dd_sequence = _repeat_sequence(self.base_dd_sequence, num_idle_moments) - return dd_sequence - - @classmethod - def from_schema(cls, schema: str): - """Create dynamical decoupling model according to a given schema.""" - if not schema in _DynamicalDecouplingSchema.__members__: - raise ValueError("Invalid schema name.") - return cls(schema=_DynamicalDecouplingSchema[schema]) - - @classmethod - def from_base_dd_sequence(cls, base_dd_sequence: list['cirq.Gate']): - """Create dynamical decoupling model according to a base sequence.""" - return cls(base_dd_sequence=base_dd_sequence) - - def _json_dict_(self) -> Dict[str, Any]: - d: Dict[str, Any] = {} - if self.schema: - d['schema'] = self.schema.name - if self.base_dd_sequence: - d['base_dd_sequence'] = self.base_dd_sequence - return d - - @classmethod - def _from_json_dict_(cls, schema=None, base_dd_sequence=None, **kwargs): - if schema: - return cls(schema=_DynamicalDecouplingSchema[schema]) - if base_dd_sequence: - return cls(base_dd_sequence=base_dd_sequence) - - def _value_equality_values_(self) -> Any: - return self.schema, self.base_dd_sequence + return True, None @transformer_api.transformer @@ -131,26 +76,43 @@ def add_dynamical_decoupling( circuit: 'cirq.AbstractCircuit', *, context: Optional['cirq.TransformerContext'] = None, - dd_model: DynamicalDecouplingModel = DynamicalDecouplingModel.from_schema("X_XINV"), + schema: Union[str, Sequence['cirq.Gate']] = 'X_XINV', ) -> 'cirq.Circuit': - """Add dynamical decoupling gate operations to a given circuit. + """Adds dynamical decoupling gate operations to idle moments of a given circuit. + This transformer preserves the moment structure of the circuit. Args: circuit: Input circuit to transform. context: `cirq.TransformerContext` storing common configurable options for transformers. - dd_model: Dynamical decoupling model that defines the schema to generate dynamical - decoupling sequences. + schema: Dynamical decoupling schema name or a dynamical decoupling sequence. + If a schema is specified, provided dynamical decouping sequence will be used. + Otherwise, customized dynamical decoupling sequence will be applied. - Return: + Returns: A copy of the input circuit with dynamical decoupling operations. + + Raises: + ValueError: If schema is not valid. """ last_busy_moment_by_qubits: Dict['cirq.Qid', int] = {q: 0 for q in circuit.all_qubits()} insert_into: list[Tuple[int, 'cirq.OP_TREE']] = [] + if isinstance(schema, str): + try: + base_dd_sequence = _get_dd_sequence_from_schema_name(schema) + except ValueError: + raise + else: + is_valid, error_message = _validate_dd_sequence(schema) + if is_valid: + base_dd_sequence = schema + else: + raise ValueError(error_message) + for moment_id, moment in enumerate(circuit): for q in moment.qubits: - insert_gates = dd_model.generate_dd_sequence( - num_idle_moments=moment_id - last_busy_moment_by_qubits[q] - 1 + insert_gates = _repeat_sequence( + base_dd_sequence, num_idle_moments=moment_id - last_busy_moment_by_qubits[q] - 1 ) for idx, gate in enumerate(insert_gates): insert_into.append((last_busy_moment_by_qubits[q] + idx + 1, gate.on(q))) diff --git a/cirq-core/cirq/transformers/dynamical_decoupling_test.py b/cirq-core/cirq/transformers/dynamical_decoupling_test.py index 59d37bbd3fe..b7a17e28425 100644 --- a/cirq-core/cirq/transformers/dynamical_decoupling_test.py +++ b/cirq-core/cirq/transformers/dynamical_decoupling_test.py @@ -12,21 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Sequence, Union import cirq -from cirq import DynamicalDecouplingModel, add_dynamical_decoupling +from cirq import add_dynamical_decoupling import pytest def assert_dd( - input_circuit: cirq.Circuit, expected_circuit: cirq.Circuit, dd_model: DynamicalDecouplingModel + input_circuit: cirq.Circuit, + expected_circuit: cirq.Circuit, + schema: Union[str, Sequence['cirq.Gate']], ): - updated_circuit = add_dynamical_decoupling(input_circuit, dd_model=dd_model) + updated_circuit = add_dynamical_decoupling(input_circuit, schema=schema) cirq.testing.assert_same_circuits(updated_circuit, expected_circuit) def test_no_insert_due_to_no_consecutive_moments(): - a = cirq.NamedQubit("a") - b = cirq.NamedQubit("b") + a = cirq.NamedQubit('a') + b = cirq.NamedQubit('b') # No insertion as there is no room for a dd sequence. assert_dd( @@ -36,23 +39,23 @@ def test_no_insert_due_to_no_consecutive_moments(): expected_circuit=cirq.Circuit( cirq.Moment(cirq.H(a)), cirq.Moment(cirq.CNOT(a, b)), cirq.Moment(cirq.H(b)) ), - dd_model=DynamicalDecouplingModel.from_schema("XX_PAIR"), + schema='XX_PAIR', ) @pytest.mark.parametrize( 'schema,inserted_gates', [ - ("XX_PAIR", [cirq.X, cirq.X]), - ("X_XINV", [cirq.X, cirq.X**-1]), - ("YY_PAIR", [cirq.Y, cirq.Y]), - ("Y_YINV", [cirq.Y, cirq.Y**-1]), + ('XX_PAIR', (cirq.X, cirq.X)), + ('X_XINV', (cirq.X, cirq.X**-1)), + ('YY_PAIR', (cirq.Y, cirq.Y)), + ('Y_YINV', (cirq.Y, cirq.Y**-1)), ], ) -def test_insert_provided_schema(schema: str, inserted_gates: list['cirq.Gate']): - a = cirq.NamedQubit("a") - b = cirq.NamedQubit("b") - c = cirq.NamedQubit("c") +def test_insert_provided_schema(schema: str, inserted_gates: Sequence['cirq.Gate']): + a = cirq.NamedQubit('a') + b = cirq.NamedQubit('b') + c = cirq.NamedQubit('c') input_circuit = cirq.Circuit( cirq.Moment(cirq.H(a)), @@ -70,15 +73,13 @@ def test_insert_provided_schema(schema: str, inserted_gates: list['cirq.Gate']): ) # Insert one dynamical decoupling sequence in idle moments. - assert_dd( - input_circuit, expected_circuit, dd_model=DynamicalDecouplingModel.from_schema(schema) - ) + assert_dd(input_circuit, expected_circuit, schema=schema) def test_insert_by_customized_dd_sequence(): - a = cirq.NamedQubit("a") - b = cirq.NamedQubit("b") - c = cirq.NamedQubit("c") + a = cirq.NamedQubit('a') + b = cirq.NamedQubit('b') + c = cirq.NamedQubit('c') assert_dd( input_circuit=cirq.Circuit( @@ -86,6 +87,8 @@ def test_insert_by_customized_dd_sequence(): cirq.Moment(cirq.CNOT(a, b)), cirq.Moment(cirq.CNOT(b, c)), cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.CNOT(b, c)), + cirq.Moment(cirq.CNOT(b, c)), cirq.Moment(cirq.measure_each(a, b, c)), ), expected_circuit=cirq.Circuit( @@ -93,29 +96,28 @@ def test_insert_by_customized_dd_sequence(): cirq.Moment(cirq.CNOT(a, b)), cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), cirq.Moment(cirq.CNOT(b, c), cirq.X(a)), + cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)), + cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)), cirq.Moment(cirq.measure_each(a, b, c)), ), - dd_model=DynamicalDecouplingModel.from_base_dd_sequence([cirq.X, cirq.X]), + schema=[cirq.X, cirq.X, cirq.Y, cirq.Y], ) -def test_dd_model_constructor(): - # Succeed - DynamicalDecouplingModel.from_schema("XX_PAIR") - DynamicalDecouplingModel.from_schema("YY_PAIR") - DynamicalDecouplingModel.from_base_dd_sequence([cirq.X, cirq.X, cirq.Y, cirq.Y]) - # Fail - with pytest.raises(ValueError, match="Specify either schema or base_dd_sequence"): - DynamicalDecouplingModel() - with pytest.raises(ValueError, match="Invalid schema name."): - DynamicalDecouplingModel.from_schema("unimplemented_schema") - with pytest.raises( - ValueError, match="Invalid dynamical decoupling sequence. Expect more than one gates." - ): - DynamicalDecouplingModel.from_base_dd_sequence([cirq.X]) - with pytest.raises( - ValueError, - match="Invalid dynamical decoupling sequence. Expect sequence production equals identity" - " up to a global phase, got", - ): - DynamicalDecouplingModel.from_base_dd_sequence([cirq.X, cirq.H]) +@pytest.mark.parametrize( + 'schema,error_msg_regex', + [ + ('INVALID_SCHEMA', 'Invalid schema name.'), + ([cirq.X], 'Invalid dynamical decoupling sequence. Expect more than one gates.'), + ( + [cirq.X, cirq.H], + 'Invalid dynamical decoupling sequence. Expect sequence production equals identity' + ' up to a global phase, got', + ), + ], +) +def test_invalid_dd_schema(schema: Union[str, Sequence['cirq.Gate']], error_msg_regex): + a = cirq.NamedQubit('a') + input_circuit = cirq.Circuit(cirq.H(a)) + with pytest.raises(ValueError, match=error_msg_regex): + add_dynamical_decoupling(input_circuit, schema=schema) From c2e8026a1580003377b69cc56f9fa0672d3c5a62 Mon Sep 17 00:00:00 2001 From: Renyi Chen Date: Thu, 9 May 2024 14:35:54 -0700 Subject: [PATCH 4/4] Apply comments. --- .../cirq/transformers/dynamical_decoupling.py | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/cirq-core/cirq/transformers/dynamical_decoupling.py b/cirq-core/cirq/transformers/dynamical_decoupling.py index a84cc7786b2..e3caa7633c3 100644 --- a/cirq-core/cirq/transformers/dynamical_decoupling.py +++ b/cirq-core/cirq/transformers/dynamical_decoupling.py @@ -47,7 +47,7 @@ def _get_dd_sequence_from_schema_name(schema: str) -> Sequence['cirq.Gate']: return dd_sequence -def _validate_dd_sequence(dd_sequence: Sequence['cirq.Gate']) -> Tuple[bool, Optional[str]]: +def _validate_dd_sequence(dd_sequence: Sequence['cirq.Gate']) -> None: """Validates a given dynamical decoupling sequence. Args: @@ -57,18 +57,30 @@ def _validate_dd_sequence(dd_sequence: Sequence['cirq.Gate']) -> Tuple[bool, Opt A tuple containing: - is_valid (bool): True if the dd sequence is valid, False otherwise. - error_message (str): An error message if the dd sequence is invalid, else None. + + Raises: + ValueError: If dd_sequence is not valid. """ if len(dd_sequence) < 2: - return False, 'Invalid dynamical decoupling sequence. Expect more than one gates.' + raise ValueError('Invalid dynamical decoupling sequence. Expect more than one gates.') matrices = [cirq.unitary(gate) for gate in dd_sequence] product = reduce(np.matmul, matrices) if not cirq.equal_up_to_global_phase(product, np.eye(2)): - return False, ( + raise ValueError( 'Invalid dynamical decoupling sequence. Expect sequence production equals' f' identity up to a global phase, got {product}.'.replace('\n', ' ') ) - return True, None + + +def _parse_dd_sequence(schema: Union[str, Sequence['cirq.Gate']]) -> Sequence['cirq.Gate']: + """Parses and returns dynamical decoupling sequence from schema.""" + if isinstance(schema, str): + dd_sequence = _get_dd_sequence_from_schema_name(schema) + else: + _validate_dd_sequence(schema) + dd_sequence = schema + return dd_sequence @transformer_api.transformer @@ -90,24 +102,11 @@ def add_dynamical_decoupling( Returns: A copy of the input circuit with dynamical decoupling operations. - - Raises: - ValueError: If schema is not valid. """ last_busy_moment_by_qubits: Dict['cirq.Qid', int] = {q: 0 for q in circuit.all_qubits()} insert_into: list[Tuple[int, 'cirq.OP_TREE']] = [] - if isinstance(schema, str): - try: - base_dd_sequence = _get_dd_sequence_from_schema_name(schema) - except ValueError: - raise - else: - is_valid, error_message = _validate_dd_sequence(schema) - if is_valid: - base_dd_sequence = schema - else: - raise ValueError(error_message) + base_dd_sequence = _parse_dd_sequence(schema) for moment_id, moment in enumerate(circuit): for q in moment.qubits: