Skip to content

Commit

Permalink
Move normalization to the C++ layer
Browse files Browse the repository at this point in the history
  • Loading branch information
drqec committed Oct 8, 2024
1 parent 3cf6434 commit d4468ba
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 76 deletions.
105 changes: 49 additions & 56 deletions src/stim/stabilizers/tableau.pybind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1969,55 +1969,56 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_<T
)DOC")
.data());

c.def(pybind11::pickle(
[](const Tableau<MAX_BITWORD_WIDTH> &self) {
pybind11::dict d;
std::vector<FlexPauliString> xs;
std::vector<FlexPauliString> zs;
for (size_t q = 0; q < self.num_qubits; q++) {
xs.push_back(FlexPauliString(self.xs[q]));
}
for (size_t q = 0; q < self.num_qubits; q++) {
zs.push_back(FlexPauliString(self.zs[q]));
}
d["xs"] = xs;
d["zs"] = zs;
return d;
},
[](const pybind11::dict &d) {
std::vector<FlexPauliString> xs;
std::vector<FlexPauliString> zs;
for (const auto &e : d["xs"]) {
xs.push_back(pybind11::cast<FlexPauliString>(e));
}
for (const auto &e : d["zs"]) {
zs.push_back(pybind11::cast<FlexPauliString>(e));
}
c.def(
pybind11::pickle(
[](const Tableau<MAX_BITWORD_WIDTH> &self) {
pybind11::dict d;
std::vector<FlexPauliString> xs;
std::vector<FlexPauliString> zs;
for (size_t q = 0; q < self.num_qubits; q++) {
xs.push_back(FlexPauliString(self.xs[q]));
}
for (size_t q = 0; q < self.num_qubits; q++) {
zs.push_back(FlexPauliString(self.zs[q]));
}
d["xs"] = xs;
d["zs"] = zs;
return d;
},
[](const pybind11::dict &d) {
std::vector<FlexPauliString> xs;
std::vector<FlexPauliString> zs;
for (const auto &e : d["xs"]) {
xs.push_back(pybind11::cast<FlexPauliString>(e));
}
for (const auto &e : d["zs"]) {
zs.push_back(pybind11::cast<FlexPauliString>(e));
}

size_t n = xs.size();
bool correct_shape = zs.size() == n;
for (const auto &e : xs) {
correct_shape &= !e.imag;
correct_shape &= e.value.num_qubits == n;
}
for (const auto &e : zs) {
correct_shape &= !e.imag;
correct_shape &= e.value.num_qubits == n;
}
if (!correct_shape) {
throw std::invalid_argument("Invalid pickle.");
}
size_t n = xs.size();
bool correct_shape = zs.size() == n;
for (const auto &e : xs) {
correct_shape &= !e.imag;
correct_shape &= e.value.num_qubits == n;
}
for (const auto &e : zs) {
correct_shape &= !e.imag;
correct_shape &= e.value.num_qubits == n;
}
if (!correct_shape) {
throw std::invalid_argument("Invalid pickle.");
}

Tableau<MAX_BITWORD_WIDTH> result(n);
for (size_t q = 0; q < n; q++) {
result.xs[q] = xs[q].value;
result.zs[q] = zs[q].value;
}
if (!result.satisfies_invariants()) {
throw std::invalid_argument("Pickled tableau was invalid. It doesn't preserve commutativity.");
}
return result;
}));
Tableau<MAX_BITWORD_WIDTH> result(n);
for (size_t q = 0; q < n; q++) {
result.xs[q] = xs[q].value;
result.zs[q] = zs[q].value;
}
if (!result.satisfies_invariants()) {
throw std::invalid_argument("Pickled tableau was invalid. It doesn't preserve commutativity.");
}
return result;
}));

c.def_static(
"from_stabilizers",
Expand Down Expand Up @@ -2130,16 +2131,8 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_<T
}

std::vector<std::complex<float>> v;
double weight = 0;
for (const auto &obj : state_vector) {
v.push_back(pybind11::cast<std::complex<float>>(obj));
weight += std::norm(v.back());
}
if (weight != 1.0 && weight > 0.0) {
std::complex<float> scale = (float)sqrt(1.0 / weight);
for (auto &amplitude : v) {
amplitude *= scale;
}
}

