def _calculate_expectation_value_for_distribution( distribution: BitstringDistribution, operator: IsingOperator, alpha: float) -> float: # Calculates expectation value per bitstring expectation_values_per_bitstring = {} for bitstring in distribution.distribution_dict: expected_value = Measurements([bitstring]).get_expectation_values( operator, use_bessel_correction=False) expectation_values_per_bitstring[bitstring] = np.sum( expected_value.values) cumulative_value = 0.0 # Get total expectation value (mean of expectation values of all bitstrings weighted by distribution) for bitstring in expectation_values_per_bitstring: prob = distribution.distribution_dict[bitstring] # For the i-th sampled bitstring, compute exp(-alpha E_i) See equation 2 in the original paper. expectation_value = np.exp(-alpha * expectation_values_per_bitstring[bitstring]) cumulative_value += prob * expectation_value final_value = -np.log(cumulative_value) return final_value
def convert_sampleset_to_measurements( sampleset: SampleSet, change_bitstring_convention: bool = False, ) -> Measurements: """ Converts dimod SampleSet to zquantum.core Measurements. Works only for the sampleset with "BINARY" vartype and variables being range of integers starting from 0. Note: Since Measurements doesn't hold information about the energy of the samples, this conversion is lossy. For more explanation regarding change_bitstring_convention please read docs of `convert_measurements_to_sampleset`. Args: sampleset: SampleSet we want to convert change_bitstring_convention: whether to flip the bits in bitstrings to, depends on the convention one is using (see note). Returns: Measurements object """ if sampleset.vartype != dimod.BINARY: raise TypeError("Sampleset needs to have vartype BINARY") for i in range(max(sampleset.variables)): if sampleset.variables[i] != i: raise ValueError( "Variables of sampleset need to be ordered list of integers") bitstrings = [ tuple( int(change_bitstring_convention != sample[i]) for i in range(len(sample))) for sample in sampleset.samples() ] return Measurements(bitstrings)
def test_save_for_numpy_integers(self): # Given target_bitstrings = [(0, 0, 0)] input_bitstrings = [(np.int8(0), np.int8(0), np.int8(0))] filename = "measurementstest.json" measurements = Measurements(input_bitstrings) target_measurements = Measurements(target_bitstrings) # When measurements.save(filename) # Then recreated_measurements = Measurements.load_from_file(filename) assert target_measurements.bitstrings == recreated_measurements.bitstrings remove_file_if_exists("measurementstest.json")
def test_converted_ising_evaluates_to_the_same_energy_as_original_qubo(): qubo = dimod.BinaryQuadraticModel( { 0: 1, 1: 2, 2: 3 }, { (0, 1): 1, (0, 2): 0.5, (1, 2): 0.5, }, -1, vartype=dimod.BINARY, ) all_solutions = [ [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], ] ising = convert_qubo_to_openfermion_ising(qubo) for solution in all_solutions: qubo_energy = qubo.energy(solution) ising_energy = np.sum( Measurements([solution]).get_expectation_values(ising).values) assert qubo_energy == ising_energy
def test_get_expectation_values_from_measurements(self): # Given measurements = Measurements([(0, 1, 0), (0, 1, 0), (0, 0, 0), (1, 0, 0), (1, 1, 1)]) ising_operator = IsingOperator("10[] + [Z0 Z1] - 15[Z1 Z2]") target_expectation_values = np.array([10, -0.2, -3]) target_correlations = np.array([[100, -2, -30], [-2, 1, -9], [-30, -9, 225]]) denominator = len(measurements.bitstrings) covariance_11 = (target_correlations[1, 1] - target_expectation_values[1]**2) / denominator covariance_12 = (target_correlations[1, 2] - target_expectation_values[1] * target_expectation_values[2]) / denominator covariance_22 = (target_correlations[2, 2] - target_expectation_values[2]**2) / denominator target_covariances = np.array([ [0, 0, 0], [0, covariance_11, covariance_12], [0, covariance_12, covariance_22], ]) # When expectation_values = measurements.get_expectation_values( ising_operator, False) # Then np.testing.assert_allclose(expectation_values.values, target_expectation_values) assert len(expectation_values.correlations) == 1 np.testing.assert_allclose(expectation_values.correlations[0], target_correlations) assert len(expectation_values.estimator_covariances) == 1 np.testing.assert_allclose(expectation_values.estimator_covariances[0], target_covariances)
def test_convert_measurements_to_sampleset_with_qubo(): bitstrings = [ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 0, 1), (0, 0, 1), ] qubo = dimod.BinaryQuadraticModel( { 0: 1, 1: 2, 2: 3 }, { (1, 2): 0.5, (1, 0): -0.25, (0, 2): 2.125 }, 0, vartype=dimod.BINARY, ) energies = [0, 3, 2, 5.5, 1, 2.75, 8.375, 6.125, 3] measurements = Measurements(bitstrings) target_sampleset = dimod.SampleSet.from_samples(bitstrings, dimod.BINARY, energies) converted_sampleset = convert_measurements_to_sampleset(measurements, qubo) assert target_sampleset == converted_sampleset
def test_convert_measurements_to_sampleset_without_qubo(): bitstrings = [ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 0, 1), (0, 0, 1), ] measurements = Measurements(bitstrings) target_sampleset = dimod.SampleSet.from_samples( bitstrings, dimod.BINARY, [np.nan for _ in bitstrings]) converted_sampleset = convert_measurements_to_sampleset(measurements) # Since energies should be np.nans, using "==" will result in error for (target_record, converted_record) in zip(target_sampleset.record, converted_sampleset.record): for target_element, converted_element in zip(target_record, converted_record): np.testing.assert_equal(target_element, converted_element) assert converted_sampleset.vartype == target_sampleset.vartype
def test_convert_sampleset_to_measurements_with_change_bitstring_convention(): bitstrings = [ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 0, 1), (0, 0, 1), ] target_bitstrings = [ (1, 1, 1), (1, 1, 0), (1, 0, 1), (1, 0, 0), (0, 1, 1), (0, 0, 1), (0, 0, 0), (0, 1, 0), (1, 1, 0), ] change_bitstring_convention = True energies = [0 for i in range(len(bitstrings))] sampleset = dimod.SampleSet.from_samples(bitstrings, dimod.BINARY, energies) target_measurements = Measurements(target_bitstrings) converted_measurements = convert_sampleset_to_measurements( sampleset, change_bitstring_convention=change_bitstring_convention) assert converted_measurements.bitstrings == target_measurements.bitstrings
def test_intialize_with_bitstrings(self): # Given bitstrings = [ (0, 0, 0), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), ] # When measurements = Measurements(bitstrings=bitstrings) # Then assert measurements.bitstrings == [ (0, 0, 0), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), ]
def get_estimated_expectation_values( self, backend: QuantumBackend, circuit: Circuit, target_operator: IsingOperator, alpha: float, n_samples: Optional[int] = None, ) -> ExpectationValues: """Given a circuit, backend, and target operators, this method produces expectation values using CVaR algorithm. Args: backend (QuantumBackend): the backend that will be used to run the circuit circuit (Circuit): the circuit that prepares the state. target_operator (SymbolicOperator): Operator to be estimated. alpha (float): defines what part of the best measurements should be taken into account in the estimation process. n_samples (int): Number of measurements done on the unknown quantum state. Raises: AttributeError: If backend is not a QuantumSimulator. Returns: ExpectationValues: expectation values for each term in the target operator. """ if alpha > 1 or alpha <= 0: raise ValueError("alpha needs to be a value between 0 and 1.") if not isinstance(target_operator, IsingOperator): raise TypeError("Operator should be of type IsingOperator.") distribution = backend.get_bitstring_distribution(circuit, n_samples=n_samples) expected_values_per_bitstring = {} for bitstring in distribution.distribution_dict: expected_value = Measurements(bitstring).get_expectation_values( target_operator ) expected_values_per_bitstring[bitstring] = expected_value.values[0] sorted_expected_values_per_bitstring_list = sorted( expected_values_per_bitstring.items(), key=lambda item: item[1] ) cumulative_prob = 0.0 cumulative_value = 0.0 for bitstring, energy in sorted_expected_values_per_bitstring_list: prob = distribution.distribution_dict[bitstring] if cumulative_prob + prob < alpha: cumulative_prob += prob cumulative_value += prob * energy else: cumulative_value += (alpha - cumulative_prob) * energy break final_value = cumulative_value / alpha return ExpectationValues(final_value)
def get_exact_qubo_solution(qubo): """Solves qubo by iterating over all the possible solutions. Args: qubo: qubo stored as a json """ qubo = load_qubo(qubo) sampleset = ExactSolver().sample(qubo) best_sample_dict = sampleset.first.sample solution_bitstring = tuple(best_sample_dict[i] for i in sorted(best_sample_dict)) Measurements([solution_bitstring]).save("exact_solution.json")
def test_add_measurements(self): # Given measurements = Measurements() bitstrings = [ (0, 0, 0), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), ] # When measurements.bitstrings = bitstrings # Then assert measurements.bitstrings == [ (0, 0, 0), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), ] # When measurements.bitstrings += bitstrings # Then assert measurements.bitstrings == [ (0, 0, 0), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), (0, 0, 0), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), ]
def run_circuit_and_measure(self, circuit, **kwargs): """ Run a circuit and measure a certain number of bitstrings Args: circuit (zquantum.core.circuit.Circuit): the circuit to prepare the state n_samples (int): the number of bitstrings to sample Returns: a list of bitstrings (a list of tuples) """ wavefunction = self.get_wavefunction(circuit) bitstrings = sample_from_wavefunction(wavefunction, self.n_samples) return Measurements(bitstrings)
def _evaluate_solution_for_hamiltonian(solution: Tuple[int], hamiltonian: QubitOperator) -> float: """Evaluates a solution of a hamiltonian by its calculating expectation value. Args: solution: solution to a problem as a tuple of bits hamiltonian: a Hamiltonian representing a problem. Returns: float: value of a solution. """ hamiltonian = change_operator_type(hamiltonian, IsingOperator) expectation_values = expectation_values_to_real( Measurements([solution]).get_expectation_values(hamiltonian)) return sum(expectation_values.values)
def solve_qubo(qubo, solver_specs, solver_params=None): """Solves qubo using any sampler implementing either dimod.Sampler or zquantum.qubo.BQMSolver""" if solver_params is None: solver_params = {} solver = create_object(solver_specs) qubo = load_qubo(qubo) sampleset = solver.sample(qubo, **solver_params) best_sample_dict = sampleset.first.sample solution_bitstring = tuple(best_sample_dict[i] for i in sorted(best_sample_dict)) lowest_energy = evaluate_bitstring_for_qubo(solution_bitstring, qubo) save_value_estimate(ValueEstimate(lowest_energy), "lowest-energy.json") Measurements([solution_bitstring]).save("solution.json") save_sampleset(sampleset, "sampleset.json")
def run_circuit_and_measure(self, circuit, n_samples: int): """Run a circuit and measure a certain number of bitstrings. Note: the number of bitstrings measured is derived from self.n_samples Args: circuit: the circuit to prepare the state n_samples: The number of samples to measure. Returns: a list of bitstrings (a list of tuples) """ super().run_circuit_and_measure(circuit, n_samples=n_samples) cxn = get_forest_connection(self.device_name, self.seed) bitstrings = cxn.run_and_measure(export_to_pyquil(circuit), trials=n_samples) if isinstance(bitstrings, dict): bitstrings = np.vstack([bitstrings[q] for q in sorted(cxn.qubits())]).T bitstrings = [tuple(b) for b in bitstrings.tolist()] return Measurements(bitstrings)
def run_circuit_and_measure(self, circuit: Circuit, n_samples: Optional[int] = None, **kwargs): """ Run a circuit and measure a certain number of bitstrings Args: circuit: the circuit to prepare the state n_samples: the number of bitstrings to sample Returns: The measured bitstrings. """ if n_samples is None: n_samples = self.n_samples wavefunction = self.get_wavefunction(circuit) bitstrings = sample_from_wavefunction(wavefunction, n_samples) return Measurements(bitstrings)
def test_convert_sampleset_to_measurements(): bitstrings = [ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 0, 1), (0, 0, 1), ] energies = [0 for i in range(len(bitstrings))] sampleset = dimod.SampleSet.from_samples(bitstrings, dimod.BINARY, energies) target_measurements = Measurements(bitstrings) converted_measurements = convert_sampleset_to_measurements(sampleset) assert converted_measurements.bitstrings == target_measurements.bitstrings
def test_converted_qubo_evaluates_to_the_same_energy_as_original_ising(): ising = IsingOperator( "2.5 [] + [Z0] + 2[Z0 Z1] +0.5[Z0 Z2] +[Z1] + 0.75[Z1 Z2] -[Z2]") all_solutions = [ [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], ] qubo = convert_openfermion_ising_to_qubo(ising) for solution in all_solutions: qubo_energy = qubo.energy(solution) ising_energy = np.sum( Measurements([solution]).get_expectation_values(ising).values) assert qubo_energy == ising_energy
def run_circuit_and_measure( self, circuit: Circuit, n_samples: int, symbol_map: Optional[Dict[Symbol, Any]] = {}, ) -> Measurements: """Run a circuit and measure a certain number of bitstrings Args: circuit: the circuit to prepare the state n_samples: the number of bitstrings to sample """ wavefunction = self.get_wavefunction(circuit).bind( symbol_map=symbol_map) if circuit.free_symbols: raise ValueError( "Cannot sample from circuit with symbolic parameters.") bitstrings = sample_from_wavefunction(wavefunction, n_samples, self._seed) return Measurements(bitstrings)
def test_add_counts(self): # Given measurements = Measurements() measurements_counts = { "000": 1, "001": 2, "010": 1, "011": 1, "100": 1, "101": 1, "110": 1, "111": 1, } # When measurements.add_counts(measurements_counts) # Then assert measurements.bitstrings == [ (0, 0, 0), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1), ] assert measurements.get_counts() == { "000": 1, "001": 2, "010": 1, "011": 1, "100": 1, "101": 1, "110": 1, "111": 1, }
def run_circuit_and_measure(self, circuit: Circuit, n_samples: Optional[int] = None, **kwargs): """ Run a circuit and measure a certain number of bitstrings Args: circuit: the circuit to prepare the state n_samples: the number of bitstrings to sample Returns: The measured bitstrings. """ if n_samples is None: if self.n_samples is None: raise ValueError( "n_samples needs to be specified either as backend attribute or as an function argument." ) else: n_samples = self.n_samples wavefunction = self.get_wavefunction(circuit) bitstrings = sample_from_wavefunction(wavefunction, n_samples) return Measurements(bitstrings)
def _calculate_expectation_value_of_bitstring( bitstring: str, operator: IsingOperator) -> float: """Calculate expectation value for a bitstring based on an operator.""" expected_value = Measurements([bitstring]).get_expectation_values( operator, use_bessel_correction=False) return np.sum(expected_value.values)
def run_circuit_and_measure(self, circuit, **kwargs): wavefunction = self.get_wavefunction(circuit) return Measurements( sample_from_wavefunction(wavefunction, self.n_samples))