Skip to content

Commit a4e099b

Browse files
committed
fix: kfold cv bug, auto_converge naming consistency, online adapter auto_converge support
1 parent 40baff3 commit a4e099b

9 files changed

Lines changed: 52 additions & 44 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Changed
1515

1616
- Reduced figures size significantly.
17+
- Implement naming consistency for `auto_converge` (removed `auto_convergence`).
1718

1819
### Fixed
1920

2021
- Fixed `boundary_degree_fallback` pass to online and streaming adapters.
2122
- Fixed `boundary_degree_fallback` pass to `custom_vertex_pass` and `VertexPassFn`.
23+
- Fixed KFold CV bug through adding explicit sorting of training subsets and using robust binary-search interpolation for each test point.
24+
- Fixed `auto_converge` support for Online adapter.
2225

2326
## [0.2.0]
2427

src/adapters/batch.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ pub struct BatchLoessBuilder<T: FloatLinalg + DistanceLinalg + SolverLinalg> {
9797
pub deferred_error: Option<LoessError>,
9898

9999
/// Tolerance for auto-convergence
100-
pub auto_convergence: Option<T>,
100+
pub auto_converge: Option<T>,
101101

102102
/// Whether to compute diagnostic statistics
103103
pub return_diagnostics: bool,
@@ -199,7 +199,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg> Batch
199199
cv_kind: None,
200200
cv_seed: None,
201201
deferred_error: None,
202-
auto_convergence: None,
202+
auto_converge: None,
203203
return_diagnostics: false,
204204
compute_residuals: false,
205205
return_robustness_weights: false,
@@ -319,7 +319,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg> Batch
319319

320320
/// Enable auto-convergence for robustness iterations.
321321
pub fn auto_converge(mut self, tolerance: T) -> Self {
322-
self.auto_convergence = Some(tolerance);
322+
self.auto_converge = Some(tolerance);
323323
self
324324
}
325325

@@ -466,7 +466,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg> Batch
466466
}
467467

468468
// Validate auto convergence tolerance
469-
if let Some(tol) = self.auto_convergence {
469+
if let Some(tol) = self.auto_converge {
470470
Validator::validate_tolerance(tol)?;
471471
}
472472

@@ -519,7 +519,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
519519
scaling_method: self.config.scaling_method,
520520
cv_fractions: self.config.cv_fractions,
521521
cv_kind: self.config.cv_kind,
522-
auto_convergence: self.config.auto_convergence,
522+
auto_converge: self.config.auto_converge,
523523
return_variance: self.config.interval_type,
524524
boundary_policy: self.config.boundary_policy,
525525
polynomial_degree: self.config.polynomial_degree,

src/adapters/online.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub struct OnlineLoessBuilder<T: FloatLinalg + DistanceLinalg + SolverLinalg> {
9191
pub iterations: usize,
9292

9393
/// Convergence tolerance for early stopping (None = disabled)
94-
pub auto_convergence: Option<T>,
94+
pub auto_converge: Option<T>,
9595

9696
/// Kernel weight function
9797
pub weight_function: WeightFunction,
@@ -205,7 +205,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg> Onlin
205205
boundary_policy: BoundaryPolicy::default(),
206206
compute_residuals: false,
207207
return_robustness_weights: false,
208-
auto_convergence: None,
208+
auto_converge: None,
209209
deferred_error: None,
210210
polynomial_degree: PolynomialDegree::default(),
211211
dimensions: 1,
@@ -319,7 +319,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg> Onlin
319319

320320
/// Enable auto-convergence for robustness iterations.
321321
pub fn auto_converge(mut self, tolerance: T) -> Self {
322-
self.auto_convergence = Some(tolerance);
322+
self.auto_converge = Some(tolerance);
323323
self
324324
}
325325

@@ -455,6 +455,9 @@ pub struct OnlineOutput<T> {
455455

456456
/// Robustness weight for the latest point (if computed)
457457
pub robustness_weight: Option<T>,
458+
459+
/// Number of robustness iterations actually performed
460+
pub iterations_used: Option<usize>,
458461
}
459462

460463
// ============================================================================
@@ -541,13 +544,14 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
541544
std_error: None,
542545
residual: Some(residual),
543546
robustness_weight: Some(T::one()),
547+
iterations_used: Some(0),
544548
}));
545549
}
546550

