Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/pauli string test #10

Merged
merged 11 commits into from
Aug 5, 2024
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;
}
jamesETsmith marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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
Loading