diff --git a/file_lists/perf_files b/file_lists/perf_files index 2d6ac07e1..857fa1e01 100644 --- a/file_lists/perf_files +++ b/file_lists/perf_files @@ -12,6 +12,7 @@ src/stim/simulators/dem_sampler.perf.cc src/stim/simulators/error_analyzer.perf.cc src/stim/simulators/frame_simulator.perf.cc src/stim/simulators/tableau_simulator.perf.cc +src/stim/stabilizers/clifford_string.perf.cc src/stim/stabilizers/pauli_string.perf.cc src/stim/stabilizers/pauli_string_iter.perf.cc src/stim/stabilizers/tableau.perf.cc diff --git a/file_lists/test_files b/file_lists/test_files index b00287c50..e171db8df 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -67,6 +67,7 @@ src/stim/simulators/measurements_to_detection_events.test.cc src/stim/simulators/sparse_rev_frame_tracker.test.cc src/stim/simulators/tableau_simulator.test.cc src/stim/simulators/vector_simulator.test.cc +src/stim/stabilizers/clifford_string.test.cc src/stim/stabilizers/flex_pauli_string.test.cc src/stim/stabilizers/flow.test.cc src/stim/stabilizers/pauli_string.test.cc diff --git a/src/stim.h b/src/stim.h index e04fcdba1..0ad32d289 100644 --- a/src/stim.h +++ b/src/stim.h @@ -92,6 +92,7 @@ #include "stim/simulators/sparse_rev_frame_tracker.h" #include "stim/simulators/tableau_simulator.h" #include "stim/simulators/vector_simulator.h" +#include "stim/stabilizers/clifford_string.h" #include "stim/stabilizers/flex_pauli_string.h" #include "stim/stabilizers/flow.h" #include "stim/stabilizers/pauli_string.h" diff --git a/src/stim/gates/gate_data_period_3.cc b/src/stim/gates/gate_data_period_3.cc index be8014697..88ff17dce 100644 --- a/src/stim/gates/gate_data_period_3.cc +++ b/src/stim/gates/gate_data_period_3.cc @@ -40,7 +40,7 @@ Parens Arguments: Qubits to operate on. )MARKDOWN", .unitary_data = {{0.5f - i * 0.5f, -0.5f - 0.5f * i}, {0.5f - 0.5f * i, 0.5f + 0.5f * i}}, - .flow_data = {"Y", "X"}, + .flow_data = {"+Y", "+X"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( S 0 S 0 @@ -102,7 +102,7 @@ Parens Arguments: Qubits to operate on. )MARKDOWN", .unitary_data = {{0.5f + i * 0.5f, -0.5f + 0.5f * i}, {0.5f + 0.5f * i, 0.5f - 0.5f * i}}, - .flow_data = {"-Y", "X"}, + .flow_data = {"-Y", "+X"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( S 0 H 0 @@ -130,7 +130,7 @@ Parens Arguments: Qubits to operate on. )MARKDOWN", .unitary_data = {{0.5f - i * 0.5f, 0.5f + 0.5f * i}, {-0.5f + 0.5f * i, 0.5f + 0.5f * i}}, - .flow_data = {"Y", "-X"}, + .flow_data = {"+Y", "-X"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( S 0 H 0 @@ -160,7 +160,7 @@ Parens Arguments: Qubits to operate on. )MARKDOWN", .unitary_data = {{0.5f + i * 0.5f, 0.5f + 0.5f * i}, {-0.5f + 0.5f * i, 0.5f - 0.5f * i}}, - .flow_data = {"Z", "Y"}, + .flow_data = {"+Z", "+Y"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( H 0 S 0 @@ -188,7 +188,7 @@ Parens Arguments: Qubits to operate on. )MARKDOWN", .unitary_data = {{0.5f - i * 0.5f, -0.5f + 0.5f * i}, {0.5f + 0.5f * i, 0.5f + 0.5f * i}}, - .flow_data = {"-Z", "Y"}, + .flow_data = {"-Z", "+Y"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( S 0 S 0 @@ -218,7 +218,7 @@ Parens Arguments: Qubits to operate on. )MARKDOWN", .unitary_data = {{0.5f - i * 0.5f, 0.5f - 0.5f * i}, {-0.5f - 0.5f * i, 0.5f + 0.5f * i}}, - .flow_data = {"Z", "-Y"}, + .flow_data = {"+Z", "-Y"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( H 0 S 0 diff --git a/src/stim/mem/bitword.h b/src/stim/mem/bitword.h index b0bce4636..3cc718ca9 100644 --- a/src/stim/mem/bitword.h +++ b/src/stim/mem/bitword.h @@ -178,6 +178,26 @@ inline bitword operator^(const bitword &self, int64_t mask) { return self ^ bitword(mask); } +template +inline bitword andnot(const bitword &inv, const bitword &val) { + return inv.andnot(val); +} +inline uint64_t andnot(uint64_t inv, uint64_t val) { + return ~inv & val; +} +inline uint32_t andnot(uint32_t inv, uint32_t val) { + return ~inv & val; +} +inline uint16_t andnot(uint16_t inv, uint16_t val) { + return ~inv & val; +} +inline uint8_t andnot(uint8_t inv, uint8_t val) { + return ~inv & val; +} +inline bool andnot(bool inv, bool val) { + return !inv && val; +} + } // namespace stim #endif diff --git a/src/stim/stabilizers/clifford_string.h b/src/stim/stabilizers/clifford_string.h new file mode 100644 index 000000000..fb370735b --- /dev/null +++ b/src/stim/stabilizers/clifford_string.h @@ -0,0 +1,383 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _STIM_STABILIZERS_CLIFFORD_STRING_H +#define _STIM_STABILIZERS_CLIFFORD_STRING_H + +#include "stim/mem/simd_bits.h" +#include "stim/gates/gates.h" +#include "stim/circuit/circuit.h" + +namespace stim { + +/// A fixed-size list of W single-qubit Clifford rotations. +template +struct CliffordWord { + Word x_signs; + Word z_signs; + Word inv_x2x; // Inverted so that zero-initializing gives the identity gate. + Word x2z; + Word z2x; + Word inv_z2z; // Inverted so that zero-initializing gives the identity gate. +}; + +inline GateType bits2gate(std::array bits) { + constexpr std::array table{ + GateType::I, + GateType::X, + GateType::Z, + GateType::Y, + + GateType::NOT_A_GATE, // These should be impossible if the class is in a good state. + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::S, + GateType::H_XY, + GateType::S_DAG, + GateType::H_NXY, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::SQRT_X_DAG, + GateType::SQRT_X, + GateType::H_YZ, + GateType::H_NYZ, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::C_ZYX, + GateType::C_ZNYX, + GateType::C_ZYNX, + GateType::C_NZYX, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + GateType::NOT_A_GATE, + + GateType::C_XYZ, + GateType::C_XYNZ, + GateType::C_XNYZ, + GateType::C_NXYZ, + + GateType::H, + GateType::SQRT_Y_DAG, + GateType::SQRT_Y, + GateType::H_NXZ, + }; + int k = (bits[0] << 0) + | (bits[1] << 1) + | (bits[2] << 2) + | (bits[3] << 3) + | (bits[4] << 4) + | (bits[5] << 5); + return table[k]; +} + +inline std::array gate_to_bits(GateType gate_type) { + Gate g = GATE_DATA[gate_type]; + if (!(g.flags & GATE_IS_SINGLE_QUBIT_GATE) || !(g.flags & GATE_IS_UNITARY)) { + throw std::invalid_argument("Not a single qubit gate: " + std::string(g.name)); + } + const auto &flows = g.flow_data; + std::string_view tx = flows[0]; + std::string_view tz = flows[1]; + bool z_sign = tz[0] == '-'; + bool inv_x2x = !(tx[1] == 'X' || tx[1] == 'Y'); + bool x2z = tx[1] == 'Z' || tx[1] == 'Y'; + bool x_sign = tx[0] == '-'; + bool z2x = tz[1] == 'X' || tz[1] == 'Y'; + bool inv_z2z = !(tz[1] == 'Z' || tz[1] == 'Y'); + return {z_sign, x_sign, inv_x2x, x2z, z2x, inv_z2z}; +} + +/// Returns the result of multiplying W rotations pair-wise. +template +inline CliffordWord operator*(const CliffordWord &lhs, const CliffordWord &rhs) { + CliffordWord result; + + // I don't have a simple explanation of why this is correct. It was produced by starting from something that was + // obviously correct, having tests to check all 24*24 cases, then iteratively applying simple rewrites to reduce + // the number of operations. So the result is correct, but somewhat incomprehensible. + result.inv_x2x = (lhs.inv_x2x | rhs.inv_x2x) ^ (lhs.z2x & rhs.x2z); + result.x2z = andnot(rhs.inv_x2x, lhs.x2z) ^ andnot(lhs.inv_z2z, rhs.x2z); + result.z2x = andnot(lhs.inv_x2x, rhs.z2x) ^ andnot(rhs.inv_z2z, lhs.z2x); + result.inv_z2z = (lhs.x2z & rhs.z2x) ^ (lhs.inv_z2z | rhs.inv_z2z); + + // I *especially* don't have an explanation of why this part is correct. But every case is tested and verified. + Word rhs_x2y = andnot(rhs.inv_x2x, rhs.x2z); + Word rhs_z2y = andnot(rhs.inv_z2z, rhs.z2x); + Word dy = (lhs.x2z & lhs.z2x) ^ lhs.inv_x2x ^ lhs.z2x ^ lhs.x2z ^ lhs.inv_z2z; + result.x_signs = rhs.x_signs + ^ andnot(rhs.inv_x2x, lhs.x_signs) + ^ (rhs_x2y & dy) + ^ (rhs.x2z & lhs.z_signs); + result.z_signs = rhs.z_signs + ^ (rhs.z2x & lhs.x_signs) + ^ (rhs_z2y & dy) + ^ andnot(rhs.inv_z2z, lhs.z_signs); + + return result; +} + +template +struct CliffordString; + +template +std::ostream &operator<<(std::ostream &out, const CliffordString &v); + +/// A string of single-qubit Clifford rotations. +template +struct CliffordString { + size_t num_qubits; + + // The 2 sign bits of a single qubit Clifford, packed into arrays for easy processing. + simd_bits x_signs; + simd_bits z_signs; + + // The 4 tableau bits of a single qubit Clifford, packed into arrays for easy processing. + // The x2x and z2z terms are inverted so that zero-initializing produces the identity gate. + simd_bits inv_x2x; + simd_bits x2z; + simd_bits z2x; + simd_bits inv_z2z; + + /// Constructs an identity CliffordString for the given number of qubits. + explicit CliffordString(size_t num_qubits) + : num_qubits(num_qubits), + x_signs(num_qubits), + z_signs(num_qubits), + inv_x2x(num_qubits), + x2z(num_qubits), + z2x(num_qubits), + inv_z2z(num_qubits) { + } + + /// Extracts rotations k*W through (k+1)*W into a CliffordWord. + inline CliffordWord> word_at(size_t k) const { + return CliffordWord>{ + x_signs.ptr_simd[k], + z_signs.ptr_simd[k], + inv_x2x.ptr_simd[k], + x2z.ptr_simd[k], + z2x.ptr_simd[k], + inv_z2z.ptr_simd[k], + }; + } + /// Writes rotations k*W through (k+1)*W from a CliffordWord. + inline void set_word_at(size_t k, CliffordWord> new_value) const { + x_signs.ptr_simd[k] = new_value.x_signs; + z_signs.ptr_simd[k] = new_value.z_signs; + inv_x2x.ptr_simd[k] = new_value.inv_x2x; + x2z.ptr_simd[k] = new_value.x2z; + z2x.ptr_simd[k] = new_value.z2x; + inv_z2z.ptr_simd[k] = new_value.inv_z2z; + } + + /// Converts the internal rotation representation into a GateType. + GateType gate_at(size_t q) const { + return bits2gate(std::array{z_signs[q], x_signs[q], inv_x2x[q], x2z[q], z2x[q], inv_z2z[q]}); + } + + /// Sets an internal rotation from a GateType. + void set_gate_at(size_t q, GateType gate_type) { + std::array bits = gate_to_bits(gate_type);; + z_signs[q] = bits[0]; + x_signs[q] = bits[1]; + inv_x2x[q] = bits[2]; + x2z[q] = bits[3]; + z2x[q] = bits[4]; + inv_z2z[q] = bits[5]; + } + + /// Inplace right-multiplication of rotations. + CliffordString &operator*=(const CliffordString &rhs) { + if (num_qubits < rhs.num_qubits) { + throw std::invalid_argument("Can't inplace-multiply by a larger Clifford string."); + } + for (size_t k = 0; k < rhs.x_signs.num_simd_words; k++) { + auto lhs_w = word_at(k); + auto rhs_w = rhs.word_at(k); + set_word_at(k, lhs_w * rhs_w); + } + return *this; + } + + void inplace_then(CircuitInstruction inst) { + std::array v = gate_to_bits(inst.gate_type); + for (const auto &t : inst.targets) { + if (!t.is_qubit_target()) { + continue; + } + uint32_t q = t.qubit_value(); + if (q >= num_qubits) { + throw std::invalid_argument("Circuit acted on qubit past end of string."); + } + size_t w = q / W; + size_t k = q % W; + CliffordWord> tmp{}; + bit_ref(&tmp.z_signs, k) ^= v[0]; + bit_ref(&tmp.x_signs, k) ^= v[1]; + bit_ref(&tmp.inv_x2x, k) ^= v[2]; + bit_ref(&tmp.x2z, k) ^= v[3]; + bit_ref(&tmp.z2x, k) ^= v[4]; + bit_ref(&tmp.inv_z2z, k) ^= v[5]; + set_word_at(w, tmp * word_at(w)); + } + } + + static CliffordString from_circuit(const Circuit &circuit) { + CliffordString result(circuit.count_qubits()); + circuit.for_each_operation([&](CircuitInstruction inst) { + result.inplace_then(inst); + }); + return result; + } + + Circuit to_circuit() const { + Circuit result; + for (size_t q = 0; q < num_qubits; q++) { + GateType g = gate_at(q); + if (g != GateType::I || q + 1 == num_qubits) { + GateTarget t = GateTarget::qubit(q); + result.safe_append(CircuitInstruction{g, {}, &t, {}}); + } + } + return result; + } + + /// Inplace left-multiplication of rotations. + CliffordString &inplace_left_mul_by(const CliffordString &lhs) { + if (num_qubits < lhs.num_qubits) { + throw std::invalid_argument("Can't inplace-multiply by a larger Clifford string."); + } + for (size_t k = 0; k < x_signs.num_simd_words; k++) { + auto lhs_w = lhs.word_at(k); + auto rhs_w = word_at(k); + set_word_at(k, lhs_w * rhs_w); + } + return *this; + } + + /// Out-of-place multiplication of rotations. + CliffordString operator*(const CliffordString &rhs) const { + CliffordString result = CliffordString(std::max(num_qubits, rhs.num_qubits)); + size_t min_words = std::min(x_signs.num_simd_words, rhs.x_signs.num_simd_words); + for (size_t k = 0; k < min_words; k++) { + auto lhs_w = word_at(k); + auto rhs_w = rhs.word_at(k); + result.set_word_at(k, lhs_w * rhs_w); + } + + // The longer string copies its tail into the result. + size_t min_qubits = std::min(num_qubits, rhs.num_qubits); + for (size_t q = min_qubits; q < num_qubits; q++) { + result.x_signs[q] = x_signs[q]; + result.z_signs[q] = z_signs[q]; + result.inv_x2x[q] = inv_x2x[q]; + result.x2z[q] = x2z[q]; + result.z2x[q] = z2x[q]; + result.inv_z2z[q] = inv_z2z[q]; + } + for (size_t q = min_qubits; q < rhs.num_qubits; q++) { + result.x_signs[q] = rhs.x_signs[q]; + result.z_signs[q] = rhs.z_signs[q]; + result.inv_x2x[q] = rhs.inv_x2x[q]; + result.x2z[q] = rhs.x2z[q]; + result.z2x[q] = rhs.z2x[q]; + result.inv_z2z[q] = rhs.inv_z2z[q]; + } + + return result; + } + + /// Determines if two Clifford strings have the same length and contents. + bool operator==(const CliffordString &other) const { + return x_signs == other.x_signs + && z_signs == other.z_signs + && inv_x2x == other.inv_x2x + && x2z == other.x2z + && z2x == other.z2x + && inv_z2z == other.inv_z2z; + } + /// Determines if two Clifford strings have different lengths or contents. + bool operator!=(const CliffordString &other) const { + return !(*this == other); + } + + /// Returns a description of the Clifford string. + std::string str() const { + std::stringstream ss; + ss << *this; + return ss.str(); + } +}; + +template +std::ostream &operator<<(std::ostream &out, const CliffordString &v) { + for (size_t q = 0; q < v.num_qubits; q++) { + if (q > 0) { + out << " "; + } + int c = v.inv_x2x[q] + v.x2z[q] * 2 + v.z2x[q] * 4 + v.inv_z2z[q] * 8; + int p = v.z_signs[q] + v.x_signs[q] * 2; + out << "_?S?V??d??????uH"[c]; + out << "IXZY"[p]; + } + return out; +} + +} // namespace stim + +#endif diff --git a/src/stim/stabilizers/clifford_string.perf.cc b/src/stim/stabilizers/clifford_string.perf.cc new file mode 100644 index 000000000..00eb90bd3 --- /dev/null +++ b/src/stim/stabilizers/clifford_string.perf.cc @@ -0,0 +1,16 @@ +#include "stim/stabilizers/clifford_string.h" + +#include "stim/perf.perf.h" + +using namespace stim; + +BENCHMARK(CliffordString_multiplication_10K) { + size_t n = 10 * 1000; + CliffordString p1(n); + CliffordString p2(n); + benchmark_go([&]() { + p1 *= p2; + }) + .goal_nanos(430) + .show_rate("Rots", n); +} diff --git a/src/stim/stabilizers/clifford_string.test.cc b/src/stim/stabilizers/clifford_string.test.cc new file mode 100644 index 000000000..4521f4c02 --- /dev/null +++ b/src/stim/stabilizers/clifford_string.test.cc @@ -0,0 +1,182 @@ +#include "stim/stabilizers/clifford_string.h" + +#include "gtest/gtest.h" +#include "stim/mem/simd_word.test.h" +#include "stim/stabilizers/tableau.h" + +using namespace stim; + +std::vector single_qubit_clifford_rotations() { + std::vector result; + for (size_t g = 0; g < NUM_DEFINED_GATES; g++) { + Gate gate = GATE_DATA[(GateType)g]; + if ((gate.flags & GateFlags::GATE_IS_SINGLE_QUBIT_GATE) && (gate.flags & GateFlags::GATE_IS_UNITARY)) { + result.push_back(gate); + } + } + assert(result.size() == 24); + return result; +} + +TEST_EACH_WORD_SIZE_W(clifford_string, set_gate_at_vs_str_vs_gate_at, { + CliffordString p = CliffordString(24); + int x = 0; + + p.set_gate_at(x++, GateType::I); + p.set_gate_at(x++, GateType::X); + p.set_gate_at(x++, GateType::Y); + p.set_gate_at(x++, GateType::Z); + + p.set_gate_at(x++, GateType::H); + p.set_gate_at(x++, GateType::SQRT_Y_DAG); + p.set_gate_at(x++, GateType::H_NXZ); + p.set_gate_at(x++, GateType::SQRT_Y); + + p.set_gate_at(x++, GateType::S); + p.set_gate_at(x++, GateType::H_XY); + p.set_gate_at(x++, GateType::H_NXY); + p.set_gate_at(x++, GateType::S_DAG); + + p.set_gate_at(x++, GateType::SQRT_X_DAG); + p.set_gate_at(x++, GateType::SQRT_X); + p.set_gate_at(x++, GateType::H_NYZ); + p.set_gate_at(x++, GateType::H_YZ); + + p.set_gate_at(x++, GateType::C_XYZ); + p.set_gate_at(x++, GateType::C_XYNZ); + p.set_gate_at(x++, GateType::C_NXYZ); + p.set_gate_at(x++, GateType::C_XNYZ); + + p.set_gate_at(x++, GateType::C_ZYX); + p.set_gate_at(x++, GateType::C_ZNYX); + p.set_gate_at(x++, GateType::C_NZYX); + p.set_gate_at(x++, GateType::C_ZYNX); + + ASSERT_EQ(p.str(), "_I _X _Y _Z HI HX HY HZ SI SX SY SZ VI VX VY VZ uI uX uY uZ dI dX dY dZ"); + + x = 0; + + EXPECT_EQ(p.gate_at(x++), GateType::I); + EXPECT_EQ(p.gate_at(x++), GateType::X); + EXPECT_EQ(p.gate_at(x++), GateType::Y); + EXPECT_EQ(p.gate_at(x++), GateType::Z); + + EXPECT_EQ(p.gate_at(x++), GateType::H); + EXPECT_EQ(p.gate_at(x++), GateType::SQRT_Y_DAG); + EXPECT_EQ(p.gate_at(x++), GateType::H_NXZ); + EXPECT_EQ(p.gate_at(x++), GateType::SQRT_Y); + + EXPECT_EQ(p.gate_at(x++), GateType::S); + EXPECT_EQ(p.gate_at(x++), GateType::H_XY); + EXPECT_EQ(p.gate_at(x++), GateType::H_NXY); + EXPECT_EQ(p.gate_at(x++), GateType::S_DAG); + + EXPECT_EQ(p.gate_at(x++), GateType::SQRT_X_DAG); + EXPECT_EQ(p.gate_at(x++), GateType::SQRT_X); + EXPECT_EQ(p.gate_at(x++), GateType::H_NYZ); + EXPECT_EQ(p.gate_at(x++), GateType::H_YZ); + + EXPECT_EQ(p.gate_at(x++), GateType::C_XYZ); + EXPECT_EQ(p.gate_at(x++), GateType::C_XYNZ); + EXPECT_EQ(p.gate_at(x++), GateType::C_NXYZ); + EXPECT_EQ(p.gate_at(x++), GateType::C_XNYZ); + + EXPECT_EQ(p.gate_at(x++), GateType::C_ZYX); + EXPECT_EQ(p.gate_at(x++), GateType::C_ZNYX); + EXPECT_EQ(p.gate_at(x++), GateType::C_NZYX); + EXPECT_EQ(p.gate_at(x++), GateType::C_ZYNX); +}); + +TEST_EACH_WORD_SIZE_W(clifford_string, multiplication_table_vs_tableau_multiplication, { + std::vector single_qubit_gates = single_qubit_clifford_rotations(); + + std::map t2g; + for (const auto &g : single_qubit_gates) { + t2g[g.tableau().str()] = g.id; + } + + CliffordString p1 = CliffordString(24 * 24); + CliffordString p2 = CliffordString(24 * 24); + CliffordString p12 = CliffordString(24 * 24); + for (size_t k1 = 0; k1 < 24; k1++) { + for (size_t k2 = 0; k2 < 24; k2++) { + size_t k = k1 * 24 + k2; + Gate g1 = single_qubit_gates[k1]; + Gate g2 = single_qubit_gates[k2]; + p1.set_gate_at(k, g1.id); + p2.set_gate_at(k, g2.id); + auto t1 = g1.tableau(); + auto t2 = g2.tableau(); + auto t3 = t2.then(t1); + auto g3 = t2g[t3.str()]; + p12.set_gate_at(k, g3); + } + } + ASSERT_EQ(p1 * p2, p12); +}) + +TEST_EACH_WORD_SIZE_W(clifford_string, to_from_circuit, { + Circuit circuit(R"CIRCUIT( + H 0 + H_XY 1 + H_YZ 2 + H_NXY 3 + H_NXZ 4 + H_NYZ 5 + S_DAG 6 + X 7 + Y 8 + Z 9 + C_XYZ 10 + C_ZYX 11 + C_NXYZ 12 + C_XNYZ 13 + C_XYNZ 14 + C_NZYX 15 + C_ZNYX 16 + C_ZYNX 17 + SQRT_X 18 + SQRT_X_DAG 19 + SQRT_Y 20 + SQRT_Y_DAG 21 + S 22 + I 23 + )CIRCUIT"); + CliffordString s = CliffordString::from_circuit(circuit); + ASSERT_EQ(s.to_circuit(), circuit); +}) + +TEST_EACH_WORD_SIZE_W(clifford_string, known_identities, { + auto s1 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + H 0 + )CIRCUIT")); + auto s2 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + H 0 + )CIRCUIT")); + auto s3 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + I 0 + )CIRCUIT")); + ASSERT_EQ(s2 * s1, s3); + + s1 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + S 0 + )CIRCUIT")); + s2 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + S 0 + )CIRCUIT")); + s3 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + Z 0 + )CIRCUIT")); + ASSERT_EQ(s2 * s1, s3); + + s1 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + S_DAG 0 + )CIRCUIT")); + s2 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + H 0 + )CIRCUIT")); + s3 = CliffordString::from_circuit(Circuit(R"CIRCUIT( + C_XYZ 0 + )CIRCUIT")); + ASSERT_EQ(s2 * s1, s3); +}) diff --git a/src/stim/util_bot/arg_parse.cc b/src/stim/util_bot/arg_parse.cc index e5ad0af6a..873258000 100644 --- a/src/stim/util_bot/arg_parse.cc +++ b/src/stim/util_bot/arg_parse.cc @@ -365,7 +365,7 @@ ostream_else_cout stim::find_output_stream_argument( msg << "Missing command line argument: '" << name_c_str << "'"; throw std::invalid_argument(msg.str()); } - return {nullptr}; + return ostream_else_cout(nullptr); } if (*path_c_str == '\0') { std::stringstream msg; @@ -378,7 +378,7 @@ ostream_else_cout stim::find_output_stream_argument( msg << "Failed to open '" << path_c_str << "'"; throw std::invalid_argument(msg.str()); } - return {std::move(f)}; + return ostream_else_cout(std::move(f)); } std::vector stim::split_view(char splitter, std::string_view text) { diff --git a/src/stim/util_bot/arg_parse.h b/src/stim/util_bot/arg_parse.h index 8896150c2..ef3feac1a 100644 --- a/src/stim/util_bot/arg_parse.h +++ b/src/stim/util_bot/arg_parse.h @@ -17,12 +17,9 @@ #ifndef _STIM_UTIL_BOT_ARG_PARSE_H #define _STIM_UTIL_BOT_ARG_PARSE_H -#include #include -#include #include #include -#include #include #include #include @@ -197,7 +194,7 @@ const T &find_enum_argument( } return values.at(default_key); } - if (values.find(text) == values.end()) { + if (!values.contains(text)) { std::stringstream msg; msg << "\033[31mUnrecognized value '" << text << "' for enum flag '" << name << "'.\n"; msg << "Recognized values are:\n"; @@ -240,7 +237,7 @@ struct ostream_else_cout { std::unique_ptr held; public: - ostream_else_cout(std::unique_ptr &&held); + explicit ostream_else_cout(std::unique_ptr &&held); std::ostream &stream(); }; @@ -249,7 +246,7 @@ struct ostream_else_cout { /// Args: /// name: The name of the command line flag that will specify the file path. /// default_std_out: If true, defaults to stdout when the command line argument isn't given. Otherwise exits with -/// failure when the command line argumen tisn't given. +/// failure when the command line argument isn't given. /// argc: Number of command line arguments. /// argv: Array of command line argument strings. ///