microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.25.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/qsharp/_device/_atom/__init__.py

296lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4from .._device import Device, Zone, ZoneType
5from ..._simulation import NoiseConfig, run_qir_clifford, run_qir_cpu, run_qir_gpu
6from ..._native import try_create_gpu_adapter
7from ..._qsharp import QirInputData
8from ... import telemetry_events
9
10from typing import List, Literal, Optional
11import time
12
13
14class NeutralAtomDevice(Device):
15 """
16 Representation of a neutral atom device quantum computer.
17 """
18
19 def __init__(
20 self,
21 column_count: int = 40,
22 register_zone_row_count: int = 25,
23 interaction_zone_row_count: int = 2,
24 measurement_zone_row_count: int = 2,
25 ):
26 default_layout = (
27 column_count == 40
28 and register_zone_row_count == 25
29 and interaction_zone_row_count == 2
30 and measurement_zone_row_count == 2
31 )
32 telemetry_events.on_neutral_atom_init(default_layout)
33
34 super().__init__(
35 column_count,
36 [
37 Zone("Register 1", register_zone_row_count, ZoneType.REG),
38 Zone("Interaction Zone", interaction_zone_row_count, ZoneType.INTER),
39 Zone("Measurement Zone", measurement_zone_row_count, ZoneType.MEAS),
40 ],
41 )
42
43 def _init_home_locs(self):
44 # Set up the home locations for qubits in the NeutralAtomDevice layout.
45 assert len(self.zones) == 3
46 assert (
47 self.zones[0].type == ZoneType.REG
48 and self.zones[1].type == ZoneType.INTER
49 and self.zones[2].type == ZoneType.MEAS
50 )
51 rz1_rows = range(self.zones[0].row_count - 1, -1, -1)
52 self.home_locs = []
53 for row in range(self.zones[0].row_count):
54 for col in range(self.column_count):
55 self.home_locs.append((rz1_rows[row], col))
56
57 def compile(
58 self,
59 program: str | QirInputData,
60 verbose: bool = False,
61 ) -> QirInputData:
62 """
63 Compile a QIR program for the NeutralAtomDevice device. This includes decomposing gates to the native gate set,
64 optimizing sequences of single qubit gates, pruning unused functions, and reordering instructions to
65 enable better scheduling during execution.
66
67 :param program: The QIR program to compile, either as a string or as QirInputData.
68 :param verbose: If true, print detailed information about each compilation step.
69 :returns QirInputData: The compiled QIR program.
70 """
71
72 from ._optimize import (
73 OptimizeSingleQubitGates,
74 PruneUnusedFunctions,
75 )
76 from ._decomp import (
77 DecomposeMultiQubitToCZ,
78 DecomposeSingleRotationToRz,
79 DecomposeSingleQubitToRzSX,
80 ReplaceResetWithMResetZ,
81 )
82 from ._reorder import Reorder
83 from pyqir import Module, Context
84
85 start_time = time.monotonic()
86 all_start_time = start_time
87 telemetry_events.on_neutral_atom_compile()
88
89 name = ""
90 if isinstance(program, QirInputData):
91 name = program._name
92
93 if verbose:
94 print(f"Compiling program {name} for NeutralAtomDevice device...")
95
96 module = Module.from_ir(Context(), str(program))
97 if verbose:
98 end_time = time.monotonic()
99 print(f" Loaded module in {end_time - start_time:.2f} seconds")
100 start_time = end_time
101
102 OptimizeSingleQubitGates().run(module)
103 if verbose:
104 end_time = time.monotonic()
105 print(
106 f" Optimized single qubit gates in {end_time - start_time:.2f} seconds"
107 )
108 start_time = end_time
109
110 DecomposeMultiQubitToCZ().run(module)
111 if verbose:
112 end_time = time.monotonic()
113 print(
114 f" Decomposed multi-qubit gates to CZ in {end_time - start_time:.2f} seconds"
115 )
116 start_time = end_time
117
118 OptimizeSingleQubitGates().run(module)
119 if verbose:
120 end_time = time.monotonic()
121 print(
122 f" Optimized single qubit gates in {end_time - start_time:.2f} seconds"
123 )
124 start_time = end_time
125
126 DecomposeSingleRotationToRz().run(module)
127 if verbose:
128 end_time = time.monotonic()
129 print(
130 f" Decomposed single rotations to Rz in {end_time - start_time:.2f} seconds"
131 )
132 start_time = end_time
133
134 OptimizeSingleQubitGates().run(module)
135 if verbose:
136 end_time = time.monotonic()
137 print(
138 f" Optimized single qubit gates in {end_time - start_time:.2f} seconds"
139 )
140 start_time = end_time
141
142 DecomposeSingleQubitToRzSX().run(module)
143 if verbose:
144 end_time = time.monotonic()
145 print(
146 f" Decomposed single qubit gates to Rz and SX in {end_time - start_time:.2f} seconds"
147 )
148 start_time = end_time
149
150 OptimizeSingleQubitGates().run(module)
151 if verbose:
152 end_time = time.monotonic()
153 print(
154 f" Optimized single qubit gates in {end_time - start_time:.2f} seconds"
155 )
156 start_time = end_time
157
158 ReplaceResetWithMResetZ().run(module)
159 if verbose:
160 end_time = time.monotonic()
161 print(
162 f" Replaced resets with mresetz in {end_time - start_time:.2f} seconds"
163 )
164 start_time = end_time
165
166 PruneUnusedFunctions().run(module)
167 if verbose:
168 end_time = time.monotonic()
169 print(f" Pruned unused functions in {end_time - start_time:.2f} seconds")
170 start_time = end_time
171
172 Reorder(self).run(module)
173 if verbose:
174 end_time = time.monotonic()
175 print(f" Reordered instructions in {end_time - start_time:.2f} seconds")
176 start_time = end_time
177
178 end_time = time.monotonic()
179 telemetry_events.on_neutral_atom_compile_end((end_time - all_start_time) * 1000)
180 if verbose:
181 print(
182 f"Finished compiling program {name} in {end_time - all_start_time:.2f} seconds"
183 )
184
185 return QirInputData(name, str(module))
186
187 def show_trace(self, qir: str | QirInputData):
188 """
189 Visualize the execution trace of a QIR program on the NeutralAtomDevice device using the Atoms widget.
190 This includes approximate layout and scheduling of the program to show the parallelism of gates and
191 movement of qubits during execution.
192
193 :param qir: The QIR program to visualize, either as a string or as QirInputData.
194 """
195
196 try:
197 from qsharp_widgets import Atoms
198 except ImportError:
199 raise ImportError(
200 "The qsharp-widgets package is required for showing atom trace visualization. "
201 "Please install it via 'pip install \"qdk[jupyter]\"' or 'pip install qsharp-widgets'."
202 )
203 from ._trace import Trace
204 from ._validate import ValidateNoConditionalBranches
205 from ._scheduler import Schedule
206 from pyqir import Module, Context
207 from IPython.display import display
208
209 start_time = time.monotonic()
210 telemetry_events.on_neutral_atom_trace()
211
212 # Compile and visualize the trace in one step.
213 compiled = self.compile(qir)
214 module = Module.from_ir(Context(), str(compiled))
215 ValidateNoConditionalBranches().run(module)
216 Schedule(self).run(module)
217 tracer = Trace(self)
218 tracer.run(module)
219 display(Atoms(machine_layout=self.get_layout(), trace_data=tracer.trace))
220
221 end_time = time.monotonic()
222 telemetry_events.on_neutral_atom_trace_end((end_time - start_time) * 1000)
223
224 def simulate(
225 self,
226 qir: str | QirInputData,
227 shots=1,
228 noise: NoiseConfig | None = None,
229 type: Optional[Literal["clifford", "cpu", "gpu"]] = None,
230 ) -> List:
231 """
232 Simulate a QIR program on the NeutralAtomDevice device. This includes approximate layout and scheduling of the program
233 to model the parallelism of gates and movement of qubits during execution. The simulation can optionally
234 include noise based on a provided noise configuration.
235
236 :param qir: The QIR program to simulate, either as a string or as QirInputData.
237 :param shots: The number of shots to simulate. Defaults to 1.
238 :param noise: An optional NoiseConfig to include noise in the simulation.
239 :param type: The type of simulator to use:
240 Use `"clifford"` if your QIR only contains Clifford gates and measurements.
241 Use `"gpu"` if you have a GPU available in your system.
242 Use `"cpu"` as a fallback option if you don't have a GPU in your system.
243 If `None` (default), the GPU simulator will be tried first, falling back to
244 CPU if a suitable GPU device could not be located.
245 :returns: The results of each shot of the simulation as a list.
246 """
247
248 from ._validate import ValidateNoConditionalBranches
249 from ._scheduler import Schedule
250 from ._decomp import DecomposeRzAnglesToCliffordGates
251 from pyqir import Module, Context
252
253 start_time = time.monotonic()
254
255 using_noise = noise is not None
256 if noise is None:
257 noise = NoiseConfig()
258
259 compiled = self.compile(qir)
260 module = Module.from_ir(Context(), str(compiled))
261 ValidateNoConditionalBranches().run(module)
262 Schedule(self).run(module)
263
264 if type is None:
265 try:
266 try_create_gpu_adapter()
267 type = "gpu"
268 except OSError:
269 telemetry_events.on_neutral_atom_cpu_fallback()
270 type = "cpu"
271
272 telemetry_events.on_neutral_atom_simulate(shots, using_noise, type)
273
274 match type:
275 case "clifford":
276 DecomposeRzAnglesToCliffordGates().run(module)
277 result = run_qir_clifford(
278 str(module),
279 shots,
280 noise,
281 )
282 case "cpu":
283 result = run_qir_cpu(str(module), shots, noise)
284 case "gpu":
285 result = run_qir_gpu(str(module), shots, noise)
286 case _:
287 raise ValueError(f"Simulation type {type} is not supported")
288
289 end_time = time.monotonic()
290 telemetry_events.on_neutral_atom_simulate_end(
291 (end_time - start_time) * 1000, shots, using_noise, type
292 )
293 return result
294
295
296__all__ = ["NeutralAtomDevice"]
297