Skip to content

Commit

Permalink
Merge commit: re-implement symbolic evaluation using sympy quantum me…
Browse files Browse the repository at this point in the history
…chanics module
  • Loading branch information
SimoneGasperini committed Nov 13, 2024
1 parent 9412a5f commit 2dcd284
Show file tree
Hide file tree
Showing 51 changed files with 1,433 additions and 1,104 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The `qiskit-symb` package is meant to be a Python tool to enable the symbolic ev

A Parameterized Quantum Circuit (PQC) is a quantum circuit where we have at least one free parameter (e.g. a rotation angle $\theta$). PQCs are particularly relevant in Quantum Machine Learning (QML) models, where the values of these parameters can be learned during training to reach the desired output.

In particular, `qiskit-symb` can be used to create a symbolic representation of a parametric quantum statevector, density matrix, or unitary operator directly from the Qiskit quantum circuit. This has been achieved through the re-implementation of some basic classes defined in the [`qiskit/quantum_info/`](https://github.com/Qiskit/qiskit/tree/main/qiskit/quantum_info) module by using [sympy](https://github.com/sympy/sympy) as a backend for symbolic expressions manipulation.
In particular, `qiskit-symb` can be used to create a symbolic representation of a parametric quantum state or operator directly from the Qiskit quantum circuit. This has been achieved through the re-implementation of some basic classes defined in the [`qiskit/quantum_info/`](https://github.com/Qiskit/qiskit/tree/main/qiskit/quantum_info) module by using [sympy](https://github.com/sympy/sympy) as a backend for symbolic expressions manipulation.


# Installation
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ disable = [
"too-many-arguments",
"too-many-positional-arguments",
"too-many-locals",
"too-many-ancestors",
"redefined-builtin",
"invalid-name",
"no-value-for-parameter",
"abstract-method",
"signature-differs",
"duplicate-code",
]
6 changes: 1 addition & 5 deletions src/qiskit_symb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
"""Qiskit symbolic module"""

from .quantum_info import (
Statevector,
DensityMatrix,
Operator
)
from .quantum_info import Statevector, Operator
70 changes: 68 additions & 2 deletions src/qiskit_symb/circuit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,70 @@
"""Symbolic quantum circuit module"""

from .gate import Gate
from .controlledgate import ControlledGate
from qiskit.circuit.controlledgate import ControlledGate
from .library import *


name2class = {
'id': IGate,
'x': XGate,
'cx': CXGate,
'y': YGate,
'cy': CYGate,
'z': ZGate,
'cz': CZGate,
'h': HGate,
'ch': CHGate,
'sx': SXGate,
'sxdg': SXdgGate,
'csx': CSXGate,
'csxdg': CSXdgGate,
's': SGate,
'sdg': SdgGate,
'cs': CSGate,
'csdg': CSdgGate,
't': TGate,
'tdg': TdgGate,
'ct': CTGate,
'ctdg': CTdgGate,
'swap': SwapGate,
'iswap': iSwapGate,
'dcx': DCXGate,
'ecr': ECRGate,
'u': UGate,
'cu': CUGate,
'u1': U1Gate,
'cu1': CU1Gate,
'u2': U2Gate,
'cu2': CU2Gate,
'u3': U3Gate,
'cu3': CU3Gate,
'rx': RXGate,
'crx': CRXGate,
'ry': RYGate,
'cry': CRYGate,
'rz': RZGate,
'crz': CRZGate,
'p': PhaseGate,
'cp': CPhaseGate,
'r': RGate,
'cr': CRGate,
'rxx': RXXGate,
'ryy': RYYGate,
'rzz': RZZGate,
'rzx': RZXGate,
'xx_minus_yy': XXMinusYYGate,
'xx_plus_yy': XXPlusYYGate,
}


def get_class(op):
"""todo"""
if isinstance(op, ControlledGate):
name = 'c' + op.base_gate.name
else:
name = op.name
try:
return name2class[name]
except KeyError as kerr:
error_message = f'Gate "{name}" is not implemented in qiskit-symb'
raise NotImplementedError(error_message) from kerr
43 changes: 7 additions & 36 deletions src/qiskit_symb/circuit/controlledgate.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,16 @@
"""Symbolic controlled gate module"""

import numpy
import sympy
from sympy.matrices import Matrix
from sympy.physics.quantum import TensorProduct
from sympy.physics.quantum.gate import CGate
from .gate import Gate


class ControlledGate(Gate):
"""Symbolic controlled gate base class"""
class ControlledGate(Gate, CGate):
"""Symbolic controlled gate abstract class"""

def __init__(self, name, num_qubits, params, base_gate, num_ctrl_qubits, ctrl_state):
def __new__(cls, controls, target_gate):
"""todo"""
super().__init__(name=name, num_qubits=num_qubits, params=params)
self.base_gate = base_gate
self.num_ctrl_qubits = num_ctrl_qubits
self.ctrl_state = '1' * num_ctrl_qubits if ctrl_state is None else ctrl_state
return CGate.__new__(cls, *controls, target_gate)

@staticmethod
def get(instruction):
def get_target_matrix(self, format='sympy'):
"""todo"""
from ..utils import get_init
gate = instruction.op
name = 'c' + gate.base_gate.name
num_ctrl_qubits = gate.num_ctrl_qubits
ctrl_state = format(gate.ctrl_state, 'b').zfill(num_ctrl_qubits)
return get_init(name)(*gate.params, num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state)

def __sympy__(self):
"""todo"""
proj = {'0': Matrix([[1, 0], [0, 0]]),
'1': Matrix([[0, 0], [0, 1]])}
sympy_matrix = sympy.zeros(2**self.num_qubits)
for state in range(2**self.num_ctrl_qubits):
bitstring = format(state, 'b').zfill(self.num_ctrl_qubits)
factors = [proj[bit] for bit in bitstring]
if bitstring == self.ctrl_state:
matrix = self.base_gate.__sympy__()
term = TensorProduct(matrix, *factors)
else:
identity = Matrix(numpy.eye(2**self.base_gate.num_qubits))
term = TensorProduct(identity, *factors)
sympy_matrix += term
return sympy_matrix
return self.gate._sympy_matrix()
98 changes: 61 additions & 37 deletions src/qiskit_symb/circuit/gate.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,80 @@
"""Symbolic gate module"""

import numpy
from sympy import matrix2numpy
from qiskit.circuit import ControlledGate as QiskitControlledGate
from sympy import Matrix, eye, zeros, sympify
from sympy.physics.quantum.matrixutils import matrix_tensor_product
from sympy.physics.quantum.gate import Gate as SympyGate


class Gate:
"""Symbolic gate base class"""
op00 = Matrix([[1, 0], [0, 0]]) # |0><0|
op01 = Matrix([[0, 1], [0, 0]]) # |0><1|
op10 = Matrix([[0, 0], [1, 0]]) # |1><0|
op11 = Matrix([[0, 0], [0, 1]]) # |1><1|

def __init__(self, name, num_qubits, params):

class Gate(SympyGate):
"""Symbolic gate abstract class"""

def __new__(cls, params, qubits):
"""todo"""
symbols = Gate.get_symbols(params=params)
return super().__new__(cls, *symbols, *qubits)

@property
def nqubits(self):
"""todo"""
self.name = name
self.num_qubits = num_qubits
self.params = params
return len(self.qubits)

@property
def label(self):
"""todo"""
return super().label[-self.nqubits:]

@staticmethod
def get(instruction):
def get_symbols(params):
"""todo"""
from .controlledgate import ControlledGate
from ..utils import get_init
gate = instruction.op
if isinstance(gate, QiskitControlledGate):
return ControlledGate.get(instruction)
return get_init(gate.name)(*gate.params)
symbols = set()
for param in params:
if hasattr(param, '_symbol_expr'):
symbols.update(param._symbol_expr.free_symbols)
symbols = sorted(symbols, key=lambda s: s.name)
return symbols

def _get_params_expr(self):
@staticmethod
def get(gate_node):
"""todo"""
from ..utils import get_symbolic_expr
return [get_symbolic_expr(par) for par in self.params]
from . import get_class
_class = get_class(op=gate_node.op)
params = gate_node.op.params
qubits = (qarg._index for qarg in gate_node.qargs)
return _class(*params, *qubits)

def _get_unique_symbols(self):
def _define_matrix(self, coeff_ops, nqubits):
"""todo"""
from ..utils import get_unique_symbols
sympy_symbols = []
for par in self.params:
sympy_symbols.extend(get_unique_symbols(par))
return list(dict.fromkeys(sympy_symbols))
matrix = sum((coeff * matrix_tensor_product(
*(ops[0] if i == nqubits - self.targets[0] - 1 else
ops[1] if i == nqubits - self.targets[1] - 1 else
eye(2) for i in range(nqubits)))
for coeff, ops in coeff_ops),
start=zeros(2**nqubits))
return matrix

def _get_tensor(self):
def get_params_expr(self):
"""todo"""
sympy_matrix = self.__sympy__()
newshape = (2, 2) * self.num_qubits
return numpy.reshape(sympy_matrix, newshape)
params_expr = tuple(sympify(par._symbol_expr)
if hasattr(par, '_symbol_expr') else par
for par in self.params)
return params_expr

def to_sympy(self):
def get_target_matrix(self, format='sympy'):
"""todo"""
from ..utils import symbols2real
sympy_matrix = self.__sympy__()
return symbols2real(sympy_matrix)
return self._sympy_matrix()

def to_numpy(self, *vals):
def _latex(self, printer, *args):
"""todo"""
sympy_symbols = self._get_unique_symbols()
sympy_matrix = self.__sympy__().subs(dict(zip(sympy_symbols, vals)))
return matrix2numpy(sympy_matrix, dtype=complex)
controls = getattr(self, 'controls', ())
qubits_label = ','.join(str(s) for s in controls + self.targets)
params_label = ','.join(str(s) for s in self.params[:3])
latex_repr = '%s_{%s}' % (self.gate_name_latex, qubits_label)
if self.params:
latex_repr += f'({params_label})'
return latex_repr
76 changes: 12 additions & 64 deletions src/qiskit_symb/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,78 +9,26 @@
SXGate, SXdgGate, CSXGate, CSXdgGate,
SGate, SdgGate, CSGate, CSdgGate,
TGate, TdgGate, CTGate, CTdgGate,
SwapGate, CSwapGate,
iSwapGate, CiSwapGate,
ECRGate, DCXGate,
UnitaryGate
SwapGate,
iSwapGate,
DCXGate,
ECRGate,
)

from .parametric_gates import (
UGate, CUGate,
U1Gate, CU1Gate,
U2Gate, CU2Gate,
U3Gate, CU3Gate,
RXGate, CRXGate,
RYGate, CRYGate,
RZGate, CRZGate,
PhaseGate, CPhaseGate,
RGate, CRGate,
RXXGate, CRXXGate,
RYYGate, CRYYGate,
RZZGate, CRZZGate,
RZXGate, CRZXGate,
RXXGate,
RYYGate,
RZZGate,
RZXGate,
XXMinusYYGate,
XXPlusYYGate
XXPlusYYGate,
)

NAME_TO_INIT = {
'id': IGate,
'x': XGate,
'cx': CXGate,
'y': YGate,
'cy': CYGate,
'z': ZGate,
'cz': CZGate,
'h': HGate,
'ch': CHGate,
'sx': SXGate,
'sxdg': SXdgGate,
'csx': CSXGate,
'csxdg': CSXdgGate,
's': SGate,
'sdg': SdgGate,
'cs': CSGate,
'csdg': CSdgGate,
't': TGate,
'tdg': TdgGate,
'ct': CTGate,
'ctdg': CTdgGate,
'u': UGate,
'u3': UGate,
'cu': CUGate,
'cu3': CUGate,
'rx': RXGate,
'crx': CRXGate,
'ry': RYGate,
'cry': CRYGate,
'rz': RZGate,
'crz': CRZGate,
'p': PhaseGate,
'cp': CPhaseGate,
'r': RGate,
'cr': CRGate,
'rxx': RXXGate,
'crxx': CRXXGate,
'ryy': RYYGate,
'cryy': CRYYGate,
'rzz': RZZGate,
'crzz': CRZZGate,
'rzx': RZXGate,
'crzx': CRZXGate,
'swap': SwapGate,
'cswap': CSwapGate,
'iswap': iSwapGate,
'ciswap': CiSwapGate,
'ecr': ECRGate,
'dcx': DCXGate,
'xx_minus_yy': XXMinusYYGate,
'xx_plus_yy': XXPlusYYGate,
'unitary': UnitaryGate
}
11 changes: 7 additions & 4 deletions src/qiskit_symb/circuit/library/parametric_gates/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""Symbolic parametric gates module"""

from .u import UGate, CUGate
from .u import U1Gate, CU1Gate
from .u import U2Gate, CU2Gate
from .u import U3Gate, CU3Gate
from .rx import RXGate, CRXGate
from .ry import RYGate, CRYGate
from .rz import RZGate, CRZGate
from .p import PhaseGate, CPhaseGate
from .r import RGate, CRGate
from .rxx import RXXGate, CRXXGate
from .ryy import RYYGate, CRYYGate
from .rzz import RZZGate, CRZZGate
from .rzx import RZXGate, CRZXGate
from .rxx import RXXGate
from .ryy import RYYGate
from .rzz import RZZGate
from .rzx import RZXGate
from .xx_minus_yy import XXMinusYYGate
from .xx_plus_yy import XXPlusYYGate
Loading

0 comments on commit 2dcd284

Please sign in to comment.