1"""
2Routine file for Cirq library
3"""
4from typing import Any, Iterable, Literal
5from collections.abc import Sequence
6import math
7
8import numpy as np
9import qiskit
10
11from qiskit.result import QuasiDistribution
12from qiskit.primitives import Sampler, BaseSamplerV2, BitArray, DataBin
13from qiskit.primitives.primitive_job import PrimitiveJob
14from qiskit.primitives.base.base_primitive_job import BasePrimitiveJob
15from qiskit.primitives.base.sampler_result import SamplerResult
16from qiskit.primitives.containers.primitive_result import PrimitiveResult
17from qiskit.primitives.containers.sampler_pub_result import SamplerPubResult
18from qiskit.primitives.containers.sampler_pub import SamplerPubLike
19
20from qlauncher.base import Backend
21from qlauncher.base.translator import Translation
22from qlauncher.exceptions import DependencyError
23try:
24 import cirq
25 from cirq.sim.sparse_simulator import Simulator
26except ImportError as e:
27 raise DependencyError(e, install_hint='cirq') from e
28
29
48
49
[docs]
50def cirq_result_to_counts(
51 result: cirq.Result
52) -> dict:
53 bitstrings = extract_bitstrings_from_result(result)
54
55 counts = {}
56 for bs in bitstrings:
57 counts[bs] = counts.get(bs, 0) + 1
58 return counts
59
60
[docs]
61def cirq_result_to_probabilities(
62 result: cirq.Result,
63 integer_keys: bool = False
64) -> dict:
65 counts = cirq_result_to_counts(result)
66
67 total_shots = sum(counts.values())
68 if integer_keys:
69 prob_dict = {int(k, 2): v / total_shots for k, v in counts.items()}
70 else:
71 prob_dict = {k: v / total_shots for k, v in counts.items()}
72
73 return prob_dict
74
75
76class _CirqRunner:
77 simulator = Simulator()
78 repetitions = 1024
79
80 @classmethod
81 def calculate_circuit(
82 cls,
83 circuit: qiskit.QuantumCircuit,
84 return_type: Literal['counts', 'dist', 'raw'] = 'counts',
85 shots: int | None = None
86 ) -> dict | list[str]:
87
88 if circuit.num_clbits == 0:
89 circuit = circuit.measure_all(inplace=False)
90
91 cirq_circ = Translation.get_translation(circuit, 'cirq')
92
93 result = cls.simulator.run(cirq_circ, repetitions=cls.repetitions if shots is None else shots)
94
95 if return_type == 'raw':
96 return extract_bitstrings_from_result(result)
97
98 return cirq_result_to_counts(result) if return_type == 'counts' else cirq_result_to_probabilities(result)
99
100
[docs]
101class CirqSampler(Sampler):
102 """ Sampler adapter for Cirq """
103
104 def _call(self, circuits: Sequence[int], parameter_values: Sequence[Sequence[float]], **run_options) -> SamplerResult:
105 bound_circuits = []
106 for i, value in zip(circuits, parameter_values):
107 bound_circuits.append(
108 self._circuits[i]
109 if len(value) == 0
110 else self._circuits[i].assign_parameters(dict(zip(self._parameters[i], value)))
111 )
112 distributions = [_CirqRunner.calculate_circuit(circuit, 'dist') for circuit in bound_circuits]
113 quasi_dists = list(map(QuasiDistribution, distributions))
114 return SamplerResult(quasi_dists, [{} for _ in range(len(parameter_values))])
115
116
[docs]
117class CirqSamplerV2(BaseSamplerV2):
118 def __init__(self) -> None:
119 super().__init__()
120 self.cirq_sampler_v1 = CirqSampler()
121
122 def _run(self, bound_circuits, shots):
123 bitstring_collections = [_CirqRunner.calculate_circuit(circuit, 'raw', shots) for circuit in bound_circuits]
124
125 results = []
126 for bsc in bitstring_collections:
127 arr = np.array(
128 [
129 np.frombuffer(int(bs, 2).to_bytes(math.ceil(len(bs)/8)), dtype=np.uint8)
130 for bs in bsc
131 ])
132 bit_array = BitArray(
133 arr,
134 num_bits=len(bsc[0])
135 )
136 results.append(SamplerPubResult(data=DataBin(meas=bit_array), metadata={'shots': len(bitstring_collections[0])}))
137 return PrimitiveResult(results, metadata={'version': 2})
138
[docs]
139 def run(
140 self,
141 pubs: Iterable[SamplerPubLike],
142 *,
143 shots: int | None = None
144 ) -> BasePrimitiveJob[PrimitiveResult[SamplerPubResult], Any]:
145 bound_circuits = []
146 for pub in pubs:
147 if isinstance(pub, qiskit.QuantumCircuit):
148 bound_circuits.append(pub)
149 elif len(pub) == 1 and isinstance(pub[0], qiskit.QuantumCircuit):
150 bound_circuits.append(pub[0])
151 elif len(pub) == 2:
152 bound_circuits.append(pub[0].assign_parameters(pub[1]))
153 else:
154 raise ValueError(
155 f"Incorrect pub, expected QuantumCircuit, tuple[QuantumCircuit] or tuple[QuantumCircuit, Iterable[float]], got {type(pub)}")
156
157 job = PrimitiveJob(self._run, bound_circuits, shots)
158 job._submit()
159 return job
160
161
[docs]
162class CirqBackend(Backend):
163 """
164
165 Args:
166 Backend (_type_): _description_
167 """
168
169 def __init__(self, name: Literal['local_simulator'] = 'local_simulator'):
170 self.sampler = CirqSamplerV2()
171 self.samplerV1 = CirqSampler()
172 super().__init__(name)