Пример #1
0
def solve_maxcut_by_exhaustive_search(graph):
    """Brute-force solver for MAXCUT instances using exhaustive search.
    Args:
        graph (networkx.Graph): undirected weighted graph describing the MAXCUT 
        instance.
    
    Returns:
        tuple: tuple whose first elements is the number of cuts, and second is a list
            of bit strings that correspond to the solution(s).
    """

    solution_set = []
    num_nodes = len(graph.nodes)

    # find one MAXCUT solution
    maxcut = -1
    one_maxcut_solution = None
    for i in range(0, 2**num_nodes):
        trial_solution = dec2bin(i, num_nodes)
        current_cut = get_solution_cut_size(trial_solution, graph)
        if current_cut > maxcut:
            one_maxcut_solution = trial_solution
            maxcut = current_cut
    solution_set.append(one_maxcut_solution)

    # search again to pick up any degeneracies
    for i in range(0, 2**num_nodes):
        trial_solution = dec2bin(i, num_nodes)
        current_cut = get_solution_cut_size(trial_solution, graph)
        if current_cut == maxcut and trial_solution != one_maxcut_solution:
            solution_set.append(trial_solution)

    return maxcut, solution_set
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 _solve_bitstring_problem_by_exhaustive_search(
    cost_function: Callable[[Tuple[int]], float],
    num_nodes: int,
) -> Tuple[float, List[Tuple[int]]]:
    """
    Solves given cost function of a graph problem using exhaustive search.
    It searches for degeneracy and returns all the best solutions if more than one exists.

    Args:
        cost_function: function which calculates the cost of solution of a given problem.
            Callable that takes a bitstring solution and outputs cost value.
        num_nodes: number of nodes of the graph for which we want to solve the problem

    Returns:
        float: value of the best solution
        List[Tuple[int]]: list of solutions which correspond to the best value, each solution is a tuple of ints.
    """
    solutions_list = []

    best_value = np.inf

    for i in range(2**num_nodes):
        trial_solution: Tuple[int] = tuple(dec2bin(i, num_nodes))
        current_value = cost_function(trial_solution)
        if current_value == best_value:
            solutions_list.append(trial_solution)
        if current_value < best_value:
            best_value = current_value
            solutions_list = [trial_solution]

    return best_value, solutions_list
    def test_thermal_sampled_distribution(self):
        # Given
        n_samples = 5000
        n_spins = 2
        temperature = 0.85
        np.random.seed(SEED)  # needed by our samplers
        random.seed(SEED)  # needed to make lea reproducible

        external_fields = np.random.rand(n_spins)
        two_body_couplings = np.random.rand(n_spins, n_spins)
        hamiltonian_parameters = [external_fields, two_body_couplings]
        expected_bitstrings = convert_tuples_to_bitstrings(
            [dec2bin(number, n_spins) for number in range(int(2 ** n_spins))]
        )
        expected_keys = expected_bitstrings[len(expected_bitstrings) :: -1]

        # When
        sample_distribution = get_thermal_sampled_distribution(
            n_samples, n_spins, temperature, hamiltonian_parameters
        )

        # Then
        self.assertListEqual(
            sorted(list(sample_distribution.distribution_dict.keys())),
            sorted(expected_keys),
        )

        self.assertAlmostEqual(
            sum(list(sample_distribution.distribution_dict.values())), 1
        )
    def test_get_thermal_target_distribution_dict(self):
        # Given
        n_spins = 5
        temperature = 1.0
        expected_bitstrings = convert_tuples_to_bitstrings(
            [dec2bin(number, n_spins) for number in range(int(2 ** n_spins))]
        )
        expected_keys = expected_bitstrings[len(expected_bitstrings) :: -1]
        np.random.seed(SEED)
        external_fields = np.random.rand(n_spins)
        two_body_couplings = np.random.rand(n_spins, n_spins)
        hamiltonian_parameters = [external_fields, two_body_couplings]

        # When
        target_distribution = get_thermal_target_bitstring_distribution(
            n_spins, temperature, hamiltonian_parameters
        )

        # Then
        self.assertListEqual(
            sorted(list(target_distribution.distribution_dict.keys())),
            sorted(expected_keys),
        )

        self.assertAlmostEqual(
            sum(list(target_distribution.distribution_dict.values())),
            1.0,
        )
Пример #6
0
def _calculate_expectation_value_for_wavefunction(wavefunction: Wavefunction,
                                                  operator: IsingOperator,
                                                  alpha: float) -> float:
    expectation_values_per_bitstring = {}
    probability_per_bitstring = {}

    n_qubits = wavefunction.amplitudes.shape[0].bit_length() - 1

    for decimal_bitstring in range(2**n_qubits):
        # `decimal_bitstring` is the bitstring converted to decimal.

        # Convert decimal bitstring into bitstring
        bitstring = "".join(
            [str(int) for int in dec2bin(decimal_bitstring, n_qubits)])

        # Calculate expectation values for each bitstring.
        expected_value = _calculate_expectation_value_of_bitstring(
            bitstring, operator)
        expectation_values_per_bitstring[bitstring] = expected_value

        # Compute the probability p(x) for each n-bitstring x from the wavefunction,
        # p(x) = |amplitude of x| ^ 2.
        probability = np.abs(wavefunction.amplitudes[decimal_bitstring])**2
        probability_per_bitstring[bitstring] = float(probability)

    return _sum_expectation_values(expectation_values_per_bitstring,
                                   probability_per_bitstring, alpha)
