From 9bdeabc1c89dcb321cde93641e4a1a233f4e4fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Fri, 2 Dec 2022 01:33:05 +0100 Subject: [PATCH] Update docstrings for wavefunction simulator (#73) * fix: typo (double space) * docs: add docstring for circuit_runner module * docs: add docstring for WavefunctionSimulator and BaseWavefunctionSimulator --- src/orquestra/quantum/api/circuit_runner.py | 3 +- .../quantum/api/wavefunction_simulator.py | 113 +++++++++++++++++- 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/src/orquestra/quantum/api/circuit_runner.py b/src/orquestra/quantum/api/circuit_runner.py index a94ef6f..f12edeb 100644 --- a/src/orquestra/quantum/api/circuit_runner.py +++ b/src/orquestra/quantum/api/circuit_runner.py @@ -1,6 +1,7 @@ ################################################################################ # © Copyright 2022 Zapata Computing Inc. ################################################################################ +"""Definition of CircuitRunner protocol and ABC for implementing it.""" from abc import ABC, abstractmethod from typing import List, Optional, Protocol, Sequence, Union @@ -180,7 +181,7 @@ def run_batch_and_measure( @abstractmethod def _run_and_measure(self, circuit: Circuit, n_samples: int) -> Measurements: - """Underlying implementation of running and measuring single circuit. + """Underlying implementation of running and measuring single circuit. Implementations of this method can assume that n_samples is positive. Furthermore, implementations of this method should not modify diff --git a/src/orquestra/quantum/api/wavefunction_simulator.py b/src/orquestra/quantum/api/wavefunction_simulator.py index 6ff487e..d8ac51e 100644 --- a/src/orquestra/quantum/api/wavefunction_simulator.py +++ b/src/orquestra/quantum/api/wavefunction_simulator.py @@ -1,7 +1,7 @@ ################################################################################ # © Copyright 2022 Zapata Computing Inc. ################################################################################ - +"""The WavefunctionSimulator protocol and ABC for implementing it.""" from abc import abstractmethod from typing import Optional, Protocol @@ -20,23 +20,84 @@ class WavefunctionSimulator(CircuitRunner, Protocol): + """The protocol for objects able to compute the wavefunction.""" + def get_wavefunction( self, circuit: Circuit, initial_state: Optional[StateVector] = None ) -> Wavefunction: - pass + """Get a wavefunction of a circuit starting from a given initial state. + + Args: + circuit: circuit whose wavefunction is to be computed. + initial_state: state used as an input to the `circuit`. If not provided, + |0...0> will be used. + Returns: + Wavefunction comprising 2 ** n amplitudes, where n is number of qubits in + `circuit`. + """ def get_exact_expectation_values( self, circuit: Circuit, operator: PauliRepresentation ) -> float: - pass + """Get an exact expectation value of an operator. + + Args: + circuit: circuit constructing state for which expectation value + is to be computed. + operator: operator of which expectation value should be computed. + Returns: + An expectation value as a single floating point number. + """ class BaseWavefunctionSimulator(BaseCircuitRunner, WavefunctionSimulator): + """ABC for implementing simple wavefunction simulators. + + This ABC is build around _get_wavefunction_from_native_circuit + method. In general, most simulators wrap some third-party resource ( + a library, service, API etc.), which can only consume circuits comprising + operations from a given set. Such operations are called "native" to + the given simulator, whereas other operations are called "nonnative". + + The idea of simulating arbitrary circuit is thus as follows: + - split circuit into alternating consecutive parts of only native and + only "nonnative" operations. + - start with some initial state + - for each part: + - if it is native, run it via third-party resource, save the new + statevector + - if it is nonnative, apply each operation in the sequence using + operation.apply(previous_statevector). Save the new statevector. + Last saved statevector is the wavefunction of the total circuit. + + For this to work, subclasses of this ABC should implement + _get_wavefunction_from_the_native_circuit method. + + Note: + Since this class inherits all the limitations of BaseCircuitRunner. + The _run_and_measure function is implemented via sampling from the + wavefunction. Care must be taken when using third-party service that + implements more sophisticated/more performant sampling method not + involving direct computation of the whole wavefunction. In such cases, + using BaseWavefunctionSimulator ABC will likely result in huge + performance hit. + """ + def __init__(self, *, seed: Optional[int] = None): super().__init__() self.seed = seed def run_and_measure(self, circuit: Circuit, n_samples: int) -> Measurements: + """Run circuit given number of times and return obtained measurements. + + See docstrings of CircuitRunner protocol for exact description of + parameters. + + Note: + For BaseWavefunctionSimulator, running a circuit comprising computing + its wavefunction and then sampling from the probability distribution + that it defines. + """ if n_samples <= 0: raise ValueError(f"Number of samples has to be positive, got {n_samples}") result = self._run_and_measure(circuit, n_samples) @@ -53,6 +114,11 @@ def _run_and_measure(self, circuit: Circuit, n_samples: int) -> Measurements: def get_wavefunction( self, circuit: Circuit, initial_state: Optional[StateVector] = None ) -> Wavefunction: + """Get a wavefunction of a circuit starting from a given initial state. + + See docstrings of WavefunctionSimulator protocol for exact description of + parameters. + """ state: StateVector if initial_state is None: state = np.zeros(2**circuit.n_qubits) @@ -79,22 +145,61 @@ def get_wavefunction( def _get_wavefunction_from_native_circuit( self, circuit: Circuit, initial_state: StateVector ) -> StateVector: - pass + """Get a wavefunction of a native circuit starting from given initial state. + + This is the only mandatory method to be implemented by classes extending + this ABC. + + Args: + circuit: circuit whose wavefunction is to be computed. This method might + assume that it comprises only operations native to this simulator. + initial_state: state used as an input to the `circuit`. If not provided, + |0...0> will be used. + Returns: + The computed wavefunction. + """ def get_exact_expectation_values( self, circuit: Circuit, operator: PauliRepresentation ) -> float: + """Get an exact expectation value of an operator. + + See docstrings of WavefunctionSimulator protocol for exact description of + parameters. + """ wavefunction = self.get_wavefunction(circuit) # Casting to real, because any non-zero imaginary part must mean some # numerical inaccuracy. return get_expectation_value(operator, wavefunction).real def is_natively_supported(self, operation: Operation) -> bool: + """Determine if given operation is natively supported by this simulator. + + By default, operation is natively supported iff it is a GateOperation. + However, this doesn't have to be true for every simulator. + If the set of natively supported operations is different for + some simulator, this method should be changed accordingly. + + Args: + operation: operation to be checked. + Returns: + True if `operation` is natively supported and False otherwise. + """ return isinstance(operation, GateOperation) def get_measurement_outcome_distribution( self, circuit: Circuit, n_samples: Optional[int] = None ) -> MeasurementOutcomeDistribution: + """Get a distribution of measurement outcomes from a given circuit. + + Args: + circuit: circuit to be sampled + n_samples: number of times `circuit` should be sampled or None, if + exact computation should be performed. + Returns: + An object representing empirical (for integral n_samples) or theoretical + (n_samples is None) distribution of measured bitstrings + """ if n_samples is None: wavefunction = self.get_wavefunction(circuit) return create_bitstring_distribution_from_probability_distribution(