Skip to content

C++ Examples

Complete C++ examples demonstrating the fastLoess C++ bindings with modern C++ features.

Batch Smoothing

Process complete datasets with the idiomatic C++ wrapper.

/**
 * @file batch_smoothing.cpp
 * @brief fastloess Batch Smoothing Example
 *
 * This example demonstrates batch LOESS smoothing features:
 * - Basic smoothing with different parameters
 * - Robustness iterations for outlier handling
 * - Confidence and prediction intervals
 * - Diagnostics and cross-validation
 *
 * The Loess class is the primary interface for
 * processing complete datasets that fit in memory.
 */

#include <cmath>
#include <cstddef>
#include <cstdio>
#include <exception>
#include <iomanip>
#include <iostream>
#include <limits>
#include <random>
#include <vector>

#include "../../bindings/cpp/include/fastloess.hpp"

namespace {

constexpr size_t k_default_point_count = 100;
constexpr unsigned int k_random_seed = 42;
constexpr double k_noise_std_dev = 1.5;
constexpr double k_outlier_magnitude_min = 10.0;
constexpr double k_outlier_magnitude_max = 20.0;
constexpr double k_x_range_max = 50.0;
constexpr double k_trend_slope = 0.5;
constexpr double k_seasonal_amplitude = 5.0;
constexpr double k_seasonal_frequency = 0.5;
constexpr size_t k_outlier_divisor = 10;
constexpr double k_basic_fraction = 0.05;
constexpr double k_confidence_level = 0.95;
constexpr double k_linear_range_max = 10.0;
constexpr size_t k_linear_point_count = 50;
constexpr double k_linear_slope = 2.0;
constexpr double k_linear_intercept = 1.0;
constexpr double k_boundary_fraction = 0.6;
constexpr double k_auto_converge_tol = 1e-3;
constexpr double k_cv_fraction_low = 0.1;
constexpr double k_cv_fraction_mid = 0.2;
constexpr int k_cv_k = 3;

struct Data {
  std::vector<double> x;
  std::vector<double> y;
  std::vector<double> y_true;
};

Data generateSampleData(size_t point_count = k_default_point_count) {
  Data data;
  data.x.resize(point_count);
  data.y.resize(point_count);
  data.y_true.resize(point_count);

  std::seed_seq generator_seed = {k_random_seed, k_random_seed, k_random_seed,
                                  k_random_seed};
  std::mt19937 generator(generator_seed);
  std::normal_distribution<> noise(0.0, k_noise_std_dev);
  std::uniform_real_distribution<> outlier_magnitude(k_outlier_magnitude_min,
                                                     k_outlier_magnitude_max);
  std::uniform_int_distribution<> outlier_sign(0, 1);

  for (size_t point_index = 0; point_index < point_count; ++point_index) {
    data.x[point_index] = static_cast<double>(point_index) * k_x_range_max /
                          static_cast<double>(point_count - 1);

    data.y_true[point_index] =
        (k_trend_slope * data.x[point_index]) +
        (k_seasonal_amplitude *
         std::sin(data.x[point_index] * k_seasonal_frequency));

    data.y[point_index] = data.y_true[point_index] + noise(generator);
  }

  const size_t outlier_count = point_count / k_outlier_divisor;
  std::uniform_int_distribution<size_t> outlier_index(0, point_count - 1);

  for (size_t outlier_number = 0; outlier_number < outlier_count;
       ++outlier_number) {
    const size_t point_index = outlier_index(generator);
    double outlier_value = outlier_magnitude(generator);
    if (outlier_sign(generator) == 0) {
      outlier_value = -outlier_value;
    }
    data.y[point_index] += outlier_value;
  }

  return data;
}

} // namespace

