diff --git a/cirq-google/cirq_google/__init__.py b/cirq-google/cirq_google/__init__.py index f5af2da5b4e..91250d859d3 100644 --- a/cirq-google/cirq_google/__init__.py +++ b/cirq-google/cirq_google/__init__.py @@ -53,6 +53,7 @@ ) from cirq_google.ops import ( + AnalogDetuneCouplerOnly as AnalogDetuneCouplerOnly, AnalogDetuneQubit as AnalogDetuneQubit, CalibrationTag as CalibrationTag, Coupler as Coupler, diff --git a/cirq-google/cirq_google/json_resolver_cache.py b/cirq-google/cirq_google/json_resolver_cache.py index 0345c1efaca..b8fdd22e8ac 100644 --- a/cirq-google/cirq_google/json_resolver_cache.py +++ b/cirq-google/cirq_google/json_resolver_cache.py @@ -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, diff --git a/cirq-google/cirq_google/json_test_data/AnalogDetuneCouplerOnly.json b/cirq-google/cirq_google/json_test_data/AnalogDetuneCouplerOnly.json new file mode 100644 index 00000000000..a32c846c65a --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/AnalogDetuneCouplerOnly.json @@ -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 +} \ No newline at end of file diff --git a/cirq-google/cirq_google/json_test_data/AnalogDetuneCouplerOnly.repr b/cirq-google/cirq_google/json_test_data/AnalogDetuneCouplerOnly.repr new file mode 100644 index 00000000000..7ce79f2bf70 --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/AnalogDetuneCouplerOnly.repr @@ -0,0 +1 @@ +cirq_google.AnalogDetuneCouplerOnly(length=sympy.Symbol('l'), w=10, g_0=5, g_max=20) \ No newline at end of file diff --git a/cirq-google/cirq_google/ops/__init__.py b/cirq-google/cirq_google/ops/__init__.py index 3102dabaf5e..13a70bbe5f8 100644 --- a/cirq-google/cirq_google/ops/__init__.py +++ b/cirq-google/cirq_google/ops/__init__.py @@ -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 diff --git a/cirq-google/cirq_google/ops/analog_detune_gates.py b/cirq-google/cirq_google/ops/analog_detune_gates.py index 86f631705d5..41157cbdccd 100644 --- a/cirq-google/cirq_google/ops/analog_detune_gates.py +++ b/cirq-google/cirq_google/ops/analog_detune_gates.py @@ -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: @@ -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. @@ -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', + ], + ) diff --git a/cirq-google/cirq_google/ops/analog_detune_gates_test.py b/cirq-google/cirq_google/ops/analog_detune_gates_test.py index 46306651f3e..f77fdc4b9ae 100644 --- a/cirq-google/cirq_google/ops/analog_detune_gates_test.py +++ b/cirq-google/cirq_google/ops/analog_detune_gates_test.py @@ -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 @@ -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))