Skip to content

Commit 66b582f

Browse files
authored
Add AnalogDetuneQubit cirq google version (#7430)
In order to keep using unit-style input, I have hacked the _resolve_parameters_ function a little bit. Assume all inputs should either be tunit value or sympy.Symbol. (I.e. no sympy expression is allowed). I have to add this since we don't have SymbolFunc or ExprFunc in cirq world.
1 parent c9839bf commit 66b582f

File tree

8 files changed

+363
-0
lines changed

8 files changed

+363
-0
lines changed

cirq-core/cirq/protocols/json_serialization_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ def _eval_repr_data_file(path: pathlib.Path, deprecation_deadline: str | None):
618618
if deprecation is not None and deprecation.old_name in content:
619619
ctx_managers.append(deprecation.deprecation_assertion) # pragma: no cover
620620

621+
# TODO: consider to add the support for 'tunits'.
621622
imports = {'cirq': cirq, 'pd': pd, 'sympy': sympy, 'np': np, 'datetime': datetime, 'nx': nx}
622623

623624
for m in TESTED_MODULES.keys():

cirq-google/cirq_google/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
)
5454

5555
from cirq_google.ops import (
56+
AnalogDetuneQubit as AnalogDetuneQubit,
5657
CalibrationTag as CalibrationTag,
5758
Coupler as Coupler,
5859
FSimGateFamily as FSimGateFamily,

cirq-google/cirq_google/json_resolver_cache.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def _old_xmon(*args, **kwargs):
4444

4545
return {
4646
'_NamedConstantXmonDevice': _old_xmon,
47+
'AnalogDetuneQubit': cirq_google.AnalogDetuneQubit,
4748
'Calibration': cirq_google.Calibration,
4849
'CalibrationTag': cirq_google.CalibrationTag,
4950
'CalibrationLayer': cirq_google.CalibrationLayer,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"cirq_type": "AnalogDetuneQubit",
3+
"length": 10,
4+
"w": 5,
5+
"target_freq": 8,
6+
"prev_freq": null,
7+
"neighbor_coupler_g_dict": null,
8+
"prev_neighbor_coupler_g_dict": null,
9+
"linear_rise": true
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cirq_google.AnalogDetuneQubit(length=10, w=5, target_freq=8)

cirq-google/cirq_google/ops/__init__.py

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

1515
"""Qubit Gates, Operations, and Tags useful for Google devices."""
1616

17+
from cirq_google.ops.analog_detune_gates import AnalogDetuneQubit as AnalogDetuneQubit
18+
1719
from cirq_google.ops.calibration_tag import CalibrationTag as CalibrationTag
1820

1921
from cirq_google.ops.coupler import Coupler as Coupler
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Copyright 2025 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+
"""Define detuning gates for Analog Experiment usage."""
16+
from __future__ import annotations
17+
18+
from typing import AbstractSet, Any, TYPE_CHECKING, TypeAlias
19+
20+
import sympy
21+
import tunits as tu
22+
23+
import cirq
24+
25+
if TYPE_CHECKING:
26+
import numpy as np
27+
28+
# The gate is intended for the google internal use, hence the typing style
29+
# follows more on the t-unit + symbol instead of float + symbol style.
30+
ValueOrSymbol: TypeAlias = tu.Value | sympy.Basic
31+
FloatOrSymbol: TypeAlias = float | sympy.Basic
32+
33+
# A sentile for not finding the key in resolver.
34+
NOT_FOUND = "__NOT_FOUND__"
35+
36+
37+
@cirq.value_equality(approximate=True)
38+
class AnalogDetuneQubit(cirq.ops.Gate):
39+
"""A step function that steup a qubit to the frequency according to analog model.
40+
41+
Pulse shape:
42+
43+
.. svgbob::
44+
:align: center
45+
| ,--------|---- amp (calculated from target freq using analog model)
46+
| / |
47+
prev_amp---|-' - - - - -| - -
48+
^
49+
| |-w -| |
50+
| |---length --|
51+
|
52+
--------------------------(calculated from previous qubit freq using analog model)
53+
54+
Note the step is held at amp with infinite length. This gate is typically used by concatenating
55+
multiple instances. To ensure the curve is continuous and avoids sudden jumps, you need
56+
prev_freq to compensate for the previous Detune gate. If not provided, no compensation
57+
will be applied, i.e. start from 0. If the target_freq is None and prev_freq provided,
58+
this Detune gate will reset the qubit freq back to absolute amp=0 according to prev_freq.
59+
"""
60+
61+
def __init__(
62+
self,
63+
length: ValueOrSymbol,
64+
w: ValueOrSymbol,
65+
target_freq: ValueOrSymbol | None = None,
66+
prev_freq: ValueOrSymbol | None = None,
67+
neighbor_coupler_g_dict: dict[str, ValueOrSymbol] | None = None,
68+
prev_neighbor_coupler_g_dict: dict[str, ValueOrSymbol] | None = None,
69+
linear_rise: bool = True,
70+
):
71+
"""Inits AnalogDetuneQubit.
72+
Args:
73+
length: The duration of gate.
74+
w: Width of the step envelope raising edge.
75+
target_freq: The target frequecy for the qubit at end of detune gate.
76+
prev_freq: Previous detuning frequecy to compensate beginning of detune gate.
77+
neighbor_coupler_g_dict: A dictionary has coupler name like "c_q0_0_q1_0"
78+
as key and the coupling strength `g` as the value.
79+
prev_neighbor_coupler_g_dict: A dictionary has the same format as
80+
`neighbor_coupler_g_dict` one but the value is provided for
81+
coupling strength g at previous step.
82+
linear_rise: If True the rising edge will be a linear function,
83+
otherwise it will be a smoothed function.
84+
"""
85+
self.length = length
86+
self.w = w
87+
self.target_freq = target_freq
88+
self.prev_freq = prev_freq
89+
self.neighbor_coupler_g_dict = neighbor_coupler_g_dict
90+
self.prev_neighbor_coupler_g_dict = prev_neighbor_coupler_g_dict
91+
self.linear_rise = linear_rise
92+
93+
def _unitary_(self) -> np.ndarray:
94+
return NotImplemented # pragma: no cover
95+
96+
def num_qubits(self) -> int:
97+
return 1
98+
99+
def _is_parameterized_(self) -> bool:
100+
def _is_parameterized_dict(dict_with_value: dict[str, ValueOrSymbol] | None) -> bool:
101+
if dict_with_value is None:
102+
return False # pragma: no cover
103+
return any(cirq.is_parameterized(v) for v in dict_with_value.values())
104+
105+
return (
106+
cirq.is_parameterized(self.length)
107+
or cirq.is_parameterized(self.w)
108+
or cirq.is_parameterized(self.target_freq)
109+
or cirq.is_parameterized(self.prev_freq)
110+
or _is_parameterized_dict(self.neighbor_coupler_g_dict)
111+
or _is_parameterized_dict(self.prev_neighbor_coupler_g_dict)
112+
)
113+
114+
def _parameter_names_(self) -> AbstractSet[str]:
115+
def dict_param_name(dict_with_value: dict[str, ValueOrSymbol] | None) -> AbstractSet[str]:
116+
if dict_with_value is None:
117+
return set()
118+
return {v.name for v in dict_with_value.values() if cirq.is_parameterized(v)}
119+
120+
return (
121+
cirq.parameter_names(self.length)
122+
| cirq.parameter_names(self.w)
123+
| cirq.parameter_names(self.target_freq)
124+
| cirq.parameter_names(self.prev_freq)
125+
| dict_param_name(self.neighbor_coupler_g_dict)
126+
| dict_param_name(self.prev_neighbor_coupler_g_dict)
127+
)
128+
129+
def _resolve_parameters_(
130+
self, resolver: cirq.ParamResolverOrSimilarType, recursive: bool
131+
) -> AnalogDetuneQubit:
132+
# A shortcut for value resolution to avoid tu.unit compare with float issue.
133+
def _direct_symbol_replacement(x, resolver: cirq.ParamResolver):
134+
if isinstance(x, sympy.Symbol):
135+
value = resolver.param_dict.get(x.name, NOT_FOUND)
136+
if value == NOT_FOUND:
137+
value = resolver.param_dict.get(x, NOT_FOUND)
138+
if value != NOT_FOUND:
139+
return value
140+
return x # pragma: no cover
141+
return x
142+
143+
resolver_ = cirq.ParamResolver(resolver)
144+
return AnalogDetuneQubit(
145+
length=_direct_symbol_replacement(self.length, resolver_),
146+
w=_direct_symbol_replacement(self.w, resolver_),
147+
target_freq=_direct_symbol_replacement(self.target_freq, resolver_),
148+
prev_freq=_direct_symbol_replacement(self.prev_freq, resolver_),
149+
neighbor_coupler_g_dict=(
150+
{
151+
k: _direct_symbol_replacement(v, resolver_)
152+
for k, v in self.neighbor_coupler_g_dict.items()
153+
}
154+
if self.neighbor_coupler_g_dict
155+
else None
156+
),
157+
prev_neighbor_coupler_g_dict=(
158+
{
159+
k: _direct_symbol_replacement(v, resolver_)
160+
for k, v in self.prev_neighbor_coupler_g_dict.items()
161+
}
162+
if self.prev_neighbor_coupler_g_dict
163+
else None
164+
),
165+
linear_rise=self.linear_rise,
166+
)
167+
168+
def __repr__(self) -> str:
169+
return (
170+
f'AnalogDetuneQubit(length={self.length}, '
171+
f'w={self.w}, '
172+
f'target_freq={self.target_freq}, '
173+
f'prev_freq={self.prev_freq}, '
174+
f'neighbor_coupler_g_dict={self.neighbor_coupler_g_dict}, '
175+
f'prev_neighbor_coupler_g_dict={self.prev_neighbor_coupler_g_dict})'
176+
)
177+
178+
def _value_equality_values_(self) -> Any:
179+
return (
180+
self.length,
181+
self.w,
182+
self.target_freq,
183+
self.prev_freq,
184+
self.neighbor_coupler_g_dict,
185+
self.prev_neighbor_coupler_g_dict,
186+
self.linear_rise,
187+
)
188+
189+
def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> str:
190+
return f"AnalogDetune(freq={self.target_freq})"
191+
192+
def _json_dict_(self):
193+
return cirq.obj_to_dict_helper(
194+
self,
195+
[
196+
'length',
197+
'w',
198+
'target_freq',
199+
'prev_freq',
200+
'neighbor_coupler_g_dict',
201+
'prev_neighbor_coupler_g_dict',
202+
'linear_rise',
203+
],
204+
)
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Copyright 2025 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 pytest
16+
import sympy
17+
import tunits as tu
18+
19+
import cirq
20+
import cirq_google.ops.analog_detune_gates as adg
21+
22+
23+
def test_equality():
24+
g1 = adg.AnalogDetuneQubit(length=20 * tu.ns, w=10 * tu.ns, target_freq=5 * tu.GHz)
25+
g2 = adg.AnalogDetuneQubit(length=20 * tu.ns, w=10 * tu.ns, target_freq=5 * tu.GHz)
26+
assert g1.num_qubits() == 1
27+
assert g1 == g2
28+
29+
g1 = adg.AnalogDetuneQubit(
30+
length=20 * tu.ns,
31+
w=10 * tu.ns,
32+
neighbor_coupler_g_dict={"c_q1_1_q0_1": 44.3, "c_q1_1_q2_1": 45.1},
33+
)
34+
g2 = adg.AnalogDetuneQubit(
35+
length=20 * tu.ns,
36+
w=10 * tu.ns,
37+
neighbor_coupler_g_dict={"c_q1_1_q2_1": 45.1, "c_q1_1_q0_1": 44.3},
38+
)
39+
assert g1 == g2
40+
41+
42+
@pytest.mark.parametrize(
43+
'gate, resolver, expected',
44+
[
45+
(
46+
adg.AnalogDetuneQubit(
47+
length=sympy.Symbol('length'), w=10 * tu.ns, target_freq=5 * tu.GHz
48+
),
49+
{'length': 50 * tu.ns},
50+
adg.AnalogDetuneQubit(length=50 * tu.ns, w=10 * tu.ns, target_freq=5 * tu.GHz),
51+
),
52+
(
53+
adg.AnalogDetuneQubit(
54+
length=50 * tu.ns,
55+
w=10 * tu.ns,
56+
target_freq=5 * tu.GHz,
57+
neighbor_coupler_g_dict={"c_q1_1_q2_1": sympy.Symbol("g")},
58+
),
59+
{'length': 50 * tu.ns, sympy.Symbol("g"): 43 * tu.MHz},
60+
adg.AnalogDetuneQubit(
61+
length=50 * tu.ns,
62+
w=10 * tu.ns,
63+
target_freq=5 * tu.GHz,
64+
neighbor_coupler_g_dict={"c_q1_1_q2_1": 43 * tu.MHz},
65+
),
66+
),
67+
(
68+
adg.AnalogDetuneQubit(
69+
length=sympy.Symbol('l'),
70+
w=sympy.Symbol('w'),
71+
target_freq=sympy.Symbol('t_freq'),
72+
prev_neighbor_coupler_g_dict={"c_q1_1_q2_1": sympy.Symbol("g2")},
73+
),
74+
{'l': 10 * tu.ns, 'w': 8 * tu.ns, 't_freq': 6 * tu.GHz, 'g2': 5 * tu.MHz},
75+
adg.AnalogDetuneQubit(
76+
length=10 * tu.ns,
77+
w=8 * tu.ns,
78+
target_freq=6 * tu.GHz,
79+
prev_neighbor_coupler_g_dict={"c_q1_1_q2_1": 5 * tu.MHz},
80+
),
81+
),
82+
],
83+
)
84+
def test_analog_detune_qubit_resolution(gate, resolver, expected):
85+
assert cirq.resolve_parameters(gate, resolver) == expected
86+
87+
88+
def test_analog_detune_qubit_parameter_names():
89+
gate = adg.AnalogDetuneQubit(
90+
length=sympy.Symbol('l'),
91+
w=10 * tu.ns,
92+
target_freq=sympy.Symbol('t_freq'),
93+
prev_freq=sympy.Symbol('p_freq'),
94+
neighbor_coupler_g_dict={"c_q1_1_q2_1": sympy.Symbol("g")},
95+
prev_neighbor_coupler_g_dict={"c_q1_1_q2_1": 54 * tu.MHz},
96+
)
97+
assert cirq.parameter_names(gate) == {'l', 't_freq', 'p_freq', 'g'}
98+
99+
gate = adg.AnalogDetuneQubit(length=sympy.Symbol('l'), w=10 * tu.ns)
100+
assert cirq.parameter_names(gate) == {'l'}
101+
102+
gate = adg.AnalogDetuneQubit(
103+
length=5 * tu.ns, w=10 * tu.ns, neighbor_coupler_g_dict={"c_q1_1_q2_1": sympy.Symbol("g")}
104+
)
105+
assert cirq.parameter_names(gate) == {'g'}
106+
107+
108+
def test_analog_detune_qubit_circuit_diagram():
109+
q = cirq.q(0, 1)
110+
gate = adg.AnalogDetuneQubit(
111+
length=sympy.Symbol('l'),
112+
w=10 * tu.ns,
113+
target_freq=sympy.Symbol('t_freq'),
114+
prev_freq=sympy.Symbol('p_freq'),
115+
)
116+
cirq.testing.assert_has_diagram(
117+
cirq.Circuit(gate(q)), "(0, 1): ───AnalogDetune(freq=t_freq)───"
118+
)
119+
gate.target_freq = 8 * tu.GHz
120+
cirq.testing.assert_has_diagram(cirq.Circuit(gate(q)), "(0, 1): ───AnalogDetune(freq=8 GHz)───")
121+
122+
123+
def test_analog_detune_qubit_jsonify():
124+
gate = adg.AnalogDetuneQubit(
125+
length=sympy.Symbol('l'),
126+
w=sympy.Symbol('w'),
127+
target_freq=sympy.Symbol('t_freq'),
128+
neighbor_coupler_g_dict={"c_q1_1_q2_1": sympy.Symbol("g")},
129+
)
130+
assert gate == cirq.read_json(json_text=cirq.to_json(gate))
131+
132+
133+
def test_analog_detune_qubit_repr():
134+
gate = adg.AnalogDetuneQubit(
135+
length=sympy.Symbol('l'),
136+
w=10 * tu.ns,
137+
target_freq=sympy.Symbol('t_freq'),
138+
prev_freq=sympy.Symbol('p_freq'),
139+
)
140+
assert repr(gate) == (
141+
"AnalogDetuneQubit(length=l, w=10 ns, target_freq=t_freq, prev_freq=p_freq,"
142+
" neighbor_coupler_g_dict=None, prev_neighbor_coupler_g_dict=None)"
143+
)

0 commit comments

Comments
 (0)