diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 00ed50b4555..137ca460eda 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -367,6 +367,9 @@ two_qubit_matrix_to_diagonal_and_operations, two_qubit_matrix_to_operations, two_qubit_matrix_to_sqrt_iswap_operations, + two_qubit_gate_product_tabulation, + TwoQubitGateTabulation, + TwoQubitGateTabulationResult, unroll_circuit_op, unroll_circuit_op_greedy_earliest, unroll_circuit_op_greedy_frontier, diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 6a017320dad..c60a8cf66d1 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -155,6 +155,7 @@ def _parallel_gate_op(gate, qubits): 'TaggedOperation': cirq.TaggedOperation, 'TiltedSquareLattice': cirq.TiltedSquareLattice, 'TrialResult': cirq.Result, # keep support for Cirq < 0.11. + 'TwoQubitGateTabulation': cirq.TwoQubitGateTabulation, '_UnconstrainedDevice': cirq.devices.unconstrained_device._UnconstrainedDevice, 'VarianceStoppingCriteria': cirq.work.VarianceStoppingCriteria, 'VirtualTag': cirq.VirtualTag, diff --git a/cirq-core/cirq/protocols/json_test_data/TwoQubitGateTabulation.json b/cirq-core/cirq/protocols/json_test_data/TwoQubitGateTabulation.json new file mode 100644 index 00000000000..39c8c273908 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/TwoQubitGateTabulation.json @@ -0,0 +1,519 @@ +{ + "cirq_type": "TwoQubitGateTabulation", + "base_gate": [ + [ + { + "cirq_type": "complex", + "real": 1.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 6.123233995736766e-17, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": -1.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": -1.0 + }, + { + "cirq_type": "complex", + "real": 6.123233995736766e-17, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.0, + "imag": 0.0 + }, + { + "cirq_type": "complex", + "real": 0.8660254037844387, + "imag": -0.49999999999999994 + } + ] + ], + "kak_vecs": [ + [ + 0.7853981633974483, + 0.7853981633974483, + 0.1308996938995748 + ], + [ + 0.5455546451806152, + 0.4274250500660468, + 1.1102230246251565e-16 + ], + [ + 0.482263980185593, + 0.15541244226118112, + -0.0 + ], + [ + 0.08994436657892213, + 0.0688663429298958, + -0.0 + ], + [ + 0.5337019863142523, + 0.31800967733278185, + -0.2565859360625904 + ], + [ + 0.48438278888761155, + 0.39121774851710633, + 0.28728266960007875 + ] + ], + "single_qubit_gates": [ + [], + [ + [ + [ + [ + { + "cirq_type": "complex", + "real": -0.8113361037323902, + "imag": 0.388228151720065 + }, + { + "cirq_type": "complex", + "real": 0.38855300559639544, + "imag": -0.20009795309891054 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.38855300559639544, + "imag": -0.20009795309891054 + }, + { + "cirq_type": "complex", + "real": -0.8113361037323902, + "imag": -0.388228151720065 + } + ] + ], + [ + [ + { + "cirq_type": "complex", + "real": -0.15500592487333173, + "imag": -0.5209557609876208 + }, + { + "cirq_type": "complex", + "real": -0.6259723589563565, + "imag": 0.5592288119996914 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.6259723589563565, + "imag": 0.5592288119996914 + }, + { + "cirq_type": "complex", + "real": -0.15500592487333173, + "imag": 0.5209557609876208 + } + ] + ] + ] + ], + [ + [ + [ + [ + { + "cirq_type": "complex", + "real": -0.7591335658137299, + "imag": -0.5262321684934362 + }, + { + "cirq_type": "complex", + "real": 0.37132129473675946, + "imag": 0.09442685090928131 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.37132129473675946, + "imag": 0.09442685090928131 + }, + { + "cirq_type": "complex", + "real": -0.7591335658137299, + "imag": 0.5262321684934362 + } + ] + ], + [ + [ + { + "cirq_type": "complex", + "real": 0.004722472920167423, + "imag": 0.9809944588837683 + }, + { + "cirq_type": "complex", + "real": 0.18002103957975657, + "imag": 0.07224953423714474 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.18002103957975657, + "imag": 0.07224953423714474 + }, + { + "cirq_type": "complex", + "real": 0.004722472920167423, + "imag": -0.9809944588837683 + } + ] + ] + ] + ], + [ + [ + [ + [ + { + "cirq_type": "complex", + "real": -0.0897362442086916, + "imag": 0.02445105905520244 + }, + { + "cirq_type": "complex", + "real": -0.8503296766825128, + "imag": -0.5179662084918382 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.8503296766825128, + "imag": -0.5179662084918382 + }, + { + "cirq_type": "complex", + "real": -0.0897362442086916, + "imag": -0.02445105905520244 + } + ] + ], + [ + [ + { + "cirq_type": "complex", + "real": 0.7441127742234152, + "imag": 0.6642425418602642 + }, + { + "cirq_type": "complex", + "real": 0.009545063892332065, + "imag": 0.07061810374004159 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.009545063892332065, + "imag": 0.07061810374004159 + }, + { + "cirq_type": "complex", + "real": 0.7441127742234152, + "imag": -0.6642425418602642 + } + ] + ] + ] + ], + [ + [ + [ + [ + { + "cirq_type": "complex", + "real": -0.8113361037323902, + "imag": 0.388228151720065 + }, + { + "cirq_type": "complex", + "real": 0.38855300559639544, + "imag": -0.20009795309891054 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.38855300559639544, + "imag": -0.20009795309891054 + }, + { + "cirq_type": "complex", + "real": -0.8113361037323902, + "imag": -0.388228151720065 + } + ] + ], + [ + [ + { + "cirq_type": "complex", + "real": -0.15500592487333173, + "imag": -0.5209557609876208 + }, + { + "cirq_type": "complex", + "real": -0.6259723589563565, + "imag": 0.5592288119996914 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.6259723589563565, + "imag": 0.5592288119996914 + }, + { + "cirq_type": "complex", + "real": -0.15500592487333173, + "imag": 0.5209557609876208 + } + ] + ] + ], + [ + [ + [ + { + "cirq_type": "complex", + "real": -0.8113361037323902, + "imag": 0.388228151720065 + }, + { + "cirq_type": "complex", + "real": 0.38855300559639544, + "imag": -0.20009795309891054 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.38855300559639544, + "imag": -0.20009795309891054 + }, + { + "cirq_type": "complex", + "real": -0.8113361037323902, + "imag": -0.388228151720065 + } + ] + ], + [ + [ + { + "cirq_type": "complex", + "real": -0.15500592487333173, + "imag": -0.5209557609876208 + }, + { + "cirq_type": "complex", + "real": -0.6259723589563565, + "imag": 0.5592288119996914 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.6259723589563565, + "imag": 0.5592288119996914 + }, + { + "cirq_type": "complex", + "real": -0.15500592487333173, + "imag": 0.5209557609876208 + } + ] + ] + ] + ], + [ + [ + [ + [ + { + "cirq_type": "complex", + "real": 0.41295648139511504, + "imag": -0.17280776715854063 + }, + { + "cirq_type": "complex", + "real": 0.5430881450957462, + "imag": -0.7103940362502399 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.5430881450957462, + "imag": -0.7103940362502399 + }, + { + "cirq_type": "complex", + "real": 0.41295648139511504, + "imag": 0.17280776715854063 + } + ] + ], + [ + [ + { + "cirq_type": "complex", + "real": 0.19013479709876246, + "imag": 0.4941656851052965 + }, + { + "cirq_type": "complex", + "real": -0.6469746262664201, + "imag": 0.5487010730480224 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.6469746262664201, + "imag": 0.5487010730480224 + }, + { + "cirq_type": "complex", + "real": 0.19013479709876246, + "imag": -0.4941656851052965 + } + ] + ] + ], + [ + [ + [ + { + "cirq_type": "complex", + "real": 0.41295648139511504, + "imag": -0.17280776715854063 + }, + { + "cirq_type": "complex", + "real": 0.5430881450957462, + "imag": -0.7103940362502399 + } + ], + [ + { + "cirq_type": "complex", + "real": -0.5430881450957462, + "imag": -0.7103940362502399 + }, + { + "cirq_type": "complex", + "real": 0.41295648139511504, + "imag": 0.17280776715854063 + } + ] + ], + [ + [ + { + "cirq_type": "complex", + "real": 0.19013479709876246, + "imag": 0.4941656851052965 + }, + { + "cirq_type": "complex", + "real": -0.6469746262664201, + "imag": 0.5487010730480224 + } + ], + [ + { + "cirq_type": "complex", + "real": 0.6469746262664201, + "imag": 0.5487010730480224 + }, + { + "cirq_type": "complex", + "real": 0.19013479709876246, + "imag": -0.4941656851052965 + } + ] + ] + ] + ] + ], + "max_expected_infidelity": 0.49, + "summary": "Fraction of Weyl chamber reached with 2 gates: 0.600\nFraction of Weyl chamber reached with 2 gates and 3 gates(same single qubit): 1.000", + "missed_points": [] +} \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/TwoQubitGateTabulation.repr b/cirq-core/cirq/protocols/json_test_data/TwoQubitGateTabulation.repr new file mode 100644 index 00000000000..b9a25ff0a33 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/TwoQubitGateTabulation.repr @@ -0,0 +1 @@ +cirq.TwoQubitGateTabulation(np.array([[(1+0j), 0j, 0j, 0j], [0j, (6.123233995736766e-17+0j), -1j, 0j], [0j, -1j, (6.123233995736766e-17+0j), 0j], [0j, 0j, 0j, (0.8660254037844387-0.49999999999999994j)]], dtype=np.complex128), np.array([[0.7853981633974483, 0.7853981633974483, 0.1308996938995748], [0.5455546451806152, 0.4274250500660468, 1.1102230246251565e-16], [0.482263980185593, 0.15541244226118112, -0.0], [0.08994436657892213, 0.0688663429298958, -0.0], [0.5337019863142523, 0.31800967733278185, -0.2565859360625904], [0.48438278888761155, 0.39121774851710633, 0.28728266960007875]], dtype=np.float64), [[],[(np.array([[(-0.8113361037323902+0.388228151720065j), (0.38855300559639544-0.20009795309891054j)], [(-0.38855300559639544-0.20009795309891054j), (-0.8113361037323902-0.388228151720065j)]], dtype=np.complex128), np.array([[(-0.15500592487333173-0.5209557609876208j), (-0.6259723589563565+0.5592288119996914j)], [(0.6259723589563565+0.5592288119996914j), (-0.15500592487333173+0.5209557609876208j)]], dtype=np.complex128))],[(np.array([[(-0.7591335658137299-0.5262321684934362j), (0.37132129473675946+0.09442685090928131j)], [(-0.37132129473675946+0.09442685090928131j), (-0.7591335658137299+0.5262321684934362j)]], dtype=np.complex128), np.array([[(0.004722472920167423+0.9809944588837683j), (0.18002103957975657+0.07224953423714474j)], [(-0.18002103957975657+0.07224953423714474j), (0.004722472920167423-0.9809944588837683j)]], dtype=np.complex128))],[(np.array([[(-0.0897362442086916+0.02445105905520244j), (-0.8503296766825128-0.5179662084918382j)], [(0.8503296766825128-0.5179662084918382j), (-0.0897362442086916-0.02445105905520244j)]], dtype=np.complex128), np.array([[(0.7441127742234152+0.6642425418602642j), (0.009545063892332065+0.07061810374004159j)], [(-0.009545063892332065+0.07061810374004159j), (0.7441127742234152-0.6642425418602642j)]], dtype=np.complex128))],[(np.array([[(-0.8113361037323902+0.388228151720065j), (0.38855300559639544-0.20009795309891054j)], [(-0.38855300559639544-0.20009795309891054j), (-0.8113361037323902-0.388228151720065j)]], dtype=np.complex128), np.array([[(-0.15500592487333173-0.5209557609876208j), (-0.6259723589563565+0.5592288119996914j)], [(0.6259723589563565+0.5592288119996914j), (-0.15500592487333173+0.5209557609876208j)]], dtype=np.complex128)),(np.array([[(-0.8113361037323902+0.388228151720065j), (0.38855300559639544-0.20009795309891054j)], [(-0.38855300559639544-0.20009795309891054j), (-0.8113361037323902-0.388228151720065j)]], dtype=np.complex128), np.array([[(-0.15500592487333173-0.5209557609876208j), (-0.6259723589563565+0.5592288119996914j)], [(0.6259723589563565+0.5592288119996914j), (-0.15500592487333173+0.5209557609876208j)]], dtype=np.complex128))],[(np.array([[(0.41295648139511504-0.17280776715854063j), (0.5430881450957462-0.7103940362502399j)], [(-0.5430881450957462-0.7103940362502399j), (0.41295648139511504+0.17280776715854063j)]], dtype=np.complex128), np.array([[(0.19013479709876246+0.4941656851052965j), (-0.6469746262664201+0.5487010730480224j)], [(0.6469746262664201+0.5487010730480224j), (0.19013479709876246-0.4941656851052965j)]], dtype=np.complex128)),(np.array([[(0.41295648139511504-0.17280776715854063j), (0.5430881450957462-0.7103940362502399j)], [(-0.5430881450957462-0.7103940362502399j), (0.41295648139511504+0.17280776715854063j)]], dtype=np.complex128), np.array([[(0.19013479709876246+0.4941656851052965j), (-0.6469746262664201+0.5487010730480224j)], [(0.6469746262664201+0.5487010730480224j), (0.19013479709876246-0.4941656851052965j)]], dtype=np.complex128))]], 0.49, 'Fraction of Weyl chamber reached with 2 gates: 0.600\nFraction of Weyl chamber reached with 2 gates and 3 gates(same single qubit): 1.000', ()) \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index e2ce8086e53..a14b56ae3cf 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -70,6 +70,7 @@ 'ThreeQubitDiagonalGate', 'Timestamp', 'TwoQubitDiagonalGate', + 'TwoQubitGateTabulationResult', 'UnitSweep', 'StateVectorSimulatorState', 'StateVectorTrialResult', diff --git a/cirq-core/cirq/transformers/__init__.py b/cirq-core/cirq/transformers/__init__.py index 7848bc7d59d..2e681b86e1a 100644 --- a/cirq-core/cirq/transformers/__init__.py +++ b/cirq-core/cirq/transformers/__init__.py @@ -35,6 +35,12 @@ two_qubit_matrix_to_sqrt_iswap_operations, ) +from cirq.transformers.heuristic_decompositions import ( + TwoQubitGateTabulation, + TwoQubitGateTabulationResult, + two_qubit_gate_product_tabulation, +) + from cirq.transformers.transformer_primitives import ( map_moments, map_operations, diff --git a/cirq-core/cirq/transformers/heuristic_decompositions/__init__.py b/cirq-core/cirq/transformers/heuristic_decompositions/__init__.py new file mode 100644 index 00000000000..69584bfa144 --- /dev/null +++ b/cirq-core/cirq/transformers/heuristic_decompositions/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2021 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. + +"""Utilities for heuristic decomposition of cirq gates.""" + +from cirq.transformers.heuristic_decompositions.two_qubit_gate_tabulation import ( + TwoQubitGateTabulation, + TwoQubitGateTabulationResult, + two_qubit_gate_product_tabulation, +) diff --git a/cirq-google/cirq_google/optimizers/two_qubit_gates/math_utils.py b/cirq-core/cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py similarity index 100% rename from cirq-google/cirq_google/optimizers/two_qubit_gates/math_utils.py rename to cirq-core/cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py diff --git a/cirq-google/cirq_google/optimizers/two_qubit_gates/math_utils_test.py b/cirq-core/cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils_test.py similarity index 94% rename from cirq-google/cirq_google/optimizers/two_qubit_gates/math_utils_test.py rename to cirq-core/cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils_test.py index 3c39e9c41db..5c53ca55891 100644 --- a/cirq-google/cirq_google/optimizers/two_qubit_gates/math_utils_test.py +++ b/cirq-core/cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils_test.py @@ -4,7 +4,7 @@ import cirq from cirq import value -from cirq_google.optimizers.two_qubit_gates.math_utils import ( +from cirq.transformers.heuristic_decompositions.gate_tabulation_math_utils import ( weyl_chamber_mesh, kak_vector_infidelity, random_qubit_unitary, diff --git a/cirq-core/cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py b/cirq-core/cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py new file mode 100644 index 00000000000..adf38a1a20b --- /dev/null +++ b/cirq-core/cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py @@ -0,0 +1,493 @@ +# Copyright 2018 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. + +"""Attempt to tabulate single qubit gates required to generate a target 2Q gate +with a product A k A.""" +from functools import reduce +from typing import Tuple, Sequence, List, NamedTuple + +from dataclasses import dataclass +import numpy as np + +import cirq +from cirq import value +from cirq._compat import proper_repr, proper_eq +from cirq.transformers.heuristic_decompositions.gate_tabulation_math_utils import ( + kak_vector_infidelity, + vector_kron, + weyl_chamber_mesh, + random_qubit_unitary, + kak_vector_to_unitary, +) + +_SingleQubitGatePair = Tuple[np.ndarray, np.ndarray] + + +class TwoQubitGateTabulationResult(NamedTuple): + r"""Represents a compilation of a target 2-qubit with respect to a base + gate. + + This object encodes the relationship between 4x4 unitary operators + + U_target ~ k_N · U_base · k_{N-1} · ... · k_1 · U_base · k_0 + + where U_target, U_base are 2-local and k_j are 1-local. + + Attributes: + base_gate: 4x4 unitary denoting U_base above. + target_gate: 4x4 unitary denoting U_target above. + local_unitaries: Sequence of 2-tuples + $(k_{00}, k_{01}), (k_{10}, k_{11}) \ldots$ where + $k_j = k_{j0} \otimes k_{j1}$ in the product above. + Each $k_{j0}, k_{j1}$ is a 2x2 unitary. + actual_gate: 4x4 unitary denoting the right hand side above, ideally + equal to U_target. + success: Whether actual_gate is expected to be close to U_target. + """ + base_gate_unitary: np.ndarray + target_gate: np.ndarray + local_unitaries: Tuple[_SingleQubitGatePair, ...] + actual_gate: np.ndarray + success: bool + + +@dataclass +class TwoQubitGateTabulation: + """A 2-qubit gate compiler based on precomputing/tabulating gate products.""" + + base_gate: np.ndarray # Base two qubit gate. (4x4 unitary) + # Sequence of KAK vectors, ideally "dense" in the Weyl chamber. Shape (N,3). + kak_vecs: np.ndarray + # Sequence of 1-local operations required to achieve a given KAK vector. + # Index j corresponds to KAK_vecs[j], and is of the form + # ( (u0[0],u1[0]), (u0[1],u1[1]), ...) where u0[k] is the kth single qubit + # unitary acting on qubit 0 (similarly for u1) + single_qubit_gates: Sequence[Sequence[_SingleQubitGatePair]] + max_expected_infidelity: float # Defined using entanglement fidelity. + summary: str # Text summarizing the results of the tabulation procedure. + # Any KAK vectors which are expected to be compilable (within infidelity + # max_expected_infidelity) using 2 or 3 base gates. + missed_points: Tuple[np.ndarray, ...] + + def compile_two_qubit_gate(self, unitary: np.ndarray) -> TwoQubitGateTabulationResult: + r"""Compute single qubit gates required to compile a desired unitary. + + Given a desired unitary U, this computes the sequence of 1-local gates + $k_j$ such that the product + + $k_{n-1} A k_{n-2} A ... k_1 A k_0$ + + is close to U. Here A is the base_gate of the tabulation. + + Args: + unitary: Unitary (U above) to compile. + + Returns: + A TwoQubitGateTabulationResult object encoding the required local + unitaries and resulting product above. + """ + unitary = np.asarray(unitary) + kak_vec = cirq.kak_vector(unitary, check_preconditions=False) + infidelities = kak_vector_infidelity(kak_vec, self.kak_vecs, ignore_equivalent_vectors=True) + nearest_ind = infidelities.argmin() + + success = infidelities[nearest_ind] < self.max_expected_infidelity + + # shape (n,2,2,2) + inner_gates = np.array(self.single_qubit_gates[nearest_ind]) + + if inner_gates.size == 0: # Only need base gate + kR, kL, actual = _outer_locals_for_unitary(unitary, self.base_gate) + return TwoQubitGateTabulationResult(self.base_gate, unitary, (kR, kL), actual, success) + + # reshape to operators on 2 qubits, (n,4,4) + inner_gates = vector_kron(inner_gates[..., 0, :, :], inner_gates[..., 1, :, :]) + + assert inner_gates.ndim == 3 + inner_product = reduce(lambda a, b: self.base_gate @ b @ a, inner_gates, self.base_gate) + kR, kL, actual = _outer_locals_for_unitary(unitary, inner_product) + + out = [kR] + out.extend(self.single_qubit_gates[nearest_ind]) + out.append(kL) + + return TwoQubitGateTabulationResult(self.base_gate, unitary, tuple(out), actual, success) + + def _json_dict_(self): + return { + 'base_gate': self.base_gate.tolist(), + 'kak_vecs': self.kak_vecs.tolist(), + 'single_qubit_gates': self.single_qubit_gates, + 'max_expected_infidelity': self.max_expected_infidelity, + 'summary': self.summary, + 'missed_points': self.missed_points, + } + + def __repr__(self) -> str: + # Construct the repr for single_qubit_gates, which is a sequence of + # sequences of tuples of NumPy arrays, which needs to be encoded with + # proper_repr. + numpy_single_qubit_gates = [] + for single_qubit_gate in self.single_qubit_gates: + gate_repr = [ + f"({proper_repr(pair[0])}, {proper_repr(pair[1])})" for pair in single_qubit_gate + ] + numpy_single_qubit_gates.append(f"[{','.join(gate_repr)}]") + + return ( + f'cirq.TwoQubitGateTabulation({proper_repr(self.base_gate)}, ' + f'{proper_repr(self.kak_vecs)}, ' + f'[{",".join(numpy_single_qubit_gates)}], ' + f' {proper_repr(self.max_expected_infidelity)}, ' + f'{proper_repr(self.summary)}, ' + f'{proper_repr(self.missed_points)})' + ) + + def __eq__(self, other): + if not isinstance(other, type(self)): + return NotImplemented + return ( + np.array_equal(self.base_gate, other.base_gate) + and np.array_equal(self.kak_vecs, other.kak_vecs) + and proper_eq(self.single_qubit_gates, other.single_qubit_gates) + and self.max_expected_infidelity == other.max_expected_infidelity + and self.summary == other.summary + and np.array_equal(self.missed_points, other.missed_points) + ) + + @classmethod + def _from_json_dict_( + cls, + base_gate, + kak_vecs, + single_qubit_gates, + max_expected_infidelity, + summary, + missed_points, + **kwargs, + ): + numpy_single_qubit_gates = [] + for single_qubit_gate in single_qubit_gates: + numpy_single_qubit_gate = [] + for pair in single_qubit_gate: + numpy_tuple = (np.array(pair[0]), np.array(pair[1])) + numpy_single_qubit_gate.append(numpy_tuple) + numpy_single_qubit_gates.append(numpy_single_qubit_gate) + + return cls( + base_gate=np.array(base_gate), + kak_vecs=np.array(kak_vecs), + single_qubit_gates=numpy_single_qubit_gates, + max_expected_infidelity=max_expected_infidelity, + summary=summary, + missed_points=missed_points, + ) + + +def _outer_locals_for_unitary( + target: np.ndarray, base: np.ndarray +) -> Tuple[_SingleQubitGatePair, _SingleQubitGatePair, np.ndarray]: + """Local unitaries mapping between locally equivalent 2-local unitaries. + + Finds the left and right 1-local unitaries kL, kR such that + + U_target = kL @ U_base @ kR + + Args: + target: The unitary to which we want to map. + base: The base unitary which maps to target. + + Returns: + kR: The right 1-local unitaries in the equation above, expressed as + 2-tuples of (2x2) single qubit unitaries. + kL: The left 1-local unitaries in the equation above, expressed as + 2-tuples of (2x2) single qubit unitaries. + actual: The outcome of kL @ base @ kR + """ + target_decomp = cirq.kak_decomposition(target) + base_decomp = cirq.kak_decomposition(base) + + # From the KAK decomposition, we have + # kLt At kRt = kL kLb Ab KRb kR + # If At=Ab, we can solve for kL and kR as + # kLt = kL kLb --> kL = kLt kLb^\dagger + # kRt = kRb kR --> kR = kRb\dagger kRt + + # 0 and 1 are qubit indices. + kLt0, kLt1 = target_decomp.single_qubit_operations_after + kLb0, kLb1 = base_decomp.single_qubit_operations_after + kL = kLt0 @ kLb0.conj().T, kLt1 @ kLb1.conj().T + + kRt0, kRt1 = target_decomp.single_qubit_operations_before + kRb0, kRb1 = base_decomp.single_qubit_operations_before + kR = kRb0.conj().T @ kRt0, kRb1.conj().T @ kRt1 + + actual = np.kron(*kL) @ base + actual = actual @ np.kron(*kR) + actual *= np.conj(target_decomp.global_phase) + + return kR, kL, actual + + +class _TabulationStepResult(NamedTuple): + # Generated KAK vectors that are uniquely close to at least one mesh point. + kept_kaks: List[np.ndarray] + # The corresponding single qubit unitaries required to obtain the desired + # KAK vectors. + kept_cycles: List[Tuple[_SingleQubitGatePair, ...]] + + +def _tabulate_kak_vectors( + *, + already_tabulated: np.ndarray, + base_gate: np.ndarray, + max_dist: float, + kak_mesh: np.ndarray, + local_unitary_pairs: Sequence[_SingleQubitGatePair], +) -> _TabulationStepResult: + """Tabulate KAK vectors from products of local unitaries with a base gate. + + Args: + already_tabulated: Record of which KAK vectors have already been + tabulated. kak_mesh[i] has been calculated if i is in tabulation. + base_gate: The base 2 qubit gate used in the gate product. + max_dist: The largest allowed Pauli error between a generated 2Q + unitary and a KAK vector mesh point that it is tabulated to. + kak_mesh: Sequence of KAK vectors filling the Weyl chamber whose + nearest neighbor distance is about 2*max_error. + local_unitary_pairs: Sequence of 2-tuples of single qubit unitary + tensors, each of shape (N,2,2). + + Returns: + The newly tabulated KAK vectors and the local unitaries used to generate + them. This function also updates already_tabulated to include the + indices of these vectors (within kak_mesh). + """ + shapes = {pair[0].shape for pair in local_unitary_pairs} + shapes.update({pair[0].shape for pair in local_unitary_pairs}) + assert len(shapes) == 1 + assert len(shapes.pop()) == 3 + + # Generate products + local_cycles = np.array([vector_kron(*pairs) for pairs in local_unitary_pairs]) + + prods = np.einsum('ab,...bc,cd', base_gate, local_cycles[0], base_gate) + for local_cycle in local_cycles[1:]: + np.einsum('ab,...bc,...cd', base_gate, local_cycle, prods, out=prods) + + kak_vectors = cirq.kak_vector(prods, check_preconditions=False) + + kept_kaks = [] + kept_cycles = [] + + for ind, vec in enumerate(kak_vectors): + # The L2 distance is an upper bound to the locally invariant distance, + # but it's much faster to compute. + dists = np.sqrt(np.sum((kak_mesh - vec) ** 2, axis=-1)) + close = (dists < max_dist).nonzero()[0] + assert close.shape[0] in (0, 1), f'close.shape: {close.shape}' + cycles_for_gate = tuple((k_0[ind], k_1[ind]) for k_0, k_1 in local_unitary_pairs) + + # Add the vector and its cycles to the tabulation if it's not already + # tabulated. + if not np.all(already_tabulated[close]): + already_tabulated[close] = True + kept_kaks.append(vec) + kept_cycles.append(cycles_for_gate) + + return _TabulationStepResult(kept_kaks, kept_cycles) + + +# TODO(#3388) Add documentation for Raises. +# pylint: disable=missing-raises-doc +def two_qubit_gate_product_tabulation( + base_gate: np.ndarray, + max_infidelity: float, + *, + sample_scaling: int = 50, + allow_missed_points: bool = True, + random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None, +) -> TwoQubitGateTabulation: + r"""Generate a TwoQubitGateTabulation for a base two qubit unitary. + + Args: + base_gate: The base gate of the tabulation. + max_infidelity: Sets the desired density of tabulated product unitaries. + The typical nearest neighbor Euclidean spacing (of the KAK vectors) + will be on the order of $\sqrt{max\_infidelity}$. Thus the number of + tabulated points will scale as $max\_infidelity^{-3/2}$. + sample_scaling: Relative number of random gate products to use in the + tabulation. The total number of random local unitaries scales as + ~ $max\_infidelity^{-3/2} * sample\_scaling$. Must be positive. + random_state: Random state or random state seed. + allow_missed_points: If True, the tabulation is allowed to conclude + even if not all points in the Weyl chamber are expected to be + compilable using 2 or 3 base gates. Otherwise an error is raised + in this case. + + Returns: + A TwoQubitGateTabulation object used to compile new two-qubit gates from + products of the base gate with 1-local unitaries. + """ + rng = value.parse_random_state(random_state) + + assert 1 / 2 > max_infidelity > 0 + spacing = np.sqrt(max_infidelity / 3) + mesh_points = weyl_chamber_mesh(spacing) + + # Number of random gate products to sample over in constructing the + # tabulation. This has to be at least the number of mesh points, as + # a single product can only be associated with one mesh point. + assert sample_scaling > 0, 'Input sample_scaling must positive.' + num_mesh_points = mesh_points.shape[0] + num_samples = num_mesh_points * sample_scaling + + # include the base gate itself + kak_vecs = [cirq.kak_vector(base_gate, check_preconditions=False)] + sq_cycles: List[Tuple[_SingleQubitGatePair, ...]] = [()] + + # Tabulate gates that are close to gates in the mesh + u_locals_0 = random_qubit_unitary((num_samples,), rng=rng) + u_locals_1 = random_qubit_unitary((num_samples,), rng=rng) + + tabulated_kak_inds = np.zeros((num_mesh_points,), dtype=bool) + + tabulation_cutoff = 0.5 * spacing + out = _tabulate_kak_vectors( + already_tabulated=tabulated_kak_inds, + base_gate=base_gate, + max_dist=tabulation_cutoff, + kak_mesh=mesh_points, + local_unitary_pairs=[(u_locals_0, u_locals_1)], + ) + kak_vecs.extend(out.kept_kaks) + sq_cycles.extend(out.kept_cycles) + + # Will be used later for getting missing KAK vectors. + kak_vecs_single = np.array(kak_vecs) + sq_cycles_single = list(sq_cycles) + + summary = ( + f'Fraction of Weyl chamber reached with 2 gates' + f': {tabulated_kak_inds.sum() / num_mesh_points :.3f}' + ) + + # repeat for double products + # Multiply by the same local unitary in the gate product + out = _tabulate_kak_vectors( + already_tabulated=tabulated_kak_inds, + base_gate=base_gate, + max_dist=tabulation_cutoff, + kak_mesh=mesh_points, + local_unitary_pairs=[(u_locals_0, u_locals_1)] * 2, + ) + + kak_vecs.extend(out.kept_kaks) + sq_cycles.extend(out.kept_cycles) + + summary += ( + f'\nFraction of Weyl chamber reached with 2 gates and 3 gates' + f'(same single qubit): ' + f'{tabulated_kak_inds.sum() / num_mesh_points :.3f}' + ) + + # If all KAK vectors in the mesh have been tabulated, return. + missing_vec_inds = np.logical_not(tabulated_kak_inds).nonzero()[0] + + if not np.any(missing_vec_inds): + # coverage: ignore + return TwoQubitGateTabulation( + base_gate, np.array(kak_vecs), sq_cycles, max_infidelity, summary, () + ) + + # Run through remaining KAK vectors that don't have products and try to + # correct them + + u_locals_0p = random_qubit_unitary((100,), rng=rng) + u_locals_1p = random_qubit_unitary((100,), rng=rng) + u_locals = vector_kron(u_locals_0p, u_locals_1p) + + # Loop through the mesh points that have not yet been tabulated. + # Consider their nonlocal parts A and compute products of the form + # base_gate^\dagger k A + # Compare the KAK vector of any of those products to the already tabulated + # KAK vectors from single products of the form + # base_gate k0 base_gate. + # If they are close, then base_gate^\dagger k A ~ base_gate k0 base_gate + # So we may compute the outer local unitaries kL, kR such that + # base_gate^\dagger k A = kL base_gate k0 base_gate kR + # A = k^\dagger base_gate kL base_gate k0 base_gate kR + # the single-qubit unitary kL is the one we need to get the desired + # KAK vector. + missed_points = [] + base_gate_dag = base_gate.conj().T + for ind in missing_vec_inds: + missing_vec = mesh_points[ind] + # Unitary A we wish to solve for + missing_unitary = kak_vector_to_unitary(missing_vec) + + # Products of the from base_gate^\dagger k A + products = np.einsum('ab,...bc,cd', base_gate_dag, u_locals, missing_unitary) + # KAK vectors for these products + kaks = cirq.kak_vector(products, check_preconditions=False) + kaks = kaks[..., np.newaxis, :] + + # Check if any of the product KAK vectors are close to a previously + # tabulated KAK vector + dists2 = np.sum((kaks - kak_vecs_single) ** 2, axis=-1) + min_dist_inds = np.unravel_index(dists2.argmin(), dists2.shape) + min_dist = np.sqrt(dists2[min_dist_inds]) + if min_dist < tabulation_cutoff: + # If so, compute the single qubit unitary k_L such that + # base_gate^\dagger k A = kL base_gate k0 base_gate kR + # where k0 is the old (previously tabulated) single qubit unitary + # and k is one of the single qubit unitaries used above. + # Indices below are for k, k0 respectively + new_ind, old_ind = min_dist_inds + + # Special case where the RHS is just base_gate (no single qubit + # gates yet applied). I.e. base_gate^\dagger k A ~ base_gate + # which implies base_gate^\dagger k A = k_L base_gate k_R + new_product = products[new_ind] + if old_ind == 0: + assert not sq_cycles_single[old_ind] + base_product = base_gate + _, kL, actual = _outer_locals_for_unitary(new_product, base_product) + # Add to the enumeration + sq_cycles.append((kL,)) + else: # typical case mentioned above + assert len(sq_cycles_single[old_ind]) == 1 + old_sq_cycle = sq_cycles_single[old_ind][0] + old_k = np.kron(*old_sq_cycle) + base_product = base_gate @ old_k @ base_gate + _, kL, actual = _outer_locals_for_unitary(new_product, base_product) + # Add to the enumeration + sq_cycles.append((old_sq_cycle, kL)) + + kak_vecs.append(cirq.kak_vector(base_gate @ actual, check_preconditions=False)) + elif not allow_missed_points: + raise ValueError(f'Failed to tabulate a KAK vector near {missing_vec}') + else: + missed_points.append(missing_vec) + + kak_vecs = np.array(kak_vecs) + summary += ( + f'\nFraction of Weyl chamber reached with 2 gates and 3 gates ' + f'(after patchup)' + f': {(len(kak_vecs) - 1) / num_mesh_points :.3f}' + ) + + return TwoQubitGateTabulation( + base_gate, kak_vecs, sq_cycles, max_infidelity, summary, tuple(missed_points) + ) diff --git a/cirq-core/cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation_test.py b/cirq-core/cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation_test.py new file mode 100644 index 00000000000..9ffe711589f --- /dev/null +++ b/cirq-core/cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation_test.py @@ -0,0 +1,108 @@ +# Copyright 2018 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 numpy as np +import pytest + +import cirq +from cirq import value +from cirq.transformers.heuristic_decompositions.two_qubit_gate_tabulation import ( + two_qubit_gate_product_tabulation, + TwoQubitGateTabulation, +) +from cirq.transformers.heuristic_decompositions.gate_tabulation_math_utils import ( + unitary_entanglement_fidelity, +) +from cirq.testing import random_special_unitary, assert_equivalent_repr + +_rng = value.parse_random_state(11) # for determinism + +sycamore_tabulation = two_qubit_gate_product_tabulation( + cirq.unitary(cirq.FSimGate(np.pi / 2, np.pi / 6)), 0.2, random_state=_rng +) + +sqrt_iswap_tabulation = two_qubit_gate_product_tabulation( + cirq.unitary(cirq.FSimGate(np.pi / 4, np.pi / 24)), 0.1, random_state=_rng +) + +_random_2Q_unitaries = np.array([random_special_unitary(4, random_state=_rng) for _ in range(100)]) + + +@pytest.mark.parametrize('tabulation', [sycamore_tabulation, sqrt_iswap_tabulation]) +@pytest.mark.parametrize('target', _random_2Q_unitaries) +def test_gate_compilation_matches_expected_max_infidelity(tabulation, target): + result = tabulation.compile_two_qubit_gate(target) + + assert result.success + max_error = tabulation.max_expected_infidelity + assert 1 - unitary_entanglement_fidelity(target, result.actual_gate) < max_error + + +@pytest.mark.parametrize('tabulation', [sycamore_tabulation, sqrt_iswap_tabulation]) +def test_gate_compilation_on_base_gate_standard(tabulation): + base_gate = tabulation.base_gate + + result = tabulation.compile_two_qubit_gate(base_gate) + + assert len(result.local_unitaries) == 2 + assert result.success + fidelity = unitary_entanglement_fidelity(result.actual_gate, base_gate) + assert fidelity > 0.99999 + + +def test_gate_compilation_on_base_gate_identity(): + tabulation = two_qubit_gate_product_tabulation(np.eye(4), 0.25) + base_gate = tabulation.base_gate + + result = tabulation.compile_two_qubit_gate(base_gate) + + assert len(result.local_unitaries) == 2 + assert result.success + fidelity = unitary_entanglement_fidelity(result.actual_gate, base_gate) + assert fidelity > 0.99999 + + +def test_gate_compilation_missing_points_raises_error(): + with pytest.raises(ValueError, match='Failed to tabulate a'): + two_qubit_gate_product_tabulation( + np.eye(4), 0.4, allow_missed_points=False, random_state=_rng + ) + + +@pytest.mark.parametrize('seed', [0, 1]) +def test_sycamore_gate_tabulation(seed): + base_gate = cirq.unitary(cirq.FSimGate(np.pi / 2, np.pi / 6)) + tab = two_qubit_gate_product_tabulation( + base_gate, 0.1, sample_scaling=2, random_state=np.random.RandomState(seed) + ) + result = tab.compile_two_qubit_gate(base_gate) + assert result.success + + +def test_sycamore_gate_tabulation_repr(): + simple_tabulation = TwoQubitGateTabulation( + np.array([[(1 + 0j), 0j, 0j, 0j]], dtype=np.complex128), + np.array([[(1 + 0j), 0j, 0j, 0j]], dtype=np.complex128), + [[]], + 0.49, + 'Sample string', + (), + ) + assert_equivalent_repr(simple_tabulation) + + +def test_sycamore_gate_tabulation_eq(): + assert sycamore_tabulation == sycamore_tabulation + assert sycamore_tabulation != sqrt_iswap_tabulation + assert sycamore_tabulation != 1 diff --git a/cirq-google/cirq_google/json_test_data/spec.py b/cirq-google/cirq_google/json_test_data/spec.py index 1a7e08b94b0..b20adc88a42 100644 --- a/cirq-google/cirq_google/json_test_data/spec.py +++ b/cirq-google/cirq_google/json_test_data/spec.py @@ -78,5 +78,7 @@ 'cirq.google.ExecutableGroupResult', ], resolver_cache=_class_resolver_dictionary(), - deprecated={}, + deprecated={ + 'GateTabulation': 'v0.16', + }, ) diff --git a/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates.py b/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates.py index 7b7d47cdbbe..7f3b013c8c8 100644 --- a/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates.py +++ b/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates.py @@ -20,7 +20,6 @@ import cirq import cirq_google as google from cirq_google.ops import SycamoreGate -from cirq_google.optimizers.two_qubit_gates.gate_compilation import GateTabulation UNITARY_ZZ = np.kron(cirq.unitary(cirq.Z), cirq.unitary(cirq.Z)) @@ -46,25 +45,27 @@ class ConvertToSycamoreGates(cirq.PointOptimizer): a TypeError. """ - def __init__(self, tabulation: Optional[GateTabulation] = None, ignore_failures=False) -> None: + def __init__( + self, tabulation: Optional[cirq.TwoQubitGateTabulation] = None, ignore_failures=False + ) -> None: """Inits ConvertToSycamoreGates. Args: tabulation: If set, a tabulation for the Sycamore gate to use for decomposing Matrix gates. If unset, an analytic calculation is - used for Matrix gates. To get a GateTabulation, call the - gate_product_tabulation method with a base gate (in this case, + used for Matrix gates. To get a TwoQubitGateTabulation, call the + `two_qubit_gate_product_tabulation` method with a base gate (in this case, usually cirq_google.SYC) and a maximum infidelity. ignore_failures: If set, gates that fail to convert are forwarded unchanged. If not set, conversion failures raise a TypeError. Raises: - ValueError: If the tabulation is not a `GateTabulation`. + ValueError: If the tabulation is not a `TwoQubitGateTabulation`. """ super().__init__() self.ignore_failures = ignore_failures - if tabulation is not None and not isinstance(tabulation, GateTabulation): - raise ValueError("Provided tabulation must be of type GateTabulation.") + if tabulation is not None and not isinstance(tabulation, cirq.TwoQubitGateTabulation): + raise ValueError("provided tabulation must be of type cirq.TwoQubitGateTabulation") self.tabulation = tabulation def _is_native_sycamore_op(self, op: cirq.Operation) -> bool: @@ -176,7 +177,7 @@ def known_two_q_operations_to_sycamore_operations( qubit_a: cirq.Qid, qubit_b: cirq.Qid, op: cirq.Operation, - tabulation: Optional[GateTabulation] = None, + tabulation: Optional[cirq.TwoQubitGateTabulation] = None, ) -> cirq.OP_TREE: """Synthesizes a known gate operation to a Sycamore operation. @@ -306,7 +307,10 @@ def decompose_phased_iswap_into_syc_precomputed( def decompose_arbitrary_into_syc_tabulation( - qubit_a: cirq.Qid, qubit_b: cirq.Qid, op: cirq.Operation, tabulation: GateTabulation + qubit_a: cirq.Qid, + qubit_b: cirq.Qid, + op: cirq.Operation, + tabulation: cirq.TwoQubitGateTabulation, ) -> cirq.OP_TREE: """Synthesize an arbitrary 2 qubit operation to a Sycamore operation using the given Tabulation. diff --git a/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates_test.py b/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates_test.py index b9098d0785b..c193b506007 100644 --- a/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates_test.py +++ b/cirq-google/cirq_google/optimizers/convert_to_sycamore_gates_test.py @@ -7,7 +7,6 @@ import cirq import cirq_google import cirq_google.optimizers.convert_to_sycamore_gates as cgoc -from cirq_google.optimizers.two_qubit_gates.gate_compilation import gate_product_tabulation _rng = cirq.value.parse_random_state(11) # for determinism @@ -301,7 +300,7 @@ def test_convert_to_sycamore_equivalent_unitaries(gate): def test_convert_to_sycamore_tabulation(): # A tabulation for the sycamore gate with an infidelity of .1. - sycamore_tabulation = gate_product_tabulation( + sycamore_tabulation = cirq.two_qubit_gate_product_tabulation( cirq.unitary(cirq_google.SYC), 0.1, random_state=_rng ) qubits = [cirq.NamedQubit('a'), cirq.NamedQubit('b')] diff --git a/cirq-google/cirq_google/optimizers/optimize_for_sycamore.py b/cirq-google/cirq_google/optimizers/optimize_for_sycamore.py index 125f21620d7..032be3c254f 100644 --- a/cirq-google/cirq_google/optimizers/optimize_for_sycamore.py +++ b/cirq-google/cirq_google/optimizers/optimize_for_sycamore.py @@ -23,8 +23,6 @@ convert_to_xmon_gates, ConvertToSycamoreGates, ConvertToSqrtIswapGates, - gate_product_tabulation, - GateTabulation, ) if TYPE_CHECKING: @@ -40,7 +38,7 @@ def _get_common_cleanup_optimizers(tolerance: float) -> List[Callable[[cirq.Circ def _get_xmon_optimizers( - tolerance: float, tabulation: Optional[GateTabulation] + tolerance: float, tabulation: Optional[cirq.TwoQubitGateTabulation] ) -> List[Callable[[cirq.Circuit], None]]: if tabulation is not None: # coverage: ignore @@ -55,7 +53,7 @@ def _get_xmon_optimizers( def _get_xmon_optimizers_part_cz( - tolerance: float, tabulation: Optional[GateTabulation] + tolerance: float, tabulation: Optional[cirq.TwoQubitGateTabulation] ) -> List[Callable[[cirq.Circuit], None]]: if tabulation is not None: # coverage: ignore @@ -69,7 +67,7 @@ def _get_xmon_optimizers_part_cz( def _get_sycamore_optimizers( - tolerance: float, tabulation: Optional[GateTabulation] + tolerance: float, tabulation: Optional[cirq.TwoQubitGateTabulation] ) -> List[Callable[[cirq.Circuit], None]]: return [ ConvertToSycamoreGates(tabulation=tabulation).optimize_circuit, @@ -79,7 +77,7 @@ def _get_sycamore_optimizers( def _get_sqrt_iswap_optimizers( - tolerance: float, tabulation: Optional[GateTabulation] + tolerance: float, tabulation: Optional[cirq.TwoQubitGateTabulation] ) -> List[Callable[[cirq.Circuit], None]]: if tabulation is not None: # coverage: ignore @@ -102,14 +100,14 @@ def _get_sqrt_iswap_optimizers( @lru_cache() def _gate_product_tabulation_cached( optimizer_type: str, tabulation_resolution: float -) -> GateTabulation: +) -> cirq.TwoQubitGateTabulation: random_state = np.random.RandomState(51) if optimizer_type == 'sycamore': - return gate_product_tabulation( + return cirq.two_qubit_gate_product_tabulation( cirq.unitary(cg_ops.SYC), tabulation_resolution, random_state=random_state ) else: - raise NotImplementedError(f"Gate tabulation not supported for {optimizer_type}") + raise NotImplementedError(f"Two qubit gate tabulation not supported for {optimizer_type}") def optimized_for_sycamore( @@ -155,7 +153,7 @@ def optimized_for_sycamore( f'types are: {_OPTIMIZER_TYPES.keys()}' ) - tabulation: Optional[GateTabulation] = None + tabulation: Optional[cirq.TwoQubitGateTabulation] = None if tabulation_resolution is not None: tabulation = _gate_product_tabulation_cached(optimizer_type, tabulation_resolution) diff --git a/cirq-google/cirq_google/optimizers/two_qubit_gates/__init__.py b/cirq-google/cirq_google/optimizers/two_qubit_gates/__init__.py index cf9e9537c47..d046027a62d 100644 --- a/cirq-google/cirq_google/optimizers/two_qubit_gates/__init__.py +++ b/cirq-google/cirq_google/optimizers/two_qubit_gates/__init__.py @@ -1,5 +1,15 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice +from cirq import _compat + from cirq_google.optimizers.two_qubit_gates.gate_compilation import ( gate_product_tabulation, GateTabulation, ) + +_compat.deprecated_submodule( + new_module_name="cirq.transformers.heuristic_decompositions.gate_tabulation_math_utils", + old_parent=__name__, + old_child="math_utils", + deadline="v0.16", + create_attribute=True, +) diff --git a/cirq-google/cirq_google/optimizers/two_qubit_gates/example.py b/cirq-google/cirq_google/optimizers/two_qubit_gates/example.py index b35e882b61b..96845771bc2 100644 --- a/cirq-google/cirq_google/optimizers/two_qubit_gates/example.py +++ b/cirq-google/cirq_google/optimizers/two_qubit_gates/example.py @@ -24,13 +24,14 @@ from matplotlib import pyplot as plt import cirq -from cirq_google.optimizers.two_qubit_gates.gate_compilation import gate_product_tabulation -from cirq_google.optimizers.two_qubit_gates.math_utils import unitary_entanglement_fidelity +from cirq.transformers.heuristic_decompositions.gate_tabulation_math_utils import ( + unitary_entanglement_fidelity, +) from cirq.testing import random_special_unitary def main(samples: int = 1000, max_infidelity: float = 0.01): - """Demonstration of the usage of the GateTabulation gate compiler. + """Demonstration of the usage of the TwoQubitGateTabulation gate compiler. Args: samples: Number of random 2-qubit unitary samples to compile. @@ -43,14 +44,14 @@ def main(samples: int = 1000, max_infidelity: float = 0.01): phi = np.pi / 24 base = cirq.unitary(cirq.FSimGate(theta, phi)) - # The GateTabulation object is essentially a tabulation of many randomly + # The TwoQubitGateTabulation object is essentially a tabulation of many randomly # generated gate products (of the form A k A or A k A k A), along with their # associate KAK vectors. The parameter max_infidelity determines the # approximate "density" of the tabulated gates. Specifically, it bounds the # typical distance between an arbitrary two-qubit gate and the nearest # tabulated gate. start = time() - tabulation = gate_product_tabulation(base, max_infidelity) + tabulation = cirq.two_qubit_gate_product_tabulation(base, max_infidelity) print(tabulation.summary) print(f'Gate tabulation time : {time() - start} seconds.') diff --git a/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation.py b/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation.py index 2ff36358713..99733b0fcd7 100644 --- a/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation.py +++ b/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation.py @@ -1,302 +1,41 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice """Attempt to tabulate single qubit gates required to generate a target 2Q gate -with a product A k A.""" -from functools import reduce -from typing import Tuple, Sequence, List, NamedTuple +with a product A k A. -from dataclasses import dataclass +Attention: this module is now deprecated! Use the classes from cirq-core instead.""" +from typing import cast import numpy as np import cirq -from cirq import value -from cirq._compat import proper_repr, proper_eq -from cirq_google.optimizers.two_qubit_gates.math_utils import ( - kak_vector_infidelity, - vector_kron, - weyl_chamber_mesh, - random_qubit_unitary, - kak_vector_to_unitary, +from cirq import ( + TwoQubitGateTabulation, + TwoQubitGateTabulationResult, + two_qubit_gate_product_tabulation, ) +from cirq._compat import deprecated, deprecated_class -_SingleQubitGatePair = Tuple[np.ndarray, np.ndarray] +@deprecated_class( + deadline='v0.16', + fix='Use cirq.TwoQubitGateTabulationResult instead.', + name='TwoQubitGateCompilation', +) +class TwoQubitGateCompilation(TwoQubitGateTabulationResult): + pass -class TwoQubitGateCompilation(NamedTuple): - r"""Represents a compilation of a target 2-qubit with respect to a base - gate. - - This object encodes the relationship between 4x4 unitary operators - - U_target ~ k_N · U_base · k_{N-1} · ... · k_1 · U_base · k_0 - - where U_target, U_base are 2-local and k_j are 1-local. - - Attributes: - base_gate: 4x4 unitary denoting U_base above. - target_gate: 4x4 unitary denoting U_target above. - local_unitaries: Sequence of 2-tuples - $(k_{00}, k_{01}), (k_{10}, k_{11}) \ldots$ where - $k_j = k_{j0} \otimes k_{j1}$ in the product above. - Each $k_{j0}, k_{j1}$ is a 2x2 unitary. - actual_gate: 4x4 unitary denoting the right hand side above, ideally - equal to U_target. - success: Whether actual_gate is expected to be close to U_target. - """ - base_gate_unitary: np.ndarray - target_gate: np.ndarray - local_unitaries: Tuple[_SingleQubitGatePair, ...] - actual_gate: np.ndarray - success: bool - - -@dataclass -class GateTabulation: - """A 2-qubit gate compiler based on precomputing/tabulating gate products.""" - - base_gate: np.ndarray # Base two qubit gate. (4x4 unitary) - # Sequence of KAK vectors, ideally "dense" in the Weyl chamber. Shape (N,3). - kak_vecs: np.ndarray - # Sequence of 1-local operations required to achieve a given KAK vector. - # Index j corresponds to KAK_vecs[j], and is of the form - # ( (u0[0],u1[0]), (u0[1],u1[1]), ...) where u0[k] is the kth single qubit - # unitary acting on qubit 0 (similarly for u1) - single_qubit_gates: Sequence[Sequence[_SingleQubitGatePair]] - max_expected_infidelity: float # Defined using entanglement fidelity. - summary: str # Text summarizing the results of the tabulation procedure. - # Any KAK vectors which are expected to be compilable (within infidelity - # max_expected_infidelity) using 2 or 3 base gates. - missed_points: Tuple[np.ndarray, ...] - - def compile_two_qubit_gate(self, unitary: np.ndarray) -> TwoQubitGateCompilation: - r"""Compute single qubit gates required to compile a desired unitary. - - Given a desired unitary U, this computes the sequence of 1-local gates - $k_j$ such that the product - - $k_{n-1} A k_{n-2} A ... k_1 A k_0$ - - is close to U. Here A is the base_gate of the tabulation. - - Args: - unitary: Unitary (U above) to compile. - - Returns: - A TwoQubitGateCompilation object encoding the required local - unitaries and resulting product above. - """ - unitary = np.asarray(unitary) - kak_vec = cirq.kak_vector(unitary, check_preconditions=False) - infidelities = kak_vector_infidelity(kak_vec, self.kak_vecs, ignore_equivalent_vectors=True) - nearest_ind = infidelities.argmin() - - success = infidelities[nearest_ind] < self.max_expected_infidelity - - # shape (n,2,2,2) - inner_gates = np.array(self.single_qubit_gates[nearest_ind]) - - if inner_gates.size == 0: # Only need base gate - kR, kL, actual = _outer_locals_for_unitary(unitary, self.base_gate) - return TwoQubitGateCompilation(self.base_gate, unitary, (kR, kL), actual, success) - - # reshape to operators on 2 qubits, (n,4,4) - inner_gates = vector_kron(inner_gates[..., 0, :, :], inner_gates[..., 1, :, :]) - - assert inner_gates.ndim == 3 - inner_product = reduce(lambda a, b: self.base_gate @ b @ a, inner_gates, self.base_gate) - kR, kL, actual = _outer_locals_for_unitary(unitary, inner_product) - - out = [kR] - out.extend(self.single_qubit_gates[nearest_ind]) - out.append(kL) - - return TwoQubitGateCompilation(self.base_gate, unitary, tuple(out), actual, success) - - def _json_dict_(self): - return { - 'base_gate': self.base_gate.tolist(), - 'kak_vecs': self.kak_vecs.tolist(), - 'single_qubit_gates': self.single_qubit_gates, - 'max_expected_infidelity': self.max_expected_infidelity, - 'summary': self.summary, - 'missed_points': self.missed_points, - } - - def __repr__(self) -> str: - # Construct the repr for single_qubit_gates, which is a sequence of - # sequences of tuples of NumPy arrays, which needs to be encoded with - # proper_repr. - numpy_single_qubit_gates = [] - for single_qubit_gate in self.single_qubit_gates: - gate_repr = [ - f"({proper_repr(pair[0])}, {proper_repr(pair[1])})" for pair in single_qubit_gate - ] - numpy_single_qubit_gates.append(f"[{','.join(gate_repr)}]") - - return ( - f'cirq_google.optimizers.two_qubit_gates.gate_compilation' - f'.GateTabulation({proper_repr(self.base_gate)}, ' - f'{proper_repr(self.kak_vecs)}, ' - f'[{",".join(numpy_single_qubit_gates)}], ' - f' {proper_repr(self.max_expected_infidelity)}, ' - f'{proper_repr(self.summary)}, ' - f'{proper_repr(self.missed_points)})' - ) - - def __eq__(self, other): - if not isinstance(other, type(self)): - return NotImplemented - return ( - np.array_equal(self.base_gate, other.base_gate) - and np.array_equal(self.kak_vecs, other.kak_vecs) - and proper_eq(self.single_qubit_gates, other.single_qubit_gates) - and self.max_expected_infidelity == other.max_expected_infidelity - and self.summary == other.summary - and np.array_equal(self.missed_points, other.missed_points) - ) - - @classmethod - def _from_json_dict_( - cls, - base_gate, - kak_vecs, - single_qubit_gates, - max_expected_infidelity, - summary, - missed_points, - **kwargs, - ): - numpy_single_qubit_gates = [] - for single_qubit_gate in single_qubit_gates: - numpy_single_qubit_gate = [] - for pair in single_qubit_gate: - numpy_tuple = (np.array(pair[0]), np.array(pair[1])) - numpy_single_qubit_gate.append(numpy_tuple) - numpy_single_qubit_gates.append(numpy_single_qubit_gate) - - return cls( - base_gate=np.array(base_gate), - kak_vecs=np.array(kak_vecs), - single_qubit_gates=numpy_single_qubit_gates, - max_expected_infidelity=max_expected_infidelity, - summary=summary, - missed_points=missed_points, - ) - - -def _outer_locals_for_unitary( - target: np.ndarray, base: np.ndarray -) -> Tuple[_SingleQubitGatePair, _SingleQubitGatePair, np.ndarray]: - """Local unitaries mapping between locally equivalent 2-local unitaries. - - Finds the left and right 1-local unitaries kL, kR such that - - U_target = kL @ U_base @ kR - - Args: - target: The unitary to which we want to map. - base: The base unitary which maps to target. - - Returns: - kR: The right 1-local unitaries in the equation above, expressed as - 2-tuples of (2x2) single qubit unitaries. - kL: The left 1-local unitaries in the equation above, expressed as - 2-tuples of (2x2) single qubit unitaries. - actual: The outcome of kL @ base @ kR - """ - target_decomp = cirq.kak_decomposition(target) - base_decomp = cirq.kak_decomposition(base) - - # From the KAK decomposition, we have - # kLt At kRt = kL kLb Ab KRb kR - # If At=Ab, we can solve for kL and kR as - # kLt = kL kLb --> kL = kLt kLb^\dagger - # kRt = kRb kR --> kR = kRb\dagger kRt - - # 0 and 1 are qubit indices. - kLt0, kLt1 = target_decomp.single_qubit_operations_after - kLb0, kLb1 = base_decomp.single_qubit_operations_after - kL = kLt0 @ kLb0.conj().T, kLt1 @ kLb1.conj().T - - kRt0, kRt1 = target_decomp.single_qubit_operations_before - kRb0, kRb1 = base_decomp.single_qubit_operations_before - kR = kRb0.conj().T @ kRt0, kRb1.conj().T @ kRt1 - - actual = np.kron(*kL) @ base - actual = actual @ np.kron(*kR) - actual *= np.conj(target_decomp.global_phase) - - return kR, kL, actual - - -class _TabulationStepResult(NamedTuple): - # Generated KAK vectors that are uniquely close to at least one mesh point. - kept_kaks: List[np.ndarray] - # The corresponding single qubit unitaries required to obtain the desired - # KAK vectors. - kept_cycles: List[Tuple[_SingleQubitGatePair, ...]] - - -def _tabulate_kak_vectors( - *, - already_tabulated: np.ndarray, - base_gate: np.ndarray, - max_dist: float, - kak_mesh: np.ndarray, - local_unitary_pairs: Sequence[_SingleQubitGatePair], -) -> _TabulationStepResult: - """Tabulate KAK vectors from products of local unitaries with a base gate. - - Args: - already_tabulated: Record of which KAK vectors have already been - tabulated. kak_mesh[i] has been calculated if i is in tabulation. - base_gate: The base 2 qubit gate used in the gate product. - max_dist: The largest allowed Pauli error between a generated 2Q - unitary and a KAK vector mesh point that it is tabulated to. - kak_mesh: Sequence of KAK vectors filling the Weyl chamber whose - nearest neighbor distance is about 2*max_error. - local_unitary_pairs: Sequence of 2-tuples of single qubit unitary - tensors, each of shape (N,2,2). - - Returns: - The newly tabulated KAK vectors and the local unitaries used to generate - them. This function also updates already_tabulated to include the - indices of these vectors (within kak_mesh). - """ - shapes = {pair[0].shape for pair in local_unitary_pairs} - shapes.update({pair[0].shape for pair in local_unitary_pairs}) - assert len(shapes) == 1 - assert len(shapes.pop()) == 3 - - # Generate products - local_cycles = np.array([vector_kron(*pairs) for pairs in local_unitary_pairs]) - - prods = np.einsum('ab,...bc,cd', base_gate, local_cycles[0], base_gate) - for local_cycle in local_cycles[1:]: - np.einsum('ab,...bc,...cd', base_gate, local_cycle, prods, out=prods) - - kak_vectors = cirq.kak_vector(prods, check_preconditions=False) - - kept_kaks = [] - kept_cycles = [] - - for ind, vec in enumerate(kak_vectors): - # The L2 distance is an upper bound to the locally invariant distance, - # but it's much faster to compute. - dists = np.sqrt(np.sum((kak_mesh - vec) ** 2, axis=-1)) - close = (dists < max_dist).nonzero()[0] - assert close.shape[0] in (0, 1), f'close.shape: {close.shape}' - cycles_for_gate = tuple((k_0[ind], k_1[ind]) for k_0, k_1 in local_unitary_pairs) - - # Add the vector and its cycles to the tabulation if it's not already - # tabulated. - if not np.all(already_tabulated[close]): - already_tabulated[close] = True - kept_kaks.append(vec) - kept_cycles.append(cycles_for_gate) - return _TabulationStepResult(kept_kaks, kept_cycles) +@deprecated_class( + deadline='v0.16', fix='Use cirq.TwoQubitGateTabulation instead.', name='GateTabulation' +) +class GateTabulation(TwoQubitGateTabulation): + pass +@deprecated( + deadline='v0.16', + fix='Use cirq.two_qubit_gate_product_tabulation instead.', + name='gate_product_tabulation', +) def gate_product_tabulation( base_gate: np.ndarray, max_infidelity: float, @@ -330,152 +69,13 @@ def gate_product_tabulation( ValueError: If `allow_missed_points` is False and not all points in the Weyl chamber were compilable using 2 or 3 base gates. """ - rng = value.parse_random_state(random_state) - - assert 1 / 2 > max_infidelity > 0 - spacing = np.sqrt(max_infidelity / 3) - mesh_points = weyl_chamber_mesh(spacing) - - # Number of random gate products to sample over in constructing the - # tabulation. This has to be at least the number of mesh points, as - # a single product can only be associated with one mesh point. - assert sample_scaling > 0, 'Input sample_scaling must positive.' - num_mesh_points = mesh_points.shape[0] - num_samples = num_mesh_points * sample_scaling - - # include the base gate itself - kak_vecs = [cirq.kak_vector(base_gate, check_preconditions=False)] - sq_cycles: List[Tuple[_SingleQubitGatePair, ...]] = [()] - - # Tabulate gates that are close to gates in the mesh - u_locals_0 = random_qubit_unitary((num_samples,), rng=rng) - u_locals_1 = random_qubit_unitary((num_samples,), rng=rng) - - tabulated_kak_inds = np.zeros((num_mesh_points,), dtype=bool) - - tabulation_cutoff = 0.5 * spacing - out = _tabulate_kak_vectors( - already_tabulated=tabulated_kak_inds, - base_gate=base_gate, - max_dist=tabulation_cutoff, - kak_mesh=mesh_points, - local_unitary_pairs=[(u_locals_0, u_locals_1)], - ) - kak_vecs.extend(out.kept_kaks) - sq_cycles.extend(out.kept_cycles) - - # Will be used later for getting missing KAK vectors. - kak_vecs_single = np.array(kak_vecs) - sq_cycles_single = list(sq_cycles) - - summary = ( - f'Fraction of Weyl chamber reached with 2 gates' - f': {tabulated_kak_inds.sum() / num_mesh_points :.3f}' - ) - - # repeat for double products - # Multiply by the same local unitary in the gate product - out = _tabulate_kak_vectors( - already_tabulated=tabulated_kak_inds, - base_gate=base_gate, - max_dist=tabulation_cutoff, - kak_mesh=mesh_points, - local_unitary_pairs=[(u_locals_0, u_locals_1)] * 2, - ) - - kak_vecs.extend(out.kept_kaks) - sq_cycles.extend(out.kept_cycles) - - summary += ( - f'\nFraction of Weyl chamber reached with 2 gates and 3 gates' - f'(same single qubit): ' - f'{tabulated_kak_inds.sum() / num_mesh_points :.3f}' - ) - - # If all KAK vectors in the mesh have been tabulated, return. - missing_vec_inds = np.logical_not(tabulated_kak_inds).nonzero()[0] - - if not np.any(missing_vec_inds): - # coverage: ignore - return GateTabulation(base_gate, np.array(kak_vecs), sq_cycles, max_infidelity, summary, ()) - - # Run through remaining KAK vectors that don't have products and try to - # correct them - - u_locals_0p = random_qubit_unitary((100,), rng=rng) - u_locals_1p = random_qubit_unitary((100,), rng=rng) - u_locals = vector_kron(u_locals_0p, u_locals_1p) - - # Loop through the mesh points that have not yet been tabulated. - # Consider their nonlocal parts A and compute products of the form - # base_gate^\dagger k A - # Compare the KAK vector of any of those products to the already tabulated - # KAK vectors from single products of the form - # base_gate k0 base_gate. - # If they are close, then base_gate^\dagger k A ~ base_gate k0 base_gate - # So we may compute the outer local unitaries kL, kR such that - # base_gate^\dagger k A = kL base_gate k0 base_gate kR - # A = k^\dagger base_gate kL base_gate k0 base_gate kR - # the single-qubit unitary kL is the one we need to get the desired - # KAK vector. - missed_points = [] - base_gate_dag = base_gate.conj().T - for ind in missing_vec_inds: - missing_vec = mesh_points[ind] - # Unitary A we wish to solve for - missing_unitary = kak_vector_to_unitary(missing_vec) - - # Products of the from base_gate^\dagger k A - products = np.einsum('ab,...bc,cd', base_gate_dag, u_locals, missing_unitary) - # KAK vectors for these products - kaks = cirq.kak_vector(products, check_preconditions=False) - kaks = kaks[..., np.newaxis, :] - - # Check if any of the product KAK vectors are close to a previously - # tabulated KAK vector - dists2 = np.sum((kaks - kak_vecs_single) ** 2, axis=-1) - min_dist_inds = np.unravel_index(dists2.argmin(), dists2.shape) - min_dist = np.sqrt(dists2[min_dist_inds]) - if min_dist < tabulation_cutoff: - # If so, compute the single qubit unitary k_L such that - # base_gate^\dagger k A = kL base_gate k0 base_gate kR - # where k0 is the old (previously tabulated) single qubit unitary - # and k is one of the single qubit unitaries used above. - # Indices below are for k, k0 respectively - new_ind, old_ind = min_dist_inds - - # Special case where the RHS is just base_gate (no single qubit - # gates yet applied). I.e. base_gate^\dagger k A ~ base_gate - # which implies base_gate^\dagger k A = k_L base_gate k_R - new_product = products[new_ind] - if old_ind == 0: - assert not sq_cycles_single[old_ind] - base_product = base_gate - _, kL, actual = _outer_locals_for_unitary(new_product, base_product) - # Add to the enumeration - sq_cycles.append((kL,)) - else: # typical case mentioned above - assert len(sq_cycles_single[old_ind]) == 1 - old_sq_cycle = sq_cycles_single[old_ind][0] - old_k = np.kron(*old_sq_cycle) - base_product = base_gate @ old_k @ base_gate - _, kL, actual = _outer_locals_for_unitary(new_product, base_product) - # Add to the enumeration - sq_cycles.append((old_sq_cycle, kL)) - - kak_vecs.append(cirq.kak_vector(base_gate @ actual, check_preconditions=False)) - elif not allow_missed_points: - raise ValueError(f'Failed to tabulate a KAK vector near {missing_vec}') - else: - missed_points.append(missing_vec) - - kak_vecs = np.array(kak_vecs) - summary += ( - f'\nFraction of Weyl chamber reached with 2 gates and 3 gates ' - f'(after patchup)' - f': {(len(kak_vecs) - 1) / num_mesh_points :.3f}' - ) - - return GateTabulation( - base_gate, kak_vecs, sq_cycles, max_infidelity, summary, tuple(missed_points) + return cast( + GateTabulation, + two_qubit_gate_product_tabulation( + base_gate, + max_infidelity, + sample_scaling=sample_scaling, + allow_missed_points=allow_missed_points, + random_state=random_state, + ), ) diff --git a/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation_test.py b/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation_test.py index e9ff9ef75da..f94aacd243e 100644 --- a/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation_test.py +++ b/cirq-google/cirq_google/optimizers/two_qubit_gates/gate_compilation_test.py @@ -1,94 +1,41 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice """Tests for gate_compilation.py""" import numpy as np -import pytest import cirq -from cirq import value from cirq_google.optimizers.two_qubit_gates.gate_compilation import ( gate_product_tabulation, GateTabulation, ) -from cirq_google.optimizers.two_qubit_gates.math_utils import unitary_entanglement_fidelity -from cirq.testing import random_special_unitary, assert_equivalent_repr - -_rng = value.parse_random_state(11) # for determinism - -sycamore_tabulation = gate_product_tabulation( - cirq.unitary(cirq.FSimGate(np.pi / 2, np.pi / 6)), 0.2, random_state=_rng -) - -sqrt_iswap_tabulation = gate_product_tabulation( - cirq.unitary(cirq.FSimGate(np.pi / 4, np.pi / 24)), 0.1, random_state=_rng -) - -_random_2Q_unitaries = np.array([random_special_unitary(4, random_state=_rng) for _ in range(100)]) - - -@pytest.mark.parametrize('tabulation', [sycamore_tabulation, sqrt_iswap_tabulation]) -@pytest.mark.parametrize('target', _random_2Q_unitaries) -def test_gate_compilation_matches_expected_max_infidelity(tabulation, target): - result = tabulation.compile_two_qubit_gate(target) - - assert result.success - max_error = tabulation.max_expected_infidelity - assert 1 - unitary_entanglement_fidelity(target, result.actual_gate) < max_error - - -@pytest.mark.parametrize('tabulation', [sycamore_tabulation, sqrt_iswap_tabulation]) -def test_gate_compilation_on_base_gate_standard(tabulation): - base_gate = tabulation.base_gate - - result = tabulation.compile_two_qubit_gate(base_gate) - - assert len(result.local_unitaries) == 2 - assert result.success - fidelity = unitary_entanglement_fidelity(result.actual_gate, base_gate) - assert fidelity > 0.99999 - - -def test_gate_compilation_on_base_gate_identity(): - tabulation = gate_product_tabulation(np.eye(4), 0.25) - base_gate = tabulation.base_gate - - result = tabulation.compile_two_qubit_gate(base_gate) - - assert len(result.local_unitaries) == 2 - assert result.success - fidelity = unitary_entanglement_fidelity(result.actual_gate, base_gate) - assert fidelity > 0.99999 - - -def test_gate_compilation_missing_points_raises_error(): - with pytest.raises(ValueError, match='Failed to tabulate a'): - gate_product_tabulation(np.eye(4), 0.4, allow_missed_points=False, random_state=_rng) - - -@pytest.mark.parametrize('seed', [0, 1]) -def test_sycamore_gate_tabulation(seed): - base_gate = cirq.unitary(cirq.FSimGate(np.pi / 2, np.pi / 6)) - tab = gate_product_tabulation( - base_gate, 0.1, sample_scaling=2, random_state=np.random.RandomState(seed) - ) - result = tab.compile_two_qubit_gate(base_gate) - assert result.success - - -def test_sycamore_gate_tabulation_repr(): - simple_tabulation = GateTabulation( - np.array([[(1 + 0j), 0j, 0j, 0j]], dtype=np.complex128), - np.array([[(1 + 0j), 0j, 0j, 0j]], dtype=np.complex128), - [[]], - 0.49, - 'Sample string', - (), - ) - assert_equivalent_repr( - simple_tabulation, setup_code="import cirq\nimport cirq_google\nimport numpy as np" - ) - - -def test_sycamore_gate_tabulation_eq(): - assert sycamore_tabulation == sycamore_tabulation - assert sycamore_tabulation != sqrt_iswap_tabulation - assert sycamore_tabulation != 1 +import cirq_google.optimizers.two_qubit_gates as cgot + + +def test_deprecated_gate_product_tabulation(): + with cirq.testing.assert_deprecated( + deadline='v0.16', + count=None, + ): + _ = gate_product_tabulation(np.eye(4), 0.25) + + +def test_deprecated_gate_tabulation_repr(): + with cirq.testing.assert_deprecated( + deadline='v0.16', + count=None, + ): + GateTabulation( + np.array([[(1 + 0j), 0j, 0j, 0j]], dtype=np.complex128), + np.array([[(1 + 0j), 0j, 0j, 0j]], dtype=np.complex128), + [[]], + 0.49, + 'Sample string', + (), + ) + + +def test_deprecated_math_utils_submodule(): + with cirq.testing.assert_deprecated( + "Use cirq.transformers.heuristic_decompositions.gate_tabulation_math_utils instead", + deadline="v0.16", + ): + _ = cgot.math_utils.weyl_chamber_mesh(spacing=0.1)