Skip to content

Commit 74e1df0

Browse files
committed
Impl dynamical decoupling transfomer.
1 parent 632f8b3 commit 74e1df0

File tree

7 files changed

+298
-0
lines changed

7 files changed

+298
-0
lines changed

cirq-core/cirq/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@
336336

337337
from cirq.transformers import (
338338
AbstractInitialMapper,
339+
add_dynamical_decoupling,
340+
DynamicalDecouplingModel,
339341
align_left,
340342
align_right,
341343
CompilationTargetGateset,

cirq-core/cirq/json_resolver_cache.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def _symmetricalqidpair(qids):
102102
import sympy
103103

104104
return {
105+
'DynamicalDecouplingModel': cirq.DynamicalDecouplingModel,
105106
'AmplitudeDampingChannel': cirq.AmplitudeDampingChannel,
106107
'AnyIntegerPowerGateFamily': cirq.AnyIntegerPowerGateFamily,
107108
'AnyUnitaryGateFamily': cirq.AnyUnitaryGateFamily,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[
2+
{
3+
"cirq_type": "DynamicalDecouplingModel",
4+
"schema": "XX_PAIR"
5+
},
6+
{
7+
"cirq_type": "DynamicalDecouplingModel",
8+
"base_dd_sequence": [
9+
{
10+
"cirq_type": "XPowGate",
11+
"exponent": 1.0,
12+
"global_shift": 0.0
13+
},
14+
{
15+
"cirq_type": "XPowGate",
16+
"exponent": 1.0,
17+
"global_shift": 0.0
18+
}
19+
]
20+
}
21+
]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[cirq.DynamicalDecouplingModel.from_schema("XX_PAIR"),
2+
cirq.DynamicalDecouplingModel(base_dd_sequence=[cirq.XPowGate(), cirq.XPowGate()])]

cirq-core/cirq/transformers/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@
7878

7979
from cirq.transformers.drop_negligible_operations import drop_negligible_operations
8080

81+
from cirq.transformers.dynamical_decoupling import (
82+
add_dynamical_decoupling,
83+
DynamicalDecouplingModel,
84+
)
85+
8186
from cirq.transformers.eject_z import eject_z
8287

8388
from cirq.transformers.measurement_transformers import (
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright 2024 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Transformer pass that adds dynamical decoupling moments to a circuit."""
16+
17+
import enum
18+
from functools import reduce
19+
from typing import Any, Dict, Optional, Tuple
20+
21+
import cirq
22+
from cirq import value
23+
import numpy as np
24+
25+
26+
@enum.unique
27+
class _DynamicalDecouplingSchema(enum.Enum):
28+
"""Supported schemes of dynamical decoupling."""
29+
30+
XX_PAIR = 'XX_PAIR'
31+
YY_PAIR = 'YY_PAIR'
32+
33+
34+
def _repeat_sequence(base_sequence: list['cirq.Gate'], num_idle_moments: int):
35+
repeat_times = num_idle_moments // len(base_sequence)
36+
return base_sequence * repeat_times
37+
38+
39+
def _generate_dd_sequence_from_schema(
40+
schema: _DynamicalDecouplingSchema, num_idle_moments: int = 2
41+
) -> list['cirq.Gate']:
42+
match schema:
43+
case _DynamicalDecouplingSchema.XX_PAIR:
44+
return _repeat_sequence([cirq.XPowGate(), cirq.XPowGate()], num_idle_moments)
45+
case _DynamicalDecouplingSchema.YY_PAIR:
46+
return _repeat_sequence([cirq.YPowGate(), cirq.YPowGate()], num_idle_moments)
47+
48+
49+
def _validate_dd_sequence(dd_sequence: list['cirq.Gate']) -> None:
50+
if len(dd_sequence) < 2:
51+
raise ValueError('Invalid dynamical decoupling sequence. Expect more than one gates.')
52+
matrices = [cirq.unitary(gate) for gate in dd_sequence]
53+
product = reduce(np.matmul, matrices)
54+
if not np.array_equal(product, np.eye(2)):
55+
raise ValueError(
56+
"Invalid dynamical decoupling sequence, sequence product doesn't equal" ' identity.'
57+
)
58+
59+
60+
@value.value_equality
61+
class DynamicalDecouplingModel:
62+
"""Dynamical decoupling model that generates dynamical decoupling gate sequences."""
63+
64+
def __init__(
65+
self,
66+
schema: Optional[_DynamicalDecouplingSchema] = None,
67+
base_dd_sequence: Optional[list['cirq.Gate']] = None,
68+
):
69+
if not schema and not base_dd_sequence:
70+
raise ValueError(
71+
'Specify either schema or base_dd_sequence to construct a valid'
72+
' DynamicalDecouplingModel.'
73+
)
74+
self.schema = schema
75+
self.base_dd_sequence = base_dd_sequence
76+
if base_dd_sequence:
77+
_validate_dd_sequence(base_dd_sequence)
78+
79+
def generate_dd_sequence(self, num_idle_moments: int = 2) -> list['cirq.Gate']:
80+
"""Returns the longest possible dynamical decoupling sequence."""
81+
if num_idle_moments <= 0:
82+
return []
83+
if self.schema:
84+
return _generate_dd_sequence_from_schema(self.schema, num_idle_moments)
85+
if self.base_dd_sequence:
86+
return _repeat_sequence(self.base_dd_sequence, num_idle_moments)
87+
return []
88+
89+
@classmethod
90+
def from_schema(cls, schema: str):
91+
"""Create dynamical decoupling model according to a given schema."""
92+
if not schema in _DynamicalDecouplingSchema.__members__:
93+
raise ValueError("Invalid schema name.")
94+
return cls(schema=_DynamicalDecouplingSchema[schema])
95+
96+
@classmethod
97+
def from_base_dd_sequence(cls, base_dd_sequence: list['cirq.Gate']):
98+
"""Create dynamical decoupling model according to a base sequence."""
99+
return cls(base_dd_sequence=base_dd_sequence)
100+
101+
def _json_dict_(self) -> Dict[str, Any]:
102+
d: Dict[str, Any] = {}
103+
if self.schema:
104+
d['schema'] = self.schema.name
105+
if self.base_dd_sequence:
106+
d['base_dd_sequence'] = self.base_dd_sequence
107+
return d
108+
109+
@classmethod
110+
def _from_json_dict_(cls, schema=None, base_dd_sequence=None, **kwargs):
111+
if schema:
112+
return cls(schema=_DynamicalDecouplingSchema[schema])
113+
if base_dd_sequence:
114+
return cls(base_dd_sequence=base_dd_sequence)
115+
116+
def _value_equality_values_(self) -> Any:
117+
return self.schema, self.base_dd_sequence
118+
119+
120+
def add_dynamical_decoupling(
121+
circuit: 'cirq.AbstractCircuit', dd_model: DynamicalDecouplingModel
122+
) -> 'cirq.Circuit':
123+
"""Add dynamical decoupling gates in a given circuit.
124+
125+
Args:
126+
circuit: Input circuit to transform.
127+
dd_model: Dynamical decoupling model that defines the schema to generate
128+
dynamical decoupling sequences.
129+
130+
Return:
131+
A circuit with dynamical decoupling operations.
132+
"""
133+
last_busy_moment_by_qubits: Dict['cirq.Qid', int] = {q: 0 for q in circuit.all_qubits()}
134+
insert_into: list[Tuple[int, 'cirq.OP_TREE']] = []
135+
for moment_id, moment in enumerate(circuit):
136+
for q in moment.qubits:
137+
insert_gates = dd_model.generate_dd_sequence(
138+
num_idle_moments=moment_id - last_busy_moment_by_qubits[q] - 1
139+
)
140+
for idx, gate in enumerate(insert_gates):
141+
insert_into.append((last_busy_moment_by_qubits[q] + idx + 1, gate.on(q)))
142+
last_busy_moment_by_qubits[q] = moment_id
143+
144+
circuit.batch_insert_into(insert_into)
145+
146+
return circuit
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Copyright 2024 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import cirq
16+
from cirq import DynamicalDecouplingModel, add_dynamical_decoupling
17+
import pytest
18+
19+
20+
def assert_dd(
21+
input_circuit: cirq.Circuit, expected_circuit: cirq.Circuit, dd_model: DynamicalDecouplingModel
22+
):
23+
updated_circuit = add_dynamical_decoupling(input_circuit, dd_model=dd_model)
24+
cirq.testing.assert_same_circuits(updated_circuit, expected_circuit)
25+
26+
27+
def test_insert_provided_schema():
28+
a = cirq.NamedQubit("a")
29+
b = cirq.NamedQubit("b")
30+
c = cirq.NamedQubit("c")
31+
32+
# No insertion as there is no room for a dd sequence.
33+
assert_dd(
34+
input_circuit=cirq.Circuit(
35+
cirq.Moment(cirq.H(a)), cirq.Moment(cirq.CNOT(a, b)), cirq.Moment(cirq.H(b))
36+
),
37+
expected_circuit=cirq.Circuit(
38+
cirq.Moment(cirq.H(a)), cirq.Moment(cirq.CNOT(a, b)), cirq.Moment(cirq.H(b))
39+
),
40+
dd_model=DynamicalDecouplingModel.from_schema("XX_PAIR"),
41+
)
42+
43+
# Insert one XX_PAIR dynamical decoupling sequence in idle moments.
44+
assert_dd(
45+
input_circuit=cirq.Circuit(
46+
cirq.Moment(cirq.H(a)),
47+
cirq.Moment(cirq.CNOT(a, b)),
48+
cirq.Moment(cirq.CNOT(b, c)),
49+
cirq.Moment(cirq.CNOT(b, c)),
50+
cirq.Moment(cirq.measure_each(a, b, c)),
51+
),
52+
expected_circuit=cirq.Circuit(
53+
cirq.Moment(cirq.H(a)),
54+
cirq.Moment(cirq.CNOT(a, b)),
55+
cirq.Moment(cirq.CNOT(b, c), cirq.X(a)),
56+
cirq.Moment(cirq.CNOT(b, c), cirq.X(a)),
57+
cirq.Moment(cirq.measure_each(a, b, c)),
58+
),
59+
dd_model=DynamicalDecouplingModel.from_schema("XX_PAIR"),
60+
)
61+
62+
# Insert one XX_PAIR dynamical decoupling sequence in idle moments.
63+
assert_dd(
64+
input_circuit=cirq.Circuit(
65+
cirq.Moment(cirq.H(a)),
66+
cirq.Moment(cirq.CNOT(a, b)),
67+
cirq.Moment(cirq.CNOT(b, c)),
68+
cirq.Moment(cirq.CNOT(b, c)),
69+
cirq.Moment(cirq.measure_each(a, b, c)),
70+
),
71+
expected_circuit=cirq.Circuit(
72+
cirq.Moment(cirq.H(a)),
73+
cirq.Moment(cirq.CNOT(a, b)),
74+
cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)),
75+
cirq.Moment(cirq.CNOT(b, c), cirq.Y(a)),
76+
cirq.Moment(cirq.measure_each(a, b, c)),
77+
),
78+
dd_model=DynamicalDecouplingModel.from_schema("YY_PAIR"),
79+
)
80+
81+
82+
def test_insert_by_customized_dd_sequence():
83+
a = cirq.NamedQubit("a")
84+
b = cirq.NamedQubit("b")
85+
c = cirq.NamedQubit("c")
86+
87+
assert_dd(
88+
input_circuit=cirq.Circuit(
89+
cirq.Moment(cirq.H(a)),
90+
cirq.Moment(cirq.CNOT(a, b)),
91+
cirq.Moment(cirq.CNOT(b, c)),
92+
cirq.Moment(cirq.CNOT(b, c)),
93+
cirq.Moment(cirq.measure_each(a, b, c)),
94+
),
95+
expected_circuit=cirq.Circuit(
96+
cirq.Moment(cirq.H(a)),
97+
cirq.Moment(cirq.CNOT(a, b)),
98+
cirq.Moment(cirq.CNOT(b, c), cirq.X(a)),
99+
cirq.Moment(cirq.CNOT(b, c), cirq.X(a)),
100+
cirq.Moment(cirq.measure_each(a, b, c)),
101+
),
102+
dd_model=DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate(), cirq.XPowGate()]),
103+
)
104+
105+
106+
def test_dd_model_constructor():
107+
# Succeed
108+
DynamicalDecouplingModel.from_schema("XX_PAIR")
109+
DynamicalDecouplingModel.from_schema("YY_PAIR")
110+
DynamicalDecouplingModel.from_base_dd_sequence(
111+
[cirq.XPowGate(), cirq.XPowGate(), cirq.YPowGate(), cirq.YPowGate()]
112+
)
113+
# Fail
114+
with pytest.raises(ValueError, match="Specify either schema or base_dd_sequence"):
115+
DynamicalDecouplingModel()
116+
with pytest.raises(ValueError, match="Invalid schema name."):
117+
DynamicalDecouplingModel.from_schema("unimplemented_schema")
118+
with pytest.raises(ValueError, match="Invalid dynamical decoupling sequence. Expect more than one gates."):
119+
DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate()])
120+
with pytest.raises(ValueError, match="Invalid dynamical decoupling sequence"):
121+
DynamicalDecouplingModel.from_base_dd_sequence([cirq.XPowGate(), cirq.YPowGate()])

0 commit comments

Comments
 (0)