From e26a1802b00496e54cda7238f2c84cc20c61e90b Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Thu, 20 May 2021 16:55:36 -0400 Subject: [PATCH 01/71] add ir --- python/ir/__init__.py | 15 + python/ir/_version.py | 17 + python/ir/interfaces/__init__.py | 15 + python/ir/interfaces/strawberryfields_io.py | 153 ++++++++ python/ir/ir.lark | 96 +++++ python/ir/parser.py | 254 ++++++++++++ python/ir/program.py | 263 +++++++++++++ python/ir/tests/test_integration.py | 93 +++++ python/ir/tests/test_io.py | 195 +++++++++ python/ir/tests/test_program.py | 413 ++++++++++++++++++++ python/ir/tests/test_utils.py | 71 ++++ python/ir/usage_guide.md | 205 ++++++++++ python/ir/utils.py | 146 +++++++ 13 files changed, 1936 insertions(+) create mode 100644 python/ir/__init__.py create mode 100644 python/ir/_version.py create mode 100644 python/ir/interfaces/__init__.py create mode 100644 python/ir/interfaces/strawberryfields_io.py create mode 100644 python/ir/ir.lark create mode 100644 python/ir/parser.py create mode 100644 python/ir/program.py create mode 100644 python/ir/tests/test_integration.py create mode 100644 python/ir/tests/test_io.py create mode 100644 python/ir/tests/test_program.py create mode 100644 python/ir/tests/test_utils.py create mode 100644 python/ir/usage_guide.md create mode 100644 python/ir/utils.py diff --git a/python/ir/__init__.py b/python/ir/__init__.py new file mode 100644 index 00000000..6d3a7344 --- /dev/null +++ b/python/ir/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +from .parser import IRTransformer, ir_parser diff --git a/python/ir/_version.py b/python/ir/_version.py new file mode 100644 index 00000000..4d81295d --- /dev/null +++ b/python/ir/_version.py @@ -0,0 +1,17 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +"""The current version number. Uses semantic versioning (https://semver.org)""" + +__version__ = "0.1.0" diff --git a/python/ir/interfaces/__init__.py b/python/ir/interfaces/__init__.py new file mode 100644 index 00000000..bc161c39 --- /dev/null +++ b/python/ir/interfaces/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +from .strawberryfields_io import to_program, to_xir, find_number_of_modes diff --git a/python/ir/interfaces/strawberryfields_io.py b/python/ir/interfaces/strawberryfields_io.py new file mode 100644 index 00000000..50abaaab --- /dev/null +++ b/python/ir/interfaces/strawberryfields_io.py @@ -0,0 +1,153 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +from decimal import Decimal +import strawberryfields as sf +from strawberryfields import ops +from ir.program import IRProgram, Statement, GateDeclaration, OutputDeclaration + +def find_number_of_modes(xir): + """Helper function to find the number of modes in an XIR program""" + wires = set() + for stmt in xir.statements: + wires.add(stmt.wires) + + return len(wires) + + +def to_program(xir, **kwargs): + """Convert an IR Program to a Strawberry Fields Program. + + Args: + xir (IRProgram): the input XIR program object + + Kwargs: + name (str): name of the resulting Strawberry Fields program + target (str): Name of the target hardware device. Default is "xir". + shots (int): number of times the program measurement evaluation is repeated + cutoff_dim (int): the Fock basis truncation size + + Returns: + Program: corresponding Strawberry Fields program + """ + num_of_modes = find_number_of_modes(xir) + name = kwargs.get("name", "xir") + if num_of_modes == 0: + raise ValueError( + "The XIR program is empty and cannot be transformed into a Strawberry Fields program" + ) + prog = sf.Program(num_of_modes, name=name) + + # append the quantum operations + with prog.context as q: + for op in xir.statements: + # check if operation name is in the list of + # defined StrawberryFields operations. + # This is used by checking against the ops.py __all__ + # module attribute, which contains the names + # of all defined quantum operations + if op.name in ops.__all__: + # get the quantum operation from the sf.ops module + gate = getattr(ops, op.name) + else: + raise NameError(f"Quantum operation {op.name!r} not defined!") + + # create the list of regrefs + regrefs = [q[i] for i in op.wires] + + if op.params != []: + # convert symbolic expressions to symbolic expressions containing the corresponding + # MeasuredParameter and FreeParameter instances. + if isinstance(op.params, dict): + vals = sf.parameters.par_convert(op.params.values(), prog) + params = dict(zip(op.params.keys(), vals)) + gate(**params) | regrefs # pylint:disable=expression-not-assigned + else: + for i, p in enumerate(op.params): + if isinstance(p, Decimal): + op.params[i] = float(p) + params = sf.parameters.par_convert(op.params, prog) + gate(*params) | regrefs # pylint:disable=expression-not-assigned + else: + gate | regrefs # pylint:disable=expression-not-assigned,pointless-statement + + prog._target = kwargs.get("target") + + if kwargs.get("shots") is not None: + prog.run_options["shots"] = kwargs.get("shots") + + if kwargs.get("cutoff_dim") is not None: + prog.backend_options["cutoff_dim"] = kwargs.get("cutoff_dim") + + return prog + + +def to_xir(prog, **kwargs): + """Convert a Strawberry Fields Program to an IR Program. + + Args: + prog (Program): the Strawberry Fields program + + Kwargs: + add_decl (bool): Whether gate and output declarations should be added to + the IR program. Default is False. + version (str): Version number for the program. Default is 0.1.0. + + Returns: + IRProgram + """ + version = kwargs.get("version", "0.1.0") + xir = IRProgram(version=version) + + # fill in the quantum circuit + for cmd in prog.circuit: + + name = cmd.op.__class__.__name__ + wires = tuple(i.ind for i in cmd.reg) + + if "Measure" in name: + if kwargs.get("add_decl", False): + output_decl = OutputDeclaration(name) + xir._declarations["output"].append(output_decl) + + params = dict() + # special case to take into account 'select' keyword argument + if cmd.op.select is not None: + params["select"] = cmd.op.select + + if cmd.op.p: + # argument is quadrature phase + params["phi"] = cmd.op.p[0] + + if name == "MeasureFock": + # special case to take into account 'dark_counts' keyword argument + if cmd.op.dark_counts is not None: + params["dark_counts"] = cmd.op.dark_counts + else: + if kwargs.get("add_decl", False): + if name not in [gdecl.name for gdecl in xir._declarations["gate"]]: + gate_decl = GateDeclaration(name, len(cmd.op.p), len(wires)) + xir._declarations["gate"].append(gate_decl) + + params = [] + for a in cmd.op.p: + if sf.parameters.par_is_symbolic(a): + # SymPy object, convert to string + a = str(a) + params.append(a) + + op = Statement(name, params, wires) + xir._statements.append(op) + + return xir diff --git a/python/ir/ir.lark b/python/ir/ir.lark new file mode 100644 index 00000000..bac3baa2 --- /dev/null +++ b/python/ir/ir.lark @@ -0,0 +1,96 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// 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. + +// IR grammar +program: (include | declarations | circuit)* +include: "use" name ";" + +// the main circuit +circuit: statement+ +statement: gatestmt | gate_def | operator_def +gatestmt: name (params | options_dict) "|" wires ";" +opstmt: expr "," obs_group";" + +gate_def: _GATE name params wires? ":" gatestmt+ _END ";" +operator_def: _OP name params wires? ":" opstmt+ _END ";" + +options_dict: "(" option ("," option)* ")" +option: name ":" val + +obs_group: obs "[" uint "]" ("@" obs "[" uint "]")* +?obs: name + +// list-types +wires : "[" (uint | range | name) ("," (uint | range | name))* "]" +params: ["(" val ("," val)* ")"] // always optional (easier to parse) + +// value-types +?val : bool | expr +float : SIGNED_FLOAT +int : SIGNED_INT +uint : INT + +name: CNAME +bool: TRUE_ | FALSE_ +range: INT ".." INT + +?expr: prodexpr + | expr "+" prodexpr -> add + | expr "-" prodexpr -> sub + +?prodexpr: atom + | prodexpr "*" atom -> prod + | prodexpr "/" atom -> div + +?atom: int + | float + | name -> var + | "-" (name | "(" expr ")" | name "(" expr ")") -> neg + | "(" expr ")" + | name "(" expr ")" -> mathop + | PI + +// reserved keywords +PI: "pi" + +_GATE: "gate" +_FUNC: "func" +_OP: "operator" +_OUT: "output" + +_END: "end" + +TRUE_: "true" +FALSE_: "false" + +// declarations +?declarations: (gate_decl | operator_decl | func_decl | output_decl) + +gate_decl: _GATE name "," uint "," uint ";" +operator_decl: _OP name "," uint "," uint ";" +func_decl: _FUNC name "," uint ";" +output_decl: _OUT name ";" + +// std imports from common.lark +%import common.ESCAPED_STRING +%import common.SIGNED_INT +%import common.INT +%import common.SIGNED_FLOAT +%import common.CPP_COMMENT +%import common.CNAME +%import common.WS + +// ignore whitespace and comments +%ignore WS +%ignore CPP_COMMENT diff --git a/python/ir/parser.py b/python/ir/parser.py new file mode 100644 index 00000000..a38ad41e --- /dev/null +++ b/python/ir/parser.py @@ -0,0 +1,254 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +"""This module contains the IRTransformer and ir_parser""" +from decimal import Decimal +from pathlib import Path +from lark import Lark, Transformer + +from .program import ( + IRProgram, + Statement, + OperatorStmt, + GateDeclaration, + OperatorDeclaration, + FuncDeclaration, + OutputDeclaration, +) +from .utils import beautify_math, check_wires + + +p = Path(__file__).parent / "ir.lark" +with p.open("r") as _f: + ir_grammar = _f.read() + +ir_parser = Lark(ir_grammar, start="program", parser='lalr') + + +class IRTransformer(Transformer): + """Transformer for processing the Lark parse tree. + + Transformers visit each node of the tree, and run the appropriate method on it according to the + node's data. All method names mirror the corresponding symbols from the grammar. + """ + + def __init__(self, *args, **kwargs): + self._program = IRProgram() + super().__init__(self, *args, **kwargs) + + def program(self, args): + """Root of AST containing: + + * Any include statements (0 or more) + * Any gate, math, or measurement declarations (0 or more) + * The main circuit containing all gate statements (0 or 1) + * The final measurement (0 or 1) + + Returns: + IRProgram: program containing all parsed data + """ + # assert all stmts are handled + assert all(a == None for a in args) + return self._program + + opdecl = list + mathdecl = list + statdecl = list + + def include(self, file_name): + """Includ statements for external files""" + self._program._include.append(file_name[0]) + + def circuit(self, args): + """Main circuit containing all the gate statements. Should be empty after tree has been parsed from the leaves up, and all statemtents been passed to the program.""" + # assert all stmts are handled + assert all(a == None for a in args) + + ############### + # basic types + ############### + + def int(self, n): + """Signed integers""" + return int(n[0]) + + def uint(self, n): + """Unsigned integers""" + return int(n[0]) + + def float(self, d): + """Floating point numbers""" + return Decimal(d[0]) + + def bool(self, b): + """Boolean expressions""" + return bool(b[0]) + + option = tuple + options_dict = dict + wires = tuple + params = list + FALSE_ = lambda self, _: True + TRUE_ = lambda self, _: False + + ############################# + # variables and expressions + ############################# + + def var(self, v): + """Expressions that are strings are considered to be variables. Can be + substituted by values at a later stage.""" + self._program._variables.add(v[0]) + return str(v[0]) + + def range(self, args): + """Range between two signed integers.""" + return range(int(args[0]), int(args[1])) + + def name(self, n): + """Name of variable, gate, operator, measurement type, option, external + file, observable, wire, mathematical operation, etc.""" + return str(n[0]) + + def expr(self, args): + """Catch-all for expressions.""" + if len(args) == 1: + return args[0] + return "".join([str(s) for s in args]) + + def operator_def(self, args): + """Operator definition. Starts with keyword 'operator'""" + name = args[0] + if isinstance(args[2], tuple): + params = args[1] + wires = args[2] + stmts = args[3:] + else: + params = args[1] + wires = () + stmts = args[2:] + + if len(wires) > 0: + check_wires(wires, stmts) + self._program.add_operator(name, params, wires, stmts) + + def gate_def(self, args): + """Gate definition. Starts with keyword 'gate'""" + name = args[0] + if isinstance(args[2], tuple): + params = args[1] + wires = args[2] + else: + params = args[1] + wires = () + stmts = args[3:] + + if len(wires) > 0: + check_wires(wires, stmts) + self._program.add_gate(name, params, wires, stmts) + + def statement(self, args): + """Any statement that is part of the circuit.""" + if args[0] is not None: + self._program._statements.append(args[0]) + + def gatestmt(self, args): + """Gate statements that are defined directly in the circuit or inside + a gate declaration.""" + name = args[0] + if isinstance(args[1], list): + params = beautify_math(args[1]) + wires = args[2] + elif isinstance(args[1], dict): + params = args[1] + wires = args[2] + else: + params = [] + wires = args[1] + + return Statement(name, params, wires) + + def opstmt(self, args): + """Operator statements defined inside an operator declaration + + Returns: + OperatorStmt: object containing statement data + """ + pref = beautify_math([args[0]])[0] + term = args[1] + return OperatorStmt(pref, term) + + def obs_group(self, args): + """Group of observables used to define an Operator statement + + Returns: + list[tuple]: each observable with corresponding wires as tuples + """ + return [(args[i], args[i + 1]) for i in range(0, len(args) - 1, 2)] + + ############### + # declarations + ############### + + def gate_decl(self, args): + decl = GateDeclaration(*args) + self._program._declarations["gate"].append(decl) + + def operator_decl(self, args): + decl = OperatorDeclaration(*args) + self._program._declarations["operator"].append(decl) + + def func_decl(self, args): + decl = FuncDeclaration(*args) + self._program._declarations["func"].append(decl) + + def output_decl(self, args): + decl = OutputDeclaration(*args) + self._program._declarations["output"].append(decl) + + + ######### + # maths + ######### + + def mathop(self, args): + self._program._called_ops.add(args[0]) + return str(args[0]) + "(" + str(args[1]) + ")" + + def add(self, args): + if all(isinstance(a, (int, Decimal)) for a in args): + return args[0] + args[1] + return "(" + " + ".join([str(i) for i in args]) + ")" + + def sub(self, args): + if all(isinstance(a, (int, Decimal)) for a in args): + return args[0] - args[1] + return "(" + " - ".join([str(i) for i in args]) + ")" + + def prod(self, args): + if all(isinstance(a, (int, Decimal)) for a in args): + return args[0] * args[1] + return " * ".join([str(i) for i in args]) + + def div(self, args): + if all(isinstance(a, (int, Decimal)) for a in args): + return Decimal(args[0]) / args[1] + return " / ".join([str(i) for i in args]) + + def neg(self, args): + if isinstance(args[0], (int, Decimal)): + return -args[0] + return "-" + str(args[0]) + + PI = lambda self, _: "PI" diff --git a/python/ir/program.py b/python/ir/program.py new file mode 100644 index 00000000..2eb90e9a --- /dev/null +++ b/python/ir/program.py @@ -0,0 +1,263 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +import re +import warnings +from decimal import Decimal +from typing import Union, List, Dict, Set, Tuple, Sequence + +from .utils import strip + +"""This module contains the IRProgram class and classes for the Xanadu IR""" + + +class Statement: + """TODO""" + + def __init__(self, name: str, params: Union[List, Dict], wires: Tuple): + self.name = name + self.params = params + self.wires = wires + + def __str__(self): + if isinstance(self.params, dict): + params = [f"{k}: {v}" for k, v in self.params.items()] + else: + params = [str(p) for p in self.params] + params_str = ", ".join(params) + + wires = ", ".join([str(w) for w in self.wires]) + + if params_str == "": + return f"{self.name} | [{wires}]" + return f"{self.name}({params_str}) | [{wires}]" + + +class OperatorStmt: + """TODO""" + + def __init__(self, pref: Union[Decimal, int, str], terms: List): + self.pref = pref + self.terms = terms + + def __str__(self): + terms = [f"{t[0]}[{t[1]}]" for t in self.terms] + terms_as_string = " @ ".join(terms) + pref = str(self.pref) + + return f"{pref}, {terms_as_string}" + +class Declaration: + """TODO""" + + def __init__(self, name: str): + self.name = name + + def __str__(self): + return f"{self.name}" + + +class OperatorDeclaration(Declaration): + """Quantum operator declarations""" + + def __init__(self, name: str, num_params: int, num_wires: int): + self.num_params = num_params + self.num_wires = num_wires + + super().__init__(name) + + def __str__(self): + return f"{self.name}, {self.num_params}, {self.num_wires}" + + +class GateDeclaration(Declaration): + """Quantum gate declarations""" + + def __init__(self, name: str, num_params: int, num_wires: int): + self.num_params = num_params + self.num_wires = num_wires + + super().__init__(name) + + def __str__(self): + return f"{self.name}, {self.num_params}, {self.num_wires}" + + +class FuncDeclaration(Declaration): + """Function declarations""" + + def __init__(self, name: str, num_params: int): + self.num_params = num_params + + super().__init__(name) + + def __str__(self): + return f"{self.name}, {self.num_params}" + + +class OutputDeclaration(Declaration): + """Output declarations""" + + +class IRProgram: + """TODO""" + + def __init__(self, version: str = "0.1.0"): + if not isinstance(version, str): + raise TypeError(f"Invalid version number input. Must be a string.") + + valid_match = re.match(r"^\d+\.\d+\.\d+$", version) + if valid_match is None or valid_match.string != version: + raise ValueError( + f"Invalid version number {version} input. Must be SemVer style (MAJOR.MINOR.PATCH)." + ) + self._version = version + + self._include = [] + self._statements = [] + + self._declarations = { + "gate": [], + "func": [], + "output": [], + "operator": [] + } + + self._gates = dict() + self._operators = dict() + self._variables = set() + + self._called_ops = set() + + def __repr__(self) -> str: + """TODO""" + return f"" + + @property + def version(self) -> str: + """TODO""" + return self._version + + @property + def include(self) -> List[str]: + """TODO""" + return self._include + + @property + def statements(self) -> List[Statement]: + """TODO""" + return self._statements + + @property + def declarations(self) -> Dict[str, List]: + """TODO""" + return self._declarations + + @property + def gates(self) -> Dict[str, Dict[str, Sequence]]: + """TODO""" + return self._gates + + @property + def operators(self) -> Dict[str, Dict[str, Sequence]]: + """TODO""" + return self._operators + + @property + def variables(self) -> Set[str]: + """TODO""" + return self._variables + + @property + def called_ops(self) -> Set[str]: + """TODO""" + return self._called_ops + + def add_gate(self, name: str, params: List[str], wires: Tuple, statements: List[Statement]): + """TODO""" + if name in self._gates: + warnings.warn("Gate already defined. Replacing old definition with new definiton.") + self._gates[name] = { + "params": params, + "wires": wires, + "statements": statements + } + + def add_operator(self, name: str, params: List[str], wires: Tuple, statements: List[OperatorStmt]): + """TODO""" + if name in self._operators: + warnings.warn("Operator already defined. Replacing old definition with new definiton.") + self._operators[name] = { + "params": params, + "wires": wires, + "statements": statements + } + + def serialize(self, minimize: bool = False) -> str: + """Serialize an IRProgram returning an XIR script + + Args: + minimize (bool): whether to strip whitespace and newlines from file + + Returns: + str: the serialized IR script + """ + res = [] + res.extend([f"use {use};" for use in self._include]) + if len(self._include) != 0: + res.append("") + + res.extend([f"gate {dec};" for dec in self._declarations["gate"]]) + res.extend([f"func {dec};" for dec in self._declarations["func"]]) + res.extend([f"output {dec};" for dec in self._declarations["output"]]) + res.extend([f"operator {dec};" for dec in self._declarations["operator"]]) + if any(len(dec) != 0 for dec in self._declarations.values()): + res.append("") + + for name, gate in self._gates.items(): + if gate["params"] != []: + params = "(" + ", ".join([str(p) for p in gate["params"]]) + ")" + else: + params = "" + if gate["wires"] != (): + wires = "[" + ", ".join([str(w) for w in gate["wires"]]) + "]" + else: + wires = "" + + res.extend([f"gate {name}{params}{wires}:"]) + + res.extend([f" {stmt};" for stmt in gate["statements"]]) + res.append("end;\n") + + for name, op in self._operators.items(): + if op["params"] != []: + params = "(" + ", ".join([str(p) for p in op["params"]]) + ")" + else: + params = "" + if op["wires"] != (): + wires = "[" + ", ".join([str(w) for w in op["wires"]]) + "]" + else: + wires = "" + + res.extend([f"operator {name}{params}{wires}:"]) + + res.extend([f" {stmt};" for stmt in op["statements"]]) + res.append("end;\n") + + res.extend([f"{str(stmt)};" for stmt in self._statements]) + + res_script = "\n".join(res).strip() + if minimize: + return strip(res_script) + return res_script diff --git a/python/ir/tests/test_integration.py b/python/ir/tests/test_integration.py new file mode 100644 index 00000000..c97ca1e1 --- /dev/null +++ b/python/ir/tests/test_integration.py @@ -0,0 +1,93 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +"""Integration tests for the IR""" + +from ..program import IRProgram +import pytest + +from .. import ir_parser, IRTransformer +from ..utils import is_equal + + +def parse_script(circuit: str) -> IRProgram: + """Parse and transform a circuit XIR script and return an IRProgram""" + tree = ir_parser.parse(circuit) + return IRTransformer().transform(tree) + + +photonics_script = """ +gate Sgate, 2, 1; +gate BSgate, 2, 2; +gate Rgate, 1, 1; +output MeasureHomodyne; + +Sgate(0.7, 0) | [1]; +BSgate(0.1, 0.0) | [0, 1]; +Rgate(0.2) | [1]; + +MeasureHomodyne(phi: 3) | [0]; +""" + +photonics_script_no_decl = """ +use xstd; + +Sgate(0.7, 0) | [1]; +BSgate(0.1, 0.0) | [0, 1]; +Rgate(0.2) | [1]; + +MeasureHomodyne(phi: 3) | [0]; +""" + +qubit_script = """ +// this file works with the current parser +use xstd; + +gate h(a)[0, 1]: + rz(-2.3932854391951004) | [0]; + rz(a) | [1]; + // rz(pi / sin(3 * 4 / 2 - 2)) | [a, 2]; +end; + +operator o(a): + 0.7 * sin(a), X[0] @ Z[1]; + -1.6, X[0]; + 2.45, Y[0] @ X[1]; +end; + +g_one(pi) | [0, 1]; +g_two | [2]; +g_three(1, 3.3) | [2]; + +// The circuit and statistics +ry(1.23) | [0]; +rot(0.1, 0.2, 0.3) | [1]; +h(0.2) | [0, 1, 2]; + +sample(observable: o(0.2), shots: 1000) | [0, 1]; +""" + + +class TestParser: + """Integration tests for parsing, and serializing, XIR scripts""" + + @pytest.mark.parametrize("circuit", [qubit_script, photonics_script, photonics_script_no_decl]) + def test_parse_and_serialize(self, circuit): + """Test parsing and serializing an XIR script. + + Tests parsing, serializing as well as the ``is_equal`` utils function. + """ + irprog = parse_script(circuit) + res = irprog.serialize() + assert is_equal(res, circuit) diff --git a/python/ir/tests/test_io.py b/python/ir/tests/test_io.py new file mode 100644 index 00000000..7560157c --- /dev/null +++ b/python/ir/tests/test_io.py @@ -0,0 +1,195 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +"""Tests for the interfaces module""" + +import pytest +from decimal import Decimal +from typing import List, Tuple + +import strawberryfields as sf +from strawberryfields import ops + +from ..program import GateDeclaration, IRProgram, Statement +from ..interfaces.strawberryfields_io import to_program, to_xir + + +def create_ir_prog( + data: List[Tuple], + external_libs: List[str] = None, + include_decl: bool = True, + version: str = None, +) -> IRProgram: + """Create an IRProgram object used for testing""" + # if no version number is passed, use the default one (by not specifying it) + if version is None: + irprog = IRProgram() + else: + irprog = IRProgram(version=version) + + # add the statements to the program + stmts = [Statement(n, p, w) for n, p, w in data] + irprog._statements.extend(stmts) + + # if declaration should be included, add them to the program + if include_decl: + declarations = [GateDeclaration(n, len(p), len(w)) for n, p, w in data] + irprog._declarations["gate"].extend(declarations) + + # if any external libraries/files are included, add them to the program + if external_libs is not None: + irprog._include.extend(external_libs) + + return irprog + + +def create_sf_prog(num_of_wires: int, ops: List[Tuple]): + """Create a Strawberry Fields program""" + prog = sf.Program(num_of_wires) + + with prog.context as q: + for gate, params, wires in ops: + regrefs = [q[w] for w in wires] + if len(params) == 0: + gate | regrefs + else: + gate(*params) | regrefs + + return prog + +class TestXIRToStrawberryFields: + """Unit tests for the XIR to Strawberry Fields conversion""" + + def test_empty_irprogram(self): + """Test that converting an empty XIR program raises an error""" + irprog = create_ir_prog(data=[]) + with pytest.raises(ValueError, match="XIR program is empty and cannot be transformed"): + to_program(irprog) + + + def test_gate_not_defined(self): + """Test unknown gate raises error""" + circuit_data = [ + ("not_a_real_gate", [Decimal("0.42")], (1, 2, 3)), + ] + irprog = create_ir_prog(data=circuit_data) + + with pytest.raises(NameError, match="operation 'not_a_real_gate' not defined"): + to_program(irprog) + + + def test_gates_no_args(self): + """Test that gates without arguments work""" + circuit_data = [ + ("Vac", [], (0,)), + ] + irprog = create_ir_prog(data=circuit_data) + + sfprog = to_program(irprog) + + assert len(sfprog) == 1 + assert sfprog.circuit[0].op.__class__.__name__ == "Vacuum" + assert sfprog.circuit[0].reg[0].ind == 0 + + def test_gates_with_args(self): + """Test that gates with arguments work""" + + circuit_data = [ + ("Sgate", [Decimal("0.1"), Decimal("0.2")], (0,)), + ("Sgate", [Decimal("0.3")], (1,)), + ] + irprog = create_ir_prog(data=circuit_data) + sfprog = to_program(irprog) + + assert len(sfprog) == 2 + assert sfprog.circuit[0].op.__class__.__name__ == "Sgate" + assert sfprog.circuit[0].op.p[0] == 0.1 + assert sfprog.circuit[0].op.p[1] == 0.2 + assert sfprog.circuit[0].reg[0].ind == 0 + + assert sfprog.circuit[1].op.__class__.__name__ == "Sgate" + assert sfprog.circuit[1].op.p[0] == 0.3 + assert sfprog.circuit[1].op.p[1] == 0.0 # default value + assert sfprog.circuit[1].reg[0].ind == 1 + + +class TestStrawberryFieldsToXIR: + """Unit tests for the XIR to Strawberry Fields conversion""" + + def test_empty_sfprogram(self): + """Test that converting from an empty SF program works""" + sfprog = create_sf_prog(num_of_wires=2, ops=[]) + irprog = to_xir(sfprog) + + assert irprog.version == "0.1.0" + assert irprog.statements == [] + assert irprog.include == [] + assert irprog.statements == [] + assert irprog.declarations == {"gate": [], "func": [], "output": [], "operator": []} + + assert irprog.gates == dict() + assert irprog.operators == dict() + assert irprog.variables == set() + assert irprog._called_ops == set() + + + @pytest.mark.parametrize("add_decl", [True, False]) + def test_gates_no_args(self, add_decl): + """Test unknown gate raises error""" + circuit_data = [ + (ops.Vac, [], (0,)), + ] + + sfprog = create_sf_prog(num_of_wires=2, ops=circuit_data) + irprog = to_xir(sfprog, add_decl=add_decl) + + assert irprog.statements[0].name == "Vacuum" + assert irprog.statements[0].params == [] + assert irprog.statements[0].wires == (0,) + + if add_decl: + assert len(irprog.declarations["gate"]) == 1 + assert irprog.declarations["gate"][0].name == "Vacuum" + assert irprog.declarations["gate"][0].num_params == 0 + assert irprog.declarations["gate"][0].num_wires == 1 + else: + assert irprog.declarations["gate"] == [] + + @pytest.mark.parametrize("add_decl", [True, False]) + def test_gates_with_args(self, add_decl): + """Test that gates with arguments work""" + + circuit_data = [ + (ops.Sgate, [0.1, 0.2], (0,)), + (ops.Sgate, [Decimal("0.3")], (1,)), + ] + + sfprog = create_sf_prog(num_of_wires=2, ops=circuit_data) + irprog = to_xir(sfprog, add_decl=add_decl) + + assert irprog.statements[0].name == "Sgate" + assert irprog.statements[0].params == [0.1, 0.2] + assert irprog.statements[0].wires == (0,) + + assert irprog.statements[1].name == "Sgate" + assert irprog.statements[1].params == [Decimal("0.3"), 0.0] + assert irprog.statements[1].wires == (1,) + + if add_decl: + assert len(irprog.declarations["gate"]) == 1 + assert irprog.declarations["gate"][0].name == "Sgate" + assert irprog.declarations["gate"][0].num_params == 2 + assert irprog.declarations["gate"][0].num_wires == 1 + else: + assert irprog.declarations["gate"] == [] diff --git a/python/ir/tests/test_program.py b/python/ir/tests/test_program.py new file mode 100644 index 00000000..c3f424e8 --- /dev/null +++ b/python/ir/tests/test_program.py @@ -0,0 +1,413 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +"""Unit tests for the program class""" + +import pytest +from decimal import Decimal + +from ..program import ( + IRProgram, + GateDeclaration, + OutputDeclaration, + FuncDeclaration, + OperatorDeclaration, + Statement, + OperatorStmt, +) + + +class TestSerialize: + """Unit tests for the serialize method of the IRProgram""" + + def test_empty_program(self): + """Test serializing an empty program""" + irprog = IRProgram() + res = irprog.serialize() + assert res == "" + + def test_includes(self): + """Test serializing the included libraries statement""" + irprog = IRProgram() + irprog._include.extend(["xstd", "randomlib"]) + res = irprog.serialize() + assert res == "use xstd;\nuse randomlib;" + + ##################### + # Test declarations + ##################### + + @pytest.mark.parametrize("name", ["rx", "CNOT", "a_gate"]) + @pytest.mark.parametrize("num_params", [0, 1, 42]) + @pytest.mark.parametrize("num_wires", [1, 42]) + def test_gate_declaration(self, name, num_params, num_wires): + """Test serializing gate declarations""" + decl = GateDeclaration(name, num_params=num_params, num_wires=num_wires) + + irprog = IRProgram() + irprog._declarations["gate"].append(decl) + res = irprog.serialize() + assert res == f"gate {name}, {num_params}, {num_wires};" + + @pytest.mark.parametrize("name", ["sin", "COS", "arc_tan"]) + @pytest.mark.parametrize("num_params", [0, 1, 42]) + def test_func_declaration(self, name, num_params): + """Test serializing function declarations""" + decl = FuncDeclaration(name, num_params=num_params) + + irprog = IRProgram() + irprog._declarations["func"].append(decl) + res = irprog.serialize() + assert res == f"func {name}, {num_params};" + + @pytest.mark.parametrize("name", ["op", "OPERATOR", "op_42"]) + @pytest.mark.parametrize("num_params", [0, 1, 42]) + @pytest.mark.parametrize("num_wires", [1, 42]) + def test_operator_declaration(self, name, num_params, num_wires): + """Test serializing operator declarations""" + decl = OperatorDeclaration(name, num_params=num_params, num_wires=num_wires) + + irprog = IRProgram() + irprog._declarations["operator"].append(decl) + res = irprog.serialize() + assert res == f"operator {name}, {num_params}, {num_wires};" + + @pytest.mark.parametrize("name", ["sample", "amplitude"]) + def test_output_declaration(self, name): + """Test serializing output declarations""" + decl = OutputDeclaration(name) + + irprog = IRProgram() + irprog._declarations["output"].append(decl) + res = irprog.serialize() + assert res == f"output {name};" + + ################### + # Test statements + ################### + + @pytest.mark.parametrize("name", ["ry", "toffoli"]) + @pytest.mark.parametrize("params", [[0, 3.14, -42]]) + @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) + def test_statements_params(self, name, params, wires): + """Test serializing general (gate) statements""" + stmt = Statement(name, params, wires) + + irprog = IRProgram() + irprog._statements.append(stmt) + res = irprog.serialize() + + params_str = ", ".join(str(p) for p in params) + wires_str = ", ".join(str(w) for w in wires) + assert res == f"{name}({params_str}) | [{wires_str}];" + + @pytest.mark.parametrize("name", ["ry", "toffoli"]) + @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) + def test_statements_no_params(self, name, wires): + """Test serializing general (gate) statements without parameters""" + stmt = Statement(name, [], wires) + + irprog = IRProgram() + irprog._statements.append(stmt) + res = irprog.serialize() + + wires_str = ", ".join(str(w) for w in wires) + assert res == f"{name} | [{wires_str}];" + + @pytest.mark.parametrize("pref", [42, Decimal("3.14"), "2 * a + 1"]) + @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) + def test_operator_stmt(self, pref, wires): + """Test serializing operator statements""" + irprog = IRProgram() + + xyz = "XYZ" + terms = [(xyz[i], w) for i, w in enumerate(wires)] + terms_str = " @ ".join(f"{t[0]}[{t[1]}]" for t in terms) + + irprog._operators["H"] = { + "params": ["a", "b"], + "wires": (0, 1), + "statements": [ + OperatorStmt(pref, terms), + ], + } + + res = irprog.serialize() + assert res == f"operator H(a, b)[0, 1]:\n {pref}, {terms_str};\nend;" + + ######################### + # Test gate definitions + ######################### + + @pytest.mark.parametrize("name", ["ry", "toffoli"]) + @pytest.mark.parametrize("params", [["a", "b"]]) + @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) + def test_gates_params_and_wires(self, name, params, wires): + """Test serializing gates with parameters and wires declared""" + irprog = IRProgram() + irprog._gates[name] = { + "params": params, + "wires": wires, + "statements": [ + Statement("rz", [0.13], (0,)), + Statement("cnot", [], (0, 1)), + ], + } + res = irprog.serialize() + + params_str = ", ".join(str(p) for p in params) + wires_str = ", ".join(str(w) for w in wires) + assert ( + res == f"gate {name}({params_str})[{wires_str}]:" + "\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + ) + + @pytest.mark.parametrize("name", ["ry", "toffoli"]) + @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) + def test_gates_no_params(self, name, wires): + """Test serializing gates with no parameters""" + irprog = IRProgram() + irprog._gates[name] = { + "params": [], + "wires": wires, + "statements": [ + Statement("rz", [0.13], (0,)), + Statement("cnot", [], (0, 1)), + ], + } + res = irprog.serialize() + + wires_str = ", ".join(str(w) for w in wires) + assert res == f"gate {name}[{wires_str}]:\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + + @pytest.mark.parametrize("name", ["ry", "toffoli"]) + @pytest.mark.parametrize("params", [["a", "b"]]) + def test_gates_no_wires(self, name, params): + """Test serializing gates without declared wires""" + irprog = IRProgram() + irprog._gates[name] = { + "params": params, + "wires": (), + "statements": [ + Statement("rz", [0.13], (0,)), + Statement("cnot", [], (0, 1)), + ], + } + res = irprog.serialize() + + params_str = ", ".join(str(p) for p in params) + assert res == f"gate {name}({params_str}):\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + + @pytest.mark.parametrize("name", ["mygate", "a_beautiful_gate"]) + def test_gates_no_params_and_no_wires(self, name): + """Test serializing gates with no parameters and without declared wires""" + irprog = IRProgram() + + irprog._gates[name] = { + "params": [], + "wires": (), + "statements": [ + Statement("rz", [0.13], (0,)), + Statement("cnot", [], (0, 1)), + ], + } + res = irprog.serialize() + assert res == f"gate {name}:\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + + ############################# + # Test operator definitions + ############################# + + @pytest.mark.parametrize("name", ["H", "my_op"]) + @pytest.mark.parametrize("params", [["a", "b"]]) + @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) + def test_operators_params_and_wires(self, name, params, wires): + """Test serializing operators with parameters and wires declared""" + irprog = IRProgram() + irprog._operators[name] = { + "params": params, + "wires": wires, + "statements": [ + OperatorStmt(42, [("X", 0), ("Y", 1)]), + ], + } + res = irprog.serialize() + + params_str = ", ".join(str(p) for p in params) + wires_str = ", ".join(str(w) for w in wires) + assert res == f"operator {name}({params_str})[{wires_str}]:\n 42, X[0] @ Y[1];\nend;" + + @pytest.mark.parametrize("name", ["H", "my_op"]) + @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) + def test_operators_no_params(self, name, wires): + """Test serializing operators with no parameters""" + xyz = "XYZ" + + irprog = IRProgram() + irprog._operators[name] = { + "params": [], + "wires": wires, + "statements": [ + OperatorStmt(42, [("X", 0), ("Y", 1)]), + ], + } + res = irprog.serialize() + + wires_str = ", ".join(str(w) for w in wires) + assert res == f"operator {name}[{wires_str}]:\n 42, X[0] @ Y[1];\nend;" + + @pytest.mark.parametrize("name", ["H", "my_op"]) + @pytest.mark.parametrize("params", [["a", "b"]]) + def test_operators_no_wires(self, name, params): + """Test serializing operators without declared wires""" + irprog = IRProgram() + irprog._operators[name] = { + "params": params, + "wires": (), + "statements": [ + OperatorStmt(42, [("X", 0), ("Y", 1)]), + ], + } + res = irprog.serialize() + + params_str = ", ".join(str(p) for p in params) + assert res == f"operator {name}({params_str}):\n 42, X[0] @ Y[1];\nend;" + + @pytest.mark.parametrize("name", ["my_op", "op2"]) + def test_operators_no_params_and_no_wires(self, name): + """Test serializing operators with no parameters and without declared wires""" + irprog = IRProgram() + + irprog._operators[name] = { + "params": [], + "wires": (), + "statements": [ + OperatorStmt(42, [("X", 0), ("Y", 1)]), + ], + } + res = irprog.serialize() + assert res == f"operator {name}:\n 42, X[0] @ Y[1];\nend;" + + +class TestIRProgram: + """Unit tests for the IRProgram class""" + + def test_empty_initialize(self): + """Test initializing an empty program""" + irprog = IRProgram() + + assert irprog.version == "0.1.0" + assert irprog.statements == [] + assert irprog.include == [] + assert irprog.statements == [] + assert irprog.declarations == {"gate": [], "func": [], "output": [], "operator": []} + + assert irprog.gates == dict() + assert irprog.operators == dict() + assert irprog.variables == set() + assert irprog._called_ops == set() + + def test_repr(self): + irprog = IRProgram() + assert irprog.__repr__() == f"" + + @pytest.mark.parametrize( + "version", + [ + "4.2.0", + "0.3.0", + ], + ) + def test_version(self, version): + """Test that the correct version is passed""" + irprog = IRProgram(version=version) + assert irprog.version == version + assert irprog.__repr__() == f"" + + @pytest.mark.parametrize("version", ["4.2", "0.1.2.3", "abc", 42, 0.2]) + def test_invalid_version(self, version): + """Test that error is raised when passing invalid version numbers""" + with pytest.raises((ValueError, TypeError), match="Invalid version number"): + IRProgram(version=version) + + def test_add_gate(self): + """Test that the add_gate function works""" + irprog = IRProgram() + statements = [ + Statement("rx", ["x"], (0,)), + Statement("ry", ["y"], (0,)), + Statement("rx", ["x"], (0,)), + ] + params = ["x", "y", "z"] + wires = (0,) + irprog.add_gate("rot", params, wires, statements) + + assert irprog.gates == {"rot": {"params": params, "wires": wires, "statements": statements}} + + # check that gate is replaced, with a warning, if added again + params = ["a", "b", "c"] + wires = (1,) + with pytest.warns(Warning, match="Gate already defined"): + irprog.add_gate("rot", params, wires, statements) + + assert irprog.gates == {"rot": {"params": params, "wires": wires, "statements": statements}} + + # check that a second gate can be added and the former is kept + params_2 = [] + wires_2 = (0, 1) + statements_2 = ["cnot", [], (0, 1)] + irprog.add_gate("cnot", params_2, wires_2, statements_2) + + assert irprog.gates == { + "rot": {"params": params, "wires": wires, "statements": statements}, + "cnot": {"params": params_2, "wires": wires_2, "statements": statements_2}, + } + + def test_add_operator(self): + """Test that the add_operator function works""" + irprog = IRProgram() + statements = [ + OperatorStmt(13, [("X", 0), ("Y", 1)]), + OperatorStmt(-2, [("ABC", 1), ("D", 0)]), + ] + params = [] + wires = (0, 1) + irprog.add_operator("H", params, wires, statements) + + assert irprog.operators == { + "H": {"params": params, "wires": wires, "statements": statements} + } + + # check that operator is replaced, with a warning, if added again + statements = [ + OperatorStmt(2, [("X", 0)]), + ] + wires = (0,) + with pytest.warns(Warning, match="Operator already defined"): + irprog.add_operator("H", params, wires, statements) + + assert irprog.operators == { + "H": {"params": params, "wires": wires, "statements": statements} + } + + # check that a second operator can be added and the former is kept + params_2 = ["a"] + wires_2 = (2,) + statements_2 = [OperatorStmt("2 * a", [("X", 2)])] + irprog.add_operator("my_op", params_2, wires_2, statements_2) + + assert irprog.operators == { + "H": {"params": params, "wires": wires, "statements": statements}, + "my_op": {"params": params_2, "wires": wires_2, "statements": statements_2}, + } diff --git a/python/ir/tests/test_utils.py b/python/ir/tests/test_utils.py new file mode 100644 index 00000000..fd7618ff --- /dev/null +++ b/python/ir/tests/test_utils.py @@ -0,0 +1,71 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +"""Unit test for the utils module""" + +import pytest + +from ..utils import is_equal + +script_1 = """ +gate Sgate, 2, 1; +gate BSgate, 2, 2; +gate Rgate, 1, 1; +output MeasureHomodyne; + +Sgate(0.7, 0) | [1]; +BSgate(0.1, 0.0) | [0, 1]; +Rgate(0.2) | [1]; + +MeasureHomodyne(phi: 3) | [0]; +""" + +script_1_no_decl = """ +use xstd; + +Sgate(0.7, 0) | [1]; +BSgate(0.1, 0.0) | [0, 1]; +Rgate(0.2) | [1]; + +MeasureHomodyne(phi: 3) | [0]; +""" + +script_2 = """ +gate Sgate, 2, 1; +gate Rgate, 1, 1; +output MeasureFock; + +Sgate(0.7, 0) | [1]; +Rgate(0.2) | [1]; + +MeasureFock | [0]; +""" + + +@pytest.mark.parametrize("check_decl", [True, False]) +def test_is_equal_same_script(check_decl): + """Tests the equality of the same script""" + assert is_equal(script_1, script_1, check_decl=check_decl) + + +def test_is_equal_with_decl(): + """Tests the ``check_decl`` bool parameter""" + assert not is_equal(script_1_no_decl, script_1, check_decl=True) + assert is_equal(script_1_no_decl, script_1, check_decl=False) + + +@pytest.mark.parametrize("check_decl", [True, False]) +def test_is_equal_different_scripts(check_decl): + """Tests that ``is_equal`` run with two different scripts returns false""" + assert not is_equal(script_1, script_2, check_decl=check_decl) diff --git a/python/ir/usage_guide.md b/python/ir/usage_guide.md new file mode 100644 index 00000000..5926e4e1 --- /dev/null +++ b/python/ir/usage_guide.md @@ -0,0 +1,205 @@ +# Xanadu IR - Grammar + +An XIR script consists of three parts, which are all optional: + +* Included external files that are needed to interpret and validate parts of the script; e.g., +`use xstd`, for including `xstd.xir`. + +* Declarations of gates, mathematical functions and/or measurement and post processing statistics. + Can be used to define which gates will be used, e.g. `cnot 0, 1`, followed by the number of + parameters and number of wires which the gate is applicable to. + +* The main circuit containing gate statements, gate and observable definitions, and measurements. + +The order in which these parts appear does not matter. + +## Including external files and libraries + +Zero or more `use` statements, declaring which other scripts that should be included with the main +script. They may contain other XIR data such as gate or operator declarations, mathematical function +declarations or measurement and post-processing statistics declarations which can be referred to in +the main script. + +These files also use the `.xir` file ending and are currently only linked to by name, and are not +imported in any way. + +Example of two included external files: + +``` +use xstd; +use xqc/device/X8; +``` + +The former would be used to import a local file, `xstd.xir` located in the same folder as the main +script. The latter would be used to include specific device gate/measurement declarations from the XQC. + +## Declarations + +There are no gates, operators or output types included in the IR, and there are very few keywords +(see 'Keyword reference' below) in use. Due to this, all such operations should to be declared +withing this section. Note that this is currently not validated in any way by the parser. + +These include: + +* Gates are declared with a name followed by the number of parameters taken and number of wires + which the gate is applicable to; e.g., `gate rx, 1, 1`. + +* Functions are declared with a name followed by the number of parameters taken; e.g., `func sin, 1`. + +* Outputs and measurement types are declared with a name, e.g., `output samples` or `output expval`. + +The parser does not care about which names are used for the declarations. It is up to the device on +which the circuit is to be run to interpret them correctly and to support that particular gate. + +Example of the three supported declaration types: +``` +gate rz, 1, 1; +gate cnot, 0, 2; + +func sin, 1; +func log, 1; + +output sample; +output expval; +``` + +Circuit +------- +The main circuit supports three different type of statements: gate statements, which apply a gate +to one or more wires; gate definitions, consisting of one or several gate statements; and operator +definitions. + +All statements must end with a semicolon. + +### Statements + +A gate statement consists of the name of the gate that is to be applied, optionally followed by +parameters enclosed in parentheses, a vertical bar, signifying a gate application, and finally the +wires on which the gate should be applied. The parameters may contain arithmetics, previously +declared functions and/or any variables accessible in the specific scope (e.g., inside a gate +definition). + +Wires are always represented by either integers or names, enclosed in square brackets and separated +by commas; e.g., `[1, a, wire_three, 4]`. + +An example of a couple of statements in a circuit: +``` +ry(1.23) | [0]; +rot(0.1, 0.2, 0.3) | [1]; +h(0.2) | [0, 1, 2]; +``` + +### User-defined gates + +Users can also define their own gates consisting of one or more gate statements following the syntax +described above. A gate definition starts with the keyword `gate` followed by the name for the new +gate, any parameter variables needed in the following statements, and potentially the wires which +the gate is applicable to, ending with a colon. Preferably, but not necessarily, the statements are +followed on separate lines, ending with the `end` statement. + +An example of a gate definition: +``` +gate h(a)[0, 1, 2, a]: + rz(-2.3932) | [0]; + rz(a) | [1]; + cnot | [a, 2]; +end; +``` + +and another one: + +``` +gate rx42: + rx(0.42) | [0]; + rx(0.42) | [2]; + rx(0.42) | [3]; +end; +``` + +Note that if no wires are defined in the gate declaration, consecutive integers are assumed. The +latter example would thus be assumed to have 4 wires `[0, 1, 2, 3]`. + +### User-defined operators + +It's also possible to define operators in a similar way to the gate definitions. An operator +definition starts with the keyword `operator` followed by the name for the operator, any necessary +parameters, and potentially the wires which the operator is using, ending with a colon. Preferably, +but not necessarily, the operator statements are followed on separate lines, ending with the `end` +statement. + +Each operator statement consists of two terms separated by a comma: a prefactor and the +operator/tensor product of operators, along with the wires they should be applied to; e.g., +`X[0] @ Z[1]`. + +``` +operator o(a): + 0.7 * sin(a), X[0] @ Z[1]; + -1.6, X[0]; + 2.45, Y[0] @ X[1]; +end; +``` + +### Variables + +Variables only exist within definitions, defined as an argument and used within the block of +statements. They are only defined within the scope of the definition, and do not exist outside of +it. It's even possible to name an operation with the same name as its argument, although, for obvious +reasons, this is not recommended. + +Variable outside of definitions and classical variables do not currently exist. + +### Comments + +XIR uses C++ style comments where everthing after `//` until the end of the line is considered a +comment. Multiline comments simply consist of several single line comments. + +``` +rx(0.42) | [0]; // this is a comment + +// these are also comments +// spread out over multiple lines +cnot | [0, 1]; +``` + +### Notes + +* Since all main parts of the XIR script are optional, an empty file is also valid. + +* XIR is written in a way that's it's not reliant on any indentation or newlines. It's possible to + remove all indentations and line-breaks, and it wouldn't change how the file is parsed. + +* Everything referred to as a "name" above can contain letters (uppercase or lowercase), digits and + underscores, and must start with a letter or an underscore. + +* All names and keywords are case-sensitive. + +* Basic arithmetic (passed as parameters) is handled by the parser, although more complicated + mathematical expressions are not. For example, `3*(6+4)/2` is stored as a Decimal object `15.0` + and `a+2+4` is stored as the string `a+6`. A caveat with this simple model is that it cannot + simplify arithmetics separated by variables. E.g., `2+a+4` is parsed as the string `(2+a)+4`, and + then beautified and stored as the string `2+a+4`. + + Further note that: + + - floats are stored as `decimal.Decimal` objects, to save the exact representation of + what is written in the script + + - integers are stored as `int` + + - variables, and expressions containing variables, are stored as strings + +Keyword reference +----------------- +- `use` (include external files) +- `gate` (gate defintition) +- `operator` (operator definition) +- `end` (end of definition/declaration) + +- `true` (boolean true) +- `false` (boolean false) + +- `pi` (mathematical constant pi) + +- `gates` (declaration of gates) +- `math` (declaration of mathematical functions) +- `statistics` (declaration of measurement and post-processing statistics) diff --git a/python/ir/utils.py b/python/ir/utils.py new file mode 100644 index 00000000..d2c9af70 --- /dev/null +++ b/python/ir/utils.py @@ -0,0 +1,146 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +"""This module contains the utility functions used when parsing""" + +import re +from typing import List, Tuple + +def beautify_math(numstr: List[str]) -> List[str]: + """Simplifies specified substrings and removes unnecessary parantheses + + Used for e.g. statement parameter inputs to beautify mathematical expressions. + For example, ``((a+2))+-(b*(3+c))`` becomes ``a+2-b*(3+c)``. + + Args: + numstr (list[str]): string(s) containing mathematical expressions + + Returns: + list[str]: the input expression(s) beautified + """ + # list of replacement strings; can be expanded with more if needed + repstr = [ + ("--", "+"), + ("+-", "-"), + ("-+", "+"), + ("++", "+"), + ] + + for i, n in enumerate(numstr): + if not isinstance(n, str): + continue + for r in repstr: + numstr[i] = numstr[i].replace(*r) + if n[0] == "+": + numstr[i] = numstr[i][1:] + + numstr = remove_paranth(numstr) + return numstr + + +def remove_paranth(numstr: List[str]) -> List[str]: + """Removes matching parantheses where unnecessary + + For example, ``((a+b)+c)`` becomes ``a+b+c``. + + Args: + numstr (list[str]): string(s) containing mathematical expressions + + Returns: + list[str]: the input expressions beautified + """ + for i, n in enumerate(numstr): + if not isinstance(n, str): + continue + + pre = [] + dels = [0, len(n)+1] + + # expand string with "+" so that outer parantheses can be removed, + # then store all indices where unnecessary mathing parantheses are found + n = "+" + n + "+" + for j, char in enumerate(n): + if char == "(": + pre.append((n[j-1], j)) + if char == ")": + p = pre.pop() + if p[0] in ("+", "-", "(") and n[j+1] in ("+", "-", ")"): + dels.extend([p[1], j]) + + # delete all unnecessary mathing parantheses found int the previous step + new_str = "" + dels = sorted(dels) + for j in range(len(dels) - 1): + new_str += n[dels[j]+1:dels[j+1]] + numstr[i] = new_str + + return numstr + +def check_wires(wires: Tuple, stmts: List): + """Check that declared wires are the same as the wires used in statements + + Args: + wires (tuple): declared wires for either a gate or operator declaration + stmts (list[Statement]): statements used in gate or operator declaration + + Raises: + ValueError: if wires differ from the wires used in the statements + """ + wires_flat = [i for s in stmts for i in s.wires] + if set(wires) != set(wires_flat): + raise ValueError(f"Wrong wires supplied. Expected {set(wires)}, got {set(wires_flat)}") + + +def strip(script: str) -> str: + """Removes comments, newlines and unnecessary whitespace from script + + Args: + script (str): the text to be stripped + + Returns: + str: the stripped text + """ + # search for any comments ("//") preceded by one or more spaces (" "), followed + # by one or more characters ("."), ending with 0 or 1 newline ("\n?") + expr = r"( *//.*\n?|\s+)" + return re.sub(expr, " ", script) + + +# TODO: fix so that the order of declarations, definitions, and statements does not matter +def is_equal(circuit_1: str, circuit_2: str, check_decl: bool=True): + """TODO""" + clist_1 = strip(circuit_1).split(";") + clist_2 = strip(circuit_2).split(";") + + i, j = 0, 0 + while i < len(clist_1) and j < len(clist_2): + i, j = i + 1, j + 1 + if not check_decl: + if is_decl(clist_1[i-1]): + j -= 1 + continue + elif is_decl(clist_2[j-1]): + i -= 1 + continue + + if clist_1[i-1].strip().lower() != clist_2[j-1].strip().lower(): + return False + return True + + +def is_decl(line: str) -> bool: + """TODO""" + if not set(line.split()).isdisjoint({"gate", "output", "operator", "use"}): + return True + return False From 5c82fc5936955878655f1f0a9c237f55cd586014 Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Thu, 20 May 2021 17:56:20 -0400 Subject: [PATCH 02/71] move tests --- python/{ir/tests => tests/ir}/test_integration.py | 0 python/{ir/tests => tests/ir}/test_io.py | 0 python/{ir/tests => tests/ir}/test_program.py | 0 python/{ir/tests => tests/ir}/test_utils.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename python/{ir/tests => tests/ir}/test_integration.py (100%) rename python/{ir/tests => tests/ir}/test_io.py (100%) rename python/{ir/tests => tests/ir}/test_program.py (100%) rename python/{ir/tests => tests/ir}/test_utils.py (100%) diff --git a/python/ir/tests/test_integration.py b/python/tests/ir/test_integration.py similarity index 100% rename from python/ir/tests/test_integration.py rename to python/tests/ir/test_integration.py diff --git a/python/ir/tests/test_io.py b/python/tests/ir/test_io.py similarity index 100% rename from python/ir/tests/test_io.py rename to python/tests/ir/test_io.py diff --git a/python/ir/tests/test_program.py b/python/tests/ir/test_program.py similarity index 100% rename from python/ir/tests/test_program.py rename to python/tests/ir/test_program.py diff --git a/python/ir/tests/test_utils.py b/python/tests/ir/test_utils.py similarity index 100% rename from python/ir/tests/test_utils.py rename to python/tests/ir/test_utils.py From 20ff84c3569b0ad5ac751629b3ecd7cf784c586e Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Thu, 20 May 2021 17:59:42 -0400 Subject: [PATCH 03/71] run black --- python/ir/interfaces/strawberryfields_io.py | 1 + python/ir/parser.py | 3 +-- python/ir/program.py | 24 ++++++--------------- python/ir/utils.py | 18 +++++++++------- python/tests/ir/test_io.py | 4 +--- 5 files changed, 20 insertions(+), 30 deletions(-) diff --git a/python/ir/interfaces/strawberryfields_io.py b/python/ir/interfaces/strawberryfields_io.py index 50abaaab..33ce17ac 100644 --- a/python/ir/interfaces/strawberryfields_io.py +++ b/python/ir/interfaces/strawberryfields_io.py @@ -17,6 +17,7 @@ from strawberryfields import ops from ir.program import IRProgram, Statement, GateDeclaration, OutputDeclaration + def find_number_of_modes(xir): """Helper function to find the number of modes in an XIR program""" wires = set() diff --git a/python/ir/parser.py b/python/ir/parser.py index a38ad41e..b3c8f166 100644 --- a/python/ir/parser.py +++ b/python/ir/parser.py @@ -33,7 +33,7 @@ with p.open("r") as _f: ir_grammar = _f.read() -ir_parser = Lark(ir_grammar, start="program", parser='lalr') +ir_parser = Lark(ir_grammar, start="program", parser="lalr") class IRTransformer(Transformer): @@ -217,7 +217,6 @@ def output_decl(self, args): decl = OutputDeclaration(*args) self._program._declarations["output"].append(decl) - ######### # maths ######### diff --git a/python/ir/program.py b/python/ir/program.py index 2eb90e9a..2ccbc20c 100644 --- a/python/ir/program.py +++ b/python/ir/program.py @@ -58,6 +58,7 @@ def __str__(self): return f"{pref}, {terms_as_string}" + class Declaration: """TODO""" @@ -127,12 +128,7 @@ def __init__(self, version: str = "0.1.0"): self._include = [] self._statements = [] - self._declarations = { - "gate": [], - "func": [], - "output": [], - "operator": [] - } + self._declarations = {"gate": [], "func": [], "output": [], "operator": []} self._gates = dict() self._operators = dict() @@ -188,21 +184,15 @@ def add_gate(self, name: str, params: List[str], wires: Tuple, statements: List[ """TODO""" if name in self._gates: warnings.warn("Gate already defined. Replacing old definition with new definiton.") - self._gates[name] = { - "params": params, - "wires": wires, - "statements": statements - } + self._gates[name] = {"params": params, "wires": wires, "statements": statements} - def add_operator(self, name: str, params: List[str], wires: Tuple, statements: List[OperatorStmt]): + def add_operator( + self, name: str, params: List[str], wires: Tuple, statements: List[OperatorStmt] + ): """TODO""" if name in self._operators: warnings.warn("Operator already defined. Replacing old definition with new definiton.") - self._operators[name] = { - "params": params, - "wires": wires, - "statements": statements - } + self._operators[name] = {"params": params, "wires": wires, "statements": statements} def serialize(self, minimize: bool = False) -> str: """Serialize an IRProgram returning an XIR script diff --git a/python/ir/utils.py b/python/ir/utils.py index d2c9af70..4a73036a 100644 --- a/python/ir/utils.py +++ b/python/ir/utils.py @@ -17,6 +17,7 @@ import re from typing import List, Tuple + def beautify_math(numstr: List[str]) -> List[str]: """Simplifies specified substrings and removes unnecessary parantheses @@ -65,28 +66,29 @@ def remove_paranth(numstr: List[str]) -> List[str]: continue pre = [] - dels = [0, len(n)+1] + dels = [0, len(n) + 1] # expand string with "+" so that outer parantheses can be removed, # then store all indices where unnecessary mathing parantheses are found n = "+" + n + "+" for j, char in enumerate(n): if char == "(": - pre.append((n[j-1], j)) + pre.append((n[j - 1], j)) if char == ")": p = pre.pop() - if p[0] in ("+", "-", "(") and n[j+1] in ("+", "-", ")"): + if p[0] in ("+", "-", "(") and n[j + 1] in ("+", "-", ")"): dels.extend([p[1], j]) # delete all unnecessary mathing parantheses found int the previous step new_str = "" dels = sorted(dels) for j in range(len(dels) - 1): - new_str += n[dels[j]+1:dels[j+1]] + new_str += n[dels[j] + 1 : dels[j + 1]] numstr[i] = new_str return numstr + def check_wires(wires: Tuple, stmts: List): """Check that declared wires are the same as the wires used in statements @@ -118,7 +120,7 @@ def strip(script: str) -> str: # TODO: fix so that the order of declarations, definitions, and statements does not matter -def is_equal(circuit_1: str, circuit_2: str, check_decl: bool=True): +def is_equal(circuit_1: str, circuit_2: str, check_decl: bool = True): """TODO""" clist_1 = strip(circuit_1).split(";") clist_2 = strip(circuit_2).split(";") @@ -127,14 +129,14 @@ def is_equal(circuit_1: str, circuit_2: str, check_decl: bool=True): while i < len(clist_1) and j < len(clist_2): i, j = i + 1, j + 1 if not check_decl: - if is_decl(clist_1[i-1]): + if is_decl(clist_1[i - 1]): j -= 1 continue - elif is_decl(clist_2[j-1]): + elif is_decl(clist_2[j - 1]): i -= 1 continue - if clist_1[i-1].strip().lower() != clist_2[j-1].strip().lower(): + if clist_1[i - 1].strip().lower() != clist_2[j - 1].strip().lower(): return False return True diff --git a/python/tests/ir/test_io.py b/python/tests/ir/test_io.py index 7560157c..835d1fb7 100644 --- a/python/tests/ir/test_io.py +++ b/python/tests/ir/test_io.py @@ -68,6 +68,7 @@ def create_sf_prog(num_of_wires: int, ops: List[Tuple]): return prog + class TestXIRToStrawberryFields: """Unit tests for the XIR to Strawberry Fields conversion""" @@ -77,7 +78,6 @@ def test_empty_irprogram(self): with pytest.raises(ValueError, match="XIR program is empty and cannot be transformed"): to_program(irprog) - def test_gate_not_defined(self): """Test unknown gate raises error""" circuit_data = [ @@ -88,7 +88,6 @@ def test_gate_not_defined(self): with pytest.raises(NameError, match="operation 'not_a_real_gate' not defined"): to_program(irprog) - def test_gates_no_args(self): """Test that gates without arguments work""" circuit_data = [ @@ -143,7 +142,6 @@ def test_empty_sfprogram(self): assert irprog.variables == set() assert irprog._called_ops == set() - @pytest.mark.parametrize("add_decl", [True, False]) def test_gates_no_args(self, add_decl): """Test unknown gate raises error""" From a250727f7a1e7a08b2661fb6871964a82048d1e1 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:36:56 -0400 Subject: [PATCH 04/71] Rename ir/ directories to xir/ --- python/tests/{ir => xir}/test_integration.py | 0 python/tests/{ir => xir}/test_io.py | 0 python/tests/{ir => xir}/test_program.py | 0 python/tests/{ir => xir}/test_utils.py | 0 python/{ir => xir}/__init__.py | 0 python/{ir => xir}/_version.py | 0 python/{ir => xir}/interfaces/__init__.py | 0 python/{ir => xir}/interfaces/strawberryfields_io.py | 0 python/{ir => xir}/ir.lark | 0 python/{ir => xir}/parser.py | 0 python/{ir => xir}/program.py | 0 python/{ir => xir}/usage_guide.md | 0 python/{ir => xir}/utils.py | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename python/tests/{ir => xir}/test_integration.py (100%) rename python/tests/{ir => xir}/test_io.py (100%) rename python/tests/{ir => xir}/test_program.py (100%) rename python/tests/{ir => xir}/test_utils.py (100%) rename python/{ir => xir}/__init__.py (100%) rename python/{ir => xir}/_version.py (100%) rename python/{ir => xir}/interfaces/__init__.py (100%) rename python/{ir => xir}/interfaces/strawberryfields_io.py (100%) rename python/{ir => xir}/ir.lark (100%) rename python/{ir => xir}/parser.py (100%) rename python/{ir => xir}/program.py (100%) rename python/{ir => xir}/usage_guide.md (100%) rename python/{ir => xir}/utils.py (100%) diff --git a/python/tests/ir/test_integration.py b/python/tests/xir/test_integration.py similarity index 100% rename from python/tests/ir/test_integration.py rename to python/tests/xir/test_integration.py diff --git a/python/tests/ir/test_io.py b/python/tests/xir/test_io.py similarity index 100% rename from python/tests/ir/test_io.py rename to python/tests/xir/test_io.py diff --git a/python/tests/ir/test_program.py b/python/tests/xir/test_program.py similarity index 100% rename from python/tests/ir/test_program.py rename to python/tests/xir/test_program.py diff --git a/python/tests/ir/test_utils.py b/python/tests/xir/test_utils.py similarity index 100% rename from python/tests/ir/test_utils.py rename to python/tests/xir/test_utils.py diff --git a/python/ir/__init__.py b/python/xir/__init__.py similarity index 100% rename from python/ir/__init__.py rename to python/xir/__init__.py diff --git a/python/ir/_version.py b/python/xir/_version.py similarity index 100% rename from python/ir/_version.py rename to python/xir/_version.py diff --git a/python/ir/interfaces/__init__.py b/python/xir/interfaces/__init__.py similarity index 100% rename from python/ir/interfaces/__init__.py rename to python/xir/interfaces/__init__.py diff --git a/python/ir/interfaces/strawberryfields_io.py b/python/xir/interfaces/strawberryfields_io.py similarity index 100% rename from python/ir/interfaces/strawberryfields_io.py rename to python/xir/interfaces/strawberryfields_io.py diff --git a/python/ir/ir.lark b/python/xir/ir.lark similarity index 100% rename from python/ir/ir.lark rename to python/xir/ir.lark diff --git a/python/ir/parser.py b/python/xir/parser.py similarity index 100% rename from python/ir/parser.py rename to python/xir/parser.py diff --git a/python/ir/program.py b/python/xir/program.py similarity index 100% rename from python/ir/program.py rename to python/xir/program.py diff --git a/python/ir/usage_guide.md b/python/xir/usage_guide.md similarity index 100% rename from python/ir/usage_guide.md rename to python/xir/usage_guide.md diff --git a/python/ir/utils.py b/python/xir/utils.py similarity index 100% rename from python/ir/utils.py rename to python/xir/utils.py From c6da8b802f094859b2eedc7b60967b72012ed75f Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:39:41 -0400 Subject: [PATCH 05/71] Apply formatter and update import paths --- python/tests/xir/test_integration.py | 10 +++-- python/tests/xir/test_io.py | 17 +++++--- python/tests/xir/test_program.py | 43 ++++++++++++++------ python/tests/xir/test_utils.py | 2 +- python/xir/interfaces/strawberryfields_io.py | 2 +- 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/python/tests/xir/test_integration.py b/python/tests/xir/test_integration.py index c97ca1e1..a11c5140 100644 --- a/python/tests/xir/test_integration.py +++ b/python/tests/xir/test_integration.py @@ -14,11 +14,11 @@ """Integration tests for the IR""" -from ..program import IRProgram import pytest -from .. import ir_parser, IRTransformer -from ..utils import is_equal +from xir import IRTransformer, ir_parser +from xir.program import IRProgram +from xir.utils import is_equal def parse_script(circuit: str) -> IRProgram: @@ -82,7 +82,9 @@ def parse_script(circuit: str) -> IRProgram: class TestParser: """Integration tests for parsing, and serializing, XIR scripts""" - @pytest.mark.parametrize("circuit", [qubit_script, photonics_script, photonics_script_no_decl]) + @pytest.mark.parametrize( + "circuit", [qubit_script, photonics_script, photonics_script_no_decl] + ) def test_parse_and_serialize(self, circuit): """Test parsing and serializing an XIR script. diff --git a/python/tests/xir/test_io.py b/python/tests/xir/test_io.py index 835d1fb7..5102ebee 100644 --- a/python/tests/xir/test_io.py +++ b/python/tests/xir/test_io.py @@ -14,15 +14,15 @@ """Tests for the interfaces module""" -import pytest from decimal import Decimal from typing import List, Tuple +import pytest import strawberryfields as sf from strawberryfields import ops -from ..program import GateDeclaration, IRProgram, Statement -from ..interfaces.strawberryfields_io import to_program, to_xir +from xir.interfaces.strawberryfields_io import to_program, to_xir +from xir.program import GateDeclaration, IRProgram, Statement def create_ir_prog( @@ -75,7 +75,9 @@ class TestXIRToStrawberryFields: def test_empty_irprogram(self): """Test that converting an empty XIR program raises an error""" irprog = create_ir_prog(data=[]) - with pytest.raises(ValueError, match="XIR program is empty and cannot be transformed"): + with pytest.raises( + ValueError, match="XIR program is empty and cannot be transformed" + ): to_program(irprog) def test_gate_not_defined(self): @@ -135,7 +137,12 @@ def test_empty_sfprogram(self): assert irprog.statements == [] assert irprog.include == [] assert irprog.statements == [] - assert irprog.declarations == {"gate": [], "func": [], "output": [], "operator": []} + assert irprog.declarations == { + "gate": [], + "func": [], + "output": [], + "operator": [], + } assert irprog.gates == dict() assert irprog.operators == dict() diff --git a/python/tests/xir/test_program.py b/python/tests/xir/test_program.py index c3f424e8..6048c82d 100644 --- a/python/tests/xir/test_program.py +++ b/python/tests/xir/test_program.py @@ -14,17 +14,18 @@ """Unit tests for the program class""" -import pytest from decimal import Decimal -from ..program import ( - IRProgram, - GateDeclaration, - OutputDeclaration, +import pytest + +from xir.program import ( FuncDeclaration, + GateDeclaration, + IRProgram, OperatorDeclaration, - Statement, OperatorStmt, + OutputDeclaration, + Statement, ) @@ -189,7 +190,10 @@ def test_gates_no_params(self, name, wires): res = irprog.serialize() wires_str = ", ".join(str(w) for w in wires) - assert res == f"gate {name}[{wires_str}]:\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + assert ( + res + == f"gate {name}[{wires_str}]:\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + ) @pytest.mark.parametrize("name", ["ry", "toffoli"]) @pytest.mark.parametrize("params", [["a", "b"]]) @@ -207,7 +211,10 @@ def test_gates_no_wires(self, name, params): res = irprog.serialize() params_str = ", ".join(str(p) for p in params) - assert res == f"gate {name}({params_str}):\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + assert ( + res + == f"gate {name}({params_str}):\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" + ) @pytest.mark.parametrize("name", ["mygate", "a_beautiful_gate"]) def test_gates_no_params_and_no_wires(self, name): @@ -246,7 +253,10 @@ def test_operators_params_and_wires(self, name, params, wires): params_str = ", ".join(str(p) for p in params) wires_str = ", ".join(str(w) for w in wires) - assert res == f"operator {name}({params_str})[{wires_str}]:\n 42, X[0] @ Y[1];\nend;" + assert ( + res + == f"operator {name}({params_str})[{wires_str}]:\n 42, X[0] @ Y[1];\nend;" + ) @pytest.mark.parametrize("name", ["H", "my_op"]) @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) @@ -311,7 +321,12 @@ def test_empty_initialize(self): assert irprog.statements == [] assert irprog.include == [] assert irprog.statements == [] - assert irprog.declarations == {"gate": [], "func": [], "output": [], "operator": []} + assert irprog.declarations == { + "gate": [], + "func": [], + "output": [], + "operator": [], + } assert irprog.gates == dict() assert irprog.operators == dict() @@ -353,7 +368,9 @@ def test_add_gate(self): wires = (0,) irprog.add_gate("rot", params, wires, statements) - assert irprog.gates == {"rot": {"params": params, "wires": wires, "statements": statements}} + assert irprog.gates == { + "rot": {"params": params, "wires": wires, "statements": statements} + } # check that gate is replaced, with a warning, if added again params = ["a", "b", "c"] @@ -361,7 +378,9 @@ def test_add_gate(self): with pytest.warns(Warning, match="Gate already defined"): irprog.add_gate("rot", params, wires, statements) - assert irprog.gates == {"rot": {"params": params, "wires": wires, "statements": statements}} + assert irprog.gates == { + "rot": {"params": params, "wires": wires, "statements": statements} + } # check that a second gate can be added and the former is kept params_2 = [] diff --git a/python/tests/xir/test_utils.py b/python/tests/xir/test_utils.py index fd7618ff..f470b704 100644 --- a/python/tests/xir/test_utils.py +++ b/python/tests/xir/test_utils.py @@ -16,7 +16,7 @@ import pytest -from ..utils import is_equal +from xir.utils import is_equal script_1 = """ gate Sgate, 2, 1; diff --git a/python/xir/interfaces/strawberryfields_io.py b/python/xir/interfaces/strawberryfields_io.py index 33ce17ac..4a49ee56 100644 --- a/python/xir/interfaces/strawberryfields_io.py +++ b/python/xir/interfaces/strawberryfields_io.py @@ -15,7 +15,7 @@ from decimal import Decimal import strawberryfields as sf from strawberryfields import ops -from ir.program import IRProgram, Statement, GateDeclaration, OutputDeclaration +from xir.program import IRProgram, Statement, GateDeclaration, OutputDeclaration def find_number_of_modes(xir): From a6cc282a837fd85bf3087bce79065c1f48893195 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:40:55 -0400 Subject: [PATCH 06/71] Clarify that top-level makefile is for C++ code --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 797c206d..8b2ae565 100644 --- a/Makefile +++ b/Makefile @@ -8,15 +8,16 @@ define HELP_BODY Please use 'make [target]'. TARGETS + install [prefix=] Install Jet headers to /include, defaults to $(.DEFAULT_PREFIX) uninstall [prefix=] Remove Jet headers from /include, defaults to $(.DEFAULT_PREFIX) - - test Build and run tests (requires cmake) - docs Build docs (requires doxygen, pandoc and pip) + test Build and run C++ tests (requires Cmake) + + docs Build docs (requires Doxygen, Pandoc and pip) - format [check=1] Apply formatters; use with 'check=1' to check instead of modify (requires clang-format) + format [check=1] Apply C++ formatter; use with 'check=1' to check instead of modify (requires clang-format) clean Remove all build artifacts From a75f6544db48bc15f4ff157e368008d9b0de4cb2 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:41:57 -0400 Subject: [PATCH 07/71] Rename Python bindings package to 'bindings' --- python/CMakeLists.txt | 6 +++--- python/src/Python.cpp | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 034725ed..d3bca96e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -8,7 +8,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(Pybind11) -# The following command also creates a CMake target called "jet". -pybind11_add_module(jet src/Python.cpp) +# The following command also creates a CMake target called "bindings". +pybind11_add_module(bindings src/Python.cpp) -target_link_libraries(jet PRIVATE Jet) +target_link_libraries(bindings PRIVATE Jet) diff --git a/python/src/Python.cpp b/python/src/Python.cpp index 9f055d09..70b29cfd 100644 --- a/python/src/Python.cpp +++ b/python/src/Python.cpp @@ -8,10 +8,9 @@ #include "TensorNetworkIO.hpp" #include "Version.hpp" -PYBIND11_MODULE(jet, m) +PYBIND11_MODULE(bindings, m) { - m.doc() = "Jet is a library for simulating quantum circuits using tensor " - "network contractions."; + m.doc() = "Python bindings for the C++ tensor network contraction headers."; using c_fp32_t = std::complex; using c_fp64_t = std::complex; From 09a01c097d330b210692933c74c8d62c755dc725 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:43:53 -0400 Subject: [PATCH 08/71] Move Python bindings tests to subdirectory --- python/tests/{ => jet}/test_pathinfo.py | 0 python/tests/{ => jet}/test_tensor.py | 0 python/tests/{ => jet}/test_tensor_network.py | 0 python/tests/{ => jet}/test_tensor_network_io.py | 0 python/tests/{ => jet}/test_version.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename python/tests/{ => jet}/test_pathinfo.py (100%) rename python/tests/{ => jet}/test_tensor.py (100%) rename python/tests/{ => jet}/test_tensor_network.py (100%) rename python/tests/{ => jet}/test_tensor_network_io.py (100%) rename python/tests/{ => jet}/test_version.py (100%) diff --git a/python/tests/test_pathinfo.py b/python/tests/jet/test_pathinfo.py similarity index 100% rename from python/tests/test_pathinfo.py rename to python/tests/jet/test_pathinfo.py diff --git a/python/tests/test_tensor.py b/python/tests/jet/test_tensor.py similarity index 100% rename from python/tests/test_tensor.py rename to python/tests/jet/test_tensor.py diff --git a/python/tests/test_tensor_network.py b/python/tests/jet/test_tensor_network.py similarity index 100% rename from python/tests/test_tensor_network.py rename to python/tests/jet/test_tensor_network.py diff --git a/python/tests/test_tensor_network_io.py b/python/tests/jet/test_tensor_network_io.py similarity index 100% rename from python/tests/test_tensor_network_io.py rename to python/tests/jet/test_tensor_network_io.py diff --git a/python/tests/test_version.py b/python/tests/jet/test_version.py similarity index 100% rename from python/tests/test_version.py rename to python/tests/jet/test_version.py From 471c54a3dadc32cf20f135bc352481504cf80257 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:44:43 -0400 Subject: [PATCH 09/71] Update import path to Python bindings module --- python/tests/jet/test_pathinfo.py | 11 ++++--- python/tests/jet/test_tensor.py | 38 ++++++++++++---------- python/tests/jet/test_tensor_network.py | 9 +++-- python/tests/jet/test_tensor_network_io.py | 19 ++++++++--- 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/python/tests/jet/test_pathinfo.py b/python/tests/jet/test_pathinfo.py index bf5d04ee..64f731ea 100644 --- a/python/tests/jet/test_pathinfo.py +++ b/python/tests/jet/test_pathinfo.py @@ -1,6 +1,7 @@ -import jet import pytest +import jet + class TestPathInfo: def test_default_constructor(self): @@ -18,11 +19,11 @@ def test_default_constructor(self): def test_constructor(self): """Tests that the tensor network and path constructor is called.""" - tn = jet.TensorNetwork64() + tn = jet.TensorNetwork() - id_a = tn.add_tensor(jet.Tensor64(), ["A"]) - id_b = tn.add_tensor(jet.Tensor64(), ["B"]) - id_c = tn.add_tensor(jet.Tensor64(), ["C"]) + id_a = tn.add_tensor(jet.Tensor(), ["A"]) + id_b = tn.add_tensor(jet.Tensor(), ["B"]) + id_c = tn.add_tensor(jet.Tensor(), ["C"]) path = [(id_a, id_b)] diff --git a/python/tests/jet/test_tensor.py b/python/tests/jet/test_tensor.py index 01dd02a7..948a3aa7 100644 --- a/python/tests/jet/test_tensor.py +++ b/python/tests/jet/test_tensor.py @@ -1,8 +1,10 @@ -import jet import pytest +import jet +import jet.bindings + -@pytest.mark.parametrize("Tensor", [jet.Tensor32, jet.Tensor64]) +@pytest.mark.parametrize("Tensor", [jet.bindings.Tensor32, jet.bindings.Tensor64]) class TestTensor: def test_default_constructor(self, Tensor): """Tests that the default constructor is called.""" @@ -116,57 +118,57 @@ def test_rename_index(self, Tensor): def test_add_tensors(): """Tests that a pair of tensors can be added.""" - tensor_1 = jet.Tensor64(shape=[2, 3], indices=["i", "j"], data=range(6)) - tensor_2 = jet.Tensor64(shape=[2, 3], indices=["i", "j"], data=range(0, 12, 2)) + tensor_1 = jet.Tensor(shape=[2, 3], indices=["i", "j"], data=range(6)) + tensor_2 = jet.Tensor(shape=[2, 3], indices=["i", "j"], data=range(0, 12, 2)) have_tensor = jet.add_tensors(tensor_1, tensor_2) - want_tensor = jet.Tensor64(shape=[2, 3], indices=["i", "j"], data=range(0, 18, 3)) + want_tensor = jet.Tensor(shape=[2, 3], indices=["i", "j"], data=range(0, 18, 3)) assert have_tensor == want_tensor def test_conj(): """Tests that the conjugate of a tensor can be taken.""" - tensor = jet.Tensor64(shape=[1, 2], indices=["i", "j"], data=[1, 2 + 3j]) + tensor = jet.Tensor(shape=[1, 2], indices=["i", "j"], data=[1, 2 + 3j]) have_tensor = jet.conj(tensor) - want_tensor = jet.Tensor64(shape=[1, 2], indices=["i", "j"], data=[1, 2 - 3j]) + want_tensor = jet.Tensor(shape=[1, 2], indices=["i", "j"], data=[1, 2 - 3j]) assert have_tensor == want_tensor def test_contract_tensors(): """Tests that a pair of tensors can be contracted.""" - tensor_1 = jet.Tensor64(shape=[2, 3, 4], indices=["i", "j", "k"]) - tensor_2 = jet.Tensor64(shape=[3, 4, 1], indices=["j", "k", "l"]) + tensor_1 = jet.Tensor(shape=[2, 3, 4], indices=["i", "j", "k"]) + tensor_2 = jet.Tensor(shape=[3, 4, 1], indices=["j", "k", "l"]) have_tensor = jet.contract_tensors(tensor_1, tensor_2) - want_tensor = jet.Tensor64(shape=[2, 1], indices=["i", "l"]) + want_tensor = jet.Tensor(shape=[2, 1], indices=["i", "l"]) assert have_tensor == want_tensor def test_slice_index(): """Tests that a tensor can be sliced.""" - tensor = jet.Tensor64(shape=[2, 3, 4], indices=["i", "j", "k"]) + tensor = jet.Tensor(shape=[2, 3, 4], indices=["i", "j", "k"]) have_tensor = jet.slice_index(tensor, "k", 3) - want_tensor = jet.Tensor64(shape=[2, 3], indices=["i", "j"]) + want_tensor = jet.Tensor(shape=[2, 3], indices=["i", "j"]) assert have_tensor == want_tensor def test_reshape(): """Tests that a tensor can be reshaped.""" - tensor = jet.Tensor64(shape=[3, 4], indices=["i", "j"]) + tensor = jet.Tensor(shape=[3, 4], indices=["i", "j"]) have_tensor = jet.reshape(tensor, [2, 6]) - want_tensor = jet.Tensor64(shape=[2, 6]) + want_tensor = jet.Tensor(shape=[2, 6]) assert have_tensor == want_tensor class TestTranspose: def test_transpose_by_index(self): """Tests that a tensor can be transposed by index.""" - tensor = jet.Tensor64(shape=[2, 3, 4], indices=["i", "j", "k"]) + tensor = jet.Tensor(shape=[2, 3, 4], indices=["i", "j", "k"]) have_tensor = jet.transpose(tensor, ["j", "k", "i"]) - want_tensor = jet.Tensor64(shape=[3, 4, 2], indices=["j", "k", "i"]) + want_tensor = jet.Tensor(shape=[3, 4, 2], indices=["j", "k", "i"]) assert have_tensor == want_tensor def test_transpose_by_order(self): """Tests that a tensor can be transposed by order.""" - tensor = jet.Tensor64(shape=[2, 3, 4], indices=["i", "j", "k"]) + tensor = jet.Tensor(shape=[2, 3, 4], indices=["i", "j", "k"]) have_tensor = jet.transpose(tensor, [1, 2, 0]) - want_tensor = jet.Tensor64(shape=[3, 4, 2], indices=["j", "k", "i"]) + want_tensor = jet.Tensor(shape=[3, 4, 2], indices=["j", "k", "i"]) assert have_tensor == want_tensor diff --git a/python/tests/jet/test_tensor_network.py b/python/tests/jet/test_tensor_network.py index 5e44efb4..bb8e8e8a 100644 --- a/python/tests/jet/test_tensor_network.py +++ b/python/tests/jet/test_tensor_network.py @@ -1,10 +1,15 @@ -import jet import pytest +import jet +import jet.bindings + @pytest.mark.parametrize( "TensorNetwork, Tensor", - [(jet.TensorNetwork32, jet.Tensor32), (jet.TensorNetwork64, jet.Tensor64)], + [ + (jet.bindings.TensorNetwork32, jet.bindings.Tensor32), + (jet.bindings.TensorNetwork64, jet.bindings.Tensor64), + ], ) class TestTensorNetwork: def test_constructor(self, TensorNetwork, Tensor): diff --git a/python/tests/jet/test_tensor_network_io.py b/python/tests/jet/test_tensor_network_io.py index 3246ae53..acc9e34e 100644 --- a/python/tests/jet/test_tensor_network_io.py +++ b/python/tests/jet/test_tensor_network_io.py @@ -1,11 +1,14 @@ import re -import jet import pytest +import jet +import jet.bindings + @pytest.mark.parametrize( - "TensorNetworkFile", [jet.TensorNetworkFile32, jet.TensorNetworkFile64] + "TensorNetworkFile", + [jet.bindings.TensorNetworkFile32, jet.bindings.TensorNetworkFile64], ) def test_tensor_network_file(TensorNetworkFile): """Tests that a tensor network file can be constructed.""" @@ -17,8 +20,16 @@ def test_tensor_network_file(TensorNetworkFile): @pytest.mark.parametrize( "Tensor, TensorNetwork, TensorNetworkSerializer", [ - (jet.Tensor32, jet.TensorNetwork32, jet.TensorNetworkSerializer32), - (jet.Tensor64, jet.TensorNetwork64, jet.TensorNetworkSerializer64), + ( + jet.bindings.Tensor32, + jet.bindings.TensorNetwork32, + jet.bindings.TensorNetworkSerializer32, + ), + ( + jet.bindings.Tensor64, + jet.bindings.TensorNetwork64, + jet.bindings.TensorNetworkSerializer64, + ), ], ) class TestTensorNetworkSerializer: From 4d495e1002edcfb03542b4a602515ebc51b54d21 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:45:58 -0400 Subject: [PATCH 10/71] Rename 'requirements_test.txt' to 'requirements.txt' --- python/{requirements_test.txt => requirements.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python/{requirements_test.txt => requirements.txt} (100%) diff --git a/python/requirements_test.txt b/python/requirements.txt similarity index 100% rename from python/requirements_test.txt rename to python/requirements.txt From a8263ad1a5340c14400e134bc8a564834e5cd8e6 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:46:26 -0400 Subject: [PATCH 11/71] Add Python wheel dependencies --- python/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/requirements.txt b/python/requirements.txt index 804d570d..3ddcaee7 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,3 +1,6 @@ black +build +cmake>=3.14 isort>5 pytest>=5,<6 +wheel \ No newline at end of file From 1f3c92195e1cc6778f31465683c49b9db76efae8 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:47:03 -0400 Subject: [PATCH 12/71] Add Python distribution directory to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f7fc3e3f..44cba89b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ include/jet/CmakeMacros.hpp # Python .venv __pycache__ +dist # Sphinx documentation docs/__pycache__/* From b4d59ada0e676559ffdb018223ded23482cf8aa6 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:48:43 -0400 Subject: [PATCH 13/71] Create top-level Jet Python package --- python/jet/__init__.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 python/jet/__init__.py diff --git a/python/jet/__init__.py b/python/jet/__init__.py new file mode 100644 index 00000000..1dc161de --- /dev/null +++ b/python/jet/__init__.py @@ -0,0 +1,31 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +from .bindings import PathInfo as PathInfo +from .bindings import Tensor64 as Tensor +from .bindings import TensorNetwork64 as TensorNetwork +from .bindings import TensorNetworkFile64 as TensorNetworkFile +from .bindings import TensorNetworkSerializer64 as TensorNetworkSerializer +from .bindings import ( + add_tensors, + conj, + contract_tensors, + reshape, + slice_index, + transpose, + version, +) + +# Grab the current Jet version from the C++ headers. +__version__ = version() From 42d13f022554d6e662a116c96b9b9d06076a3431 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 21 May 2021 18:49:19 -0400 Subject: [PATCH 14/71] Create setup.py to build quantum-jet Python wheels --- python/Makefile | 32 ++++++++++---- setup.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 setup.py diff --git a/python/Makefile b/python/Makefile index b5342701..5b1f8bd6 100644 --- a/python/Makefile +++ b/python/Makefile @@ -18,33 +18,49 @@ TARGETS endef + .PHONY: help help: @: $(info $(HELP_BODY)) + .PHONY: setup -setup: $(.VENV_DIR)/requirements_test.txt.touch +setup: $(.VENV_DIR)/touch + .PHONY: format -format: +format: $(.VENV_DIR)/requirements.txt.touch ifdef check - $(.VENV_BIN)/black --check tests && $(.VENV_BIN)/isort --profile black --check-only tests + $(.VENV_BIN)/black --check tests + $(.VENV_BIN)/isort --profile black --check-only tests else - $(.VENV_BIN)/black tests && $(.VENV_BIN)/isort --profile black tests + $(.VENV_BIN)/black tests + $(.VENV_BIN)/isort --profile black tests endif + +.PHONY: build +build: $(.VENV_DIR)/requirements.txt.touch + $(.VENV_BIN)/python -m build -n .. + rm -rf quantum_jet.egg-info + + .PHONY: test -test: $(.VENV_DIR)/requirements_test.txt.touch - PYTHONPATH="../build/python" $(.VENV_BIN)/python -m pytest ./tests $(args) +test: build + $(.VENV_BIN)/pip install $(wildcard ../dist/*.whl) + $(.VENV_BIN)/python -I -m pytest ./tests $(args) + .PHONY: clean clean: rm -rf $(.VENV_DIR) -$(.VENV_DIR)/requirements_test.txt.touch: $(.VENV_DIR)/touch requirements_test.txt - $(.VENV_DIR)/bin/pip install -r requirements_test.txt + +$(.VENV_DIR)/requirements.txt.touch: $(.VENV_DIR)/touch requirements.txt + $(.VENV_DIR)/bin/pip install -r requirements.txt @touch $@ + $(.VENV_DIR)/touch: $(python) -m venv ${.VENV_DIR} @touch $@ diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..c14d0760 --- /dev/null +++ b/setup.py @@ -0,0 +1,114 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. + +import os +import re +import sys +import subprocess + +from setuptools import setup, Extension, find_packages +from setuptools.command.build_ext import build_ext + +# The following two classes are adaptations of the Python example for pybind11: +# https://github.com/pybind/python_example/blob/master/setup.py + + +class CMakeExtension(Extension): + def __init__(self, name, sourcedir=""): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) + + +class CMakeBuild(build_ext): + def build_extension(self, ext): + path = self.get_ext_fullpath(ext.name) + extdir = os.path.abspath(os.path.dirname(path)) + + # Required for auto-detection of auxiliary "native" libs. + if not extdir.endswith(os.path.sep): + extdir += os.path.sep + + os.makedirs(self.build_temp, exist_ok=True) + + # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON + cmake_args = [ + f"-DBUILD_PYTHON=ON", + f"-DCMAKE_BUILD_TYPE=Release", + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}", + f"-DPYTHON_EXECUTABLE={sys.executable}", + ] + + cmake_cmd = ["cmake", ext.sourcedir] + cmake_args + build_cmd = ["cmake", "--build", "."] + + subprocess.check_call(cmake_cmd, cwd=self.build_temp) + subprocess.check_call(build_cmd, cwd=self.build_temp) + + +# Hack to get the Jet version number without including the Python package. +with open("include/jet/Version.hpp", "r") as f: + contents = f.read() + major = re.search(r"MAJOR_VERSION\s*=\s*(\d+)", contents).group(1) + minor = re.search(r"MINOR_VERSION\s*=\s*(\d+)", contents).group(1) + patch = re.search(r"PATCH_VERSION\s*=\s*(\d+)", contents).group(1) + version = f"{major}.{minor}.{patch}" + +requirements = [ + "lark-parser>=0.11.0", + "StrawberryFields==0.18.0", +] + +info = { + "cmdclass": {"build_ext": CMakeBuild}, + "description": ( + "Jet is an open-source library for quantum circuit simulation " + "using tensor network contractions" + ), + "ext_modules": [CMakeExtension(name="jet.bindings")], + "include_package_data": True, + "install_requires": requirements, + "license": "Apache License 2.0", + "long_description": open("README.rst", encoding="utf-8").read(), + "long_description_content_type": "text/x-rst", + "maintainer": "Xanadu Inc.", + "maintainer_email": "software@xanadu.ai", + "name": "quantum-jet", + "package_data": {"xir": ["ir.lark"]}, + "package_dir": {"": "python"}, + "packages": find_packages(where="python"), + "provides": ["jet", "xir"], + "url": "https://github.com/XanaduAI/jet", + "version": version, + "zip_safe": False, +} + +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: POSIX", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering :: Physics", +] + +setup(classifiers=classifiers, **(info)) From 5e0990825ec299330920fabcef7cd95918ea353e Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 12:45:29 -0400 Subject: [PATCH 15/71] Move setup.py to python/ directory --- setup.py => python/setup.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename setup.py => python/setup.py (100%) diff --git a/setup.py b/python/setup.py similarity index 100% rename from setup.py rename to python/setup.py From 2d08472ee03695402348e46e6615fc63205f7129 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 13:23:04 -0400 Subject: [PATCH 16/71] Make cleaned directory prefixes consistent --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8b2ae565..24d989ab 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ docs: $(.VENV_DIR) .PHONY: clean clean: - rm -rf docs/_build docs/api docs/doxyoutput + rm -rf ./docs/_build ./docs/api ./docs/doxyoutput rm -rf $(.TEST_BUILD_DIR) rm -rf $(.VENV_DIR) From 8c5475a226b7be53c568d4f893ce3a42fcdc669e Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 13:51:02 -0400 Subject: [PATCH 17/71] Add 'build' help documentation and clean build/ and dist/ --- python/Makefile | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/python/Makefile b/python/Makefile index 5b1f8bd6..ffa877ff 100644 --- a/python/Makefile +++ b/python/Makefile @@ -10,10 +10,12 @@ TARGETS setup [python=] Set up virtualenv using the Python interpreter at , defaults to $(python) - test [args=] Run tests; use with 'args=' to pass test arguments + build Create source distribution and wheel format [check=1] Apply formatters; use with 'check=1' to check instead of modify + test [args=] Run tests; use with 'args=' to pass test arguments + clean Remove all build artifacts endef @@ -41,19 +43,22 @@ endif .PHONY: build build: $(.VENV_DIR)/requirements.txt.touch - $(.VENV_BIN)/python -m build -n .. + $(.VENV_BIN)/python -m build + @# Delete the .egg-info artifact to prevent pip from assuming the package was installed. + @# See https://github.com/pypa/pip/issues/6558 for more details. rm -rf quantum_jet.egg-info .PHONY: test test: build - $(.VENV_BIN)/pip install $(wildcard ../dist/*.whl) + $(.VENV_BIN)/pip install $(wildcard dist/*.whl) --upgrade + @# The -I flag ensures the installed `jet` package is imported instead of the local one. $(.VENV_BIN)/python -I -m pytest ./tests $(args) .PHONY: clean clean: - rm -rf $(.VENV_DIR) + rm -rf $(.VENV_DIR) build dist $(.VENV_DIR)/requirements.txt.touch: $(.VENV_DIR)/touch requirements.txt From 97eed02fb2a8947aa7d723b4e4ab0a4b425c04cc Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 13:51:34 -0400 Subject: [PATCH 18/71] Change source directory to parent directory --- python/setup.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/python/setup.py b/python/setup.py index c14d0760..251fa4e9 100644 --- a/python/setup.py +++ b/python/setup.py @@ -1,17 +1,3 @@ -# Copyright 2021 Xanadu Quantum Technologies Inc. - -# 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. - import os import re import sys @@ -25,7 +11,7 @@ class CMakeExtension(Extension): - def __init__(self, name, sourcedir=""): + def __init__(self, name, sourcedir=".."): Extension.__init__(self, name, sources=[]) self.sourcedir = os.path.abspath(sourcedir) @@ -57,7 +43,7 @@ def build_extension(self, ext): # Hack to get the Jet version number without including the Python package. -with open("include/jet/Version.hpp", "r") as f: +with open("../include/jet/Version.hpp", "r") as f: contents = f.read() major = re.search(r"MAJOR_VERSION\s*=\s*(\d+)", contents).group(1) minor = re.search(r"MINOR_VERSION\s*=\s*(\d+)", contents).group(1) @@ -79,14 +65,13 @@ def build_extension(self, ext): "include_package_data": True, "install_requires": requirements, "license": "Apache License 2.0", - "long_description": open("README.rst", encoding="utf-8").read(), + "long_description": open("../README.rst", encoding="utf-8").read(), "long_description_content_type": "text/x-rst", "maintainer": "Xanadu Inc.", "maintainer_email": "software@xanadu.ai", "name": "quantum-jet", "package_data": {"xir": ["ir.lark"]}, - "package_dir": {"": "python"}, - "packages": find_packages(where="python"), + "packages": find_packages(where="."), "provides": ["jet", "xir"], "url": "https://github.com/XanaduAI/jet", "version": version, From 05b7c0869049226dc4995cb761857f8a99aebabe Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 13:52:07 -0400 Subject: [PATCH 19/71] Import all bindings directly into the 'jet' package --- python/jet/__init__.py | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/python/jet/__init__.py b/python/jet/__init__.py index 1dc161de..45dd47cb 100644 --- a/python/jet/__init__.py +++ b/python/jet/__init__.py @@ -1,31 +1,11 @@ -# Copyright 2021 Xanadu Quantum Technologies Inc. - -# 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. - -from .bindings import PathInfo as PathInfo -from .bindings import Tensor64 as Tensor -from .bindings import TensorNetwork64 as TensorNetwork -from .bindings import TensorNetworkFile64 as TensorNetworkFile -from .bindings import TensorNetworkSerializer64 as TensorNetworkSerializer -from .bindings import ( - add_tensors, - conj, - contract_tensors, - reshape, - slice_index, - transpose, - version, -) +# The existence of a Python binding is proof that it is intended to be exposed. +from .bindings import * + +# The default Python floating-point type occupies 64 bits. +Tensor = Tensor64 +TensorNetwork = TensorNetwork64 +TensorNetworkFile = TensorNetworkFile64 +TensorNetworkSerializer = TensorNetworkSerializer64 # Grab the current Jet version from the C++ headers. __version__ = version() From 95685fd359b9104b78ac047923b8df62e3f1ace8 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 14:22:33 -0400 Subject: [PATCH 20/71] Exclude tests from package --- python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/setup.py b/python/setup.py index 251fa4e9..84321e64 100644 --- a/python/setup.py +++ b/python/setup.py @@ -71,7 +71,7 @@ def build_extension(self, ext): "maintainer_email": "software@xanadu.ai", "name": "quantum-jet", "package_data": {"xir": ["ir.lark"]}, - "packages": find_packages(where="."), + "packages": find_packages(where=".", exclude=["src", "tests"]), "provides": ["jet", "xir"], "url": "https://github.com/XanaduAI/jet", "version": version, From 01ae9b9d66eb0188a7510d625d9230eef66cc51d Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 14:58:33 -0400 Subject: [PATCH 21/71] Fix wheel build error for lark --- python/requirements.txt | 1 + python/setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/requirements.txt b/python/requirements.txt index 80e32e39..de4c95f4 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -4,3 +4,4 @@ cmake>=3.14 isort>5 pytest>=5,<6 strawberryfields +wheel \ No newline at end of file diff --git a/python/setup.py b/python/setup.py index 84321e64..8ce44ea3 100644 --- a/python/setup.py +++ b/python/setup.py @@ -51,7 +51,7 @@ def build_extension(self, ext): version = f"{major}.{minor}.{patch}" requirements = [ - "lark-parser>=0.11.0", + "lark>=0.11.0", "StrawberryFields==0.18.0", ] From b292e529cf0e6263477f66deeef5bf39ac4bf68a Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 15:08:38 -0400 Subject: [PATCH 22/71] Remove deprecated Python bindings build steps --- .github/workflows/python.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index f3d4f83a..d7ac6eb9 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -26,17 +26,6 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Initialize build directory - run: | - mkdir build - cd build - cmake -DBUILD_PYTHON=ON ../ - - - name: Generate Python bindings - run: | - cd build - make -j`nproc` - - name: Create virtual environment run: | cd python @@ -63,17 +52,6 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Initialize build directory - run: | - mkdir build - cd build - cmake -DBUILD_PYTHON=ON ../ - - - name: Generate Python bindings - run: | - cd build - make -j`sysctl -n hw.physicalcpu` - - name: Create virtual environment run: | cd python From c102416ee6b88eb18f2ee6327c9b3a6aea4bf611 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 15:08:55 -0400 Subject: [PATCH 23/71] Update changelog --- .github/CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 9d51d93b..51f92e89 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,10 @@ ### New features since last release +* Running `make build` from the `python` directory now creates a Python distribution package. [(#12)](https://github.com/XanaduAI/jet/pull/12) + +* A new intermediate representation (IR) is added, including a parser, IR representation program, and a Strawberry Fields interface. [(#11)](https://github.com/XanaduAI/jet/pull/11) + * Python bindings are now available for the `TensorNetworkSerializer` class. [(#5)](https://github.com/XanaduAI/jet/pull/5) * Python bindings are now available for the `TensorNetwork` and `PathInfo` classes [(#7)](https://github.com/XanaduAI/jet/pull/7) @@ -10,8 +14,6 @@ * Running CMake with `-DBUILD_PYTHON=ON` now generates Python bindings within a `jet` package. [(#1)](https://github.com/XanaduAI/jet/pull/1) -* A new intermediate representation (IR) is added, including a parser, IR representation program, and a Strawberry Fields interface. [(#11)](https://github.com/XanaduAI/jet/pull/11) - ### Improvements * Exceptions are now favoured in place of `std::terminate` with `Exception` being the new base type for all exceptions thrown by Jet. [(#3)](https://github.com/XanaduAI/jet/pull/3) From c86b04a507ea7c0d0e2eb3a9181513c2327c91f2 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 15:10:24 -0400 Subject: [PATCH 24/71] Adjust PR number --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 51f92e89..53d2c4d2 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,7 +2,7 @@ ### New features since last release -* Running `make build` from the `python` directory now creates a Python distribution package. [(#12)](https://github.com/XanaduAI/jet/pull/12) +* Running `make build` from the `python` directory now creates a Python distribution package. [(#13)](https://github.com/XanaduAI/jet/pull/13) * A new intermediate representation (IR) is added, including a parser, IR representation program, and a Strawberry Fields interface. [(#11)](https://github.com/XanaduAI/jet/pull/11) From 6218b5ac0de090679a652cec186ce4b6fe9b346a Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 15:34:50 -0400 Subject: [PATCH 25/71] Flatten jet.bindings package and apply formatter --- python/tests/jet/test_pathinfo.py | 2 -- python/tests/jet/test_tensor.py | 3 +-- python/tests/jet/test_tensor_network.py | 5 ++--- python/tests/jet/test_tensor_network_io.py | 13 ++++++------ python/tests/xir/test_integration.py | 4 +--- python/tests/xir/test_io.py | 4 +--- python/tests/xir/test_program.py | 23 +++++----------------- 7 files changed, 16 insertions(+), 38 deletions(-) diff --git a/python/tests/jet/test_pathinfo.py b/python/tests/jet/test_pathinfo.py index 64f731ea..76e4c2af 100644 --- a/python/tests/jet/test_pathinfo.py +++ b/python/tests/jet/test_pathinfo.py @@ -1,5 +1,3 @@ -import pytest - import jet diff --git a/python/tests/jet/test_tensor.py b/python/tests/jet/test_tensor.py index e74982a2..de39178e 100644 --- a/python/tests/jet/test_tensor.py +++ b/python/tests/jet/test_tensor.py @@ -1,10 +1,9 @@ import pytest import jet -import jet.bindings -@pytest.mark.parametrize("Tensor", [jet.bindings.Tensor32, jet.bindings.Tensor64]) +@pytest.mark.parametrize("Tensor", [jet.Tensor32, jet.Tensor64]) class TestTensor: def test_default_constructor(self, Tensor): """Tests that the default constructor is called.""" diff --git a/python/tests/jet/test_tensor_network.py b/python/tests/jet/test_tensor_network.py index bb8e8e8a..72621d3a 100644 --- a/python/tests/jet/test_tensor_network.py +++ b/python/tests/jet/test_tensor_network.py @@ -1,14 +1,13 @@ import pytest import jet -import jet.bindings @pytest.mark.parametrize( "TensorNetwork, Tensor", [ - (jet.bindings.TensorNetwork32, jet.bindings.Tensor32), - (jet.bindings.TensorNetwork64, jet.bindings.Tensor64), + (jet.TensorNetwork32, jet.Tensor32), + (jet.TensorNetwork64, jet.Tensor64), ], ) class TestTensorNetwork: diff --git a/python/tests/jet/test_tensor_network_io.py b/python/tests/jet/test_tensor_network_io.py index 8b59a8ab..9adb66cb 100644 --- a/python/tests/jet/test_tensor_network_io.py +++ b/python/tests/jet/test_tensor_network_io.py @@ -3,7 +3,6 @@ import pytest import jet -import jet.bindings @pytest.mark.parametrize("TensorNetworkFile", [jet.TensorNetworkFile32, jet.TensorNetworkFile64]) @@ -18,14 +17,14 @@ def test_tensor_network_file(TensorNetworkFile): "Tensor, TensorNetwork, TensorNetworkSerializer", [ ( - jet.bindings.Tensor32, - jet.bindings.TensorNetwork32, - jet.bindings.TensorNetworkSerializer32, + jet.Tensor32, + jet.TensorNetwork32, + jet.TensorNetworkSerializer32, ), ( - jet.bindings.Tensor64, - jet.bindings.TensorNetwork64, - jet.bindings.TensorNetworkSerializer64, + jet.Tensor64, + jet.TensorNetwork64, + jet.TensorNetworkSerializer64, ), ], ) diff --git a/python/tests/xir/test_integration.py b/python/tests/xir/test_integration.py index 86d71a3b..66d8239a 100644 --- a/python/tests/xir/test_integration.py +++ b/python/tests/xir/test_integration.py @@ -82,9 +82,7 @@ def parse_script(circuit: str) -> XIRProgram: class TestParser: """Integration tests for parsing, and serializing, XIR scripts""" - @pytest.mark.parametrize( - "circuit", [qubit_script, photonics_script, photonics_script_no_decl] - ) + @pytest.mark.parametrize("circuit", [qubit_script, photonics_script, photonics_script_no_decl]) def test_parse_and_serialize(self, circuit): """Test parsing and serializing an XIR script. diff --git a/python/tests/xir/test_io.py b/python/tests/xir/test_io.py index 1ae4fc5a..4d812584 100644 --- a/python/tests/xir/test_io.py +++ b/python/tests/xir/test_io.py @@ -75,9 +75,7 @@ class TestXIRToStrawberryFields: def test_empty_irprogram(self): """Test that converting an empty XIR program raises an error""" irprog = create_xir_prog(data=[]) - with pytest.raises( - ValueError, match="XIR program is empty and cannot be transformed" - ): + with pytest.raises(ValueError, match="XIR program is empty and cannot be transformed"): to_program(irprog) def test_gate_not_defined(self): diff --git a/python/tests/xir/test_program.py b/python/tests/xir/test_program.py index ecc6bc5f..ed42fb10 100644 --- a/python/tests/xir/test_program.py +++ b/python/tests/xir/test_program.py @@ -190,10 +190,7 @@ def test_gates_no_params(self, name, wires): res = irprog.serialize() wires_str = ", ".join(str(w) for w in wires) - assert ( - res - == f"gate {name}[{wires_str}]:\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" - ) + assert res == f"gate {name}[{wires_str}]:\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" @pytest.mark.parametrize("name", ["ry", "toffoli"]) @pytest.mark.parametrize("params", [["a", "b"]]) @@ -211,10 +208,7 @@ def test_gates_no_wires(self, name, params): res = irprog.serialize() params_str = ", ".join(str(p) for p in params) - assert ( - res - == f"gate {name}({params_str}):\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" - ) + assert res == f"gate {name}({params_str}):\n rz(0.13) | [0];\n cnot | [0, 1];\nend;" @pytest.mark.parametrize("name", ["mygate", "a_beautiful_gate"]) def test_gates_no_params_and_no_wires(self, name): @@ -253,10 +247,7 @@ def test_operators_params_and_wires(self, name, params, wires): params_str = ", ".join(str(p) for p in params) wires_str = ", ".join(str(w) for w in wires) - assert ( - res - == f"operator {name}({params_str})[{wires_str}]:\n 42, X[0] @ Y[1];\nend;" - ) + assert res == f"operator {name}({params_str})[{wires_str}]:\n 42, X[0] @ Y[1];\nend;" @pytest.mark.parametrize("name", ["H", "my_op"]) @pytest.mark.parametrize("wires", [(0, 1), (0,), (0, 2, 42)]) @@ -368,9 +359,7 @@ def test_add_gate(self): wires = (0,) irprog.add_gate("rot", params, wires, statements) - assert irprog.gates == { - "rot": {"params": params, "wires": wires, "statements": statements} - } + assert irprog.gates == {"rot": {"params": params, "wires": wires, "statements": statements}} # check that gate is replaced, with a warning, if added again params = ["a", "b", "c"] @@ -378,9 +367,7 @@ def test_add_gate(self): with pytest.warns(Warning, match="Gate already defined"): irprog.add_gate("rot", params, wires, statements) - assert irprog.gates == { - "rot": {"params": params, "wires": wires, "statements": statements} - } + assert irprog.gates == {"rot": {"params": params, "wires": wires, "statements": statements}} # check that a second gate can be added and the former is kept params_2 = [] From 477f548cb3ab4026d919c13b364fa3445a72f103 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 25 May 2021 15:48:33 -0400 Subject: [PATCH 26/71] Change 'lark' requirement to 'lark-parser' --- python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/setup.py b/python/setup.py index 8ce44ea3..84321e64 100644 --- a/python/setup.py +++ b/python/setup.py @@ -51,7 +51,7 @@ def build_extension(self, ext): version = f"{major}.{minor}.{patch}" requirements = [ - "lark>=0.11.0", + "lark-parser>=0.11.0", "StrawberryFields==0.18.0", ] From 17da911629f91bd1a095939ce24c3b0a0a255f5a Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Thu, 27 May 2021 00:05:08 -0400 Subject: [PATCH 27/71] add circuit class --- python/jet/circuit.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 python/jet/circuit.py diff --git a/python/jet/circuit.py b/python/jet/circuit.py new file mode 100644 index 00000000..29f2155a --- /dev/null +++ b/python/jet/circuit.py @@ -0,0 +1,19 @@ +"""Contains the circuit class for building a tensor network""" + +import jet +from .gates import Gate + + +class Circuit: + """Circuit class""" + + def __init__(self) -> None: + self.circuit = [] + + def tensor_network(self) -> jet.TensorNetwork: + """Builds and returns the tensor network corresponding to the circuit""" + tn = jet.TensorNetwork() + return tn + + def add_gate(self, gate: Gate) -> None: + """Adds a gate to the circuit""" From 48b82b73b1f8e42e1fc58f07db31eb35fc8b0425 Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Thu, 27 May 2021 00:05:14 -0400 Subject: [PATCH 28/71] add first gate --- python/jet/gates.py | 74 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 python/jet/gates.py diff --git a/python/jet/gates.py b/python/jet/gates.py new file mode 100644 index 00000000..57dbbaa8 --- /dev/null +++ b/python/jet/gates.py @@ -0,0 +1,74 @@ +"""Tensor representations of quantum gates""" + +import numpy as np + +import jet + +ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + +class Gate: + """Gate class""" + + def __init__(self, label: str) -> None: + self.label = label + + def tensor(self, adjoint: bool = False): + """Tensor representation of gate""" + raise NotImplementedError + + def _data(self) -> np.ndarray: + """Matrix representation of the gate""" + raise NotImplementedError + + +class Squeezing(Gate): + """Squeezing gate""" + + def __init__(self, label: str, r: float, theta: float, cutoff: int) -> None: + self.r = r + self.theta = theta + self.cutoff = cutoff + + super().__init__(label) + + def tensor(self, adjoint: bool = False) -> jet.Tensor: + """Tensor representation of gate""" + indices = list(ALPHABET[:self.cutoff]) + shape = [self.cutoff, self.cutoff] + + if adjoint: + data = np.conj(self._data()).T.flatten() + else: + data = self._data().flatten() + + return jet.Tensor(indices, shape, data) + + def _data(self) -> np.ndarray: + """Calculates the matrix elements of the squeezing gate using a recurrence relation. + + Args: + r (float): squeezing magnitude + theta (float): squeezing angle + cutoff (int): Fock ladder cutoff + dtype (data type): Specifies the data type used for the calculation + + Returns: + array[complex]: matrix representing the squeezing gate. + """ + S = np.zeros((self.cutoff, self.cutoff), dtype=np.complex128) + sqrt = np.sqrt(np.arange(self.cutoff, dtype=np.complex128)) + + eitheta_tanhr = np.exp(1j * self.theta) * np.tanh(self.r) + sechr = 1.0 / np.cosh(self.r) + R = np.array([[-eitheta_tanhr, sechr], [sechr, np.conj(eitheta_tanhr)],]) + + S[0, 0] = np.sqrt(sechr) + for m in range(2, self.cutoff, 2): + S[m, 0] = sqrt[m - 1] / sqrt[m] * R[0, 0] * S[m - 2, 0] + + for m in range(0, self.cutoff): + for n in range(1, self.cutoff): + if (m + n) % 2 == 0: + S[m, n] = sqrt[n - 1] / sqrt[n] * R[1, 1] * S[m, n - 2] + sqrt[m] / sqrt[n] * R[0, 1] * S[m - 1, n - 1] + return S From bf59af7a6da880baf5bb53e825f5147ce5c87179 Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Fri, 28 May 2021 15:58:40 -0400 Subject: [PATCH 29/71] Add gates (WIP) --- python/jet/gates.py | 684 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 640 insertions(+), 44 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 57dbbaa8..6555f89c 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -1,74 +1,670 @@ """Tensor representations of quantum gates""" +import math +import cmath +from numbers import Number +from functools import lru_cache +from typing import Sequence, Union, List, Optional + import numpy as np +from thewalrus.fock_gradients import ( + displacement, + grad_displacement, + squeezing, + grad_squeezing, + two_mode_squeezing, + grad_two_mode_squeezing, + beamsplitter, + grad_beamsplitter +) import jet -ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +__all__ = [ + "Displacement", + "Squeezing", + "TwoModeSqueezing", + "Beamsplitter", + "Hadamard", + "PauliX", + "PauliY", + "PauliZ", + "PauliRot", + "MultiRZ", + "S", + "T", + "SX", + "CNOT", + "CZ", + "CY", + "SWAP", + "ISWAP", + "CSWAP", + "Toffoli", + "RX", + "RY", + "RZ", + "PhaseShift", + "ControlledPhaseShift", + "CPhase", + "Rot", + "CRX", + "CRY", + "CRZ", + "CRot", + "U1", + "U2", + "U3", +] +ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +INV_SQRT2 = 1 / math.sqrt(2) class Gate: - """Gate class""" + """Gate class + + Args: + name (str): name of the gate + num_wires (int): the number of wires the gate is applied to - def __init__(self, label: str) -> None: - self.label = label + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, name: str, num_wires: int, tensor_id: int = None) -> None: + self.name = name + self.tensor_id = tensor_id + + self._indices = None + self._num_wires = num_wires def tensor(self, adjoint: bool = False): """Tensor representation of gate""" - raise NotImplementedError + if adjoint: + data = np.conj(self._data()).T.flatten() + else: + data = self._data().flatten() + + indices = self.indices + if indices is None: + indices = list(ALPHABET[:2 * self._num_wires]) + shape = int(len(data) ** ((2 * self._num_wires) ** -1)) + + return jet.Tensor(indices, [shape] * 2 * self._num_wires, data) def _data(self) -> np.ndarray: """Matrix representation of the gate""" - raise NotImplementedError + raise NotImplementedError("No tensor data available for generic gate.") + @property + def indices(self) -> Optional[List[str]]: + """Indices for connecting tensors""" + return self._indices -class Squeezing(Gate): - """Squeezing gate""" + @indices.setter + def indices(self, indices: Optional[List[str]]) -> None: + """Setter method for indices""" + # validate that indices is a list of unique strings + if ( + not isinstance(indices, Sequence) + or not all(isinstance(idx, str) for idx in indices) + or len(set(indices)) != len(indices) + ): + raise ValueError("Indices must be a sequence of unique strings.") - def __init__(self, label: str, r: float, theta: float, cutoff: int) -> None: - self.r = r - self.theta = theta - self.cutoff = cutoff + # validate that indices has the correct lenght (or is None) + if indices is None: + self._indices = indices + elif len(indices) == 2 * self._num_wires: + self._indices = indices + else: + raise ValueError( + f"Must have 2 indices per wire. Got {len(indices)} indices for" + f"{self._num_wires} wires." + ) - super().__init__(label) - def tensor(self, adjoint: bool = False) -> jet.Tensor: - """Tensor representation of gate""" - indices = list(ALPHABET[:self.cutoff]) - shape = [self.cutoff, self.cutoff] +class Displacement(Gate): + """Displacement gate - if adjoint: - data = np.conj(self._data()).T.flatten() - else: - data = self._data().flatten() + Args: + r (float): displacement magnitude + phi (float): displacement angle + cutoff (int): Fock ladder cutoff - return jet.Tensor(indices, shape, data) + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "Displacement" + num_wires = 1 + num_params = 3 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache def _data(self) -> np.ndarray: - """Calculates the matrix elements of the squeezing gate using a recurrence relation. + """The matrix representation of the displacement gate + + Returns: + array[complex]: matrix representing the displacement gate. + """ + return displacement(*self.params, dtype=np.complex128) + + +class Squeezing(Gate): + """Squeezing gate + + Args: + r (float): squeezing magnitude + theta (float): squeezing angle + cutoff (int): Fock ladder cutoff + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "Squeezing" + num_wires = 1 + num_params = 3 - Args: - r (float): squeezing magnitude - theta (float): squeezing angle - cutoff (int): Fock ladder cutoff - dtype (data type): Specifies the data type used for the calculation + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """The matrix representation of the squeezing gate Returns: array[complex]: matrix representing the squeezing gate. """ - S = np.zeros((self.cutoff, self.cutoff), dtype=np.complex128) - sqrt = np.sqrt(np.arange(self.cutoff, dtype=np.complex128)) - - eitheta_tanhr = np.exp(1j * self.theta) * np.tanh(self.r) - sechr = 1.0 / np.cosh(self.r) - R = np.array([[-eitheta_tanhr, sechr], [sechr, np.conj(eitheta_tanhr)],]) - - S[0, 0] = np.sqrt(sechr) - for m in range(2, self.cutoff, 2): - S[m, 0] = sqrt[m - 1] / sqrt[m] * R[0, 0] * S[m - 2, 0] - - for m in range(0, self.cutoff): - for n in range(1, self.cutoff): - if (m + n) % 2 == 0: - S[m, n] = sqrt[n - 1] / sqrt[n] * R[1, 1] * S[m, n - 2] + sqrt[m] / sqrt[n] * R[0, 1] * S[m - 1, n - 1] - return S + return squeezing(*self.params, dtype=np.complex128) + + +class TwoModeSqueezing(Gate): + """TwoModeSqueezing gate + + Args: + r (float): squeezing magnitude + theta (float): squeezing angle + cutoff (int): Fock ladder cutoff + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "TwoModeSqueezing" + num_wires = 2 + num_params = 3 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """The matrix representation of the two-mode squeezing gate + + Returns: + array[complex]: matrix representing the two-mode squeezing gate. + """ + return two_mode_squeezing(*self.params, dtype=np.complex128) + + +class Beamsplitter(Gate): + """Beamsplitter gate + + Args: + theta (float): transmissivity angle of the beamsplitter. The transmissivity is :math:`t=\cos(\theta)` + phi (float): reflection phase of the beamsplitter + cutoff (int): Fock ladder cutoff + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "Beamsplitter" + num_wires = 1 + num_params = 3 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """The matrix representation of the beamsplitter gate + + Returns: + array[complex]: matrix representing the beamsplitter gate. + """ + return beamsplitter(*self.params, dtype=np.complex128) + + +class CNOT(Gate): + """CNOT gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "CNOT" + num_wires = 2 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """CNOT matrix""" + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] + return np.array(mat, dtype=np.complex128) + + +class Hadamard(Gate): + """Hadamard gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "Hadamard" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Hadamard matrix""" + mat = [[INV_SQRT2, INV_SQRT2], [INV_SQRT2, -INV_SQRT2]] + return np.array(mat, dtype=np.complex128) + + +class PauliX(Gate): + """PauliX gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "PauliX" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """PauliX matrix""" + mat = [[0, 1], [1, 0]] + return np.array(mat, dtype=np.complex128) + + +class PauliY(Gate): + """PauliX gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "PauliY" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """PauliY matrix""" + mat = [[0, -1j], [1j, 0]] + return np.array(mat, dtype=np.complex128) + + +class PauliZ(Gate): + """PauliZ gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "PauliZ" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """PauliZ matrix""" + mat = [[1, 0], [0, -1]] + return np.array(mat, dtype=np.complex128) + + +class S(Gate): + """The single-qubit phase gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "S" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single-qubit phase gate matrix""" + mat = [[1, 0], [0, 1j]] + return np.array(mat, dtype=np.complex128) + + +class T(Gate): + """The single-qubit T gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "T" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single-qubit T gate matrix""" + mat = [[1, 0], [0, cmath.exp(0.25j * np.pi)]] + return np.array(mat, dtype=np.complex128) + + +class SX(Gate): + """The single-qubit Square-Root X gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "SX" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single-qubit Square-Root X operator matrix""" + mat = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]] + return np.array(mat, dtype=np.complex128) + + +class CZ(Gate): + """The controlled-Z gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "CZ" + num_wires = 2 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Controlled-Z gate matrix""" + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] + return np.array(mat, dtype=np.complex128) + + +class CY(Gate): + """The controlled-Y gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "CY" + num_wires = 2 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Controlled-Y operator matrix""" + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1j], [0, 0, 1j, 0]] + return np.array(mat, dtype=np.complex128) + + +class SWAP(Gate): + """The swap gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "SWAP" + num_wires = 2 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Swap operator matrix""" + mat = [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] + return np.array(mat, dtype=np.complex128) + + +class ISWAP(Gate): + """The i-swap gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "iSWAP" + num_wires = 1 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """i-swap operator matrix""" + mat = [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]] + return np.array(mat, dtype=np.complex128) + + +class CSWAP(Gate): + """The CSWAP gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "CSWAP" + num_wires = 3 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """CSWAP operator matrix""" + mat = [ + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1], + ] + return np.array(mat, dtype=np.complex128) + + +class Toffoli(Gate): + """The Toffoli gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: Number, tensor_id: int) -> None: + name = "Toffoli" + num_wires = 3 + num_params = 0 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Toffoli operator matrix""" + mat = [ + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 1, 0], + ] + return np.array(mat, dtype=np.complex128) + + +class RX(Gate): + """The single qubit X gate + + Kwargs: + tensor_id (int): identification number for the gate-tensor + """ + + def __init__(self, *params: float, tensor_id: int) -> None: + name = "RX" + num_wires = 1 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, tensor_id=tensor_id) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single qubit X operator matrix""" + theta = self.params[0] + c = math.cos(theta / 2) + js = 1j * math.sin(-theta / 2) + return np.array([[c, js], [js, c]], dtype=np.complex128) From 6bc04b8cb4fb5de7a86e460dbac6477aeb245a09 Mon Sep 17 00:00:00 2001 From: Theodor Isacsson Date: Fri, 28 May 2021 18:31:32 -0400 Subject: [PATCH 30/71] add gates --- python/jet/gates.py | 569 ++++++++++++++++++++++++++++++++------------ 1 file changed, 416 insertions(+), 153 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 6555f89c..153929c7 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -2,29 +2,27 @@ import math import cmath -from numbers import Number from functools import lru_cache -from typing import Sequence, Union, List, Optional +from typing import Sequence, List, Optional import numpy as np from thewalrus.fock_gradients import ( displacement, - grad_displacement, squeezing, - grad_squeezing, two_mode_squeezing, - grad_two_mode_squeezing, beamsplitter, - grad_beamsplitter ) import jet __all__ = [ + # CV fock gates "Displacement", "Squeezing", "TwoModeSqueezing", "Beamsplitter", + + # qubit gates "Hadamard", "PauliX", "PauliY", @@ -60,6 +58,8 @@ ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" INV_SQRT2 = 1 / math.sqrt(2) +DEFAULT_DTYPE = np.complex128 + class Gate: """Gate class @@ -69,12 +69,14 @@ class Gate: Kwargs: tensor_id (int): identification number for the gate-tensor + dtype (type): type to use in matrix representations of gates """ - def __init__(self, name: str, num_wires: int, tensor_id: int = None) -> None: + def __init__(self, name: str, num_wires: int, **kwargs) -> None: self.name = name - self.tensor_id = tensor_id + self.tensor_id = kwargs.get("tensor_id", None) + self._dtype = kwargs.get("dtype", DEFAULT_DTYPE) self._indices = None self._num_wires = num_wires @@ -124,6 +126,11 @@ def indices(self, indices: Optional[List[str]]) -> None: ) +################################## +# Continuous variable Fock gates +################################## + + class Displacement(Gate): """Displacement gate @@ -131,12 +138,9 @@ class Displacement(Gate): r (float): displacement magnitude phi (float): displacement angle cutoff (int): Fock ladder cutoff - - Kwargs: - tensor_id (int): identification number for the gate-tensor """ - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "Displacement" num_wires = 1 num_params = 3 @@ -145,7 +149,7 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache @@ -155,7 +159,7 @@ def _data(self) -> np.ndarray: Returns: array[complex]: matrix representing the displacement gate. """ - return displacement(*self.params, dtype=np.complex128) + return displacement(*self.params, dtype=self._dtype) class Squeezing(Gate): @@ -165,12 +169,9 @@ class Squeezing(Gate): r (float): squeezing magnitude theta (float): squeezing angle cutoff (int): Fock ladder cutoff - - Kwargs: - tensor_id (int): identification number for the gate-tensor """ - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "Squeezing" num_wires = 1 num_params = 3 @@ -179,7 +180,7 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache @@ -189,7 +190,7 @@ def _data(self) -> np.ndarray: Returns: array[complex]: matrix representing the squeezing gate. """ - return squeezing(*self.params, dtype=np.complex128) + return squeezing(*self.params, dtype=self._dtype) class TwoModeSqueezing(Gate): @@ -199,12 +200,9 @@ class TwoModeSqueezing(Gate): r (float): squeezing magnitude theta (float): squeezing angle cutoff (int): Fock ladder cutoff - - Kwargs: - tensor_id (int): identification number for the gate-tensor """ - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "TwoModeSqueezing" num_wires = 2 num_params = 3 @@ -213,7 +211,7 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache @@ -223,7 +221,7 @@ def _data(self) -> np.ndarray: Returns: array[complex]: matrix representing the two-mode squeezing gate. """ - return two_mode_squeezing(*self.params, dtype=np.complex128) + return two_mode_squeezing(*self.params, dtype=self._dtype) class Beamsplitter(Gate): @@ -233,12 +231,9 @@ class Beamsplitter(Gate): theta (float): transmissivity angle of the beamsplitter. The transmissivity is :math:`t=\cos(\theta)` phi (float): reflection phase of the beamsplitter cutoff (int): Fock ladder cutoff - - Kwargs: - tensor_id (int): identification number for the gate-tensor """ - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "Beamsplitter" num_wires = 1 num_params = 3 @@ -247,7 +242,7 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache @@ -257,17 +252,18 @@ def _data(self) -> np.ndarray: Returns: array[complex]: matrix representing the beamsplitter gate. """ - return beamsplitter(*self.params, dtype=np.complex128) + return beamsplitter(*self.params, dtype=self._dtype) -class CNOT(Gate): - """CNOT gate +############### +# Qubit gates +############### - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - def __init__(self, *params: Number, tensor_id: int) -> None: +class CNOT(Gate): + """CNOT gate""" + + def __init__(self, *params, **kwargs) -> None: name = "CNOT" num_wires = 2 num_params = 0 @@ -276,24 +272,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """CNOT matrix""" mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class Hadamard(Gate): - """Hadamard gate + """Hadamard gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "Hadamard" num_wires = 1 num_params = 0 @@ -302,24 +294,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """Hadamard matrix""" mat = [[INV_SQRT2, INV_SQRT2], [INV_SQRT2, -INV_SQRT2]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class PauliX(Gate): - """PauliX gate + """PauliX gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "PauliX" num_wires = 1 num_params = 0 @@ -328,24 +316,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """PauliX matrix""" mat = [[0, 1], [1, 0]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class PauliY(Gate): - """PauliX gate - - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ + """PauliX gate""" - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "PauliY" num_wires = 1 num_params = 0 @@ -354,24 +338,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """PauliY matrix""" mat = [[0, -1j], [1j, 0]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class PauliZ(Gate): - """PauliZ gate - - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ + """PauliZ gate""" - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "PauliZ" num_wires = 1 num_params = 0 @@ -380,24 +360,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """PauliZ matrix""" mat = [[1, 0], [0, -1]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class S(Gate): - """The single-qubit phase gate + """The single-qubit phase gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "S" num_wires = 1 num_params = 0 @@ -406,24 +382,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """Single-qubit phase gate matrix""" mat = [[1, 0], [0, 1j]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class T(Gate): - """The single-qubit T gate - - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ + """The single-qubit T gate""" - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "T" num_wires = 1 num_params = 0 @@ -432,24 +404,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """Single-qubit T gate matrix""" mat = [[1, 0], [0, cmath.exp(0.25j * np.pi)]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class SX(Gate): - """The single-qubit Square-Root X gate + """The single-qubit Square-Root X gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "SX" num_wires = 1 num_params = 0 @@ -458,24 +426,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """Single-qubit Square-Root X operator matrix""" mat = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class CZ(Gate): - """The controlled-Z gate + """The controlled-Z gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "CZ" num_wires = 2 num_params = 0 @@ -484,24 +448,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """Controlled-Z gate matrix""" mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class CY(Gate): - """The controlled-Y gate - - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ + """The controlled-Y gate""" - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "CY" num_wires = 2 num_params = 0 @@ -510,24 +470,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """Controlled-Y operator matrix""" mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1j], [0, 0, 1j, 0]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class SWAP(Gate): - """The swap gate - - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ + """The swap gate""" - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "SWAP" num_wires = 2 num_params = 0 @@ -536,24 +492,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """Swap operator matrix""" mat = [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class ISWAP(Gate): - """The i-swap gate + """The i-swap gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "iSWAP" num_wires = 1 num_params = 0 @@ -562,24 +514,20 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: """i-swap operator matrix""" mat = [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class CSWAP(Gate): - """The CSWAP gate - - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ + """The CSWAP gate""" - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "CSWAP" num_wires = 3 num_params = 0 @@ -588,7 +536,7 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache @@ -604,17 +552,13 @@ def _data(self) -> np.ndarray: [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], ] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class Toffoli(Gate): - """The Toffoli gate + """The Toffoli gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: Number, tensor_id: int) -> None: + def __init__(self, *params, **kwargs) -> None: name = "Toffoli" num_wires = 3 num_params = 0 @@ -623,7 +567,7 @@ def __init__(self, *params: Number, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) @lru_cache @@ -639,17 +583,13 @@ def _data(self) -> np.ndarray: [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0], ] - return np.array(mat, dtype=np.complex128) + return np.array(mat, dtype=self._dtype) class RX(Gate): - """The single qubit X gate + """The single qubit X rotation gate""" - Kwargs: - tensor_id (int): identification number for the gate-tensor - """ - - def __init__(self, *params: float, tensor_id: int) -> None: + def __init__(self, *params: float, **kwargs) -> None: name = "RX" num_wires = 1 num_params = 1 @@ -658,13 +598,336 @@ def __init__(self, *params: float, tensor_id: int) -> None: raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") self.params = params - super().__init__(name, num_wires, tensor_id=tensor_id) + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single qubit X rotation matrix""" + theta = self.params[0] + c = math.cos(theta / 2) + js = 1j * math.sin(-theta / 2) + + mat = [[c, js], [js, c]] + return np.array(mat, dtype=self._dtype) + + +class RY(Gate): + """The single qubit Y rotation gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "RY" + num_wires = 1 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single qubit Y rotation matrix""" + theta = self.params[0] + + c = math.cos(theta / 2) + s = math.sin(theta / 2) + + mat = [[c, -s], [s, c]] + return np.array(mat, self._dtype) + + +class RZ(Gate): + """The single qubit Z rotation gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "RZ" + num_wires = 1 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single qubit Z rotation matrix""" + theta = self.params[0] + p = cmath.exp(-0.5j * theta) + + mat = [[p, 0], [0, np.conj(p)]] + return np.array(mat, dtype=self._dtype) + + +class PhaseShift(Gate): + """The single qubit local phase shift gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "PhaseShift" + num_wires = 1 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Single qubit local phase shift operator matrix""" + phi = self.params[0] + mat = [[1, 0], [0, cmath.exp(1j * phi)]] + + return np.array(mat, dtype=self._dtype) + + +class CPhase(Gate): + """The controlled phase shift gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "CPhase" + num_wires = 2 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Controlled phase shift operator matrix""" + phi = self.params[0] + mat = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, cmath.exp(1j * phi)] + ] + return np.array(mat, dtype=self._dtype) + +class Rot(Gate): + """The arbitrary single qubit rotation gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "Rot" + num_wires = 1 + num_params = 3 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Arbitrary single qubit rotation operator matrix""" + phi, theta, omega = self.params + c = math.cos(theta / 2) + s = math.sin(theta / 2) + + mat =[ + [cmath.exp(-0.5j * (phi + omega)) * c, -cmath.exp(0.5j * (phi - omega)) * s], + [cmath.exp(-0.5j * (phi - omega)) * s, cmath.exp(0.5j * (phi + omega)) * c], + ] + return np.array(mat, dtype=self._dtype) + + +class CRX(Gate): + """The controlled-RX gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "CRX" + num_wires = 2 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) @lru_cache def _data(self) -> np.ndarray: - """Single qubit X operator matrix""" + """Controlled-RX operator matrix""" theta = self.params[0] c = math.cos(theta / 2) js = 1j * math.sin(-theta / 2) - return np.array([[c, js], [js, c]], dtype=np.complex128) + + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, js], [0, 0, js, c]] + return np.array(mat, dtype=self._dtype) + + +class CRY(Gate): + """The controlled-RY gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "CRY" + num_wires = 2 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Controlled-RY operator matrix""" + theta = self.params[0] + c = math.cos(theta / 2) + s = math.sin(theta / 2) + + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, -s], [0, 0, s, c]] + return np.array(mat, dtype=self._dtype) + + +class CRZ(Gate): + """The controlled-RZ gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "CRZ" + num_wires = 2 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Controlled-RZ operator matrix""" + theta = self.params[0] + mat = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, cmath.exp(-0.5j * theta), 0], + [0, 0, 0, cmath.exp(0.5j * theta)], + ] + return np.array(mat, dtype=self._dtype) + + +class CRot(Gate): + """The controlled-rotation gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "CRZ" + num_wires = 2 + num_params = 3 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Controlled-rotation operator matrix""" + phi, theta, omega = self.params + c = math.cos(theta / 2) + s = math.sin(theta / 2) + + mat = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, cmath.exp(-0.5j * (phi + omega)) * c, -cmath.exp(0.5j * (phi - omega)) * s], + [0, 0, cmath.exp(-0.5j * (phi - omega)) * s, cmath.exp(0.5j * (phi + omega)) * c], + ] + return np.array(mat, dtype=self._dtype) + + +class U1(Gate): + """The U1 gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "U1" + num_wires = 1 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """U1 operator matrix""" + phi = self.params[0] + mat = [[1, 0], [0, cmath.exp(1j * phi)]] + return np.array(mat, dtype=self._dtype) + + +class U2(Gate): + """The U2 gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "U2" + num_wires = 2 + num_params = 1 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """U2 operator matrix""" + phi, lam = self.params + mat = [ + [INV_SQRT2, -INV_SQRT2 * cmath.exp(1j * lam)], + [INV_SQRT2 * cmath.exp(1j * phi), INV_SQRT2 * cmath.exp(1j * (phi + lam))] + ] + return np.array(mat, dtype=self._dtype) + + +class U3(Gate): + """The arbitrary single qubit unitary gate""" + + def __init__(self, *params, **kwargs) -> None: + name = "U3" + num_wires = 1 + num_params = 3 + + if len(params) != num_params: + raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + self.params = params + + super().__init__(name, num_wires, **kwargs) + + + @lru_cache + def _data(self) -> np.ndarray: + """Arbitrary single qubit unitary operator matrix""" + theta, phi, lam = self.params + c = math.cos(theta / 2) + s = math.sin(theta / 2) + + mat = [ + [c, -s * cmath.exp(1j * lam)], + [s * cmath.exp(1j * phi), c * cmath.exp(1j * (phi + lam))], + ] + return np.array(mat, dtype=self._dtype) From 19e7cc05d848d3992d26aabc57e6d1cf10694a59 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 1 Jun 2021 15:50:07 -0400 Subject: [PATCH 31/71] Add jet/ to formatted directories --- python/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/Makefile b/python/Makefile index 8a5894d1..2c6a8130 100644 --- a/python/Makefile +++ b/python/Makefile @@ -33,11 +33,11 @@ setup: $(.VENV_DIR)/touch .PHONY: format format: $(.VENV_DIR)/requirements.txt.touch ifdef check - $(.VENV_BIN)/black -l 100 --check tests - $(.VENV_BIN)/isort --profile black --check-only tests + $(.VENV_BIN)/black -l 100 --check jet tests + $(.VENV_BIN)/isort --profile black --check-only jet tests else - $(.VENV_BIN)/black -l 100 tests - $(.VENV_BIN)/isort --profile black tests + $(.VENV_BIN)/black -l 100 jet tests + $(.VENV_BIN)/isort --profile black jet tests endif From a1ba8ffaaf371bca645ed03741ddb538bd705e5a Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 1 Jun 2021 15:51:51 -0400 Subject: [PATCH 32/71] Refactor Gate class --- python/jet/gates.py | 69 ++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 153929c7..94f10c7c 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -1,16 +1,14 @@ -"""Tensor representations of quantum gates""" - -import math import cmath +import math from functools import lru_cache -from typing import Sequence, List, Optional +from typing import List, Optional, Sequence import numpy as np from thewalrus.fock_gradients import ( + beamsplitter, displacement, squeezing, two_mode_squeezing, - beamsplitter, ) import jet @@ -21,8 +19,7 @@ "Squeezing", "TwoModeSqueezing", "Beamsplitter", - - # qubit gates + # Qubit gates "Hadamard", "PauliX", "PauliY", @@ -55,33 +52,30 @@ "U3", ] -ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" INV_SQRT2 = 1 / math.sqrt(2) -DEFAULT_DTYPE = np.complex128 class Gate: - """Gate class - - Args: - name (str): name of the gate - num_wires (int): the number of wires the gate is applied to + def __init__(self, name: str, num_wires: int, **kwargs) -> None: + """Constructs a quantum gate. - Kwargs: - tensor_id (int): identification number for the gate-tensor - dtype (type): type to use in matrix representations of gates - """ + Args: + name: name of the gate. + num_wires: number of wires the gate is applied to. - def __init__(self, name: str, num_wires: int, **kwargs) -> None: + Kwargs: + tensor_id (int): identification number for the gate-tensor. + dtype (type): type to use in matrix representations of gates. + """ self.name = name self.tensor_id = kwargs.get("tensor_id", None) - self._dtype = kwargs.get("dtype", DEFAULT_DTYPE) + self._dtype = kwargs.get("dtype", np.complex128) self._indices = None self._num_wires = num_wires - def tensor(self, adjoint: bool = False): - """Tensor representation of gate""" + def tensor(self, adjoint: bool = False) -> jet.Tensor: + """Returns the tensor representation of this gate.""" if adjoint: data = np.conj(self._data()).T.flatten() else: @@ -89,24 +83,26 @@ def tensor(self, adjoint: bool = False): indices = self.indices if indices is None: - indices = list(ALPHABET[:2 * self._num_wires]) - shape = int(len(data) ** ((2 * self._num_wires) ** -1)) + indices = list(map(str, range(2 * self._num_wires))) - return jet.Tensor(indices, [shape] * 2 * self._num_wires, data) + dimension = int(len(data) ** (1 / len(indices))) + shape = [dimension] * len(indices) + + return jet.Tensor(indices=indices, shape=shape, data=data) def _data(self) -> np.ndarray: - """Matrix representation of the gate""" + """Returns the matrix representation of this gate.""" raise NotImplementedError("No tensor data available for generic gate.") @property def indices(self) -> Optional[List[str]]: - """Indices for connecting tensors""" + """Returns the indices of this gate for connecting tensors.""" return self._indices @indices.setter - def indices(self, indices: Optional[List[str]]) -> None: - """Setter method for indices""" - # validate that indices is a list of unique strings + def indices(self, indices: Optional[Sequence[str]]) -> None: + """Sets the indices of this gate for connecting tensors.""" + # Check that `indices is a sequence of unique strings. if ( not isinstance(indices, Sequence) or not all(isinstance(idx, str) for idx in indices) @@ -114,16 +110,13 @@ def indices(self, indices: Optional[List[str]]) -> None: ): raise ValueError("Indices must be a sequence of unique strings.") - # validate that indices has the correct lenght (or is None) - if indices is None: - self._indices = indices - elif len(indices) == 2 * self._num_wires: - self._indices = indices - else: + # Check that `indices` has the correct length (or is None) + if indices is not None and len(indices) != 2 * self._num_wires: raise ValueError( - f"Must have 2 indices per wire. Got {len(indices)} indices for" - f"{self._num_wires} wires." + f"Indices must have two indices per wire. " + f"Received {len(indices)} indices for {self._num_wires} wires." ) + self._indices = indices ################################## From 54454129ef842d5f16613c15845bbc441efc1516 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 1 Jun 2021 15:52:15 -0400 Subject: [PATCH 33/71] Apply formatter to jet/ directory --- python/jet/circuit.py | 1 + python/jet/gates.py | 169 ++++++++++++++++++++++++------------------ 2 files changed, 99 insertions(+), 71 deletions(-) diff --git a/python/jet/circuit.py b/python/jet/circuit.py index 29f2155a..9f611afe 100644 --- a/python/jet/circuit.py +++ b/python/jet/circuit.py @@ -1,6 +1,7 @@ """Contains the circuit class for building a tensor network""" import jet + from .gates import Gate diff --git a/python/jet/gates.py b/python/jet/gates.py index 94f10c7c..3d420dd5 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -56,7 +56,7 @@ class Gate: - def __init__(self, name: str, num_wires: int, **kwargs) -> None: + def __init__(self, name: str, num_wires: int, **kwargs): """Constructs a quantum gate. Args: @@ -139,12 +139,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 3 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """The matrix representation of the displacement gate @@ -170,12 +171,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 3 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """The matrix representation of the squeezing gate @@ -201,12 +203,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 3 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """The matrix representation of the two-mode squeezing gate @@ -232,12 +235,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 3 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """The matrix representation of the beamsplitter gate @@ -262,12 +266,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """CNOT matrix""" @@ -284,12 +289,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Hadamard matrix""" @@ -306,12 +312,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """PauliX matrix""" @@ -328,12 +335,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """PauliY matrix""" @@ -350,12 +358,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """PauliZ matrix""" @@ -372,12 +381,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Single-qubit phase gate matrix""" @@ -394,12 +404,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Single-qubit T gate matrix""" @@ -416,12 +427,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Single-qubit Square-Root X operator matrix""" @@ -438,12 +450,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Controlled-Z gate matrix""" @@ -460,12 +473,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Controlled-Y operator matrix""" @@ -482,12 +496,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Swap operator matrix""" @@ -504,12 +519,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """i-swap operator matrix""" @@ -526,12 +542,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """CSWAP operator matrix""" @@ -557,12 +574,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 0 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Toffoli operator matrix""" @@ -588,12 +606,13 @@ def __init__(self, *params: float, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Single qubit X rotation matrix""" @@ -614,12 +633,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Single qubit Y rotation matrix""" @@ -641,12 +661,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Single qubit Z rotation matrix""" @@ -666,12 +687,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Single qubit local phase shift operator matrix""" @@ -690,24 +712,21 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Controlled phase shift operator matrix""" phi = self.params[0] - mat = [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, cmath.exp(1j * phi)] - ] + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, cmath.exp(1j * phi)]] return np.array(mat, dtype=self._dtype) + class Rot(Gate): """The arbitrary single qubit rotation gate""" @@ -717,12 +736,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 3 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Arbitrary single qubit rotation operator matrix""" @@ -730,7 +750,7 @@ def _data(self) -> np.ndarray: c = math.cos(theta / 2) s = math.sin(theta / 2) - mat =[ + mat = [ [cmath.exp(-0.5j * (phi + omega)) * c, -cmath.exp(0.5j * (phi - omega)) * s], [cmath.exp(-0.5j * (phi - omega)) * s, cmath.exp(0.5j * (phi + omega)) * c], ] @@ -746,12 +766,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Controlled-RX operator matrix""" @@ -772,12 +793,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Controlled-RY operator matrix""" @@ -798,12 +820,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Controlled-RZ operator matrix""" @@ -826,12 +849,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 3 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Controlled-rotation operator matrix""" @@ -857,12 +881,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """U1 operator matrix""" @@ -880,19 +905,20 @@ def __init__(self, *params, **kwargs) -> None: num_params = 1 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """U2 operator matrix""" phi, lam = self.params mat = [ [INV_SQRT2, -INV_SQRT2 * cmath.exp(1j * lam)], - [INV_SQRT2 * cmath.exp(1j * phi), INV_SQRT2 * cmath.exp(1j * (phi + lam))] + [INV_SQRT2 * cmath.exp(1j * phi), INV_SQRT2 * cmath.exp(1j * (phi + lam))], ] return np.array(mat, dtype=self._dtype) @@ -906,12 +932,13 @@ def __init__(self, *params, **kwargs) -> None: num_params = 3 if len(params) != num_params: - raise ValueError(f"{len(params)} passed. The {name} gate only accepts {num_params} parameters.") + raise ValueError( + f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." + ) self.params = params super().__init__(name, num_wires, **kwargs) - @lru_cache def _data(self) -> np.ndarray: """Arbitrary single qubit unitary operator matrix""" From 0fd4c5b18a2e4231203e06b491485b2bfb2f39ec Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 1 Jun 2021 16:49:01 -0400 Subject: [PATCH 34/71] Factor parameter validation to Gate class --- python/jet/gates.py | 679 +++++++++++--------------------------------- 1 file changed, 169 insertions(+), 510 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 3d420dd5..5afb86df 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -11,10 +11,10 @@ two_mode_squeezing, ) -import jet +from jet import Tensor __all__ = [ - # CV fock gates + # CV Fock gates "Displacement", "Squeezing", "TwoModeSqueezing", @@ -56,12 +56,13 @@ class Gate: - def __init__(self, name: str, num_wires: int, **kwargs): + def __init__(self, name: str, num_wires: int, params: List, **kwargs): """Constructs a quantum gate. Args: name: name of the gate. num_wires: number of wires the gate is applied to. + params: parameters of the gate. Kwargs: tensor_id (int): identification number for the gate-tensor. @@ -73,8 +74,9 @@ def __init__(self, name: str, num_wires: int, **kwargs): self._dtype = kwargs.get("dtype", np.complex128) self._indices = None self._num_wires = num_wires + self._params = params - def tensor(self, adjoint: bool = False) -> jet.Tensor: + def tensor(self, adjoint: bool = False) -> Tensor: """Returns the tensor representation of this gate.""" if adjoint: data = np.conj(self._data()).T.flatten() @@ -88,17 +90,30 @@ def tensor(self, adjoint: bool = False) -> jet.Tensor: dimension = int(len(data) ** (1 / len(indices))) shape = [dimension] * len(indices) - return jet.Tensor(indices=indices, shape=shape, data=data) + return Tensor(indices=indices, shape=shape, data=data) def _data(self) -> np.ndarray: """Returns the matrix representation of this gate.""" raise NotImplementedError("No tensor data available for generic gate.") + def _validate(self, num_params: int): + """Throws a ValueError if the given quantity differs from the number of gate parameters.""" + if len(self.params) != num_params: + raise ValueError( + f"The {self.name} gate accepts exactly {num_params} parameters " + f"but {len(self.params)} parameters were given." + ) + @property def indices(self) -> Optional[List[str]]: """Returns the indices of this gate for connecting tensors.""" return self._indices + @property + def params(self) -> Optional[List]: + """Returns the parameters of this gate.""" + return self._params + @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: """Sets the indices of this gate for connecting tensors.""" @@ -119,182 +134,102 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: self._indices = indices -################################## +#################################################################################################### # Continuous variable Fock gates -################################## +#################################################################################################### class Displacement(Gate): - """Displacement gate - - Args: - r (float): displacement magnitude - phi (float): displacement angle - cutoff (int): Fock ladder cutoff - """ + def __init__(self, *params, **kwargs): + """Constructs a displacement gate. - def __init__(self, *params, **kwargs) -> None: - name = "Displacement" - num_wires = 1 - num_params = 3 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + Args: + r (float): displacement magnitude. + phi (float): displacement angle. + cutoff (int): Fock ladder cutoff. + """ + super().__init__(name="Displacement", num_wires=1, *params, **kwargs) + self._validate(num_params=3) @lru_cache def _data(self) -> np.ndarray: - """The matrix representation of the displacement gate - - Returns: - array[complex]: matrix representing the displacement gate. - """ return displacement(*self.params, dtype=self._dtype) class Squeezing(Gate): - """Squeezing gate - - Args: - r (float): squeezing magnitude - theta (float): squeezing angle - cutoff (int): Fock ladder cutoff - """ + def __init__(self, *params, **kwargs): + """Constructs a squeezing gate. - def __init__(self, *params, **kwargs) -> None: - name = "Squeezing" - num_wires = 1 - num_params = 3 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + Args: + r (float): squeezing magnitude. + theta (float): squeezing angle. + cutoff (int): Fock ladder cutoff. + """ + super().__init__(name="Squeezing", num_wires=1, *params, **kwargs) + self._validate(num_params=3) @lru_cache def _data(self) -> np.ndarray: - """The matrix representation of the squeezing gate - - Returns: - array[complex]: matrix representing the squeezing gate. - """ return squeezing(*self.params, dtype=self._dtype) class TwoModeSqueezing(Gate): - """TwoModeSqueezing gate + def __init__(self, *params, **kwargs): + """Constructs a two-mode squeezing gate. - Args: - r (float): squeezing magnitude - theta (float): squeezing angle - cutoff (int): Fock ladder cutoff - """ - - def __init__(self, *params, **kwargs) -> None: - name = "TwoModeSqueezing" - num_wires = 2 - num_params = 3 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + Args: + r (float): squeezing magnitude. + theta (float): squeezing angle. + cutoff (int): Fock ladder cutoff. + """ + super().__init__(name="TwoModeSqueezing", num_wires=2, *params, **kwargs) + self._validate(num_params=3) @lru_cache def _data(self) -> np.ndarray: - """The matrix representation of the two-mode squeezing gate - - Returns: - array[complex]: matrix representing the two-mode squeezing gate. - """ return two_mode_squeezing(*self.params, dtype=self._dtype) class Beamsplitter(Gate): - """Beamsplitter gate - - Args: - theta (float): transmissivity angle of the beamsplitter. The transmissivity is :math:`t=\cos(\theta)` - phi (float): reflection phase of the beamsplitter - cutoff (int): Fock ladder cutoff - """ - - def __init__(self, *params, **kwargs) -> None: - name = "Beamsplitter" - num_wires = 1 - num_params = 3 + def __init__(self, *params, **kwargs): + """Constructs a beamsplitter gate. - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + Args: + theta (float): transmissivity angle of the beamsplitter. The transmissivity is + :math:`t=\\cos(\\theta)`. + phi (float): reflection phase of the beamsplitter. + cutoff (int): Fock ladder cutoff. + """ + super().__init__(name="Beamsplitter", num_wires=1, *params, **kwargs) + self._validate(num_params=3) @lru_cache def _data(self) -> np.ndarray: - """The matrix representation of the beamsplitter gate - - Returns: - array[complex]: matrix representing the beamsplitter gate. - """ return beamsplitter(*self.params, dtype=self._dtype) -############### +#################################################################################################### # Qubit gates -############### +#################################################################################################### class CNOT(Gate): - """CNOT gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CNOT" - num_wires = 2 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a CNOT gate.""" + super().__init__(name="CNOT", num_wires=2, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """CNOT matrix""" mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] return np.array(mat, dtype=self._dtype) class Hadamard(Gate): - """Hadamard gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "Hadamard" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a Hadamard gate.""" + super().__init__(name="Hadamard", num_wires=1, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -304,254 +239,133 @@ def _data(self) -> np.ndarray: class PauliX(Gate): - """PauliX gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "PauliX" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a PauliX gate.""" + super().__init__(name="PauliX", num_wires=1, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """PauliX matrix""" mat = [[0, 1], [1, 0]] return np.array(mat, dtype=self._dtype) class PauliY(Gate): - """PauliX gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "PauliY" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a PauliY gate.""" + super().__init__(name="PauliY", num_wires=1, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """PauliY matrix""" mat = [[0, -1j], [1j, 0]] return np.array(mat, dtype=self._dtype) class PauliZ(Gate): - """PauliZ gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "PauliZ" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a PauliZ gate.""" + super().__init__(name="PauliZ", num_wires=1, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """PauliZ matrix""" mat = [[1, 0], [0, -1]] return np.array(mat, dtype=self._dtype) class S(Gate): - """The single-qubit phase gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "S" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a single-qubit phase gate.""" + super().__init__(name="S", num_wires=1, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """Single-qubit phase gate matrix""" mat = [[1, 0], [0, 1j]] return np.array(mat, dtype=self._dtype) class T(Gate): - """The single-qubit T gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "T" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a single-qubit T gate.""" + super().__init__(name="T", num_wires=1, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """Single-qubit T gate matrix""" mat = [[1, 0], [0, cmath.exp(0.25j * np.pi)]] return np.array(mat, dtype=self._dtype) class SX(Gate): - """The single-qubit Square-Root X gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "SX" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a single-qubit Square-Root X gate.""" + super().__init__(name="SX", num_wires=1, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """Single-qubit Square-Root X operator matrix""" mat = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]] return np.array(mat, dtype=self._dtype) class CZ(Gate): - """The controlled-Z gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CZ" - num_wires = 2 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a controlled-Z gate.""" + super().__init__(name="CZ", num_wires=2, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """Controlled-Z gate matrix""" mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] return np.array(mat, dtype=self._dtype) class CY(Gate): - """The controlled-Y gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CY" - num_wires = 2 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a controlled-Y gate.""" + super().__init__(name="CY", num_wires=2, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """Controlled-Y operator matrix""" mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1j], [0, 0, 1j, 0]] return np.array(mat, dtype=self._dtype) class SWAP(Gate): - """The swap gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "SWAP" - num_wires = 2 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a SWAP gate.""" + super().__init__(name="SWAP", num_wires=2, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """Swap operator matrix""" mat = [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] return np.array(mat, dtype=self._dtype) class ISWAP(Gate): - """The i-swap gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "iSWAP" - num_wires = 1 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs an ISWAP gate.""" + super().__init__(name="ISWAP", num_wires=2, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """i-swap operator matrix""" mat = [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]] return np.array(mat, dtype=self._dtype) class CSWAP(Gate): - """The CSWAP gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CSWAP" - num_wires = 3 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a CSWAP gate.""" + super().__init__(name="CSWAP", num_wires=3, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """CSWAP operator matrix""" mat = [ [1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], @@ -566,24 +380,13 @@ def _data(self) -> np.ndarray: class Toffoli(Gate): - """The Toffoli gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "Toffoli" - num_wires = 3 - num_params = 0 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a Toffoli gate.""" + super().__init__(name="Toffoli", num_wires=3, *params, **kwargs) + self._validate(num_params=0) @lru_cache def _data(self) -> np.ndarray: - """Toffoli operator matrix""" mat = [ [1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], @@ -598,24 +401,13 @@ def _data(self) -> np.ndarray: class RX(Gate): - """The single qubit X rotation gate""" - - def __init__(self, *params: float, **kwargs) -> None: - name = "RX" - num_wires = 1 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a single-qubit X rotation gate.""" + super().__init__(name="RX", num_wires=1, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Single qubit X rotation matrix""" theta = self.params[0] c = math.cos(theta / 2) js = 1j * math.sin(-theta / 2) @@ -625,24 +417,13 @@ def _data(self) -> np.ndarray: class RY(Gate): - """The single qubit Y rotation gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "RY" - num_wires = 1 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a single-qubit Y rotation gate.""" + super().__init__(name="RY", num_wires=1, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Single qubit Y rotation matrix""" theta = self.params[0] c = math.cos(theta / 2) @@ -653,24 +434,13 @@ def _data(self) -> np.ndarray: class RZ(Gate): - """The single qubit Z rotation gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "RZ" - num_wires = 1 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a single-qubit Z rotation gate.""" + super().__init__(name="RZ", num_wires=1, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Single qubit Z rotation matrix""" theta = self.params[0] p = cmath.exp(-0.5j * theta) @@ -679,73 +449,39 @@ def _data(self) -> np.ndarray: class PhaseShift(Gate): - """The single qubit local phase shift gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "PhaseShift" - num_wires = 1 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a single-qubit local phase shift gate.""" + super().__init__(name="PhaseShift", num_wires=1, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Single qubit local phase shift operator matrix""" phi = self.params[0] mat = [[1, 0], [0, cmath.exp(1j * phi)]] - return np.array(mat, dtype=self._dtype) class CPhase(Gate): - """The controlled phase shift gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CPhase" - num_wires = 2 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a controlled phase shift gate.""" + super().__init__(name="CPhase", num_wires=2, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Controlled phase shift operator matrix""" phi = self.params[0] mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, cmath.exp(1j * phi)]] return np.array(mat, dtype=self._dtype) class Rot(Gate): - """The arbitrary single qubit rotation gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "Rot" - num_wires = 1 - num_params = 3 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs an arbitrary single-qubit rotation gate.""" + super().__init__(name="Rot", num_wires=1, *params, **kwargs) + self._validate(num_params=3) @lru_cache def _data(self) -> np.ndarray: - """Arbitrary single qubit rotation operator matrix""" phi, theta, omega = self.params c = math.cos(theta / 2) s = math.sin(theta / 2) @@ -758,24 +494,13 @@ def _data(self) -> np.ndarray: class CRX(Gate): - """The controlled-RX gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CRX" - num_wires = 2 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a controlled-RX gate.""" + super().__init__(name="CRX", num_wires=2, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Controlled-RX operator matrix""" theta = self.params[0] c = math.cos(theta / 2) js = 1j * math.sin(-theta / 2) @@ -785,24 +510,13 @@ def _data(self) -> np.ndarray: class CRY(Gate): - """The controlled-RY gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CRY" - num_wires = 2 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a controlled-RY gate.""" + super().__init__(name="CRY", num_wires=2, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Controlled-RY operator matrix""" theta = self.params[0] c = math.cos(theta / 2) s = math.sin(theta / 2) @@ -812,24 +526,13 @@ def _data(self) -> np.ndarray: class CRZ(Gate): - """The controlled-RZ gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CRZ" - num_wires = 2 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a controlled-RZ gate.""" + super().__init__(name="CRZ", num_wires=2, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """Controlled-RZ operator matrix""" theta = self.params[0] mat = [ [1, 0, 0, 0], @@ -841,24 +544,13 @@ def _data(self) -> np.ndarray: class CRot(Gate): - """The controlled-rotation gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "CRZ" - num_wires = 2 - num_params = 3 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a controlled-rotation gate.""" + super().__init__(name="CRot", num_wires=2, *params, **kwargs) + self._validate(num_params=3) @lru_cache def _data(self) -> np.ndarray: - """Controlled-rotation operator matrix""" phi, theta, omega = self.params c = math.cos(theta / 2) s = math.sin(theta / 2) @@ -873,48 +565,26 @@ def _data(self) -> np.ndarray: class U1(Gate): - """The U1 gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "U1" - num_wires = 1 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a U1 gate.""" + super().__init__(name="U1", num_wires=1, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """U1 operator matrix""" phi = self.params[0] mat = [[1, 0], [0, cmath.exp(1j * phi)]] return np.array(mat, dtype=self._dtype) class U2(Gate): - """The U2 gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "U2" - num_wires = 2 - num_params = 1 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs a U2 gate.""" + super().__init__(name="U2", num_wires=2, *params, **kwargs) + self._validate(num_params=1) @lru_cache def _data(self) -> np.ndarray: - """U2 operator matrix""" phi, lam = self.params mat = [ [INV_SQRT2, -INV_SQRT2 * cmath.exp(1j * lam)], @@ -924,24 +594,13 @@ def _data(self) -> np.ndarray: class U3(Gate): - """The arbitrary single qubit unitary gate""" - - def __init__(self, *params, **kwargs) -> None: - name = "U3" - num_wires = 1 - num_params = 3 - - if len(params) != num_params: - raise ValueError( - f"{len(params)} passed. The {name} gate only accepts {num_params} parameters." - ) - self.params = params - - super().__init__(name, num_wires, **kwargs) + def __init__(self, *params, **kwargs): + """Constructs an arbitrary single-qubit unitary gate.""" + super().__init__(name="U3", num_wires=1, *params, **kwargs) + self._validate(num_params=3) @lru_cache def _data(self) -> np.ndarray: - """Arbitrary single qubit unitary operator matrix""" theta, phi, lam = self.params c = math.cos(theta / 2) s = math.sin(theta / 2) From dc54995bbc15675b88a7403c03d9c4065923cc88 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 1 Jun 2021 16:58:52 -0400 Subject: [PATCH 35/71] Remove positional arguments after keyword arguments --- python/jet/gates.py | 76 ++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 5afb86df..a65e5852 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -56,13 +56,13 @@ class Gate: - def __init__(self, name: str, num_wires: int, params: List, **kwargs): + def __init__(self, name: str, num_wires: int, *params, **kwargs): """Constructs a quantum gate. Args: name: name of the gate. num_wires: number of wires the gate is applied to. - params: parameters of the gate. + params: gate parameters. Kwargs: tensor_id (int): identification number for the gate-tensor. @@ -109,11 +109,6 @@ def indices(self) -> Optional[List[str]]: """Returns the indices of this gate for connecting tensors.""" return self._indices - @property - def params(self) -> Optional[List]: - """Returns the parameters of this gate.""" - return self._params - @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: """Sets the indices of this gate for connecting tensors.""" @@ -133,6 +128,11 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: ) self._indices = indices + @property + def params(self) -> Optional[List]: + """Returns the parameters of this gate.""" + return self._params + #################################################################################################### # Continuous variable Fock gates @@ -148,7 +148,7 @@ def __init__(self, *params, **kwargs): phi (float): displacement angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Displacement", num_wires=1, *params, **kwargs) + super().__init__(name="Displacement", num_wires=1, params=params, **kwargs) self._validate(num_params=3) @lru_cache @@ -165,7 +165,7 @@ def __init__(self, *params, **kwargs): theta (float): squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Squeezing", num_wires=1, *params, **kwargs) + super().__init__(name="Squeezing", num_wires=1, params=params, **kwargs) self._validate(num_params=3) @lru_cache @@ -182,7 +182,7 @@ def __init__(self, *params, **kwargs): theta (float): squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="TwoModeSqueezing", num_wires=2, *params, **kwargs) + super().__init__(name="TwoModeSqueezing", num_wires=2, params=params, **kwargs) self._validate(num_params=3) @lru_cache @@ -200,7 +200,7 @@ def __init__(self, *params, **kwargs): phi (float): reflection phase of the beamsplitter. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Beamsplitter", num_wires=1, *params, **kwargs) + super().__init__(name="Beamsplitter", num_wires=1, params=params, **kwargs) self._validate(num_params=3) @lru_cache @@ -216,7 +216,7 @@ def _data(self) -> np.ndarray: class CNOT(Gate): def __init__(self, *params, **kwargs): """Constructs a CNOT gate.""" - super().__init__(name="CNOT", num_wires=2, *params, **kwargs) + super().__init__(name="CNOT", num_wires=2, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -228,7 +228,7 @@ def _data(self) -> np.ndarray: class Hadamard(Gate): def __init__(self, *params, **kwargs): """Constructs a Hadamard gate.""" - super().__init__(name="Hadamard", num_wires=1, *params, **kwargs) + super().__init__(name="Hadamard", num_wires=1, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -241,7 +241,7 @@ def _data(self) -> np.ndarray: class PauliX(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliX gate.""" - super().__init__(name="PauliX", num_wires=1, *params, **kwargs) + super().__init__(name="PauliX", num_wires=1, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -253,7 +253,7 @@ def _data(self) -> np.ndarray: class PauliY(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliY gate.""" - super().__init__(name="PauliY", num_wires=1, *params, **kwargs) + super().__init__(name="PauliY", num_wires=1, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -265,7 +265,7 @@ def _data(self) -> np.ndarray: class PauliZ(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliZ gate.""" - super().__init__(name="PauliZ", num_wires=1, *params, **kwargs) + super().__init__(name="PauliZ", num_wires=1, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -277,7 +277,7 @@ def _data(self) -> np.ndarray: class S(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit phase gate.""" - super().__init__(name="S", num_wires=1, *params, **kwargs) + super().__init__(name="S", num_wires=1, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -289,7 +289,7 @@ def _data(self) -> np.ndarray: class T(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit T gate.""" - super().__init__(name="T", num_wires=1, *params, **kwargs) + super().__init__(name="T", num_wires=1, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -301,7 +301,7 @@ def _data(self) -> np.ndarray: class SX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Square-Root X gate.""" - super().__init__(name="SX", num_wires=1, *params, **kwargs) + super().__init__(name="SX", num_wires=1, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -313,7 +313,7 @@ def _data(self) -> np.ndarray: class CZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Z gate.""" - super().__init__(name="CZ", num_wires=2, *params, **kwargs) + super().__init__(name="CZ", num_wires=2, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -325,7 +325,7 @@ def _data(self) -> np.ndarray: class CY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Y gate.""" - super().__init__(name="CY", num_wires=2, *params, **kwargs) + super().__init__(name="CY", num_wires=2, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -337,7 +337,7 @@ def _data(self) -> np.ndarray: class SWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a SWAP gate.""" - super().__init__(name="SWAP", num_wires=2, *params, **kwargs) + super().__init__(name="SWAP", num_wires=2, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -349,7 +349,7 @@ def _data(self) -> np.ndarray: class ISWAP(Gate): def __init__(self, *params, **kwargs): """Constructs an ISWAP gate.""" - super().__init__(name="ISWAP", num_wires=2, *params, **kwargs) + super().__init__(name="ISWAP", num_wires=2, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -361,7 +361,7 @@ def _data(self) -> np.ndarray: class CSWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a CSWAP gate.""" - super().__init__(name="CSWAP", num_wires=3, *params, **kwargs) + super().__init__(name="CSWAP", num_wires=3, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -382,7 +382,7 @@ def _data(self) -> np.ndarray: class Toffoli(Gate): def __init__(self, *params, **kwargs): """Constructs a Toffoli gate.""" - super().__init__(name="Toffoli", num_wires=3, *params, **kwargs) + super().__init__(name="Toffoli", num_wires=3, params=params, **kwargs) self._validate(num_params=0) @lru_cache @@ -403,7 +403,7 @@ def _data(self) -> np.ndarray: class RX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit X rotation gate.""" - super().__init__(name="RX", num_wires=1, *params, **kwargs) + super().__init__(name="RX", num_wires=1, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -419,7 +419,7 @@ def _data(self) -> np.ndarray: class RY(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Y rotation gate.""" - super().__init__(name="RY", num_wires=1, *params, **kwargs) + super().__init__(name="RY", num_wires=1, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -436,7 +436,7 @@ def _data(self) -> np.ndarray: class RZ(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Z rotation gate.""" - super().__init__(name="RZ", num_wires=1, *params, **kwargs) + super().__init__(name="RZ", num_wires=1, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -451,7 +451,7 @@ def _data(self) -> np.ndarray: class PhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit local phase shift gate.""" - super().__init__(name="PhaseShift", num_wires=1, *params, **kwargs) + super().__init__(name="PhaseShift", num_wires=1, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -464,7 +464,7 @@ def _data(self) -> np.ndarray: class CPhase(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled phase shift gate.""" - super().__init__(name="CPhase", num_wires=2, *params, **kwargs) + super().__init__(name="CPhase", num_wires=2, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -477,7 +477,7 @@ def _data(self) -> np.ndarray: class Rot(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit rotation gate.""" - super().__init__(name="Rot", num_wires=1, *params, **kwargs) + super().__init__(name="Rot", num_wires=1, params=params, **kwargs) self._validate(num_params=3) @lru_cache @@ -496,7 +496,7 @@ def _data(self) -> np.ndarray: class CRX(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RX gate.""" - super().__init__(name="CRX", num_wires=2, *params, **kwargs) + super().__init__(name="CRX", num_wires=2, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -512,7 +512,7 @@ def _data(self) -> np.ndarray: class CRY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RY gate.""" - super().__init__(name="CRY", num_wires=2, *params, **kwargs) + super().__init__(name="CRY", num_wires=2, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -528,7 +528,7 @@ def _data(self) -> np.ndarray: class CRZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RZ gate.""" - super().__init__(name="CRZ", num_wires=2, *params, **kwargs) + super().__init__(name="CRZ", num_wires=2, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -546,7 +546,7 @@ def _data(self) -> np.ndarray: class CRot(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-rotation gate.""" - super().__init__(name="CRot", num_wires=2, *params, **kwargs) + super().__init__(name="CRot", num_wires=2, params=params, **kwargs) self._validate(num_params=3) @lru_cache @@ -567,7 +567,7 @@ def _data(self) -> np.ndarray: class U1(Gate): def __init__(self, *params, **kwargs): """Constructs a U1 gate.""" - super().__init__(name="U1", num_wires=1, *params, **kwargs) + super().__init__(name="U1", num_wires=1, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -580,7 +580,7 @@ def _data(self) -> np.ndarray: class U2(Gate): def __init__(self, *params, **kwargs): """Constructs a U2 gate.""" - super().__init__(name="U2", num_wires=2, *params, **kwargs) + super().__init__(name="U2", num_wires=2, params=params, **kwargs) self._validate(num_params=1) @lru_cache @@ -596,7 +596,7 @@ def _data(self) -> np.ndarray: class U3(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit unitary gate.""" - super().__init__(name="U3", num_wires=1, *params, **kwargs) + super().__init__(name="U3", num_wires=1, params=params, **kwargs) self._validate(num_params=3) @lru_cache From aac1fab54ca363b115c7d4eb9330557052604ad3 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 2 Jun 2021 10:57:01 -0400 Subject: [PATCH 36/71] Move dtype factories to separate module to avoid circular dependency --- python/jet/__init__.py | 86 ++++------------------------------------ python/jet/factory.py | 89 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 78 deletions(-) create mode 100644 python/jet/factory.py diff --git a/python/jet/__init__.py b/python/jet/__init__.py index 7573d449..bbb2b6a1 100644 --- a/python/jet/__init__.py +++ b/python/jet/__init__.py @@ -1,83 +1,13 @@ -from typing import Union - -import numpy as np - # The existence of a Python binding is proof of its intention to be exposed. from .bindings import * - - -def TaskBasedCpuContractor( - *args, **kwargs -) -> Union[TaskBasedCpuContractorC64, TaskBasedCpuContractorC128]: - """Constructs a task-based CPU contractor with the specified data type. If a - `dtype` keyword argument is not provided, a TaskBasedCpuContractorC128 - instance will be returned. - """ - dtype = kwargs.pop("dtype", "complex128") - if np.dtype(dtype) == np.complex64: - return TaskBasedCpuContractorC64(*args, **kwargs) - elif np.dtype(dtype) == np.complex128: - return TaskBasedCpuContractorC128(*args, **kwargs) - else: - raise TypeError(f"Data type '{dtype}' is not supported.") - - -def Tensor(*args, **kwargs) -> Union[TensorC64, TensorC128]: - """Constructs a tensor with the specified data type. If a `dtype` keyword - argument is not provided, a TensorC128 instance will be returned. - """ - dtype = kwargs.pop("dtype", "complex128") - if np.dtype(dtype) == np.complex64: - return TensorC64(*args, **kwargs) - elif np.dtype(dtype) == np.complex128: - return TensorC128(*args, **kwargs) - else: - raise TypeError(f"Data type '{dtype}' is not supported.") - - -def TensorNetwork(*args, **kwargs) -> Union[TensorNetworkC64, TensorNetworkC128]: - """Constructs a tensor network with the specified data type. If a `dtype` - keyword argument is not provided, a TensorNetworkC128 instance will be - returned. - """ - dtype = kwargs.pop("dtype", "complex128") - if np.dtype(dtype) == np.complex64: - return TensorNetworkC64(*args, **kwargs) - elif np.dtype(dtype) == np.complex128: - return TensorNetworkC128(*args, **kwargs) - else: - raise TypeError(f"Data type '{dtype}' is not supported.") - - -def TensorNetworkFile(*args, **kwargs) -> Union[TensorNetworkFileC64, TensorNetworkFileC128]: - """Constructs a tensor network file with the specified data type. If a - `dtype` keyword argument is not provided, a TensorNetworkFileC128 instance - will be returned. - """ - dtype = kwargs.pop("dtype", "complex128") - if np.dtype(dtype) == np.complex64: - return TensorNetworkFileC64(*args, **kwargs) - elif np.dtype(dtype) == np.complex128: - return TensorNetworkFileC128(*args, **kwargs) - else: - raise TypeError(f"Data type '{dtype}' is not supported.") - - -def TensorNetworkSerializer( - *args, **kwargs -) -> Union[TensorNetworkSerializerC64, TensorNetworkSerializerC128]: - """Constructs a tensor network serializer with the specified data type. If a - `dtype` keyword argument is not provided, a TensorNetworkSerializerC128 - instance will be returned. - """ - dtype = kwargs.pop("dtype", "complex128") - if np.dtype(dtype) == np.complex64: - return TensorNetworkSerializerC64(*args, **kwargs) - elif np.dtype(dtype) == np.complex128: - return TensorNetworkSerializerC128(*args, **kwargs) - else: - raise TypeError(f"Data type '{dtype}' is not supported.") - +from .factory import ( + TaskBasedCpuContractor, + Tensor, + TensorNetwork, + TensorNetworkFile, + TensorNetworkSerializer, +) +from .gates import Gate # Grab the current Jet version from the C++ headers. __version__ = version() diff --git a/python/jet/factory.py b/python/jet/factory.py new file mode 100644 index 00000000..b172e27f --- /dev/null +++ b/python/jet/factory.py @@ -0,0 +1,89 @@ +from typing import Union + +import numpy as np + +from .bindings import ( + TaskBasedCpuContractorC64, + TaskBasedCpuContractorC128, + TensorC64, + TensorC128, + TensorNetworkC64, + TensorNetworkC128, + TensorNetworkFileC64, + TensorNetworkFileC128, + TensorNetworkSerializerC64, + TensorNetworkSerializerC128, +) + + +def TaskBasedCpuContractor( + *args, **kwargs +) -> Union[TaskBasedCpuContractorC64, TaskBasedCpuContractorC128]: + """Constructs a task-based CPU contractor with the specified data type. If a + `dtype` keyword argument is not provided, a TaskBasedCpuContractorC128 + instance will be returned. + """ + dtype = kwargs.pop("dtype", "complex128") + if np.dtype(dtype) == np.complex64: + return TaskBasedCpuContractorC64(*args, **kwargs) + elif np.dtype(dtype) == np.complex128: + return TaskBasedCpuContractorC128(*args, **kwargs) + else: + raise TypeError(f"Data type '{dtype}' is not supported.") + + +def Tensor(*args, **kwargs) -> Union[TensorC64, TensorC128]: + """Constructs a tensor with the specified data type. If a `dtype` keyword + argument is not provided, a TensorC128 instance will be returned. + """ + dtype = kwargs.pop("dtype", "complex128") + if np.dtype(dtype) == np.complex64: + return TensorC64(*args, **kwargs) + elif np.dtype(dtype) == np.complex128: + return TensorC128(*args, **kwargs) + else: + raise TypeError(f"Data type '{dtype}' is not supported.") + + +def TensorNetwork(*args, **kwargs) -> Union[TensorNetworkC64, TensorNetworkC128]: + """Constructs a tensor network with the specified data type. If a `dtype` + keyword argument is not provided, a TensorNetworkC128 instance will be + returned. + """ + dtype = kwargs.pop("dtype", "complex128") + if np.dtype(dtype) == np.complex64: + return TensorNetworkC64(*args, **kwargs) + elif np.dtype(dtype) == np.complex128: + return TensorNetworkC128(*args, **kwargs) + else: + raise TypeError(f"Data type '{dtype}' is not supported.") + + +def TensorNetworkFile(*args, **kwargs) -> Union[TensorNetworkFileC64, TensorNetworkFileC128]: + """Constructs a tensor network file with the specified data type. If a + `dtype` keyword argument is not provided, a TensorNetworkFileC128 instance + will be returned. + """ + dtype = kwargs.pop("dtype", "complex128") + if np.dtype(dtype) == np.complex64: + return TensorNetworkFileC64(*args, **kwargs) + elif np.dtype(dtype) == np.complex128: + return TensorNetworkFileC128(*args, **kwargs) + else: + raise TypeError(f"Data type '{dtype}' is not supported.") + + +def TensorNetworkSerializer( + *args, **kwargs +) -> Union[TensorNetworkSerializerC64, TensorNetworkSerializerC128]: + """Constructs a tensor network serializer with the specified data type. If a + `dtype` keyword argument is not provided, a TensorNetworkSerializerC128 + instance will be returned. + """ + dtype = kwargs.pop("dtype", "complex128") + if np.dtype(dtype) == np.complex64: + return TensorNetworkSerializerC64(*args, **kwargs) + elif np.dtype(dtype) == np.complex128: + return TensorNetworkSerializerC128(*args, **kwargs) + else: + raise TypeError(f"Data type '{dtype}' is not supported.") From e878297f932410fedc7d341b2e5a13541754417d Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 2 Jun 2021 10:57:41 -0400 Subject: [PATCH 37/71] Add unit tests for the Gate class --- python/jet/gates.py | 50 ++++++++++++----------- python/tests/jet/test_gates.py | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 python/tests/jet/test_gates.py diff --git a/python/jet/gates.py b/python/jet/gates.py index a65e5852..4f6af9fc 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -1,7 +1,7 @@ import cmath import math from functools import lru_cache -from typing import List, Optional, Sequence +from typing import List, Optional, Sequence, Union import numpy as np from thewalrus.fock_gradients import ( @@ -11,7 +11,8 @@ two_mode_squeezing, ) -from jet import Tensor +from .bindings import TensorC64, TensorC128 +from .factory import Tensor __all__ = [ # CV Fock gates @@ -24,8 +25,6 @@ "PauliX", "PauliY", "PauliZ", - "PauliRot", - "MultiRZ", "S", "T", "SX", @@ -40,8 +39,7 @@ "RY", "RZ", "PhaseShift", - "ControlledPhaseShift", - "CPhase", + "CPhaseShift", "Rot", "CRX", "CRY", @@ -52,21 +50,21 @@ "U3", ] -INV_SQRT2 = 1 / math.sqrt(2) +INV_SQRT2 = math.sqrt(2) / 2 class Gate: - def __init__(self, name: str, num_wires: int, *params, **kwargs): + def __init__(self, name: str, num_wires: int, **kwargs): """Constructs a quantum gate. Args: name: name of the gate. num_wires: number of wires the gate is applied to. - params: gate parameters. Kwargs: - tensor_id (int): identification number for the gate-tensor. dtype (type): type to use in matrix representations of gates. + params (list): gate parameters. + tensor_id (int): identification number for the gate-tensor. """ self.name = name self.tensor_id = kwargs.get("tensor_id", None) @@ -74,9 +72,9 @@ def __init__(self, name: str, num_wires: int, *params, **kwargs): self._dtype = kwargs.get("dtype", np.complex128) self._indices = None self._num_wires = num_wires - self._params = params + self._params = kwargs.get("params", []) - def tensor(self, adjoint: bool = False) -> Tensor: + def tensor(self, adjoint: bool = False) -> Union[TensorC64, TensorC128]: """Returns the tensor representation of this gate.""" if adjoint: data = np.conj(self._data()).T.flatten() @@ -87,21 +85,22 @@ def tensor(self, adjoint: bool = False) -> Tensor: if indices is None: indices = list(map(str, range(2 * self._num_wires))) - dimension = int(len(data) ** (1 / len(indices))) + dimension = int(round(len(data) ** (1 / len(indices)))) shape = [dimension] * len(indices) - return Tensor(indices=indices, shape=shape, data=data) + return Tensor(indices=indices, shape=shape, data=data, dtype=self._dtype) def _data(self) -> np.ndarray: """Returns the matrix representation of this gate.""" raise NotImplementedError("No tensor data available for generic gate.") - def _validate(self, num_params: int): + def _validate(self, want_num_params: int): """Throws a ValueError if the given quantity differs from the number of gate parameters.""" - if len(self.params) != num_params: + have_num_params = 0 if self.params is None else len(self.params) + if have_num_params != want_num_params: raise ValueError( - f"The {self.name} gate accepts exactly {num_params} parameters " - f"but {len(self.params)} parameters were given." + f"The {self.name} gate accepts exactly {want_num_params} parameters " + f"but {have_num_params} parameters were given." ) @property @@ -112,20 +111,25 @@ def indices(self) -> Optional[List[str]]: @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: """Sets the indices of this gate for connecting tensors.""" + # Skip the sequence property checks if `indices` is None. + if indices is None: + pass + # Check that `indices is a sequence of unique strings. - if ( + elif ( not isinstance(indices, Sequence) or not all(isinstance(idx, str) for idx in indices) or len(set(indices)) != len(indices) ): raise ValueError("Indices must be a sequence of unique strings.") - # Check that `indices` has the correct length (or is None) - if indices is not None and len(indices) != 2 * self._num_wires: + # Check that `indices` has the correct length (or is None). + elif len(indices) != 2 * self._num_wires: raise ValueError( f"Indices must have two indices per wire. " f"Received {len(indices)} indices for {self._num_wires} wires." ) + self._indices = indices @property @@ -461,10 +465,10 @@ def _data(self) -> np.ndarray: return np.array(mat, dtype=self._dtype) -class CPhase(Gate): +class CPhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled phase shift gate.""" - super().__init__(name="CPhase", num_wires=2, params=params, **kwargs) + super().__init__(name="CPhaseShift", num_wires=2, params=params, **kwargs) self._validate(num_params=1) @lru_cache diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py new file mode 100644 index 00000000..e8ddec3e --- /dev/null +++ b/python/tests/jet/test_gates.py @@ -0,0 +1,74 @@ +import numpy as np +import pytest + +from jet import Gate + + +class TestGate: + @pytest.fixture + def gate(self): + """Returns a generic gate with two wires and one parameter.""" + return Gate(name="G", num_wires=2, params=[1]) + + def test_tensor_indices_not_set(self, gate): + """Tests that the correct tensor is returned for a gate with unset indices.""" + gate._data = lambda: np.arange(16) + tensor = gate.tensor() + + assert tensor.indices == ["0", "1", "2", "3"] + assert tensor.shape == [2, 2, 2, 2] + assert tensor.data == list(range(16)) + + def test_tensor_indices_are_set(self, gate): + """Tests that the correct tensor is returned for a gate with specified indices.""" + gate._data = lambda: np.arange(3 ** 4) + gate.indices = ["r", "g", "b", "a"] + tensor = gate.tensor() + + assert tensor.indices == ["r", "g", "b", "a"] + assert tensor.shape == [3, 3, 3, 3] + assert tensor.data == list(range(3 ** 4)) + + def test_tensor_adjoint(self, gate): + """Tests that the correct adjoint tensor is returned for a gate.""" + gate._data = lambda: np.array([1 + 2j]) + tensor = gate.tensor(adjoint=True) + + assert tensor.indices == ["0", "1", "2", "3"] + assert tensor.shape == [1, 1, 1, 1] + assert tensor.data == [1 - 2j] + + def test_data(self, gate): + """Tests that a NotImplementedError is raised when the data of a generic + gate is retrieved. + """ + with pytest.raises(NotImplementedError): + gate._data() + + def test_validate_wrong_number_of_parameters(self, gate): + """Tests that a ValueError is raised when a gate with the wrong number + of parameters is validated. + """ + with pytest.raises(ValueError): + gate._validate(want_num_params=2) + + def test_validate_correct_number_of_parameters(self, gate): + """Tests that no exceptions are raised when a gate with the correct + number of parameters is validated. + """ + gate._validate(want_num_params=1) + + @pytest.mark.parametrize("indices", [1, ["i", 2, "k"], ["x", "x"], []]) + def test_indices_are_invalid(self, gate, indices): + """Tests that a ValueError is raised when the indices of a gate are set + to an invalid value. + """ + with pytest.raises(ValueError): + gate.indices = indices + + @pytest.mark.parametrize("indices", [None, ["1", "2", "3", "4"]]) + def test_indices_are_valid(self, gate, indices): + """Tests that the indices of a gate can be set and retrieved.""" + assert gate.indices is None + gate.indices = indices + assert gate.indices == indices From 24f0ba71e3f6c79ed05dddf76a6422392871f3cf Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 2 Jun 2021 14:31:37 -0400 Subject: [PATCH 38/71] Delegate package exports to modules --- python/jet/__init__.py | 12 ++++-------- python/jet/factory.py | 8 ++++++++ python/jet/gates.py | 3 ++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/python/jet/__init__.py b/python/jet/__init__.py index bbb2b6a1..466164ef 100644 --- a/python/jet/__init__.py +++ b/python/jet/__init__.py @@ -1,13 +1,9 @@ # The existence of a Python binding is proof of its intention to be exposed. from .bindings import * -from .factory import ( - TaskBasedCpuContractor, - Tensor, - TensorNetwork, - TensorNetworkFile, - TensorNetworkSerializer, -) -from .gates import Gate + +# The rest of the modules control their exports using `__all__`. +from .factory import * +from .gates import * # Grab the current Jet version from the C++ headers. __version__ = version() diff --git a/python/jet/factory.py b/python/jet/factory.py index b172e27f..542dd648 100644 --- a/python/jet/factory.py +++ b/python/jet/factory.py @@ -15,6 +15,14 @@ TensorNetworkSerializerC128, ) +__all__ = [ + "TaskBasedCpuContractor", + "Tensor", + "TensorNetwork", + "TensorNetworkFile", + "TensorNetworkSerializer", +] + def TaskBasedCpuContractor( *args, **kwargs diff --git a/python/jet/gates.py b/python/jet/gates.py index 4f6af9fc..3204abc6 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -15,6 +15,7 @@ from .factory import Tensor __all__ = [ + "Gate", # CV Fock gates "Displacement", "Squeezing", @@ -115,7 +116,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: if indices is None: pass - # Check that `indices is a sequence of unique strings. + # Check that `indices` is a sequence of unique strings. elif ( not isinstance(indices, Sequence) or not all(isinstance(idx, str) for idx in indices) From a26448eb1203d9e2d211ee928f25465e75004536 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 2 Jun 2021 14:32:16 -0400 Subject: [PATCH 39/71] Update name of validation parameter --- python/jet/gates.py | 65 +++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 3204abc6..42793234 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -51,7 +51,8 @@ "U3", ] -INV_SQRT2 = math.sqrt(2) / 2 + +INV_SQRT2 = 1 / math.sqrt(2) class Gate: @@ -154,7 +155,7 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="Displacement", num_wires=1, params=params, **kwargs) - self._validate(num_params=3) + self._validate(want_num_params=3) @lru_cache def _data(self) -> np.ndarray: @@ -171,7 +172,7 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="Squeezing", num_wires=1, params=params, **kwargs) - self._validate(num_params=3) + self._validate(want_num_params=3) @lru_cache def _data(self) -> np.ndarray: @@ -188,7 +189,7 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="TwoModeSqueezing", num_wires=2, params=params, **kwargs) - self._validate(num_params=3) + self._validate(want_num_params=3) @lru_cache def _data(self) -> np.ndarray: @@ -206,7 +207,7 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="Beamsplitter", num_wires=1, params=params, **kwargs) - self._validate(num_params=3) + self._validate(want_num_params=3) @lru_cache def _data(self) -> np.ndarray: @@ -222,7 +223,7 @@ class CNOT(Gate): def __init__(self, *params, **kwargs): """Constructs a CNOT gate.""" super().__init__(name="CNOT", num_wires=2, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -234,7 +235,7 @@ class Hadamard(Gate): def __init__(self, *params, **kwargs): """Constructs a Hadamard gate.""" super().__init__(name="Hadamard", num_wires=1, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -247,7 +248,7 @@ class PauliX(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliX gate.""" super().__init__(name="PauliX", num_wires=1, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -259,7 +260,7 @@ class PauliY(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliY gate.""" super().__init__(name="PauliY", num_wires=1, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -271,7 +272,7 @@ class PauliZ(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliZ gate.""" super().__init__(name="PauliZ", num_wires=1, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -283,7 +284,7 @@ class S(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit phase gate.""" super().__init__(name="S", num_wires=1, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -295,7 +296,7 @@ class T(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit T gate.""" super().__init__(name="T", num_wires=1, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -307,7 +308,7 @@ class SX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Square-Root X gate.""" super().__init__(name="SX", num_wires=1, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -319,7 +320,7 @@ class CZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Z gate.""" super().__init__(name="CZ", num_wires=2, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -331,7 +332,7 @@ class CY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Y gate.""" super().__init__(name="CY", num_wires=2, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -343,7 +344,7 @@ class SWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a SWAP gate.""" super().__init__(name="SWAP", num_wires=2, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -355,7 +356,7 @@ class ISWAP(Gate): def __init__(self, *params, **kwargs): """Constructs an ISWAP gate.""" super().__init__(name="ISWAP", num_wires=2, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -367,7 +368,7 @@ class CSWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a CSWAP gate.""" super().__init__(name="CSWAP", num_wires=3, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -388,7 +389,7 @@ class Toffoli(Gate): def __init__(self, *params, **kwargs): """Constructs a Toffoli gate.""" super().__init__(name="Toffoli", num_wires=3, params=params, **kwargs) - self._validate(num_params=0) + self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: @@ -409,7 +410,7 @@ class RX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit X rotation gate.""" super().__init__(name="RX", num_wires=1, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -425,7 +426,7 @@ class RY(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Y rotation gate.""" super().__init__(name="RY", num_wires=1, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -442,7 +443,7 @@ class RZ(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Z rotation gate.""" super().__init__(name="RZ", num_wires=1, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -457,7 +458,7 @@ class PhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit local phase shift gate.""" super().__init__(name="PhaseShift", num_wires=1, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -470,7 +471,7 @@ class CPhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled phase shift gate.""" super().__init__(name="CPhaseShift", num_wires=2, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -483,7 +484,7 @@ class Rot(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit rotation gate.""" super().__init__(name="Rot", num_wires=1, params=params, **kwargs) - self._validate(num_params=3) + self._validate(want_num_params=3) @lru_cache def _data(self) -> np.ndarray: @@ -502,7 +503,7 @@ class CRX(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RX gate.""" super().__init__(name="CRX", num_wires=2, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -518,7 +519,7 @@ class CRY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RY gate.""" super().__init__(name="CRY", num_wires=2, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -534,7 +535,7 @@ class CRZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RZ gate.""" super().__init__(name="CRZ", num_wires=2, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -552,7 +553,7 @@ class CRot(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-rotation gate.""" super().__init__(name="CRot", num_wires=2, params=params, **kwargs) - self._validate(num_params=3) + self._validate(want_num_params=3) @lru_cache def _data(self) -> np.ndarray: @@ -573,7 +574,7 @@ class U1(Gate): def __init__(self, *params, **kwargs): """Constructs a U1 gate.""" super().__init__(name="U1", num_wires=1, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -586,7 +587,7 @@ class U2(Gate): def __init__(self, *params, **kwargs): """Constructs a U2 gate.""" super().__init__(name="U2", num_wires=2, params=params, **kwargs) - self._validate(num_params=1) + self._validate(want_num_params=1) @lru_cache def _data(self) -> np.ndarray: @@ -602,7 +603,7 @@ class U3(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit unitary gate.""" super().__init__(name="U3", num_wires=1, params=params, **kwargs) - self._validate(num_params=3) + self._validate(want_num_params=3) @lru_cache def _data(self) -> np.ndarray: From 08c7975f690b0f0054509ab503a2f0d9aa9e8e83 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 2 Jun 2021 14:32:41 -0400 Subject: [PATCH 40/71] Add unit tests for H, CNOT, and Pauli gates --- python/tests/jet/test_gates.py | 110 ++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index e8ddec3e..a4e30fba 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -1,14 +1,19 @@ +import math + import numpy as np import pytest -from jet import Gate +import jet + + +INV_SQRT2 = 1 / math.sqrt(2) class TestGate: @pytest.fixture def gate(self): """Returns a generic gate with two wires and one parameter.""" - return Gate(name="G", num_wires=2, params=[1]) + return jet.Gate(name="G", num_wires=2, params=[1]) def test_tensor_indices_not_set(self, gate): """Tests that the correct tensor is returned for a gate with unset indices.""" @@ -72,3 +77,104 @@ def test_indices_are_valid(self, gate, indices): assert gate.indices is None gate.indices = indices assert gate.indices == indices + + +# @pytest.mark.parametrize( +# ["state", "want_tensor"], +# [ +# ( +# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), +# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), +# ), +# ( +# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), +# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), +# ), +# ( +# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), +# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), +# ), +# ( +# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), +# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), +# ), +# ], +# ) +# def test_cnot(state, want_tensor): +# gate = jet.CNOT() +# have_tensor = jet.contract_tensors(gate.tensor(), state) +# assert have_tensor.data == pytest.approx(want_tensor.data) +# assert have_tensor.shape == want_tensor.shape +# assert have_tensor.indices == want_tensor.indices + + +@pytest.mark.parametrize( + ["gate", "state", "want_tensor"], + [ + ( + jet.CNOT(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CNOT(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CNOT(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + ), + ( + jet.CNOT(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + ), + ( + jet.Hadamard(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, INV_SQRT2]), + ), + ( + jet.Hadamard(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, -INV_SQRT2]), + ), + ( + jet.PauliX(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1]), + ), + ( + jet.PauliX(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + ), + ( + jet.PauliY(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + ), + ( + jet.PauliY(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), + ), + ( + jet.PauliZ(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + ), + ( + jet.PauliZ(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[0, -1]), + ), + ], +) +def test_gate(gate, state, want_tensor): + have_tensor = jet.contract_tensors(gate.tensor(), state) + assert have_tensor.data == pytest.approx(want_tensor.data) + assert have_tensor.shape == want_tensor.shape + assert have_tensor.indices == want_tensor.indices From a99a0b0d1406d7938626d89be3a566b6c8aede22 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 2 Jun 2021 17:16:34 -0400 Subject: [PATCH 41/71] Add unit tests for remaining gates --- python/jet/gates.py | 90 +++---- python/tests/jet/test_gates.py | 459 ++++++++++++++++++++++++++++++--- 2 files changed, 462 insertions(+), 87 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 42793234..24a913c5 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -30,8 +30,8 @@ "T", "SX", "CNOT", - "CZ", "CY", + "CZ", "SWAP", "ISWAP", "CSWAP", @@ -219,18 +219,6 @@ def _data(self) -> np.ndarray: #################################################################################################### -class CNOT(Gate): - def __init__(self, *params, **kwargs): - """Constructs a CNOT gate.""" - super().__init__(name="CNOT", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=0) - - @lru_cache - def _data(self) -> np.ndarray: - mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] - return np.array(mat, dtype=self._dtype) - - class Hadamard(Gate): def __init__(self, *params, **kwargs): """Constructs a Hadamard gate.""" @@ -316,15 +304,41 @@ def _data(self) -> np.ndarray: return np.array(mat, dtype=self._dtype) -class CZ(Gate): +class PhaseShift(Gate): def __init__(self, *params, **kwargs): - """Constructs a controlled-Z gate.""" - super().__init__(name="CZ", num_wires=2, params=params, **kwargs) + """Constructs a single-qubit local phase shift gate.""" + super().__init__(name="PhaseShift", num_wires=1, params=params, **kwargs) + self._validate(want_num_params=1) + + @lru_cache + def _data(self) -> np.ndarray: + phi = self.params[0] + mat = [[1, 0], [0, cmath.exp(1j * phi)]] + return np.array(mat, dtype=self._dtype) + + +class CPhaseShift(Gate): + def __init__(self, *params, **kwargs): + """Constructs a controlled phase shift gate.""" + super().__init__(name="CPhaseShift", num_wires=2, params=params, **kwargs) + self._validate(want_num_params=1) + + @lru_cache + def _data(self) -> np.ndarray: + phi = self.params[0] + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, cmath.exp(1j * phi)]] + return np.array(mat, dtype=self._dtype) + + +class CNOT(Gate): + def __init__(self, *params, **kwargs): + """Constructs a CNOT gate.""" + super().__init__(name="CNOT", num_wires=2, params=params, **kwargs) self._validate(want_num_params=0) @lru_cache def _data(self) -> np.ndarray: - mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] return np.array(mat, dtype=self._dtype) @@ -340,6 +354,18 @@ def _data(self) -> np.ndarray: return np.array(mat, dtype=self._dtype) +class CZ(Gate): + def __init__(self, *params, **kwargs): + """Constructs a controlled-Z gate.""" + super().__init__(name="CZ", num_wires=2, params=params, **kwargs) + self._validate(want_num_params=0) + + @lru_cache + def _data(self) -> np.ndarray: + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] + return np.array(mat, dtype=self._dtype) + + class SWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a SWAP gate.""" @@ -454,32 +480,6 @@ def _data(self) -> np.ndarray: return np.array(mat, dtype=self._dtype) -class PhaseShift(Gate): - def __init__(self, *params, **kwargs): - """Constructs a single-qubit local phase shift gate.""" - super().__init__(name="PhaseShift", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=1) - - @lru_cache - def _data(self) -> np.ndarray: - phi = self.params[0] - mat = [[1, 0], [0, cmath.exp(1j * phi)]] - return np.array(mat, dtype=self._dtype) - - -class CPhaseShift(Gate): - def __init__(self, *params, **kwargs): - """Constructs a controlled phase shift gate.""" - super().__init__(name="CPhaseShift", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=1) - - @lru_cache - def _data(self) -> np.ndarray: - phi = self.params[0] - mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, cmath.exp(1j * phi)]] - return np.array(mat, dtype=self._dtype) - - class Rot(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit rotation gate.""" @@ -586,8 +586,8 @@ def _data(self) -> np.ndarray: class U2(Gate): def __init__(self, *params, **kwargs): """Constructs a U2 gate.""" - super().__init__(name="U2", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=1) + super().__init__(name="U2", num_wires=1, params=params, **kwargs) + self._validate(want_num_params=2) @lru_cache def _data(self) -> np.ndarray: diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index a4e30fba..f7665b34 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -79,38 +79,124 @@ def test_indices_are_valid(self, gate, indices): assert gate.indices == indices -# @pytest.mark.parametrize( -# ["state", "want_tensor"], -# [ -# ( -# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), -# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), -# ), -# ( -# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), -# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), -# ), -# ( -# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), -# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), -# ), -# ( -# jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), -# jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), -# ), -# ], -# ) -# def test_cnot(state, want_tensor): -# gate = jet.CNOT() -# have_tensor = jet.contract_tensors(gate.tensor(), state) -# assert have_tensor.data == pytest.approx(want_tensor.data) -# assert have_tensor.shape == want_tensor.shape -# assert have_tensor.indices == want_tensor.indices - - @pytest.mark.parametrize( ["gate", "state", "want_tensor"], [ + ############################################################################################ + # Hadamard gate + ############################################################################################ + ( + jet.Hadamard(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, INV_SQRT2]), + ), + ( + jet.Hadamard(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, -INV_SQRT2]), + ), + ############################################################################################ + # Pauli gates + ############################################################################################ + ( + jet.PauliX(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1]), + ), + ( + jet.PauliX(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + ), + ( + jet.PauliY(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + ), + ( + jet.PauliY(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), + ), + ( + jet.PauliZ(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + ), + ( + jet.PauliZ(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[0, -1]), + ), + ############################################################################################ + # Unparametrized phase shift gates + ############################################################################################ + ( + jet.S(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + ), + ( + jet.S(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + ), + ( + jet.T(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + ), + ( + jet.T(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[0, INV_SQRT2 + INV_SQRT2 * 1j]), + ), + ( + jet.SX(), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[1 / 2 + 1j / 2, 1 / 2 - 1j / 2]), + ), + ( + jet.SX(), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[1 / 2 - 1j / 2, 1 / 2 + 1j / 2]), + ), + ############################################################################################ + # Parametrized phase shift gates + ############################################################################################ + ( + jet.PhaseShift(math.pi / 2), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + ), + ( + jet.PhaseShift(math.pi / 2), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + ), + ( + jet.CPhaseShift(math.pi / 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CPhaseShift(math.pi / 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CPhaseShift(math.pi / 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + ), + ( + jet.CPhaseShift(math.pi / 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), + ), + ############################################################################################ + # Control gates + ############################################################################################ ( jet.CNOT(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), @@ -132,44 +218,333 @@ def test_indices_are_valid(self, gate, indices): jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), ), ( - jet.Hadamard(), + jet.CY(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CY(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CY(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), + ), + ( + jet.CY(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), + ), + ( + jet.CZ(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CZ(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CZ(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + ), + ( + jet.CZ(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -1]), + ), + ############################################################################################ + # SWAP gates + ############################################################################################ + ( + jet.SWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.SWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + ), + ( + jet.SWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.SWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + ), + ( + jet.ISWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.ISWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1j, 0]), + ), + ( + jet.ISWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1j, 0, 0]), + ), + ( + jet.ISWAP(), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), + ), + ( + jet.CSWAP(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), + ), + ############################################################################################ + # Toffoli gate + ############################################################################################ + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), + ), + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), + ), + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), + ), + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), + ), + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), + ), + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), + ), + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), + ), + ( + jet.Toffoli(), + jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), + jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), + ), + ############################################################################################ + # Rotation gates + ############################################################################################ + ( + jet.RX(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), - jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, INV_SQRT2]), + jet.Tensor(indices=["0"], shape=[2], data=[0, -1j]), ), ( - jet.Hadamard(), + jet.RX(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), - jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, -INV_SQRT2]), + jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), ), ( - jet.PauliX(), + jet.RY(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1]), ), ( - jet.PauliX(), + jet.RY(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), - jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[-1, 0]), ), ( - jet.PauliY(), + jet.RZ(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), + ), + ( + jet.RZ(math.pi), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), ), ( - jet.PauliY(), + jet.Rot(math.pi / 2, math.pi, 2 * math.pi), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[0, -INV_SQRT2 + INV_SQRT2 * 1j]), + ), + ( + jet.Rot(math.pi / 2, math.pi, 2 * math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), - jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2 + INV_SQRT2 * 1j, 0]), ), + ############################################################################################ + # Controlled rotation gates + ############################################################################################ ( - jet.PauliZ(), + jet.CRX(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CRX(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CRX(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -1j]), + ), + ( + jet.CRX(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), + ), + ( + jet.CRY(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CRY(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CRY(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + ), + ( + jet.CRY(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1, 0]), + ), + ( + jet.CRZ(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CRZ(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CRZ(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), + ), + ( + jet.CRZ(math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), + ), + ( + jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + ), + ( + jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + ), + ( + jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor( + indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -INV_SQRT2 + INV_SQRT2 * 1j] + ), + ), + ( + jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor( + indices=["0", "1"], shape=[2, 2], data=[0, 0, INV_SQRT2 + INV_SQRT2 * 1j, 0] + ), + ), + ############################################################################################ + # U gates + ############################################################################################ + ( + jet.U1(math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), ), ( - jet.PauliZ(), + jet.U1(math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), - jet.Tensor(indices=["0"], shape=[2], data=[0, -1]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + ), + ( + jet.U2(math.pi / 2, math.pi), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, INV_SQRT2 * 1j]), + ), + ( + jet.U2(math.pi / 2, math.pi), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, -INV_SQRT2 * 1j]), + ), + ( + jet.U3(2 * math.pi, math.pi, math.pi / 2), + jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), + jet.Tensor(indices=["0"], shape=[2], data=[-1, 0]), + ), + ( + jet.U3(2 * math.pi, math.pi, math.pi / 2), + jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), + jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), ), ], ) From 0604c8bb7376daab23c453f5a58e9cb65fd8caa8 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 2 Jun 2021 22:23:02 -0400 Subject: [PATCH 42/71] Add unit test IDs --- python/tests/jet/test_gates.py | 266 ++++++++++++++++++++++----------- 1 file changed, 180 insertions(+), 86 deletions(-) diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index f7665b34..bb69287e 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -79,472 +79,566 @@ def test_indices_are_valid(self, gate, indices): assert gate.indices == indices +# def get_test_gate_parameter_id(val) -> str: +# """Returns the ID of the given test_gate() parameter.""" +# if isinstance(val, jet.Gate): +# return val.name +# elif isinstance(val, jet.TensorC128): +# return str(tuple(val.indices)) + + @pytest.mark.parametrize( ["gate", "state", "want_tensor"], [ ############################################################################################ # Hadamard gate ############################################################################################ - ( + pytest.param( jet.Hadamard(), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, INV_SQRT2]), + id="H|0>", ), - ( + pytest.param( jet.Hadamard(), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, -INV_SQRT2]), + id="H|1>", ), ############################################################################################ # Pauli gates ############################################################################################ - ( + pytest.param( jet.PauliX(), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1]), + id="X|0>", ), - ( + pytest.param( jet.PauliX(), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + id="X|1>", ), - ( + pytest.param( jet.PauliY(), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + id="Y|0>", ), - ( + pytest.param( jet.PauliY(), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), + id="Y|1>", ), - ( + pytest.param( jet.PauliZ(), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + id="Z|0>", ), - ( + pytest.param( jet.PauliZ(), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, -1]), + id="Z|1>", ), ############################################################################################ # Unparametrized phase shift gates ############################################################################################ - ( + pytest.param( jet.S(), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + id="S|0>", ), - ( + pytest.param( jet.S(), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + id="S|1>", ), - ( + pytest.param( jet.T(), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + id="T|0>", ), - ( + pytest.param( jet.T(), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, INV_SQRT2 + INV_SQRT2 * 1j]), + id="T|1>", ), - ( + pytest.param( jet.SX(), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1 / 2 + 1j / 2, 1 / 2 - 1j / 2]), + id="SX|0>", ), - ( + pytest.param( jet.SX(), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[1 / 2 - 1j / 2, 1 / 2 + 1j / 2]), + id="SX|1>", ), ############################################################################################ # Parametrized phase shift gates ############################################################################################ - ( + pytest.param( jet.PhaseShift(math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + id="P|0>", ), - ( + pytest.param( jet.PhaseShift(math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + id="P|1>", ), - ( + pytest.param( jet.CPhaseShift(math.pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CP|00>", ), - ( + pytest.param( jet.CPhaseShift(math.pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CP|01>", ), - ( + pytest.param( jet.CPhaseShift(math.pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + id="CP|10>", ), - ( + pytest.param( jet.CPhaseShift(math.pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), + id="CP|11>", ), ############################################################################################ # Control gates ############################################################################################ - ( + pytest.param( jet.CNOT(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CNOT|00>", ), - ( + pytest.param( jet.CNOT(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CNOT|01>", ), - ( + pytest.param( jet.CNOT(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + id="CNOT|10>", ), - ( + pytest.param( jet.CNOT(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + id="CNOT|11>", ), - ( + pytest.param( jet.CY(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CY|00>", ), - ( + pytest.param( jet.CY(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CY|01>", ), - ( + pytest.param( jet.CY(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), + id="CY|10>", ), - ( + pytest.param( jet.CY(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), + id="CY|11>", ), - ( + pytest.param( jet.CZ(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CZ|00>", ), - ( + pytest.param( jet.CZ(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CZ|01>", ), - ( + pytest.param( jet.CZ(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + id="CZ|10>", ), - ( + pytest.param( jet.CZ(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -1]), + id="CZ|11>", ), ############################################################################################ # SWAP gates ############################################################################################ - ( + pytest.param( jet.SWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="SWAP|00>", ), - ( + pytest.param( jet.SWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), + id="SWAP|01>", ), - ( + pytest.param( jet.SWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="SWAP|10>", ), - ( + pytest.param( jet.SWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + id="SWAP|11>", ), - ( + pytest.param( jet.ISWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="ISWAP|00>", ), - ( + pytest.param( jet.ISWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1j, 0]), + id="ISWAP|01>", ), - ( + pytest.param( jet.ISWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1j, 0, 0]), + id="ISWAP|10>", ), - ( + pytest.param( jet.ISWAP(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + id="ISWAP|11>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), + id="CSWAP|000>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), + id="CSWAP|001>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), + id="CSWAP|010>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), + id="CSWAP|011>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), + id="CSWAP|100>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), + id="CSWAP|101>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), + id="CSWAP|110>", ), - ( + pytest.param( jet.CSWAP(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), + id="CSWAP|111>", ), ############################################################################################ # Toffoli gate ############################################################################################ - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[1, 0, 0, 0, 0, 0, 0, 0]), + id="T|000>", ), - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 1, 0, 0, 0, 0, 0, 0]), + id="T|001>", ), - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 1, 0, 0, 0, 0, 0]), + id="T|010>", ), - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 1, 0, 0, 0, 0]), + id="T|011>", ), - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 1, 0, 0, 0]), + id="T|100>", ), - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 1, 0, 0]), + id="T|101>", ), - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), + id="T|110>", ), - ( + pytest.param( jet.Toffoli(), jet.Tensor(indices=["3", "4", "5"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 0, 1]), jet.Tensor(indices=["0", "1", "2"], shape=[2, 2, 2], data=[0, 0, 0, 0, 0, 0, 1, 0]), + id="T|111>", ), ############################################################################################ # Rotation gates ############################################################################################ - ( + pytest.param( jet.RX(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, -1j]), + id="RX(pi)|0>", ), - ( + pytest.param( jet.RX(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), + id="RX(pi)|1>", ), - ( + pytest.param( jet.RY(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1]), + id="RY(pi)|0>", ), - ( + pytest.param( jet.RY(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[-1, 0]), + id="RY(pi)|1>", ), - ( + pytest.param( jet.RZ(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), + id="RZ(pi)|0>", ), - ( + pytest.param( jet.RZ(math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + id="RY(pi)|1>", ), - ( + pytest.param( jet.Rot(math.pi / 2, math.pi, 2 * math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, -INV_SQRT2 + INV_SQRT2 * 1j]), + id="Rot(pi/2,pi,2*pi)|0>", ), - ( + pytest.param( jet.Rot(math.pi / 2, math.pi, 2 * math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2 + INV_SQRT2 * 1j, 0]), + id="Rot(pi/2,pi,2*pi)|1>", ), ############################################################################################ # Controlled rotation gates ############################################################################################ - ( + pytest.param( jet.CRX(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CRX|00>", ), - ( + pytest.param( jet.CRX(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CRX|01>", ), - ( + pytest.param( jet.CRX(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -1j]), + id="CRX|10>", ), - ( + pytest.param( jet.CRX(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), + id="CRX|11>", ), - ( + pytest.param( jet.CRY(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CRY|00>", ), - ( + pytest.param( jet.CRY(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CRY|01>", ), - ( + pytest.param( jet.CRY(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), + id="CRY|10>", ), - ( + pytest.param( jet.CRY(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1, 0]), + id="CRY|11>", ), - ( + pytest.param( jet.CRZ(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CRZ|00>", ), - ( + pytest.param( jet.CRZ(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CRZ|01>", ), - ( + pytest.param( jet.CRZ(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), + id="CRZ|10>", ), - ( + pytest.param( jet.CRZ(math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), + id="CRZ|11>", ), - ( + pytest.param( jet.CRot(math.pi / 2, math.pi, 2 * math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), + id="CRot|00>", ), - ( + pytest.param( jet.CRot(math.pi / 2, math.pi, 2 * math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), + id="CRot|01>", ), - ( + pytest.param( jet.CRot(math.pi / 2, math.pi, 2 * math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor( indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -INV_SQRT2 + INV_SQRT2 * 1j] ), + id="CRot|10>", ), - ( + pytest.param( jet.CRot(math.pi / 2, math.pi, 2 * math.pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor( indices=["0", "1"], shape=[2, 2], data=[0, 0, INV_SQRT2 + INV_SQRT2 * 1j, 0] ), + id="CRot|11>", ), ############################################################################################ # U gates ############################################################################################ - ( + pytest.param( jet.U1(math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), + id="U1|0>", ), - ( + pytest.param( jet.U1(math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + id="U1|1>", ), - ( + pytest.param( jet.U2(math.pi / 2, math.pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, INV_SQRT2 * 1j]), + id="U2|0>", ), - ( + pytest.param( jet.U2(math.pi / 2, math.pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, -INV_SQRT2 * 1j]), + id="U2|1>", ), - ( + pytest.param( jet.U3(2 * math.pi, math.pi, math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[-1, 0]), + id="U3(2*pi,pi,pi/2)|0>", ), - ( + pytest.param( jet.U3(2 * math.pi, math.pi, math.pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), + id="U3(2*pi,pi,pi/2)|1>", ), ], ) From 56c6ac57b5cd5f02bc6febe77c7af3bea78e9c84 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 3 Jun 2021 11:42:01 -0400 Subject: [PATCH 43/71] Add unit tests for CV Fock gates --- python/jet/gates.py | 2 +- python/tests/jet/test_gates.py | 134 ++++++++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 10 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 24a913c5..1dad496f 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -206,7 +206,7 @@ def __init__(self, *params, **kwargs): phi (float): reflection phase of the beamsplitter. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Beamsplitter", num_wires=1, params=params, **kwargs) + super().__init__(name="Beamsplitter", num_wires=2, params=params, **kwargs) self._validate(want_num_params=3) @lru_cache diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index bb69287e..4c29dbbf 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -5,7 +5,6 @@ import jet - INV_SQRT2 = 1 / math.sqrt(2) @@ -79,17 +78,134 @@ def test_indices_are_valid(self, gate, indices): assert gate.indices == indices -# def get_test_gate_parameter_id(val) -> str: -# """Returns the ID of the given test_gate() parameter.""" -# if isinstance(val, jet.Gate): -# return val.name -# elif isinstance(val, jet.TensorC128): -# return str(tuple(val.indices)) - - @pytest.mark.parametrize( ["gate", "state", "want_tensor"], [ + ############################################################################################ + # Continuous variable Fock gates + ############################################################################################ + pytest.param( + jet.Displacement(2, math.pi / 2, 3), + jet.Tensor(indices=["1"], shape=[3], data=[1, 0, 0]), + jet.Tensor( + indices=["0"], shape=[3], data=[0.135335283237, 0.270670566473j, -0.382785986042] + ), + id="Displacement(2,pi/2,3)|1>", + ), + pytest.param( + jet.Displacement(2, math.pi / 2, 3), + jet.Tensor(indices=["1"], shape=[3], data=[0, 1, 0]), + jet.Tensor( + indices=["0"], shape=[3], data=[0.270670566473j, -0.40600584971, -0.382785986042j] + ), + id="Displacement(2,pi/2,3)|2>", + ), + pytest.param( + jet.Displacement(2, math.pi / 2, 3), + jet.Tensor(indices=["1"], shape=[3], data=[0, 0, 1]), + jet.Tensor( + indices=["0"], shape=[3], data=[-0.382785986042, -0.382785986042j, 0.135335283237] + ), + id="Displacement(2,pi/2,3)|3>", + ), + pytest.param( + jet.Squeezing(2, math.pi / 2, 3), + jet.Tensor(indices=["1"], shape=[3], data=[1, 0, 0]), + jet.Tensor(indices=["0"], shape=[3], data=[0.515560111756, 0, -0.351442087775j]), + id="Squeezing(2,pi/2,3)|1>", + ), + pytest.param( + jet.Squeezing(2, math.pi / 2, 3), + jet.Tensor(indices=["1"], shape=[3], data=[0, 1, 0]), + jet.Tensor(indices=["0"], shape=[3], data=[0, 0.137037026803, 0]), + id="Squeezing(2,pi/2,3)|2>", + ), + pytest.param( + jet.Squeezing(2, math.pi / 2, 3), + jet.Tensor(indices=["1"], shape=[3], data=[0, 0, 1]), + jet.Tensor(indices=["0"], shape=[3], data=[-0.351442087775j, 0, -0.203142935143]), + id="Squeezing(2,pi/2,3)|3>", + ), + pytest.param( + jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[0.099327927419, 0, 0, 0.069888119434 + 0.069888119434j], + ), + id="TwoModeSqueezing(3,pi/4,2)|00>", + ), + pytest.param( + jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[0, 0.009866037165, 0, 0], + ), + id="TwoModeSqueezing(3,pi/4,2)|01>", + ), + pytest.param( + jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[0, 0, 0.009866037165, 0], + ), + id="TwoModeSqueezing(3,pi/4,2)|10>", + ), + pytest.param( + jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[-0.069888119434 + 0.069888119434j, 0, 0, -0.097367981372], + ), + id="TwoModeSqueezing(3,pi/4,2)|10>", + ), + pytest.param( + jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[1, 0, 0, 0], + ), + id="Beamsplitter(pi/4,pi/2,2)|00>", + ), + pytest.param( + jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[0, INV_SQRT2, INV_SQRT2 * 1j, 0], + ), + id="Beamsplitter(pi/4,pi/2,2)|01>", + ), + pytest.param( + jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[0, INV_SQRT2 * 1j, INV_SQRT2, 0], + ), + id="Beamsplitter(pi/4,pi/2,2)|10>", + ), + pytest.param( + jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), + jet.Tensor( + indices=["0", "1"], + shape=[2, 2], + data=[0, 0, 0, 0], + ), + id="Beamsplitter(pi/4,pi/2,2)|11>", + ), ############################################################################################ # Hadamard gate ############################################################################################ From a389e28dd12297a36b68663fd7597150914fe6d7 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 3 Jun 2021 11:48:51 -0400 Subject: [PATCH 44/71] Defer circuit.py to future PR --- python/jet/circuit.py | 20 -------------------- python/tests/jet/test_gates.py | 1 + 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 python/jet/circuit.py diff --git a/python/jet/circuit.py b/python/jet/circuit.py deleted file mode 100644 index 9f611afe..00000000 --- a/python/jet/circuit.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Contains the circuit class for building a tensor network""" - -import jet - -from .gates import Gate - - -class Circuit: - """Circuit class""" - - def __init__(self) -> None: - self.circuit = [] - - def tensor_network(self) -> jet.TensorNetwork: - """Builds and returns the tensor network corresponding to the circuit""" - tn = jet.TensorNetwork() - return tn - - def add_gate(self, gate: Gate) -> None: - """Adds a gate to the circuit""" diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index 4c29dbbf..2d9b80f1 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -759,6 +759,7 @@ def test_indices_are_valid(self, gate, indices): ], ) def test_gate(gate, state, want_tensor): + """Tests that the correct transformation is applied by a gate to a basis state.""" have_tensor = jet.contract_tensors(gate.tensor(), state) assert have_tensor.data == pytest.approx(want_tensor.data) assert have_tensor.shape == want_tensor.shape From 1c7edffd9621c2104ae75f9aa36de226360f6c48 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 3 Jun 2021 11:55:35 -0400 Subject: [PATCH 45/71] Update changelog --- .github/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index a6beed66..c118a159 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,8 @@ ### New features since last release +* Quantum gate models have been added to the `jet` Python package. [(#16)](https://github.com/XanaduAI/jet/pull/16) + * Python bindings are now available for the `TaskBasedCpuContractor` class. [(#19)](https://github.com/XanaduAI/jet/pull/19) * Python bindings now include a factory method which accepts a `dtype` parameter. [(#18)](https://github.com/XanaduAI/jet/pull/18) @@ -20,7 +22,8 @@ ### Improvements -* Use camel case for type aliases [(#17)](https://github.com/XanaduAI/jet/pull/17) +* Use camel case for type aliases. [(#17)](https://github.com/XanaduAI/jet/pull/17) + * Exceptions are now favoured in place of `std::terminate` with `Exception` being the new base type for all exceptions thrown by Jet. [(#3)](https://github.com/XanaduAI/jet/pull/3) * `TaskBasedCpuContractor` now stores `Tensor` results. [(#8)](https://github.com/XanaduAI/jet/pull/8) From 1f833d1026d913f94064dce7d1859cda3a470bb7 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 3 Jun 2021 12:19:55 -0400 Subject: [PATCH 46/71] Add thewalrus to package requirements and specify numpy versions --- python/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/setup.py b/python/setup.py index 67935fbd..bf8f2c38 100644 --- a/python/setup.py +++ b/python/setup.py @@ -52,8 +52,9 @@ def build_extension(self, ext): requirements = [ "lark-parser>=0.11.0", - "numpy", + "numpy>=1.0.0", "StrawberryFields==0.18.0", + "thewalrus>=0.15.0", ] info = { From 27220533ade08caad987404e165debc6b26e0bc2 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 4 Jun 2021 11:50:44 -0400 Subject: [PATCH 47/71] Create type aliases for union of template class specializations --- python/jet/factory.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/python/jet/factory.py b/python/jet/factory.py index 542dd648..af26701e 100644 --- a/python/jet/factory.py +++ b/python/jet/factory.py @@ -16,6 +16,11 @@ ) __all__ = [ + "TaskBasedCpuContractorType", + "TensorType", + "TensorNetworkType", + "TensorNetworkFileType", + "TensorNetworkSerializerType", "TaskBasedCpuContractor", "Tensor", "TensorNetwork", @@ -23,10 +28,15 @@ "TensorNetworkSerializer", ] +# Type aliases to avoid enumerating class specializations. +TaskBasedCpuContractorType = Union[TaskBasedCpuContractorC64, TaskBasedCpuContractorC128] +TensorType = Union[TensorC64, TensorC128] +TensorNetworkType = Union[TensorNetworkC64, TensorNetworkC128] +TensorNetworkFileType = Union[TensorNetworkFileC64, TensorNetworkFileC128] +TensorNetworkSerializerType = Union[TensorNetworkSerializerC64, TensorNetworkSerializerC128] -def TaskBasedCpuContractor( - *args, **kwargs -) -> Union[TaskBasedCpuContractorC64, TaskBasedCpuContractorC128]: + +def TaskBasedCpuContractor(*args, **kwargs) -> TaskBasedCpuContractorType: """Constructs a task-based CPU contractor with the specified data type. If a `dtype` keyword argument is not provided, a TaskBasedCpuContractorC128 instance will be returned. @@ -40,7 +50,7 @@ def TaskBasedCpuContractor( raise TypeError(f"Data type '{dtype}' is not supported.") -def Tensor(*args, **kwargs) -> Union[TensorC64, TensorC128]: +def Tensor(*args, **kwargs) -> TensorType: """Constructs a tensor with the specified data type. If a `dtype` keyword argument is not provided, a TensorC128 instance will be returned. """ @@ -53,7 +63,7 @@ def Tensor(*args, **kwargs) -> Union[TensorC64, TensorC128]: raise TypeError(f"Data type '{dtype}' is not supported.") -def TensorNetwork(*args, **kwargs) -> Union[TensorNetworkC64, TensorNetworkC128]: +def TensorNetwork(*args, **kwargs) -> TensorNetworkType: """Constructs a tensor network with the specified data type. If a `dtype` keyword argument is not provided, a TensorNetworkC128 instance will be returned. @@ -67,7 +77,7 @@ def TensorNetwork(*args, **kwargs) -> Union[TensorNetworkC64, TensorNetworkC128] raise TypeError(f"Data type '{dtype}' is not supported.") -def TensorNetworkFile(*args, **kwargs) -> Union[TensorNetworkFileC64, TensorNetworkFileC128]: +def TensorNetworkFile(*args, **kwargs) -> TensorNetworkFileType: """Constructs a tensor network file with the specified data type. If a `dtype` keyword argument is not provided, a TensorNetworkFileC128 instance will be returned. @@ -81,9 +91,7 @@ def TensorNetworkFile(*args, **kwargs) -> Union[TensorNetworkFileC64, TensorNetw raise TypeError(f"Data type '{dtype}' is not supported.") -def TensorNetworkSerializer( - *args, **kwargs -) -> Union[TensorNetworkSerializerC64, TensorNetworkSerializerC128]: +def TensorNetworkSerializer(*args, **kwargs) -> TensorNetworkSerializerType: """Constructs a tensor network serializer with the specified data type. If a `dtype` keyword argument is not provided, a TensorNetworkSerializerC128 instance will be returned. From 65d81a8511881e4b26803b9722672c32996dbcc2 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 4 Jun 2021 12:02:26 -0400 Subject: [PATCH 48/71] Convert Gate into an ABC, expose num_wires, and distribute validation --- python/jet/gates.py | 277 ++++++++++++++++++++------------- python/tests/jet/test_gates.py | 24 +-- 2 files changed, 171 insertions(+), 130 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 1dad496f..71a629af 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -1,7 +1,8 @@ import cmath import math +from abc import ABC, abstractmethod from functools import lru_cache -from typing import List, Optional, Sequence, Union +from typing import List, Optional, Sequence import numpy as np from thewalrus.fock_gradients import ( @@ -11,8 +12,7 @@ two_mode_squeezing, ) -from .bindings import TensorC64, TensorC128 -from .factory import Tensor +from .factory import Tensor, TensorType __all__ = [ "Gate", @@ -55,64 +55,37 @@ INV_SQRT2 = 1 / math.sqrt(2) -class Gate: - def __init__(self, name: str, num_wires: int, **kwargs): +class Gate(ABC): + def __init__( + self, + name: str, + num_wires: int, + params: Optional[List] = None, + tensor_id: Optional[int] = None, + ): """Constructs a quantum gate. Args: name: name of the gate. num_wires: number of wires the gate is applied to. - - Kwargs: - dtype (type): type to use in matrix representations of gates. - params (list): gate parameters. - tensor_id (int): identification number for the gate-tensor. + params: gate parameters. + tensor_id: ID of the gate tensor. """ self.name = name - self.tensor_id = kwargs.get("tensor_id", None) + self.tensor_id = tensor_id - self._dtype = kwargs.get("dtype", np.complex128) self._indices = None self._num_wires = num_wires - self._params = kwargs.get("params", []) - - def tensor(self, adjoint: bool = False) -> Union[TensorC64, TensorC128]: - """Returns the tensor representation of this gate.""" - if adjoint: - data = np.conj(self._data()).T.flatten() - else: - data = self._data().flatten() - - indices = self.indices - if indices is None: - indices = list(map(str, range(2 * self._num_wires))) - - dimension = int(round(len(data) ** (1 / len(indices)))) - shape = [dimension] * len(indices) - - return Tensor(indices=indices, shape=shape, data=data, dtype=self._dtype) - - def _data(self) -> np.ndarray: - """Returns the matrix representation of this gate.""" - raise NotImplementedError("No tensor data available for generic gate.") - - def _validate(self, want_num_params: int): - """Throws a ValueError if the given quantity differs from the number of gate parameters.""" - have_num_params = 0 if self.params is None else len(self.params) - if have_num_params != want_num_params: - raise ValueError( - f"The {self.name} gate accepts exactly {want_num_params} parameters " - f"but {have_num_params} parameters were given." - ) + self._params = params or [] @property def indices(self) -> Optional[List[str]]: - """Returns the indices of this gate for connecting tensors.""" + """Returns the indices of this gate.""" return self._indices @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: - """Sets the indices of this gate for connecting tensors.""" + """Sets the indices of this gate.""" # Skip the sequence property checks if `indices` is None. if indices is None: pass @@ -128,17 +101,43 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: # Check that `indices` has the correct length (or is None). elif len(indices) != 2 * self._num_wires: raise ValueError( - f"Indices must have two indices per wire. " - f"Received {len(indices)} indices for {self._num_wires} wires." + f"Gates must have two indices per wire; received {len(indices)}" + f"indices for {self._num_wires} wires." ) self._indices = indices + @property + def num_wires(self) -> int: + """Returns the number of wires this gate affects.""" + return self._num_wires + @property def params(self) -> Optional[List]: """Returns the parameters of this gate.""" return self._params + @abstractmethod + def _data(self) -> np.ndarray: + """Returns the matrix representation of this gate.""" + pass + + def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorType: + """Returns the tensor representation of this gate.""" + if adjoint: + data = np.conj(self._data()).T.flatten() + else: + data = self._data().flatten() + + indices = self.indices + if indices is None: + indices = list(map(str, range(2 * self._num_wires))) + + dimension = int(round(len(data) ** (1 / len(indices)))) + shape = [dimension] * len(indices) + + return Tensor(indices=indices, shape=shape, data=data, dtype=dtype) + #################################################################################################### # Continuous variable Fock gates @@ -155,11 +154,13 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="Displacement", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=3) + + if len(params) != 3: + raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: - return displacement(*self.params, dtype=self._dtype) + return displacement(*self.params) class Squeezing(Gate): @@ -172,11 +173,13 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="Squeezing", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=3) + + if len(params) != 3: + raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: - return squeezing(*self.params, dtype=self._dtype) + return squeezing(*self.params) class TwoModeSqueezing(Gate): @@ -189,11 +192,13 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="TwoModeSqueezing", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=3) + + if len(params) != 3: + raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: - return two_mode_squeezing(*self.params, dtype=self._dtype) + return two_mode_squeezing(*self.params) class Beamsplitter(Gate): @@ -207,11 +212,13 @@ def __init__(self, *params, **kwargs): cutoff (int): Fock ladder cutoff. """ super().__init__(name="Beamsplitter", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=3) + + if len(params) != 3: + raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: - return beamsplitter(*self.params, dtype=self._dtype) + return beamsplitter(*self.params) #################################################################################################### @@ -223,178 +230,208 @@ class Hadamard(Gate): def __init__(self, *params, **kwargs): """Constructs a Hadamard gate.""" super().__init__(name="Hadamard", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: """Hadamard matrix""" mat = [[INV_SQRT2, INV_SQRT2], [INV_SQRT2, -INV_SQRT2]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class PauliX(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliX gate.""" super().__init__(name="PauliX", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[0, 1], [1, 0]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class PauliY(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliY gate.""" super().__init__(name="PauliY", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[0, -1j], [1j, 0]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class PauliZ(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliZ gate.""" super().__init__(name="PauliZ", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0], [0, -1]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class S(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit phase gate.""" super().__init__(name="S", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0], [0, 1j]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class T(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit T gate.""" super().__init__(name="T", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0], [0, cmath.exp(0.25j * np.pi)]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class SX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Square-Root X gate.""" super().__init__(name="SX", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class PhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit local phase shift gate.""" super().__init__(name="PhaseShift", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: phi = self.params[0] mat = [[1, 0], [0, cmath.exp(1j * phi)]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CPhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled phase shift gate.""" super().__init__(name="CPhaseShift", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: phi = self.params[0] mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, cmath.exp(1j * phi)]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CNOT(Gate): def __init__(self, *params, **kwargs): """Constructs a CNOT gate.""" super().__init__(name="CNOT", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Y gate.""" super().__init__(name="CY", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1j], [0, 0, 1j, 0]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Z gate.""" super().__init__(name="CZ", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class SWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a SWAP gate.""" super().__init__(name="SWAP", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class ISWAP(Gate): def __init__(self, *params, **kwargs): """Constructs an ISWAP gate.""" super().__init__(name="ISWAP", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: mat = [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CSWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a CSWAP gate.""" super().__init__(name="CSWAP", num_wires=3, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -408,14 +445,16 @@ def _data(self) -> np.ndarray: [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], ] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class Toffoli(Gate): def __init__(self, *params, **kwargs): """Constructs a Toffoli gate.""" super().__init__(name="Toffoli", num_wires=3, params=params, **kwargs) - self._validate(want_num_params=0) + + if len(params) != 0: + raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -429,14 +468,16 @@ def _data(self) -> np.ndarray: [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0], ] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class RX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit X rotation gate.""" super().__init__(name="RX", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -445,14 +486,16 @@ def _data(self) -> np.ndarray: js = 1j * math.sin(-theta / 2) mat = [[c, js], [js, c]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class RY(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Y rotation gate.""" super().__init__(name="RY", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -462,14 +505,16 @@ def _data(self) -> np.ndarray: s = math.sin(theta / 2) mat = [[c, -s], [s, c]] - return np.array(mat, self._dtype) + return np.array(mat) class RZ(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Z rotation gate.""" super().__init__(name="RZ", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -477,14 +522,16 @@ def _data(self) -> np.ndarray: p = cmath.exp(-0.5j * theta) mat = [[p, 0], [0, np.conj(p)]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class Rot(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit rotation gate.""" super().__init__(name="Rot", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=3) + + if len(params) != 3: + raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -496,14 +543,16 @@ def _data(self) -> np.ndarray: [cmath.exp(-0.5j * (phi + omega)) * c, -cmath.exp(0.5j * (phi - omega)) * s], [cmath.exp(-0.5j * (phi - omega)) * s, cmath.exp(0.5j * (phi + omega)) * c], ] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CRX(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RX gate.""" super().__init__(name="CRX", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -512,14 +561,16 @@ def _data(self) -> np.ndarray: js = 1j * math.sin(-theta / 2) mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, js], [0, 0, js, c]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CRY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RY gate.""" super().__init__(name="CRY", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -528,14 +579,16 @@ def _data(self) -> np.ndarray: s = math.sin(theta / 2) mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, -s], [0, 0, s, c]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CRZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RZ gate.""" super().__init__(name="CRZ", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -546,14 +599,16 @@ def _data(self) -> np.ndarray: [0, 0, cmath.exp(-0.5j * theta), 0], [0, 0, 0, cmath.exp(0.5j * theta)], ] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class CRot(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-rotation gate.""" super().__init__(name="CRot", num_wires=2, params=params, **kwargs) - self._validate(want_num_params=3) + + if len(params) != 3: + raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -567,27 +622,31 @@ def _data(self) -> np.ndarray: [0, 0, cmath.exp(-0.5j * (phi + omega)) * c, -cmath.exp(0.5j * (phi - omega)) * s], [0, 0, cmath.exp(-0.5j * (phi - omega)) * s, cmath.exp(0.5j * (phi + omega)) * c], ] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class U1(Gate): def __init__(self, *params, **kwargs): """Constructs a U1 gate.""" super().__init__(name="U1", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=1) + + if len(params) != 1: + raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: phi = self.params[0] mat = [[1, 0], [0, cmath.exp(1j * phi)]] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class U2(Gate): def __init__(self, *params, **kwargs): """Constructs a U2 gate.""" super().__init__(name="U2", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=2) + + if len(params) != 2: + raise ValueError(f"Received {len(params)} (!= 2) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -596,14 +655,16 @@ def _data(self) -> np.ndarray: [INV_SQRT2, -INV_SQRT2 * cmath.exp(1j * lam)], [INV_SQRT2 * cmath.exp(1j * phi), INV_SQRT2 * cmath.exp(1j * (phi + lam))], ] - return np.array(mat, dtype=self._dtype) + return np.array(mat) class U3(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit unitary gate.""" super().__init__(name="U3", num_wires=1, params=params, **kwargs) - self._validate(want_num_params=3) + + if len(params) != 3: + raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") @lru_cache def _data(self) -> np.ndarray: @@ -615,4 +676,4 @@ def _data(self) -> np.ndarray: [c, -s * cmath.exp(1j * lam)], [s * cmath.exp(1j * phi), c * cmath.exp(1j * (phi + lam))], ] - return np.array(mat, dtype=self._dtype) + return np.array(mat) diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index 2d9b80f1..5808f0e8 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -11,8 +11,8 @@ class TestGate: @pytest.fixture def gate(self): - """Returns a generic gate with two wires and one parameter.""" - return jet.Gate(name="G", num_wires=2, params=[1]) + """Returns a gate with two wires and one parameter.""" + return jet.CPhaseShift(0) def test_tensor_indices_not_set(self, gate): """Tests that the correct tensor is returned for a gate with unset indices.""" @@ -42,26 +42,6 @@ def test_tensor_adjoint(self, gate): assert tensor.shape == [1, 1, 1, 1] assert tensor.data == [1 - 2j] - def test_data(self, gate): - """Tests that a NotImplementedError is raised when the data of a generic - gate is retrieved. - """ - with pytest.raises(NotImplementedError): - gate._data() - - def test_validate_wrong_number_of_parameters(self, gate): - """Tests that a ValueError is raised when a gate with the wrong number - of parameters is validated. - """ - with pytest.raises(ValueError): - gate._validate(want_num_params=2) - - def test_validate_correct_number_of_parameters(self, gate): - """Tests that no exceptions are raised when a gate with the correct - number of parameters is validated. - """ - gate._validate(want_num_params=1) - @pytest.mark.parametrize("indices", [1, ["i", 2, "k"], ["x", "x"], []]) def test_indices_are_invalid(self, gate, indices): """Tests that a ValueError is raised when the indices of a gate are set From db43ea325a884edbc155a9b27adcd0cfa4250973 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 4 Jun 2021 13:43:31 -0400 Subject: [PATCH 49/71] Use MockGate instead of CPhaseShift in Gate tests --- python/tests/jet/test_gates.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index 5808f0e8..d399aca3 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -8,41 +8,48 @@ INV_SQRT2 = 1 / math.sqrt(2) +class MockGate(jet.Gate): + """MockGate represents a fictional, unnormalized gate which can be applied to pairs of qutrits.""" + + def __init__(self): + super().__init__(name="MockGate", num_wires=2) + + def _data(self) -> np.ndarray: + return np.arange(3 ** 4) * (1 + 1j) + + class TestGate: @pytest.fixture def gate(self): - """Returns a gate with two wires and one parameter.""" - return jet.CPhaseShift(0) + """Returns a mock gate instance.""" + return MockGate() def test_tensor_indices_not_set(self, gate): """Tests that the correct tensor is returned for a gate with unset indices.""" - gate._data = lambda: np.arange(16) tensor = gate.tensor() assert tensor.indices == ["0", "1", "2", "3"] - assert tensor.shape == [2, 2, 2, 2] - assert tensor.data == list(range(16)) + assert tensor.shape == [3, 3, 3, 3] + assert tensor.data == [i * (1 + 1j) for i in range(3 ** 4)] def test_tensor_indices_are_set(self, gate): """Tests that the correct tensor is returned for a gate with specified indices.""" - gate._data = lambda: np.arange(3 ** 4) gate.indices = ["r", "g", "b", "a"] tensor = gate.tensor() assert tensor.indices == ["r", "g", "b", "a"] assert tensor.shape == [3, 3, 3, 3] - assert tensor.data == list(range(3 ** 4)) + assert tensor.data == [i * (1 + 1j) for i in range(3 ** 4)] def test_tensor_adjoint(self, gate): """Tests that the correct adjoint tensor is returned for a gate.""" - gate._data = lambda: np.array([1 + 2j]) tensor = gate.tensor(adjoint=True) assert tensor.indices == ["0", "1", "2", "3"] - assert tensor.shape == [1, 1, 1, 1] - assert tensor.data == [1 - 2j] + assert tensor.shape == [3, 3, 3, 3] + assert tensor.data == [i * (1 - 1j) for i in range(3 ** 4)] - @pytest.mark.parametrize("indices", [1, ["i", 2, "k"], ["x", "x"], []]) + @pytest.mark.parametrize("indices", [1, ["i", "j", "k", 4], ["x", "x", "x", "x"], []]) def test_indices_are_invalid(self, gate, indices): """Tests that a ValueError is raised when the indices of a gate are set to an invalid value. From bcf086d59cd50f0c29a95193c9dac05f0a5b1434 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 11:50:53 -0400 Subject: [PATCH 50/71] Elaborate docstring comments --- python/jet/gates.py | 215 ++++++++++++++++---------------------------- 1 file changed, 78 insertions(+), 137 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index 71a629af..d1a3f38b 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -60,16 +60,22 @@ def __init__( self, name: str, num_wires: int, + num_params: int, params: Optional[List] = None, tensor_id: Optional[int] = None, ): """Constructs a quantum gate. + Raises: + ValueError if the number of gate parameters differs from the + expected number of gate parameters. + Args: - name: name of the gate. - num_wires: number of wires the gate is applied to. - params: gate parameters. - tensor_id: ID of the gate tensor. + name (str): name of the gate. + num_wires (int): number of wires the gate is applied to. + num_params (int): expected number of gate parameters. + params (list or None): parameters of the gate. + tensor_id (int or None): ID of the gate tensor. """ self.name = name self.tensor_id = tensor_id @@ -78,14 +84,32 @@ def __init__( self._num_wires = num_wires self._params = params or [] + if len(self._params) != num_params: + raise ValueError( + f"Received {len(params)} (!= {num_params}) parameters for a {name} gate." + ) + @property - def indices(self) -> Optional[List[str]]: - """Returns the indices of this gate.""" + def indices(self) -> Optional[Sequence[str]]: + """Returns the indices of this gate. An index is a label associated with + an axis of the tensor representation of a gate and altogether determine + the connectivity of a tensor in the context of a tensor network. + """ return self._indices @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: - """Sets the indices of this gate.""" + """Sets the indices of this gate. If the indices of a gate are not None, + they are used to construct the tensor representation of that gate. See + @indices.getter for more information about tensor indices. + + Raises: + ValueError if the given indices are not a sequence of unique strings + or the number of provided indices is invalid. + + Args: + indices (Sequence[str] or None): new indices of the gate. + """ # Skip the sequence property checks if `indices` is None. if indices is None: pass @@ -98,7 +122,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: ): raise ValueError("Indices must be a sequence of unique strings.") - # Check that `indices` has the correct length (or is None). + # Check that `indices` has the correct length. elif len(indices) != 2 * self._num_wires: raise ValueError( f"Gates must have two indices per wire; received {len(indices)}" @@ -109,7 +133,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: @property def num_wires(self) -> int: - """Returns the number of wires this gate affects.""" + """Returns the number of wires connected to this gate.""" return self._num_wires @property @@ -146,17 +170,16 @@ def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorTy class Displacement(Gate): def __init__(self, *params, **kwargs): - """Constructs a displacement gate. + """Constructs a displacement gate. See `thewalrus.displacement + `__ + for more details on the gate parameters. Args: r (float): displacement magnitude. phi (float): displacement angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Displacement", num_wires=1, params=params, **kwargs) - - if len(params) != 3: - raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") + super().__init__(name="Displacement", num_wires=1, num_params=3, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -165,17 +188,16 @@ def _data(self) -> np.ndarray: class Squeezing(Gate): def __init__(self, *params, **kwargs): - """Constructs a squeezing gate. + """Constructs a squeezing gate. See `thewalrus.squeezing + `__ + for more details on the gate parameters. Args: r (float): squeezing magnitude. theta (float): squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Squeezing", num_wires=1, params=params, **kwargs) - - if len(params) != 3: - raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") + super().__init__(name="Squeezing", num_wires=1, num_params=3, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -184,17 +206,18 @@ def _data(self) -> np.ndarray: class TwoModeSqueezing(Gate): def __init__(self, *params, **kwargs): - """Constructs a two-mode squeezing gate. + """Constructs a two-mode squeezing gate. See `thewalrus.two_mode_squeezing + `__ + for more details on the gate parameters. Args: r (float): squeezing magnitude. theta (float): squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="TwoModeSqueezing", num_wires=2, params=params, **kwargs) - - if len(params) != 3: - raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") + super().__init__( + name="TwoModeSqueezing", num_wires=2, num_params=3, params=params, **kwargs + ) @lru_cache def _data(self) -> np.ndarray: @@ -203,7 +226,9 @@ def _data(self) -> np.ndarray: class Beamsplitter(Gate): def __init__(self, *params, **kwargs): - """Constructs a beamsplitter gate. + """Constructs a beamsplitter gate. See `thewalrus.beamsplitter + `__ + for more details on the gate parameters. Args: theta (float): transmissivity angle of the beamsplitter. The transmissivity is @@ -211,10 +236,7 @@ def __init__(self, *params, **kwargs): phi (float): reflection phase of the beamsplitter. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Beamsplitter", num_wires=2, params=params, **kwargs) - - if len(params) != 3: - raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") + super().__init__(name="Beamsplitter", num_wires=2, num_params=3, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -229,10 +251,7 @@ def _data(self) -> np.ndarray: class Hadamard(Gate): def __init__(self, *params, **kwargs): """Constructs a Hadamard gate.""" - super().__init__(name="Hadamard", num_wires=1, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="Hadamard", num_wires=1, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -244,10 +263,7 @@ def _data(self) -> np.ndarray: class PauliX(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliX gate.""" - super().__init__(name="PauliX", num_wires=1, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="PauliX", num_wires=1, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -258,10 +274,7 @@ def _data(self) -> np.ndarray: class PauliY(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliY gate.""" - super().__init__(name="PauliY", num_wires=1, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="PauliY", num_wires=1, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -272,10 +285,7 @@ def _data(self) -> np.ndarray: class PauliZ(Gate): def __init__(self, *params, **kwargs): """Constructs a PauliZ gate.""" - super().__init__(name="PauliZ", num_wires=1, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="PauliZ", num_wires=1, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -286,10 +296,7 @@ def _data(self) -> np.ndarray: class S(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit phase gate.""" - super().__init__(name="S", num_wires=1, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="S", num_wires=1, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -300,10 +307,7 @@ def _data(self) -> np.ndarray: class T(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit T gate.""" - super().__init__(name="T", num_wires=1, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="T", num_wires=1, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -314,10 +318,7 @@ def _data(self) -> np.ndarray: class SX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Square-Root X gate.""" - super().__init__(name="SX", num_wires=1, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="SX", num_wires=1, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -328,10 +329,7 @@ def _data(self) -> np.ndarray: class PhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit local phase shift gate.""" - super().__init__(name="PhaseShift", num_wires=1, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="PhaseShift", num_wires=1, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -343,10 +341,7 @@ def _data(self) -> np.ndarray: class CPhaseShift(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled phase shift gate.""" - super().__init__(name="CPhaseShift", num_wires=2, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="CPhaseShift", num_wires=2, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -358,10 +353,7 @@ def _data(self) -> np.ndarray: class CNOT(Gate): def __init__(self, *params, **kwargs): """Constructs a CNOT gate.""" - super().__init__(name="CNOT", num_wires=2, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="CNOT", num_wires=2, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -372,10 +364,7 @@ def _data(self) -> np.ndarray: class CY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Y gate.""" - super().__init__(name="CY", num_wires=2, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="CY", num_wires=2, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -386,10 +375,7 @@ def _data(self) -> np.ndarray: class CZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-Z gate.""" - super().__init__(name="CZ", num_wires=2, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="CZ", num_wires=2, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -400,10 +386,7 @@ def _data(self) -> np.ndarray: class SWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a SWAP gate.""" - super().__init__(name="SWAP", num_wires=2, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="SWAP", num_wires=2, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -414,10 +397,7 @@ def _data(self) -> np.ndarray: class ISWAP(Gate): def __init__(self, *params, **kwargs): """Constructs an ISWAP gate.""" - super().__init__(name="ISWAP", num_wires=2, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="ISWAP", num_wires=2, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -428,10 +408,7 @@ def _data(self) -> np.ndarray: class CSWAP(Gate): def __init__(self, *params, **kwargs): """Constructs a CSWAP gate.""" - super().__init__(name="CSWAP", num_wires=3, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="CSWAP", num_wires=3, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -451,10 +428,7 @@ def _data(self) -> np.ndarray: class Toffoli(Gate): def __init__(self, *params, **kwargs): """Constructs a Toffoli gate.""" - super().__init__(name="Toffoli", num_wires=3, params=params, **kwargs) - - if len(params) != 0: - raise ValueError(f"Received {len(params)} (!= 0) parameters for a {self.name} gate.") + super().__init__(name="Toffoli", num_wires=3, num_params=0, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -474,10 +448,7 @@ def _data(self) -> np.ndarray: class RX(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit X rotation gate.""" - super().__init__(name="RX", num_wires=1, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="RX", num_wires=1, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -492,10 +463,7 @@ def _data(self) -> np.ndarray: class RY(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Y rotation gate.""" - super().__init__(name="RY", num_wires=1, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="RY", num_wires=1, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -511,10 +479,7 @@ def _data(self) -> np.ndarray: class RZ(Gate): def __init__(self, *params, **kwargs): """Constructs a single-qubit Z rotation gate.""" - super().__init__(name="RZ", num_wires=1, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="RZ", num_wires=1, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -528,10 +493,7 @@ def _data(self) -> np.ndarray: class Rot(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit rotation gate.""" - super().__init__(name="Rot", num_wires=1, params=params, **kwargs) - - if len(params) != 3: - raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") + super().__init__(name="Rot", num_wires=1, num_params=3, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -549,10 +511,7 @@ def _data(self) -> np.ndarray: class CRX(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RX gate.""" - super().__init__(name="CRX", num_wires=2, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="CRX", num_wires=2, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -567,10 +526,7 @@ def _data(self) -> np.ndarray: class CRY(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RY gate.""" - super().__init__(name="CRY", num_wires=2, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="CRY", num_wires=2, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -585,10 +541,7 @@ def _data(self) -> np.ndarray: class CRZ(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-RZ gate.""" - super().__init__(name="CRZ", num_wires=2, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="CRZ", num_wires=2, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -605,10 +558,7 @@ def _data(self) -> np.ndarray: class CRot(Gate): def __init__(self, *params, **kwargs): """Constructs a controlled-rotation gate.""" - super().__init__(name="CRot", num_wires=2, params=params, **kwargs) - - if len(params) != 3: - raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") + super().__init__(name="CRot", num_wires=2, num_params=3, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -628,10 +578,7 @@ def _data(self) -> np.ndarray: class U1(Gate): def __init__(self, *params, **kwargs): """Constructs a U1 gate.""" - super().__init__(name="U1", num_wires=1, params=params, **kwargs) - - if len(params) != 1: - raise ValueError(f"Received {len(params)} (!= 1) parameters for a {self.name} gate.") + super().__init__(name="U1", num_wires=1, num_params=1, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -643,10 +590,7 @@ def _data(self) -> np.ndarray: class U2(Gate): def __init__(self, *params, **kwargs): """Constructs a U2 gate.""" - super().__init__(name="U2", num_wires=1, params=params, **kwargs) - - if len(params) != 2: - raise ValueError(f"Received {len(params)} (!= 2) parameters for a {self.name} gate.") + super().__init__(name="U2", num_wires=1, num_params=2, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -661,10 +605,7 @@ def _data(self) -> np.ndarray: class U3(Gate): def __init__(self, *params, **kwargs): """Constructs an arbitrary single-qubit unitary gate.""" - super().__init__(name="U3", num_wires=1, params=params, **kwargs) - - if len(params) != 3: - raise ValueError(f"Received {len(params)} (!= 3) parameters for a {self.name} gate.") + super().__init__(name="U3", num_wires=1, num_params=3, params=params, **kwargs) @lru_cache def _data(self) -> np.ndarray: From 686c4627fd0f6b123e202902d177b5d9cc70085d Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 11:51:10 -0400 Subject: [PATCH 51/71] Replace conjugate transpose with matrix inverse --- python/jet/gates.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index d1a3f38b..e1b6569b 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -147,9 +147,14 @@ def _data(self) -> np.ndarray: pass def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorType: - """Returns the tensor representation of this gate.""" + """Returns the tensor representation of this gate. + + Args: + dtype (type): data type of the tensor. + adjoint (bool): whether to take the adjoint of the tensor. + """ if adjoint: - data = np.conj(self._data()).T.flatten() + data = np.linalg.inv(self._data()).flatten() else: data = self._data().flatten() From ccfb710f2f99ec627f50f3f50a9e05d83820a2a7 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 11:52:20 -0400 Subject: [PATCH 52/71] Replace MockGate data with invertible matrix --- python/tests/jet/test_gates.py | 117 +++++++++++++++++---------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index d399aca3..24de4f34 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -1,21 +1,22 @@ -import math +from itertools import chain +from math import pi, sqrt import numpy as np import pytest import jet -INV_SQRT2 = 1 / math.sqrt(2) +INV_SQRT2 = 1 / sqrt(2) class MockGate(jet.Gate): """MockGate represents a fictional, unnormalized gate which can be applied to pairs of qutrits.""" def __init__(self): - super().__init__(name="MockGate", num_wires=2) + super().__init__(name="MockGate", num_params=0, num_wires=2) def _data(self) -> np.ndarray: - return np.arange(3 ** 4) * (1 + 1j) + return np.eye(3 ** 2) * (1 + 1j) class TestGate: @@ -30,7 +31,7 @@ def test_tensor_indices_not_set(self, gate): assert tensor.indices == ["0", "1", "2", "3"] assert tensor.shape == [3, 3, 3, 3] - assert tensor.data == [i * (1 + 1j) for i in range(3 ** 4)] + assert tensor.data == np.ravel(np.eye(9) * (1 + 1j)).tolist() def test_tensor_indices_are_set(self, gate): """Tests that the correct tensor is returned for a gate with specified indices.""" @@ -39,7 +40,7 @@ def test_tensor_indices_are_set(self, gate): assert tensor.indices == ["r", "g", "b", "a"] assert tensor.shape == [3, 3, 3, 3] - assert tensor.data == [i * (1 + 1j) for i in range(3 ** 4)] + assert tensor.data == np.ravel(np.eye(9) * (1 + 1j)).tolist() def test_tensor_adjoint(self, gate): """Tests that the correct adjoint tensor is returned for a gate.""" @@ -47,7 +48,7 @@ def test_tensor_adjoint(self, gate): assert tensor.indices == ["0", "1", "2", "3"] assert tensor.shape == [3, 3, 3, 3] - assert tensor.data == [i * (1 - 1j) for i in range(3 ** 4)] + assert tensor.data == np.ravel(np.eye(9) * (1 - 1j) / 2).tolist() @pytest.mark.parametrize("indices", [1, ["i", "j", "k", 4], ["x", "x", "x", "x"], []]) def test_indices_are_invalid(self, gate, indices): @@ -72,7 +73,7 @@ def test_indices_are_valid(self, gate, indices): # Continuous variable Fock gates ############################################################################################ pytest.param( - jet.Displacement(2, math.pi / 2, 3), + jet.Displacement(2, pi / 2, 3), jet.Tensor(indices=["1"], shape=[3], data=[1, 0, 0]), jet.Tensor( indices=["0"], shape=[3], data=[0.135335283237, 0.270670566473j, -0.382785986042] @@ -80,7 +81,7 @@ def test_indices_are_valid(self, gate, indices): id="Displacement(2,pi/2,3)|1>", ), pytest.param( - jet.Displacement(2, math.pi / 2, 3), + jet.Displacement(2, pi / 2, 3), jet.Tensor(indices=["1"], shape=[3], data=[0, 1, 0]), jet.Tensor( indices=["0"], shape=[3], data=[0.270670566473j, -0.40600584971, -0.382785986042j] @@ -88,7 +89,7 @@ def test_indices_are_valid(self, gate, indices): id="Displacement(2,pi/2,3)|2>", ), pytest.param( - jet.Displacement(2, math.pi / 2, 3), + jet.Displacement(2, pi / 2, 3), jet.Tensor(indices=["1"], shape=[3], data=[0, 0, 1]), jet.Tensor( indices=["0"], shape=[3], data=[-0.382785986042, -0.382785986042j, 0.135335283237] @@ -96,25 +97,25 @@ def test_indices_are_valid(self, gate, indices): id="Displacement(2,pi/2,3)|3>", ), pytest.param( - jet.Squeezing(2, math.pi / 2, 3), + jet.Squeezing(2, pi / 2, 3), jet.Tensor(indices=["1"], shape=[3], data=[1, 0, 0]), jet.Tensor(indices=["0"], shape=[3], data=[0.515560111756, 0, -0.351442087775j]), id="Squeezing(2,pi/2,3)|1>", ), pytest.param( - jet.Squeezing(2, math.pi / 2, 3), + jet.Squeezing(2, pi / 2, 3), jet.Tensor(indices=["1"], shape=[3], data=[0, 1, 0]), jet.Tensor(indices=["0"], shape=[3], data=[0, 0.137037026803, 0]), id="Squeezing(2,pi/2,3)|2>", ), pytest.param( - jet.Squeezing(2, math.pi / 2, 3), + jet.Squeezing(2, pi / 2, 3), jet.Tensor(indices=["1"], shape=[3], data=[0, 0, 1]), jet.Tensor(indices=["0"], shape=[3], data=[-0.351442087775j, 0, -0.203142935143]), id="Squeezing(2,pi/2,3)|3>", ), pytest.param( - jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.TwoModeSqueezing(3, pi / 4, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor( indices=["0", "1"], @@ -124,7 +125,7 @@ def test_indices_are_valid(self, gate, indices): id="TwoModeSqueezing(3,pi/4,2)|00>", ), pytest.param( - jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.TwoModeSqueezing(3, pi / 4, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor( indices=["0", "1"], @@ -134,7 +135,7 @@ def test_indices_are_valid(self, gate, indices): id="TwoModeSqueezing(3,pi/4,2)|01>", ), pytest.param( - jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.TwoModeSqueezing(3, pi / 4, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor( indices=["0", "1"], @@ -144,17 +145,17 @@ def test_indices_are_valid(self, gate, indices): id="TwoModeSqueezing(3,pi/4,2)|10>", ), pytest.param( - jet.TwoModeSqueezing(3, math.pi / 4, 2), + jet.TwoModeSqueezing(3, pi / 4, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor( indices=["0", "1"], shape=[2, 2], data=[-0.069888119434 + 0.069888119434j, 0, 0, -0.097367981372], ), - id="TwoModeSqueezing(3,pi/4,2)|10>", + id="TwoModeSqueezing(3,pi/4,2)|11>", ), pytest.param( - jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Beamsplitter(pi / 4, pi / 2, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor( indices=["0", "1"], @@ -164,7 +165,7 @@ def test_indices_are_valid(self, gate, indices): id="Beamsplitter(pi/4,pi/2,2)|00>", ), pytest.param( - jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Beamsplitter(pi / 4, pi / 2, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor( indices=["0", "1"], @@ -174,7 +175,7 @@ def test_indices_are_valid(self, gate, indices): id="Beamsplitter(pi/4,pi/2,2)|01>", ), pytest.param( - jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Beamsplitter(pi / 4, pi / 2, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor( indices=["0", "1"], @@ -184,7 +185,7 @@ def test_indices_are_valid(self, gate, indices): id="Beamsplitter(pi/4,pi/2,2)|10>", ), pytest.param( - jet.Beamsplitter(math.pi / 4, math.pi / 2, 2), + jet.Beamsplitter(pi / 4, pi / 2, 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor( indices=["0", "1"], @@ -290,37 +291,37 @@ def test_indices_are_valid(self, gate, indices): # Parametrized phase shift gates ############################################################################################ pytest.param( - jet.PhaseShift(math.pi / 2), + jet.PhaseShift(pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), id="P|0>", ), pytest.param( - jet.PhaseShift(math.pi / 2), + jet.PhaseShift(pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), id="P|1>", ), pytest.param( - jet.CPhaseShift(math.pi / 2), + jet.CPhaseShift(pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), id="CP|00>", ), pytest.param( - jet.CPhaseShift(math.pi / 2), + jet.CPhaseShift(pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), id="CP|01>", ), pytest.param( - jet.CPhaseShift(math.pi / 2), + jet.CPhaseShift(pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), id="CP|10>", ), pytest.param( - jet.CPhaseShift(math.pi / 2), + jet.CPhaseShift(pi / 2), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), id="CP|11>", @@ -554,49 +555,49 @@ def test_indices_are_valid(self, gate, indices): # Rotation gates ############################################################################################ pytest.param( - jet.RX(math.pi), + jet.RX(pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, -1j]), id="RX(pi)|0>", ), pytest.param( - jet.RX(math.pi), + jet.RX(pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), id="RX(pi)|1>", ), pytest.param( - jet.RY(math.pi), + jet.RY(pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1]), id="RY(pi)|0>", ), pytest.param( - jet.RY(math.pi), + jet.RY(pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[-1, 0]), id="RY(pi)|1>", ), pytest.param( - jet.RZ(math.pi), + jet.RZ(pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[-1j, 0]), id="RZ(pi)|0>", ), pytest.param( - jet.RZ(math.pi), + jet.RZ(pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), id="RY(pi)|1>", ), pytest.param( - jet.Rot(math.pi / 2, math.pi, 2 * math.pi), + jet.Rot(pi / 2, pi, 2 * pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[0, -INV_SQRT2 + INV_SQRT2 * 1j]), id="Rot(pi/2,pi,2*pi)|0>", ), pytest.param( - jet.Rot(math.pi / 2, math.pi, 2 * math.pi), + jet.Rot(pi / 2, pi, 2 * pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2 + INV_SQRT2 * 1j, 0]), id="Rot(pi/2,pi,2*pi)|1>", @@ -605,91 +606,91 @@ def test_indices_are_valid(self, gate, indices): # Controlled rotation gates ############################################################################################ pytest.param( - jet.CRX(math.pi), + jet.CRX(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), id="CRX|00>", ), pytest.param( - jet.CRX(math.pi), + jet.CRX(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), id="CRX|01>", ), pytest.param( - jet.CRX(math.pi), + jet.CRX(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -1j]), id="CRX|10>", ), pytest.param( - jet.CRX(math.pi), + jet.CRX(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), id="CRX|11>", ), pytest.param( - jet.CRY(math.pi), + jet.CRY(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), id="CRY|00>", ), pytest.param( - jet.CRY(math.pi), + jet.CRY(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), id="CRY|01>", ), pytest.param( - jet.CRY(math.pi), + jet.CRY(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), id="CRY|10>", ), pytest.param( - jet.CRY(math.pi), + jet.CRY(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1, 0]), id="CRY|11>", ), pytest.param( - jet.CRZ(math.pi), + jet.CRZ(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), id="CRZ|00>", ), pytest.param( - jet.CRZ(math.pi), + jet.CRZ(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), id="CRZ|01>", ), pytest.param( - jet.CRZ(math.pi), + jet.CRZ(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, -1j, 0]), id="CRZ|10>", ), pytest.param( - jet.CRZ(math.pi), + jet.CRZ(pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1j]), id="CRZ|11>", ), pytest.param( - jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.CRot(pi / 2, pi, 2 * pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), id="CRot|00>", ), pytest.param( - jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.CRot(pi / 2, pi, 2 * pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), id="CRot|01>", ), pytest.param( - jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.CRot(pi / 2, pi, 2 * pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor( indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, -INV_SQRT2 + INV_SQRT2 * 1j] @@ -697,7 +698,7 @@ def test_indices_are_valid(self, gate, indices): id="CRot|10>", ), pytest.param( - jet.CRot(math.pi / 2, math.pi, 2 * math.pi), + jet.CRot(pi / 2, pi, 2 * pi), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor( indices=["0", "1"], shape=[2, 2], data=[0, 0, INV_SQRT2 + INV_SQRT2 * 1j, 0] @@ -708,37 +709,37 @@ def test_indices_are_valid(self, gate, indices): # U gates ############################################################################################ pytest.param( - jet.U1(math.pi / 2), + jet.U1(pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[1, 0]), id="U1|0>", ), pytest.param( - jet.U1(math.pi / 2), + jet.U1(pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), id="U1|1>", ), pytest.param( - jet.U2(math.pi / 2, math.pi), + jet.U2(pi / 2, pi), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, INV_SQRT2 * 1j]), id="U2|0>", ), pytest.param( - jet.U2(math.pi / 2, math.pi), + jet.U2(pi / 2, pi), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[INV_SQRT2, -INV_SQRT2 * 1j]), id="U2|1>", ), pytest.param( - jet.U3(2 * math.pi, math.pi, math.pi / 2), + jet.U3(2 * pi, pi, pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[1, 0]), jet.Tensor(indices=["0"], shape=[2], data=[-1, 0]), id="U3(2*pi,pi,pi/2)|0>", ), pytest.param( - jet.U3(2 * math.pi, math.pi, math.pi / 2), + jet.U3(2 * pi, pi, pi / 2), jet.Tensor(indices=["1"], shape=[2], data=[0, 1]), jet.Tensor(indices=["0"], shape=[2], data=[0, 1j]), id="U3(2*pi,pi,pi/2)|1>", From c68c08bd84425b893e22131e2a960d872bf2d5d3 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 13:47:25 -0400 Subject: [PATCH 53/71] Move parameter validation to gate constructors --- python/jet/gates.py | 241 ++++++++++++++++++++------------- python/tests/jet/test_gates.py | 2 +- 2 files changed, 147 insertions(+), 96 deletions(-) diff --git a/python/jet/gates.py b/python/jet/gates.py index e1b6569b..8b932a58 100644 --- a/python/jet/gates.py +++ b/python/jet/gates.py @@ -60,20 +60,14 @@ def __init__( self, name: str, num_wires: int, - num_params: int, - params: Optional[List] = None, + params: Optional[List[float]] = None, tensor_id: Optional[int] = None, ): """Constructs a quantum gate. - Raises: - ValueError if the number of gate parameters differs from the - expected number of gate parameters. - Args: name (str): name of the gate. num_wires (int): number of wires the gate is applied to. - num_params (int): expected number of gate parameters. params (list or None): parameters of the gate. tensor_id (int or None): ID of the gate tensor. """ @@ -82,12 +76,7 @@ def __init__( self._indices = None self._num_wires = num_wires - self._params = params or [] - - if len(self._params) != num_params: - raise ValueError( - f"Received {len(params)} (!= {num_params}) parameters for a {name} gate." - ) + self._params = params @property def indices(self) -> Optional[Sequence[str]]: @@ -137,7 +126,7 @@ def num_wires(self) -> int: return self._num_wires @property - def params(self) -> Optional[List]: + def params(self) -> Optional[List[float]]: """Returns the parameters of this gate.""" return self._params @@ -174,17 +163,17 @@ def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorTy class Displacement(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, r: float, phi: float, cutoff: int, *params, **kwargs): """Constructs a displacement gate. See `thewalrus.displacement `__ - for more details on the gate parameters. + for more details. Args: r (float): displacement magnitude. phi (float): displacement angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Displacement", num_wires=1, num_params=3, params=params, **kwargs) + super().__init__(name="Displacement", num_wires=1, params=[r, phi, cutoff], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -192,17 +181,17 @@ def _data(self) -> np.ndarray: class Squeezing(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, r: float, theta: float, cutoff: int, **kwargs): """Constructs a squeezing gate. See `thewalrus.squeezing `__ - for more details on the gate parameters. + for more details. Args: r (float): squeezing magnitude. theta (float): squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Squeezing", num_wires=1, num_params=3, params=params, **kwargs) + super().__init__(name="Squeezing", num_wires=1, params=[r, theta, cutoff], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -210,19 +199,17 @@ def _data(self) -> np.ndarray: class TwoModeSqueezing(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, r: float, theta: float, cutoff: int, **kwargs): """Constructs a two-mode squeezing gate. See `thewalrus.two_mode_squeezing `__ - for more details on the gate parameters. + for more details. Args: r (float): squeezing magnitude. theta (float): squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__( - name="TwoModeSqueezing", num_wires=2, num_params=3, params=params, **kwargs - ) + super().__init__(name="TwoModeSqueezing", num_wires=2, params=[r, theta, cutoff], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -230,10 +217,10 @@ def _data(self) -> np.ndarray: class Beamsplitter(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, theta: float, phi: float, cutoff: int, **kwargs): """Constructs a beamsplitter gate. See `thewalrus.beamsplitter `__ - for more details on the gate parameters. + for more details. Args: theta (float): transmissivity angle of the beamsplitter. The transmissivity is @@ -241,7 +228,7 @@ def __init__(self, *params, **kwargs): phi (float): reflection phase of the beamsplitter. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Beamsplitter", num_wires=2, num_params=3, params=params, **kwargs) + super().__init__(name="Beamsplitter", num_wires=2, params=[theta, phi, cutoff], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -254,9 +241,9 @@ def _data(self) -> np.ndarray: class Hadamard(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a Hadamard gate.""" - super().__init__(name="Hadamard", num_wires=1, num_params=0, params=params, **kwargs) + super().__init__(name="Hadamard", num_wires=1, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -266,9 +253,9 @@ def _data(self) -> np.ndarray: class PauliX(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a PauliX gate.""" - super().__init__(name="PauliX", num_wires=1, num_params=0, params=params, **kwargs) + super().__init__(name="PauliX", num_wires=1, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -277,9 +264,9 @@ def _data(self) -> np.ndarray: class PauliY(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a PauliY gate.""" - super().__init__(name="PauliY", num_wires=1, num_params=0, params=params, **kwargs) + super().__init__(name="PauliY", num_wires=1, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -288,9 +275,9 @@ def _data(self) -> np.ndarray: class PauliZ(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a PauliZ gate.""" - super().__init__(name="PauliZ", num_wires=1, num_params=0, params=params, **kwargs) + super().__init__(name="PauliZ", num_wires=1, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -299,9 +286,9 @@ def _data(self) -> np.ndarray: class S(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a single-qubit phase gate.""" - super().__init__(name="S", num_wires=1, num_params=0, params=params, **kwargs) + super().__init__(name="S", num_wires=1, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -310,9 +297,9 @@ def _data(self) -> np.ndarray: class T(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a single-qubit T gate.""" - super().__init__(name="T", num_wires=1, num_params=0, params=params, **kwargs) + super().__init__(name="T", num_wires=1, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -321,9 +308,9 @@ def _data(self) -> np.ndarray: class SX(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a single-qubit Square-Root X gate.""" - super().__init__(name="SX", num_wires=1, num_params=0, params=params, **kwargs) + super().__init__(name="SX", num_wires=1, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -332,9 +319,13 @@ def _data(self) -> np.ndarray: class PhaseShift(Gate): - def __init__(self, *params, **kwargs): - """Constructs a single-qubit local phase shift gate.""" - super().__init__(name="PhaseShift", num_wires=1, num_params=1, params=params, **kwargs) + def __init__(self, phi: float, **kwargs): + """Constructs a single-qubit local phase shift gate. + + Args: + phi (float): phase shift. + """ + super().__init__(name="PhaseShift", num_wires=1, params=[phi], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -344,9 +335,13 @@ def _data(self) -> np.ndarray: class CPhaseShift(Gate): - def __init__(self, *params, **kwargs): - """Constructs a controlled phase shift gate.""" - super().__init__(name="CPhaseShift", num_wires=2, num_params=1, params=params, **kwargs) + def __init__(self, phi: float, **kwargs): + """Constructs a controlled phase shift gate. + + Args: + phi (float): phase shift. + """ + super().__init__(name="CPhaseShift", num_wires=2, params=[phi], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -356,9 +351,9 @@ def _data(self) -> np.ndarray: class CNOT(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a CNOT gate.""" - super().__init__(name="CNOT", num_wires=2, num_params=0, params=params, **kwargs) + super().__init__(name="CNOT", num_wires=2, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -367,9 +362,9 @@ def _data(self) -> np.ndarray: class CY(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a controlled-Y gate.""" - super().__init__(name="CY", num_wires=2, num_params=0, params=params, **kwargs) + super().__init__(name="CY", num_wires=2, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -378,9 +373,9 @@ def _data(self) -> np.ndarray: class CZ(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a controlled-Z gate.""" - super().__init__(name="CZ", num_wires=2, num_params=0, params=params, **kwargs) + super().__init__(name="CZ", num_wires=2, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -389,9 +384,9 @@ def _data(self) -> np.ndarray: class SWAP(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a SWAP gate.""" - super().__init__(name="SWAP", num_wires=2, num_params=0, params=params, **kwargs) + super().__init__(name="SWAP", num_wires=2, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -400,9 +395,9 @@ def _data(self) -> np.ndarray: class ISWAP(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs an ISWAP gate.""" - super().__init__(name="ISWAP", num_wires=2, num_params=0, params=params, **kwargs) + super().__init__(name="ISWAP", num_wires=2, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -411,9 +406,9 @@ def _data(self) -> np.ndarray: class CSWAP(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a CSWAP gate.""" - super().__init__(name="CSWAP", num_wires=3, num_params=0, params=params, **kwargs) + super().__init__(name="CSWAP", num_wires=3, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -431,9 +426,9 @@ def _data(self) -> np.ndarray: class Toffoli(Gate): - def __init__(self, *params, **kwargs): + def __init__(self, **kwargs): """Constructs a Toffoli gate.""" - super().__init__(name="Toffoli", num_wires=3, num_params=0, params=params, **kwargs) + super().__init__(name="Toffoli", num_wires=3, **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -451,9 +446,13 @@ def _data(self) -> np.ndarray: class RX(Gate): - def __init__(self, *params, **kwargs): - """Constructs a single-qubit X rotation gate.""" - super().__init__(name="RX", num_wires=1, num_params=1, params=params, **kwargs) + def __init__(self, theta: float, **kwargs): + """Constructs a single-qubit X rotation gate. + + Args: + theta (float): rotation angle around the X-axis. + """ + super().__init__(name="RX", num_wires=1, params=[theta], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -466,9 +465,13 @@ def _data(self) -> np.ndarray: class RY(Gate): - def __init__(self, *params, **kwargs): - """Constructs a single-qubit Y rotation gate.""" - super().__init__(name="RY", num_wires=1, num_params=1, params=params, **kwargs) + def __init__(self, theta: float, **kwargs): + """Constructs a single-qubit Y rotation gate. + + Args: + theta (float): rotation angle around the Y-axis. + """ + super().__init__(name="RY", num_wires=1, params=[theta], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -482,9 +485,13 @@ def _data(self) -> np.ndarray: class RZ(Gate): - def __init__(self, *params, **kwargs): - """Constructs a single-qubit Z rotation gate.""" - super().__init__(name="RZ", num_wires=1, num_params=1, params=params, **kwargs) + def __init__(self, theta: float, **kwargs): + """Constructs a single-qubit Z rotation gate. + + Args: + theta (float): rotation angle around the Z-axis. + """ + super().__init__(name="RZ", num_wires=1, params=[theta], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -496,9 +503,20 @@ def _data(self) -> np.ndarray: class Rot(Gate): - def __init__(self, *params, **kwargs): - """Constructs an arbitrary single-qubit rotation gate.""" - super().__init__(name="Rot", num_wires=1, num_params=3, params=params, **kwargs) + def __init__(self, phi: float, theta: float, omega: float, **kwargs): + """Constructs an arbitrary single-qubit rotation gate. Each of the Pauli + rotation gates can be recovered by fixing two of the three parameters: + + >>> assert RX(theta).tensor() == Rot(pi/2, -pi/2, theta).tensor() + >>> assert RY(theta).tensor() == Rot(0, 0, theta).tensor() + >>> assert RZ(theta).tensor() == Rot(theta, 0, 0).tensor() + + Args: + phi (float): first rotation angle. + theta (float): second rotation angle. + omega (float): third rotation angle. + """ + super().__init__(name="Rot", num_wires=1, params=[phi, theta, omega], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -514,9 +532,13 @@ def _data(self) -> np.ndarray: class CRX(Gate): - def __init__(self, *params, **kwargs): - """Constructs a controlled-RX gate.""" - super().__init__(name="CRX", num_wires=2, num_params=1, params=params, **kwargs) + def __init__(self, theta: float, **kwargs): + """Constructs a controlled-RX gate. + + Args: + theta (float): rotation angle around the X-axis. + """ + super().__init__(name="CRX", num_wires=2, params=[theta], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -529,9 +551,13 @@ def _data(self) -> np.ndarray: class CRY(Gate): - def __init__(self, *params, **kwargs): - """Constructs a controlled-RY gate.""" - super().__init__(name="CRY", num_wires=2, num_params=1, params=params, **kwargs) + def __init__(self, theta: float, **kwargs): + """Constructs a controlled-RY gate. + + Args: + theta (float): rotation angle around the Y-axis. + """ + super().__init__(name="CRY", num_wires=2, params=[theta], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -544,9 +570,13 @@ def _data(self) -> np.ndarray: class CRZ(Gate): - def __init__(self, *params, **kwargs): - """Constructs a controlled-RZ gate.""" - super().__init__(name="CRZ", num_wires=2, num_params=1, params=params, **kwargs) + def __init__(self, theta: float, **kwargs): + """Constructs a controlled-RZ gate. + + Args: + theta (float): rotation angle around the Z-axis. + """ + super().__init__(name="CRZ", num_wires=2, params=[theta], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -561,9 +591,15 @@ def _data(self) -> np.ndarray: class CRot(Gate): - def __init__(self, *params, **kwargs): - """Constructs a controlled-rotation gate.""" - super().__init__(name="CRot", num_wires=2, num_params=3, params=params, **kwargs) + def __init__(self, phi: float, theta: float, omega: float, **kwargs): + """Constructs a controlled-rotation gate. + + Args: + phi (float): first rotation angle. + theta (float): second rotation angle. + omega (float): third rotation angle. + """ + super().__init__(name="CRot", num_wires=2, params=[phi, theta, omega], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -581,9 +617,13 @@ def _data(self) -> np.ndarray: class U1(Gate): - def __init__(self, *params, **kwargs): - """Constructs a U1 gate.""" - super().__init__(name="U1", num_wires=1, num_params=1, params=params, **kwargs) + def __init__(self, phi: float, **kwargs): + """Constructs a U1 gate. + + Args: + phi (float): rotation angle. + """ + super().__init__(name="U1", num_wires=1, params=[phi], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -593,9 +633,14 @@ def _data(self) -> np.ndarray: class U2(Gate): - def __init__(self, *params, **kwargs): - """Constructs a U2 gate.""" - super().__init__(name="U2", num_wires=1, num_params=2, params=params, **kwargs) + def __init__(self, phi: float, lam: float, **kwargs): + """Constructs a U2 gate. + + Args: + phi (float): first rotation angle. + lam (float): second rotation angle. + """ + super().__init__(name="U2", num_wires=1, params=[phi, lam], **kwargs) @lru_cache def _data(self) -> np.ndarray: @@ -608,9 +653,15 @@ def _data(self) -> np.ndarray: class U3(Gate): - def __init__(self, *params, **kwargs): - """Constructs an arbitrary single-qubit unitary gate.""" - super().__init__(name="U3", num_wires=1, num_params=3, params=params, **kwargs) + def __init__(self, theta: float, phi: float, lam: float, **kwargs): + """Constructs a U3 gate. + + Args: + theta (float): first rotation angle. + phi (float): second rotation angle. + lam (float): third rotation angle. + """ + super().__init__(name="U3", num_wires=1, params=[theta, phi, lam], **kwargs) @lru_cache def _data(self) -> np.ndarray: diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gates.py index 24de4f34..b89b85be 100644 --- a/python/tests/jet/test_gates.py +++ b/python/tests/jet/test_gates.py @@ -13,7 +13,7 @@ class MockGate(jet.Gate): """MockGate represents a fictional, unnormalized gate which can be applied to pairs of qutrits.""" def __init__(self): - super().__init__(name="MockGate", num_params=0, num_wires=2) + super().__init__(name="MockGate", num_wires=2) def _data(self) -> np.ndarray: return np.eye(3 ** 2) * (1 + 1j) From 886a51349bfbce761611cd4c42b3a97da4d9ea3f Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 13:48:51 -0400 Subject: [PATCH 54/71] Rename 'gates.py' to 'gate.py' --- python/jet/__init__.py | 2 +- python/jet/{gates.py => gate.py} | 0 python/tests/jet/{test_gates.py => test_gate.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename python/jet/{gates.py => gate.py} (100%) rename python/tests/jet/{test_gates.py => test_gate.py} (100%) diff --git a/python/jet/__init__.py b/python/jet/__init__.py index 466164ef..d98ec82b 100644 --- a/python/jet/__init__.py +++ b/python/jet/__init__.py @@ -3,7 +3,7 @@ # The rest of the modules control their exports using `__all__`. from .factory import * -from .gates import * +from .gate import * # Grab the current Jet version from the C++ headers. __version__ = version() diff --git a/python/jet/gates.py b/python/jet/gate.py similarity index 100% rename from python/jet/gates.py rename to python/jet/gate.py diff --git a/python/tests/jet/test_gates.py b/python/tests/jet/test_gate.py similarity index 100% rename from python/tests/jet/test_gates.py rename to python/tests/jet/test_gate.py From 55af49d05fc90cb91d3f2a68d67fe88b48853dbc Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 15:15:16 -0400 Subject: [PATCH 55/71] Reword @indices.getter docstring --- python/jet/gate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 8b932a58..06fa67e3 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -81,15 +81,15 @@ def __init__( @property def indices(self) -> Optional[Sequence[str]]: """Returns the indices of this gate. An index is a label associated with - an axis of the tensor representation of a gate and altogether determine - the connectivity of a tensor in the context of a tensor network. + an axis of the tensor representation of a gate; the indices of a tensor + determine its connectivity in the context of a tensor network. """ return self._indices @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: """Sets the indices of this gate. If the indices of a gate are not None, - they are used to construct the tensor representation of that gate. See + they are used to construct the tensor representation of that gate. See @indices.getter for more information about tensor indices. Raises: From 4335a1f65afcb4b2bada52b7995a7feb679df731 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 16:46:38 -0400 Subject: [PATCH 56/71] Remove leftover *params from Displacement constructor --- python/jet/gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 06fa67e3..9503e855 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -163,7 +163,7 @@ def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorTy class Displacement(Gate): - def __init__(self, r: float, phi: float, cutoff: int, *params, **kwargs): + def __init__(self, r: float, phi: float, cutoff: int, **kwargs): """Constructs a displacement gate. See `thewalrus.displacement `__ for more details. From 4cf73bc17e0e39c4c9609f03b8083df1bc7d2305 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Tue, 8 Jun 2021 16:47:14 -0400 Subject: [PATCH 57/71] Import symbols from math and cmath modules explicitly --- python/jet/gate.py | 64 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 9503e855..33238bfa 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -1,7 +1,7 @@ -import cmath -import math from abc import ABC, abstractmethod +from cmath import exp from functools import lru_cache +from math import cos, sin, sqrt from typing import List, Optional, Sequence import numpy as np @@ -52,7 +52,7 @@ ] -INV_SQRT2 = 1 / math.sqrt(2) +INV_SQRT2 = 1 / sqrt(2) class Gate(ABC): @@ -303,7 +303,7 @@ def __init__(self, **kwargs): @lru_cache def _data(self) -> np.ndarray: - mat = [[1, 0], [0, cmath.exp(0.25j * np.pi)]] + mat = [[1, 0], [0, exp(0.25j * np.pi)]] return np.array(mat) @@ -330,7 +330,7 @@ def __init__(self, phi: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: phi = self.params[0] - mat = [[1, 0], [0, cmath.exp(1j * phi)]] + mat = [[1, 0], [0, exp(1j * phi)]] return np.array(mat) @@ -346,7 +346,7 @@ def __init__(self, phi: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: phi = self.params[0] - mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, cmath.exp(1j * phi)]] + mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, exp(1j * phi)]] return np.array(mat) @@ -457,8 +457,8 @@ def __init__(self, theta: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: theta = self.params[0] - c = math.cos(theta / 2) - js = 1j * math.sin(-theta / 2) + c = cos(theta / 2) + js = 1j * sin(-theta / 2) mat = [[c, js], [js, c]] return np.array(mat) @@ -477,8 +477,8 @@ def __init__(self, theta: float, **kwargs): def _data(self) -> np.ndarray: theta = self.params[0] - c = math.cos(theta / 2) - s = math.sin(theta / 2) + c = cos(theta / 2) + s = sin(theta / 2) mat = [[c, -s], [s, c]] return np.array(mat) @@ -496,7 +496,7 @@ def __init__(self, theta: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: theta = self.params[0] - p = cmath.exp(-0.5j * theta) + p = exp(-0.5j * theta) mat = [[p, 0], [0, np.conj(p)]] return np.array(mat) @@ -521,12 +521,12 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: phi, theta, omega = self.params - c = math.cos(theta / 2) - s = math.sin(theta / 2) + c = cos(theta / 2) + s = sin(theta / 2) mat = [ - [cmath.exp(-0.5j * (phi + omega)) * c, -cmath.exp(0.5j * (phi - omega)) * s], - [cmath.exp(-0.5j * (phi - omega)) * s, cmath.exp(0.5j * (phi + omega)) * c], + [exp(-0.5j * (phi + omega)) * c, -exp(0.5j * (phi - omega)) * s], + [exp(-0.5j * (phi - omega)) * s, exp(0.5j * (phi + omega)) * c], ] return np.array(mat) @@ -543,8 +543,8 @@ def __init__(self, theta: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: theta = self.params[0] - c = math.cos(theta / 2) - js = 1j * math.sin(-theta / 2) + c = cos(theta / 2) + js = 1j * sin(-theta / 2) mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, js], [0, 0, js, c]] return np.array(mat) @@ -562,8 +562,8 @@ def __init__(self, theta: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: theta = self.params[0] - c = math.cos(theta / 2) - s = math.sin(theta / 2) + c = cos(theta / 2) + s = sin(theta / 2) mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, c, -s], [0, 0, s, c]] return np.array(mat) @@ -584,8 +584,8 @@ def _data(self) -> np.ndarray: mat = [ [1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, cmath.exp(-0.5j * theta), 0], - [0, 0, 0, cmath.exp(0.5j * theta)], + [0, 0, exp(-0.5j * theta), 0], + [0, 0, 0, exp(0.5j * theta)], ] return np.array(mat) @@ -604,14 +604,14 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: phi, theta, omega = self.params - c = math.cos(theta / 2) - s = math.sin(theta / 2) + c = cos(theta / 2) + s = sin(theta / 2) mat = [ [1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, cmath.exp(-0.5j * (phi + omega)) * c, -cmath.exp(0.5j * (phi - omega)) * s], - [0, 0, cmath.exp(-0.5j * (phi - omega)) * s, cmath.exp(0.5j * (phi + omega)) * c], + [0, 0, exp(-0.5j * (phi + omega)) * c, -exp(0.5j * (phi - omega)) * s], + [0, 0, exp(-0.5j * (phi - omega)) * s, exp(0.5j * (phi + omega)) * c], ] return np.array(mat) @@ -628,7 +628,7 @@ def __init__(self, phi: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: phi = self.params[0] - mat = [[1, 0], [0, cmath.exp(1j * phi)]] + mat = [[1, 0], [0, exp(1j * phi)]] return np.array(mat) @@ -646,8 +646,8 @@ def __init__(self, phi: float, lam: float, **kwargs): def _data(self) -> np.ndarray: phi, lam = self.params mat = [ - [INV_SQRT2, -INV_SQRT2 * cmath.exp(1j * lam)], - [INV_SQRT2 * cmath.exp(1j * phi), INV_SQRT2 * cmath.exp(1j * (phi + lam))], + [INV_SQRT2, -INV_SQRT2 * exp(1j * lam)], + [INV_SQRT2 * exp(1j * phi), INV_SQRT2 * exp(1j * (phi + lam))], ] return np.array(mat) @@ -666,11 +666,11 @@ def __init__(self, theta: float, phi: float, lam: float, **kwargs): @lru_cache def _data(self) -> np.ndarray: theta, phi, lam = self.params - c = math.cos(theta / 2) - s = math.sin(theta / 2) + c = cos(theta / 2) + s = sin(theta / 2) mat = [ - [c, -s * cmath.exp(1j * lam)], - [s * cmath.exp(1j * phi), c * cmath.exp(1j * (phi + lam))], + [c, -s * exp(1j * lam)], + [s * exp(1j * phi), c * exp(1j * (phi + lam))], ] return np.array(mat) From f46188164517995adcf7f827f5fb5c7e645a7414 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 9 Jun 2021 17:32:51 -0400 Subject: [PATCH 58/71] Rename 'CNOT' gate to 'CX' gate --- python/jet/gate.py | 14 +++++++------- python/tests/jet/test_gate.py | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 33238bfa..a0e67666 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -29,7 +29,7 @@ "S", "T", "SX", - "CNOT", + "CX", "CY", "CZ", "SWAP", @@ -254,7 +254,7 @@ def _data(self) -> np.ndarray: class PauliX(Gate): def __init__(self, **kwargs): - """Constructs a PauliX gate.""" + """Constructs a Pauli-X gate.""" super().__init__(name="PauliX", num_wires=1, **kwargs) @lru_cache @@ -265,7 +265,7 @@ def _data(self) -> np.ndarray: class PauliY(Gate): def __init__(self, **kwargs): - """Constructs a PauliY gate.""" + """Constructs a Pauli-Y gate.""" super().__init__(name="PauliY", num_wires=1, **kwargs) @lru_cache @@ -276,7 +276,7 @@ def _data(self) -> np.ndarray: class PauliZ(Gate): def __init__(self, **kwargs): - """Constructs a PauliZ gate.""" + """Constructs a Pauli-Z gate.""" super().__init__(name="PauliZ", num_wires=1, **kwargs) @lru_cache @@ -350,10 +350,10 @@ def _data(self) -> np.ndarray: return np.array(mat) -class CNOT(Gate): +class CX(Gate): def __init__(self, **kwargs): - """Constructs a CNOT gate.""" - super().__init__(name="CNOT", num_wires=2, **kwargs) + """Constructs a controlled-X gate.""" + super().__init__(name="CX", num_wires=2, **kwargs) @lru_cache def _data(self) -> np.ndarray: diff --git a/python/tests/jet/test_gate.py b/python/tests/jet/test_gate.py index b89b85be..7f934a99 100644 --- a/python/tests/jet/test_gate.py +++ b/python/tests/jet/test_gate.py @@ -330,28 +330,28 @@ def test_indices_are_valid(self, gate, indices): # Control gates ############################################################################################ pytest.param( - jet.CNOT(), + jet.CX(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[1, 0, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[1, 0, 0, 0]), - id="CNOT|00>", + id="CX|00>", ), pytest.param( - jet.CNOT(), + jet.CX(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 1, 0, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 1, 0, 0]), - id="CNOT|01>", + id="CX|01>", ), pytest.param( - jet.CNOT(), + jet.CX(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 1, 0]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 0, 1]), - id="CNOT|10>", + id="CX|10>", ), pytest.param( - jet.CNOT(), + jet.CX(), jet.Tensor(indices=["2", "3"], shape=[2, 2], data=[0, 0, 0, 1]), jet.Tensor(indices=["0", "1"], shape=[2, 2], data=[0, 0, 1, 0]), - id="CNOT|11>", + id="CX|11>", ), pytest.param( jet.CY(), From e80f43a430c39c4e8369f4f87cc22f9f8678f83a Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 10 Jun 2021 10:40:37 -0400 Subject: [PATCH 59/71] Add aliases for NOT and CNOT gates --- python/jet/gate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/jet/gate.py b/python/jet/gate.py index a0e67666..78ad1aaf 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -23,12 +23,14 @@ "Beamsplitter", # Qubit gates "Hadamard", + "NOT", "PauliX", "PauliY", "PauliZ", "S", "T", "SX", + "CNOT", "CX", "CY", "CZ", @@ -674,3 +676,8 @@ def _data(self) -> np.ndarray: [s * exp(1j * phi), c * exp(1j * (phi + lam))], ] return np.array(mat) + + +# Some gates have different names depending on their context. +NOT = PauliX +CNOT = CX From 6708325fea1bf0a1721574ae4c7df01860ca145a Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:21:43 -0400 Subject: [PATCH 60/71] Add code span formatting to None Co-authored-by: antalszava --- python/jet/gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 78ad1aaf..2b1b9dab 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -90,7 +90,7 @@ def indices(self) -> Optional[Sequence[str]]: @indices.setter def indices(self, indices: Optional[Sequence[str]]) -> None: - """Sets the indices of this gate. If the indices of a gate are not None, + """Sets the indices of this gate. If the indices of a gate are not ``None``, they are used to construct the tensor representation of that gate. See @indices.getter for more information about tensor indices. From c6a782baf5fa35334d461319d5d0c4af79778c71 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:23:46 -0400 Subject: [PATCH 61/71] Add colon after Exception class in docstring Co-authored-by: antalszava --- python/jet/gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 2b1b9dab..0c6bb205 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -95,7 +95,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: @indices.getter for more information about tensor indices. Raises: - ValueError if the given indices are not a sequence of unique strings + ValueError: if the given indices are not a sequence of unique strings or the number of provided indices is invalid. Args: From 7922fd173cdbb4dfebd52f204c1a7fe2feed9ee6 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:25:50 -0400 Subject: [PATCH 62/71] Apply hanging indent to Exception description Co-authored-by: antalszava --- python/jet/gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 0c6bb205..166fa979 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -96,7 +96,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: Raises: ValueError: if the given indices are not a sequence of unique strings - or the number of provided indices is invalid. + or the number of provided indices is invalid. Args: indices (Sequence[str] or None): new indices of the gate. From 1b9ac3b1cc62ba347c96c60225de7c2aeaa2e20e Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:26:38 -0400 Subject: [PATCH 63/71] Update wording of num_wires attribute Co-authored-by: antalszava --- python/jet/gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 166fa979..5aabaf93 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -124,7 +124,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: @property def num_wires(self) -> int: - """Returns the number of wires connected to this gate.""" + """Returns the number of wires this gate acts on.""" return self._num_wires @property From c2d2630f904cc4c8268e49f4a94fac37dabe60c3 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:31:04 -0400 Subject: [PATCH 64/71] Remove full stop from docstring argument descriptions --- python/jet/gate.py | 80 +++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 5aabaf93..d9f74bde 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -68,10 +68,10 @@ def __init__( """Constructs a quantum gate. Args: - name (str): name of the gate. - num_wires (int): number of wires the gate is applied to. - params (list or None): parameters of the gate. - tensor_id (int or None): ID of the gate tensor. + name (str): name of the gate + num_wires (int): number of wires the gate is applied to + params (list or None): parameters of the gate + tensor_id (int or None): ID of the gate tensor """ self.name = name self.tensor_id = tensor_id @@ -99,7 +99,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: or the number of provided indices is invalid. Args: - indices (Sequence[str] or None): new indices of the gate. + indices (Sequence[str] or None): new indices of the gate """ # Skip the sequence property checks if `indices` is None. if indices is None: @@ -141,8 +141,8 @@ def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorTy """Returns the tensor representation of this gate. Args: - dtype (type): data type of the tensor. - adjoint (bool): whether to take the adjoint of the tensor. + dtype (type): data type of the tensor + adjoint (bool): whether to take the adjoint of the tensor """ if adjoint: data = np.linalg.inv(self._data()).flatten() @@ -171,9 +171,9 @@ def __init__(self, r: float, phi: float, cutoff: int, **kwargs): for more details. Args: - r (float): displacement magnitude. - phi (float): displacement angle. - cutoff (int): Fock ladder cutoff. + r (float): displacement magnitude + phi (float): displacement angle + cutoff (int): Fock ladder cutoff """ super().__init__(name="Displacement", num_wires=1, params=[r, phi, cutoff], **kwargs) @@ -189,9 +189,9 @@ def __init__(self, r: float, theta: float, cutoff: int, **kwargs): for more details. Args: - r (float): squeezing magnitude. - theta (float): squeezing angle. - cutoff (int): Fock ladder cutoff. + r (float): squeezing magnitude + theta (float): squeezing angle + cutoff (int): Fock ladder cutoff """ super().__init__(name="Squeezing", num_wires=1, params=[r, theta, cutoff], **kwargs) @@ -207,9 +207,9 @@ def __init__(self, r: float, theta: float, cutoff: int, **kwargs): for more details. Args: - r (float): squeezing magnitude. - theta (float): squeezing angle. - cutoff (int): Fock ladder cutoff. + r (float): squeezing magnitude + theta (float): squeezing angle + cutoff (int): Fock ladder cutoff """ super().__init__(name="TwoModeSqueezing", num_wires=2, params=[r, theta, cutoff], **kwargs) @@ -225,10 +225,10 @@ def __init__(self, theta: float, phi: float, cutoff: int, **kwargs): for more details. Args: - theta (float): transmissivity angle of the beamsplitter. The transmissivity is - :math:`t=\\cos(\\theta)`. - phi (float): reflection phase of the beamsplitter. - cutoff (int): Fock ladder cutoff. + theta (float): transmissivity angle of the beamsplitter. The + transmissivity is :math:`t=\\cos(\\theta)`. + phi (float): reflection phase of the beamsplitter + cutoff (int): Fock ladder cutoff """ super().__init__(name="Beamsplitter", num_wires=2, params=[theta, phi, cutoff], **kwargs) @@ -325,7 +325,7 @@ def __init__(self, phi: float, **kwargs): """Constructs a single-qubit local phase shift gate. Args: - phi (float): phase shift. + phi (float): phase shift """ super().__init__(name="PhaseShift", num_wires=1, params=[phi], **kwargs) @@ -341,7 +341,7 @@ def __init__(self, phi: float, **kwargs): """Constructs a controlled phase shift gate. Args: - phi (float): phase shift. + phi (float): phase shift """ super().__init__(name="CPhaseShift", num_wires=2, params=[phi], **kwargs) @@ -452,7 +452,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a single-qubit X rotation gate. Args: - theta (float): rotation angle around the X-axis. + theta (float): rotation angle around the X-axis """ super().__init__(name="RX", num_wires=1, params=[theta], **kwargs) @@ -471,7 +471,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a single-qubit Y rotation gate. Args: - theta (float): rotation angle around the Y-axis. + theta (float): rotation angle around the Y-axis """ super().__init__(name="RY", num_wires=1, params=[theta], **kwargs) @@ -491,7 +491,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a single-qubit Z rotation gate. Args: - theta (float): rotation angle around the Z-axis. + theta (float): rotation angle around the Z-axis """ super().__init__(name="RZ", num_wires=1, params=[theta], **kwargs) @@ -514,9 +514,9 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): >>> assert RZ(theta).tensor() == Rot(theta, 0, 0).tensor() Args: - phi (float): first rotation angle. - theta (float): second rotation angle. - omega (float): third rotation angle. + phi (float): first rotation angle + theta (float): second rotation angle + omega (float): third rotation angle """ super().__init__(name="Rot", num_wires=1, params=[phi, theta, omega], **kwargs) @@ -538,7 +538,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a controlled-RX gate. Args: - theta (float): rotation angle around the X-axis. + theta (float): rotation angle around the X-axis """ super().__init__(name="CRX", num_wires=2, params=[theta], **kwargs) @@ -557,7 +557,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a controlled-RY gate. Args: - theta (float): rotation angle around the Y-axis. + theta (float): rotation angle around the Y-axis """ super().__init__(name="CRY", num_wires=2, params=[theta], **kwargs) @@ -576,7 +576,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a controlled-RZ gate. Args: - theta (float): rotation angle around the Z-axis. + theta (float): rotation angle around the Z-axis """ super().__init__(name="CRZ", num_wires=2, params=[theta], **kwargs) @@ -597,9 +597,9 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): """Constructs a controlled-rotation gate. Args: - phi (float): first rotation angle. - theta (float): second rotation angle. - omega (float): third rotation angle. + phi (float): first rotation angle + theta (float): second rotation angle + omega (float): third rotation angle """ super().__init__(name="CRot", num_wires=2, params=[phi, theta, omega], **kwargs) @@ -623,7 +623,7 @@ def __init__(self, phi: float, **kwargs): """Constructs a U1 gate. Args: - phi (float): rotation angle. + phi (float): rotation angle """ super().__init__(name="U1", num_wires=1, params=[phi], **kwargs) @@ -639,8 +639,8 @@ def __init__(self, phi: float, lam: float, **kwargs): """Constructs a U2 gate. Args: - phi (float): first rotation angle. - lam (float): second rotation angle. + phi (float): first rotation angle + lam (float): second rotation angle """ super().__init__(name="U2", num_wires=1, params=[phi, lam], **kwargs) @@ -659,9 +659,9 @@ def __init__(self, theta: float, phi: float, lam: float, **kwargs): """Constructs a U3 gate. Args: - theta (float): first rotation angle. - phi (float): second rotation angle. - lam (float): third rotation angle. + theta (float): first rotation angle + phi (float): second rotation angle + lam (float): third rotation angle """ super().__init__(name="U3", num_wires=1, params=[theta, phi, lam], **kwargs) From 9534313fa234e8fc8bfbb816e6352753cad5f0af Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:39:19 -0400 Subject: [PATCH 65/71] Restore full stop and capitalize argument descriptions --- python/jet/gate.py | 78 +++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index d9f74bde..09a446bc 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -68,10 +68,10 @@ def __init__( """Constructs a quantum gate. Args: - name (str): name of the gate - num_wires (int): number of wires the gate is applied to - params (list or None): parameters of the gate - tensor_id (int or None): ID of the gate tensor + name (str): Name of the gate. + num_wires (int): Number of wires the gate is applied to. + params (list or None): Parameters of the gate. + tensor_id (int or None): ID of the gate tensor. """ self.name = name self.tensor_id = tensor_id @@ -99,7 +99,7 @@ def indices(self, indices: Optional[Sequence[str]]) -> None: or the number of provided indices is invalid. Args: - indices (Sequence[str] or None): new indices of the gate + indices (Sequence[str] or None): New indices of the gate. """ # Skip the sequence property checks if `indices` is None. if indices is None: @@ -141,8 +141,8 @@ def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorTy """Returns the tensor representation of this gate. Args: - dtype (type): data type of the tensor - adjoint (bool): whether to take the adjoint of the tensor + dtype (type): Data type of the tensor. + adjoint (bool): Whether to take the adjoint of the tensor. """ if adjoint: data = np.linalg.inv(self._data()).flatten() @@ -171,9 +171,9 @@ def __init__(self, r: float, phi: float, cutoff: int, **kwargs): for more details. Args: - r (float): displacement magnitude - phi (float): displacement angle - cutoff (int): Fock ladder cutoff + r (float): Displacement magnitude. + phi (float): Displacement angle. + cutoff (int): Fock ladder cutoff. """ super().__init__(name="Displacement", num_wires=1, params=[r, phi, cutoff], **kwargs) @@ -189,9 +189,9 @@ def __init__(self, r: float, theta: float, cutoff: int, **kwargs): for more details. Args: - r (float): squeezing magnitude - theta (float): squeezing angle - cutoff (int): Fock ladder cutoff + r (float): Squeezing magnitude. + theta (float): Squeezing angle. + cutoff (int): Fock ladder cutoff. """ super().__init__(name="Squeezing", num_wires=1, params=[r, theta, cutoff], **kwargs) @@ -207,9 +207,9 @@ def __init__(self, r: float, theta: float, cutoff: int, **kwargs): for more details. Args: - r (float): squeezing magnitude - theta (float): squeezing angle - cutoff (int): Fock ladder cutoff + r (float): Squeezing magnitude. + theta (float): Squeezing angle. + cutoff (int): Fock ladder cutoff. """ super().__init__(name="TwoModeSqueezing", num_wires=2, params=[r, theta, cutoff], **kwargs) @@ -225,10 +225,10 @@ def __init__(self, theta: float, phi: float, cutoff: int, **kwargs): for more details. Args: - theta (float): transmissivity angle of the beamsplitter. The + theta (float): Transmissivity angle of The beamsplitter. The. transmissivity is :math:`t=\\cos(\\theta)`. - phi (float): reflection phase of the beamsplitter - cutoff (int): Fock ladder cutoff + phi (float): Reflection phase of the beamsplitter. + cutoff (int): Fock ladder cutoff. """ super().__init__(name="Beamsplitter", num_wires=2, params=[theta, phi, cutoff], **kwargs) @@ -325,7 +325,7 @@ def __init__(self, phi: float, **kwargs): """Constructs a single-qubit local phase shift gate. Args: - phi (float): phase shift + phi (float): Phase shift angle. """ super().__init__(name="PhaseShift", num_wires=1, params=[phi], **kwargs) @@ -341,7 +341,7 @@ def __init__(self, phi: float, **kwargs): """Constructs a controlled phase shift gate. Args: - phi (float): phase shift + phi (float): Phase shift angle. """ super().__init__(name="CPhaseShift", num_wires=2, params=[phi], **kwargs) @@ -452,7 +452,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a single-qubit X rotation gate. Args: - theta (float): rotation angle around the X-axis + theta (float): Rotation angle around the X-axis. """ super().__init__(name="RX", num_wires=1, params=[theta], **kwargs) @@ -471,7 +471,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a single-qubit Y rotation gate. Args: - theta (float): rotation angle around the Y-axis + theta (float): Rotation angle around the Y-axis. """ super().__init__(name="RY", num_wires=1, params=[theta], **kwargs) @@ -491,7 +491,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a single-qubit Z rotation gate. Args: - theta (float): rotation angle around the Z-axis + theta (float): Rotation angle around the Z-axis. """ super().__init__(name="RZ", num_wires=1, params=[theta], **kwargs) @@ -514,9 +514,9 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): >>> assert RZ(theta).tensor() == Rot(theta, 0, 0).tensor() Args: - phi (float): first rotation angle - theta (float): second rotation angle - omega (float): third rotation angle + phi (float): First rotation angle. + theta (float): Second rotation angle. + omega (float): Third rotation angle. """ super().__init__(name="Rot", num_wires=1, params=[phi, theta, omega], **kwargs) @@ -538,7 +538,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a controlled-RX gate. Args: - theta (float): rotation angle around the X-axis + theta (float): Rotation angle around the X-axis. """ super().__init__(name="CRX", num_wires=2, params=[theta], **kwargs) @@ -557,7 +557,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a controlled-RY gate. Args: - theta (float): rotation angle around the Y-axis + theta (float): Rotation angle around the Y-axis. """ super().__init__(name="CRY", num_wires=2, params=[theta], **kwargs) @@ -576,7 +576,7 @@ def __init__(self, theta: float, **kwargs): """Constructs a controlled-RZ gate. Args: - theta (float): rotation angle around the Z-axis + theta (float): Rotation angle around the Z-axis. """ super().__init__(name="CRZ", num_wires=2, params=[theta], **kwargs) @@ -597,9 +597,9 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): """Constructs a controlled-rotation gate. Args: - phi (float): first rotation angle - theta (float): second rotation angle - omega (float): third rotation angle + phi (float): First rotation angle. + theta (float): Second rotation angle. + omega (float): Third rotation angle. """ super().__init__(name="CRot", num_wires=2, params=[phi, theta, omega], **kwargs) @@ -623,7 +623,7 @@ def __init__(self, phi: float, **kwargs): """Constructs a U1 gate. Args: - phi (float): rotation angle + phi (float): Rotation angle. """ super().__init__(name="U1", num_wires=1, params=[phi], **kwargs) @@ -639,8 +639,8 @@ def __init__(self, phi: float, lam: float, **kwargs): """Constructs a U2 gate. Args: - phi (float): first rotation angle - lam (float): second rotation angle + phi (float): First rotation angle. + lam (float): Second rotation angle. """ super().__init__(name="U2", num_wires=1, params=[phi, lam], **kwargs) @@ -659,9 +659,9 @@ def __init__(self, theta: float, phi: float, lam: float, **kwargs): """Constructs a U3 gate. Args: - theta (float): first rotation angle - phi (float): second rotation angle - lam (float): third rotation angle + theta (float): First rotation angle. + phi (float): Second rotation angle. + lam (float): Third rotation angle. """ super().__init__(name="U3", num_wires=1, params=[theta, phi, lam], **kwargs) From 1b43784a8f43a3eb747ad5b7f5558790c6b4133e Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:45:56 -0400 Subject: [PATCH 66/71] Fix grammar error in transmissivity argument description --- python/jet/gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index 09a446bc..e51efcb5 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -225,7 +225,7 @@ def __init__(self, theta: float, phi: float, cutoff: int, **kwargs): for more details. Args: - theta (float): Transmissivity angle of The beamsplitter. The. + theta (float): Transmissivity angle of the beamsplitter. The transmissivity is :math:`t=\\cos(\\theta)`. phi (float): Reflection phase of the beamsplitter. cutoff (int): Fock ladder cutoff. From 7552030a98f99ca9eb6b6bb1ff07427993da2789 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 10:55:58 -0400 Subject: [PATCH 67/71] Remove tensor ID (and **kwargs) from Gate (sub)class(es) --- python/jet/gate.py | 127 ++++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/python/jet/gate.py b/python/jet/gate.py index e51efcb5..7361b401 100644 --- a/python/jet/gate.py +++ b/python/jet/gate.py @@ -63,7 +63,6 @@ def __init__( name: str, num_wires: int, params: Optional[List[float]] = None, - tensor_id: Optional[int] = None, ): """Constructs a quantum gate. @@ -71,10 +70,8 @@ def __init__( name (str): Name of the gate. num_wires (int): Number of wires the gate is applied to. params (list or None): Parameters of the gate. - tensor_id (int or None): ID of the gate tensor. """ self.name = name - self.tensor_id = tensor_id self._indices = None self._num_wires = num_wires @@ -165,7 +162,7 @@ def tensor(self, dtype: type = np.complex128, adjoint: bool = False) -> TensorTy class Displacement(Gate): - def __init__(self, r: float, phi: float, cutoff: int, **kwargs): + def __init__(self, r: float, phi: float, cutoff: int): """Constructs a displacement gate. See `thewalrus.displacement `__ for more details. @@ -175,7 +172,7 @@ def __init__(self, r: float, phi: float, cutoff: int, **kwargs): phi (float): Displacement angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Displacement", num_wires=1, params=[r, phi, cutoff], **kwargs) + super().__init__(name="Displacement", num_wires=1, params=[r, phi, cutoff]) @lru_cache def _data(self) -> np.ndarray: @@ -183,7 +180,7 @@ def _data(self) -> np.ndarray: class Squeezing(Gate): - def __init__(self, r: float, theta: float, cutoff: int, **kwargs): + def __init__(self, r: float, theta: float, cutoff: int): """Constructs a squeezing gate. See `thewalrus.squeezing `__ for more details. @@ -193,7 +190,7 @@ def __init__(self, r: float, theta: float, cutoff: int, **kwargs): theta (float): Squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Squeezing", num_wires=1, params=[r, theta, cutoff], **kwargs) + super().__init__(name="Squeezing", num_wires=1, params=[r, theta, cutoff]) @lru_cache def _data(self) -> np.ndarray: @@ -201,7 +198,7 @@ def _data(self) -> np.ndarray: class TwoModeSqueezing(Gate): - def __init__(self, r: float, theta: float, cutoff: int, **kwargs): + def __init__(self, r: float, theta: float, cutoff: int): """Constructs a two-mode squeezing gate. See `thewalrus.two_mode_squeezing `__ for more details. @@ -211,7 +208,7 @@ def __init__(self, r: float, theta: float, cutoff: int, **kwargs): theta (float): Squeezing angle. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="TwoModeSqueezing", num_wires=2, params=[r, theta, cutoff], **kwargs) + super().__init__(name="TwoModeSqueezing", num_wires=2, params=[r, theta, cutoff]) @lru_cache def _data(self) -> np.ndarray: @@ -219,7 +216,7 @@ def _data(self) -> np.ndarray: class Beamsplitter(Gate): - def __init__(self, theta: float, phi: float, cutoff: int, **kwargs): + def __init__(self, theta: float, phi: float, cutoff: int): """Constructs a beamsplitter gate. See `thewalrus.beamsplitter `__ for more details. @@ -230,7 +227,7 @@ def __init__(self, theta: float, phi: float, cutoff: int, **kwargs): phi (float): Reflection phase of the beamsplitter. cutoff (int): Fock ladder cutoff. """ - super().__init__(name="Beamsplitter", num_wires=2, params=[theta, phi, cutoff], **kwargs) + super().__init__(name="Beamsplitter", num_wires=2, params=[theta, phi, cutoff]) @lru_cache def _data(self) -> np.ndarray: @@ -243,9 +240,9 @@ def _data(self) -> np.ndarray: class Hadamard(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a Hadamard gate.""" - super().__init__(name="Hadamard", num_wires=1, **kwargs) + super().__init__(name="Hadamard", num_wires=1) @lru_cache def _data(self) -> np.ndarray: @@ -255,9 +252,9 @@ def _data(self) -> np.ndarray: class PauliX(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a Pauli-X gate.""" - super().__init__(name="PauliX", num_wires=1, **kwargs) + super().__init__(name="PauliX", num_wires=1) @lru_cache def _data(self) -> np.ndarray: @@ -266,9 +263,9 @@ def _data(self) -> np.ndarray: class PauliY(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a Pauli-Y gate.""" - super().__init__(name="PauliY", num_wires=1, **kwargs) + super().__init__(name="PauliY", num_wires=1) @lru_cache def _data(self) -> np.ndarray: @@ -277,9 +274,9 @@ def _data(self) -> np.ndarray: class PauliZ(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a Pauli-Z gate.""" - super().__init__(name="PauliZ", num_wires=1, **kwargs) + super().__init__(name="PauliZ", num_wires=1) @lru_cache def _data(self) -> np.ndarray: @@ -288,9 +285,9 @@ def _data(self) -> np.ndarray: class S(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a single-qubit phase gate.""" - super().__init__(name="S", num_wires=1, **kwargs) + super().__init__(name="S", num_wires=1) @lru_cache def _data(self) -> np.ndarray: @@ -299,9 +296,9 @@ def _data(self) -> np.ndarray: class T(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a single-qubit T gate.""" - super().__init__(name="T", num_wires=1, **kwargs) + super().__init__(name="T", num_wires=1) @lru_cache def _data(self) -> np.ndarray: @@ -310,9 +307,9 @@ def _data(self) -> np.ndarray: class SX(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a single-qubit Square-Root X gate.""" - super().__init__(name="SX", num_wires=1, **kwargs) + super().__init__(name="SX", num_wires=1) @lru_cache def _data(self) -> np.ndarray: @@ -321,13 +318,13 @@ def _data(self) -> np.ndarray: class PhaseShift(Gate): - def __init__(self, phi: float, **kwargs): + def __init__(self, phi: float): """Constructs a single-qubit local phase shift gate. Args: phi (float): Phase shift angle. """ - super().__init__(name="PhaseShift", num_wires=1, params=[phi], **kwargs) + super().__init__(name="PhaseShift", num_wires=1, params=[phi]) @lru_cache def _data(self) -> np.ndarray: @@ -337,13 +334,13 @@ def _data(self) -> np.ndarray: class CPhaseShift(Gate): - def __init__(self, phi: float, **kwargs): + def __init__(self, phi: float): """Constructs a controlled phase shift gate. Args: phi (float): Phase shift angle. """ - super().__init__(name="CPhaseShift", num_wires=2, params=[phi], **kwargs) + super().__init__(name="CPhaseShift", num_wires=2, params=[phi]) @lru_cache def _data(self) -> np.ndarray: @@ -353,9 +350,9 @@ def _data(self) -> np.ndarray: class CX(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a controlled-X gate.""" - super().__init__(name="CX", num_wires=2, **kwargs) + super().__init__(name="CX", num_wires=2) @lru_cache def _data(self) -> np.ndarray: @@ -364,9 +361,9 @@ def _data(self) -> np.ndarray: class CY(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a controlled-Y gate.""" - super().__init__(name="CY", num_wires=2, **kwargs) + super().__init__(name="CY", num_wires=2) @lru_cache def _data(self) -> np.ndarray: @@ -375,9 +372,9 @@ def _data(self) -> np.ndarray: class CZ(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a controlled-Z gate.""" - super().__init__(name="CZ", num_wires=2, **kwargs) + super().__init__(name="CZ", num_wires=2) @lru_cache def _data(self) -> np.ndarray: @@ -386,9 +383,9 @@ def _data(self) -> np.ndarray: class SWAP(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a SWAP gate.""" - super().__init__(name="SWAP", num_wires=2, **kwargs) + super().__init__(name="SWAP", num_wires=2) @lru_cache def _data(self) -> np.ndarray: @@ -397,9 +394,9 @@ def _data(self) -> np.ndarray: class ISWAP(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs an ISWAP gate.""" - super().__init__(name="ISWAP", num_wires=2, **kwargs) + super().__init__(name="ISWAP", num_wires=2) @lru_cache def _data(self) -> np.ndarray: @@ -408,9 +405,9 @@ def _data(self) -> np.ndarray: class CSWAP(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a CSWAP gate.""" - super().__init__(name="CSWAP", num_wires=3, **kwargs) + super().__init__(name="CSWAP", num_wires=3) @lru_cache def _data(self) -> np.ndarray: @@ -428,9 +425,9 @@ def _data(self) -> np.ndarray: class Toffoli(Gate): - def __init__(self, **kwargs): + def __init__(self): """Constructs a Toffoli gate.""" - super().__init__(name="Toffoli", num_wires=3, **kwargs) + super().__init__(name="Toffoli", num_wires=3) @lru_cache def _data(self) -> np.ndarray: @@ -448,13 +445,13 @@ def _data(self) -> np.ndarray: class RX(Gate): - def __init__(self, theta: float, **kwargs): + def __init__(self, theta: float): """Constructs a single-qubit X rotation gate. Args: theta (float): Rotation angle around the X-axis. """ - super().__init__(name="RX", num_wires=1, params=[theta], **kwargs) + super().__init__(name="RX", num_wires=1, params=[theta]) @lru_cache def _data(self) -> np.ndarray: @@ -467,13 +464,13 @@ def _data(self) -> np.ndarray: class RY(Gate): - def __init__(self, theta: float, **kwargs): + def __init__(self, theta: float): """Constructs a single-qubit Y rotation gate. Args: theta (float): Rotation angle around the Y-axis. """ - super().__init__(name="RY", num_wires=1, params=[theta], **kwargs) + super().__init__(name="RY", num_wires=1, params=[theta]) @lru_cache def _data(self) -> np.ndarray: @@ -487,13 +484,13 @@ def _data(self) -> np.ndarray: class RZ(Gate): - def __init__(self, theta: float, **kwargs): + def __init__(self, theta: float): """Constructs a single-qubit Z rotation gate. Args: theta (float): Rotation angle around the Z-axis. """ - super().__init__(name="RZ", num_wires=1, params=[theta], **kwargs) + super().__init__(name="RZ", num_wires=1, params=[theta]) @lru_cache def _data(self) -> np.ndarray: @@ -505,7 +502,7 @@ def _data(self) -> np.ndarray: class Rot(Gate): - def __init__(self, phi: float, theta: float, omega: float, **kwargs): + def __init__(self, phi: float, theta: float, omega: float): """Constructs an arbitrary single-qubit rotation gate. Each of the Pauli rotation gates can be recovered by fixing two of the three parameters: @@ -518,7 +515,7 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): theta (float): Second rotation angle. omega (float): Third rotation angle. """ - super().__init__(name="Rot", num_wires=1, params=[phi, theta, omega], **kwargs) + super().__init__(name="Rot", num_wires=1, params=[phi, theta, omega]) @lru_cache def _data(self) -> np.ndarray: @@ -534,13 +531,13 @@ def _data(self) -> np.ndarray: class CRX(Gate): - def __init__(self, theta: float, **kwargs): + def __init__(self, theta: float): """Constructs a controlled-RX gate. Args: theta (float): Rotation angle around the X-axis. """ - super().__init__(name="CRX", num_wires=2, params=[theta], **kwargs) + super().__init__(name="CRX", num_wires=2, params=[theta]) @lru_cache def _data(self) -> np.ndarray: @@ -553,13 +550,13 @@ def _data(self) -> np.ndarray: class CRY(Gate): - def __init__(self, theta: float, **kwargs): + def __init__(self, theta: float): """Constructs a controlled-RY gate. Args: theta (float): Rotation angle around the Y-axis. """ - super().__init__(name="CRY", num_wires=2, params=[theta], **kwargs) + super().__init__(name="CRY", num_wires=2, params=[theta]) @lru_cache def _data(self) -> np.ndarray: @@ -572,13 +569,13 @@ def _data(self) -> np.ndarray: class CRZ(Gate): - def __init__(self, theta: float, **kwargs): + def __init__(self, theta: float): """Constructs a controlled-RZ gate. Args: theta (float): Rotation angle around the Z-axis. """ - super().__init__(name="CRZ", num_wires=2, params=[theta], **kwargs) + super().__init__(name="CRZ", num_wires=2, params=[theta]) @lru_cache def _data(self) -> np.ndarray: @@ -593,7 +590,7 @@ def _data(self) -> np.ndarray: class CRot(Gate): - def __init__(self, phi: float, theta: float, omega: float, **kwargs): + def __init__(self, phi: float, theta: float, omega: float): """Constructs a controlled-rotation gate. Args: @@ -601,7 +598,7 @@ def __init__(self, phi: float, theta: float, omega: float, **kwargs): theta (float): Second rotation angle. omega (float): Third rotation angle. """ - super().__init__(name="CRot", num_wires=2, params=[phi, theta, omega], **kwargs) + super().__init__(name="CRot", num_wires=2, params=[phi, theta, omega]) @lru_cache def _data(self) -> np.ndarray: @@ -619,13 +616,13 @@ def _data(self) -> np.ndarray: class U1(Gate): - def __init__(self, phi: float, **kwargs): + def __init__(self, phi: float): """Constructs a U1 gate. Args: phi (float): Rotation angle. """ - super().__init__(name="U1", num_wires=1, params=[phi], **kwargs) + super().__init__(name="U1", num_wires=1, params=[phi]) @lru_cache def _data(self) -> np.ndarray: @@ -635,14 +632,14 @@ def _data(self) -> np.ndarray: class U2(Gate): - def __init__(self, phi: float, lam: float, **kwargs): + def __init__(self, phi: float, lam: float): """Constructs a U2 gate. Args: phi (float): First rotation angle. lam (float): Second rotation angle. """ - super().__init__(name="U2", num_wires=1, params=[phi, lam], **kwargs) + super().__init__(name="U2", num_wires=1, params=[phi, lam]) @lru_cache def _data(self) -> np.ndarray: @@ -655,7 +652,7 @@ def _data(self) -> np.ndarray: class U3(Gate): - def __init__(self, theta: float, phi: float, lam: float, **kwargs): + def __init__(self, theta: float, phi: float, lam: float): """Constructs a U3 gate. Args: @@ -663,7 +660,7 @@ def __init__(self, theta: float, phi: float, lam: float, **kwargs): phi (float): Second rotation angle. lam (float): Third rotation angle. """ - super().__init__(name="U3", num_wires=1, params=[theta, phi, lam], **kwargs) + super().__init__(name="U3", num_wires=1, params=[theta, phi, lam]) @lru_cache def _data(self) -> np.ndarray: From 5335954611025989ba2a38c352f738f684ca5fca Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 11:12:34 -0400 Subject: [PATCH 68/71] Test multi-character string indices --- python/tests/jet/test_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/jet/test_gate.py b/python/tests/jet/test_gate.py index 7f934a99..f610eec6 100644 --- a/python/tests/jet/test_gate.py +++ b/python/tests/jet/test_gate.py @@ -58,7 +58,7 @@ def test_indices_are_invalid(self, gate, indices): with pytest.raises(ValueError): gate.indices = indices - @pytest.mark.parametrize("indices", [None, ["1", "2", "3", "4"]]) + @pytest.mark.parametrize("indices", [None, ["1", "2", "3", "4"], ["ABC", "D", "E" , "F"]]) def test_indices_are_valid(self, gate, indices): """Tests that the indices of a gate can be set and retrieved.""" assert gate.indices is None From 73777eecee6e0ea8d1b81b38c36aab624654429b Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 11:26:43 -0400 Subject: [PATCH 69/71] Add descriptions to *args and **kwargs --- python/jet/factory.py | 65 +++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/python/jet/factory.py b/python/jet/factory.py index af26701e..50f2151e 100644 --- a/python/jet/factory.py +++ b/python/jet/factory.py @@ -37,11 +37,18 @@ def TaskBasedCpuContractor(*args, **kwargs) -> TaskBasedCpuContractorType: - """Constructs a task-based CPU contractor with the specified data type. If a - `dtype` keyword argument is not provided, a TaskBasedCpuContractorC128 - instance will be returned. + """Constructs a task-based CPU contractor (TBCC) with the specified data + type. If a ``dtype`` keyword argument is not provided, a + ``TaskBasedCpuContractorC128`` instance will be returned. + + Args: + *args: Positional arguments to pass to the TBCC constructor. + **kwargs: Keyword arguments to pass to the TBCC constructor. + + Returns: + Task-based CPU contractor instance. """ - dtype = kwargs.pop("dtype", "complex128") + dtype = kwargs.pop("dtype", np.complex128) if np.dtype(dtype) == np.complex64: return TaskBasedCpuContractorC64(*args, **kwargs) elif np.dtype(dtype) == np.complex128: @@ -51,10 +58,17 @@ def TaskBasedCpuContractor(*args, **kwargs) -> TaskBasedCpuContractorType: def Tensor(*args, **kwargs) -> TensorType: - """Constructs a tensor with the specified data type. If a `dtype` keyword - argument is not provided, a TensorC128 instance will be returned. + """Constructs a tensor with the specified data type. If a ``dtype`` keyword + argument is not provided, a ``TensorC128`` instance will be returned. + + Args: + *args: Positional arguments to pass to the tensor constructor. + **kwargs: Keyword arguments to pass to the tensor constructor. + + Returns: + Tensor instance. """ - dtype = kwargs.pop("dtype", "complex128") + dtype = kwargs.pop("dtype", np.complex128) if np.dtype(dtype) == np.complex64: return TensorC64(*args, **kwargs) elif np.dtype(dtype) == np.complex128: @@ -64,11 +78,18 @@ def Tensor(*args, **kwargs) -> TensorType: def TensorNetwork(*args, **kwargs) -> TensorNetworkType: - """Constructs a tensor network with the specified data type. If a `dtype` - keyword argument is not provided, a TensorNetworkC128 instance will be + """Constructs a tensor network with the specified data type. If a ``dtype`` + keyword argument is not provided, a ``TensorNetworkC128`` instance will be returned. + + Args: + *args: Positional arguments to pass to the tensor network constructor. + **kwargs: Keyword arguments to pass to the tensor network constructor. + + Returns: + Tensor network instance. """ - dtype = kwargs.pop("dtype", "complex128") + dtype = kwargs.pop("dtype", np.complex128) if np.dtype(dtype) == np.complex64: return TensorNetworkC64(*args, **kwargs) elif np.dtype(dtype) == np.complex128: @@ -79,10 +100,17 @@ def TensorNetwork(*args, **kwargs) -> TensorNetworkType: def TensorNetworkFile(*args, **kwargs) -> TensorNetworkFileType: """Constructs a tensor network file with the specified data type. If a - `dtype` keyword argument is not provided, a TensorNetworkFileC128 instance - will be returned. + ``dtype`` keyword argument is not provided, a ``TensorNetworkFileC128`` + instance will be returned. + + Args: + *args: Positional arguments to pass to the tensor network file constructor. + **kwargs: Keyword arguments to pass to the tensor network file constructor. + + Returns: + Tensor network file instance. """ - dtype = kwargs.pop("dtype", "complex128") + dtype = kwargs.pop("dtype", np.complex128) if np.dtype(dtype) == np.complex64: return TensorNetworkFileC64(*args, **kwargs) elif np.dtype(dtype) == np.complex128: @@ -93,10 +121,17 @@ def TensorNetworkFile(*args, **kwargs) -> TensorNetworkFileType: def TensorNetworkSerializer(*args, **kwargs) -> TensorNetworkSerializerType: """Constructs a tensor network serializer with the specified data type. If a - `dtype` keyword argument is not provided, a TensorNetworkSerializerC128 + ``dtype`` keyword argument is not provided, a ``TensorNetworkSerializerC128`` instance will be returned. + + Args: + *args: Positional arguments to pass to the tensor network serializer constructor. + **kwargs: Keyword arguments to pass to the tensor network serializer constructor. + + Returns: + Tensor network serializer instance. """ - dtype = kwargs.pop("dtype", "complex128") + dtype = kwargs.pop("dtype", np.complex128) if np.dtype(dtype) == np.complex64: return TensorNetworkSerializerC64(*args, **kwargs) elif np.dtype(dtype) == np.complex128: From 0a4d102f4a67a99a4997b88b0c62a68c2e4afae3 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 11:30:27 -0400 Subject: [PATCH 70/71] Remove extra space from list --- python/tests/jet/test_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/jet/test_gate.py b/python/tests/jet/test_gate.py index f610eec6..5d9deff2 100644 --- a/python/tests/jet/test_gate.py +++ b/python/tests/jet/test_gate.py @@ -58,7 +58,7 @@ def test_indices_are_invalid(self, gate, indices): with pytest.raises(ValueError): gate.indices = indices - @pytest.mark.parametrize("indices", [None, ["1", "2", "3", "4"], ["ABC", "D", "E" , "F"]]) + @pytest.mark.parametrize("indices", [None, ["1", "2", "3", "4"], ["ABC", "D", "E", "F"]]) def test_indices_are_valid(self, gate, indices): """Tests that the indices of a gate can be set and retrieved.""" assert gate.indices is None From 0f4647ac5939240e3f3bc770ef4d8310419d9fe3 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 11 Jun 2021 11:36:07 -0400 Subject: [PATCH 71/71] Mention Antal and Josh in changelog --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 25fb2a52..3b6732ed 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -54,7 +54,7 @@ This release contains contributions from (in alphabetical order): -[Mikhail Andrenkov](https://github.com/Mandrenkov), [Jack Brown](https://github.com/brownj85), [Theodor Isacsson](https://github.com/thisac), [Lee J. O'Riordan](https://github.com/mlxd), [Trevor Vincent](https://github.com/trevor-vincent). +[Mikhail Andrenkov](https://github.com/Mandrenkov), [Jack Brown](https://github.com/brownj85), [Theodor Isacsson](https://github.com/thisac), [Josh Izaac](https://github.com/josh146), [Lee J. O'Riordan](https://github.com/mlxd), [Antal Száva](https://github.com/antalszava), [Trevor Vincent](https://github.com/trevor-vincent). ## Release 0.1.0 (current release)