Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursively expand defcals #1270

Merged
merged 15 commits into from
Nov 19, 2020
2 changes: 1 addition & 1 deletion pyquil/_parser/PyQuilListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def exitGate(self, ctx: QuilParser.GateContext):
gate_name = ctx.name().getText()
modifiers = [mod.getText() for mod in ctx.modifier()]
params = list(map(_param, ctx.param()))
qubits = list(map(_qubit, ctx.qubit()))
qubits = list(map(_qubitOrFormal, ctx.qubitOrFormal()))

# The parsed string 'DAGGER CONTROLLED X 0 1' gives
# modifiers ['DAGGER', 'CONTROLLED']
Expand Down
86 changes: 43 additions & 43 deletions pyquil/_parser/gen3/QuilParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,44 +160,44 @@ def serializedATN():
buf.write("\3\2\2\2\u00f0\u00f2\3\2\2\2\u00f1\u00ef\3\2\2\2\u00f2")
buf.write("\u00f3\7W\2\2\u00f3\u00f5\3\2\2\2\u00f4\u00e9\3\2\2\2")
buf.write("\u00f4\u00f5\3\2\2\2\u00f5\u00f7\3\2\2\2\u00f6\u00f8\5")
buf.write("\f\7\2\u00f7\u00f6\3\2\2\2\u00f8\u00f9\3\2\2\2\u00f9\u00f7")
buf.write("\3\2\2\2\u00f9\u00fa\3\2\2\2\u00fa\t\3\2\2\2\u00fb\u00fc")
buf.write("\7P\2\2\u00fc\13\3\2\2\2\u00fd\u00fe\7Q\2\2\u00fe\r\3")
buf.write("\2\2\2\u00ff\u0100\5f\64\2\u0100\17\3\2\2\2\u0101\u0102")
buf.write("\t\2\2\2\u0102\21\3\2\2\2\u0103\u0104\7\3\2\2\u0104\u0112")
buf.write("\5\n\6\2\u0105\u0106\7V\2\2\u0106\u010b\5\26\f\2\u0107")
buf.write("\u0108\7U\2\2\u0108\u010a\5\26\f\2\u0109\u0107\3\2\2\2")
buf.write("\u010a\u010d\3\2\2\2\u010b\u0109\3\2\2\2\u010b\u010c\3")
buf.write("\2\2\2\u010c\u010e\3\2\2\2\u010d\u010b\3\2\2\2\u010e\u010f")
buf.write("\7W\2\2\u010f\u0113\3\2\2\2\u0110\u0111\7\23\2\2\u0111")
buf.write("\u0113\5\30\r\2\u0112\u0105\3\2\2\2\u0112\u0110\3\2\2")
buf.write("\2\u0112\u0113\3\2\2\2\u0113\u0114\3\2\2\2\u0114\u0115")
buf.write("\7Z\2\2\u0115\u0116\7`\2\2\u0116\u0117\5\32\16\2\u0117")
buf.write("\23\3\2\2\2\u0118\u0119\7\3\2\2\u0119\u0125\5\n\6\2\u011a")
buf.write("\u011b\7V\2\2\u011b\u0120\5\26\f\2\u011c\u011d\7U\2\2")
buf.write("\u011d\u011f\5\26\f\2\u011e\u011c\3\2\2\2\u011f\u0122")
buf.write("\3\2\2\2\u0120\u011e\3\2\2\2\u0120\u0121\3\2\2\2\u0121")
buf.write("\u0123\3\2\2\2\u0122\u0120\3\2\2\2\u0123\u0124\7W\2\2")
buf.write("\u0124\u0126\3\2\2\2\u0125\u011a\3\2\2\2\u0125\u0126\3")
buf.write("\2\2\2\u0126\u0128\3\2\2\2\u0127\u0129\5$\23\2\u0128\u0127")
buf.write("\3\2\2\2\u0129\u012a\3\2\2\2\u012a\u0128\3\2\2\2\u012a")
buf.write("\u012b\3\2\2\2\u012b\u012c\3\2\2\2\u012c\u012d\7\23\2")
buf.write("\2\u012d\u012e\7\26\2\2\u012e\u012f\7Z\2\2\u012f\u0130")
buf.write("\7`\2\2\u0130\u0131\5\36\20\2\u0131\25\3\2\2\2\u0132\u0133")
buf.write("\7[\2\2\u0133\u0134\7P\2\2\u0134\27\3\2\2\2\u0135\u0136")
buf.write("\t\3\2\2\u0136\31\3\2\2\2\u0137\u0138\5\34\17\2\u0138")
buf.write("\u0139\7`\2\2\u0139\u013b\3\2\2\2\u013a\u0137\3\2\2\2")
buf.write("\u013b\u013e\3\2\2\2\u013c\u013a\3\2\2\2\u013c\u013d\3")
buf.write("\2\2\2\u013d\u013f\3\2\2\2\u013e\u013c\3\2\2\2\u013f\u0140")
buf.write("\5\34\17\2\u0140\33\3\2\2\2\u0141\u0142\7_\2\2\u0142\u0147")
buf.write("\5f\64\2\u0143\u0144\7U\2\2\u0144\u0146\5f\64\2\u0145")
buf.write("\u0143\3\2\2\2\u0146\u0149\3\2\2\2\u0147\u0145\3\2\2\2")
buf.write("\u0147\u0148\3\2\2\2\u0148\35\3\2\2\2\u0149\u0147\3\2")
buf.write("\2\2\u014a\u014b\5 \21\2\u014b\u014c\7`\2\2\u014c\u014e")
buf.write("\3\2\2\2\u014d\u014a\3\2\2\2\u014e\u0151\3\2\2\2\u014f")
buf.write("\u014d\3\2\2\2\u014f\u0150\3\2\2\2\u0150\u0152\3\2\2\2")
buf.write("\u0151\u014f\3\2\2\2\u0152\u0153\5 \21\2\u0153\37\3\2")
buf.write("\2\2\u0154\u0155\7_\2\2\u0155\u0156\7P\2\2\u0156\u0157")
buf.write("\u0098M\2\u00f7\u00f6\3\2\2\2\u00f8\u00f9\3\2\2\2\u00f9")
buf.write("\u00f7\3\2\2\2\u00f9\u00fa\3\2\2\2\u00fa\t\3\2\2\2\u00fb")
buf.write("\u00fc\7P\2\2\u00fc\13\3\2\2\2\u00fd\u00fe\7Q\2\2\u00fe")
buf.write("\r\3\2\2\2\u00ff\u0100\5f\64\2\u0100\17\3\2\2\2\u0101")
buf.write("\u0102\t\2\2\2\u0102\21\3\2\2\2\u0103\u0104\7\3\2\2\u0104")
buf.write("\u0112\5\n\6\2\u0105\u0106\7V\2\2\u0106\u010b\5\26\f\2")
buf.write("\u0107\u0108\7U\2\2\u0108\u010a\5\26\f\2\u0109\u0107\3")
buf.write("\2\2\2\u010a\u010d\3\2\2\2\u010b\u0109\3\2\2\2\u010b\u010c")
buf.write("\3\2\2\2\u010c\u010e\3\2\2\2\u010d\u010b\3\2\2\2\u010e")
buf.write("\u010f\7W\2\2\u010f\u0113\3\2\2\2\u0110\u0111\7\23\2\2")
buf.write("\u0111\u0113\5\30\r\2\u0112\u0105\3\2\2\2\u0112\u0110")
buf.write("\3\2\2\2\u0112\u0113\3\2\2\2\u0113\u0114\3\2\2\2\u0114")
buf.write("\u0115\7Z\2\2\u0115\u0116\7`\2\2\u0116\u0117\5\32\16\2")
buf.write("\u0117\23\3\2\2\2\u0118\u0119\7\3\2\2\u0119\u0125\5\n")
buf.write("\6\2\u011a\u011b\7V\2\2\u011b\u0120\5\26\f\2\u011c\u011d")
buf.write("\7U\2\2\u011d\u011f\5\26\f\2\u011e\u011c\3\2\2\2\u011f")
buf.write("\u0122\3\2\2\2\u0120\u011e\3\2\2\2\u0120\u0121\3\2\2\2")
buf.write("\u0121\u0123\3\2\2\2\u0122\u0120\3\2\2\2\u0123\u0124\7")
buf.write("W\2\2\u0124\u0126\3\2\2\2\u0125\u011a\3\2\2\2\u0125\u0126")
buf.write("\3\2\2\2\u0126\u0128\3\2\2\2\u0127\u0129\5$\23\2\u0128")
buf.write("\u0127\3\2\2\2\u0129\u012a\3\2\2\2\u012a\u0128\3\2\2\2")
buf.write("\u012a\u012b\3\2\2\2\u012b\u012c\3\2\2\2\u012c\u012d\7")
buf.write("\23\2\2\u012d\u012e\7\26\2\2\u012e\u012f\7Z\2\2\u012f")
buf.write("\u0130\7`\2\2\u0130\u0131\5\36\20\2\u0131\25\3\2\2\2\u0132")
buf.write("\u0133\7[\2\2\u0133\u0134\7P\2\2\u0134\27\3\2\2\2\u0135")
buf.write("\u0136\t\3\2\2\u0136\31\3\2\2\2\u0137\u0138\5\34\17\2")
buf.write("\u0138\u0139\7`\2\2\u0139\u013b\3\2\2\2\u013a\u0137\3")
buf.write("\2\2\2\u013b\u013e\3\2\2\2\u013c\u013a\3\2\2\2\u013c\u013d")
buf.write("\3\2\2\2\u013d\u013f\3\2\2\2\u013e\u013c\3\2\2\2\u013f")
buf.write("\u0140\5\34\17\2\u0140\33\3\2\2\2\u0141\u0142\7_\2\2\u0142")
buf.write("\u0147\5f\64\2\u0143\u0144\7U\2\2\u0144\u0146\5f\64\2")
buf.write("\u0145\u0143\3\2\2\2\u0146\u0149\3\2\2\2\u0147\u0145\3")
buf.write("\2\2\2\u0147\u0148\3\2\2\2\u0148\35\3\2\2\2\u0149\u0147")
buf.write("\3\2\2\2\u014a\u014b\5 \21\2\u014b\u014c\7`\2\2\u014c")
buf.write("\u014e\3\2\2\2\u014d\u014a\3\2\2\2\u014e\u0151\3\2\2\2")
buf.write("\u014f\u014d\3\2\2\2\u014f\u0150\3\2\2\2\u0150\u0152\3")
buf.write("\2\2\2\u0151\u014f\3\2\2\2\u0152\u0153\5 \21\2\u0153\37")
buf.write("\3\2\2\2\u0154\u0155\7_\2\2\u0155\u0156\7P\2\2\u0156\u0157")
buf.write("\7V\2\2\u0157\u0158\5f\64\2\u0158\u015a\7W\2\2\u0159\u015b")
buf.write("\5$\23\2\u015a\u0159\3\2\2\2\u015b\u015c\3\2\2\2\u015c")
buf.write("\u015a\3\2\2\2\u015c\u015d\3\2\2\2\u015d!\3\2\2\2\u015e")
Expand Down Expand Up @@ -1240,11 +1240,11 @@ def param(self, i:int=None):
def RPAREN(self):
return self.getToken(QuilParser.RPAREN, 0)

