Skip to content

Commit

Permalink
feat: splitted the circuit and frequency cases
Browse files Browse the repository at this point in the history
  • Loading branch information
BrunoLiegiBastonLiegi committed Oct 16, 2024
1 parent 761eaa8 commit 4cdfe6b
Showing 1 changed file with 86 additions and 58 deletions.
144 changes: 86 additions & 58 deletions src/qibo/hamiltonians/hamiltonians.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,83 +553,77 @@ def calculate_dense(self):
def expectation(self, state, normalize=False):
return Hamiltonian.expectation(self, state, normalize)

def expectation_from_samples(
self, freq: Union[dict, "Circuit"], qubit_map: dict = None, nshots: int = None
):
def _exp_from_circuit(self, circuit: dict, qubit_map: dict, nshots: int = None):
"""
Calculate the expectation value starting from the samples. 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.
Calculate the expecation value from a circuit.
This allows for non diagonal observables. Each term of the observable is
treated separately, by measuring in the correct basis and re-executing the
circuit.
Args:
freq (dict | :class:`qibo.models.Circuit`): either the ``dict`` of the
frequencies of the samples or a :class:`qibo.models.Circuit` where to
extract the samples from. A :class:`qibo.models.Circuit` is needed in
case the observable is not diagonal in the computational basis.
qubit_map (dict): Qubit map.
nshots (int, optional): Number of shots for the expecation value
calculation. This is used only when a :class:`qibo.models.Circuit`
is passed as input.
freq (dict): input frequencies.
qubit_map (dict): qubit map.
Returns:
(float) the computed expectation value.
(float): the calculated expectation value
"""
from qibo import Circuit, gates

terms = self.terms
for term in terms:
if len(term.factors) != len(set(term.factors)):
raise_error(NotImplementedError, "Z^k is not implemented since Z^2=I.")
if nshots is None:
nshots = 1000
rotated_circuits = []
coefficients = []
for term in self.terms:
coefficients.append(term.coefficient)
Z_observable = SymbolicHamiltonian(
prod([Z(q) for q in term.target_qubits]),
nqubits=circuit.nqubits,
backend=self.backend,
)
measurements = [
gates.M(factor.target_qubit, basis=factor.gate.__class__)
for factor in term.factors
]
circ_copy = circuit.copy(True)
[circ_copy.add(m) for m in measurements]
rotated_circuits.append(circ_copy)
frequencies = [
result.frequencies()
for result in self.backend.execute_circuits(rotated_circuits, nshots=nshots)
]
return sum(
[
c * Z_observable._exp_from_freq(f, qubit_map)
for c, f in zip(coefficients, frequencies)
]
)

def _exp_from_freq(self, freq: dict, qubit_map: dict):
"""
Calculate the expecation value from some frequencies.
The observable has to be diagonal in the computational basis.
Args:
freq (dict): input frequencies.
qubit_map (dict): 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) and not isinstance(freq, Circuit):
if not isinstance(factor, Z):
raise_error(
NotImplementedError, "Observable is not a Z Pauli string."
)

if isinstance(freq, Circuit):
if nshots is None:
nshots = 1000
rotated_circuits = []
coefficients = []
for term in terms:
coefficients.append(term.coefficient)
Z_observable = SymbolicHamiltonian(
prod([Z(q) for q in term.target_qubits]),
nqubits=freq.nqubits,
backend=self.backend,
)
measurements = [
gates.M(factor.target_qubit, basis=factor.gate.__class__)
for factor in term.factors
]
circ_copy = freq.copy(True)
[circ_copy.add(m) for m in measurements]
rotated_circuits.append(circ_copy)
frequencies = [
result.frequencies()
for result in self.backend.execute_circuits(
rotated_circuits, nshots=nshots
)
]
return sum(
[
c * Z_observable.expectation_from_samples(f, qubit_map)
for c, f in zip(coefficients, frequencies)
]
)

keys = list(freq.keys())
counts = self.backend.cast(list(freq.values()), self.backend.precision) / sum(
freq.values()
)
qubits = []
for term in terms:
for term in self.terms:
qubits_term = []
for k in term.target_qubits:
qubits_term.append(k)
Expand All @@ -649,6 +643,40 @@ def expectation_from_samples(
expval += expval_q * self.terms[j].coefficient.real
return expval + self.constant.real

def expectation_from_samples(
self, data: Union[dict, "Circuit"], qubit_map: dict = None, nshots: int = None
):
"""
Calculate the expectation value starting from the samples. 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.
Args:
freq (dict | :class:`qibo.models.Circuit`): either the ``dict`` of the
frequencies of the samples or a :class:`qibo.models.Circuit` where to
extract the samples from. A :class:`qibo.models.Circuit` is needed in
case the observable is not diagonal in the computational basis.
qubit_map (dict): qubit map.
nshots (int, optional): number of shots for the expecation value
calculation. This is used only when a :class:`qibo.models.Circuit`
is passed as input.
Returns:
(float) the computed expectation value.
"""
for term in self.terms:
if len(term.factors) != len(set(term.factors)):
raise_error(NotImplementedError, "Z^k is not implemented since Z^2=I.")

if isinstance(data, dict):
return self._exp_from_freq(data, qubit_map)
return self._exp_from_circuit(data, qubit_map, nshots)

def __add__(self, o):
if isinstance(o, self.__class__):
if self.nqubits != o.nqubits:
Expand Down

0 comments on commit 4cdfe6b

Please sign in to comment.