int main() {
  try {
    std::cout << "=== fastloess Batch Smoothing Example ===\n";

    // 1. Generate Data
    auto data = generateSampleData(k_default_point_count);
    std::cout << "Generated " << data.x.size() << " data points with outliers."
              << '\n';

    // 2. Basic Smoothing (Default parameters)
    std::cout << "Running basic smoothing...\n";
    fastloess::LoessOptions basic_opts;
    basic_opts.fraction = k_basic_fraction;
    basic_opts.iterations = 0;
    fastloess::Loess model_basic(basic_opts);
    auto res_basic = model_basic.fit(data.x, data.y).value();

    // 3. Robust Smoothing (IRLS)
    std::cout << "Running robust smoothing (3 iterations)...\n";
    fastloess::LoessOptions robust_opts;
    robust_opts.fraction = k_basic_fraction;
    robust_opts.iterations = 3;
    robust_opts.robustness_method = "bisquare";
    robust_opts.return_robustness_weights = true;

    fastloess::Loess model_robust(robust_opts);
    auto res_robust = model_robust.fit(data.x, data.y).value();

    // 4. Uncertainty Quantification
    std::cout << "Computing confidence and prediction intervals..." << '\n';
    fastloess::LoessOptions interval_opts;
    interval_opts.fraction = k_basic_fraction;
    interval_opts.confidence_intervals = k_confidence_level;
    interval_opts.prediction_intervals = k_confidence_level;
    interval_opts.return_diagnostics = true;

    fastloess::Loess model_intervals(interval_opts);
    auto res_intervals = model_intervals.fit(data.x, data.y).value();

    // 5. Cross-Validation for optimal fraction
    std::cout << "Running cross-validation to find optimal fraction..." << '\n';

    // Manual CV search
    const std::vector<double> fractions = {k_basic_fraction, 0.1, 0.2, 0.4};
    double best_fraction = 0.0;
    double min_rmse = std::numeric_limits<double>::max();

    for (const double fraction : fractions) {
      fastloess::LoessOptions cv_opts;
      cv_opts.fraction = fraction;
      cv_opts.return_diagnostics = true;
      fastloess::Loess model(cv_opts);
      auto res_exp = model.fit(data.x, data.y);

      // Use non-throwing interface
      if (res_exp.has_value()) {
        auto &res = res_exp.value();
        if (res.diagnostics().has_value()) {
          const double rmse = res.diagnostics().rmse();
          if (rmse < min_rmse) {
            min_rmse = rmse;
            best_fraction = fraction;
          }
        }
      }
    }
    std::cout << "Optimal fraction found (manual CV): " << best_fraction
              << '\n';

    // Diagnostics Printout
    if (res_intervals.diagnostics().has_value()) {
      const auto diag = res_intervals.diagnostics();
      std::cout << "\nFit Statistics (Intervals Model):\n";
      std::cout << " - R^2:   " << diag.r_squared() << '\n';
      std::cout << " - RMSE: " << diag.rmse() << '\n';
      std::cout << " - MAE:  " << diag.mae() << '\n';
    }

    // 6. Boundary Policy Comparison
    std::cout << "\nDemonstrating boundary policy effects on linear data..."
              << '\n';
    std::vector<double> linear_x(k_linear_point_count);
    std::vector<double> linear_y(k_linear_point_count);
    for (size_t point_index = 0; point_index < k_linear_point_count;
         ++point_index) {
      linear_x[point_index] = static_cast<double>(point_index) *
                              k_linear_range_max /
                              static_cast<double>(k_linear_point_count - 1);
      linear_y[point_index] =
          (k_linear_slope * linear_x[point_index]) + k_linear_intercept;
    }

    fastloess::LoessOptions opt_ext;
    opt_ext.fraction = k_boundary_fraction;
    opt_ext.boundary_policy = "extend";
    auto r_ext = fastloess::Loess(opt_ext).fit(linear_x, linear_y).value();

    fastloess::LoessOptions opt_ref;
    opt_ref.fraction = k_boundary_fraction;
    opt_ref.boundary_policy = "reflect";
    auto r_ref = fastloess::Loess(opt_ref).fit(linear_x, linear_y).value();

    fastloess::LoessOptions opt_zero;
    opt_zero.fraction = k_boundary_fraction;
    opt_zero.boundary_policy = "zero";
    auto r_zr = fastloess::Loess(opt_zero).fit(linear_x, linear_y).value();

    std::cout << "Boundary policy comparison:\n";
    std::cout << std::fixed << std::setprecision(2);
    std::cout << " - Extend (Default): first=" << r_ext.y_value(0)
              << ", last=" << r_ext.y_value(k_linear_point_count - 1) << '\n';
    std::cout << " - Reflect:          first=" << r_ref.y_value(0)
              << ", last=" << r_ref.y_value(k_linear_point_count - 1) << '\n';
    std::cout << " - Zero:             first=" << r_zr.y_value(0)
              << ", last=" << r_zr.y_value(k_linear_point_count - 1) << '\n';

    // 7. Boundary policy: noboundary
    std::cout << "\n--- Boundary Policy: noboundary ---\n";
    fastloess::LoessOptions nb_opts;
    nb_opts.fraction = k_boundary_fraction;
    nb_opts.boundary_policy = "noboundary";
    auto nb_res = fastloess::Loess(nb_opts).fit(linear_x, linear_y).value();
    std::cout << " - noboundary first=" << nb_res.y_value(0)
              << ", last=" << nb_res.y_value(k_linear_point_count - 1) << '\n';

    // 8. Weight function variants
    std::cout << "\n--- Weight Function Variants ---\n";
    for (const char *wfn : {"tricube", "epanechnikov", "gaussian", "uniform",
                            "biweight", "triangle", "cosine"}) {
      fastloess::LoessOptions wf_opts;
      wf_opts.fraction = k_basic_fraction;
      wf_opts.weight_function = wfn;
      auto wf_res = fastloess::Loess(wf_opts).fit(data.x, data.y).value();
      std::cout << "  weight_function=" << wfn << "  y[0]=" << wf_res.y_value(0)
                << '\n';
    }

    // 9. Polynomial degrees
    std::cout << "\n--- Polynomial Degrees ---\n";
    for (const char *deg : {"constant", "linear", "quadratic"}) {
      fastloess::LoessOptions deg_opts;
      deg_opts.fraction = k_basic_fraction;
      deg_opts.degree = deg;
      auto deg_res = fastloess::Loess(deg_opts).fit(data.x, data.y).value();
      std::cout << "  degree=" << deg << "  y[0]=" << deg_res.y_value(0)
                << '\n';
    }

    // 10. Scaling methods
    std::cout << "\n--- Scaling Methods ---\n";
    for (const char *scl : {"mad", "mar", "mean"}) {
      fastloess::LoessOptions scl_opts;
      scl_opts.fraction = k_basic_fraction;
      scl_opts.scaling_method = scl;
      auto scl_res = fastloess::Loess(scl_opts).fit(data.x, data.y).value();
      std::cout << "  scaling_method=" << scl << "  y[0]=" << scl_res.y_value(0)
                << '\n';
    }

    // 11. Distance metrics
    std::cout << "\n--- Distance Metrics ---\n";
    for (const char *met :
         {"euclidean", "normalized", "manhattan", "chebyshev"}) {
      fastloess::LoessOptions met_opts;
      met_opts.fraction = k_basic_fraction;
      met_opts.distance_metric = met;
      auto met_res = fastloess::Loess(met_opts).fit(data.x, data.y).value();
      std::cout << "  distance_metric=" << met
                << "  y[0]=" << met_res.y_value(0) << '\n';
    }

    // 12. Robustness method variants (bisquare shown above; add huber + talwar)
    std::cout << "\n--- Robustness Method Variants ---\n";
    for (const char *rob : {"huber", "talwar"}) {
      fastloess::LoessOptions rob_opts;
      rob_opts.fraction = k_basic_fraction;
      rob_opts.iterations = 2;
      rob_opts.robustness_method = rob;
      auto rob_res = fastloess::Loess(rob_opts).fit(data.x, data.y).value();
      std::cout << "  robustness_method=" << rob
                << "  y[0]=" << rob_res.y_value(0) << '\n';
    }

    // 13. Surface mode "direct" + return_se: standard errors and hat-matrix
    // stats
    std::cout << "\n--- Surface Mode: direct + Standard Errors ---\n";
    fastloess::LoessOptions se_opts;
    se_opts.fraction = k_basic_fraction;
    se_opts.surface_mode = "direct";
    se_opts.return_se = true;
    auto se_res = fastloess::Loess(se_opts).fit(data.x, data.y).value();
    std::cout << "  enp:           " << se_res.enp() << '\n';
    std::cout << "  trace_hat:     " << se_res.trace_hat() << '\n';
    std::cout << "  delta1:        " << se_res.delta1() << '\n';
    std::cout << "  delta2:        " << se_res.delta2() << '\n';
    std::cout << "  residual_scale:" << se_res.residual_scale() << '\n';
    {
      const auto std_errors = se_res.standard_errors();
      if (!std_errors.empty()) {
        std::cout << "  se[0]:         " << std_errors[0] << '\n';
      }
      const auto leverage_vals = se_res.leverage();
      if (!leverage_vals.empty()) {
        std::cout << "  leverage[0]:   " << leverage_vals[0] << '\n';
      }
    }

    // 14. return_residuals + return_robustness_weights + result metadata
    std::cout
        << "\n--- Residuals, Robustness Weights, and Result Metadata ---\n";
    fastloess::LoessOptions meta_opts;
    meta_opts.fraction = k_basic_fraction;
    meta_opts.iterations = 2;
    meta_opts.robustness_method = "huber";
    meta_opts.return_residuals = true;
    meta_opts.return_robustness_weights = true;
    auto meta_res = fastloess::Loess(meta_opts).fit(data.x, data.y).value();
    std::cout << "  fraction_used:   " << meta_res.fraction_used() << '\n';
    std::cout << "  iterations_used: " << meta_res.iterations_used() << '\n';
    std::cout << "  dimensions:      " << meta_res.dimensions() << '\n';
    std::cout << "  valid():         " << meta_res.valid() << '\n';
    std::cout << "  x_vector().size() " << meta_res.x_vector().size() << '\n';
    std::cout << "  y_vector().size() " << meta_res.y_vector().size() << '\n';
    {
      const auto residuals = meta_res.residuals();
      if (!residuals.empty()) {
        std::cout << "  residuals[0]:    " << residuals[0] << '\n';
      }
      const auto rob_weights = meta_res.robustness_weights();
      if (!rob_weights.empty()) {
        std::cout << "  robWeight[0]:    " << rob_weights[0] << '\n';
      }
    }

    // 15. Auto-convergence
    std::cout << "\n--- Auto-Convergence ---\n";
    fastloess::LoessOptions conv_opts;
    conv_opts.fraction = k_basic_fraction;
    conv_opts.auto_converge = k_auto_converge_tol;
    auto conv_res = fastloess::Loess(conv_opts).fit(data.x, data.y).value();
    std::cout << "  auto_converge=" << k_auto_converge_tol
              << "  iterations_used=" << conv_res.iterations_used() << '\n';

    // 16. Zero-weight fallback options
    std::cout << "\n--- Zero-Weight Fallback ---\n";
    for (const char *zfb :
         {"use_local_mean", "return_original", "return_none"}) {
      fastloess::LoessOptions zfb_opts;
      zfb_opts.fraction = k_basic_fraction;
      zfb_opts.zero_weight_fallback = zfb;
      auto zfb_res = fastloess::Loess(zfb_opts).fit(data.x, data.y).value();
      std::cout << "  zero_weight_fallback=" << zfb
                << "  y[0]=" << zfb_res.y_value(0) << '\n';
    }

    // 17. Built-in cross-validation (cv_fractions, cv_method, cv_k)
    std::cout << "\n--- Built-in Cross-Validation ---\n";
    fastloess::LoessOptions cv2_opts;
    cv2_opts.cv_fractions = {k_cv_fraction_low, k_basic_fraction,
                             k_cv_fraction_mid};
    cv2_opts.cv_method = "kfold";
    cv2_opts.cv_k = k_cv_k;
    auto cv2_res = fastloess::Loess(cv2_opts).fit(data.x, data.y).value();
    std::cout << "  CV-selected fraction: " << cv2_res.fraction_used() << '\n';

    // 18. Parallel smoothing
    std::cout << "\n--- Parallel Smoothing ---\n";
    fastloess::LoessOptions par_opts;
    par_opts.fraction = k_basic_fraction;
    par_opts.parallel = true;
    auto par_res = fastloess::Loess(par_opts).fit(data.x, data.y).value();
    std::cout << "  parallel result size: " << par_res.size() << '\n';

    // 19. Expected<> error path: has_value() / error()
    std::cout << "\n--- Expected<> Error Path ---\n";
    {
      const fastloess::LoessOptions err_opts;
      const std::vector<double> short_x = {1.0, 2.0, 3.0};
      const std::vector<double> short_y = {1.0, 2.0};
      auto err_exp = fastloess::Loess(err_opts).fit(short_x, short_y);
      if (!err_exp.has_value()) {
        std::cout << "  has_value()=false, error: " << err_exp.error() << '\n';
      }
    }

    // 20. Full diagnostics (aic, aicc, effective_df, residual_sd, has_value)
    std::cout << "\n--- Full Diagnostics ---\n";
    fastloess::LoessOptions full_diag_opts;
    full_diag_opts.fraction = k_basic_fraction;
    full_diag_opts.return_diagnostics = true;
    auto full_diag_res =
        fastloess::Loess(full_diag_opts).fit(data.x, data.y).value();
    const auto full_diag = full_diag_res.diagnostics();
    if (full_diag.has_value()) {
      std::cout << "  aic:          " << full_diag.aic() << '\n';
      std::cout << "  aicc:         " << full_diag.aicc() << '\n';
      std::cout << "  effective_df: " << full_diag.effective_df() << '\n';
      std::cout << "  residual_sd:  " << full_diag.residual_sd() << '\n';
    }

    std::cout << "\n=== Batch Smoothing Example Complete ===\n";

  } catch (const std::exception &exception) {
    std::fputs("Error: ", stderr);
    std::fputs(exception.what(), stderr);
    std::fputc('\n', stderr);
    return 1;
  }
  return 0;
}

