Repository: quantumlib/Cirq

Issue:
Consolidate SQRT_ISWAP gate definitions
**Is your feature request related to a use case or problem? Please describe.**

Recently, #4213 added (will add) the SQRT_ISWAP=ISWAP**0.5 gate to the core set of gates.  Several places throughout Cirq define it and one place even defines it as [ISWAP**-0.5](https://github.com/quantumlib/Cirq/blob/2f8326c5f71006ff17c9353e30d35cd9e11aca2a/cirq-google/cirq_google/calibration/xeb_wrapper_test.py) (see https://github.com/quantumlib/Cirq/search?q=SQRT_ISWAP for the full list) which could lead to inconsistencies or bugs.

**Describe the solution you'd like**
These duplicate definitions `SQRT_ISWAP = ISWAP**0.5` should be removed and all uses of the gate should be updated to `ops.SQRT_ISWAP` or `cirq.SQRT_ISWAP` as appropriate.  https://github.com/quantumlib/Cirq/search?q=SQRT_ISWAP lists all places that need to be updated.

**What is the urgency from your perspective for this issue? Is it blocking important work?**
<!-- Please choose one and remove the others -->

P2 - we should do it in the next couple of quarters 

<!-- [optional] additional comment / context -->


Discussion:
Hi @cduck, I am a beginner learning to contribute to open source. I am not sure if I completely understand the issue, but I would like to try solving this issue. I might be able to work on it with some guidance.
Correct me if I am wrong, for example, [here](https://github.com/quantumlib/Cirq/blob/0311e1746acdb4519f334c421a90e15ff3035891/cirq-google/cirq_google/optimizers/convert_to_sqrt_iswap.py), are we trying to remove
https://github.com/quantumlib/Cirq/blob/0311e1746acdb4519f334c421a90e15ff3035891/cirq-google/cirq_google/optimizers/convert_to_sqrt_iswap.py#L21
and use `cirq.SQRT_ISWAP` instead of `SQRT_ISWAP` in every single occurrence of `SQRT_ISWAP`?

And, are we just considering the cases where we have `SQRT_ISWAP = ISWAP**0.5` or  `SQRT_ISWAP = ISWAP**-0.5`?

That's correct.  Within cirq-core, use `ops.SQRT_ISWAP` and use `cirq.SQRT_ISWAP` elsewhere (`cirq.ops` is used to avoid circular dependencies within cirq-core).  If the file uses `ISWAP**-0.5`, this is equivalent to `cirq.SQRT_ISWAP**-1` (i.e. `x**-0.5 == (x**0.5)**-1`).
Thanks for the clarification. I will try to solve and issue a PR. I will let you know if I have any questions. :)  
I like the idea of consolidating these definitions. I'd suggest that we define both `SQRT_ISWAP = ISWAP ** 0.5` and `SQRT_ISWAP_INV = ISWAP ** -0.5`.
@cduck, Can you please guide me on how can I perform the test? I couldn't find the procedure in `CONTRIBUTING.md` file.
Perform what test?  Maybe @balopat can advise on current continuous integration advice.
Maybe I can issue a PR, and you can suggest me some changes if I have do something different. Also, I realized need to have my email signature in order to have my cla approved.

Resolving patch:
diff --git a/cirq-core/cirq/experiments/xeb_fitting.py b/cirq-core/cirq/experiments/xeb_fitting.py
--- a/cirq-core/cirq/experiments/xeb_fitting.py
+++ b/cirq-core/cirq/experiments/xeb_fitting.py
@@ -40,8 +40,6 @@
 THETA_SYMBOL, ZETA_SYMBOL, CHI_SYMBOL, GAMMA_SYMBOL, PHI_SYMBOL = sympy.symbols(
     'theta zeta chi gamma phi'
 )
-SQRT_ISWAP = ops.ISWAP ** 0.5
-
 
 # TODO(#3388) Add documentation for Raises.
 # pylint: disable=missing-raises-doc
@@ -163,10 +161,10 @@ def phased_fsim_angles_from_gate(gate: 'cirq.Gate') -> Dict[str, float]:
         'gamma_default': 0.0,
         'phi_default': 0.0,
     }
-    if gate == SQRT_ISWAP:
+    if gate == ops.SQRT_ISWAP:
         defaults['theta_default'] = -np.pi / 4
         return defaults
-    if gate == SQRT_ISWAP ** -1:
+    if gate == ops.SQRT_ISWAP_INV:
         defaults['theta_default'] = np.pi / 4
         return defaults
     if isinstance(gate, ops.FSimGate):
