##############################################################################
# Copyright 2016-2018 Rigetti Computing
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##############################################################################
"""
Contains the core pyQuil objects that correspond to Quil instructions.
"""
import collections
import json
from numbers import Complex
from typing import (
Any,
Callable,
ClassVar,
Container,
Dict,
Iterable,
List,
Optional,
Sequence,
Set,
Tuple,
Union,
TYPE_CHECKING,
cast,
)
import numpy as np
from pyquil.quilatom import (
Expression,
ExpressionDesignator,
Label,
LabelPlaceholder,
MemoryReference,
Parameter,
ParameterDesignator,
Frame,
Waveform,
Qubit,
QubitDesignator,
QubitPlaceholder,
FormalArgument,
_contained_parameters,
format_parameter,
unpack_qubit,
_complex_str,
)
if TYPE_CHECKING:
from pyquil.paulis import PauliSum
from dataclasses import dataclass
[docs]class AbstractInstruction(object):
"""
Abstract class for representing single instructions.
"""
[docs] def out(self) -> str:
pass
def __str__(self) -> str:
return self.out()
def __eq__(self, other: object) -> bool:
return isinstance(other, self.__class__) and self.out() == other.out()
def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
return hash(self.out())
RESERVED_WORDS: Container[str] = [
"DEFGATE",
"DEFCIRCUIT",
"MEASURE",
"LABEL",
"HALT",
"JUMP",
"JUMP-WHEN",
"JUMP-UNLESS",
"RESET",
"WAIT",
"NOP",
"INCLUDE",
"PRAGMA",
"DECLARE",
"NEG",
"NOT",
"AND",
"IOR",
"XOR",
"MOVE",
"EXCHANGE",
"CONVERT",
"ADD",
"SUB",
"MUL",
"DIV",
"EQ",
"GT",
"GE",
"LT",
"LE",
"LOAD",
"STORE",
# Quil-T additions:
"DEFCAL",
"DEFFRAME",
"DEFWAVEFORM",
"PULSE",
"CAPTURE",
"RAW-CAPTURE",
"DELAY",
"FENCE",
"SET-FREQUENCY",
"SET-PHASE",
"SHIFT-PHASE",
"SWAP-PHASES",
"SET-SCALE",
"SAMPLE-RATE",
"INITIAL-FREQUENCY",
# to be removed:
"TRUE",
"FALSE",
"OR",
]
def _extract_qubit_index(qubit: Union[Qubit, QubitPlaceholder, FormalArgument], index: bool = True) -> QubitDesignator:
if index and isinstance(qubit, Qubit):
return qubit.index
return qubit
def _get_frame_qubits(frame: Frame, index: bool = True) -> Set[QubitDesignator]:
for q in frame.qubits:
if isinstance(q, FormalArgument):
raise ValueError("Attempted to extract FormalArgument where a Qubit is expected.")
return {_extract_qubit_index(q, index) for q in cast(List[Qubit], frame.qubits)}
def _format_qubit_str(qubit: Union[Qubit, QubitPlaceholder, FormalArgument]) -> str:
if isinstance(qubit, QubitPlaceholder):
return "{%s}" % str(qubit)
return str(qubit)
def _format_qubits_str(qubits: Iterable[Union[Qubit, QubitPlaceholder, FormalArgument]]) -> str:
return " ".join([_format_qubit_str(qubit) for qubit in qubits])
def _format_qubits_out(qubits: Iterable[Union[Qubit, QubitPlaceholder, FormalArgument]]) -> str:
return " ".join([qubit.out() for qubit in qubits])
def _format_params(params: Iterable[ParameterDesignator]) -> str:
return "(" + ",".join(format_parameter(param) for param in params) + ")"
def _join_strings(*args: str) -> str:
return " ".join(map(str, args))
[docs]class Gate(AbstractInstruction):
"""
This is the pyQuil object for a quantum gate instruction.
"""
def __init__(
self,
name: str,
params: Iterable[ParameterDesignator],
qubits: Iterable[Union[Qubit, QubitPlaceholder, FormalArgument]],
):
if not isinstance(name, str):
raise TypeError("Gate name must be a string")
if name in RESERVED_WORDS:
raise ValueError("Cannot use {} for a gate name since it's a reserved word".format(name))
if not isinstance(params, collections.abc.Iterable):
raise TypeError("Gate params must be an Iterable")
if not isinstance(qubits, collections.abc.Iterable):
raise TypeError("Gate arguments must be an Iterable")
for qubit in qubits:
if not isinstance(qubit, (Qubit, QubitPlaceholder, FormalArgument)):
raise TypeError("Gate arguments must all be Qubits")
qubits_list = list(qubits)
if len(qubits_list) == 0:
raise TypeError("Gate arguments must be non-empty")
self.name = name
self.params = list(params)
self.qubits = qubits_list
self.modifiers: List[str] = []
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return {_extract_qubit_index(q, indices) for q in self.qubits}
[docs] def out(self) -> str:
if self.params:
return "{}{}{} {}".format(
" ".join(self.modifiers) + " " if self.modifiers else "",
self.name,
_format_params(self.params),
_format_qubits_out(self.qubits),
)
else:
return "{}{} {}".format(
" ".join(self.modifiers) + " " if self.modifiers else "",
self.name,
_format_qubits_out(self.qubits),
)
[docs] def controlled(self, control_qubit: Union[QubitDesignator, Sequence[QubitDesignator]]) -> "Gate":
"""
Add the CONTROLLED modifier to the gate with the given control qubit or Sequence of control
qubits.
"""
control_qubit = control_qubit if isinstance(control_qubit, Sequence) else [control_qubit]
for qubit in control_qubit:
qubit = unpack_qubit(qubit)
self.modifiers.insert(0, "CONTROLLED")
self.qubits.insert(0, qubit)
return self
[docs] def forked(self, fork_qubit: QubitDesignator, alt_params: List[ParameterDesignator]) -> "Gate":
"""
Add the FORKED modifier to the gate with the given fork qubit and given additional
parameters.
"""
if not isinstance(alt_params, list):
raise TypeError("Gate params must be a list")
if len(self.params) != len(alt_params):
raise ValueError("Expected {} parameters but received {}".format(len(self.params), len(alt_params)))
fork_qubit = unpack_qubit(fork_qubit)
self.modifiers.insert(0, "FORKED")
self.qubits.insert(0, fork_qubit)
self.params += alt_params
return self
[docs] def dagger(self) -> "Gate":
"""
Add the DAGGER modifier to the gate.
"""
self.modifiers.insert(0, "DAGGER")
return self
def __repr__(self) -> str:
return "<Gate " + str(self) + ">"
def __str__(self) -> str:
if self.params:
return "{}{}{} {}".format(
" ".join(self.modifiers) + " " if self.modifiers else "",
self.name,
_format_params(self.params),
_format_qubits_str(self.qubits),
)
else:
return "{}{} {}".format(
" ".join(self.modifiers) + " " if self.modifiers else "",
self.name,
_format_qubits_str(self.qubits),
)
def _strip_modifiers(gate: Gate, limit: Optional[int] = None) -> Gate:
"""
Remove modifiers from :py:class:`Gate`.
This function removes up to ``limit`` gate modifiers from the given gate,
starting from the leftmost gate modifier.
:param gate: A gate.
:param limit: An upper bound on how many modifiers to remove.
"""
if limit is None:
limit = len(gate.modifiers)
# We walk the modifiers from left-to-right, tracking indices to identify
# qubits/params introduced by gate modifiers.
#
# Invariants:
# - gate.qubits[0:qubit_index] are qubits introduced by gate modifiers
# - gate.params[param_index:] are parameters introduced by gate modifiers
qubit_index = 0
param_index = len(gate.params)
for m in gate.modifiers[:limit]:
if m == "CONTROLLED":
qubit_index += 1
elif m == "FORKED":
if param_index % 2 != 0:
raise ValueError("FORKED gate has an invalid number of parameters.")
param_index //= 2
qubit_index += 1
elif m == "DAGGER":
pass
else:
raise TypeError("Unsupported gate modifier {}".format(m))
stripped = Gate(gate.name, gate.params[:param_index], gate.qubits[qubit_index:])
stripped.modifiers = gate.modifiers[limit:]
return stripped
[docs]class Measurement(AbstractInstruction):
"""
This is the pyQuil object for a Quil measurement instruction.
"""
def __init__(
self,
qubit: Union[Qubit, QubitPlaceholder, FormalArgument],
classical_reg: Optional[MemoryReference],
):
if not isinstance(qubit, (Qubit, QubitPlaceholder, FormalArgument)):
raise TypeError("qubit should be a Qubit")
if classical_reg is not None and not isinstance(classical_reg, MemoryReference):
raise TypeError("classical_reg should be None or a MemoryReference instance")
self.qubit = qubit
self.classical_reg = classical_reg
[docs] def out(self) -> str:
if self.classical_reg:
return "MEASURE {} {}".format(self.qubit.out(), self.classical_reg.out())
else:
return "MEASURE {}".format(self.qubit.out())
def __str__(self) -> str:
if self.classical_reg:
return "MEASURE {} {}".format(_format_qubit_str(self.qubit), str(self.classical_reg))
else:
return "MEASURE {}".format(_format_qubit_str(self.qubit))
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return {_extract_qubit_index(self.qubit, indices)}
[docs]class ResetQubit(AbstractInstruction):
"""
This is the pyQuil object for a Quil targeted reset instruction.
"""
def __init__(self, qubit: Union[Qubit, QubitPlaceholder, FormalArgument]):
if not isinstance(qubit, (Qubit, QubitPlaceholder, FormalArgument)):
raise TypeError("qubit should be a Qubit")
self.qubit = qubit
[docs] def out(self) -> str:
return "RESET {}".format(self.qubit.out())
def __str__(self) -> str:
return "RESET {}".format(_format_qubit_str(self.qubit))
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return {_extract_qubit_index(self.qubit, indices)}
[docs]class DefGate(AbstractInstruction):
"""
A DEFGATE directive.
:param name: The name of the newly defined gate.
:param matrix: The matrix defining this gate.
:param parameters: list of parameters that are used in this gate
"""
def __init__(
self,
name: str,
matrix: Union[List[List[Any]], np.ndarray, np.matrix],
parameters: Optional[List[Parameter]] = None,
):
if not isinstance(name, str):
raise TypeError("Gate name must be a string")
if name in RESERVED_WORDS:
raise ValueError("Cannot use {} for a gate name since it's a reserved word".format(name))
if isinstance(matrix, list):
rows = len(matrix)
if not all([len(row) == rows for row in matrix]):
raise ValueError("Matrix must be square.")
elif isinstance(matrix, (np.ndarray, np.matrix)):
rows, cols = matrix.shape
if rows != cols:
raise ValueError("Matrix must be square.")
else:
raise TypeError("Matrix argument must be a list or NumPy array/matrix")
if 0 != rows & (rows - 1):
raise ValueError("Dimension of matrix must be a power of 2, got {0}".format(rows))
self.name = name
self.matrix = np.asarray(matrix)
if parameters:
if not isinstance(parameters, list):
raise TypeError("Paramaters must be a list")
expressions = [elem for row in self.matrix for elem in row if isinstance(elem, Expression)]
used_params = {param for exp in expressions for param in _contained_parameters(exp)}
if set(parameters) != used_params:
raise ValueError(
"Parameters list does not match parameters actually used in gate matrix:\n"
"Parameters in argument: {}, Parameters in matrix: {}".format(parameters, used_params)
)
else:
is_unitary = np.allclose(np.eye(rows), self.matrix.dot(self.matrix.T.conj()))
if not is_unitary:
raise ValueError("Matrix must be unitary.")
self.parameters = parameters
[docs] def out(self) -> str:
"""
Prints a readable Quil string representation of this gate.
:returns: String representation of a gate
"""
def format_matrix_element(element: Union[ExpressionDesignator, str]) -> str:
"""
Formats a parameterized matrix element.
:param element: The parameterized element to format.
"""
if isinstance(element, (int, float, complex, np.int_)):
return format_parameter(element)
elif isinstance(element, str):
return element
elif isinstance(element, Expression):
return str(element)
else:
raise TypeError("Invalid matrix element: %r" % element)
if self.parameters:
result = "DEFGATE {}({}):\n".format(self.name, ", ".join(map(str, self.parameters)))
else:
result = "DEFGATE {}:\n".format(self.name)
for row in self.matrix:
result += " "
fcols = [format_matrix_element(col) for col in row]
result += ", ".join(fcols)
result += "\n"
return result
[docs] def get_constructor(self) -> Union[Callable[..., Gate], Callable[..., Callable[..., Gate]]]:
"""
:returns: A function that constructs this gate on variable qubit indices. E.g.
`mygate.get_constructor()(1) applies the gate to qubit 1.`
"""
if self.parameters:
return lambda *params: lambda *qubits: Gate(
name=self.name, params=list(params), qubits=list(map(unpack_qubit, qubits))
)
else:
return lambda *qubits: Gate(name=self.name, params=[], qubits=list(map(unpack_qubit, qubits)))
[docs] def num_args(self) -> int:
"""
:return: The number of qubit arguments the gate takes.
"""
rows = len(self.matrix)
return int(np.log2(rows))
[docs]class DefPermutationGate(DefGate):
def __init__(self, name: str, permutation: Union[List[Union[int, np.int_]], np.ndarray]):
if not isinstance(name, str):
raise TypeError("Gate name must be a string")
if name in RESERVED_WORDS:
raise ValueError(f"Cannot use {name} for a gate name since it's a reserved word")
if not isinstance(permutation, (list, np.ndarray)):
raise ValueError(f"Permutation must be a list or NumPy array, got value of type {type(permutation)}")
permutation = np.asarray(permutation)
ndim = permutation.ndim
if 1 != ndim:
raise ValueError(f"Permutation must have dimension 1, got {permutation.ndim}")
elts = permutation.shape[0]
if 0 != elts & (elts - 1):
raise ValueError(f"Dimension of permutation must be a power of 2, got {elts}")
self.name = name
self.permutation = permutation
self.parameters = None
[docs] def out(self) -> str:
body = ", ".join([str(p) for p in self.permutation])
return f"DEFGATE {self.name} AS PERMUTATION:\n {body}"
[docs] def num_args(self) -> int:
"""
:return: The number of qubit arguments the gate takes.
"""
return int(np.log2(len(self.permutation)))
[docs]class DefGateByPaulis(DefGate):
"""
Records a gate definition as the exponentiation of a PauliSum.
"""
def __init__(
self,
gate_name: str,
parameters: List[Parameter],
arguments: List[QubitDesignator],
body: "PauliSum",
):
if not isinstance(gate_name, str):
raise TypeError("Gate name must be a string")
if gate_name in RESERVED_WORDS:
raise ValueError(f"Cannot use {gate_name} for a gate name since it's a reserved word")
self.name = gate_name
self.parameters = parameters
self.arguments = arguments
self.body = body
[docs] def out(self) -> str:
out = f"DEFGATE {self.name}"
if self.parameters is not None:
out += f"({', '.join(map(str, self.parameters))}) "
out += f"{' '.join(map(str, self.arguments))} AS PAULI-SUM:\n"
for term in self.body:
args = term._ops.keys()
word = term._ops.values()
out += f" {''.join(word)}({term.coefficient}) " + " ".join(map(str, args)) + "\n"
return out
[docs] def num_args(self) -> int:
return len(self.arguments)
[docs]class JumpTarget(AbstractInstruction):
"""
Representation of a target that can be jumped to.
"""
def __init__(self, label: Union[Label, LabelPlaceholder]):
if not isinstance(label, (Label, LabelPlaceholder)):
raise TypeError("label must be a Label")
self.label = label
def __repr__(self) -> str:
return "<JumpTarget {0}>".format(str(self.label))
[docs] def out(self) -> str:
return "LABEL {0}".format(str(self.label))
[docs]class JumpConditional(AbstractInstruction):
"""
Abstract representation of an conditional jump instruction.
"""
op: ClassVar[str]
def __init__(self, target: Union[Label, LabelPlaceholder], condition: MemoryReference):
if not isinstance(target, (Label, LabelPlaceholder)):
raise TypeError("target should be a Label")
if not isinstance(condition, MemoryReference):
raise TypeError("condition should be an MemoryReference")
self.target = target
self.condition = condition
[docs] def out(self) -> str:
return "%s %s %s" % (self.op, self.target, self.condition)
[docs]class JumpWhen(JumpConditional):
"""
The JUMP-WHEN instruction.
"""
op = "JUMP-WHEN"
[docs]class JumpUnless(JumpConditional):
"""
The JUMP-UNLESS instruction.
"""
op = "JUMP-UNLESS"
[docs]class SimpleInstruction(AbstractInstruction):
"""
Abstract class for simple instructions with no arguments.
"""
op: ClassVar[str]
[docs] def out(self) -> str:
return self.op
[docs]class Halt(SimpleInstruction):
"""
The HALT instruction.
"""
op = "HALT"
[docs]class Wait(SimpleInstruction):
"""
The WAIT instruction.
"""
op = "WAIT"
[docs]class Reset(SimpleInstruction):
"""
The RESET instruction.
"""
op = "RESET"
[docs]class Nop(SimpleInstruction):
"""
The NOP instruction.
"""
op = "NOP"
[docs]class UnaryClassicalInstruction(AbstractInstruction):
"""
The abstract class for unary classical instructions.
"""
op: ClassVar[str]
def __init__(self, target: MemoryReference):
if not isinstance(target, MemoryReference):
raise TypeError("target operand should be an MemoryReference")
self.target = target
[docs] def out(self) -> str:
return "%s %s" % (self.op, self.target)
[docs]class ClassicalNeg(UnaryClassicalInstruction):
"""
The NEG instruction.
"""
op = "NEG"
[docs]class ClassicalNot(UnaryClassicalInstruction):
"""
The NOT instruction.
"""
op = "NOT"
[docs]class LogicalBinaryOp(AbstractInstruction):
"""
The abstract class for binary logical classical instructions.
"""
op: ClassVar[str]
def __init__(self, left: MemoryReference, right: Union[MemoryReference, int]):
if not isinstance(left, MemoryReference):
raise TypeError("left operand should be an MemoryReference")
if not isinstance(right, MemoryReference) and not isinstance(right, int):
raise TypeError("right operand should be an MemoryReference or an Int")
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s" % (self.op, self.left, self.right)
[docs]class ClassicalAnd(LogicalBinaryOp):
"""
WARNING: The operand order for ClassicalAnd has changed. In pyQuil versions <= 1.9, AND had
signature
AND %source %target
Now, AND has signature
AND %target %source
"""
op = "AND"
[docs]class ClassicalInclusiveOr(LogicalBinaryOp):
"""
The IOR instruction.
"""
op = "IOR"
[docs]class ClassicalExclusiveOr(LogicalBinaryOp):
"""
The XOR instruction.
"""
op = "XOR"
[docs]class ArithmeticBinaryOp(AbstractInstruction):
"""
The abstract class for binary arithmetic classical instructions.
"""
op: ClassVar[str]
def __init__(self, left: MemoryReference, right: Union[MemoryReference, int, float]):
if not isinstance(left, MemoryReference):
raise TypeError("left operand should be an MemoryReference")
if not isinstance(right, MemoryReference) and not isinstance(right, int) and not isinstance(right, float):
raise TypeError("right operand should be an MemoryReference or a numeric literal")
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s" % (self.op, self.left, self.right)
[docs]class ClassicalAdd(ArithmeticBinaryOp):
"""
The ADD instruction.
"""
op = "ADD"
[docs]class ClassicalSub(ArithmeticBinaryOp):
"""
The SUB instruction.
"""
op = "SUB"
[docs]class ClassicalMul(ArithmeticBinaryOp):
"""
The MUL instruction.
"""
op = "MUL"
[docs]class ClassicalDiv(ArithmeticBinaryOp):
"""
The DIV instruction.
"""
op = "DIV"
[docs]class ClassicalMove(AbstractInstruction):
"""
The MOVE instruction.
WARNING: In pyQuil 2.0, the order of operands is as MOVE <target> <source>.
In pyQuil 1.9, the order of operands was MOVE <source> <target>.
These have reversed.
"""
op = "MOVE"
def __init__(self, left: MemoryReference, right: Union[MemoryReference, int, float]):
if not isinstance(left, MemoryReference):
raise TypeError(
"Left operand of MOVE should be an MemoryReference. "
"Note that the order of the operands in pyQuil 2.0 has reversed from "
"the order of pyQuil 1.9 ."
)
if not isinstance(right, MemoryReference) and not isinstance(right, int) and not isinstance(right, float):
raise TypeError("Right operand of MOVE should be an MemoryReference or a numeric literal")
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s" % (self.op, self.left, self.right)
[docs]class ClassicalExchange(AbstractInstruction):
"""
The EXCHANGE instruction.
"""
op = "EXCHANGE"
def __init__(self, left: MemoryReference, right: MemoryReference):
if not isinstance(left, MemoryReference):
raise TypeError("left operand should be an MemoryReference")
if not isinstance(right, MemoryReference):
raise TypeError("right operand should be an MemoryReference")
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s" % (self.op, self.left, self.right)
[docs]class ClassicalConvert(AbstractInstruction):
"""
The CONVERT instruction.
"""
op = "CONVERT"
def __init__(self, left: MemoryReference, right: MemoryReference):
if not isinstance(left, MemoryReference):
raise TypeError("left operand should be an MemoryReference")
if not isinstance(right, MemoryReference):
raise TypeError("right operand should be an MemoryReference")
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s" % (self.op, self.left, self.right)
[docs]class ClassicalLoad(AbstractInstruction):
"""
The LOAD instruction.
"""
op = "LOAD"
def __init__(self, target: MemoryReference, left: str, right: MemoryReference):
if not isinstance(target, MemoryReference):
raise TypeError("target operand should be an MemoryReference")
if not isinstance(right, MemoryReference):
raise TypeError("right operand should be an MemoryReference")
self.target = target
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s %s" % (self.op, self.target, self.left, self.right)
[docs]class ClassicalStore(AbstractInstruction):
"""
The STORE instruction.
"""
op = "STORE"
def __init__(self, target: str, left: MemoryReference, right: Union[MemoryReference, int, float]):
if not isinstance(left, MemoryReference):
raise TypeError("left operand should be an MemoryReference")
if not (isinstance(right, MemoryReference) or isinstance(right, int) or isinstance(right, float)):
raise TypeError("right operand should be an MemoryReference or an int or float.")
self.target = target
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s %s" % (self.op, self.target, self.left, self.right)
[docs]class ClassicalComparison(AbstractInstruction):
"""
Abstract class for ternary comparison instructions.
"""
op: ClassVar[str]
def __init__(
self,
target: MemoryReference,
left: MemoryReference,
right: Union[MemoryReference, int, float],
):
if not isinstance(target, MemoryReference):
raise TypeError("target operand should be an MemoryReference")
if not isinstance(left, MemoryReference):
raise TypeError("left operand should be an MemoryReference")
if not (isinstance(right, MemoryReference) or isinstance(right, int) or isinstance(right, float)):
raise TypeError("right operand should be an MemoryReference or an int or float.")
self.target = target
self.left = left
self.right = right
[docs] def out(self) -> str:
return "%s %s %s %s" % (self.op, self.target, self.left, self.right)
[docs]class ClassicalEqual(ClassicalComparison):
"""
The EQ comparison instruction.
"""
op = "EQ"
[docs]class ClassicalLessThan(ClassicalComparison):
"""
The LT comparison instruction.
"""
op = "LT"
[docs]class ClassicalLessEqual(ClassicalComparison):
"""
The LE comparison instruction.
"""
op = "LE"
[docs]class ClassicalGreaterThan(ClassicalComparison):
"""
The GT comparison instruction.
"""
op = "GT"
[docs]class ClassicalGreaterEqual(ClassicalComparison):
"""
The GE comparison instruction.
"""
op = "GE"
[docs]class Jump(AbstractInstruction):
"""
Representation of an unconditional jump instruction (JUMP).
"""
def __init__(self, target: Union[Label, LabelPlaceholder]):
if not isinstance(target, (Label, LabelPlaceholder)):
raise TypeError("target should be a Label: {target}")
self.target = target
[docs] def out(self) -> str:
return "JUMP %s" % self.target
[docs]class Pragma(AbstractInstruction):
"""
A PRAGMA instruction.
This is printed in QUIL as::
PRAGMA <command> <arg1> <arg2> ... <argn> "<freeform_string>"
"""
def __init__(
self,
command: str,
args: Iterable[Union[QubitDesignator, str]] = (),
freeform_string: str = "",
):
if not isinstance(command, str):
raise TypeError(f"Pragma's require an identifier: {command}")
if not isinstance(args, collections.abc.Iterable):
raise TypeError(f"Pragma arguments must be an Iterable: {args}")
for a in args:
if not (
isinstance(a, str) or isinstance(a, int) or isinstance(a, QubitPlaceholder) or isinstance(a, Qubit)
):
raise TypeError(f"Pragma arguments must be strings or integers: {a}")
if not isinstance(freeform_string, str):
raise TypeError(f"The freeform string argument must be a string: {freeform_string}")
self.command = command
self.args = tuple(args)
self.freeform_string = freeform_string
[docs] def out(self) -> str:
ret = "PRAGMA {}".format(self.command)
if self.args:
ret += " {}".format(" ".join(str(a) for a in self.args))
if self.freeform_string:
ret += ' "{}"'.format(self.freeform_string)
return ret
def __repr__(self) -> str:
return "<PRAGMA {}>".format(self.command)
[docs]class Declare(AbstractInstruction):
"""
A DECLARE directive.
This is printed in Quil as::
DECLARE <name> <memory-type> (SHARING <other-name> (OFFSET <amount> <type>)* )?
"""
def __init__(
self,
name: str,
memory_type: str,
memory_size: int = 1,
shared_region: Optional[str] = None,
offsets: Optional[Iterable[Tuple[int, str]]] = None,
):
self.name = name
self.memory_type = memory_type
self.memory_size = memory_size
self.shared_region = shared_region
if offsets is None:
offsets = []
self.offsets = offsets
[docs] def asdict(self) -> Dict[str, Union[Iterable[Tuple[int, str]], Optional[str], int]]:
return {
"name": self.name,
"memory_type": self.memory_type,
"memory_size": self.memory_size,
"shared_region": self.shared_region,
"offsets": self.offsets,
}
[docs] def out(self) -> str:
ret = "DECLARE {} {}[{}]".format(self.name, self.memory_type, self.memory_size)
if self.shared_region:
ret += " SHARING {}".format(self.shared_region)
for offset in self.offsets:
ret += " OFFSET {} {}".format(offset[0], offset[1])
return ret
def __repr__(self) -> str:
return "<DECLARE {}>".format(self.name)
[docs]class RawInstr(AbstractInstruction):
"""
A raw instruction represented as a string.
"""
def __init__(self, instr_str: str):
if not isinstance(instr_str, str):
raise TypeError("Raw instructions require a string.")
self.instr = instr_str
[docs] def out(self) -> str:
return self.instr
def __repr__(self) -> str:
return "<RawInstr {}>".format(self.instr)
[docs]class Pulse(AbstractInstruction):
def __init__(self, frame: Frame, waveform: Waveform, nonblocking: bool = False):
self.frame = frame
self.waveform = waveform
self.nonblocking = nonblocking
[docs] def out(self) -> str:
result = "NONBLOCKING " if self.nonblocking else ""
result += f"PULSE {self.frame} {self.waveform.out()}"
return result
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class SetFrequency(AbstractInstruction):
def __init__(self, frame: Frame, freq: ParameterDesignator):
self.frame = frame
self.freq = freq
[docs] def out(self) -> str:
return f"SET-FREQUENCY {self.frame} {self.freq}"
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class ShiftFrequency(AbstractInstruction):
def __init__(self, frame: Frame, freq: ParameterDesignator):
self.frame = frame
self.freq = freq
[docs] def out(self) -> str:
return f"SHIFT-FREQUENCY {self.frame} {self.freq}"
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class SetPhase(AbstractInstruction):
def __init__(self, frame: Frame, phase: ParameterDesignator):
self.frame = frame
self.phase = phase
[docs] def out(self) -> str:
return f"SET-PHASE {self.frame} {self.phase}"
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class ShiftPhase(AbstractInstruction):
def __init__(self, frame: Frame, phase: ParameterDesignator):
self.frame = frame
self.phase = phase
[docs] def out(self) -> str:
return f"SHIFT-PHASE {self.frame} {self.phase}"
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class SwapPhase(AbstractInstruction):
def __init__(self, frameA: Frame, frameB: Frame):
self.frameA = frameA
self.frameB = frameB
[docs] def out(self) -> str:
return f"SWAP-PHASE {self.frameA} {self.frameB}"
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frameA, indices) | _get_frame_qubits(self.frameB, indices)
[docs]class SetScale(AbstractInstruction):
def __init__(self, frame: Frame, scale: ParameterDesignator):
self.frame = frame
self.scale = scale
[docs] def out(self) -> str:
return f"SET-SCALE {self.frame} {self.scale}"
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class Capture(AbstractInstruction):
def __init__(
self,
frame: Frame,
kernel: Waveform,
memory_region: MemoryReference,
nonblocking: bool = False,
):
self.frame = frame
self.kernel = kernel
self.memory_region = memory_region
self.nonblocking = nonblocking
[docs] def out(self) -> str:
result = "NONBLOCKING " if self.nonblocking else ""
result += f"CAPTURE {self.frame} {self.kernel.out()}"
result += f" {self.memory_region.out()}" if self.memory_region else ""
return result
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class RawCapture(AbstractInstruction):
def __init__(
self,
frame: Frame,
duration: float,
memory_region: MemoryReference,
nonblocking: bool = False,
):
self.frame = frame
self.duration = duration
self.memory_region = memory_region
self.nonblocking = nonblocking
[docs] def out(self) -> str:
result = "NONBLOCKING " if self.nonblocking else ""
result += f"RAW-CAPTURE {self.frame} {self.duration} {self.memory_region.out()}"
return result
[docs] def get_qubits(self, indices: bool = True) -> Set[QubitDesignator]:
return _get_frame_qubits(self.frame, indices)
[docs]class DelayFrames(AbstractInstruction):
def __init__(self, frames: List[Frame], duration: float):
# all frames should be on the same qubits
if len(frames) == 0:
raise ValueError("DELAY expected nonempty list of frames.")
if len(set(tuple(f.qubits) for f in frames)) != 1:
raise ValueError("DELAY with explicit frames requires all frames are on the same qubits.")
self.frames = frames
self.duration = duration
[docs] def out(self) -> str:
qubits = self.frames[0].qubits
ret = "DELAY " + _format_qubits_str(qubits)
for f in self.frames:
ret += f' "{f.name}"'
ret += f" {self.duration}"
return ret
[docs]class DelayQubits(AbstractInstruction):
def __init__(self, qubits: List[Union[Qubit, FormalArgument]], duration: float):
self.qubits = qubits
self.duration = duration
[docs] def out(self) -> str:
return f"DELAY {_format_qubits_str(self.qubits)} {self.duration}"
[docs]class FenceAll(SimpleInstruction):
"""
The FENCE instruction.
"""
op = "FENCE"
[docs]class Fence(AbstractInstruction):
def __init__(self, qubits: List[Union[Qubit, FormalArgument]]):
self.qubits = qubits
[docs] def out(self) -> str:
ret = "FENCE " + _format_qubits_str(self.qubits)
return ret
[docs]class DefCalibration(AbstractInstruction):
def __init__(
self,
name: str,
parameters: List[ParameterDesignator],
qubits: List[Union[Qubit, FormalArgument]],
instrs: List[AbstractInstruction],
):
self.name = name
self.parameters = parameters
self.qubits = qubits
self.instrs = instrs
[docs] def out(self) -> str:
ret = f"DEFCAL {self.name}"
if len(self.parameters) > 0:
ret += _format_params(self.parameters)
ret += " " + _format_qubits_str(self.qubits) + ":\n"
for instr in self.instrs:
ret += f" {instr.out()}\n"
return ret
[docs]class DefMeasureCalibration(AbstractInstruction):
def __init__(
self,
qubit: Union[Qubit, FormalArgument],
memory_reference: Optional[MemoryReference],
instrs: List[AbstractInstruction],
):
self.qubit = qubit
self.memory_reference = memory_reference
self.instrs = instrs
[docs] def out(self) -> str:
ret = f"DEFCAL MEASURE {self.qubit}"
if self.memory_reference is not None:
ret += f" {self.memory_reference}"
ret += ":\n"
for instr in self.instrs:
ret += f" {instr.out()}\n"
return ret
[docs]@dataclass
class DefFrame(AbstractInstruction):
frame: Frame
""" The frame being defined. """
direction: Optional[str] = None
""" The direction of the frame, i.e. 'tx' or 'rx'. """
initial_frequency: Optional[float] = None
""" The initial frequency of the frame. """
hardware_object: Optional[str] = None
""" The name of the hardware object associated to the frame. """
sample_rate: Optional[float] = None
""" The sample rate of the frame [Hz]. """
center_frequency: Optional[float] = None
""" The 'center' frequency of the frame, used for detuning arithmetic. """
[docs] def out(self) -> str:
r = f"DEFFRAME {self.frame.out()}"
options = [
(self.direction, "DIRECTION"),
(self.initial_frequency, "INITIAL-FREQUENCY"),
(self.center_frequency, "CENTER-FREQUENCY"),
(self.hardware_object, "HARDWARE-OBJECT"),
(self.sample_rate, "SAMPLE-RATE"),
]
if any(value for (value, name) in options):
r += ":"
for value, name in options:
if value is None:
continue
else:
r += f"\n {name}: {json.dumps(value)}"
return r + "\n"