Skip to content

Add AnalogDetuneCouplerOnly #7444

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

Merged
merged 5 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from all 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 cirq-google/cirq_google/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
)

from cirq_google.ops import (
AnalogDetuneCouplerOnly as AnalogDetuneCouplerOnly,
AnalogDetuneQubit as AnalogDetuneQubit,
CalibrationTag as CalibrationTag,
Coupler as Coupler,
Expand Down
1 change: 1 addition & 0 deletions cirq-google/cirq_google/json_resolver_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def _old_xmon(*args, **kwargs):

return {
'_NamedConstantXmonDevice': _old_xmon,
'AnalogDetuneCouplerOnly': cirq_google.AnalogDetuneCouplerOnly,
'AnalogDetuneQubit': cirq_google.AnalogDetuneQubit,
'Calibration': cirq_google.Calibration,
'CalibrationTag': cirq_google.CalibrationTag,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"cirq_type": "AnalogDetuneCouplerOnly",
"length": {
"cirq_type": "sympy.Symbol",
"name": "l"
},
"w": 10,
"g_0": 5,
"g_max": 20,
"g_ramp_exponent": 1.0,
"neighbor_qubits_freq": [
null,
null
],
"prev_neighbor_qubits_freq": [
null,
null
],
"interpolate_coupling_cal": true,
"analog_cal_for_pulseshaping": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cirq_google.AnalogDetuneCouplerOnly(length=sympy.Symbol('l'), w=10, g_0=5, g_max=20)
2 changes: 2 additions & 0 deletions cirq-google/cirq_google/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from cirq_google.ops.analog_detune_gates import AnalogDetuneQubit as AnalogDetuneQubit

from cirq_google.ops.analog_detune_gates import AnalogDetuneCouplerOnly as AnalogDetuneCouplerOnly

from cirq_google.ops.calibration_tag import CalibrationTag as CalibrationTag

from cirq_google.ops.coupler import Coupler as Coupler
Expand Down
181 changes: 180 additions & 1 deletion cirq-google/cirq_google/ops/analog_detune_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
"""Define detuning gates for Analog Experiment usage."""
from __future__ import annotations

from typing import AbstractSet, Any, TYPE_CHECKING
from typing import AbstractSet, Any, Iterable, TYPE_CHECKING

import cirq
from cirq_google.ops import coupler
from cirq_google.study import symbol_util as su

if TYPE_CHECKING:
Expand Down Expand Up @@ -59,6 +60,7 @@ def __init__(
linear_rise: bool = True,
):
"""Inits AnalogDetuneQubit.

Args:
length: The duration of gate.
w: Width of the step envelope raising edge.
Expand Down Expand Up @@ -171,3 +173,180 @@ def _json_dict_(self):
'linear_rise',
],
)


@cirq.value_equality(approximate=True)
class AnalogDetuneCouplerOnly(cirq.ops.Gate):
"""Set a coupler detuning from g_0 to g_max according to analog model.

The shape of pulse followed by the g=g_0+A*t^g_exp (1 gives linear ramp),
where the coefficient is auto calculated by the g_max.

Pulse shape:

.. svgbob::
:align: center

| ,--------|---- amp_max (parsed from g_max)
| / |
amp_0---|-' - - - - -| - -
|
| |-w -| |
| |---length --|
|
--------------------------(calculated from the g_0)
"""

def __init__(
self,
length: su.ValueOrSymbol,
w: su.ValueOrSymbol,
g_0: su.ValueOrSymbol,
g_max: su.ValueOrSymbol,
g_ramp_exponent: cirq.TParamVal = 1.0,
neighbor_qubits_freq: tuple[su.ValueOrSymbol | None, su.ValueOrSymbol | None] = (
None,
None,
),
prev_neighbor_qubits_freq: tuple[su.ValueOrSymbol | None, su.ValueOrSymbol | None] = (
None,
None,
),
interpolate_coupling_cal: bool = True,
analog_cal_for_pulseshaping: bool = False,
):
"""Inits AnalogDetuneCouplerOnly.

Args:
length: The duration of gate.
w: Width of the step envelope raising edge.
g_0: The pulse shape is specified with the equation g(t) = g_0+A*t^g_exp.
where g(0)=g_0 and g(w)=g_max. The ramp is according to the power law
with exponent specified by g_ramp_exponent.
g_max: See g_0.
g_ramp_exponent: See g_0.
neighbor_qubits_freq: Two frequency of the neighbor qubits at the moment.
If the provided value is None, we assume neighbor qubits are at idle freq.
prev_neighbor_qubits_freq: Two frequency of the neighbor qubits at preivous moment.
interpolate_coupling_cal: If true, find the required amp for the coupling strength
through interpolation. If not true, require all coupling strength has associated
amp calibrated in the registry.
analog_cal_for_pulseshaping: If ture, using the analog model instead of
standard transmon model to find the amp of pulse.
"""
self.length = length
self.w = w
self.g_0 = g_0
self.g_max = g_max
self.g_ramp_exponent = g_ramp_exponent
self.neighbor_qubits_freq = tuple(neighbor_qubits_freq)
self.prev_neighbor_qubits_freq = tuple(prev_neighbor_qubits_freq)
self.interpolate_coupling_cal = interpolate_coupling_cal
self.analog_cal_for_pulseshaping = analog_cal_for_pulseshaping

def _unitary_(self) -> np.ndarray:
return NotImplemented # pragma: no cover

def on(self, *qubits: cirq.Qid) -> cirq.Operation:
"""Returns an application of this gate to the given qubits.

Coupler gate will silently change two qubit Qids into
a single Coupler Qid object so that adjacent couplers
can be simultaneously acted on in the same moment.
"""
if len(qubits) == 2:
return super().on(coupler.Coupler(qubits[0], qubits[1]))
else:
return super().on(*qubits)

def on_each(self, *targets: cirq.Qid | Iterable[Any]) -> list[cirq.Operation]:
"""Returns a list of operations applying the gate to all targets."""
return [self.on(t) if isinstance(t, cirq.Qid) else self.on(*t) for t in targets]

def _num_qubits_(self) -> int:
return 1

def _is_parameterized_(self) -> bool:
return (
cirq.is_parameterized(self.length)
or cirq.is_parameterized(self.w)
or cirq.is_parameterized(self.g_0)
or cirq.is_parameterized(self.g_max)
or cirq.is_parameterized(self.g_ramp_exponent)
or cirq.is_parameterized(self.neighbor_qubits_freq)
or cirq.is_parameterized(self.prev_neighbor_qubits_freq)
)

def _parameter_names_(self) -> AbstractSet[str]:
return (
cirq.parameter_names(self.length)
| cirq.parameter_names(self.w)
| cirq.parameter_names(self.g_0)
| cirq.parameter_names(self.g_max)
| cirq.parameter_names(self.g_ramp_exponent)
| cirq.parameter_names(self.neighbor_qubits_freq)
| cirq.parameter_names(self.prev_neighbor_qubits_freq)
)

def _resolve_parameters_(
self, resolver: cirq.ParamResolverOrSimilarType, recursive: bool
) -> AnalogDetuneCouplerOnly:
resolver_ = cirq.ParamResolver(resolver)
return AnalogDetuneCouplerOnly(
length=su.direct_symbol_replacement(self.length, resolver_),
w=su.direct_symbol_replacement(self.w, resolver_),
g_0=su.direct_symbol_replacement(self.g_0, resolver_),
g_max=su.direct_symbol_replacement(self.g_max, resolver_),
g_ramp_exponent=su.direct_symbol_replacement(self.g_ramp_exponent, resolver_),
neighbor_qubits_freq=tuple(
su.direct_symbol_replacement(f, resolver_) for f in self.neighbor_qubits_freq
),
prev_neighbor_qubits_freq=tuple(
su.direct_symbol_replacement(f, resolver_) for f in self.prev_neighbor_qubits_freq
),
interpolate_coupling_cal=self.interpolate_coupling_cal,
analog_cal_for_pulseshaping=self.analog_cal_for_pulseshaping,
)

def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> str:
return f"AnalogDetuneCouplerOnly(length={self.length}, g_max={self.g_max})"

def __repr__(self) -> str:
return (
f'AnalogDetuneCouplerOnly(length={self.length}, '
f'w={self.w}, '
f'g_0={self.g_0}, '
f'g_max={self.g_max}, '
f'g_ramp_exponent={self.g_ramp_exponent}, '
f'neighbor_qubits_freq={self.neighbor_qubits_freq}, '
f'prev_neighbor_qubits_freq={self.prev_neighbor_qubits_freq})'
)

def _value_equality_values_(self) -> Any:
return (
self.length,
self.w,
self.g_0,
self.g_max,
self.g_ramp_exponent,
self.neighbor_qubits_freq,
self.prev_neighbor_qubits_freq,
self.interpolate_coupling_cal,
self.analog_cal_for_pulseshaping,
)

def _json_dict_(self):
return cirq.obj_to_dict_helper(
self,
[
'length',
'w',
'g_0',
'g_max',
'g_ramp_exponent',
'neighbor_qubits_freq',
'prev_neighbor_qubits_freq',
'interpolate_coupling_cal',
'analog_cal_for_pulseshaping',
],
)
99 changes: 97 additions & 2 deletions cirq-google/cirq_google/ops/analog_detune_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import textwrap

import pytest
import sympy
import tunits as tu

import cirq
import cirq_google.ops.analog_detune_gates as adg
import cirq_google as cg
from cirq_google.ops import analog_detune_gates as adg


def test_equality():
def test_analog_detune_qubit_equality():
g1 = adg.AnalogDetuneQubit(length=20 * tu.ns, w=10 * tu.ns, target_freq=5 * tu.GHz)
g2 = adg.AnalogDetuneQubit(length=20 * tu.ns, w=10 * tu.ns, target_freq=5 * tu.GHz)
assert g1.num_qubits() == 1
Expand Down Expand Up @@ -141,3 +144,95 @@ def test_analog_detune_qubit_repr():
"AnalogDetuneQubit(length=l, w=10 ns, target_freq=t_freq, prev_freq=p_freq,"
" neighbor_coupler_g_dict=None, prev_neighbor_coupler_g_dict=None)"
)


def test_analog_detune_coupler_equality() -> None:
g1 = adg.AnalogDetuneCouplerOnly(
length=20 * tu.ns, w=10 * tu.ns, g_0=5 * tu.GHz, g_max=sympy.Symbol("g")
)
g2 = adg.AnalogDetuneCouplerOnly(
length=20 * tu.ns, w=10 * tu.ns, g_0=5 * tu.GHz, g_max=sympy.Symbol("g")
)
assert g1.num_qubits() == 1
assert g1 == g2


def test_analog_detune_coupler_resolution() -> None:
gate = adg.AnalogDetuneCouplerOnly(
length=sympy.Symbol('length'),
w=10 * tu.ns,
g_0=5 * tu.MHz,
g_max=sympy.Symbol('g'),
neighbor_qubits_freq=(sympy.Symbol('q'), None),
prev_neighbor_qubits_freq=(5, 6),
)
resolver = {'length': 50 * tu.ns, sympy.Symbol('g'): 2 * tu.MHz, 'q': 5}
assert cirq.resolve_parameters(gate, resolver) == adg.AnalogDetuneCouplerOnly(
length=50 * tu.ns,
w=10 * tu.ns,
g_0=5 * tu.MHz,
g_max=2 * tu.MHz,
neighbor_qubits_freq=(5, None),
prev_neighbor_qubits_freq=(5, 6),
)


def test_analog_detune_coupler_parameter_names() -> None:
gate = adg.AnalogDetuneCouplerOnly(
length=sympy.Symbol('l'),
w=10 * tu.ns,
g_0=5 * tu.MHz,
g_max=sympy.Symbol('g'),
neighbor_qubits_freq=(sympy.Symbol('q'), None),
prev_neighbor_qubits_freq=(5, 6),
)
assert cirq.parameter_names(gate) == {'l', 'g', 'q'}


def test_analog_detune_coupler_repr() -> None:
gate = adg.AnalogDetuneCouplerOnly(
length=sympy.Symbol('l'),
w=10 * tu.ns,
g_0=5 * tu.MHz,
g_max=sympy.Symbol('g'),
neighbor_qubits_freq=(sympy.Symbol('q'), None),
prev_neighbor_qubits_freq=(5, 6),
)
assert repr(gate) == (
"AnalogDetuneCouplerOnly(length=l, w=10 ns, g_0=5 MHz, g_max=g, g_ramp_exponent=1.0,"
" neighbor_qubits_freq=(q, None), prev_neighbor_qubits_freq=(5, 6))"
)


def test_analog_detune_coupler_circuit_diagram() -> None:
q1, q2 = cirq.q(0, 0), cirq.q(0, 1)
gate = adg.AnalogDetuneCouplerOnly(
length=sympy.Symbol('l'), w=10 * tu.ns, g_0=5 * tu.MHz, g_max=20 * tu.MHz
)
cirq.testing.assert_has_diagram(
cirq.Circuit(gate.on(q1, q2)),
"c(q(0, 0),q(0, 1)): ───AnalogDetuneCouplerOnly(length=l, g_max=20 MHz)───",
)

gate.g_max = None
cirq.testing.assert_has_diagram(
cirq.Circuit(gate.on(cg.Coupler(q1, q2))),
"c(q(0, 0),q(0, 1)): ───AnalogDetuneCouplerOnly(length=l, g_max=None)───",
)

q3, q4 = cirq.q(0, 2), cirq.q(0, 3)
cirq.testing.assert_has_diagram(
cirq.Circuit(gate.on_each((q1, q2), (q3, q4))),
textwrap.dedent(
"""
c(q(0, 0),q(0, 1)): ───AnalogDetuneCouplerOnly(length=l, g_max=None)───

c(q(0, 2),q(0, 3)): ───AnalogDetuneCouplerOnly(length=l, g_max=None)───
"""
),
)


def test_analog_detune_coupler_jsonify() -> None:
gate = adg.AnalogDetuneCouplerOnly(length=sympy.Symbol('l'), w=10, g_0=5, g_max=20)
assert gate == cirq.read_json(json_text=cirq.to_json(gate))