From 3196227a1d91abbad728a7216547f346d4e437a6 Mon Sep 17 00:00:00 2001 From: Marek Wydmuch Date: Mon, 20 Jan 2025 22:34:16 +0100 Subject: [PATCH] Fix a typo in python-test-build.yml --- .github/workflows/python-test-build.yml | 2 +- src/isotonic_regression.h | 143 ++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/isotonic_regression.h diff --git a/.github/workflows/python-test-build.yml b/.github/workflows/python-test-build.yml index 72ad87f..054065d 100644 --- a/.github/workflows/python-test-build.yml +++ b/.github/workflows/python-test-build.yml @@ -18,7 +18,7 @@ jobs: #python-version: [ '3.9', '3.10', '3.11', '3.12' ] python-version: [ '3.9', '3.12' ] # Test only on 3.9 and 3.12 (the oldest and newest) to save time and resources - name: Build Pyhton binding on ${{ matrix.os }} for Python ${{ matrix.python-version }} + name: Build Python binding on ${{ matrix.os }} for Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} steps: diff --git a/src/isotonic_regression.h b/src/isotonic_regression.h new file mode 100644 index 0000000..4f4b37e --- /dev/null +++ b/src/isotonic_regression.h @@ -0,0 +1,143 @@ +#include +#include +#include + +/* +class IsotonicRegression { +public: + IsotonicRegression() = default; + IsotonicRegression( + + // Fit the isotonic regression model + void fit(const std::vector& predictions, const std::vector& targets) { + if (predictions.empty() || predictions.size() != targets.size()) { + throw std::invalid_argument("Invalid input sizes"); + } + + // Create initial blocks and sort by prediction value + std::vector indices(predictions.size()); + std::iota(indices.begin(), indices.end(), 0); + std::sort(indices.begin(), indices.end(), + [&predictions](size_t i1, size_t i2) { + return predictions[i1] < predictions[i2]; + }); + + fitted_blocks_.clear(); + fitted_blocks_.reserve(predictions.size()); + for (size_t idx : indices) { + fitted_blocks_.emplace_back(predictions[idx], targets[idx]); + } + + // Pool Adjacent Violators Algorithm + bool violation_exists = true; + while (violation_exists) { + violation_exists = false; + + for (size_t i = 0; i < fitted_blocks_.size() - 1; ++i) { + if (fitted_blocks_[i].target > fitted_blocks_[i + 1].target) { + // Merge blocks that violate monotonicity + Point merged = mergeBlocks(fitted_blocks_[i], fitted_blocks_[i + 1]); + fitted_blocks_[i] = merged; + fitted_blocks_[i + 1] = merged; + violation_exists = true; + } + } + + // Remove duplicate blocks after merging + auto new_end = std::unique(fitted_blocks_.begin(), fitted_blocks_.end()); + fitted_blocks_.erase(new_end, fitted_blocks_.end()); + } + + is_fitted_ = true; + } + + // Transform new data using the fitted model + std::vector transform(const std::vector& predictions) const { + if (!is_fitted_) { + throw std::runtime_error("Model must be fitted before transform"); + } + + std::vector transformed(predictions.size()); + + for (size_t i = 0; i < predictions.size(); ++i) { + const float pred = predictions[i]; + + // Find the appropriate blocks for interpolation + auto it = std::lower_bound( + fitted_blocks_.begin(), fitted_blocks_.end(), pred, + [](const Point& p, float val) { return p.pred < val; } + ); + + if (it == fitted_blocks_.end()) { + // If beyond the last block, use the last target value + transformed[i] = fitted_blocks_.back().target; + } else if (it == fitted_blocks_.begin()) { + // If before the first block, use the first target value + transformed[i] = fitted_blocks_.front().target; + } else { + // Interpolate between blocks + transformed[i] = interpolate(*(it - 1), *it, pred); + } + } + + return transformed; + } + + // Fit and transform in one step + std::vector fit_transform(const std::vector& predictions, + const std::vector& targets) { + fit(predictions, targets); + return transform(predictions); + } + + // Check if the model has been fitted + bool is_fitted() const { + return is_fitted_; + } + + // Get the fitted blocks (for debugging or analysis) + std::vector> get_fitted_points() const { + if (!is_fitted_) { + throw std::runtime_error("Model must be fitted first"); + } + std::vector> points; + points.reserve(fitted_blocks_.size()); + for (const auto& block : fitted_blocks_) { + points.emplace_back(block.pred, block.target); + } + return points; + } + +private: + // Helper struct to store prediction, target, and weight + struct Point { + float pred; + float target; + float weight; + + Point(float p, float t, float w = 1.0f) + : pred(p), target(t), weight(w) {} + + bool operator==(const Point& other) const { + return target == other.target; + } + }; + + std::vector fitted_blocks_; + bool is_fitted_ = false; + + // Helper function to merge two blocks + static Point mergeBlocks(const Point& p1, const Point& p2) { + float total_weight = p1.weight + p2.weight; + float weighted_avg = (p1.target * p1.weight + p2.target * p2.weight) / total_weight; + return Point(p1.pred, weighted_avg, total_weight); + } + + // Helper function to interpolate between two points + static float interpolate(const Point& left, const Point& right, float x) { + if (left.pred == right.pred) return left.target; + float t = (x - left.pred) / (right.pred - left.pred); + return left.target * (1 - t) + right.target * t; + } +}; +*/ \ No newline at end of file