C++ Examples¶
Complete C++ examples demonstrating the fastLowess C++ bindings with modern C++ features.
Batch Smoothing¶
Process complete datasets with the idiomatic C++ wrapper.
/**
* @file batch_smoothing.cpp
* @brief fastlowess Batch Smoothing Example
*
* This example demonstrates batch LOWESS smoothing features:
* - Basic smoothing with different parameters
* - Robustness iterations for outlier handling
* - Confidence and prediction intervals
* - Diagnostics and cross-validation
*
* The Lowess 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/fastlowess.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;
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 << "=== fastlowess 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";
fastlowess::LowessOptions basic_opts;
basic_opts.fraction = k_basic_fraction;
basic_opts.iterations = 0;
fastlowess::Lowess 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";
fastlowess::LowessOptions robust_opts;
robust_opts.fraction = k_basic_fraction;
robust_opts.iterations = 3;
robust_opts.robustness_method = "bisquare";
robust_opts.return_robustness_weights = true;
fastlowess::Lowess 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';
fastlowess::LowessOptions 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;
fastlowess::Lowess 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) {
fastlowess::LowessOptions cv_opts;
cv_opts.fraction = fraction;
cv_opts.return_diagnostics = true;
fastlowess::Lowess model(cv_opts);
auto res_exp = model.fit(data.x, data.y);
// Use non-throwing interface
if (res_exp.hasValue()) {
auto &res = res_exp.value();
if (res.diagnostics().hasValue()) {
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().hasValue()) {
const auto diag = res_intervals.diagnostics();
std::cout << "\nFit Statistics (Intervals Model):\n";
std::cout << " - R^2: " << diag.rSquared() << '\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;
}
fastlowess::LowessOptions opt_ext;
opt_ext.fraction = k_boundary_fraction;
opt_ext.boundary_policy = "extend";
auto r_ext = fastlowess::Lowess(opt_ext).fit(linear_x, linear_y).value();
fastlowess::LowessOptions opt_ref;
opt_ref.fraction = k_boundary_fraction;
opt_ref.boundary_policy = "reflect";
auto r_ref = fastlowess::Lowess(opt_ref).fit(linear_x, linear_y).value();
fastlowess::LowessOptions opt_zero;
opt_zero.fraction = k_boundary_fraction;
opt_zero.boundary_policy = "zero";
auto r_zr = fastlowess::Lowess(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.yValue(0)
<< ", last=" << r_ext.yValue(k_linear_point_count - 1) << '\n';
std::cout << " - Reflect: first=" << r_ref.yValue(0)
<< ", last=" << r_ref.yValue(k_linear_point_count - 1) << '\n';
std::cout << " - Zero: first=" << r_zr.yValue(0)
<< ", last=" << r_zr.yValue(k_linear_point_count - 1) << '\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;
}
Streaming Smoothing¶
Process large datasets in memory-efficient chunks.
/**
* @file streaming_smoothing.cpp
* @brief Streaming LOWESS 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/fastlowess.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 LOWESS 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
fastlowess::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';
fastlowess::StreamingLowess 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.processChunk(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.xValue(0) << " y=" << final_res.yValue(0)
<< '\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 LOWESS 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/fastlowess.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 LOWESS 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
fastlowess::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';
fastlowess::OnlineLowess 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) {
const std::vector<double> x_sample = {x_values[point_index]};
const std::vector<double> y_sample = {y_values[point_index]};
auto res = model.addPoints(x_sample, y_sample).value();
total_emitted += res.size();
if (point_index > 0 && point_index % k_progress_interval == 0 &&
res.size() > 0) {
std::cout << " t=" << point_index
<< " original=" << y_values[point_index]
<< " smoothed=" << res.yValue(res.size() - 1) << '\n';
}
}
std::cout << "\nOnline processing completed:\n";
std::cout << " Total points emitted: " << total_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;
}
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 -lfastlowess_cpp \
-o batch_smoothing
Quick Start¶
#include <fastlowess.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
fastlowess::LowessOptions options;
options.fraction = 0.3;
options.iterations = 3;
options.confidence_intervals = 0.95;
options.return_diagnostics = true;
// Smooth
try {
auto result = fastlowess::smooth(x, y, options);
std::cout << "R²: " << result.diagnostics().rSquared() << std::endl;
// Access smoothed values
auto smoothed = result.yVector();
} catch (const fastlowess::LowessError& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
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 -
fastlowess::LowessErrorfor error handling - Modern C++ idioms - Designated initializers, move semantics