microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/add-link-to-qsharp-application

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/tests/qre/test_models.py

1069lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4from qsharp.qre import LOGICAL, PHYSICAL
5from qsharp.qre.instruction_ids import (
6 T,
7 CCZ,
8 CCX,
9 CCY,
10 CNOT,
11 CZ,
12 H,
13 MEAS_Z,
14 MEAS_X,
15 MEAS_XX,
16 MEAS_ZZ,
17 PAULI_I,
18 PREP_X,
19 PREP_Z,
20 LATTICE_SURGERY,
21 MEMORY,
22 SQRT_SQRT_X,
23 SQRT_SQRT_X_DAG,
24 SQRT_SQRT_Y,
25 SQRT_SQRT_Y_DAG,
26 SQRT_SQRT_Z,
27 SQRT_SQRT_Z_DAG,
28)
29from qsharp.qre.models import (
30 GateBased,
31 Majorana,
32 RoundBasedFactory,
33 MagicUpToClifford,
34 Litinski19Factory,
35 SurfaceCode,
36 ThreeAux,
37 TwoDimensionalYokedSurfaceCode,
38)
39from qsharp.qre.property_keys import DISTANCE
40
41
42# ---------------------------------------------------------------------------
43# GateBased architecture tests
44# ---------------------------------------------------------------------------
45
46
47class TestGateBased:
48 def test_default_error_rate(self):
49 """Test that default error rate is 1e-4."""
50 arch = GateBased(gate_time=50, measurement_time=100)
51 assert arch.error_rate == 1e-4
52
53 def test_custom_error_rate(self):
54 """Test that a custom error rate is accepted."""
55 arch = GateBased(error_rate=1e-3, gate_time=50, measurement_time=100)
56 assert arch.error_rate == 1e-3
57
58 def test_provided_isa_contains_expected_instructions(self):
59 """Test that GateBased ISA contains all expected physical instructions."""
60 arch = GateBased(gate_time=50, measurement_time=100)
61 ctx = arch.context()
62 isa = ctx.isa
63
64 for instr_id in [PAULI_I, CNOT, CZ, H, MEAS_Z, T]:
65 assert instr_id in isa
66
67 def test_instruction_encodings_are_physical(self):
68 """Test that all GateBased ISA instructions have PHYSICAL encoding."""
69 arch = GateBased(gate_time=50, measurement_time=100)
70 ctx = arch.context()
71 isa = ctx.isa
72
73 for instr_id in [PAULI_I, CNOT, CZ, H, MEAS_Z, T]:
74 assert isa[instr_id].encoding == PHYSICAL
75
76 def test_instruction_error_rates_match(self):
77 """Test that all instruction error rates match the architecture error rate."""
78 rate = 1e-3
79 arch = GateBased(error_rate=rate, gate_time=50, measurement_time=100)
80 ctx = arch.context()
81 isa = ctx.isa
82
83 for instr_id in [PAULI_I, CNOT, CZ, H, MEAS_Z, T]:
84 assert isa[instr_id].expect_error_rate() == rate
85
86 def test_gate_times(self):
87 """Test that gate times match the configured gate and measurement times."""
88 arch = GateBased(gate_time=50, measurement_time=100)
89 ctx = arch.context()
90 isa = ctx.isa
91
92 # Single-qubit gates: 50ns
93 for instr_id in [PAULI_I, H, T]:
94 assert isa[instr_id].expect_time() == 50
95
96 # Two-qubit gates: 50ns
97 for instr_id in [CNOT, CZ]:
98 assert isa[instr_id].expect_time() == 50
99
100 # Measurement: 100ns
101 assert isa[MEAS_Z].expect_time() == 100
102
103 def test_arities(self):
104 """Test that instruction arities match expected values."""
105 arch = GateBased(gate_time=50, measurement_time=100)
106 ctx = arch.context()
107 isa = ctx.isa
108
109 assert isa[PAULI_I].arity == 1
110 assert isa[CNOT].arity == 2
111 assert isa[CZ].arity == 2
112 assert isa[H].arity == 1
113 assert isa[MEAS_Z].arity == 1
114
115 def test_context_creation(self):
116 """Test that context creation succeeds."""
117 arch = GateBased(gate_time=50, measurement_time=100)
118 ctx = arch.context()
119 assert ctx is not None
120
121
122# ---------------------------------------------------------------------------
123# Majorana architecture tests
124# ---------------------------------------------------------------------------
125
126
127class TestMajorana:
128 def test_default_error_rate(self):
129 """Test that default Majorana error rate is 1e-5."""
130 arch = Majorana()
131 assert arch.error_rate == 1e-5
132
133 def test_provided_isa_contains_expected_instructions(self):
134 """Test that Majorana ISA contains all expected instructions."""
135 arch = Majorana()
136 ctx = arch.context()
137 isa = ctx.isa
138
139 for instr_id in [PREP_X, PREP_Z, MEAS_XX, MEAS_ZZ, MEAS_X, MEAS_Z, T]:
140 assert instr_id in isa
141
142 def test_all_times_are_1us(self):
143 """Test that all Majorana instruction times are 1000 ns."""
144 arch = Majorana()
145 ctx = arch.context()
146 isa = ctx.isa
147
148 for instr_id in [PREP_X, PREP_Z, MEAS_XX, MEAS_ZZ, MEAS_X, MEAS_Z, T]:
149 assert isa[instr_id].expect_time() == 1000
150
151 def test_clifford_error_rates_match_qubit_error(self):
152 """Test that Clifford error rates match the qubit error rate."""
153 for rate in [1e-4, 1e-5, 1e-6]:
154 arch = Majorana(error_rate=rate)
155 ctx = arch.context()
156 isa = ctx.isa
157
158 for instr_id in [PREP_X, PREP_Z, MEAS_XX, MEAS_ZZ, MEAS_X, MEAS_Z]:
159 assert isa[instr_id].expect_error_rate() == rate
160
161 def test_t_error_rate_mapping(self):
162 """T error rate maps: 1e-4 -> 5%, 1e-5 -> 1.5%, 1e-6 -> 1%."""
163 expected = {1e-4: 0.05, 1e-5: 0.015, 1e-6: 0.01}
164
165 for qubit_rate, t_rate in expected.items():
166 arch = Majorana(error_rate=qubit_rate)
167 ctx = arch.context()
168 isa = ctx.isa
169 assert isa[T].expect_error_rate() == t_rate
170
171 def test_two_qubit_measurement_arities(self):
172 """Test that two-qubit measurement instructions have arity 2."""
173 arch = Majorana()
174 ctx = arch.context()
175 isa = ctx.isa
176
177 assert isa[MEAS_XX].arity == 2
178 assert isa[MEAS_ZZ].arity == 2
179
180
181# ---------------------------------------------------------------------------
182# SurfaceCode QEC tests
183# ---------------------------------------------------------------------------
184
185
186class TestSurfaceCode:
187 def test_required_isa(self):
188 """Test that SurfaceCode has non-None required ISA."""
189 reqs = SurfaceCode.required_isa()
190 assert reqs is not None
191
192 def test_default_distance(self):
193 """Test SurfaceCode with explicit distance parameter."""
194 sc = SurfaceCode(distance=3)
195 assert sc.distance == 3
196
197 def test_provides_lattice_surgery(self):
198 """Test that SurfaceCode provides a logical LATTICE_SURGERY instruction."""
199 arch = GateBased(gate_time=50, measurement_time=100)
200 ctx = arch.context()
201 sc = SurfaceCode(distance=3)
202
203 isas = list(sc.provided_isa(ctx.isa, ctx))
204 assert len(isas) == 1
205
206 isa = isas[0]
207 assert LATTICE_SURGERY in isa
208
209 ls = isa[LATTICE_SURGERY]
210 assert ls.encoding == LOGICAL
211
212 def test_space_scales_with_distance(self):
213 """Space = 2*d^2 - 1 physical qubits per logical qubit."""
214 arch = GateBased(gate_time=50, measurement_time=100)
215
216 for d in [3, 5, 7, 9]:
217 ctx = arch.context()
218 sc = SurfaceCode(distance=d)
219 isas = list(sc.provided_isa(ctx.isa, ctx))
220 ls = isas[0][LATTICE_SURGERY]
221 expected_space = 2 * d**2 - 1
222 assert ls.expect_space(1) == expected_space
223
224 def test_time_scales_with_distance(self):
225 """Time = (h_time + 4*cnot_time + meas_time) * d."""
226 arch = GateBased(gate_time=50, measurement_time=100)
227 # h=50, cnot=50, meas=100 for GateBased
228 syndrome_time = 50 + 4 * 50 + 100 # = 350
229
230 for d in [3, 5, 7]:
231 ctx = arch.context()
232 sc = SurfaceCode(distance=d)
233 isas = list(sc.provided_isa(ctx.isa, ctx))
234 ls = isas[0][LATTICE_SURGERY]
235 assert ls.expect_time(1) == syndrome_time * d
236
237 def test_error_rate_decreases_with_distance(self):
238 """Test that logical error rate decreases as code distance increases."""
239 arch = GateBased(gate_time=50, measurement_time=100)
240
241 errors = []
242 for d in [3, 5, 7, 9, 11]:
243 ctx = arch.context()
244 sc = SurfaceCode(distance=d)
245 isas = list(sc.provided_isa(ctx.isa, ctx))
246 errors.append(isas[0][LATTICE_SURGERY].expect_error_rate(1))
247
248 # Each successive distance should have a lower error rate
249 for i in range(len(errors) - 1):
250 assert errors[i] > errors[i + 1]
251
252 def test_enumeration_via_query(self):
253 """Enumerating SurfaceCode.q() should yield multiple distances."""
254 arch = GateBased(gate_time=50, measurement_time=100)
255 ctx = arch.context()
256
257 count = 0
258 for isa in SurfaceCode.q().enumerate(ctx):
259 assert LATTICE_SURGERY in isa
260 count += 1
261
262 # domain is range(3, 26, 2) = 12 distances
263 assert count == 12
264
265 def test_custom_crossing_prefactor(self):
266 """Test that doubling the crossing prefactor doubles the error rate."""
267 arch = GateBased(gate_time=50, measurement_time=100)
268 ctx = arch.context()
269
270 sc_default = SurfaceCode(distance=5)
271 sc_custom = SurfaceCode(crossing_prefactor=0.06, distance=5)
272
273 default_error = list(sc_default.provided_isa(ctx.isa, ctx))[0][
274 LATTICE_SURGERY
275 ].expect_error_rate(1)
276
277 ctx2 = arch.context()
278 custom_error = list(sc_custom.provided_isa(ctx2.isa, ctx2))[0][
279 LATTICE_SURGERY
280 ].expect_error_rate(1)
281
282 # Doubling prefactor should double the error rate
283 assert abs(custom_error - 2 * default_error) < 1e-20
284
285 def test_custom_error_correction_threshold(self):
286 """Test that a lower error correction threshold yields a higher logical error."""
287 arch = GateBased(gate_time=50, measurement_time=100)
288
289 ctx1 = arch.context()
290 sc_low_threshold = SurfaceCode(error_correction_threshold=0.005, distance=5)
291 error_low = list(sc_low_threshold.provided_isa(ctx1.isa, ctx1))[0][
292 LATTICE_SURGERY
293 ].expect_error_rate(1)
294
295 ctx2 = arch.context()
296 sc_high_threshold = SurfaceCode(error_correction_threshold=0.02, distance=5)
297 error_high = list(sc_high_threshold.provided_isa(ctx2.isa, ctx2))[0][
298 LATTICE_SURGERY
299 ].expect_error_rate(1)
300
301 # Lower threshold means worse ratio => higher logical error
302 assert error_low > error_high
303
304
305# ---------------------------------------------------------------------------
306# ThreeAux QEC tests
307# ---------------------------------------------------------------------------
308
309
310class TestThreeAux:
311 def test_required_isa(self):
312 """Test that ThreeAux has non-None required ISA."""
313 reqs = ThreeAux.required_isa()
314 assert reqs is not None
315
316 def test_provides_lattice_surgery(self):
317 """Test that ThreeAux provides a LATTICE_SURGERY instruction."""
318 arch = Majorana()
319 ctx = arch.context()
320 ta = ThreeAux(distance=3)
321
322 isas = list(ta.provided_isa(ctx.isa, ctx))
323 assert len(isas) == 1
324 assert LATTICE_SURGERY in isas[0]
325
326 def test_space_formula(self):
327 """Space = 4*d^2 - 3 per logical qubit."""
328 arch = Majorana()
329
330 for d in [3, 5, 7]:
331 ctx = arch.context()
332 ta = ThreeAux(distance=d)
333 isas = list(ta.provided_isa(ctx.isa, ctx))
334 ls = isas[0][LATTICE_SURGERY]
335 expected = 4 * d**2 - 3
336 assert ls.expect_space(1) == expected
337
338 def test_time_formula_double_rail(self):
339 """Time = gate_time * (4*d + 4) for double-rail (default)."""
340 arch = Majorana()
341
342 for d in [3, 5, 7]:
343 ctx = arch.context()
344 ta = ThreeAux(distance=d, single_rail=False)
345 isas = list(ta.provided_isa(ctx.isa, ctx))
346 ls = isas[0][LATTICE_SURGERY]
347 # MEAS_XX and MEAS_ZZ have time=1000 each; max is 1000
348 expected_time = 1000 * (4 * d + 4)
349 assert ls.expect_time(1) == expected_time
350
351 def test_time_formula_single_rail(self):
352 """Time = gate_time * (5*d + 4) for single-rail."""
353 arch = Majorana()
354
355 for d in [3, 5, 7]:
356 ctx = arch.context()
357 ta = ThreeAux(distance=d, single_rail=True)
358 isas = list(ta.provided_isa(ctx.isa, ctx))
359 ls = isas[0][LATTICE_SURGERY]
360 expected_time = 1000 * (5 * d + 4)
361 assert ls.expect_time(1) == expected_time
362
363 def test_error_rate_decreases_with_distance(self):
364 """Test that ThreeAux error rate decreases with increasing distance."""
365 arch = Majorana()
366
367 errors = []
368 for d in [3, 5, 7, 9]:
369 ctx = arch.context()
370 ta = ThreeAux(distance=d)
371 isas = list(ta.provided_isa(ctx.isa, ctx))
372 errors.append(isas[0][LATTICE_SURGERY].expect_error_rate(1))
373
374 for i in range(len(errors) - 1):
375 assert errors[i] > errors[i + 1]
376
377 def test_single_rail_has_different_error_threshold(self):
378 """Single-rail has threshold 0.0051, double-rail 0.0066."""
379 arch = Majorana()
380
381 ctx1 = arch.context()
382 double = ThreeAux(distance=5, single_rail=False)
383 error_double = list(double.provided_isa(ctx1.isa, ctx1))[0][
384 LATTICE_SURGERY
385 ].expect_error_rate(1)
386
387 ctx2 = arch.context()
388 single = ThreeAux(distance=5, single_rail=True)
389 error_single = list(single.provided_isa(ctx2.isa, ctx2))[0][
390 LATTICE_SURGERY
391 ].expect_error_rate(1)
392
393 # Both should be positive but differ
394 assert error_double > 0
395 assert error_single > 0
396 assert error_double != error_single
397
398 def test_enumeration_via_query(self):
399 """Test that ThreeAux.q() enumerates all distance and rail combinations."""
400 arch = Majorana()
401 ctx = arch.context()
402
403 count = 0
404 for isa in ThreeAux.q().enumerate(ctx):
405 assert LATTICE_SURGERY in isa
406 count += 1
407
408 # domain: range(3, 26, 2) × {True, False} for single_rail
409 # = 12 distances × 2 = 24
410 assert count == 24
411
412
413# ---------------------------------------------------------------------------
414# YokedSurfaceCode tests
415# ---------------------------------------------------------------------------
416
417
418class TestYokedSurfaceCode:
419 def _get_lattice_surgery_isa(self, distance=5):
420 """Helper to get a lattice surgery ISA from SurfaceCode."""
421 arch = GateBased(gate_time=50, measurement_time=100)
422 ctx = arch.context()
423 sc = SurfaceCode(distance=distance)
424 isas = list(sc.provided_isa(ctx.isa, ctx))
425 return isas[0], ctx
426
427 def test_provides_memory_instruction(self):
428 """Test that YokedSurfaceCode provides a MEMORY instruction."""
429 ls_isa, ctx = self._get_lattice_surgery_isa()
430 ysc = TwoDimensionalYokedSurfaceCode()
431
432 isas = list(ysc.provided_isa(ls_isa, ctx))
433 assert len(isas) == 1
434 assert MEMORY in isas[0]
435
436 def test_memory_is_logical(self):
437 """Test that the MEMORY instruction has LOGICAL encoding."""
438 ls_isa, ctx = self._get_lattice_surgery_isa()
439 ysc = TwoDimensionalYokedSurfaceCode()
440
441 isas = list(ysc.provided_isa(ls_isa, ctx))
442 mem = isas[0][MEMORY]
443 assert mem.encoding == LOGICAL
444
445 def test_memory_arity_is_variable(self):
446 """Test that MEMORY instruction has variable arity (None)."""
447 ls_isa, ctx = self._get_lattice_surgery_isa()
448 ysc = TwoDimensionalYokedSurfaceCode()
449
450 isas = list(ysc.provided_isa(ls_isa, ctx))
451 mem = isas[0][MEMORY]
452 # arity=None means variable arity
453 assert mem.arity is None
454
455 def test_space_increases_with_arity(self):
456 """Test that MEMORY space increases with the number of qubits."""
457 ls_isa, ctx = self._get_lattice_surgery_isa()
458 ysc = TwoDimensionalYokedSurfaceCode()
459
460 isas = list(ysc.provided_isa(ls_isa, ctx))
461 mem = isas[0][MEMORY]
462
463 spaces = [mem.expect_space(n) for n in [4, 16, 64]]
464 for i in range(len(spaces) - 1):
465 assert spaces[i] < spaces[i + 1]
466
467 def test_time_increases_with_arity(self):
468 """Test that MEMORY time increases with the number of qubits."""
469 ls_isa, ctx = self._get_lattice_surgery_isa()
470 ysc = TwoDimensionalYokedSurfaceCode()
471
472 isas = list(ysc.provided_isa(ls_isa, ctx))
473 mem = isas[0][MEMORY]
474
475 times = [mem.expect_time(n) for n in [4, 16, 64]]
476 for i in range(len(times) - 1):
477 assert times[i] < times[i + 1]
478
479 def test_error_rate_increases_with_arity(self):
480 """Test that MEMORY error rate increases with the number of qubits."""
481 ls_isa, ctx = self._get_lattice_surgery_isa()
482 ysc = TwoDimensionalYokedSurfaceCode()
483
484 isas = list(ysc.provided_isa(ls_isa, ctx))
485 mem = isas[0][MEMORY]
486
487 errors = [mem.expect_error_rate(n) for n in [4, 16, 64]]
488 for i in range(len(errors) - 1):
489 assert errors[i] < errors[i + 1]
490
491 def test_distance_property_propagated(self):
492 """Test that the distance property is propagated to the MEMORY instruction."""
493 d = 7
494 ls_isa, ctx = self._get_lattice_surgery_isa(distance=d)
495 ysc = TwoDimensionalYokedSurfaceCode()
496
497 isas = list(ysc.provided_isa(ls_isa, ctx))
498 mem = isas[0][MEMORY]
499 assert mem.get_property(DISTANCE) == d
500
501
502# ---------------------------------------------------------------------------
503# Litinski19Factory tests
504# ---------------------------------------------------------------------------
505
506
507class TestLitinski19Factory:
508 def test_required_isa(self):
509 """Test that Litinski19Factory has non-None required ISA."""
510 reqs = Litinski19Factory.required_isa()
511 assert reqs is not None
512
513 def test_table1_yields_t_and_ccz(self):
514 """GateBased (error 1e-4) matches Table 1 scenario: T & CCZ."""
515 arch = GateBased(gate_time=50, measurement_time=100)
516 ctx = arch.context()
517 factory = Litinski19Factory()
518
519 isas = list(factory.provided_isa(ctx.isa, ctx))
520
521 # 6 T entries × 1 CCZ entry = 6 combinations
522 assert len(isas) == 6
523
524 for isa in isas:
525 assert T in isa
526 assert CCZ in isa
527 assert len(isa) == 2
528
529 def test_table1_instruction_properties(self):
530 """Test that Table 1 T and CCZ instructions have valid properties."""
531 arch = GateBased(gate_time=50, measurement_time=100)
532 ctx = arch.context()
533 factory = Litinski19Factory()
534
535 for isa in factory.provided_isa(ctx.isa, ctx):
536 t_instr = isa[T]
537 ccz_instr = isa[CCZ]
538
539 assert t_instr.arity == 1
540 assert t_instr.encoding == LOGICAL
541 assert t_instr.expect_error_rate() > 0
542 assert t_instr.expect_time() > 0
543 assert t_instr.expect_space() > 0
544
545 assert ccz_instr.arity == 3
546 assert ccz_instr.encoding == LOGICAL
547 assert ccz_instr.expect_error_rate() > 0
548
549 def test_table1_t_error_rates_are_diverse(self):
550 """T entries in Table 1 should span a range of error rates."""
551 arch = GateBased(gate_time=50, measurement_time=100)
552 ctx = arch.context()
553 factory = Litinski19Factory()
554
555 isas = list(factory.provided_isa(ctx.isa, ctx))
556 t_errors = [isa[T].expect_error_rate() for isa in isas]
557
558 # Should have multiple distinct T error rates
559 unique_errors = set(t_errors)
560 assert len(unique_errors) > 1
561
562 # All error rates should be positive and very small
563 for err in t_errors:
564 assert 0 < err < 1e-5
565
566 def test_table1_1e3_clifford_yields_6_isas(self):
567 """GateBased with 1e-3 error matches Table 1 at 1e-3 Clifford."""
568 arch = GateBased(error_rate=1e-3, gate_time=50, measurement_time=100)
569 ctx = arch.context()
570 factory = Litinski19Factory()
571
572 isas = list(factory.provided_isa(ctx.isa, ctx))
573
574 # 6 T entries × 1 CCZ entry = 6 combinations
575 assert len(isas) == 6
576
577 for isa in isas:
578 assert T in isa
579 assert CCZ in isa
580
581 def test_table2_scenario_no_ccz(self):
582 """Table 2 scenario: T error ~10x higher than Clifford, no CCZ."""
583 from qsharp.qre._qre import _ProvenanceGraph
584
585 arch = GateBased(gate_time=50, measurement_time=100)
586 ctx = arch.context()
587
588 # Manually create ISA with T error rate 10x Clifford
589 graph = _ProvenanceGraph()
590 isa_input = graph.make_isa(
591 [
592 graph.add_instruction(CNOT, arity=2, time=50, error_rate=1e-4),
593 graph.add_instruction(H, time=50, error_rate=1e-4),
594 graph.add_instruction(MEAS_Z, time=100, error_rate=1e-4),
595 graph.add_instruction(T, time=50, error_rate=1e-3),
596 ]
597 )
598
599 factory = Litinski19Factory()
600 isas = list(factory.provided_isa(isa_input, ctx))
601
602 # Table 2 at 1e-4 Clifford: 4 T entries, no CCZ
603 assert len(isas) == 4
604
605 for isa in isas:
606 assert T in isa
607 assert CCZ not in isa
608
609 def test_no_yield_when_error_too_high(self):
610 """If T error > 10x Clifford, no entries match."""
611 from qsharp.qre._qre import _ProvenanceGraph
612
613 arch = GateBased(gate_time=50, measurement_time=100)
614 ctx = arch.context()
615
616 graph = _ProvenanceGraph()
617 isa_input = graph.make_isa(
618 [
619 graph.add_instruction(CNOT, arity=2, time=50, error_rate=1e-4),
620 graph.add_instruction(H, time=50, error_rate=1e-4),
621 graph.add_instruction(MEAS_Z, time=100, error_rate=1e-4),
622 graph.add_instruction(T, time=50, error_rate=0.05),
623 ]
624 )
625
626 factory = Litinski19Factory()
627 isas = list(factory.provided_isa(isa_input, ctx))
628 assert len(isas) == 0
629
630 def test_time_based_on_syndrome_extraction(self):
631 """Time should be based on syndrome extraction time × cycles."""
632 arch = GateBased(gate_time=50, measurement_time=100)
633 ctx = arch.context()
634 factory = Litinski19Factory()
635
636 # For GateBased: syndrome_extraction_time = 4*50 + 50 + 100 = 350
637 syndrome_time = 4 * 50 + 50 + 100 # 350 ns
638
639 isas = list(factory.provided_isa(ctx.isa, ctx))
640 for isa in isas:
641 t_time = isa[T].expect_time()
642 assert t_time > 0
643 # Time should be ceil(syndrome_time * cycles), so it must be at
644 # least syndrome_time (cycles >= 1)
645 assert t_time >= syndrome_time
646
647
648# ---------------------------------------------------------------------------
649# MagicUpToClifford tests
650# ---------------------------------------------------------------------------
651
652
653class TestMagicUpToClifford:
654 def test_required_isa_is_empty(self):
655 """Test that MagicUpToClifford has non-None required ISA."""
656 reqs = MagicUpToClifford.required_isa()
657 assert reqs is not None
658
659 def test_adds_clifford_equivalent_t_gates(self):
660 """Given T gate, should add SQRT_SQRT_X/Y/Z and dagger variants."""
661 arch = GateBased(gate_time=50, measurement_time=100)
662 ctx = arch.context()
663 factory = Litinski19Factory()
664 modifier = MagicUpToClifford()
665
666 for isa in factory.provided_isa(ctx.isa, ctx):
667 modified_isas = list(modifier.provided_isa(isa, ctx))
668 assert len(modified_isas) == 1
669 modified_isa = modified_isas[0]
670
671 # T family equivalents
672 for equiv_id in [
673 SQRT_SQRT_X,
674 SQRT_SQRT_X_DAG,
675 SQRT_SQRT_Y,
676 SQRT_SQRT_Y_DAG,
677 SQRT_SQRT_Z,
678 SQRT_SQRT_Z_DAG,
679 ]:
680 assert equiv_id in modified_isa
681
682 break # Just test the first one
683
684 def test_adds_clifford_equivalent_ccz(self):
685 """Given CCZ, should add CCX and CCY."""
686 arch = GateBased(gate_time=50, measurement_time=100)
687 ctx = arch.context()
688 factory = Litinski19Factory()
689 modifier = MagicUpToClifford()
690
691 for isa in factory.provided_isa(ctx.isa, ctx):
692 modified_isas = list(modifier.provided_isa(isa, ctx))
693 modified_isa = modified_isas[0]
694
695 assert CCX in modified_isa
696 assert CCY in modified_isa
697 assert CCZ in modified_isa
698 break
699
700 def test_full_count_of_instructions(self):
701 """T gate (1) + 5 equivalents (SQRT_SQRT_*) + CCZ (1) + 2 equivalents (CCX, CCY) = 9."""
702 arch = GateBased(gate_time=50, measurement_time=100)
703 ctx = arch.context()
704 factory = Litinski19Factory()
705 modifier = MagicUpToClifford()
706
707 for isa in factory.provided_isa(ctx.isa, ctx):
708 modified_isas = list(modifier.provided_isa(isa, ctx))
709 assert len(modified_isas[0]) == 9
710 break
711
712 def test_equivalent_instructions_share_properties(self):
713 """Clifford equivalents should have same time, space, error rate."""
714 arch = GateBased(gate_time=50, measurement_time=100)
715 ctx = arch.context()
716 factory = Litinski19Factory()
717 modifier = MagicUpToClifford()
718
719 for isa in factory.provided_isa(ctx.isa, ctx):
720 modified_isas = list(modifier.provided_isa(isa, ctx))
721 modified_isa = modified_isas[0]
722
723 t_instr = modified_isa[T]
724 for equiv_id in [
725 SQRT_SQRT_X,
726 SQRT_SQRT_X_DAG,
727 SQRT_SQRT_Y,
728 SQRT_SQRT_Y_DAG,
729 SQRT_SQRT_Z_DAG,
730 ]:
731 equiv = modified_isa[equiv_id]
732 assert equiv.expect_error_rate() == t_instr.expect_error_rate()
733 assert equiv.expect_time() == t_instr.expect_time()
734 assert equiv.expect_space() == t_instr.expect_space()
735
736 ccz_instr = modified_isa[CCZ]
737 for equiv_id in [CCX, CCY]:
738 equiv = modified_isa[equiv_id]
739 assert equiv.expect_error_rate() == ccz_instr.expect_error_rate()
740
741 break
742
743 def test_modification_count_matches_factory_output(self):
744 """MagicUpToClifford should produce one modified ISA per input ISA."""
745 arch = GateBased(gate_time=50, measurement_time=100)
746 ctx = arch.context()
747 factory = Litinski19Factory()
748 modifier = MagicUpToClifford()
749
750 modified_count = 0
751 for isa in factory.provided_isa(ctx.isa, ctx):
752 for _ in modifier.provided_isa(isa, ctx):
753 modified_count += 1
754
755 assert modified_count == 6
756
757 def test_no_family_present_passes_through(self):
758 """If no family member is present, ISA passes through unchanged."""
759 from qsharp.qre._qre import _ProvenanceGraph
760
761 arch = GateBased(gate_time=50, measurement_time=100)
762 ctx = arch.context()
763 modifier = MagicUpToClifford()
764
765 # ISA with only a LATTICE_SURGERY instruction (no T or CCZ family)
766 from qsharp.qre import linear_function
767
768 graph = _ProvenanceGraph()
769 isa_input = graph.make_isa(
770 [
771 graph.add_instruction(
772 LATTICE_SURGERY,
773 encoding=LOGICAL,
774 arity=None,
775 time=1000,
776 space=linear_function(17),
777 error_rate=linear_function(1e-10),
778 )
779 ]
780 )
781
782 results = list(modifier.provided_isa(isa_input, ctx))
783 assert len(results) == 1
784 # Should only contain the original instruction
785 assert len(results[0]) == 1
786
787
788# ---------------------------------------------------------------------------
789# Litinski19Factory + MagicUpToClifford integration (from original test)
790# ---------------------------------------------------------------------------
791
792
793def test_isa_manipulation():
794 """Test Litinski19Factory and MagicUpToClifford ISA integration."""
795 arch = GateBased(gate_time=50, measurement_time=100)
796 factory = Litinski19Factory()
797 modifier = MagicUpToClifford()
798
799 ctx = arch.context()
800
801 # Table 1 scenario: should yield ISAs with both T and CCZ instructions
802 isas = list(factory.provided_isa(ctx.isa, ctx))
803
804 # 6 T entries × 1 CCZ entry = 6 combinations
805 assert len(isas) == 6
806
807 for isa in isas:
808 # Each ISA should contain both T and CCZ instructions
809 assert T in isa
810 assert CCZ in isa
811 assert len(isa) == 2
812
813 t_instr = isa[T]
814 ccz_instr = isa[CCZ]
815
816 # Verify instruction properties
817 assert t_instr.arity == 1
818 assert t_instr.encoding == LOGICAL
819 assert t_instr.expect_error_rate() > 0
820
821 assert ccz_instr.arity == 3
822 assert ccz_instr.encoding == LOGICAL
823 assert ccz_instr.expect_error_rate() > 0
824
825 # After MagicUpToClifford modifier
826 modified_count = 0
827 for isa in factory.provided_isa(ctx.isa, ctx):
828 for modified_isa in modifier.provided_isa(isa, ctx):
829 modified_count += 1
830 # MagicUpToClifford should add derived instructions
831 assert T in modified_isa
832 assert CCZ in modified_isa
833 assert CCX in modified_isa
834 assert len(modified_isa) == 9
835
836 assert modified_count == 6
837
838
839# ---------------------------------------------------------------------------
840# RoundBasedFactory tests
841# ---------------------------------------------------------------------------
842
843
844class TestRoundBasedFactory:
845 def test_required_isa(self):
846 """Test that RoundBasedFactory has non-None required ISA."""
847 reqs = RoundBasedFactory.required_isa()
848 assert reqs is not None
849
850 def test_produces_logical_t_gates(self):
851 """Test that RoundBasedFactory produces logical T gates with valid properties."""
852 arch = GateBased(gate_time=50, measurement_time=100)
853
854 for isa in RoundBasedFactory.q(use_cache=False).enumerate(arch.context()):
855 t = isa[T]
856 assert t.encoding == LOGICAL
857 assert t.arity == 1
858 assert t.expect_error_rate() > 0
859 assert t.expect_time() > 0
860 assert t.expect_space() > 0
861 break # Just check the first
862
863 def test_error_rates_are_bounded(self):
864 """Distilled T error rates should be bounded and mostly small."""
865 arch = GateBased(gate_time=50, measurement_time=100) # T error rate is 1e-4
866
867 errors = []
868 for isa in RoundBasedFactory.q(use_cache=False).enumerate(arch.context()):
869 errors.append(isa[T].expect_error_rate())
870
871 # All should be positive
872 assert all(e > 0 for e in errors)
873 # Most distilled error rates should be much lower than 1
874 assert min(errors) < 1e-4
875 # Median should be well below raw physical error
876 sorted_errors = sorted(errors)
877 median = sorted_errors[len(sorted_errors) // 2]
878 assert median < 1e-3
879
880 def test_max_produces_fewer_or_equal_results_than_sum(self):
881 """Using max for physical_qubit_calculation may filter differently."""
882 arch = GateBased(gate_time=50, measurement_time=100)
883
884 sum_count = sum(
885 1 for _ in RoundBasedFactory.q(use_cache=False).enumerate(arch.context())
886 )
887 max_count = sum(
888 1
889 for _ in RoundBasedFactory.q(
890 use_cache=False, physical_qubit_calculation=max
891 ).enumerate(arch.context())
892 )
893
894 assert max_count <= sum_count
895
896 def test_max_space_less_than_or_equal_sum_space(self):
897 """max-aggregated space should be <= sum-aggregated space for each."""
898 arch = GateBased(gate_time=50, measurement_time=100)
899
900 sum_spaces = sorted(
901 isa[T].expect_space()
902 for isa in RoundBasedFactory.q(use_cache=False).enumerate(arch.context())
903 )
904
905 max_spaces = sorted(
906 isa[T].expect_space()
907 for isa in RoundBasedFactory.q(
908 use_cache=False, physical_qubit_calculation=max
909 ).enumerate(arch.context())
910 )
911
912 # The minimum space with max should be <= minimum space with sum
913 assert max_spaces[0] <= sum_spaces[0]
914
915 def test_with_three_aux_code_query(self):
916 """RoundBasedFactory with ThreeAux code query should produce results."""
917 arch = Majorana()
918
919 count = 0
920 for isa in RoundBasedFactory.q(
921 use_cache=False, code_query=ThreeAux.q()
922 ).enumerate(arch.context()):
923 assert T in isa
924 assert isa[T].encoding == LOGICAL
925 count += 1
926
927 assert count > 0
928
929 def test_round_based_gate_based_sum(self):
930 """Test RoundBasedFactory aggregated totals with GateBased sum mode."""
931 arch = GateBased(gate_time=50, measurement_time=100)
932
933 total_space = 0
934 total_time = 0
935 total_error = 0.0
936 count = 0
937
938 for isa in RoundBasedFactory.q(use_cache=False).enumerate(arch.context()):
939 count += 1
940 total_space += isa[T].expect_space()
941 total_time += isa[T].expect_time()
942 total_error += isa[T].expect_error_rate()
943
944 assert total_space == 12_946_488
945 assert total_time == 12_032_250
946 assert abs(total_error - 0.001_463_030_863_973_197_8) < 1e-8
947 assert count == 107
948
949 def test_round_based_gate_based_max(self):
950 """Test RoundBasedFactory aggregated totals with GateBased max mode."""
951 arch = GateBased(gate_time=50, measurement_time=100)
952
953 total_space = 0
954 total_time = 0
955 total_error = 0.0
956 count = 0
957
958 for isa in RoundBasedFactory.q(
959 use_cache=False, physical_qubit_calculation=max
960 ).enumerate(arch.context()):
961 count += 1
962 total_space += isa[T].expect_space()
963 total_time += isa[T].expect_time()
964 total_error += isa[T].expect_error_rate()
965
966 assert total_space == 4_651_617
967 assert total_time == 7_785_000
968 assert abs(total_error - 0.001_463_030_863_973_197_8) < 1e-8
969 assert count == 77
970
971 def test_round_based_msft_sum(self):
972 """Test RoundBasedFactory aggregated totals with Majorana sum mode."""
973 arch = Majorana()
974
975 total_space = 0
976 total_time = 0
977 total_error = 0.0
978 count = 0
979
980 for isa in RoundBasedFactory.q(
981 use_cache=False, code_query=ThreeAux.q()
982 ).enumerate(arch.context()):
983 count += 1
984 total_space += isa[T].expect_space()
985 total_time += isa[T].expect_time()
986 total_error += isa[T].expect_error_rate()
987
988 assert total_space == 255_952_723
989 assert total_time == 478_235_000
990 assert abs(total_error - 0.000_880_967_766_732_897_4) < 1e-8
991 assert count == 301
992
993
994# ---------------------------------------------------------------------------
995# Cross-model integration tests
996# ---------------------------------------------------------------------------
997
998
999class TestCrossModelIntegration:
1000 def test_surface_code_feeds_into_litinski(self):
1001 """SurfaceCode -> Litinski19Factory pipeline works end to end."""
1002 arch = GateBased(gate_time=50, measurement_time=100)
1003 ctx = arch.context()
1004
1005 # SurfaceCode takes gate-based physical ISA -> LATTICE_SURGERY
1006 sc = SurfaceCode(distance=5)
1007 sc_isas = list(sc.provided_isa(ctx.isa, ctx))
1008 assert len(sc_isas) == 1
1009
1010 # Litinski takes H, CNOT, MEAS_Z, T from the physical ISA
1011 factory = Litinski19Factory()
1012 factory_isas = list(factory.provided_isa(ctx.isa, ctx))
1013 assert len(factory_isas) > 0
1014
1015 def test_three_aux_feeds_into_round_based(self):
1016 """ThreeAux -> RoundBasedFactory pipeline works."""
1017 arch = Majorana()
1018 ctx = arch.context()
1019
1020 count = 0
1021 for isa in RoundBasedFactory.q(
1022 use_cache=False, code_query=ThreeAux.q()
1023 ).enumerate(ctx):
1024 assert T in isa
1025 count += 1
1026
1027 assert count > 0
1028
1029 def test_litinski_with_magic_up_to_clifford_query(self):
1030 """Full query chain: Litinski19Factory -> MagicUpToClifford."""
1031 arch = GateBased(gate_time=50, measurement_time=100)
1032 ctx = arch.context()
1033
1034 count = 0
1035 for isa in MagicUpToClifford.q(source=Litinski19Factory.q()).enumerate(ctx):
1036 assert T in isa
1037 assert CCX in isa
1038 assert CCY in isa
1039 assert CCZ in isa
1040 count += 1
1041
1042 assert count == 6
1043
1044 def test_surface_code_with_yoked_surface_code(self):
1045 """SurfaceCode -> YokedSurfaceCode pipeline provides MEMORY."""
1046 arch = GateBased(gate_time=50, measurement_time=100)
1047 ctx = arch.context()
1048
1049 count = 0
1050 for isa in TwoDimensionalYokedSurfaceCode.q(source=SurfaceCode.q()).enumerate(
1051 ctx
1052 ):
1053 assert MEMORY in isa
1054 count += 1
1055
1056 # 12 distances × 1 shape heuristic = 12
1057 assert count == 12
1058
1059 def test_majorana_three_aux_yoked(self):
1060 """Majorana -> ThreeAux -> YokedSurfaceCode pipeline."""
1061 arch = Majorana()
1062 ctx = arch.context()
1063
1064 count = 0
1065 for isa in TwoDimensionalYokedSurfaceCode.q(source=ThreeAux.q()).enumerate(ctx):
1066 assert MEMORY in isa
1067 count += 1
1068
1069 assert count > 0
1070