Skip to content

Commit

Permalink
Fix types in the AST
Browse files Browse the repository at this point in the history
- Implement ControlledSemantic to be able to express CNOT as a
  controlled X, for instance
- Separate validation of qubit ranges from type-checking in a separate
  ANTLR tree visitor
- snake_case instead of camelCase for variable names
- Add docstrings to parsing classes
- Writer turned into impl pattern
- Visitor pattern for the SquirrelAST class
- Use those visitors in e.g. writer and test_interpreter
- Various code fixes
- Rename AST -> IR
- Use inspect to know Python function signatures
 This makes the gate semantic API nice to use from Python.
  • Loading branch information
pablolh committed Dec 13, 2023
1 parent 280aa4a commit d65f685
Show file tree
Hide file tree
Showing 28 changed files with 987 additions and 864 deletions.
4 changes: 2 additions & 2 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

::: opensquirrel.replacer

::: opensquirrel.squirrel_ast
::: opensquirrel.squirrel_ir

::: opensquirrel.squirrel_ast_creator
::: opensquirrel.squirrel_ir_creator

::: opensquirrel.squirrel_error_handler

Expand Down
2 changes: 1 addition & 1 deletion opensquirrel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from opensquirrel.circuit import Circuit
from opensquirrel.circuit_builder import CircuitBuilder
from opensquirrel.default_gates import DefaultGates
from opensquirrel.default_gates import default_aliases
75 changes: 23 additions & 52 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import antlr4
import numpy as np

from opensquirrel.default_gates import DefaultGates # For the doctest.
from opensquirrel.mckay_decomposer import McKayDecomposer
from opensquirrel.parsing.antlr.generated import CQasm3Lexer, CQasm3Parser
from opensquirrel.parsing.antlr.squirrel_ast_creator import SquirrelASTCreator
from opensquirrel.replacer import Replacer
from opensquirrel.squirrel_ast import SquirrelAST
from opensquirrel.squirrel_error_handler import SquirrelErrorHandler
from opensquirrel.test_interpreter import TestInterpreter
from opensquirrel.type_checker import TypeChecker
from opensquirrel.writer import Writer
import opensquirrel.mckay_decomposer as mckay_decomposer
import opensquirrel.replacer as replacer
import opensquirrel.test_interpreter as test_interpreter
import opensquirrel.writer as writer
from opensquirrel.default_gates import default_aliases # For the doctest.
from opensquirrel.parsing.antlr.squirrel_ir_from_string import squirrel_ir_from_string
from opensquirrel.squirrel_ir import SquirrelIR


class Circuit:
Expand All @@ -21,7 +17,7 @@ class Circuit:
A default set of gates is exposed as `opensquirrel.default_gates` but it can be replaced and extended.
Examples:
>>> c = Circuit.from_string(DefaultGates, "version 3.0; qubit[3] q; h q[0]")
>>> c = Circuit.from_string(default_aliases, "version 3.0; qubit[3] q; h q[0]")
>>> c
version 3.0
<BLANKLINE>
Expand All @@ -39,16 +35,12 @@ class Circuit:
rz q[0], 1.5707963
x90 q[0]
<BLANKLINE>
Args:
squirrelAST: OpenSquirrel internal AST.
"""

def __init__(self, squirrelAST: SquirrelAST):
"""Create a circuit object from a SquirrelAST object."""
def __init__(self, squirrel_ir: SquirrelIR):
"""Create a circuit object from a SquirrelIR object."""

self.gates = squirrelAST.gates
self.squirrel_ast = squirrelAST
self.squirrel_ir = squirrel_ir

@classmethod
def from_string(cls, gates: dict, cqasm3_string: str):
Expand All @@ -61,31 +53,15 @@ def from_string(cls, gates: dict, cqasm3_string: str):
* for example of `gates` dictionary, please look at TestGates.py
"""

input_stream = antlr4.InputStream(cqasm3_string)

lexer = CQasm3Lexer.CQasm3Lexer(input_stream)

stream = antlr4.CommonTokenStream(lexer)

parser = CQasm3Parser.CQasm3Parser(stream)

parser.removeErrorListeners()
parser.addErrorListener(SquirrelErrorHandler())

tree = parser.prog()

typeChecker = TypeChecker(gates)
typeChecker.visit(tree) # FIXME: return error instead of throwing?

squirrelASTCreator = SquirrelASTCreator(gates)

return Circuit(squirrelASTCreator.visit(tree))
return Circuit(squirrel_ir_from_string(gates, cqasm3_string))

def getNumberOfQubits(self) -> int:
return self.squirrel_ast.nQubits
@property
def number_of_qubits(self) -> int:
return self.squirrel_ir.number_of_qubits

def getQubitRegisterName(self) -> str:
return self.squirrel_ast.qubitRegisterName
@property
def qubit_register_name(self) -> str:
return self.squirrel_ir.qubit_register_name

def decompose_mckay(self):
"""Perform gate fusion on all one-qubit gates and decompose them in the McKay style.
Expand All @@ -98,19 +74,16 @@ def decompose_mckay(self):
for the input and output circuit - those outputs should be equivalent modulo global phase.
"""

mcKayDecomposer = McKayDecomposer(self.gates)
self.squirrel_ast = mcKayDecomposer.process(self.squirrel_ast)
self.squirrel_ir = mckay_decomposer.decompose_mckay(self.squirrel_ir) # FIXME: inplace

