Skip to content

Commit 8ce5c4a

Browse files
authored
loschmidt.tilted_square_lattice Benchmark (#236)
* loschmidt.tilted_square_lattice benchmark Change-Id: Id74c4e1c56cd7702862f6972d49d035eb83dd166 * better description of benchmark Change-Id: Ie637306bd284cd20b09f464ee8551d48d1a1d684 * Use deprecated json namespace for now Change-Id: Iadcaac412d43ccaa32140e7a0418011620ee8d8f * Disable tesing against cirq previous Change-Id: Ia229563ad399f1ae5e5c9916e70586623babb2c1 * copyright * note json resolver * test without monkeypatch * comment test Change-Id: I48a9d4cd5e507d344b0f8f32f88c7485d627e0e2 * happy new year Change-Id: Ib42d67bfc938e5d1fbcdef4a5aaf1cdb9d4d098c * happy new year Change-Id: Ic9d4c7b3e2948a6d1121d7d915e525c38baad27a
1 parent ad2cb61 commit 8ce5c4a

File tree

7 files changed

+316
-6
lines changed

7 files changed

+316
-6
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ jobs:
2525
runs-on: ubuntu-latest
2626
strategy:
2727
matrix:
28-
cirq-version: [ 'previous', 'current', 'next' ]
28+
cirq-version:
29+
- 'current'
30+
# - 'previous' (gh-247: Disabled 2012-12-15 for cirq.NamedTopologies)
31+
- 'next'
2932
fail-fast: false
3033
steps:
3134
- uses: actions/checkout@v2

recirq/algorithmic_benchmark_library.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
import pandas as pd
77

8+
from recirq.otoc.loschmidt.tilted_square_lattice import (
9+
TiltedSquareLatticeLoschmidtSpec,
10+
get_all_tilted_square_lattice_executables,
11+
)
12+
813
try:
914
from cirq_google.workflow import ExecutableSpec, QuantumExecutableGroup
1015

@@ -32,7 +37,6 @@ class AlgorithmicBenchmark:
3237
The executable family is the fully-qualified leaf-module where the code
3338
for this AlgorithmicBenchmark lives.
3439
spec_class: The ExecutableSpec subclass for this AlgorithmicBenchmark.
35-
data_class: The class which can contain ETL-ed data for this AlgorithmicBenchmark.
3640
executable_generator_func: The function that returns a QuantumExecutableGroup for a
3741
given Config.
3842
configs: A list of available `BenchmarkConfig` for this benchmark.
@@ -42,15 +46,13 @@ class AlgorithmicBenchmark:
4246
name: str
4347
executable_family: str
4448
spec_class: Type['ExecutableSpec']
45-
data_class: Type
4649
executable_generator_func: Callable[[Any], 'QuantumExecutableGroup']
4750
configs: List['BenchmarkConfig']
4851

4952
def as_strings(self):
5053
"""Get values of this class as strings suitable for printing."""
5154
ret = {k: str(v) for k, v in dataclasses.asdict(self).items()}
5255
ret['spec_class'] = self.spec_class.__name__
53-
ret['data_class'] = self.data_class.__name__
5456
ret['executable_generator_func'] = self.executable_generator_func.__name__
5557
return ret
5658

@@ -74,6 +76,15 @@ class BenchmarkConfig:
7476

7577

7678
BENCHMARKS = [
79+
AlgorithmicBenchmark(
80+
domain='recirq.otoc',
81+
name='loschmidt.tilted_square_lattice',
82+
executable_family='recirq.otoc.loschmidt.tilted_square_lattice',
83+
spec_class=TiltedSquareLatticeLoschmidtSpec,
84+
executable_generator_func=get_all_tilted_square_lattice_executables,
85+
configs=[
86+
]
87+
),
7788
]
7889

7990

recirq/algorithmic_benchmark_library_test.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ def test_classes_and_funcs(algo):
4949
mod = import_module(algo.executable_family)
5050
assert algo.spec_class == getattr(mod, algo.spec_class.__name__), \
5151
"The spec_class must exist in the benchmark's module"
52-
assert algo.data_class == getattr(mod, algo.data_class.__name__), \
53-
"The data_class must exist in the benchmark's module"
5452
assert algo.executable_generator_func == getattr(mod, algo.executable_generator_func.__name__), \
5553
"the executable_generator_func must exist in the benchmark's module"
5654

recirq/otoc/loschmidt/__init__.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
from functools import lru_cache
15+
from typing import Optional
16+
17+
from cirq.protocols.json_serialization import ObjectFactory, DEFAULT_RESOLVERS
18+
from .tilted_square_lattice import (
19+
TiltedSquareLatticeLoschmidtSpec,
20+
)
21+
22+
23+
@lru_cache()
24+
def _resolve_json(cirq_type: str) -> Optional[ObjectFactory]:
25+
"""Resolve the types of `recirq.otoc.` json objects.
26+
27+
This is a Cirq JSON resolver suitable for appending to
28+
`cirq.protocols.json_serialization.DEFAULT_RESOLVERS`.
29+
"""
30+
if not cirq_type.startswith('recirq.otoc.'):
31+
return None
32+
33+
cirq_type = cirq_type[len('recirq.otoc.'):]
34+
return {k.__name__: k for k in [
35+
TiltedSquareLatticeLoschmidtSpec,
36+
]}.get(cirq_type, None)
37+
38+
39+
DEFAULT_RESOLVERS.append(_resolve_json)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .tilted_square_lattice import *
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
])
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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 numpy as np
16+
17+
import cirq
18+
# Need this exact import for monkeypatching to work (below)
19+
import recirq.otoc.loschmidt.tilted_square_lattice.tilted_square_lattice
20+
from recirq.otoc.loschmidt.tilted_square_lattice import \
21+
create_tilted_square_lattice_loschmidt_echo_circuit, TiltedSquareLatticeLoschmidtSpec, \
22+
get_all_tilted_square_lattice_executables
23+
24+
25+
def test_create_tilted_square_lattice_loschmidt_echo_circuit():
26+
topology = cirq.TiltedSquareLattice(width=3, height=2)
27+
macrocycle_depth = 2
28+
circuit = create_tilted_square_lattice_loschmidt_echo_circuit(
29+
topology=topology, macrocycle_depth=macrocycle_depth, rs=np.random.RandomState(52)
30+
)
31+
assert isinstance(circuit, cirq.FrozenCircuit)
32+
33+
assert len(circuit.all_qubits()) == topology.n_nodes
34+
assert sorted(circuit.all_qubits()) == sorted(topology.nodes_as_gridqubits())
35+
36+
edge_coloring_n = 4 # grid
37+
forward_backward = 2
38+
n_moment_per_microcycle = 2 # layer of single- and two- qubit gate
39+
measure_moment = 1
40+
extra_single_q_layer = 1
41+
assert len(circuit) == (edge_coloring_n * macrocycle_depth *
42+
n_moment_per_microcycle + extra_single_q_layer) \
43+
* forward_backward + measure_moment
44+
45+
46+
def test_tilted_square_lattice_loschmidt_spec(tmpdir):
47+
topology = cirq.TiltedSquareLattice(width=3, height=2)
48+
macrocycle_depth = 2
49+
spec1 = TiltedSquareLatticeLoschmidtSpec(
50+
topology=topology,
51+
macrocycle_depth=macrocycle_depth,
52+
instance_i=0,
53+
n_repetitions=10_000,
54+
)
55+
assert spec1.executable_family == 'recirq.otoc.loschmidt.tilted_square_lattice'
56+
57+
fn = f'{tmpdir}/spec.json'
58+
cirq.to_json(spec1, fn)
59+
spec2 = cirq.read_json(fn)
60+
assert spec1 == spec2
61+
62+
63+
def test_get_all_tilted_square_lattice_executables_default_args(monkeypatch):
64+
call_count = 0
65+
66+
# Creating random circuits is expensive, but this test tests the default arguments for
67+
# this function and asserts that it's called the correct number of times.
68+
69+
def mock_get_circuit(topology: cirq.TiltedSquareLattice, macrocycle_depth: int,
70+
twoq_gate: cirq.Gate = cirq.FSimGate(np.pi / 4, 0.0),
71+
rs: cirq.RANDOM_STATE_OR_SEED_LIKE = None):
72+
nonlocal call_count
73+
call_count += 1
74+
return cirq.Circuit()
75+
76+
monkeypatch.setattr(recirq.otoc.loschmidt.tilted_square_lattice.tilted_square_lattice,
77+
"create_tilted_square_lattice_loschmidt_echo_circuit", mock_get_circuit)
78+
get_all_tilted_square_lattice_executables()
79+
n_instances = 10
80+
n_macrocycle_depths = 4 # 2,4,6,8
81+
n_side_lengths = 4 # width or height # of possibilities
82+
n_topos = n_side_lengths * (n_side_lengths + 1) / 2
83+
assert call_count == n_instances * n_macrocycle_depths * n_topos
84+
85+
86+
def test_get_all_tilted_square_lattice_executables():
87+
exes = get_all_tilted_square_lattice_executables(
88+
n_instances=2,
89+
min_side_length=2,
90+
max_side_length=3,
91+
side_length_step=1,
92+
macrocycle_depths=[2, 4],
93+
)
94+
assert sorted({exe.spec.instance_i for exe in exes}) == [0, 1]
95+
assert sorted({exe.spec.macrocycle_depth for exe in exes}) == [2, 4]
96+
assert sorted({exe.spec.topology.width for exe in exes}) == [2, 3]
97+
assert sorted({exe.spec.topology.height for exe in exes}) == [2, 3]

0 commit comments

Comments
 (0)