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_get_measurements_representing_distribution_randomly_samples_leftover_bitstrings_when_probabilities_equal( self, ): random.seed(RNDSEED) bitstring_distribution = BitstringDistribution({"00": 0.5, "11": 0.5}) number_of_samples = 51 max_number_of_trials = 10 got_different_measurements = False previous_measurements = Measurements.get_measurements_representing_distribution( bitstring_distribution, number_of_samples) while not got_different_measurements: measurements = Measurements.get_measurements_representing_distribution( bitstring_distribution, number_of_samples) assert measurements.get_counts() == { "00": 25, "11": 26, } or measurements.get_counts() == { "00": 26, "11": 25 } if measurements.get_counts() != previous_measurements.get_counts(): got_different_measurements = True max_number_of_trials -= 1 if max_number_of_trials == 0: break assert got_different_measurements
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 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_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 _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 test_get_measurements_representing_distribution_doesnt_raise( self, bitstring_distribution): number_of_samples = 100 max_number_of_trials = 100 for _ in range(max_number_of_trials): _ = Measurements.get_measurements_representing_distribution( bitstring_distribution, number_of_samples)
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 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_intialize_with_counts(self): # Given counts = { "000": 1, "001": 2, "010": 1, "011": 1, "100": 1, "101": 1, "110": 1, "111": 1, } # When measurements = Measurements.from_counts(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), ]
def convert_measurements_to_sampleset( measurements, qubo, change_bitstring_convention=False ): qubo = load_qubo(qubo) measurements = Measurements.load_from_file(measurements) sampleset = _convert_measurements_to_sampleset( measurements, qubo, change_bitstring_convention ) save_sampleset(sampleset.aggregate(), "sampleset.json")
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 test_get_measurements_representing_distribution_gives_exactly_right_counts( self, bitstring_distribution): number_of_samples = 10000 measurements = Measurements.get_measurements_representing_distribution( bitstring_distribution, number_of_samples) counts = measurements.get_counts() for bitstring, probability in bitstring_distribution.distribution_dict.items( ): assert probability * number_of_samples == counts[bitstring]
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 aggregregate_measurements( self, jobs: List[IBMQJob], batches: List[List[QuantumCircuit]], multiplicities: List[int], **kwargs, ) -> List[Measurements]: """Combine samples from a circuit set that has been expanded and batched to obtain a set of measurements for each of the original circuits. Also applies readout correction after combining. Args: jobs: The submitted IBMQ jobs. batches: The batches of experiments submitted. multiplicities: The number of copies of each of the original circuits. kwargs: Passed to self.apply_readout_correction. Returns: A list of list of measurements, where each list of measurements corresponds to one of the circuits of the original (unexpanded) circuit set. """ ibmq_circuit_counts_set = [] for job, batch in zip(jobs, batches): for experiment in batch: ibmq_circuit_counts_set.append(job.result().get_counts(experiment)) measurements_set = [] ibmq_circuit_index = 0 for multiplicity in multiplicities: combined_counts = Counts({}) for i in range(multiplicity): for bitstring, counts in ibmq_circuit_counts_set[ ibmq_circuit_index ].items(): combined_counts[bitstring] = ( combined_counts.get(bitstring, 0) + counts ) ibmq_circuit_index += 1 if self.readout_correction: combined_counts = self.apply_readout_correction(combined_counts, kwargs) # qiskit counts object maps bitstrings in reversed order to ints, so we must # flip the bitstrings reversed_counts = {} for bitstring in combined_counts.keys(): reversed_counts[bitstring[::-1]] = int(combined_counts[bitstring]) measurements = Measurements.from_counts(reversed_counts) measurements_set.append(measurements) return measurements_set
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 get_summed_expectation_values( operator: str, measurements: str, use_bessel_correction: Optional[bool] = True): if isinstance(operator, str): operator = load_qubit_operator(operator) operator = change_operator_type(operator, openfermion.IsingOperator) if isinstance(measurements, str): measurements = Measurements.load_from_file(measurements) expectation_values = measurements.get_expectation_values( operator, use_bessel_correction=use_bessel_correction) value_estimate = sum_expectation_values(expectation_values) save_value_estimate(value_estimate, "value-estimate.json")
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_circuitset_and_measure(self, circuitset, **kwargs): """Run a set of circuits and measure a certain number of bitstrings. Note: the number of bitstrings measured is derived from self.n_samples Args: circuit (zquantum.core.circuit.Circuit): the circuit to prepare the state Returns: a list of lists of bitstrings (a list of lists of tuples) """ self.num_circuits_run += len(circuitset) self.num_jobs_run += 1 ibmq_circuitset = [] for circuit in circuitset: num_qubits = len(circuit.qubits) ibmq_circuit = circuit.to_qiskit() ibmq_circuit.barrier(range(num_qubits)) ibmq_circuit.measure(range(num_qubits), range(num_qubits)) ibmq_circuitset.append(ibmq_circuit) coupling_map = None if self.device_connectivity is not None: coupling_map = CouplingMap(self.device_connectivity.connectivity) # Run job on device and get counts job = execute( ibmq_circuitset, self.device, shots=self.n_samples, noise_model=self.noise_model, coupling_map=coupling_map, basis_gates=self.basis_gates, optimization_level=self.optimization_level, ) measurements_set = [] for i, ibmq_circuit in enumerate(ibmq_circuitset): circuit_counts = job.result().get_counts(ibmq_circuit) # qiskit counts object maps bitstrings in reversed order to ints, so we must flip the bitstrings reversed_counts = {} for bitstring in circuit_counts.keys(): reversed_counts[bitstring[::-1]] = circuit_counts[bitstring] measurements = Measurements.from_counts(reversed_counts) measurements_set.append(measurements) return measurements_set
def test_get_distribution(self): # Given measurements_data = { "schema": SCHEMA_VERSION + "-measurements", "counts": { "000": 1, "001": 2, "010": 1, "011": 1, "100": 1, "101": 1, "110": 1, "111": 1, }, "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], ], } input_filename = "measurements_input_test.json" with open(input_filename, "w") as f: f.write(json.dumps(measurements_data, indent=2)) measurements = Measurements.load_from_file(input_filename) # When distribution = measurements.get_distribution() # Then assert distribution.distribution_dict == { "000": 1 / 9, "001": 2 / 9, "010": 1 / 9, "011": 1 / 9, "100": 1 / 9, "101": 1 / 9, "110": 1 / 9, "111": 1 / 9, } remove_file_if_exists(input_filename)
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 _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 test_bitstrings(self): # Given measurements_data = { "schema": SCHEMA_VERSION + "-measurements", "counts": { "000": 1, "001": 2, "010": 1, "011": 1, "100": 1, "101": 1, "110": 1, "111": 1, }, "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], ], } input_filename = "measurements_input_test.json" with open(input_filename, "w") as f: f.write(json.dumps(measurements_data, indent=2)) measurements = Measurements.load_from_file(input_filename) # When/Then assert 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), ] remove_file_if_exists(input_filename)
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_io(self): # Given measurements_data = { "schema": SCHEMA_VERSION + "-measurements", "counts": { "000": 1, "001": 2, "010": 1, "011": 1, "100": 1, "101": 1, "110": 1, "111": 1, }, "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], ], } input_filename = "measurements_input_test.json" output_filename = "measurements_output_test.json" with open(input_filename, "w") as f: f.write(json.dumps(measurements_data, indent=2)) # When measurements = Measurements.load_from_file(input_filename) measurements.save(output_filename) # Then with open(output_filename, "r") as f: output_data = json.load(f) assert measurements_data == output_data remove_file_if_exists(input_filename) remove_file_if_exists(output_filename)
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