|
| 1 | +# Copyright 2022 Google |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +import itertools |
| 16 | +from dataclasses import dataclass |
| 17 | + |
| 18 | +import numpy as np |
| 19 | + |
| 20 | +import cirq |
| 21 | +from cirq import TiltedSquareLattice |
| 22 | +from cirq.experiments import random_rotations_between_grid_interaction_layers_circuit |
| 23 | +from cirq.protocols import dataclass_json_dict |
| 24 | +from cirq_google.workflow import QuantumExecutable, BitstringsMeasurement, QuantumExecutableGroup, \ |
| 25 | + ExecutableSpec |
| 26 | + |
| 27 | + |
| 28 | +def create_tilted_square_lattice_loschmidt_echo_circuit( |
| 29 | + topology: TiltedSquareLattice, |
| 30 | + macrocycle_depth: int, |
| 31 | + twoq_gate: cirq.Gate = cirq.FSimGate(np.pi / 4, 0.0), |
| 32 | + rs: cirq.RANDOM_STATE_OR_SEED_LIKE = None, |
| 33 | +) -> cirq.FrozenCircuit: |
| 34 | + """Returns a Loschmidt echo circuit using a random unitary U. |
| 35 | +
|
| 36 | + Args: |
| 37 | + topology: The TiltedSquareLattice topology. |
| 38 | + macrocycle_depth: Number of complete, macrocycles in the random unitary. A macrocycle is |
| 39 | + 4 cycles corresponding to the 4 grid directions. Each cycle is one layer of entangling |
| 40 | + gates and one layer of random single qubit rotations. The total circuit depth is |
| 41 | + twice as large because we also do the inverse. |
| 42 | + twoq_gate: Two-qubit gate to use. |
| 43 | + rs: The random state for random circuit generation. |
| 44 | + """ |
| 45 | + |
| 46 | + # Forward (U) operations. |
| 47 | + exponents = np.linspace(0, 7 / 4, 8) |
| 48 | + single_qubit_gates = [ |
| 49 | + cirq.PhasedXZGate(x_exponent=0.5, z_exponent=z, axis_phase_exponent=a) |
| 50 | + for a, z in itertools.product(exponents, repeat=2) |
| 51 | + ] |
| 52 | + qubits = sorted(topology.nodes_as_gridqubits()) |
| 53 | + forward = random_rotations_between_grid_interaction_layers_circuit( |
| 54 | + # note: this function should take a topology probably. |
| 55 | + qubits=qubits, |
| 56 | + # note: in this function, `depth` refers to cycles. |
| 57 | + depth=4 * macrocycle_depth, |
| 58 | + two_qubit_op_factory=lambda a, b, _: twoq_gate.on(a, b), |
| 59 | + pattern=cirq.experiments.GRID_STAGGERED_PATTERN, |
| 60 | + single_qubit_gates=single_qubit_gates, |
| 61 | + seed=rs |
| 62 | + ) |
| 63 | + |
| 64 | + # Reverse (U^\dagger) operations. |
| 65 | + reverse = cirq.inverse(forward) |
| 66 | + |
| 67 | + return (forward + reverse + cirq.measure(*qubits, key='z')).freeze() |
| 68 | + |
| 69 | + |
| 70 | +def _get_all_tilted_square_lattices(min_side_length=2, max_side_length=8, side_length_step=2): |
| 71 | + """Helper function to get a range of tilted square lattices.""" |
| 72 | + width_heights = np.arange(min_side_length, max_side_length + 1, side_length_step) |
| 73 | + return [TiltedSquareLattice(width, height) |
| 74 | + for width, height in itertools.combinations_with_replacement(width_heights, r=2)] |
| 75 | + |
| 76 | + |
| 77 | +@dataclass(frozen=True) |
| 78 | +class TiltedSquareLatticeLoschmidtSpec(ExecutableSpec): |
| 79 | + """The ExecutableSpec for the tilted square lattice loschmidt benchmark. |
| 80 | +
|
| 81 | + The loschmidt echo runs a random unitary forward and backwards and measures how often |
| 82 | + we return to the starting state. This benchmark checks random unitaries generated |
| 83 | + on the TiltedSquareLattice topology. |
| 84 | +
|
| 85 | + Args: |
| 86 | + topology: The topology |
| 87 | + macrocycle_depth: Number of complete, macrocycles in the random unitary. A macrocycle is |
| 88 | + 4 cycles corresponding to the 4 grid directions. Each cycle is one layer of entangling |
| 89 | + gates and one layer of random single qubit rotations. The total circuit depth is |
| 90 | + twice as large because we also do the inverse. |
| 91 | + instance_i: An arbitary index into the random instantiation |
| 92 | + n_repetitions: The number of repetitions to sample to measure the return probability. |
| 93 | + executable_family: The globally unique string identifier for this benchmark. |
| 94 | + """ |
| 95 | + |
| 96 | + topology: TiltedSquareLattice |
| 97 | + macrocycle_depth: int |
| 98 | + instance_i: int |
| 99 | + n_repetitions: int |
| 100 | + executable_family: str = 'recirq.otoc.loschmidt.tilted_square_lattice' |
| 101 | + |
| 102 | + @classmethod |
| 103 | + def _json_namespace_(cls): |
| 104 | + return 'recirq.otoc' |
| 105 | + |
| 106 | + def _json_dict_(self): |
| 107 | + return dataclass_json_dict(self, namespace=self._json_namespace_()) |
| 108 | + |
| 109 | + |
| 110 | +def get_all_tilted_square_lattice_executables( |
| 111 | + *, n_instances=10, n_repetitions=1_000, |
| 112 | + min_side_length=2, max_side_length=8, |
| 113 | + side_length_step=2, |
| 114 | + macrocycle_depths=None, seed=52, |
| 115 | +) -> QuantumExecutableGroup: |
| 116 | + """Return a collection of quantum executables for various parameter settings of the tilted |
| 117 | + square lattice loschmidt benchmark. |
| 118 | +
|
| 119 | + Args: |
| 120 | + n_instances: The number of random instances to make per setting |
| 121 | + n_repetitions: The number of circuit repetitions to use for measuring the return |
| 122 | + probability. |
| 123 | + min_side_length, max_side_length, side_length_step: generate a range of |
| 124 | + TiltedSquareLattice topologies with widths and heights in this range. |
| 125 | + seed: The random seed to make this deterministic. |
| 126 | + macrocycle_depths: The collection of macrocycle depths to use per setting. |
| 127 | + """ |
| 128 | + |
| 129 | + rs = np.random.RandomState(seed) |
| 130 | + if macrocycle_depths is None: |
| 131 | + macrocycle_depths = np.arange(2, 8 + 1, 2) |
| 132 | + topologies = _get_all_tilted_square_lattices( |
| 133 | + min_side_length=min_side_length, |
| 134 | + max_side_length=max_side_length, |
| 135 | + side_length_step=side_length_step, |
| 136 | + ) |
| 137 | + |
| 138 | + specs = [ |
| 139 | + TiltedSquareLatticeLoschmidtSpec( |
| 140 | + topology=topology, |
| 141 | + macrocycle_depth=macrocycle_depth, |
| 142 | + instance_i=instance_i, |
| 143 | + n_repetitions=n_repetitions |
| 144 | + ) |
| 145 | + for topology, macrocycle_depth, instance_i in itertools.product( |
| 146 | + topologies, macrocycle_depths, range(n_instances)) |
| 147 | + ] |
| 148 | + |
| 149 | + return QuantumExecutableGroup([ |
| 150 | + QuantumExecutable( |
| 151 | + spec=spec, |
| 152 | + problem_topology=spec.topology, |
| 153 | + circuit=create_tilted_square_lattice_loschmidt_echo_circuit( |
| 154 | + topology=spec.topology, |
| 155 | + macrocycle_depth=spec.macrocycle_depth, |
| 156 | + rs=rs, |
| 157 | + ), |
| 158 | + measurement=BitstringsMeasurement(spec.n_repetitions) |
| 159 | + ) |
| 160 | + for spec in specs |
| 161 | + ]) |
0 commit comments