From dcbb156d07fa11fa6bededf160755ea6303ef025 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 18 Dec 2024 08:18:30 +0100 Subject: [PATCH 01/31] feat: removed terms and dense setter --- src/qibo/hamiltonians/hamiltonians.py | 54 ++++++++++++++------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 0016ddb05b..1b975c3f94 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -1,5 +1,6 @@ """Module defining Hamiltonian classes.""" +from functools import cached_property from itertools import chain from math import prod from typing import Optional @@ -325,9 +326,7 @@ class SymbolicHamiltonian(AbstractHamiltonian): def __init__(self, form=None, nqubits=None, symbol_map={}, backend=None): super().__init__() self._form = None - self._terms = None self.constant = 0 # used only when we perform calculations using ``_terms`` - self._dense = None self.symbol_map = symbol_map # if a symbol in the given form is not a Qibo symbol it must be # included in the ``symbol_map`` @@ -342,32 +341,40 @@ def __init__(self, form=None, nqubits=None, symbol_map={}, backend=None): if form is not None: self.form = form - if nqubits is not None: - self.nqubits = nqubits + self.nqubits = ( + self._calculate_nqubits_from_form(form) if nqubits is None else nqubits + ) - @property + @cached_property def dense(self) -> "MatrixHamiltonian": """Creates the equivalent Hamiltonian matrix.""" - if self._dense is None: - log.warning( - "Calculating the dense form of a symbolic Hamiltonian. " - "This operation is memory inefficient." - ) - self.dense = self.calculate_dense() - return self._dense - - @dense.setter - def dense(self, hamiltonian): - assert isinstance(hamiltonian, Hamiltonian) - self._dense = hamiltonian - self._eigenvalues = hamiltonian._eigenvalues - self._eigenvectors = hamiltonian._eigenvectors - self._exp = hamiltonian._exp + log.warning( + "Calculating the dense form of a symbolic Hamiltonian. " + "This operation is memory inefficient." + ) + return self.calculate_dense() @property def form(self): return self._form + def _calculate_nqubits_from_form(self, form): + nqubits = 0 + for symbol in form.free_symbols: + if isinstance(symbol, self._qiboSymbol): + q = symbol.target_qubit + elif isinstance(symbol, sympy.Expr): + if symbol not in self.symbol_map: + raise_error(ValueError, f"Symbol {symbol} is not in symbol map.") + q, matrix = self.symbol_map.get(symbol) + if not isinstance(matrix, self.backend.tensor_types): + # ignore symbols that do not correspond to quantum operators + # for example parameters in the MaxCut Hamiltonian + q = 0 + if q > nqubits: + nqubits = q + return nqubits + 1 + @form.setter def form(self, form): # Check that given form is a ``sympy`` expression @@ -396,7 +403,7 @@ def form(self, form): self._form = form self.nqubits = nqubits + 1 - @property + @cached_property def terms(self): """List of terms of which the Hamiltonian is a sum of. @@ -419,11 +426,6 @@ def terms(self): self._terms = terms return self._terms - @terms.setter - def terms(self, terms): - self._terms = terms - self.nqubits = max(q for term in self._terms for q in term.target_qubits) + 1 - @property def matrix(self): """Returns the full matrix representation. From d49ce6cb409bf1beacb9f712d7564ef9dc12000e Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 18 Dec 2024 09:28:44 +0100 Subject: [PATCH 02/31] feat: adapting hamiltonian --- src/qibo/hamiltonians/hamiltonians.py | 181 +++++++------------------- 1 file changed, 47 insertions(+), 134 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 1b975c3f94..5f35d0e46a 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -359,6 +359,9 @@ def form(self): return self._form def _calculate_nqubits_from_form(self, form): + """Calculate number of qubits in the system described by the given + Hamiltonian formula + """ nqubits = 0 for symbol in form.free_symbols: if isinstance(symbol, self._qiboSymbol): @@ -383,25 +386,8 @@ def form(self, form): TypeError, f"Symbolic Hamiltonian should be a ``sympy`` expression but is {type(form)}.", ) - # Calculate number of qubits in the system described by the given - # Hamiltonian formula - nqubits = 0 - for symbol in form.free_symbols: - if isinstance(symbol, self._qiboSymbol): - q = symbol.target_qubit - elif isinstance(symbol, sympy.Expr): - if symbol not in self.symbol_map: - raise_error(ValueError, f"Symbol {symbol} is not in symbol map.") - q, matrix = self.symbol_map.get(symbol) - if not isinstance(matrix, self.backend.tensor_types): - # ignore symbols that do not correspond to quantum operators - # for example parameters in the MaxCut Hamiltonian - q = 0 - if q > nqubits: - nqubits = q - self._form = form - self.nqubits = nqubits + 1 + self.nqubits = self._calculate_nqubits_from_form(form) @cached_property def terms(self): @@ -409,22 +395,21 @@ def terms(self): Terms will be objects of type :class:`qibo.core.terms.HamiltonianTerm`. """ - if self._terms is None: - # Calculate terms based on ``self.form`` - from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel - SymbolicTerm, - ) + # Calculate terms based on ``self.form`` + from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel + SymbolicTerm, + ) - form = sympy.expand(self.form) - terms = [] - for f, c in form.as_coefficients_dict().items(): - term = SymbolicTerm(c, f, self.symbol_map) - if term.target_qubits: - terms.append(term) - else: - self.constant += term.coefficient - self._terms = terms - return self._terms + self.constant = 0.0 + + form = sympy.expand(self.form) + terms = [] + for f, c in form.as_coefficients_dict().items(): + term = SymbolicTerm(c, f, self.symbol_map) + if term.target_qubits: + terms.append(term) + else: + self.constant += term.coefficient @property def matrix(self): @@ -548,10 +533,10 @@ def _calculate_dense_from_terms(self) -> Hamiltonian: return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant def calculate_dense(self): - if self._terms is None: - # calculate dense matrix directly using the form to avoid the - # costly ``sympy.expand`` call - return self._calculate_dense_from_form() + # if self._terms is None: + # calculate dense matrix directly using the form to avoid the + # costly ``sympy.expand`` call + # return self._calculate_dense_from_form() return self._calculate_dense_from_terms() def expectation(self, state, normalize=False): @@ -672,120 +657,48 @@ def expectation_from_samples(self, freq: dict, qubit_map: dict = None) -> float: ) return self.backend.np.sum(expvals @ counts.T) + self.constant.real - def __add__(self, o): + def _compose(self, o, operator): + new_ham = self.__class__( + form=self._form, symbol_map=dict(self.symbol_map), backend=self.backend + ) + if isinstance(o, self.__class__): if self.nqubits != o.nqubits: raise_error( RuntimeError, - "Only hamiltonians with the same number of qubits can be added.", + "Only hamiltonians with the same number of qubits can be composed.", ) - new_ham = self.__class__( - symbol_map=dict(self.symbol_map), backend=self.backend - ) - if self._form is not None and o._form is not None: - new_ham.form = self.form + o.form + + if o._form is not None: new_ham.symbol_map.update(o.symbol_map) - if self._terms is not None and o._terms is not None: - new_ham.terms = self.terms + o.terms - new_ham.constant = self.constant + o.constant - if self._dense is not None and o._dense is not None: - new_ham.dense = self.dense + o.dense + new_ham.form = ( + operator(self._form, o._form) if self._form is not None else o._form + ) - elif isinstance(o, self.backend.numeric_types): - new_ham = self.__class__( - symbol_map=dict(self.symbol_map), backend=self.backend + elif isinstance(o, (self.backend.numeric_types, self.backend.tensor_types)): + new_ham.form = ( + operator(self._form, o) if self._form is not None else complex(o) ) - if self._form is not None: - new_ham.form = self.form + o - if self._terms is not None: - new_ham.terms = self.terms - new_ham.constant = self.constant + o - if self._dense is not None: - new_ham.dense = self.dense + o - else: raise_error( NotImplementedError, - f"SymbolicHamiltonian addition to {type(o)} not implemented.", + f"SymbolicHamiltonian composition to {type(o)} not implemented.", ) - return new_ham - def __sub__(self, o): - if isinstance(o, self.__class__): - if self.nqubits != o.nqubits: - raise_error( - RuntimeError, - "Only hamiltonians with the same number of qubits can be subtracted.", - ) - new_ham = self.__class__( - symbol_map=dict(self.symbol_map), backend=self.backend - ) - if self._form is not None and o._form is not None: - new_ham.form = self.form - o.form - new_ham.symbol_map.update(o.symbol_map) - if self._terms is not None and o._terms is not None: - new_ham.terms = self.terms + [-1 * x for x in o.terms] - new_ham.constant = self.constant - o.constant - if self._dense is not None and o._dense is not None: - new_ham.dense = self.dense - o.dense + return new_ham - elif isinstance(o, self.backend.numeric_types): - new_ham = self.__class__( - symbol_map=dict(self.symbol_map), backend=self.backend - ) - if self._form is not None: - new_ham.form = self.form - o - if self._terms is not None: - new_ham.terms = self.terms - new_ham.constant = self.constant - o - if self._dense is not None: - new_ham.dense = self.dense - o + def __add__(self, o): + return self._compose(o, lambda x, y: x + y) - else: - raise_error( - NotImplementedError, - f"Hamiltonian subtraction to {type(o)} " "not implemented.", - ) - return new_ham + def __sub__(self, o): + return self._compose(o, lambda x, y: x - y) def __rsub__(self, o): - if isinstance(o, self.backend.numeric_types): - new_ham = self.__class__( - symbol_map=dict(self.symbol_map), backend=self.backend - ) - if self._form is not None: - new_ham.form = o - self.form - if self._terms is not None: - new_ham.terms = [-1 * x for x in self.terms] - new_ham.constant = o - self.constant - if self._dense is not None: - new_ham.dense = o - self.dense - else: - raise_error( - NotImplementedError, - f"Hamiltonian subtraction to {type(o)} not implemented.", - ) - return new_ham + return self._compose(o, lambda x, y: y - x) def __mul__(self, o): - if not isinstance(o, (self.backend.numeric_types, self.backend.tensor_types)): - raise_error( - NotImplementedError, - f"Hamiltonian multiplication to {type(o)} not implemented.", - ) - o = complex(o) - new_ham = self.__class__(symbol_map=dict(self.symbol_map), backend=self.backend) - if self._form is not None: - new_ham.form = o * self.form - if self._terms is not None: - new_ham.terms = [o * x for x in self.terms] - new_ham.constant = self.constant * o - if self._dense is not None: - new_ham.dense = o * self._dense - - new_ham.nqubits = self.nqubits - - return new_ham + # o = complex(o) + return self._compose(o, lambda x, y: y * x) def apply_gates(self, state, density_matrix=False): """Applies gates corresponding to the Hamiltonian terms. @@ -821,8 +734,8 @@ def __matmul__(self, o): new_ham = self.__class__( new_form, symbol_map=new_symbol_map, backend=self.backend ) - if self._dense is not None and o._dense is not None: - new_ham.dense = self.dense @ o.dense + # if self._dense is not None and o._dense is not None: + # new_ham.dense = self.dense @ o.dense return new_ham if isinstance(o, self.backend.tensor_types): From 49ea30bb9e95295af5df5b95645d8151a5e76c47 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 18 Dec 2024 12:58:46 +0100 Subject: [PATCH 03/31] feat: patching hamiltonian/models to build starting from forms --- src/qibo/hamiltonians/hamiltonians.py | 12 +-- src/qibo/hamiltonians/models.py | 134 +++++++++++++++++++++----- 2 files changed, 114 insertions(+), 32 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 5f35d0e46a..d663d10d05 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -348,10 +348,6 @@ def __init__(self, form=None, nqubits=None, symbol_map={}, backend=None): @cached_property def dense(self) -> "MatrixHamiltonian": """Creates the equivalent Hamiltonian matrix.""" - log.warning( - "Calculating the dense form of a symbolic Hamiltonian. " - "This operation is memory inefficient." - ) return self.calculate_dense() @property @@ -533,11 +529,15 @@ def _calculate_dense_from_terms(self) -> Hamiltonian: return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant def calculate_dense(self): + log.warning( + "Calculating the dense form of a symbolic Hamiltonian. " + "This operation is memory inefficient." + ) # if self._terms is None: # calculate dense matrix directly using the form to avoid the # costly ``sympy.expand`` call - # return self._calculate_dense_from_form() - return self._calculate_dense_from_terms() + return self._calculate_dense_from_form() + # return self._calculate_dense_from_terms() def expectation(self, state, normalize=False): return Hamiltonian.expectation(self, state, normalize) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 61fa918021..1e4f3ccca5 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -3,6 +3,7 @@ import numpy as np +from qibo import symbols from qibo.backends import _check_backend, matrices from qibo.config import raise_error from qibo.hamiltonians.hamiltonians import Hamiltonian, SymbolicHamiltonian @@ -25,7 +26,7 @@ def X(nqubits, dense: bool = True, backend=None): in the execution. If ``None``, it uses the current backend. Defaults to ``None``. """ - return _OneBodyPauli(nqubits, matrices.X, dense, backend=backend) + return _OneBodyPauli(nqubits, symbols.X, dense, backend=backend) def Y(nqubits, dense: bool = True, backend=None): @@ -43,7 +44,7 @@ def Y(nqubits, dense: bool = True, backend=None): in the execution. If ``None``, it uses the current backend. Defaults to ``None``. """ - return _OneBodyPauli(nqubits, matrices.Y, dense, backend=backend) + return _OneBodyPauli(nqubits, symbols.Y, dense, backend=backend) def Z(nqubits, dense: bool = True, backend=None): @@ -61,7 +62,7 @@ def Z(nqubits, dense: bool = True, backend=None): in the execution. If ``None``, it uses the current backend. Defaults to ``None``. """ - return _OneBodyPauli(nqubits, matrices.Z, dense, backend=backend) + return _OneBodyPauli(nqubits, symbols.Z, dense, backend=backend) def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None): @@ -82,19 +83,25 @@ def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None): raise_error(ValueError, "Number of qubits must be larger than one.") if dense: condition = lambda i, j: i in {j % nqubits, (j + 1) % nqubits} - ham = -_build_spin_model(nqubits, matrices.Z, condition) + ham = -_build_spin_model(nqubits, backend.matrices.Z, condition, backend) if h != 0: condition = lambda i, j: i == j % nqubits - ham -= h * _build_spin_model(nqubits, matrices.X, condition) + ham -= h * _build_spin_model( + nqubits, backend.matrices.X, condition, backend + ) return Hamiltonian(nqubits, ham, backend=backend) - matrix = -( - _multikron([matrices.Z, matrices.Z]) + h * _multikron([matrices.X, matrices.I]) + term = lambda q1, q2: symbols.Z(q1) * symbols.Z(q2) + h * symbols.X(q1) * symbols.I( + q2 ) - terms = [HamiltonianTerm(matrix, i, i + 1) for i in range(nqubits - 1)] - terms.append(HamiltonianTerm(matrix, nqubits - 1, 0)) - ham = SymbolicHamiltonian(backend=backend) - ham.terms = terms + form = sum(term(i, i + 1) for i in range(nqubits - 1)) + # matrix = -( + # _multikron([backend.matrices.Z, backend.matrices.Z], backend) + h * _multikron([backend.matrices.X, backend.matrices.I], backend) + # ) + # terms = [HamiltonianTerm(matrix, i, i + 1) for i in range(nqubits - 1)] + # terms.append(HamiltonianTerm(matrix, nqubits - 1, 0)) + ham = SymbolicHamiltonian(form=form, nqubits=nqubits, backend=backend) + # ham.terms = terms return ham @@ -210,7 +217,7 @@ def Heisenberg( matrix = np.zeros((2**nqubits, 2**nqubits), dtype=complex) matrix = backend.cast(matrix, dtype=matrix.dtype) for ind, pauli in enumerate(paulis): - double_term = _build_spin_model(nqubits, pauli, condition) + double_term = _build_spin_model(nqubits, pauli, condition, backend) double_term = backend.cast(double_term, dtype=double_term.dtype) matrix = matrix - coupling_constants[ind] * double_term matrix = ( @@ -221,6 +228,26 @@ def Heisenberg( return Hamiltonian(nqubits, matrix, backend=backend) + paulis = (symbols.X, symbols.Y, symbols.Z) + + def h(symbol): + return lambda q1, q2: symbol(q1) * symbol(q2) + + def term(q1, q2): + return -1 * sum( + coeff * h(operator)(q1, q2) + for coeff, operator in zip(coupling_constants, paulis) + ) + + form = sum(term(i, i + 1) for i in range(nqubits - 1)) + form -= sum( + field_strength * pauli(qubit) + for qubit in range(nqubits) + for field_strength, pauli in zip(external_field_strengths, paulis) + if field_strength != 0.0 + ) + + """ hx = _multikron([matrices.X, matrices.X]) hy = _multikron([matrices.Y, matrices.Y]) hz = _multikron([matrices.Z, matrices.Z]) @@ -242,9 +269,10 @@ def Heisenberg( if field_strength != 0.0 ] ) + """ - ham = SymbolicHamiltonian(backend=backend) - ham.terms = terms + ham = SymbolicHamiltonian(form=form, backend=backend) + # ham.terms = terms return ham @@ -342,7 +370,7 @@ def XXZ(nqubits, delta=0.5, dense: bool = True, backend=None): return Heisenberg(nqubits, [-1, -1, -delta], 0, dense=dense, backend=backend) -def _multikron(matrix_list): +def _multikron(matrix_list, backend=None): """Calculates Kronecker product of a list of matrices. Args: @@ -351,28 +379,82 @@ def _multikron(matrix_list): Returns: ndarray: Kronecker product of all matrices in ``matrix_list``. """ + """ + # this is a better implementation but requires the whole + # hamiltonian/symbols modules to be adapted + indices = list(range(2*len(matrix_list))) + even, odd = indices[::2], indices[1::2] + lhs = zip(even, odd) + rhs = even + odd + einsum_args = [item for pair in zip(matrix_list, lhs) for item in pair] + dim = 2 ** len(matrix_list) + if backend.platform != "tensorflow": + h = backend.np.einsum(*einsum_args, rhs) + else: + h = np.einsum(*einsum_args, rhs) + h = backend.np.sum(backend.np.reshape(h, (dim, dim)), axis=0) + return h + """ return reduce(np.kron, matrix_list) -def _build_spin_model(nqubits, matrix, condition): +def _build_spin_model(nqubits, matrix, condition, backend): """Helper method for building nearest-neighbor spin model Hamiltonians.""" - h = sum( - _multikron(matrix if condition(i, j) else matrices.I for j in range(nqubits)) - for i in range(nqubits) + indices = list(range(2 * nqubits)) + even, odd = indices[::2], indices[1::2] + lhs = zip( + nqubits + * [ + len(indices), + ], + even, + odd, ) + rhs = ( + [ + len(indices), + ] + + even + + odd + ) + columns = [ + backend.np.reshape( + backend.np.concatenate( + [ + matrix if condition(i, j) else backend.matrices.I() + for i in range(nqubits) + ], + axis=0, + ), + (nqubits, 2, 2), + ) + for j in range(nqubits) + ] + einsum_args = [item for pair in zip(columns, lhs) for item in pair] + dim = 2**nqubits + if backend.platform != "tensorflow": + h = backend.np.einsum(*einsum_args, rhs) + else: + h = np.einsum(*einsum_args, rhs) + h = backend.np.sum(backend.np.reshape(h, (nqubits, dim, dim)), axis=0) + # h = sum( + # _multikron(matrix if condition(i, j) else matrices.I for j in range(nqubits)) + # for i in range(nqubits) + # ) return h -def _OneBodyPauli(nqubits, matrix, dense: bool = True, backend=None): - """Helper method for constracting non-interacting +def _OneBodyPauli(nqubits, operator, dense: bool = True, backend=None): + """Helper method for constructing non-interacting :math:`X`, :math:`Y`, and :math:`Z` Hamiltonians.""" if dense: condition = lambda i, j: i == j % nqubits - ham = -_build_spin_model(nqubits, matrix, condition) + ham = -_build_spin_model(nqubits, operator(0).matrix, condition, backend) return Hamiltonian(nqubits, ham, backend=backend) - matrix = -matrix - terms = [HamiltonianTerm(matrix, i) for i in range(nqubits)] - ham = SymbolicHamiltonian(backend=backend) - ham.terms = terms + # matrix = -matrix + # terms = [HamiltonianTerm(matrix, i) for i in range(nqubits)] + form = sum([-1 * operator(i) for i in range(nqubits)]) + ham = SymbolicHamiltonian(form=form, backend=backend) + # ham.terms = terms return ham From 083ebba88718e377dcc07b82546d9f0885b26709 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 18 Dec 2024 19:23:47 +0100 Subject: [PATCH 04/31] fix: some fixes for tests --- src/qibo/hamiltonians/hamiltonians.py | 1 + src/qibo/hamiltonians/models.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index d663d10d05..b6a6e35e2c 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -406,6 +406,7 @@ def terms(self): terms.append(term) else: self.constant += term.coefficient + return terms @property def matrix(self): diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 1e4f3ccca5..703200048f 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -210,14 +210,16 @@ def Heisenberg( backend = _check_backend(backend) - paulis = [matrices.X, matrices.Y, matrices.Z] + paulis = (symbols.X, symbols.Y, symbols.Z) if dense: condition = lambda i, j: i in {j % nqubits, (j + 1) % nqubits} matrix = np.zeros((2**nqubits, 2**nqubits), dtype=complex) matrix = backend.cast(matrix, dtype=matrix.dtype) for ind, pauli in enumerate(paulis): - double_term = _build_spin_model(nqubits, pauli, condition, backend) + double_term = _build_spin_model( + nqubits, backend.cast(pauli(0).matrix), condition, backend + ) double_term = backend.cast(double_term, dtype=double_term.dtype) matrix = matrix - coupling_constants[ind] * double_term matrix = ( @@ -432,10 +434,12 @@ def _build_spin_model(nqubits, matrix, condition, backend): ] einsum_args = [item for pair in zip(columns, lhs) for item in pair] dim = 2**nqubits - if backend.platform != "tensorflow": - h = backend.np.einsum(*einsum_args, rhs) - else: + if backend.platform == "tensorflow": h = np.einsum(*einsum_args, rhs) + elif backend.platform == "cupy": + h = backend.cp.einsum(*einsum_args, rhs) + else: + h = backend.np.einsum(*einsum_args, rhs) h = backend.np.sum(backend.np.reshape(h, (nqubits, dim, dim)), axis=0) # h = sum( # _multikron(matrix if condition(i, j) else matrices.I for j in range(nqubits)) @@ -449,7 +453,9 @@ def _OneBodyPauli(nqubits, operator, dense: bool = True, backend=None): :math:`X`, :math:`Y`, and :math:`Z` Hamiltonians.""" if dense: condition = lambda i, j: i == j % nqubits - ham = -_build_spin_model(nqubits, operator(0).matrix, condition, backend) + ham = -_build_spin_model( + nqubits, backend.cast(operator(0).matrix), condition, backend + ) return Hamiltonian(nqubits, ham, backend=backend) # matrix = -matrix From 068993823e41f2bc2751feb96c76a66d619f7875 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 18 Dec 2024 20:53:23 +0100 Subject: [PATCH 05/31] fix: some further fixes --- src/qibo/hamiltonians/hamiltonians.py | 16 +--------------- src/qibo/hamiltonians/models.py | 7 +++---- tests/test_hamiltonians.py | 14 +++++++------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index b6a6e35e2c..9c8abe111f 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -723,21 +723,7 @@ def apply_gates(self, state, density_matrix=False): def __matmul__(self, o): """Matrix multiplication with other Hamiltonians or state vectors.""" if isinstance(o, self.__class__): - if self._form is None or o._form is None: - raise_error( - NotImplementedError, - "Multiplication of symbolic Hamiltonians " - "without symbolic form is not implemented.", - ) - new_form = self.form * o.form - new_symbol_map = dict(self.symbol_map) - new_symbol_map.update(o.symbol_map) - new_ham = self.__class__( - new_form, symbol_map=new_symbol_map, backend=self.backend - ) - # if self._dense is not None and o._dense is not None: - # new_ham.dense = self.dense @ o.dense - return new_ham + return o * self if isinstance(o, self.backend.tensor_types): rank = len(tuple(o.shape)) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 703200048f..ad442684b0 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -91,10 +91,9 @@ def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None): ) return Hamiltonian(nqubits, ham, backend=backend) - term = lambda q1, q2: symbols.Z(q1) * symbols.Z(q2) + h * symbols.X(q1) * symbols.I( - q2 - ) - form = sum(term(i, i + 1) for i in range(nqubits - 1)) + term = lambda q1, q2: symbols.Z(q1) * symbols.Z(q2) + h * symbols.X(q1) + form = sum(term(i, i + 1) for i in range(nqubits - 1)) + term(nqubits - 1, 0) + print(form) # matrix = -( # _multikron([backend.matrices.Z, backend.matrices.Z], backend) + h * _multikron([backend.matrices.X, backend.matrices.I], backend) # ) diff --git a/tests/test_hamiltonians.py b/tests/test_hamiltonians.py index e66c824da2..5d96ac464c 100644 --- a/tests/test_hamiltonians.py +++ b/tests/test_hamiltonians.py @@ -341,14 +341,14 @@ def test_hamiltonian_eigenvalues(backend, dtype, sparse_type, dense): c1 = dtype(2.5) H2 = c1 * H1 - H2_eigen = sorted(backend.to_numpy(H2._eigenvalues)) + H2_eigen = sorted(backend.to_numpy(H2.eigenvalues())) hH2_eigen = sorted(backend.to_numpy(backend.calculate_eigenvalues(c1 * H1.matrix))) backend.assert_allclose(H2_eigen, hH2_eigen) c2 = dtype(-11.1) H3 = H1 * c2 if sparse_type is None: - H3_eigen = sorted(backend.to_numpy(H3._eigenvalues)) + H3_eigen = sorted(backend.to_numpy(H3.eigenvalues())) hH3_eigen = sorted( backend.to_numpy(backend.calculate_eigenvalues(H1.matrix * c2)) ) @@ -369,20 +369,20 @@ def test_hamiltonian_eigenvectors(backend, dtype, dense): c1 = dtype(2.5) H2 = c1 * H1 - V2 = backend.to_numpy(H2._eigenvectors) - U2 = backend.to_numpy(H2._eigenvalues) + V2 = backend.to_numpy(H2.eigenvectors()) + U2 = backend.to_numpy(H2.eigenvalues()) backend.assert_allclose(H2.matrix, V2 @ np.diag(U2) @ V2.T) c2 = dtype(-11.1) H3 = H1 * c2 V3 = backend.to_numpy(H3.eigenvectors()) - U3 = backend.to_numpy(H3._eigenvalues) + U3 = backend.to_numpy(H3.eigenvalues()) backend.assert_allclose(H3.matrix, V3 @ np.diag(U3) @ V3.T) c3 = dtype(0) H4 = c3 * H1 - V4 = backend.to_numpy(H4._eigenvectors) - U4 = backend.to_numpy(H4._eigenvalues) + V4 = backend.to_numpy(H4.eigenvectors()) + U4 = backend.to_numpy(H4.eigenvalues()) backend.assert_allclose(H4.matrix, V4 @ np.diag(U4) @ V4.T) From 6fb5270bb823df51468f7c8a50d880f49227622e Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Thu, 19 Dec 2024 09:30:22 +0100 Subject: [PATCH 06/31] fix: fixing the form of some H models --- src/qibo/hamiltonians/models.py | 9 ++---- tests/test_hamiltonians_symbolic.py | 4 +-- tests/test_hamiltonians_trotter.py | 49 ----------------------------- 3 files changed, 5 insertions(+), 57 deletions(-) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index ad442684b0..8707412cd5 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -92,8 +92,7 @@ def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None): return Hamiltonian(nqubits, ham, backend=backend) term = lambda q1, q2: symbols.Z(q1) * symbols.Z(q2) + h * symbols.X(q1) - form = sum(term(i, i + 1) for i in range(nqubits - 1)) + term(nqubits - 1, 0) - print(form) + form = -1 * sum(term(i, i + 1) for i in range(nqubits - 1)) - term(nqubits - 1, 0) # matrix = -( # _multikron([backend.matrices.Z, backend.matrices.Z], backend) + h * _multikron([backend.matrices.X, backend.matrices.I], backend) # ) @@ -229,18 +228,16 @@ def Heisenberg( return Hamiltonian(nqubits, matrix, backend=backend) - paulis = (symbols.X, symbols.Y, symbols.Z) - def h(symbol): return lambda q1, q2: symbol(q1) * symbol(q2) def term(q1, q2): - return -1 * sum( + return sum( coeff * h(operator)(q1, q2) for coeff, operator in zip(coupling_constants, paulis) ) - form = sum(term(i, i + 1) for i in range(nqubits - 1)) + form = -1 * sum(term(i, i + 1) for i in range(nqubits - 1)) - term(nqubits - 1, 0) form -= sum( field_strength * pauli(qubit) for qubit in range(nqubits) diff --git a/tests/test_hamiltonians_symbolic.py b/tests/test_hamiltonians_symbolic.py index 0a0f225728..a3b82d7b66 100644 --- a/tests/test_hamiltonians_symbolic.py +++ b/tests/test_hamiltonians_symbolic.py @@ -351,8 +351,8 @@ def test_trotter_hamiltonian_operation_errors(backend): with pytest.raises(NotImplementedError): h = h1 @ np.ones((2, 2, 2, 2)) h2 = XXZ(3, dense=False, backend=backend) - with pytest.raises(NotImplementedError): - h = h1 @ h2 + # with pytest.raises(NotImplementedError): + # h = h1 @ h2 def test_symbolic_hamiltonian_with_constant(backend): diff --git a/tests/test_hamiltonians_trotter.py b/tests/test_hamiltonians_trotter.py index 0c2d2f1756..b63b3a0814 100644 --- a/tests/test_hamiltonians_trotter.py +++ b/tests/test_hamiltonians_trotter.py @@ -96,55 +96,6 @@ def test_trotter_hamiltonian_matmul(backend, nqubits, normalize): backend.assert_allclose(trotter_matmul, target_matmul) -def test_trotter_hamiltonian_three_qubit_term(backend): - """Test creating ``TrotterHamiltonian`` with three qubit term.""" - from scipy.linalg import expm - - from qibo.hamiltonians.terms import HamiltonianTerm - - numpy_backend = NumpyBackend() - - m1 = random_hermitian(2**3, backend=numpy_backend) - m2 = random_hermitian(2**2, backend=numpy_backend) - m3 = random_hermitian(2**1, backend=numpy_backend) - - terms = [ - HamiltonianTerm(m1, 0, 1, 2), - HamiltonianTerm(m2, 2, 3), - HamiltonianTerm(m3, 1), - ] - m1 = backend.cast(m1, dtype=m1.dtype) - m2 = backend.cast(m2, dtype=m2.dtype) - m3 = backend.cast(m3, dtype=m3.dtype) - - ham = hamiltonians.SymbolicHamiltonian(backend=backend) - ham.terms = terms - - # Test that the `TrotterHamiltonian` dense matrix is correct - eye = np.eye(2, dtype=complex) - eye = backend.cast(eye, dtype=eye.dtype) - mm1 = backend.np.kron(m1, eye) - mm2 = backend.np.kron(backend.np.kron(eye, eye), m2) - mm3 = backend.np.kron(backend.np.kron(eye, m3), backend.np.kron(eye, eye)) - target_ham = hamiltonians.Hamiltonian(4, mm1 + mm2 + mm3, backend=backend) - backend.assert_allclose(ham.matrix, target_ham.matrix) - - dt = 1e-2 - initial_state = random_statevector(2**4, backend=backend) - circuit = ham.circuit(dt=dt) - final_state = backend.execute_circuit( - circuit, backend.np.copy(initial_state) - ).state() - mm1 = backend.to_numpy(mm1) - mm2 = backend.to_numpy(mm2) - mm3 = backend.to_numpy(mm3) - u = [expm(-0.5j * dt * (mm1 + mm3)), expm(-0.5j * dt * mm2)] - u = backend.cast(u) - target_state = backend.np.matmul(u[1], backend.np.matmul(u[0], initial_state)) - target_state = backend.np.matmul(u[0], backend.np.matmul(u[1], target_state)) - backend.assert_allclose(final_state, target_state) - - def test_symbolic_hamiltonian_circuit_different_dts(backend): """Issue: https://github.com/qiboteam/qibo/issues/1357.""" ham = hamiltonians.SymbolicHamiltonian(symbols.Z(0)) From eceb4130a658f7a0c486549b6c3abee87f7b8df7 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Thu, 19 Dec 2024 10:26:09 +0100 Subject: [PATCH 07/31] fix: some fixes to _compose --- src/qibo/hamiltonians/hamiltonians.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index d844a85ac8..6e591dc675 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -652,9 +652,8 @@ def expectation_from_samples(self, freq: dict, qubit_map: list = None) -> float: return self.backend.np.sum(expvals @ counts.T) + self.constant.real def _compose(self, o, operator): - new_ham = self.__class__( - form=self._form, symbol_map=dict(self.symbol_map), backend=self.backend - ) + form = self._form + symbol_map = self.symbol_map if isinstance(o, self.__class__): if self.nqubits != o.nqubits: @@ -664,22 +663,20 @@ def _compose(self, o, operator): ) if o._form is not None: - new_ham.symbol_map.update(o.symbol_map) - new_ham.form = ( - operator(self._form, o._form) if self._form is not None else o._form - ) + symbol_map.update(o.symbol_map) + form = operator(form, o._form) if form is not None else o._form elif isinstance(o, (self.backend.numeric_types, self.backend.tensor_types)): - new_ham.form = ( - operator(self._form, o) if self._form is not None else complex(o) - ) + form = operator(form, complex(o)) if form is not None else complex(o) else: raise_error( NotImplementedError, f"SymbolicHamiltonian composition to {type(o)} not implemented.", ) - return new_ham + return self.__class__( + form=form, symbol_map=symbol_map, nqubits=self.nqubits, backend=self.backend + ) def __add__(self, o): return self._compose(o, lambda x, y: x + y) From f80f4028d59e810c604c7ae1632e19f6c208565f Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Thu, 19 Dec 2024 10:58:04 +0100 Subject: [PATCH 08/31] fix: add missing _check_backend --- doc/final_result.npy | Bin 0 -> 968 bytes src/qibo/hamiltonians/models.py | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 doc/final_result.npy diff --git a/doc/final_result.npy b/doc/final_result.npy new file mode 100644 index 0000000000000000000000000000000000000000..38783b6eba11d6d233fb4575f34db51b7e1606a3 GIT binary patch literal 968 zcmbtSOK;Oa5O$ih1=I3=ziP}Q2?UcMfvO6rNRc=YHAR+?Oi^U5olU*Mui0ILqBIhh zsN~F_ZI{Md{e`6Hcut3ne3*ldL;;~JoBXv zTEWueVSz%Wr_ErgTk)WJ2sIm)8@)=iBn{;_r8=xQOPZ$bZo=yP$03}t;cTNbTZUQUX3H>H z%%n^BzQFwFcFX7t7RfK)%JjtKbe{=Hxmz}fvoJ)OpM)Vvg=#q})@yo1u)LMIr@HT% zs$JSfMp=_~%J(S(uEY^kx{!HH9{U0p*C%-r9)VOR*AIzML~}>AKcW*iE^z+{O-w#btpar=@R|2%>stwoBo?dMR z*Mq{9(I(tbuH3ZY*20xHaQnC`nuI&vyldcGJioV#=gA0nCEW97aNmXvXBF}C>%r(d zD|n4Xkf4hXc79czWxVJA2bj5_F5`a2q@7ELhfWP~U|s@`^3t~T((1f|$9bKQL{fON z&{kL48a6abZBJ2K70dn3Yj~!_wQP8f;)0X1ER^*Ysd!G8Rp6&bcp;(fWkf>9v;F~M Cj}1rw literal 0 HcmV?d00001 diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 8707412cd5..f013e1bde2 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -81,6 +81,7 @@ def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None): """ if nqubits < 2: raise_error(ValueError, "Number of qubits must be larger than one.") + backend = _check_backend(backend) if dense: condition = lambda i, j: i in {j % nqubits, (j + 1) % nqubits} ham = -_build_spin_model(nqubits, backend.matrices.Z, condition, backend) @@ -447,6 +448,7 @@ def _build_spin_model(nqubits, matrix, condition, backend): def _OneBodyPauli(nqubits, operator, dense: bool = True, backend=None): """Helper method for constructing non-interacting :math:`X`, :math:`Y`, and :math:`Z` Hamiltonians.""" + backend = _check_backend(backend) if dense: condition = lambda i, j: i == j % nqubits ham = -_build_spin_model( From 74bc480e8f8d5e0b62c1617563045b51aa79bd9a Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Thu, 19 Dec 2024 12:29:30 +0100 Subject: [PATCH 09/31] feat: using indices in _dense_from_terms + check that form is sympy --- doc/final_result.npy | Bin 968 -> 0 bytes src/qibo/hamiltonians/hamiltonians.py | 54 +++++++++++++++++--------- src/qibo/hamiltonians/models.py | 2 - 3 files changed, 36 insertions(+), 20 deletions(-) delete mode 100644 doc/final_result.npy diff --git a/doc/final_result.npy b/doc/final_result.npy deleted file mode 100644 index 38783b6eba11d6d233fb4575f34db51b7e1606a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 968 zcmbtSOK;Oa5O$ih1=I3=ziP}Q2?UcMfvO6rNRc=YHAR+?Oi^U5olU*Mui0ILqBIhh zsN~F_ZI{Md{e`6Hcut3ne3*ldL;;~JoBXv zTEWueVSz%Wr_ErgTk)WJ2sIm)8@)=iBn{;_r8=xQOPZ$bZo=yP$03}t;cTNbTZUQUX3H>H z%%n^BzQFwFcFX7t7RfK)%JjtKbe{=Hxmz}fvoJ)OpM)Vvg=#q})@yo1u)LMIr@HT% zs$JSfMp=_~%J(S(uEY^kx{!HH9{U0p*C%-r9)VOR*AIzML~}>AKcW*iE^z+{O-w#btpar=@R|2%>stwoBo?dMR z*Mq{9(I(tbuH3ZY*20xHaQnC`nuI&vyldcGJioV#=gA0nCEW97aNmXvXBF}C>%r(d zD|n4Xkf4hXc79czWxVJA2bj5_F5`a2q@7ELhfWP~U|s@`^3t~T((1f|$9bKQL{fON z&{kL48a6abZBJ2K70dn3Yj~!_wQP8f;)0X1ER^*Ysd!G8Rp6&bcp;(fWkf>9v;F~M Cj}1rw diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 6e591dc675..0b42b2dc26 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -8,6 +8,7 @@ import numpy as np import sympy +from qibo.backends import Backend, _check_backend from qibo.config import EINSUM_CHARS, log, raise_error from qibo.hamiltonians.abstract import AbstractHamiltonian from qibo.symbols import Z @@ -323,11 +324,22 @@ class SymbolicHamiltonian(AbstractHamiltonian): Defaults to ``None``. """ - def __init__(self, form=None, nqubits=None, symbol_map={}, backend=None): + def __init__( + self, + form: sympy.Expr, + nqubits: Optional[int] = None, + symbol_map: Optional[dict] = None, + backend: Optional[Backend] = None, + ): super().__init__() - self._form = None + if not isinstance(form, sympy.Expr): + raise_error( + TypeError, + f"The ``form`` of a ``SymbolicHamiltonian`` has to be a ``sympy.Expr``, but a {type(form)} was passed.", + ) + self._form = form self.constant = 0 # used only when we perform calculations using ``_terms`` - self.symbol_map = symbol_map + self.symbol_map = symbol_map if symbol_map is not None else {} # if a symbol in the given form is not a Qibo symbol it must be # included in the ``symbol_map`` @@ -335,15 +347,11 @@ def __init__(self, form=None, nqubits=None, symbol_map={}, backend=None): self._qiboSymbol = Symbol # also used in ``self._get_symbol_matrix`` - from qibo.backends import _check_backend - self.backend = _check_backend(backend) - if form is not None: - self.form = form - self.nqubits = ( - self._calculate_nqubits_from_form(form) if nqubits is None else nqubits - ) + self.nqubits = ( + self._calculate_nqubits_from_form(form) if nqubits is None else nqubits + ) @cached_property def dense(self) -> "MatrixHamiltonian": @@ -516,17 +524,27 @@ def _calculate_dense_from_terms(self) -> Hamiltonian: raise_error(NotImplementedError, "Not enough einsum characters.") matrix = 0 - chars = EINSUM_CHARS[: 2 * self.nqubits] + # chars = EINSUM_CHARS[: 2 * self.nqubits] + indices = list(range(2 * self.nqubits)) for term in self.terms: ntargets = len(term.target_qubits) tmat = np.reshape(term.matrix, 2 * ntargets * (2,)) n = self.nqubits - ntargets emat = np.reshape(np.eye(2**n, dtype=tmat.dtype), 2 * n * (2,)) - gen = lambda x: (chars[i + x] for i in term.target_qubits) - tc = "".join(chain(gen(0), gen(self.nqubits))) - ec = "".join(c for c in chars if c not in tc) - matrix += np.einsum(f"{tc},{ec}->{chars}", tmat, emat) - matrix = np.reshape(matrix, 2 * (2**self.nqubits,)) + # gen = lambda x: (chars[i + x] for i in term.target_qubits) + gen = lambda x: (indices[i + x] for i in term.target_qubits) + # tc = "".join(chain(gen(0), gen(self.nqubits))) + tc = list(chain(gen(0), gen(self.nqubits))) + # ec = "".join(c for c in chars if c not in tc) + ec = list(c for c in indices if c not in tc) + # matrix += np.einsum(f"{tc},{ec}->{chars}", tmat, emat) + matrix += np.einsum(tmat, tc, emat, ec, indices) + + matrix = ( + np.reshape(matrix, 2 * (2**self.nqubits,)) + if len(self.terms) > 0 + else self.backend.np.zeros(2 * (2**self.nqubits,)) + ) return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant def calculate_dense(self): @@ -537,8 +555,8 @@ def calculate_dense(self): # if self._terms is None: # calculate dense matrix directly using the form to avoid the # costly ``sympy.expand`` call - return self._calculate_dense_from_form() - # return self._calculate_dense_from_terms() + # return self._calculate_dense_from_form() + return self._calculate_dense_from_terms() def expectation(self, state, normalize=False): return Hamiltonian.expectation(self, state, normalize) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index f013e1bde2..be4593c696 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -433,8 +433,6 @@ def _build_spin_model(nqubits, matrix, condition, backend): dim = 2**nqubits if backend.platform == "tensorflow": h = np.einsum(*einsum_args, rhs) - elif backend.platform == "cupy": - h = backend.cp.einsum(*einsum_args, rhs) else: h = backend.np.einsum(*einsum_args, rhs) h = backend.np.sum(backend.np.reshape(h, (nqubits, dim, dim)), axis=0) From 6cc8435af939686c1c5097ad028adbb7bfe9a96b Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Thu, 19 Dec 2024 12:48:36 +0100 Subject: [PATCH 10/31] feat: made dense_from_terms backend aware + some comments --- src/qibo/hamiltonians/hamiltonians.py | 39 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 0b42b2dc26..5c3df94783 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -335,7 +335,7 @@ def __init__( if not isinstance(form, sympy.Expr): raise_error( TypeError, - f"The ``form`` of a ``SymbolicHamiltonian`` has to be a ``sympy.Expr``, but a {type(form)} was passed.", + f"The ``form`` of a ``SymbolicHamiltonian`` has to be a ``sympy.Expr``, but a ``{type(form)}`` was passed.", ) self._form = form self.constant = 0 # used only when we perform calculations using ``_terms`` @@ -436,6 +436,7 @@ def ground_state(self): def exp(self, a): return self.dense.exp(a) + # only useful for dense_from_form, which might not be needed in the end def _get_symbol_matrix(self, term): """Calculates numerical matrix corresponding to symbolic expression. @@ -509,6 +510,8 @@ def _get_symbol_matrix(self, term): return result + # not sure this is useful, it appears to be significantly slower than + # the from_terms counterpart def _calculate_dense_from_form(self) -> Hamiltonian: """Calculates equivalent Hamiltonian using symbolic form. @@ -519,35 +522,41 @@ def _calculate_dense_from_form(self) -> Hamiltonian: def _calculate_dense_from_terms(self) -> Hamiltonian: """Calculates equivalent Hamiltonian using the term representation.""" - if 2 * self.nqubits > len(EINSUM_CHARS): # pragma: no cover - # case not tested because it only happens in large examples - raise_error(NotImplementedError, "Not enough einsum characters.") - matrix = 0 - # chars = EINSUM_CHARS[: 2 * self.nqubits] indices = list(range(2 * self.nqubits)) + # most likely the looped einsum could be avoided by preparing all the + # matrices first and performing a single einsum in the end with a suitable + # choice of indices for term in self.terms: ntargets = len(term.target_qubits) - tmat = np.reshape(term.matrix, 2 * ntargets * (2,)) + # I have to cast to a backend array because SymbolicTerm does not support + # backend declaration and just works with numpy, might be worth implementing + # a SymbolicTerm.matrix(backend=None) method that returns the matrix in the + # desired backend type and defaults to numpy or GlobalBackend + # A similar argument holds for qibo Symbols + tmat = self.backend.np.reshape( + self.backend.cast(term.matrix), 2 * ntargets * (2,) + ) n = self.nqubits - ntargets - emat = np.reshape(np.eye(2**n, dtype=tmat.dtype), 2 * n * (2,)) - # gen = lambda x: (chars[i + x] for i in term.target_qubits) + emat = self.backend.np.reshape( + self.backend.np.eye(2**n, dtype=tmat.dtype), 2 * n * (2,) + ) gen = lambda x: (indices[i + x] for i in term.target_qubits) - # tc = "".join(chain(gen(0), gen(self.nqubits))) tc = list(chain(gen(0), gen(self.nqubits))) - # ec = "".join(c for c in chars if c not in tc) ec = list(c for c in indices if c not in tc) - # matrix += np.einsum(f"{tc},{ec}->{chars}", tmat, emat) - matrix += np.einsum(tmat, tc, emat, ec, indices) + if self.backend.platform == "tensorflow": + matrix += np.einsum(tmat, tc, emat, ec, indices) + else: + matrix += self.backend.np.einsum(tmat, tc, emat, ec, indices) matrix = ( - np.reshape(matrix, 2 * (2**self.nqubits,)) + self.backend.np.reshape(matrix, 2 * (2**self.nqubits,)) if len(self.terms) > 0 else self.backend.np.zeros(2 * (2**self.nqubits,)) ) return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant - def calculate_dense(self): + def calculate_dense(self) -> Hamiltonian: log.warning( "Calculating the dense form of a symbolic Hamiltonian. " "This operation is memory inefficient." From 977d8b53113ca6bb608fa8a7ca74d50033db50e9 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Fri, 10 Jan 2025 10:34:51 +0100 Subject: [PATCH 11/31] feat: added backend to symbols + refactor maxcut --- src/qibo/hamiltonians/#hamiltonians.py# | 804 ++++++++++++++++++++++++ src/qibo/hamiltonians/.#hamiltonians.py | 1 + src/qibo/hamiltonians/hamiltonians.py | 3 +- src/qibo/hamiltonians/models.py | 66 +- src/qibo/symbols.py | 32 +- 5 files changed, 854 insertions(+), 52 deletions(-) create mode 100644 src/qibo/hamiltonians/#hamiltonians.py# create mode 120000 src/qibo/hamiltonians/.#hamiltonians.py diff --git a/src/qibo/hamiltonians/#hamiltonians.py# b/src/qibo/hamiltonians/#hamiltonians.py# new file mode 100644 index 0000000000..c2f1e4a150 --- /dev/null +++ b/src/qibo/hamiltonians/#hamiltonians.py# @@ -0,0 +1,804 @@ +"""Module defining Hamiltonian classes.""" + +from functools import cached_property +from itertools import chain +from math import prod +from typing import Optional + +import numpy as np +import sympy + +from qibo.backends import Backend, _check_backend +from qibo.config import EINSUM_CHARS, log, raise_error +from qibo.hamiltonians.abstract import AbstractHamiltonian +from qibo.symbols import Z + + +class Hamiltonian(AbstractHamiltonian): + """Hamiltonian based on a dense or sparse matrix representation. + + Args: + nqubits (int): number of quantum bits. + matrix (ndarray): Matrix representation of the Hamiltonian in the + computational basis as an array of shape :math:`2^{n} \\times 2^{n}`. + Sparse matrices based on ``scipy.sparse`` for ``numpy`` / ``qibojit`` backends + (or on ``tf.sparse`` for the ``tensorflow`` backend) are also supported. + backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used + in the execution. If ``None``, it uses the current backend. + Defaults to ``None``. + """ + + def __init__(self, nqubits, matrix, backend=None): + from qibo.backends import _check_backend + + self.backend = _check_backend(backend) + + if not ( + isinstance(matrix, self.backend.tensor_types) + or self.backend.is_sparse(matrix) + ): + raise_error( + TypeError, + f"Matrix of invalid type {type(matrix)} given during Hamiltonian initialization", + ) + matrix = self.backend.cast(matrix) + + super().__init__() + self.nqubits = nqubits + self.matrix = matrix + self._eigenvalues = None + self._eigenvectors = None + self._exp = {"a": None, "result": None} + + @property + def matrix(self): + """Returns the full matrix representation. + + For :math:`n` qubits, can be a dense :math:`2^{n} \\times 2^{n}` array or a sparse + matrix, depending on how the Hamiltonian was created. + """ + return self._matrix + + @matrix.setter + def matrix(self, mat): + shape = tuple(mat.shape) + if shape != 2 * (2**self.nqubits,): + raise_error( + ValueError, + f"The Hamiltonian is defined for {self.nqubits} qubits " + + f"while the given matrix has shape {shape}.", + ) + self._matrix = mat + + @classmethod + def from_symbolic(cls, symbolic_hamiltonian, symbol_map, backend=None): + """Creates a :class:`qibo.hamiltonian.Hamiltonian` from a symbolic Hamiltonian. + + We refer to :ref:`How to define custom Hamiltonians using symbols? ` + for more details. + + Args: + symbolic_hamiltonian (sympy.Expr): full Hamiltonian written with ``sympy`` symbols. + symbol_map (dict): Dictionary that maps each symbol that appears in + the Hamiltonian to a pair ``(target, matrix)``. + backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used + in the execution. If ``None``, it uses the current backend. + Defaults to ``None``. + + Returns: + :class:`qibo.hamiltonians.SymbolicHamiltonian`: object that implements the + Hamiltonian represented by the given symbolic expression. + """ + log.warning( + "`Hamiltonian.from_symbolic` and the use of symbol maps is " + "deprecated. Please use `SymbolicHamiltonian` and Qibo symbols " + "to construct Hamiltonians using symbols." + ) + return SymbolicHamiltonian( + symbolic_hamiltonian, symbol_map=symbol_map, backend=backend + ) + + def eigenvalues(self, k=6): + if self._eigenvalues is None: + self._eigenvalues = self.backend.calculate_eigenvalues(self.matrix, k) + return self._eigenvalues + + def eigenvectors(self, k=6): + if self._eigenvectors is None: + self._eigenvalues, self._eigenvectors = self.backend.calculate_eigenvectors( + self.matrix, k + ) + return self._eigenvectors + + def exp(self, a): + from qibo.quantum_info.linalg_operations import ( # pylint: disable=C0415 + matrix_exponentiation, + ) + + if self._exp.get("a") != a: + self._exp["a"] = a + self._exp["result"] = matrix_exponentiation( + a, self.matrix, self._eigenvectors, self._eigenvalues, self.backend + ) + return self._exp.get("result") + + def expectation(self, state, normalize=False): + if isinstance(state, self.backend.tensor_types): + state = self.backend.cast(state) + shape = tuple(state.shape) + if len(shape) == 1: # state vector + return self.backend.calculate_expectation_state(self, state, normalize) + + if len(shape) == 2: # density matrix + return self.backend.calculate_expectation_density_matrix( + self, state, normalize + ) + + raise_error( + ValueError, + "Cannot calculate Hamiltonian expectation value " + + f"for state of shape {shape}", + ) + + raise_error( + TypeError, + "Cannot calculate Hamiltonian expectation " + + f"value for state of type {type(state)}", + ) + + def expectation_from_samples(self, freq, qubit_map=None): + obs = self.matrix + if ( + self.backend.np.count_nonzero( + obs - self.backend.np.diag(self.backend.np.diagonal(obs)) + ) + != 0 + ): + raise_error( + NotImplementedError, + "Observable is not diagonal. Expectation of non diagonal observables starting from samples is currently supported for `qibo.hamiltonians.hamiltonians.SymbolicHamiltonian` only.", + ) + keys = list(freq.keys()) + if qubit_map is None: + qubit_map = list(range(int(np.log2(len(obs))))) + counts = np.array(list(freq.values())) / sum(freq.values()) + expval = 0 + size = len(qubit_map) + for j, k in enumerate(keys): + index = 0 + for i in qubit_map: + index += int(k[qubit_map.index(i)]) * 2 ** (size - 1 - i) + expval += obs[index, index] * counts[j] + return self.backend.np.real(expval) + + def eye(self, dim: Optional[int] = None): + """Generate Identity matrix with dimension ``dim``""" + if dim is None: + dim = int(self.matrix.shape[0]) + return self.backend.cast(self.backend.matrices.I(dim), dtype=self.matrix.dtype) + + def energy_fluctuation(self, state): + """ + Evaluate energy fluctuation: + + .. math:: + \\Xi_{k}(\\mu) = \\sqrt{\\bra{\\mu} \\, H^{2} \\, \\ket{\\mu} + - \\bra{\\mu} \\, H \\, \\ket{\\mu}^2} \\, . + + for a given state :math:`\\ket{\\mu}`. + + Args: + state (ndarray): quantum state to be used to compute the energy fluctuation. + + Returns: + float: Energy fluctuation value. + """ + state = self.backend.cast(state) + energy = self.expectation(state) + h = self.matrix + h2 = Hamiltonian(nqubits=self.nqubits, matrix=h @ h, backend=self.backend) + average_h2 = self.backend.calculate_expectation_state(h2, state, normalize=True) + return self.backend.np.sqrt(self.backend.np.abs(average_h2 - energy**2)) + + def __add__(self, o): + if isinstance(o, self.__class__): + if self.nqubits != o.nqubits: + raise_error( + RuntimeError, + "Only hamiltonians with the same number of qubits can be added.", + ) + new_matrix = self.matrix + o.matrix + elif isinstance(o, self.backend.numeric_types): + new_matrix = self.matrix + o * self.eye() + else: + raise_error( + NotImplementedError, + f"Hamiltonian addition to {type(o)} not implemented.", + ) + return self.__class__(self.nqubits, new_matrix, backend=self.backend) + + def __sub__(self, o): + if isinstance(o, self.__class__): + if self.nqubits != o.nqubits: + raise_error( + RuntimeError, + "Only hamiltonians with the same number of qubits can be subtracted.", + ) + new_matrix = self.matrix - o.matrix + elif isinstance(o, self.backend.numeric_types): + new_matrix = self.matrix - o * self.eye() + else: + raise_error( + NotImplementedError, + f"Hamiltonian subtraction to {type(o)} not implemented.", + ) + return self.__class__(self.nqubits, new_matrix, backend=self.backend) + + def __rsub__(self, o): + if isinstance(o, self.__class__): # pragma: no cover + # impractical case because it will be handled by `__sub__` + if self.nqubits != o.nqubits: + raise_error( + RuntimeError, + "Only hamiltonians with the same number of qubits can be added.", + ) + new_matrix = o.matrix - self.matrix + elif isinstance(o, self.backend.numeric_types): + new_matrix = o * self.eye() - self.matrix + else: + raise_error( + NotImplementedError, + f"Hamiltonian subtraction to {type(o)} not implemented.", + ) + return self.__class__(self.nqubits, new_matrix, backend=self.backend) + + def __mul__(self, o): + if isinstance(o, self.backend.tensor_types): + o = complex(o) + elif not isinstance(o, self.backend.numeric_types): + raise_error( + NotImplementedError, + f"Hamiltonian multiplication to {type(o)} not implemented.", + ) + new_matrix = self.matrix * o + r = self.__class__(self.nqubits, new_matrix, backend=self.backend) + o = self.backend.cast(o) + if self._eigenvalues is not None: + if self.backend.np.real(o) >= 0: # TODO: check for side effects K.qnp + r._eigenvalues = o * self._eigenvalues + elif not self.backend.is_sparse(self.matrix): + axis = (0,) if (self.backend.platform == "pytorch") else 0 + r._eigenvalues = o * self.backend.np.flip(self._eigenvalues, axis) + if self._eigenvectors is not None: + if self.backend.np.real(o) > 0: # TODO: see above + r._eigenvectors = self._eigenvectors + elif o == 0: + r._eigenvectors = self.eye(int(self._eigenvectors.shape[0])) + return r + + def __matmul__(self, o): + if isinstance(o, self.__class__): + matrix = self.backend.calculate_hamiltonian_matrix_product( + self.matrix, o.matrix + ) + return self.__class__(self.nqubits, matrix, backend=self.backend) + + if isinstance(o, self.backend.tensor_types): + return self.backend.calculate_hamiltonian_state_product(self.matrix, o) + + raise_error( + NotImplementedError, + f"Hamiltonian matmul to {type(o)} not implemented.", + ) + + +class SymbolicHamiltonian(AbstractHamiltonian): + """Hamiltonian based on a symbolic representation. + + Calculations using symbolic Hamiltonians are either done directly using + the given ``sympy`` expression as it is (``form``) or by parsing the + corresponding ``terms`` (which are :class:`qibo.core.terms.SymbolicTerm` + objects). The latter approach is more computationally costly as it uses + a ``sympy.expand`` call on the given form before parsing the terms. + For this reason the ``terms`` are calculated only when needed, for example + during Trotterization. + The dense matrix of the symbolic Hamiltonian can be calculated directly + from ``form`` without requiring ``terms`` calculation (see + :meth:`qibo.core.hamiltonians.SymbolicHamiltonian.calculate_dense` for details). + + Args: + form (sympy.Expr): Hamiltonian form as a ``sympy.Expr``. Ideally the + Hamiltonian should be written using Qibo symbols. + See :ref:`How to define custom Hamiltonians using symbols? ` + example for more details. + symbol_map (dict): Dictionary that maps each ``sympy.Symbol`` to a tuple + of (target qubit, matrix representation). This feature is kept for + compatibility with older versions where Qibo symbols were not available + and may be deprecated in the future. + It is not required if the Hamiltonian is constructed using Qibo symbols. + The symbol_map can also be used to pass non-quantum operator arguments + to the symbolic Hamiltonian, such as the parameters in the + :meth:`qibo.hamiltonians.models.MaxCut` Hamiltonian. + backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used + in the execution. If ``None``, it uses the current backend. + Defaults to ``None``. + """ + + def __init__( + self, + form: sympy.Expr, + nqubits: Optional[int] = None, + symbol_map: Optional[dict] = None, + backend: Optional[Backend] = None, + ): + super().__init__() + if not isinstance(form, sympy.Expr): + raise_error( + TypeError, + f"The ``form`` of a ``SymbolicHamiltonian`` has to be a ``sympy.Expr``, but a ``{type(form)}`` was passed.", + ) + self._form = form + self.constant = 0 # used only when we perform calculations using ``_terms`` + self.symbol_map = symbol_map if symbol_map is not None else {} + # if a symbol in the given form is not a Qibo symbol it must be + # included in the ``symbol_map`` + + from qibo.symbols import Symbol # pylint: disable=import-outside-toplevel + + self._qiboSymbol = Symbol # also used in ``self._get_symbol_matrix`` + + self.backend = _check_backend(backend) + + self.nqubits = ( + self._calculate_nqubits_from_form(form) if nqubits is None else nqubits + ) + + @cached_property + def dense(self) -> "MatrixHamiltonian": + """Creates the equivalent Hamiltonian matrix.""" + return self.calculate_dense() + + @property + def form(self): + return self._form + + def _calculate_nqubits_from_form(self, form): + """Calculate number of qubits in the system described by the given + Hamiltonian formula + """ + nqubits = 0 + for symbol in form.free_symbols: + if isinstance(symbol, self._qiboSymbol): + q = symbol.target_qubit + elif isinstance(symbol, sympy.Expr): + if symbol not in self.symbol_map: + raise_error(ValueError, f"Symbol {symbol} is not in symbol map.") + q, matrix = self.symbol_map.get(symbol) + if not isinstance(matrix, self.backend.tensor_types): + # ignore symbols that do not correspond to quantum operators + # for example parameters in the MaxCut Hamiltonian + q = 0 + if q > nqubits: + nqubits = q + return nqubits + 1 + + @form.setter + def form(self, form): + # Check that given form is a ``sympy`` expression + if not isinstance(form, sympy.Expr): + raise_error( + TypeError, + f"Symbolic Hamiltonian should be a ``sympy`` expression but is {type(form)}.", + ) + self._form = form + self.nqubits = self._calculate_nqubits_from_form(form) + + @cached_property + def terms(self): + """List of terms of which the Hamiltonian is a sum of. + + Terms will be objects of type :class:`qibo.core.terms.HamiltonianTerm`. + """ + # Calculate terms based on ``self.form`` + from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel + SymbolicTerm, + ) + + self.constant = 0.0 + + form = sympy.expand(self.form) + terms = [] + for f, c in form.as_coefficients_dict().items(): + term = SymbolicTerm(c, f, self.symbol_map) + if term.target_qubits: + terms.append(term) + else: + self.constant += term.coefficient + return terms + + @property + def matrix(self): + """Returns the full matrix representation. + + Consisting of :math:`2^{n} \\times 2^{n}`` elements. + """ + return self.dense.matrix + + def eigenvalues(self, k=6): + return self.dense.eigenvalues(k) + + def eigenvectors(self, k=6): + return self.dense.eigenvectors(k) + + def ground_state(self): + return self.eigenvectors()[:, 0] + + def exp(self, a): + return self.dense.exp(a) + + # only useful for dense_from_form, which might not be needed in the end + def _get_symbol_matrix(self, term): + """Calculates numerical matrix corresponding to symbolic expression. + + This is partly equivalent to sympy's ``.subs``, which does not work + in our case as it does not allow us to substitute ``sympy.Symbol`` + with numpy arrays and there are different complication when switching + to ``sympy.MatrixSymbol``. Here we calculate the full numerical matrix + given the symbolic expression using recursion. + Helper method for ``_calculate_dense_from_form``. + + Args: + term (sympy.Expr): Symbolic expression containing local operators. + + Returns: + ndarray: matrix corresponding to the given expression as an array + of shape ``(2 ** self.nqubits, 2 ** self.nqubits)``. + """ + if isinstance(term, sympy.Add): + # symbolic op for addition + result = sum( + self._get_symbol_matrix(subterm) for subterm in term.as_ordered_terms() + ) + + elif isinstance(term, sympy.Mul): + # symbolic op for multiplication + # note that we need to use matrix multiplication even though + # we use scalar symbols for convenience + factors = term.as_ordered_factors() + result = self._get_symbol_matrix(factors[0]) + for subterm in factors[1:]: + result = result @ self._get_symbol_matrix(subterm) + + elif isinstance(term, sympy.Pow): + # symbolic op for power + base, exponent = term.as_base_exp() + matrix = self._get_symbol_matrix(base) + # multiply ``base`` matrix ``exponent`` times to itself + result = matrix + for _ in range(exponent - 1): + result = result @ matrix + + elif isinstance(term, sympy.Symbol): + # if the term is a ``Symbol`` then it corresponds to a quantum + # operator for which we can construct the full matrix directly + if isinstance(term, self._qiboSymbol): + # if we have a Qibo symbol the matrix construction is + # implemented in :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. + result = term.full_matrix(self.nqubits) + else: + q, matrix = self.symbol_map.get(term) + if not isinstance(matrix, self.backend.tensor_types): + # symbols that do not correspond to quantum operators + # for example parameters in the MaxCut Hamiltonian + result = complex(matrix) * np.eye(2**self.nqubits) + else: + # if we do not have a Qibo symbol we construct one and use + # :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. + result = self._qiboSymbol(q, matrix).full_matrix(self.nqubits) + + elif term.is_number: + # if the term is number we should return in the form of identity + # matrix because in expressions like `1 + Z`, `1` is not correspond + # to the float 1 but the identity operator (matrix) + result = complex(term) * np.eye(2**self.nqubits) + + else: + raise_error( + TypeError, + f"Cannot calculate matrix for symbolic term of type {type(term)}.", + ) + + return result + + # not sure this is useful, it appears to be significantly slower than + # the from_terms counterpart + def _calculate_dense_from_form(self) -> Hamiltonian: + """Calculates equivalent Hamiltonian using symbolic form. + + Useful when the term representation is not available. + """ + matrix = self._get_symbol_matrix(self.form) + return Hamiltonian(self.nqubits, matrix, backend=self.backend) + + def _calculate_dense_from_terms(self) -> Hamiltonian: + """Calculates equivalent Hamiltonian using the term representation.""" + matrix = 0 + indices = list(range(2 * self.nqubits)) + # most likely the looped einsum could be avoided by preparing all the + # matrices first and performing a single einsum in the end with a suitable + # choice of indices + for term in self.terms: + ntargets = len(term.target_qubits) + # I have to cast to a backend array because SymbolicTerm does not support + # backend declaration and just works with numpy, might be worth implementing + # a SymbolicTerm.matrix(backend=None) method that returns the matrix in the + # desired backend type and defaults to numpy or GlobalBackend + # A similar argument holds for qibo Symbols + tmat = self.backend.np.reshape( + self.backend.cast(term.matrix), 2 * ntargets * (2,) + ) + n = self.nqubits - ntargets + emat = self.backend.np.reshape( + self.backend.np.eye(2**n, dtype=tmat.dtype), 2 * n * (2,) + ) + gen = lambda x: (indices[i + x] for i in term.target_qubits) + tc = list(chain(gen(0), gen(self.nqubits))) + ec = list(c for c in indices if c not in tc) + if self.backend.platform == "tensorflow": + matrix += np.einsum(tmat, tc, emat, ec, indices) + else: + matrix += self.backend.np.einsum(tmat, tc, emat, ec, indices) + + matrix = ( + self.backend.np.reshape(matrix, 2 * (2**self.nqubits,)) + if len(self.terms) > 0 + else self.backend.np.zeros(2 * (2**self.nqubits,)) + ) + return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant + + def calculate_dense(self) -> Hamiltonian: + log.warning( + "Calculating the dense form of a symbolic Hamiltonian. " + "This operation is memory inefficient." + ) + # calculate dense matrix directly using the form to avoid the + # costly ``sympy.expand`` call + if len(self.terms) > 40: + return self._calculate_dense_from_form() + return self._calculate_dense_from_terms() + + def expectation(self, state, normalize=False): + return Hamiltonian.expectation(self, state, normalize) + + def expectation_from_circuit(self, circuit: "Circuit", nshots: int = 1000) -> float: + """ + Calculate the expectation value from a circuit. + This even works for observables not completely diagonal in the computational + basis, but only diagonal at a term level in a defined basis. Namely, for + an observable of the form :math:``H = \\sum_i H_i``, where each :math:``H_i`` + consists in a `n`-qubits pauli operator :math:`P_0 \\otimes P_1 \\otimes \\cdots \\otimes P_n`, + the expectation value is computed by rotating the input circuit in the suitable + basis for each term :math:``H_i`` thus extracting the `term-wise` expectations + that are then summed to build the global expectation value. + Each term of the observable is treated separately, by measuring in the correct + basis and re-executing the circuit. + + Args: + circuit (Circuit): input circuit. + nshots (int): number of shots, defaults to 1000. + + Returns: + (float): the calculated expectation value. + """ + from qibo import gates + + rotated_circuits = [] + coefficients = [] + Z_observables = [] + qubit_maps = [] + for term in self.terms: + # store coefficient + coefficients.append(term.coefficient) + # Only care about non-I terms + non_identity_factors = [ + factor for factor in term.factors if factor.name[0] != "I" + ] + # build diagonal observable + Z_observables.append( + SymbolicHamiltonian( + prod(Z(factor.target_qubit) for factor in non_identity_factors), + nqubits=circuit.nqubits, + backend=self.backend, + ) + ) + # Get the qubits we want to measure for each term + qubit_map = sorted(factor.target_qubit for factor in non_identity_factors) + # prepare the measurement basis and append it to the circuit + measurements = [ + gates.M(factor.target_qubit, basis=factor.gate.__class__) + for factor in non_identity_factors + ] + circ_copy = circuit.copy(True) + circ_copy.add(measurements) + rotated_circuits.append(circ_copy) + # for mapping the obtained sample frequencies to the original qubits + qubit_maps.append(qubit_map) + frequencies = [ + result.frequencies() + for result in self.backend.execute_circuits(rotated_circuits, nshots=nshots) + ] + return sum( + coeff * obs.expectation_from_samples(freq, qubit_map) + for coeff, freq, obs, qubit_map in zip( + coefficients, frequencies, Z_observables, qubit_maps + ) + ) + + def expectation_from_samples(self, freq: dict, qubit_map: list = None) -> float: + """ + Calculate the expectation value from the samples. + The observable has to be diagonal in the computational basis. + + Args: + freq (dict): input frequencies of the samples. + qubit_map (list): qubit map. + + Returns: + (float): the calculated expectation value. + """ + for term in self.terms: + # pylint: disable=E1101 + for factor in term.factors: + if not isinstance(factor, Z): + raise_error( + NotImplementedError, "Observable is not a Z Pauli string." + ) + + if qubit_map is None: + qubit_map = list(range(self.nqubits)) + + keys = list(freq.keys()) + counts = self.backend.cast(list(freq.values()), self.backend.precision) / sum( + freq.values() + ) + expvals = [] + for term in self.terms: + qubits = { + factor.target_qubit for factor in term.factors if factor.name[0] != "I" + } + expvals.extend( + [ + term.coefficient.real + * (-1) ** [state[qubit_map.index(q)] for q in qubits].count("1") + for state in keys + ] + ) + expvals = self.backend.cast(expvals, dtype=counts.dtype).reshape( + len(self.terms), len(freq) + ) + return self.backend.np.sum(expvals @ counts.T) + self.constant.real + + def _compose(self, o, operator): + form = self._form + symbol_map = self.symbol_map + + if isinstance(o, self.__class__): + if self.nqubits != o.nqubits: + raise_error( + RuntimeError, + "Only hamiltonians with the same number of qubits can be composed.", + ) + + if o._form is not None: + symbol_map.update(o.symbol_map) + form = operator(form, o._form) if form is not None else o._form + + elif isinstance(o, (self.backend.numeric_types, self.backend.tensor_types)): + form = operator(form, complex(o)) if form is not None else complex(o) + else: + raise_error( + NotImplementedError, + f"SymbolicHamiltonian composition to {type(o)} not implemented.", + ) + + return self.__class__( + form=form, symbol_map=symbol_map, nqubits=self.nqubits, backend=self.backend + ) + + def __add__(self, o): + return self._compose(o, lambda x, y: x + y) + + def __sub__(self, o): + return self._compose(o, lambda x, y: x - y) + + def __rsub__(self, o): + return self._compose(o, lambda x, y: y - x) + + def __mul__(self, o): + # o = complex(o) + return self._compose(o, lambda x, y: y * x) + + def apply_gates(self, state, density_matrix=False): + """Applies gates corresponding to the Hamiltonian terms. + + Gates are applied to the given state. + + Helper method for :meth:`qibo.hamiltonians.SymbolicHamiltonian.__matmul__`. + """ + total = 0 + for term in self.terms: + total += term( + self.backend, + self.backend.cast(state, copy=True), + self.nqubits, + density_matrix=density_matrix, + ) + if self.constant: # pragma: no cover + total += self.constant * state + return total + + def __matmul__(self, o): + """Matrix multiplication with other Hamiltonians or state vectors.""" + if isinstance(o, self.__class__): + return o * self + + if isinstance(o, self.backend.tensor_types): + rank = len(tuple(o.shape)) + if rank not in (1, 2): + raise_error( + NotImplementedError, + f"Cannot multiply Hamiltonian with rank-{rank} tensor.", + ) + state_qubits = int(np.log2(int(o.shape[0]))) + if state_qubits != self.nqubits: + raise_error( + ValueError, + f"Cannot multiply Hamiltonian on {self.nqubits} qubits to " + + f"state of {state_qubits} qubits.", + ) + if rank == 1: # state vector + return self.apply_gates(o) + + return self.apply_gates(o, density_matrix=True) + + raise_error( + NotImplementedError, + f"Hamiltonian matmul to {type(o)} not implemented.", + ) + + def circuit(self, dt, accelerators=None): + """Circuit that implements a Trotter step of this Hamiltonian. + + Args: + dt (float): Time step used for Trotterization. + accelerators (dict, optional): Dictionary with accelerators for distributed circuits. + Defaults to ``None``. + """ + from qibo import Circuit # pylint: disable=import-outside-toplevel + from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel + TermGroup, + ) + + groups = TermGroup.from_terms(self.terms) + circuit = Circuit(self.nqubits, accelerators=accelerators) + circuit.add( + group.term.expgate(dt / 2.0) for group in chain(groups, groups[::-1]) + ) + + return circuit + + +class TrotterHamiltonian: + """""" + + def __init__(self, *parts): + raise_error( + NotImplementedError, + "`TrotterHamiltonian` is substituted by `SymbolicHamiltonian` " + + "and is no longer supported. Please check the documentation " + + "of `SymbolicHamiltonian` for more details.", + ) + + @classmethod + def from_symbolic(cls, symbolic_hamiltonian, symbol_map): + return cls() diff --git a/src/qibo/hamiltonians/.#hamiltonians.py b/src/qibo/hamiltonians/.#hamiltonians.py new file mode 120000 index 0000000000..977a90663a --- /dev/null +++ b/src/qibo/hamiltonians/.#hamiltonians.py @@ -0,0 +1 @@ +andrea@MacBook-Pro-3.local.11311 \ No newline at end of file diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 5c3df94783..6594a1db8e 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -564,7 +564,8 @@ def calculate_dense(self) -> Hamiltonian: # if self._terms is None: # calculate dense matrix directly using the form to avoid the # costly ``sympy.expand`` call - # return self._calculate_dense_from_form() + if len(self.terms) > 40: + return self._calculate_dense_from_form() return self._calculate_dense_from_terms() def expectation(self, state, normalize=False): diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index be4593c696..3826a8d140 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -1,5 +1,5 @@ from functools import reduce -from typing import Union +from typing import Optional, Union import numpy as np @@ -94,17 +94,16 @@ def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None): term = lambda q1, q2: symbols.Z(q1) * symbols.Z(q2) + h * symbols.X(q1) form = -1 * sum(term(i, i + 1) for i in range(nqubits - 1)) - term(nqubits - 1, 0) - # matrix = -( - # _multikron([backend.matrices.Z, backend.matrices.Z], backend) + h * _multikron([backend.matrices.X, backend.matrices.I], backend) - # ) - # terms = [HamiltonianTerm(matrix, i, i + 1) for i in range(nqubits - 1)] - # terms.append(HamiltonianTerm(matrix, nqubits - 1, 0)) ham = SymbolicHamiltonian(form=form, nqubits=nqubits, backend=backend) - # ham.terms = terms return ham -def MaxCut(nqubits, dense: bool = True, backend=None): +def MaxCut( + nqubits, + dense: bool = True, + adj_matrix: Optional[Union[list[list[float]], np.ndarray]] = None, + backend=None, +): """Max Cut Hamiltonian. .. math:: @@ -115,26 +114,29 @@ def MaxCut(nqubits, dense: bool = True, backend=None): dense (bool): If ``True`` it creates the Hamiltonian as a :class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`. + adj_matrix (list[list[float]] | np.ndarray): Adjecency matrix of the graph. Defaults to a + homogeneous fully connected graph with all edges having an equal 1.0 weight. backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses the current backend. Defaults to ``None``. """ - import sympy as sp + if adj_matrix is None: + adj_matrix = np.ones((nqubits, nqubits)) + elif len(adj_matrix) != nqubits: + raise_error( + RuntimeError, + f"Expected an adjacency matrix of shape ({nqubits},{nqubits}) for a {nqubits}-qubits system.", + ) - Z = sp.symbols(f"Z:{nqubits}") - V = sp.symbols(f"V:{nqubits**2}") - sham = -sum( - V[i * nqubits + j] * (1 - Z[i] * Z[j]) + form = -sum( + adj_matrix[i][j] + * (1 - symbols.Z(i, backend=backend) * symbols.Z(j, backend=backend)) for i in range(nqubits) for j in range(nqubits) ) - sham /= 2 + form /= 2 - v = np.ones(nqubits**2) - smap = {s: (i, matrices.Z) for i, s in enumerate(Z)} - smap.update({s: (i, v[i]) for i, s in enumerate(V)}) - - ham = SymbolicHamiltonian(sham, symbol_map=smap, backend=backend) + ham = SymbolicHamiltonian(form, nqubits=nqubits, backend=backend) if dense: return ham.dense return ham @@ -246,33 +248,7 @@ def term(q1, q2): if field_strength != 0.0 ) - """ - hx = _multikron([matrices.X, matrices.X]) - hy = _multikron([matrices.Y, matrices.Y]) - hz = _multikron([matrices.Z, matrices.Z]) - - matrix = ( - -coupling_constants[0] * hx - - coupling_constants[1] * hy - - coupling_constants[2] * hz - ) - - terms = [HamiltonianTerm(matrix, i, i + 1) for i in range(nqubits - 1)] - terms.append(HamiltonianTerm(matrix, nqubits - 1, 0)) - - terms.extend( - [ - -field_strength * HamiltonianTerm(pauli, qubit) - for qubit in range(nqubits) - for field_strength, pauli in zip(external_field_strengths, paulis) - if field_strength != 0.0 - ] - ) - """ - ham = SymbolicHamiltonian(form=form, backend=backend) - # ham.terms = terms - return ham diff --git a/src/qibo/symbols.py b/src/qibo/symbols.py index edb03eb677..4dbb7e28f6 100644 --- a/src/qibo/symbols.py +++ b/src/qibo/symbols.py @@ -1,8 +1,10 @@ +from typing import Optional + import numpy as np import sympy from qibo import gates -from qibo.backends import matrices +from qibo.backends import Backend, _check_backend, get_backend, matrices from qibo.config import raise_error @@ -42,7 +44,14 @@ def __new__(cls, q, matrix=None, name="Symbol", commutative=False, **assumptions assumptions["commutative"] = commutative return super().__new__(cls=cls, name=name, **assumptions) - def __init__(self, q, matrix=None, name="Symbol", commutative=False): + def __init__( + self, + q, + matrix=None, + name="Symbol", + commutative=False, + backend: Optional[Backend] = None, + ): self.target_qubit = q self._gate = None if not ( @@ -64,7 +73,8 @@ def __init__(self, q, matrix=None, name="Symbol", commutative=False): ) ): raise_error(TypeError, f"Invalid type {type(matrix)} of symbol matrix.") - self.matrix = matrix + self._matrix = matrix + self.backend = _check_backend(backend) def __getstate__(self): return { @@ -78,6 +88,7 @@ def __setstate__(self, data): self.matrix = data.get("matrix") self.name = data.get("name") self._gate = None + self.backend = get_backend() @property def gate(self): @@ -89,11 +100,20 @@ def gate(self): def calculate_gate(self): # pragma: no cover return gates.Unitary(self.matrix, self.target_qubit) + @property + def matrix(self): + return self.backend.cast(self._matrix) + + @matrix.setter + def matrix(self, matrix): + self._matrix = matrix + def full_matrix(self, nqubits): """Calculates the full dense matrix corresponding to the symbol as part of a bigger system. Args: nqubits (int): Total number of qubits in the system. + backend (Backend): Optional backend to represent the matrix with. By default the global backend is used. Returns: Matrix of dimension (2^nqubits, 2^nqubits) composed of the Kronecker @@ -101,7 +121,7 @@ def full_matrix(self, nqubits): """ from qibo.hamiltonians.models import _multikron - matrix_list = self.target_qubit * [matrices.I] + matrix_list = self.target_qubit * [backend.matrices.I] matrix_list.append(self.matrix) n = nqubits - self.target_qubit - 1 matrix_list.extend(matrices.I for _ in range(n)) @@ -113,10 +133,10 @@ def __new__(cls, q, commutative=False, **assumptions): matrix = getattr(matrices, cls.__name__) return super().__new__(cls, q, matrix, cls.__name__, commutative, **assumptions) - def __init__(self, q, commutative=False): + def __init__(self, q, commutative=False, backend: Optional[Backend] = None): name = self.__class__.__name__ matrix = getattr(matrices, name) - super().__init__(q, matrix, name, commutative) + super().__init__(q, matrix, name, commutative, backend=backend) def calculate_gate(self): name = self.__class__.__name__ From febf76b969952605164ca59596f30aad2afbe212 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Fri, 10 Jan 2025 11:18:21 +0100 Subject: [PATCH 12/31] fix: removed cache files --- src/qibo/hamiltonians/#hamiltonians.py# | 804 ------------------------ src/qibo/hamiltonians/.#hamiltonians.py | 1 - src/qibo/hamiltonians/hamiltonians.py | 5 +- src/qibo/hamiltonians/models.py | 4 +- 4 files changed, 5 insertions(+), 809 deletions(-) delete mode 100644 src/qibo/hamiltonians/#hamiltonians.py# delete mode 120000 src/qibo/hamiltonians/.#hamiltonians.py diff --git a/src/qibo/hamiltonians/#hamiltonians.py# b/src/qibo/hamiltonians/#hamiltonians.py# deleted file mode 100644 index c2f1e4a150..0000000000 --- a/src/qibo/hamiltonians/#hamiltonians.py# +++ /dev/null @@ -1,804 +0,0 @@ -"""Module defining Hamiltonian classes.""" - -from functools import cached_property -from itertools import chain -from math import prod -from typing import Optional - -import numpy as np -import sympy - -from qibo.backends import Backend, _check_backend -from qibo.config import EINSUM_CHARS, log, raise_error -from qibo.hamiltonians.abstract import AbstractHamiltonian -from qibo.symbols import Z - - -class Hamiltonian(AbstractHamiltonian): - """Hamiltonian based on a dense or sparse matrix representation. - - Args: - nqubits (int): number of quantum bits. - matrix (ndarray): Matrix representation of the Hamiltonian in the - computational basis as an array of shape :math:`2^{n} \\times 2^{n}`. - Sparse matrices based on ``scipy.sparse`` for ``numpy`` / ``qibojit`` backends - (or on ``tf.sparse`` for the ``tensorflow`` backend) are also supported. - backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used - in the execution. If ``None``, it uses the current backend. - Defaults to ``None``. - """ - - def __init__(self, nqubits, matrix, backend=None): - from qibo.backends import _check_backend - - self.backend = _check_backend(backend) - - if not ( - isinstance(matrix, self.backend.tensor_types) - or self.backend.is_sparse(matrix) - ): - raise_error( - TypeError, - f"Matrix of invalid type {type(matrix)} given during Hamiltonian initialization", - ) - matrix = self.backend.cast(matrix) - - super().__init__() - self.nqubits = nqubits - self.matrix = matrix - self._eigenvalues = None - self._eigenvectors = None - self._exp = {"a": None, "result": None} - - @property - def matrix(self): - """Returns the full matrix representation. - - For :math:`n` qubits, can be a dense :math:`2^{n} \\times 2^{n}` array or a sparse - matrix, depending on how the Hamiltonian was created. - """ - return self._matrix - - @matrix.setter - def matrix(self, mat): - shape = tuple(mat.shape) - if shape != 2 * (2**self.nqubits,): - raise_error( - ValueError, - f"The Hamiltonian is defined for {self.nqubits} qubits " - + f"while the given matrix has shape {shape}.", - ) - self._matrix = mat - - @classmethod - def from_symbolic(cls, symbolic_hamiltonian, symbol_map, backend=None): - """Creates a :class:`qibo.hamiltonian.Hamiltonian` from a symbolic Hamiltonian. - - We refer to :ref:`How to define custom Hamiltonians using symbols? ` - for more details. - - Args: - symbolic_hamiltonian (sympy.Expr): full Hamiltonian written with ``sympy`` symbols. - symbol_map (dict): Dictionary that maps each symbol that appears in - the Hamiltonian to a pair ``(target, matrix)``. - backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used - in the execution. If ``None``, it uses the current backend. - Defaults to ``None``. - - Returns: - :class:`qibo.hamiltonians.SymbolicHamiltonian`: object that implements the - Hamiltonian represented by the given symbolic expression. - """ - log.warning( - "`Hamiltonian.from_symbolic` and the use of symbol maps is " - "deprecated. Please use `SymbolicHamiltonian` and Qibo symbols " - "to construct Hamiltonians using symbols." - ) - return SymbolicHamiltonian( - symbolic_hamiltonian, symbol_map=symbol_map, backend=backend - ) - - def eigenvalues(self, k=6): - if self._eigenvalues is None: - self._eigenvalues = self.backend.calculate_eigenvalues(self.matrix, k) - return self._eigenvalues - - def eigenvectors(self, k=6): - if self._eigenvectors is None: - self._eigenvalues, self._eigenvectors = self.backend.calculate_eigenvectors( - self.matrix, k - ) - return self._eigenvectors - - def exp(self, a): - from qibo.quantum_info.linalg_operations import ( # pylint: disable=C0415 - matrix_exponentiation, - ) - - if self._exp.get("a") != a: - self._exp["a"] = a - self._exp["result"] = matrix_exponentiation( - a, self.matrix, self._eigenvectors, self._eigenvalues, self.backend - ) - return self._exp.get("result") - - def expectation(self, state, normalize=False): - if isinstance(state, self.backend.tensor_types): - state = self.backend.cast(state) - shape = tuple(state.shape) - if len(shape) == 1: # state vector - return self.backend.calculate_expectation_state(self, state, normalize) - - if len(shape) == 2: # density matrix - return self.backend.calculate_expectation_density_matrix( - self, state, normalize - ) - - raise_error( - ValueError, - "Cannot calculate Hamiltonian expectation value " - + f"for state of shape {shape}", - ) - - raise_error( - TypeError, - "Cannot calculate Hamiltonian expectation " - + f"value for state of type {type(state)}", - ) - - def expectation_from_samples(self, freq, qubit_map=None): - obs = self.matrix - if ( - self.backend.np.count_nonzero( - obs - self.backend.np.diag(self.backend.np.diagonal(obs)) - ) - != 0 - ): - raise_error( - NotImplementedError, - "Observable is not diagonal. Expectation of non diagonal observables starting from samples is currently supported for `qibo.hamiltonians.hamiltonians.SymbolicHamiltonian` only.", - ) - keys = list(freq.keys()) - if qubit_map is None: - qubit_map = list(range(int(np.log2(len(obs))))) - counts = np.array(list(freq.values())) / sum(freq.values()) - expval = 0 - size = len(qubit_map) - for j, k in enumerate(keys): - index = 0 - for i in qubit_map: - index += int(k[qubit_map.index(i)]) * 2 ** (size - 1 - i) - expval += obs[index, index] * counts[j] - return self.backend.np.real(expval) - - def eye(self, dim: Optional[int] = None): - """Generate Identity matrix with dimension ``dim``""" - if dim is None: - dim = int(self.matrix.shape[0]) - return self.backend.cast(self.backend.matrices.I(dim), dtype=self.matrix.dtype) - - def energy_fluctuation(self, state): - """ - Evaluate energy fluctuation: - - .. math:: - \\Xi_{k}(\\mu) = \\sqrt{\\bra{\\mu} \\, H^{2} \\, \\ket{\\mu} - - \\bra{\\mu} \\, H \\, \\ket{\\mu}^2} \\, . - - for a given state :math:`\\ket{\\mu}`. - - Args: - state (ndarray): quantum state to be used to compute the energy fluctuation. - - Returns: - float: Energy fluctuation value. - """ - state = self.backend.cast(state) - energy = self.expectation(state) - h = self.matrix - h2 = Hamiltonian(nqubits=self.nqubits, matrix=h @ h, backend=self.backend) - average_h2 = self.backend.calculate_expectation_state(h2, state, normalize=True) - return self.backend.np.sqrt(self.backend.np.abs(average_h2 - energy**2)) - - def __add__(self, o): - if isinstance(o, self.__class__): - if self.nqubits != o.nqubits: - raise_error( - RuntimeError, - "Only hamiltonians with the same number of qubits can be added.", - ) - new_matrix = self.matrix + o.matrix - elif isinstance(o, self.backend.numeric_types): - new_matrix = self.matrix + o * self.eye() - else: - raise_error( - NotImplementedError, - f"Hamiltonian addition to {type(o)} not implemented.", - ) - return self.__class__(self.nqubits, new_matrix, backend=self.backend) - - def __sub__(self, o): - if isinstance(o, self.__class__): - if self.nqubits != o.nqubits: - raise_error( - RuntimeError, - "Only hamiltonians with the same number of qubits can be subtracted.", - ) - new_matrix = self.matrix - o.matrix - elif isinstance(o, self.backend.numeric_types): - new_matrix = self.matrix - o * self.eye() - else: - raise_error( - NotImplementedError, - f"Hamiltonian subtraction to {type(o)} not implemented.", - ) - return self.__class__(self.nqubits, new_matrix, backend=self.backend) - - def __rsub__(self, o): - if isinstance(o, self.__class__): # pragma: no cover - # impractical case because it will be handled by `__sub__` - if self.nqubits != o.nqubits: - raise_error( - RuntimeError, - "Only hamiltonians with the same number of qubits can be added.", - ) - new_matrix = o.matrix - self.matrix - elif isinstance(o, self.backend.numeric_types): - new_matrix = o * self.eye() - self.matrix - else: - raise_error( - NotImplementedError, - f"Hamiltonian subtraction to {type(o)} not implemented.", - ) - return self.__class__(self.nqubits, new_matrix, backend=self.backend) - - def __mul__(self, o): - if isinstance(o, self.backend.tensor_types): - o = complex(o) - elif not isinstance(o, self.backend.numeric_types): - raise_error( - NotImplementedError, - f"Hamiltonian multiplication to {type(o)} not implemented.", - ) - new_matrix = self.matrix * o - r = self.__class__(self.nqubits, new_matrix, backend=self.backend) - o = self.backend.cast(o) - if self._eigenvalues is not None: - if self.backend.np.real(o) >= 0: # TODO: check for side effects K.qnp - r._eigenvalues = o * self._eigenvalues - elif not self.backend.is_sparse(self.matrix): - axis = (0,) if (self.backend.platform == "pytorch") else 0 - r._eigenvalues = o * self.backend.np.flip(self._eigenvalues, axis) - if self._eigenvectors is not None: - if self.backend.np.real(o) > 0: # TODO: see above - r._eigenvectors = self._eigenvectors - elif o == 0: - r._eigenvectors = self.eye(int(self._eigenvectors.shape[0])) - return r - - def __matmul__(self, o): - if isinstance(o, self.__class__): - matrix = self.backend.calculate_hamiltonian_matrix_product( - self.matrix, o.matrix - ) - return self.__class__(self.nqubits, matrix, backend=self.backend) - - if isinstance(o, self.backend.tensor_types): - return self.backend.calculate_hamiltonian_state_product(self.matrix, o) - - raise_error( - NotImplementedError, - f"Hamiltonian matmul to {type(o)} not implemented.", - ) - - -class SymbolicHamiltonian(AbstractHamiltonian): - """Hamiltonian based on a symbolic representation. - - Calculations using symbolic Hamiltonians are either done directly using - the given ``sympy`` expression as it is (``form``) or by parsing the - corresponding ``terms`` (which are :class:`qibo.core.terms.SymbolicTerm` - objects). The latter approach is more computationally costly as it uses - a ``sympy.expand`` call on the given form before parsing the terms. - For this reason the ``terms`` are calculated only when needed, for example - during Trotterization. - The dense matrix of the symbolic Hamiltonian can be calculated directly - from ``form`` without requiring ``terms`` calculation (see - :meth:`qibo.core.hamiltonians.SymbolicHamiltonian.calculate_dense` for details). - - Args: - form (sympy.Expr): Hamiltonian form as a ``sympy.Expr``. Ideally the - Hamiltonian should be written using Qibo symbols. - See :ref:`How to define custom Hamiltonians using symbols? ` - example for more details. - symbol_map (dict): Dictionary that maps each ``sympy.Symbol`` to a tuple - of (target qubit, matrix representation). This feature is kept for - compatibility with older versions where Qibo symbols were not available - and may be deprecated in the future. - It is not required if the Hamiltonian is constructed using Qibo symbols. - The symbol_map can also be used to pass non-quantum operator arguments - to the symbolic Hamiltonian, such as the parameters in the - :meth:`qibo.hamiltonians.models.MaxCut` Hamiltonian. - backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used - in the execution. If ``None``, it uses the current backend. - Defaults to ``None``. - """ - - def __init__( - self, - form: sympy.Expr, - nqubits: Optional[int] = None, - symbol_map: Optional[dict] = None, - backend: Optional[Backend] = None, - ): - super().__init__() - if not isinstance(form, sympy.Expr): - raise_error( - TypeError, - f"The ``form`` of a ``SymbolicHamiltonian`` has to be a ``sympy.Expr``, but a ``{type(form)}`` was passed.", - ) - self._form = form - self.constant = 0 # used only when we perform calculations using ``_terms`` - self.symbol_map = symbol_map if symbol_map is not None else {} - # if a symbol in the given form is not a Qibo symbol it must be - # included in the ``symbol_map`` - - from qibo.symbols import Symbol # pylint: disable=import-outside-toplevel - - self._qiboSymbol = Symbol # also used in ``self._get_symbol_matrix`` - - self.backend = _check_backend(backend) - - self.nqubits = ( - self._calculate_nqubits_from_form(form) if nqubits is None else nqubits - ) - - @cached_property - def dense(self) -> "MatrixHamiltonian": - """Creates the equivalent Hamiltonian matrix.""" - return self.calculate_dense() - - @property - def form(self): - return self._form - - def _calculate_nqubits_from_form(self, form): - """Calculate number of qubits in the system described by the given - Hamiltonian formula - """ - nqubits = 0 - for symbol in form.free_symbols: - if isinstance(symbol, self._qiboSymbol): - q = symbol.target_qubit - elif isinstance(symbol, sympy.Expr): - if symbol not in self.symbol_map: - raise_error(ValueError, f"Symbol {symbol} is not in symbol map.") - q, matrix = self.symbol_map.get(symbol) - if not isinstance(matrix, self.backend.tensor_types): - # ignore symbols that do not correspond to quantum operators - # for example parameters in the MaxCut Hamiltonian - q = 0 - if q > nqubits: - nqubits = q - return nqubits + 1 - - @form.setter - def form(self, form): - # Check that given form is a ``sympy`` expression - if not isinstance(form, sympy.Expr): - raise_error( - TypeError, - f"Symbolic Hamiltonian should be a ``sympy`` expression but is {type(form)}.", - ) - self._form = form - self.nqubits = self._calculate_nqubits_from_form(form) - - @cached_property - def terms(self): - """List of terms of which the Hamiltonian is a sum of. - - Terms will be objects of type :class:`qibo.core.terms.HamiltonianTerm`. - """ - # Calculate terms based on ``self.form`` - from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel - SymbolicTerm, - ) - - self.constant = 0.0 - - form = sympy.expand(self.form) - terms = [] - for f, c in form.as_coefficients_dict().items(): - term = SymbolicTerm(c, f, self.symbol_map) - if term.target_qubits: - terms.append(term) - else: - self.constant += term.coefficient - return terms - - @property - def matrix(self): - """Returns the full matrix representation. - - Consisting of :math:`2^{n} \\times 2^{n}`` elements. - """ - return self.dense.matrix - - def eigenvalues(self, k=6): - return self.dense.eigenvalues(k) - - def eigenvectors(self, k=6): - return self.dense.eigenvectors(k) - - def ground_state(self): - return self.eigenvectors()[:, 0] - - def exp(self, a): - return self.dense.exp(a) - - # only useful for dense_from_form, which might not be needed in the end - def _get_symbol_matrix(self, term): - """Calculates numerical matrix corresponding to symbolic expression. - - This is partly equivalent to sympy's ``.subs``, which does not work - in our case as it does not allow us to substitute ``sympy.Symbol`` - with numpy arrays and there are different complication when switching - to ``sympy.MatrixSymbol``. Here we calculate the full numerical matrix - given the symbolic expression using recursion. - Helper method for ``_calculate_dense_from_form``. - - Args: - term (sympy.Expr): Symbolic expression containing local operators. - - Returns: - ndarray: matrix corresponding to the given expression as an array - of shape ``(2 ** self.nqubits, 2 ** self.nqubits)``. - """ - if isinstance(term, sympy.Add): - # symbolic op for addition - result = sum( - self._get_symbol_matrix(subterm) for subterm in term.as_ordered_terms() - ) - - elif isinstance(term, sympy.Mul): - # symbolic op for multiplication - # note that we need to use matrix multiplication even though - # we use scalar symbols for convenience - factors = term.as_ordered_factors() - result = self._get_symbol_matrix(factors[0]) - for subterm in factors[1:]: - result = result @ self._get_symbol_matrix(subterm) - - elif isinstance(term, sympy.Pow): - # symbolic op for power - base, exponent = term.as_base_exp() - matrix = self._get_symbol_matrix(base) - # multiply ``base`` matrix ``exponent`` times to itself - result = matrix - for _ in range(exponent - 1): - result = result @ matrix - - elif isinstance(term, sympy.Symbol): - # if the term is a ``Symbol`` then it corresponds to a quantum - # operator for which we can construct the full matrix directly - if isinstance(term, self._qiboSymbol): - # if we have a Qibo symbol the matrix construction is - # implemented in :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. - result = term.full_matrix(self.nqubits) - else: - q, matrix = self.symbol_map.get(term) - if not isinstance(matrix, self.backend.tensor_types): - # symbols that do not correspond to quantum operators - # for example parameters in the MaxCut Hamiltonian - result = complex(matrix) * np.eye(2**self.nqubits) - else: - # if we do not have a Qibo symbol we construct one and use - # :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. - result = self._qiboSymbol(q, matrix).full_matrix(self.nqubits) - - elif term.is_number: - # if the term is number we should return in the form of identity - # matrix because in expressions like `1 + Z`, `1` is not correspond - # to the float 1 but the identity operator (matrix) - result = complex(term) * np.eye(2**self.nqubits) - - else: - raise_error( - TypeError, - f"Cannot calculate matrix for symbolic term of type {type(term)}.", - ) - - return result - - # not sure this is useful, it appears to be significantly slower than - # the from_terms counterpart - def _calculate_dense_from_form(self) -> Hamiltonian: - """Calculates equivalent Hamiltonian using symbolic form. - - Useful when the term representation is not available. - """ - matrix = self._get_symbol_matrix(self.form) - return Hamiltonian(self.nqubits, matrix, backend=self.backend) - - def _calculate_dense_from_terms(self) -> Hamiltonian: - """Calculates equivalent Hamiltonian using the term representation.""" - matrix = 0 - indices = list(range(2 * self.nqubits)) - # most likely the looped einsum could be avoided by preparing all the - # matrices first and performing a single einsum in the end with a suitable - # choice of indices - for term in self.terms: - ntargets = len(term.target_qubits) - # I have to cast to a backend array because SymbolicTerm does not support - # backend declaration and just works with numpy, might be worth implementing - # a SymbolicTerm.matrix(backend=None) method that returns the matrix in the - # desired backend type and defaults to numpy or GlobalBackend - # A similar argument holds for qibo Symbols - tmat = self.backend.np.reshape( - self.backend.cast(term.matrix), 2 * ntargets * (2,) - ) - n = self.nqubits - ntargets - emat = self.backend.np.reshape( - self.backend.np.eye(2**n, dtype=tmat.dtype), 2 * n * (2,) - ) - gen = lambda x: (indices[i + x] for i in term.target_qubits) - tc = list(chain(gen(0), gen(self.nqubits))) - ec = list(c for c in indices if c not in tc) - if self.backend.platform == "tensorflow": - matrix += np.einsum(tmat, tc, emat, ec, indices) - else: - matrix += self.backend.np.einsum(tmat, tc, emat, ec, indices) - - matrix = ( - self.backend.np.reshape(matrix, 2 * (2**self.nqubits,)) - if len(self.terms) > 0 - else self.backend.np.zeros(2 * (2**self.nqubits,)) - ) - return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant - - def calculate_dense(self) -> Hamiltonian: - log.warning( - "Calculating the dense form of a symbolic Hamiltonian. " - "This operation is memory inefficient." - ) - # calculate dense matrix directly using the form to avoid the - # costly ``sympy.expand`` call - if len(self.terms) > 40: - return self._calculate_dense_from_form() - return self._calculate_dense_from_terms() - - def expectation(self, state, normalize=False): - return Hamiltonian.expectation(self, state, normalize) - - def expectation_from_circuit(self, circuit: "Circuit", nshots: int = 1000) -> float: - """ - Calculate the expectation value from a circuit. - This even works for observables not completely diagonal in the computational - basis, but only diagonal at a term level in a defined basis. Namely, for - an observable of the form :math:``H = \\sum_i H_i``, where each :math:``H_i`` - consists in a `n`-qubits pauli operator :math:`P_0 \\otimes P_1 \\otimes \\cdots \\otimes P_n`, - the expectation value is computed by rotating the input circuit in the suitable - basis for each term :math:``H_i`` thus extracting the `term-wise` expectations - that are then summed to build the global expectation value. - Each term of the observable is treated separately, by measuring in the correct - basis and re-executing the circuit. - - Args: - circuit (Circuit): input circuit. - nshots (int): number of shots, defaults to 1000. - - Returns: - (float): the calculated expectation value. - """ - from qibo import gates - - rotated_circuits = [] - coefficients = [] - Z_observables = [] - qubit_maps = [] - for term in self.terms: - # store coefficient - coefficients.append(term.coefficient) - # Only care about non-I terms - non_identity_factors = [ - factor for factor in term.factors if factor.name[0] != "I" - ] - # build diagonal observable - Z_observables.append( - SymbolicHamiltonian( - prod(Z(factor.target_qubit) for factor in non_identity_factors), - nqubits=circuit.nqubits, - backend=self.backend, - ) - ) - # Get the qubits we want to measure for each term - qubit_map = sorted(factor.target_qubit for factor in non_identity_factors) - # prepare the measurement basis and append it to the circuit - measurements = [ - gates.M(factor.target_qubit, basis=factor.gate.__class__) - for factor in non_identity_factors - ] - circ_copy = circuit.copy(True) - circ_copy.add(measurements) - rotated_circuits.append(circ_copy) - # for mapping the obtained sample frequencies to the original qubits - qubit_maps.append(qubit_map) - frequencies = [ - result.frequencies() - for result in self.backend.execute_circuits(rotated_circuits, nshots=nshots) - ] - return sum( - coeff * obs.expectation_from_samples(freq, qubit_map) - for coeff, freq, obs, qubit_map in zip( - coefficients, frequencies, Z_observables, qubit_maps - ) - ) - - def expectation_from_samples(self, freq: dict, qubit_map: list = None) -> float: - """ - Calculate the expectation value from the samples. - The observable has to be diagonal in the computational basis. - - Args: - freq (dict): input frequencies of the samples. - qubit_map (list): qubit map. - - Returns: - (float): the calculated expectation value. - """ - for term in self.terms: - # pylint: disable=E1101 - for factor in term.factors: - if not isinstance(factor, Z): - raise_error( - NotImplementedError, "Observable is not a Z Pauli string." - ) - - if qubit_map is None: - qubit_map = list(range(self.nqubits)) - - keys = list(freq.keys()) - counts = self.backend.cast(list(freq.values()), self.backend.precision) / sum( - freq.values() - ) - expvals = [] - for term in self.terms: - qubits = { - factor.target_qubit for factor in term.factors if factor.name[0] != "I" - } - expvals.extend( - [ - term.coefficient.real - * (-1) ** [state[qubit_map.index(q)] for q in qubits].count("1") - for state in keys - ] - ) - expvals = self.backend.cast(expvals, dtype=counts.dtype).reshape( - len(self.terms), len(freq) - ) - return self.backend.np.sum(expvals @ counts.T) + self.constant.real - - def _compose(self, o, operator): - form = self._form - symbol_map = self.symbol_map - - if isinstance(o, self.__class__): - if self.nqubits != o.nqubits: - raise_error( - RuntimeError, - "Only hamiltonians with the same number of qubits can be composed.", - ) - - if o._form is not None: - symbol_map.update(o.symbol_map) - form = operator(form, o._form) if form is not None else o._form - - elif isinstance(o, (self.backend.numeric_types, self.backend.tensor_types)): - form = operator(form, complex(o)) if form is not None else complex(o) - else: - raise_error( - NotImplementedError, - f"SymbolicHamiltonian composition to {type(o)} not implemented.", - ) - - return self.__class__( - form=form, symbol_map=symbol_map, nqubits=self.nqubits, backend=self.backend - ) - - def __add__(self, o): - return self._compose(o, lambda x, y: x + y) - - def __sub__(self, o): - return self._compose(o, lambda x, y: x - y) - - def __rsub__(self, o): - return self._compose(o, lambda x, y: y - x) - - def __mul__(self, o): - # o = complex(o) - return self._compose(o, lambda x, y: y * x) - - def apply_gates(self, state, density_matrix=False): - """Applies gates corresponding to the Hamiltonian terms. - - Gates are applied to the given state. - - Helper method for :meth:`qibo.hamiltonians.SymbolicHamiltonian.__matmul__`. - """ - total = 0 - for term in self.terms: - total += term( - self.backend, - self.backend.cast(state, copy=True), - self.nqubits, - density_matrix=density_matrix, - ) - if self.constant: # pragma: no cover - total += self.constant * state - return total - - def __matmul__(self, o): - """Matrix multiplication with other Hamiltonians or state vectors.""" - if isinstance(o, self.__class__): - return o * self - - if isinstance(o, self.backend.tensor_types): - rank = len(tuple(o.shape)) - if rank not in (1, 2): - raise_error( - NotImplementedError, - f"Cannot multiply Hamiltonian with rank-{rank} tensor.", - ) - state_qubits = int(np.log2(int(o.shape[0]))) - if state_qubits != self.nqubits: - raise_error( - ValueError, - f"Cannot multiply Hamiltonian on {self.nqubits} qubits to " - + f"state of {state_qubits} qubits.", - ) - if rank == 1: # state vector - return self.apply_gates(o) - - return self.apply_gates(o, density_matrix=True) - - raise_error( - NotImplementedError, - f"Hamiltonian matmul to {type(o)} not implemented.", - ) - - def circuit(self, dt, accelerators=None): - """Circuit that implements a Trotter step of this Hamiltonian. - - Args: - dt (float): Time step used for Trotterization. - accelerators (dict, optional): Dictionary with accelerators for distributed circuits. - Defaults to ``None``. - """ - from qibo import Circuit # pylint: disable=import-outside-toplevel - from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel - TermGroup, - ) - - groups = TermGroup.from_terms(self.terms) - circuit = Circuit(self.nqubits, accelerators=accelerators) - circuit.add( - group.term.expgate(dt / 2.0) for group in chain(groups, groups[::-1]) - ) - - return circuit - - -class TrotterHamiltonian: - """""" - - def __init__(self, *parts): - raise_error( - NotImplementedError, - "`TrotterHamiltonian` is substituted by `SymbolicHamiltonian` " - + "and is no longer supported. Please check the documentation " - + "of `SymbolicHamiltonian` for more details.", - ) - - @classmethod - def from_symbolic(cls, symbolic_hamiltonian, symbol_map): - return cls() diff --git a/src/qibo/hamiltonians/.#hamiltonians.py b/src/qibo/hamiltonians/.#hamiltonians.py deleted file mode 120000 index 977a90663a..0000000000 --- a/src/qibo/hamiltonians/.#hamiltonians.py +++ /dev/null @@ -1 +0,0 @@ -andrea@MacBook-Pro-3.local.11311 \ No newline at end of file diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 6594a1db8e..cd0ff6afaa 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -208,7 +208,9 @@ def __add__(self, o): "Only hamiltonians with the same number of qubits can be added.", ) new_matrix = self.matrix + o.matrix - elif isinstance(o, self.backend.numeric_types): + elif isinstance(o, self.backend.numeric_types) or isinstance( + o, self.backend.tensor_types + ): new_matrix = self.matrix + o * self.eye() else: raise_error( @@ -561,7 +563,6 @@ def calculate_dense(self) -> Hamiltonian: "Calculating the dense form of a symbolic Hamiltonian. " "This operation is memory inefficient." ) - # if self._terms is None: # calculate dense matrix directly using the form to avoid the # costly ``sympy.expand`` call if len(self.terms) > 40: diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 3826a8d140..dab39dbe7b 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -4,7 +4,7 @@ import numpy as np from qibo import symbols -from qibo.backends import _check_backend, matrices +from qibo.backends import Backend, _check_backend, matrices from qibo.config import raise_error from qibo.hamiltonians.hamiltonians import Hamiltonian, SymbolicHamiltonian from qibo.hamiltonians.terms import HamiltonianTerm @@ -102,7 +102,7 @@ def MaxCut( nqubits, dense: bool = True, adj_matrix: Optional[Union[list[list[float]], np.ndarray]] = None, - backend=None, + backend: Optional[Backend] = None, ): """Max Cut Hamiltonian. From ac0d9aa83397c0cb723f89838e34737d50a1d20d Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Fri, 10 Jan 2025 15:19:52 +0100 Subject: [PATCH 13/31] feat: made symbolicterm backend aware --- src/qibo/hamiltonians/models.py | 12 ++++++------ src/qibo/hamiltonians/terms.py | 10 ++++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index dab39dbe7b..9ab9f0d53c 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -219,7 +219,7 @@ def Heisenberg( matrix = backend.cast(matrix, dtype=matrix.dtype) for ind, pauli in enumerate(paulis): double_term = _build_spin_model( - nqubits, backend.cast(pauli(0).matrix), condition, backend + nqubits, pauli(0, backend=backend).matrix, condition, backend ) double_term = backend.cast(double_term, dtype=double_term.dtype) matrix = matrix - coupling_constants[ind] * double_term @@ -392,13 +392,13 @@ def _build_spin_model(nqubits, matrix, condition, backend): + even + odd ) + eye = backend.matrices.I() + if backend.platform == "cupy": + eye = backend.cast(eye) columns = [ backend.np.reshape( backend.np.concatenate( - [ - matrix if condition(i, j) else backend.matrices.I() - for i in range(nqubits) - ], + [matrix if condition(i, j) else eye for i in range(nqubits)], axis=0, ), (nqubits, 2, 2), @@ -426,7 +426,7 @@ def _OneBodyPauli(nqubits, operator, dense: bool = True, backend=None): if dense: condition = lambda i, j: i == j % nqubits ham = -_build_spin_model( - nqubits, backend.cast(operator(0).matrix), condition, backend + nqubits, operator(0, backend=backend).matrix, condition, backend ) return Hamiltonian(nqubits, ham, backend=backend) diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index c46557abeb..af858f70a8 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -1,7 +1,10 @@ +from typing import Optional + import numpy as np import sympy from qibo import gates, symbols +from qibo.backends import Backend, _check_backend from qibo.config import raise_error from qibo.symbols import I, X, Y, Z @@ -135,11 +138,14 @@ class SymbolicTerm(HamiltonianTerm): symbols were not available. """ - def __init__(self, coefficient, factors=1, symbol_map={}): + def __init__( + self, coefficient, factors=1, symbol_map={}, backend: Optional[Backend] = None + ): self.coefficient = complex(coefficient) self._matrix = None self._gate = None self.hamiltonian = None + self.backend = _check_backend(backend) # List of :class:`qibo.symbols.Symbol` that represent the term factors self.factors = [] @@ -175,7 +181,7 @@ def __init__(self, coefficient, factors=1, symbol_map={}): factor = Symbol(q, matrix, name=factor.name) if isinstance(factor, sympy.Symbol): - if isinstance(factor.matrix, np.ndarray): + if isinstance(factor.matrix, self.backend.tensor_types): self.factors.extend(pow * [factor]) q = factor.target_qubit # if pow > 1 the matrix should be multiplied multiple From 86dc4b77d7e7260d1689776f13249e63c18e7f07 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Fri, 10 Jan 2025 17:51:10 +0100 Subject: [PATCH 14/31] fix: fix to symb ham terms --- src/qibo/hamiltonians/hamiltonians.py | 2 +- src/qibo/hamiltonians/models.py | 4 ++-- src/qibo/symbols.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index cd0ff6afaa..ae10702ec2 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -411,7 +411,7 @@ def terms(self): form = sympy.expand(self.form) terms = [] for f, c in form.as_coefficients_dict().items(): - term = SymbolicTerm(c, f, self.symbol_map) + term = SymbolicTerm(c, f, self.symbol_map, backend=self.backend) if term.target_qubits: terms.append(term) else: diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 9ab9f0d53c..63facfcd61 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -345,7 +345,7 @@ def XXZ(nqubits, delta=0.5, dense: bool = True, backend=None): return Heisenberg(nqubits, [-1, -1, -delta], 0, dense=dense, backend=backend) -def _multikron(matrix_list, backend=None): +def _multikron(matrix_list, backend): """Calculates Kronecker product of a list of matrices. Args: @@ -370,7 +370,7 @@ def _multikron(matrix_list, backend=None): h = backend.np.sum(backend.np.reshape(h, (dim, dim)), axis=0) return h """ - return reduce(np.kron, matrix_list) + return reduce(backend.np.kron, matrix_list) def _build_spin_model(nqubits, matrix, condition, backend): diff --git a/src/qibo/symbols.py b/src/qibo/symbols.py index 4dbb7e28f6..928bd7d0ee 100644 --- a/src/qibo/symbols.py +++ b/src/qibo/symbols.py @@ -121,11 +121,11 @@ def full_matrix(self, nqubits): """ from qibo.hamiltonians.models import _multikron - matrix_list = self.target_qubit * [backend.matrices.I] + matrix_list = self.target_qubit * [self.backend.matrices.I()] matrix_list.append(self.matrix) n = nqubits - self.target_qubit - 1 - matrix_list.extend(matrices.I for _ in range(n)) - return _multikron(matrix_list) + matrix_list.extend(self.backend.matrices.I() for _ in range(n)) + return _multikron(matrix_list, backend=self.backend) class PauliSymbol(Symbol): From 0f5f66e3d645beb1f0e1003295f3af04a49ec1d2 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Fri, 10 Jan 2025 18:35:31 +0100 Subject: [PATCH 15/31] feat: replaced loop with einsum in sym term matrix --- src/qibo/hamiltonians/models.py | 8 ++++---- src/qibo/hamiltonians/terms.py | 15 +++++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 63facfcd61..9913ddbcfe 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -1,4 +1,3 @@ -from functools import reduce from typing import Optional, Union import numpy as np @@ -354,10 +353,10 @@ def _multikron(matrix_list, backend): Returns: ndarray: Kronecker product of all matrices in ``matrix_list``. """ - """ + # this is a better implementation but requires the whole # hamiltonian/symbols modules to be adapted - indices = list(range(2*len(matrix_list))) + indices = list(range(2 * len(matrix_list))) even, odd = indices[::2], indices[1::2] lhs = zip(even, odd) rhs = even + odd @@ -367,10 +366,11 @@ def _multikron(matrix_list, backend): h = backend.np.einsum(*einsum_args, rhs) else: h = np.einsum(*einsum_args, rhs) - h = backend.np.sum(backend.np.reshape(h, (dim, dim)), axis=0) + h = backend.np.sum(backend.np.reshape(h, (-1, dim, dim)), axis=0) return h """ return reduce(backend.np.kron, matrix_list) + """ def _build_spin_model(nqubits, matrix, condition, backend): diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index af858f70a8..231a4423f4 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -216,6 +216,10 @@ def matrix(self): """ if self._matrix is None: + einsum = ( + self.backend.np.einsum if self.backend.name != "qiboml" else np.einsum + ) + def matrices_product(matrices): """Product of matrices that act on the same tuple of qubits. @@ -223,12 +227,11 @@ def matrices_product(matrices): matrices (list): List of matrices to multiply, as exists in the values of ``SymbolicTerm.matrix_map``. """ - if len(matrices) == 1: - return matrices[0] - matrix = np.copy(matrices[0]) - for m in matrices[1:]: - matrix = matrix @ m - return matrix + nmat = len(matrices) + indices = zip(range(nmat), range(1, nmat + 1)) + lhs = zip(matrices, indices) + lhs = [el for item in lhs for el in item] + return einsum(*lhs, (0, nmat)) self._matrix = self.coefficient for q in self.target_qubits: From 318cae3b116d5d3f592214b6577cbe3269b362ea Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Sun, 12 Jan 2025 12:17:00 +0100 Subject: [PATCH 16/31] feat: removed unnecessary cast --- src/qibo/hamiltonians/hamiltonians.py | 4 +- src/qibo/hamiltonians/terms.py | 56 ++++++++++++++------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index ae10702ec2..fe413d48b4 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -536,9 +536,7 @@ def _calculate_dense_from_terms(self) -> Hamiltonian: # a SymbolicTerm.matrix(backend=None) method that returns the matrix in the # desired backend type and defaults to numpy or GlobalBackend # A similar argument holds for qibo Symbols - tmat = self.backend.np.reshape( - self.backend.cast(term.matrix), 2 * ntargets * (2,) - ) + tmat = self.backend.np.reshape(term.matrix, 2 * ntargets * (2,)) n = self.nqubits - ntargets emat = self.backend.np.reshape( self.backend.np.eye(2**n, dtype=tmat.dtype), 2 * n * (2,) diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index 231a4423f4..f79cafbaf6 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -1,3 +1,4 @@ +from functools import cached_property from typing import Optional import numpy as np @@ -142,7 +143,6 @@ def __init__( self, coefficient, factors=1, symbol_map={}, backend: Optional[Backend] = None ): self.coefficient = complex(coefficient) - self._matrix = None self._gate = None self.hamiltonian = None self.backend = _check_backend(backend) @@ -178,7 +178,7 @@ def __init__( from qibo.symbols import Symbol q, matrix = symbol_map.get(factor) - factor = Symbol(q, matrix, name=factor.name) + factor = Symbol(q, matrix, name=factor.name, backend=self.backend) if isinstance(factor, sympy.Symbol): if isinstance(factor.matrix, self.backend.tensor_types): @@ -205,7 +205,7 @@ def __init__( self.target_qubits = tuple(sorted(self.matrix_map.keys())) - @property + @cached_property def matrix(self): """Calculates the full matrix corresponding to this term. @@ -214,30 +214,32 @@ def matrix(self): where ``ntargets`` is the number of qubits included in the factors of this term. """ - if self._matrix is None: - - einsum = ( - self.backend.np.einsum if self.backend.name != "qiboml" else np.einsum - ) - - def matrices_product(matrices): - """Product of matrices that act on the same tuple of qubits. - - Args: - matrices (list): List of matrices to multiply, as exists in - the values of ``SymbolicTerm.matrix_map``. - """ - nmat = len(matrices) - indices = zip(range(nmat), range(1, nmat + 1)) - lhs = zip(matrices, indices) - lhs = [el for item in lhs for el in item] - return einsum(*lhs, (0, nmat)) - - self._matrix = self.coefficient - for q in self.target_qubits: - matrix = matrices_product(self.matrix_map.get(q)) - self._matrix = np.kron(self._matrix, matrix) - return self._matrix + einsum = ( + self.backend.np.einsum + if self.backend.platform != "tensorflow" + else np.einsum + ) + + def matrices_product(matrices): + """Product of matrices that act on the same tuple of qubits. + + Args: + matrices (list): List of matrices to multiply, as exists in + the values of ``SymbolicTerm.matrix_map``. + """ + nmat = len(matrices) + indices = zip(range(nmat), range(1, nmat + 1)) + lhs = zip(matrices, indices) + lhs = [el for item in lhs for el in item] + return einsum(*lhs, (0, nmat)) + + # if self.backend.platform == "pytorch": + # breakpoint() + matrix = self.coefficient * self.backend.np.ones(1) + for q in self.target_qubits: + prod = matrices_product(self.matrix_map.get(q)) + matrix = self.backend.np.kron(matrix, prod) + return matrix def copy(self): """Creates a shallow copy of the term with the same attributes.""" From e461f96f24c894244684e545e1e1628fbf77ff73 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Mon, 13 Jan 2025 15:47:03 +0100 Subject: [PATCH 17/31] fix: adding backend here and there --- src/qibo/hamiltonians/hamiltonians.py | 8 +- src/qibo/hamiltonians/models.py | 14 +- src/qibo/hamiltonians/terms.py | 76 ++++--- src/qibo/models/tsp.py | 12 +- src/qibo/symbols.py | 3 +- tests/#test_hamiltonians_from_symbols.py# | 248 ++++++++++++++++++++++ tests/.#test_hamiltonians_from_symbols.py | 1 + tests/test_hamiltonians_from_symbols.py | 130 +++++++----- tests/test_hamiltonians_symbolic.py | 115 +++++++--- tests/test_models_error_mitigation.py | 2 +- tests/test_states.py | 8 +- 11 files changed, 484 insertions(+), 133 deletions(-) create mode 100644 tests/#test_hamiltonians_from_symbols.py# create mode 120000 tests/.#test_hamiltonians_from_symbols.py diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index fe413d48b4..5a5fa638e4 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -492,17 +492,19 @@ def _get_symbol_matrix(self, term): if not isinstance(matrix, self.backend.tensor_types): # symbols that do not correspond to quantum operators # for example parameters in the MaxCut Hamiltonian - result = complex(matrix) * np.eye(2**self.nqubits) + result = complex(matrix) * self.backend.np.eye(2**self.nqubits) else: # if we do not have a Qibo symbol we construct one and use # :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. - result = self._qiboSymbol(q, matrix).full_matrix(self.nqubits) + result = self._qiboSymbol( + q, matrix, backend=self.backend + ).full_matrix(self.nqubits) elif term.is_number: # if the term is number we should return in the form of identity # matrix because in expressions like `1 + Z`, `1` is not correspond # to the float 1 but the identity operator (matrix) - result = complex(term) * np.eye(2**self.nqubits) + result = complex(term) * self.backend.np.eye(2**self.nqubits, dtype=float) else: raise_error( diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 9913ddbcfe..3f41a132f7 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -91,7 +91,9 @@ def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None): ) return Hamiltonian(nqubits, ham, backend=backend) - term = lambda q1, q2: symbols.Z(q1) * symbols.Z(q2) + h * symbols.X(q1) + term = lambda q1, q2: symbols.Z(q1, backend=backend) * symbols.Z( + q2, backend=backend + ) + h * symbols.X(q1, backend=backend) form = -1 * sum(term(i, i + 1) for i in range(nqubits - 1)) - term(nqubits - 1, 0) ham = SymbolicHamiltonian(form=form, nqubits=nqubits, backend=backend) return ham @@ -231,7 +233,7 @@ def Heisenberg( return Hamiltonian(nqubits, matrix, backend=backend) def h(symbol): - return lambda q1, q2: symbol(q1) * symbol(q2) + return lambda q1, q2: symbol(q1, backend=backend) * symbol(q2, backend=backend) def term(q1, q2): return sum( @@ -368,9 +370,6 @@ def _multikron(matrix_list, backend): h = np.einsum(*einsum_args, rhs) h = backend.np.sum(backend.np.reshape(h, (-1, dim, dim)), axis=0) return h - """ - return reduce(backend.np.kron, matrix_list) - """ def _build_spin_model(nqubits, matrix, condition, backend): @@ -430,9 +429,6 @@ def _OneBodyPauli(nqubits, operator, dense: bool = True, backend=None): ) return Hamiltonian(nqubits, ham, backend=backend) - # matrix = -matrix - # terms = [HamiltonianTerm(matrix, i) for i in range(nqubits)] - form = sum([-1 * operator(i) for i in range(nqubits)]) + form = sum([-1 * operator(i, backend=backend) for i in range(nqubits)]) ham = SymbolicHamiltonian(form=form, backend=backend) - # ham.terms = terms return ham diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index f79cafbaf6..da58e4730c 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -25,14 +25,15 @@ class HamiltonianTerm: q (list): List of target qubit ids. """ - def __init__(self, matrix, *q): + def __init__(self, matrix, *q, backend: Optional[Backend] = None): + self.backend = _check_backend(backend) for qi in q: if qi < 0: raise_error( ValueError, f"Invalid qubit id {qi} < 0 was given in Hamiltonian term.", ) - if not isinstance(matrix, np.ndarray): + if not isinstance(matrix, self.backend.tensor_types): raise_error(TypeError, f"Invalid type {type(matrix)} of symbol matrix.") dim = int(matrix.shape[0]) if 2 ** len(q) != dim: @@ -82,8 +83,10 @@ def merge(self, term): "Cannot merge HamiltonianTerm acting on " + f"qubits {term.target_qubits} to term on qubits {self.target_qubits}.", ) - matrix = np.kron(term.matrix, np.eye(2 ** (len(self) - len(term)))) - matrix = np.reshape(matrix, 2 * len(self) * (2,)) + matrix = self.backend.np.kron( + term.matrix, self.backend.np.eye(2 ** (len(self) - len(term))) + ) + matrix = self.backend.np.reshape(matrix, 2 * len(self) * (2,)) order = [] i = len(term) for qubit in self.target_qubits: @@ -93,15 +96,19 @@ def merge(self, term): order.append(i) i += 1 order.extend([x + len(order) for x in order]) - matrix = np.transpose(matrix, order) - matrix = np.reshape(matrix, 2 * (2 ** len(self),)) - return HamiltonianTerm(self.matrix + matrix, *self.target_qubits) + matrix = self.backend.np.transpose(matrix, order) + matrix = self.backend.np.reshape(matrix, 2 * (2 ** len(self),)) + return HamiltonianTerm( + self.matrix + matrix, *self.target_qubits, backend=self.backend + ) def __len__(self): return len(self.target_qubits) def __mul__(self, x): - return HamiltonianTerm(x * self.matrix, *self.target_qubits) + return HamiltonianTerm( + x * self.matrix, *self.target_qubits, backend=self.backend + ) def __rmul__(self, x): return self.__mul__(x) @@ -142,15 +149,17 @@ class SymbolicTerm(HamiltonianTerm): def __init__( self, coefficient, factors=1, symbol_map={}, backend: Optional[Backend] = None ): - self.coefficient = complex(coefficient) self._gate = None self.hamiltonian = None self.backend = _check_backend(backend) + self.coefficient = complex(coefficient) # List of :class:`qibo.symbols.Symbol` that represent the term factors self.factors = [] # Dictionary that maps target qubit ids to a list of matrices that act on each qubit self.matrix_map = {} + # if backend.name == "qiboml": + # breakpoint() if factors != 1: for factor in factors.as_ordered_factors(): # check if factor has some power ``power`` so that the corresponding @@ -214,32 +223,39 @@ def matrix(self): where ``ntargets`` is the number of qubits included in the factors of this term. """ + from qibo.hamiltonians.models import _multikron + einsum = ( self.backend.np.einsum if self.backend.platform != "tensorflow" else np.einsum ) - def matrices_product(matrices): - """Product of matrices that act on the same tuple of qubits. - - Args: - matrices (list): List of matrices to multiply, as exists in - the values of ``SymbolicTerm.matrix_map``. - """ - nmat = len(matrices) - indices = zip(range(nmat), range(1, nmat + 1)) - lhs = zip(matrices, indices) - lhs = [el for item in lhs for el in item] - return einsum(*lhs, (0, nmat)) - - # if self.backend.platform == "pytorch": - # breakpoint() - matrix = self.coefficient * self.backend.np.ones(1) - for q in self.target_qubits: - prod = matrices_product(self.matrix_map.get(q)) - matrix = self.backend.np.kron(matrix, prod) - return matrix + # find the max number of matrices for each qubit + max_len = max(len(v) for v in self.matrix_map.values()) + nqubits = len(self.matrix_map) + # pad each list with identity to max_len + matrix = [] + for qubit, matrices in self.matrix_map.items(): + matrix.append( + self.backend.np.concatenate( + self.matrix_map[qubit] + + (max_len - len(matrices)) * [self.backend.np.eye(2)], + axis=0, + ) + ) + # separate in `max_len`-column tensors of shape (`nqubits`, 2, 2) + matrix = self.backend.np.transpose( + self.backend.np.reshape( + self.backend.np.concatenate(matrix, axis=0), (nqubits, max_len, 2, 2) + ), + (1, 0, 2, 3), + ) + indices = list(zip(max_len * [0], range(1, max_len + 1), range(2, max_len + 2))) + lhs = zip(matrix, indices) + lhs = [el for item in lhs for el in item] + matrix = einsum(*lhs, (0, 1, max_len + 1)) + return self.coefficient * _multikron(matrix, self.backend) def copy(self): """Creates a shallow copy of the term with the same attributes.""" @@ -253,8 +269,6 @@ def __mul__(self, x): """Multiplication of scalar to the Hamiltonian term.""" new = self.copy() new.coefficient *= x - if self._matrix is not None: - new._matrix = x * self._matrix return new def __call__(self, backend, state, nqubits, density_matrix=False): diff --git a/src/qibo/models/tsp.py b/src/qibo/models/tsp.py index 7fcfc92439..c50a9dd71a 100644 --- a/src/qibo/models/tsp.py +++ b/src/qibo/models/tsp.py @@ -20,8 +20,8 @@ def tsp_phaser(distance_matrix, backend=None): if u != v: form += ( distance_matrix[u, v] - * Z(int(two_to_one[u, i])) - * Z(int(two_to_one[v, (i + 1) % num_cities])) + * Z(int(two_to_one[u, i]), backend=backend) + * Z(int(two_to_one[v, (i + 1) % num_cities]), backend=backend) ) ham = SymbolicHamiltonian(form, backend=backend) return ham @@ -29,8 +29,12 @@ def tsp_phaser(distance_matrix, backend=None): def tsp_mixer(num_cities, backend=None): two_to_one = calculate_two_to_one(num_cities) - splus = lambda u, i: X(int(two_to_one[u, i])) + 1j * Y(int(two_to_one[u, i])) - sminus = lambda u, i: X(int(two_to_one[u, i])) - 1j * Y(int(two_to_one[u, i])) + splus = lambda u, i: X(int(two_to_one[u, i]), backend=backend) + 1j * Y( + int(two_to_one[u, i]), backend=backend + ) + sminus = lambda u, i: X(int(two_to_one[u, i]), backend=backend) - 1j * Y( + int(two_to_one[u, i]), backend=backend + ) form = 0 for i in range(num_cities): for u in range(num_cities): diff --git a/src/qibo/symbols.py b/src/qibo/symbols.py index 928bd7d0ee..ac9b145eee 100644 --- a/src/qibo/symbols.py +++ b/src/qibo/symbols.py @@ -53,10 +53,12 @@ def __init__( backend: Optional[Backend] = None, ): self.target_qubit = q + self.backend = _check_backend(backend) self._gate = None if not ( matrix is None or isinstance(matrix, np.ndarray) + or isinstance(matrix, self.backend.tensor_types) or isinstance( matrix, ( @@ -74,7 +76,6 @@ def __init__( ): raise_error(TypeError, f"Invalid type {type(matrix)} of symbol matrix.") self._matrix = matrix - self.backend = _check_backend(backend) def __getstate__(self): return { diff --git a/tests/#test_hamiltonians_from_symbols.py# b/tests/#test_hamiltonians_from_symbols.py# new file mode 100644 index 0000000000..bf908449d9 --- /dev/null +++ b/tests/#test_hamiltonians_from_symbols.py# @@ -0,0 +1,248 @@ +"""Test dense matrix of Hamiltonians constructed using symbols.""" + +import pickle + +import numpy as np +import pytest +import sympy + +from qibo import hamiltonians, matrices +from qibo.backends import NumpyBackend +from qibo.quantum_info import random_hermitian +from qibo.symbols import I, Symbol, X, Y, Z + + +@pytest.mark.parametrize("symbol", [I, X, Y, Z]) +def test_symbols_pickling(symbol): + symbol = symbol(int(np.random.randint(4))) + dumped_symbol = pickle.dumps(symbol) + new_symbol = pickle.loads(dumped_symbol) + for attr in ("target_qubit", "name", "_gate"): + assert getattr(symbol, attr) == getattr(new_symbol, attr) + np.testing.assert_allclose(symbol.matrix, new_symbol.matrix) + + +@pytest.mark.parametrize("nqubits", [4, 5]) +@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) +@pytest.mark.parametrize("calcterms", [False, True]) +def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): + """Check creating TFIM Hamiltonian using sympy.""" + if hamtype == "symbolic": + h = 0.5 + symham = sum(Z(i, backend=backend) * Z(i + 1, backend=backend) for i in range(nqubits - 1)) + symham += Z(0, backend=backend) * Z(nqubits - 1, backend=backend) + symham += h * sum(X(i, backend=backend) for i in range(nqubits)) + ham = hamiltonians.SymbolicHamiltonian(-symham, backend=backend) + else: + h = 0.5 + z_symbols = sympy.symbols(" ".join(f"Z{i}" for i in range(nqubits))) + x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(nqubits))) + + symham = sum(z_symbols[i] * z_symbols[i + 1] for i in range(nqubits - 1)) + symham += z_symbols[0] * z_symbols[-1] + symham += h * sum(x_symbols) + symmap = {z: (i, backend.matrices.Z) for i, z in enumerate(z_symbols)} + symmap.update({x: (i, backend.matrices.X) for i, x in enumerate(x_symbols)}) + ham = hamiltonians.Hamiltonian.from_symbolic(-symham, symmap, backend=backend) + + if calcterms: + _ = ham.terms + final_matrix = ham.matrix + target_matrix = hamiltonians.TFIM(nqubits, h=h, backend=backend).matrix + backend.assert_allclose(final_matrix, target_matrix) + + +@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) +@pytest.mark.parametrize("calcterms", [False, True]) +def test_from_symbolic_with_power(backend, hamtype, calcterms): + """Check ``from_symbolic`` when the expression contains powers.""" + npbackend = NumpyBackend() + if hamtype == "symbolic": + matrix = random_hermitian(2, backend=npbackend) + symham = ( + Symbol(0, matrix, backend=backend) ** 2 + - Symbol(1, matrix, backend=backend) ** 2 + + 3 * Symbol(1, matrix, backend=backend) + - 2 * Symbol(0, matrix, backend=backend) * Symbol(2, matrix, backend=backend) + + 1 + ) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) + else: + z = sympy.symbols(" ".join(f"Z{i}" for i in range(3))) + symham = z[0] ** 2 - z[1] ** 2 + 3 * z[1] - 2 * z[0] * z[2] + 1 + matrix = random_hermitian(2, backend=npbackend) + symmap = {x: (i, matrix) for i, x in enumerate(z)} + ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) + + if calcterms: + _ = ham.terms + + final_matrix = ham.matrix + matrix2 = matrix.dot(matrix) + eye = np.eye(2, dtype=matrix.dtype) + target_matrix = np.kron(np.kron(matrix2, eye), eye) + target_matrix -= np.kron(np.kron(eye, matrix2), eye) + target_matrix += 3 * np.kron(np.kron(eye, matrix), eye) + target_matrix -= 2 * np.kron(np.kron(matrix, eye), matrix) + target_matrix += np.eye(8, dtype=matrix.dtype) + backend.assert_allclose(final_matrix, target_matrix) + + +@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) +@pytest.mark.parametrize("calcterms", [False, True]) +def test_from_symbolic_with_complex_numbers(backend, hamtype, calcterms): + """Check ``from_symbolic`` when the expression contains imaginary unit.""" + if hamtype == "symbolic": + symham = ( + (1 + 2j) * X(0, backend=backend) * X(1, backend=backend) + + 2 * Y(0, backend=backend) * Y(1, backend=backend) + - 3j * X(0, backend=backend) * Y(1, backend=backend) + + 1j * Y(0, backend=backend) * X(1, backend=backend) + ) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) + else: + x = sympy.symbols(" ".join(f"X{i}" for i in range(2))) + y = sympy.symbols(" ".join(f"Y{i}" for i in range(2))) + symham = ( + (1 + 2j) * x[0] * x[1] + + 2 * y[0] * y[1] + - 3j * x[0] * y[1] + + 1j * y[0] * x[1] + ) + symmap = {s: (i, backend.matrices.X) for i, s in enumerate(x)} + symmap.update({s: (i, backend.matrices.Y) for i, s in enumerate(y)}) + ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) + + if calcterms: + _ = ham.terms + final_matrix = ham.matrix + target_matrix = (1 + 2j) * backend.np.kron(backend.matrices.X, backend.matrices.X) + target_matrix += 2 * backend.np.kron(backend.matrices.Y, backend.matrices.Y) + target_matrix -= 3j * backend.np.kron(backend.matrices.X, backend.matrices.Y) + target_matrix += 1j * backend.np.kron(backend.matrices.Y, backend.matrices.X) + backend.assert_allclose(final_matrix, target_matrix) + + +@pytest.mark.parametrize("calcterms", [False, True]) +def test_from_symbolic_application_hamiltonian(backend, calcterms): + """Check ``from_symbolic`` for a specific four-qubit Hamiltonian.""" + z1, z2, z3, z4 = sympy.symbols("z1 z2 z3 z4") + symmap = {z: (i, matrices.Z) for i, z in enumerate([z1, z2, z3, z4])} + symham = ( + z1 * z2 + - 0.5 * z1 * z3 + + 2 * z2 * z3 + + 0.35 * z2 + + 0.25 * z3 * z4 + + 0.5 * z3 + + z4 + - z1 + ) + # Check that Trotter dense matrix agrees will full Hamiltonian matrix + fham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) + symham = ( + Z(0, backend=backend) * Z(1, backend=backend) + - 0.5 * Z(0, backend=backend) * Z(2, backend=backend) + + 2 * Z(1, backend=backend) * Z(2, backend=backend) + + 0.35 * Z(1, backend=backend) + + 0.25 * Z(2, backend=backend) * Z(3, backend=backend) + + 0.5 * Z(2, backend=backend) + + Z(3, backend=backend) + - Z(0, backend=backend) + ) + sham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) + if calcterms: + _ = sham.terms + backend.assert_allclose(sham.matrix, fham.matrix) + + +@pytest.mark.parametrize("nqubits", [4, 5]) +@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) +@pytest.mark.parametrize("calcterms", [False, True]) +def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): + """Check creating sum(X) Hamiltonian using sympy.""" + if hamtype == "symbolic": + symham = -sum(X(i, backend=backend) for i in range(nqubits)) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) + else: + x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(nqubits))) + symham = -sum(x_symbols) + symmap = {x: (i, backend. + matrices.X) for i, x in enumerate(x_symbols)} + ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) + if calcterms: + _ = ham.terms + final_matrix = ham.matrix + target_matrix = hamiltonians.X(nqubits, backend=backend).matrix + backend.assert_allclose(final_matrix, target_matrix) + + +@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) +@pytest.mark.parametrize("calcterms", [False, True]) +def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype, calcterms): + """Check creating Hamiltonian with three-qubit interaction using sympy.""" + if hamtype == "symbolic": + symham = X(0, backend=backend) * Y(1, backend=backend) * Z(2, backend=backend) + 0.5 * Y(0, backend=backend) * Z(1, backend=backend) * X(3, backend=backend) + Z(0, backend=backend) * X(2, backend=backend) + symham += Y(2, backend=backend) + 1.5 * Z(1, backend=backend) - 2 - 3 * X(1, backend=backend) * Y(3, backend=backend) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) + else: + x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(4))) + y_symbols = sympy.symbols(" ".join(f"Y{i}" for i in range(4))) + z_symbols = sympy.symbols(" ".join(f"Z{i}" for i in range(4))) + symmap = {x: (i, matrices.X) for i, x in enumerate(x_symbols)} + symmap.update({x: (i, matrices.Y) for i, x in enumerate(y_symbols)}) + symmap.update({x: (i, matrices.Z) for i, x in enumerate(z_symbols)}) + + symham = x_symbols[0] * y_symbols[1] * z_symbols[2] + symham += 0.5 * y_symbols[0] * z_symbols[1] * x_symbols[3] + symham += z_symbols[0] * x_symbols[2] + symham += -3 * x_symbols[1] * y_symbols[3] + symham += y_symbols[2] + symham += 1.5 * z_symbols[1] + symham -= 2 + ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) + + if calcterms: + _ = ham.terms + final_matrix = ham.matrix + target_matrix = backend.np.kron( + backend.np.kron(backend.matrices.X, backend.matrices.Y), backend.np.kron(backend.matrices.Z, backend.matrices.I()) + ) + target_matrix += 0.5 * backend.np.kron( + backend.np.kron(backend.matrices.Y, backend.matrices.Z), backend.np.kron(backend.matrices.I(), backend.matrices.X) + ) + target_matrix += backend.np.kron( + backend.np.kron(backend.matrices.Z, backend.matrices.I()), backend.np.kron(backend.matrices.X, backend.matrices.I()) + ) + target_matrix += -3 * backend.np.kron( + backend.np.kron(backend.matrices.I(), backend.matrices.X), backend.np.kron(backend.matrices.I(), backend.matrices.Y) + ) + target_matrix += backend.np.kron( + backend.np.kron(backend.matrices.I(), backend.matrices.I()), backend.np.kron(backend.matrices.Y, backend.matrices.I()) + ) + target_matrix += 1.5 * backend.np.kron( + backend.np.kron(backend.matrices.I(), backend.matrices.Z), backend.np.kron(backend.matrices.I(), backend.matrices.I()) + ) + target_matrix -= 2 * backend.np.eye(2**4, dtype=target_matrix.dtype) + backend.assert_allclose(final_matrix, target_matrix) + + +@pytest.mark.parametrize("calcterms", [False, True]) +def test_hamiltonian_with_identity_symbol(backend, calcterms): + """Check creating Hamiltonian from expression which contains the identity symbol.""" + symham = X(0, backend=backend) * I(1, backend=backend) * Z(2, backend=backend) + 0.5 * Y(0, backend=backend) * Z(1, backend=backend) * I(3, backend=backend) + Z(0, backend=backend) * I(1, backend=backend) * X(2, backend=backend) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) + + if calcterms: + _ = ham.terms + final_matrix = ham.matrix + target_matrix = backend.np.kron( + backend.np.kron(backend.matrices.X, backend.matrices.I()), backend.np.kron(backend.matrices.Z, backend.matrices.I()) + ) + target_matrix += 0.5 * np.kron( + backend.np.kron(backend.matrices.Y, backend.matrices.Z), backend.np.kron(backend.matrices.I(), backend.matrices.I()) + ) + target_matrix += backend.np.kron( + backend.np.kron(backend.matrices.Z, backend.matrices.I()), backend.np.kron(backend.matrices.X, backend.matrices.I()) + ) + backend.assert_allclose(final_matrix, target_matrix) diff --git a/tests/.#test_hamiltonians_from_symbols.py b/tests/.#test_hamiltonians_from_symbols.py new file mode 120000 index 0000000000..977a90663a --- /dev/null +++ b/tests/.#test_hamiltonians_from_symbols.py @@ -0,0 +1 @@ +andrea@MacBook-Pro-3.local.11311 \ No newline at end of file diff --git a/tests/test_hamiltonians_from_symbols.py b/tests/test_hamiltonians_from_symbols.py index 4e45fec904..bbc864fcb5 100644 --- a/tests/test_hamiltonians_from_symbols.py +++ b/tests/test_hamiltonians_from_symbols.py @@ -29,9 +29,12 @@ def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): """Check creating TFIM Hamiltonian using sympy.""" if hamtype == "symbolic": h = 0.5 - symham = sum(Z(i) * Z(i + 1) for i in range(nqubits - 1)) - symham += Z(0) * Z(nqubits - 1) - symham += h * sum(X(i) for i in range(nqubits)) + symham = sum( + Z(i, backend=backend) * Z(i + 1, backend=backend) + for i in range(nqubits - 1) + ) + symham += Z(0, backend=backend) * Z(nqubits - 1, backend=backend) + symham += h * sum(X(i, backend=backend) for i in range(nqubits)) ham = hamiltonians.SymbolicHamiltonian(-symham, backend=backend) else: h = 0.5 @@ -41,8 +44,8 @@ def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): symham = sum(z_symbols[i] * z_symbols[i + 1] for i in range(nqubits - 1)) symham += z_symbols[0] * z_symbols[-1] symham += h * sum(x_symbols) - symmap = {z: (i, matrices.Z) for i, z in enumerate(z_symbols)} - symmap.update({x: (i, matrices.X) for i, x in enumerate(x_symbols)}) + symmap = {z: (i, backend.matrices.Z) for i, z in enumerate(z_symbols)} + symmap.update({x: (i, backend.matrices.X) for i, x in enumerate(x_symbols)}) ham = hamiltonians.Hamiltonian.from_symbolic(-symham, symmap, backend=backend) if calcterms: @@ -60,10 +63,12 @@ def test_from_symbolic_with_power(backend, hamtype, calcterms): if hamtype == "symbolic": matrix = random_hermitian(2, backend=npbackend) symham = ( - Symbol(0, matrix) ** 2 - - Symbol(1, matrix) ** 2 - + 3 * Symbol(1, matrix) - - 2 * Symbol(0, matrix) * Symbol(2, matrix) + Symbol(0, matrix, backend=backend) ** 2 + - Symbol(1, matrix, backend=backend) ** 2 + + 3 * Symbol(1, matrix, backend=backend) + - 2 + * Symbol(0, matrix, backend=backend) + * Symbol(2, matrix, backend=backend) + 1 ) ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) @@ -94,10 +99,10 @@ def test_from_symbolic_with_complex_numbers(backend, hamtype, calcterms): """Check ``from_symbolic`` when the expression contains imaginary unit.""" if hamtype == "symbolic": symham = ( - (1 + 2j) * X(0) * X(1) - + 2 * Y(0) * Y(1) - - 3j * X(0) * Y(1) - + 1j * Y(0) * X(1) + (1 + 2j) * X(0, backend=backend) * X(1, backend=backend) + + 2 * Y(0, backend=backend) * Y(1, backend=backend) + - 3j * X(0, backend=backend) * Y(1, backend=backend) + + 1j * Y(0, backend=backend) * X(1, backend=backend) ) ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) else: @@ -109,17 +114,17 @@ def test_from_symbolic_with_complex_numbers(backend, hamtype, calcterms): - 3j * x[0] * y[1] + 1j * y[0] * x[1] ) - symmap = {s: (i, matrices.X) for i, s in enumerate(x)} - symmap.update({s: (i, matrices.Y) for i, s in enumerate(y)}) + symmap = {s: (i, backend.matrices.X) for i, s in enumerate(x)} + symmap.update({s: (i, backend.matrices.Y) for i, s in enumerate(y)}) ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) if calcterms: _ = ham.terms final_matrix = ham.matrix - target_matrix = (1 + 2j) * np.kron(matrices.X, matrices.X) - target_matrix += 2 * np.kron(matrices.Y, matrices.Y) - target_matrix -= 3j * np.kron(matrices.X, matrices.Y) - target_matrix += 1j * np.kron(matrices.Y, matrices.X) + target_matrix = (1 + 2j) * backend.np.kron(backend.matrices.X, backend.matrices.X) + target_matrix += 2 * backend.np.kron(backend.matrices.Y, backend.matrices.Y) + target_matrix -= 3j * backend.np.kron(backend.matrices.X, backend.matrices.Y) + target_matrix += 1j * backend.np.kron(backend.matrices.Y, backend.matrices.X) backend.assert_allclose(final_matrix, target_matrix) @@ -141,14 +146,14 @@ def test_from_symbolic_application_hamiltonian(backend, calcterms): # Check that Trotter dense matrix agrees will full Hamiltonian matrix fham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) symham = ( - Z(0) * Z(1) - - 0.5 * Z(0) * Z(2) - + 2 * Z(1) * Z(2) - + 0.35 * Z(1) - + 0.25 * Z(2) * Z(3) - + 0.5 * Z(2) - + Z(3) - - Z(0) + Z(0, backend=backend) * Z(1, backend=backend) + - 0.5 * Z(0, backend=backend) * Z(2, backend=backend) + + 2 * Z(1, backend=backend) * Z(2, backend=backend) + + 0.35 * Z(1, backend=backend) + + 0.25 * Z(2, backend=backend) * Z(3, backend=backend) + + 0.5 * Z(2, backend=backend) + + Z(3, backend=backend) + - Z(0, backend=backend) ) sham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) if calcterms: @@ -162,12 +167,12 @@ def test_from_symbolic_application_hamiltonian(backend, calcterms): def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): """Check creating sum(X) Hamiltonian using sympy.""" if hamtype == "symbolic": - symham = -sum(X(i) for i in range(nqubits)) + symham = -sum(X(i, backend=backend) for i in range(nqubits)) ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) else: x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(nqubits))) symham = -sum(x_symbols) - symmap = {x: (i, matrices.X) for i, x in enumerate(x_symbols)} + symmap = {x: (i, backend.matrices.X) for i, x in enumerate(x_symbols)} ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) if calcterms: _ = ham.terms @@ -181,8 +186,20 @@ def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype, calcterms): """Check creating Hamiltonian with three-qubit interaction using sympy.""" if hamtype == "symbolic": - symham = X(0) * Y(1) * Z(2) + 0.5 * Y(0) * Z(1) * X(3) + Z(0) * X(2) - symham += Y(2) + 1.5 * Z(1) - 2 - 3 * X(1) * Y(3) + symham = ( + X(0, backend=backend) * Y(1, backend=backend) * Z(2, backend=backend) + + 0.5 + * Y(0, backend=backend) + * Z(1, backend=backend) + * X(3, backend=backend) + + Z(0, backend=backend) * X(2, backend=backend) + ) + symham += ( + Y(2, backend=backend) + + 1.5 * Z(1, backend=backend) + - 2 + - 3 * X(1, backend=backend) * Y(3, backend=backend) + ) ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) else: x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(4))) @@ -204,44 +221,57 @@ def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype, calcterms): if calcterms: _ = ham.terms final_matrix = ham.matrix - target_matrix = np.kron( - np.kron(matrices.X, matrices.Y), np.kron(matrices.Z, matrices.I) + target_matrix = backend.np.kron( + backend.np.kron(backend.matrices.X, backend.matrices.Y), + backend.np.kron(backend.matrices.Z, backend.matrices.I()), ) - target_matrix += 0.5 * np.kron( - np.kron(matrices.Y, matrices.Z), np.kron(matrices.I, matrices.X) + target_matrix += 0.5 * backend.np.kron( + backend.np.kron(backend.matrices.Y, backend.matrices.Z), + backend.np.kron(backend.matrices.I(), backend.matrices.X), ) - target_matrix += np.kron( - np.kron(matrices.Z, matrices.I), np.kron(matrices.X, matrices.I) + target_matrix += backend.np.kron( + backend.np.kron(backend.matrices.Z, backend.matrices.I()), + backend.np.kron(backend.matrices.X, backend.matrices.I()), ) - target_matrix += -3 * np.kron( - np.kron(matrices.I, matrices.X), np.kron(matrices.I, matrices.Y) + target_matrix += -3 * backend.np.kron( + backend.np.kron(backend.matrices.I(), backend.matrices.X), + backend.np.kron(backend.matrices.I(), backend.matrices.Y), ) - target_matrix += np.kron( - np.kron(matrices.I, matrices.I), np.kron(matrices.Y, matrices.I) + target_matrix += backend.np.kron( + backend.np.kron(backend.matrices.I(), backend.matrices.I()), + backend.np.kron(backend.matrices.Y, backend.matrices.I()), ) - target_matrix += 1.5 * np.kron( - np.kron(matrices.I, matrices.Z), np.kron(matrices.I, matrices.I) + target_matrix += 1.5 * backend.np.kron( + backend.np.kron(backend.matrices.I(), backend.matrices.Z), + backend.np.kron(backend.matrices.I(), backend.matrices.I()), ) - target_matrix -= 2 * np.eye(2**4, dtype=target_matrix.dtype) + target_matrix -= 2 * backend.np.eye(2**4, dtype=target_matrix.dtype) backend.assert_allclose(final_matrix, target_matrix) @pytest.mark.parametrize("calcterms", [False, True]) def test_hamiltonian_with_identity_symbol(backend, calcterms): """Check creating Hamiltonian from expression which contains the identity symbol.""" - symham = X(0) * I(1) * Z(2) + 0.5 * Y(0) * Z(1) * I(3) + Z(0) * I(1) * X(2) + symham = ( + X(0, backend=backend) * I(1, backend=backend) * Z(2, backend=backend) + + 0.5 * Y(0, backend=backend) * Z(1, backend=backend) * I(3, backend=backend) + + Z(0, backend=backend) * I(1, backend=backend) * X(2, backend=backend) + ) ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) if calcterms: _ = ham.terms final_matrix = ham.matrix - target_matrix = np.kron( - np.kron(matrices.X, matrices.I), np.kron(matrices.Z, matrices.I) + target_matrix = backend.np.kron( + backend.np.kron(backend.matrices.X, backend.matrices.I()), + backend.np.kron(backend.matrices.Z, backend.matrices.I()), ) target_matrix += 0.5 * np.kron( - np.kron(matrices.Y, matrices.Z), np.kron(matrices.I, matrices.I) + backend.np.kron(backend.matrices.Y, backend.matrices.Z), + backend.np.kron(backend.matrices.I(), backend.matrices.I()), ) - target_matrix += np.kron( - np.kron(matrices.Z, matrices.I), np.kron(matrices.X, matrices.I) + target_matrix += backend.np.kron( + backend.np.kron(backend.matrices.Z, backend.matrices.I()), + backend.np.kron(backend.matrices.X, backend.matrices.I()), ) backend.assert_allclose(final_matrix, target_matrix) diff --git a/tests/test_hamiltonians_symbolic.py b/tests/test_hamiltonians_symbolic.py index 00da0f0b35..f9ecc788a0 100644 --- a/tests/test_hamiltonians_symbolic.py +++ b/tests/test_hamiltonians_symbolic.py @@ -10,13 +10,15 @@ from qibo.symbols import I, Symbol, X, Y, Z -def symbolic_tfim(nqubits, h=1.0): +def symbolic_tfim(nqubits, backend, h=1.0): """Constructs symbolic Hamiltonian for TFIM.""" from qibo.symbols import X, Z - sham = -sum(Z(i) * Z(i + 1) for i in range(nqubits - 1)) - sham -= Z(0) * Z(nqubits - 1) - sham -= h * sum(X(i) for i in range(nqubits)) + sham = -sum( + Z(i, backend=backend) * Z(i + 1, backend=backend) for i in range(nqubits - 1) + ) + sham -= Z(0, backend=backend) * Z(nqubits - 1, backend=backend) + sham -= h * sum(X(i, backend=backend) for i in range(nqubits)) return sham @@ -43,7 +45,9 @@ def test_symbolic_hamiltonian_errors(backend): @pytest.mark.parametrize("nqubits", [3, 4]) @pytest.mark.parametrize("calcterms", [False, True]) def test_symbolictfim_hamiltonian_to_dense(backend, nqubits, calcterms): - final_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1), backend=backend) + final_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1), backend=backend + ) target_ham = TFIM(nqubits, h=1, backend=backend) if calcterms: _ = final_ham.terms @@ -53,10 +57,20 @@ def test_symbolictfim_hamiltonian_to_dense(backend, nqubits, calcterms): @pytest.mark.parametrize("nqubits", [3, 4]) @pytest.mark.parametrize("calcterms", [False, True]) def test_symbolicxxz_hamiltonian_to_dense(backend, nqubits, calcterms): - sham = sum(X(i) * X(i + 1) for i in range(nqubits - 1)) - sham += sum(Y(i) * Y(i + 1) for i in range(nqubits - 1)) - sham += 0.5 * sum(Z(i) * Z(i + 1) for i in range(nqubits - 1)) - sham += X(0) * X(nqubits - 1) + Y(0) * Y(nqubits - 1) + 0.5 * Z(0) * Z(nqubits - 1) + sham = sum( + X(i, backend=backend) * X(i + 1, backend=backend) for i in range(nqubits - 1) + ) + sham += sum( + Y(i, backend=backend) * Y(i + 1, backend=backend) for i in range(nqubits - 1) + ) + sham += 0.5 * sum( + Z(i, backend=backend) * Z(i + 1, backend=backend) for i in range(nqubits - 1) + ) + sham += ( + X(0, backend=backend) * X(nqubits - 1, backend=backend) + + Y(0, backend=backend) * Y(nqubits - 1, backend=backend) + + 0.5 * Z(0, backend=backend) * Z(nqubits - 1, backend=backend) + ) final_ham = SymbolicHamiltonian(sham, backend=backend) target_ham = XXZ(nqubits, backend=backend) if calcterms: @@ -69,7 +83,9 @@ def test_symbolicxxz_hamiltonian_to_dense(backend, nqubits, calcterms): @pytest.mark.parametrize("calcdense", [False, True]) def test_symbolic_hamiltonian_scalar_mul(backend, nqubits, calcterms, calcdense): """Test multiplication of Trotter Hamiltonian with scalar.""" - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + local_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) target_ham = 2 * TFIM(nqubits, h=1.0, backend=backend) if calcterms: _ = local_ham.terms @@ -78,7 +94,9 @@ def test_symbolic_hamiltonian_scalar_mul(backend, nqubits, calcterms, calcdense) local_dense = (2 * local_ham).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + local_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) if calcterms: _ = local_ham.terms if calcdense: @@ -92,7 +110,9 @@ def test_symbolic_hamiltonian_scalar_mul(backend, nqubits, calcterms, calcdense) @pytest.mark.parametrize("calcdense", [False, True]) def test_symbolic_hamiltonian_scalar_add(backend, nqubits, calcterms, calcdense): """Test addition of Trotter Hamiltonian with scalar.""" - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + local_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) target_ham = 2 + TFIM(nqubits, h=1.0, backend=backend) if calcterms: _ = local_ham.terms @@ -101,7 +121,9 @@ def test_symbolic_hamiltonian_scalar_add(backend, nqubits, calcterms, calcdense) local_dense = (2 + local_ham).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + local_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) if calcterms: _ = local_ham.terms if calcdense: @@ -115,7 +137,9 @@ def test_symbolic_hamiltonian_scalar_add(backend, nqubits, calcterms, calcdense) @pytest.mark.parametrize("calcdense", [False, True]) def test_symbolic_hamiltonian_scalar_sub(backend, nqubits, calcterms, calcdense): """Test subtraction of Trotter Hamiltonian with scalar.""" - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + local_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) target_ham = 2 - TFIM(nqubits, h=1.0, backend=backend) if calcterms: _ = local_ham.terms @@ -125,7 +149,9 @@ def test_symbolic_hamiltonian_scalar_sub(backend, nqubits, calcterms, calcdense) backend.assert_allclose(local_dense.matrix, target_ham.matrix) target_ham = TFIM(nqubits, h=1.0, backend=backend) - 2 - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + local_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) if calcterms: _ = local_ham.terms if calcdense: @@ -141,8 +167,12 @@ def test_symbolic_hamiltonian_operator_add_and_sub( backend, nqubits, calcterms, calcdense ): """Test addition and subtraction between Trotter Hamiltonians.""" - local_ham1 = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) - local_ham2 = SymbolicHamiltonian(symbolic_tfim(nqubits, h=0.5), backend=backend) + local_ham1 = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) + local_ham2 = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=0.5), backend=backend + ) if calcterms: _ = local_ham1.terms _ = local_ham2.terms @@ -156,8 +186,12 @@ def test_symbolic_hamiltonian_operator_add_and_sub( dense = local_ham.dense backend.assert_allclose(dense.matrix, target_ham.matrix) - local_ham1 = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) - local_ham2 = SymbolicHamiltonian(symbolic_tfim(nqubits, h=0.5), backend=backend) + local_ham1 = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) + local_ham2 = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=0.5), backend=backend + ) if calcterms: _ = local_ham1.terms _ = local_ham2.terms @@ -174,13 +208,22 @@ def test_symbolic_hamiltonian_operator_add_and_sub( # Test multiplication and sum target = XXZ(nqubits, backend=backend) term_1 = SymbolicHamiltonian( - X(0) * X(1) + X(1) * X(2) + X(0) * X(2), backend=backend + X(0, backend=backend) * X(1, backend=backend) + + X(1, backend=backend) * X(2, backend=backend) + + X(0, backend=backend) * X(2, backend=backend), + backend=backend, ) term_2 = SymbolicHamiltonian( - Y(0) * Y(1) + Y(1) * Y(2) + Y(0) * Y(2), backend=backend + Y(0, backend=backend) * Y(1, backend=backend) + + Y(1, backend=backend) * Y(2, backend=backend) + + Y(0, backend=backend) * Y(2, backend=backend), + backend=backend, ) term_3 = SymbolicHamiltonian( - Z(0) * Z(1) + Z(1) * Z(2) + Z(0) * Z(2), backend=backend + Z(0, backend=backend) * Z(1, backend=backend) + + Z(1, backend=backend) * Z(2, backend=backend) + + Z(0, backend=backend) * Z(2, backend=backend), + backend=backend, ) hamiltonian = term_1 + term_2 + 0.5 * term_3 matrix = hamiltonian.dense.matrix @@ -192,8 +235,12 @@ def test_symbolic_hamiltonian_operator_add_and_sub( @pytest.mark.parametrize("calcterms", [False, True]) @pytest.mark.parametrize("calcdense", [False, True]) def test_symbolic_hamiltonian_hamiltonianmatmul(backend, nqubits, calcterms, calcdense): - local_ham1 = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) - local_ham2 = SymbolicHamiltonian(symbolic_tfim(nqubits, h=0.5), backend=backend) + local_ham1 = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) + local_ham2 = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=0.5), backend=backend + ) dense_ham1 = TFIM(nqubits, h=1.0, backend=backend) dense_ham2 = TFIM(nqubits, h=0.5, backend=backend) if calcterms: @@ -216,7 +263,9 @@ def test_symbolic_hamiltonian_matmul(backend, nqubits, density_matrix, calcterms if density_matrix else random_statevector(2**nqubits, backend=backend) ) - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + local_ham = SymbolicHamiltonian( + symbolic_tfim(nqubits, backend, h=1.0), backend=backend + ) dense_ham = TFIM(nqubits, h=1.0, backend=backend) if calcterms: _ = local_ham.terms @@ -231,7 +280,9 @@ def test_symbolic_hamiltonian_matmul(backend, nqubits, density_matrix, calcterms def test_symbolic_hamiltonian_state_expectation( backend, nqubits, normalize, calcterms, calcdense ): - local_ham = SymbolicHamiltonian(symbolic_tfim(nqubits, h=1.0), backend=backend) + 2 + local_ham = ( + SymbolicHamiltonian(symbolic_tfim(nqubits, backend, h=1.0), backend=backend) + 2 + ) if calcterms: _ = local_ham.terms if calcdense: @@ -255,7 +306,7 @@ def test_symbolic_hamiltonian_state_expectation( def test_symbolic_hamiltonian_state_expectation_different_nqubits( backend, give_nqubits, calcterms, calcdense ): - expr = symbolic_tfim(3, h=1.0) + expr = symbolic_tfim(3, backend, h=1.0) if give_nqubits: local_ham = SymbolicHamiltonian(expr, nqubits=5, backend=backend) else: @@ -317,7 +368,9 @@ def test_symbolic_hamiltonian_abstract_symbol_ev(backend, density_matrix, calcte from qibo.symbols import Symbol, X matrix = np.random.random((2, 2)) - form = X(0) * Symbol(1, matrix) + Symbol(0, matrix) * X(1) + form = X(0, backend=backend) * Symbol(1, matrix, backend=backend) + Symbol( + 0, matrix, backend=backend + ) * X(1, backend=backend) local_ham = SymbolicHamiltonian(form, backend=backend) if calcterms: _ = local_ham.terms @@ -334,8 +387,8 @@ def test_symbolic_hamiltonian_abstract_symbol_ev(backend, density_matrix, calcte def test_trotter_hamiltonian_operation_errors(backend): """Test errors in ``SymbolicHamiltonian`` addition and subtraction.""" - h1 = SymbolicHamiltonian(symbolic_tfim(3, h=1.0), backend=backend) - h2 = SymbolicHamiltonian(symbolic_tfim(4, h=1.0), backend=backend) + h1 = SymbolicHamiltonian(symbolic_tfim(3, backend, h=1.0), backend=backend) + h2 = SymbolicHamiltonian(symbolic_tfim(4, backend, h=1.0), backend=backend) with pytest.raises(RuntimeError): h = h1 + h2 with pytest.raises(RuntimeError): @@ -355,8 +408,6 @@ def test_trotter_hamiltonian_operation_errors(backend): with pytest.raises(NotImplementedError): h = h1 @ np.ones((2, 2, 2, 2)) h2 = XXZ(3, dense=False, backend=backend) - # with pytest.raises(NotImplementedError): - # h = h1 @ h2 def test_symbolic_hamiltonian_with_constant(backend): diff --git a/tests/test_models_error_mitigation.py b/tests/test_models_error_mitigation.py index 62bd804f5e..a73f70cf7a 100644 --- a/tests/test_models_error_mitigation.py +++ b/tests/test_models_error_mitigation.py @@ -342,7 +342,7 @@ def test_ics(backend, nqubits, noise, full_output, readout): # Define the circuit c = get_circuit(nqubits, nqubits - 1) # Define the observable - obs = np.prod([Z(i) for i in range(nqubits - 1)]) + obs = np.prod([Z(i, backend=backend) for i in range(nqubits - 1)]) obs_exact = SymbolicHamiltonian(obs, nqubits=nqubits, backend=backend) obs = SymbolicHamiltonian(obs, backend=backend) # Noise-free expected value diff --git a/tests/test_states.py b/tests/test_states.py index 3c885c13fd..0bf559648a 100644 --- a/tests/test_states.py +++ b/tests/test_states.py @@ -91,8 +91,12 @@ def test_state_probabilities(backend, density_matrix): def test_expectation_from_samples(backend): # fix seed to use the same samples in every execution np.random.seed(123) - obs0 = 2 * Z(0) * Z(1) + Z(0) * Z(2) - obs1 = 2 * Z(0) * Z(1) + Z(0) * Z(2) * I(3) + obs0 = 2 * Z(0, backend=backend) * Z(1, backend=backend) + Z( + 0, backend=backend + ) * Z(2, backend=backend) + obs1 = 2 * Z(0, backend=backend) * Z(1, backend=backend) + Z( + 0, backend=backend + ) * Z(2, backend=backend) * I(3, backend=backend) h_sym = hamiltonians.SymbolicHamiltonian(obs0, backend=backend) h_dense = hamiltonians.Hamiltonian(3, h_sym.matrix, backend=backend) h1 = hamiltonians.SymbolicHamiltonian(obs1, backend=backend) From 80bf3f7b4376fda42b91318e74b98d3ccb235ebe Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Mon, 13 Jan 2025 16:00:00 +0100 Subject: [PATCH 18/31] feat: symbolic term forces the backend of its symbols --- src/qibo/hamiltonians/terms.py | 4 + tests/#test_hamiltonians_from_symbols.py# | 248 ---------------------- tests/.#test_hamiltonians_from_symbols.py | 1 - 3 files changed, 4 insertions(+), 249 deletions(-) delete mode 100644 tests/#test_hamiltonians_from_symbols.py# delete mode 120000 tests/.#test_hamiltonians_from_symbols.py diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index da58e4730c..b5ef69e477 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -190,6 +190,10 @@ def __init__( factor = Symbol(q, matrix, name=factor.name, backend=self.backend) if isinstance(factor, sympy.Symbol): + # forces the backend of the factor + # this way it is not necessary to explicitely define the + # backend of a symbol, i.e. Z(q, backend=backend) + factor.backend = self.backend if isinstance(factor.matrix, self.backend.tensor_types): self.factors.extend(pow * [factor]) q = factor.target_qubit diff --git a/tests/#test_hamiltonians_from_symbols.py# b/tests/#test_hamiltonians_from_symbols.py# deleted file mode 100644 index bf908449d9..0000000000 --- a/tests/#test_hamiltonians_from_symbols.py# +++ /dev/null @@ -1,248 +0,0 @@ -"""Test dense matrix of Hamiltonians constructed using symbols.""" - -import pickle - -import numpy as np -import pytest -import sympy - -from qibo import hamiltonians, matrices -from qibo.backends import NumpyBackend -from qibo.quantum_info import random_hermitian -from qibo.symbols import I, Symbol, X, Y, Z - - -@pytest.mark.parametrize("symbol", [I, X, Y, Z]) -def test_symbols_pickling(symbol): - symbol = symbol(int(np.random.randint(4))) - dumped_symbol = pickle.dumps(symbol) - new_symbol = pickle.loads(dumped_symbol) - for attr in ("target_qubit", "name", "_gate"): - assert getattr(symbol, attr) == getattr(new_symbol, attr) - np.testing.assert_allclose(symbol.matrix, new_symbol.matrix) - - -@pytest.mark.parametrize("nqubits", [4, 5]) -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): - """Check creating TFIM Hamiltonian using sympy.""" - if hamtype == "symbolic": - h = 0.5 - symham = sum(Z(i, backend=backend) * Z(i + 1, backend=backend) for i in range(nqubits - 1)) - symham += Z(0, backend=backend) * Z(nqubits - 1, backend=backend) - symham += h * sum(X(i, backend=backend) for i in range(nqubits)) - ham = hamiltonians.SymbolicHamiltonian(-symham, backend=backend) - else: - h = 0.5 - z_symbols = sympy.symbols(" ".join(f"Z{i}" for i in range(nqubits))) - x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(nqubits))) - - symham = sum(z_symbols[i] * z_symbols[i + 1] for i in range(nqubits - 1)) - symham += z_symbols[0] * z_symbols[-1] - symham += h * sum(x_symbols) - symmap = {z: (i, backend.matrices.Z) for i, z in enumerate(z_symbols)} - symmap.update({x: (i, backend.matrices.X) for i, x in enumerate(x_symbols)}) - ham = hamiltonians.Hamiltonian.from_symbolic(-symham, symmap, backend=backend) - - if calcterms: - _ = ham.terms - final_matrix = ham.matrix - target_matrix = hamiltonians.TFIM(nqubits, h=h, backend=backend).matrix - backend.assert_allclose(final_matrix, target_matrix) - - -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_from_symbolic_with_power(backend, hamtype, calcterms): - """Check ``from_symbolic`` when the expression contains powers.""" - npbackend = NumpyBackend() - if hamtype == "symbolic": - matrix = random_hermitian(2, backend=npbackend) - symham = ( - Symbol(0, matrix, backend=backend) ** 2 - - Symbol(1, matrix, backend=backend) ** 2 - + 3 * Symbol(1, matrix, backend=backend) - - 2 * Symbol(0, matrix, backend=backend) * Symbol(2, matrix, backend=backend) - + 1 - ) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - z = sympy.symbols(" ".join(f"Z{i}" for i in range(3))) - symham = z[0] ** 2 - z[1] ** 2 + 3 * z[1] - 2 * z[0] * z[2] + 1 - matrix = random_hermitian(2, backend=npbackend) - symmap = {x: (i, matrix) for i, x in enumerate(z)} - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - - if calcterms: - _ = ham.terms - - final_matrix = ham.matrix - matrix2 = matrix.dot(matrix) - eye = np.eye(2, dtype=matrix.dtype) - target_matrix = np.kron(np.kron(matrix2, eye), eye) - target_matrix -= np.kron(np.kron(eye, matrix2), eye) - target_matrix += 3 * np.kron(np.kron(eye, matrix), eye) - target_matrix -= 2 * np.kron(np.kron(matrix, eye), matrix) - target_matrix += np.eye(8, dtype=matrix.dtype) - backend.assert_allclose(final_matrix, target_matrix) - - -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_from_symbolic_with_complex_numbers(backend, hamtype, calcterms): - """Check ``from_symbolic`` when the expression contains imaginary unit.""" - if hamtype == "symbolic": - symham = ( - (1 + 2j) * X(0, backend=backend) * X(1, backend=backend) - + 2 * Y(0, backend=backend) * Y(1, backend=backend) - - 3j * X(0, backend=backend) * Y(1, backend=backend) - + 1j * Y(0, backend=backend) * X(1, backend=backend) - ) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - x = sympy.symbols(" ".join(f"X{i}" for i in range(2))) - y = sympy.symbols(" ".join(f"Y{i}" for i in range(2))) - symham = ( - (1 + 2j) * x[0] * x[1] - + 2 * y[0] * y[1] - - 3j * x[0] * y[1] - + 1j * y[0] * x[1] - ) - symmap = {s: (i, backend.matrices.X) for i, s in enumerate(x)} - symmap.update({s: (i, backend.matrices.Y) for i, s in enumerate(y)}) - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - - if calcterms: - _ = ham.terms - final_matrix = ham.matrix - target_matrix = (1 + 2j) * backend.np.kron(backend.matrices.X, backend.matrices.X) - target_matrix += 2 * backend.np.kron(backend.matrices.Y, backend.matrices.Y) - target_matrix -= 3j * backend.np.kron(backend.matrices.X, backend.matrices.Y) - target_matrix += 1j * backend.np.kron(backend.matrices.Y, backend.matrices.X) - backend.assert_allclose(final_matrix, target_matrix) - - -@pytest.mark.parametrize("calcterms", [False, True]) -def test_from_symbolic_application_hamiltonian(backend, calcterms): - """Check ``from_symbolic`` for a specific four-qubit Hamiltonian.""" - z1, z2, z3, z4 = sympy.symbols("z1 z2 z3 z4") - symmap = {z: (i, matrices.Z) for i, z in enumerate([z1, z2, z3, z4])} - symham = ( - z1 * z2 - - 0.5 * z1 * z3 - + 2 * z2 * z3 - + 0.35 * z2 - + 0.25 * z3 * z4 - + 0.5 * z3 - + z4 - - z1 - ) - # Check that Trotter dense matrix agrees will full Hamiltonian matrix - fham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - symham = ( - Z(0, backend=backend) * Z(1, backend=backend) - - 0.5 * Z(0, backend=backend) * Z(2, backend=backend) - + 2 * Z(1, backend=backend) * Z(2, backend=backend) - + 0.35 * Z(1, backend=backend) - + 0.25 * Z(2, backend=backend) * Z(3, backend=backend) - + 0.5 * Z(2, backend=backend) - + Z(3, backend=backend) - - Z(0, backend=backend) - ) - sham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - if calcterms: - _ = sham.terms - backend.assert_allclose(sham.matrix, fham.matrix) - - -@pytest.mark.parametrize("nqubits", [4, 5]) -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): - """Check creating sum(X) Hamiltonian using sympy.""" - if hamtype == "symbolic": - symham = -sum(X(i, backend=backend) for i in range(nqubits)) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(nqubits))) - symham = -sum(x_symbols) - symmap = {x: (i, backend. - matrices.X) for i, x in enumerate(x_symbols)} - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - if calcterms: - _ = ham.terms - final_matrix = ham.matrix - target_matrix = hamiltonians.X(nqubits, backend=backend).matrix - backend.assert_allclose(final_matrix, target_matrix) - - -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype, calcterms): - """Check creating Hamiltonian with three-qubit interaction using sympy.""" - if hamtype == "symbolic": - symham = X(0, backend=backend) * Y(1, backend=backend) * Z(2, backend=backend) + 0.5 * Y(0, backend=backend) * Z(1, backend=backend) * X(3, backend=backend) + Z(0, backend=backend) * X(2, backend=backend) - symham += Y(2, backend=backend) + 1.5 * Z(1, backend=backend) - 2 - 3 * X(1, backend=backend) * Y(3, backend=backend) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(4))) - y_symbols = sympy.symbols(" ".join(f"Y{i}" for i in range(4))) - z_symbols = sympy.symbols(" ".join(f"Z{i}" for i in range(4))) - symmap = {x: (i, matrices.X) for i, x in enumerate(x_symbols)} - symmap.update({x: (i, matrices.Y) for i, x in enumerate(y_symbols)}) - symmap.update({x: (i, matrices.Z) for i, x in enumerate(z_symbols)}) - - symham = x_symbols[0] * y_symbols[1] * z_symbols[2] - symham += 0.5 * y_symbols[0] * z_symbols[1] * x_symbols[3] - symham += z_symbols[0] * x_symbols[2] - symham += -3 * x_symbols[1] * y_symbols[3] - symham += y_symbols[2] - symham += 1.5 * z_symbols[1] - symham -= 2 - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - - if calcterms: - _ = ham.terms - final_matrix = ham.matrix - target_matrix = backend.np.kron( - backend.np.kron(backend.matrices.X, backend.matrices.Y), backend.np.kron(backend.matrices.Z, backend.matrices.I()) - ) - target_matrix += 0.5 * backend.np.kron( - backend.np.kron(backend.matrices.Y, backend.matrices.Z), backend.np.kron(backend.matrices.I(), backend.matrices.X) - ) - target_matrix += backend.np.kron( - backend.np.kron(backend.matrices.Z, backend.matrices.I()), backend.np.kron(backend.matrices.X, backend.matrices.I()) - ) - target_matrix += -3 * backend.np.kron( - backend.np.kron(backend.matrices.I(), backend.matrices.X), backend.np.kron(backend.matrices.I(), backend.matrices.Y) - ) - target_matrix += backend.np.kron( - backend.np.kron(backend.matrices.I(), backend.matrices.I()), backend.np.kron(backend.matrices.Y, backend.matrices.I()) - ) - target_matrix += 1.5 * backend.np.kron( - backend.np.kron(backend.matrices.I(), backend.matrices.Z), backend.np.kron(backend.matrices.I(), backend.matrices.I()) - ) - target_matrix -= 2 * backend.np.eye(2**4, dtype=target_matrix.dtype) - backend.assert_allclose(final_matrix, target_matrix) - - -@pytest.mark.parametrize("calcterms", [False, True]) -def test_hamiltonian_with_identity_symbol(backend, calcterms): - """Check creating Hamiltonian from expression which contains the identity symbol.""" - symham = X(0, backend=backend) * I(1, backend=backend) * Z(2, backend=backend) + 0.5 * Y(0, backend=backend) * Z(1, backend=backend) * I(3, backend=backend) + Z(0, backend=backend) * I(1, backend=backend) * X(2, backend=backend) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - - if calcterms: - _ = ham.terms - final_matrix = ham.matrix - target_matrix = backend.np.kron( - backend.np.kron(backend.matrices.X, backend.matrices.I()), backend.np.kron(backend.matrices.Z, backend.matrices.I()) - ) - target_matrix += 0.5 * np.kron( - backend.np.kron(backend.matrices.Y, backend.matrices.Z), backend.np.kron(backend.matrices.I(), backend.matrices.I()) - ) - target_matrix += backend.np.kron( - backend.np.kron(backend.matrices.Z, backend.matrices.I()), backend.np.kron(backend.matrices.X, backend.matrices.I()) - ) - backend.assert_allclose(final_matrix, target_matrix) diff --git a/tests/.#test_hamiltonians_from_symbols.py b/tests/.#test_hamiltonians_from_symbols.py deleted file mode 120000 index 977a90663a..0000000000 --- a/tests/.#test_hamiltonians_from_symbols.py +++ /dev/null @@ -1 +0,0 @@ -andrea@MacBook-Pro-3.local.11311 \ No newline at end of file From 1b34d33ceac58eb3c5deb02f6bbcfe92d3f59132 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Mon, 13 Jan 2025 17:20:51 +0100 Subject: [PATCH 19/31] fix: removed calcterms/calcdense tests --- src/qibo/hamiltonians/__init__.py | 6 +- src/qibo/hamiltonians/hamiltonians.py | 100 ++--------- src/qibo/hamiltonians/terms.py | 17 +- tests/test_hamiltonians_from_symbols.py | 211 ++++++------------------ tests/test_hamiltonians_models.py | 8 +- tests/test_hamiltonians_symbolic.py | 113 ++----------- tests/test_hamiltonians_terms.py | 19 +-- tests/test_hamiltonians_trotter.py | 8 - 8 files changed, 84 insertions(+), 398 deletions(-) diff --git a/src/qibo/hamiltonians/__init__.py b/src/qibo/hamiltonians/__init__.py index 35c2673e32..f91e1e8bc0 100644 --- a/src/qibo/hamiltonians/__init__.py +++ b/src/qibo/hamiltonians/__init__.py @@ -1,6 +1,2 @@ -from qibo.hamiltonians.hamiltonians import ( - Hamiltonian, - SymbolicHamiltonian, - TrotterHamiltonian, -) +from qibo.hamiltonians.hamiltonians import Hamiltonian, SymbolicHamiltonian from qibo.hamiltonians.models import TFIM, XXX, XXZ, Heisenberg, MaxCut, X, Y, Z diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 5a5fa638e4..a023c7ec82 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -70,34 +70,6 @@ def matrix(self, mat): ) self._matrix = mat - @classmethod - def from_symbolic(cls, symbolic_hamiltonian, symbol_map, backend=None): - """Creates a :class:`qibo.hamiltonian.Hamiltonian` from a symbolic Hamiltonian. - - We refer to :ref:`How to define custom Hamiltonians using symbols? ` - for more details. - - Args: - symbolic_hamiltonian (sympy.Expr): full Hamiltonian written with ``sympy`` symbols. - symbol_map (dict): Dictionary that maps each symbol that appears in - the Hamiltonian to a pair ``(target, matrix)``. - backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used - in the execution. If ``None``, it uses the current backend. - Defaults to ``None``. - - Returns: - :class:`qibo.hamiltonians.SymbolicHamiltonian`: object that implements the - Hamiltonian represented by the given symbolic expression. - """ - log.warning( - "`Hamiltonian.from_symbolic` and the use of symbol maps is " - "deprecated. Please use `SymbolicHamiltonian` and Qibo symbols " - "to construct Hamiltonians using symbols." - ) - return SymbolicHamiltonian( - symbolic_hamiltonian, symbol_map=symbol_map, backend=backend - ) - def eigenvalues(self, k=6): if self._eigenvalues is None: self._eigenvalues = self.backend.calculate_eigenvalues(self.matrix, k) @@ -313,14 +285,6 @@ class SymbolicHamiltonian(AbstractHamiltonian): Hamiltonian should be written using Qibo symbols. See :ref:`How to define custom Hamiltonians using symbols? ` example for more details. - symbol_map (dict): Dictionary that maps each ``sympy.Symbol`` to a tuple - of (target qubit, matrix representation). This feature is kept for - compatibility with older versions where Qibo symbols were not available - and may be deprecated in the future. - It is not required if the Hamiltonian is constructed using Qibo symbols. - The symbol_map can also be used to pass non-quantum operator arguments - to the symbolic Hamiltonian, such as the parameters in the - :meth:`qibo.hamiltonians.models.MaxCut` Hamiltonian. backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses the current backend. Defaults to ``None``. @@ -330,7 +294,6 @@ def __init__( self, form: sympy.Expr, nqubits: Optional[int] = None, - symbol_map: Optional[dict] = None, backend: Optional[Backend] = None, ): super().__init__() @@ -341,9 +304,6 @@ def __init__( ) self._form = form self.constant = 0 # used only when we perform calculations using ``_terms`` - self.symbol_map = symbol_map if symbol_map is not None else {} - # if a symbol in the given form is not a Qibo symbol it must be - # included in the ``symbol_map`` from qibo.symbols import Symbol # pylint: disable=import-outside-toplevel @@ -372,14 +332,11 @@ def _calculate_nqubits_from_form(self, form): for symbol in form.free_symbols: if isinstance(symbol, self._qiboSymbol): q = symbol.target_qubit - elif isinstance(symbol, sympy.Expr): - if symbol not in self.symbol_map: - raise_error(ValueError, f"Symbol {symbol} is not in symbol map.") - q, matrix = self.symbol_map.get(symbol) - if not isinstance(matrix, self.backend.tensor_types): - # ignore symbols that do not correspond to quantum operators - # for example parameters in the MaxCut Hamiltonian - q = 0 + else: + raise_error( + RuntimeError, + f"Symbol {symbol} is not a ``qibo.symbols.Symbol``, you can define a custom symbol for {symbol} by subclassing ``qibo.symbols.Symbol``.", + ) if q > nqubits: nqubits = q return nqubits + 1 @@ -411,7 +368,7 @@ def terms(self): form = sympy.expand(self.form) terms = [] for f, c in form.as_coefficients_dict().items(): - term = SymbolicTerm(c, f, self.symbol_map, backend=self.backend) + term = SymbolicTerm(c, f, backend=self.backend) if term.target_qubits: terms.append(term) else: @@ -480,25 +437,10 @@ def _get_symbol_matrix(self, term): for _ in range(exponent - 1): result = result @ matrix - elif isinstance(term, sympy.Symbol): - # if the term is a ``Symbol`` then it corresponds to a quantum - # operator for which we can construct the full matrix directly - if isinstance(term, self._qiboSymbol): - # if we have a Qibo symbol the matrix construction is - # implemented in :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. - result = term.full_matrix(self.nqubits) - else: - q, matrix = self.symbol_map.get(term) - if not isinstance(matrix, self.backend.tensor_types): - # symbols that do not correspond to quantum operators - # for example parameters in the MaxCut Hamiltonian - result = complex(matrix) * self.backend.np.eye(2**self.nqubits) - else: - # if we do not have a Qibo symbol we construct one and use - # :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. - result = self._qiboSymbol( - q, matrix, backend=self.backend - ).full_matrix(self.nqubits) + elif isinstance(term, self._qiboSymbol): + # if we have a Qibo symbol the matrix construction is + # implemented in :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. + result = term.full_matrix(self.nqubits) elif term.is_number: # if the term is number we should return in the form of identity @@ -682,7 +624,6 @@ def expectation_from_samples(self, freq: dict, qubit_map: list = None) -> float: def _compose(self, o, operator): form = self._form - symbol_map = self.symbol_map if isinstance(o, self.__class__): if self.nqubits != o.nqubits: @@ -692,7 +633,6 @@ def _compose(self, o, operator): ) if o._form is not None: - symbol_map.update(o.symbol_map) form = operator(form, o._form) if form is not None else o._form elif isinstance(o, (self.backend.numeric_types, self.backend.tensor_types)): @@ -703,9 +643,7 @@ def _compose(self, o, operator): f"SymbolicHamiltonian composition to {type(o)} not implemented.", ) - return self.__class__( - form=form, symbol_map=symbol_map, nqubits=self.nqubits, backend=self.backend - ) + return self.__class__(form=form, nqubits=self.nqubits, backend=self.backend) def __add__(self, o): return self._compose(o, lambda x, y: x + y) @@ -788,19 +726,3 @@ def circuit(self, dt, accelerators=None): ) return circuit - - -class TrotterHamiltonian: - """""" - - def __init__(self, *parts): - raise_error( - NotImplementedError, - "`TrotterHamiltonian` is substituted by `SymbolicHamiltonian` " - + "and is no longer supported. Please check the documentation " - + "of `SymbolicHamiltonian` for more details.", - ) - - @classmethod - def from_symbolic(cls, symbolic_hamiltonian, symbol_map): - return cls() diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index b5ef69e477..9cc2b29c03 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -139,16 +139,9 @@ class SymbolicTerm(HamiltonianTerm): coefficient (complex): Complex number coefficient of the underlying term in the Hamiltonian. factors (sympy.Expr): Sympy expression for the underlying term. - symbol_map (dict): Dictionary that maps symbols in the given ``factors`` - expression to tuples of (target qubit id, matrix). - This is required only if the expression is not created using Qibo - symbols and to keep compatibility with older versions where Qibo - symbols were not available. """ - def __init__( - self, coefficient, factors=1, symbol_map={}, backend: Optional[Backend] = None - ): + def __init__(self, coefficient, factors=1, backend: Optional[Backend] = None): self._gate = None self.hamiltonian = None self.backend = _check_backend(backend) @@ -181,14 +174,6 @@ def __init__( else: pow = 1 - # if the user is using ``symbol_map`` instead of qibo symbols, - # create the corresponding symbols - if factor in symbol_map: - from qibo.symbols import Symbol - - q, matrix = symbol_map.get(factor) - factor = Symbol(q, matrix, name=factor.name, backend=self.backend) - if isinstance(factor, sympy.Symbol): # forces the backend of the factor # this way it is not necessary to explicitely define the diff --git a/tests/test_hamiltonians_from_symbols.py b/tests/test_hamiltonians_from_symbols.py index bbc864fcb5..d3aecd88d0 100644 --- a/tests/test_hamiltonians_from_symbols.py +++ b/tests/test_hamiltonians_from_symbols.py @@ -23,64 +23,34 @@ def test_symbols_pickling(symbol): @pytest.mark.parametrize("nqubits", [4, 5]) -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): +@pytest.mark.parametrize("hamtype", ["symbolic"]) +def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype): """Check creating TFIM Hamiltonian using sympy.""" - if hamtype == "symbolic": - h = 0.5 - symham = sum( - Z(i, backend=backend) * Z(i + 1, backend=backend) - for i in range(nqubits - 1) - ) - symham += Z(0, backend=backend) * Z(nqubits - 1, backend=backend) - symham += h * sum(X(i, backend=backend) for i in range(nqubits)) - ham = hamiltonians.SymbolicHamiltonian(-symham, backend=backend) - else: - h = 0.5 - z_symbols = sympy.symbols(" ".join(f"Z{i}" for i in range(nqubits))) - x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(nqubits))) - - symham = sum(z_symbols[i] * z_symbols[i + 1] for i in range(nqubits - 1)) - symham += z_symbols[0] * z_symbols[-1] - symham += h * sum(x_symbols) - symmap = {z: (i, backend.matrices.Z) for i, z in enumerate(z_symbols)} - symmap.update({x: (i, backend.matrices.X) for i, x in enumerate(x_symbols)}) - ham = hamiltonians.Hamiltonian.from_symbolic(-symham, symmap, backend=backend) - - if calcterms: - _ = ham.terms + h = 0.5 + symham = sum( + Z(i, backend=backend) * Z(i + 1, backend=backend) for i in range(nqubits - 1) + ) + symham += Z(0, backend=backend) * Z(nqubits - 1, backend=backend) + symham += h * sum(X(i, backend=backend) for i in range(nqubits)) + ham = hamiltonians.SymbolicHamiltonian(-symham, backend=backend) final_matrix = ham.matrix target_matrix = hamiltonians.TFIM(nqubits, h=h, backend=backend).matrix backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_from_symbolic_with_power(backend, hamtype, calcterms): +@pytest.mark.parametrize("hamtype", ["symbolic"]) +def test_from_symbolic_with_power(backend, hamtype): """Check ``from_symbolic`` when the expression contains powers.""" npbackend = NumpyBackend() - if hamtype == "symbolic": - matrix = random_hermitian(2, backend=npbackend) - symham = ( - Symbol(0, matrix, backend=backend) ** 2 - - Symbol(1, matrix, backend=backend) ** 2 - + 3 * Symbol(1, matrix, backend=backend) - - 2 - * Symbol(0, matrix, backend=backend) - * Symbol(2, matrix, backend=backend) - + 1 - ) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - z = sympy.symbols(" ".join(f"Z{i}" for i in range(3))) - symham = z[0] ** 2 - z[1] ** 2 + 3 * z[1] - 2 * z[0] * z[2] + 1 - matrix = random_hermitian(2, backend=npbackend) - symmap = {x: (i, matrix) for i, x in enumerate(z)} - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - - if calcterms: - _ = ham.terms + matrix = random_hermitian(2, backend=npbackend) + symham = ( + Symbol(0, matrix, backend=backend) ** 2 + - Symbol(1, matrix, backend=backend) ** 2 + + 3 * Symbol(1, matrix, backend=backend) + - 2 * Symbol(0, matrix, backend=backend) * Symbol(2, matrix, backend=backend) + + 1 + ) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) final_matrix = ham.matrix matrix2 = matrix.dot(matrix) @@ -93,33 +63,17 @@ def test_from_symbolic_with_power(backend, hamtype, calcterms): backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_from_symbolic_with_complex_numbers(backend, hamtype, calcterms): +@pytest.mark.parametrize("hamtype", ["symbolic"]) +def test_from_symbolic_with_complex_numbers(backend, hamtype): """Check ``from_symbolic`` when the expression contains imaginary unit.""" - if hamtype == "symbolic": - symham = ( - (1 + 2j) * X(0, backend=backend) * X(1, backend=backend) - + 2 * Y(0, backend=backend) * Y(1, backend=backend) - - 3j * X(0, backend=backend) * Y(1, backend=backend) - + 1j * Y(0, backend=backend) * X(1, backend=backend) - ) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - x = sympy.symbols(" ".join(f"X{i}" for i in range(2))) - y = sympy.symbols(" ".join(f"Y{i}" for i in range(2))) - symham = ( - (1 + 2j) * x[0] * x[1] - + 2 * y[0] * y[1] - - 3j * x[0] * y[1] - + 1j * y[0] * x[1] - ) - symmap = {s: (i, backend.matrices.X) for i, s in enumerate(x)} - symmap.update({s: (i, backend.matrices.Y) for i, s in enumerate(y)}) - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) + symham = ( + (1 + 2j) * X(0, backend=backend) * X(1, backend=backend) + + 2 * Y(0, backend=backend) * Y(1, backend=backend) + - 3j * X(0, backend=backend) * Y(1, backend=backend) + + 1j * Y(0, backend=backend) * X(1, backend=backend) + ) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - if calcterms: - _ = ham.terms final_matrix = ham.matrix target_matrix = (1 + 2j) * backend.np.kron(backend.matrices.X, backend.matrices.X) target_matrix += 2 * backend.np.kron(backend.matrices.Y, backend.matrices.Y) @@ -128,98 +82,32 @@ def test_from_symbolic_with_complex_numbers(backend, hamtype, calcterms): backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_from_symbolic_application_hamiltonian(backend, calcterms): - """Check ``from_symbolic`` for a specific four-qubit Hamiltonian.""" - z1, z2, z3, z4 = sympy.symbols("z1 z2 z3 z4") - symmap = {z: (i, matrices.Z) for i, z in enumerate([z1, z2, z3, z4])} - symham = ( - z1 * z2 - - 0.5 * z1 * z3 - + 2 * z2 * z3 - + 0.35 * z2 - + 0.25 * z3 * z4 - + 0.5 * z3 - + z4 - - z1 - ) - # Check that Trotter dense matrix agrees will full Hamiltonian matrix - fham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - symham = ( - Z(0, backend=backend) * Z(1, backend=backend) - - 0.5 * Z(0, backend=backend) * Z(2, backend=backend) - + 2 * Z(1, backend=backend) * Z(2, backend=backend) - + 0.35 * Z(1, backend=backend) - + 0.25 * Z(2, backend=backend) * Z(3, backend=backend) - + 0.5 * Z(2, backend=backend) - + Z(3, backend=backend) - - Z(0, backend=backend) - ) - sham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - if calcterms: - _ = sham.terms - backend.assert_allclose(sham.matrix, fham.matrix) - - @pytest.mark.parametrize("nqubits", [4, 5]) -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype, calcterms): +@pytest.mark.parametrize("hamtype", ["symbolic"]) +def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype): """Check creating sum(X) Hamiltonian using sympy.""" - if hamtype == "symbolic": - symham = -sum(X(i, backend=backend) for i in range(nqubits)) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(nqubits))) - symham = -sum(x_symbols) - symmap = {x: (i, backend.matrices.X) for i, x in enumerate(x_symbols)} - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - if calcterms: - _ = ham.terms + symham = -sum(X(i, backend=backend) for i in range(nqubits)) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) final_matrix = ham.matrix target_matrix = hamiltonians.X(nqubits, backend=backend).matrix backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("hamtype", ["normal", "symbolic"]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype, calcterms): +@pytest.mark.parametrize("hamtype", ["symbolic"]) +def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype): """Check creating Hamiltonian with three-qubit interaction using sympy.""" - if hamtype == "symbolic": - symham = ( - X(0, backend=backend) * Y(1, backend=backend) * Z(2, backend=backend) - + 0.5 - * Y(0, backend=backend) - * Z(1, backend=backend) - * X(3, backend=backend) - + Z(0, backend=backend) * X(2, backend=backend) - ) - symham += ( - Y(2, backend=backend) - + 1.5 * Z(1, backend=backend) - - 2 - - 3 * X(1, backend=backend) * Y(3, backend=backend) - ) - ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - else: - x_symbols = sympy.symbols(" ".join(f"X{i}" for i in range(4))) - y_symbols = sympy.symbols(" ".join(f"Y{i}" for i in range(4))) - z_symbols = sympy.symbols(" ".join(f"Z{i}" for i in range(4))) - symmap = {x: (i, matrices.X) for i, x in enumerate(x_symbols)} - symmap.update({x: (i, matrices.Y) for i, x in enumerate(y_symbols)}) - symmap.update({x: (i, matrices.Z) for i, x in enumerate(z_symbols)}) - - symham = x_symbols[0] * y_symbols[1] * z_symbols[2] - symham += 0.5 * y_symbols[0] * z_symbols[1] * x_symbols[3] - symham += z_symbols[0] * x_symbols[2] - symham += -3 * x_symbols[1] * y_symbols[3] - symham += y_symbols[2] - symham += 1.5 * z_symbols[1] - symham -= 2 - ham = hamiltonians.Hamiltonian.from_symbolic(symham, symmap, backend=backend) - - if calcterms: - _ = ham.terms + symham = ( + X(0, backend=backend) * Y(1, backend=backend) * Z(2, backend=backend) + + 0.5 * Y(0, backend=backend) * Z(1, backend=backend) * X(3, backend=backend) + + Z(0, backend=backend) * X(2, backend=backend) + ) + symham += ( + Y(2, backend=backend) + + 1.5 * Z(1, backend=backend) + - 2 + - 3 * X(1, backend=backend) * Y(3, backend=backend) + ) + ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) final_matrix = ham.matrix target_matrix = backend.np.kron( backend.np.kron(backend.matrices.X, backend.matrices.Y), @@ -249,8 +137,7 @@ def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype, calcterms): backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_hamiltonian_with_identity_symbol(backend, calcterms): +def test_hamiltonian_with_identity_symbol(backend): """Check creating Hamiltonian from expression which contains the identity symbol.""" symham = ( X(0, backend=backend) * I(1, backend=backend) * Z(2, backend=backend) @@ -259,8 +146,6 @@ def test_hamiltonian_with_identity_symbol(backend, calcterms): ) ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) - if calcterms: - _ = ham.terms final_matrix = ham.matrix target_matrix = backend.np.kron( backend.np.kron(backend.matrices.X, backend.matrices.I()), diff --git a/tests/test_hamiltonians_models.py b/tests/test_hamiltonians_models.py index 5d2a2017d2..7e0866acc8 100644 --- a/tests/test_hamiltonians_models.py +++ b/tests/test_hamiltonians_models.py @@ -33,10 +33,8 @@ def test_hamiltonian_models(backend, model, kwargs, filename): @pytest.mark.parametrize("nqubits", [3, 4]) -@pytest.mark.parametrize( - "dense,calcterms", [(True, False), (False, False), (False, True)] -) -def test_maxcut(backend, nqubits, dense, calcterms): +@pytest.mark.parametrize("dense", [True, False]) +def test_maxcut(backend, nqubits, dense): size = 2**nqubits ham = np.zeros(shape=(size, size), dtype=np.complex128) for i in range(nqubits): @@ -51,8 +49,6 @@ def test_maxcut(backend, nqubits, dense, calcterms): ham += M target_ham = backend.cast(-ham / 2) final_ham = hamiltonians.MaxCut(nqubits, dense, backend=backend) - if (not dense) and calcterms: - _ = final_ham.terms backend.assert_allclose(final_ham.matrix, target_ham) diff --git a/tests/test_hamiltonians_symbolic.py b/tests/test_hamiltonians_symbolic.py index f9ecc788a0..0637e9bca7 100644 --- a/tests/test_hamiltonians_symbolic.py +++ b/tests/test_hamiltonians_symbolic.py @@ -29,34 +29,29 @@ def test_symbolic_hamiltonian_errors(backend): # Wrong type of symbolic expression with pytest.raises(TypeError): ham = SymbolicHamiltonian("test", backend=backend) - # Passing form with symbol that is not in ``symbol_map`` + # Passing form with symbol that is not a ``qibo.symbols.Symbol`` from qibo import matrices - Z, X = sympy.Symbol("Z"), sympy.Symbol("X") - symbol_map = {Z: (0, matrices.Z)} - with pytest.raises(ValueError): - ham = SymbolicHamiltonian(Z * X, symbol_map=symbol_map, backend=backend) + z, x = sympy.Symbol("z"), sympy.Symbol("x") + with pytest.raises(RuntimeError): + ham = SymbolicHamiltonian(z * x, backend=backend) # Invalid operation in Hamiltonian expresion - ham = SymbolicHamiltonian(sympy.cos(Z), symbol_map=symbol_map, backend=backend) + ham = SymbolicHamiltonian(sympy.cos(Z(0, backend=backend)), backend=backend) with pytest.raises(TypeError): dense = ham.dense @pytest.mark.parametrize("nqubits", [3, 4]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_symbolictfim_hamiltonian_to_dense(backend, nqubits, calcterms): +def test_symbolictfim_hamiltonian_to_dense(backend, nqubits): final_ham = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1), backend=backend ) target_ham = TFIM(nqubits, h=1, backend=backend) - if calcterms: - _ = final_ham.terms backend.assert_allclose(final_ham.matrix, target_ham.matrix, atol=1e-15) @pytest.mark.parametrize("nqubits", [3, 4]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_symbolicxxz_hamiltonian_to_dense(backend, nqubits, calcterms): +def test_symbolicxxz_hamiltonian_to_dense(backend, nqubits): sham = sum( X(i, backend=backend) * X(i + 1, backend=backend) for i in range(nqubits - 1) ) @@ -73,78 +68,50 @@ def test_symbolicxxz_hamiltonian_to_dense(backend, nqubits, calcterms): ) final_ham = SymbolicHamiltonian(sham, backend=backend) target_ham = XXZ(nqubits, backend=backend) - if calcterms: - _ = final_ham.terms backend.assert_allclose(final_ham.matrix, target_ham.matrix, atol=1e-15) @pytest.mark.parametrize("nqubits", [3]) -@pytest.mark.parametrize("calcterms", [False, True]) -@pytest.mark.parametrize("calcdense", [False, True]) -def test_symbolic_hamiltonian_scalar_mul(backend, nqubits, calcterms, calcdense): +def test_symbolic_hamiltonian_scalar_mul(backend, nqubits): """Test multiplication of Trotter Hamiltonian with scalar.""" local_ham = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) target_ham = 2 * TFIM(nqubits, h=1.0, backend=backend) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense local_dense = (2 * local_ham).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) local_ham = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense local_dense = (local_ham * 2).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) @pytest.mark.parametrize("nqubits", [4]) -@pytest.mark.parametrize("calcterms", [False, True]) -@pytest.mark.parametrize("calcdense", [False, True]) -def test_symbolic_hamiltonian_scalar_add(backend, nqubits, calcterms, calcdense): +def test_symbolic_hamiltonian_scalar_add(backend, nqubits): """Test addition of Trotter Hamiltonian with scalar.""" local_ham = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) target_ham = 2 + TFIM(nqubits, h=1.0, backend=backend) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense local_dense = (2 + local_ham).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) local_ham = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense local_dense = (local_ham + 2).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) @pytest.mark.parametrize("nqubits", [3]) -@pytest.mark.parametrize("calcterms", [False, True]) -@pytest.mark.parametrize("calcdense", [False, True]) -def test_symbolic_hamiltonian_scalar_sub(backend, nqubits, calcterms, calcdense): +def test_symbolic_hamiltonian_scalar_sub(backend, nqubits): """Test subtraction of Trotter Hamiltonian with scalar.""" local_ham = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) target_ham = 2 - TFIM(nqubits, h=1.0, backend=backend) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense local_dense = (2 - local_ham).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) @@ -152,20 +119,12 @@ def test_symbolic_hamiltonian_scalar_sub(backend, nqubits, calcterms, calcdense) local_ham = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense local_dense = (local_ham - 2).dense backend.assert_allclose(local_dense.matrix, target_ham.matrix) @pytest.mark.parametrize("nqubits", [3]) -@pytest.mark.parametrize("calcterms", [False, True]) -@pytest.mark.parametrize("calcdense", [False, True]) -def test_symbolic_hamiltonian_operator_add_and_sub( - backend, nqubits, calcterms, calcdense -): +def test_symbolic_hamiltonian_operator_add_and_sub(backend, nqubits): """Test addition and subtraction between Trotter Hamiltonians.""" local_ham1 = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend @@ -173,12 +132,6 @@ def test_symbolic_hamiltonian_operator_add_and_sub( local_ham2 = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=0.5), backend=backend ) - if calcterms: - _ = local_ham1.terms - _ = local_ham2.terms - if calcdense: - _ = local_ham1.dense - _ = local_ham2.dense local_ham = local_ham1 + local_ham2 target_ham = TFIM(nqubits, h=1.0, backend=backend) + TFIM( nqubits, h=0.5, backend=backend @@ -192,12 +145,6 @@ def test_symbolic_hamiltonian_operator_add_and_sub( local_ham2 = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=0.5), backend=backend ) - if calcterms: - _ = local_ham1.terms - _ = local_ham2.terms - if calcdense: - _ = local_ham1.dense - _ = local_ham2.dense local_ham = local_ham1 - local_ham2 target_ham = TFIM(nqubits, h=1.0, backend=backend) - TFIM( nqubits, h=0.5, backend=backend @@ -232,9 +179,7 @@ def test_symbolic_hamiltonian_operator_add_and_sub( @pytest.mark.parametrize("nqubits", [5]) -@pytest.mark.parametrize("calcterms", [False, True]) -@pytest.mark.parametrize("calcdense", [False, True]) -def test_symbolic_hamiltonian_hamiltonianmatmul(backend, nqubits, calcterms, calcdense): +def test_symbolic_hamiltonian_hamiltonianmatmul(backend, nqubits): local_ham1 = SymbolicHamiltonian( symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) @@ -243,12 +188,6 @@ def test_symbolic_hamiltonian_hamiltonianmatmul(backend, nqubits, calcterms, cal ) dense_ham1 = TFIM(nqubits, h=1.0, backend=backend) dense_ham2 = TFIM(nqubits, h=0.5, backend=backend) - if calcterms: - _ = local_ham1.terms - _ = local_ham2.terms - if calcdense: - _ = local_ham1.dense - _ = local_ham2.dense local_matmul = local_ham1 @ local_ham2 target_matmul = dense_ham1 @ dense_ham2 backend.assert_allclose(local_matmul.matrix, target_matmul.matrix) @@ -256,8 +195,7 @@ def test_symbolic_hamiltonian_hamiltonianmatmul(backend, nqubits, calcterms, cal @pytest.mark.parametrize("nqubits", [3, 4]) @pytest.mark.parametrize("density_matrix", [False, True]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_symbolic_hamiltonian_matmul(backend, nqubits, density_matrix, calcterms): +def test_symbolic_hamiltonian_matmul(backend, nqubits, density_matrix): state = ( random_density_matrix(2**nqubits, backend=backend) if density_matrix @@ -267,26 +205,16 @@ def test_symbolic_hamiltonian_matmul(backend, nqubits, density_matrix, calcterms symbolic_tfim(nqubits, backend, h=1.0), backend=backend ) dense_ham = TFIM(nqubits, h=1.0, backend=backend) - if calcterms: - _ = local_ham.terms local_matmul = local_ham @ state target_matmul = dense_ham @ state backend.assert_allclose(local_matmul, target_matmul) @pytest.mark.parametrize("nqubits,normalize", [(3, False), (4, False)]) -@pytest.mark.parametrize("calcterms", [False, True]) -@pytest.mark.parametrize("calcdense", [False, True]) -def test_symbolic_hamiltonian_state_expectation( - backend, nqubits, normalize, calcterms, calcdense -): +def test_symbolic_hamiltonian_state_expectation(backend, nqubits, normalize): local_ham = ( SymbolicHamiltonian(symbolic_tfim(nqubits, backend, h=1.0), backend=backend) + 2 ) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense dense_ham = TFIM(nqubits, h=1.0, backend=backend) + 2 state = random_statevector(2**nqubits, backend=backend) @@ -301,20 +229,14 @@ def test_symbolic_hamiltonian_state_expectation( @pytest.mark.parametrize("give_nqubits", [False, True]) -@pytest.mark.parametrize("calcterms", [False, True]) -@pytest.mark.parametrize("calcdense", [False, True]) def test_symbolic_hamiltonian_state_expectation_different_nqubits( - backend, give_nqubits, calcterms, calcdense + backend, give_nqubits ): expr = symbolic_tfim(3, backend, h=1.0) if give_nqubits: local_ham = SymbolicHamiltonian(expr, nqubits=5, backend=backend) else: local_ham = SymbolicHamiltonian(expr, backend=backend) - if calcterms: - _ = local_ham.terms - if calcdense: - _ = local_ham.dense dense_ham = TFIM(3, h=1.0, backend=backend) dense_matrix = np.kron(backend.to_numpy(dense_ham.matrix), np.eye(4)) @@ -363,8 +285,7 @@ def test_hamiltonian_expectation_from_samples(backend, observable, qubit_map): @pytest.mark.parametrize("density_matrix", [False, True]) -@pytest.mark.parametrize("calcterms", [False, True]) -def test_symbolic_hamiltonian_abstract_symbol_ev(backend, density_matrix, calcterms): +def test_symbolic_hamiltonian_abstract_symbol_ev(backend, density_matrix): from qibo.symbols import Symbol, X matrix = np.random.random((2, 2)) @@ -372,8 +293,6 @@ def test_symbolic_hamiltonian_abstract_symbol_ev(backend, density_matrix, calcte 0, matrix, backend=backend ) * X(1, backend=backend) local_ham = SymbolicHamiltonian(form, backend=backend) - if calcterms: - _ = local_ham.terms state = ( random_density_matrix(4, backend=backend) diff --git a/tests/test_hamiltonians_terms.py b/tests/test_hamiltonians_terms.py index 0addb08adc..c3dbe6b2d3 100644 --- a/tests/test_hamiltonians_terms.py +++ b/tests/test_hamiltonians_terms.py @@ -99,21 +99,12 @@ def test_hamiltonian_term_merge(backend): term1.merge(term2) -@pytest.mark.parametrize("use_symbols", [True, False]) -def test_symbolic_term_creation(backend, use_symbols): +def test_symbolic_term_creation(backend): """Test creating ``SymbolicTerm`` from sympy expression.""" - if use_symbols: - from qibo.symbols import X, Y - - expression = X(0) * Y(1) * X(1) - symbol_map = {} - else: - import sympy - - x0, x1, y1 = sympy.symbols("X0 X1 Y1", commutative=False) - expression = x0 * y1 * x1 - symbol_map = {x0: (0, matrices.X), x1: (1, matrices.X), y1: (1, matrices.Y)} - term = terms.SymbolicTerm(2, expression, symbol_map) + from qibo.symbols import X, Y + + expression = X(0) * Y(1) * X(1) + term = terms.SymbolicTerm(2, expression) assert term.target_qubits == (0, 1) assert len(term.matrix_map) == 2 backend.assert_allclose(term.matrix_map.get(0)[0], matrices.X) diff --git a/tests/test_hamiltonians_trotter.py b/tests/test_hamiltonians_trotter.py index b63b3a0814..844d59e10e 100644 --- a/tests/test_hamiltonians_trotter.py +++ b/tests/test_hamiltonians_trotter.py @@ -104,11 +104,3 @@ def test_symbolic_hamiltonian_circuit_different_dts(backend): matrix1 = ham.circuit(0.2).unitary(backend) matrix2 = (a + b).unitary(backend) backend.assert_allclose(matrix1, matrix2) - - -def test_old_trotter_hamiltonian_errors(): - """Check errors when creating the deprecated ``TrotterHamiltonian`` object.""" - with pytest.raises(NotImplementedError): - h = hamiltonians.TrotterHamiltonian() - with pytest.raises(NotImplementedError): - h = hamiltonians.TrotterHamiltonian.from_symbolic(0, 1) From 4d8c9c1211c850a5e740607de943da75fc31f8b2 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Mon, 13 Jan 2025 17:38:02 +0100 Subject: [PATCH 20/31] fix: minor adjustments --- src/qibo/hamiltonians/models.py | 6 ------ src/qibo/hamiltonians/terms.py | 2 -- src/qibo/symbols.py | 7 +++---- tests/test_models_tsp.py | 4 ++-- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 3f41a132f7..4992392063 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -356,8 +356,6 @@ def _multikron(matrix_list, backend): ndarray: Kronecker product of all matrices in ``matrix_list``. """ - # this is a better implementation but requires the whole - # hamiltonian/symbols modules to be adapted indices = list(range(2 * len(matrix_list))) even, odd = indices[::2], indices[1::2] lhs = zip(even, odd) @@ -411,10 +409,6 @@ def _build_spin_model(nqubits, matrix, condition, backend): else: h = backend.np.einsum(*einsum_args, rhs) h = backend.np.sum(backend.np.reshape(h, (nqubits, dim, dim)), axis=0) - # h = sum( - # _multikron(matrix if condition(i, j) else matrices.I for j in range(nqubits)) - # for i in range(nqubits) - # ) return h diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index 9cc2b29c03..eeef194e17 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -151,8 +151,6 @@ def __init__(self, coefficient, factors=1, backend: Optional[Backend] = None): self.factors = [] # Dictionary that maps target qubit ids to a list of matrices that act on each qubit self.matrix_map = {} - # if backend.name == "qiboml": - # breakpoint() if factors != 1: for factor in factors.as_ordered_factors(): # check if factor has some power ``power`` so that the corresponding diff --git a/src/qibo/symbols.py b/src/qibo/symbols.py index ac9b145eee..c5b7ef289d 100644 --- a/src/qibo/symbols.py +++ b/src/qibo/symbols.py @@ -39,7 +39,7 @@ class Symbol(sympy.Symbol): (for example when the Hamiltonian consists of Z terms only). """ - def __new__(cls, q, matrix=None, name="Symbol", commutative=False, **assumptions): + def __new__(cls, q, matrix, name="Symbol", commutative=False, **assumptions): name = f"{name}{q}" assumptions["commutative"] = commutative return super().__new__(cls=cls, name=name, **assumptions) @@ -47,7 +47,7 @@ def __new__(cls, q, matrix=None, name="Symbol", commutative=False, **assumptions def __init__( self, q, - matrix=None, + matrix, name="Symbol", commutative=False, backend: Optional[Backend] = None, @@ -56,8 +56,7 @@ def __init__( self.backend = _check_backend(backend) self._gate = None if not ( - matrix is None - or isinstance(matrix, np.ndarray) + isinstance(matrix, np.ndarray) or isinstance(matrix, self.backend.tensor_types) or isinstance( matrix, diff --git a/tests/test_models_tsp.py b/tests/test_models_tsp.py index d0501844ae..5681fc86b6 100644 --- a/tests/test_models_tsp.py +++ b/tests/test_models_tsp.py @@ -38,8 +38,8 @@ def qaoa_function_of_layer(backend, layer): def test_tsp(backend, nlayers): final_state = backend.to_numpy(qaoa_function_of_layer(backend, nlayers)) assert_regression_fixture( - backend, final_state.real, f"tsp_layer{nlayers}_real.out", rtol=1e-3, atol=1e-5 + backend, final_state.real, f"tsp_layer{nlayers}_real.out", rtol=1e-3, atol=1e-2 ) assert_regression_fixture( - backend, final_state.imag, f"tsp_layer{nlayers}_imag.out", rtol=1e-3, atol=1e-5 + backend, final_state.imag, f"tsp_layer{nlayers}_imag.out", rtol=1e-3, atol=1e-2 ) From ff26b760f8ee20ef0ef4571589a3a54b30854f74 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Tue, 14 Jan 2025 15:04:55 +0100 Subject: [PATCH 21/31] feat: minor optimizations: reintroduced reduce and using cache --- src/qibo/hamiltonians/hamiltonians.py | 34 ++++++++++----------------- src/qibo/hamiltonians/models.py | 15 ++++++++---- src/qibo/hamiltonians/terms.py | 11 +++++++-- tests/test_hamiltonians_models.py | 14 ++++++++--- tests/test_hamiltonians_symbolic.py | 8 +++++++ 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index a023c7ec82..64ec34e079 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -1,6 +1,6 @@ """Module defining Hamiltonian classes.""" -from functools import cached_property +from functools import cache, cached_property, reduce from itertools import chain from math import prod from typing import Optional @@ -396,6 +396,7 @@ def exp(self, a): return self.dense.exp(a) # only useful for dense_from_form, which might not be needed in the end + @cache def _get_symbol_matrix(self, term): """Calculates numerical matrix corresponding to symbolic expression. @@ -424,18 +425,16 @@ def _get_symbol_matrix(self, term): # note that we need to use matrix multiplication even though # we use scalar symbols for convenience factors = term.as_ordered_factors() - result = self._get_symbol_matrix(factors[0]) - for subterm in factors[1:]: - result = result @ self._get_symbol_matrix(subterm) + result = reduce( + self.backend.np.matmul, + [self._get_symbol_matrix(subterm) for subterm in factors], + ) elif isinstance(term, sympy.Pow): # symbolic op for power base, exponent = term.as_base_exp() matrix = self._get_symbol_matrix(base) - # multiply ``base`` matrix ``exponent`` times to itself - result = matrix - for _ in range(exponent - 1): - result = result @ matrix + result = self.backend.np.linalg.matrix_power(matrix, exponent) elif isinstance(term, self._qiboSymbol): # if we have a Qibo symbol the matrix construction is @@ -470,16 +469,13 @@ def _calculate_dense_from_terms(self) -> Hamiltonian: """Calculates equivalent Hamiltonian using the term representation.""" matrix = 0 indices = list(range(2 * self.nqubits)) - # most likely the looped einsum could be avoided by preparing all the - # matrices first and performing a single einsum in the end with a suitable - # choice of indices + einsum = ( + np.einsum + if self.backend.platform == "tensorflow" + else self.backend.np.einsum + ) for term in self.terms: ntargets = len(term.target_qubits) - # I have to cast to a backend array because SymbolicTerm does not support - # backend declaration and just works with numpy, might be worth implementing - # a SymbolicTerm.matrix(backend=None) method that returns the matrix in the - # desired backend type and defaults to numpy or GlobalBackend - # A similar argument holds for qibo Symbols tmat = self.backend.np.reshape(term.matrix, 2 * ntargets * (2,)) n = self.nqubits - ntargets emat = self.backend.np.reshape( @@ -488,10 +484,7 @@ def _calculate_dense_from_terms(self) -> Hamiltonian: gen = lambda x: (indices[i + x] for i in term.target_qubits) tc = list(chain(gen(0), gen(self.nqubits))) ec = list(c for c in indices if c not in tc) - if self.backend.platform == "tensorflow": - matrix += np.einsum(tmat, tc, emat, ec, indices) - else: - matrix += self.backend.np.einsum(tmat, tc, emat, ec, indices) + matrix += einsum(tmat, tc, emat, ec, indices) matrix = ( self.backend.np.reshape(matrix, 2 * (2**self.nqubits,)) @@ -655,7 +648,6 @@ def __rsub__(self, o): return self._compose(o, lambda x, y: y - x) def __mul__(self, o): - # o = complex(o) return self._compose(o, lambda x, y: y * x) def apply_gates(self, state, density_matrix=False): diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 4992392063..b2a22d8b2d 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -1,3 +1,4 @@ +from functools import reduce from typing import Optional, Union import numpy as np @@ -355,7 +356,8 @@ def _multikron(matrix_list, backend): Returns: ndarray: Kronecker product of all matrices in ``matrix_list``. """ - + # TO DO: check whether this scales better on gpu + """ indices = list(range(2 * len(matrix_list))) even, odd = indices[::2], indices[1::2] lhs = zip(even, odd) @@ -368,6 +370,9 @@ def _multikron(matrix_list, backend): h = np.einsum(*einsum_args, rhs) h = backend.np.sum(backend.np.reshape(h, (-1, dim, dim)), axis=0) return h + """ + # reduce appears to be faster especially when matrix_list is long + return reduce(backend.np.kron, matrix_list) def _build_spin_model(nqubits, matrix, condition, backend): @@ -389,13 +394,13 @@ def _build_spin_model(nqubits, matrix, condition, backend): + even + odd ) - eye = backend.matrices.I() - if backend.platform == "cupy": - eye = backend.cast(eye) columns = [ backend.np.reshape( backend.np.concatenate( - [matrix if condition(i, j) else eye for i in range(nqubits)], + [ + matrix if condition(i, j) else backend.matrices.I() + for i in range(nqubits) + ], axis=0, ), (nqubits, 2, 2), diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index eeef194e17..0a0c79ee13 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -1,4 +1,4 @@ -from functools import cached_property +from functools import cached_property, reduce from typing import Optional import numpy as np @@ -210,8 +210,9 @@ def matrix(self): where ``ntargets`` is the number of qubits included in the factors of this term. """ - from qibo.hamiltonians.models import _multikron + # from qibo.hamiltonians.models import _multikron + """ einsum = ( self.backend.np.einsum if self.backend.platform != "tensorflow" @@ -243,6 +244,12 @@ def matrix(self): lhs = [el for item in lhs for el in item] matrix = einsum(*lhs, (0, 1, max_len + 1)) return self.coefficient * _multikron(matrix, self.backend) + """ + matrices = [ + reduce(self.backend.np.matmul, self.matrix_map.get(q)) + for q in self.target_qubits + ] + return self.coefficient * reduce(self.backend.np.kron, matrices) def copy(self): """Creates a shallow copy of the term with the same attributes.""" diff --git a/tests/test_hamiltonians_models.py b/tests/test_hamiltonians_models.py index 7e0866acc8..b547cd3a6d 100644 --- a/tests/test_hamiltonians_models.py +++ b/tests/test_hamiltonians_models.py @@ -32,9 +32,15 @@ def test_hamiltonian_models(backend, model, kwargs, filename): assert_regression_fixture(backend, matrix, filename) -@pytest.mark.parametrize("nqubits", [3, 4]) +@pytest.mark.parametrize("nqubits,adj_matrix", zip([3, 4], [None, [[0, 1], [2, 3]]])) @pytest.mark.parametrize("dense", [True, False]) -def test_maxcut(backend, nqubits, dense): +def test_maxcut(backend, nqubits, adj_matrix, dense): + if adj_matrix is not None: + with pytest.raises(RuntimeError): + final_ham = hamiltonians.MaxCut( + nqubits, dense, adj_matrix=adj_matrix, backend=backend + ) + size = 2**nqubits ham = np.zeros(shape=(size, size), dtype=np.complex128) for i in range(nqubits): @@ -48,7 +54,9 @@ def test_maxcut(backend, nqubits, dense): M = np.eye(2**nqubits) - h ham += M target_ham = backend.cast(-ham / 2) - final_ham = hamiltonians.MaxCut(nqubits, dense, backend=backend) + final_ham = hamiltonians.MaxCut( + nqubits, dense, adj_matrix=adj_matrix, backend=backend + ) backend.assert_allclose(final_ham.matrix, target_ham) diff --git a/tests/test_hamiltonians_symbolic.py b/tests/test_hamiltonians_symbolic.py index 0637e9bca7..7d4e7a7cbc 100644 --- a/tests/test_hamiltonians_symbolic.py +++ b/tests/test_hamiltonians_symbolic.py @@ -41,6 +41,14 @@ def test_symbolic_hamiltonian_errors(backend): dense = ham.dense +def test_symbolic_hamiltonian_form_setter(backend): + h = SymbolicHamiltonian(Z(0), backend=backend) + new_form = Z(0) * X(1) * Y(3) + h.form = new_form + assert h.form == new_form + assert h.nqubits == 4 + + @pytest.mark.parametrize("nqubits", [3, 4]) def test_symbolictfim_hamiltonian_to_dense(backend, nqubits): final_ham = SymbolicHamiltonian( From feb98a18bba64856e4275b00cf8e2a1ea4a7e0da Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Tue, 14 Jan 2025 15:37:46 +0100 Subject: [PATCH 22/31] fix: minor fix --- src/qibo/hamiltonians/terms.py | 2 +- tests/test_hamiltonians_models.py | 36 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index 0a0c79ee13..a1d9015938 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -249,7 +249,7 @@ def matrix(self): reduce(self.backend.np.matmul, self.matrix_map.get(q)) for q in self.target_qubits ] - return self.coefficient * reduce(self.backend.np.kron, matrices) + return complex(self.coefficient) * reduce(self.backend.np.kron, matrices) def copy(self): """Creates a shallow copy of the term with the same attributes.""" diff --git a/tests/test_hamiltonians_models.py b/tests/test_hamiltonians_models.py index b547cd3a6d..a5b52f5baf 100644 --- a/tests/test_hamiltonians_models.py +++ b/tests/test_hamiltonians_models.py @@ -40,24 +40,24 @@ def test_maxcut(backend, nqubits, adj_matrix, dense): final_ham = hamiltonians.MaxCut( nqubits, dense, adj_matrix=adj_matrix, backend=backend ) - - size = 2**nqubits - ham = np.zeros(shape=(size, size), dtype=np.complex128) - for i in range(nqubits): - for j in range(nqubits): - h = np.eye(1) - for k in range(nqubits): - if (k == i) ^ (k == j): - h = np.kron(h, matrices.Z) - else: - h = np.kron(h, matrices.I) - M = np.eye(2**nqubits) - h - ham += M - target_ham = backend.cast(-ham / 2) - final_ham = hamiltonians.MaxCut( - nqubits, dense, adj_matrix=adj_matrix, backend=backend - ) - backend.assert_allclose(final_ham.matrix, target_ham) + else: + size = 2**nqubits + ham = np.zeros(shape=(size, size), dtype=np.complex128) + for i in range(nqubits): + for j in range(nqubits): + h = np.eye(1) + for k in range(nqubits): + if (k == i) ^ (k == j): + h = np.kron(h, matrices.Z) + else: + h = np.kron(h, matrices.I) + M = np.eye(2**nqubits) - h + ham += M + target_ham = backend.cast(-ham / 2) + final_ham = hamiltonians.MaxCut( + nqubits, dense, adj_matrix=adj_matrix, backend=backend + ) + backend.assert_allclose(final_ham.matrix, target_ham) @pytest.mark.parametrize("model", ["XXZ", "TFIM"]) From 29a010e40af567e6ee9e3fda6c52cfba565b4a29 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Tue, 14 Jan 2025 15:40:44 +0100 Subject: [PATCH 23/31] fix: restored tol for tsp test --- tests/test_models_tsp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_models_tsp.py b/tests/test_models_tsp.py index 5681fc86b6..d0501844ae 100644 --- a/tests/test_models_tsp.py +++ b/tests/test_models_tsp.py @@ -38,8 +38,8 @@ def qaoa_function_of_layer(backend, layer): def test_tsp(backend, nlayers): final_state = backend.to_numpy(qaoa_function_of_layer(backend, nlayers)) assert_regression_fixture( - backend, final_state.real, f"tsp_layer{nlayers}_real.out", rtol=1e-3, atol=1e-2 + backend, final_state.real, f"tsp_layer{nlayers}_real.out", rtol=1e-3, atol=1e-5 ) assert_regression_fixture( - backend, final_state.imag, f"tsp_layer{nlayers}_imag.out", rtol=1e-3, atol=1e-2 + backend, final_state.imag, f"tsp_layer{nlayers}_imag.out", rtol=1e-3, atol=1e-5 ) From 5dc31599121f0c6ecfda0df17255aa06022f4c8c Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Tue, 14 Jan 2025 16:27:50 +0100 Subject: [PATCH 24/31] fix: replaced another einsum --- src/qibo/hamiltonians/models.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index b2a22d8b2d..1c25fb731a 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -377,6 +377,17 @@ def _multikron(matrix_list, backend): def _build_spin_model(nqubits, matrix, condition, backend): """Helper method for building nearest-neighbor spin model Hamiltonians.""" + h = sum( + reduce( + backend.np.kron, + ( + matrix if condition(i, j) else backend.matrices.I() + for j in range(nqubits) + ), + ) + for i in range(nqubits) + ) + """ indices = list(range(2 * nqubits)) even, odd = indices[::2], indices[1::2] lhs = zip( @@ -412,8 +423,9 @@ def _build_spin_model(nqubits, matrix, condition, backend): if backend.platform == "tensorflow": h = np.einsum(*einsum_args, rhs) else: - h = backend.np.einsum(*einsum_args, rhs) + h = backend.np.einsum(*einsum_args, rhs, optimize=True) h = backend.np.sum(backend.np.reshape(h, (nqubits, dim, dim)), axis=0) + """ return h From 1216b5a148a1c3d3c5ac679fc3d1275e31d3c0e9 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Tue, 14 Jan 2025 17:25:34 +0100 Subject: [PATCH 25/31] feat: added a more comprehensice test for to dense --- src/qibo/hamiltonians/hamiltonians.py | 22 ++++++++++++++-------- tests/test_hamiltonians_symbolic.py | 9 +++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 64ec34e079..1a87905b8c 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -395,7 +395,6 @@ def ground_state(self): def exp(self, a): return self.dense.exp(a) - # only useful for dense_from_form, which might not be needed in the end @cache def _get_symbol_matrix(self, term): """Calculates numerical matrix corresponding to symbolic expression. @@ -434,11 +433,18 @@ def _get_symbol_matrix(self, term): # symbolic op for power base, exponent = term.as_base_exp() matrix = self._get_symbol_matrix(base) - result = self.backend.np.linalg.matrix_power(matrix, exponent) + matrix_power = ( + np.linalg.matrix_power + if self.backend.name == "tensorflow" + else self.backend.np.linalg.matrix_power + ) + result = matrix_power(matrix, int(exponent)) elif isinstance(term, self._qiboSymbol): # if we have a Qibo symbol the matrix construction is # implemented in :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. + # I have to force the symbol's backend + term.backend = self.backend result = term.full_matrix(self.nqubits) elif term.is_number: @@ -455,8 +461,6 @@ def _get_symbol_matrix(self, term): return result - # not sure this is useful, it appears to be significantly slower than - # the from_terms counterpart def _calculate_dense_from_form(self) -> Hamiltonian: """Calculates equivalent Hamiltonian using symbolic form. @@ -465,8 +469,9 @@ def _calculate_dense_from_form(self) -> Hamiltonian: matrix = self._get_symbol_matrix(self.form) return Hamiltonian(self.nqubits, matrix, backend=self.backend) + """ def _calculate_dense_from_terms(self) -> Hamiltonian: - """Calculates equivalent Hamiltonian using the term representation.""" + "Calculates equivalent Hamiltonian using the term representation." matrix = 0 indices = list(range(2 * self.nqubits)) einsum = ( @@ -492,6 +497,7 @@ def _calculate_dense_from_terms(self) -> Hamiltonian: else self.backend.np.zeros(2 * (2**self.nqubits,)) ) return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant + """ def calculate_dense(self) -> Hamiltonian: log.warning( @@ -500,9 +506,9 @@ def calculate_dense(self) -> Hamiltonian: ) # calculate dense matrix directly using the form to avoid the # costly ``sympy.expand`` call - if len(self.terms) > 40: - return self._calculate_dense_from_form() - return self._calculate_dense_from_terms() + # if len(self.terms) > 40: + return self._calculate_dense_from_form() + # return self._calculate_dense_from_terms() def expectation(self, state, normalize=False): return Hamiltonian.expectation(self, state, normalize) diff --git a/tests/test_hamiltonians_symbolic.py b/tests/test_hamiltonians_symbolic.py index 7d4e7a7cbc..ebb99c1f73 100644 --- a/tests/test_hamiltonians_symbolic.py +++ b/tests/test_hamiltonians_symbolic.py @@ -49,6 +49,15 @@ def test_symbolic_hamiltonian_form_setter(backend): assert h.nqubits == 4 +def test_symbolic_hamiltonian_dense(backend): + target_matrix = backend.cast( + Z(0).matrix @ Z(0).matrix + X(0).matrix @ Y(0).matrix + np.eye(2) + ) + form = Z(0) ** 2 + X(0) * Y(0) + 1 + sham = SymbolicHamiltonian(form, nqubits=1, backend=backend) + backend.assert_allclose(sham.dense.matrix, target_matrix) + + @pytest.mark.parametrize("nqubits", [3, 4]) def test_symbolictfim_hamiltonian_to_dense(backend, nqubits): final_ham = SymbolicHamiltonian( From 8087c5809d7983de30592edf9e6c58e3e5855380 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Thu, 6 Feb 2025 14:55:09 +0100 Subject: [PATCH 26/31] build: using qibojit branch --- poetry.lock | 29 ++++++++++++----------------- pyproject.toml | 8 ++++---- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index 981db7210e..87cd634265 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,20 +171,20 @@ dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest [[package]] name = "beautifulsoup4" -version = "4.13.0" +version = "4.13.3" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" groups = ["docs", "tests"] markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "beautifulsoup4-4.13.0-py3-none-any.whl", hash = "sha256:9c4c3dfa67aba55f6cd03769c441b21e6a369797fd6766e4b4c6b3399aae2735"}, - {file = "beautifulsoup4-4.13.0.tar.gz", hash = "sha256:b6e5afb3a2b1472c8db751a92eabf7834e5c7099f990c5e4b35f1f16b60bae64"}, + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, ] [package.dependencies] soupsieve = ">1.2" -typing-extensions = "*" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -2580,15 +2580,15 @@ files = [ [[package]] name = "mako" -version = "1.3.8" +version = "1.3.9" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, - {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, + {file = "Mako-1.3.9-py3-none-any.whl", hash = "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1"}, + {file = "mako-1.3.9.tar.gz", hash = "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac"}, ] [package.dependencies] @@ -4585,8 +4585,8 @@ scipy = "^1.10.1" [package.source] type = "git" url = "https://github.com/qiboteam/qibojit.git" -reference = "HEAD" -resolved_reference = "cb89902d0378cf2d75861a5c9998d395411f598b" +reference = "cupy_identity_matrix" +resolved_reference = "ce4f083cd9d92e7b11484f8465269fb926e5bf0d" [[package]] name = "qiboml" @@ -4633,7 +4633,7 @@ cuda = ["cupy-cuda11x (>=11.6.0,<12.0.0)", "cuquantum-python-cu11 (>=23.3.0,<24. type = "git" url = "https://github.com/qiboteam/qibotn.git" reference = "HEAD" -resolved_reference = "820ddbdff509a8296f52dcb7b103eb6a391ab021" +resolved_reference = "5f9df4591ccd97911ee6123d94ca2664be370158" [[package]] name = "quimb" @@ -4866,7 +4866,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -4875,7 +4874,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -4884,7 +4882,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -4893,7 +4890,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -4902,7 +4898,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -6063,4 +6058,4 @@ torch = ["torch"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<3.13" -content-hash = "d3062c6fdf698d1aa52e4e73e6bf7261d01079bf34b1ed1e922010f731461c84" +content-hash = "7583873230f27c00f421652a4d53cb7e510b77a08713020450aea2e342bdff66" diff --git a/pyproject.toml b/pyproject.toml index e4af7fc956..00fb1b7156 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ ipython = "^8.10.0" qulacs = { version = "^0.6.4", markers = "(sys_platform == 'darwin' and python_version > '3.9') or sys_platform != 'darwin'" } seaborn = "^0.13.2" ipykernel = "^6.29.4" -qibojit = { git = "https://github.com/qiboteam/qibojit.git"} +qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix"} [tool.poetry.group.tests] optional = true @@ -70,7 +70,7 @@ pytest-cov = "^4.0.0" pylint = "3.1.0" matplotlib = "^3.7.0" torch = "^2.1.1,<2.4" -qibojit = { git = "https://github.com/qiboteam/qibojit.git"} +qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix"} qibotn = { git = "https://github.com/qiboteam/qibotn.git" } qiboml = { git = "https://github.com/qiboteam/qiboml.git" } stim = "^1.12.0" @@ -91,7 +91,7 @@ optional = true [tool.poetry.group.cuda11.dependencies] cupy-cuda11x = "^13.1.0" cuquantum-python-cu11 = "^23.3.0" -qibojit = { git = "https://github.com/qiboteam/qibojit.git" } +qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix" } qibotn = { git = "https://github.com/qiboteam/qibotn.git" } @@ -101,7 +101,7 @@ optional = true [tool.poetry.group.cuda12.dependencies] cupy-cuda12x = "^13.1.0" cuquantum-python-cu12 = "^23.3.0" -qibojit = { git = "https://github.com/qiboteam/qibojit.git"} +qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix"} qibotn = { git = "https://github.com/qiboteam/qibotn.git" } [tool.poetry.extras] From e80c0c7e0e243c3db0e9d858d74e3d2a60dc835c Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Thu, 6 Feb 2025 16:40:34 +0100 Subject: [PATCH 27/31] feat: some fixes --- src/qibo/hamiltonians/hamiltonians.py | 2 +- src/qibo/hamiltonians/terms.py | 9 ++--- tests/test_hamiltonians_from_symbols.py | 6 +-- tests/test_hamiltonians_symbolic.py | 4 +- tests/test_hamiltonians_terms.py | 54 +++++++++++++------------ tests/test_hamiltonians_trotter.py | 2 +- 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 1a87905b8c..3bdc9f76b3 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -451,7 +451,7 @@ def _get_symbol_matrix(self, term): # if the term is number we should return in the form of identity # matrix because in expressions like `1 + Z`, `1` is not correspond # to the float 1 but the identity operator (matrix) - result = complex(term) * self.backend.np.eye(2**self.nqubits, dtype=float) + result = complex(term) * self.backend.matrices.I(2**self.nqubits) else: raise_error( diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index a1d9015938..e6a4b3e4a4 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -46,7 +46,7 @@ def __init__(self, matrix, *q, backend: Optional[Backend] = None): self.target_qubits = tuple(q) self._gate = None self.hamiltonian = None - self._matrix = matrix + self._matrix = self.backend.cast(matrix) @property def matrix(self): @@ -62,9 +62,7 @@ def gate(self): def exp(self, x): """Matrix exponentiation of the term.""" - from scipy.linalg import expm - - return expm(-1j * x * self.matrix) + return self.backend.calculate_matrix_exp(x, self.matrix) def expgate(self, x): """:class:`qibo.gates.gates.Unitary` gate implementing the action of exp(term) on states.""" @@ -84,7 +82,7 @@ def merge(self, term): + f"qubits {term.target_qubits} to term on qubits {self.target_qubits}.", ) matrix = self.backend.np.kron( - term.matrix, self.backend.np.eye(2 ** (len(self) - len(term))) + term.matrix, self.backend.matrices.I(2 ** (len(self) - len(term))) ) matrix = self.backend.np.reshape(matrix, 2 * len(self) * (2,)) order = [] @@ -257,6 +255,7 @@ def copy(self): new.factors = self.factors new.matrix_map = self.matrix_map new.target_qubits = self.target_qubits + new.backend = self.backend return new def __mul__(self, x): diff --git a/tests/test_hamiltonians_from_symbols.py b/tests/test_hamiltonians_from_symbols.py index d3aecd88d0..f21a29b813 100644 --- a/tests/test_hamiltonians_from_symbols.py +++ b/tests/test_hamiltonians_from_symbols.py @@ -6,7 +6,7 @@ import pytest import sympy -from qibo import hamiltonians, matrices +from qibo import get_backend, hamiltonians, matrices from qibo.backends import NumpyBackend from qibo.quantum_info import random_hermitian from qibo.symbols import I, Symbol, X, Y, Z @@ -19,7 +19,7 @@ def test_symbols_pickling(symbol): new_symbol = pickle.loads(dumped_symbol) for attr in ("target_qubit", "name", "_gate"): assert getattr(symbol, attr) == getattr(new_symbol, attr) - np.testing.assert_allclose(symbol.matrix, new_symbol.matrix) + get_backend().assert_allclose(symbol.matrix, new_symbol.matrix) @pytest.mark.parametrize("nqubits", [4, 5]) @@ -133,7 +133,7 @@ def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype): backend.np.kron(backend.matrices.I(), backend.matrices.Z), backend.np.kron(backend.matrices.I(), backend.matrices.I()), ) - target_matrix -= 2 * backend.np.eye(2**4, dtype=target_matrix.dtype) + target_matrix -= 2 * backend.matrices.I(2**4) backend.assert_allclose(final_matrix, target_matrix) diff --git a/tests/test_hamiltonians_symbolic.py b/tests/test_hamiltonians_symbolic.py index ebb99c1f73..1b73abc50b 100644 --- a/tests/test_hamiltonians_symbolic.py +++ b/tests/test_hamiltonians_symbolic.py @@ -51,7 +51,9 @@ def test_symbolic_hamiltonian_form_setter(backend): def test_symbolic_hamiltonian_dense(backend): target_matrix = backend.cast( - Z(0).matrix @ Z(0).matrix + X(0).matrix @ Y(0).matrix + np.eye(2) + Z(0, backend=backend).matrix @ Z(0, backend=backend).matrix + + X(0, backend=backend).matrix @ Y(0, backend=backend).matrix + + backend.matrices.I() ) form = Z(0) ** 2 + X(0) * Y(0) + 1 sham = SymbolicHamiltonian(form, nqubits=1, backend=backend) diff --git a/tests/test_hamiltonians_terms.py b/tests/test_hamiltonians_terms.py index c3dbe6b2d3..14a79ae1ea 100644 --- a/tests/test_hamiltonians_terms.py +++ b/tests/test_hamiltonians_terms.py @@ -11,11 +11,11 @@ def test_hamiltonian_term_initialization(backend): """Test initialization and matrix assignment of ``HamiltonianTerm``.""" matrix = np.random.random((2, 2)) - term = terms.HamiltonianTerm(matrix, 0) + term = terms.HamiltonianTerm(matrix, 0, backend=backend) assert term.target_qubits == (0,) backend.assert_allclose(term.matrix, matrix) matrix = np.random.random((4, 4)) - term = terms.HamiltonianTerm(matrix, 2, 3) + term = terms.HamiltonianTerm(matrix, 2, 3, backend=backend) assert term.target_qubits == (2, 3) backend.assert_allclose(term.matrix, matrix) @@ -42,7 +42,7 @@ def test_hamiltonian_term_gates(backend): """Test gate application of ``HamiltonianTerm``.""" nqubits = 4 matrix = np.random.random((nqubits, nqubits)) - term = terms.HamiltonianTerm(matrix, 1, 2) + term = terms.HamiltonianTerm(matrix, 1, 2, backend=backend) gate = term.gate assert gate.target_qubits == (1, 2) backend.assert_allclose(gate.matrix(backend), matrix) @@ -62,8 +62,8 @@ def test_hamiltonian_term_exponentiation(backend): from scipy.linalg import expm # pylint: disable=C0415 matrix = np.random.random((2, 2)) - term = terms.HamiltonianTerm(matrix, 1) - exp_matrix = expm(-0.5j * matrix) + term = terms.HamiltonianTerm(matrix, 1, backend=backend) + exp_matrix = backend.cast(expm(-0.5j * matrix)) backend.assert_allclose(term.exp(0.5), exp_matrix) initial_state = random_statevector(4, backend=backend) @@ -76,7 +76,7 @@ def test_hamiltonian_term_exponentiation(backend): def test_hamiltonian_term_mul(backend): """Test scalar multiplication of ``HamiltonianTerm``.""" matrix = np.random.random((4, 4)) - term = terms.HamiltonianTerm(matrix, 0, 2) + term = terms.HamiltonianTerm(matrix, 0, 2, backend=backend) mterm = 2 * term assert mterm.target_qubits == term.target_qubits backend.assert_allclose(mterm.matrix, 2 * matrix) @@ -89,8 +89,8 @@ def test_hamiltonian_term_merge(backend): """Test ``HamiltonianTerm.merge``.""" matrix1 = np.random.random((2, 2)) matrix2 = np.random.random((4, 4)) - term1 = terms.HamiltonianTerm(matrix1, 1) - term2 = terms.HamiltonianTerm(matrix2, 0, 1) + term1 = terms.HamiltonianTerm(matrix1, 1, backend=backend) + term2 = terms.HamiltonianTerm(matrix2, 0, 1, backend=backend) mterm = term2.merge(term1) target_matrix = np.kron(np.eye(2), matrix1) + matrix2 assert mterm.target_qubits == (0, 1) @@ -104,12 +104,12 @@ def test_symbolic_term_creation(backend): from qibo.symbols import X, Y expression = X(0) * Y(1) * X(1) - term = terms.SymbolicTerm(2, expression) + term = terms.SymbolicTerm(2, expression, backend=backend) assert term.target_qubits == (0, 1) assert len(term.matrix_map) == 2 - backend.assert_allclose(term.matrix_map.get(0)[0], matrices.X) - backend.assert_allclose(term.matrix_map.get(1)[0], matrices.Y) - backend.assert_allclose(term.matrix_map.get(1)[1], matrices.X) + backend.assert_allclose(term.matrix_map.get(0)[0], backend.matrices.X) + backend.assert_allclose(term.matrix_map.get(1)[0], backend.matrices.Y) + backend.assert_allclose(term.matrix_map.get(1)[1], backend.matrices.X) def test_symbolic_term_with_power_creation(backend): @@ -117,14 +117,14 @@ def test_symbolic_term_with_power_creation(backend): from qibo.symbols import X, Z expression = X(0) ** 4 * Z(1) ** 2 * X(2) - term = terms.SymbolicTerm(2, expression) + term = terms.SymbolicTerm(2, expression, backend=backend) assert term.target_qubits == (2,) assert len(term.matrix_map) == 1 assert term.coefficient == 2 - backend.assert_allclose(term.matrix_map.get(2), [matrices.X]) + backend.assert_allclose(term.matrix_map.get(2), [backend.matrices.X]) -def test_symbolic_term_with_imag_creation(backend): +def test_symbolic_term_with_imag_creation(): """Test creating ``SymbolicTerm`` from sympy expression that contains imaginary coefficients.""" from qibo.symbols import Y @@ -139,10 +139,10 @@ def test_symbolic_term_matrix(backend): from qibo.symbols import X, Y, Z expression = X(0) * Y(1) * Z(2) * X(1) - term = terms.SymbolicTerm(2, expression) + term = terms.SymbolicTerm(2, expression, backend=backend) assert term.target_qubits == (0, 1, 2) target_matrix = np.kron(matrices.X, matrices.Y @ matrices.X) - target_matrix = 2 * np.kron(target_matrix, matrices.Z) + target_matrix = backend.cast(2 * np.kron(target_matrix, matrices.Z)) backend.assert_allclose(term.matrix, target_matrix) @@ -151,9 +151,11 @@ def test_symbolic_term_mul(backend): from qibo.symbols import X, Y, Z expression = Y(2) * Z(3) * X(2) * X(3) - term = terms.SymbolicTerm(1, expression) + term = terms.SymbolicTerm(1, expression, backend=backend) assert term.target_qubits == (2, 3) - target_matrix = np.kron(matrices.Y @ matrices.X, matrices.Z @ matrices.X) + target_matrix = backend.cast( + np.kron(matrices.Y @ matrices.X, matrices.Z @ matrices.X) + ) backend.assert_allclose(term.matrix, target_matrix) backend.assert_allclose((2 * term).matrix, 2 * target_matrix) backend.assert_allclose((term * 3).matrix, 3 * target_matrix) @@ -191,10 +193,10 @@ def test_symbolic_term_merge(backend): from qibo.symbols import X, Z matrix = np.random.random((4, 4)) - term1 = terms.HamiltonianTerm(matrix, 0, 1) - term2 = terms.SymbolicTerm(1, X(0) * Z(1)) + term1 = terms.HamiltonianTerm(matrix, 0, 1, backend=backend) + term2 = terms.SymbolicTerm(1, X(0) * Z(1), backend=backend) term = term1.merge(term2) - target_matrix = matrix + np.kron(matrices.X, matrices.Z) + target_matrix = backend.cast(matrix + np.kron(matrices.X, matrices.Z)) backend.assert_allclose(term.matrix, target_matrix) @@ -218,13 +220,13 @@ def test_term_group_to_term(backend): from qibo.symbols import X, Z matrix = np.random.random((8, 8)) - term1 = terms.HamiltonianTerm(matrix, 0, 1, 3) - term2 = terms.SymbolicTerm(1, X(0) * Z(3)) - term3 = terms.SymbolicTerm(2, X(1)) + term1 = terms.HamiltonianTerm(matrix, 0, 1, 3, backend=backend) + term2 = terms.SymbolicTerm(1, X(0) * Z(3), backend=backend) + term3 = terms.SymbolicTerm(2, X(1), backend=backend) group = terms.TermGroup(term1) group.append(term2) group.append(term3) matrix2 = np.kron(np.kron(matrices.X, np.eye(2)), matrices.Z) matrix3 = np.kron(np.kron(np.eye(2), matrices.X), np.eye(2)) - target_matrix = matrix + matrix2 + 2 * matrix3 + target_matrix = backend.cast(matrix + matrix2 + 2 * matrix3) backend.assert_allclose(group.term.matrix, target_matrix) diff --git a/tests/test_hamiltonians_trotter.py b/tests/test_hamiltonians_trotter.py index 844d59e10e..a6855b82f8 100644 --- a/tests/test_hamiltonians_trotter.py +++ b/tests/test_hamiltonians_trotter.py @@ -98,7 +98,7 @@ def test_trotter_hamiltonian_matmul(backend, nqubits, normalize): def test_symbolic_hamiltonian_circuit_different_dts(backend): """Issue: https://github.com/qiboteam/qibo/issues/1357.""" - ham = hamiltonians.SymbolicHamiltonian(symbols.Z(0)) + ham = hamiltonians.SymbolicHamiltonian(symbols.Z(0), backend=backend) a = ham.circuit(0.1) b = ham.circuit(0.1) matrix1 = ham.circuit(0.2).unitary(backend) From 73d2686347fd582c10a1e77f7d77bff38e86265e Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:30:54 +0100 Subject: [PATCH 28/31] Update src/qibo/hamiltonians/hamiltonians.py Co-authored-by: Alessandro Candido --- src/qibo/hamiltonians/hamiltonians.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 3bdc9f76b3..368f0d0398 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -426,7 +426,7 @@ def _get_symbol_matrix(self, term): factors = term.as_ordered_factors() result = reduce( self.backend.np.matmul, - [self._get_symbol_matrix(subterm) for subterm in factors], + (self._get_symbol_matrix(subterm) for subterm in factors), ) elif isinstance(term, sympy.Pow): From 617788e0c0cd5f8997fa7fbb5b382bf87442fa9f Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 12 Feb 2025 09:46:51 +0100 Subject: [PATCH 29/31] fix: apply suggestions --- examples/falqon/README.md | 2 +- src/qibo/hamiltonians/hamiltonians.py | 218 ++++++++++-------------- src/qibo/solvers.py | 2 +- tests/test_hamiltonians_from_symbols.py | 15 +- 4 files changed, 99 insertions(+), 138 deletions(-) diff --git a/examples/falqon/README.md b/examples/falqon/README.md index feca37acba..af2c0157f0 100644 --- a/examples/falqon/README.md +++ b/examples/falqon/README.md @@ -31,7 +31,7 @@ The `FALQON` class behaves similarly to the `QAOA` one. It admits the following - `accelerators`: Dictionary of devices to use for distributed execution. See `qibo.core.distcircuit.DistributedCircuit` for more details. This option is available only when ``hamiltonian`` - is a `qibo.abstractions.hamiltonians.TrotterHamiltonian`. + is a `qibo.abstractions.hamiltonians.SymbolicHamiltonian`. - `memory_device`: Name of device where the full state will be saved. Relevant only for distributed execution (when ``accelerators`` is given). diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 368f0d0398..aeef9c1123 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -1,5 +1,6 @@ """Module defining Hamiltonian classes.""" +import operator from functools import cache, cached_property, reduce from itertools import chain from math import prod @@ -9,9 +10,10 @@ import sympy from qibo.backends import Backend, _check_backend -from qibo.config import EINSUM_CHARS, log, raise_error +from qibo.config import log, raise_error from qibo.hamiltonians.abstract import AbstractHamiltonian -from qibo.symbols import Z +from qibo.hamiltonians.terms import SymbolicTerm +from qibo.symbols import Symbol, Z class Hamiltonian(AbstractHamiltonian): @@ -172,100 +174,118 @@ def energy_fluctuation(self, state): average_h2 = self.backend.calculate_expectation_state(h2, state, normalize=True) return self.backend.np.sqrt(self.backend.np.abs(average_h2 - energy**2)) - def __add__(self, o): - if isinstance(o, self.__class__): - if self.nqubits != o.nqubits: + def __add__(self, other): + if isinstance(other, self.__class__): + if self.nqubits != other.nqubits: raise_error( RuntimeError, "Only hamiltonians with the same number of qubits can be added.", ) - new_matrix = self.matrix + o.matrix - elif isinstance(o, self.backend.numeric_types) or isinstance( - o, self.backend.tensor_types + new_matrix = self.matrix + other.matrix + elif isinstance(other, self.backend.numeric_types) or isinstance( + other, self.backend.tensor_types ): - new_matrix = self.matrix + o * self.eye() + new_matrix = self.matrix + other * self.eye() else: raise_error( NotImplementedError, - f"Hamiltonian addition to {type(o)} not implemented.", + f"Hamiltonian addition to {type(other)} not implemented.", ) return self.__class__(self.nqubits, new_matrix, backend=self.backend) - def __sub__(self, o): - if isinstance(o, self.__class__): - if self.nqubits != o.nqubits: + def __sub__(self, other): + if isinstance(other, self.__class__): + if self.nqubits != other.nqubits: raise_error( RuntimeError, "Only hamiltonians with the same number of qubits can be subtracted.", ) - new_matrix = self.matrix - o.matrix - elif isinstance(o, self.backend.numeric_types): - new_matrix = self.matrix - o * self.eye() + new_matrix = self.matrix - other.matrix + elif isinstance(other, self.backend.numeric_types): + new_matrix = self.matrix - other * self.eye() else: raise_error( NotImplementedError, - f"Hamiltonian subtraction to {type(o)} not implemented.", + f"Hamiltonian subtraction to {type(other)} not implemented.", ) return self.__class__(self.nqubits, new_matrix, backend=self.backend) - def __rsub__(self, o): - if isinstance(o, self.__class__): # pragma: no cover + def __rsub__(self, other): + if isinstance(other, self.__class__): # pragma: no cover # impractical case because it will be handled by `__sub__` - if self.nqubits != o.nqubits: + if self.nqubits != other.nqubits: raise_error( RuntimeError, "Only hamiltonians with the same number of qubits can be added.", ) - new_matrix = o.matrix - self.matrix - elif isinstance(o, self.backend.numeric_types): - new_matrix = o * self.eye() - self.matrix + new_matrix = other.matrix - self.matrix + elif isinstance(other, self.backend.numeric_types): + new_matrix = other * self.eye() - self.matrix else: raise_error( NotImplementedError, - f"Hamiltonian subtraction to {type(o)} not implemented.", + f"Hamiltonian subtraction to {type(other)} not implemented.", ) return self.__class__(self.nqubits, new_matrix, backend=self.backend) - def __mul__(self, o): - if isinstance(o, self.backend.tensor_types): - o = complex(o) - elif not isinstance(o, self.backend.numeric_types): + def __mul__(self, other): + if isinstance(other, self.backend.tensor_types): + other = complex(other) + elif not isinstance(other, self.backend.numeric_types): raise_error( NotImplementedError, - f"Hamiltonian multiplication to {type(o)} not implemented.", + f"Hamiltonian multiplication to {type(other)} not implemented.", ) - new_matrix = self.matrix * o + new_matrix = self.matrix * other r = self.__class__(self.nqubits, new_matrix, backend=self.backend) - o = self.backend.cast(o) + other = self.backend.cast(other) if self._eigenvalues is not None: - if self.backend.np.real(o) >= 0: # TODO: check for side effects K.qnp - r._eigenvalues = o * self._eigenvalues + if self.backend.np.real(other) >= 0: # TODO: check for side effects K.qnp + r._eigenvalues = other * self._eigenvalues elif not self.backend.is_sparse(self.matrix): axis = (0,) if (self.backend.platform == "pytorch") else 0 - r._eigenvalues = o * self.backend.np.flip(self._eigenvalues, axis) + r._eigenvalues = other * self.backend.np.flip(self._eigenvalues, axis) if self._eigenvectors is not None: - if self.backend.np.real(o) > 0: # TODO: see above + if self.backend.np.real(other) > 0: # TODO: see above r._eigenvectors = self._eigenvectors - elif o == 0: + elif other == 0: r._eigenvectors = self.eye(int(self._eigenvectors.shape[0])) return r - def __matmul__(self, o): - if isinstance(o, self.__class__): + def __matmul__(self, other): + if isinstance(other, self.__class__): matrix = self.backend.calculate_hamiltonian_matrix_product( - self.matrix, o.matrix + self.matrix, other.matrix ) return self.__class__(self.nqubits, matrix, backend=self.backend) - if isinstance(o, self.backend.tensor_types): - return self.backend.calculate_hamiltonian_state_product(self.matrix, o) + if isinstance(other, self.backend.tensor_types): + return self.backend.calculate_hamiltonian_state_product(self.matrix, other) raise_error( NotImplementedError, - f"Hamiltonian matmul to {type(o)} not implemented.", + f"Hamiltonian matmul to {type(other)} not implemented.", ) +def _calculate_nqubits_from_form(form): + """Calculate number of qubits in the system described by the given + Hamiltonian formula + """ + nqubits = 0 + for symbol in form.free_symbols: + if isinstance(symbol, Symbol): + q = symbol.target_qubit + else: + raise_error( + RuntimeError, + f"Symbol {symbol} is not a ``qibo.symbols.Symbol``, you can define a custom symbol for {symbol} by subclassing ``qibo.symbols.Symbol``.", + ) + if q > nqubits: + nqubits = q + return nqubits + 1 + + class SymbolicHamiltonian(AbstractHamiltonian): """Hamiltonian based on a symbolic representation. @@ -305,14 +325,10 @@ def __init__( self._form = form self.constant = 0 # used only when we perform calculations using ``_terms`` - from qibo.symbols import Symbol # pylint: disable=import-outside-toplevel - - self._qiboSymbol = Symbol # also used in ``self._get_symbol_matrix`` - self.backend = _check_backend(backend) self.nqubits = ( - self._calculate_nqubits_from_form(form) if nqubits is None else nqubits + _calculate_nqubits_from_form(form) if nqubits is None else nqubits ) @cached_property @@ -324,23 +340,6 @@ def dense(self) -> "MatrixHamiltonian": def form(self): return self._form - def _calculate_nqubits_from_form(self, form): - """Calculate number of qubits in the system described by the given - Hamiltonian formula - """ - nqubits = 0 - for symbol in form.free_symbols: - if isinstance(symbol, self._qiboSymbol): - q = symbol.target_qubit - else: - raise_error( - RuntimeError, - f"Symbol {symbol} is not a ``qibo.symbols.Symbol``, you can define a custom symbol for {symbol} by subclassing ``qibo.symbols.Symbol``.", - ) - if q > nqubits: - nqubits = q - return nqubits + 1 - @form.setter def form(self, form): # Check that given form is a ``sympy`` expression @@ -350,7 +349,7 @@ def form(self, form): f"Symbolic Hamiltonian should be a ``sympy`` expression but is {type(form)}.", ) self._form = form - self.nqubits = self._calculate_nqubits_from_form(form) + self.nqubits = _calculate_nqubits_from_form(form) @cached_property def terms(self): @@ -359,9 +358,6 @@ def terms(self): Terms will be objects of type :class:`qibo.core.terms.HamiltonianTerm`. """ # Calculate terms based on ``self.form`` - from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel - SymbolicTerm, - ) self.constant = 0.0 @@ -440,7 +436,7 @@ def _get_symbol_matrix(self, term): ) result = matrix_power(matrix, int(exponent)) - elif isinstance(term, self._qiboSymbol): + elif isinstance(term, Symbol): # if we have a Qibo symbol the matrix construction is # implemented in :meth:`qibo.core.terms.SymbolicTerm.full_matrix`. # I have to force the symbol's backend @@ -469,36 +465,6 @@ def _calculate_dense_from_form(self) -> Hamiltonian: matrix = self._get_symbol_matrix(self.form) return Hamiltonian(self.nqubits, matrix, backend=self.backend) - """ - def _calculate_dense_from_terms(self) -> Hamiltonian: - "Calculates equivalent Hamiltonian using the term representation." - matrix = 0 - indices = list(range(2 * self.nqubits)) - einsum = ( - np.einsum - if self.backend.platform == "tensorflow" - else self.backend.np.einsum - ) - for term in self.terms: - ntargets = len(term.target_qubits) - tmat = self.backend.np.reshape(term.matrix, 2 * ntargets * (2,)) - n = self.nqubits - ntargets - emat = self.backend.np.reshape( - self.backend.np.eye(2**n, dtype=tmat.dtype), 2 * n * (2,) - ) - gen = lambda x: (indices[i + x] for i in term.target_qubits) - tc = list(chain(gen(0), gen(self.nqubits))) - ec = list(c for c in indices if c not in tc) - matrix += einsum(tmat, tc, emat, ec, indices) - - matrix = ( - self.backend.np.reshape(matrix, 2 * (2**self.nqubits,)) - if len(self.terms) > 0 - else self.backend.np.zeros(2 * (2**self.nqubits,)) - ) - return Hamiltonian(self.nqubits, matrix, backend=self.backend) + self.constant - """ - def calculate_dense(self) -> Hamiltonian: log.warning( "Calculating the dense form of a symbolic Hamiltonian. " @@ -506,9 +472,7 @@ def calculate_dense(self) -> Hamiltonian: ) # calculate dense matrix directly using the form to avoid the # costly ``sympy.expand`` call - # if len(self.terms) > 40: return self._calculate_dense_from_form() - # return self._calculate_dense_from_terms() def expectation(self, state, normalize=False): return Hamiltonian.expectation(self, state, normalize) @@ -621,40 +585,42 @@ def expectation_from_samples(self, freq: dict, qubit_map: list = None) -> float: ) return self.backend.np.sum(expvals @ counts.T) + self.constant.real - def _compose(self, o, operator): + def _compose(self, other, operator): form = self._form - if isinstance(o, self.__class__): - if self.nqubits != o.nqubits: + if isinstance(other, self.__class__): + if self.nqubits != other.nqubits: raise_error( RuntimeError, "Only hamiltonians with the same number of qubits can be composed.", ) - if o._form is not None: - form = operator(form, o._form) if form is not None else o._form + if other._form is not None: + form = operator(form, other._form) if form is not None else other._form - elif isinstance(o, (self.backend.numeric_types, self.backend.tensor_types)): - form = operator(form, complex(o)) if form is not None else complex(o) + elif isinstance(other, (self.backend.numeric_types, self.backend.tensor_types)): + form = ( + operator(form, complex(other)) if form is not None else complex(other) + ) else: raise_error( NotImplementedError, - f"SymbolicHamiltonian composition to {type(o)} not implemented.", + f"SymbolicHamiltonian composition to {type(other)} not implemented.", ) return self.__class__(form=form, nqubits=self.nqubits, backend=self.backend) - def __add__(self, o): - return self._compose(o, lambda x, y: x + y) + def __add__(self, other): + return self._compose(other, operator.add) - def __sub__(self, o): - return self._compose(o, lambda x, y: x - y) + def __sub__(self, other): + return self._compose(other, operator.sub) - def __rsub__(self, o): - return self._compose(o, lambda x, y: y - x) + def __rsub__(self, other): + return self._compose(other, lambda x, y: y - x) - def __mul__(self, o): - return self._compose(o, lambda x, y: y * x) + def __mul__(self, other): + return self._compose(other, operator.mul) def apply_gates(self, state, density_matrix=False): """Applies gates corresponding to the Hamiltonian terms. @@ -675,19 +641,19 @@ def apply_gates(self, state, density_matrix=False): total += self.constant * state return total - def __matmul__(self, o): + def __matmul__(self, other): """Matrix multiplication with other Hamiltonians or state vectors.""" - if isinstance(o, self.__class__): - return o * self + if isinstance(other, self.__class__): + return other * self - if isinstance(o, self.backend.tensor_types): - rank = len(tuple(o.shape)) + if isinstance(other, self.backend.tensor_types): + rank = len(tuple(other.shape)) if rank not in (1, 2): raise_error( NotImplementedError, f"Cannot multiply Hamiltonian with rank-{rank} tensor.", ) - state_qubits = int(np.log2(int(o.shape[0]))) + state_qubits = int(np.log2(int(other.shape[0]))) if state_qubits != self.nqubits: raise_error( ValueError, @@ -695,13 +661,13 @@ def __matmul__(self, o): + f"state of {state_qubits} qubits.", ) if rank == 1: # state vector - return self.apply_gates(o) + return self.apply_gates(other) - return self.apply_gates(o, density_matrix=True) + return self.apply_gates(other, density_matrix=True) raise_error( NotImplementedError, - f"Hamiltonian matmul to {type(o)} not implemented.", + f"Hamiltonian matmul to {type(other)} not implemented.", ) def circuit(self, dt, accelerators=None): diff --git a/src/qibo/solvers.py b/src/qibo/solvers.py index 69ab0d3622..2364364d19 100644 --- a/src/qibo/solvers.py +++ b/src/qibo/solvers.py @@ -44,7 +44,7 @@ class TrotterizedExponential(BaseSolver): Created automatically from the :class:`qibo.solvers.Exponential` if the given Hamiltonian object is a - :class:`qibo.hamiltonians.hamiltonians.TrotterHamiltonian`. + :class:`qibo.hamiltonians.hamiltonians.SymbolicHamiltonian`. """ def __init__(self, dt, hamiltonian): diff --git a/tests/test_hamiltonians_from_symbols.py b/tests/test_hamiltonians_from_symbols.py index f21a29b813..e56c0ab4ae 100644 --- a/tests/test_hamiltonians_from_symbols.py +++ b/tests/test_hamiltonians_from_symbols.py @@ -23,8 +23,7 @@ def test_symbols_pickling(symbol): @pytest.mark.parametrize("nqubits", [4, 5]) -@pytest.mark.parametrize("hamtype", ["symbolic"]) -def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype): +def test_tfim_hamiltonian_from_symbols(backend, nqubits): """Check creating TFIM Hamiltonian using sympy.""" h = 0.5 symham = sum( @@ -38,8 +37,7 @@ def test_tfim_hamiltonian_from_symbols(backend, nqubits, hamtype): backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("hamtype", ["symbolic"]) -def test_from_symbolic_with_power(backend, hamtype): +def test_from_symbolic_with_power(backend): """Check ``from_symbolic`` when the expression contains powers.""" npbackend = NumpyBackend() matrix = random_hermitian(2, backend=npbackend) @@ -63,8 +61,7 @@ def test_from_symbolic_with_power(backend, hamtype): backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("hamtype", ["symbolic"]) -def test_from_symbolic_with_complex_numbers(backend, hamtype): +def test_from_symbolic_with_complex_numbers(backend): """Check ``from_symbolic`` when the expression contains imaginary unit.""" symham = ( (1 + 2j) * X(0, backend=backend) * X(1, backend=backend) @@ -83,8 +80,7 @@ def test_from_symbolic_with_complex_numbers(backend, hamtype): @pytest.mark.parametrize("nqubits", [4, 5]) -@pytest.mark.parametrize("hamtype", ["symbolic"]) -def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype): +def test_x_hamiltonian_from_symbols(backend, nqubits): """Check creating sum(X) Hamiltonian using sympy.""" symham = -sum(X(i, backend=backend) for i in range(nqubits)) ham = hamiltonians.SymbolicHamiltonian(symham, backend=backend) @@ -93,8 +89,7 @@ def test_x_hamiltonian_from_symbols(backend, nqubits, hamtype): backend.assert_allclose(final_matrix, target_matrix) -@pytest.mark.parametrize("hamtype", ["symbolic"]) -def test_three_qubit_term_hamiltonian_from_symbols(backend, hamtype): +def test_three_qubit_term_hamiltonian_from_symbols(backend): """Check creating Hamiltonian with three-qubit interaction using sympy.""" symham = ( X(0, backend=backend) * Y(1, backend=backend) * Z(2, backend=backend) From bf50e9953571e4a475f5ce3455dace1505668e85 Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 12 Feb 2025 10:48:39 +0100 Subject: [PATCH 30/31] fix: changed qibojit dep + reverted a previous change --- poetry.lock | 357 +++++++++++++------------- pyproject.toml | 8 +- src/qibo/hamiltonians/hamiltonians.py | 2 +- 3 files changed, 184 insertions(+), 183 deletions(-) diff --git a/poetry.lock b/poetry.lock index 87cd634265..f1f5a2a83e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -788,75 +788,76 @@ test = ["altair", "baytune", "chocolate", "cmaes", "dask", "distributed", "kahyp [[package]] name = "coverage" -version = "7.6.10" +version = "7.6.12" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["tests"] markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, - {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, - {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, - {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, - {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, - {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, - {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, - {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, - {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, - {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, - {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, - {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, - {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, - {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, - {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, - {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, + {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, + {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, + {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, + {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, + {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, + {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, + {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, + {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, + {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, + {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, + {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, + {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, + {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, + {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, + {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, ] [package.dependencies] @@ -1487,63 +1488,63 @@ typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "fonttools" -version = "4.55.8" +version = "4.56.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" groups = ["cuda11", "cuda12", "docs", "tests"] markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "fonttools-4.55.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d11600f5343092697d7434f3bf77a393c7ae74be206fe30e577b9a195fd53165"}, - {file = "fonttools-4.55.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c96f2506ce1a0beeaa9595f9a8b7446477eb133f40c0e41fc078744c28149f80"}, - {file = "fonttools-4.55.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b5f05ef72e846e9f49ccdd74b9da4309901a4248434c63c1ee9321adcb51d65"}, - {file = "fonttools-4.55.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba45b637da80a262b55b7657aec68da2ac54b8ae7891cd977a5dbe5fd26db429"}, - {file = "fonttools-4.55.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:edcffaeadba9a334c1c3866e275d7dd495465e7dbd296f688901bdbd71758113"}, - {file = "fonttools-4.55.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b9f9fce3c9b2196e162182ec5db8af8eb3acd0d76c2eafe9fdba5f370044e556"}, - {file = "fonttools-4.55.8-cp310-cp310-win32.whl", hash = "sha256:f089e8da0990cfe2d67e81d9cf581ff372b48dc5acf2782701844211cd1f0eb3"}, - {file = "fonttools-4.55.8-cp310-cp310-win_amd64.whl", hash = "sha256:01ea3901b0802fc5f9e854f5aeb5bc27770dd9dd24c28df8f74ba90f8b3f5915"}, - {file = "fonttools-4.55.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95f5a1d4432b3cea6571f5ce4f4e9b25bf36efbd61c32f4f90130a690925d6ee"}, - {file = "fonttools-4.55.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d20f152de7625a0008ba1513f126daaaa0de3b4b9030aa72dd5c27294992260"}, - {file = "fonttools-4.55.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a3ff5bb95fd5a3962b2754f8435e6d930c84fc9e9921c51e802dddf40acd56"}, - {file = "fonttools-4.55.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99d4fd2b6d0a00c7336c8363fccc7a11eccef4b17393af75ca6e77cf93ff413"}, - {file = "fonttools-4.55.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d637e4d33e46619c79d1a6c725f74d71b574cd15fb5bbb9b6f3eba8f28363573"}, - {file = "fonttools-4.55.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0f38bfb6b7a39c4162c3eb0820a0bdf8e3bdd125cd54e10ba242397d15e32439"}, - {file = "fonttools-4.55.8-cp311-cp311-win32.whl", hash = "sha256:acfec948de41cd5e640d5c15d0200e8b8e7c5c6bb82afe1ca095cbc4af1188ee"}, - {file = "fonttools-4.55.8-cp311-cp311-win_amd64.whl", hash = "sha256:604c805b41241b4880e2dc86cf2d4754c06777371c8299799ac88d836cb18c3b"}, - {file = "fonttools-4.55.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63403ee0f2fa4e1de28e539f8c24f2bdca1d8ecb503fa9ea2d231d9f1e729809"}, - {file = "fonttools-4.55.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:302e1003a760b222f711d5ba6d1ad7fd5f7f713eb872cd6a3eb44390bc9770af"}, - {file = "fonttools-4.55.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e72a7816ff8a759be9ca36ca46934f8ccf4383711ef597d9240306fe1878cb8d"}, - {file = "fonttools-4.55.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c2b50b54e6e8b3564b232e57e8f58be217cf441cf0155745d9e44a76f9c30f"}, - {file = "fonttools-4.55.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7230f7590f9570d26ee903b6a4540274494e200fae978df0d9325b7b9144529"}, - {file = "fonttools-4.55.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:466a78984f0572305c3c48377f4e3f7f4e909f1209f45ef8e7041d5c8a744a56"}, - {file = "fonttools-4.55.8-cp312-cp312-win32.whl", hash = "sha256:243cbfc0b7cb1c307af40e321f8343a48d0a080bc1f9466cf2b5468f776ef108"}, - {file = "fonttools-4.55.8-cp312-cp312-win_amd64.whl", hash = "sha256:a19059aa892676822c1f05cb5a67296ecdfeb267fe7c47d4758f3e8e942c2b2a"}, - {file = "fonttools-4.55.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:332883b6280b9d90d2ba7e9e81be77cf2ace696161e60cdcf40cfcd2b3ed06fa"}, - {file = "fonttools-4.55.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6b8d7c149d47b47de7ec81763396c8266e5ebe2e0b14aa9c3ccf29e52260ab2f"}, - {file = "fonttools-4.55.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dfae7c94987149bdaa0388e6c937566aa398fa0eec973b17952350a069cff4e"}, - {file = "fonttools-4.55.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0fe12f06169af2fdc642d26a8df53e40adc3beedbd6ffedb19f1c5397b63afd"}, - {file = "fonttools-4.55.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f971aa5f50c22dc4b63a891503624ae2c77330429b34ead32f23c2260c5618cd"}, - {file = "fonttools-4.55.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:708cb17b2590b7f6c6854999df0039ff1140dda9e6f56d67c3599ba6f968fab5"}, - {file = "fonttools-4.55.8-cp313-cp313-win32.whl", hash = "sha256:cfe9cf30f391a0f2875247a3e5e44d8dcb61596e5cf89b360cdffec8a80e9961"}, - {file = "fonttools-4.55.8-cp313-cp313-win_amd64.whl", hash = "sha256:1e10efc8ee10d6f1fe2931d41bccc90cd4b872f2ee4ff21f2231a2c293b2dbf8"}, - {file = "fonttools-4.55.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9b6fcff4dc755b32faff955d989ee26394ddad3a90ea7d558db17a4633c8390c"}, - {file = "fonttools-4.55.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:02c41322e5bdcb484b61b776fcea150215c83619b39c96aa0b44d4fd87bb5574"}, - {file = "fonttools-4.55.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9164f44add0acec0f12fce682824c040dc52e483bfe3838c37142897150c8364"}, - {file = "fonttools-4.55.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2248ebfbcea0d0b3cb459d76a9f67f2eadc10ec0d07e9cadab8777d3f016bf2"}, - {file = "fonttools-4.55.8-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3461347016c94cb42b36caa907e11565878c4c2c375604f3651d11dc06d1ab3e"}, - {file = "fonttools-4.55.8-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:67df1c3935838fb9e56f227d7f506c9043b149a4a3b667bef17929c7a1114d19"}, - {file = "fonttools-4.55.8-cp38-cp38-win32.whl", hash = "sha256:cb121d6dd34625cece32234a5fa0359475bb118838b6b4295ffdb13b935edb04"}, - {file = "fonttools-4.55.8-cp38-cp38-win_amd64.whl", hash = "sha256:285c1ac10c160fbdff6d05358230e66c4f98cbbf271f3ec7eb34e967771543e8"}, - {file = "fonttools-4.55.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8abd135e427d88e461a4833c03cf96cfb9028c78c15d58123291f22398e25492"}, - {file = "fonttools-4.55.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65cb8f97eed7906dcf19bc2736b70c6239e9d7e77aad7c6110ba7239ae082e81"}, - {file = "fonttools-4.55.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:450c354c04a6e12a3db968e915fe05730f79ff3d39560947ef8ee6eaa2ab2212"}, - {file = "fonttools-4.55.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2232012a1502b2b8ab4c6bc1d3524bfe90238c0c1a50ac94a0a2085aa87a58a5"}, - {file = "fonttools-4.55.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d39f0c977639be0f9f5505d4c7c478236737f960c567a35f058649c056e41434"}, - {file = "fonttools-4.55.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:de78d6d0dbe32561ce059265437021f4746e56073c4799f0f1095828ae7232bd"}, - {file = "fonttools-4.55.8-cp39-cp39-win32.whl", hash = "sha256:bf4b5b3496ddfdd4e57112e77ec51f1ab388d35ac17322c1248addb2eb0d429a"}, - {file = "fonttools-4.55.8-cp39-cp39-win_amd64.whl", hash = "sha256:ccf8ae02918f431953d338db4d0a675a395faf82bab3a76025582cf32a2f3b7b"}, - {file = "fonttools-4.55.8-py3-none-any.whl", hash = "sha256:07636dae94f7fe88561f9da7a46b13d8e3f529f87fdb221b11d85f91eabceeb7"}, - {file = "fonttools-4.55.8.tar.gz", hash = "sha256:54d481d456dcd59af25d4a9c56b2c4c3f20e9620b261b84144e5950f33e8df17"}, + {file = "fonttools-4.56.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:331954d002dbf5e704c7f3756028e21db07097c19722569983ba4d74df014000"}, + {file = "fonttools-4.56.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d1613abd5af2f93c05867b3a3759a56e8bf97eb79b1da76b2bc10892f96ff16"}, + {file = "fonttools-4.56.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:705837eae384fe21cee5e5746fd4f4b2f06f87544fa60f60740007e0aa600311"}, + {file = "fonttools-4.56.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc871904a53a9d4d908673c6faa15689874af1c7c5ac403a8e12d967ebd0c0dc"}, + {file = "fonttools-4.56.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:38b947de71748bab150259ee05a775e8a0635891568e9fdb3cdd7d0e0004e62f"}, + {file = "fonttools-4.56.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86b2a1013ef7a64d2e94606632683f07712045ed86d937c11ef4dde97319c086"}, + {file = "fonttools-4.56.0-cp310-cp310-win32.whl", hash = "sha256:133bedb9a5c6376ad43e6518b7e2cd2f866a05b1998f14842631d5feb36b5786"}, + {file = "fonttools-4.56.0-cp310-cp310-win_amd64.whl", hash = "sha256:17f39313b649037f6c800209984a11fc256a6137cbe5487091c6c7187cae4685"}, + {file = "fonttools-4.56.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ef04bc7827adb7532be3d14462390dd71287644516af3f1e67f1e6ff9c6d6df"}, + {file = "fonttools-4.56.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ffda9b8cd9cb8b301cae2602ec62375b59e2e2108a117746f12215145e3f786c"}, + {file = "fonttools-4.56.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e993e8db36306cc3f1734edc8ea67906c55f98683d6fd34c3fc5593fdbba4c"}, + {file = "fonttools-4.56.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:003548eadd674175510773f73fb2060bb46adb77c94854af3e0cc5bc70260049"}, + {file = "fonttools-4.56.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd9825822e7bb243f285013e653f6741954d8147427aaa0324a862cdbf4cbf62"}, + {file = "fonttools-4.56.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b23d30a2c0b992fb1c4f8ac9bfde44b5586d23457759b6cf9a787f1a35179ee0"}, + {file = "fonttools-4.56.0-cp311-cp311-win32.whl", hash = "sha256:47b5e4680002ae1756d3ae3b6114e20aaee6cc5c69d1e5911f5ffffd3ee46c6b"}, + {file = "fonttools-4.56.0-cp311-cp311-win_amd64.whl", hash = "sha256:14a3e3e6b211660db54ca1ef7006401e4a694e53ffd4553ab9bc87ead01d0f05"}, + {file = "fonttools-4.56.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6f195c14c01bd057bc9b4f70756b510e009c83c5ea67b25ced3e2c38e6ee6e9"}, + {file = "fonttools-4.56.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa760e5fe8b50cbc2d71884a1eff2ed2b95a005f02dda2fa431560db0ddd927f"}, + {file = "fonttools-4.56.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54a45d30251f1d729e69e5b675f9a08b7da413391a1227781e2a297fa37f6d2"}, + {file = "fonttools-4.56.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661a8995d11e6e4914a44ca7d52d1286e2d9b154f685a4d1f69add8418961563"}, + {file = "fonttools-4.56.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d94449ad0a5f2a8bf5d2f8d71d65088aee48adbe45f3c5f8e00e3ad861ed81a"}, + {file = "fonttools-4.56.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f59746f7953f69cc3290ce2f971ab01056e55ddd0fb8b792c31a8acd7fee2d28"}, + {file = "fonttools-4.56.0-cp312-cp312-win32.whl", hash = "sha256:bce60f9a977c9d3d51de475af3f3581d9b36952e1f8fc19a1f2254f1dda7ce9c"}, + {file = "fonttools-4.56.0-cp312-cp312-win_amd64.whl", hash = "sha256:300c310bb725b2bdb4f5fc7e148e190bd69f01925c7ab437b9c0ca3e1c7cd9ba"}, + {file = "fonttools-4.56.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f20e2c0dfab82983a90f3d00703ac0960412036153e5023eed2b4641d7d5e692"}, + {file = "fonttools-4.56.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f36a0868f47b7566237640c026c65a86d09a3d9ca5df1cd039e30a1da73098a0"}, + {file = "fonttools-4.56.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62b4c6802fa28e14dba010e75190e0e6228513573f1eeae57b11aa1a39b7e5b1"}, + {file = "fonttools-4.56.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05d1f07eb0a7d755fbe01fee1fd255c3a4d3730130cf1bfefb682d18fd2fcea"}, + {file = "fonttools-4.56.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0073b62c3438cf0058488c002ea90489e8801d3a7af5ce5f7c05c105bee815c3"}, + {file = "fonttools-4.56.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cad98c94833465bcf28f51c248aaf07ca022efc6a3eba750ad9c1e0256d278"}, + {file = "fonttools-4.56.0-cp313-cp313-win32.whl", hash = "sha256:d0cb73ccf7f6d7ca8d0bc7ea8ac0a5b84969a41c56ac3ac3422a24df2680546f"}, + {file = "fonttools-4.56.0-cp313-cp313-win_amd64.whl", hash = "sha256:62cc1253827d1e500fde9dbe981219fea4eb000fd63402283472d38e7d8aa1c6"}, + {file = "fonttools-4.56.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3fd3fccb7b9adaaecfa79ad51b759f2123e1aba97f857936ce044d4f029abd71"}, + {file = "fonttools-4.56.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193b86e9f769320bc98ffdb42accafb5d0c8c49bd62884f1c0702bc598b3f0a2"}, + {file = "fonttools-4.56.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e81c1cc80c1d8bf071356cc3e0e25071fbba1c75afc48d41b26048980b3c771"}, + {file = "fonttools-4.56.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9270505a19361e81eecdbc2c251ad1e1a9a9c2ad75fa022ccdee533f55535dc"}, + {file = "fonttools-4.56.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53f5e9767978a4daf46f28e09dbeb7d010319924ae622f7b56174b777258e5ba"}, + {file = "fonttools-4.56.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9da650cb29bc098b8cfd15ef09009c914b35c7986c8fa9f08b51108b7bc393b4"}, + {file = "fonttools-4.56.0-cp38-cp38-win32.whl", hash = "sha256:965d0209e6dbdb9416100123b6709cb13f5232e2d52d17ed37f9df0cc31e2b35"}, + {file = "fonttools-4.56.0-cp38-cp38-win_amd64.whl", hash = "sha256:654ac4583e2d7c62aebc6fc6a4c6736f078f50300e18aa105d87ce8925cfac31"}, + {file = "fonttools-4.56.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca7962e8e5fc047cc4e59389959843aafbf7445b6c08c20d883e60ced46370a5"}, + {file = "fonttools-4.56.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1af375734018951c31c0737d04a9d5fd0a353a0253db5fbed2ccd44eac62d8c"}, + {file = "fonttools-4.56.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:442ad4122468d0e47d83bc59d0e91b474593a8c813839e1872e47c7a0cb53b10"}, + {file = "fonttools-4.56.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf4f8d2a30b454ac682e12c61831dcb174950c406011418e739de592bbf8f76"}, + {file = "fonttools-4.56.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96a4271f63a615bcb902b9f56de00ea225d6896052c49f20d0c91e9f43529a29"}, + {file = "fonttools-4.56.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d38642ca2dddc7ae992ef5d026e5061a84f10ff2b906be5680ab089f55bb8"}, + {file = "fonttools-4.56.0-cp39-cp39-win32.whl", hash = "sha256:2d351275f73ebdd81dd5b09a8b8dac7a30f29a279d41e1c1192aedf1b6dced40"}, + {file = "fonttools-4.56.0-cp39-cp39-win_amd64.whl", hash = "sha256:d6ca96d1b61a707ba01a43318c9c40aaf11a5a568d1e61146fafa6ab20890793"}, + {file = "fonttools-4.56.0-py3-none-any.whl", hash = "sha256:1088182f68c303b50ca4dc0c82d42083d176cba37af1937e1a976a31149d4d14"}, + {file = "fonttools-4.56.0.tar.gz", hash = "sha256:a114d1567e1a1586b7e9e7fc2ff686ca542a82769a296cef131e4c4af51e58f4"}, ] [package.extras] @@ -3390,15 +3391,15 @@ files = [ [[package]] name = "optuna" -version = "4.2.0" +version = "4.2.1" description = "A hyperparameter optimization framework" optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "optuna-4.2.0-py3-none-any.whl", hash = "sha256:539a47fda44104eb2b879c883d4640704b482df416ae99d0f24505f6e2280c28"}, - {file = "optuna-4.2.0.tar.gz", hash = "sha256:6ce226483317cf73df133d9ddf300091f946c0ad0dbdbc50b28891049b8400f2"}, + {file = "optuna-4.2.1-py3-none-any.whl", hash = "sha256:6d38199013441d3f70fac27136e05c0188c5f4ec3848db708ac311cbdeb30dbf"}, + {file = "optuna-4.2.1.tar.gz", hash = "sha256:2ecd74cdc8aaf5dda1f2b9e267999bab21def9a33e0a4f415ecae0c468c401e0"}, ] [package.dependencies] @@ -4585,8 +4586,8 @@ scipy = "^1.10.1" [package.source] type = "git" url = "https://github.com/qiboteam/qibojit.git" -reference = "cupy_identity_matrix" -resolved_reference = "ce4f083cd9d92e7b11484f8465269fb926e5bf0d" +reference = "HEAD" +resolved_reference = "6f9f63ad8259d7bddc14d280deb61a463d618b25" [[package]] name = "qiboml" @@ -5311,70 +5312,70 @@ test = ["pytest"] [[package]] name = "sqlalchemy" -version = "2.0.37" +version = "2.0.38" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main"] markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"}, - {file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"}, - {file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-win32.whl", hash = "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297"}, + {file = "SQLAlchemy-2.0.38-cp310-cp310-win_amd64.whl", hash = "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-win32.whl", hash = "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba"}, + {file = "SQLAlchemy-2.0.38-cp311-cp311-win_amd64.whl", hash = "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-win32.whl", hash = "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725"}, + {file = "SQLAlchemy-2.0.38-cp312-cp312-win_amd64.whl", hash = "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-win32.whl", hash = "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120"}, + {file = "SQLAlchemy-2.0.38-cp313-cp313-win_amd64.whl", hash = "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:40310db77a55512a18827488e592965d3dec6a3f1e3d8af3f8243134029daca3"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3043375dd5bbcb2282894cbb12e6c559654c67b5fffb462fda815a55bf93f7"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70065dfabf023b155a9c2a18f573e47e6ca709b9e8619b2e04c54d5bcf193178"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c058b84c3b24812c859300f3b5abf300daa34df20d4d4f42e9652a4d1c48c8a4"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0398361acebb42975deb747a824b5188817d32b5c8f8aba767d51ad0cc7bb08d"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-win32.whl", hash = "sha256:a2bc4e49e8329f3283d99840c136ff2cd1a29e49b5624a46a290f04dff48e079"}, + {file = "SQLAlchemy-2.0.38-cp37-cp37m-win_amd64.whl", hash = "sha256:9cd136184dd5f58892f24001cdce986f5d7e96059d004118d5410671579834a4"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:665255e7aae5f38237b3a6eae49d2358d83a59f39ac21036413fab5d1e810578"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:92f99f2623ff16bd4aaf786ccde759c1f676d39c7bf2855eb0b540e1ac4530c8"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa498d1392216fae47eaf10c593e06c34476ced9549657fca713d0d1ba5f7248"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9afbc3909d0274d6ac8ec891e30210563b2c8bdd52ebbda14146354e7a69373"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:57dd41ba32430cbcc812041d4de8d2ca4651aeefad2626921ae2a23deb8cd6ff"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3e35d5565b35b66905b79ca4ae85840a8d40d31e0b3e2990f2e7692071b179ca"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-win32.whl", hash = "sha256:f0d3de936b192980209d7b5149e3c98977c3810d401482d05fb6d668d53c1c63"}, + {file = "SQLAlchemy-2.0.38-cp38-cp38-win_amd64.whl", hash = "sha256:3868acb639c136d98107c9096303d2d8e5da2880f7706f9f8c06a7f961961149"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07258341402a718f166618470cde0c34e4cec85a39767dce4e24f61ba5e667ea"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a826f21848632add58bef4f755a33d45105d25656a0c849f2dc2df1c71f6f50"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:386b7d136919bb66ced64d2228b92d66140de5fefb3c7df6bd79069a269a7b06"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f2951dc4b4f990a4b394d6b382accb33141d4d3bd3ef4e2b27287135d6bdd68"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bf312ed8ac096d674c6aa9131b249093c1b37c35db6a967daa4c84746bc1bc9"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6db316d6e340f862ec059dc12e395d71f39746a20503b124edc255973977b728"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-win32.whl", hash = "sha256:c09a6ea87658695e527104cf857c70f79f14e9484605e205217aae0ec27b45fc"}, + {file = "SQLAlchemy-2.0.38-cp39-cp39-win_amd64.whl", hash = "sha256:12f5c9ed53334c3ce719155424dc5407aaa4f6cadeb09c5b627e06abb93933a1"}, + {file = "SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753"}, + {file = "sqlalchemy-2.0.38.tar.gz", hash = "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb"}, ] [package.dependencies] @@ -6058,4 +6059,4 @@ torch = ["torch"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<3.13" -content-hash = "7583873230f27c00f421652a4d53cb7e510b77a08713020450aea2e342bdff66" +content-hash = "d3062c6fdf698d1aa52e4e73e6bf7261d01079bf34b1ed1e922010f731461c84" diff --git a/pyproject.toml b/pyproject.toml index 00fb1b7156..79e7335ffa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ ipython = "^8.10.0" qulacs = { version = "^0.6.4", markers = "(sys_platform == 'darwin' and python_version > '3.9') or sys_platform != 'darwin'" } seaborn = "^0.13.2" ipykernel = "^6.29.4" -qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix"} +qibojit = { git = "https://github.com/qiboteam/qibojit.git" } [tool.poetry.group.tests] optional = true @@ -70,7 +70,7 @@ pytest-cov = "^4.0.0" pylint = "3.1.0" matplotlib = "^3.7.0" torch = "^2.1.1,<2.4" -qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix"} +qibojit = { git = "https://github.com/qiboteam/qibojit.git" } qibotn = { git = "https://github.com/qiboteam/qibotn.git" } qiboml = { git = "https://github.com/qiboteam/qiboml.git" } stim = "^1.12.0" @@ -91,7 +91,7 @@ optional = true [tool.poetry.group.cuda11.dependencies] cupy-cuda11x = "^13.1.0" cuquantum-python-cu11 = "^23.3.0" -qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix" } +qibojit = { git = "https://github.com/qiboteam/qibojit.git" } qibotn = { git = "https://github.com/qiboteam/qibotn.git" } @@ -101,7 +101,7 @@ optional = true [tool.poetry.group.cuda12.dependencies] cupy-cuda12x = "^13.1.0" cuquantum-python-cu12 = "^23.3.0" -qibojit = { git = "https://github.com/qiboteam/qibojit.git", branch = "cupy_identity_matrix"} +qibojit = { git = "https://github.com/qiboteam/qibojit.git" } qibotn = { git = "https://github.com/qiboteam/qibotn.git" } [tool.poetry.extras] diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index aeef9c1123..7eaa0a9472 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -620,7 +620,7 @@ def __rsub__(self, other): return self._compose(other, lambda x, y: y - x) def __mul__(self, other): - return self._compose(other, operator.mul) + return self._compose(other, lambda x, y: y * x) def apply_gates(self, state, density_matrix=False): """Applies gates corresponding to the Hamiltonian terms. From 3544ebe26e09ab47e3de0432162cb4dce3d0daea Mon Sep 17 00:00:00 2001 From: BrunoLiegiBastonLiegi Date: Wed, 12 Feb 2025 14:29:40 +0100 Subject: [PATCH 31/31] fix: removed commented einsum implementations --- src/qibo/hamiltonians/models.py | 55 --------------------------------- src/qibo/hamiltonians/terms.py | 37 +--------------------- 2 files changed, 1 insertion(+), 91 deletions(-) diff --git a/src/qibo/hamiltonians/models.py b/src/qibo/hamiltonians/models.py index 1c25fb731a..2cf7251e45 100644 --- a/src/qibo/hamiltonians/models.py +++ b/src/qibo/hamiltonians/models.py @@ -356,22 +356,6 @@ def _multikron(matrix_list, backend): Returns: ndarray: Kronecker product of all matrices in ``matrix_list``. """ - # TO DO: check whether this scales better on gpu - """ - indices = list(range(2 * len(matrix_list))) - even, odd = indices[::2], indices[1::2] - lhs = zip(even, odd) - rhs = even + odd - einsum_args = [item for pair in zip(matrix_list, lhs) for item in pair] - dim = 2 ** len(matrix_list) - if backend.platform != "tensorflow": - h = backend.np.einsum(*einsum_args, rhs) - else: - h = np.einsum(*einsum_args, rhs) - h = backend.np.sum(backend.np.reshape(h, (-1, dim, dim)), axis=0) - return h - """ - # reduce appears to be faster especially when matrix_list is long return reduce(backend.np.kron, matrix_list) @@ -387,45 +371,6 @@ def _build_spin_model(nqubits, matrix, condition, backend): ) for i in range(nqubits) ) - """ - indices = list(range(2 * nqubits)) - even, odd = indices[::2], indices[1::2] - lhs = zip( - nqubits - * [ - len(indices), - ], - even, - odd, - ) - rhs = ( - [ - len(indices), - ] - + even - + odd - ) - columns = [ - backend.np.reshape( - backend.np.concatenate( - [ - matrix if condition(i, j) else backend.matrices.I() - for i in range(nqubits) - ], - axis=0, - ), - (nqubits, 2, 2), - ) - for j in range(nqubits) - ] - einsum_args = [item for pair in zip(columns, lhs) for item in pair] - dim = 2**nqubits - if backend.platform == "tensorflow": - h = np.einsum(*einsum_args, rhs) - else: - h = backend.np.einsum(*einsum_args, rhs, optimize=True) - h = backend.np.sum(backend.np.reshape(h, (nqubits, dim, dim)), axis=0) - """ return h diff --git a/src/qibo/hamiltonians/terms.py b/src/qibo/hamiltonians/terms.py index e6a4b3e4a4..3ceca1f9fa 100644 --- a/src/qibo/hamiltonians/terms.py +++ b/src/qibo/hamiltonians/terms.py @@ -4,7 +4,7 @@ import numpy as np import sympy -from qibo import gates, symbols +from qibo import gates from qibo.backends import Backend, _check_backend from qibo.config import raise_error from qibo.symbols import I, X, Y, Z @@ -208,41 +208,6 @@ def matrix(self): where ``ntargets`` is the number of qubits included in the factors of this term. """ - # from qibo.hamiltonians.models import _multikron - - """ - einsum = ( - self.backend.np.einsum - if self.backend.platform != "tensorflow" - else np.einsum - ) - - # find the max number of matrices for each qubit - max_len = max(len(v) for v in self.matrix_map.values()) - nqubits = len(self.matrix_map) - # pad each list with identity to max_len - matrix = [] - for qubit, matrices in self.matrix_map.items(): - matrix.append( - self.backend.np.concatenate( - self.matrix_map[qubit] - + (max_len - len(matrices)) * [self.backend.np.eye(2)], - axis=0, - ) - ) - # separate in `max_len`-column tensors of shape (`nqubits`, 2, 2) - matrix = self.backend.np.transpose( - self.backend.np.reshape( - self.backend.np.concatenate(matrix, axis=0), (nqubits, max_len, 2, 2) - ), - (1, 0, 2, 3), - ) - indices = list(zip(max_len * [0], range(1, max_len + 1), range(2, max_len + 2))) - lhs = zip(matrix, indices) - lhs = [el for item in lhs for el in item] - matrix = einsum(*lhs, (0, 1, max_len + 1)) - return self.coefficient * _multikron(matrix, self.backend) - """ matrices = [ reduce(self.backend.np.matmul, self.matrix_map.get(q)) for q in self.target_qubits