Skip to content

Commit

Permalink
fix: Program serialization is deterministic, and program equality che…
Browse files Browse the repository at this point in the history
…cks have been corrected. (#1767)

* fix: Programs with the same set of calibrations compare as equal, regardless of their order

* Add test

* formatting

* formatting

* note for correctness

* update qcs-sdk-python/quil, update test

* update lockfile

* fix tests
  • Loading branch information
MarquessV authored Apr 17, 2024
1 parent 2cba86e commit 08fd1a7
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 63 deletions.
2 changes: 1 addition & 1 deletion docs/source/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ method.

# Declare our memory spaces
branching_prog = Program()
test_register = branching_prog.declare('test_register', 'BIT')
ro = branching_prog.declare('ro', 'BIT')
test_register = branching_prog.declare('test_register', 'BIT')

# Construct each branch of our if-statement. We can have empty branches
# simply by having empty programs.
Expand Down
97 changes: 46 additions & 51 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ lark = "^0.11.1"
rpcq = "^3.10.0"
networkx = ">=2.5"
importlib-metadata = { version = ">=3.7.3,<5", python = "<3.8" }
qcs-sdk-python = "0.17.4"
qcs-sdk-python = "0.17.6"
tenacity = "^8.2.2"
types-python-dateutil = "^2.8.19"
types-retry = "^0.9.9"
Expand Down
8 changes: 7 additions & 1 deletion pyquil/quil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@ def __iter__(self) -> Iterator[AbstractInstruction]:

def __eq__(self, other: object) -> bool:
if isinstance(other, Program):
return self._program.to_instructions() == other._program.to_instructions()
return self._program == other._program
return False

def __len__(self) -> int:
Expand All @@ -1035,6 +1035,12 @@ def __str__(self) -> str:
"""
return self._program.to_quil_or_debug()

def get_all_instructions(self) -> List[AbstractInstruction]:
"""
Get _all_ instructions that makeup the program.
"""
return _convert_to_py_instructions(self._program.to_instructions())


def merge_with_pauli_noise(
prog_list: Iterable[Program], probabilities: Sequence[float], qubits: Sequence[int]
Expand Down
15 changes: 12 additions & 3 deletions test/unit/__snapshots__/test_quil.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@

'''
# ---
# name: test_classical_regs
'''
DECLARE reg BIT[2]
DECLARE ro BIT[2]
X 0
MEASURE 0 reg[1]

'''
# ---
# name: test_construction_syntax
'''
DECLARE ro BIT[2]
Expand Down Expand Up @@ -338,13 +347,13 @@
# ---
# name: test_prog_merge.1
'''
DEFGATE PERM AS PERMUTATION:
0, 1, 3, 2

DEFGATE test AS MATRIX:
1, 0
0, 1

DEFGATE PERM AS PERMUTATION:
0, 1, 3, 2

X 0
test 0
PERM 0 1
Expand Down
55 changes: 52 additions & 3 deletions test/unit/test_quil.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
)
from pyquil.quilatom import Frame, MemoryReference, Parameter, QubitPlaceholder, Sub, quil_cos, quil_sin
from pyquil.quilbase import (
AbstractInstruction,
DefGate,
DefFrame,
Gate,
Expand All @@ -89,6 +90,7 @@
DefCalibration,
DefMeasureCalibration,
DefPermutationGate,
DefWaveform,
)


Expand Down Expand Up @@ -314,14 +316,14 @@ def test_prog_init(snapshot):
assert p.out() == snapshot


def test_classical_regs():
def test_classical_regs(snapshot: SnapshotAssertion):
p = Program()
p.inst(
Declare("ro", "BIT", 2),
Declare("reg", "BIT", 2),
Declare("ro", "BIT", 2),
X(0),
).measure(0, MemoryReference("reg", 1))
assert p.out() == "DECLARE reg BIT[2]\nDECLARE ro BIT[2]\nX 0\nMEASURE 0 reg[1]\n"
assert p.out() == snapshot
assert p.declarations == {
"reg": Declare("reg", "BIT", 2),
"ro": Declare("ro", "BIT", 2),
Expand Down Expand Up @@ -1194,3 +1196,50 @@ def test_out_without_calibrations():
combined_program = quilt_program + quil_program

assert combined_program.out(calibrations=False) == quil_program.out()


def test_program_equality():
program = Program("""DECLARE foo REAL[1]
DEFFRAME 1 "rx":
HARDWARE-OBJECT: "hardware"
DEFCAL I 0:
DELAY 0 1
DEFCAL I 1:
DELAY 0 1
DEFCAL I 2:
DELAY 0 1
DEFCAL MEASURE 0 addr:
CAPTURE 0 "ro_rx" custom addr
DEFCAL MEASURE 1 addr:
CAPTURE 1 "ro_rx" custom addr
DEFWAVEFORM custom:
1,2
DEFWAVEFORM custom2:
3,4
DEFWAVEFORM another1:
4,5
DEFGATE BAR AS MATRIX:
0, 1
1, 0
DEFGATE FOO AS MATRIX:
0, 1
1, 0
H 1
CNOT 2 3""")

# Definitions in Quil are "global" in the sense that the order they are defined in the program does should not
# effect equality.
def is_global_state_instruction(i: AbstractInstruction) -> bool:
return isinstance(i, (DefFrame, DefWaveform, DefGate))

# Construct a copy of the program, inserting global instructions in reverse order. Since their order does not matter
# the new program should be equal to the original program.
base_program = program.filter_instructions(lambda i: not is_global_state_instruction(i))
global_instructions = program.filter_instructions(lambda i: is_global_state_instruction(i)).get_all_instructions()
global_reversed_program = base_program + global_instructions[::-1]

assert global_reversed_program == program

# Program with reversed instructions that aren't "global" should not be equivalent
non_global_reversed_program = Program(base_program.get_all_instructions()[::-1]) + global_instructions
assert non_global_reversed_program != program
Loading

0 comments on commit 08fd1a7

Please sign in to comment.