Skip to content

Commit 1ef8fb4

Browse files
authored
[CQT-360] QX simulator outputs invalid states (#205)
* Fix SparseComplex::operator=, which was not always performing the assignment (only when a check was true, and that check was wrong). * Add control_gate_modifier__ctrl_x90_after_h_q1 integration test. * Implement operator<< for qx::Measurement and qx::SuperposedState. * Add fmt::formatters for qx::Measurement and qx::SuperposedState. * Remove rule of 5 for SparseComplex. * Remove SparseArray::operator=(MapBasisVectorToSparseComplex). * Update CHANGELOG.md.
1 parent 75260c7 commit 1ef8fb4

File tree

7 files changed

+52
-63
lines changed

7 files changed

+52
-63
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1414

1515
### Fixed
1616
- Pi-half rotation gates defined incorrectly.
17+
- Bug in SparseComplex::operator=, which was not always performing the assignment.
1718

1819

1920
## [ 0.8.0 ] - [ 2025-03-21 ]
@@ -122,7 +123,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
122123
- Meaningful error message when parsing or simulation fails.
123124

124125
### Fixed
125-
- Does not crash when too many qubits asked.
126+
- QX does not crash when too many qubits asked.
126127

127128

128129
## [ 0.6.3 ] - [ 2023-09-19 ]

include/qx/quantum_state.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class QuantumState {
3636
QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size);
3737
QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size,
3838
std::initializer_list<PairBasisVectorStringComplex> values);
39+
3940
[[nodiscard]] std::size_t get_number_of_qubits() const;
4041
[[nodiscard]] std::size_t get_number_of_bits() const;
4142
[[nodiscard]] bool is_normalized();

include/qx/simulation_result.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ struct SimulationResult {
8484
Measurements bit_measurements;
8585
};
8686

87+
std::ostream& operator<<(std::ostream& os, const Measurement& measurement);
88+
std::ostream& operator<<(std::ostream& os, const SuperposedState& superposed_state);
8789
std::ostream& operator<<(std::ostream& os, const SimulationResult& result);
8890

8991
//----------------------------//
@@ -124,5 +126,9 @@ class SimulationIterationAccumulator {
124126

125127
} // namespace qx
126128

129+
template <>
130+
struct fmt::formatter<qx::Measurement> : fmt::ostream_formatter {};
131+
template <>
132+
struct fmt::formatter<qx::SuperposedState> : fmt::ostream_formatter {};
127133
template <>
128134
struct fmt::formatter<qx::SimulationResult> : fmt::ostream_formatter {};

