Source code for mimiqcircuits.operations.operators.custom

#
# Copyright © 2022-2023 University of Strasbourg. All Rights Reserved.
# Copyright © 2032-2024 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.
#

import mimiqcircuits as mc
import symengine as se
import numpy as np
import sympy as sp


[docs] class Operator(mc.AbstractOperator): r"""N-qubit operator specified by a :math:`2^N \times 2^N` matrix. .. note:: Only one and two qubit operators are supported. This operator does not have to be unitary. See Also: :class:`AbstractOperator`, :class:`ExpectationValue`, :class:`KrausChannel` Parameters: matrix (list or np.ndarray): The :math:`2^N \times 2^N` matrix representing the operator. Examples: >>> from mimiqcircuits import * >>> from symengine import Matrix >>> op = Operator(Matrix([[1, 2], [3, 4]])) >>> op 1-qubit Operator: ├── 1 2 └── 3 4 >>> op = Operator(Matrix([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 1]])) >>> op 2-qubit Operator: ├── 1 0 0 1 ├── 0 0 0 0 ├── 0 0 0 0 └── 1 0 0 1 Operators can be used for expectation values: >>> c = Circuit() >>> c.push(ExpectationValue(Operator(Matrix([[0, 1], [0, 0]]))), 1, 1) 2-qubit circuit with 1 instructions: └── ⟨Operator([[0, 1], [0, 0]])⟩ @ q[1], z[1] <BLANKLINE> """ _name = "Operator" _num_qubits = None _parnames = ("mat",) _num_qregs = 1 def __init__(self, mat): # Check if the input is a valid matrix type (symengine, numpy, or sympy) if not isinstance(mat, (se.Matrix, np.ndarray, sp.Matrix)): raise TypeError("Must be a symengine, sympy, or numpy Matrix") # Convert any numpy or sympy matrices to symengine.Matrix mat = self._convert_to_symengine_matrix(mat) self.mat = mat # Get the matrix dimensions using symengine.Matrix properties dim_rows = mat.rows dim_cols = mat.cols # Check if the matrix is square if dim_rows != dim_cols: raise ValueError(f"Must be a square matrix, but got {dim_rows}x{dim_cols}") # Ensure the dimension is a power of 2 if not self.is_valid_power_of_2(dim_rows): raise ValueError("Dimension of operator has to be 2^n with n>=1") # Determine the number of qubits from the matrix dimension self.N = int(se.log(dim_rows, 2)) if self.N < 1: raise ValueError("Cannot define a 0-qubit operator") if self.N > 2: raise ValueError("Operators larger than 2 qubits are not supported") super().__init__() self._num_qubits = self.N self._qregsizes = [self._num_qubits]
[docs] @staticmethod def is_valid_power_of_2(n): """Check if a number is a power of 2.""" return n > 0 and (n & (n - 1)) == 0
def _convert_to_symengine_matrix(self, matrix): """Helper function to convert numpy or sympy matrices to symengine.Matrix.""" if isinstance(matrix, (np.ndarray, sp.Matrix)): return se.Matrix(matrix.tolist()) return matrix
[docs] def opname(self): """Return the operator name.""" return "Operator"
def _matrix(self): return self.mat @property def parnames(self): """Return the parameter names.""" return self._parnames
[docs] def iswrapper(self): """Placeholder method.""" pass
[docs] def unwrappedmatrix(self): """Return the matrix unwrapped (if needed).""" return self.mat
def __repr__(self): """Return a pretty-printed representation.""" return self.pretty_print() def __str__(self): """String representation for printing.""" if self.N < 2: return f"{self.opname()}({se.Matrix(self.mat).tolist()})" else: return f"{self.opname()}(...)"
[docs] def pretty_print(self): """Return a detailed string representation of the operator.""" U = np.array(self.mat.tolist(), dtype=object) result = f"{self.N}-qubit Operator:\n" rows, cols = U.shape for i in range(rows): if i < rows - 1: result += "├── " else: result += "└── " result += " ".join(map(str, U[i, :])) if i < rows - 1: result += "\n" return result
[docs] def opsquared(self): """Return the operator squared (O' * O).""" O_dagger = self.mat.T.conjugate() squared_matrix = O_dagger * self.mat return Operator(squared_matrix)
[docs] def rescale(self, scale): """Return a new rescaled Operator by scaling the matrix.""" scaled_matrix = self.mat * scale return Operator(scaled_matrix)
[docs] def rescale_inplace(self, scale): """In-place rescaling of the operator matrix.""" self.mat *= scale return self
__all__ = ["Operator"]