From 12c67f0c42aefd0cff1096ae18aa2c0dc863e4f2 Mon Sep 17 00:00:00 2001 From: mattia-robbiano Date: Wed, 18 Feb 2026 09:32:34 +0100 Subject: [PATCH 1/5] refactor: use direct TN contraction for symbolic expectation values --- src/qibotn/backends/quimb.py | 49 ++++++++++-------------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/src/qibotn/backends/quimb.py b/src/qibotn/backends/quimb.py index 3ee200d..4c33c1a 100644 --- a/src/qibotn/backends/quimb.py +++ b/src/qibotn/backends/quimb.py @@ -210,7 +210,8 @@ def exp_value_observable_symbolic( This method takes a Qibo circuit, converts it to a Quimb tensor network circuit, and evaluates the expectation value of a Hamiltonian specified by three lists of strings: operators, sites, and coefficients. The expectation value is computed by summing the contributions from each term in the Hamiltonian, where each term's - expectation is calculated using Quimb's `local_expectation` function. + expectation is calculated applying gates to the tensor network representation of the circuit and contracting it + with the respective bra. Each operator string must act on all different qubits, i.e., for each term, the corresponding sites tuple must contain unique qubit indices. Example: operators_list = ['xyz', 'xyz'], sites_list = [(1,2,3), (1,2,3)], coeffs_list = [1, 2] @@ -230,6 +231,7 @@ def exp_value_observable_symbolic( float The real part of the expectation value of the Hamiltonian on the given circuit state. """ + # Validate that no term acts multiple times on the same qubit (no repeated indices in a sites tuple) for sites in sites_list: if len(sites) != len(set(sites)): @@ -238,29 +240,25 @@ def exp_value_observable_symbolic( f"Invalid Hamiltonian term sites {sites}: repeated qubit indices are not allowed " "within a single term (e.g. (0,0,0) is invalid).", ) - quimb_circuit = self._qibo_circuit_to_quimb( + + ket = self._qibo_circuit_to_quimb( circuit, quimb_circuit_type=self.circuit_ansatz, gate_opts={"max_bond": self.max_bond_dimension, "cutoff": self.svd_cutoff}, - ) + ).psi + + bra = ket.copy().H expectation_value = 0.0 for opstr, sites, coeff in zip(operators_list, sites_list, coeffs_list): - - ops = self._string_to_quimb_operator(opstr) coeff = coeff.real - - exp_values = quimb_circuit.local_expectation( - ops, - where=sites, - backend=self.backend, - optimize=self.contractions_optimizer, - simplify_sequence="R", - ) - + for label, site in zip(opstr, sites): + op_matrix = qu.pauli(label.upper()) + ket.gate_(op_matrix, site) + exp_values = (bra & ket).contract(optimize="auto-hq").real expectation_value = expectation_value + coeff * exp_values - return self.real(expectation_value) + return expectation_value def _qibo_circuit_to_quimb( @@ -313,26 +311,6 @@ def _qibo_circuit_to_quimb( return circ -def _string_to_quimb_operator(self, op_str): - """ - Convert a Pauli string (e.g. 'xzy') to a Quimb operator using '&' chaining. - - Parameters - ---------- - op_str : str - A string like 'xzy', where each character is one of 'x', 'y', 'z', 'i'. - - Returns - ------- - qu_op : quimb.Qarray - The corresponding Quimb operator. - """ - op_str = op_str.lower() - op = qu.pauli(op_str[0]) - for c in op_str[1:]: - op = op & qu.pauli(c) - return op - CLASSES_ROOTS = {"numpy": "Numpy", "torch": "PyTorch", "jax": "Jax"} @@ -343,7 +321,6 @@ METHODS = { "execute_circuit": execute_circuit, "exp_value_observable_symbolic": exp_value_observable_symbolic, "_qibo_circuit_to_quimb": _qibo_circuit_to_quimb, - "_string_to_quimb_operator": _string_to_quimb_operator, "circuit_ansatz": circuit_ansatz, } From 48d8b5b9967b5ac5334632a6f9571a04d9357ff1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:37:01 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibotn/backends/quimb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qibotn/backends/quimb.py b/src/qibotn/backends/quimb.py index 4c33c1a..0eefbad 100644 --- a/src/qibotn/backends/quimb.py +++ b/src/qibotn/backends/quimb.py @@ -252,7 +252,7 @@ def exp_value_observable_symbolic( expectation_value = 0.0 for opstr, sites, coeff in zip(operators_list, sites_list, coeffs_list): coeff = coeff.real - for label, site in zip(opstr, sites): + for label, site in zip(opstr, sites): op_matrix = qu.pauli(label.upper()) ket.gate_(op_matrix, site) exp_values = (bra & ket).contract(optimize="auto-hq").real @@ -311,7 +311,6 @@ def _qibo_circuit_to_quimb( return circ - CLASSES_ROOTS = {"numpy": "Numpy", "torch": "PyTorch", "jax": "Jax"} METHODS = { From 58930223b098f2d696a0b40a542a382cc3ec96e9 Mon Sep 17 00:00:00 2001 From: mattia-robbiano Date: Wed, 18 Feb 2026 10:05:58 +0100 Subject: [PATCH 3/5] fix: forgot to reset state object in computing different observables --- src/qibotn/backends/quimb.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/qibotn/backends/quimb.py b/src/qibotn/backends/quimb.py index 4c33c1a..6bc97a3 100644 --- a/src/qibotn/backends/quimb.py +++ b/src/qibotn/backends/quimb.py @@ -241,22 +241,22 @@ def exp_value_observable_symbolic( "within a single term (e.g. (0,0,0) is invalid).", ) - ket = self._qibo_circuit_to_quimb( + circuit_tn = self._qibo_circuit_to_quimb( circuit, quimb_circuit_type=self.circuit_ansatz, gate_opts={"max_bond": self.max_bond_dimension, "cutoff": self.svd_cutoff}, ).psi - - bra = ket.copy().H + bra = circuit_tn.copy().H expectation_value = 0.0 for opstr, sites, coeff in zip(operators_list, sites_list, coeffs_list): + ket = circuit_tn.copy() coeff = coeff.real for label, site in zip(opstr, sites): op_matrix = qu.pauli(label.upper()) ket.gate_(op_matrix, site) - exp_values = (bra & ket).contract(optimize="auto-hq").real - expectation_value = expectation_value + coeff * exp_values + exp_value = (bra & ket).contract(optimize="auto-hq").real + expectation_value = expectation_value + coeff * exp_value return expectation_value From cf8216bc052872c736d3e49692f81388dad86561 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:12:13 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibotn/backends/quimb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qibotn/backends/quimb.py b/src/qibotn/backends/quimb.py index 6bc97a3..3b38e3a 100644 --- a/src/qibotn/backends/quimb.py +++ b/src/qibotn/backends/quimb.py @@ -252,7 +252,7 @@ def exp_value_observable_symbolic( for opstr, sites, coeff in zip(operators_list, sites_list, coeffs_list): ket = circuit_tn.copy() coeff = coeff.real - for label, site in zip(opstr, sites): + for label, site in zip(opstr, sites): op_matrix = qu.pauli(label.upper()) ket.gate_(op_matrix, site) exp_value = (bra & ket).contract(optimize="auto-hq").real @@ -311,7 +311,6 @@ def _qibo_circuit_to_quimb( return circ - CLASSES_ROOTS = {"numpy": "Numpy", "torch": "PyTorch", "jax": "Jax"} METHODS = { From f118201037b75320cc963d2dabc942caec41406c Mon Sep 17 00:00:00 2001 From: mattia-robbiano Date: Fri, 27 Feb 2026 16:12:55 +0200 Subject: [PATCH 5/5] fix: setting user defined optimizer --- src/qibotn/backends/quimb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/backends/quimb.py b/src/qibotn/backends/quimb.py index 3b38e3a..d9dc3fa 100644 --- a/src/qibotn/backends/quimb.py +++ b/src/qibotn/backends/quimb.py @@ -255,7 +255,7 @@ def exp_value_observable_symbolic( for label, site in zip(opstr, sites): op_matrix = qu.pauli(label.upper()) ket.gate_(op_matrix, site) - exp_value = (bra & ket).contract(optimize="auto-hq").real + exp_value = (bra & ket).contract(optimize=self.contraction_optimizer).real expectation_value = expectation_value + coeff * exp_value return expectation_value