Source code for qlauncher.routines.cirq

  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
[docs] 30def extract_bitstrings_from_result( 31 result: cirq.Result 32) -> list[str]: 33 measurements = result.measurements 34 35 sorted_keys = list(measurements.keys()) 36 37 bitstrings = [] 38 num_shots = len(measurements[sorted_keys[0]]) 39 40 for shot_index in range(num_shots): 41 bits = [] 42 for key in sorted_keys: 43 bits.extend(str(b) for b in measurements[key][shot_index]) 44 45 bitstring = "".join(bits) 46 bitstrings.append(bitstring) 47 return bitstrings
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)