include/qx/sparse_array.hpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ struct SparseComplex {
2828

2929
SparseComplex() = default;
3030
explicit SparseComplex(std::complex<double> c);
31-
SparseComplex(const SparseComplex& other);
32-
SparseComplex(SparseComplex&& other) noexcept;
33-
SparseComplex& operator=(const SparseComplex& other);
34-
SparseComplex& operator=(SparseComplex&& other) noexcept;
3531
};
3632
using SparseElement = std::pair<BasisVector, SparseComplex>;
3733
bool compare_sparse_elements(const SparseElement& lhs, const SparseElement& rhs);
@@ -53,7 +49,6 @@ class SparseArray {
5349
[[nodiscard]] Iterator begin();
5450
[[nodiscard]] Iterator end();
5551

56-
SparseArray& operator=(MapBasisVectorToSparseComplex map);
5752
SparseArray& operator*=(double d);
5853
SparseComplex& operator[](const BasisVector& index);
5954

src/qx/simulation_result.cpp

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "qx/simulation_result.hpp"
22

33
#include <fmt/core.h>
4+
#include <fmt/ranges.h>
45

56
#include <cstdint> // uint8_t
67
#include <ostream>
@@ -34,37 +35,27 @@ std::uint8_t SimulationResult::get_bit_measurement(
3435
return static_cast<std::uint8_t>(state_string[state_string.size() - index - 1] - '0');
3536
}
3637

38+
std::ostream& operator<<(std::ostream& os, const Measurement& measurement) {
39+
return os << (measurement.state.empty()
40+
? std::string{}
41+
: fmt::format("state='{}', count='{}'", measurement.state, measurement.count));
42+
}
43+
44+
std::ostream& operator<<(std::ostream& os, const SuperposedState& superposed_state) {
45+
return os << fmt::format("value='{1}', amplitude='{2:.{0}f} + {3:.{0}f}i', norm='{4:.{0}f}'",
46+
config::OUTPUT_DECIMALS,
47+
superposed_state.value,
48+
superposed_state.amplitude.real,
49+
superposed_state.amplitude.imag,
50+
superposed_state.amplitude.norm);
51+
}
52+
3753
std::ostream& operator<<(std::ostream& os, const SimulationResult& simulation_result) {
38-
fmt::print(os, "State:\n");
39-
for (const auto& superposed_state : simulation_result.state) {
40-
fmt::print(os,
41-
"\t{1} {2:.{0}f} + {3:.{0}f}i (norm = {4:.{0}f})\n",
42-
config::OUTPUT_DECIMALS,
43-
superposed_state.value,
44-
superposed_state.amplitude.real,
45-
superposed_state.amplitude.imag,
46-
superposed_state.amplitude.norm);
47-
}
48-
fmt::print(os, "Measurements:\n");
49-
for (const auto& measurement : simulation_result.measurements) {
50-
fmt::print(os,
51-
"\t{1} {2}/{3} (count/shots % = {4:.{0}f})\n",
52-
config::OUTPUT_DECIMALS,
53-
measurement.state,
54-
measurement.count,
55-
simulation_result.shots_done,
56-
static_cast<double>(measurement.count) / static_cast<double>(simulation_result.shots_done));
57-
}
58-
fmt::print(os, "Bit measurements:\n");
59-
for (const auto& bit_measurement : simulation_result.bit_measurements) {
60-
fmt::print(os,
61-
"\t{1} {2}/{3} (count/shots % = {4:.{0}f})\n",
62-
config::OUTPUT_DECIMALS,
63-
bit_measurement.state,
64-
bit_measurement.count,
65-
simulation_result.shots_done,
66-
static_cast<double>(bit_measurement.count) / static_cast<double>(simulation_result.shots_done));
67-
}
54+
fmt::print(os, "Shots requested: {}\n", simulation_result.shots_requested);
55+
fmt::print(os, "Shots done: {}\n", simulation_result.shots_done);
56+
fmt::print(os, "State:\n\t{}\n", fmt::join(simulation_result.state, "\n\t"));
57+
fmt::print(os, "Measurements:\n\t{}\n", fmt::join(simulation_result.measurements, "\n\t"));
58+
fmt::print(os, "Bit measurements:\n\t{}\n", fmt::join(simulation_result.bit_measurements, "\n\t"));
6859
fmt::print(os, "Qubit register:\n\t{}\n", simulation_result.qubit_register);
6960
fmt::print(os, "Bit register:\n\t{}", simulation_result.bit_register);
7061
return os;

src/qx/sparse_array.cpp

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,6 @@ SparseComplex::SparseComplex(std::complex<double> c) {
1616
value = c;
1717
}
1818

19-
SparseComplex::SparseComplex(const SparseComplex& other) {
20-
value = other.value;
21-
}
22-
23-
SparseComplex::SparseComplex(SparseComplex&& other) noexcept {
24-
value = other.value;
25-
}
26-
27-
SparseComplex& SparseComplex::operator=(const SparseComplex& other) {
28-
if (std::abs(other.value) >= config::EPSILON) {
29-
value = other.value;
30-
}
31-
return *this;
32-
}
33-
34-
SparseComplex& SparseComplex::operator=(SparseComplex&& other) noexcept {
35-
if (std::abs(other.value) >= config::EPSILON) {
36-
value = other.value;
37-
}
38-
return *this;
39-
}
40-
4119
bool compare_sparse_elements(const SparseElement& lhs, const SparseElement& rhs) {
4220
return lhs.first < rhs.first;
4321
}
@@ -56,11 +34,6 @@ SparseArray::SparseArray(std::size_t s, std::initializer_list<PairBasisVectorStr
5634
}
5735
}
5836

59-
SparseArray& SparseArray::operator=(MapBasisVectorToSparseComplex map) {
60-
data_ = std::move(map);
61-
return *this;
62-
}
63-
6437
SparseArray& SparseArray::operator*=(double d) {
6538
for (auto& [_, sparse_complex] : data_) {
6639
sparse_complex.value *= d;

test/integration_test.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,28 @@ H q[1]
548548
}));
549549
}
550550

551+
TEST_F(IntegrationTest, control_gate_modifier__ctrl_x90_after_h_q1) {
552+
auto program = R"(
553+
version 3.0
554+
555+
qubit[2] q
556+
557+
H q[1]
558+
ctrl.X90 q[0], q[1]
559+
H q[0]
560+
H q[1]
561+
)";
562+
std::size_t iterations = 1;
563+
auto actual = run_from_string(program, iterations, "3.0");
564+
565+
// Expected 'q' state should be |00>+|01>
566+
EXPECT_EQ(actual.state,
567+
(SimulationResult::State{
568+
{ "00", core::Complex{ .real = 1. / gates::SQRT_2, .imag = 0, .norm = 0.5 } },
569+
{ "01", core::Complex{ .real = 1. / gates::SQRT_2, .imag = 0, .norm = 0.5 } }
570+
}));
571+
}
572+
551573
TEST_F(IntegrationTest, control_gate_modifier__ctrl_rx_half_pi) {
552574
auto program = R"(
553575
version 3.0

0 commit comments

Comments
 (0)