Skip to content

Commit

Permalink
Merge pull request #10 from qognitive/feature/pauli_string_test
Browse files Browse the repository at this point in the history
Feature/pauli string test
  • Loading branch information
stand-by authored Aug 5, 2024
2 parents 6f4940a + 08f8af5 commit fae3020
Show file tree
Hide file tree
Showing 13 changed files with 545 additions and 140 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/all_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ jobs:
# Build your program with the given configuration
run: |
cmake --build ${{github.workspace}}/build --verbose --parallel
- name: Install
run: |
cmake --install ${{github.workspace}}/build
python -m pip install .[dev]
- name: Test C++
env:
OMP_NUM_THREADS: 2
Expand All @@ -40,6 +44,8 @@ jobs:
./${CPP_TEST_DIR}/test_pauli_op --test-case-exclude="*multistring*"
./${CPP_TEST_DIR}/test_pauli_string
./${CPP_TEST_DIR}/test_summed_pauli_op
- name: Test Python
run: make test-py

# - name: Test Python
# run: PYTHONPATH=build:$PYTHONPATH pytest -v test
12 changes: 8 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ build:
python -m pip install ".[dev]"
python -m build .

.PHONY: tests
tests:
test-cpp:
ctest --test-dir build
python -m pytest fast_pauli/py/tests
python -m pytest tests

test-py:
python -m pytest -v fast_pauli/py/tests
python -m pytest -v tests

.PHONY: test
test: test-cpp test-py

.PHONY: clean
clean:
Expand Down
138 changes: 138 additions & 0 deletions fast_pauli/cpp/include/__pauli_helpers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#ifndef __PAULI_HELPERS_HPP
#define __PAULI_HELPERS_HPP

#include <fmt/format.h>

#include <ranges>

#include "__pauli_string.hpp"

namespace fast_pauli {
//
// Helper
//

/**
* @brief Get the nontrivial sets of pauli matrices given a weight.
*
* @param weight
* @return std::vector<std::string>
*/
std::vector<std::string> get_nontrivial_paulis(size_t const weight) {
// We want to return no paulis for weight 0
if (weight == 0) {
return {};
}

// For Weight >= 1
std::vector<std::string> set_of_nontrivial_paulis{"X", "Y", "Z"};

for (size_t i = 1; i < weight; i++) {
std::vector<std::string> updated_set_of_nontrivial_paulis;
for (auto const &str : set_of_nontrivial_paulis) {
for (auto pauli : {"X", "Y", "Z"}) {
updated_set_of_nontrivial_paulis.push_back(str + pauli);
}
}
set_of_nontrivial_paulis = std::move(updated_set_of_nontrivial_paulis);
}
return set_of_nontrivial_paulis;
}

/**
* @brief Get all the combinations of k indices for a given array of size n.
*
* @param n
* @param k
* @return std::vector<std::vector<size_t>>
*/
std::vector<std::vector<size_t>> idx_combinations(size_t const n,
size_t const k) {

// TODO this is a very inefficient way to do this
std::vector<std::vector<size_t>> result;
std::vector<size_t> bitmask(k, 1); // K leading 1's
bitmask.resize(n, 0); // N-K trailing 0's

do {
std::vector<size_t> combo;
for (size_t i = 0; i < n; ++i) {
if (bitmask[i]) {
combo.push_back(i);
}
}
result.push_back(combo);
} while (std::ranges::prev_permutation(bitmask).found);
return result;
}

/**
* @brief Calculate all possible PauliStrings for a given number of qubits and
* weight and return them in lexicographical order.
*
* @param n_qubits
* @param weight
* @return std::vector<PauliString>
*/
std::vector<PauliString> calcutate_pauli_strings(size_t const n_qubits,
size_t const weight) {

// base case
if (weight == 0) {
return {PauliString(std::string(n_qubits, 'I'))};
}

// for weight >= 1
std::string base_str(n_qubits, 'I');

auto nontrivial_paulis = get_nontrivial_paulis(weight);
auto idx_combos = idx_combinations(n_qubits, weight);
size_t n_pauli_strings = nontrivial_paulis.size() * idx_combos.size();
std::vector<PauliString> result(n_pauli_strings);

fmt::println(
"n_qubits = {} weight = {} n_nontrivial_paulis = {} n_combos = {}",
n_qubits, weight, nontrivial_paulis.size(), idx_combos.size());

// Iterate through all the nontrivial paulis and all the combinations
for (size_t i = 0; i < nontrivial_paulis.size(); ++i) {
for (size_t j = 0; j < idx_combos.size(); ++j) {
// Creating new pauli string at index i*idx_combos.size() + j
// Overwriting the base string with the appropriate nontrivial paulis
// at the specified indices
std::string str = base_str;
for (size_t k = 0; k < idx_combos[j].size(); ++k) {
size_t idx = idx_combos[j][k];
str[idx] = nontrivial_paulis[i][k];
}
result[i * idx_combos.size() + j] = PauliString(str);
}
}

return result;
}

/**
* @brief Calculate all possible PauliStrings for a given number of qubits and
* all weights less than or equal to a given weight.
*
* @param n_qubits
* @param weight
* @return std::vector<PauliString>
*/
std::vector<PauliString> calculate_pauli_strings_max_weight(size_t n_qubits,
size_t weight) {
std::vector<PauliString> result;
for (size_t i = 0; i <= weight; ++i) {
auto ps = calcutate_pauli_strings(n_qubits, i);
result.insert(result.end(), ps.begin(), ps.end());
}

fmt::println("n_qubits = {} weight = {} n_pauli_strings = {}", n_qubits,
weight, result.size());
return result;
}

} // namespace fast_pauli