547551
// Smooth using LOESS for windows of size >= 3
548552

549553
// Choose update strategy based on configuration
550-
let (smoothed, std_err, rob_weight) = match self.config.update_mode {
554+
let (smoothed, std_err, rob_weight, iterations) = match self.config.update_mode {
551555
UpdateMode::Incremental => {
552556
// Incremental mode: single-pass fit (no robustness) for maximum performance.
553557
let n = x_vec.len() / self.config.dimensions;
@@ -572,13 +576,13 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
572576
iterations: 0, // No robustness for incremental mode (speed)
573577
weight_function: self.config.weight_function,
574578
robustness_method: self.config.robustness_method,
575-
scaling_method: self.config.scaling_method, // ADDED
579+
scaling_method: self.config.scaling_method,
576580
zero_weight_fallback: self.config.zero_weight_fallback,
577581
boundary_policy: self.config.boundary_policy,
578582
polynomial_degree: self.config.polynomial_degree,
579583
dimensions: self.config.dimensions,
580584
distance_metric: self.config.distance_metric.clone(),
581-
auto_convergence: None,
585+
auto_converge: None,
582586
cv_fractions: None,
583587
cv_kind: None,
584588
return_variance: None,
@@ -605,7 +609,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
605609
LoessError::InvalidNumericValue("No smoothed output produced".into())
606610
})?;
607611

608-
(smoothed_val, None, Some(T::one()))
612+
(smoothed_val, None, Some(T::one()), result.iterations)
609613
}
610614
UpdateMode::Full => {
611615
// Validate grid resolution
@@ -632,13 +636,13 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
632636
iterations: self.config.iterations,
633637
weight_function: self.config.weight_function,
634638
robustness_method: self.config.robustness_method,
635-
scaling_method: self.config.scaling_method, // ADDED
639+
scaling_method: self.config.scaling_method,
636640
zero_weight_fallback: self.config.zero_weight_fallback,
637641
boundary_policy: self.config.boundary_policy,
638642
polynomial_degree: self.config.polynomial_degree,
639643
dimensions: self.config.dimensions,
640644
distance_metric: self.config.distance_metric.clone(),
641-
auto_convergence: self.config.auto_convergence,
645+
auto_converge: self.config.auto_converge,
642646
cv_fractions: None,
643647
cv_kind: None,
644648
return_variance: None,
@@ -674,7 +678,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
674678
None
675679
};
676680

677-
(smoothed_val, std_err, rob_weight)
681+
(smoothed_val, std_err, rob_weight, result.iterations)
678682
}
679683
};
680684

@@ -685,6 +689,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
685689
std_error: std_err,
686690
residual: Some(residual),
687691
robustness_weight: rob_weight,
692+
iterations_used: iterations,
688693
}))
689694
}
690695

src/adapters/streaming.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ pub struct StreamingLoessBuilder<T: FloatLinalg + DistanceLinalg + SolverLinalg>
102102
pub iterations: usize,
103103

104104
/// Convergence tolerance for early stopping (None = disabled)
105-
pub auto_convergence: Option<T>,
105+
pub auto_converge: Option<T>,
106106

107107
/// Kernel weight function
108108
pub weight_function: WeightFunction,
@@ -223,7 +223,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg>
223223
compute_residuals: false,
224224
return_diagnostics: false,
225225
return_robustness_weights: false,
226-
auto_convergence: None,
226+
auto_converge: None,
227227
deferred_error: None,
228228
polynomial_degree: PolynomialDegree::default(),
229229
dimensions: 1,
@@ -337,7 +337,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg>
337337

338338
/// Enable auto-convergence for robustness iterations.
339339
pub fn auto_converge(mut self, tolerance: T) -> Self {
340-
self.auto_convergence = Some(tolerance);
340+
self.auto_converge = Some(tolerance);
341341
self
342342
}
343343

@@ -536,7 +536,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
536536
distance_metric: self.config.distance_metric.clone(),
537537
cv_fractions: None,
538538
cv_kind: None,
539-
auto_convergence: self.config.auto_convergence,
539+
auto_converge: self.config.auto_converge,
540540
return_variance: None,
541541
cv_seed: None,
542542
surface_mode: self.config.surface_mode,