Download batch_smoothing.cpp


Streaming Smoothing

Process large datasets in memory-efficient chunks.

/**
 * @file streaming_smoothing.cpp
 * @brief Streaming LOESS smoothing example
 *
 * Demonstrates chunk-based processing for large datasets.
 */

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <exception>
#include <iostream>
#include <random>
#include <vector>

#include "../../bindings/cpp/include/fastloess.hpp"

namespace {

constexpr size_t k_point_count = 10000;
constexpr unsigned int k_random_seed = 42;
constexpr double k_noise_std_dev = 0.5;
constexpr double k_sine_divisor = 10.0;
constexpr double k_scale_divisor = 50.0;
constexpr double k_fraction = 0.1;
constexpr int k_chunk_size = 1000;
constexpr int k_overlap = 100;
constexpr size_t k_progress_interval = 2000;

} // namespace

int main() {
  try {
    std::cout << "=== Streaming LOESS Smoothing Example ===\n";

    // Generate large synthetic dataset
    const size_t point_count = k_point_count;
    std::vector<double> x_values(point_count);
    std::vector<double> y_values(point_count);

    std::seed_seq generator_seed = {k_random_seed, k_random_seed, k_random_seed,
                                    k_random_seed};
    std::mt19937 generator(generator_seed);
    std::normal_distribution<> noise(0.0, k_noise_std_dev);

    for (size_t point_index = 0; point_index < point_count; ++point_index) {
      x_values[point_index] = static_cast<double>(point_index) / k_overlap;
      y_values[point_index] =
          ((std::sin(x_values[point_index] / k_sine_divisor) *
            x_values[point_index]) /
           k_scale_divisor) +
          noise(generator);
    }

    std::cout << "Generated " << point_count << " data points\n";

    // Streaming smoothing
    fastloess::StreamingOptions opts;
    opts.fraction = k_fraction;
    opts.iterations = 2;
    opts.chunk_size = k_chunk_size;
    opts.overlap = k_overlap;
    opts.return_diagnostics = true;

    std::cout << "\nProcessing with chunk_size=" << opts.chunk_size
              << ", overlap=" << opts.overlap << '\n';

    fastloess::StreamingLoess model(opts);

    std::cout << "\nProcessing data in chunks...\n";

    const size_t chunk_size = static_cast<size_t>(opts.chunk_size);
    size_t total_processed = 0;

    for (size_t chunk_start = 0; chunk_start < point_count;
         chunk_start += chunk_size) {
      const size_t current_chunk_len =
          std::min(chunk_size, point_count - chunk_start);
      std::vector<double> x_chunk(current_chunk_len);
      std::vector<double> y_chunk(current_chunk_len);

      std::copy_n(x_values.begin() + static_cast<std::ptrdiff_t>(chunk_start),
                  static_cast<std::ptrdiff_t>(current_chunk_len),
                  x_chunk.begin());
      std::copy_n(y_values.begin() + static_cast<std::ptrdiff_t>(chunk_start),
                  static_cast<std::ptrdiff_t>(current_chunk_len),
                  y_chunk.begin());

      auto res = model.process_chunk(x_chunk, y_chunk).value();
      total_processed += res.size();

      if (chunk_start % k_progress_interval == 0) {
        std::cout << "  Processed " << chunk_start << " points...\n";
      }
    }

    auto final_res = model.finalize().value();
    total_processed += final_res.size();

    std::cout << "\nStreaming completed:\n";
    std::cout << "  Total points smoothed: " << total_processed << '\n';

    // Show sample of final results
    if (final_res.size() > 0) {
      std::cout << "\nSample from final chunk:\n";
      std::cout << "  x=" << final_res.x_value(0)
                << " y=" << final_res.y_value(0) << '\n';
    }

    // Merge strategy variants
    std::cout << "\n--- Merge Strategy Variants ---\n";
    for (const char *strat : {"weighted", "average", "first", "last"}) {
      fastloess::StreamingOptions ms_opts;
      ms_opts.fraction = k_fraction;
      ms_opts.chunk_size = k_chunk_size;
      ms_opts.merge_strategy = strat;
      fastloess::StreamingLoess ms_model(ms_opts);
      const std::vector<double> x_s(x_values.begin(),
                                    x_values.begin() + k_overlap);
      const std::vector<double> y_s(y_values.begin(),
                                    y_values.begin() + k_overlap);
      auto ms_r1 = ms_model.process_chunk(x_s, y_s).value();
      auto ms_r2 = ms_model.finalize().value();
      std::cout << "  merge_strategy=" << strat
                << "  total=" << ms_r1.size() + ms_r2.size() << '\n';
    }

    // Advanced inherited options: degree, scaling_method, distance_metric,
    // surface_mode, return_se, return_residuals, zero_weight_fallback
    std::cout << "\n--- Advanced Streaming Options ---\n";
    {
      fastloess::StreamingOptions adv_opts;
      adv_opts.fraction = k_fraction;
      adv_opts.chunk_size = k_chunk_size;
      adv_opts.degree = "quadratic";
      adv_opts.scaling_method = "mean";
      adv_opts.distance_metric = "euclidean";
      adv_opts.surface_mode = "direct";
      adv_opts.return_se = true;
      adv_opts.return_residuals = true;
      adv_opts.zero_weight_fallback = "return_original";
      fastloess::StreamingLoess adv_model(adv_opts);
      const std::vector<double> a_x(
          x_values.begin(),
          x_values.begin() + static_cast<std::ptrdiff_t>(k_chunk_size));
      const std::vector<double> a_y(
          y_values.begin(),
          y_values.begin() + static_cast<std::ptrdiff_t>(k_chunk_size));
      auto adv_r1 = adv_model.process_chunk(a_x, a_y).value();
      auto adv_r2 = adv_model.finalize().value();
      std::cout << "  total points: " << adv_r1.size() + adv_r2.size() << '\n';
    }

    std::cout << "\n=== Example completed successfully ===\n";

  } catch (const std::exception &exception) {
    std::fputs("Error: ", stderr);
    std::fputs(exception.what(), stderr);
    std::fputc('\n', stderr);
    return 1;
  }
  return 0;
}

