refactor: rewrite the expectation method using Qibo built in features

This commit is contained in:
MatteoRobbiati
2025-02-10 13:17:19 +01:00
parent afc6c5d196
commit bb76e2b64d

View File

@@ -198,7 +198,7 @@ class QMatchaTeaBackend(QibotnBackend, NumpyBackend):
operators = qmatchatea.QCOperators() operators = qmatchatea.QCOperators()
observables = qtealeaves.observables.TNObservables() observables = qtealeaves.observables.TNObservables()
# Add custom observable # Add custom observable
observables += self._qiboobs_to_qmatchaobs(hamiltonian_form=observable) observables += self._qiboobs_to_qmatchaobs(hamiltonian=observable)
results = qmatchatea.run_simulation( results = qmatchatea.run_simulation(
circ=circuit, circ=circuit,
@@ -225,92 +225,68 @@ class QMatchaTeaBackend(QibotnBackend, NumpyBackend):
) )
return qiskit_circuit return qiskit_circuit
def _qiboobs_to_qmatchaobs( def _qiboobs_to_qmatchaobs(self, hamiltonian, observable_name="custom_hamiltonian"):
self, hamiltonian_form, observable_name="custom_hamiltonian" """
): Convert a Qibo SymbolicHamiltonian into a qmatchatea TNObsWeightedSum observable.
"""Convert a Qibo-style symbolic expression (e.g. '2.0*Y2*Z0 + Z0*Z2')
into a qmatchatea ``TNObsWeightedSum`` observable.
The parsing logic here assumes: The SymbolicHamiltonian is expected to have a collection of terms, where each term has:
- Each term may have an optional leading coefficient (defaults to 1.0). - `coefficient`: A numeric value.
- Each operator is a single-letter from [XYZI] plus a qubit index (e.g., 'X2' means X on qubit 2). - `factors`: A list of factors, each a string such as "X2" or "Z0", representing an operator
- Terms are separated by '+' (and optionally '-') signs. If negative, we parse it as a negative coefficient. and the qubit it acts on.
Args: Args:
hamiltonian_form: e.g. 'Y2*Z0 + 2.5*Z0*Z2' hamiltonian (qibo.SymbolicHamiltonian): The symbolic Hamiltonian containing the terms.
observable_name (str): A name for the resulting ``TNObsWeightedSum``. observable_name (str): The name for the resulting TNObsWeightedSum observable.
Returns: Returns:
TNObsWeightedSum: An observable suitable for qmatchatea. TNObsWeightedSum: An observable suitable for use with qmatchatea.
""" """
hamiltonian_form = str(hamiltonian_form)
# Collect all the simple terms in the string and preserve the sign
# whenever a coefficient is negative
hamiltonian_form = hamiltonian_form.replace("-", "+-")
raw_terms = [t.strip() for t in hamiltonian_form.split("+") if t.strip()]
coeff_list = [] coeff_list = []
tensor_product_obs = None
# Regex for leading coefficient: e.g. "2.5*" or "-0.3*" # Regex to split an operator factor (e.g., "X2" -> operator "X", qubit 2)
# group(1) will capture the numeric part, group(0) includes the sign if present factor_pattern = re.compile(r"([^\d]+)(\d+)")
leading_coeff_pattern = re.compile(r"^([+-]?\d+(\.\d+)?)\*")
for i, hamiltonian_term in enumerate(raw_terms): # Iterate over each term in the symbolic Hamiltonian
# Set default coefficient to 1.0 for i, term in enumerate(hamiltonian.terms):
coeff = 1.0 # Store the term's coefficient
# Look for a leading numeric coefficient coeff_list.append(term.coefficient)
match = leading_coeff_pattern.search(hamiltonian_term)
operator_names = []
acting_on_qubits = []
# Process each factor in the term
for factor in term.factors:
# Assume each factor is a string like "Y2" or "Z0"
match = factor_pattern.match(str(factor))
if match: if match:
# Parse that coefficient operator_name = match.group(1)
coeff = float(match.group(1)) qubit_index = int(match.group(2))
# Remove that portion from the term string so only operators remain
hamiltonian_term = leading_coeff_pattern.sub(
"", hamiltonian_term, count=1
)
# Now isolate the single terms in the product (if there are more than 1)
operators_qubits = hamiltonian_term.split("*")
# Prepare lists for qmatchatea
operator_names, acting_on_qubits = [], []
# Each sub-term is e.g. "Y2", so operator = "Y", qubit = 2
# We assume the operator is the single letter, the rest is the qubit index
for operator in operators_qubits:
operator = operator.strip()
# Use a regex to split the operator and the qubit index
match = re.match(r"([^\d]+)(\d+)", operator)
if match:
operator_name = match.group(
1
) # All characters before the number (e.g., 'XYZ')
qubit_index = int(match.group(2)) # The number part (e.g., 2)
operator_names.append(operator_name) operator_names.append(operator_name)
acting_on_qubits.append([qubit_index]) acting_on_qubits.append([qubit_index])
# Build collection of tensor product operators (tpo)
if i == 0:
tpo = qtealeaves.observables.TNObsTensorProduct(
name=f"{hamiltonian_term}",
operators=operator_names,
sites=acting_on_qubits,
)
else: else:
tpo += qtealeaves.observables.TNObsTensorProduct( raise ValueError(
name=f"{hamiltonian_term}", f"Factor '{str(factor)}' does not match the expected format."
)
# Create a TNObsTensorProduct for this term.
term_tensor_prod = qtealeaves.observables.TNObsTensorProduct(
name=f"term_{i}",
operators=operator_names, operators=operator_names,
sites=acting_on_qubits, sites=acting_on_qubits,
) )
# And also keep track of coefficients
coeff_list.append(coeff)
# Combine everything into a WeightedSum # Combine tensor products from each term
if tensor_product_obs is None:
tensor_product_obs = term_tensor_prod
else:
tensor_product_obs += term_tensor_prod
# Combine all terms into a weighted sum observable
obs_sum = qtealeaves.observables.TNObsWeightedSum( obs_sum = qtealeaves.observables.TNObsWeightedSum(
name=observable_name, tp_operators=tpo, coeffs=coeff_list, use_itpo=False name=observable_name,
tp_operators=tensor_product_obs,
coeffs=coeff_list,
use_itpo=False,
) )
return obs_sum return obs_sum