@@ -319,7 +317,9 @@ def _json_dict_(self):
 
 def SqrtISwapXEBOptions(*args, **kwargs):
     """Options for calibrating a sqrt(ISWAP) gate using XEB."""
-    return XEBPhasedFSimCharacterizationOptions(*args, **kwargs).with_defaults_from_gate(SQRT_ISWAP)
+    return XEBPhasedFSimCharacterizationOptions(*args, **kwargs).with_defaults_from_gate(
+        ops.SQRT_ISWAP
+    )
 
 
 def parameterize_circuit(
diff --git a/cirq-google/cirq_google/optimizers/convert_to_sqrt_iswap.py b/cirq-google/cirq_google/optimizers/convert_to_sqrt_iswap.py
--- a/cirq-google/cirq_google/optimizers/convert_to_sqrt_iswap.py
+++ b/cirq-google/cirq_google/optimizers/convert_to_sqrt_iswap.py
@@ -18,10 +18,6 @@
 
 import cirq
 
-SQRT_ISWAP = cirq.ISWAP ** 0.5
-SQRT_ISWAP_INV = cirq.ISWAP ** -0.5
-
-
 # TODO: Combine this with the equivalent functions in google/gate_set.py
 # Or better yet, write a proper gate set so we don't need this in two places.
 # Github issue: https://github.com/quantumlib/Cirq/issues/2970
@@ -207,9 +203,9 @@ def cphase_to_sqrt_iswap(a, b, turns):
     yield cirq.rz(sign * 0.5 * theta_prime).on(b)
     yield cirq.rx(xi).on(a)
     yield cirq.X(b) ** (-sign * 0.5)
-    yield SQRT_ISWAP_INV(a, b)
+    yield cirq.SQRT_ISWAP_INV(a, b)
     yield cirq.rx(-2 * phi).on(a)
-    yield SQRT_ISWAP(a, b)
+    yield cirq.SQRT_ISWAP(a, b)
 
     yield cirq.rx(xi).on(a)
     yield cirq.X(b) ** (sign * 0.5)
@@ -238,9 +234,9 @@ def cphase_symbols_to_sqrt_iswap(a, b, turns):
     yield cirq.rz(sign * 0.5 * theta_prime).on(b)
     yield cirq.rx(xi).on(a)
     yield cirq.X(b) ** (-sign * 0.5)
-    yield SQRT_ISWAP_INV(a, b)
+    yield cirq.SQRT_ISWAP_INV(a, b)
     yield cirq.rx(-2 * phi).on(a)
-    yield SQRT_ISWAP(a, b)
+    yield cirq.SQRT_ISWAP(a, b)
     yield cirq.rx(xi).on(a)
     yield cirq.X(b) ** (sign * 0.5)
 
@@ -261,10 +257,10 @@ def iswap_to_sqrt_iswap(a, b, turns):
     """
     yield cirq.Z(a) ** 0.75
     yield cirq.Z(b) ** 0.25
-    yield SQRT_ISWAP_INV(a, b)
+    yield cirq.SQRT_ISWAP_INV(a, b)
     yield cirq.Z(a) ** (-turns / 2 + 1)
     yield cirq.Z(b) ** (turns / 2)
-    yield SQRT_ISWAP_INV(a, b)
+    yield cirq.SQRT_ISWAP_INV(a, b)
     yield cirq.Z(a) ** 0.25
     yield cirq.Z(b) ** -0.25
 
@@ -290,13 +286,13 @@ def swap_to_sqrt_iswap(a, b, turns):
         # Decomposition for cirq.SWAP
         yield cirq.Y(a) ** 0.5
         yield cirq.Y(b) ** 0.5
-        yield SQRT_ISWAP(a, b)
+        yield cirq.SQRT_ISWAP(a, b)
         yield cirq.Y(a) ** -0.5
         yield cirq.Y(b) ** -0.5
-        yield SQRT_ISWAP(a, b)
+        yield cirq.SQRT_ISWAP(a, b)
         yield cirq.X(a) ** -0.5
         yield cirq.X(b) ** -0.5
-        yield SQRT_ISWAP(a, b)
+        yield cirq.SQRT_ISWAP(a, b)
         yield cirq.X(a) ** 0.5
         yield cirq.X(b) ** 0.5
         return


Test patch:
diff --git a/cirq-core/cirq/experiments/xeb_fitting_test.py b/cirq-core/cirq/experiments/xeb_fitting_test.py
--- a/cirq-core/cirq/experiments/xeb_fitting_test.py
+++ b/cirq-core/cirq/experiments/xeb_fitting_test.py
@@ -23,7 +23,6 @@
 import cirq
 import cirq.experiments.random_quantum_circuit_generation as rqcg
 from cirq.experiments.xeb_fitting import (
-    SQRT_ISWAP,
     benchmark_2q_xeb_fidelities,
     parameterize_circuit,
     SqrtISwapXEBOptions,
@@ -42,7 +41,7 @@ def circuits_cycle_depths_sampled_df():
     q0, q1 = cirq.LineQubit.range(2)
     circuits = [
         rqcg.random_rotations_between_two_qubit_circuit(
-            q0, q1, depth=50, two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP(a, b), seed=52
+            q0, q1, depth=50, two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b), seed=52
         )
         for _ in range(2)
     ]
@@ -167,7 +166,7 @@ def _summary_stats(row):
     pd.testing.assert_frame_equal(df_old, df)
 
 
-@pytest.mark.parametrize('gate', [SQRT_ISWAP, cirq.FSimGate(np.pi / 4, 0)])
+@pytest.mark.parametrize('gate', [cirq.SQRT_ISWAP, cirq.FSimGate(np.pi / 4, 0)])
 def test_parameterize_phased_fsim_circuit(gate):
     q0, q1 = cirq.LineQubit.range(2)
     circuit = rqcg.random_rotations_between_two_qubit_circuit(
@@ -215,7 +214,7 @@ def test_characterize_phased_fsim_parameters_with_xeb():
             q0,
             q1,
             depth=20,
-            two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP(a, b),
+            two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b),
             seed=rs,
         )
         for _ in range(2)
diff --git a/cirq-core/cirq/experiments/xeb_sampling_test.py b/cirq-core/cirq/experiments/xeb_sampling_test.py
--- a/cirq-core/cirq/experiments/xeb_sampling_test.py
+++ b/cirq-core/cirq/experiments/xeb_sampling_test.py
@@ -24,8 +24,6 @@
 import cirq.experiments.random_quantum_circuit_generation as rqcg
 from cirq.experiments.xeb_sampling import sample_2q_xeb_circuits
 
-SQRT_ISWAP = cirq.ISWAP ** 0.5
-
 
 def test_sample_2q_xeb_circuits():
     q0 = cirq.NamedQubit('a')
@@ -35,7 +33,7 @@ def test_sample_2q_xeb_circuits():
             q0,
             q1,
             depth=20,
-            two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP(a, b),
+            two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b),
         )
         for _ in range(2)
     ]
diff --git a/cirq-core/cirq/experiments/xeb_simulation_test.py b/cirq-core/cirq/experiments/xeb_simulation_test.py
--- a/cirq-core/cirq/experiments/xeb_simulation_test.py
+++ b/cirq-core/cirq/experiments/xeb_simulation_test.py
@@ -24,8 +24,6 @@
 import cirq.experiments.random_quantum_circuit_generation as rqcg
 from cirq.experiments.xeb_simulation import simulate_2q_xeb_circuits
 
-SQRT_ISWAP = cirq.ISWAP ** 0.5
-
 
 def test_simulate_2q_xeb_circuits():
     q0, q1 = cirq.LineQubit.range(2)
@@ -34,7 +32,7 @@ def test_simulate_2q_xeb_circuits():
             q0,
             q1,
             depth=50,
-            two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP(a, b),
+            two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b),
         )
         for _ in range(2)
     ]
@@ -64,7 +62,7 @@ def test_simulate_circuit_length_validation():
             q0,
             q1,
             depth=10,  # not long enough!
-            two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP(a, b),
+            two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b),
         )
         for _ in range(2)
     ]
@@ -142,7 +140,7 @@ def test_incremental_simulate(multiprocess):
             q0,
             q1,
             depth=100,
-            two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP(a, b),
+            two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b),
         )
         for _ in range(20)
     ]
diff --git a/cirq-google/cirq_google/calibration/xeb_wrapper_test.py b/cirq-google/cirq_google/calibration/xeb_wrapper_test.py
--- a/cirq-google/cirq_google/calibration/xeb_wrapper_test.py
+++ b/cirq-google/cirq_google/calibration/xeb_wrapper_test.py
@@ -33,8 +33,6 @@
     _maybe_multiprocessing_pool,
 )
 
-SQRT_ISWAP = cirq.ISWAP ** -0.5
-
 
 def _minimize_patch(
     fun,
@@ -122,7 +120,7 @@ def _minimize_patch_2(*args, **kwargs):
         random_rotations_between_grid_interaction_layers_circuit(
             qubits,
             depth=depth,
-            two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP.on(a, b),
+            two_qubit_op_factory=lambda a, b, _: (cirq.SQRT_ISWAP_INV.on(a, b)),
             pattern=cirq.experiments.GRID_ALIGNED_PATTERN,
             seed=10,
         )