1#[cfg(not(feature = "std"))]
9use alloc::vec::Vec;
10#[cfg(feature = "std")]
11use std::vec::Vec;
12
13use num_traits::Float;
15
16use crate::adapters::batch::BatchLowessBuilder;
18use crate::adapters::online::OnlineLowessBuilder;
19use crate::adapters::streaming::StreamingLowessBuilder;
20use crate::engine::executor::{CVPassFn, IntervalPassFn, SmoothPassFn};
21use crate::evaluation::cv::{CVConfig, CVKind};
22use crate::evaluation::intervals::IntervalMethod;
23use crate::primitives::backend::Backend;
24
25pub use crate::adapters::online::UpdateMode;
27pub use crate::adapters::streaming::MergeStrategy;
28pub use crate::algorithms::regression::ZeroWeightFallback;
29pub use crate::algorithms::robustness::RobustnessMethod;
30pub use crate::engine::output::LowessResult;
31pub use crate::evaluation::cv::{KFold, LOOCV};
32pub use crate::math::boundary::BoundaryPolicy;
33pub use crate::math::kernel::WeightFunction;
34pub use crate::math::scaling::ScalingMethod;
35pub use crate::primitives::errors::LowessError;
36
37#[allow(non_snake_case)]
39pub mod Adapter {
40 pub use super::{Batch, Online, Streaming};
41}
42
43#[derive(Debug, Clone)]
45pub struct LowessBuilder<T> {
46 pub fraction: Option<T>,
48
49 pub iterations: Option<usize>,
51
52 pub delta: Option<T>,
54
55 pub weight_function: Option<WeightFunction>,
57
58 pub robustness_method: Option<RobustnessMethod>,
60
61 pub scaling_method: Option<ScalingMethod>,
63
64 pub interval_type: Option<IntervalMethod<T>>,
66
67 pub cv_fractions: Option<Vec<T>>,
69
70 pub(crate) cv_kind: Option<CVKind>,
72
73 pub(crate) cv_seed: Option<u64>,
75
76 pub auto_convergence: Option<T>,
78
79 pub return_diagnostics: Option<bool>,
81
82 pub compute_residuals: Option<bool>,
84
85 pub return_robustness_weights: Option<bool>,
87
88 pub boundary_policy: Option<BoundaryPolicy>,
90
91 pub zero_weight_fallback: Option<ZeroWeightFallback>,
93
94 pub merge_strategy: Option<MergeStrategy>,
96
97 pub update_mode: Option<UpdateMode>,
99
100 pub chunk_size: Option<usize>,
102
103 pub overlap: Option<usize>,
105
106 pub window_capacity: Option<usize>,
108
109 pub min_points: Option<usize>,
111
112 #[doc(hidden)]
117 pub custom_smooth_pass: Option<SmoothPassFn<T>>,
118
119 #[doc(hidden)]
121 pub custom_cv_pass: Option<CVPassFn<T>>,
122
123 #[doc(hidden)]
125 pub custom_interval_pass: Option<IntervalPassFn<T>>,
126
127 #[doc(hidden)]
129 pub backend: Option<Backend>,
130
131 #[doc(hidden)]
133 pub parallel: Option<bool>,
134
135 #[doc(hidden)]
137 pub duplicate_param: Option<&'static str>,
138}
139
140impl<T: Float> Default for LowessBuilder<T> {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl<T: Float> LowessBuilder<T> {
147 pub fn adapter<A>(self, _adapter: A) -> A::Output
149 where
150 A: LowessAdapter<T>,
151 {
152 A::convert(self)
153 }
154
155 pub fn new() -> Self {
157 Self {
158 fraction: None,
159 iterations: None,
160 delta: None,
161 weight_function: None,
162 robustness_method: None,
163 scaling_method: None,
164 interval_type: None,
165 cv_fractions: None,
166 cv_kind: None,
167 cv_seed: None,
168 auto_convergence: None,
169 return_diagnostics: None,
170 compute_residuals: None,
171 return_robustness_weights: None,
172 boundary_policy: None,
173 zero_weight_fallback: None,
174 merge_strategy: None,
175 update_mode: None,
176 chunk_size: None,
177 overlap: None,
178 window_capacity: None,
179 min_points: None,
180 custom_smooth_pass: None,
181 custom_cv_pass: None,
182 custom_interval_pass: None,
183 backend: None,
184 parallel: None,
185 duplicate_param: None,
186 }
187 }
188
189 pub fn zero_weight_fallback(mut self, policy: ZeroWeightFallback) -> Self {
191 if self.zero_weight_fallback.is_some() {
192 self.duplicate_param = Some("zero_weight_fallback");
193 }
194 self.zero_weight_fallback = Some(policy);
195 self
196 }
197
198 pub fn boundary_policy(mut self, policy: BoundaryPolicy) -> Self {
200 if self.boundary_policy.is_some() {
201 self.duplicate_param = Some("boundary_policy");
202 }
203 self.boundary_policy = Some(policy);
204 self
205 }
206
207 pub fn merge_strategy(mut self, strategy: MergeStrategy) -> Self {
209 if self.merge_strategy.is_some() {
210 self.duplicate_param = Some("merge_strategy");
211 }
212 self.merge_strategy = Some(strategy);
213 self
214 }
215
216 pub fn update_mode(mut self, mode: UpdateMode) -> Self {
218 if self.update_mode.is_some() {
219 self.duplicate_param = Some("update_mode");
220 }
221 self.update_mode = Some(mode);
222 self
223 }
224
225 pub fn chunk_size(mut self, size: usize) -> Self {
227 if self.chunk_size.is_some() {
228 self.duplicate_param = Some("chunk_size");
229 }
230 self.chunk_size = Some(size);
231 self
232 }
233
234 pub fn overlap(mut self, overlap: usize) -> Self {
236 if self.overlap.is_some() {
237 self.duplicate_param = Some("overlap");
238 }
239 self.overlap = Some(overlap);
240 self
241 }
242
243 pub fn window_capacity(mut self, capacity: usize) -> Self {
245 if self.window_capacity.is_some() {
246 self.duplicate_param = Some("window_capacity");
247 }
248 self.window_capacity = Some(capacity);
249 self
250 }
251
252 pub fn min_points(mut self, points: usize) -> Self {
254 if self.min_points.is_some() {
255 self.duplicate_param = Some("min_points");
256 }
257 self.min_points = Some(points);
258 self
259 }
260
261 pub fn fraction(mut self, fraction: T) -> Self {
263 if self.fraction.is_some() {
264 self.duplicate_param = Some("fraction");
265 }
266 self.fraction = Some(fraction);
267 self
268 }
269
270 pub fn iterations(mut self, iterations: usize) -> Self {
272 if self.iterations.is_some() {
273 self.duplicate_param = Some("iterations");
274 }
275 self.iterations = Some(iterations);
276 self
277 }
278
279 pub fn delta(mut self, delta: T) -> Self {
281 if self.delta.is_some() {
282 self.duplicate_param = Some("delta");
283 }
284 self.delta = Some(delta);
285 self
286 }
287
288 pub fn weight_function(mut self, wf: WeightFunction) -> Self {
290 if self.weight_function.is_some() {
291 self.duplicate_param = Some("weight_function");
292 }
293 self.weight_function = Some(wf);
294 self
295 }
296
297 pub fn robustness_method(mut self, rm: RobustnessMethod) -> Self {
299 if self.robustness_method.is_some() {
300 self.duplicate_param = Some("robustness_method");
301 }
302 self.robustness_method = Some(rm);
303 self
304 }
305
306 pub fn scaling_method(mut self, sm: ScalingMethod) -> Self {
308 if self.scaling_method.is_some() {
309 self.duplicate_param = Some("scaling_method");
310 }
311 self.scaling_method = Some(sm);
312 self
313 }
314
315 pub fn return_se(mut self) -> Self {
317 if self.interval_type.is_none() {
318 self.interval_type = Some(IntervalMethod::se());
319 }
320 self
321 }
322
323 pub fn confidence_intervals(mut self, level: T) -> Self {
325 if self.interval_type.as_ref().is_some_and(|it| it.confidence) {
326 self.duplicate_param = Some("confidence_intervals");
327 }
328 self.interval_type = Some(match self.interval_type {
329 Some(existing) if existing.prediction => IntervalMethod {
330 level,
331 confidence: true,
332 prediction: true,
333 se: true,
334 },
335 _ => IntervalMethod::confidence(level),
336 });
337 self
338 }
339
340 pub fn prediction_intervals(mut self, level: T) -> Self {
342 if self.interval_type.as_ref().is_some_and(|it| it.prediction) {
343 self.duplicate_param = Some("prediction_intervals");
344 }
345 self.interval_type = Some(match self.interval_type {
346 Some(existing) if existing.confidence => IntervalMethod {
347 level,
348 confidence: true,
349 prediction: true,
350 se: true,
351 },
352 _ => IntervalMethod::prediction(level),
353 });
354 self
355 }
356
357 pub fn cross_validate(mut self, config: CVConfig<'_, T>) -> Self {
359 if self.cv_fractions.is_some() {
360 self.duplicate_param = Some("cross_validate");
361 }
362 self.cv_fractions = Some(config.fractions().to_vec());
363 self.cv_kind = Some(config.kind());
364 self.cv_seed = config.get_seed();
365 self
366 }
367
368 pub fn auto_converge(mut self, tolerance: T) -> Self {
370 if self.auto_convergence.is_some() {
371 self.duplicate_param = Some("auto_converge");
372 }
373 self.auto_convergence = Some(tolerance);
374 self
375 }
376
377 pub fn return_diagnostics(mut self) -> Self {
379 self.return_diagnostics = Some(true);
380 self
381 }
382
383 pub fn return_residuals(mut self) -> Self {
385 self.compute_residuals = Some(true);
386 self
387 }
388
389 pub fn return_robustness_weights(mut self) -> Self {
391 self.return_robustness_weights = Some(true);
392 self
393 }
394
395 #[doc(hidden)]
401 pub fn custom_smooth_pass(mut self, pass: SmoothPassFn<T>) -> Self {
402 self.custom_smooth_pass = Some(pass);
403 self
404 }
405
406 #[doc(hidden)]
408 pub fn custom_cv_pass(mut self, pass: CVPassFn<T>) -> Self {
409 self.custom_cv_pass = Some(pass);
410 self
411 }
412
413 #[doc(hidden)]
415 pub fn custom_interval_pass(mut self, pass: IntervalPassFn<T>) -> Self {
416 self.custom_interval_pass = Some(pass);
417 self
418 }
419
420 #[doc(hidden)]
422 pub fn backend(mut self, backend: Backend) -> Self {
423 self.backend = Some(backend);
424 self
425 }
426
427 #[doc(hidden)]
429 pub fn parallel(mut self, parallel: bool) -> Self {
430 self.parallel = Some(parallel);
431 self
432 }
433}
434
435pub trait LowessAdapter<T: Float> {
437 type Output;
439
440 fn convert(builder: LowessBuilder<T>) -> Self::Output;
442}
443
444#[derive(Debug, Clone, Copy)]
446pub struct Batch;
447
448impl<T: Float> LowessAdapter<T> for Batch {
449 type Output = BatchLowessBuilder<T>;
450
451 fn convert(builder: LowessBuilder<T>) -> Self::Output {
452 let mut result = BatchLowessBuilder::default();
453
454 if let Some(fraction) = builder.fraction {
455 result.fraction = fraction;
456 }
457 if let Some(iterations) = builder.iterations {
458 result.iterations = iterations;
459 }
460 if let Some(delta) = builder.delta {
461 result.delta = Some(delta);
462 }
463 if let Some(wf) = builder.weight_function {
464 result.weight_function = wf;
465 }
466 if let Some(rm) = builder.robustness_method {
467 result.robustness_method = rm;
468 }
469 if let Some(it) = builder.interval_type {
470 result.interval_type = Some(it);
471 }
472 if let Some(cvf) = builder.cv_fractions {
473 result.cv_fractions = Some(cvf);
474 }
475 if let Some(cvk) = builder.cv_kind {
476 result.cv_kind = Some(cvk);
477 }
478 result.cv_seed = builder.cv_seed;
479 if let Some(ac) = builder.auto_convergence {
480 result.auto_convergence = Some(ac);
481 }
482 if let Some(zwf) = builder.zero_weight_fallback {
483 result.zero_weight_fallback = zwf;
484 }
485 if let Some(bp) = builder.boundary_policy {
486 result.boundary_policy = bp;
487 }
488 if let Some(sm) = builder.scaling_method {
489 result.scaling_method = sm;
490 }
491
492 if let Some(rw) = builder.return_robustness_weights {
493 result.return_robustness_weights = rw;
494 }
495 if let Some(rd) = builder.return_diagnostics {
496 result.return_diagnostics = rd;
497 }
498 if let Some(cr) = builder.compute_residuals {
499 result.compute_residuals = cr;
500 }
501
502 if let Some(sp) = builder.custom_smooth_pass {
506 result.custom_smooth_pass = Some(sp);
507 }
508 if let Some(cp) = builder.custom_cv_pass {
509 result.custom_cv_pass = Some(cp);
510 }
511 if let Some(ip) = builder.custom_interval_pass {
512 result.custom_interval_pass = Some(ip);
513 }
514 if let Some(b) = builder.backend {
515 result.backend = Some(b);
516 }
517 if let Some(p) = builder.parallel {
518 result.parallel = Some(p);
519 }
520
521 result.duplicate_param = builder.duplicate_param;
522
523 result
524 }
525}
526
527#[derive(Debug, Clone, Copy)]
529pub struct Streaming;
530
531impl<T: Float> LowessAdapter<T> for Streaming {
532 type Output = StreamingLowessBuilder<T>;
533
534 fn convert(builder: LowessBuilder<T>) -> Self::Output {
535 let mut result = StreamingLowessBuilder::default();
536
537 if let Some(chunk_size) = builder.chunk_size {
539 result.chunk_size = chunk_size;
540 }
541 if let Some(overlap) = builder.overlap {
542 result.overlap = overlap;
543 }
544 if let Some(fraction) = builder.fraction {
545 result.fraction = fraction;
546 }
547 if let Some(iterations) = builder.iterations {
548 result.iterations = iterations;
549 }
550 if let Some(delta) = builder.delta {
551 result.delta = delta;
552 }
553 if let Some(wf) = builder.weight_function {
554 result.weight_function = wf;
555 }
556 if let Some(bp) = builder.boundary_policy {
557 result.boundary_policy = bp;
558 }
559 if let Some(rm) = builder.robustness_method {
560 result.robustness_method = rm;
561 }
562 if let Some(zwf) = builder.zero_weight_fallback {
563 result.zero_weight_fallback = zwf;
564 }
565 if let Some(ms) = builder.merge_strategy {
566 result.merge_strategy = ms;
567 }
568 if let Some(sm) = builder.scaling_method {
569 result.scaling_method = sm;
570 }
571
572 if let Some(rw) = builder.return_robustness_weights {
573 result.return_robustness_weights = rw;
574 }
575 if let Some(rd) = builder.return_diagnostics {
576 result.return_diagnostics = rd;
577 }
578 if let Some(cr) = builder.compute_residuals {
579 result.compute_residuals = cr;
580 }
581 if let Some(ac) = builder.auto_convergence {
582 result.auto_convergence = Some(ac);
583 }
584
585 if let Some(sp) = builder.custom_smooth_pass {
590 result.custom_smooth_pass = Some(sp);
591 }
592 if let Some(cp) = builder.custom_cv_pass {
593 result.custom_cv_pass = Some(cp);
594 }
595 if let Some(ip) = builder.custom_interval_pass {
596 result.custom_interval_pass = Some(ip);
597 }
598 if let Some(b) = builder.backend {
599 result.backend = Some(b);
600 }
601 if let Some(p) = builder.parallel {
602 result.parallel = Some(p);
603 }
604 result.duplicate_param = builder.duplicate_param;
605
606 result
607 }
608}
609
610#[derive(Debug, Clone, Copy)]
612pub struct Online;
613
614impl<T: Float> LowessAdapter<T> for Online {
615 type Output = OnlineLowessBuilder<T>;
616
617 fn convert(builder: LowessBuilder<T>) -> Self::Output {
618 let mut result = OnlineLowessBuilder::default();
619
620 if let Some(window_capacity) = builder.window_capacity {
622 result.window_capacity = window_capacity;
623 }
624 if let Some(min_points) = builder.min_points {
625 result.min_points = min_points;
626 }
627 if let Some(fraction) = builder.fraction {
628 result.fraction = fraction;
629 }
630 if let Some(iterations) = builder.iterations {
631 result.iterations = iterations;
632 }
633 if let Some(delta) = builder.delta {
634 result.delta = delta;
635 }
636 if let Some(wf) = builder.weight_function {
637 result.weight_function = wf;
638 }
639 if let Some(um) = builder.update_mode {
640 result.update_mode = um;
641 }
642 if let Some(rm) = builder.robustness_method {
643 result.robustness_method = rm;
644 }
645 if let Some(bp) = builder.boundary_policy {
646 result.boundary_policy = bp;
647 }
648 if let Some(zwf) = builder.zero_weight_fallback {
649 result.zero_weight_fallback = zwf;
650 }
651 if let Some(sm) = builder.scaling_method {
652 result.scaling_method = sm;
653 }
654
655 if let Some(cr) = builder.compute_residuals {
656 result.compute_residuals = cr;
657 }
658 if let Some(rw) = builder.return_robustness_weights {
659 result.return_robustness_weights = rw;
660 }
661 if let Some(ac) = builder.auto_convergence {
662 result.auto_convergence = Some(ac);
663 }
664
665 if let Some(sp) = builder.custom_smooth_pass {
670 result.custom_smooth_pass = Some(sp);
671 }
672 if let Some(cp) = builder.custom_cv_pass {
673 result.custom_cv_pass = Some(cp);
674 }
675 if let Some(ip) = builder.custom_interval_pass {
676 result.custom_interval_pass = Some(ip);
677 }
678 if let Some(b) = builder.backend {
679 result.backend = Some(b);
680 }
681 if let Some(p) = builder.parallel {
682 result.parallel = Some(p);
683 }
684 result.duplicate_param = builder.duplicate_param;
685
686 result
687 }
688}