def replace(self, gateName: str, f):
def replace(self, gate_name: str, f):
"""Manually replace occurrences of a given gate with a list of gates.
* this can be called decomposition - but it's the least fancy version of it
* function parameter gives the decomposition based on parameters of original gate
"""

assert gateName in self.gates, f"Cannot replace unknown gate `{gateName}`"
replacer = Replacer(self.gates) # FIXME: only one instance of this is needed.
self.squirrel_ast = replacer.process(self.squirrel_ast, gateName, f)
replacer.replace(self.squirrel_ir, gate_name, f)

def test_get_circuit_matrix(self) -> np.ndarray:
"""Get the (large) unitary matrix corresponding to the circuit.
Expand All @@ -120,14 +93,12 @@ def test_get_circuit_matrix(self) -> np.ndarray:
* result is stored as a numpy array of complex numbers
"""

interpreter = TestInterpreter(self.gates)
return interpreter.process(self.squirrel_ast)
return test_interpreter.get_circuit_matrix(self.squirrel_ir)

def __repr__(self) -> str:
"""Write the circuit to a cQasm3 string.
* comments are removed
"""

writer = Writer(self.gates)
return writer.process(self.squirrel_ast)
return writer.squirrel_ir_to_string(self.squirrel_ir)
50 changes: 36 additions & 14 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,54 @@
import inspect

from opensquirrel.circuit import Circuit
from opensquirrel.default_gates import DefaultGates
from opensquirrel.squirrel_ast import SquirrelAST
from opensquirrel.default_gates import default_aliases
from opensquirrel.squirrel_ir import Comment, Qubit, SquirrelIR


class CircuitBuilder:
"""A class using the builder pattern to make construction of circuits easy.
"""
A class using the builder pattern to make construction of circuits easy from Python.
Adds corresponding gate when a method is called. Checks gates are known and called with the right arguments.
Mainly here to allow for Qiskit-style circuit construction:
>>> myCircuit = CircuitBuilder(DefaultGates, 3).h(0).cnot(0, 1).cnot(0, 2).to_circuit()
>>> CircuitBuilder(default_aliases, 3).h(Qubit(0)).cnot(Qubit(0), Qubit(1)).cnot(Qubit(0), Qubit(2)).to_circuit()
version 3.0
<BLANKLINE>
qubit[3] q
<BLANKLINE>
h q[0]
cnot q[0], q[1]
cnot q[0], q[2]
<BLANKLINE>
"""

__default_qubit_register_name = "q"
_default_qubit_register_name = "q"

def __init__(self, gates: dict, numberOfQubits: int):
self.squirrelAST = SquirrelAST(gates, numberOfQubits, self.__default_qubit_register_name)
def __init__(self, aliases: dict, number_of_qubits: int):
self.aliases = aliases
self.squirrel_ir = SquirrelIR(number_of_qubits, self._default_qubit_register_name)

def __getattr__(self, attr):
def addComment(commentString: str):
self.squirrelAST.add_comment(commentString)
def add_comment(comment_string: str):
self.squirrel_ir.add_comment(Comment(comment_string))
return self

def addThisGate(*args):
self.squirrelAST.add_gate(attr, *args)
def add_this_gate(*args):
if attr not in self.aliases:
raise Exception(f"Unknown gate `{attr}`")

generator_f = self.aliases[attr]

for i, par in enumerate(inspect.signature(generator_f).parameters.values()):
if not isinstance(args[i], par.annotation):
raise TypeError(
f"Wrong argument type for gate `{attr}`, got {type(args[i])} but expected {par.annotation}"
)

self.squirrel_ir.add_gate(generator_f(*args))
return self

return addComment if attr == "comment" else addThisGate
return add_comment if attr == "comment" else add_this_gate

def to_circuit(self) -> Circuit:
return Circuit(self.squirrelAST)
return Circuit(self.squirrel_ir)
29 changes: 8 additions & 21 deletions opensquirrel/common.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
import cmath
import math
from enum import Enum
from typing import Tuple

import numpy as np

ATOL = 0.0000001


class ExprType(Enum):
QUBITREFS = 1
FLOAT = 2
INT = 3


class ArgType(Enum):
QUBIT = 0
FLOAT = 1
INT = 2


def exprTypeToArgType(t: ExprType) -> ArgType:
if t == ExprType.QUBITREFS:
return ArgType.QUBIT
if t == ExprType.FLOAT:
return ArgType.FLOAT
if t == ExprType.INT:
return ArgType.INT
def normalize_angle(x: float) -> float:
t = x - 2 * math.pi * (x // (2 * math.pi) + 1)
if t < -math.pi + ATOL:
t += 2 * math.pi
elif t > math.pi:
t -= 2 * math.pi
return t


X = np.array([[0, 1], [1, 0]])
Y = np.array([[0, -1j], [1j, 0]])
Z = np.array([[1, 0], [0, -1]])


def Can1(axis: Tuple[float, float, float], angle: float, phase: float = 0) -> np.ndarray:
def can1(axis: Tuple[float, float, float], angle: float, phase: float = 0) -> np.ndarray:
nx, ny, nz = axis
norm = math.sqrt(nx**2 + ny**2 + nz**2)
assert norm > 0.00000001
Expand Down
Loading

0 comments on commit d65f685

Please sign in to comment.