#endif // __PAULI_HELPERS_HPP
144 changes: 12 additions & 132 deletions fast_pauli/cpp/include/__pauli_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,13 @@ struct PauliString {

/**
* @brief Return the dimension (2^n_qubits) of the PauliString.
* @note this returns 0 if the PauliString is empty.
*
* @return size_t
*/
size_t dims() const noexcept { return 1UL << paulis.size(); }
size_t dims() const noexcept {
return paulis.size() ? 1UL << paulis.size() : 0;
}

/**
* @brief Get the sparse representation of the pauli string matrix.
Expand Down Expand Up @@ -139,7 +142,10 @@ struct PauliString {
size_t const nY =
std::count_if(ps.begin(), ps.end(),
[](fast_pauli::Pauli const &p) { return p.code == 2; });
size_t const dim = 1 << n;
size_t const dim = n ? 1 << n : 0;

if (dim == 0)
return;

// Safe, but expensive, we overwrite the vectors
k = std::vector<size_t>(dim);
Expand All @@ -155,7 +161,7 @@ struct PauliString {
}
};
// Helper function that resolves first value of pauli string
auto inital_value = [&nY]() -> std::complex<T> {
auto initial_value = [&nY]() -> std::complex<T> {
switch (nY % 4) {
case 0:
return 1.0;
Expand All @@ -174,7 +180,7 @@ struct PauliString {
for (size_t i = 0; i < ps.size(); ++i) {
k[0] += (1UL << i) * diag(ps[i]);
}
m[0] = inital_value();
m[0] = initial_value();

// Populate the rest of the values in a recursive-like manner
for (size_t l = 0; l < n; ++l) {
Expand Down Expand Up @@ -224,7 +230,7 @@ struct PauliString {
*/
template <std::floating_point T>
std::vector<std::complex<T>>
apply(std::mdspan<const std::complex<T>, std::dextents<size_t, 1>> v) const {
apply(std::mdspan<std::complex<T> const, std::dextents<size_t, 1>> v) const {
// Input check
if (v.size() != dims()) {
throw std::invalid_argument(
Expand Down Expand Up @@ -322,132 +328,6 @@ struct PauliString {
}
};

//
// Helper
//
// TODO arrange following functions in a separate header __pauli_utils.hpp

/**
* @brief Get the nontrivial sets of pauli matrices given a weight.
*
* @param weight
* @return std::vector<std::string>
*/
std::vector<std::string> get_nontrivial_paulis(size_t const weight) {
// We want to return no paulis for weight 0
if (weight == 0) {
return {};
}

// For Weight >= 1
std::vector<std::string> set_of_nontrivial_paulis{"X", "Y", "Z"};

for (size_t i = 1; i < weight; i++) {
std::vector<std::string> updated_set_of_nontrivial_paulis;
for (auto str : set_of_nontrivial_paulis) {
for (auto pauli : {"X", "Y", "Z"}) {
updated_set_of_nontrivial_paulis.push_back(str + pauli);
}
}
set_of_nontrivial_paulis = std::move(updated_set_of_nontrivial_paulis);
}
return set_of_nontrivial_paulis;
}

/**
* @brief Get all the combinations of k indices for a given array of size n.
*
* @param n
* @param k
* @return std::vector<std::vector<size_t>>
*/
std::vector<std::vector<size_t>> idx_combinations(size_t const n,
size_t const k) {

// TODO this is a very inefficient way to do this
std::vector<std::vector<size_t>> result;
std::vector<size_t> bitmask(k, 1); // K leading 1's
bitmask.resize(n, 0); // N-K trailing 0's

do {
std::vector<size_t> combo;
for (size_t i = 0; i < n; ++i) {
if (bitmask[i]) {
combo.push_back(i);
}
}
result.push_back(combo);
} while (std::ranges::prev_permutation(bitmask).found);
return result;
}

/**
* @brief Calculate all possible PauliStrings for a given number of qubits and
* weight and return them in lexicographical order.
*
* @param n_qubits
* @param weight
* @return std::vector<PauliString>
*/
std::vector<PauliString> calcutate_pauli_strings(size_t const n_qubits,
size_t const weight) {

// base case
if (weight == 0) {
return {PauliString(std::string(n_qubits, 'I'))};
}

// for weight >= 1
std::string base_str(n_qubits, 'I');

auto nontrivial_paulis = get_nontrivial_paulis(weight);
auto idx_combos = idx_combinations(n_qubits, weight);
size_t n_pauli_strings = nontrivial_paulis.size() * idx_combos.size();
std::vector<PauliString> result(n_pauli_strings);

fmt::println(
"n_qubits = {} weight = {} n_nontrivial_paulis = {} n_combos = {}",
n_qubits, weight, nontrivial_paulis.size(), idx_combos.size());

// Iterate through all the nontrivial paulis and all the combinations
for (size_t i = 0; i < nontrivial_paulis.size(); ++i) {
for (size_t j = 0; j < idx_combos.size(); ++j) {
// Creating new pauli string at index i*idx_combos.size() + j
// Overwriting the base string with the appropriate nontrivial paulis
// at the specified indices
std::string str = base_str;
for (size_t k = 0; k < idx_combos[j].size(); ++k) {
size_t idx = idx_combos[j][k];
str[idx] = nontrivial_paulis[i][k];
}
result[i * idx_combos.size() + j] = PauliString(str);
}
}

return result;
}

/**
* @brief Calculate all possible PauliStrings for a given number of qubits and
* all weights less than or equal to a given weight.
*
* @param n_qubits
* @param weight
* @return std::vector<PauliString>
*/
std::vector<PauliString> calculate_pauli_strings_max_weight(size_t n_qubits,
size_t weight) {
std::vector<PauliString> result;
for (size_t i = 0; i <= weight; ++i) {
auto ps = calcutate_pauli_strings(n_qubits, i);
result.insert(result.end(), ps.begin(), ps.end());
}

fmt::println("n_qubits = {} weight = {} n_pauli_strings = {}", n_qubits,
weight, result.size());
return result;
}

} // namespace fast_pauli

//
Expand All @@ -461,7 +341,7 @@ template <> struct fmt::formatter<fast_pauli::PauliString> {
template <typename FormatContext>
auto format(fast_pauli::PauliString const &ps, FormatContext &ctx) const {
std::vector<fast_pauli::Pauli> paulis = ps.paulis;
return fmt::format_to(ctx.out(), "{}", fmt::join(paulis, "x"));
return fmt::format_to(ctx.out(), "{}", fmt::join(paulis, ""));
}
};

Expand Down
1 change: 1 addition & 0 deletions fast_pauli/cpp/include/fast_pauli.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "__factory.hpp"
#include "__pauli.hpp"
#include "__pauli_helpers.hpp"
#include "__pauli_op.hpp"
#include "__pauli_string.hpp"
#include "__summed_pauli_op.hpp"
Expand Down
Loading

0 comments on commit fae3020

Please sign in to comment.