Download streaming_smoothing.cpp


Online Smoothing

Real-time smoothing with sliding window for streaming data.

/**
 * @file online_smoothing.cpp
 * @brief Online LOESS smoothing example
 *
 * Demonstrates sliding window smoothing for real-time data.
 */

#include <cmath>
#include <cstddef>
#include <cstdio>
#include <exception>
#include <iostream>
#include <random>
#include <vector>

#include "../../bindings/cpp/include/fastloess.hpp"

namespace {

constexpr size_t k_point_count = 200;
constexpr unsigned int k_random_seed = 42;
constexpr double k_noise_std_dev = 0.3;
constexpr double k_trend_slope = 0.1;
constexpr double k_seasonal_amplitude = 5.0;
constexpr double k_seasonal_period_divisor = 20.0;
constexpr double k_fraction = 0.5;
constexpr int k_window_capacity = 50;
constexpr int k_min_points = 10;
constexpr size_t k_progress_interval = 40;

} // namespace

int main() {
  try {
    std::cout << "=== Online LOESS Smoothing Example ===\n";

    // Simulate streaming data arrival
    const size_t point_count = k_point_count;
    std::vector<double> x_values(point_count);
    std::vector<double> y_values(point_count);

    std::seed_seq generator_seed = {k_random_seed, k_random_seed, k_random_seed,
                                    k_random_seed};
    std::mt19937 generator(generator_seed);
    std::normal_distribution<> noise(0.0, k_noise_std_dev);

    for (size_t point_index = 0; point_index < point_count; ++point_index) {
      x_values[point_index] = static_cast<double>(point_index);
      // Trend with seasonal component + noise
      y_values[point_index] =
          (k_trend_slope * x_values[point_index]) +
          (k_seasonal_amplitude *
           std::sin(x_values[point_index] / k_seasonal_period_divisor)) +
          noise(generator);
    }

    std::cout << "Generated " << point_count << " streaming data points\n";

    // Online smoothing with sliding window
    fastloess::OnlineOptions opts;
    opts.fraction = k_fraction;
    opts.iterations = 2;
    opts.window_capacity = k_window_capacity;
    opts.min_points = k_min_points;
    opts.update_mode = "full";

    std::cout << "\nProcessing with window_capacity=" << opts.window_capacity
              << ", min_points=" << opts.min_points << '\n';

    fastloess::OnlineLoess model(opts);

    std::cout << "\nProcessing data point-by-point...\n";

    size_t total_emitted = 0;
    for (size_t point_index = 0; point_index < point_count; ++point_index) {
      auto res =
          model.add_point(x_values[point_index], y_values[point_index]).value();

      if (res.has_value()) {
        total_emitted++;
        if (point_index > 0 && point_index % k_progress_interval == 0) {
          std::cout << "  t=" << point_index
                    << " original=" << y_values[point_index]
                    << " smoothed=" << res.smoothed() << '\n';
        }
      }
    }

    std::cout << "\nOnline processing completed:\n";
    std::cout << "  Total points emitted: " << total_emitted << '\n';

    // Incremental update mode
    std::cout << "\n--- Update Mode: incremental ---\n";
    {
      fastloess::OnlineOptions inc_opts;
      inc_opts.fraction = k_fraction;
      inc_opts.window_capacity = k_window_capacity;
      inc_opts.min_points = k_min_points;
      inc_opts.update_mode = "incremental";
      fastloess::OnlineLoess inc_model(inc_opts);
      size_t inc_emitted = 0;
      for (size_t point_index = 0; point_index < point_count; ++point_index) {
        auto res_i =
            inc_model.add_point(x_values[point_index], y_values[point_index])
                .value();
        if (res_i.has_value()) {
          inc_emitted++;
        }
      }
      std::cout << "  incremental mode total emitted: " << inc_emitted << '\n';
    }

    // Advanced inherited options: degree, scaling_method, distance_metric,
    // zero_weight_fallback
    std::cout << "\n--- Advanced Online Options ---\n";
    {
      fastloess::OnlineOptions adv_opts;
      adv_opts.fraction = k_fraction;
      adv_opts.window_capacity = k_window_capacity;
      adv_opts.min_points = k_min_points;
      adv_opts.degree = "quadratic";
      adv_opts.scaling_method = "mar";
      adv_opts.distance_metric = "chebyshev";
      adv_opts.zero_weight_fallback = "return_original";
      fastloess::OnlineLoess adv_model(adv_opts);
      size_t adv_emitted = 0;
      for (size_t point_index = 0; point_index < point_count; ++point_index) {
        auto res_a =
            adv_model.add_point(x_values[point_index], y_values[point_index])
                .value();
        if (res_a.has_value()) {
          adv_emitted++;
        }
      }
      std::cout << "  advanced online total emitted: " << adv_emitted << '\n';
    }

    std::cout << "\n=== Example completed successfully ===\n";

  } catch (const std::exception &exception) {
    std::fputs("Error: ", stderr);
    std::fputs(exception.what(), stderr);
    std::fputc('\n', stderr);
    return 1;
  }
  return 0;
}

