Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Desired measurement result #263

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
18c56bf
Ported translate modules
KrzysztofB-1qbit Nov 22, 2022
54d32ed
Added the measurement saving functionality
KrzysztofB-1qbit Nov 25, 2022
3914752
commit modified file adapt.ipynb
KrzysztofB-1qbit Nov 25, 2022
4e29836
Corrected tests
KrzysztofB-1qbit Nov 25, 2022
1a415e2
Full port of the desired_meas_result option
KrzysztofB-1qbit Nov 29, 2022
c4d7734
Fixes after first review
KrzysztofB-1qbit Nov 29, 2022
e872d06
Changed Backend.marginal_frequencies -> post_selection.split_frequenc…
KrzysztofB-1qbit Nov 30, 2022
5a8a8af
Changed Backend.marginal_frequencies -> post_selection.split_frequenc…
KrzysztofB-1qbit Nov 30, 2022
093423a
Merged with save_mid_circuit_meas
KrzysztofB-1qbit Nov 30, 2022
788fb5a
Moved split_frequency_dict call to Backend, fixes to target_cirq
KrzysztofB-1qbit Dec 5, 2022
93b631f
Merged with save_mid_circuit_meas, some cleanup in cirq
KrzysztofB-1qbit Dec 6, 2022
3f354fe
Last merge with latest version of save_mid_circuit_meas
KrzysztofB-1qbit Jan 9, 2023
c4f0a02
First revision
KrzysztofB-1qbit Jan 18, 2023
23dd49a
Second revision
KrzysztofB-1qbit Jan 30, 2023
e56db46
updated desired_meas_result to new format
JamesB-1qbit Feb 7, 2023
d90984c
temp skip of dmet notebook, takes too long
JamesB-1qbit Feb 7, 2023
fb3cd29
Merge branch 'develop' into desired_meas_result
JamesB-1qbit Feb 7, 2023
15b9f4a
fix conformance issue
JamesB-1qbit Feb 7, 2023
bc6cb9d
fix for errors
JamesB-1qbit Feb 7, 2023
136a71f
Merge branch 'develop' into desired_meas_result
JamesB-1qbit Feb 10, 2023
99716c1
added more tests, cleaned up comments
JamesB-1qbit Feb 13, 2023
da4b8fe
reverted previous test change
JamesB-1qbit Feb 14, 2023
3e8b1f0
Merge branch 'develop' into desired_meas_result
JamesB-1qbit Feb 14, 2023
5387461
added functions to qiskit simulate to simplify code, other minor fixes
JamesB-1qbit Feb 14, 2023
e7b0cd5
added comments, shifted function positions
JamesB-1qbit Feb 15, 2023
c983a26
move vairable declaration up
JamesB-1qbit Feb 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 59 additions & 19 deletions tangelo/linq/target/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ def simulate_circuit(self):
"""
pass

def simulate(self, source_circuit, return_statevector=False, initial_statevector=None, save_mid_circuit_meas=False):
def simulate(self, source_circuit, return_statevector=False, initial_statevector=None,
desired_meas_result=None, save_mid_circuit_meas=False):
"""Perform state preparation corresponding to the input circuit on the
target backend, return the frequencies of the different observables, and
either the statevector or None depending on the availability of the
Expand All @@ -191,6 +192,9 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector
if available.
initial_statevector (list/array) : A valid statevector in the format
supported by the target backend.
desired_meas_result (str): The binary string of the desired measurement.
Must have the same length as the number of MEASURE gates in source_circuit
If self.n_shots is set, statistics are performed assuming self.n_shots successes
save_mid_circuit_meas (bool): Save mid-circuit measurement results to
self.mid_circuit_meas_freqs. All measurements will be saved to
self.all_frequencies, with keys of length (n_meas + n_qubits).
Expand All @@ -205,7 +209,19 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector
numpy.array: The statevector, if available for the target backend
and requested by the user (if not, set to None).
"""
if source_circuit.is_mixed_state and not self.n_shots:
n_meas = source_circuit.counts.get("MEASURE", 0)

if desired_meas_result is not None:
if not isinstance(desired_meas_result, str) or len(desired_meas_result) != n_meas:
raise ValueError("desired_meas result is not a string with the same length as the number of measurements"
"in the circuit.")
save_mid_circuit_meas = True
elif save_mid_circuit_meas and return_statevector:
if self.n_shots != 1:
raise ValueError("The combination of save_mid_circuit_meas and return_statevector without specifying desired_meas_result"
"is only valid for self.n_shots=1 as the result is a mixed state otherwise, "
f"but you requested n_shots={self.n_shots}.")
elif source_circuit.is_mixed_state and not self.n_shots:
raise ValueError("Circuit contains MEASURE instruction, and is assumed to prepare a mixed state."
"Please set the n_shots attribute to an appropriate value.")

Expand All @@ -224,24 +240,26 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector
statevector[0] = 1.0
return (frequencies, statevector) if return_statevector else (frequencies, None)

# For mid-circuit measurements post-process the result
KrzysztofB-1qbit marked this conversation as resolved.
Show resolved Hide resolved
if save_mid_circuit_meas:
# TODO: refactor to break a circular import. May involve by relocating get_xxx_oneterm functions
alexfleury-sb marked this conversation as resolved.
Show resolved Hide resolved
from tangelo.toolboxes.post_processing.post_selection import split_frequency_dict

(all_frequencies, statevector) = self.simulate_circuit(source_circuit,
KrzysztofB-1qbit marked this conversation as resolved.
Show resolved Hide resolved
return_statevector=return_statevector,
initial_statevector=initial_statevector,
desired_meas_result=desired_meas_result,
save_mid_circuit_meas=save_mid_circuit_meas)
n_meas = source_circuit.counts.get("MEASURE", 0)
self.mid_circuit_meas_freqs, frequencies = split_frequency_dict(all_frequencies, list(range(n_meas)))
self.mid_circuit_meas_freqs, frequencies = split_frequency_dict(all_frequencies,
KrzysztofB-1qbit marked this conversation as resolved.
Show resolved Hide resolved
list(range(n_meas)),
desired_measurement=desired_meas_result)
return (frequencies, statevector)

return self.simulate_circuit(source_circuit,
return_statevector=return_statevector,
initial_statevector=initial_statevector,
save_mid_circuit_meas=save_mid_circuit_meas)
initial_statevector=initial_statevector)

def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_statevector=None):
def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_statevector=None, desired_meas_result=None):
r"""Take as input a qubit operator H and a quantum circuit preparing a
state |\psi>. Return the expectation value <\psi | H | \psi>.