return circuit_to_tableau<MAX_BITWORD_WIDTH>(
Expand All @@ -2155,7 +2148,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_<T
Args:
state_vector: A list of complex amplitudes specifying a superposition. The
vector must correspond to a state that is reachable using Clifford
operations.
operations. May be unnormalized.
endian:
"little": state vector is in little endian order, where higher index
qubits correspond to larger changes in the state index.
Expand Down
11 changes: 3 additions & 8 deletions src/stim/stabilizers/tableau_pybind_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand Down Expand Up @@ -54,6 +56,7 @@ def test_from_named_gate():
with pytest.raises(IndexError, match="not unitary"):
stim.Tableau.from_named_gate("X_ERROR")


def test_from_state_vector():
t = stim.Tableau.from_state_vector([
0.5**0.5,
Expand All @@ -67,14 +70,6 @@ def test_from_state_vector():
assert t.z_output(0) == stim.PauliString("XX")
assert t.z_output(1) == stim.PauliString("ZZ")

unnormalized = stim.Tableau.from_state_vector([
1,
0,
0,
1
], endian='little')
assert unnormalized == t


def test_identity():
t = stim.Tableau(3)
Expand Down
20 changes: 10 additions & 10 deletions src/stim/util_top/circuit_vs_amplitudes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ Circuit stim::stabilizer_state_vector_to_circuit(
}

uint8_t num_qubits = floor_lg2(state_vector.size());
double weight = 0;
for (const auto &c : state_vector) {
weight += std::norm(c);
}
if (abs(weight - 1) > 0.125) {
throw std::invalid_argument(
"The given state vector wasn't a unit vector. It had a length of " + std::to_string(weight) + ".");
}

VectorSimulator sim(num_qubits);
sim.state = state_vector;
/*
double weight = 0;
for (const auto &c : sim.state) {
weight += std::norm(c);
}
if (weight == 0.0) {
throw std::invalid_argument("The given state vector has zero length.");
}
*/

Circuit recorded;
auto apply = [&](GateType gate_type, uint32_t target) {
Expand All @@ -73,7 +73,7 @@ Circuit stim::stabilizer_state_vector_to_circuit(
{});
};

// Move biggest amplitude to start of state vector..
// Move biggest amplitude to start of state vector.
size_t pivot = biggest_index(state_vector);
for (size_t q = 0; q < num_qubits; q++) {
if ((pivot >> q) & 1) {
Expand Down
3 changes: 2 additions & 1 deletion src/stim/util_top/circuit_vs_amplitudes.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace stim {
/// Synthesizes a circuit to generate the given state vector.
///
/// Args:
/// stabilizer_state_vector: The vector of amplitudes to produce using a circuit.
/// stabilizer_state_vector: The vector of amplitudes to produce using a circuit. Must be non-zero and will be
/// normalized if necessary.
/// little_endian: Whether the vector is using little endian or big endian ordering.
/// inverted_circuit: If false, returns a circuit that sends |000...0> to the state vector.
/// If true, returns a circuit that sends the state vector to |000...0> instead of a cir.
Expand Down
33 changes: 32 additions & 1 deletion src/stim/util_top/circuit_vs_amplitudes.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
using namespace stim;

TEST(conversions, stabilizer_state_vector_to_circuit_basic) {
ASSERT_THROW(stabilizer_state_vector_to_circuit({}, false), std::invalid_argument);

ASSERT_THROW(
stabilizer_state_vector_to_circuit(
{
{0.5},
{0},
},
false),
std::invalid_argument);
Expand Down Expand Up @@ -175,6 +177,35 @@ TEST(conversions, stabilizer_state_vector_to_circuit_fuzz_round_trip) {
}
}

TEST(conversions, stabilizer_state_vector_to_circuit_unnormalized_fuzz_round_trip) {
auto rng = INDEPENDENT_TEST_RNG();
auto little_endian = true;

for (size_t i = 0; i < 100; i++) {
// Pick a random stabilizer state.
size_t n = i % 5;
TableauSimulator<64> sim(INDEPENDENT_TEST_RNG(), n);
sim.inv_state = Tableau<64>::random(n, rng);
auto desired_vec = sim.to_state_vector(little_endian);

// Unnormalize by multiplying by a random non-zero factor.
auto scaled_vec = desired_vec;
std::uniform_real_distribution<float> dist(-1000.0, +1000.0);
std::complex<float> scale = {dist(rng), dist(rng)};
while (std::norm(scale) < 0.01) {
scale = {dist(rng), dist(rng)};
}
for (auto &c : scaled_vec) {
c *= scale;
}

// Round trip through a circuit.
auto circuit = stabilizer_state_vector_to_circuit(scaled_vec, little_endian);
auto actual_vec = circuit_to_output_state_vector(circuit, little_endian);
ASSERT_EQ(actual_vec, desired_vec) << " scale=" << scale;
}
}

TEST(conversions, circuit_to_output_state_vector) {
ASSERT_EQ(circuit_to_output_state_vector(Circuit(""), false), (std::vector<std::complex<float>>{{1}}));
ASSERT_EQ(
Expand Down

0 comments on commit d4468ba

Please sign in to comment.