def qubit(self, i:int=None):
def qubitOrFormal(self, i:int=None):
if i is None:
return self.getTypedRuleContexts(QuilParser.QubitContext)
return self.getTypedRuleContexts(QuilParser.QubitOrFormalContext)
else:
return self.getTypedRuleContext(QuilParser.QubitContext,i)
return self.getTypedRuleContext(QuilParser.QubitOrFormalContext,i)


def COMMA(self, i:int=None):
Expand Down Expand Up @@ -1315,11 +1315,11 @@ def gate(self):
_la = self._input.LA(1)
while True:
self.state = 244
self.qubit()
self.qubitOrFormal()
self.state = 247
self._errHandler.sync(self)
_la = self._input.LA(1)
if not (_la==QuilParser.INT):
if not (_la==QuilParser.IDENTIFIER or _la==QuilParser.INT):
break

except RecognitionException as re:
Expand Down
38 changes: 30 additions & 8 deletions pyquil/quil.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
DefWaveform,
)
from pyquil.quiltcalibrations import (
CalibrationError,
CalibrationMatch,
expand_calibration,
match_calibration,
Expand Down Expand Up @@ -718,17 +719,38 @@ def calibrate(self, instr: AbstractInstruction) -> List[AbstractInstruction]:
If a calibration definition matches the provided instruction, then the definition
body is returned with appropriate substitutions made for parameters and qubit
arguments. If no calibration definition matches, then the original instruction
is returned.
is returned. Calibrations are performed recursively, so that if a calibrated
instruction produces an instruction that has a corresponding calibration, it
will be expanded, and so on.

:param instr: An instruction.
:returns: A list of instructions, with the active calibration expanded.
:returns: A list of instructions, with the active calibrations expanded.
"""
# TODO: recursively expand?
match = self.match_calibrations(instr)
if match is not None:
return expand_calibration(match)
else:
return [instr]
queue = [instr]
calibrated_instructions: List[AbstractInstruction] = []
seen_instructions: Set[AbstractInstruction] = set()

while len(queue) > 0:
next_instruction, *queue = queue

if not isinstance(next_instruction, (Gate, Measurement)):
calibrated_instructions.append(next_instruction)
else:
match = self.match_calibrations(next_instruction)
if match is not None:
expanded_instructions = expand_calibration(match)
for expanded_instruction in expanded_instructions:
if expanded_instruction in seen_instructions:
raise CalibrationError(
f"Recursive calibration of {instr} produced a cyclic path."
)
else:
seen_instructions |= {expanded_instruction}
queue += expanded_instructions
else:
calibrated_instructions.append(next_instruction)

return calibrated_instructions

def is_protoquil(self, quilt: bool = False) -> bool:
"""
Expand Down
8 changes: 6 additions & 2 deletions pyquil/quiltcalibrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@
)


