From 11614715bafaed7033d8567c59067c7ebb9a420a Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 00:01:58 +0000 Subject: [PATCH 01/14] Add AnalogTrajectory and FreqMap --- .../analog_experiments/__init__.py | 20 +++ .../analog_trajectory_util.py | 163 ++++++++++++++++++ .../analog_trajectory_util_test.py | 84 +++++++++ 3 files changed, 267 insertions(+) create mode 100644 cirq-google/cirq_google/experimental/analog_experiments/__init__.py create mode 100644 cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py create mode 100644 cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py diff --git a/cirq-google/cirq_google/experimental/analog_experiments/__init__.py b/cirq-google/cirq_google/experimental/analog_experiments/__init__.py new file mode 100644 index 00000000000..a3bff376f59 --- /dev/null +++ b/cirq-google/cirq_google/experimental/analog_experiments/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2019 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. + +"""Folder for Running Analog experiments.""" + +from cirq_google.experimental.analog_experiments.analog_trajectory_util import ( + FreqMap as FreqMap + AnalogTrajectory as AnalogTrajectory +) \ No newline at end of file diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py new file mode 100644 index 00000000000..d75092953af --- /dev/null +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -0,0 +1,163 @@ +# Copyright 2019 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. + +from __future__ import annotations + +from typing import AbstractSet, Any + +import attrs +import sympy +import tunits as tu + +import cirq + +ValueOrSymbol = tu.Value | sympy.Symbol + + +@attrs.mutable +class FreqMap: + """Object containing information about the step to a new analog Hamiltonian. + + Attributes: + duration: duration of step + qubit_freqs: dict describing qubit frequencies at end of step (None if idle) + couplings: dict describing coupling rates at end of step + """ + + duration: ValueOrSymbol + qubit_freqs: dict[str, ValueOrSymbol | None] + couplings: dict[tuple[str, str], ValueOrSymbol] + + def _is_parameterized_(self) -> bool: + def _is_parameterized_dict(dict_with_value: dict[str, ValueOrSymbol | None]) -> bool: + return any(cirq.is_parameterized(v) for v in dict_with_value.values()) + + return ( + cirq.is_parameterized(self.duration) + or _is_parameterized_dict(self.qubit_freqs) + or _is_parameterized_dict(self.couplings) + ) + + def _parameter_names_(self) -> AbstractSet[str]: + def dict_param_name(dict_with_value: dict[Any, ValueOrSymbol | None]) -> AbstractSet[str]: + return {v.name for v in dict_with_value.values() if cirq.is_parameterized(v)} + + return ( + cirq.parameter_names(self.duration) + | dict_param_name(self.qubit_freqs) + | dict_param_name(self.couplings) + ) + + def _resolve_parameters_( + self, resolver: cirq.ParamResolverOrSimilarType, recursive: bool + ) -> FreqMap: + def _direct_symbol_replacement(x, resolver: cirq.ParamResolver): + if isinstance(x, sympy.Symbol): + value = resolver.param_dict.get(x.name, "__NOT_FOUND__") + if value == "__NOT_FOUND__": + value = resolver.param_dict.get(x, "__NOT_FOUND__") + if value != "__NOT_FOUND__": + return value + return x # pragma: no cover + return x + + resolver_ = cirq.ParamResolver(resolver) + return FreqMap( + duration=_direct_symbol_replacement(self.duration, resolver_), + qubit_freqs={ + k: _direct_symbol_replacement(v, resolver_) for k, v in self.qubit_freqs.items() + }, + couplings={ + k: _direct_symbol_replacement(v, resolver_) for k, v in self.couplings.items() + }, + ) + + +class AnalogTrajectory: + """Class for handling qubit frequency and coupling trajectories that + define analog experiments. The class is defined using a sparse_trajectory, + which contains time durations of each Hamiltonian ramp element and the + corresponding qubit frequencies and couplings (unassigned qubits and/or + couplers are left unchanged). + """ + + def __init__( + self, *, full_trajectory: list[FreqMap], qubits: list[str], pairs: list[tuple[str, str]] + ): + self.full_trajectory = full_trajectory + self.qubits = qubits + self.pairs = pairs + + @classmethod + def from_sparse_trajecotry( + cls, + sparse_trajectory: list[ + tuple[tu.Value, dict[str, ValueOrSymbol | None], dict[tuple[str, str], ValueOrSymbol]], + ], + qubits: list[str] | None = None, + pairs: list[tuple[str, str]] | None = None, + ): + """Construct AnalogTrajectory from sparse trajectory. + + Args: + sparse_trajectory: A list of tuples, where each tuple defines a segment of `FreqMap` + and contains three elements: (duration, qubit_freqs, coupling_strengths). + `duration` is a tunits value, `qubit_freqs` is a dictionary mapping qubit strings + to detuning frequencies, and `coupling_strengths` is a dictionary mapping qubit + pairs to their coupling strength. This format is considered "sparse" because each + tuple does not need to fully specify all qubits and coupling pairs; any missing + detuning frequency or coupling strength will be set to the same value as the + previous value in the list. + qubits: The qubits in interest. If not provided, automatically parsed from trajectory. + pairs: The pairs in interest. If not provided, automatically parsed from trajectory. + """ + if qubits is None or pairs is None: + qubits_in_traj: list[str] = [] + pairs_in_traj: list[tuple[str, str]] = [] + for _, q, p in sparse_trajectory: + qubits_in_traj.extend(q.keys()) + pairs_in_traj.extend(p.keys()) + qubits = list(set(qubits_in_traj)) + pairs = list(set(pairs_in_traj)) + + full_trajectory: list[FreqMap] = [] + init_qubit_freq_dict: dict[str, tu.Value | None] = {q: None for q in qubits} + init_g_dict: dict[tuple[str, str], tu.Value] = {p: 0 * tu.MHz for p in pairs} + full_trajectory.append(FreqMap(0 * tu.ns, init_qubit_freq_dict, init_g_dict)) + + for dt, qubit_freq_dict, g_dict in sparse_trajectory: + # If no freq provided, set equal to previous + new_qubit_freq_dict = { + q: qubit_freq_dict.get(q, full_trajectory[-1].qubit_freqs.get(q)) for q in qubits + } + # If no g provided, set equal to previous + new_g_dict: dict[tuple[str, str], tu.Value] = { + p: g_dict.get(p, full_trajectory[-1].couplings.get(p)) for p in pairs + } + + full_trajectory.append(FreqMap(dt, new_qubit_freq_dict, new_g_dict)) + return cls(full_trajectory=full_trajectory, qubits=qubits, pairs=pairs) + + def get_full_trajectory_with_resolved_idles( + self, idle_freq_map: dict[str, tu.Value] + ) -> list[FreqMap]: + """Insert idle frequencies instead of None in trajectory.""" + + resolved_trajectory: list[FreqMap] = [] + for dt, qubit_freq_dict, g_dict in self.full_trajectory: + resolved_qubit_freq_dict = { + q: idle_freq_map[q] if f is None else f for q, f in qubit_freq_dict.items() + } + resolved_trajectory.append(FreqMap(dt, resolved_qubit_freq_dict, g_dict)) # type: ignore + return resolved_trajectory diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py new file mode 100644 index 00000000000..0e8bee027a1 --- /dev/null +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py @@ -0,0 +1,84 @@ +# Copyright 2019 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 pytest +import sympy +import tunits as tu + +import cirq +from cirq_google.experimental.analog_experiments import analog_trajectory_util as atu + + +@pytest.fixture +def freq_map() -> atu.FreqMap: + return atu.FreqMap( + 10 * tu.ns, + {"q0_0": 5 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": sympy.Symbol("f_q0_2")}, + {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): sympy.Symbol("g_q0_1_q0_2")}, + ) + + +def test_freq_map_param_names(freq_map): + assert cirq.is_parameterized(freq_map) + assert cirq.parameter_names(freq_map) == {"f_q0_2", "g_q0_1_q0_2"} + + +def test_freq_map_resolve(freq_map): + resolved_freq_map = cirq.resolve_parameters( + freq_map, {"f_q0_2": 6 * tu.GHz, "g_q0_1_q0_2": 7 * tu.MHz} + ) + assert resolved_freq_map == atu.FreqMap( + 10 * tu.ns, + {"q0_0": 5 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 6 * tu.GHz}, + {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 7 * tu.MHz}, + ) + + +@pytest.fixture +def sparse_trajectory() -> ( + list[tuple[tu.Value, dict[str, tu.Value | None], dict[tuple[str, str], tu.Value]]] +): + traj1 = (20 * tu.ns, {"q0_1": 5 * tu.GHz}, {}) + traj2 = (30 * tu.ns, {"q0_2": 8 * tu.GHz}, {}) + traj3 = ( + 40 * tu.ns, + {"q0_0": 8 * tu.GHz, "q0_1": None, "q0_2": None}, + {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, + ) + return [traj1, traj2, traj3] + + +def test_full_traj(sparse_trajectory): + analog_traj = atu.AnalogTrajectory.from_sparse_trajecotry(sparse_trajectory) + assert len(analog_traj.full_trajectory) == 4 + assert analog_traj.full_trajectory[0] == atu.FreqMap( + 0 * tu.ns, + {"q0_0": None, "q0_1": None, "q0_2": None}, + {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, + ) + assert analog_traj.full_trajectory[1] == atu.FreqMap( + 20 * tu.ns, + {"q0_0": None, "q0_1": 5 * tu.GHz, "q0_2": None}, + {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, + ) + assert analog_traj.full_trajectory[2] == atu.FreqMap( + 30 * tu.ns, + {"q0_0": None, "q0_1": 5 * tu.GHz, "q0_2": 8 * tu.GHz}, + {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, + ) + assert analog_traj.full_trajectory[3] == atu.FreqMap( + 40 * tu.ns, + {"q0_0": 8 * tu.GHz, "q0_1": None, "q0_2": None}, + {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, + ) From 3dc317a6c8a4276b96c480382a33a2302aaa4311 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 00:09:16 +0000 Subject: [PATCH 02/14] fix input --- .../analog_experiments/__init__.py | 6 +-- .../analog_trajectory_util.py | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/__init__.py b/cirq-google/cirq_google/experimental/analog_experiments/__init__.py index a3bff376f59..1daf944e4a7 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/__init__.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/__init__.py @@ -15,6 +15,6 @@ """Folder for Running Analog experiments.""" from cirq_google.experimental.analog_experiments.analog_trajectory_util import ( - FreqMap as FreqMap - AnalogTrajectory as AnalogTrajectory -) \ No newline at end of file + FreqMap as FreqMap, + AnalogTrajectory as AnalogTrajectory, +) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index d75092953af..40d1c3a6862 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -16,6 +16,8 @@ from typing import AbstractSet, Any +import matplotlib.pyplot as plt +import numpy as np import attrs import sympy import tunits as tu @@ -161,3 +163,42 @@ def get_full_trajectory_with_resolved_idles( } resolved_trajectory.append(FreqMap(dt, resolved_qubit_freq_dict, g_dict)) # type: ignore return resolved_trajectory + + # TODO make this plot better. + def plot( + self, + idle_freq_map: dict[str, tu.Value] | None = None, + default_idle_freq: tu.Value = 6.5 * tu.GHz, + resolver: cirq.ParamResolverOrSimilarType | None = None, + ): + if idle_freq_map is None: + idle_freq_map = {q: default_idle_freq for q in self.qubits} + full_trajectory_resolved = cirq.resolve_parameters( + self.get_full_trajectory_with_resolved_idles(idle_freq_map), resolver + ) + + plt.figure(figsize=(10, 4)) + times = np.cumsum([step.duration[tu.ns] for step in full_trajectory_resolved]) + ylabels = ["Qubit freq. (GHz)", "Coupling (MHz)"] + + plt.subplot(1, 2, 1) + for qubit_agent in self.qubits: + plt.plot( + times, + [step.qubit_freqs[qubit_agent][tu.GHz] for step in full_trajectory_resolved], + label=qubit_agent, + ) + plt.subplot(1, 2, 2) + for pair_agent in self.pairs: + plt.plot( + times, + [step.couplings[pair_agent][tu.MHz] for step in full_trajectory_resolved], + label=pair_agent, + ) + + for i in range(2): + plt.subplot(1, 2, i + 1) + plt.legend() + plt.xlabel("Time (ns)") + plt.ylabel(ylabels[i]) + plt.tight_layout() From 4dfdf09d4759d49f2109c883a8623c19583c2419 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 21:34:48 +0000 Subject: [PATCH 03/14] Split the util and polish plot func --- .../analog_trajectory_util.py | 87 ++++++++----------- .../analog_experiments/symbol_util.py | 36 ++++++++ .../cirq_google/ops/analog_detune_gates.py | 67 ++++---------- 3 files changed, 91 insertions(+), 99 deletions(-) create mode 100644 cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index 40d1c3a6862..c1c25558865 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -14,17 +14,18 @@ from __future__ import annotations -from typing import AbstractSet, Any +from typing import AbstractSet, TYPE_CHECKING +import attrs import matplotlib.pyplot as plt import numpy as np -import attrs -import sympy import tunits as tu import cirq +from cirq_google.experimental.analog_experiments import symbol_util as su -ValueOrSymbol = tu.Value | sympy.Symbol +if TYPE_CHECKING: + from matplotlib.axes import Axes @attrs.mutable @@ -37,51 +38,35 @@ class FreqMap: couplings: dict describing coupling rates at end of step """ - duration: ValueOrSymbol - qubit_freqs: dict[str, ValueOrSymbol | None] - couplings: dict[tuple[str, str], ValueOrSymbol] + duration: su.ValueOrSymbol + qubit_freqs: dict[str, su.ValueOrSymbol | None] + couplings: dict[tuple[str, str], su.ValueOrSymbol] def _is_parameterized_(self) -> bool: - def _is_parameterized_dict(dict_with_value: dict[str, ValueOrSymbol | None]) -> bool: - return any(cirq.is_parameterized(v) for v in dict_with_value.values()) - return ( cirq.is_parameterized(self.duration) - or _is_parameterized_dict(self.qubit_freqs) - or _is_parameterized_dict(self.couplings) + or su.is_parameterized_dict(self.qubit_freqs) + or su.is_parameterized_dict(self.couplings) ) def _parameter_names_(self) -> AbstractSet[str]: - def dict_param_name(dict_with_value: dict[Any, ValueOrSymbol | None]) -> AbstractSet[str]: - return {v.name for v in dict_with_value.values() if cirq.is_parameterized(v)} - return ( cirq.parameter_names(self.duration) - | dict_param_name(self.qubit_freqs) - | dict_param_name(self.couplings) + | su.dict_param_name(self.qubit_freqs) + | su.dict_param_name(self.couplings) ) def _resolve_parameters_( self, resolver: cirq.ParamResolverOrSimilarType, recursive: bool ) -> FreqMap: - def _direct_symbol_replacement(x, resolver: cirq.ParamResolver): - if isinstance(x, sympy.Symbol): - value = resolver.param_dict.get(x.name, "__NOT_FOUND__") - if value == "__NOT_FOUND__": - value = resolver.param_dict.get(x, "__NOT_FOUND__") - if value != "__NOT_FOUND__": - return value - return x # pragma: no cover - return x - resolver_ = cirq.ParamResolver(resolver) return FreqMap( - duration=_direct_symbol_replacement(self.duration, resolver_), + duration=su.direct_symbol_replacement(self.duration, resolver_), qubit_freqs={ - k: _direct_symbol_replacement(v, resolver_) for k, v in self.qubit_freqs.items() + k: su.direct_symbol_replacement(v, resolver_) for k, v in self.qubit_freqs.items() }, couplings={ - k: _direct_symbol_replacement(v, resolver_) for k, v in self.couplings.items() + k: su.direct_symbol_replacement(v, resolver_) for k, v in self.couplings.items() }, ) @@ -105,7 +90,11 @@ def __init__( def from_sparse_trajecotry( cls, sparse_trajectory: list[ - tuple[tu.Value, dict[str, ValueOrSymbol | None], dict[tuple[str, str], ValueOrSymbol]], + tuple[ + tu.Value, + dict[str, su.ValueOrSymbol | None], + dict[tuple[str, str], su.ValueOrSymbol], + ], ], qubits: list[str] | None = None, pairs: list[tuple[str, str]] | None = None, @@ -157,48 +146,46 @@ def get_full_trajectory_with_resolved_idles( """Insert idle frequencies instead of None in trajectory.""" resolved_trajectory: list[FreqMap] = [] - for dt, qubit_freq_dict, g_dict in self.full_trajectory: - resolved_qubit_freq_dict = { - q: idle_freq_map[q] if f is None else f for q, f in qubit_freq_dict.items() + for freq_map in self.full_trajectory: + resolved_qubit_freqs = { + q: idle_freq_map[q] if f is None else f for q, f in freq_map.qubit_freqs.items() } - resolved_trajectory.append(FreqMap(dt, resolved_qubit_freq_dict, g_dict)) # type: ignore + resolved_trajectory.append(attrs.evolve(freq_map, qubit_freqs=resolved_qubit_freqs)) return resolved_trajectory - # TODO make this plot better. def plot( self, idle_freq_map: dict[str, tu.Value] | None = None, default_idle_freq: tu.Value = 6.5 * tu.GHz, resolver: cirq.ParamResolverOrSimilarType | None = None, - ): + axes: tuple[Axes, Axes] | None = None, + ) -> tuple[Axes, Axes]: if idle_freq_map is None: idle_freq_map = {q: default_idle_freq for q in self.qubits} full_trajectory_resolved = cirq.resolve_parameters( self.get_full_trajectory_with_resolved_idles(idle_freq_map), resolver ) - - plt.figure(figsize=(10, 4)) times = np.cumsum([step.duration[tu.ns] for step in full_trajectory_resolved]) - ylabels = ["Qubit freq. (GHz)", "Coupling (MHz)"] - plt.subplot(1, 2, 1) + if axes is None: + _, axes = plt.subplots(1, 2, figsize=(10, 4)) + for qubit_agent in self.qubits: - plt.plot( + axes[0].plot( times, [step.qubit_freqs[qubit_agent][tu.GHz] for step in full_trajectory_resolved], label=qubit_agent, ) - plt.subplot(1, 2, 2) for pair_agent in self.pairs: - plt.plot( + axes[1].plot( times, [step.couplings[pair_agent][tu.MHz] for step in full_trajectory_resolved], label=pair_agent, ) - for i in range(2): - plt.subplot(1, 2, i + 1) - plt.legend() - plt.xlabel("Time (ns)") - plt.ylabel(ylabels[i]) - plt.tight_layout() + for ax, ylabel in zip(axes, ["Qubit freq. (GHz)", "Coupling (MHz)"]): + ax.set_xlabel("Time (ns)") + ax.set_ylabel(ylabel) + ax.legend() + plt.tight_layout() + return axes diff --git a/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py b/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py new file mode 100644 index 00000000000..084459faff9 --- /dev/null +++ b/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py @@ -0,0 +1,36 @@ +from typing import TypeAlias, AbstractSet + +import cirq +import sympy +import tunits as tu + + +# The gate is intended for the google internal use, hence the typing style +# follows more on the t-unit + symbol instead of float + symbol style. +ValueOrSymbol: TypeAlias = tu.Value | sympy.Basic + +# A sentile for not finding the key in resolver. +NOT_FOUND = "__NOT_FOUND__" + +def direct_symbol_replacement(x, resolver: cirq.ParamResolver): + """A shortcut for value resolution to avoid tu.unit compare with float issue.""" + if isinstance(x, sympy.Symbol): + value = resolver.param_dict.get(x.name, NOT_FOUND) + if value == NOT_FOUND: + value = resolver.param_dict.get(x, NOT_FOUND) + if value != NOT_FOUND: + return value + return x # pragma: no cover + return x + +def dict_param_name(dict_with_value: dict[str, ValueOrSymbol] | None) -> AbstractSet[str]: + """Find the names of all parameterized value in a dictionary.""" + if dict_with_value is None: + return set() + return {v.name for v in dict_with_value.values() if cirq.is_parameterized(v)} + +def is_parameterized_dict(dict_with_value: dict[str, ValueOrSymbol] | None) -> bool: + """Check if any values in the dictionary is parameterized.""" + if dict_with_value is None: + return False # pragma: no cover + return any(cirq.is_parameterized(v) for v in dict_with_value.values()) \ No newline at end of file diff --git a/cirq-google/cirq_google/ops/analog_detune_gates.py b/cirq-google/cirq_google/ops/analog_detune_gates.py index dbd1f095812..6a9d57c5e2f 100644 --- a/cirq-google/cirq_google/ops/analog_detune_gates.py +++ b/cirq-google/cirq_google/ops/analog_detune_gates.py @@ -15,24 +15,14 @@ """Define detuning gates for Analog Experiment usage.""" from __future__ import annotations -from typing import AbstractSet, Any, TYPE_CHECKING, TypeAlias - -import sympy -import tunits as tu +from typing import AbstractSet, Any, TYPE_CHECKING import cirq +from cirq_google.experimental.analog_experiments import symbol_util as su if TYPE_CHECKING: import numpy as np -# The gate is intended for the google internal use, hence the typing style -# follows more on the t-unit + symbol instead of float + symbol style. -ValueOrSymbol: TypeAlias = tu.Value | sympy.Basic -FloatOrSymbol: TypeAlias = float | sympy.Basic - -# A sentile for not finding the key in resolver. -NOT_FOUND = "__NOT_FOUND__" - @cirq.value_equality(approximate=True) class AnalogDetuneQubit(cirq.ops.Gate): @@ -60,12 +50,12 @@ class AnalogDetuneQubit(cirq.ops.Gate): def __init__( self, - length: ValueOrSymbol, - w: ValueOrSymbol, - target_freq: ValueOrSymbol | None = None, - prev_freq: ValueOrSymbol | None = None, - neighbor_coupler_g_dict: dict[str, ValueOrSymbol] | None = None, - prev_neighbor_coupler_g_dict: dict[str, ValueOrSymbol] | None = None, + length: su.ValueOrSymbol, + w: su.ValueOrSymbol, + target_freq: su.ValueOrSymbol | None = None, + prev_freq: su.ValueOrSymbol | None = None, + neighbor_coupler_g_dict: dict[str, su.ValueOrSymbol] | None = None, + prev_neighbor_coupler_g_dict: dict[str, su.ValueOrSymbol] | None = None, linear_rise: bool = True, ): """Inits AnalogDetuneQubit. @@ -97,58 +87,37 @@ def num_qubits(self) -> int: return 1 def _is_parameterized_(self) -> bool: - def _is_parameterized_dict(dict_with_value: dict[str, ValueOrSymbol] | None) -> bool: - if dict_with_value is None: - return False # pragma: no cover - return any(cirq.is_parameterized(v) for v in dict_with_value.values()) - return ( cirq.is_parameterized(self.length) or cirq.is_parameterized(self.w) or cirq.is_parameterized(self.target_freq) or cirq.is_parameterized(self.prev_freq) - or _is_parameterized_dict(self.neighbor_coupler_g_dict) - or _is_parameterized_dict(self.prev_neighbor_coupler_g_dict) + or su.is_parameterized_dict(self.neighbor_coupler_g_dict) + or su.is_parameterized_dict(self.prev_neighbor_coupler_g_dict) ) def _parameter_names_(self) -> AbstractSet[str]: - def dict_param_name(dict_with_value: dict[str, ValueOrSymbol] | None) -> AbstractSet[str]: - if dict_with_value is None: - return set() - return {v.name for v in dict_with_value.values() if cirq.is_parameterized(v)} - return ( cirq.parameter_names(self.length) | cirq.parameter_names(self.w) | cirq.parameter_names(self.target_freq) | cirq.parameter_names(self.prev_freq) - | dict_param_name(self.neighbor_coupler_g_dict) - | dict_param_name(self.prev_neighbor_coupler_g_dict) + | su.dict_param_name(self.neighbor_coupler_g_dict) + | su.dict_param_name(self.prev_neighbor_coupler_g_dict) ) def _resolve_parameters_( self, resolver: cirq.ParamResolverOrSimilarType, recursive: bool ) -> AnalogDetuneQubit: - # A shortcut for value resolution to avoid tu.unit compare with float issue. - def _direct_symbol_replacement(x, resolver: cirq.ParamResolver): - if isinstance(x, sympy.Symbol): - value = resolver.param_dict.get(x.name, NOT_FOUND) - if value == NOT_FOUND: - value = resolver.param_dict.get(x, NOT_FOUND) - if value != NOT_FOUND: - return value - return x # pragma: no cover - return x - resolver_ = cirq.ParamResolver(resolver) return AnalogDetuneQubit( - length=_direct_symbol_replacement(self.length, resolver_), - w=_direct_symbol_replacement(self.w, resolver_), - target_freq=_direct_symbol_replacement(self.target_freq, resolver_), - prev_freq=_direct_symbol_replacement(self.prev_freq, resolver_), + length=su.direct_symbol_replacement(self.length, resolver_), + w=su.direct_symbol_replacement(self.w, resolver_), + target_freq=su.direct_symbol_replacement(self.target_freq, resolver_), + prev_freq=su.direct_symbol_replacement(self.prev_freq, resolver_), neighbor_coupler_g_dict=( { - k: _direct_symbol_replacement(v, resolver_) + k: su.direct_symbol_replacement(v, resolver_) for k, v in self.neighbor_coupler_g_dict.items() } if self.neighbor_coupler_g_dict @@ -156,7 +125,7 @@ def _direct_symbol_replacement(x, resolver: cirq.ParamResolver): ), prev_neighbor_coupler_g_dict=( { - k: _direct_symbol_replacement(v, resolver_) + k: su.direct_symbol_replacement(v, resolver_) for k, v in self.prev_neighbor_coupler_g_dict.items() } if self.prev_neighbor_coupler_g_dict From e6563713cc590ad2aa824b74bd9c30f15020d525 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 13:13:16 -0700 Subject: [PATCH 04/14] 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. --- .../experimental/analog_experiments/symbol_util.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py b/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py index 084459faff9..7085247ecbb 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py @@ -1,9 +1,9 @@ -from typing import TypeAlias, AbstractSet +from typing import AbstractSet, TypeAlias -import cirq import sympy import tunits as tu +import cirq # The gate is intended for the google internal use, hence the typing style # follows more on the t-unit + symbol instead of float + symbol style. @@ -12,6 +12,7 @@ # A sentile for not finding the key in resolver. NOT_FOUND = "__NOT_FOUND__" + def direct_symbol_replacement(x, resolver: cirq.ParamResolver): """A shortcut for value resolution to avoid tu.unit compare with float issue.""" if isinstance(x, sympy.Symbol): @@ -23,14 +24,16 @@ def direct_symbol_replacement(x, resolver: cirq.ParamResolver): return x # pragma: no cover return x + def dict_param_name(dict_with_value: dict[str, ValueOrSymbol] | None) -> AbstractSet[str]: """Find the names of all parameterized value in a dictionary.""" if dict_with_value is None: return set() return {v.name for v in dict_with_value.values() if cirq.is_parameterized(v)} - + + def is_parameterized_dict(dict_with_value: dict[str, ValueOrSymbol] | None) -> bool: """Check if any values in the dictionary is parameterized.""" if dict_with_value is None: return False # pragma: no cover - return any(cirq.is_parameterized(v) for v in dict_with_value.values()) \ No newline at end of file + return any(cirq.is_parameterized(v) for v in dict_with_value.values()) From 9d0feb2c43391f96852ae830f6527a253889965c Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 22:01:39 +0000 Subject: [PATCH 05/14] type and lint --- .../analog_trajectory_util.py | 4 ++-- .../analog_trajectory_util_test.py | 13 ++++++------ .../analog_experiments/symbol_util.py | 20 ++++++++++++++++--- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index c1c25558865..a03454d1cee 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -134,7 +134,7 @@ def from_sparse_trajecotry( } # If no g provided, set equal to previous new_g_dict: dict[tuple[str, str], tu.Value] = { - p: g_dict.get(p, full_trajectory[-1].couplings.get(p)) for p in pairs + p: g_dict.get(p, full_trajectory[-1].couplings.get(p)) for p in pairs # type: ignore[misc] } full_trajectory.append(FreqMap(dt, new_qubit_freq_dict, new_g_dict)) @@ -173,7 +173,7 @@ def plot( for qubit_agent in self.qubits: axes[0].plot( times, - [step.qubit_freqs[qubit_agent][tu.GHz] for step in full_trajectory_resolved], + [step.qubit_freqs[qubit_agent][tu.GHz] for step in full_trajectory_resolved], # type: ignore[misc] label=qubit_agent, ) for pair_agent in self.pairs: diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py index 0e8bee027a1..69218761b2e 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py @@ -45,13 +45,14 @@ def test_freq_map_resolve(freq_map): ) +FreqMapType = tuple[tu.Value, dict[str, tu.Value | None], dict[tuple[str, str], tu.Value]] + + @pytest.fixture -def sparse_trajectory() -> ( - list[tuple[tu.Value, dict[str, tu.Value | None], dict[tuple[str, str], tu.Value]]] -): - traj1 = (20 * tu.ns, {"q0_1": 5 * tu.GHz}, {}) - traj2 = (30 * tu.ns, {"q0_2": 8 * tu.GHz}, {}) - traj3 = ( +def sparse_trajectory() -> list[FreqMapType]: + traj1: FreqMapType = (20 * tu.ns, {"q0_1": 5 * tu.GHz}, {}) + traj2: FreqMapType = (30 * tu.ns, {"q0_2": 8 * tu.GHz}, {}) + traj3: FreqMapType = ( 40 * tu.ns, {"q0_0": 8 * tu.GHz, "q0_1": None, "q0_2": None}, {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, diff --git a/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py b/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py index 7085247ecbb..2e08ff7f61c 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py @@ -1,4 +1,18 @@ -from typing import AbstractSet, TypeAlias +# Copyright 2019 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. + +from typing import AbstractSet, Any, TypeAlias import sympy import tunits as tu @@ -25,14 +39,14 @@ def direct_symbol_replacement(x, resolver: cirq.ParamResolver): return x -def dict_param_name(dict_with_value: dict[str, ValueOrSymbol] | None) -> AbstractSet[str]: +def dict_param_name(dict_with_value: dict[Any, ValueOrSymbol] | None) -> AbstractSet[str]: """Find the names of all parameterized value in a dictionary.""" if dict_with_value is None: return set() return {v.name for v in dict_with_value.values() if cirq.is_parameterized(v)} -def is_parameterized_dict(dict_with_value: dict[str, ValueOrSymbol] | None) -> bool: +def is_parameterized_dict(dict_with_value: dict[Any, ValueOrSymbol] | None) -> bool: """Check if any values in the dictionary is parameterized.""" if dict_with_value is None: return False # pragma: no cover From 771f39aea2bc89d1d28629376657c39a304a4cb9 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 22:03:27 +0000 Subject: [PATCH 06/14] coverage --- .../experimental/analog_experiments/analog_trajectory_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index a03454d1cee..bb7c481993c 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -159,7 +159,7 @@ def plot( default_idle_freq: tu.Value = 6.5 * tu.GHz, resolver: cirq.ParamResolverOrSimilarType | None = None, axes: tuple[Axes, Axes] | None = None, - ) -> tuple[Axes, Axes]: + ) -> tuple[Axes, Axes]: # pragma: no cover if idle_freq_map is None: idle_freq_map = {q: default_idle_freq for q in self.qubits} full_trajectory_resolved = cirq.resolve_parameters( From 0b9fb1981d87096bee4ffffc901684b07eb57bcb Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 22:19:42 +0000 Subject: [PATCH 07/14] fix --- .../analog_experiments/analog_trajectory_util.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index bb7c481993c..17c43bc8a44 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -165,6 +165,14 @@ def plot( full_trajectory_resolved = cirq.resolve_parameters( self.get_full_trajectory_with_resolved_idles(idle_freq_map), resolver ) + unresolved_param_names = set().union( + *[cirq.parameter_names(freq_map) for freq_map in full_trajectory_resolved] + ) + if unresolved_param_names: + raise ValueError( + f"There are some parameters {unresolved_param_names} not resolved." + ) + times = np.cumsum([step.duration[tu.ns] for step in full_trajectory_resolved]) if axes is None: @@ -173,7 +181,7 @@ def plot( for qubit_agent in self.qubits: axes[0].plot( times, - [step.qubit_freqs[qubit_agent][tu.GHz] for step in full_trajectory_resolved], # type: ignore[misc] + [step.qubit_freqs[qubit_agent][tu.GHz] for step in full_trajectory_resolved], # type: ignore[index] label=qubit_agent, ) for pair_agent in self.pairs: From 8c9e0c6596355e8822eaeb411045bbdb120e0990 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 22:25:08 +0000 Subject: [PATCH 08/14] format --- .../experimental/analog_experiments/analog_trajectory_util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index 17c43bc8a44..3444e6a4a66 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -169,9 +169,7 @@ def plot( *[cirq.parameter_names(freq_map) for freq_map in full_trajectory_resolved] ) if unresolved_param_names: - raise ValueError( - f"There are some parameters {unresolved_param_names} not resolved." - ) + raise ValueError(f"There are some parameters {unresolved_param_names} not resolved.") times = np.cumsum([step.duration[tu.ns] for step in full_trajectory_resolved]) From 036fa3086bca4cde05c330fb4c29c86b90b03f74 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 22:26:19 +0000 Subject: [PATCH 09/14] fix typo --- .../experimental/analog_experiments/analog_trajectory_util.py | 2 +- .../analog_experiments/analog_trajectory_util_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index 3444e6a4a66..b25e26c320a 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -87,7 +87,7 @@ def __init__( self.pairs = pairs @classmethod - def from_sparse_trajecotry( + def from_sparse_trajectory( cls, sparse_trajectory: list[ tuple[ diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py index 69218761b2e..2bf0476c3f2 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py @@ -61,7 +61,7 @@ def sparse_trajectory() -> list[FreqMapType]: def test_full_traj(sparse_trajectory): - analog_traj = atu.AnalogTrajectory.from_sparse_trajecotry(sparse_trajectory) + analog_traj = atu.AnalogTrajectory.from_sparse_trajectory(sparse_trajectory) assert len(analog_traj.full_trajectory) == 4 assert analog_traj.full_trajectory[0] == atu.FreqMap( 0 * tu.ns, From cc313ae13aeb9410956fdc57554b0ceca4c984d8 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Wed, 25 Jun 2025 23:12:20 +0000 Subject: [PATCH 10/14] test_get_full_trajectory_with_resolved_idles --- .../analog_trajectory_util_test.py | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py index 2bf0476c3f2..6324d067f19 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py @@ -29,12 +29,12 @@ def freq_map() -> atu.FreqMap: ) -def test_freq_map_param_names(freq_map): +def test_freq_map_param_names(freq_map: atu.FreqMap) -> None: assert cirq.is_parameterized(freq_map) assert cirq.parameter_names(freq_map) == {"f_q0_2", "g_q0_1_q0_2"} -def test_freq_map_resolve(freq_map): +def test_freq_map_resolve(freq_map: atu.FreqMap) -> None: resolved_freq_map = cirq.resolve_parameters( freq_map, {"f_q0_2": 6 * tu.GHz, "g_q0_1_q0_2": 7 * tu.MHz} ) @@ -60,7 +60,7 @@ def sparse_trajectory() -> list[FreqMapType]: return [traj1, traj2, traj3] -def test_full_traj(sparse_trajectory): +def test_full_traj(sparse_trajectory: list[FreqMapType]) -> None: analog_traj = atu.AnalogTrajectory.from_sparse_trajectory(sparse_trajectory) assert len(analog_traj.full_trajectory) == 4 assert analog_traj.full_trajectory[0] == atu.FreqMap( @@ -83,3 +83,33 @@ def test_full_traj(sparse_trajectory): {"q0_0": 8 * tu.GHz, "q0_1": None, "q0_2": None}, {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, ) + + +def test_get_full_trajectory_with_resolved_idles(sparse_trajectory: list[FreqMapType]) -> None: + + analog_traj = atu.AnalogTrajectory.from_sparse_trajectory(sparse_trajectory) + resolved_full_traj = analog_traj.get_full_trajectory_with_resolved_idles( + {"q0_0": 5 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 7 * tu.GHz} + ) + + assert len(resolved_full_traj) == 4 + assert resolved_full_traj[0] == atu.FreqMap( + 0 * tu.ns, + {"q0_0": 5 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 7 * tu.GHz}, + {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, + ) + assert resolved_full_traj[1] == atu.FreqMap( + 20 * tu.ns, + {"q0_0": 5 * tu.GHz, "q0_1": 5 * tu.GHz, "q0_2": 7 * tu.GHz}, + {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, + ) + assert resolved_full_traj[2] == atu.FreqMap( + 30 * tu.ns, + {"q0_0": 5 * tu.GHz, "q0_1": 5 * tu.GHz, "q0_2": 8 * tu.GHz}, + {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, + ) + assert resolved_full_traj[3] == atu.FreqMap( + 40 * tu.ns, + {"q0_0": 8 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 7 * tu.GHz}, + {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, + ) From 951259ca5efe94077d795f73386ff7e9f9356f35 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Thu, 26 Jun 2025 00:16:16 +0000 Subject: [PATCH 11/14] add more check --- .../analog_experiments/analog_trajectory_util.py | 2 +- .../analog_trajectory_util_test.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index b25e26c320a..c9c5b6303df 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -159,7 +159,7 @@ def plot( default_idle_freq: tu.Value = 6.5 * tu.GHz, resolver: cirq.ParamResolverOrSimilarType | None = None, axes: tuple[Axes, Axes] | None = None, - ) -> tuple[Axes, Axes]: # pragma: no cover + ) -> tuple[Axes, Axes]: if idle_freq_map is None: idle_freq_map = {q: default_idle_freq for q in self.qubits} full_trajectory_resolved = cirq.resolve_parameters( diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py index 6324d067f19..75fa371385f 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py @@ -113,3 +113,19 @@ def test_get_full_trajectory_with_resolved_idles(sparse_trajectory: list[FreqMap {"q0_0": 8 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 7 * tu.GHz}, {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, ) + + +def test_plot_with_unresolved_parameters(): + traj1: FreqMapType = (20 * tu.ns, {"q0_1": sympy.Symbol("qf")}, {}) + traj2: FreqMapType = (sympy.Symbol("t"), {"q0_2": 8 * tu.GHz}, {}) + analog_traj = atu.AnalogTrajectory.from_sparse_trajectory([traj1, traj2]) + + with pytest.raises(ValueError): + analog_traj.plot() + + +def test_analog_traj_plot(): + traj1: FreqMapType = (20 * tu.ns, {"q0_1": sympy.Symbol("qf")}, {}) + traj2: FreqMapType = (sympy.Symbol("t"), {"q0_2": 8 * tu.GHz}, {}) + analog_traj = atu.AnalogTrajectory.from_sparse_trajectory([traj1, traj2]) + analog_traj.plot(resolver={"t": 10 * tu.ns, "qf": 5 * tu.GHz}) From 3f4412f5b5a7ee7f1e9f5738bda0910a82e90e9f Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Thu, 26 Jun 2025 17:26:16 +0000 Subject: [PATCH 12/14] coverage --- .../analog_experiments/analog_trajectory_util_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py index 75fa371385f..cedb6acefca 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py @@ -125,7 +125,7 @@ def test_plot_with_unresolved_parameters(): def test_analog_traj_plot(): - traj1: FreqMapType = (20 * tu.ns, {"q0_1": sympy.Symbol("qf")}, {}) + traj1: FreqMapType = (5 * tu.ns, {"q0_1": sympy.Symbol("qf")}, {("q0_0", "q0_1"): 2 * tu.MHz}) traj2: FreqMapType = (sympy.Symbol("t"), {"q0_2": 8 * tu.GHz}, {}) analog_traj = atu.AnalogTrajectory.from_sparse_trajectory([traj1, traj2]) analog_traj.plot(resolver={"t": 10 * tu.ns, "qf": 5 * tu.GHz}) From a18a0359436c97f468549b6a33ea14f94ec8fd47 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Thu, 26 Jun 2025 18:03:54 +0000 Subject: [PATCH 13/14] move symbol_util and rename --- .../analog_experiments/__init__.py | 4 +-- .../analog_trajectory_util.py | 28 +++++++++++-------- .../analog_trajectory_util_test.py | 28 +++++++++---------- .../cirq_google/ops/analog_detune_gates.py | 2 +- .../symbol_util.py | 2 +- 5 files changed, 34 insertions(+), 30 deletions(-) rename cirq-google/cirq_google/{experimental/analog_experiments => study}/symbol_util.py (98%) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/__init__.py b/cirq-google/cirq_google/experimental/analog_experiments/__init__.py index 1daf944e4a7..13bda79ea9b 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/__init__.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019 The Cirq Developers +# Copyright 2025 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. @@ -15,6 +15,6 @@ """Folder for Running Analog experiments.""" from cirq_google.experimental.analog_experiments.analog_trajectory_util import ( - FreqMap as FreqMap, + FrequencyMap as FrequencyMap, AnalogTrajectory as AnalogTrajectory, ) diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py index c9c5b6303df..4635aa4370e 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util.py @@ -1,4 +1,4 @@ -# Copyright 2019 The Cirq Developers +# Copyright 2025 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. @@ -22,14 +22,14 @@ import tunits as tu import cirq -from cirq_google.experimental.analog_experiments import symbol_util as su +from cirq_google.study import symbol_util as su if TYPE_CHECKING: from matplotlib.axes import Axes @attrs.mutable -class FreqMap: +class FrequencyMap: """Object containing information about the step to a new analog Hamiltonian. Attributes: @@ -58,9 +58,9 @@ def _parameter_names_(self) -> AbstractSet[str]: def _resolve_parameters_( self, resolver: cirq.ParamResolverOrSimilarType, recursive: bool - ) -> FreqMap: + ) -> FrequencyMap: resolver_ = cirq.ParamResolver(resolver) - return FreqMap( + return FrequencyMap( duration=su.direct_symbol_replacement(self.duration, resolver_), qubit_freqs={ k: su.direct_symbol_replacement(v, resolver_) for k, v in self.qubit_freqs.items() @@ -80,7 +80,11 @@ class AnalogTrajectory: """ def __init__( - self, *, full_trajectory: list[FreqMap], qubits: list[str], pairs: list[tuple[str, str]] + self, + *, + full_trajectory: list[FrequencyMap], + qubits: list[str], + pairs: list[tuple[str, str]], ): self.full_trajectory = full_trajectory self.qubits = qubits @@ -102,7 +106,7 @@ def from_sparse_trajectory( """Construct AnalogTrajectory from sparse trajectory. Args: - sparse_trajectory: A list of tuples, where each tuple defines a segment of `FreqMap` + sparse_trajectory: A list of tuples, where each tuple defines a `FrequencyMap` and contains three elements: (duration, qubit_freqs, coupling_strengths). `duration` is a tunits value, `qubit_freqs` is a dictionary mapping qubit strings to detuning frequencies, and `coupling_strengths` is a dictionary mapping qubit @@ -122,10 +126,10 @@ def from_sparse_trajectory( qubits = list(set(qubits_in_traj)) pairs = list(set(pairs_in_traj)) - full_trajectory: list[FreqMap] = [] + full_trajectory: list[FrequencyMap] = [] init_qubit_freq_dict: dict[str, tu.Value | None] = {q: None for q in qubits} init_g_dict: dict[tuple[str, str], tu.Value] = {p: 0 * tu.MHz for p in pairs} - full_trajectory.append(FreqMap(0 * tu.ns, init_qubit_freq_dict, init_g_dict)) + full_trajectory.append(FrequencyMap(0 * tu.ns, init_qubit_freq_dict, init_g_dict)) for dt, qubit_freq_dict, g_dict in sparse_trajectory: # If no freq provided, set equal to previous @@ -137,15 +141,15 @@ def from_sparse_trajectory( p: g_dict.get(p, full_trajectory[-1].couplings.get(p)) for p in pairs # type: ignore[misc] } - full_trajectory.append(FreqMap(dt, new_qubit_freq_dict, new_g_dict)) + full_trajectory.append(FrequencyMap(dt, new_qubit_freq_dict, new_g_dict)) return cls(full_trajectory=full_trajectory, qubits=qubits, pairs=pairs) def get_full_trajectory_with_resolved_idles( self, idle_freq_map: dict[str, tu.Value] - ) -> list[FreqMap]: + ) -> list[FrequencyMap]: """Insert idle frequencies instead of None in trajectory.""" - resolved_trajectory: list[FreqMap] = [] + resolved_trajectory: list[FrequencyMap] = [] for freq_map in self.full_trajectory: resolved_qubit_freqs = { q: idle_freq_map[q] if f is None else f for q, f in freq_map.qubit_freqs.items() diff --git a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py index cedb6acefca..e57dfd58411 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py +++ b/cirq-google/cirq_google/experimental/analog_experiments/analog_trajectory_util_test.py @@ -1,4 +1,4 @@ -# Copyright 2019 The Cirq Developers +# Copyright 2025 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. @@ -21,24 +21,24 @@ @pytest.fixture -def freq_map() -> atu.FreqMap: - return atu.FreqMap( +def freq_map() -> atu.FrequencyMap: + return atu.FrequencyMap( 10 * tu.ns, {"q0_0": 5 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": sympy.Symbol("f_q0_2")}, {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): sympy.Symbol("g_q0_1_q0_2")}, ) -def test_freq_map_param_names(freq_map: atu.FreqMap) -> None: +def test_freq_map_param_names(freq_map: atu.FrequencyMap) -> None: assert cirq.is_parameterized(freq_map) assert cirq.parameter_names(freq_map) == {"f_q0_2", "g_q0_1_q0_2"} -def test_freq_map_resolve(freq_map: atu.FreqMap) -> None: +def test_freq_map_resolve(freq_map: atu.FrequencyMap) -> None: resolved_freq_map = cirq.resolve_parameters( freq_map, {"f_q0_2": 6 * tu.GHz, "g_q0_1_q0_2": 7 * tu.MHz} ) - assert resolved_freq_map == atu.FreqMap( + assert resolved_freq_map == atu.FrequencyMap( 10 * tu.ns, {"q0_0": 5 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 6 * tu.GHz}, {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 7 * tu.MHz}, @@ -63,22 +63,22 @@ def sparse_trajectory() -> list[FreqMapType]: def test_full_traj(sparse_trajectory: list[FreqMapType]) -> None: analog_traj = atu.AnalogTrajectory.from_sparse_trajectory(sparse_trajectory) assert len(analog_traj.full_trajectory) == 4 - assert analog_traj.full_trajectory[0] == atu.FreqMap( + assert analog_traj.full_trajectory[0] == atu.FrequencyMap( 0 * tu.ns, {"q0_0": None, "q0_1": None, "q0_2": None}, {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, ) - assert analog_traj.full_trajectory[1] == atu.FreqMap( + assert analog_traj.full_trajectory[1] == atu.FrequencyMap( 20 * tu.ns, {"q0_0": None, "q0_1": 5 * tu.GHz, "q0_2": None}, {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, ) - assert analog_traj.full_trajectory[2] == atu.FreqMap( + assert analog_traj.full_trajectory[2] == atu.FrequencyMap( 30 * tu.ns, {"q0_0": None, "q0_1": 5 * tu.GHz, "q0_2": 8 * tu.GHz}, {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, ) - assert analog_traj.full_trajectory[3] == atu.FreqMap( + assert analog_traj.full_trajectory[3] == atu.FrequencyMap( 40 * tu.ns, {"q0_0": 8 * tu.GHz, "q0_1": None, "q0_2": None}, {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, @@ -93,22 +93,22 @@ def test_get_full_trajectory_with_resolved_idles(sparse_trajectory: list[FreqMap ) assert len(resolved_full_traj) == 4 - assert resolved_full_traj[0] == atu.FreqMap( + assert resolved_full_traj[0] == atu.FrequencyMap( 0 * tu.ns, {"q0_0": 5 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 7 * tu.GHz}, {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, ) - assert resolved_full_traj[1] == atu.FreqMap( + assert resolved_full_traj[1] == atu.FrequencyMap( 20 * tu.ns, {"q0_0": 5 * tu.GHz, "q0_1": 5 * tu.GHz, "q0_2": 7 * tu.GHz}, {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, ) - assert resolved_full_traj[2] == atu.FreqMap( + assert resolved_full_traj[2] == atu.FrequencyMap( 30 * tu.ns, {"q0_0": 5 * tu.GHz, "q0_1": 5 * tu.GHz, "q0_2": 8 * tu.GHz}, {("q0_0", "q0_1"): 0 * tu.MHz, ("q0_1", "q0_2"): 0 * tu.MHz}, ) - assert resolved_full_traj[3] == atu.FreqMap( + assert resolved_full_traj[3] == atu.FrequencyMap( 40 * tu.ns, {"q0_0": 8 * tu.GHz, "q0_1": 6 * tu.GHz, "q0_2": 7 * tu.GHz}, {("q0_0", "q0_1"): 5 * tu.MHz, ("q0_1", "q0_2"): 8 * tu.MHz}, diff --git a/cirq-google/cirq_google/ops/analog_detune_gates.py b/cirq-google/cirq_google/ops/analog_detune_gates.py index 6a9d57c5e2f..86f631705d5 100644 --- a/cirq-google/cirq_google/ops/analog_detune_gates.py +++ b/cirq-google/cirq_google/ops/analog_detune_gates.py @@ -18,7 +18,7 @@ from typing import AbstractSet, Any, TYPE_CHECKING import cirq -from cirq_google.experimental.analog_experiments import symbol_util as su +from cirq_google.study import symbol_util as su if TYPE_CHECKING: import numpy as np diff --git a/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py b/cirq-google/cirq_google/study/symbol_util.py similarity index 98% rename from cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py rename to cirq-google/cirq_google/study/symbol_util.py index 2e08ff7f61c..b2360543201 100644 --- a/cirq-google/cirq_google/experimental/analog_experiments/symbol_util.py +++ b/cirq-google/cirq_google/study/symbol_util.py @@ -1,4 +1,4 @@ -# Copyright 2019 The Cirq Developers +# Copyright 2025 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. From 66d4904e39917e9ebddba0e6372f5d58efd717c6 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Thu, 26 Jun 2025 18:21:11 +0000 Subject: [PATCH 14/14] Add more test for symbol_util --- .../cirq_google/study/symbol_util_test.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 cirq-google/cirq_google/study/symbol_util_test.py diff --git a/cirq-google/cirq_google/study/symbol_util_test.py b/cirq-google/cirq_google/study/symbol_util_test.py new file mode 100644 index 00000000000..a6ef9d50b4d --- /dev/null +++ b/cirq-google/cirq_google/study/symbol_util_test.py @@ -0,0 +1,48 @@ +# Copyright 2025 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 pytest +import sympy +import tunits as tu + +import cirq +from cirq_google.study import symbol_util as su + + +def test_dict_param_name(): + d = {"a": 54, "b": sympy.Symbol("t"), "c": sympy.Symbol("t"), "d": "sd"} + + assert su.dict_param_name(None) == set() + assert su.dict_param_name(d) == {"t"} + + +@pytest.mark.parametrize( + "d,expected", + [ + (None, False), + ({}, False), + ({"a": 50}, False), + ({"a": 54, "b": sympy.Symbol("t"), "c": sympy.Symbol("t"), "d": "sd"}, True), + ], +) +def test_is_parameterized_dict(d, expected): + assert su.is_parameterized_dict(d) == expected + + +def test_direct_symbol_replacement(): + value_list = [sympy.Symbol("t"), sympy.Symbol("v"), sympy.Symbol("z"), 123, "fd"] + resolver = cirq.ParamResolver({"t": 5 * tu.ns, sympy.Symbol("v"): 8 * tu.GHz}) + value_resolved = [su.direct_symbol_replacement(v, resolver) for v in value_list] + + assert value_resolved == [5 * tu.ns, 8 * tu.GHz, sympy.Symbol("z"), 123, "fd"]