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
- The GateLibrary superclass is used to factorize code for gateset-aware
classes (e.g. TypeChecker)
- Semantic classes are removed, absorbed into Gate subclasses
- Move ALL ANTLR parsing classes to parsing folder - this has already
  been done, but only partially
- Gates are specified directly with Python functions and a decorator
- Lay the basis of handling of anonymous gates
- Writer turned into impl pattern
- Rename AST -> IR
- Make test_testinterpreter a proper unit test, before it was testing
  parsing too
- Visitor pattern for the SquirrelIR class
- Use those visitors in e.g. writer and test_interpreter
- Various code fixes
- Use inspect to know Python function signatures
 This makes the gate semantic API nice to use from Python.
  • Loading branch information
pablolh committed Dec 19, 2023
1 parent cca2bfe commit 007aff4
Show file tree
Hide file tree
Showing 38 changed files with 1,342 additions and 1,087 deletions.
16 changes: 4 additions & 12 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,18 @@

::: opensquirrel.circuit_builder

::: opensquirrel.circuit_matrix_calculator

::: opensquirrel.common

::: opensquirrel.default_gates

::: opensquirrel.gates

::: opensquirrel.matrix_expander
::: opensquirrel.gate_library

::: opensquirrel.mckay_decomposer

::: opensquirrel.replacer

::: opensquirrel.squirrel_ast

::: opensquirrel.squirrel_ast_creator

::: opensquirrel.squirrel_error_handler

::: opensquirrel.test_interpreter

::: opensquirrel.type_checker
::: opensquirrel.squirrel_ir

::: opensquirrel.writer
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_gate_aliases
85 changes: 28 additions & 57 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import antlr4
from typing import Callable, Dict

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
from opensquirrel import circuit_matrix_calculator, mckay_decomposer, replacer, writer
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
from opensquirrel.parsing.antlr.squirrel_ir_from_string import squirrel_ir_from_string
from opensquirrel.squirrel_ir import Gate, SquirrelIR


class Circuit:
"""The Circuit class is the only interface to access OpenSquirrel's features.
A Circuit object is constructed from a cQasm3 string, representing a quantum circuit, and a Python dictionary
containing the prototypes and semantic of the allowed quantum gates.
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("version 3.0; qubit[3] q; h q[0]")
>>> c
version 3.0
<BLANKLINE>
Expand All @@ -39,19 +30,20 @@ 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):
def from_string(
cls,
cqasm3_string: str,
gate_set: [Callable[..., Gate]] = default_gate_set,
gate_aliases: Dict[str, Callable[..., Gate]] = default_gate_aliases,
):
"""Create a circuit object from a cQasm3 string. All the gates in the circuit need to be defined in
the `gates` argument.
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(cqasm3_string, gate_set=gate_set, gate_aliases=gate_aliases))

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 circuit_matrix_calculator.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)
58 changes: 43 additions & 15 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,60 @@
import inspect
from typing import Callable, Dict

from opensquirrel.circuit import Circuit
from opensquirrel.default_gates import DefaultGates
from opensquirrel.squirrel_ast import SquirrelAST
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
from opensquirrel.gate_library import GateLibrary
from opensquirrel.squirrel_ir import Comment, Gate, Qubit, SquirrelIR


class CircuitBuilder:
"""A class using the builder pattern to make construction of circuits easy.
class CircuitBuilder(GateLibrary):
"""
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(number_of_qubits=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,
number_of_qubits: int,
gate_set: [Callable[..., Gate]] = default_gate_set,
gate_aliases: Dict[str, Callable[..., Gate]] = default_gate_aliases,
):
GateLibrary.__init__(self, gate_set, gate_aliases)
self.squirrel_ir = SquirrelIR(
number_of_qubits=number_of_qubits, qubit_register_name=self._default_qubit_register_name
)

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

def addThisGate(*args):
self.squirrelAST.addGate(attr, *args)
def add_this_gate(*args):
generator_f = GateLibrary.get_gate_f(self, 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)
30 changes: 30 additions & 0 deletions opensquirrel/circuit_matrix_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import numpy as np

from opensquirrel.squirrel_ir import Comment, Gate, SquirrelIR, SquirrelIRVisitor
from opensquirrel.utils.matrix_expander import get_matrix


class _CircuitMatrixCalculator(SquirrelIRVisitor):
def __init__(self, number_of_qubits):
self.number_of_qubits = number_of_qubits
self.matrix = np.eye(1 << self.number_of_qubits, dtype=np.complex128)

def visit_gate(self, gate: Gate):
big_matrix = get_matrix(gate, number_of_qubits=self.number_of_qubits)
self.matrix = big_matrix @ self.matrix

def visit_comment(self, comment: Comment):
pass


def get_circuit_matrix(squirrel_ir: SquirrelIR):
"""
Compute the Numpy unitary matrix corresponding to the circuit.
The size of this matrix grows exponentially with the number of qubits.
"""

impl = _CircuitMatrixCalculator(squirrel_ir.number_of_qubits)

squirrel_ir.accept(impl)

return impl.matrix
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 007aff4

Please sign in to comment.