Expand All @@ -257,6 +275,7 @@ def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_stat
operator.
state_prep_circuit (Circuit): an abstract circuit used for state preparation.
initial_statevector (array): The initial statevector for the simulation
desired_meas_result (str): The mid-circuit measurement results to select for.

Returns:
complex: The expectation value of this operator with regards to the
Expand All @@ -279,9 +298,14 @@ def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_stat
if are_coefficients_real:
if self._noise_model or not self.statevector_available \
or state_prep_circuit.is_mixed_state or state_prep_circuit.size == 0:
return self._get_expectation_value_from_frequencies(qubit_operator, state_prep_circuit, initial_statevector=initial_statevector)
return self._get_expectation_value_from_frequencies(qubit_operator,
state_prep_circuit,
initial_statevector=initial_statevector,
desired_meas_result=desired_meas_result)
elif self.statevector_available:
return self._get_expectation_value_from_statevector(qubit_operator, state_prep_circuit, initial_statevector=initial_statevector)
return self._get_expectation_value_from_statevector(qubit_operator,
state_prep_circuit,
initial_statevector=initial_statevector)

# Else, separate the operator into 2 hermitian operators, use linearity and call this function twice
else:
Expand All @@ -294,7 +318,7 @@ def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_stat
exp_imag = self.get_expectation_value(qb_op_imag, state_prep_circuit, initial_statevector=initial_statevector)
return exp_real if (exp_imag == 0.) else exp_real + 1.0j * exp_imag

def get_variance(self, qubit_operator, state_prep_circuit, initial_statevector=None):
def get_variance(self, qubit_operator, state_prep_circuit, initial_statevector=None, desired_meas_result=None):
r"""Take as input a qubit operator H and a quantum circuit preparing a
state |\psi>. Return the variance <\psi | H | \psi>.

Expand All @@ -311,6 +335,7 @@ def get_variance(self, qubit_operator, state_prep_circuit, initial_statevector=N
state_prep_circuit (Circuit): an abstract circuit used for state preparation.
initial_statevector (list/array) : A valid statevector in the format
supported by the target backend.
desired_meas_result (str): The mid-circuit measurement results to select for.

Returns:
complex: The variance of this operator with regard to the
Expand All @@ -331,7 +356,10 @@ def get_variance(self, qubit_operator, state_prep_circuit, initial_statevector=N

# If the underlying operator is hermitian, expectation value is real and can be computed right away
if are_coefficients_real:
return self._get_variance_from_frequencies(qubit_operator, state_prep_circuit, initial_statevector=initial_statevector)
return self._get_variance_from_frequencies(qubit_operator,
state_prep_circuit,
initial_statevector=initial_statevector,
desired_meas_result=desired_meas_result)

# Else, separate the operator into 2 hermitian operators, use linearity and call this function twice
else:
Expand All @@ -345,7 +373,7 @@ def get_variance(self, qubit_operator, state_prep_circuit, initial_statevector=N
# https://en.wikipedia.org/wiki/Complex_random_variable#Variance_and_pseudo-variance
return var_real if (var_imag == 0.) else var_real + var_imag # always non-negative real number

def get_standard_error(self, qubit_operator, state_prep_circuit, initial_statevector=None):
def get_standard_error(self, qubit_operator, state_prep_circuit, initial_statevector=None, desired_meas_result=None):
r"""Take as input a qubit operator H and a quantum circuit preparing a
state |\psi>. Return the standard error of <\psi | H | \psi>, e.g. sqrt(Var H / n_shots).

