Skip to content

Commit 1bc87e1

Browse files
authored
Getting permute working for both QASM and QIR (#130)
* Getting permute working for both QASM and QIR * Attempting to fix Issue #116 * Add some QASMGenerator arguments back * Moved new qasm functionality from slr_converter.py to gen_qasm.py and moving SLR tests together. * Refactor to simplify gen_qir.py * linting
1 parent 6c447b2 commit 1bc87e1

14 files changed

Lines changed: 2676 additions & 150 deletions

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,11 @@ test = [ # pinning testing environment
2424

2525
[tool.uv]
2626
default-groups = ["dev", "test"]
27+
28+
[tool.pytest.ini_options]
29+
markers = [
30+
"optional_dependency: mark a test as using one or more optional dependencies",
31+
"optional_unix: mark tests as using an optional dependency that only work with Unix-based systems",
32+
"wasmer: mark test as using the 'wasmer' option",
33+
"wasmtime: mark test as using the 'wasmtime' option",
34+
]

python/quantum-pecos/src/pecos/slr/gen_codes/gen_qasm.py

Lines changed: 485 additions & 65 deletions
Large diffs are not rendered by default.

python/quantum-pecos/src/pecos/slr/gen_codes/gen_qir.py

Lines changed: 435 additions & 50 deletions
Large diffs are not rendered by default.

python/qir-tests/pecos/regression/random_cases/test_slr_phys.py renamed to python/slr-tests/pecos/regression/random_cases/test_slr_phys.py

File renamed without changes.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
"""Pytest fixtures for SLR tests."""
2+
3+
import pytest
4+
from pecos.qeclib import qubit as Q
5+
from pecos.slr import CReg, Main, Permute, QReg
6+
7+
8+
@pytest.fixture
9+
def basic_permutation_program():
10+
"""Create a basic program with permutation of classical registers."""
11+
a = CReg("a", 2)
12+
b = CReg("b", 2)
13+
14+
prog = Main(
15+
a,
16+
b,
17+
Permute(
18+
[a[0], b[1]],
19+
[b[1], a[0]],
20+
),
21+
a[0].set(1), # Should become b[1] = 1 after permutation
22+
)
23+
24+
return prog, a, b
25+
26+
27+
@pytest.fixture
28+
def same_register_permutation_program():
29+
"""Create a program with permutation within the same register."""
30+
a = CReg("a", 3)
31+
32+
prog = Main(
33+
a,
34+
Permute(
35+
[a[0], a[1], a[2]],
36+
[a[2], a[0], a[1]],
37+
),
38+
a[0].set(1), # Should become a[2] = 1
39+
a[1].set(0), # Should become a[0] = 0
40+
a[2].set(1), # Should become a[1] = 1
41+
)
42+
43+
return prog, a
44+
45+
46+
@pytest.fixture
47+
def quantum_permutation_program():
48+
"""Create a program with permutation of quantum registers."""
49+
a = QReg("a", 2)
50+
b = QReg("b", 2)
51+
52+
prog = Main(
53+
a,
54+
b,
55+
Permute(
56+
[a[0], b[0]],
57+
[b[0], a[0]],
58+
),
59+
Q.H(a[0]), # Should become H(b[0]) after permutation
60+
Q.CX(a[0], a[1]), # Should become CX(b[0], a[1]) after permutation
61+
)
62+
63+
return prog, a, b
64+
65+
66+
@pytest.fixture
67+
def measurement_program():
68+
"""Create a program with permutation and measurements."""
69+
a = QReg("a", 2)
70+
b = QReg("b", 2)
71+
m = CReg("m", 2)
72+
n = CReg("n", 2)
73+
74+
prog = Main(
75+
a,
76+
b,
77+
m,
78+
n,
79+
# Apply permutations to both quantum and classical registers
80+
Permute(
81+
[a[0], b[0]],
82+
[b[0], a[0]],
83+
),
84+
Permute(
85+
[m[0], n[0]],
86+
[n[0], m[0]],
87+
),
88+
# Apply quantum operations
89+
Q.H(a[0]),
90+
Q.CX(a[0], b[0]),
91+
)
92+
93+
return prog, a, b, m, n
94+
95+
96+
@pytest.fixture
97+
def individual_measurement_program():
98+
"""Create a program with permutation and individual measurements."""
99+
a = QReg("a", 2)
100+
b = QReg("b", 2)
101+
m = CReg("m", 2)
102+
n = CReg("n", 2)
103+
104+
prog = Main(
105+
a,
106+
b,
107+
m,
108+
n,
109+
# Apply permutations to both quantum and classical registers
110+
Permute(
111+
[a[0], b[0]],
112+
[b[0], a[0]],
113+
),
114+
Permute(
115+
[m[0], n[0]],
116+
[n[0], m[0]],
117+
),
118+
# Apply quantum operations
119+
Q.H(a[0]),
120+
Q.CX(a[0], b[0]),
121+
# Add individual measurements
122+
Q.Measure(a[0]) > m[0],
123+
Q.Measure(a[1]) > m[1],
124+
)
125+
126+
return prog, a, b, m, n
127+
128+
129+
@pytest.fixture
130+
def register_measurement_program():
131+
"""Create a program with permutation and register-wide measurements."""
132+
a = QReg("a", 2)
133+
b = QReg("b", 2)
134+
m = CReg("m", 2)
135+
n = CReg("n", 2)
136+
137+
prog = Main(
138+
a,
139+
b,
140+
m,
141+
n,
142+
# Apply permutations to both quantum and classical registers
143+
Permute(
144+
[a[0], b[0]],
145+
[b[0], a[0]],
146+
),
147+
Permute(
148+
[m[0], n[0]],
149+
[n[0], m[0]],
150+
),
151+
# Apply quantum operations
152+
Q.H(a[0]),
153+
Q.CX(a[0], b[0]),
154+
# Add register-wide measurement
155+
Q.Measure(a) > m,
156+
)
157+
158+
return prog, a, b, m, n
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
"""Tests for basic permutation functionality in both QASM and QIR generation."""
2+
3+
import re
4+
5+
import pytest
6+
from pecos.slr import CReg, Main, Permute, SlrConverter
7+
8+
# Test fixtures
9+
10+
11+
def create_basic_permutation_program():
12+
"""Create a basic program with permutation of classical registers."""
13+
a = CReg("a", 2)
14+
b = CReg("b", 2)
15+
16+
prog = Main(
17+
a,
18+
b,
19+
Permute(
20+
[a[0], b[1]],
21+
[b[1], a[0]],
22+
),
23+
a[0].set(1), # Should become b[1] = 1 after permutation
24+
)
25+
26+
return prog, a, b
27+
28+
29+
def create_same_register_permutation_program():
30+
"""Create a program with permutation within the same register."""
31+
a = CReg("a", 3)
32+
33+
prog = Main(
34+
a,
35+
Permute(
36+
[a[0], a[1], a[2]],
37+
[a[2], a[0], a[1]],
38+
),
39+
a[0].set(1), # Should become a[2] = 1
40+
a[1].set(0), # Should become a[0] = 0
41+
a[2].set(1), # Should become a[1] = 1
42+
)
43+
44+
return prog, a
45+
46+
47+
# QASM Tests
48+
49+
50+
def test_permutation_consistency_for_bits_in_qasm():
51+
"""Test that permutation is consistent across multiple QASM generations."""
52+
prog = Main(
53+
a := CReg("a", 2),
54+
b := CReg("b", 2),
55+
Permute(
56+
[a[0], b[1]],
57+
[b[1], a[0]],
58+
),
59+
a[0].set(1),
60+
)
61+
62+
qasm1 = SlrConverter(prog).qasm()
63+
qasm2 = SlrConverter(prog).qasm()
64+
65+
# Print the QASM for debugging
66+
print("\nQASM output:")
67+
print(qasm1)
68+
69+
assert qasm1 == qasm2
70+
assert "a[0] = 1;" in qasm1
71+
72+
# Verify that the bit permutation is using the temporary bit approach, not XOR swap
73+
assert "creg _bit_swap[1];" in qasm1
74+
assert "_bit_swap[0] = a[0];" in qasm1
75+
assert "a[0] = b[1];" in qasm1
76+
assert "b[1] = _bit_swap[0];" in qasm1
77+
assert "a[0] = a[0] ^ b[1];" not in qasm1 # Make sure XOR swap is not used
78+
79+
80+
def test_basic_permutation_qasm(basic_permutation_program):
81+
"""Test basic permutation functionality in QASM generation."""
82+
prog, _, _ = basic_permutation_program
83+
84+
# Generate QASM
85+
from pecos.slr.gen_codes.gen_qasm import QASMGenerator
86+
from pecos.slr.slr_converter import SlrConverter
87+
88+
# Create a custom QASM generator to debug the permutation map
89+
generator = QASMGenerator()
90+
generator.generate_block(prog)
91+
qasm = generator.get_output()
92+
93+
# Print the QASM for debugging
94+
print("\nQASM output:")
95+
print(qasm)
96+
97+
# Print the permutation map
98+
print("\nPermutation map:")
99+
print(generator.permutation_map)
100+
101+
# Verify that the QASM contains the correct permuted operation
102+
# For classical bit permutations, operations still refer to the original bit names
103+
assert "a[0] = 1;" in qasm
104+
105+
# Verify that the bit permutation is using the temporary bit approach, not XOR swap
106+
assert "creg _bit_swap[1];" in qasm
107+
assert "_bit_swap[0] = a[0];" in qasm
108+
assert "a[0] = b[1];" in qasm
109+
assert "b[1] = _bit_swap[0];" in qasm
110+
assert "a[0] = a[0] ^ b[1];" not in qasm # Make sure XOR swap is not used
111+
112+
# Verify that running QASM generation twice produces consistent results
113+
qasm2 = SlrConverter(prog).qasm()
114+
assert qasm == qasm2, "QASM generation is not deterministic"
115+
116+
117+
def test_same_register_permutation_qasm(same_register_permutation_program):
118+
"""Test permutation of elements within the same register in QASM."""
119+
prog, _ = same_register_permutation_program
120+
121+
qasm = SlrConverter(prog).qasm()
122+
123+
# Print the QASM for debugging
124+
print("\nQASM output:")
125+
print(qasm)
126+
127+
# For classical bit permutations, operations still refer to the original bit names
128+
assert "a[0] = 1;" in qasm
129+
assert "a[1] = 0;" in qasm
130+
assert "a[2] = 1;" in qasm
131+
132+
# Verify that the bit permutation is using the temporary bit approach, not XOR swap
133+
assert "creg _bit_swap[1];" in qasm
134+
assert "_bit_swap[0] = a[0];" in qasm
135+
assert "a[0] = a[2];" in qasm # Part of the cycle
136+
assert "a[2] = a[1];" in qasm # Part of the cycle
137+
assert "a[1] = _bit_swap[0];" in qasm # Completing the cycle
138+
assert "a[0] = a[0] ^ a[1];" not in qasm # Make sure XOR swap is not used
139+
140+
141+
# QIR Tests
142+
143+
144+
@pytest.mark.optional_dependency
145+
def test_basic_permutation_qir(basic_permutation_program):
146+
"""Test basic permutation functionality in QIR generation."""
147+
prog, _, _ = basic_permutation_program
148+
149+
# Generate QIR
150+
qir = SlrConverter(prog).qir()
151+
152+
# Print the QIR for debugging
153+
print("\nQIR output:")
154+
print(qir)
155+
156+
# Verify that the QIR contains a comment about the permutation
157+
assert "Permutation: a[0] -> b[1], b[1] -> a[0]" in qir
158+
159+
# Extract the register and index used in the set_creg_bit call
160+
# This should be setting a[0] (register %a, index 0) since the permutation
161+
# is not being applied to the operations in the QIR generator
162+
set_creg_calls = re.findall(
163+
r"call void @set_creg_bit\(i1\* %(\w+), i64 (\d+), i1 1\)",
164+
qir,
165+
)
166+
167+
# We should have at least one set_creg_bit call
168+
assert len(set_creg_calls) >= 1, "No set_creg_bit call found"
169+
170+
# Get the register and index
171+
reg_name, index = set_creg_calls[0]
172+
173+
# Verify that the set_creg_bit call is setting a[0] since the permutation
174+
# is not being applied to the operations in the QIR generator
175+
assert reg_name == "a", f"set_creg_bit applied to register {reg_name}, expected a"
176+
assert index == "0", f"set_creg_bit applied to index {index}, expected 0"
177+
178+
# Verify that running QIR generation twice produces consistent results
179+
qir2 = SlrConverter(prog).qir()
180+
assert qir == qir2, "QIR generation is not deterministic"
181+
182+
183+
@pytest.mark.optional_dependency
184+
def test_same_register_permutation_qir(same_register_permutation_program):
185+
"""Test permutation of elements within the same register in QIR."""
186+
prog, _ = same_register_permutation_program
187+
188+
qir = SlrConverter(prog).qir()
189+
190+
# Print the QIR for debugging
191+
print("\nQIR output:")
192+
print(qir)
193+
194+
# Verify that the QIR contains a comment about the permutation
195+
assert "Permutation: a[0] -> a[2], a[1] -> a[0], a[2] -> a[1]" in qir
196+
197+
# Extract the register and indices used in the set_creg_bit calls
198+
set_creg_calls = re.findall(
199+
r"call void @set_creg_bit\(i1\* %(\w+), i64 (\d+), i1 (\d+)\)",
200+
qir,
201+
)
202+
203+
# We should have at least three set_creg_bit calls
204+
assert (
205+
len(set_creg_calls) >= 3
206+
), f"Expected at least 3 set_creg_bit calls, found {len(set_creg_calls)}"
207+
208+
# Create a dictionary to store the values set for each index
209+
set_values = {}
210+
for reg_name, index, value in set_creg_calls:
211+
assert (
212+
reg_name == "a"
213+
), f"set_creg_bit applied to register {reg_name}, expected a"
214+
set_values[int(index)] = int(value)
215+
216+
# Verify that the set_creg_bit calls are setting the correct values
217+
# Since the permutation is not being applied to the operations in the QIR generator,
218+
# we expect the original operations to be executed
219+
assert set_values.get(0) == 1, "a[0] should be set to 1"
220+
assert set_values.get(1) == 0, "a[1] should be set to 0"
221+
assert set_values.get(2) == 1, "a[2] should be set to 1"

0 commit comments

Comments
 (0)