class CalibrationDoesntMatch(Exception):
class CalibrationError(Exception):
pass


class CalibrationDoesntMatch(CalibrationError):
pass


Expand Down Expand Up @@ -125,7 +129,7 @@ def fill_placeholders(obj, placeholder_values: Dict[Union[FormalArgument, Parame
)
return updated
else:
raise ValueError(f"Unable to fill placeholders in object {obj}.")
raise CalibrationError(f"Unable to fill placeholders in object {obj}.")
except Exception as e:
raise e
# raise ValueError(f"Unable to fill placeholders in object {obj}.")
Expand Down
5 changes: 0 additions & 5 deletions pyquil/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,11 +378,6 @@ def test_pragma():
parse_equals("PRAGMA NO-NOISE", Pragma("NO-NOISE"))


def test_invalid():
with pytest.raises(RuntimeError):
parse("H X")


def test_empty_program():
parse_equals("")

Expand Down
53 changes: 53 additions & 0 deletions pyquil/tests/test_quilt.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

import numpy as np

from pyquil.quil import Program
Expand All @@ -16,6 +18,7 @@
BoxcarAveragerKernel,
)
from pyquil.quiltcalibrations import (
CalibrationError,
CalibrationMatch,
fill_placeholders,
match_calibration,
Expand Down Expand Up @@ -195,6 +198,56 @@ def test_program_calibrate():
assert calibrated == Program('SHIFT-PHASE 0 "rf" -pi').instructions


def test_program_calibrate_recursive():
prog = Program(
"""
DEFCAL RX(%theta) q:
RY(%theta) q

DEFCAL RZ(%theta) q:
RX(%theta) q
"""
)
calibrated = prog.calibrate(Gate("RZ", [np.pi], [Qubit(0)]))
assert calibrated == Program("RY(pi) 0").instructions


@pytest.mark.parametrize(
"program_text",
(
"""
DEFCAL RZ(%theta) q:
RZ(%theta) q
""",
"""
DEFCAL RX(%theta) q:
RZ(%theta) q

DEFCAL RZ(%theta) q:
RX(%theta) q
""",
"""
DEFCAL RX(%theta) q:
RZ(0) q

DEFCAL RZ(%theta) q:
RX(%theta) q
""",
"""
DEFCAL RX(%theta) q:
RZ(%theta) q

DEFCAL RZ(%theta) q:
RX(0) q
""",
),
)
def test_program_calibrate_cyclic_error(program_text):
prog = Program(program_text)
with pytest.raises(CalibrationError):
prog.calibrate(Gate("RZ", [np.pi], [Qubit(0)]))


def test_merge_programs_with_quilt_features():
prog_1 = Program(
"""
Expand Down