def get_bars_and_stripes_target_distribution(nrows, ncols, fraction=1., method="zigzag"): ''' Generates bars and stripes (BAS) data in zigzag pattern Args: nrows (int): number of rows in BAS dataset ncols (int): number of columns in BAS dataset fraction (float): maximum fraction of patterns to include (at least one pattern will always be included) method (string): the method to use to label the qubits Returns: Array of list of BAS pattern. ''' if method == "zigzag": data = bars_and_stripes_zigzag(nrows, ncols) else: raise RuntimeError( "Method: {} is not supported for generated a target distribution for bars and stripes" .format(method)) # Remove patterns until left with a subset that has cardinality less than or equal to the percentage * total number of patterns num_desired_patterns = int(len(data) * fraction) num_desired_patterns = max(num_desired_patterns, 1) data = random.sample(list(data), num_desired_patterns) distribution_dict = {} for pattern in data: bitstring = "" for qubit in pattern: bitstring += str(qubit) distribution_dict[bitstring] = 1. return BitstringDistribution(distribution_dict)
def get_thermal_sampled_distribution( n_samples: int, n_spins: int, temperature: float, hamiltonian_parameters: typing.Tuple[np.ndarray, np.ndarray], ) -> BitstringDistribution: """Generates thermal states sample distribution Args: n_samples: the number of samples from the original distribution n_spins: number of spins in the Ising system temperature: temperature factor in the Boltzmann distribution Returns: histogram_samples: keys are binary string representations and values are corresponding probabilities. """ distribution = get_thermal_target_bitstring_distribution( n_spins, temperature, hamiltonian_parameters ).distribution_dict sample_distribution_dict = sample_from_probability_distribution( distribution, n_samples ) histogram_samples = np.zeros(2 ** n_spins) for samples, counts in sample_distribution_dict.items(): integer_list: typing.List[int] = [] for elem in samples: integer_list.append(int(elem)) idx = convert_ising_bitstring_to_integer(integer_list) histogram_samples[idx] += counts / n_samples for spin in range(int(2 ** n_spins)): binary_bitstring = convert_tuples_to_bitstrings([dec2bin(spin, n_spins)])[0] sample_distribution_dict[binary_bitstring] = histogram_samples[spin] return BitstringDistribution(sample_distribution_dict)
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 setUp(self): number_of_layers = 1 number_of_qubits = 4 topology = "all" self.ansatz = QCBMAnsatz(number_of_layers, number_of_qubits, topology) self.target_bitstring_distribution = BitstringDistribution({ "0000": 1.0, "0001": 0.0, "0010": 0.0, "0011": 1.0, "0100": 0.0, "0101": 1.0, "0110": 0.0, "0111": 0.0, "1000": 0.0, "1001": 0.0, "1010": 1.0, "1011": 0.0, "1100": 1.0, "1101": 0.0, "1110": 0.0, "1111": 1.0, }) self.backend = create_object({ "module_name": "zquantum.core.interfaces.mock_objects", "function_name": "MockQuantumSimulator", "n_samples": 1, }) self.gradient_types = ["finite_difference"]
def QCBMCostFunction( ansatz: Ansatz, backend: QuantumBackend, distance_measure: Callable, distance_measure_parameters: dict, target_bitstring_distribution: BitstringDistribution, gradient_type: str = "finite_difference", ): """Cost function used for evaluating QCBM. Args: ansatz (zquantum.core.interfaces.ansatz.Ansatz): the ansatz used to construct the variational circuits backend (zquantum.core.interfaces.backend.QuantumBackend): backend used for QCBM evaluation distance_measure (callable): function used to calculate the distance measure distance_measure_parameters (dict): dictionary containing the relevant parameters for the chosen distance measure target_bitstring_distribution (zquantum.core.bitstring_distribution.BitstringDistribution): bistring distribution which QCBM aims to learn gradient_type (str): parameter indicating which type of gradient should be used. Returns: Callable that evaluates the parametrized circuit produced by the ansatz with the given parameters and returns the distance between the produced bitstring distribution and the target distribution """ assert (int(target_bitstring_distribution.get_qubits_number()) == ansatz.number_of_qubits) def cost_function(parameters: np.ndarray, store_artifact: StoreArtifact = None) -> ValueEstimate: """ Evaluates the value of the cost function for given parameters. Args: parameters: parameters for which the evaluation should occur. Returns: (float): cost function value for given parameters zquantum.core.bitstring_distribution.BitstringDistribution: distribution obtained """ circuit = ansatz.get_executable_circuit(parameters) distribution = backend.get_bitstring_distribution(circuit) value = evaluate_distribution_distance( target_bitstring_distribution, distribution, distance_measure, distance_measure_parameters=distance_measure_parameters, ) if store_artifact: store_artifact("bitstring_distribution", distribution) return ValueEstimate(value) if gradient_type == "finite_difference": cost_function = function_with_gradient( cost_function, finite_differences_gradient(cost_function)) else: raise RuntimeError("Unsupported gradient type: ", gradient_type) return cost_function
def get_thermal_target_bitstring_distribution( n_spins: int, temperature: float, hamiltonian_parameters: typing.Tuple[np.ndarray, np.ndarray], ) -> BitstringDistribution: """Generates thermal states target distribution, saved in a dict where keys are bitstrings and values are corresponding probabilities according to the Boltzmann Distribution formula. Args: n_spins: positive number of spins in the Ising system temperature: temperature factor in the boltzman distribution hamiltonian_parameters: values of hamiltonian parameters, namely external fields and two body couplings. Returns: Thermal target distribution. Number of positive spins in the spin state. """ partition_function = 0 external_fields, two_body_couplings = hamiltonian_parameters beta = 1.0 / temperature distribution = {} for spin in range(int(2 ** n_spins)): ising_bitstring = convert_integer_to_ising_bitstring(spin, n_spins) energy = 0 for i in range(n_spins): energy -= ising_bitstring[i] * external_fields[i] if i != n_spins - 1: energy -= ( ising_bitstring[i] * ising_bitstring[i + 1] * two_body_couplings[i, i + 1] ) boltzmann_factor = np.exp(energy * beta) partition_function += boltzmann_factor binary_bitstring = convert_tuples_to_bitstrings([dec2bin(spin, n_spins)])[0] distribution[binary_bitstring] = boltzmann_factor normalized_distribution = { key: value / partition_function for key, value in distribution.items() } return BitstringDistribution(normalized_distribution)
def _create_QCBM_cost_function( ansatz: Ansatz, backend: QuantumBackend, n_samples: int, distance_measure: DistanceMeasure, distance_measure_parameters: dict, target_bitstring_distribution: BitstringDistribution, ): assert (int(target_bitstring_distribution.get_qubits_number()) == ansatz.number_of_qubits) def cost_function(parameters: np.ndarray, store_artifact: StoreArtifact = None) -> ValueEstimate: """ Evaluates the value of the cost function for given parameters. Args: parameters: parameters for which the evaluation should occur. store_artifact: callable defining how the bitstring distributions should be stored. """ # TODO: we use private method here due to performance reasons. # This should be fixed once better mechanism for handling it will be implemented. # In case of questions ask mstechly. # circuit = ansatz.get_executable_circuit(parameters) circuit = ansatz._generate_circuit(parameters) distribution = backend.get_bitstring_distribution(circuit, n_samples) value = evaluate_distribution_distance( target_bitstring_distribution, distribution, distance_measure, distance_measure_parameters=distance_measure_parameters, ) if store_artifact: store_artifact("bitstring_distribution", distribution) return ValueEstimate(value) return cost_function
HistoryEntry( call_number=0, params=np.array([0.1, 0.2, 0.3j]), value=ValueEstimate(0.5, precision=6), ), HistoryEntry(call_number=1, params=np.array([1, 2, 3]), value=-10.0), HistoryEntryWithArtifacts( call_number=2, params=np.array([-1, -0.5, -0.6]), value=-20.0, artifacts={ "bitstring": "0111", "bitstring_distribution": BitstringDistribution({ "111": 0.25, "010": 0.75 }), }, ), ], ) EXPECTED_DESERIALIZED_RESULT = { "schema": "zapata-v1-optimization_result", "opt_value": 0.5, "opt_params": convert_array_to_dict(np.array([0, 0.5, 2.5])), "nit": 3,
class TestMeasurements: 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_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_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 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 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 test_get_counts(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 counts = measurements.get_counts() # Then assert measurements_data["counts"] == counts remove_file_if_exists(input_filename) 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 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 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_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_expectation_values_from_measurements_with_bessel_correction( 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) - 1 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, True) # 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) @pytest.mark.parametrize( "bitstring_distribution, number_of_samples", [ (BitstringDistribution({ "00": 0.5, "11": 0.5 }), 1), (BitstringDistribution({ "00": 0.5, "11": 0.5 }), 10), (BitstringDistribution({ "00": 0.5, "11": 0.5 }), 51), (BitstringDistribution({ "00": 0.5, "11": 0.5 }), 137), (BitstringDistribution({ "00": 0.5, "11": 0.5 }), 5000), (BitstringDistribution({ "0000": 0.137, "0001": 0.863 }), 100), ( BitstringDistribution({ "00": 0.1234, "01": 0.5467, "10": 0.0023, "11": 0.3276 }), 100, ), ( BitstringDistribution({ "0000": 0.06835580857498666, "1000": 0.060975627112613416, "0100": 0.05976605586194627, "1100": 0.07138587439957303, "0010": 0.06474168297455969, "1010": 0.0825036470378936, "0110": 0.09861252446183953, "1110": 0.0503013698630137, "0001": 0.04496424123821384, "1001": 0.07317221135029355, "0101": 0.08171161714997331, "1101": 0.03753940579967977, "0011": 0.05157676570005337, "1011": 0.05, "0111": 0.04964419142501335, "1111": 0.05474897705034692, }), 5621, ), ], ) def test_get_measurements_representing_distribution_returns_right_number_of_samples( self, bitstring_distribution, number_of_samples): measurements = Measurements.get_measurements_representing_distribution( bitstring_distribution, number_of_samples) assert len(measurements.bitstrings) == number_of_samples @pytest.mark.parametrize( "bitstring_distribution, number_of_samples, expected_counts", [ ( BitstringDistribution({ "01": 0.3333333, "11": (1 - 0.3333333) }), 3, { "01": 1, "11": 2 }, ), ( BitstringDistribution({ "01": 0.9999999, "11": (1 - 0.9999999) }), 1, { "01": 1 }, ), ], ) def test_get_measurements_representing_distribution_correctly_samples_leftover_bitstrings( self, bitstring_distribution, number_of_samples, expected_counts): random.seed(RNDSEED) measurements = Measurements.get_measurements_representing_distribution( bitstring_distribution, number_of_samples) assert measurements.get_counts() == expected_counts 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 @pytest.mark.parametrize( "bitstring_distribution", [ BitstringDistribution({ "00": 0.5, "11": 0.5 }), BitstringDistribution({ "000": 0.5, "101": 0.5 }), BitstringDistribution({ "0000": 0.137, "0001": 0.863 }), BitstringDistribution({ "00": 0.1234, "01": 0.5467, "10": 0.0023, "11": 0.3276 }), ], ) 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]
from zquantum.qcbm.cost_function import QCBMCostFunction, create_QCBM_cost_function number_of_layers = 1 number_of_qubits = 4 topology = "all" ansatz = QCBMAnsatz(number_of_layers, number_of_qubits, topology) target_bitstring_distribution = BitstringDistribution( { "0000": 1.0, "0001": 0.0, "0010": 0.0, "0011": 1.0, "0100": 0.0, "0101": 1.0, "0110": 0.0, "0111": 0.0, "1000": 0.0, "1001": 0.0, "1010": 1.0, "1011": 0.0, "1100": 1.0, "1101": 0.0, "1110": 0.0, "1111": 1.0, } ) backend = create_object( { "module_name": "zquantum.core.symbolic_simulator", "function_name": "SymbolicSimulator", }