src/api.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ pub struct LoessBuilder<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug +
9797
pub(crate) cv_seed: Option<u64>,
9898

9999
/// Relative convergence tolerance.
100-
pub auto_convergence: Option<T>,
100+
pub auto_converge: Option<T>,
101101

102102
/// Enable performance/statistical diagnostics.
103103
pub return_diagnostics: Option<bool>,
@@ -214,7 +214,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
214214
cv_fractions: None,
215215
cv_kind: None,
216216
cv_seed: None,
217-
auto_convergence: None,
217+
auto_converge: None,
218218
return_diagnostics: None,
219219
compute_residuals: None,
220220
return_robustness_weights: None,
@@ -414,10 +414,10 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
414414

415415
/// Enable automatic convergence detection based on relative change.
416416
pub fn auto_converge(mut self, tolerance: T) -> Self {
417-
if self.auto_convergence.is_some() {
417+
if self.auto_converge.is_some() {
418418
self.duplicate_param = Some("auto_converge");
419419
}
420-
self.auto_convergence = Some(tolerance);
420+
self.auto_converge = Some(tolerance);
421421
self
422422
}
423423

@@ -598,8 +598,8 @@ impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync> Loess
598598
result.cv_kind = Some(cvk);
599599
}
600600
result.cv_seed = builder.cv_seed;
601-
if let Some(ac) = builder.auto_convergence {
602-
result.auto_convergence = Some(ac);
601+
if let Some(ac) = builder.auto_converge {
602+
result.auto_converge = Some(ac);
603603
}
604604
if let Some(zwf) = builder.zero_weight_fallback {
605605
result.zero_weight_fallback = zwf;
@@ -717,8 +717,8 @@ impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync> Loess
717717
if let Some(cr) = builder.compute_residuals {
718718
result.compute_residuals = cr;
719719
}
720-
if let Some(ac) = builder.auto_convergence {
721-
result.auto_convergence = Some(ac);
720+
if let Some(ac) = builder.auto_converge {
721+
result.auto_converge = Some(ac);
722722
}
723723
if let Some(pd) = builder.polynomial_degree {
724724
result.polynomial_degree = pd;
@@ -817,8 +817,8 @@ impl<T: FloatLinalg + DistanceLinalg + SolverLinalg + Debug + Send + Sync> Loess
817817
if let Some(rw) = builder.return_robustness_weights {
818818
result.return_robustness_weights = rw;
819819
}
820-
if let Some(ac) = builder.auto_convergence {
821-
result.auto_convergence = Some(ac);
820+
if let Some(ac) = builder.auto_converge {
821+
result.auto_converge = Some(ac);
822822
}
823823
if let Some(pd) = builder.polynomial_degree {
824824
result.polynomial_degree = pd;

src/engine/executor.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ pub struct LoessConfig<T: FloatLinalg + SolverLinalg> {
298298
pub cv_seed: Option<u64>,
299299

300300
/// Convergence tolerance for early stopping of robustness iterations.
301-
pub auto_convergence: Option<T>,
301+
pub auto_converge: Option<T>,
302302

303303
/// Configuration for standard errors and intervals.
304304
pub return_variance: Option<IntervalMethod<T>>,
@@ -380,7 +380,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + SolverLinalg> Defau
380380
cv_fractions: None,
381381
cv_kind: None,
382382
cv_seed: None,
383-
auto_convergence: None,
383+
auto_converge: None,
384384
return_variance: None,
385385
boundary_policy: BoundaryPolicy::default(),
386386
polynomial_degree: PolynomialDegree::default(),
@@ -805,7 +805,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
805805
y,
806806
Some(best_frac),
807807
Some(config.iterations),
808-
config.auto_convergence,
808+
config.auto_converge,
809809
config.return_variance.as_ref(),
810810
Some(&mut workspace),
811811
);
@@ -819,7 +819,7 @@ impl<T: FloatLinalg + DistanceLinalg + Debug + Send + Sync + 'static + SolverLin
819819
y,
820820
config.fraction,
821821
Some(config.iterations),
822-
config.auto_convergence,
822+
config.auto_converge,
823823
config.return_variance.as_ref(),
824824
Some(&mut workspace),
825825
)

src/evaluation/cv.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,6 @@ impl CVKind {
397397

398398
let fold_size = n / k;
399399
let mut cv_scores = vec![T::zero(); fractions.len()];
400-
401-
// Generate indices and optionally shuffle if seed is provided
402400
let mut indices: Vec<usize> = (0..n).collect();
403401
if let Some(s) = seed {
404402
let mut rng = SimpleRng::new(s);
@@ -463,8 +461,10 @@ impl CVKind {
463461
let (sorted_tx, sorted_ty): (Vec<T>, Vec<T>) = train_data.into_iter().unzip();
464462

465463
let train_smooth = smoother(&sorted_tx, &sorted_ty, frac);
466-
let mut preds = vec![T::zero(); tex.len() / dims];
467-
Self::interpolate_prediction_batch(&sorted_tx, &train_smooth, tex, &mut preds);
464+
let mut preds = Vec::with_capacity(tex.len() / dims);
465+
for &xi in tex.iter() {
466+
preds.push(Self::interpolate_prediction(&sorted_tx, &train_smooth, xi));
467+
}
468468
preds
469469
};
470470

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@
280280
//! | **zero_weight_fallback** | `UseLocalMean` | 3 fallback options | Behavior when all weights are zero | All |
281281
//! | **return_residuals** | false | true/false | Include residuals in output | All |
282282
//! | **boundary_policy** | `Extend` | 4 policy options | Edge handling strategy (reduces boundary bias) | All |
283-
//! | **boundary_degree_fallback** | true | true/false | Use linear fit at boundaries | All |
284-
//! | **auto_convergence** | None | Tolerance value | Early stopping for robustness | All |
283+
//! | **boundary_degree_fallback** | true | true/false | Use linear fit at boundaries | All |
284+
//! | **auto_converge** | None | Tolerance value | Early stopping for robustness | All |
285285
//! | **return_robustness_weights** | false | true/false | Include final weights in output | All |
286286
//! | **degree** | `Linear` | 0, 1, 2, 3, 4 | Polynomial degree (constant to quartic) | All |
287287
//! | **dimensions** | 1 | [1, ∞) | Number of predictor dimensions | All |

tests/engine_executor_tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ fn test_config_defaults() {
111111
);
112112
assert!(config.cv_kind.is_none(), "Default CV kind should be None");
113113
assert!(
114-
config.auto_convergence.is_none(),
114+
config.auto_converge.is_none(),
115115
"Default auto-convergence should be None"
116116
);
117117
assert!(
@@ -135,7 +135,7 @@ fn test_config_custom() {
135135
scaling_method: ScalingMethod::default(),
136136
cv_fractions: Some(vec![0.3, 0.5, 0.7]),
137137
cv_kind: None,
138-
auto_convergence: Some(1e-6),
138+
auto_converge: Some(1e-6),
139139
return_variance: None,
140140
boundary_policy: BoundaryPolicy::default(),
141141
custom_smooth_pass: None,
@@ -165,7 +165,7 @@ fn test_config_custom() {
165165
);
166166
assert_eq!(config.robustness_method, RobustnessMethod::Huber);
167167
assert_eq!(config.cv_fractions, Some(vec![0.3, 0.5, 0.7]));
168-
assert_eq!(config.auto_convergence, Some(1e-6));
168+
assert_eq!(config.auto_converge, Some(1e-6));
169169
}
170170

171171
// ============================================================================
@@ -340,7 +340,7 @@ fn test_config_f32() {
340340
scaling_method: ScalingMethod::default(),
341341
cv_fractions: None,
342342
cv_kind: None,
343-
auto_convergence: None,
343+
auto_converge: None,
344344
return_variance: None,
345345
boundary_policy: BoundaryPolicy::default(),
346346
custom_smooth_pass: None,
@@ -404,7 +404,7 @@ fn test_executor_convergence_zero_tolerance() {
404404
scaling_method: ScalingMethod::default(),
405405
cv_fractions: None,
406406
cv_kind: None,
407-
auto_convergence: Some(0.0), // Zero tolerance
407+
auto_converge: Some(0.0), // Zero tolerance
408408
return_variance: None,
409409
boundary_policy: BoundaryPolicy::Extend,
410410
custom_smooth_pass: None,

0 commit comments

Comments
 (0)