#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::string::{String, ToString};
#[cfg(feature = "std")]
use std::vec::Vec;
use core::fmt::Debug;
use core::marker::PhantomData;
use crate::adapters::batch::BatchLoessBuilder;
use crate::adapters::online::OnlineLoessBuilder;
use crate::adapters::streaming::StreamingLoessBuilder;
use crate::algorithms::regression::SolverLinalg;
use crate::engine::executor::{CVPassFn, IntervalPassFn, SmoothPassFn};
use crate::evaluation::cv::CVKind;
use crate::evaluation::defaults::DEFAULT_CV_K_FOLDS;
use crate::evaluation::intervals::IntervalMethod;
use crate::math::distance::DistanceLinalg;
use crate::math::linalg::FloatLinalg;
use crate::primitives::backend::Backend;
pub use crate::adapters::online::UpdateMode;
pub use crate::adapters::streaming::MergeStrategy;
pub use crate::algorithms::regression::{PolynomialDegree, ZeroWeightFallback};
pub use crate::algorithms::robustness::RobustnessMethod;
pub use crate::engine::executor::SurfaceMode;
pub use crate::engine::output::LoessResult;
pub use crate::math::boundary::BoundaryPolicy;
pub use crate::math::distance::DistanceMetric;
pub use crate::math::kernel::WeightFunction;
pub use crate::math::scaling::ScalingMethod;
pub use crate::primitives::errors::LoessError;
pub(crate) trait IntoEnum<E> {
fn into_enum(self) -> Result<E, LoessError>;
}
macro_rules! impl_into_enum_for {
($ty:ty) => {
impl IntoEnum<$ty> for $ty {
#[inline]
fn into_enum(self) -> Result<$ty, LoessError> {
Ok(self)
}
}
impl IntoEnum<$ty> for &str {
#[inline]
fn into_enum(self) -> Result<$ty, LoessError> {
self.parse()
}
}
impl IntoEnum<$ty> for String {
#[inline]
fn into_enum(self) -> Result<$ty, LoessError> {
self.as_str().parse()
}
}
};
}
impl_into_enum_for!(BoundaryPolicy);
impl_into_enum_for!(MergeStrategy);
impl_into_enum_for!(PolynomialDegree);
impl_into_enum_for!(RobustnessMethod);
impl_into_enum_for!(ScalingMethod);
impl_into_enum_for!(SurfaceMode);
impl_into_enum_for!(UpdateMode);
impl_into_enum_for!(WeightFunction);
impl_into_enum_for!(ZeroWeightFallback);
impl<T> IntoEnum<DistanceMetric<T>> for DistanceMetric<T> {
#[inline]
fn into_enum(self) -> Result<DistanceMetric<T>, LoessError> {
Ok(self)
}
}
impl<T> IntoEnum<DistanceMetric<T>> for &str
where
T: num_traits::Float + core::str::FromStr,
{
#[inline]
fn into_enum(self) -> Result<DistanceMetric<T>, LoessError> {
self.parse()
}
}
impl<T> IntoEnum<DistanceMetric<T>> for String
where
T: num_traits::Float + core::str::FromStr,
{
#[inline]
fn into_enum(self) -> Result<DistanceMetric<T>, LoessError> {
self.as_str().parse()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct BatchMode;
#[derive(Debug, Clone, Copy, Default)]
pub struct StreamingMode;
#[derive(Debug, Clone, Copy, Default)]
pub struct OnlineMode;
pub type Loess<T = f64> = LoessBuilder<T, BatchMode>;
pub type StreamingLoess<T = f64> = LoessBuilder<T, StreamingMode>;
pub type OnlineLoess<T = f64> = LoessBuilder<T, OnlineMode>;
#[derive(Debug, Clone)]
pub struct LoessBuilder<
T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync,
Mode = BatchMode,
> {
pub fraction: Option<T>,
pub iterations: Option<usize>,
pub weight_function: Option<WeightFunction>,
pub robustness_method: Option<RobustnessMethod>,
pub scaling_method: Option<ScalingMethod>,
pub interval_type: Option<IntervalMethod<T>>,
pub cv_fractions: Option<Vec<T>>,
pub(crate) cv_kind: Option<CVKind>,
pub(crate) cv_seed: Option<u64>,
pub auto_converge: Option<T>,
pub return_diagnostics: Option<bool>,
pub compute_residuals: Option<bool>,
pub return_robustness_weights: Option<bool>,
pub boundary_policy: Option<BoundaryPolicy>,
pub zero_weight_fallback: Option<ZeroWeightFallback>,
pub merge_strategy: Option<MergeStrategy>,
pub update_mode: Option<UpdateMode>,
pub chunk_size: Option<usize>,
pub overlap: Option<usize>,
pub window_capacity: Option<usize>,
pub min_points: Option<usize>,
pub polynomial_degree: Option<PolynomialDegree>,
pub dimensions: Option<usize>,
pub distance_metric: Option<DistanceMetric<T>>,
pub surface_mode: Option<SurfaceMode>,
pub cell: Option<T>,
pub interpolation_vertices: Option<usize>,
pub boundary_degree_fallback: Option<bool>,
pub custom_weights: Option<Vec<T>>,
pub cv_method_str: Option<String>,
pub cv_k_val: usize,
pub weighted_metric_weights: Option<Vec<T>>,
#[doc(hidden)]
pub custom_smooth_pass: Option<SmoothPassFn<T>>,
#[doc(hidden)]
pub custom_cv_pass: Option<CVPassFn<T>>,
#[doc(hidden)]
pub custom_interval_pass: Option<IntervalPassFn<T>>,
#[doc(hidden)]
pub backend: Option<Backend>,
#[doc(hidden)]
pub parallel: Option<bool>,
#[doc(hidden)]
pub duplicate_param: Option<&'static str>,
#[doc(hidden)]
pub parse_errors: Vec<LoessError>,
#[doc(hidden)]
pub _mode: PhantomData<Mode>,
}
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync, Mode: Default> Default
for LoessBuilder<T, Mode>
{
fn default() -> Self {
Self::new()
}
}
#[allow(private_bounds)]
impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLinalg, Mode: Default>
LoessBuilder<T, Mode>
{
pub fn new() -> Self {
Self {
fraction: None,
iterations: None,
weight_function: None,
robustness_method: None,
scaling_method: None,
interval_type: None,
cv_fractions: None,
cv_kind: None,
cv_seed: None,
auto_converge: None,
return_diagnostics: None,
compute_residuals: None,
return_robustness_weights: None,
boundary_policy: None,
zero_weight_fallback: None,
merge_strategy: None,
update_mode: None,
chunk_size: None,
overlap: None,
window_capacity: None,
min_points: None,
polynomial_degree: None,
dimensions: None,
distance_metric: None,
surface_mode: None,
cell: None,
interpolation_vertices: None,
boundary_degree_fallback: None,
custom_weights: None,
cv_method_str: None,
cv_k_val: DEFAULT_CV_K_FOLDS,
weighted_metric_weights: None,
custom_smooth_pass: None,
custom_cv_pass: None,
custom_interval_pass: None,
backend: None,
parallel: None,
duplicate_param: None,
parse_errors: Vec::new(),
_mode: PhantomData,
}
}
pub fn zero_weight_fallback(mut self, policy: impl IntoEnum<ZeroWeightFallback>) -> Self {
if self.zero_weight_fallback.is_some() {
self.duplicate_param = Some("zero_weight_fallback");
}
match policy.into_enum() {
Ok(p) => self.zero_weight_fallback = Some(p),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn boundary_policy(mut self, policy: impl IntoEnum<BoundaryPolicy>) -> Self {
if self.boundary_policy.is_some() {
self.duplicate_param = Some("boundary_policy");
}
match policy.into_enum() {
Ok(p) => self.boundary_policy = Some(p),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn merge_strategy(mut self, strategy: impl IntoEnum<MergeStrategy>) -> Self {
if self.merge_strategy.is_some() {
self.duplicate_param = Some("merge_strategy");
}
match strategy.into_enum() {
Ok(s) => self.merge_strategy = Some(s),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn update_mode(mut self, mode: impl IntoEnum<UpdateMode>) -> Self {
if self.update_mode.is_some() {
self.duplicate_param = Some("update_mode");
}
match mode.into_enum() {
Ok(m) => self.update_mode = Some(m),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn chunk_size(mut self, size: usize) -> Self {
if self.chunk_size.is_some() {
self.duplicate_param = Some("chunk_size");
}
self.chunk_size = Some(size);
self
}
pub fn overlap(mut self, overlap: usize) -> Self {
if self.overlap.is_some() {
self.duplicate_param = Some("overlap");
}
self.overlap = Some(overlap);
self
}
pub fn window_capacity(mut self, capacity: usize) -> Self {
if self.window_capacity.is_some() {
self.duplicate_param = Some("window_capacity");
}
self.window_capacity = Some(capacity);
self
}
pub fn min_points(mut self, points: usize) -> Self {
if self.min_points.is_some() {
self.duplicate_param = Some("min_points");
}
self.min_points = Some(points);
self
}
pub fn fraction(mut self, fraction: T) -> Self {
if self.fraction.is_some() {
self.duplicate_param = Some("fraction");
}
self.fraction = Some(fraction);
self
}
pub fn iterations(mut self, iterations: usize) -> Self {
if self.iterations.is_some() {
self.duplicate_param = Some("iterations");
}
self.iterations = Some(iterations);
self
}
pub fn weight_function(mut self, wf: impl IntoEnum<WeightFunction>) -> Self {
if self.weight_function.is_some() {
self.duplicate_param = Some("weight_function");
}
match wf.into_enum() {
Ok(w) => self.weight_function = Some(w),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn robustness_method(mut self, rm: impl IntoEnum<RobustnessMethod>) -> Self {
if self.robustness_method.is_some() {
self.duplicate_param = Some("robustness_method");
}
match rm.into_enum() {
Ok(r) => self.robustness_method = Some(r),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn scaling_method(mut self, sm: impl IntoEnum<ScalingMethod>) -> Self {
if self.scaling_method.is_some() {
self.duplicate_param = Some("scaling_method");
}
match sm.into_enum() {
Ok(s) => self.scaling_method = Some(s),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn return_se(mut self) -> Self {
if self.interval_type.is_none() {
self.interval_type = Some(IntervalMethod::se());
}
self
}
pub fn confidence_intervals(mut self, level: T) -> Self {
if self.interval_type.as_ref().is_some_and(|it| it.confidence) {
self.duplicate_param = Some("confidence_intervals");
}
self.interval_type = Some(match self.interval_type {
Some(existing) if existing.prediction => IntervalMethod {
level,
confidence: true,
prediction: true,
se: true,
},
_ => IntervalMethod::confidence(level),
});
self
}
pub fn prediction_intervals(mut self, level: T) -> Self {
if self.interval_type.as_ref().is_some_and(|it| it.prediction) {
self.duplicate_param = Some("prediction_intervals");
}
self.interval_type = Some(match self.interval_type {
Some(existing) if existing.confidence => IntervalMethod {
level,
confidence: true,
prediction: true,
se: true,
},
_ => IntervalMethod::prediction(level),
});
self
}
pub fn cv_method(mut self, method: &str) -> Self {
self.cv_method_str = Some(method.to_string());
self
}
pub fn cv_k(mut self, k: usize) -> Self {
self.cv_k_val = k;
self
}
pub fn cv_fractions(mut self, fractions: Vec<T>) -> Self {
if self.cv_fractions.is_some() {
self.duplicate_param = Some("cv_fractions");
}
self.cv_fractions = Some(fractions);
self
}
pub fn cv_seed(mut self, seed: u64) -> Self {
self.cv_seed = Some(seed);
self
}
pub fn weighted_metric_weights(mut self, weights: Vec<T>) -> Self {
self.weighted_metric_weights = Some(weights);
self
}
pub fn auto_converge(mut self, tolerance: T) -> Self {
if self.auto_converge.is_some() {
self.duplicate_param = Some("auto_converge");
}
self.auto_converge = Some(tolerance);
self
}
pub fn return_diagnostics(mut self) -> Self {
self.return_diagnostics = Some(true);
self
}
pub fn return_residuals(mut self) -> Self {
self.compute_residuals = Some(true);
self
}
pub fn return_robustness_weights(mut self) -> Self {
self.return_robustness_weights = Some(true);
self
}
pub fn degree(mut self, degree: impl IntoEnum<PolynomialDegree>) -> Self {
if self.polynomial_degree.is_some() {
self.duplicate_param = Some("degree");
}
match degree.into_enum() {
Ok(d) => self.polynomial_degree = Some(d),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn dimensions(mut self, dims: usize) -> Self {
if self.dimensions.is_some() {
self.duplicate_param = Some("dimensions");
}
self.dimensions = Some(dims);
self
}
pub fn distance_metric(mut self, metric: impl IntoEnum<DistanceMetric<T>>) -> Self {
if self.distance_metric.is_some() {
self.duplicate_param = Some("distance_metric");
}
match metric.into_enum() {
Ok(m) => self.distance_metric = Some(m),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn surface_mode(mut self, mode: impl IntoEnum<SurfaceMode>) -> Self {
if self.surface_mode.is_some() {
self.duplicate_param = Some("surface_mode");
}
match mode.into_enum() {
Ok(m) => self.surface_mode = Some(m),
Err(e) => self.parse_errors.push(e),
}
self
}
pub fn cell(mut self, cell: T) -> Self {
if self.cell.is_some() {
self.duplicate_param = Some("cell");
}
self.cell = Some(cell);
self
}
pub fn interpolation_vertices(mut self, vertices: usize) -> Self {
if self.interpolation_vertices.is_some() {
self.duplicate_param = Some("interpolation_vertices");
}
self.interpolation_vertices = Some(vertices);
self
}
pub fn boundary_degree_fallback(mut self, enabled: bool) -> Self {
if self.boundary_degree_fallback.is_some() {
self.duplicate_param = Some("boundary_degree_fallback");
}
self.boundary_degree_fallback = Some(enabled);
self
}
pub fn custom_weights(mut self, weights: Vec<T>) -> Self {
if self.custom_weights.is_some() {
self.duplicate_param = Some("custom_weights");
}
self.custom_weights = Some(weights);
self
}
#[doc(hidden)]
pub fn custom_smooth_pass(mut self, pass: SmoothPassFn<T>) -> Self {
self.custom_smooth_pass = Some(pass);
self
}
#[doc(hidden)]
pub fn custom_cv_pass(mut self, pass: CVPassFn<T>) -> Self {
self.custom_cv_pass = Some(pass);
self
}
#[doc(hidden)]
pub fn custom_interval_pass(mut self, pass: IntervalPassFn<T>) -> Self {
self.custom_interval_pass = Some(pass);
self
}
#[doc(hidden)]
pub fn backend(mut self, backend: Backend) -> Self {
self.backend = Some(backend);
self
}
#[doc(hidden)]
pub fn parallel(mut self, parallel: bool) -> Self {
self.parallel = Some(parallel);
self
}
}
#[allow(private_bounds)]
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync + 'static>
LoessBuilder<T, BatchMode>
{
pub fn build(self) -> Result<crate::adapters::batch::BatchLoess<T>, LoessError> {
Batch::convert(self).build()
}
}
#[allow(private_bounds)]
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync + 'static, Mode: Default>
LoessBuilder<T, Mode>
{
#[doc(hidden)]
pub fn adapter<A>(self, _adapter: A) -> A::Output
where
A: LoessAdapter<T>,
{
A::convert(self)
}
}
#[allow(private_bounds)]
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync + 'static>
LoessBuilder<T, StreamingMode>
{
pub fn build(self) -> Result<crate::adapters::streaming::StreamingLoess<T>, LoessError> {
Streaming::convert(self).build()
}
}
#[allow(private_bounds)]
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync + 'static>
LoessBuilder<T, OnlineMode>
{
pub fn build(self) -> Result<crate::adapters::online::OnlineLoess<T>, LoessError> {
Online::convert(self).build()
}
}
pub trait LoessAdapter<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync> {
type Output;
fn convert<Mode>(builder: LoessBuilder<T, Mode>) -> Self::Output;
}
#[derive(Debug, Clone, Copy)]
pub struct Batch;
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync> LoessAdapter<T>
for Batch
{
type Output = BatchLoessBuilder<T>;
fn convert<Mode>(builder: LoessBuilder<T, Mode>) -> Self::Output {
let mut result = BatchLoessBuilder::default();
if let Some(fraction) = builder.fraction {
result.fraction = fraction;
}
if let Some(iterations) = builder.iterations {
result.iterations = iterations;
}
if let Some(wf) = builder.weight_function {
result.weight_function = wf;
}
if let Some(rm) = builder.robustness_method {
result.robustness_method = rm;
}
if let Some(sm) = builder.scaling_method {
result.scaling_method = sm;
}
if let Some(it) = builder.interval_type {
result.interval_type = Some(it);
}
if let Some(cvf) = builder.cv_fractions {
result.cv_fractions = Some(cvf);
}
if let Some(cvk) = builder.cv_kind {
result.cv_kind = Some(cvk);
}
result.cv_seed = builder.cv_seed;
if result.cv_kind.is_none()
&& let Some(method_str) = builder.cv_method_str
{
let lower = method_str.to_lowercase();
match lower.as_str() {
"kfold" | "k_fold" | "k-fold" => {
result.cv_kind = Some(CVKind::KFold(builder.cv_k_val));
}
"loocv" | "loo_cv" | "loo-cv" => {
result.cv_kind = Some(CVKind::LOOCV);
}
_ => {
result.deferred_error = Some(LoessError::InvalidOption {
option: "cv_method",
value: method_str,
valid: "kfold, loocv",
});
}
}
}
if let Some(ac) = builder.auto_converge {
result.auto_converge = Some(ac);
}
if let Some(zwf) = builder.zero_weight_fallback {
result.zero_weight_fallback = zwf;
}
if let Some(bp) = builder.boundary_policy {
result.boundary_policy = bp;
}
if let Some(rw) = builder.return_robustness_weights {
result.return_robustness_weights = rw;
}
if let Some(rd) = builder.return_diagnostics {
result.return_diagnostics = rd;
}
if let Some(cr) = builder.compute_residuals {
result.compute_residuals = cr;
}
if let Some(pd) = builder.polynomial_degree {
result.polynomial_degree = pd;
}
if let Some(dims) = builder.dimensions {
result.dimensions = dims;
}
if let Some(mut dm) = builder.distance_metric {
if let DistanceMetric::Weighted(ref mut w) = dm
&& let Some(wmw) = builder.weighted_metric_weights
{
*w = wmw;
}
result.distance_metric = dm;
}
if let Some(cell) = builder.cell {
result.cell = Some(cell.to_f64().unwrap());
}
if let Some(iv) = builder.interpolation_vertices {
result.interpolation_vertices = Some(iv);
}
if let Some(sm) = builder.surface_mode {
result.surface_mode = sm;
}
if let Some(bdf) = builder.boundary_degree_fallback {
result.boundary_degree_fallback = bdf;
}
if let Some(uw) = builder.custom_weights {
result.custom_weights = Some(uw);
}
if let Some(sp) = builder.custom_smooth_pass {
result.custom_smooth_pass = Some(sp);
}
if let Some(cp) = builder.custom_cv_pass {
result.custom_cv_pass = Some(cp);
}
if let Some(ip) = builder.custom_interval_pass {
result.custom_interval_pass = Some(ip);
}
if let Some(b) = builder.backend {
result.backend = Some(b);
}
if let Some(p) = builder.parallel {
result.parallel = Some(p);
}
result.duplicate_param = builder.duplicate_param;
if !builder.parse_errors.is_empty() {
result.deferred_error = Some(LoessError::ParseErrors(builder.parse_errors));
}
result
}
}
#[derive(Debug, Clone, Copy)]
pub struct Streaming;
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync> LoessAdapter<T>
for Streaming
{
type Output = StreamingLoessBuilder<T>;
fn convert<Mode>(builder: LoessBuilder<T, Mode>) -> Self::Output {
let mut result = StreamingLoessBuilder::default();
if let Some(chunk_size) = builder.chunk_size {
result.chunk_size = chunk_size;
}
if let Some(overlap) = builder.overlap {
result.overlap = overlap;
}
if let Some(fraction) = builder.fraction {
result.fraction = fraction;
}
if let Some(iterations) = builder.iterations {
result.iterations = iterations;
}
if let Some(wf) = builder.weight_function {
result.weight_function = wf;
}
if let Some(bp) = builder.boundary_policy {
result.boundary_policy = bp;
}
if let Some(rm) = builder.robustness_method {
result.robustness_method = rm;
}
if let Some(sm) = builder.scaling_method {
result.scaling_method = sm;
}
if let Some(zwf) = builder.zero_weight_fallback {
result.zero_weight_fallback = zwf;
}
if let Some(ms) = builder.merge_strategy {
result.merge_strategy = ms;
}
if let Some(rw) = builder.return_robustness_weights {
result.return_robustness_weights = rw;
}
if let Some(rd) = builder.return_diagnostics {
result.return_diagnostics = rd;
}
if let Some(cr) = builder.compute_residuals {
result.compute_residuals = cr;
}
if let Some(ac) = builder.auto_converge {
result.auto_converge = Some(ac);
}
if let Some(pd) = builder.polynomial_degree {
result.polynomial_degree = pd;
}
if let Some(dims) = builder.dimensions {
result.dimensions = dims;
}
if let Some(mut dm) = builder.distance_metric {
if let DistanceMetric::Weighted(ref mut w) = dm
&& let Some(wmw) = builder.weighted_metric_weights
{
*w = wmw;
}
result.distance_metric = dm;
}
if let Some(cell) = builder.cell {
result.cell = Some(cell.to_f64().unwrap());
}
if let Some(iv) = builder.interpolation_vertices {
result.interpolation_vertices = Some(iv);
}
if let Some(sm) = builder.surface_mode {
result.surface_mode = sm;
}
if let Some(bdf) = builder.boundary_degree_fallback {
result.boundary_degree_fallback = bdf;
}
if let Some(sp) = builder.custom_smooth_pass {
result.custom_smooth_pass = Some(sp);
}
if let Some(cp) = builder.custom_cv_pass {
result.custom_cv_pass = Some(cp);
}
if let Some(ip) = builder.custom_interval_pass {
result.custom_interval_pass = Some(ip);
}
if let Some(b) = builder.backend {
result.backend = Some(b);
}
if let Some(p) = builder.parallel {
result.parallel = Some(p);
}
result.duplicate_param = builder.duplicate_param;
if !builder.parse_errors.is_empty() {
result.deferred_error = Some(LoessError::ParseErrors(builder.parse_errors));
}
result
}
}
#[derive(Debug, Clone, Copy)]
pub struct Online;
impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync> LoessAdapter<T>
for Online
{
type Output = OnlineLoessBuilder<T>;
fn convert<Mode>(builder: LoessBuilder<T, Mode>) -> Self::Output {
let mut result = OnlineLoessBuilder::default();
if let Some(window_capacity) = builder.window_capacity {
result.window_capacity = window_capacity;
}
if let Some(min_points) = builder.min_points {
result.min_points = min_points;
}
if let Some(fraction) = builder.fraction {
result.fraction = fraction;
}
if let Some(iterations) = builder.iterations {
result.iterations = iterations;
}
if let Some(wf) = builder.weight_function {
result.weight_function = wf;
}
if let Some(um) = builder.update_mode {
result.update_mode = um;
}
if let Some(rm) = builder.robustness_method {
result.robustness_method = rm;
}
if let Some(sm) = builder.scaling_method {
result.scaling_method = sm;
}
if let Some(bp) = builder.boundary_policy {
result.boundary_policy = bp;
}
if let Some(zwf) = builder.zero_weight_fallback {
result.zero_weight_fallback = zwf;
}
if let Some(cr) = builder.compute_residuals {
result.compute_residuals = cr;
}
if let Some(rw) = builder.return_robustness_weights {
result.return_robustness_weights = rw;
}
if let Some(ac) = builder.auto_converge {
result.auto_converge = Some(ac);
}
if let Some(pd) = builder.polynomial_degree {
result.polynomial_degree = pd;
}
if let Some(dims) = builder.dimensions {
result.dimensions = dims;
}
if let Some(mut dm) = builder.distance_metric {
if let DistanceMetric::Weighted(ref mut w) = dm
&& let Some(wmw) = builder.weighted_metric_weights
{
*w = wmw;
}
result.distance_metric = dm;
}
if let Some(cell) = builder.cell {
result.cell = Some(cell.to_f64().unwrap());
}
if let Some(iv) = builder.interpolation_vertices {
result.interpolation_vertices = Some(iv);
}
if let Some(sm) = builder.surface_mode {
result.surface_mode = sm;
}
if let Some(bdf) = builder.boundary_degree_fallback {
result.boundary_degree_fallback = bdf;
}
if let Some(sp) = builder.custom_smooth_pass {
result.custom_smooth_pass = Some(sp);
}
if let Some(cp) = builder.custom_cv_pass {
result.custom_cv_pass = Some(cp);
}
if let Some(ip) = builder.custom_interval_pass {
result.custom_interval_pass = Some(ip);
}
if let Some(b) = builder.backend {
result.backend = Some(b);
}
if let Some(p) = builder.parallel {
result.parallel = Some(p);
}
result.duplicate_param = builder.duplicate_param;
if !builder.parse_errors.is_empty() {
result.deferred_error = Some(LoessError::ParseErrors(builder.parse_errors));
}
result
}
}
use core::str::FromStr;
use num_traits::Float;
impl FromStr for WeightFunction {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"cosine" => Ok(WeightFunction::Cosine),
"epanechnikov" => Ok(WeightFunction::Epanechnikov),
"gaussian" => Ok(WeightFunction::Gaussian),
"biweight" | "bisquare" => Ok(WeightFunction::Biweight),
"triangle" | "triangular" => Ok(WeightFunction::Triangle),
"tricube" => Ok(WeightFunction::Tricube),
"uniform" | "boxcar" => Ok(WeightFunction::Uniform),
_ => Err(LoessError::InvalidOption {
option: "weight_function",
value: s.to_string(),
valid: "tricube, epanechnikov, gaussian, uniform, biweight, triangle, cosine",
}),
}
}
}
impl FromStr for BoundaryPolicy {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"extend" | "pad" => Ok(BoundaryPolicy::Extend),
"reflect" | "mirror" => Ok(BoundaryPolicy::Reflect),
"zero" => Ok(BoundaryPolicy::Zero),
"noboundary" | "none" => Ok(BoundaryPolicy::NoBoundary),
_ => Err(LoessError::InvalidOption {
option: "boundary_policy",
value: s.to_string(),
valid: "extend, reflect, zero, noboundary",
}),
}
}
}
impl<T> FromStr for DistanceMetric<T>
where
T: Float + FromStr,
{
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lower = s.to_lowercase();
if let Some(p_str) = lower.strip_prefix("minkowski:") {
let p: T = p_str.parse().map_err(|_| LoessError::InvalidOption {
option: "distance_metric",
value: s.to_string(),
valid: "normalized, euclidean, manhattan, chebyshev, minkowski, minkowski:<p>, weighted",
})?;
return Ok(DistanceMetric::Minkowski(p));
}
match lower.as_str() {
"normalized" | "norm" => Ok(DistanceMetric::Normalized),
"euclidean" | "euclid" => Ok(DistanceMetric::Euclidean),
"manhattan" | "l1" => Ok(DistanceMetric::Manhattan),
"chebyshev" | "linf" => Ok(DistanceMetric::Chebyshev),
"minkowski" => Ok(DistanceMetric::Minkowski(T::from(2.0).unwrap())),
"weighted" | "weighted_euclidean" => Ok(DistanceMetric::Weighted(Vec::new())),
_ => Err(LoessError::InvalidOption {
option: "distance_metric",
value: s.to_string(),
valid: "normalized, euclidean, manhattan, chebyshev, minkowski, minkowski:<p>, weighted",
}),
}
}
}
impl FromStr for ScalingMethod {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"mar" | "median_absolute_residual" => Ok(ScalingMethod::MAR),
"mad" | "median_absolute_deviation" => Ok(ScalingMethod::MAD),
"mean" | "mean_absolute_residual" => Ok(ScalingMethod::Mean),
_ => Err(LoessError::InvalidOption {
option: "scaling_method",
value: s.to_string(),
valid: "mad, mar, mean",
}),
}
}
}
impl FromStr for RobustnessMethod {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"bisquare" | "biweight" => Ok(RobustnessMethod::Bisquare),
"huber" => Ok(RobustnessMethod::Huber),
"talwar" => Ok(RobustnessMethod::Talwar),
_ => Err(LoessError::InvalidOption {
option: "robustness_method",
value: s.to_string(),
valid: "bisquare, huber, talwar",
}),
}
}
}
impl FromStr for SurfaceMode {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"interpolation" | "interp" | "interpolate" => Ok(SurfaceMode::Interpolation),
"direct" => Ok(SurfaceMode::Direct),
_ => Err(LoessError::InvalidOption {
option: "surface_mode",
value: s.to_string(),
valid: "interpolation, direct",
}),
}
}
}
impl FromStr for PolynomialDegree {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"constant" | "0" => Ok(PolynomialDegree::Constant),
"linear" | "1" => Ok(PolynomialDegree::Linear),
"quadratic" | "2" => Ok(PolynomialDegree::Quadratic),
"cubic" | "3" => Ok(PolynomialDegree::Cubic),
"quartic" | "4" => Ok(PolynomialDegree::Quartic),
_ => Err(LoessError::InvalidOption {
option: "degree",
value: s.to_string(),
valid: "constant (0), linear (1), quadratic (2), cubic (3), quartic (4)",
}),
}
}
}
impl FromStr for ZeroWeightFallback {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"use_local_mean" | "local_mean" | "mean" => Ok(ZeroWeightFallback::UseLocalMean),
"return_original" | "original" => Ok(ZeroWeightFallback::ReturnOriginal),
"return_none" | "none" | "nan" => Ok(ZeroWeightFallback::ReturnNone),
_ => Err(LoessError::InvalidOption {
option: "zero_weight_fallback",
value: s.to_string(),
valid: "use_local_mean, return_original, return_none",
}),
}
}
}
impl FromStr for MergeStrategy {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"average" | "mean" => Ok(MergeStrategy::Average),
"weighted_average" | "weighted" | "weightedaverage" => {
Ok(MergeStrategy::WeightedAverage)
}
"take_first" | "first" | "takefirst" | "left" => Ok(MergeStrategy::TakeFirst),
"take_last" | "last" | "takelast" | "right" => Ok(MergeStrategy::TakeLast),
_ => Err(LoessError::InvalidOption {
option: "merge_strategy",
value: s.to_string(),
valid: "average, weighted_average, take_first, take_last",
}),
}
}
}
impl FromStr for UpdateMode {
type Err = LoessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"full" | "resmooth" => Ok(UpdateMode::Full),
"incremental" | "single" => Ok(UpdateMode::Incremental),
_ => Err(LoessError::InvalidOption {
option: "update_mode",
value: s.to_string(),
valid: "full, incremental",
}),
}
}
}
#[cfg(feature = "dev")]
pub mod helpers {
use super::{
BoundaryPolicy, DistanceMetric, LoessError, MergeStrategy, PolynomialDegree,
RobustnessMethod, ScalingMethod, SurfaceMode, UpdateMode, WeightFunction,
ZeroWeightFallback,
};
pub fn parse_weight_function(s: &str) -> Result<WeightFunction, LoessError> {
s.parse()
}
pub fn parse_robustness_method(s: &str) -> Result<RobustnessMethod, LoessError> {
s.parse()
}
pub fn parse_zero_weight_fallback(s: &str) -> Result<ZeroWeightFallback, LoessError> {
s.parse()
}
pub fn parse_boundary_policy(s: &str) -> Result<BoundaryPolicy, LoessError> {
s.parse()
}
pub fn parse_scaling_method(s: &str) -> Result<ScalingMethod, LoessError> {
s.parse()
}
pub fn parse_polynomial_degree(s: &str) -> Result<PolynomialDegree, LoessError> {
s.parse()
}
pub fn parse_distance_metric(s: &str) -> Result<DistanceMetric<f64>, LoessError> {
s.parse()
}
pub fn parse_surface_mode(s: &str) -> Result<SurfaceMode, LoessError> {
s.parse()
}
pub fn parse_update_mode(s: &str) -> Result<UpdateMode, LoessError> {
s.parse()
}
pub fn parse_merge_strategy(s: &str) -> Result<MergeStrategy, LoessError> {
s.parse()
}
pub fn weight_function_str(v: WeightFunction) -> &'static str {
match v {
WeightFunction::Tricube => "tricube",
WeightFunction::Epanechnikov => "epanechnikov",
WeightFunction::Gaussian => "gaussian",
WeightFunction::Uniform => "uniform",
WeightFunction::Biweight => "biweight",
WeightFunction::Triangle => "triangle",
WeightFunction::Cosine => "cosine",
}
}
pub fn robustness_method_str(v: RobustnessMethod) -> &'static str {
match v {
RobustnessMethod::Bisquare => "bisquare",
RobustnessMethod::Huber => "huber",
RobustnessMethod::Talwar => "talwar",
}
}
pub fn scaling_method_str(v: ScalingMethod) -> &'static str {
match v {
ScalingMethod::MAD => "mad",
ScalingMethod::MAR => "mar",
ScalingMethod::Mean => "mean",
}
}
pub fn zero_weight_fallback_str(v: ZeroWeightFallback) -> &'static str {
match v {
ZeroWeightFallback::UseLocalMean => "use_local_mean",
ZeroWeightFallback::ReturnOriginal => "return_original",
ZeroWeightFallback::ReturnNone => "return_none",
}
}
pub fn boundary_policy_str(v: BoundaryPolicy) -> &'static str {
match v {
BoundaryPolicy::Extend => "extend",
BoundaryPolicy::Reflect => "reflect",
BoundaryPolicy::Zero => "zero",
BoundaryPolicy::NoBoundary => "noboundary",
}
}
pub fn polynomial_degree_str(v: PolynomialDegree) -> &'static str {
match v {
PolynomialDegree::Constant => "constant",
PolynomialDegree::Linear => "linear",
PolynomialDegree::Quadratic => "quadratic",
PolynomialDegree::Cubic => "cubic",
PolynomialDegree::Quartic => "quartic",
}
}
pub fn surface_mode_str(v: SurfaceMode) -> &'static str {
match v {
SurfaceMode::Interpolation => "interpolation",
SurfaceMode::Direct => "direct",
}
}
pub fn update_mode_str(v: UpdateMode) -> &'static str {
match v {
UpdateMode::Full => "full",
UpdateMode::Incremental => "incremental",
}
}
pub fn merge_strategy_str(v: MergeStrategy) -> &'static str {
match v {
MergeStrategy::Average => "average",
MergeStrategy::WeightedAverage => "weighted_average",
MergeStrategy::TakeFirst => "take_first",
MergeStrategy::TakeLast => "take_last",
}
}
}