Пример #7
0
        def f(j):  # Function which computes the index of the nonzero
            # element in P for a given column j

            j_str = dec2bin(j, n)
            for index in range(0, n):
                if label_vec[index] in [1, 2]:  # flip if X or Y
                    j_str[index] = int(not j_str[index])
            return bin2dec(j_str)
def convert_integer_to_ising_bitstring(number: int, length: int) -> typing.List[int]:
    """Converts an integer into a +/-1s bitstring (also called Ising bitstring).
    Args:
        number: positive number to be converted into its corresponding Ising bitstring representation
        length: length of the Ising bitstring (i.e. positive number of spins in the Ising system)
    Returns:
        Ising bitstring representation (1D array of +/-1)."""

    if length < 0:
        raise ValueError("Length cannot be negative.")
    binary_bitstring = dec2bin(number, length)
    ising_bitstring = [bit * 2 - 1 for bit in binary_bitstring]
    return ising_bitstring
Пример #9
0
        def nz(j):  # Function which computes the value of the nonzero
            # element in P on the column j

            val_nz = 1.0
            j_str = dec2bin(j, n)
            for index in range(0, n):
                if label_vec[index] == 2:
                    if j_str[index] == 0:
                        val_nz = val_nz * (1j)
                    if j_str[index] == 1:
                        val_nz = val_nz * (-1j)
                if label_vec[index] == 3:
                    if j_str[index] == 1:
                        val_nz = val_nz * (-1)
            return val_nz
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)
Пример #11
0
def get_qubitop_from_matrix(operator: List[List]) -> QubitOperator:
    r"""Expands a 2^n by 2^n matrix into n-qubit Pauli basis. The runtime of
	this function is O(2^2n).

	Args:
		operator: a list of lists (rows) representing a 2^n by 2^n
			matrix.

	Returns:
		A QubitOperator instance corresponding to the expansion of
		the input operator as a sum of Pauli strings:

		O = 2^-n \sum_P tr(O*P) P
	"""

    nrows = len(operator)
    ncols = len(operator[0])

    # Check if the input operator is square
    if nrows != ncols:
        raise Exception('The input operator is not square')

    # Check if the dimensions are powers of 2
    if not (((nrows & (nrows - 1)) == 0) and nrows > 0):
        raise Exception('The number of rows is not a power of 2')
    if not (((ncols & (ncols - 1)) == 0) and ncols > 0):
        raise Exception('The number of cols is not a power of 2')

    n = int(np.log2(nrows))  # number of qubits

    def decode(bit_string):  # Helper function for converting any 2n-bit
        # string to a label vector representing a Pauli
        # string of length n

        if len(bit_string) != 2 * n:
            raise Exception('LH_expand:decode: input bit string length not 2n')

        output_label = list(np.zeros(n))
        for i in range(0, n):
            output_label[i] = bin2dec(bit_string[2 * i:2 * i + 2])

        return output_label

    def trace_product(label_vec):  # Helper function for computing tr(OP)
        # where O is the input operator and P is a
        # Pauli string operator

        def f(j):  # Function which computes the index of the nonzero
            # element in P for a given column j

            j_str = dec2bin(j, n)
            for index in range(0, n):
                if label_vec[index] in [1, 2]:  # flip if X or Y
                    j_str[index] = int(not j_str[index])
            return bin2dec(j_str)

        def nz(j):  # Function which computes the value of the nonzero
            # element in P on the column j

            val_nz = 1.0
            j_str = dec2bin(j, n)
            for index in range(0, n):
                if label_vec[index] == 2:
                    if j_str[index] == 0:
                        val_nz = val_nz * (1j)
                    if j_str[index] == 1:
                        val_nz = val_nz * (-1j)
                if label_vec[index] == 3:
                    if j_str[index] == 1:
                        val_nz = val_nz * (-1)
            return val_nz

        # Compute the trace
        tr = 0.0
        for j in range(0, 2**n):  # loop over the columns
            tr = tr + operator[j][f(j)] * nz(j)

        return tr / 2**n

    # Expand the operator in Pauli basis
    coeffs = list(np.zeros(4**n))
    labels = list(np.zeros(4**n))
    for i in range(0, 4**n):  # loop over all 2n-bit strings
        current_string = dec2bin(i, 2 * n)  # see util.py
        current_label = decode(current_string)
        coeffs[i] = trace_product(current_label)
        labels[i] = current_label

    return get_qubitop_from_coeffs_and_labels(coeffs, labels)
Пример #12
0
 def test_dec_bin_conversion(self):
     integer = random.randint(1, 10 ** 9)
     integer2 = bin2dec(dec2bin(integer, 30))
     assert integer == integer2
Пример #13
0
def test_ordered_bitstring(num_qubits):
    bitstrings = get_ordered_list_of_bitstrings(num_qubits)
    expected_bitstrings = convert_tuples_to_bitstrings(
        [dec2bin(integer, num_qubits) for integer in range(2**num_qubits)])
    assert np.all(expected_bitstrings == bitstrings)
    assert np.all([len(bitstring) == num_qubits for bitstring in bitstrings])