diff --git a/examples/adapt.ipynb b/examples/adapt.ipynb index 1f4738f5c..fa5aece32 100755 --- a/examples/adapt.ipynb +++ b/examples/adapt.ipynb @@ -66,14 +66,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/alex/Codes/Tangelo/tangelo/algorithms/variational/vqe_solver.py:260: RuntimeWarning: No variational gate found in the circuit.\n", + "/Users/krzysztofbieniasz/Code/env/lib/python3.9/site-packages/tangelo/algorithms/variational/vqe_solver.py:260: RuntimeWarning: No variational gate found in the circuit.\n", " warnings.warn(\"No variational gate found in the circuit.\", RuntimeWarning)\n" ] }, { "data": { "text/plain": [ - "-2.028211284185079" + "-2.0282112841580124" ] }, "execution_count": 2, @@ -117,7 +117,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -681,9 +681,9 @@ "hash": "fd77f6ebaf3d18999f00320d0aca64091b39e7847b653c69719c9ddc4e72c63f" }, "kernelspec": { - "display_name": "qsdk", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "qsdk" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -695,7 +695,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.9" + "version": "3.9.10" } }, "nbformat": 4, diff --git a/tangelo/linq/target/backend.py b/tangelo/linq/target/backend.py index aae2619a1..e99d7216e 100644 --- a/tangelo/linq/target/backend.py +++ b/tangelo/linq/target/backend.py @@ -48,9 +48,9 @@ def get_expectation_value_from_frequencies_oneterm(term, frequencies): the result of a state-preparation. Args: - term(openfermion-style QubitOperator object): a qubit operator, with + term (openfermion-style QubitOperator object): a qubit operator, with only a single term. - frequencies(dict): histogram of frequencies of measurements (assumed + frequencies (dict): histogram of frequencies of measurements (assumed to be in lsq-first format). Returns: @@ -82,9 +82,9 @@ def get_variance_from_frequencies_oneterm(term, frequencies): """Return the variance of the expectation value of a single-term qubit-operator, given the result of a state-preparation. Args: - term(openfermion-style QubitOperator object): a qubit operator, with + term (openfermion-style QubitOperator object): a qubit operator, with only a single term. - frequencies(dict): histogram of frequencies of measurements (assumed + frequencies (dict): histogram of frequencies of measurements (assumed to be in lsq-first format). Returns: complex: The variance of this operator with regard to the @@ -153,12 +153,19 @@ def simulate_circuit(self): equivalent gates. Args: - source_circuit: a circuit in the abstract format to be translated + source_circuit (Circuit): a circuit in the abstract format to be translated for the target backend. - return_statevector(bool): option to return the statevector as well, + return_statevector (bool): option to return the statevector as well, if available. - initial_statevector(list/array) : A valid statevector in the format + initial_statevector (list/array) : A valid statevector in the format supported by the target backend. + 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). + The leading n_meas values will hold the results of the MEASURE gates, + ordered by their appearance in the source_circuit. + The last n_qubits values will hold the measurements performed on + each of qubits at the end of the circuit. Returns: dict: A dictionary mapping multi-qubit states to their corresponding @@ -168,7 +175,7 @@ def simulate_circuit(self): """ pass - def simulate(self, source_circuit, return_statevector=False, initial_statevector=None): + def simulate(self, source_circuit, return_statevector=False, initial_statevector=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 @@ -178,12 +185,19 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector equivalent gates. Args: - source_circuit: a circuit in the abstract format to be translated + source_circuit (Circuit): a circuit in the abstract format to be translated for the target backend. - return_statevector(bool): option to return the statevector as well, + return_statevector (bool): option to return the statevector as well, if available. - initial_statevector(list/array) : A valid statevector in the format + initial_statevector (list/array) : A valid statevector in the format supported by the target backend. + 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). + The leading n_meas values will hold the results of the MEASURE gates, + ordered by their appearance in the source_circuit. + The last n_qubits values will hold the measurements performed on + each of qubits at the end of the circuit. Returns: dict: A dictionary mapping multi-qubit states to their corresponding @@ -191,7 +205,6 @@ 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: raise ValueError("Circuit contains MEASURE instruction, and is assumed to prepare a mixed state." "Please set the n_shots attribute to an appropriate value.") @@ -211,7 +224,22 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector statevector[0] = 1.0 return (frequencies, statevector) if return_statevector else (frequencies, None) - return self.simulate_circuit(source_circuit, return_statevector=return_statevector, initial_statevector=initial_statevector) + if save_mid_circuit_meas: + # TODO: refactor to break a circular import. May involve by relocating get_xxx_oneterm functions + from tangelo.toolboxes.post_processing.post_selection import split_frequency_dict + + (all_frequencies, statevector) = self.simulate_circuit(source_circuit, + return_statevector=return_statevector, + initial_statevector=initial_statevector, + 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))) + 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) def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_statevector=None): r"""Take as input a qubit operator H and a quantum circuit preparing a @@ -225,9 +253,10 @@ def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_stat actual QPU. Args: - qubit_operator(openfermion-style QubitOperator class): a qubit + qubit_operator (openfermion-style QubitOperator class): a qubit operator. - state_prep_circuit: an abstract circuit used for state preparation. + state_prep_circuit (Circuit): an abstract circuit used for state preparation. + initial_statevector (array): The initial statevector for the simulation Returns: complex: The expectation value of this operator with regards to the @@ -277,10 +306,10 @@ def get_variance(self, qubit_operator, state_prep_circuit, initial_statevector=N actual QPU. Args: - qubit_operator(openfermion-style QubitOperator class): a qubit + qubit_operator (openfermion-style QubitOperator class): a qubit operator. - state_prep_circuit: an abstract circuit used for state preparation. - initial_statevector(list/array) : A valid statevector in the format + 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. Returns: @@ -328,10 +357,10 @@ def get_standard_error(self, qubit_operator, state_prep_circuit, initial_stateve actual QPU. Args: - qubit_operator(openfermion-style QubitOperator class): a qubit + qubit_operator (openfermion-style QubitOperator class): a qubit operator. - state_prep_circuit: an abstract circuit used for state preparation. - initial_statevector(list/array) : A valid statevector in the format + 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. Returns: @@ -348,10 +377,9 @@ def _get_expectation_value_from_statevector(self, qubit_operator, state_prep_cir this function directly, please call "get_expectation_value" instead. Args: - qubit_operator(openfermion-style QubitOperator class): a qubit - operator. - state_prep_circuit: an abstract circuit used for state preparation - (only pure states). + qubit_operator (openfermion-style QubitOperator class): a qubit operator. + state_prep_circuit (Circuit): an abstract circuit used for state preparation (only pure states). + initial_statevector (array): The initial state of the system Returns: complex: The expectation value of this operator with regards to the @@ -400,9 +428,9 @@ def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_cir using the frequencies of observable states. Args: - qubit_operator(openfermion-style QubitOperator class): a qubit - operator. - state_prep_circuit: an abstract circuit used for state preparation. + 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 Returns: complex: The expectation value of this operator with regard to the @@ -442,10 +470,9 @@ def _get_variance_from_frequencies(self, qubit_operator, state_prep_circuit, ini using the frequencies of observable states. Args: - qubit_operator(openfermion-style QubitOperator class): a qubit - operator. - state_prep_circuit: an abstract circuit used for state preparation. - initial_statevector(list/array) : A valid statevector in the format + qubit_operator (openfermion-style QubitOperator class): a qubit operator. + 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. Returns: @@ -487,9 +514,9 @@ def get_expectation_value_from_frequencies_oneterm(term, frequencies): the result of a state-preparation. Args: - term(openfermion-style QubitOperator object): a qubit operator, with + term (openfermion-style QubitOperator object): a qubit operator, with only a single term. - frequencies(dict): histogram of frequencies of measurements (assumed + frequencies (dict): histogram of frequencies of measurements (assumed to be in lsq-first format). Returns: @@ -505,9 +532,9 @@ def get_variance_from_frequencies_oneterm(term, frequencies): the result of a state-preparation. Args: - term(openfermion-style QubitOperator object): a qubit operator, with + term (openfermion-style QubitOperator object): a qubit operator, with only a single term. - frequencies(dict): histogram of frequencies of measurements (assumed + frequencies (dict): histogram of frequencies of measurements (assumed to be in lsq-first format). Returns: @@ -525,7 +552,7 @@ def _statevector_to_frequencies(self, statevector): state |0>. Args: - statevector(list or ndarray(complex)): an iterable 1D data-structure + statevector (list or ndarray(complex)): an iterable 1D data-structure containing the amplitudes. Returns: diff --git a/tangelo/linq/target/target_cirq.py b/tangelo/linq/target/target_cirq.py index 3be677f84..08369795b 100644 --- a/tangelo/linq/target/target_cirq.py +++ b/tangelo/linq/target/target_cirq.py @@ -36,7 +36,7 @@ def __init__(self, n_shots=None, noise_model=None): super().__init__(n_shots=n_shots, noise_model=noise_model) self.cirq = cirq - def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=None): + def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=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 @@ -46,12 +46,19 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in equivalent gates. Args: - source_circuit: a circuit in the abstract format to be translated + source_circuit (Circuit): a circuit in the abstract format to be translated for the target backend. - return_statevector(bool): option to return the statevector as well, + return_statevector (bool): option to return the statevector as well, if available. - initial_statevector(list/array) : A valid statevector in the format + initial_statevector (list/array) : A valid statevector in the format supported by the target backend. + 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). + The leading n_meas values will hold the results of the MEASURE gates, + ordered by their appearance in the source_circuit. + The last n_qubits values will hold the measurements performed on + each of qubits at the end of the circuit. Returns: dict: A dictionary mapping multi-qubit states to their corresponding @@ -59,12 +66,9 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in numpy.array: The statevector, if available for the target backend and requested by the user (if not, set to None). """ - - translated_circuit = translate_c(source_circuit, "cirq", - output_options={"noise_model": self._noise_model}) - - if source_circuit.is_mixed_state or self._noise_model: - # Only DensityMatrixSimulator handles noise well, can use Simulator but it is slower + n_meas = source_circuit.counts.get("MEASURE", 0) + # Only DensityMatrixSimulator handles noise well, can use Simulator, but it is slower + if self._noise_model or (source_circuit.is_mixed_state and not save_mid_circuit_meas): cirq_simulator = self.cirq.DensityMatrixSimulator(dtype=np.complex128) else: cirq_simulator = self.cirq.Simulator(dtype=np.complex128) @@ -73,7 +77,9 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in cirq_initial_statevector = initial_statevector if initial_statevector is not None else 0 # Calculate final density matrix and sample from that for noisy simulation or simulating mixed states - if self._noise_model or source_circuit.is_mixed_state: + if (self._noise_model or source_circuit.is_mixed_state) and not save_mid_circuit_meas: + translated_circuit = translate_c(source_circuit, "cirq", + output_options={"noise_model": self._noise_model, "save_measurements": save_mid_circuit_meas}) # cirq.dephase_measurements changes measurement gates to Krauss operators so simulators # can be called once and density matrix sampled repeatedly. translated_circuit = self.cirq.dephase_measurements(translated_circuit) @@ -82,10 +88,45 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in indices = list(range(source_circuit.width)) isamples = self.cirq.sample_density_matrix(sim.final_density_matrix, indices, repetitions=self.n_shots) samples = [''.join([str(int(q))for q in isamples[i]]) for i in range(self.n_shots)] - frequencies = {k: v / self.n_shots for k, v in Counter(samples).items()} + # Noiseless simulation using the statevector simulator otherwise + # Run all shots at once and post-process to return measured frequencies on qubits only + elif save_mid_circuit_meas and not return_statevector: + translated_circuit = translate_c(source_circuit, "cirq", + output_options={"noise_model": self._noise_model, "save_measurements": True}) + qubit_list = self.cirq.LineQubit.range(source_circuit.width) + for i, qubit in enumerate(qubit_list): + translated_circuit.append(self.cirq.measure(qubit, key=str(i + n_meas))) + job_sim = cirq_simulator.run(translated_circuit, repetitions=self.n_shots) + samples = dict() + for j in range(self.n_shots): + bitstr = "".join([str(job_sim.measurements[str(i)][j, 0]) for i in range(n_meas + source_circuit.width)]) + samples[bitstr] = samples.get(bitstr, 0) + 1 + self.all_frequencies = {k: v / self.n_shots for k, v in samples.items()} + frequencies = self.all_frequencies + + # Run shot by shot and keep track of desired_meas_result only (generally slower) + elif save_mid_circuit_meas and return_statevector: + translated_circuit = translate_c(source_circuit, "cirq", + output_options={"noise_model": self._noise_model, "save_measurements": True}) + samples = dict() + self._current_state = None + indices = list(range(source_circuit.width)) + for _ in range(self.n_shots): + job_sim = cirq_simulator.simulate(translated_circuit, initial_state=cirq_initial_statevector) + measure = "".join([str(job_sim.measurements[str(i)][0]) for i in range(n_meas)]) + current_state = job_sim.final_density_matrix if self._noise_model else job_sim.final_state_vector + isamples = (self.cirq.sample_density_matrix(current_state, indices, repetitions=1) if self._noise_model + else self.cirq.sample_state_vector(current_state, indices, repetitions=1)) + sample = "".join([str(int(q)) for q in isamples[0]]) + bitstr = measure + sample + samples[bitstr] = samples.get(bitstr, 0) + 1 + self.all_frequencies = {k: v / self.n_shots for k, v in sample.items()} + frequencies = self.all_frequencies + else: + translated_circuit = translate_c(source_circuit, "cirq", output_options={"noise_model": self._noise_model}) job_sim = cirq_simulator.simulate(translated_circuit, initial_state=cirq_initial_statevector) self._current_state = job_sim.final_state_vector frequencies = self._statevector_to_frequencies(self._current_state) @@ -93,7 +134,19 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in return (frequencies, np.array(self._current_state)) if return_statevector else (frequencies, None) def expectation_value_from_prepared_state(self, qubit_operator, n_qubits, prepared_state): + """ Compute an expectation value using a representation of the state (density matrix, state vector...) + using Cirq functionalities. + Args: + qubit_operator (QubitOperator): a qubit operator in tangelo format + n_qubits (int): the number of qubits the operator acts on + prepared_state (np.array): a numpy array encoding the state (can be a vector or a matrix) + + Returns: + float64 : the expectation value of the qubit operator w.r.t the input state + """ + + # Construct equivalent Pauli operator in Cirq format GATE_CIRQ = get_cirq_gates() qubit_labels = self.cirq.LineQubit.range(n_qubits) qubit_map = {q: i for i, q in enumerate(qubit_labels)} @@ -101,6 +154,8 @@ def expectation_value_from_prepared_state(self, qubit_operator, n_qubits, prepar for term, coef in qubit_operator.terms.items(): pauli_list = [GATE_CIRQ[pauli](qubit_labels[index]) for index, pauli in term] paulisum += self.cirq.PauliString(pauli_list, coefficient=coef) + + # Compute expectation value using Cirq's features if self._noise_model: exp_value = paulisum.expectation_from_density_matrix(prepared_state, qubit_map) else: diff --git a/tangelo/linq/target/target_qdk.py b/tangelo/linq/target/target_qdk.py index aac6d1b8f..91564e2e6 100644 --- a/tangelo/linq/target/target_qdk.py +++ b/tangelo/linq/target/target_qdk.py @@ -24,7 +24,7 @@ def __init__(self, n_shots=None, noise_model=None): super().__init__(n_shots=n_shots, noise_model=noise_model) self.qsharp = qsharp - def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=None): + def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=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 @@ -34,12 +34,19 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in equivalent gates. Args: - source_circuit: a circuit in the abstract format to be translated + source_circuit (Circuit): a circuit in the abstract format to be translated for the target backend. - return_statevector(bool): option to return the statevector as well, + return_statevector (bool): option to return the statevector as well, if available. - initial_statevector(list/array) : A valid statevector in the format + initial_statevector (list/array) : A valid statevector in the format supported by the target backend. + 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). + The leading n_meas values will hold the results of the MEASURE gates, + ordered by their appearance in the source_circuit. + The last n_qubits values will hold the measurements performed on + each of qubits at the end of the circuit. Returns: dict: A dictionary mapping multi-qubit states to their corresponding @@ -47,21 +54,26 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in numpy.array: The statevector, if available for the target backend and requested by the user (if not, set to None). """ - translated_circuit = translate_c(source_circuit, "qdk") + translated_circuit = translate_c(source_circuit, "qdk", + output_options={"save_measurements": save_mid_circuit_meas}) with open('tmp_circuit.qs', 'w+') as f_out: f_out.write(translated_circuit) + n_meas = source_circuit.counts.get("MEASURE", 0) + key_length = n_meas + source_circuit.width if save_mid_circuit_meas else source_circuit.width # Compile, import and call Q# operation to compute frequencies. Only import qsharp module if qdk is running # TODO: A try block to catch an exception at compile time, for Q#? Probably as an ImportError. self.qsharp.reload() from MyNamespace import EstimateFrequencies - frequencies_list = EstimateFrequencies.simulate(nQubits=source_circuit.width, nShots=self.n_shots) + frequencies_list = EstimateFrequencies.simulate(nQubits=key_length, nShots=self.n_shots) print("Q# frequency estimation with {0} shots: \n {1}".format(self.n_shots, frequencies_list)) # Convert Q# output to frequency dictionary, apply threshold frequencies = {bin(i).split('b')[-1]: freq for i, freq in enumerate(frequencies_list)} - frequencies = {("0"*(source_circuit.width-len(k))+k)[::-1]: v for k, v in frequencies.items() + frequencies = {("0" * (key_length - len(k)) + k)[::-1]: v for k, v in frequencies.items() if v > self.freq_threshold} + self.all_frequencies = frequencies.copy() + return (frequencies, None) @staticmethod diff --git a/tangelo/linq/target/target_qiskit.py b/tangelo/linq/target/target_qiskit.py index 7b09109b2..1dcd82a85 100644 --- a/tangelo/linq/target/target_qiskit.py +++ b/tangelo/linq/target/target_qiskit.py @@ -31,7 +31,7 @@ def __init__(self, n_shots=None, noise_model=None): self.qiskit = qiskit self.AerSimulator = AerSimulator - def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=None): + def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=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 @@ -41,12 +41,19 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in equivalent gates. Args: - source_circuit: a circuit in the abstract format to be translated + source_circuit (Circuit): a circuit in the abstract format to be translated for the target backend. - return_statevector(bool): option to return the statevector as well, + return_statevector (bool): option to return the statevector as well, if available. - initial_statevector(list/array) : A valid statevector in the format + initial_statevector (list/array) : A valid statevector in the format supported by the target backend. + 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). + The leading n_meas values will hold the results of the MEASURE gates, + ordered by their appearance in the source_circuit. + The last n_qubits values will hold the measurements performed on + each of qubits at the end of the circuit. Returns: dict: A dictionary mapping multi-qubit states to their corresponding @@ -54,8 +61,7 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in numpy.array: The statevector, if available for the target backend and requested by the user (if not, set to None). """ - - translated_circuit = translate_c(source_circuit, "qiskit") + translated_circuit = translate_c(source_circuit, "qiskit", output_options={"save_measurements": save_mid_circuit_meas}) # If requested, set initial state if initial_statevector is not None: @@ -63,16 +69,20 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in raise ValueError("Cannot load an initial state if using a noise model, with Qiskit") else: n_qubits = int(math.log2(len(initial_statevector))) - initial_state_circuit = self.qiskit.QuantumCircuit(n_qubits, n_qubits) + n_meas = source_circuit.counts.get("MEASURE", 0) + n_registers = n_meas + source_circuit.width if save_mid_circuit_meas else source_circuit.width + initial_state_circuit = self.qiskit.QuantumCircuit(n_qubits, n_registers) initial_state_circuit.initialize(initial_statevector, list(range(n_qubits))) translated_circuit = initial_state_circuit.compose(translated_circuit) # Drawing individual shots with the qasm simulator, for noisy simulation or simulating mixed states - if self._noise_model or source_circuit.is_mixed_state: + if self._noise_model or source_circuit.is_mixed_state and not return_statevector: from tangelo.linq.noisy_simulation.noise_models import get_qiskit_noise_model - meas_range = range(source_circuit.width) - translated_circuit.measure(meas_range, meas_range) + n_meas = source_circuit.counts.get("MEASURE", 0) + meas_start = n_meas if save_mid_circuit_meas else 0 + meas_range = range(meas_start, meas_start + source_circuit.width) + translated_circuit.measure(range(source_circuit.width), meas_range) return_statevector = False backend = self.AerSimulator() @@ -84,6 +94,9 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in sim_results = job_sim.result() frequencies = {state[::-1]: count/self.n_shots for state, count in sim_results.get_counts(0).items()} + self.all_frequencies = frequencies.copy() + self._current_state = None + # Noiseless simulation using the statevector simulator otherwise else: backend = self.AerSimulator(method='statevector') @@ -93,7 +106,7 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in self._current_state = np.asarray(sim_results.get_statevector(translated_circuit)) frequencies = self._statevector_to_frequencies(self._current_state) - return (frequencies, np.array(sim_results.get_statevector())) if return_statevector else (frequencies, None) + return (frequencies, np.array(self._current_state)) if (return_statevector and self._current_state is not None) else (frequencies, None) @staticmethod def backend_info(): diff --git a/tangelo/linq/target/target_qulacs.py b/tangelo/linq/target/target_qulacs.py index 7010cc6e6..537820f55 100644 --- a/tangelo/linq/target/target_qulacs.py +++ b/tangelo/linq/target/target_qulacs.py @@ -30,7 +30,7 @@ def __init__(self, n_shots=None, noise_model=None): super().__init__(n_shots=n_shots, noise_model=noise_model) self.qulacs = qulacs - def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=None): + def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=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 @@ -40,12 +40,19 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in equivalent gates. Args: - source_circuit: a circuit in the abstract format to be translated + source_circuit (Circuit): a circuit in the abstract format to be translated for the target backend. - return_statevector(bool): option to return the statevector as well, + return_statevector (bool): option to return the statevector as well, if available. - initial_statevector(list/array) : A valid statevector in the format + initial_statevector (list/array) : A valid statevector in the format supported by the target backend. + 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). + The leading n_meas values will hold the results of the MEASURE gates, + ordered by their appearance in the source_circuit. + The last n_qubits values will hold the measurements performed on + each of qubits at the end of the circuit. Returns: dict: A dictionary mapping multi-qubit states to their corresponding @@ -53,43 +60,66 @@ def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, in numpy.array: The statevector, if available for the target backend and requested by the user (if not, set to None). """ - translated_circuit = translate_c(source_circuit, "qulacs", - output_options={"noise_model": self._noise_model}) + output_options={"noise_model": self._noise_model, "save_measurements": save_mid_circuit_meas}) # Initialize state on GPU if available and desired. Default to CPU otherwise. if ('QuantumStateGpu' in dir(self.qulacs)) and (int(os.getenv("QULACS_USE_GPU", 0)) != 0): state = self.qulacs.QuantumStateGpu(source_circuit.width) else: state = self.qulacs.QuantumState(source_circuit.width) + + python_statevector = None if initial_statevector is not None: state.load(initial_statevector) - if (source_circuit.is_mixed_state or self._noise_model): - samples = list() - for i in range(self.n_shots): + # If you don't want to save the mid-circuit measurements for a mixed state + if (source_circuit.is_mixed_state or self._noise_model) and not save_mid_circuit_meas: + samples = dict() + for _ in range(self.n_shots): + translated_circuit.update_quantum_state(state) + bitstr = state.sampling(1)[0] + samples[bitstr] = samples.get(bitstr, 0) + 1 + if initial_statevector is not None: + state.load(initial_statevector) + else: + state.set_zero_state() + + # To save mid-circuit measurement results + elif save_mid_circuit_meas: + n_meas = source_circuit.counts.get("MEASURE", 0) + samples = dict() + for _ in range(self.n_shots): translated_circuit.update_quantum_state(state) - samples.append(state.sampling(1)[0]) + measurement = "".join([str(state.get_classical_value(i)) for i in range(n_meas)]) + sample = self._int_to_binstr(state.sampling(1)[0], source_circuit.width) + bitstr = measurement + sample + samples[bitstr] = samples.get(bitstr, 0) + 1 if initial_statevector is not None: state.load(initial_statevector) else: state.set_zero_state() - python_statevector = None + self.all_frequencies = {k: v / self.n_shots for k, v in samples.items()} + return (self.all_frequencies, python_statevector) if return_statevector else (self.all_frequencies, None) + + # All other cases for shot-based simulation elif self.n_shots is not None: translated_circuit.update_quantum_state(state) self._current_state = state python_statevector = np.array(state.get_vector()) if return_statevector else None - samples = state.sampling(self.n_shots) + samples = Counter(state.sampling(self.n_shots)) # this sampling still returns a list + + # Statevector simulation else: translated_circuit.update_quantum_state(state) self._current_state = state - python_statevector = state.get_vector() + python_statevector = np.array(state.get_vector()) frequencies = self._statevector_to_frequencies(python_statevector) - return (frequencies, np.array(python_statevector)) if return_statevector else (frequencies, None) + return (frequencies, python_statevector) if return_statevector else (frequencies, None) frequencies = {self._int_to_binstr(k, source_circuit.width): v / self.n_shots - for k, v in Counter(samples).items()} - return (frequencies, python_statevector) + for k, v in samples.items()} + return (frequencies, python_statevector) if return_statevector else (frequencies, None) def expectation_value_from_prepared_state(self, qubit_operator, n_qubits, prepared_state): diff --git a/tangelo/linq/tests/test_simulator.py b/tangelo/linq/tests/test_simulator.py index 70c13311d..5dcaa9e69 100644 --- a/tangelo/linq/tests/test_simulator.py +++ b/tangelo/linq/tests/test_simulator.py @@ -85,6 +85,8 @@ reference_exp_values = np.array([[0., 0., 0.], [0., -1., 0.], [-0.41614684, 0.7651474, -1.6096484], [1., 0., 0.], [-0.20175269, -0.0600213, 1.2972912]]) reference_mixed = {'01': 0.163, '11': 0.066, '10': 0.225, '00': 0.545} # With Qiskit noiseless, 1M shots +reference_all = {'101': 0.163, '011': 0.066, '010': 0.225, '100': 0.545} +reference_mid = {'1': 0.7, '0': 0.3} class TestSimulateAllBackends(unittest.TestCase): @@ -125,6 +127,18 @@ def test_simulate_mixed_state(self): results[b], _ = sim.simulate(circuit_mixed) assert_freq_dict_almost_equal(results[b], reference_mixed, 1e-2) + def test_simulate_mixed_state_save_measures(self): + """ Test mid-circuit measurement (mixed-state simulation) for all installed backends. + Mixed-states do not have a statevector representation, as they are a statistical mixture of several quantum states. + """ + results = dict() + for b in installed_simulator: + sim = get_backend(target=b, n_shots=10 ** 3) + results[b], _ = sim.simulate(circuit_mixed, save_mid_circuit_meas=True) + assert_freq_dict_almost_equal(results[b], reference_mixed, 8e-2) + assert_freq_dict_almost_equal(sim.all_frequencies, reference_all, 8e-2) + assert_freq_dict_almost_equal(sim.mid_circuit_meas_freqs, reference_mid, 8e-2) + def test_get_exp_value_mixed_state(self): """ Test expectation value for mixed-state simulation. Computation done by drawing individual shots. Some simulators are NOT good at this, by design (ProjectQ). """ @@ -447,7 +461,7 @@ def __init__(self, n_shots=None, noise_model=None, return_zeros=True): super().__init__(n_shots=n_shots, noise_model=noise_model) self.return_zeros = return_zeros - def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=None): + def simulate_circuit(self, source_circuit: Circuit, return_statevector=False, initial_statevector=None, save_mid_circuit_meas=False): """Perform state preparation corresponding self.return_zeros.""" statevector = np.zeros(2**source_circuit.width, dtype=complex) diff --git a/tangelo/linq/tests/test_simulator_noisy.py b/tangelo/linq/tests/test_simulator_noisy.py index 8f85e4937..8b4fb5d75 100644 --- a/tangelo/linq/tests/test_simulator_noisy.py +++ b/tangelo/linq/tests/test_simulator_noisy.py @@ -18,6 +18,7 @@ import unittest +import numpy as np from openfermion.ops import QubitOperator from tangelo.linq import Gate, Circuit, get_backend, backend_info @@ -27,8 +28,9 @@ # Noisy simulation: circuits, noise models, references cn1 = Circuit([Gate('X', target=0)]) cn2 = Circuit([Gate('CNOT', target=1, control=0)]) +circuit_mixed = Circuit([Gate("RX", 0, parameter=2.), Gate("RY", 1, parameter=-1.), Gate("MEASURE", 0), Gate("X", 0)]) -nmp, nmd, nmc = NoiseModel(), NoiseModel(), NoiseModel() +nmp, nmd, nmc, nmm = NoiseModel(), NoiseModel(), NoiseModel(), NoiseModel() # nmp: pauli noise with equal probabilities, on X and CNOT gates nmp.add_quantum_error("X", 'pauli', [1 / 3] * 3) nmp.add_quantum_error("CNOT", 'pauli', [1 / 3] * 3) @@ -38,12 +40,16 @@ # nmc: cumulates 2 Pauli noises (here, is equivalent to no noise, as it applies Y twice when X is ran) nmc.add_quantum_error("X", 'pauli', [0., 1., 0.]) nmc.add_quantum_error("X", 'depol', 4/3) +# nmm: only apply noise to X gate +nmm.add_quantum_error("X", 'pauli', [0.2, 0., 0.]) ref_pauli1 = {'1': 1 / 3, '0': 2 / 3} ref_pauli2 = {'01': 2 / 9, '11': 4 / 9, '10': 2 / 9, '00': 1 / 9} ref_depol1 = {'1': 1 / 2, '0': 1 / 2} ref_depol2 = {'01': 1 / 4, '11': 1 / 4, '10': 1 / 4, '00': 1 / 4} ref_cumul = {'0': 1/3, '1': 2/3} +ref_mixed = {'10': 0.2876, '11': 0.0844, '01': 0.1472, '00': 0.4808} +ref_mixed_0 = {'00': 0.1488, '10': 0.6113, '01': 0.0448, '11': 0.1950} class TestSimulate(unittest.TestCase): @@ -123,6 +129,10 @@ def test_noisy_simulation_qulacs(self): res_cumul, _ = s_nmc.simulate(cn1) assert_freq_dict_almost_equal(res_cumul, ref_cumul, 1e-2) + s_nmm = get_backend(target="qulacs", n_shots=10 ** 4, noise_model=nmm) + res_mixed, _ = s_nmm.simulate(circuit_mixed) + assert_freq_dict_almost_equal(res_mixed, ref_mixed, 7.e-2) + @unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Backend not available \n") def test_noisy_simulation_qiskit(self): """ @@ -149,6 +159,10 @@ def test_noisy_simulation_qiskit(self): res_cumul, _ = s_nmp.simulate(cn1) assert_freq_dict_almost_equal(res_cumul, ref_cumul, 1e-2) + s_nmm = get_backend(target="qiskit", n_shots=10 ** 4, noise_model=nmm) + res_mixed, _ = s_nmm.simulate(circuit_mixed) + assert_freq_dict_almost_equal(ref_mixed, res_mixed, 7.e-2) + @unittest.skipIf("cirq" not in installed_backends, "Test Skipped: Backend not available \n") def test_noisy_simulation_cirq(self): """ @@ -175,6 +189,11 @@ def test_noisy_simulation_cirq(self): res_cumul, _ = s_nmc.simulate(cn1) assert_freq_dict_almost_equal(res_cumul, ref_cumul, 1e-2) + # Noisy mixed state without returning mid-circuit measurements + s_nmm = get_backend(target="cirq", n_shots=10 ** 4, noise_model=nmm) + res_mixed, _ = s_nmm.simulate(circuit_mixed) + assert_freq_dict_almost_equal(ref_mixed, res_mixed, 7.e-2) + def test_get_expectation_value_noisy(self): """Test of the get_expectation_value function with a noisy simulator""" # Test Hamiltonian. diff --git a/tangelo/linq/translator/translate_cirq.py b/tangelo/linq/translator/translate_cirq.py index 5e39b1345..b313ae202 100644 --- a/tangelo/linq/translator/translate_cirq.py +++ b/tangelo/linq/translator/translate_cirq.py @@ -76,12 +76,16 @@ def translate_cirq(source_circuit): return translate_c_to_cirq(source_circuit) -def translate_c_to_cirq(source_circuit, noise_model=None): +def translate_c_to_cirq(source_circuit, noise_model=None, save_measurements=False): """Take in an abstract circuit, return an equivalent cirq QuantumCircuit object. Args: - source_circuit: quantum circuit in the abstract format. + source_circuit (Circuit): quantum circuit in the abstract format. + noise_model (NoiseModel): The noise model to use + save_measurements (bool): If True, all measurements in the circuit are saved + with the key 'n' for the nth measurement in the Circuit. If False, no + measurements are saved. Returns: cirq.Circuit: a corresponding cirq Circuit. Right now, the structure is @@ -99,6 +103,8 @@ def translate_c_to_cirq(source_circuit, noise_model=None): # cirq will otherwise only initialize qubits that have gates target_circuit.append(cirq.I.on_each(qubit_list)) + measure_count = 0 + # Maps the gate information properly. Different for each backend (order, values) for gate in source_circuit._gates: if (gate.control is not None) and gate.name != 'CNOT': @@ -115,7 +121,9 @@ def translate_c_to_cirq(source_circuit, noise_model=None): elif gate.name in {"CNOT"}: target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.control[0]], qubit_list[gate.target[0]])) elif gate.name in {"MEASURE"}: - target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]])) + key = str(measure_count) if save_measurements else None + target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]], key=key)) + measure_count += 1 elif gate.name in {"CRZ", "CRX", "CRY"}: next_gate = GATE_CIRQ[gate.name](gate.parameter).controlled(num_controls) target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]])) diff --git a/tangelo/linq/translator/translate_qdk.py b/tangelo/linq/translator/translate_qdk.py index 9f3900c05..c24b6910c 100644 --- a/tangelo/linq/translator/translate_qdk.py +++ b/tangelo/linq/translator/translate_qdk.py @@ -64,7 +64,7 @@ def translate_qsharp(source_circuit): return translate_c_to_qsharp(source_circuit) -def translate_c_to_qsharp(source_circuit, operation="MyQsharpOperation"): +def translate_c_to_qsharp(source_circuit, operation="MyQsharpOperation", save_measurements=False): """Take in an abstract circuit, generate the corresponding Q# operation (state prep + measurement) string, in the appropriate Q# template. The Q# output can be written to file and will be compiled at runtime. @@ -72,6 +72,9 @@ def translate_c_to_qsharp(source_circuit, operation="MyQsharpOperation"): Args: source_circuit: quantum circuit in the abstract format. operation (str), optional: name of the Q# operation. + save_measurements (bool), optional: True, return all mid-circuit measurement results. + This returns a frequency vector that is of size 2^(n_meas+n_qubits). False, + all measurements are overwritten. Returns: str: The Q# code (operation + template). This needs to be written into a @@ -80,12 +83,15 @@ def translate_c_to_qsharp(source_circuit, operation="MyQsharpOperation"): GATE_QDK = get_qdk_gates() + n_meas = source_circuit._gate_counts.get("MEASURE", 0) if save_measurements else 0 + n_c = n_meas + source_circuit.width + measurement = 0 # Prepare Q# operation header qsharp_string = "" qsharp_string += "@EntryPoint()\n" qsharp_string += f"operation {operation}() : Result[] {{\n" # qsharp_string += "body (...) {\n\n" - qsharp_string += f"\tmutable c = new Result[{source_circuit.width}];\n" + qsharp_string += f"\tmutable c = new Result[{n_c}];\n" qsharp_string += f"\tusing (qreg = Qubit[{source_circuit.width}]) {{\n" # Generate Q# strings with the right syntax, order and values for the gate inputs @@ -112,10 +118,13 @@ def translate_c_to_qsharp(source_circuit, operation="MyQsharpOperation"): elif gate.name in {"CSWAP"}: body_str += f"\t\tControlled {GATE_QDK[gate.name]}({control_string}, (qreg[{gate.target[0]}], qreg[{gate.target[1]}]));\n" elif gate.name in {"MEASURE"}: - body_str += f"\t\tset c w/= {gate.target[0]} <- {GATE_QDK[gate.name]}(qreg[{gate.target[0]}]);\n" + body_str += f"\t\tset c w/= {measurement} <- {GATE_QDK[gate.name]}(qreg[{gate.target[0]}]);\n" + if save_measurements: + measurement += 1 else: raise ValueError(f"Gate '{gate.name}' not supported on backend qdk") - qsharp_string += body_str + "\n\t\treturn ForEach(MResetZ, qreg);\n" + return_str = f"\n\t\tfor index in 0 .. Length(qreg) - 1 {{\n\t\t\tset c w/= {n_meas} + index <- MResetZ(qreg[index]);\n\t\t}}\n" + qsharp_string += body_str + return_str + "\n\t\treturn c;\n" qsharp_string += "\t}\n" # qsharp_string += "}\n adjoint auto;\n" qsharp_string += "}\n" diff --git a/tangelo/linq/translator/translate_qiskit.py b/tangelo/linq/translator/translate_qiskit.py index e62dd9a68..d115efa7d 100644 --- a/tangelo/linq/translator/translate_qiskit.py +++ b/tangelo/linq/translator/translate_qiskit.py @@ -79,11 +79,13 @@ def translate_qiskit(source_circuit): return translate_c_to_qiskit(source_circuit) -def translate_c_to_qiskit(source_circuit: Circuit): +def translate_c_to_qiskit(source_circuit: Circuit, save_measurements=False): """Take in a Circuit, return an equivalent qiskit.QuantumCircuit Args: - source_circuit (Circuit): quantum circuit in the Tangelo format. + source_circuit (Circuit): quantum circuit in the abstract format. + save_measurements (bool): Return mid-circuit measurements in the order + they appear in the circuit in the classical registers Returns: qiskit.QuantumCircuit: the corresponding qiskit.QuantumCircuit @@ -92,7 +94,11 @@ def translate_c_to_qiskit(source_circuit: Circuit): GATE_QISKIT = get_qiskit_gates() - target_circuit = qiskit.QuantumCircuit(source_circuit.width, source_circuit.width) + n_meas = source_circuit._gate_counts.get("MEASURE", 0) if save_measurements else 0 + n_measures = n_meas + source_circuit.width + target_circuit = qiskit.QuantumCircuit(source_circuit.width, n_measures) + + measurement = 0 # Maps the gate information properly. Different for each backend (order, values) for gate in source_circuit._gates: @@ -114,7 +120,9 @@ def translate_c_to_qiskit(source_circuit: Circuit): elif gate.name in {"XX"}: (GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.target[0], gate.target[1]) elif gate.name in {"MEASURE"}: - (GATE_QISKIT[gate.name])(target_circuit, gate.target[0], gate.target[0]) + (GATE_QISKIT[gate.name])(target_circuit, gate.target[0], measurement) + if save_measurements: + measurement += 1 else: raise ValueError(f"Gate '{gate.name}' not supported on backend qiskit") diff --git a/tangelo/linq/translator/translate_qulacs.py b/tangelo/linq/translator/translate_qulacs.py index 2e46d0938..a04f6d68c 100644 --- a/tangelo/linq/translator/translate_qulacs.py +++ b/tangelo/linq/translator/translate_qulacs.py @@ -78,16 +78,19 @@ def translate_qulacs(source_circuit, noise_model=None): return translate_c_to_qulacs(source_circuit, noise_model) -def translate_c_to_qulacs(source_circuit, noise_model=None): +def translate_c_to_qulacs(source_circuit, noise_model=None, save_measurements=False): """Take in an abstract circuit, return an equivalent qulacs QuantumCircuit instance. If provided with a noise model, will add noisy gates at translation. Not very useful to look at, as qulacs does not provide much information about the noisy gates added when printing the "noisy circuit". Args: - source_circuit: quantum circuit in the abstract format. - noise_model: A NoiseModel object from this package, located in the + source_circuit (Circuit): quantum circuit in the abstract format. + noise_model (NoiseModel): A NoiseModel object from this package, located in the noisy_simulation subpackage. + save_measurements (bool): If True, each nth measurement in the circuit is saved in + the nth classical register. Otherwise, each measurement overwrites + the first classical register. Returns: qulacs.QuantumCircuit: the corresponding qulacs quantum circuit. @@ -99,6 +102,8 @@ def translate_c_to_qulacs(source_circuit, noise_model=None): GATE_QULACS = get_qulacs_gates() target_circuit = qulacs.QuantumCircuit(source_circuit.width) + measure_count = 0 + # Maps the gate information properly. Different for each backend (order, values) for gate in source_circuit._gates: if gate.name in {"H", "X", "Y", "Z", "S", "T"}: @@ -141,8 +146,10 @@ def translate_c_to_qulacs(source_circuit, noise_model=None): elif gate.name in {"CNOT"}: (GATE_QULACS[gate.name])(target_circuit, gate.control[0], gate.target[0]) elif gate.name in {"MEASURE"}: - gate = (GATE_QULACS[gate.name])(gate.target[0], gate.target[0]) - target_circuit.add_gate(gate) + m_gate = (GATE_QULACS[gate.name])(gate.target[0], measure_count) + target_circuit.add_gate(m_gate) + if save_measurements: + measure_count += 1 else: raise ValueError(f"Gate '{gate.name}' not supported on backend qulacs") diff --git a/tangelo/toolboxes/post_processing/post_selection.py b/tangelo/toolboxes/post_processing/post_selection.py index 6c4359dcc..d8afb1b5f 100644 --- a/tangelo/toolboxes/post_processing/post_selection.py +++ b/tangelo/toolboxes/post_processing/post_selection.py @@ -47,7 +47,8 @@ def ancilla_symmetry_circuit(circuit, sym_op): if n_qubits < op_len: raise RuntimeError("The size of the symmetry operator is bigger than the number of qubits.") elif n_qubits > op_len: - warnings.warn("The size of the symmetry operator is smaller than the number of qubits. Remaining qubits will be measured in the Z-basis.") + warnings.warn( + "The size of the symmetry operator is smaller than the number of qubits. Remaining qubits will be measured in the Z-basis.") if isinstance(sym_op, str): basis_gates = measurement_basis_gates(pauli_string_to_of(sym_op)) @@ -56,7 +57,8 @@ def ancilla_symmetry_circuit(circuit, sym_op): elif isinstance(sym_op, QubitOperator): basis_gates = measurement_basis_gates(list(sym_op.terms.keys())[0]) else: - raise RuntimeError("The symmetry operator must be an OpenFermion-style operator, a QubitOperator, or a Pauli word.") + raise RuntimeError( + "The symmetry operator must be an OpenFermion-style operator, a QubitOperator, or a Pauli word.") basis_circ = Circuit(basis_gates) parity_gates = [Gate("CNOT", n_qubits, i) for i in range(n_qubits)] @@ -97,3 +99,26 @@ def strip_post_selection(freqs, *qubits): hist = Histogram(freqs, n_shots=0) hist.remove_qubit_indices(*qubits) return hist.frequencies + + +def split_frequency_dict(frequencies, indices): + """Marginalize the frequencies dictionary over the indices. + This splits the frequency dictionary into two frequency dictionaries + and aggregates the corresponding frequencies. + + Args: + frequencies (dict): The input frequency dictionary + indices (list): The list of indices in the frequency dictionary to marginalize over + + Returns: + dict: The marginal frequencies for provided indices + dict: The marginal frequencies for remaining indices""" + key_length = len(next(iter(frequencies))) + other_indices = [i for i in range(key_length) if i not in indices] + + new_hist = Histogram(frequencies) + new_hist.remove_qubit_indices(*other_indices) + other_hist = Histogram(frequencies) + other_hist.remove_qubit_indices(*indices) + + return new_hist.frequencies, other_hist.frequencies