Skip to content

Commit 9c9a191

Browse files
authored
Merge branch 'master' into mh-fix-np-warnings
2 parents d0bf4c1 + aa79bd8 commit 9c9a191

File tree

7 files changed

+310
-9
lines changed

7 files changed

+310
-9
lines changed

recirq/kpz/experiment.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def _kurtosis_excluding_i(self, i: int) -> float:
166166
)
167167

168168
def jackknife_mean(self) -> float:
169-
"""Compute the statistical uncertainty of the mean using the remove-one jackknife.
169+
r"""Compute the statistical uncertainty of the mean using the remove-one jackknife.
170170
If there is only one initial state (for example if $\mu = \infty$), zero uncertainty
171171
is returned.
172172
"""
@@ -176,7 +176,7 @@ def jackknife_mean(self) -> float:
176176
return np.std(mean_i) * np.sqrt(self.num_initial_states - 1)
177177

178178
def jackknife_variance(self) -> float:
179-
"""Compute the statistical uncertainty of the variance using the remove-one jackknife.
179+
r"""Compute the statistical uncertainty of the variance using the remove-one jackknife.
180180
If there is only one initial state (for example if $\mu = \infty$), zero uncertainty
181181
is returned.
182182
"""
@@ -188,7 +188,7 @@ def jackknife_variance(self) -> float:
188188
return np.std(variance_i) * np.sqrt(self.num_initial_states - 1)
189189

190190
def jackknife_skew(self) -> float:
191-
"""Compute the statistical uncertainty of the skewness using the remove-one jackknife.
191+
r"""Compute the statistical uncertainty of the skewness using the remove-one jackknife.
192192
If there is only one initial state (for example if $\mu = \infty$), zero uncertainty
193193
is returned.
194194
"""
@@ -198,7 +198,7 @@ def jackknife_skew(self) -> float:
198198
return np.std(skew_i) * np.sqrt(self.num_initial_states - 1)
199199

200200
def jackknife_kurtosis(self) -> float:
201-
"""Compute the statistical uncertainty of the kurtosis using the remove-one jackknife.
201+
r"""Compute the statistical uncertainty of the kurtosis using the remove-one jackknife.
202202
If there is only one initial state (for example if $\mu = \infty$), zero uncertainty
203203
is returned.
204204
"""
@@ -234,7 +234,7 @@ def plot_histogram(self, ax: Optional[Union[None, plt.Axes]] = None) -> plt.Axes
234234
edgecolor="k",
235235
)
236236
ax.tick_params(direction="in", top=True, right=True)
237-
ax.set_xlabel("Number of 1s that crossed center, $\mathcal{M}/2$")
237+
ax.set_xlabel(r"Number of 1s that crossed center, $\mathcal{M}/2$")
238238
ax.set_ylabel("Probability")
239239
return ax
240240

@@ -382,13 +382,13 @@ def plot_histogram(self, ax: Optional[Union[None, plt.Axes]] = None) -> plt.Axes
382382
edgecolor="k",
383383
)
384384
ax.tick_params(direction="in", top=True, right=True)
385-
ax.set_xlabel("Number of 1s that crossed center, $\mathcal{M}/2$")
385+
ax.set_xlabel(r"Number of 1s that crossed center, $\mathcal{M}/2$")
386386
ax.set_ylabel("Probability")
387387
return ax
388388

389389

390390
class KPZExperiment:
391-
"""A class for running/simulating the KPZ experiment.
391+
r"""A class for running/simulating the KPZ experiment.
392392
393393
This class implements 1D Floquet XXZ dynamics, realized as alternating layers of fSim
394394
gates. The initial states, parameterized by mu, interpolate between an

recirq/qaoa/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,30 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
15+
from functools import lru_cache
16+
from typing import Optional
17+
18+
from cirq.protocols.json_serialization import ObjectFactory, DEFAULT_RESOLVERS
19+
from .sk_model import (
20+
SKModelQAOASpec,
21+
)
22+
23+
24+
@lru_cache()
25+
def _resolve_json(cirq_type: str) -> Optional[ObjectFactory]:
26+
"""Resolve the types of `recirq.qaoa.` json objects.
27+
28+
This is a Cirq JSON resolver suitable for appending to
29+
`cirq.protocols.json_serialization.DEFAULT_RESOLVERS`.
30+
"""
31+
if not cirq_type.startswith('recirq.qaoa.'):
32+
return None
33+
34+
cirq_type = cirq_type[len('recirq.qaoa.'):]
35+
return {k.__name__: k for k in [
36+
SKModelQAOASpec,
37+
]}.get(cirq_type, None)
38+
39+
40+
DEFAULT_RESOLVERS.append(_resolve_json)

recirq/qaoa/classical_angle_optimization.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
from timeit import default_timer as timer
16+
from typing import List
1617