Download online_smoothing.cpp


Building the Examples

# Build the C++ bindings
make cpp

# The examples are built as part of the bindings
# Or compile manually:
g++ -std=c++20 -I bindings/cpp/include \
    examples/cpp/batch_smoothing.cpp \
    -L target/release -lfastloess_cpp \
    -o batch_smoothing

Quick Start

#include <fastloess.hpp>
#include <iostream>
#include <vector>

int main() {
    // Generate sample data
    std::vector<double> x(100), y(100);
    for (size_t i = 0; i < 100; ++i) {
        x[i] = i * 0.1;
        y[i] = std::sin(x[i]) + 0.1;
    }

    // Configure options
    fastloess::LoessOptions options;
    options.fraction = 0.3;
    options.iterations = 3;
    options.confidence_intervals = 0.95;
    options.return_diagnostics = true;

    // Smooth
    fastloess::Loess model(options);
    auto expected = model.fit(x, y);
    if (!expected.has_value()) {
        std::cerr << "Error: " << expected.error() << std::endl;
        return 1;
    }
    auto& result = expected.value();

    std::cout << "R²: " << result.diagnostics().r_squared() << std::endl;

    // Access smoothed values
    auto smoothed = result.y_vector();

    return 0;
}

Features

The C++ bindings provide:

  • RAII memory management - Resources automatically freed
  • STL container support - std::vector<double> for all arrays
  • Exception-based errors - fastloess::LoessError for error handling
  • Modern C++ idioms - Designated initializers, move semantics