fastlowess-wasm 1.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  <a href="https://anaconda.org/conda-forge/r-rfastlowess"><img src="https://img.shields.io/badge/rfastlowess_(R)-44A833?logo=anaconda&logoColor=white" alt="rfastlowess (R)"></a>
17
17
  <br>
18
18
  <a href="https://github.com/thisisamirv/lowess-project/actions/workflows/ci.yml"><img src="https://github.com/thisisamirv/lowess-project/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
19
+ <a href="https://github.com/ropensci/software-review/issues/769"><img src="https://badges.ropensci.org/769_status.svg" alt="Status at rOpenSci Software Peer Review"></a>
19
20
  </p>
20
21
 
21
22
  <p align="center">
@@ -45,7 +46,11 @@ The fastest, most robust, and most feature-complete language-agnostic LOWESS (Lo
45
46
 
46
47
  > [!NOTE]
47
48
  >
48
- > Currently available for R, Python, Rust, Julia, Node.js, WebAssembly, and C++. See [INSTALLATION.md](https://github.com/thisisamirv/lowess-project/blob/main/INSTALLATION.md) for detailed installation instructions.
49
+ > Currently available for R, Python, Rust, Julia, Node.js, WebAssembly, and C++. See the [Installation Guide](https://lowess.readthedocs.io/getting-started/installation/) for detailed installation instructions.
50
+
51
+ ### GPU Backend
52
+
53
+ In addition to `parallel = true` (multi-core CPU), the batch `Lowess` class in every binding — except WebAssembly — as well as the `fastLowess` Rust crate itself, can run on the GPU via `wgpu` (Vulkan/Metal/DX12). It's opt-in and worth enabling for high-throughput processing of large datasets (roughly 10k+ points); for smaller inputs the CPU backend is typically faster. `StreamingLowess`/`OnlineLowess` remain CPU-only. See the [GPU Backend guide](https://lowess.readthedocs.io/en/latest/user-guide/gpu-backend/) for installation instructions and usage.
49
54
 
50
55
  ## Documentation
51
56
 
@@ -57,12 +62,12 @@ The fastest, most robust, and most feature-complete language-agnostic LOWESS (Lo
57
62
 
58
63
  ## LOESS vs. LOWESS
59
64
 
60
- | Feature | LOESS (This Crate) | LOWESS |
61
- |-----------------------|-----------------------------------|--------------------------------|
62
- | **Polynomial Degree** | Linear, Quadratic, Cubic, Quartic | Linear (Degree 1) |
63
- | **Dimensions** | Multivariate (n-D support) | Univariate (1-D only) |
64
- | **Flexibility** | High (Distance metrics) | Standard |
65
- | **Complexity** | Higher (Matrix inversion) | Lower (Weighted average/slope) |
65
+ | Feature | LOESS | LOWESS (This Crate) |
66
+ | --- | --- | --- |
67
+ | **Polynomial Degree** | Linear, Quadratic, Cubic, Quartic | Linear (Degree 1) |
68
+ | **Dimensions** | Multivariate (n-D support) | Univariate (1-D only) |
69
+ | **Flexibility** | High (Distance metrics) | Standard |
70
+ | **Complexity** | Higher (Matrix inversion) | Lower (Weighted average/slope) |
66
71
 
67
72
  > [!TIP]
68
73
  > **Note:** For a **LOESS** implementation, use [`loess-project`](https://github.com/thisisamirv/loess-project).
@@ -75,7 +80,7 @@ The fastest, most robust, and most feature-complete language-agnostic LOWESS (Lo
75
80
 
76
81
  The `lowess` project beats the competition in terms of speed, whether in single-threaded or multi-threaded parallel execution. It is on average **200-327x faster** than Python's `statsmodels.lowess` and **2-3x faster** than R's `lowess`.
77
82
 
78
- For more details on the performance comparison, see the [BENCHMARKS](https://github.com/thisisamirv/lowess-project/blob/main/BENCHMARKS.md) file.
83
+ For more details on the performance comparison, see the [Benchmarks](https://lowess.readthedocs.io/benchmarks/) page.
79
84
 
80
85
  ### Robustness
81
86
 
@@ -117,46 +122,47 @@ This crate applies a range of different *boundary policies* at dataset edges:
117
122
 
118
123
  A variety of features, supporting a range of use cases:
119
124
 
120
- | Feature | This package | statsmodels | R (stats) |
121
- |----------------------|:-------------:|:------------:|:------------:|
122
- | Kernel | 7 options | only Tricube | only Tricube |
123
- | Robustness Weighting | 3 options | only Huber | only Huber |
124
- | Scale Estimation | 2 options | only MAR | only MAR |
125
- | Boundary Padding | 4 options | no padding | no padding |
126
- | Zero Weight Fallback | 3 options | no | no |
127
- | Auto Convergence | yes | no | no |
128
- | Online Mode | yes | no | no |
129
- | Streaming Mode | yes | no | no |
130
- | Confidence Intervals | yes | no | no |
131
- | Prediction Intervals | yes | no | no |
132
- | Cross-Validation | 2 options | no | no |
133
- | Parallel Execution | yes | no | no |
134
- | GPU Acceleration | yes* | no | no |
135
- | `no-std` Support | yes | no | no |
136
-
137
- \* GPU acceleration is currently in beta and may not be available on all platforms.
125
+ | Feature | This package | statsmodels | R (stats) |
126
+ | --- | :---: | :---: | :---: |
127
+ | Kernel | 7 options | only Tricube | only Tricube |
128
+ | Robustness Weighting | 3 options | only Huber | only Huber |
129
+ | Scale Estimation | 2 options | only MAR | only MAR |
130
+ | Boundary Padding | 4 options | no padding | no padding |
131
+ | Zero Weight Fallback | 3 options | no | no |
132
+ | Auto Convergence | yes | no | no |
133
+ | Online Mode | yes | no | no |
134
+ | Streaming Mode | yes | no | no |
135
+ | Confidence Intervals | yes | no | no |
136
+ | Prediction Intervals | yes | no | no |
137
+ | Cross-Validation | 2 options | no | no |
138
+ | Parallel Execution | yes | no | no |
139
+ | GPU Acceleration | yes | no | no |
140
+ | `no-std` Support | yes | no | no |
138
141
 
139
142
  ## Validation
140
143
 
141
144
  All implementations are **numerical twins** of R's `lowess`:
142
145
 
143
- | Aspect | Status | Details |
144
- |-----------------|----------------|-----------------------------------------------|
145
- | **Accuracy** | ✅ EXACT MATCH | Max diff < 1e-12 across all scenarios |
146
- | **Consistency** | ✅ PERFECT | Multiple scenarios pass with strict tolerance |
147
- | **Robustness** | ✅ VERIFIED | Robust smoothing matches R exactly |
146
+ | Aspect | Status | Details |
147
+ | --- | --- | --- |
148
+ | **Accuracy** | ✅ EXACT MATCH | Max diff < 1e-12 across all scenarios |
149
+ | **Consistency** | ✅ PERFECT | Multiple scenarios pass with strict tolerance |
150
+ | **Robustness** | ✅ VERIFIED | Robust smoothing matches R exactly |
148
151
 
149
152
  ## API Reference
150
153
 
151
154
  **R:**
152
155
 
153
156
  ```r
154
- Lowess(
157
+ library(rfastlowess)
158
+
159
+ model <- Lowess(
155
160
  fraction = 0.5,
156
161
  iterations = 3L,
157
162
  delta = 0.01,
158
163
  weight_function = "tricube",
159
164
  robustness_method = "bisquare",
165
+ scaling_method = "mad",
160
166
  zero_weight_fallback = "use_local_mean",
161
167
  boundary_policy = "extend",
162
168
  confidence_intervals = 0.95,
@@ -164,12 +170,16 @@ Lowess(
164
170
  return_diagnostics = TRUE,
165
171
  return_residuals = TRUE,
166
172
  return_robustness_weights = TRUE,
173
+ return_se = TRUE,
167
174
  cv_fractions = c(0.3, 0.5, 0.7),
168
175
  cv_method = "kfold",
169
176
  cv_k = 5L,
177
+ cv_seed = 123L,
170
178
  auto_converge = 1e-4,
171
179
  parallel = TRUE
172
- )$fit(x, y)
180
+ )
181
+ custom_weights <- rep(1, length(x))
182
+ result <- fit(model, x, y, custom_weights = custom_weights)
173
183
 
174
184
  # Result structure:
175
185
  result$x,
@@ -198,6 +208,7 @@ model = Lowess(
198
208
  delta=0.01,
199
209
  weight_function="tricube",
200
210
  robustness_method="bisquare",
211
+ scaling_method="mad",
201
212
  zero_weight_fallback="use_local_mean",
202
213
  boundary_policy="extend",
203
214
  confidence_intervals=0.95,
@@ -205,13 +216,16 @@ model = Lowess(
205
216
  return_diagnostics=True,
206
217
  return_residuals=True,
207
218
  return_robustness_weights=True,
219
+ return_se=True,
208
220
  cv_fractions=[0.3, 0.5, 0.7],
209
221
  cv_method="kfold",
210
222
  cv_k=5,
223
+ cv_seed=123,
211
224
  auto_converge=1e-4,
212
225
  parallel=True
213
226
  )
214
- result = model.fit(x, y)
227
+ custom_weights = [1.0] * len(x)
228
+ result = model.fit(x, y, custom_weights=custom_weights)
215
229
 
216
230
  # Result structure:
217
231
  result.x,
@@ -232,27 +246,32 @@ result.cv_scores
232
246
  **Rust:**
233
247
 
234
248
  ```rust
235
- Lowess::new()
249
+ use lowess::prelude::*;
250
+
251
+ let model = Lowess::new()
236
252
  .fraction(0.5)
237
253
  .iterations(3)
238
254
  .delta(0.01)
239
- .weight_function(Tricube)
240
- .robustness_method(Bisquare)
241
- .zero_weight_fallback(UseLocalMean)
242
- .boundary_policy(Extend)
255
+ .weight_function("tricube")
256
+ .robustness_method("bisquare")
257
+ .scaling_method("mad")
258
+ .zero_weight_fallback("use_local_mean")
259
+ .boundary_policy("extend")
260
+ .return_se()
243
261
  .confidence_intervals(0.95)
244
262
  .prediction_intervals(0.95)
245
263
  .return_diagnostics()
246
264
  .return_residuals()
247
265
  .return_robustness_weights()
248
- .cross_validate(KFold(5, &[0.3, 0.5, 0.7]).seed(123))
266
+ .cv_method("kfold")
267
+ .cv_k(5)
268
+ .cv_fractions(vec![0.3, 0.5, 0.7])
269
+ .cv_seed(123)
249
270
  .auto_converge(1e-4)
250
- .adapter(Batch)
251
- .parallel(true) // fastLowess only
252
- .backend(CPU) // fastLowess only: CPU or GPU
271
+ .custom_weights(vec![1.0; x.len()])
253
272
  .build()?;
254
273
 
255
- let result = model.fit(x, y);
274
+ let result = model.fit(&x, &y)?;
256
275
 
257
276
  // Result structure:
258
277
  pub struct LowessResult<T> {
@@ -275,12 +294,15 @@ pub struct LowessResult<T> {
275
294
  **Julia:**
276
295
 
277
296
  ```julia
278
- Lowess(;
297
+ using FastLOWESS
298
+
299
+ model = Lowess(;
279
300
  fraction=0.5,
280
301
  iterations=3,
281
302
  delta=NaN, # NaN for auto
282
303
  weight_function="tricube",
283
304
  robustness_method="bisquare",
305
+ scaling_method="mad",
284
306
  zero_weight_fallback="use_local_mean",
285
307
  boundary_policy="extend",
286
308
  confidence_intervals=NaN,
@@ -288,12 +310,16 @@ Lowess(;
288
310
  return_diagnostics=true,
289
311
  return_residuals=true,
290
312
  return_robustness_weights=true,
313
+ return_se=true,
291
314
  cv_fractions=Float64[], # e.g. [0.3, 0.5]
292
315
  cv_method="kfold",
293
316
  cv_k=5,
317
+ cv_seed=123,
294
318
  auto_converge=NaN,
295
319
  parallel=true
296
320
  )
321
+ custom_weights = ones(length(x))
322
+ result = fit(model, x, y; custom_weights=custom_weights)
297
323
 
298
324
  # Result structure:
299
325
  result.x,
@@ -314,90 +340,107 @@ result.cv_scores
314
340
  **Node.js:**
315
341
 
316
342
  ```javascript
317
- new Lowess({
343
+ import { Lowess } from "fastlowess"
344
+
345
+ const model = new Lowess({
318
346
  fraction: 0.5,
319
347
  iterations: 3,
320
348
  delta: 0.01,
321
- weightFunction: "tricube",
322
- robustnessMethod: "bisquare",
323
- zeroWeightFallback: "use_local_mean",
324
- boundaryPolicy: "extend",
325
- confidenceIntervals: 0.95,
326
- predictionIntervals: 0.95,
327
- returnDiagnostics: true,
328
- returnResiduals: true,
329
- returnRobustnessWeights: true,
330
- cvFractions: [0.3, 0.5, 0.7],
331
- cvMethod: "kfold",
332
- cvK: 5,
333
- autoConverge: 1e-4,
349
+ weight_function: "tricube",
350
+ robustness_method: "bisquare",
351
+ scaling_method: "mad",
352
+ zero_weight_fallback: "use_local_mean",
353
+ boundary_policy: "extend",
354
+ return_se: true,
355
+ confidence_intervals: 0.95,
356
+ prediction_intervals: 0.95,
357
+ return_diagnostics: true,
358
+ return_residuals: true,
359
+ return_robustness_weights: true,
360
+ cv_fractions: [0.3, 0.5, 0.7],
361
+ cv_method: "kfold",
362
+ cv_k: 5,
363
+ cv_seed: 123,
364
+ auto_converge: 1e-4,
334
365
  parallel: true
335
- }).fit(x, y)
366
+ })
367
+ const custom_weights = Array(x.length).fill(1.0)
368
+ const result = model.fit(x, y, custom_weights)
336
369
 
337
370
  // Result structure:
338
371
  result.x,
339
372
  result.y,
340
- result.standardErrors,
341
- result.confidenceLower,
342
- result.confidenceUpper,
343
- result.predictionLower,
344
- result.predictionUpper,
373
+ result.standard_errors,
374
+ result.confidence_lower,
375
+ result.confidence_upper,
376
+ result.prediction_lower,
377
+ result.prediction_upper,
345
378
  result.residuals,
346
- result.robustnessWeights,
379
+ result.robustness_weights,
347
380
  result.diagnostics,
348
- result.iterationsUsed,
349
- result.fractionUsed,
350
- result.cvScores
381
+ result.iterations_used,
382
+ result.fraction_used,
383
+ result.cv_scores
351
384
  ```
352
385
 
353
386
  **WebAssembly:**
354
387
 
355
388
  ```javascript
356
- smooth(x, y, {
389
+ import { Lowess } from "fastlowess-wasm"
390
+
391
+ const model = new Lowess({
357
392
  fraction: 0.5,
358
393
  iterations: 3,
359
394
  delta: 0.01,
360
- weightFunction: "tricube",
361
- robustnessMethod: "bisquare",
362
- zeroWeightFallback: "use_local_mean",
363
- boundaryPolicy: "extend",
364
- confidenceIntervals: 0.95,
365
- predictionIntervals: 0.95,
366
- returnDiagnostics: true,
367
- returnResiduals: true,
368
- returnRobustnessWeights: true,
369
- cvFractions: [0.3, 0.5, 0.7],
370
- cvMethod: "kfold",
371
- cvK: 5,
372
- autoConverge: 1e-4,
395
+ weight_function: "tricube",
396
+ robustness_method: "bisquare",
397
+ scaling_method: "mad",
398
+ zero_weight_fallback: "use_local_mean",
399
+ boundary_policy: "extend",
400
+ return_se: true,
401
+ confidence_intervals: 0.95,
402
+ prediction_intervals: 0.95,
403
+ return_diagnostics: true,
404
+ return_residuals: true,
405
+ return_robustness_weights: true,
406
+ cv_fractions: [0.3, 0.5, 0.7],
407
+ cv_method: "kfold",
408
+ cv_k: 5,
409
+ cv_seed: 123,
410
+ auto_converge: 1e-4,
373
411
  parallel: true
374
412
  })
413
+ const custom_weights = new Float64Array(x.length).fill(1)
414
+ const result = model.fit(x, y, custom_weights)
375
415
 
376
416
  // Result structure:
377
417
  result.x,
378
418
  result.y,
379
- result.standardErrors,
380
- result.confidenceLower,
381
- result.confidenceUpper,
382
- result.predictionLower,
383
- result.predictionUpper,
419
+ result.standard_errors,
420
+ result.confidence_lower,
421
+ result.confidence_upper,
422
+ result.prediction_lower,
423
+ result.prediction_upper,
384
424
  result.residuals,
385
- result.robustnessWeights,
425
+ result.robustness_weights,
386
426
  result.diagnostics,
387
- result.iterationsUsed,
388
- result.fractionUsed,
389
- result.cvScores
427
+ result.iterations_used,
428
+ result.fraction_used,
429
+ result.cv_scores
390
430
  ```
391
431
 
392
432
  **C++:**
393
433
 
394
434
  ```cpp
435
+ #include "fastlowess.hpp"
436
+
395
437
  fastlowess::LowessOptions options;
396
438
  options.fraction = 0.5;
397
439
  options.iterations = 3;
398
440
  options.delta = 0.01;
399
441
  options.weight_function = "tricube";
400
442
  options.robustness_method = "bisquare";
443
+ options.scaling_method = "mad";
401
444
  options.zero_weight_fallback = "use_local_mean";
402
445
  options.boundary_policy = "extend";
403
446
  options.confidence_intervals = 0.95;
@@ -405,28 +448,31 @@ options.prediction_intervals = 0.95;
405
448
  options.return_diagnostics = true;
406
449
  options.return_residuals = true;
407
450
  options.return_robustness_weights = true;
451
+ options.return_se = true;
408
452
  options.cv_fractions = {0.3, 0.5, 0.7};
409
453
  options.cv_method = "kfold";
410
454
  options.cv_k = 5;
455
+ options.cv_seed = 123;
411
456
  options.auto_converge = 1e-4;
412
457
  options.parallel = true;
413
458
 
414
459
  fastlowess::Lowess model(options);
415
- auto result = model.fit(x, y);
460
+ std::vector<double> custom_weights(x.size(), 1.0);
461
+ const auto result = model.fit(x, y, custom_weights).value();
416
462
 
417
463
  // Result structure:
418
- result.xVector(),
419
- result.yVector(),
420
- result.standardErrors(),
421
- result.confidenceLower(),
422
- result.confidenceUpper(),
423
- result.predictionLower(),
424
- result.predictionUpper(),
464
+ result.x_vector(),
465
+ result.y_vector(),
466
+ result.standard_errors(),
467
+ result.confidence_lower(),
468
+ result.confidence_upper(),
469
+ result.prediction_lower(),
470
+ result.prediction_upper(),
425
471
  result.residuals(),
426
- result.robustnessWeights(),
472
+ result.robustness_weights(),
427
473
  result.diagnostics(),
428
- result.iterationsUsed(),
429
- result.fractionUsed(),
474
+ result.iterations_used(),
475
+ result.fraction_used(),
430
476
  result.cv_scores()
431
477
  ```
432
478
 
@@ -434,7 +480,11 @@ result.cv_scores()
434
480
 
435
481
  ## Contributing
436
482
 
437
- Contributions are welcome! Please see the [CONTRIBUTING.md](https://github.com/thisisamirv/lowess-project/blob/main/CONTRIBUTING.md) file for more information.
483
+ Contributions are welcome! Please see the [Contributing Guide](https://lowess.readthedocs.io/en/latest/contributing/) for more information.
484
+
485
+ ## Changelog
486
+
487
+ See the [Changelog](https://lwoess.readthedocs.io/en/latest/changelog/) for a history of changes.
438
488
 
439
489
  ## License
440
490