Expand All @@ -362,12 +390,13 @@ def get_standard_error(self, qubit_operator, state_prep_circuit, initial_stateve
state_prep_circuit (Circuit): an abstract circuit used for state preparation.
initial_statevector (list/array): A valid statevector in the format
supported by the target backend.
desired_meas_result (str): The mid-circuit measurement results to select for.

Returns:
complex: The standard error of this operator with regard to the
state preparation.
"""
variance = self.get_variance(qubit_operator, state_prep_circuit, initial_statevector)
variance = self.get_variance(qubit_operator, state_prep_circuit, initial_statevector, desired_meas_result=desired_meas_result)
return np.sqrt(variance/self.n_shots) if self.n_shots else 0.

def _get_expectation_value_from_statevector(self, qubit_operator, state_prep_circuit, initial_statevector=None):
Expand Down Expand Up @@ -422,7 +451,7 @@ def _get_expectation_value_from_statevector(self, qubit_operator, state_prep_cir

return expectation_value

def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_circuit, initial_statevector=None):
def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_circuit, initial_statevector=None, desired_meas_result=None):
KrzysztofB-1qbit marked this conversation as resolved.
Show resolved Hide resolved
r"""Take as input a qubit operator H and a state preparation returning a
ket |\psi>. Return the expectation value <\psi | H | \psi> computed
using the frequencies of observable states.
Expand All @@ -431,6 +460,7 @@ def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_cir
qubit_operator (openfermion-style QubitOperator class): a qubitoperator.
state_prep_circuit (Circuit): an abstract circuit used for state preparation.
initial_statevector (array): The initial state of the system
desired_meas_result (str): The mid-circuit measurement results to select for.

Returns:
complex: The expectation value of this operator with regard to the
Expand All @@ -445,7 +475,10 @@ def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_cir
updated_statevector = initial_statevector
else:
initial_circuit = Circuit(n_qubits=n_qubits)
_, updated_statevector = self.simulate(state_prep_circuit, return_statevector=True, initial_statevector=initial_statevector)
_, updated_statevector = self.simulate(state_prep_circuit,
return_statevector=True,
initial_statevector=initial_statevector,
desired_meas_result=desired_meas_result)

expectation_value = 0.
for term, coef in qubit_operator.terms.items():
Expand All @@ -458,13 +491,15 @@ def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_cir

basis_circuit = Circuit(measurement_basis_gates(term))
full_circuit = initial_circuit + basis_circuit if (basis_circuit.size > 0) else initial_circuit
frequencies, _ = self.simulate(full_circuit, initial_statevector=updated_statevector)
frequencies, _ = self.simulate(full_circuit,
initial_statevector=updated_statevector,
desired_meas_result=desired_meas_result)
expectation_term = self.get_expectation_value_from_frequencies_oneterm(term, frequencies)
expectation_value += coef * expectation_term

return expectation_value

def _get_variance_from_frequencies(self, qubit_operator, state_prep_circuit, initial_statevector=None):
def _get_variance_from_frequencies(self, qubit_operator, state_prep_circuit, initial_statevector=None, desired_meas_result=None):
r"""Take as input a qubit operator H and a state preparation returning a
ket |\psi>. Return the variance of <\psi | H | \psi> computed
using the frequencies of observable states.
Expand All @@ -474,6 +509,7 @@ def _get_variance_from_frequencies(self, qubit_operator, state_prep_circuit, ini
state_prep_circuit (Circuit): an abstract circuit used for state preparation.
initial_statevector (list/array) : A valid statevector in the format
supported by the target backend.
desired_meas_result (str): The mid-circuit measurement results to select for.

Returns:
complex: The variance of this operator with regard to the
Expand All @@ -488,7 +524,10 @@ def _get_variance_from_frequencies(self, qubit_operator, state_prep_circuit, ini
updated_statevector = initial_statevector
else:
initial_circuit = Circuit(n_qubits=n_qubits)
_, updated_statevector = self.simulate(state_prep_circuit, return_statevector=True, initial_statevector=initial_statevector)
_, updated_statevector = self.simulate(state_prep_circuit,
return_statevector=True,
initial_statevector=initial_statevector,
desired_meas_result=desired_meas_result)

variance = 0.
for term, coef in qubit_operator.terms.items():
Expand Down Expand Up @@ -599,7 +638,8 @@ def _int_to_binstr(self, i, n_qubits, use_ordering=True):
Args:
i (int): integer to convert to bit string.
n_qubits (int): The number of qubits and length of returned bit string.
use_ordering (bool): Flip the order of the returned bit string depending on self.statevector_order being "msq_first" or "lsq_first"
use_ordering (bool): Flip the order of the returned bit string
depending on self.statevector_order being "msq_first" or "lsq_first"

Returns:
string: The bit string of the integer in lsq-first order.
Expand Down
Loading