microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.27.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/pip/qsharp/interop/cirq/_neutral_atom.py

166lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4"""NeutralAtomSampler — a cirq.Sampler backed by the local NeutralAtomDevice."""
5
6from __future__ import annotations
7
8from typing import List, Optional, TYPE_CHECKING
9
10import cirq
11
12from ._result import NeutralAtomCirqResult, measurement_dict, to_cirq_result
13
14if TYPE_CHECKING:
15 from qsharp._simulation import NoiseConfig
16 from qsharp._device._atom import NeutralAtomDevice
17
18
19class NeutralAtomSampler(cirq.Sampler):
20 """A ``cirq.Sampler`` that runs Cirq circuits on the local NeutralAtomDevice simulator.
21
22 This sampler integrates with the standard Cirq sampler protocol, so it can
23 be used anywhere a ``cirq.Sampler`` is expected.
24
25 Pipeline for each ``run()`` call:
26
27 1. ``cirq.Circuit.to_qasm(version="3.0")`` → OpenQASM 3.0
28 2. OpenQASM 3.0 → QIR (base profile, via the Q# compiler)
29 3. QIR → ``NeutralAtomDevice.simulate()`` (decompose, schedule, simulate)
30 4. Raw shots → :class:`NeutralAtomCirqResult`
31
32 Args:
33 noise: Optional :class:`~qsharp._simulation.NoiseConfig` describing
34 per-gate noise. The device decomposes gates to the native set
35 ``{Rz, SX, CZ, MResetZ}``; configure noise on those native gates.
36 For example, a Cirq ``X`` gate arriving via QASM 2.0 is decomposed
37 to ``SX·SX``, so ``noise.sx`` is the relevant field. Defaults to
38 ``None`` (noiseless).
39 simulator_type: Force a particular simulator backend.
40 - ``"clifford"`` — Clifford-only, fast. Requires a Clifford circuit.
41 - ``"cpu"`` — Full state-vector on CPU.
42 - ``"gpu"`` — Full state-vector on GPU.
43 - ``None`` (default) — GPU if available, CPU otherwise.
44 seed: Optional integer seed for reproducibility. Defaults to ``None``.
45 device: An existing :class:`~qsharp._device._atom.NeutralAtomDevice`
46 instance to reuse across calls. A default-configured device is
47 created lazily on the first call when not provided.
48
49 Example::
50
51 import cirq
52 from qsharp.interop.cirq import NeutralAtomSampler
53 from qsharp._simulation import NoiseConfig
54
55 q0, q1 = cirq.LineQubit.range(2)
56 circuit = cirq.Circuit([
57 cirq.H(q0),
58 cirq.CNOT(q0, q1),
59 cirq.measure(q0, q1, key="m"),
60 ])
61
62 # Noiseless simulation
63 sampler = NeutralAtomSampler(seed=42)
64 result = sampler.run(circuit, repetitions=1000)
65 print(result.histogram(key="m"))
66
67 # Noisy simulation — 1% loss on Rz (native gate)
68 noise = NoiseConfig()
69 noise.rz.loss = 0.01
70 sampler = NeutralAtomSampler(noise=noise, seed=42)
71 result = sampler.run(circuit, repetitions=1000)
72 print(f"Accepted: {len(result.measurements['m'])} / {len(result.raw_shots)}")
73 """
74
75 def __init__(
76 self,
77 *,
78 noise: Optional["NoiseConfig"] = None,
79 simulator_type: Optional[str] = None,
80 seed: Optional[int] = None,
81 device: Optional["NeutralAtomDevice"] = None,
82 ) -> None:
83 self._noise = noise
84 self._simulator_type = simulator_type
85 self._seed = seed
86 self._device = device
87
88 def _get_device(self) -> "NeutralAtomDevice":
89 """Return the NeutralAtomDevice, creating a default one on first access."""
90 if self._device is None:
91 from qsharp._device._atom import NeutralAtomDevice
92
93 self._device = NeutralAtomDevice()
94 return self._device
95
96 def run_sweep(
97 self,
98 program: cirq.AbstractCircuit,
99 params: cirq.Sweepable,
100 repetitions: int = 1,
101 ) -> List[NeutralAtomCirqResult]:
102 """Run the circuit for each parameter resolver in the sweep.
103
104 Args:
105 program: The Cirq circuit to simulate.
106 params: A :class:`cirq.Sweepable` defining the parameter resolvers
107 to sweep over. Each resolver produces one result.
108 repetitions: Number of shots per parameter resolver.
109
110 Returns:
111 A list of :class:`NeutralAtomCirqResult` objects, one per resolver.
112 """
113 resolvers = list(cirq.to_sweep(params)) if params is not None else [cirq.ParamResolver()]
114 return [
115 self._run_once(program, resolver, repetitions) for resolver in resolvers
116 ]
117
118 def _run_once(
119 self,
120 circuit: cirq.AbstractCircuit,
121 param_resolver: cirq.ParamResolver,
122 repetitions: int,
123 ) -> NeutralAtomCirqResult:
124 from qsharp._native import compile_qasm_program_to_qir
125 from qsharp._fs import read_file, list_directory, resolve
126 from qsharp._http import fetch_github
127 from qsharp._qsharp import TargetProfile
128
129 # Resolve parameters
130 resolved_circuit = cirq.resolve_parameters(circuit, param_resolver)
131
132 # Step 1: Cirq circuit → QASM 3.0
133 try:
134 qasm = resolved_circuit.to_qasm(version="3.0")
135 except Exception as exc:
136 raise ValueError(
137 "Failed to convert the Cirq circuit to QASM 3.0. "
138 "Ensure every gate in the circuit supports QASM serialization "
139 f"(see cirq.Circuit.to_qasm). Original error: {exc}"
140 ) from exc
141
142 # Step 2: QASM 3.0 → QIR (base profile)
143 qir = compile_qasm_program_to_qir(
144 qasm,
145 read_file,
146 list_directory,
147 resolve,
148 fetch_github,
149 name="cirq_circuit",
150 target_profile=TargetProfile.Base,
151 search_path=".",
152 )
153
154 # Step 3: QIR → NeutralAtomDevice simulation
155 device = self._get_device()
156 raw_shots = device.simulate(
157 qir,
158 shots=repetitions,
159 noise=self._noise,
160 type=self._simulator_type,
161 seed=self._seed,
162 )
163
164 # Step 4: Build NeutralAtomCirqResult
165 meas_dict = measurement_dict(resolved_circuit)
166 return to_cirq_result(raw_shots, meas_dict, param_resolver)