1718
import networkx as nx
1819
import numpy as np
@@ -53,7 +54,7 @@ def optimize_instance_interp_heuristic(graph: nx.Graph,
5354
param_guess_at_p1=None,
5455
node_to_index_map=None,
5556
dtype=np.complex128,
56-
verbose=False):
57+
verbose=False) -> List[OptimizationResult]:
5758
r"""
5859
Given a graph, find QAOA parameters that minimizes C=\sum_{<ij>} w_{ij} Z_i Z_j
5960

recirq/qaoa/problem_circuits.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
compile_to_non_negligible,
1515
validate_well_structured,
1616
compile_problem_unitary_to_swap_network, compile_swap_network_to_zzswap,
17-
measure_with_final_permutation, compile_problem_unitary_to_arbitrary_zz)
17+
measure_with_final_permutation, compile_problem_unitary_to_arbitrary_zz, ZZSwap)
1818
from recirq.qaoa.placement import place_on_device
1919
from recirq.qaoa.problems import HardwareGridProblem, SKProblem, ThreeRegularProblem
2020

@@ -122,10 +122,17 @@ def get_routed_sk_model_circuit(
122122
qubits: List[cirq.Qid],
123123
gammas: Sequence[float],
124124
betas: Sequence[float],
125+
*,
126+
keep_zzswap_as_one_op=True,
125127
) -> cirq.Circuit:
126128
"""Get a QAOA circuit for a fully-connected problem using the linear swap
127129
network.
128130
131+
This leaves the circuit in an "uncompiled" form, using ZZ, Swap, and/or ZZSwap gates
132+
for the two-qubit gate and will append a permutation gate for odd p depths. The former
133+
should be compiled to hardware-native two-qubit gates; the latter can be absorbed into
134+
analysis routines.
135+
129136
See Also:
130137
:py:func:`get_compiled_sk_model_circuit`
131138
@@ -135,11 +142,18 @@ def get_routed_sk_model_circuit(
135142
qubits: The qubits to use in construction of the circuit.
136143
gammas: Gamma angles to use as parameters for problem unitaries
137144
betas: Beta angles to use as parameters for driver unitaries
145+
keep_zzswap_as_one_op: If True, use `recirq.qaoa.gates_and_compilation.ZZSwap`
146+
custom, composite gate. This is required for using `get_compiled_sk_model_circuit`
147+
and `compile_to_syc`. Otherwise, decompose each ZZSwap operation into a ZZPowGate
148+
and SWAP gate. This is useful if you plan to use vanilla Cirq transformers.
138149
"""
139150
circuit = get_generic_qaoa_circuit(problem_graph, qubits, gammas, betas)
140151
circuit = compile_problem_unitary_to_swap_network(circuit)
141152
circuit = compile_swap_network_to_zzswap(circuit)
142153
circuit = compile_driver_unitary_to_rx(circuit)
154+
if not keep_zzswap_as_one_op:
155+
circuit = cirq.expand_composite(
156+
circuit, no_decomp=lambda op: not isinstance(op.gate, ZZSwap))
143157
return circuit
144158

145159

recirq/qaoa/sk_model/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .sk_model import *

recirq/qaoa/sk_model/sk_model.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
from typing import List, Tuple, Iterable, Sequence
18+
19+
import networkx as nx
20+
import numpy as np
21+
22+
import cirq
23+
from cirq.protocols import dataclass_json_dict
24+
from cirq_google.workflow import QuantumExecutable, BitstringsMeasurement, QuantumExecutableGroup, \
25+
ExecutableSpec
26+
from recirq.qaoa.classical_angle_optimization import optimize_instance_interp_heuristic
27+
from recirq.qaoa.problem_circuits import get_routed_sk_model_circuit
28+
29+
30+
def _graph_from_row_major_upper_triangular(
31+
all_to_all_couplings: Sequence[float], *, n: int
32+
) -> nx.Graph:
33+
"""Get `all_to_all_couplings` in the form of a NetworkX graph."""
34+
if not len(all_to_all_couplings) == n * (n - 1) / 2:
35+
raise ValueError("Number of couplings does not match the number of nodes.")
36+
37+
g = nx.Graph()
38+
for (u, v), coupling in zip(itertools.combinations(range(n), r=2), all_to_all_couplings):
39+
g.add_edge(u, v, weight=coupling)
40+
return g
41+
42+
43+
def _all_to_all_couplings_from_graph(graph: nx.Graph) -> Tuple[int, ...]:
44+
"""Given a networkx graph, turn it into a tuple of all-to-all couplings."""
45+
n = graph.number_of_nodes()
46+
if not sorted(graph.nodes) == sorted(range(n)):
47+
raise ValueError("Nodes must be contiguous and zero-indexed.")
48+
49+
edges = graph.edges
50+
return tuple(edges[u, v]['weight'] for u, v in itertools.combinations(range(n), r=2))
51+
52+
53+
@dataclass(frozen=True)
54+
class SKModelQAOASpec(ExecutableSpec):
55+
"""ExecutableSpec for running SK-model QAOA.
56+
57+
QAOA uses alternating applications of a problem-specific entangling unitary and a
58+
problem-agnostic driver unitary. It is a variational algorithm, but for this spec
59+
we rely on optimizing the angles via classical simulation.
60+
61+
The SK model is an all-to-all 2-body spin problem that we can route using the
62+
"swap network" to require only linear connectivity (but circuit depth scales with problem
63+
size)
64+
65+
Args:
66+
n_nodes: The number of nodes in the SK problem. This is equal to the number of qubits.
67+
all_to_all_couplings: The n(n-1)/2 pairwise coupling constants that defines the problem
68+
as a serializable tuple of the row-major upper triangular coupling matrix.
69+
p_depth: The depth hyperparemeter that presecribes the number of U_problem * U_driver
70+
repetitions.
71+
n_repetitions: The number of shots to take when running the circuits.
72+
executable_family: `recirq.qaoa.sk_model`.
73+
74+
"""
75+
76+
n_nodes: int
77+
all_to_all_couplings: Tuple[int, ...]
78+
p_depth: int
79+
n_repetitions: int
80+
executable_family: str = 'recirq.qaoa.sk_model'
81+
82+
def __post_init__(self):
83+
object.__setattr__(self, 'all_to_all_couplings', tuple(self.all_to_all_couplings))
84+
85+
def get_graph(self) -> nx.Graph:
86+
"""Get `all_to_all_couplings` in the form of a NetworkX graph."""
87+
return _graph_from_row_major_upper_triangular(self.all_to_all_couplings, n=self.n_nodes)
88+
89+
@staticmethod
90+
def get_all_to_all_couplings_from_graph(graph: nx.Graph) -> Tuple[int, ...]:
91+
"""Given a networkx graph, turn it into a tuple of all-to-all couplings."""
92+
return _all_to_all_couplings_from_graph(graph)
93+
94+
@classmethod
95+
def _json_namespace_(cls):
96+
return 'recirq.qaoa'
97+
98+
def _json_dict_(self):
99+
return dataclass_json_dict(self, namespace=self._json_namespace_())
100+
101+
102+
def _classically_optimize_qaoa_parameters(graph: nx.Graph, *, n: int, p_depth: int):
103+
param_guess = [
104+
np.arccos(np.sqrt((1 + np.sqrt((n - 2) / (n - 1))) / 2)),
105+
-np.pi / 8
106+
]
107+
108+
optima = optimize_instance_interp_heuristic(
109+
graph=graph,
110+
# Potential performance improvement: To optimize for a given p_depth,
111+
# we also find the optima for lower p values.
112+
# You could cache these instead of re-finding for each executable.
113+
p_max=p_depth,
114+
param_guess_at_p1=param_guess,
115+
verbose=True,
116+
)
117+
# The above returns a list, but since we asked for p_max = spec.p_depth,
118+
# we always want the last one.
119+
optimum = optima[-1]
120+
assert optimum.p == p_depth
121+
return optimum
122+
123+
124+
def sk_model_qaoa_spec_to_exe(
125+
spec: SKModelQAOASpec,
126+
) -> QuantumExecutable:
127+
"""Create a full `QuantumExecutable` from a given `SKModelQAOASpec`
128+
129+
Args:
130+
spec: The spec
131+
132+
Returns:
133+
a QuantumExecutable corresponding to the input specification.
134+
"""
135+
n = spec.n_nodes
136+
graph = spec.get_graph()
137+
138+
# Get params
139+
optimum = _classically_optimize_qaoa_parameters(graph, n=n, p_depth=spec.p_depth)
140+
141+
# Make the circuit
142+
qubits = cirq.LineQubit.range(n)
143+
circuit = get_routed_sk_model_circuit(
144+
graph, qubits, optimum.gammas, optimum.betas, keep_zzswap_as_one_op=False)
145+
146+
# QAOA code optionally finishes with a QubitPermutationGate, which we want to
147+
# absorb into measurement. Maybe at some point this can be part of
148+
# `cg.BitstringsMeasurement`, but for now we'll do it implicitly in the analysis code.
149+
if spec.p_depth % 2 == 1:
150+
assert len(circuit[-1]) == 1
151+
permute_op, = circuit[-1]
152+
assert isinstance(permute_op.gate, cirq.QubitPermutationGate)
153+
circuit = circuit[:-1]
154+
155+
# Measure
156+
circuit += cirq.measure(*qubits, key='z')
157+
158+
return QuantumExecutable(
159+
spec=spec,
160+
problem_topology=cirq.LineTopology(n),
161+
circuit=circuit,
162+
measurement=BitstringsMeasurement(spec.n_repetitions),
163+
)

0 commit comments

Comments
 (0)