Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions include/pineforge/generic_matrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,30 @@ inline void sort_impl(std::vector<Row>& data, int column, bool ascending) {

template <typename T>
class PineGenericMatrix {
std::vector<std::vector<T>> data_;
using Data = std::vector<std::vector<T>>;

Data data_;
bool valid_{false};

void require_valid() const {
if (!valid_) throw std::runtime_error("matrix operation on na ID");
}

public:
class Snapshot {
Data state_;

explicit Snapshot(const Data& state) : state_(state) {}

friend class PineGenericMatrix;

public:
Snapshot(const Snapshot&) = default;
Snapshot& operator=(const Snapshot&) = default;
Snapshot(Snapshot&&) = default;
Snapshot& operator=(Snapshot&&) = default;
};

~PineGenericMatrix() = default;

[[nodiscard]] static PineGenericMatrix new_(int rows, int cols, T init) {
Expand Down Expand Up @@ -347,6 +363,17 @@ class PineGenericMatrix {
}

[[nodiscard]] bool is_na() const noexcept { return !valid_; }

[[nodiscard]] Snapshot snapshot() const {
require_valid();
return Snapshot(data_);
}

void restore(const Snapshot& snapshot) {
Data replacement(snapshot.state_);
data_.swap(replacement);
valid_ = true;
}
};

// PineGenericMatrix<bool> uses std::vector<char> as the row container because
Expand All @@ -356,14 +383,30 @@ class PineGenericMatrix {
// to the same detail:: helpers used by the primary template.
template <>
class PineGenericMatrix<bool> {
std::vector<std::vector<char>> data_;
using Data = std::vector<std::vector<char>>;

Data data_;
bool valid_{false};

void require_valid() const {
if (!valid_) throw std::runtime_error("matrix operation on na ID");
}

public:
class Snapshot {
Data state_;

explicit Snapshot(const Data& state) : state_(state) {}

friend class PineGenericMatrix;

public:
Snapshot(const Snapshot&) = default;
Snapshot& operator=(const Snapshot&) = default;
Snapshot(Snapshot&&) = default;
Snapshot& operator=(Snapshot&&) = default;
};

[[nodiscard]] static PineGenericMatrix new_(int rows, int cols, bool init) {
if (rows < 0 || cols < 0)
throw std::invalid_argument("matrix.new: negative dimensions");
Expand Down Expand Up @@ -562,6 +605,17 @@ class PineGenericMatrix<bool> {
}

[[nodiscard]] bool is_na() const noexcept { return !valid_; }

[[nodiscard]] Snapshot snapshot() const {
require_valid();
return Snapshot(data_);
}

void restore(const Snapshot& snapshot) {
Data replacement(snapshot.state_);
data_.swap(replacement);
valid_ = true;
}
};

template <typename T>
Expand Down
20 changes: 20 additions & 0 deletions include/pineforge/matrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ class PineMatrix {
void require_valid() const;

public:
// Opaque value checkpoint used by generated rollback code. This API is
// intentionally identity-neutral: matrix assignment retains its existing
// value semantics until the separate Pine ID-semantics change lands.
class Snapshot {
Eigen::MatrixXd state_;

explicit Snapshot(const Eigen::MatrixXd& state) : state_(state) {}

friend class PineMatrix;

public:
Snapshot(const Snapshot&) = default;
Snapshot& operator=(const Snapshot&) = default;
Snapshot(Snapshot&&) = default;
Snapshot& operator=(Snapshot&&) = default;
};

// Construction
static PineMatrix new_(int rows, int cols, double init_val = 0.0);

Expand Down Expand Up @@ -74,6 +91,9 @@ class PineMatrix {
// matrix.new() returns a valid ID even when its dimensions are 0x0.
[[nodiscard]] bool is_na() const noexcept { return !valid_; }

[[nodiscard]] Snapshot snapshot() const;
void restore(const Snapshot& snapshot);

// Properties
bool is_square() const;
bool is_identity() const;
Expand Down
11 changes: 11 additions & 0 deletions src/matrix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ void PineMatrix::require_valid() const {
if (!valid_) throw std::runtime_error("matrix operation on na ID");
}

PineMatrix::Snapshot PineMatrix::snapshot() const {
require_valid();
return Snapshot(data_);
}

void PineMatrix::restore(const Snapshot& snapshot) {
Eigen::MatrixXd replacement(snapshot.state_);
data_.swap(replacement);
valid_ = true;
}

// ── Access ──────────────────────────────────────────────────────────────────

double PineMatrix::get(int row, int col) const {
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ set(TEST_SOURCES
test_ta_na_source_rules
test_matrix
test_matrix_na
test_matrix_snapshot_compat
test_session_time
test_time_tradingday
test_chart_timezone
Expand Down
132 changes: 132 additions & 0 deletions tests/test_matrix_snapshot_compat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#include <pineforge/generic_matrix.hpp>
#include <pineforge/matrix.hpp>

#include <cassert>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>

Comment on lines +4 to +9
using pineforge::PineGenericMatrix;
using pineforge::PineMatrix;

template <typename Matrix>
void require_na_snapshot_error(const Matrix& matrix) {
try {
(void)matrix.snapshot();
assert(false && "snapshot() on na must throw");
} catch (const std::runtime_error& error) {
assert(std::string(error.what()) == "matrix operation on na ID");
}
}

template <typename T>
void check_generic_snapshot_round_trip(const T& initial, const T& changed) {
using Matrix = PineGenericMatrix<T>;
using Snapshot = typename Matrix::Snapshot;

static_assert(std::is_copy_constructible_v<Snapshot>);
static_assert(std::is_copy_assignable_v<Snapshot>);
static_assert(std::is_move_constructible_v<Snapshot>);
static_assert(std::is_move_assignable_v<Snapshot>);
static_assert(!std::is_default_constructible_v<Snapshot>);

Matrix matrix = Matrix::new_(1, 1, initial);
Snapshot snapshot = matrix.snapshot();
Snapshot copied = snapshot;
Snapshot moved = std::move(copied);

matrix.set(0, 0, changed);
matrix.restore(snapshot);
assert(matrix.get(0, 0) == initial);

matrix = Matrix::new_(2, 1, changed);
matrix.restore(moved);
assert(matrix.rows() == 1);
assert(matrix.columns() == 1);
assert(matrix.get(0, 0) == initial);

Matrix receiver;
receiver.restore(snapshot);
assert(!receiver.is_na());
assert(receiver.get(0, 0) == initial);

receiver.set(0, 0, changed);
receiver.restore(snapshot);
assert(receiver.get(0, 0) == initial);

Matrix empty = Matrix::new_(0, 0, initial);
Snapshot empty_snapshot = empty.snapshot();
Matrix empty_receiver;
empty_receiver.restore(empty_snapshot);
assert(!empty_receiver.is_na());
assert(empty_receiver.rows() == 0);
assert(empty_receiver.columns() == 0);

require_na_snapshot_error(Matrix{});
}

int main() {
using Snapshot = PineMatrix::Snapshot;
static_assert(std::is_copy_constructible_v<Snapshot>);
static_assert(std::is_copy_assignable_v<Snapshot>);
static_assert(std::is_move_constructible_v<Snapshot>);
static_assert(std::is_move_assignable_v<Snapshot>);
static_assert(!std::is_default_constructible_v<Snapshot>);

PineMatrix matrix = PineMatrix::new_(1, 1, 7.0);
PineMatrix assigned = matrix;
assigned.set(0, 0, 13.0);
assert(matrix.get(0, 0) == 7.0);
assert(assigned.get(0, 0) == 13.0);

PineMatrix explicit_copy = matrix.copy();
explicit_copy.set(0, 0, 99.0);
assert(matrix.get(0, 0) == 7.0);
assert(explicit_copy.get(0, 0) == 99.0);

Snapshot snapshot = matrix.snapshot();
Snapshot copied = snapshot;
Snapshot moved = std::move(copied);
matrix.set(0, 0, 3.0);
matrix.restore(snapshot);
assert(matrix.get(0, 0) == 7.0);

matrix = PineMatrix::new_(2, 1, 5.0);
matrix.restore(moved);
assert(matrix.rows() == 1);
assert(matrix.columns() == 1);
assert(matrix.get(0, 0) == 7.0);

PineMatrix receiver;
receiver.restore(snapshot);
assert(!receiver.is_na());
assert(receiver.get(0, 0) == 7.0);
receiver.set(0, 0, 8.0);
receiver.restore(snapshot);
assert(receiver.get(0, 0) == 7.0);

PineMatrix empty = PineMatrix::new_(0, 0, 0.0);
Snapshot empty_snapshot = empty.snapshot();
PineMatrix empty_receiver;
empty_receiver.restore(empty_snapshot);
assert(!empty_receiver.is_na());
assert(empty_receiver.rows() == 0);
assert(empty_receiver.columns() == 0);

require_na_snapshot_error(PineMatrix{});

PineGenericMatrix<int> generic = PineGenericMatrix<int>::new_(1, 1, 7);
PineGenericMatrix<int> generic_assigned = generic;
generic_assigned.set(0, 0, 13);
assert(generic.get(0, 0) == 7);
assert(generic_assigned.get(0, 0) == 13);
PineGenericMatrix<int> generic_copy = generic.copy();
generic_copy.set(0, 0, 99);
assert(generic.get(0, 0) == 7);
assert(generic_copy.get(0, 0) == 99);

check_generic_snapshot_round_trip<int>(7, 13);
check_generic_snapshot_round_trip<std::string>("alpha", "beta");
check_generic_snapshot_round_trip<bool>(true, false);
}
Loading