Source code for mimiqcircuits.gatedecl

#
# Copyright © 2022-2024 University of Strasbourg. All Rights Reserved.
# Copyright © 2023-2025 QPerfect. All Rights Reserved.
#
# 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 typing import Tuple
import mimiqcircuits as mc
from symengine import Symbol
import symengine as se
import numpy as np
import sympy as sp


[docs] class GateDecl: """ Simple declaration of gates using the @gatedecl decorator. Examples: First Way Import necessary libaries >>> from symengine import symbols >>> from mimiqcircuits import * >>> x, y = symbols('x y') Declare a gate using the @gatedecl decorator >>> @gatedecl("ansatz") ... def ansatz(x): ... c = Circuit() ... c.push(GateX(), 0) ... c.push(GateRX(x), 1) ... return c Create calls to the gate declariation >>> ansatz(x) ansatz(x) >>> ansatz(y) ansatz(y) Decompose >>> ansatz(x).decompose() 2-qubit circuit with 2 instructions: ├── X @ q[0] └── RX(x) @ q[1] <BLANKLINE> >>> ansatz(2) ansatz(2) >>> ansatz(2).decompose() 2-qubit circuit with 2 instructions: ├── X @ q[0] └── RX(2) @ q[1] <BLANKLINE> Second Way >>> from symengine import * >>> from mimiqcircuits import * Define symbols for the gate arguments >>> x, y = symbols('x y') From a circuit, create a GateDecl object directly >>> c = Circuit() >>> c.push(GateXXplusYY(x,y), 0,1) 2-qubit circuit with 1 instructions: └── XXplusYY(x, y) @ q[0,1] <BLANKLINE> >>> c.push(GateRX(x), 1) 2-qubit circuit with 2 instructions: ├── XXplusYY(x, y) @ q[0,1] └── RX(x) @ q[1] <BLANKLINE> >>> gate_decl = GateDecl("ansatz", (x,y), c) >>> gate_decl gate ansatz(x, y) = ├── XXplusYY(x, y) @ q[0,1] └── RX(x) @ q[1] <BLANKLINE> >>> GateCall(gate_decl, (2,4)) ansatz(2, 4) Decompose the GateCall into a quantum circuit >>> GateCall(gate_decl, (2,4)).decompose() 2-qubit circuit with 2 instructions: ├── XXplusYY(2, 4) @ q[0,1] └── RX(2) @ q[1] <BLANKLINE> Add to Circuit >>> g = GateCall(gate_decl, (2,4)) >>> c = Circuit() >>> c.push(g,10,22) 23-qubit circuit with 1 instructions: └── ansatz(2, 4) @ q[10,22] <BLANKLINE> """ _name = "GateDecl" def __init__(self, name: str, arguments: Tuple[Symbol, ...], circuit: mc.Circuit): if not all(isinstance(arg, Symbol) for arg in arguments): raise TypeError("All GateDecl arguments must be symbols.") if len(circuit) == 0: raise ValueError("GateDecl cannot be defined from empty circuits.") for inst in circuit: # check if the instruction operation is from the Gate operation if not isinstance(inst.get_operation(), mc.Gate): raise ValueError("GateDecl instructions must be gates.") if inst.num_bits() != 0 or inst.num_zvars() != 0: raise ValueError("GateDecl instructions cannot have bits or zvars.") if inst.num_qubits() == 0: raise ValueError("GateDecl instructions must have qubits.") nq = circuit.num_qubits() np = len(arguments) super().__init__() self.name = name self.arguments = arguments self.circuit = circuit @property def num_qubits(self): return self.circuit.num_qubits() @num_qubits.setter def num_qubits(self, _): raise ValueError("Cannot set num_qubits. Read only parameter.") @property def num_bits(self): return self.circuit.num_bits() @num_bits.setter def num_bits(self, _): raise ValueError("Cannot set num_bits. Read only parameter.") @property def num_zvars(self): return self.circuit.num_zvars() @num_zvars.setter def num_zvars(self, _): raise ValueError("Cannot set num_zvars. Read only parameter.") def __str__(self): result = f"gate {self.name}({', '.join(map(str, self.arguments))}) =\n" N = len(self.circuit) for i, inst in enumerate(self.circuit): if i == N - 1: result += f"└── {inst}\n" else: result += f"├── {inst}\n" return result def __repr__(self): return str(self) def __call__(self, *args): return mc.GateCall(self, args)
[docs] class GateCall(mc.Gate): """GateCall""" _name = "GateCall" _num_bits = 0 _num_qubits = None def __init__(self, decl: GateDecl, args: Tuple[float, ...]): if len(args) != len(decl.arguments): raise ValueError("Wrong number of arguments for GateCall.") self._num_qubits = decl.num_qubits self._decl = decl self._args = args self._qregsizes = [self._num_qubits] def _matrix(self): pass
[docs] def decompose(self): circ = mc.Circuit() d = dict(zip(self._decl.arguments, self._args)) for inst in self._decl.circuit: op = inst.operation.evaluate(d) qubits = [i for i in inst.get_qubits()] bits = [i for i in inst.get_bits()] zvars = [i for i in inst.get_zvars()] circ.push(op, *qubits, *bits, *zvars) return circ
def _decompose(self, circ, qubits, bits, zvars): d = dict(zip(self._decl.arguments, self._args)) if len(qubits) != self._decl.num_qubits: raise ValueError("Wrong number of qubits for GateCall.") if len(bits) != self._decl.num_bits: raise ValueError("Wrong number of bits for GateCall.") if len(zvars) != self._decl.num_zvars: raise ValueError("Wrong number of zvars for GateCall.") for inst in self._decl.circuit: op = inst.operation.evaluate(d) # Map the instruction qubits to the actual qubits in the circuit inst_qubits = [qubits[q] for q in inst.get_qubits()] # Push the operation with the mapped qubits into the circuit circ.push(op, *inst_qubits, *bits, *zvars) return circ def __str__(self): return f"{self._decl.name}"
[docs] def evaluate(self, d): new_args = [ arg.subs(d) if hasattr(arg, "subs") else arg for arg in self._args ] return type(self)(self._decl, tuple(new_args))
[docs] def matrix(self): from functools import reduce N = self._num_qubits dim = 2**N identity = se.Matrix(np.eye(dim, dtype=complex).tolist()) iter = map(lambda inst: inst.matrix(N), self.decompose()) return reduce(lambda a, b: a * b, reversed(list(iter)), identity)
[docs] def gatedecl(name): def decorator(func): def get_arg_names(func): import inspect sig = inspect.signature(func) return tuple(sig.parameters.keys()) arguments = [Symbol(arg) for arg in get_arg_names(func)] circ = func(*arguments) if not isinstance(circ, mc.Circuit): raise ValueError("GateDecl must return a Circuit object.") return GateDecl(name, tuple(arguments), circ) return decorator
__all__ = ["GateCall", "GateDecl", "gatedecl"]