openquantum-sdk-cirq 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ # Unit tests ship in the sdist. The wheel does not install them.
2
+ include README.md
3
+ include pyproject.toml
4
+ recursive-include cirq_openquantum *.py
5
+ recursive-include tests *.py
6
+ global-exclude *.pyc
7
+ global-exclude __pycache__
8
+ prune .venv
9
+ prune .pytest_cache
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: openquantum-sdk-cirq
3
+ Version: 0.5.0
4
+ Summary: Cirq Python SDK for Open Quantum
5
+ License: Apache-2.0
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: openquantum-sdk>=1.0.0
13
+ Requires-Dist: cirq-core>=1.4
14
+ Requires-Dist: numpy>=1.22
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7.0; extra == "dev"
17
+ Requires-Dist: pytest-cov; extra == "dev"
18
+ Requires-Dist: pytest-mock>=3.10; extra == "dev"
19
+
20
+ # Cirq-Open Quantum Plugin
21
+
22
+ > **Beta.** This plugin is in beta; its API and defaults may still change. Feedback is welcome in the [Quantum Rings community](https://www.quantumrings.com/community).
23
+
24
+ A [Cirq](https://quantumai.google/cirq) sampler that submits circuits to the
25
+ [Open Quantum](https://www.openquantum.com) platform. Write circuits with Cirq
26
+ and run them on real quantum hardware.
27
+
28
+ ## How it works
29
+
30
+ Cirq exports OpenQASM natively (`cirq.Circuit.to_qasm()`), so this plugin does
31
+ not depend on the Qiskit plugin. `OpenQuantumSampler` is a thin
32
+ [`cirq.Sampler`](https://quantumai.google/reference/python/cirq/Sampler):
33
+
34
+ - `OpenQuantumClient.from_env` (Core SDK) handles authentication and backend
35
+ discovery.
36
+ - Each resolved circuit is serialized to OpenQASM and submitted through the
37
+ framework-agnostic `SchedulerClient`.
38
+ - Platform counts are reconstructed into `cirq.Result` objects.
39
+
40
+ Because it implements `run_sweep`, the standard `cirq.Sampler` methods —
41
+ `run`, `sample`, `run_sweep`, and `run_batch` — all work.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ # Plugin (and core SDK)
47
+ pip install openquantum-sdk-cirq
48
+
49
+ # or via extras:
50
+ pip install "openquantum-sdk[cirq]"
51
+ ```
52
+
53
+ This pulls in `cirq-core` and `openquantum-sdk`.
54
+
55
+ ## Quick Start
56
+
57
+ ```python
58
+ import cirq
59
+ from cirq_openquantum import OpenQuantumSampler
60
+
61
+ sampler = OpenQuantumSampler(
62
+ backend="ionq:forte-1",
63
+ client_id="your_client_id",
64
+ client_secret="your_client_secret",
65
+ )
66
+
67
+ q = cirq.LineQubit.range(2)
68
+ circuit = cirq.Circuit([
69
+ cirq.H(q[0]),
70
+ cirq.CNOT(q[0], q[1]),
71
+ cirq.measure(*q, key="m"),
72
+ ])
73
+
74
+ result = sampler.run(circuit, repetitions=1024)
75
+ print(result.histogram(key="m"))
76
+ # Counter({0: ~512, 3: ~512}) # 00 and 11
77
+ ```
78
+
79
+ Circuits must contain at least one `cirq.measure(...)`; the QASM exporter does
80
+ not add measurements for you.
81
+
82
+ ## Authentication
83
+
84
+ Resolved in this order:
85
+
86
+ 1. **Pre-built client** — pass `client=OpenQuantumClient(...)` for full control
87
+ (saved accounts, custom URLs, keyring).
88
+ 2. **Bearer token** — pass `token=` or set `OPENQUANTUM_TOKEN`.
89
+ 3. **Client credentials** — pass `client_id=` + `client_secret=` or set
90
+ `OPENQUANTUM_CLIENT_ID` + `OPENQUANTUM_CLIENT_SECRET`.
91
+
92
+ | Parameter | Environment Variable | Description |
93
+ |-----------|---------------------|-------------|
94
+ | `token` | `OPENQUANTUM_TOKEN` | Bearer token |
95
+ | `client_id` | `OPENQUANTUM_CLIENT_ID` | OAuth2 client ID |
96
+ | `client_secret` | `OPENQUANTUM_CLIENT_SECRET` | OAuth2 client secret |
97
+ | | `OPENQUANTUM_SDK_KEY` | Path to an SDK-key JSON file (`{client_id, client_secret}`) |
98
+ | `organization_id` | `OPENQUANTUM_ORGANIZATION_ID` | Organization UUID (optional if single org) |
99
+ | `scheduler_url` | `OPENQUANTUM_SCHEDULER_URL` | Scheduler API base, when the argument is omitted |
100
+ | `management_url` | `OPENQUANTUM_MANAGEMENT_URL` | Management API base, when the argument is omitted |
101
+ | `keycloak_base` | `OPENQUANTUM_KEYCLOAK_BASE` | Keycloak base, when the argument is omitted |
102
+ | `realm` | `OPENQUANTUM_KEYCLOAK_REALM` | Keycloak realm, when the argument is omitted |
103
+
104
+ `from_env()` raises `AuthenticationError` when no credential resolves. The older `OPENQUANTUM_*_BASE` URL names are not read here; those remain an MCP-only fallback.
105
+
106
+ ## Configuration
107
+
108
+ | Parameter | Default | Description |
109
+ |-----------|---------|-------------|
110
+ | `backend` | required | Backend class ID or short_code (e.g. `"ionq:forte-1"`) |
111
+ | `job_subcategory_id` | `"oth:oth"` | Workload subcategory (set the most specific match) |
112
+ | `execution_plan` | `"auto"` | `"auto"`, `"public"`, `"private"`, an enum, or a plan UUID. `"auto"` picks the cheapest plan |
113
+ | `queue_priority` | `"auto"` | `"auto"`, `"standard"`, `"priority"`, `"instant"`, an enum, or a priority UUID. `"auto"` picks the cheapest priority |
114
+ | `export_format` | `"qasm3"` | `"qasm2"` or `"qasm3"` |
115
+ | `configuration_data` | `{}` | Backend-specific config passthrough (e.g. `{"engine": "amber_quantum_rings"}`) |
116
+ | `reverse_bits` | `True` | Reverse platform bitstrings to Cirq's big-endian order |
117
+ | `verbose` | `False` | Print submission progress and credit cost to stdout |
118
+ | `job_timeout_seconds` | `86400` | Max wait for job completion (1 day) |
119
+
120
+ Shots are set per run via `repetitions` and injected into `configuration_data`.
121
+
122
+ ## Backend-specific configuration
123
+
124
+ Some backends require extra fields. The free Quantum Rings simulator
125
+ (`qr:gpu-gb10`) requires an `engine`:
126
+
127
+ ```python
128
+ sampler = OpenQuantumSampler(
129
+ backend="qr:gpu-gb10",
130
+ configuration_data={"engine": "amber_quantum_rings"},
131
+ )
132
+ ```
133
+
134
+ ## Bit ordering
135
+
136
+ The platform returns counts little-endian (the first classical bit is the
137
+ rightmost character, Qiskit-style), while Cirq is big-endian (the first
138
+ measured qubit is the most-significant bit). The sampler reverses each
139
+ bitstring by default (`reverse_bits=True`) so the mapping is correct, verified
140
+ live against an asymmetric `|10>` state. Set `reverse_bits=False` only for a
141
+ backend that returns counts big-endian.
142
+
143
+ ## License
144
+
145
+ Apache License 2.0
@@ -0,0 +1,126 @@
1
+ # Cirq-Open Quantum Plugin
2
+
3
+ > **Beta.** This plugin is in beta; its API and defaults may still change. Feedback is welcome in the [Quantum Rings community](https://www.quantumrings.com/community).
4
+
5
+ A [Cirq](https://quantumai.google/cirq) sampler that submits circuits to the
6
+ [Open Quantum](https://www.openquantum.com) platform. Write circuits with Cirq
7
+ and run them on real quantum hardware.
8
+
9
+ ## How it works
10
+
11
+ Cirq exports OpenQASM natively (`cirq.Circuit.to_qasm()`), so this plugin does
12
+ not depend on the Qiskit plugin. `OpenQuantumSampler` is a thin
13
+ [`cirq.Sampler`](https://quantumai.google/reference/python/cirq/Sampler):
14
+
15
+ - `OpenQuantumClient.from_env` (Core SDK) handles authentication and backend
16
+ discovery.
17
+ - Each resolved circuit is serialized to OpenQASM and submitted through the
18
+ framework-agnostic `SchedulerClient`.
19
+ - Platform counts are reconstructed into `cirq.Result` objects.
20
+
21
+ Because it implements `run_sweep`, the standard `cirq.Sampler` methods —
22
+ `run`, `sample`, `run_sweep`, and `run_batch` — all work.
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ # Plugin (and core SDK)
28
+ pip install openquantum-sdk-cirq
29
+
30
+ # or via extras:
31
+ pip install "openquantum-sdk[cirq]"
32
+ ```
33
+
34
+ This pulls in `cirq-core` and `openquantum-sdk`.
35
+
36
+ ## Quick Start
37
+
38
+ ```python
39
+ import cirq
40
+ from cirq_openquantum import OpenQuantumSampler
41
+
42
+ sampler = OpenQuantumSampler(
43
+ backend="ionq:forte-1",
44
+ client_id="your_client_id",
45
+ client_secret="your_client_secret",
46
+ )
47
+
48
+ q = cirq.LineQubit.range(2)
49
+ circuit = cirq.Circuit([
50
+ cirq.H(q[0]),
51
+ cirq.CNOT(q[0], q[1]),
52
+ cirq.measure(*q, key="m"),
53
+ ])
54
+
55
+ result = sampler.run(circuit, repetitions=1024)
56
+ print(result.histogram(key="m"))
57
+ # Counter({0: ~512, 3: ~512}) # 00 and 11
58
+ ```
59
+
60
+ Circuits must contain at least one `cirq.measure(...)`; the QASM exporter does
61
+ not add measurements for you.
62
+
63
+ ## Authentication
64
+
65
+ Resolved in this order:
66
+
67
+ 1. **Pre-built client** — pass `client=OpenQuantumClient(...)` for full control
68
+ (saved accounts, custom URLs, keyring).
69
+ 2. **Bearer token** — pass `token=` or set `OPENQUANTUM_TOKEN`.
70
+ 3. **Client credentials** — pass `client_id=` + `client_secret=` or set
71
+ `OPENQUANTUM_CLIENT_ID` + `OPENQUANTUM_CLIENT_SECRET`.
72
+
73
+ | Parameter | Environment Variable | Description |
74
+ |-----------|---------------------|-------------|
75
+ | `token` | `OPENQUANTUM_TOKEN` | Bearer token |
76
+ | `client_id` | `OPENQUANTUM_CLIENT_ID` | OAuth2 client ID |
77
+ | `client_secret` | `OPENQUANTUM_CLIENT_SECRET` | OAuth2 client secret |
78
+ | | `OPENQUANTUM_SDK_KEY` | Path to an SDK-key JSON file (`{client_id, client_secret}`) |
79
+ | `organization_id` | `OPENQUANTUM_ORGANIZATION_ID` | Organization UUID (optional if single org) |
80
+ | `scheduler_url` | `OPENQUANTUM_SCHEDULER_URL` | Scheduler API base, when the argument is omitted |
81
+ | `management_url` | `OPENQUANTUM_MANAGEMENT_URL` | Management API base, when the argument is omitted |
82
+ | `keycloak_base` | `OPENQUANTUM_KEYCLOAK_BASE` | Keycloak base, when the argument is omitted |
83
+ | `realm` | `OPENQUANTUM_KEYCLOAK_REALM` | Keycloak realm, when the argument is omitted |
84
+
85
+ `from_env()` raises `AuthenticationError` when no credential resolves. The older `OPENQUANTUM_*_BASE` URL names are not read here; those remain an MCP-only fallback.
86
+
87
+ ## Configuration
88
+
89
+ | Parameter | Default | Description |
90
+ |-----------|---------|-------------|
91
+ | `backend` | required | Backend class ID or short_code (e.g. `"ionq:forte-1"`) |
92
+ | `job_subcategory_id` | `"oth:oth"` | Workload subcategory (set the most specific match) |
93
+ | `execution_plan` | `"auto"` | `"auto"`, `"public"`, `"private"`, an enum, or a plan UUID. `"auto"` picks the cheapest plan |
94
+ | `queue_priority` | `"auto"` | `"auto"`, `"standard"`, `"priority"`, `"instant"`, an enum, or a priority UUID. `"auto"` picks the cheapest priority |
95
+ | `export_format` | `"qasm3"` | `"qasm2"` or `"qasm3"` |
96
+ | `configuration_data` | `{}` | Backend-specific config passthrough (e.g. `{"engine": "amber_quantum_rings"}`) |
97
+ | `reverse_bits` | `True` | Reverse platform bitstrings to Cirq's big-endian order |
98
+ | `verbose` | `False` | Print submission progress and credit cost to stdout |
99
+ | `job_timeout_seconds` | `86400` | Max wait for job completion (1 day) |
100
+
101
+ Shots are set per run via `repetitions` and injected into `configuration_data`.
102
+
103
+ ## Backend-specific configuration
104
+
105
+ Some backends require extra fields. The free Quantum Rings simulator
106
+ (`qr:gpu-gb10`) requires an `engine`:
107
+
108
+ ```python
109
+ sampler = OpenQuantumSampler(
110
+ backend="qr:gpu-gb10",
111
+ configuration_data={"engine": "amber_quantum_rings"},
112
+ )
113
+ ```
114
+
115
+ ## Bit ordering
116
+
117
+ The platform returns counts little-endian (the first classical bit is the
118
+ rightmost character, Qiskit-style), while Cirq is big-endian (the first
119
+ measured qubit is the most-significant bit). The sampler reverses each
120
+ bitstring by default (`reverse_bits=True`) so the mapping is correct, verified
121
+ live against an asymmetric `|10>` state. Set `reverse_bits=False` only for a
122
+ backend that returns counts big-endian.
123
+
124
+ ## License
125
+
126
+ Apache License 2.0
@@ -0,0 +1,7 @@
1
+ """Cirq sampler for the Open Quantum quantum computing platform."""
2
+
3
+ from cirq_openquantum.result import result_from_counts
4
+ from cirq_openquantum.sampler import OpenQuantumSampler
5
+
6
+ __version__ = "0.5.0"
7
+ __all__ = ["OpenQuantumSampler", "result_from_counts"]
@@ -0,0 +1,215 @@
1
+ """Build ``cirq.Result`` objects from Open Quantum platform counts.
2
+
3
+ The platform returns a ``{bitstring: count}`` histogram for each submitted
4
+ circuit. Cirq's result layer wants per-shot measurement records keyed by
5
+ measurement key, so this module expands the histogram back into the
6
+ ``(repetitions, num_qubits)`` ``numpy`` arrays a :class:`cirq.ResultDict`
7
+ expects.
8
+
9
+ **Bitstring layout.** The platform indexes each returned bitstring by
10
+ *classical bit*, not by qubit. It parses the exported OpenQASM, allocates one
11
+ classical register (``creg``/``bit[]``) per measurement, and concatenates them
12
+ little-endian in declaration order (Qiskit convention): the first-declared
13
+ register occupies the least-significant (rightmost) bits, and the rightmost
14
+ character of the string is classical bit 0. ``OpenQuantumSampler`` reverses each
15
+ bitstring by default (``reverse_bits=True``) so the mapping to ascending
16
+ classical-bit index is correct.
17
+
18
+ Because the layout is classical-bit based, it is reconstructed from the exact
19
+ QASM that was submitted: each ``measure q[i] -> m_<key>[j]`` statement ties a
20
+ qubit to a classical bit, and the register declaration order fixes the global
21
+ bit offsets. Slicing by qubit index (as an earlier version did) only coincided
22
+ with the true layout when every qubit was measured once, in qubit order, under
23
+ a single key; reordered, multi-key, or subset measurements came back wrong.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import re
29
+ from typing import Dict, List, Mapping, Optional, Sequence, Tuple
30
+
31
+ import cirq
32
+ import numpy as np
33
+
34
+ from openquantum_sdk.exceptions import CircuitValidationError, JobError
35
+
36
+ # QASM 2.0: ``creg name[width];`` QASM 3.0: ``bit[width] name;`` / ``bit name;``
37
+ _DECL_CREG = re.compile(r"\bcreg\s+(\w+)\s*\[\s*(\d+)\s*\]")
38
+ _DECL_BIT_N = re.compile(r"\bbit\s*\[\s*(\d+)\s*\]\s*(\w+)")
39
+ _DECL_BIT_1 = re.compile(r"\bbit\s+(\w+)\s*;")
40
+ # QASM 2.0: ``measure q[i] -> name[j];`` QASM 3.0: ``name[j] = measure q[i];``
41
+ _MEAS_Q2 = re.compile(r"\bmeasure\s+q\s*\[\s*(\d+)\s*\]\s*->\s*(\w+)\s*\[\s*(\d+)\s*\]")
42
+ _MEAS_Q3 = re.compile(r"(\w+)\s*\[\s*(\d+)\s*\]\s*=\s*measure\s+q\s*\[\s*(\d+)\s*\]")
43
+
44
+
45
+ def _parse_qasm_registers(qasm: str):
46
+ """Parse classical register declarations and measurements from QASM.
47
+
48
+ Returns ``(cregs, offsets, total, creg_qubits)`` where ``cregs`` is the
49
+ ordered ``[(name, width)]`` list of register declarations, ``offsets`` maps
50
+ each register name to its global classical-bit offset (first-declared
51
+ register at offset 0, i.e. least significant), ``total`` is the total
52
+ classical-bit width, and ``creg_qubits`` maps each register name to
53
+ ``{creg_bit: qubit_index}`` recovered from the measure statements.
54
+ """
55
+ cregs: List[Tuple[str, int]] = [] # (name, width) in declaration order
56
+ creg_qubits: Dict[str, Dict[int, int]] = {}
57
+
58
+ for raw in qasm.splitlines():
59
+ line = raw.split("//", 1)[0] # drop trailing comments
60
+ if not line.strip():
61
+ continue
62
+
63
+ bit_n = _DECL_BIT_N.search(line)
64
+ creg = _DECL_CREG.search(line)
65
+ bit_1 = _DECL_BIT_1.search(line)
66
+ if bit_n:
67
+ cregs.append((bit_n.group(2), int(bit_n.group(1))))
68
+ elif creg:
69
+ cregs.append((creg.group(1), int(creg.group(2))))
70
+ elif bit_1:
71
+ cregs.append((bit_1.group(1), 1))
72
+
73
+ for mo in _MEAS_Q2.finditer(line):
74
+ qidx, name, cbit = int(mo.group(1)), mo.group(2), int(mo.group(3))
75
+ creg_qubits.setdefault(name, {})[cbit] = qidx
76
+ for mo in _MEAS_Q3.finditer(line):
77
+ name, cbit, qidx = mo.group(1), int(mo.group(2)), int(mo.group(3))
78
+ creg_qubits.setdefault(name, {})[cbit] = qidx
79
+
80
+ offsets: Dict[str, int] = {}
81
+ offset = 0
82
+ for name, width in cregs:
83
+ offsets[name] = offset
84
+ offset += width
85
+ return cregs, offsets, offset, creg_qubits
86
+
87
+
88
+ def _measurement_layout(
89
+ circuit, qasm: str
90
+ ) -> Tuple[List[Tuple[str, List[int]]], int]:
91
+ """Return ``([(key, global_classical_bits)], total_bits)`` for a circuit.
92
+
93
+ Each measurement key is matched to the classical register that captured it
94
+ by comparing the ordered tuple of measured qubit indices (recovered from the
95
+ QASM measure statements) rather than by register name, since Cirq sanitises
96
+ unsafe keys into names like ``m0``/``m1`` that do not echo the key. Column
97
+ ``c`` of a key's result array maps to its register's classical bit ``c``.
98
+ """
99
+ cregs, offsets, total, creg_qubits = _parse_qasm_registers(qasm)
100
+
101
+ ordered_qubits = cirq.QubitOrder.DEFAULT.order_for(circuit.all_qubits())
102
+ index_of = {q: i for i, q in enumerate(ordered_qubits)}
103
+
104
+ # Ordered qubit-index tuple each register captured, used for matching.
105
+ creg_tuple = {
106
+ name: tuple(creg_qubits.get(name, {}).get(c) for c in range(width))
107
+ for name, width in cregs
108
+ }
109
+
110
+ layout: List[Tuple[str, List[int]]] = []
111
+ used = set()
112
+ for op in circuit.all_operations():
113
+ gate = getattr(op, "gate", None)
114
+ if not isinstance(gate, cirq.MeasurementGate):
115
+ continue
116
+ key = cirq.measurement_key_name(op)
117
+ qtuple = tuple(index_of[q] for q in op.qubits)
118
+
119
+ match = None
120
+ for name, _width in cregs:
121
+ if name not in used and creg_tuple[name] == qtuple:
122
+ match = name
123
+ break
124
+ if match is None:
125
+ raise JobError(
126
+ f"Could not map measurement key {key!r} (qubits {qtuple}) to a "
127
+ f"classical register in the submitted QASM."
128
+ )
129
+ used.add(match)
130
+ base = offsets[match]
131
+ layout.append((key, [base + c for c in range(len(qtuple))]))
132
+
133
+ return layout, total
134
+
135
+
136
+ def result_from_counts(
137
+ counts: Mapping[str, int],
138
+ circuit,
139
+ params=None,
140
+ *,
141
+ reverse_bits: bool = False,
142
+ qasm: Optional[str] = None,
143
+ ):
144
+ """Reconstruct a ``cirq.ResultDict`` from a platform counts histogram.
145
+
146
+ Args:
147
+ counts: ``{bitstring: count}`` as returned by the scheduler.
148
+ circuit: The resolved ``cirq.Circuit`` that was submitted.
149
+ params: The ``cirq.ParamResolver`` used for this circuit (stored on the
150
+ result so sweeps round-trip their parameters).
151
+ reverse_bits: Reverse each platform bitstring before mapping. The
152
+ platform returns counts little-endian (classical bit 0 is the
153
+ rightmost character), so this must be ``True`` to map onto ascending
154
+ classical-bit index. The sampler passes ``True`` by default.
155
+ qasm: The exact OpenQASM submitted for this circuit. When omitted it is
156
+ regenerated with ``circuit.to_qasm()``; pass the submitted string to
157
+ guarantee the layout matches what the platform parsed.
158
+
159
+ Returns:
160
+ A ``cirq.ResultDict`` whose ``measurements[key]`` is a
161
+ ``(repetitions, num_qubits_in_key)`` ``uint8`` array.
162
+ """
163
+ if qasm is None:
164
+ qasm = circuit.to_qasm()
165
+
166
+ layout, total = _measurement_layout(circuit, qasm)
167
+ if not layout:
168
+ raise CircuitValidationError(
169
+ "Circuit has no measurement gates; add cirq.measure(...) before "
170
+ "sampling on the Open Quantum platform."
171
+ )
172
+
173
+ per_key_rows: Dict[str, List[List[int]]] = {key: [] for key, _ in layout}
174
+ for bitstring, count in counts.items():
175
+ clean = bitstring.replace(" ", "")
176
+ if len(clean) < total:
177
+ # The platform may omit leading (most-significant) zero bits.
178
+ clean = clean.rjust(total, "0")
179
+ elif len(clean) > total:
180
+ raise JobError(
181
+ f"Counts bitstring {bitstring!r} has {len(clean)} bits but the "
182
+ f"circuit declares {total} classical bit(s)."
183
+ )
184
+ if reverse_bits:
185
+ clean = clean[::-1]
186
+ bits = [int(c) for c in clean]
187
+ for key, global_bits in layout:
188
+ segment = [bits[g] for g in global_bits]
189
+ per_key_rows[key].extend([segment] * int(count))
190
+
191
+ measurements = {
192
+ key: np.array(per_key_rows[key], dtype=np.uint8).reshape(
193
+ len(per_key_rows[key]), len(global_bits)
194
+ )
195
+ for key, global_bits in layout
196
+ }
197
+ resolver = params if params is not None else cirq.ParamResolver({})
198
+ return cirq.ResultDict(params=resolver, measurements=measurements)
199
+
200
+
201
+ def results_from_counts_list(
202
+ counts_list: Sequence[Mapping[str, int]],
203
+ circuit,
204
+ resolvers,
205
+ *,
206
+ reverse_bits: bool = False,
207
+ qasm: Optional[str] = None,
208
+ ):
209
+ """Build one ``cirq.ResultDict`` per resolver from a list of histograms."""
210
+ return [
211
+ result_from_counts(
212
+ counts, circuit, resolver, reverse_bits=reverse_bits, qasm=qasm
213
+ )
214
+ for counts, resolver in zip(counts_list, resolvers)
215
+ ]