1#[cfg(feature = "python")]
2use numpy::{IntoPyArray, PyArray1};
3#[cfg(feature = "python")]
4use pyo3::exceptions::PyValueError;
5#[cfg(feature = "python")]
6use pyo3::prelude::*;
7#[cfg(feature = "python")]
8use pyo3::types::PyDict;
9
10#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
11use serde::{Deserialize, Serialize};
12#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
13use wasm_bindgen::prelude::*;
14
15use crate::utilities::data_loader::{source_type, Candles};
16use crate::utilities::enums::Kernel;
17use crate::utilities::helpers::{
18 alloc_with_nan_prefix, detect_best_batch_kernel, detect_best_kernel, init_matrix_prefixes,
19 make_uninit_matrix,
20};
21#[cfg(feature = "python")]
22use crate::utilities::kernel_validation::validate_kernel;
23use aligned_vec::{AVec, CACHELINE_ALIGN};
24#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
25use core::arch::x86_64::*;
26#[cfg(not(target_arch = "wasm32"))]
27use rayon::prelude::*;
28use std::cmp::Ordering;
29use std::cmp::Reverse;
30use std::collections::BinaryHeap;
31use thiserror::Error;
32
33impl<'a> AsRef<[f64]> for RviInput<'a> {
34 #[inline(always)]
35 fn as_ref(&self) -> &[f64] {
36 match &self.data {
37 RviData::Slice(slice) => slice,
38 RviData::Candles { candles, source } => source_type(candles, source),
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
44pub enum RviData<'a> {
45 Candles {
46 candles: &'a Candles,
47 source: &'a str,
48 },
49 Slice(&'a [f64]),
50}
51
52#[derive(Debug, Clone)]
53pub struct RviOutput {
54 pub values: Vec<f64>,
55}
56
57#[derive(Debug, Clone)]
58#[cfg_attr(
59 all(target_arch = "wasm32", feature = "wasm"),
60 derive(Serialize, Deserialize)
61)]
62pub struct RviParams {
63 pub period: Option<usize>,
64 pub ma_len: Option<usize>,
65 pub matype: Option<usize>,
66 pub devtype: Option<usize>,
67}
68
69impl Default for RviParams {
70 fn default() -> Self {
71 Self {
72 period: Some(10),
73 ma_len: Some(14),
74 matype: Some(1),
75 devtype: Some(0),
76 }
77 }
78}
79
80#[derive(Debug, Clone)]
81pub struct RviInput<'a> {
82 pub data: RviData<'a>,
83 pub params: RviParams,
84}
85
86impl<'a> RviInput<'a> {
87 #[inline]
88 pub fn from_candles(c: &'a Candles, s: &'a str, p: RviParams) -> Self {
89 Self {
90 data: RviData::Candles {
91 candles: c,
92 source: s,
93 },
94 params: p,
95 }
96 }
97 #[inline]
98 pub fn from_slice(sl: &'a [f64], p: RviParams) -> Self {
99 Self {
100 data: RviData::Slice(sl),
101 params: p,
102 }
103 }
104 #[inline]
105 pub fn with_default_candles(c: &'a Candles) -> Self {
106 Self::from_candles(c, "close", RviParams::default())
107 }
108 #[inline]
109 pub fn get_period(&self) -> usize {
110 self.params.period.unwrap_or(10)
111 }
112 #[inline]
113 pub fn get_ma_len(&self) -> usize {
114 self.params.ma_len.unwrap_or(14)
115 }
116 #[inline]
117 pub fn get_matype(&self) -> usize {
118 self.params.matype.unwrap_or(1)
119 }
120 #[inline]
121 pub fn get_devtype(&self) -> usize {
122 self.params.devtype.unwrap_or(0)
123 }
124}
125
126#[derive(Copy, Clone, Debug)]
127pub struct RviBuilder {
128 period: Option<usize>,
129 ma_len: Option<usize>,
130 matype: Option<usize>,
131 devtype: Option<usize>,
132 kernel: Kernel,
133}
134
135impl Default for RviBuilder {
136 fn default() -> Self {
137 Self {
138 period: None,
139 ma_len: None,
140 matype: None,
141 devtype: None,
142 kernel: Kernel::Auto,
143 }
144 }
145}
146
147impl RviBuilder {
148 #[inline(always)]
149 pub fn new() -> Self {
150 Self::default()
151 }
152 #[inline(always)]
153 pub fn period(mut self, n: usize) -> Self {
154 self.period = Some(n);
155 self
156 }
157 #[inline(always)]
158 pub fn ma_len(mut self, n: usize) -> Self {
159 self.ma_len = Some(n);
160 self
161 }
162 #[inline(always)]
163 pub fn matype(mut self, n: usize) -> Self {
164 self.matype = Some(n);
165 self
166 }
167 #[inline(always)]
168 pub fn devtype(mut self, n: usize) -> Self {
169 self.devtype = Some(n);
170 self
171 }
172 #[inline(always)]
173 pub fn kernel(mut self, k: Kernel) -> Self {
174 self.kernel = k;
175 self
176 }
177 #[inline(always)]
178 pub fn apply(self, c: &Candles) -> Result<RviOutput, RviError> {
179 let p = RviParams {
180 period: self.period,
181 ma_len: self.ma_len,
182 matype: self.matype,
183 devtype: self.devtype,
184 };
185 let i = RviInput::from_candles(c, "close", p);
186 rvi_with_kernel(&i, self.kernel)
187 }
188 #[inline(always)]
189 pub fn apply_slice(self, d: &[f64]) -> Result<RviOutput, RviError> {
190 let p = RviParams {
191 period: self.period,
192 ma_len: self.ma_len,
193 matype: self.matype,
194 devtype: self.devtype,
195 };
196 let i = RviInput::from_slice(d, p);
197 rvi_with_kernel(&i, self.kernel)
198 }
199 #[inline(always)]
200 pub fn into_stream(self) -> Result<RviStream, RviError> {
201 let p = RviParams {
202 period: self.period,
203 ma_len: self.ma_len,
204 matype: self.matype,
205 devtype: self.devtype,
206 };
207 RviStream::try_new(p)
208 }
209}
210
211#[derive(Debug, Error)]
212pub enum RviError {
213 #[error("rvi: Empty data provided.")]
214 EmptyInputData,
215 #[error(
216 "rvi: Invalid period or ma_len: period = {period}, ma_len = {ma_len}, data length = {data_len}"
217 )]
218 InvalidPeriod {
219 period: usize,
220 ma_len: usize,
221 data_len: usize,
222 },
223 #[error("rvi: Not enough valid data: needed = {needed}, valid = {valid}")]
224 NotEnoughValidData { needed: usize, valid: usize },
225 #[error("rvi: All values are NaN.")]
226 AllValuesNaN,
227 #[error("rvi: Output slice length {got} != data length {expected}.")]
228 OutputLengthMismatch { expected: usize, got: usize },
229 #[error("rvi: invalid range: start = {start}, end = {end}, step = {step}")]
230 InvalidRange { start: i128, end: i128, step: i128 },
231 #[error("rvi: invalid kernel for batch: {0:?}")]
232 InvalidKernelForBatch(Kernel),
233 #[error("rvi: invalid input: {0}")]
234 InvalidInput(String),
235}
236
237#[inline]
238pub fn rvi(input: &RviInput) -> Result<RviOutput, RviError> {
239 rvi_with_kernel(input, Kernel::Auto)
240}
241
242pub fn rvi_with_kernel(input: &RviInput, kernel: Kernel) -> Result<RviOutput, RviError> {
243 let data: &[f64] = input.as_ref();
244 if data.is_empty() {
245 return Err(RviError::EmptyInputData);
246 }
247
248 let first = data
249 .iter()
250 .position(|x| !x.is_nan())
251 .ok_or(RviError::AllValuesNaN)?;
252
253 let period = input.get_period();
254 let ma_len = input.get_ma_len();
255 let matype = input.get_matype();
256 let devtype = input.get_devtype();
257 if period == 0 || ma_len == 0 || period > data.len() || ma_len > data.len() {
258 return Err(RviError::InvalidPeriod {
259 period,
260 ma_len,
261 data_len: data.len(),
262 });
263 }
264 let max_needed = period.saturating_sub(1) + ma_len.saturating_sub(1);
265 if (data.len() - first) <= max_needed {
266 return Err(RviError::NotEnoughValidData {
267 needed: max_needed + 1,
268 valid: data.len() - first,
269 });
270 }
271 let warmup_period = first + period.saturating_sub(1) + ma_len.saturating_sub(1);
272 let mut out = alloc_with_nan_prefix(data.len(), warmup_period);
273 let chosen = match kernel {
274 Kernel::Auto => Kernel::Scalar,
275 other => other,
276 };
277 unsafe {
278 match chosen {
279 Kernel::Scalar | Kernel::ScalarBatch => {
280 rvi_scalar(data, period, ma_len, matype, devtype, first, &mut out)
281 }
282 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
283 Kernel::Avx2 | Kernel::Avx2Batch => {
284 rvi_avx2(data, period, ma_len, matype, devtype, first, &mut out)
285 }
286 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
287 Kernel::Avx512 | Kernel::Avx512Batch => {
288 rvi_avx512(data, period, ma_len, matype, devtype, first, &mut out)
289 }
290 _ => unreachable!(),
291 }
292 }
293 Ok(RviOutput { values: out })
294}
295
296#[inline]
297pub fn rvi_into_slice(dst: &mut [f64], input: &RviInput, kern: Kernel) -> Result<(), RviError> {
298 let data: &[f64] = input.as_ref();
299 if data.is_empty() {
300 return Err(RviError::EmptyInputData);
301 }
302
303 let first = data
304 .iter()
305 .position(|x| !x.is_nan())
306 .ok_or(RviError::AllValuesNaN)?;
307
308 let period = input.get_period();
309 let ma_len = input.get_ma_len();
310 let matype = input.get_matype();
311 let devtype = input.get_devtype();
312
313 if period == 0 || ma_len == 0 {
314 return Err(RviError::InvalidPeriod {
315 period,
316 ma_len,
317 data_len: data.len(),
318 });
319 }
320
321 if dst.len() != data.len() {
322 return Err(RviError::OutputLengthMismatch {
323 expected: data.len(),
324 got: dst.len(),
325 });
326 }
327
328 let max_needed = period.saturating_sub(1) + ma_len.saturating_sub(1);
329 let valid_len = data.len() - first;
330
331 if period > data.len() || ma_len > data.len() {
332 return Err(RviError::InvalidPeriod {
333 period,
334 ma_len,
335 data_len: data.len(),
336 });
337 }
338
339 if valid_len <= max_needed {
340 return Err(RviError::NotEnoughValidData {
341 needed: max_needed + 1,
342 valid: valid_len,
343 });
344 }
345
346 let warmup_period = first + period.saturating_sub(1) + ma_len.saturating_sub(1);
347
348 for v in &mut dst[..warmup_period] {
349 *v = f64::NAN;
350 }
351
352 let chosen = match kern {
353 Kernel::Auto => Kernel::Scalar,
354 other => other,
355 };
356
357 unsafe {
358 match chosen {
359 Kernel::Scalar | Kernel::ScalarBatch => {
360 rvi_scalar(data, period, ma_len, matype, devtype, first, dst)
361 }
362 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
363 Kernel::Avx2 | Kernel::Avx2Batch => {
364 rvi_avx2(data, period, ma_len, matype, devtype, first, dst)
365 }
366 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
367 Kernel::Avx512 | Kernel::Avx512Batch => {
368 rvi_avx512(data, period, ma_len, matype, devtype, first, dst)
369 }
370 _ => unreachable!(),
371 }
372 }
373
374 Ok(())
375}
376
377#[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
378#[inline]
379pub fn rvi_into(input: &RviInput, out: &mut [f64]) -> Result<(), RviError> {
380 rvi_into_slice(out, input, Kernel::Auto)
381}
382
383#[inline]
384pub fn rvi_scalar(
385 data: &[f64],
386 period: usize,
387 ma_len: usize,
388 matype: usize,
389 devtype: usize,
390 first: usize,
391 out: &mut [f64],
392) {
393 debug_assert_eq!(out.len(), data.len());
394 let n = data.len();
395 if n == 0 {
396 return;
397 }
398
399 let warmup = first + period.saturating_sub(1) + ma_len.saturating_sub(1);
400
401 let inv_p = 1.0f64 / period as f64;
402 let inv_m = 1.0f64 / ma_len as f64;
403
404 let mut sum = 0.0f64;
405 let mut sumsq = 0.0f64;
406
407 let mut ring = vec![f64::NAN; period];
408 let mut r_head = 0usize;
409 let mut r_filled = false;
410
411 let mut scratch = if devtype == 2 {
412 vec![0.0f64; period]
413 } else {
414 Vec::new()
415 };
416
417 let use_sma = matype == 0;
418
419 let mut up_sum = 0.0f64;
420 let mut dn_sum = 0.0f64;
421 let mut up_ring = if use_sma {
422 vec![0.0f64; ma_len]
423 } else {
424 Vec::new()
425 };
426 let mut dn_ring = if use_sma {
427 vec![0.0f64; ma_len]
428 } else {
429 Vec::new()
430 };
431 let mut up_h = 0usize;
432 let mut dn_h = 0usize;
433 let mut up_cnt = 0usize;
434 let mut dn_cnt = 0usize;
435
436 let alpha = if !use_sma {
437 2.0 / (ma_len as f64 + 1.0)
438 } else {
439 0.0
440 };
441 let one_m_alpha = 1.0 - alpha;
442 let mut up_prev = 0.0f64;
443 let mut dn_prev = 0.0f64;
444 let mut up_started = false;
445 let mut dn_started = false;
446 let mut up_seed_sum = 0.0f64;
447 let mut dn_seed_sum = 0.0f64;
448 let mut up_seed_cnt = 0usize;
449 let mut dn_seed_cnt = 0usize;
450
451 let mut prev = data[0];
452
453 for i in 0..period.min(n) {
454 let x = data[i];
455 if x.is_nan() {
456 sum = f64::NAN;
457 sumsq = f64::NAN;
458 break;
459 }
460 sum += x;
461 sumsq += x * x;
462 if devtype != 0 {
463 ring[i] = x;
464 if i + 1 == period {
465 r_filled = true;
466 r_head = 0;
467 }
468 }
469 }
470
471 if devtype == 0 {
472 let mut prev = data[0];
473 if use_sma {
474 for i in 0..n {
475 let x = data[i];
476 let d = if i == 0 || x.is_nan() || prev.is_nan() {
477 f64::NAN
478 } else {
479 x - prev
480 };
481 prev = x;
482
483 let dev = if i + 1 < period {
484 f64::NAN
485 } else if i == period - 1 {
486 if sum.is_nan() {
487 f64::NAN
488 } else {
489 let mean = sum * inv_p;
490 let mean_sq = sumsq * inv_p;
491 (mean_sq - mean * mean).sqrt()
492 }
493 } else {
494 let leaving = data[i - period];
495 if leaving.is_nan() || x.is_nan() || sum.is_nan() || sumsq.is_nan() {
496 sum = 0.0;
497 sumsq = 0.0;
498 let start = i + 1 - period;
499 let mut bad = false;
500 for k in start..=i {
501 let v = data[k];
502 if v.is_nan() {
503 bad = true;
504 break;
505 }
506 sum += v;
507 sumsq += v * v;
508 }
509 if bad {
510 sum = f64::NAN;
511 sumsq = f64::NAN;
512 f64::NAN
513 } else {
514 let mean = sum * inv_p;
515 let mean_sq = sumsq * inv_p;
516 (mean_sq - mean * mean).sqrt()
517 }
518 } else {
519 sum += x - leaving;
520 sumsq += x * x - leaving * leaving;
521 let mean = sum * inv_p;
522 let mean_sq = sumsq * inv_p;
523 (mean_sq - mean * mean).sqrt()
524 }
525 };
526
527 let (up_i, dn_i) = if d.is_nan() || dev.is_nan() {
528 (f64::NAN, f64::NAN)
529 } else if d > 0.0 {
530 (dev, 0.0)
531 } else if d < 0.0 {
532 (0.0, dev)
533 } else {
534 (0.0, 0.0)
535 };
536
537 let up_s = if up_i.is_nan() {
538 up_sum = 0.0;
539 up_cnt = 0;
540 up_h = 0;
541 f64::NAN
542 } else if up_cnt < ma_len {
543 unsafe {
544 *up_ring.get_unchecked_mut(up_h) = up_i;
545 }
546 up_sum += up_i;
547 up_h = (up_h + 1) % ma_len;
548 up_cnt += 1;
549 if up_cnt == ma_len {
550 up_sum * inv_m
551 } else {
552 f64::NAN
553 }
554 } else {
555 let old = unsafe { *up_ring.get_unchecked(up_h) };
556 unsafe {
557 *up_ring.get_unchecked_mut(up_h) = up_i;
558 }
559 up_h = (up_h + 1) % ma_len;
560 up_sum += up_i - old;
561 up_sum * inv_m
562 };
563
564 let dn_s = if dn_i.is_nan() {
565 dn_sum = 0.0;
566 dn_cnt = 0;
567 dn_h = 0;
568 f64::NAN
569 } else if dn_cnt < ma_len {
570 unsafe {
571 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
572 }
573 dn_sum += dn_i;
574 dn_h = (dn_h + 1) % ma_len;
575 dn_cnt += 1;
576 if dn_cnt == ma_len {
577 dn_sum * inv_m
578 } else {
579 f64::NAN
580 }
581 } else {
582 let old = unsafe { *dn_ring.get_unchecked(dn_h) };
583 unsafe {
584 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
585 }
586 dn_h = (dn_h + 1) % ma_len;
587 dn_sum += dn_i - old;
588 dn_sum * inv_m
589 };
590
591 if i >= warmup {
592 if up_s.is_nan() || dn_s.is_nan() {
593 out[i] = f64::NAN;
594 } else {
595 let denom = up_s + dn_s;
596 out[i] = if denom.abs() < f64::EPSILON {
597 f64::NAN
598 } else {
599 100.0 * (up_s / denom)
600 };
601 }
602 }
603 }
604 } else {
605 for i in 0..n {
606 let x = data[i];
607 let d = if i == 0 || x.is_nan() || prev.is_nan() {
608 f64::NAN
609 } else {
610 x - prev
611 };
612 prev = x;
613
614 let dev = if i + 1 < period {
615 f64::NAN
616 } else if i == period - 1 {
617 if sum.is_nan() {
618 f64::NAN
619 } else {
620 let mean = sum * inv_p;
621 let mean_sq = sumsq * inv_p;
622 (mean_sq - mean * mean).sqrt()
623 }
624 } else {
625 let leaving = data[i - period];
626 if leaving.is_nan() || x.is_nan() || sum.is_nan() || sumsq.is_nan() {
627 sum = 0.0;
628 sumsq = 0.0;
629 let start = i + 1 - period;
630 let mut bad = false;
631 for k in start..=i {
632 let v = data[k];
633 if v.is_nan() {
634 bad = true;
635 break;
636 }
637 sum += v;
638 sumsq += v * v;
639 }
640 if bad {
641 sum = f64::NAN;
642 sumsq = f64::NAN;
643 f64::NAN
644 } else {
645 let mean = sum * inv_p;
646 let mean_sq = sumsq * inv_p;
647 (mean_sq - mean * mean).sqrt()
648 }
649 } else {
650 sum += x - leaving;
651 sumsq += x * x - leaving * leaving;
652 let mean = sum * inv_p;
653 let mean_sq = sumsq * inv_p;
654 (mean_sq - mean * mean).sqrt()
655 }
656 };
657
658 let (up_i, dn_i) = if d.is_nan() || dev.is_nan() {
659 (f64::NAN, f64::NAN)
660 } else if d > 0.0 {
661 (dev, 0.0)
662 } else if d < 0.0 {
663 (0.0, dev)
664 } else {
665 (0.0, 0.0)
666 };
667
668 let up_s = if up_i.is_nan() {
669 up_started = false;
670 up_seed_sum = 0.0;
671 up_seed_cnt = 0;
672 f64::NAN
673 } else if !up_started {
674 up_seed_sum += up_i;
675 up_seed_cnt += 1;
676 if up_seed_cnt == ma_len {
677 up_prev = up_seed_sum * inv_m;
678 up_started = true;
679 up_prev
680 } else {
681 f64::NAN
682 }
683 } else {
684 up_prev = alpha.mul_add(up_i, one_m_alpha * up_prev);
685 up_prev
686 };
687
688 let dn_s = if dn_i.is_nan() {
689 dn_started = false;
690 dn_seed_sum = 0.0;
691 dn_seed_cnt = 0;
692 f64::NAN
693 } else if !dn_started {
694 dn_seed_sum += dn_i;
695 dn_seed_cnt += 1;
696 if dn_seed_cnt == ma_len {
697 dn_prev = dn_seed_sum * inv_m;
698 dn_started = true;
699 dn_prev
700 } else {
701 f64::NAN
702 }
703 } else {
704 dn_prev = alpha.mul_add(dn_i, one_m_alpha * dn_prev);
705 dn_prev
706 };
707
708 if i >= warmup {
709 if up_s.is_nan() || dn_s.is_nan() {
710 out[i] = f64::NAN;
711 } else {
712 let denom = up_s + dn_s;
713 out[i] = if denom.abs() < f64::EPSILON {
714 f64::NAN
715 } else {
716 100.0 * (up_s / denom)
717 };
718 }
719 }
720 }
721 }
722 return;
723 }
724
725 for i in 0..n {
726 let x = data[i];
727
728 let d = if i == 0 || x.is_nan() || prev.is_nan() {
729 f64::NAN
730 } else {
731 x - prev
732 };
733 prev = x;
734
735 let dev = if i + 1 < period {
736 f64::NAN
737 } else {
738 match devtype {
739 0 => {
740 if i == period - 1 {
741 if sum.is_nan() {
742 f64::NAN
743 } else {
744 let mean = sum * inv_p;
745 let mean_sq = sumsq * inv_p;
746 (mean_sq - mean * mean).sqrt()
747 }
748 } else {
749 let leaving = data[i - period];
750 let incoming = x;
751 if leaving.is_nan() || incoming.is_nan() || sum.is_nan() || sumsq.is_nan() {
752 sum = 0.0;
753 sumsq = 0.0;
754 let start = i + 1 - period;
755 let mut bad = false;
756 for k in start..=i {
757 let v = data[k];
758 if v.is_nan() {
759 bad = true;
760 break;
761 }
762 sum += v;
763 sumsq += v * v;
764 }
765 if bad {
766 sum = f64::NAN;
767 sumsq = f64::NAN;
768 f64::NAN
769 } else {
770 let mean = sum * inv_p;
771 let mean_sq = sumsq * inv_p;
772 (mean_sq - mean * mean).sqrt()
773 }
774 } else {
775 sum += incoming - leaving;
776 sumsq += incoming * incoming - leaving * leaving;
777 let mean = sum * inv_p;
778 let mean_sq = sumsq * inv_p;
779 (mean_sq - mean * mean).sqrt()
780 }
781 }
782 }
783 1 => {
784 let incoming = x;
785 if i < period {
786 if !incoming.is_nan() {
787 ring[i] = incoming;
788 if i + 1 == period {
789 r_filled = true;
790 r_head = 0;
791 }
792 }
793 if i + 1 < period {
794 f64::NAN
795 } else {
796 let mut s = 0.0;
797 unsafe {
798 for k in 0..period {
799 s += *ring.get_unchecked(k);
800 }
801 }
802 let mean = s * inv_p;
803 let mut abs_sum = 0.0;
804 unsafe {
805 for k in 0..period {
806 abs_sum += (*ring.get_unchecked(k) - mean).abs();
807 }
808 }
809 abs_sum * inv_p
810 }
811 } else {
812 let leaving = data[i - period];
813 if incoming.is_nan() || leaving.is_nan() {
814 r_filled = false;
815 for j in 0..period {
816 ring[j] = f64::NAN;
817 }
818 f64::NAN
819 } else {
820 unsafe {
821 *ring.get_unchecked_mut(r_head) = incoming;
822 }
823 r_head = (r_head + 1) % period;
824 r_filled = true;
825
826 let mut s = 0.0;
827 unsafe {
828 for k in 0..period {
829 s += *ring.get_unchecked(k);
830 }
831 }
832 let mean = s * inv_p;
833 let mut abs_sum = 0.0;
834 unsafe {
835 for k in 0..period {
836 abs_sum += (*ring.get_unchecked(k) - mean).abs();
837 }
838 }
839 abs_sum * inv_p
840 }
841 }
842 }
843 _ => {
844 let incoming = x;
845 if i < period {
846 if !incoming.is_nan() {
847 ring[i] = incoming;
848 if i + 1 == period {
849 r_filled = true;
850 r_head = 0;
851 }
852 }
853 if i + 1 < period {
854 f64::NAN
855 } else {
856 unsafe {
857 for k in 0..period {
858 *scratch.get_unchecked_mut(k) = *ring.get_unchecked(k);
859 }
860 }
861 scratch.sort_by(|a, b| {
862 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
863 });
864 let median = if period & 1 == 1 {
865 scratch[period / 2]
866 } else {
867 (scratch[period / 2 - 1] + scratch[period / 2]) * 0.5
868 };
869 let mut abs_sum = 0.0;
870 unsafe {
871 for k in 0..period {
872 abs_sum += (*ring.get_unchecked(k) - median).abs();
873 }
874 }
875 abs_sum * inv_p
876 }
877 } else {
878 let leaving = data[i - period];
879 if incoming.is_nan() || leaving.is_nan() {
880 r_filled = false;
881 for j in 0..period {
882 ring[j] = f64::NAN;
883 }
884 f64::NAN
885 } else {
886 unsafe {
887 *ring.get_unchecked_mut(r_head) = incoming;
888 }
889 r_head = (r_head + 1) % period;
890 r_filled = true;
891
892 unsafe {
893 for k in 0..period {
894 *scratch.get_unchecked_mut(k) =
895 *ring.get_unchecked((r_head + k) % period);
896 }
897 }
898 scratch.sort_by(|a, b| {
899 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
900 });
901 let median = if period & 1 == 1 {
902 scratch[period / 2]
903 } else {
904 (scratch[period / 2 - 1] + scratch[period / 2]) * 0.5
905 };
906 let mut abs_sum = 0.0;
907
908 unsafe {
909 for k in 0..period {
910 abs_sum +=
911 (*ring.get_unchecked((r_head + k) % period) - median).abs();
912 }
913 }
914 abs_sum * inv_p
915 }
916 }
917 }
918 }
919 };
920
921 let (up_i, dn_i) = if d.is_nan() || dev.is_nan() {
922 (f64::NAN, f64::NAN)
923 } else if d > 0.0 {
924 (dev, 0.0)
925 } else if d < 0.0 {
926 (0.0, dev)
927 } else {
928 (0.0, 0.0)
929 };
930
931 let (up_s, dn_s) = if use_sma {
932 let up_smooth = if up_i.is_nan() {
933 up_sum = 0.0;
934 up_cnt = 0;
935 up_h = 0;
936 f64::NAN
937 } else {
938 if up_cnt < ma_len {
939 unsafe {
940 *up_ring.get_unchecked_mut(up_h) = up_i;
941 }
942 up_sum += up_i;
943 up_h = (up_h + 1) % ma_len;
944 up_cnt += 1;
945 if up_cnt == ma_len {
946 up_sum * inv_m
947 } else {
948 f64::NAN
949 }
950 } else {
951 let old = unsafe { *up_ring.get_unchecked(up_h) };
952 unsafe {
953 *up_ring.get_unchecked_mut(up_h) = up_i;
954 }
955 up_h = (up_h + 1) % ma_len;
956 up_sum += up_i - old;
957 up_sum * inv_m
958 }
959 };
960
961 let dn_smooth = if dn_i.is_nan() {
962 dn_sum = 0.0;
963 dn_cnt = 0;
964 dn_h = 0;
965 f64::NAN
966 } else {
967 if dn_cnt < ma_len {
968 unsafe {
969 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
970 }
971 dn_sum += dn_i;
972 dn_h = (dn_h + 1) % ma_len;
973 dn_cnt += 1;
974 if dn_cnt == ma_len {
975 dn_sum * inv_m
976 } else {
977 f64::NAN
978 }
979 } else {
980 let old = unsafe { *dn_ring.get_unchecked(dn_h) };
981 unsafe {
982 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
983 }
984 dn_h = (dn_h + 1) % ma_len;
985 dn_sum += dn_i - old;
986 dn_sum * inv_m
987 }
988 };
989 (up_smooth, dn_smooth)
990 } else {
991 let up_smooth = if up_i.is_nan() {
992 up_started = false;
993 up_seed_sum = 0.0;
994 up_seed_cnt = 0;
995 f64::NAN
996 } else if !up_started {
997 up_seed_sum += up_i;
998 up_seed_cnt += 1;
999 if up_seed_cnt == ma_len {
1000 up_prev = up_seed_sum * inv_m;
1001 up_started = true;
1002 up_prev
1003 } else {
1004 f64::NAN
1005 }
1006 } else {
1007 up_prev = alpha.mul_add(up_i, one_m_alpha * up_prev);
1008 up_prev
1009 };
1010 let dn_smooth = if dn_i.is_nan() {
1011 dn_started = false;
1012 dn_seed_sum = 0.0;
1013 dn_seed_cnt = 0;
1014 f64::NAN
1015 } else if !dn_started {
1016 dn_seed_sum += dn_i;
1017 dn_seed_cnt += 1;
1018 if dn_seed_cnt == ma_len {
1019 dn_prev = dn_seed_sum * inv_m;
1020 dn_started = true;
1021 dn_prev
1022 } else {
1023 f64::NAN
1024 }
1025 } else {
1026 dn_prev = alpha.mul_add(dn_i, one_m_alpha * dn_prev);
1027 dn_prev
1028 };
1029 (up_smooth, dn_smooth)
1030 };
1031
1032 if i >= warmup {
1033 if up_s.is_nan() || dn_s.is_nan() {
1034 out[i] = f64::NAN;
1035 } else {
1036 let denom = up_s + dn_s;
1037 out[i] = if denom.abs() < f64::EPSILON {
1038 f64::NAN
1039 } else {
1040 100.0 * (up_s / denom)
1041 };
1042 }
1043 }
1044 }
1045}
1046
1047#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1048#[inline]
1049pub fn rvi_avx512(
1050 data: &[f64],
1051 period: usize,
1052 ma_len: usize,
1053 matype: usize,
1054 devtype: usize,
1055 first: usize,
1056 out: &mut [f64],
1057) {
1058 if period <= 32 {
1059 unsafe { rvi_avx512_short(data, period, ma_len, matype, devtype, first, out) }
1060 } else {
1061 unsafe { rvi_avx512_long(data, period, ma_len, matype, devtype, first, out) }
1062 }
1063}
1064
1065#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1066#[inline]
1067pub unsafe fn rvi_avx2(
1068 data: &[f64],
1069 period: usize,
1070 ma_len: usize,
1071 matype: usize,
1072 devtype: usize,
1073 first: usize,
1074 out: &mut [f64],
1075) {
1076 rvi_scalar(data, period, ma_len, matype, devtype, first, out)
1077}
1078
1079#[inline]
1080pub fn rvi_scalar_opt(
1081 data: &[f64],
1082 period: usize,
1083 ma_len: usize,
1084 matype: usize,
1085 devtype: usize,
1086 first: usize,
1087 out: &mut [f64],
1088) {
1089 debug_assert_eq!(out.len(), data.len());
1090 let n = data.len();
1091 if n == 0 {
1092 return;
1093 }
1094
1095 let warmup = first + period.saturating_sub(1) + ma_len.saturating_sub(1);
1096 let inv_p = 1.0 / (period as f64);
1097 let inv_m = 1.0 / (ma_len as f64);
1098 let use_sma = matype == 0;
1099
1100 let mut up_sum = 0.0f64;
1101 let mut dn_sum = 0.0f64;
1102 let mut up_ring = if use_sma {
1103 vec![0.0f64; ma_len]
1104 } else {
1105 Vec::new()
1106 };
1107 let mut dn_ring = if use_sma {
1108 vec![0.0f64; ma_len]
1109 } else {
1110 Vec::new()
1111 };
1112 let mut up_h: usize = 0;
1113 let mut dn_h: usize = 0;
1114 let mut up_cnt: usize = 0;
1115 let mut dn_cnt: usize = 0;
1116 let alpha = if !use_sma {
1117 2.0 / (ma_len as f64 + 1.0)
1118 } else {
1119 0.0
1120 };
1121 let one_m_alpha = 1.0 - alpha;
1122 let mut up_prev = 0.0f64;
1123 let mut dn_prev = 0.0f64;
1124 let mut up_started = false;
1125 let mut dn_started = false;
1126 let mut up_seed_sum = 0.0f64;
1127 let mut dn_seed_sum = 0.0f64;
1128 let mut up_seed_cnt = 0usize;
1129 let mut dn_seed_cnt = 0usize;
1130
1131 #[inline(always)]
1132 fn bump_idx(idx: &mut usize, len: usize) {
1133 *idx += 1;
1134 if *idx == len {
1135 *idx = 0;
1136 }
1137 }
1138
1139 if devtype == 0 {
1140 let mut prev = data[0];
1141 let mut sum = 0.0f64;
1142 let mut sumsq = 0.0f64;
1143 let mut vflag = vec![0u8; period];
1144 let mut vcnt: usize = 0;
1145 let mut head: usize = 0;
1146 for i in 0..period.min(n) {
1147 let x = unsafe { *data.get_unchecked(i) };
1148 if !x.is_nan() {
1149 sum += x;
1150 sumsq += x * x;
1151 vflag[i] = 1;
1152 vcnt += 1;
1153 }
1154 }
1155 for i in 0..n {
1156 let x = unsafe { *data.get_unchecked(i) };
1157 let d = if i == 0 || x.is_nan() || prev.is_nan() {
1158 f64::NAN
1159 } else {
1160 x - prev
1161 };
1162 prev = x;
1163 let dev = if i + 1 < period {
1164 f64::NAN
1165 } else if i == period - 1 {
1166 if vcnt == period {
1167 let mean = sum * inv_p;
1168 let mean_sq = sumsq * inv_p;
1169 (mean_sq - mean * mean).sqrt()
1170 } else {
1171 f64::NAN
1172 }
1173 } else {
1174 let leaving_valid = unsafe { *vflag.get_unchecked(head) } != 0;
1175 if leaving_valid {
1176 let leaving = unsafe { *data.get_unchecked(i - period) };
1177 sum -= leaving;
1178 sumsq -= leaving * leaving;
1179 vcnt -= 1;
1180 }
1181 if !x.is_nan() {
1182 sum += x;
1183 sumsq += x * x;
1184 unsafe { *vflag.get_unchecked_mut(head) = 1 };
1185 vcnt += 1;
1186 } else {
1187 unsafe { *vflag.get_unchecked_mut(head) = 0 };
1188 }
1189 bump_idx(&mut head, period);
1190 if vcnt == period {
1191 let mean = sum * inv_p;
1192 let mean_sq = sumsq * inv_p;
1193 (mean_sq - mean * mean).sqrt()
1194 } else {
1195 f64::NAN
1196 }
1197 };
1198
1199 let (up_i, dn_i) = if d.is_nan() || dev.is_nan() {
1200 (f64::NAN, f64::NAN)
1201 } else if d > 0.0 {
1202 (dev, 0.0)
1203 } else if d < 0.0 {
1204 (0.0, dev)
1205 } else {
1206 (0.0, 0.0)
1207 };
1208 let up_s = if use_sma {
1209 if up_i.is_nan() {
1210 up_sum = 0.0;
1211 up_cnt = 0;
1212 up_h = 0;
1213 f64::NAN
1214 } else if up_cnt < ma_len {
1215 unsafe {
1216 *up_ring.get_unchecked_mut(up_h) = up_i;
1217 }
1218 up_sum += up_i;
1219 bump_idx(&mut up_h, ma_len);
1220 up_cnt += 1;
1221 if up_cnt == ma_len {
1222 up_sum * inv_m
1223 } else {
1224 f64::NAN
1225 }
1226 } else {
1227 let old = unsafe { *up_ring.get_unchecked(up_h) };
1228 unsafe {
1229 *up_ring.get_unchecked_mut(up_h) = up_i;
1230 }
1231 up_sum += up_i - old;
1232 bump_idx(&mut up_h, ma_len);
1233 up_sum * inv_m
1234 }
1235 } else {
1236 if up_i.is_nan() {
1237 up_started = false;
1238 up_seed_sum = 0.0;
1239 up_seed_cnt = 0;
1240 f64::NAN
1241 } else if !up_started {
1242 up_seed_sum += up_i;
1243 up_seed_cnt += 1;
1244 if up_seed_cnt == ma_len {
1245 up_prev = up_seed_sum * inv_m;
1246 up_started = true;
1247 up_prev
1248 } else {
1249 f64::NAN
1250 }
1251 } else {
1252 up_prev = alpha.mul_add(up_i, one_m_alpha * up_prev);
1253 up_prev
1254 }
1255 };
1256 let dn_s = if use_sma {
1257 if dn_i.is_nan() {
1258 dn_sum = 0.0;
1259 dn_cnt = 0;
1260 dn_h = 0;
1261 f64::NAN
1262 } else if dn_cnt < ma_len {
1263 unsafe {
1264 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
1265 }
1266 dn_sum += dn_i;
1267 bump_idx(&mut dn_h, ma_len);
1268 dn_cnt += 1;
1269 if dn_cnt == ma_len {
1270 dn_sum * inv_m
1271 } else {
1272 f64::NAN
1273 }
1274 } else {
1275 let old = unsafe { *dn_ring.get_unchecked(dn_h) };
1276 unsafe {
1277 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
1278 }
1279 dn_sum += dn_i - old;
1280 bump_idx(&mut dn_h, ma_len);
1281 dn_sum * inv_m
1282 }
1283 } else {
1284 if dn_i.is_nan() {
1285 dn_started = false;
1286 dn_seed_sum = 0.0;
1287 dn_seed_cnt = 0;
1288 f64::NAN
1289 } else if !dn_started {
1290 dn_seed_sum += dn_i;
1291 dn_seed_cnt += 1;
1292 if dn_seed_cnt == ma_len {
1293 dn_prev = dn_seed_sum * inv_m;
1294 dn_started = true;
1295 dn_prev
1296 } else {
1297 f64::NAN
1298 }
1299 } else {
1300 dn_prev = alpha.mul_add(dn_i, one_m_alpha * dn_prev);
1301 dn_prev
1302 }
1303 };
1304 if i >= warmup {
1305 if up_s.is_nan() || dn_s.is_nan() {
1306 out[i] = f64::NAN;
1307 } else {
1308 let denom = up_s + dn_s;
1309 out[i] = if denom.abs() < f64::EPSILON {
1310 f64::NAN
1311 } else {
1312 100.0 * (up_s / denom)
1313 };
1314 }
1315 }
1316 }
1317 return;
1318 }
1319
1320 let mut prev = data[0];
1321 let mut ring = vec![0.0f64; period];
1322 let mut head: usize = 0;
1323 let mut filled_cnt: usize = 0;
1324 let mut ring_sum = 0.0f64;
1325 let mut scratch = if devtype == 2 {
1326 vec![0.0f64; period]
1327 } else {
1328 Vec::new()
1329 };
1330
1331 #[inline(always)]
1332 fn abs_dev_mean_unrolled(r: &[f64], mean: f64) -> f64 {
1333 let mut acc = 0.0f64;
1334 let len = r.len();
1335 let mut k = 0usize;
1336 while k + 4 <= len {
1337 unsafe {
1338 let a0 = *r.get_unchecked(k) - mean;
1339 let a1 = *r.get_unchecked(k + 1) - mean;
1340 let a2 = *r.get_unchecked(k + 2) - mean;
1341 let a3 = *r.get_unchecked(k + 3) - mean;
1342 acc += a0.abs() + a1.abs() + a2.abs() + a3.abs();
1343 }
1344 k += 4;
1345 }
1346 while k < len {
1347 unsafe {
1348 acc += (*r.get_unchecked(k) - mean).abs();
1349 }
1350 k += 1;
1351 }
1352 acc
1353 }
1354
1355 for i in 0..n {
1356 let x = unsafe { *data.get_unchecked(i) };
1357 let d = if i == 0 || x.is_nan() || prev.is_nan() {
1358 f64::NAN
1359 } else {
1360 x - prev
1361 };
1362 prev = x;
1363 let dev = if filled_cnt < period {
1364 if !x.is_nan() {
1365 unsafe {
1366 *ring.get_unchecked_mut(head) = x;
1367 }
1368 ring_sum += x;
1369 bump_idx(&mut head, period);
1370 filled_cnt += 1;
1371 if filled_cnt == period {
1372 if devtype == 1 {
1373 let mean = ring_sum * inv_p;
1374 abs_dev_mean_unrolled(&ring, mean) * inv_p
1375 } else {
1376 unsafe {
1377 core::ptr::copy_nonoverlapping(
1378 ring.as_ptr(),
1379 scratch.as_mut_ptr(),
1380 period,
1381 );
1382 }
1383 let mid = period >> 1;
1384 let cmp = |a: &f64, b: &f64| {
1385 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
1386 };
1387 let median = if period & 1 == 1 {
1388 let (_lt, m, _gt) = scratch.select_nth_unstable_by(mid, cmp);
1389 *m
1390 } else {
1391 let m_hi_val = {
1392 let (_lt, m_hi, _gt) = scratch.select_nth_unstable_by(mid, cmp);
1393 *m_hi
1394 };
1395 let lo = {
1396 let lower = &mut scratch[..mid];
1397 let (_lt2, m_lo, _gt2) = lower.select_nth_unstable_by(mid - 1, cmp);
1398 *m_lo
1399 };
1400 (lo + m_hi_val) * 0.5
1401 };
1402 abs_dev_mean_unrolled(&ring, median) * inv_p
1403 }
1404 } else {
1405 f64::NAN
1406 }
1407 } else {
1408 head = 0;
1409 filled_cnt = 0;
1410 ring_sum = 0.0;
1411 f64::NAN
1412 }
1413 } else {
1414 if x.is_nan() {
1415 head = 0;
1416 filled_cnt = 0;
1417 ring_sum = 0.0;
1418 f64::NAN
1419 } else {
1420 let leaving = unsafe { *ring.get_unchecked(head) };
1421 unsafe {
1422 *ring.get_unchecked_mut(head) = x;
1423 }
1424 bump_idx(&mut head, period);
1425 ring_sum += x - leaving;
1426 if devtype == 1 {
1427 let mean = ring_sum * inv_p;
1428 abs_dev_mean_unrolled(&ring, mean) * inv_p
1429 } else {
1430 unsafe {
1431 core::ptr::copy_nonoverlapping(ring.as_ptr(), scratch.as_mut_ptr(), period);
1432 }
1433 let mid = period >> 1;
1434 let cmp =
1435 |a: &f64, b: &f64| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal);
1436 let median = if period & 1 == 1 {
1437 let (_lt, m, _gt) = scratch.select_nth_unstable_by(mid, cmp);
1438 *m
1439 } else {
1440 let m_hi_val = {
1441 let (_lt, m_hi, _gt) = scratch.select_nth_unstable_by(mid, cmp);
1442 *m_hi
1443 };
1444 let lo = {
1445 let lower = &mut scratch[..mid];
1446 let (_lt2, m_lo, _gt2) = lower.select_nth_unstable_by(mid - 1, cmp);
1447 *m_lo
1448 };
1449 (lo + m_hi_val) * 0.5
1450 };
1451 abs_dev_mean_unrolled(&ring, median) * inv_p
1452 }
1453 }
1454 };
1455
1456 let (up_i, dn_i) = if d.is_nan() || dev.is_nan() {
1457 (f64::NAN, f64::NAN)
1458 } else if d > 0.0 {
1459 (dev, 0.0)
1460 } else if d < 0.0 {
1461 (0.0, dev)
1462 } else {
1463 (0.0, 0.0)
1464 };
1465 let up_s = if use_sma {
1466 if up_i.is_nan() {
1467 up_sum = 0.0;
1468 up_cnt = 0;
1469 up_h = 0;
1470 f64::NAN
1471 } else if up_cnt < ma_len {
1472 unsafe {
1473 *up_ring.get_unchecked_mut(up_h) = up_i;
1474 }
1475 up_sum += up_i;
1476 bump_idx(&mut up_h, ma_len);
1477 up_cnt += 1;
1478 if up_cnt == ma_len {
1479 up_sum * inv_m
1480 } else {
1481 f64::NAN
1482 }
1483 } else {
1484 let old = unsafe { *up_ring.get_unchecked(up_h) };
1485 unsafe {
1486 *up_ring.get_unchecked_mut(up_h) = up_i;
1487 }
1488 up_sum += up_i - old;
1489 bump_idx(&mut up_h, ma_len);
1490 up_sum * inv_m
1491 }
1492 } else {
1493 if up_i.is_nan() {
1494 up_started = false;
1495 up_seed_sum = 0.0;
1496 up_seed_cnt = 0;
1497 f64::NAN
1498 } else if !up_started {
1499 up_seed_sum += up_i;
1500 up_seed_cnt += 1;
1501 if up_seed_cnt == ma_len {
1502 up_prev = up_seed_sum * inv_m;
1503 up_started = true;
1504 up_prev
1505 } else {
1506 f64::NAN
1507 }
1508 } else {
1509 up_prev = alpha.mul_add(up_i, one_m_alpha * up_prev);
1510 up_prev
1511 }
1512 };
1513 let dn_s = if use_sma {
1514 if dn_i.is_nan() {
1515 dn_sum = 0.0;
1516 dn_cnt = 0;
1517 dn_h = 0;
1518 f64::NAN
1519 } else if dn_cnt < ma_len {
1520 unsafe {
1521 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
1522 }
1523 dn_sum += dn_i;
1524 bump_idx(&mut dn_h, ma_len);
1525 dn_cnt += 1;
1526 if dn_cnt == ma_len {
1527 dn_sum * inv_m
1528 } else {
1529 f64::NAN
1530 }
1531 } else {
1532 let old = unsafe { *dn_ring.get_unchecked(dn_h) };
1533 unsafe {
1534 *dn_ring.get_unchecked_mut(dn_h) = dn_i;
1535 }
1536 dn_sum += dn_i - old;
1537 bump_idx(&mut dn_h, ma_len);
1538 dn_sum * inv_m
1539 }
1540 } else {
1541 if dn_i.is_nan() {
1542 dn_started = false;
1543 dn_seed_sum = 0.0;
1544 dn_seed_cnt = 0;
1545 f64::NAN
1546 } else if !dn_started {
1547 dn_seed_sum += dn_i;
1548 dn_seed_cnt += 1;
1549 if dn_seed_cnt == ma_len {
1550 dn_prev = dn_seed_sum * inv_m;
1551 dn_started = true;
1552 dn_prev
1553 } else {
1554 f64::NAN
1555 }
1556 } else {
1557 dn_prev = alpha.mul_add(dn_i, one_m_alpha * dn_prev);
1558 dn_prev
1559 }
1560 };
1561 if i >= warmup {
1562 if up_s.is_nan() || dn_s.is_nan() {
1563 out[i] = f64::NAN;
1564 } else {
1565 let denom = up_s + dn_s;
1566 out[i] = if denom.abs() < f64::EPSILON {
1567 f64::NAN
1568 } else {
1569 100.0 * (up_s / denom)
1570 };
1571 }
1572 }
1573 }
1574}
1575
1576#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1577#[inline]
1578pub unsafe fn rvi_avx512_short(
1579 data: &[f64],
1580 period: usize,
1581 ma_len: usize,
1582 matype: usize,
1583 devtype: usize,
1584 first: usize,
1585 out: &mut [f64],
1586) {
1587 rvi_scalar(data, period, ma_len, matype, devtype, first, out)
1588}
1589
1590#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
1591#[inline]
1592pub unsafe fn rvi_avx512_long(
1593 data: &[f64],
1594 period: usize,
1595 ma_len: usize,
1596 matype: usize,
1597 devtype: usize,
1598 first: usize,
1599 out: &mut [f64],
1600) {
1601 rvi_scalar(data, period, ma_len, matype, devtype, first, out)
1602}
1603
1604#[derive(Copy, Clone, Debug)]
1605struct HeapItem {
1606 val: f64,
1607 id: usize,
1608}
1609impl PartialEq for HeapItem {
1610 #[inline(always)]
1611 fn eq(&self, other: &Self) -> bool {
1612 self.id == other.id && self.val.to_bits() == other.val.to_bits()
1613 }
1614}
1615impl Eq for HeapItem {}
1616impl Ord for HeapItem {
1617 #[inline(always)]
1618 fn cmp(&self, other: &Self) -> Ordering {
1619 match self.val.partial_cmp(&other.val).unwrap() {
1620 Ordering::Equal => self.id.cmp(&other.id),
1621 ord => ord,
1622 }
1623 }
1624}
1625impl PartialOrd for HeapItem {
1626 #[inline(always)]
1627 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1628 Some(self.cmp(other))
1629 }
1630}
1631
1632#[derive(Clone, Debug)]
1633pub struct RviStream {
1634 period: usize,
1635 ma_len: usize,
1636 matype: usize,
1637 devtype: usize,
1638
1639 inv_p: f64,
1640 inv_m: f64,
1641 use_sma: bool,
1642 alpha: f64,
1643 one_m_alpha: f64,
1644
1645 prev_x: f64,
1646 have_prev: bool,
1647
1648 win: Vec<f64>,
1649 head: usize,
1650 filled: usize,
1651
1652 sum: f64,
1653 sumsq: f64,
1654
1655 mad_sum: f64,
1656
1657 left: BinaryHeap<HeapItem>,
1658 right: BinaryHeap<Reverse<HeapItem>>,
1659 side_of_id: Vec<u8>,
1660 deleted: Vec<u8>,
1661 n_left: usize,
1662 n_right: usize,
1663 s_left: f64,
1664 s_right: f64,
1665
1666 up_ring: Vec<f64>,
1667 dn_ring: Vec<f64>,
1668 up_sum: f64,
1669 dn_sum: f64,
1670 up_h: usize,
1671 dn_h: usize,
1672 up_cnt: usize,
1673 dn_cnt: usize,
1674
1675 up_prev: f64,
1676 dn_prev: f64,
1677 up_started: bool,
1678 dn_started: bool,
1679 up_seed_sum: f64,
1680 dn_seed_sum: f64,
1681 up_seed_cnt: usize,
1682 dn_seed_cnt: usize,
1683}
1684
1685impl RviStream {
1686 pub fn try_new(params: RviParams) -> Result<Self, RviError> {
1687 let period = params.period.unwrap_or(10);
1688 let ma_len = params.ma_len.unwrap_or(14);
1689 let matype = params.matype.unwrap_or(1);
1690 let devtype = params.devtype.unwrap_or(0);
1691 if period == 0 || ma_len == 0 {
1692 return Err(RviError::InvalidPeriod {
1693 period,
1694 ma_len,
1695 data_len: 0,
1696 });
1697 }
1698
1699 let inv_p = 1.0 / period as f64;
1700 let inv_m = 1.0 / ma_len as f64;
1701 let use_sma = matype == 0;
1702 let alpha = if use_sma {
1703 0.0
1704 } else {
1705 2.0 / (ma_len as f64 + 1.0)
1706 };
1707 let one_m_alpha = 1.0 - alpha;
1708
1709 Ok(Self {
1710 period,
1711 ma_len,
1712 matype,
1713 devtype,
1714
1715 inv_p,
1716 inv_m,
1717 use_sma,
1718 alpha,
1719 one_m_alpha,
1720
1721 prev_x: f64::NAN,
1722 have_prev: false,
1723
1724 win: vec![f64::NAN; period],
1725 head: 0,
1726 filled: 0,
1727
1728 sum: 0.0,
1729 sumsq: 0.0,
1730
1731 mad_sum: 0.0,
1732
1733 left: BinaryHeap::new(),
1734 right: BinaryHeap::new(),
1735 side_of_id: vec![0; period],
1736 deleted: vec![1; period],
1737 n_left: 0,
1738 n_right: 0,
1739 s_left: 0.0,
1740 s_right: 0.0,
1741
1742 up_ring: if use_sma {
1743 vec![0.0; ma_len]
1744 } else {
1745 Vec::new()
1746 },
1747 dn_ring: if use_sma {
1748 vec![0.0; ma_len]
1749 } else {
1750 Vec::new()
1751 },
1752 up_sum: 0.0,
1753 dn_sum: 0.0,
1754 up_h: 0,
1755 dn_h: 0,
1756 up_cnt: 0,
1757 dn_cnt: 0,
1758 up_prev: 0.0,
1759 dn_prev: 0.0,
1760 up_started: false,
1761 dn_started: false,
1762 up_seed_sum: 0.0,
1763 dn_seed_sum: 0.0,
1764 up_seed_cnt: 0,
1765 dn_seed_cnt: 0,
1766 })
1767 }
1768
1769 #[inline(always)]
1770 fn reset_smoothing(&mut self) {
1771 if self.use_sma {
1772 self.up_sum = 0.0;
1773 self.dn_sum = 0.0;
1774 self.up_h = 0;
1775 self.dn_h = 0;
1776 self.up_cnt = 0;
1777 self.dn_cnt = 0;
1778 }
1779 self.up_prev = 0.0;
1780 self.dn_prev = 0.0;
1781 self.up_started = false;
1782 self.dn_started = false;
1783 self.up_seed_sum = 0.0;
1784 self.dn_seed_sum = 0.0;
1785 self.up_seed_cnt = 0;
1786 self.dn_seed_cnt = 0;
1787 }
1788
1789 #[inline(always)]
1790 fn reset_all(&mut self) {
1791 self.prev_x = f64::NAN;
1792 self.have_prev = false;
1793 self.head = 0;
1794 self.filled = 0;
1795 self.sum = 0.0;
1796 self.sumsq = 0.0;
1797 self.mad_sum = 0.0;
1798 for i in 0..self.period {
1799 self.win[i] = f64::NAN;
1800 self.deleted[i] = 1;
1801 }
1802 self.left.clear();
1803 self.right.clear();
1804 self.n_left = 0;
1805 self.n_right = 0;
1806 self.s_left = 0.0;
1807 self.s_right = 0.0;
1808 self.reset_smoothing();
1809 }
1810
1811 #[inline(always)]
1812 fn prune_left(&mut self) {
1813 while let Some(top) = self.left.peek() {
1814 if self.deleted[top.id] != 0 {
1815 self.left.pop();
1816 } else {
1817 break;
1818 }
1819 }
1820 }
1821 #[inline(always)]
1822 fn prune_right(&mut self) {
1823 while let Some(Reverse(top)) = self.right.peek() {
1824 if self.deleted[top.id] != 0 {
1825 self.right.pop();
1826 } else {
1827 break;
1828 }
1829 }
1830 }
1831 #[inline(always)]
1832 fn rebalance(&mut self) {
1833 self.prune_left();
1834 self.prune_right();
1835 if self.n_left > self.n_right + 1 {
1836 let item = self.left.pop().unwrap();
1837 self.prune_left();
1838 self.n_left -= 1;
1839 self.s_left -= item.val;
1840 self.n_right += 1;
1841 self.s_right += item.val;
1842 self.side_of_id[item.id] = 1;
1843 self.right.push(Reverse(item));
1844 self.prune_right();
1845 } else if self.n_left < self.n_right {
1846 let Reverse(item) = self.right.pop().unwrap();
1847 self.prune_right();
1848 self.n_right -= 1;
1849 self.s_right -= item.val;
1850 self.n_left += 1;
1851 self.s_left += item.val;
1852 self.side_of_id[item.id] = 0;
1853 self.left.push(item);
1854 self.prune_left();
1855 }
1856 }
1857 #[inline(always)]
1858 fn median_insert(&mut self, id: usize, val: f64) {
1859 self.prune_left();
1860 if self.n_left == 0 || self.left.peek().map(|t| val <= t.val).unwrap_or(true) {
1861 self.left.push(HeapItem { val, id });
1862 self.side_of_id[id] = 0;
1863 self.deleted[id] = 0;
1864 self.n_left += 1;
1865 self.s_left += val;
1866 } else {
1867 self.right.push(Reverse(HeapItem { val, id }));
1868 self.side_of_id[id] = 1;
1869 self.deleted[id] = 0;
1870 self.n_right += 1;
1871 self.s_right += val;
1872 }
1873 self.rebalance();
1874 }
1875 #[inline(always)]
1876 fn median_remove(&mut self, id: usize, val: f64) {
1877 self.deleted[id] = 1;
1878 if self.side_of_id[id] == 0 {
1879 if self.n_left > 0 {
1880 self.n_left -= 1;
1881 self.s_left -= val;
1882 }
1883 } else {
1884 if self.n_right > 0 {
1885 self.n_right -= 1;
1886 self.s_right -= val;
1887 }
1888 }
1889 self.rebalance();
1890 }
1891 #[inline(always)]
1892 fn median_value(&mut self) -> Option<f64> {
1893 self.prune_left();
1894 self.left.peek().map(|t| t.val)
1895 }
1896 #[inline(always)]
1897 fn mean_abs_dev_about_median(&mut self, m: f64) -> f64 {
1898 let l = self.n_left as f64;
1899 let r = self.n_right as f64;
1900 let l1 = m * l - self.s_left + self.s_right - m * r;
1901 l1 * self.inv_p
1902 }
1903
1904 #[inline(always)]
1905 fn stddev_current(&self) -> f64 {
1906 let mean = self.sum * self.inv_p;
1907 let mean_sq = self.sumsq * self.inv_p;
1908 (mean_sq - mean * mean).sqrt()
1909 }
1910
1911 #[inline(always)]
1912 fn push_sma(
1913 sum: &mut f64,
1914 ring: &mut [f64],
1915 head: &mut usize,
1916 cnt: &mut usize,
1917 inv_m: f64,
1918 x: f64,
1919 ) -> Option<f64> {
1920 if *cnt < ring.len() {
1921 ring[*head] = x;
1922 *sum += x;
1923 *head += 1;
1924 if *head == ring.len() {
1925 *head = 0;
1926 }
1927 *cnt += 1;
1928 if *cnt == ring.len() {
1929 Some(*sum * inv_m)
1930 } else {
1931 None
1932 }
1933 } else {
1934 let old = ring[*head];
1935 ring[*head] = x;
1936 *head += 1;
1937 if *head == ring.len() {
1938 *head = 0;
1939 }
1940 *sum += x - old;
1941 Some(*sum * inv_m)
1942 }
1943 }
1944
1945 #[inline(always)]
1946 fn push_ema(
1947 prev: &mut f64,
1948 started: &mut bool,
1949 seed_sum: &mut f64,
1950 seed_cnt: &mut usize,
1951 ma_len: usize,
1952 inv_m: f64,
1953 alpha: f64,
1954 one_m_alpha: f64,
1955 x: f64,
1956 ) -> Option<f64> {
1957 if !*started {
1958 *seed_sum += x;
1959 *seed_cnt += 1;
1960 if *seed_cnt == ma_len {
1961 *prev = *seed_sum * inv_m;
1962 *started = true;
1963 Some(*prev)
1964 } else {
1965 None
1966 }
1967 } else {
1968 *prev = alpha.mul_add(x, one_m_alpha * *prev);
1969 Some(*prev)
1970 }
1971 }
1972
1973 #[inline(always)]
1974 fn reset_smoothers_on_gap(&mut self) {
1975 self.reset_smoothing();
1976 }
1977
1978 #[inline(always)]
1979 fn combine_rvi(us: Option<f64>, ds: Option<f64>) -> Option<f64> {
1980 match (us, ds) {
1981 (Some(u), Some(d)) => {
1982 let denom = u + d;
1983 if denom.abs() < f64::EPSILON {
1984 None
1985 } else {
1986 Some(100.0 * (u / denom))
1987 }
1988 }
1989 _ => None,
1990 }
1991 }
1992
1993 #[inline(always)]
1994 pub fn update(&mut self, value: f64) -> Option<f64> {
1995 if value.is_nan() {
1996 self.reset_all();
1997 return None;
1998 }
1999
2000 let d = if self.have_prev {
2001 value - self.prev_x
2002 } else {
2003 f64::NAN
2004 };
2005 self.prev_x = value;
2006 self.have_prev = true;
2007
2008 let id = self.head;
2009 if self.filled < self.period {
2010 self.win[id] = value;
2011 self.head += 1;
2012 if self.head == self.period {
2013 self.head = 0;
2014 }
2015 self.filled += 1;
2016 match self.devtype {
2017 0 => {
2018 self.sum += value;
2019 self.sumsq += value * value;
2020 }
2021 1 => {
2022 self.mad_sum += value;
2023 }
2024 2 => {
2025 self.median_insert(id, value);
2026 }
2027 _ => {}
2028 }
2029 } else {
2030 let leaving = self.win[id];
2031 self.win[id] = value;
2032 self.head += 1;
2033 if self.head == self.period {
2034 self.head = 0;
2035 }
2036 match self.devtype {
2037 0 => {
2038 self.sum += value - leaving;
2039 self.sumsq += value * value - leaving * leaving;
2040 }
2041 1 => {
2042 self.mad_sum += value - leaving;
2043 }
2044 2 => {
2045 self.median_remove(id, leaving);
2046 self.median_insert(id, value);
2047 }
2048 _ => {}
2049 }
2050 }
2051
2052 if self.filled < self.period {
2053 self.reset_smoothers_on_gap();
2054 return None;
2055 }
2056
2057 let dev = match self.devtype {
2058 0 => {
2059 let sd = self.stddev_current();
2060 if !sd.is_finite() {
2061 self.reset_smoothers_on_gap();
2062 return None;
2063 }
2064 sd
2065 }
2066 1 => {
2067 let mean = self.mad_sum * self.inv_p;
2068 let mut abs_sum = 0.0;
2069 for k in 0..self.period {
2070 abs_sum += (self.win[k] - mean).abs();
2071 }
2072 abs_sum * self.inv_p
2073 }
2074 2 => {
2075 if let Some(med) = self.median_value() {
2076 self.mean_abs_dev_about_median(med)
2077 } else {
2078 self.reset_smoothers_on_gap();
2079 return None;
2080 }
2081 }
2082 _ => unreachable!(),
2083 };
2084
2085 if !d.is_finite() || !dev.is_finite() {
2086 self.reset_smoothers_on_gap();
2087 return None;
2088 }
2089 let (up_i, dn_i) = if d > 0.0 {
2090 (dev, 0.0)
2091 } else if d < 0.0 {
2092 (0.0, dev)
2093 } else {
2094 (0.0, 0.0)
2095 };
2096
2097 let (up_s, dn_s) = if self.use_sma {
2098 let up_s = Self::push_sma(
2099 &mut self.up_sum,
2100 &mut self.up_ring,
2101 &mut self.up_h,
2102 &mut self.up_cnt,
2103 self.inv_m,
2104 up_i,
2105 );
2106 let dn_s = Self::push_sma(
2107 &mut self.dn_sum,
2108 &mut self.dn_ring,
2109 &mut self.dn_h,
2110 &mut self.dn_cnt,
2111 self.inv_m,
2112 dn_i,
2113 );
2114 (up_s, dn_s)
2115 } else {
2116 let up_s = Self::push_ema(
2117 &mut self.up_prev,
2118 &mut self.up_started,
2119 &mut self.up_seed_sum,
2120 &mut self.up_seed_cnt,
2121 self.ma_len,
2122 self.inv_m,
2123 self.alpha,
2124 self.one_m_alpha,
2125 up_i,
2126 );
2127 let dn_s = Self::push_ema(
2128 &mut self.dn_prev,
2129 &mut self.dn_started,
2130 &mut self.dn_seed_sum,
2131 &mut self.dn_seed_cnt,
2132 self.ma_len,
2133 self.inv_m,
2134 self.alpha,
2135 self.one_m_alpha,
2136 dn_i,
2137 );
2138 (up_s, dn_s)
2139 };
2140
2141 Self::combine_rvi(up_s, dn_s)
2142 }
2143}
2144
2145#[derive(Clone, Debug)]
2146pub struct RviBatchRange {
2147 pub period: (usize, usize, usize),
2148 pub ma_len: (usize, usize, usize),
2149 pub matype: (usize, usize, usize),
2150 pub devtype: (usize, usize, usize),
2151}
2152
2153impl Default for RviBatchRange {
2154 fn default() -> Self {
2155 Self {
2156 period: (10, 259, 1),
2157 ma_len: (14, 14, 0),
2158 matype: (1, 1, 0),
2159 devtype: (0, 0, 0),
2160 }
2161 }
2162}
2163
2164#[derive(Clone, Debug, Default)]
2165pub struct RviBatchBuilder {
2166 range: RviBatchRange,
2167 kernel: Kernel,
2168}
2169
2170impl RviBatchBuilder {
2171 pub fn new() -> Self {
2172 Self::default()
2173 }
2174 pub fn kernel(mut self, k: Kernel) -> Self {
2175 self.kernel = k;
2176 self
2177 }
2178 #[inline]
2179 pub fn period_range(mut self, start: usize, end: usize, step: usize) -> Self {
2180 self.range.period = (start, end, step);
2181 self
2182 }
2183 #[inline]
2184 pub fn period_static(mut self, p: usize) -> Self {
2185 self.range.period = (p, p, 0);
2186 self
2187 }
2188 #[inline]
2189 pub fn ma_len_range(mut self, start: usize, end: usize, step: usize) -> Self {
2190 self.range.ma_len = (start, end, step);
2191 self
2192 }
2193 #[inline]
2194 pub fn ma_len_static(mut self, p: usize) -> Self {
2195 self.range.ma_len = (p, p, 0);
2196 self
2197 }
2198 #[inline]
2199 pub fn matype_range(mut self, start: usize, end: usize, step: usize) -> Self {
2200 self.range.matype = (start, end, step);
2201 self
2202 }
2203 #[inline]
2204 pub fn matype_static(mut self, p: usize) -> Self {
2205 self.range.matype = (p, p, 0);
2206 self
2207 }
2208 #[inline]
2209 pub fn devtype_range(mut self, start: usize, end: usize, step: usize) -> Self {
2210 self.range.devtype = (start, end, step);
2211 self
2212 }
2213 #[inline]
2214 pub fn devtype_static(mut self, p: usize) -> Self {
2215 self.range.devtype = (p, p, 0);
2216 self
2217 }
2218
2219 pub fn apply_slice(self, data: &[f64]) -> Result<RviBatchOutput, RviError> {
2220 rvi_batch_with_kernel(data, &self.range, self.kernel)
2221 }
2222
2223 pub fn with_default_slice(data: &[f64], k: Kernel) -> Result<RviBatchOutput, RviError> {
2224 RviBatchBuilder::new().kernel(k).apply_slice(data)
2225 }
2226
2227 pub fn apply_candles(self, c: &Candles, src: &str) -> Result<RviBatchOutput, RviError> {
2228 let slice = source_type(c, src);
2229 self.apply_slice(slice)
2230 }
2231
2232 pub fn with_default_candles(c: &Candles) -> Result<RviBatchOutput, RviError> {
2233 RviBatchBuilder::new()
2234 .kernel(Kernel::Auto)
2235 .apply_candles(c, "close")
2236 }
2237}
2238
2239#[derive(Clone, Debug)]
2240pub struct RviBatchOutput {
2241 pub values: Vec<f64>,
2242 pub combos: Vec<RviParams>,
2243 pub rows: usize,
2244 pub cols: usize,
2245}
2246
2247impl RviBatchOutput {
2248 pub fn row_for_params(&self, p: &RviParams) -> Option<usize> {
2249 self.combos.iter().position(|c| {
2250 c.period.unwrap_or(10) == p.period.unwrap_or(10)
2251 && c.ma_len.unwrap_or(14) == p.ma_len.unwrap_or(14)
2252 && c.matype.unwrap_or(1) == p.matype.unwrap_or(1)
2253 && c.devtype.unwrap_or(0) == p.devtype.unwrap_or(0)
2254 })
2255 }
2256 pub fn values_for(&self, p: &RviParams) -> Option<&[f64]> {
2257 self.row_for_params(p).map(|row| {
2258 let start = row * self.cols;
2259 &self.values[start..start + self.cols]
2260 })
2261 }
2262}
2263
2264#[inline(always)]
2265fn expand_grid(r: &RviBatchRange) -> Result<Vec<RviParams>, RviError> {
2266 fn axis_usize((start, end, step): (usize, usize, usize)) -> Result<Vec<usize>, RviError> {
2267 let s = start as i128;
2268 let e = end as i128;
2269 let st = step as i128;
2270 if step == 0 || start == end {
2271 return Ok(vec![start]);
2272 }
2273 let mut v = Vec::new();
2274 if start <= end {
2275 let stp = step.max(1);
2276 let mut cur = start;
2277 while cur <= end {
2278 v.push(cur);
2279 cur = match cur.checked_add(stp) {
2280 Some(n) => n,
2281 None => break,
2282 };
2283 }
2284 } else {
2285 let stp = step.max(1);
2286 let mut cur = start;
2287 loop {
2288 v.push(cur);
2289 if cur <= end {
2290 break;
2291 }
2292 cur = match cur.checked_sub(stp) {
2293 Some(n) => n,
2294 None => break,
2295 };
2296 if cur < end {
2297 break;
2298 }
2299 }
2300 }
2301 if v.is_empty() {
2302 Err(RviError::InvalidRange {
2303 start: s,
2304 end: e,
2305 step: st,
2306 })
2307 } else {
2308 Ok(v)
2309 }
2310 }
2311
2312 let periods = axis_usize(r.period)?;
2313 let ma_lens = axis_usize(r.ma_len)?;
2314 let matypes = axis_usize(r.matype)?;
2315 let devtypes = axis_usize(r.devtype)?;
2316
2317 let cap = periods
2318 .len()
2319 .checked_mul(ma_lens.len())
2320 .and_then(|x| x.checked_mul(matypes.len()))
2321 .and_then(|x| x.checked_mul(devtypes.len()))
2322 .ok_or_else(|| RviError::InvalidInput("parameter grid too large".into()))?;
2323
2324 let mut out = Vec::with_capacity(cap);
2325 for &p in &periods {
2326 for &m in &ma_lens {
2327 for &t in &matypes {
2328 for &d in &devtypes {
2329 out.push(RviParams {
2330 period: Some(p),
2331 ma_len: Some(m),
2332 matype: Some(t),
2333 devtype: Some(d),
2334 });
2335 }
2336 }
2337 }
2338 }
2339 Ok(out)
2340}
2341
2342#[inline(always)]
2343pub fn rvi_batch_with_kernel(
2344 data: &[f64],
2345 sweep: &RviBatchRange,
2346 k: Kernel,
2347) -> Result<RviBatchOutput, RviError> {
2348 let kernel = match k {
2349 Kernel::Auto => detect_best_batch_kernel(),
2350 other if other.is_batch() => other,
2351 _ => return Err(RviError::InvalidKernelForBatch(k)),
2352 };
2353 let simd = match kernel {
2354 Kernel::Avx512Batch => Kernel::Avx512,
2355 Kernel::Avx2Batch => Kernel::Avx2,
2356 Kernel::ScalarBatch => Kernel::Scalar,
2357 _ => unreachable!(),
2358 };
2359 rvi_batch_par_slice(data, sweep, simd)
2360}
2361
2362#[inline(always)]
2363pub fn rvi_batch_slice(
2364 data: &[f64],
2365 sweep: &RviBatchRange,
2366 kern: Kernel,
2367) -> Result<RviBatchOutput, RviError> {
2368 rvi_batch_inner(data, sweep, kern, false)
2369}
2370
2371#[inline(always)]
2372pub fn rvi_batch_par_slice(
2373 data: &[f64],
2374 sweep: &RviBatchRange,
2375 kern: Kernel,
2376) -> Result<RviBatchOutput, RviError> {
2377 rvi_batch_inner(data, sweep, kern, true)
2378}
2379
2380#[inline(always)]
2381fn rvi_batch_inner(
2382 data: &[f64],
2383 sweep: &RviBatchRange,
2384 kern: Kernel,
2385 parallel: bool,
2386) -> Result<RviBatchOutput, RviError> {
2387 if data.is_empty() {
2388 return Err(RviError::EmptyInputData);
2389 }
2390 let combos = expand_grid(sweep)?;
2391 if combos.is_empty() {
2392 return Err(RviError::InvalidRange {
2393 start: 0,
2394 end: 0,
2395 step: 0,
2396 });
2397 }
2398 let first = data
2399 .iter()
2400 .position(|x| !x.is_nan())
2401 .ok_or(RviError::AllValuesNaN)?;
2402 let max_p = combos.iter().map(|c| c.period.unwrap()).max().unwrap();
2403 let max_m = combos.iter().map(|c| c.ma_len.unwrap()).max().unwrap();
2404 let need = max_p.saturating_sub(1) + max_m.saturating_sub(1) + 1;
2405 if (data.len() - first) <= (max_p.saturating_sub(1) + max_m.saturating_sub(1)) {
2406 return Err(RviError::NotEnoughValidData {
2407 needed: need,
2408 valid: data.len() - first,
2409 });
2410 }
2411 let rows = combos.len();
2412 let cols = data.len();
2413 let _ = rows
2414 .checked_mul(cols)
2415 .ok_or_else(|| RviError::InvalidInput("rows * cols overflow".into()))?;
2416
2417 let mut buf_mu = make_uninit_matrix(rows, cols);
2418
2419 let warm: Vec<usize> = combos
2420 .iter()
2421 .map(|c| first + c.period.unwrap().saturating_sub(1) + c.ma_len.unwrap().saturating_sub(1))
2422 .collect();
2423 init_matrix_prefixes(&mut buf_mu, cols, &warm);
2424
2425 let mut buf_guard = core::mem::ManuallyDrop::new(buf_mu);
2426 let out: &mut [f64] = unsafe {
2427 core::slice::from_raw_parts_mut(buf_guard.as_mut_ptr() as *mut f64, buf_guard.len())
2428 };
2429
2430 let chosen_kernel = match kern {
2431 Kernel::Auto => detect_best_batch_kernel(),
2432 other => other,
2433 };
2434
2435 let do_row = |row: usize, out_row: &mut [f64]| unsafe {
2436 let prm = &combos[row];
2437 match chosen_kernel {
2438 Kernel::Scalar | Kernel::ScalarBatch => rvi_row_scalar(data, first, prm, out_row),
2439 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2440 Kernel::Avx2 | Kernel::Avx2Batch => rvi_row_avx2(data, first, prm, out_row),
2441 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2442 Kernel::Avx512 | Kernel::Avx512Batch => rvi_row_avx512(data, first, prm, out_row),
2443 _ => rvi_row_scalar(data, first, prm, out_row),
2444 }
2445 };
2446 if parallel {
2447 #[cfg(not(target_arch = "wasm32"))]
2448 {
2449 out.par_chunks_mut(cols)
2450 .enumerate()
2451 .for_each(|(row, slice)| do_row(row, slice));
2452 }
2453
2454 #[cfg(target_arch = "wasm32")]
2455 {
2456 for (row, slice) in out.chunks_mut(cols).enumerate() {
2457 do_row(row, slice);
2458 }
2459 }
2460 } else {
2461 for (row, slice) in out.chunks_mut(cols).enumerate() {
2462 do_row(row, slice);
2463 }
2464 }
2465
2466 let values = unsafe {
2467 Vec::from_raw_parts(
2468 buf_guard.as_mut_ptr() as *mut f64,
2469 buf_guard.len(),
2470 buf_guard.capacity(),
2471 )
2472 };
2473
2474 Ok(RviBatchOutput {
2475 values,
2476 combos,
2477 rows,
2478 cols,
2479 })
2480}
2481
2482#[inline(always)]
2483fn rvi_batch_inner_into(
2484 data: &[f64],
2485 sweep: &RviBatchRange,
2486 kern: Kernel,
2487 parallel: bool,
2488 output: &mut [f64],
2489) -> Result<Vec<RviParams>, RviError> {
2490 if data.is_empty() {
2491 return Err(RviError::EmptyInputData);
2492 }
2493 let combos = expand_grid(sweep)?;
2494 if combos.is_empty() {
2495 return Err(RviError::InvalidRange {
2496 start: 0,
2497 end: 0,
2498 step: 0,
2499 });
2500 }
2501 let first = data
2502 .iter()
2503 .position(|x| !x.is_nan())
2504 .ok_or(RviError::AllValuesNaN)?;
2505 let max_p = combos.iter().map(|c| c.period.unwrap()).max().unwrap();
2506 let max_m = combos.iter().map(|c| c.ma_len.unwrap()).max().unwrap();
2507 let need = max_p.saturating_sub(1) + max_m.saturating_sub(1) + 1;
2508 if (data.len() - first) <= (max_p.saturating_sub(1) + max_m.saturating_sub(1)) {
2509 return Err(RviError::NotEnoughValidData {
2510 needed: need,
2511 valid: data.len() - first,
2512 });
2513 }
2514 let rows = combos.len();
2515 let cols = data.len();
2516 let expected_len = rows
2517 .checked_mul(cols)
2518 .ok_or_else(|| RviError::InvalidInput("rows * cols overflow".into()))?;
2519 if output.len() != expected_len {
2520 return Err(RviError::OutputLengthMismatch {
2521 expected: expected_len,
2522 got: output.len(),
2523 });
2524 }
2525
2526 let chosen_kernel = match kern {
2527 Kernel::Auto => detect_best_batch_kernel(),
2528 other => other,
2529 };
2530
2531 for (row, combo) in combos.iter().enumerate() {
2532 let warmup = first
2533 + combo.period.unwrap().saturating_sub(1)
2534 + combo.ma_len.unwrap().saturating_sub(1);
2535 let row_start = row * cols;
2536 for i in 0..warmup.min(cols) {
2537 output[row_start + i] = f64::NAN;
2538 }
2539 }
2540 let do_row = |row: usize, out_row: &mut [f64]| unsafe {
2541 let prm = &combos[row];
2542 match chosen_kernel {
2543 Kernel::Scalar | Kernel::ScalarBatch => rvi_row_scalar(data, first, prm, out_row),
2544 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2545 Kernel::Avx2 | Kernel::Avx2Batch => rvi_row_avx2(data, first, prm, out_row),
2546 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2547 Kernel::Avx512 | Kernel::Avx512Batch => rvi_row_avx512(data, first, prm, out_row),
2548 _ => rvi_row_scalar(data, first, prm, out_row),
2549 }
2550 };
2551 if parallel {
2552 #[cfg(not(target_arch = "wasm32"))]
2553 {
2554 output
2555 .par_chunks_mut(cols)
2556 .enumerate()
2557 .for_each(|(row, slice)| do_row(row, slice));
2558 }
2559
2560 #[cfg(target_arch = "wasm32")]
2561 {
2562 for (row, slice) in output.chunks_mut(cols).enumerate() {
2563 do_row(row, slice);
2564 }
2565 }
2566 } else {
2567 for (row, slice) in output.chunks_mut(cols).enumerate() {
2568 do_row(row, slice);
2569 }
2570 }
2571 Ok(combos)
2572}
2573
2574#[inline(always)]
2575unsafe fn rvi_row_scalar(data: &[f64], first: usize, params: &RviParams, out: &mut [f64]) {
2576 rvi_scalar(
2577 data,
2578 params.period.unwrap(),
2579 params.ma_len.unwrap(),
2580 params.matype.unwrap(),
2581 params.devtype.unwrap(),
2582 first,
2583 out,
2584 )
2585}
2586
2587#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2588#[inline(always)]
2589unsafe fn rvi_row_avx2(data: &[f64], first: usize, params: &RviParams, out: &mut [f64]) {
2590 rvi_avx2(
2591 data,
2592 params.period.unwrap(),
2593 params.ma_len.unwrap(),
2594 params.matype.unwrap(),
2595 params.devtype.unwrap(),
2596 first,
2597 out,
2598 )
2599}
2600
2601#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2602#[inline(always)]
2603unsafe fn rvi_row_avx512(data: &[f64], first: usize, params: &RviParams, out: &mut [f64]) {
2604 rvi_avx512(
2605 data,
2606 params.period.unwrap(),
2607 params.ma_len.unwrap(),
2608 params.matype.unwrap(),
2609 params.devtype.unwrap(),
2610 first,
2611 out,
2612 )
2613}
2614
2615#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2616#[inline(always)]
2617unsafe fn rvi_row_avx512_short(data: &[f64], first: usize, params: &RviParams, out: &mut [f64]) {
2618 rvi_avx512_short(
2619 data,
2620 params.period.unwrap(),
2621 params.ma_len.unwrap(),
2622 params.matype.unwrap(),
2623 params.devtype.unwrap(),
2624 first,
2625 out,
2626 )
2627}
2628
2629#[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
2630#[inline(always)]
2631unsafe fn rvi_row_avx512_long(data: &[f64], first: usize, params: &RviParams, out: &mut [f64]) {
2632 rvi_avx512_long(
2633 data,
2634 params.period.unwrap(),
2635 params.ma_len.unwrap(),
2636 params.matype.unwrap(),
2637 params.devtype.unwrap(),
2638 first,
2639 out,
2640 )
2641}
2642
2643#[cfg(test)]
2644mod tests {
2645 use super::*;
2646 use crate::skip_if_unsupported;
2647 use crate::utilities::data_loader::read_candles_from_csv;
2648
2649 fn check_rvi_partial_params(
2650 test_name: &str,
2651 kernel: Kernel,
2652 ) -> Result<(), Box<dyn std::error::Error>> {
2653 skip_if_unsupported!(kernel, test_name);
2654 let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2655 let candles = read_candles_from_csv(file_path)?;
2656 let partial_params = RviParams {
2657 period: Some(10),
2658 ma_len: None,
2659 matype: None,
2660 devtype: None,
2661 };
2662 let input = RviInput::from_candles(&candles, "close", partial_params);
2663 let output = rvi_with_kernel(&input, kernel)?;
2664 assert_eq!(output.values.len(), candles.close.len());
2665 Ok(())
2666 }
2667
2668 fn check_rvi_default_params(
2669 test_name: &str,
2670 kernel: Kernel,
2671 ) -> Result<(), Box<dyn std::error::Error>> {
2672 skip_if_unsupported!(kernel, test_name);
2673 let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2674 let candles = read_candles_from_csv(file_path)?;
2675 let input = RviInput::with_default_candles(&candles);
2676 let output = rvi_with_kernel(&input, kernel)?;
2677 assert_eq!(output.values.len(), candles.close.len());
2678 Ok(())
2679 }
2680
2681 fn check_rvi_error_zero_period(
2682 test_name: &str,
2683 kernel: Kernel,
2684 ) -> Result<(), Box<dyn std::error::Error>> {
2685 skip_if_unsupported!(kernel, test_name);
2686 let data = [10.0, 20.0, 30.0, 40.0];
2687 let params = RviParams {
2688 period: Some(0),
2689 ma_len: Some(14),
2690 matype: Some(1),
2691 devtype: Some(0),
2692 };
2693 let input = RviInput::from_slice(&data, params);
2694 let result = rvi_with_kernel(&input, kernel);
2695 assert!(result.is_err());
2696 Ok(())
2697 }
2698
2699 fn check_rvi_error_zero_ma_len(
2700 test_name: &str,
2701 kernel: Kernel,
2702 ) -> Result<(), Box<dyn std::error::Error>> {
2703 skip_if_unsupported!(kernel, test_name);
2704 let data = [10.0, 20.0, 30.0, 40.0];
2705 let params = RviParams {
2706 period: Some(10),
2707 ma_len: Some(0),
2708 matype: Some(1),
2709 devtype: Some(0),
2710 };
2711 let input = RviInput::from_slice(&data, params);
2712 let result = rvi_with_kernel(&input, kernel);
2713 assert!(result.is_err());
2714 Ok(())
2715 }
2716
2717 fn check_rvi_error_period_exceeds_data_length(
2718 test_name: &str,
2719 kernel: Kernel,
2720 ) -> Result<(), Box<dyn std::error::Error>> {
2721 skip_if_unsupported!(kernel, test_name);
2722 let data = [10.0, 20.0, 30.0];
2723 let params = RviParams {
2724 period: Some(10),
2725 ma_len: Some(14),
2726 matype: Some(1),
2727 devtype: Some(0),
2728 };
2729 let input = RviInput::from_slice(&data, params);
2730 let result = rvi_with_kernel(&input, kernel);
2731 assert!(result.is_err());
2732 Ok(())
2733 }
2734
2735 fn check_rvi_all_nan_input(
2736 test_name: &str,
2737 kernel: Kernel,
2738 ) -> Result<(), Box<dyn std::error::Error>> {
2739 skip_if_unsupported!(kernel, test_name);
2740 let data = [f64::NAN, f64::NAN, f64::NAN];
2741 let params = RviParams::default();
2742 let input = RviInput::from_slice(&data, params);
2743 let result = rvi_with_kernel(&input, kernel);
2744 assert!(result.is_err());
2745 Ok(())
2746 }
2747
2748 fn check_rvi_not_enough_valid_data(
2749 test_name: &str,
2750 kernel: Kernel,
2751 ) -> Result<(), Box<dyn std::error::Error>> {
2752 skip_if_unsupported!(kernel, test_name);
2753 let data = [f64::NAN, 1.0, 2.0, 3.0];
2754 let params = RviParams {
2755 period: Some(3),
2756 ma_len: Some(5),
2757 matype: Some(1),
2758 devtype: Some(0),
2759 };
2760 let input = RviInput::from_slice(&data, params);
2761 let result = rvi_with_kernel(&input, kernel);
2762 assert!(result.is_err());
2763 Ok(())
2764 }
2765
2766 fn check_rvi_example_values(
2767 test_name: &str,
2768 kernel: Kernel,
2769 ) -> Result<(), Box<dyn std::error::Error>> {
2770 skip_if_unsupported!(kernel, test_name);
2771 let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2772 let candles = read_candles_from_csv(file_path)?;
2773 let params = RviParams {
2774 period: Some(10),
2775 ma_len: Some(14),
2776 matype: Some(1),
2777 devtype: Some(0),
2778 };
2779 let input = RviInput::from_candles(&candles, "close", params);
2780 let output = rvi_with_kernel(&input, kernel)?;
2781 assert_eq!(output.values.len(), candles.close.len());
2782 let last_five = &output.values[output.values.len().saturating_sub(5)..];
2783 let expected = [
2784 67.48579363423423,
2785 62.03322230763894,
2786 56.71819195768154,
2787 60.487299747927636,
2788 55.022521428674175,
2789 ];
2790 for (i, &val) in last_five.iter().enumerate() {
2791 let exp = expected[i];
2792 assert!(
2793 val.is_finite(),
2794 "Expected a finite RVI value, got NaN at index {}",
2795 i
2796 );
2797 let diff = (val - exp).abs();
2798 assert!(
2799 diff < 1e-1,
2800 "Mismatch at index {} -> got: {}, expected: {}, diff: {}",
2801 i,
2802 val,
2803 exp,
2804 diff
2805 );
2806 }
2807 Ok(())
2808 }
2809
2810 #[cfg(debug_assertions)]
2811 fn check_rvi_no_poison(
2812 test_name: &str,
2813 kernel: Kernel,
2814 ) -> Result<(), Box<dyn std::error::Error>> {
2815 skip_if_unsupported!(kernel, test_name);
2816
2817 let file_path = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
2818 let candles = read_candles_from_csv(file_path)?;
2819
2820 let test_params = vec![
2821 RviParams::default(),
2822 RviParams {
2823 period: Some(2),
2824 ma_len: Some(2),
2825 matype: Some(0),
2826 devtype: Some(0),
2827 },
2828 RviParams {
2829 period: Some(5),
2830 ma_len: Some(5),
2831 matype: Some(1),
2832 devtype: Some(1),
2833 },
2834 RviParams {
2835 period: Some(10),
2836 ma_len: Some(20),
2837 matype: Some(0),
2838 devtype: Some(2),
2839 },
2840 RviParams {
2841 period: Some(20),
2842 ma_len: Some(30),
2843 matype: Some(1),
2844 devtype: Some(0),
2845 },
2846 RviParams {
2847 period: Some(50),
2848 ma_len: Some(50),
2849 matype: Some(0),
2850 devtype: Some(1),
2851 },
2852 RviParams {
2853 period: Some(100),
2854 ma_len: Some(20),
2855 matype: Some(1),
2856 devtype: Some(2),
2857 },
2858 RviParams {
2859 period: Some(14),
2860 ma_len: Some(100),
2861 matype: Some(0),
2862 devtype: Some(0),
2863 },
2864 ];
2865
2866 for (param_idx, params) in test_params.iter().enumerate() {
2867 let input = RviInput::from_candles(&candles, "close", params.clone());
2868 let output = rvi_with_kernel(&input, kernel)?;
2869
2870 for (i, &val) in output.values.iter().enumerate() {
2871 if val.is_nan() {
2872 continue;
2873 }
2874
2875 let bits = val.to_bits();
2876
2877 if bits == 0x11111111_11111111 {
2878 panic!(
2879 "[{}] Found alloc_with_nan_prefix poison value {} (0x{:016X}) at index {} \
2880 with params: {:?} (param set {})",
2881 test_name, val, bits, i, params, param_idx
2882 );
2883 }
2884
2885 if bits == 0x22222222_22222222 {
2886 panic!(
2887 "[{}] Found init_matrix_prefixes poison value {} (0x{:016X}) at index {} \
2888 with params: {:?} (param set {})",
2889 test_name, val, bits, i, params, param_idx
2890 );
2891 }
2892
2893 if bits == 0x33333333_33333333 {
2894 panic!(
2895 "[{}] Found make_uninit_matrix poison value {} (0x{:016X}) at index {} \
2896 with params: {:?} (param set {})",
2897 test_name, val, bits, i, params, param_idx
2898 );
2899 }
2900 }
2901 }
2902
2903 Ok(())
2904 }
2905
2906 #[cfg(not(debug_assertions))]
2907 fn check_rvi_no_poison(
2908 _test_name: &str,
2909 _kernel: Kernel,
2910 ) -> Result<(), Box<dyn std::error::Error>> {
2911 Ok(())
2912 }
2913
2914 #[cfg(feature = "proptest")]
2915 #[allow(clippy::float_cmp)]
2916 fn check_rvi_property(
2917 test_name: &str,
2918 kernel: Kernel,
2919 ) -> Result<(), Box<dyn std::error::Error>> {
2920 use proptest::prelude::*;
2921 skip_if_unsupported!(kernel, test_name);
2922
2923 let strat = (2usize..=30, 2usize..=30, 0usize..=1, 0usize..=2).prop_flat_map(
2924 |(period, ma_len, matype, devtype)| {
2925 (
2926 prop::collection::vec(
2927 (0.01f64..1e6f64).prop_filter("finite", |x| x.is_finite()),
2928 (period + ma_len)..400,
2929 ),
2930 Just(period),
2931 Just(ma_len),
2932 Just(matype),
2933 Just(devtype),
2934 )
2935 },
2936 );
2937
2938 proptest::test_runner::TestRunner::default().run(
2939 &strat,
2940 |(data, period, ma_len, matype, devtype)| {
2941 let params = RviParams {
2942 period: Some(period),
2943 ma_len: Some(ma_len),
2944 matype: Some(matype),
2945 devtype: Some(devtype),
2946 };
2947 let input = RviInput::from_slice(&data, params.clone());
2948
2949 let RviOutput { values: out } = rvi_with_kernel(&input, kernel).unwrap();
2950
2951 let RviOutput { values: ref_out } =
2952 rvi_with_kernel(&input, Kernel::Scalar).unwrap();
2953
2954 let warmup = period.saturating_sub(1) + ma_len.saturating_sub(1);
2955
2956 for i in 0..warmup.min(data.len()) {
2957 prop_assert!(
2958 out[i].is_nan(),
2959 "Expected NaN during warmup at index {}, got {}",
2960 i,
2961 out[i]
2962 );
2963 }
2964
2965 for i in warmup..data.len() {
2966 let y = out[i];
2967 let r = ref_out[i];
2968
2969 if y.is_finite() {
2970 prop_assert!(
2971 y >= -1e-9 && y <= 100.0 + 1e-9,
2972 "RVI out of bounds at idx {}: {} (should be 0-100)",
2973 i,
2974 y
2975 );
2976 }
2977
2978 if !y.is_finite() || !r.is_finite() {
2979 prop_assert!(
2980 y.to_bits() == r.to_bits(),
2981 "finite/NaN mismatch idx {}: {} vs {}",
2982 i,
2983 y,
2984 r
2985 );
2986 } else {
2987 let y_bits = y.to_bits();
2988 let r_bits = r.to_bits();
2989 let ulp_diff: u64 = y_bits.abs_diff(r_bits);
2990
2991 prop_assert!(
2992 (y - r).abs() <= 1e-9 || ulp_diff <= 4,
2993 "Kernel mismatch at idx {}: {} vs {} (ULP={})",
2994 i,
2995 y,
2996 r,
2997 ulp_diff
2998 );
2999 }
3000 }
3001
3002 let is_monotonic_increasing = data.windows(2).all(|w| w[1] >= w[0] - f64::EPSILON);
3003
3004 if is_monotonic_increasing && out.len() > warmup + 10 {
3005 let last_values = &out[out.len().saturating_sub(10)..];
3006 let finite_values: Vec<f64> = last_values
3007 .iter()
3008 .filter(|v| v.is_finite())
3009 .copied()
3010 .collect();
3011
3012 if !finite_values.is_empty() {
3013 let avg_rvi =
3014 finite_values.iter().sum::<f64>() / finite_values.len() as f64;
3015 prop_assert!(
3016 avg_rvi >= 90.0,
3017 "RVI should be high for monotonic increasing data, got avg {}",
3018 avg_rvi
3019 );
3020 }
3021 }
3022
3023 let is_monotonic_decreasing = data.windows(2).all(|w| w[1] <= w[0] + f64::EPSILON);
3024
3025 if is_monotonic_decreasing && out.len() > warmup + 10 {
3026 let last_values = &out[out.len().saturating_sub(10)..];
3027 let finite_values: Vec<f64> = last_values
3028 .iter()
3029 .filter(|v| v.is_finite())
3030 .copied()
3031 .collect();
3032
3033 if !finite_values.is_empty() {
3034 let avg_rvi =
3035 finite_values.iter().sum::<f64>() / finite_values.len() as f64;
3036 prop_assert!(
3037 avg_rvi <= 10.0,
3038 "RVI should be low for monotonic decreasing data, got avg {}",
3039 avg_rvi
3040 );
3041 }
3042 }
3043
3044 let is_constant = data
3045 .windows(2)
3046 .all(|w| (w[0] - w[1]).abs() <= f64::EPSILON * w[0].abs().max(1.0));
3047
3048 if is_constant && out.len() > warmup {
3049 for i in warmup..out.len() {
3050 prop_assert!(
3051 out[i].is_nan(),
3052 "RVI should be NaN for constant data at idx {}, got {}",
3053 i,
3054 out[i]
3055 );
3056 }
3057 }
3058
3059 let mut is_alternating = data.len() >= 4;
3060 if is_alternating {
3061 for i in 1..data.len().saturating_sub(1) {
3062 let diff1 = data[i] - data[i - 1];
3063 let diff2 = data[i + 1] - data[i];
3064
3065 if diff1 * diff2 >= 0.0 && diff1.abs() > f64::EPSILON {
3066 is_alternating = false;
3067 break;
3068 }
3069 }
3070 }
3071
3072 if is_alternating && out.len() > warmup + 10 {
3073 let last_values = &out[out.len().saturating_sub(10)..];
3074 let finite_values: Vec<f64> = last_values
3075 .iter()
3076 .filter(|v| v.is_finite())
3077 .copied()
3078 .collect();
3079
3080 if !finite_values.is_empty() {
3081 let avg_rvi =
3082 finite_values.iter().sum::<f64>() / finite_values.len() as f64;
3083 prop_assert!(
3084 avg_rvi >= 35.0 && avg_rvi <= 65.0,
3085 "RVI should be near 50 for alternating data, got avg {}",
3086 avg_rvi
3087 );
3088 }
3089 }
3090
3091 Ok(())
3092 },
3093 )?;
3094
3095 Ok(())
3096 }
3097
3098 macro_rules! generate_all_rvi_tests {
3099 ($($test_fn:ident),*) => {
3100 paste::paste! {
3101 $(
3102 #[test]
3103 fn [<$test_fn _scalar_f64>]() {
3104 let _ = $test_fn(stringify!([<$test_fn _scalar_f64>]), Kernel::Scalar);
3105 }
3106 )*
3107 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
3108 $(
3109 #[test]
3110 fn [<$test_fn _avx2_f64>]() {
3111 let _ = $test_fn(stringify!([<$test_fn _avx2_f64>]), Kernel::Avx2);
3112 }
3113 #[test]
3114 fn [<$test_fn _avx512_f64>]() {
3115 let _ = $test_fn(stringify!([<$test_fn _avx512_f64>]), Kernel::Avx512);
3116 }
3117 )*
3118 }
3119 }
3120 }
3121
3122 generate_all_rvi_tests!(
3123 check_rvi_partial_params,
3124 check_rvi_default_params,
3125 check_rvi_error_zero_period,
3126 check_rvi_error_zero_ma_len,
3127 check_rvi_error_period_exceeds_data_length,
3128 check_rvi_all_nan_input,
3129 check_rvi_not_enough_valid_data,
3130 check_rvi_example_values,
3131 check_rvi_no_poison
3132 );
3133
3134 #[cfg(feature = "proptest")]
3135 generate_all_rvi_tests!(check_rvi_property);
3136
3137 fn check_batch_default_row(
3138 test: &str,
3139 kernel: Kernel,
3140 ) -> Result<(), Box<dyn std::error::Error>> {
3141 skip_if_unsupported!(kernel, test);
3142 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3143 let c = read_candles_from_csv(file)?;
3144 let output = RviBatchBuilder::new()
3145 .kernel(kernel)
3146 .apply_candles(&c, "close")?;
3147 let def = RviParams::default();
3148 let row = output.values_for(&def).expect("default row missing");
3149 assert_eq!(row.len(), c.close.len());
3150 Ok(())
3151 }
3152
3153 #[cfg(debug_assertions)]
3154 fn check_batch_no_poison(test: &str, kernel: Kernel) -> Result<(), Box<dyn std::error::Error>> {
3155 skip_if_unsupported!(kernel, test);
3156
3157 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3158 let c = read_candles_from_csv(file)?;
3159
3160 let test_configs = vec![
3161 (2, 10, 2, 2, 10, 2),
3162 (5, 25, 5, 10, 30, 5),
3163 (30, 60, 15, 20, 40, 10),
3164 (2, 5, 1, 2, 5, 1),
3165 (10, 20, 5, 50, 100, 25),
3166 (50, 100, 50, 10, 20, 10),
3167 ];
3168
3169 for (cfg_idx, &(p_start, p_end, p_step, m_start, m_end, m_step)) in
3170 test_configs.iter().enumerate()
3171 {
3172 for matype in [0, 1].iter() {
3173 for devtype in [0, 1, 2].iter() {
3174 let output = RviBatchBuilder::new()
3175 .kernel(kernel)
3176 .period_range(p_start, p_end, p_step)
3177 .ma_len_range(m_start, m_end, m_step)
3178 .matype_static(*matype)
3179 .devtype_static(*devtype)
3180 .apply_candles(&c, "close")?;
3181
3182 for (idx, &val) in output.values.iter().enumerate() {
3183 if val.is_nan() {
3184 continue;
3185 }
3186
3187 let bits = val.to_bits();
3188 let row = idx / output.cols;
3189 let col = idx % output.cols;
3190 let combo = &output.combos[row];
3191
3192 if bits == 0x11111111_11111111 {
3193 panic!(
3194 "[{}] Config {} (matype={}, devtype={}): Found alloc_with_nan_prefix poison value {} (0x{:016X}) \
3195 at row {} col {} (flat index {}) with params: {:?}",
3196 test, cfg_idx, matype, devtype, val, bits, row, col, idx, combo
3197 );
3198 }
3199
3200 if bits == 0x22222222_22222222 {
3201 panic!(
3202 "[{}] Config {} (matype={}, devtype={}): Found init_matrix_prefixes poison value {} (0x{:016X}) \
3203 at row {} col {} (flat index {}) with params: {:?}",
3204 test, cfg_idx, matype, devtype, val, bits, row, col, idx, combo
3205 );
3206 }
3207
3208 if bits == 0x33333333_33333333 {
3209 panic!(
3210 "[{}] Config {} (matype={}, devtype={}): Found make_uninit_matrix poison value {} (0x{:016X}) \
3211 at row {} col {} (flat index {}) with params: {:?}",
3212 test, cfg_idx, matype, devtype, val, bits, row, col, idx, combo
3213 );
3214 }
3215 }
3216 }
3217 }
3218 }
3219
3220 Ok(())
3221 }
3222
3223 #[cfg(not(debug_assertions))]
3224 fn check_batch_no_poison(
3225 _test: &str,
3226 _kernel: Kernel,
3227 ) -> Result<(), Box<dyn std::error::Error>> {
3228 Ok(())
3229 }
3230
3231 macro_rules! gen_batch_tests {
3232 ($fn_name:ident) => {
3233 paste::paste! {
3234 #[test] fn [<$fn_name _scalar>]() {
3235 let _ = $fn_name(stringify!([<$fn_name _scalar>]), Kernel::ScalarBatch);
3236 }
3237 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
3238 #[test] fn [<$fn_name _avx2>]() {
3239 let _ = $fn_name(stringify!([<$fn_name _avx2>]), Kernel::Avx2Batch);
3240 }
3241 #[cfg(all(feature = "nightly-avx", target_arch = "x86_64"))]
3242 #[test] fn [<$fn_name _avx512>]() {
3243 let _ = $fn_name(stringify!([<$fn_name _avx512>]), Kernel::Avx512Batch);
3244 }
3245 #[test] fn [<$fn_name _auto_detect>]() {
3246 let _ = $fn_name(stringify!([<$fn_name _auto_detect>]), Kernel::Auto);
3247 }
3248 }
3249 };
3250 }
3251 gen_batch_tests!(check_batch_default_row);
3252 gen_batch_tests!(check_batch_no_poison);
3253
3254 #[test]
3255 fn test_rvi_into_matches_api() -> Result<(), Box<dyn std::error::Error>> {
3256 let file = "src/data/2018-09-01-2024-Bitfinex_Spot-4h.csv";
3257 let candles = read_candles_from_csv(file)?;
3258 let input = RviInput::with_default_candles(&candles);
3259
3260 let baseline = rvi(&input)?.values;
3261
3262 let mut out = vec![0.0f64; candles.close.len()];
3263 #[cfg(not(all(target_arch = "wasm32", feature = "wasm")))]
3264 {
3265 rvi_into(&input, &mut out)?;
3266 }
3267 #[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3268 {
3269 rvi_into_slice(&mut out, &input, Kernel::Auto)?;
3270 }
3271
3272 assert_eq!(baseline.len(), out.len());
3273
3274 #[inline]
3275 fn eq_or_both_nan(a: f64, b: f64) -> bool {
3276 (a.is_nan() && b.is_nan()) || (a - b).abs() <= 1e-12
3277 }
3278
3279 for i in 0..out.len() {
3280 assert!(
3281 eq_or_both_nan(baseline[i], out[i]),
3282 "mismatch at index {}: baseline={}, into={}",
3283 i,
3284 baseline[i],
3285 out[i]
3286 );
3287 }
3288
3289 Ok(())
3290 }
3291}
3292
3293#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3294#[wasm_bindgen]
3295pub fn rvi_js(
3296 data: &[f64],
3297 period: usize,
3298 ma_len: usize,
3299 matype: usize,
3300 devtype: usize,
3301) -> Result<Vec<f64>, JsValue> {
3302 if data.is_empty() {
3303 return Err(JsValue::from_str("rvi: Empty data provided."));
3304 }
3305
3306 if data.iter().all(|&x| x.is_nan()) {
3307 return Err(JsValue::from_str("rvi: All values are NaN."));
3308 }
3309
3310 if period == 0 || ma_len == 0 {
3311 return Err(JsValue::from_str("rvi: Invalid period"));
3312 }
3313
3314 let first = data.iter().position(|&x| !x.is_nan()).unwrap_or(0);
3315 let needed = period.saturating_sub(1) + ma_len.saturating_sub(1) + 1;
3316 let valid_len = data.len() - first;
3317
3318 if period > data.len() || ma_len > data.len() {
3319 return Err(JsValue::from_str("rvi: Invalid period"));
3320 } else if valid_len < needed {
3321 return Err(JsValue::from_str("rvi: Not enough valid data"));
3322 }
3323
3324 let params = RviParams {
3325 period: Some(period),
3326 ma_len: Some(ma_len),
3327 matype: Some(matype),
3328 devtype: Some(devtype),
3329 };
3330 let input = RviInput::from_slice(data, params);
3331 let mut out = vec![f64::NAN; data.len()];
3332 rvi_into_slice(&mut out, &input, detect_best_kernel())
3333 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3334 Ok(out)
3335}
3336
3337#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3338#[wasm_bindgen]
3339pub fn rvi_alloc(len: usize) -> *mut f64 {
3340 let mut vec = Vec::<f64>::with_capacity(len);
3341 let ptr = vec.as_mut_ptr();
3342 std::mem::forget(vec);
3343 ptr
3344}
3345
3346#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3347#[wasm_bindgen]
3348pub fn rvi_free(ptr: *mut f64, len: usize) {
3349 if !ptr.is_null() {
3350 unsafe {
3351 let _ = Vec::from_raw_parts(ptr, len, len);
3352 }
3353 }
3354}
3355
3356#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3357#[wasm_bindgen]
3358pub fn rvi_into(
3359 in_ptr: *const f64,
3360 out_ptr: *mut f64,
3361 len: usize,
3362 period: usize,
3363 ma_len: usize,
3364 matype: usize,
3365 devtype: usize,
3366) -> Result<(), JsValue> {
3367 if len == 0 {
3368 return Err(JsValue::from_str("rvi_into: len cannot be 0"));
3369 }
3370
3371 unsafe {
3372 let data = std::slice::from_raw_parts(in_ptr, len);
3373 let out = std::slice::from_raw_parts_mut(out_ptr, len);
3374 let params = RviParams {
3375 period: Some(period),
3376 ma_len: Some(ma_len),
3377 matype: Some(matype),
3378 devtype: Some(devtype),
3379 };
3380 let input = RviInput::from_slice(data, params);
3381
3382 if std::ptr::eq(in_ptr, out_ptr) {
3383 let mut tmp = vec![f64::NAN; len];
3384 rvi_into_slice(&mut tmp, &input, detect_best_kernel())
3385 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3386 out.copy_from_slice(&tmp);
3387 } else {
3388 rvi_into_slice(out, &input, detect_best_kernel())
3389 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3390 }
3391 Ok(())
3392 }
3393}
3394
3395#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3396#[derive(Serialize, Deserialize)]
3397pub struct RviBatchConfig {
3398 pub period_range: (usize, usize, usize),
3399 pub ma_len_range: (usize, usize, usize),
3400 pub matype_range: (usize, usize, usize),
3401 pub devtype_range: (usize, usize, usize),
3402}
3403
3404#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3405#[derive(Serialize, Deserialize)]
3406pub struct RviBatchJsOutput {
3407 pub values: Vec<f64>,
3408 pub periods: Vec<usize>,
3409 pub ma_lens: Vec<usize>,
3410 pub matypes: Vec<usize>,
3411 pub devtypes: Vec<usize>,
3412 pub rows: usize,
3413 pub cols: usize,
3414}
3415
3416#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3417#[wasm_bindgen(js_name = rvi_batch)]
3418pub fn rvi_batch_unified_js(data: &[f64], config: JsValue) -> Result<JsValue, JsValue> {
3419 let cfg: RviBatchConfig = serde_wasm_bindgen::from_value(config)
3420 .map_err(|e| JsValue::from_str(&format!("Invalid config: {}", e)))?;
3421
3422 let sweep = RviBatchRange {
3423 period: cfg.period_range,
3424 ma_len: cfg.ma_len_range,
3425 matype: cfg.matype_range,
3426 devtype: cfg.devtype_range,
3427 };
3428
3429 let output = rvi_batch_inner(data, &sweep, detect_best_kernel(), false)
3430 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3431
3432 let js_out = RviBatchJsOutput {
3433 values: output.values,
3434 periods: output.combos.iter().map(|c| c.period.unwrap()).collect(),
3435 ma_lens: output.combos.iter().map(|c| c.ma_len.unwrap()).collect(),
3436 matypes: output.combos.iter().map(|c| c.matype.unwrap()).collect(),
3437 devtypes: output.combos.iter().map(|c| c.devtype.unwrap()).collect(),
3438 rows: output.rows,
3439 cols: output.cols,
3440 };
3441 serde_wasm_bindgen::to_value(&js_out)
3442 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
3443}
3444
3445#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
3446#[wasm_bindgen]
3447pub fn rvi_batch_into(
3448 in_ptr: *const f64,
3449 out_ptr: *mut f64,
3450 len: usize,
3451 p_start: usize,
3452 p_end: usize,
3453 p_step: usize,
3454 m_start: usize,
3455 m_end: usize,
3456 m_step: usize,
3457 t_start: usize,
3458 t_end: usize,
3459 t_step: usize,
3460 d_start: usize,
3461 d_end: usize,
3462 d_step: usize,
3463) -> Result<usize, JsValue> {
3464 if in_ptr.is_null() || out_ptr.is_null() {
3465 return Err(JsValue::from_str("null pointer to rvi_batch_into"));
3466 }
3467 unsafe {
3468 let data = std::slice::from_raw_parts(in_ptr, len);
3469 let sweep = RviBatchRange {
3470 period: (p_start, p_end, p_step),
3471 ma_len: (m_start, m_end, m_step),
3472 matype: (t_start, t_end, t_step),
3473 devtype: (d_start, d_end, d_step),
3474 };
3475 let combos = expand_grid(&sweep).map_err(|e| JsValue::from_str(&e.to_string()))?;
3476 let rows = combos.len();
3477 let cols = len;
3478 let total = rows
3479 .checked_mul(cols)
3480 .ok_or_else(|| JsValue::from_str("rvi_batch_into: rows * cols overflow"))?;
3481 let out = std::slice::from_raw_parts_mut(out_ptr, total);
3482
3483 let simd = match detect_best_batch_kernel() {
3484 Kernel::Avx512Batch => Kernel::Avx512,
3485 Kernel::Avx2Batch => Kernel::Avx2,
3486 _ => Kernel::Scalar,
3487 };
3488 rvi_batch_inner_into(data, &sweep, simd, false, out)
3489 .map_err(|e| JsValue::from_str(&e.to_string()))?;
3490 Ok(rows)
3491 }
3492}
3493
3494#[cfg(feature = "python")]
3495#[pyfunction(name = "rvi")]
3496#[pyo3(signature = (data, period, ma_len, matype, devtype, kernel=None))]
3497pub fn rvi_py<'py>(
3498 py: Python<'py>,
3499 data: numpy::PyReadonlyArray1<'py, f64>,
3500 period: usize,
3501 ma_len: usize,
3502 matype: usize,
3503 devtype: usize,
3504 kernel: Option<&str>,
3505) -> PyResult<Bound<'py, numpy::PyArray1<f64>>> {
3506 use numpy::{IntoPyArray, PyArray1, PyArrayMethods};
3507
3508 let slice_in = data.as_slice()?;
3509 let kern = validate_kernel(kernel, false)?;
3510
3511 let out_arr = unsafe { PyArray1::<f64>::new(py, [slice_in.len()], false) };
3512 let out_slice = unsafe { out_arr.as_slice_mut()? };
3513
3514 let params = RviParams {
3515 period: Some(period),
3516 ma_len: Some(ma_len),
3517 matype: Some(matype),
3518 devtype: Some(devtype),
3519 };
3520 let input = RviInput::from_slice(slice_in, params);
3521
3522 py.allow_threads(|| rvi_into_slice(out_slice, &input, kern))
3523 .map_err(|e| PyValueError::new_err(e.to_string()))?;
3524
3525 Ok(out_arr)
3526}
3527
3528#[cfg(feature = "python")]
3529#[pyfunction(name = "rvi_batch")]
3530#[pyo3(signature = (data, period_range, ma_len_range, matype_range, devtype_range, kernel=None))]
3531pub fn rvi_batch_py<'py>(
3532 py: Python<'py>,
3533 data: numpy::PyReadonlyArray1<'py, f64>,
3534 period_range: (usize, usize, usize),
3535 ma_len_range: (usize, usize, usize),
3536 matype_range: (usize, usize, usize),
3537 devtype_range: (usize, usize, usize),
3538 kernel: Option<&str>,
3539) -> PyResult<Bound<'py, PyDict>> {
3540 use numpy::{IntoPyArray, PyArray1, PyArrayMethods};
3541
3542 let slice_in = data.as_slice()?;
3543
3544 let sweep = RviBatchRange {
3545 period: period_range,
3546 ma_len: ma_len_range,
3547 matype: matype_range,
3548 devtype: devtype_range,
3549 };
3550
3551 let combos = expand_grid(&sweep).map_err(|e| PyValueError::new_err(e.to_string()))?;
3552 let rows = combos.len();
3553 let cols = slice_in.len();
3554 let total = rows
3555 .checked_mul(cols)
3556 .ok_or_else(|| PyValueError::new_err("rvi_batch: rows * cols overflow"))?;
3557
3558 let out_arr = unsafe { PyArray1::<f64>::new(py, [total], false) };
3559 let out_slice = unsafe { out_arr.as_slice_mut()? };
3560
3561 let kern = validate_kernel(kernel, true)?;
3562 let simd = match kern {
3563 Kernel::Auto => detect_best_batch_kernel(),
3564 k => k,
3565 };
3566 let simd = match simd {
3567 Kernel::Avx512Batch => Kernel::Avx512,
3568 Kernel::Avx2Batch => Kernel::Avx2,
3569 Kernel::ScalarBatch => Kernel::Scalar,
3570 _ => Kernel::Scalar,
3571 };
3572
3573 let combos_back = py
3574 .allow_threads(|| rvi_batch_inner_into(slice_in, &sweep, simd, true, out_slice))
3575 .map_err(|e| PyValueError::new_err(e.to_string()))?;
3576
3577 let d = PyDict::new(py);
3578 d.set_item("values", out_arr.reshape((rows, cols))?)?;
3579 d.set_item(
3580 "periods",
3581 combos_back
3582 .iter()
3583 .map(|p| p.period.unwrap() as u64)
3584 .collect::<Vec<_>>()
3585 .into_pyarray(py),
3586 )?;
3587 d.set_item(
3588 "ma_lens",
3589 combos_back
3590 .iter()
3591 .map(|p| p.ma_len.unwrap() as u64)
3592 .collect::<Vec<_>>()
3593 .into_pyarray(py),
3594 )?;
3595 d.set_item(
3596 "matypes",
3597 combos_back
3598 .iter()
3599 .map(|p| p.matype.unwrap() as u64)
3600 .collect::<Vec<_>>()
3601 .into_pyarray(py),
3602 )?;
3603 d.set_item(
3604 "devtypes",
3605 combos_back
3606 .iter()
3607 .map(|p| p.devtype.unwrap() as u64)
3608 .collect::<Vec<_>>()
3609 .into_pyarray(py),
3610 )?;
3611 Ok(d)
3612}
3613
3614#[cfg(feature = "python")]
3615#[pyclass(name = "RviStream")]
3616pub struct RviStreamPy {
3617 stream: RviStream,
3618}
3619
3620#[cfg(feature = "python")]
3621#[pymethods]
3622impl RviStreamPy {
3623 #[new]
3624 fn new(period: usize, ma_len: usize, matype: usize, devtype: usize) -> PyResult<Self> {
3625 let params = RviParams {
3626 period: Some(period),
3627 ma_len: Some(ma_len),
3628 matype: Some(matype),
3629 devtype: Some(devtype),
3630 };
3631 let stream =
3632 RviStream::try_new(params).map_err(|e| PyValueError::new_err(e.to_string()))?;
3633 Ok(RviStreamPy { stream })
3634 }
3635
3636 fn update(&mut self, value: f64) -> Option<f64> {
3637 self.stream.update(value)
3638 }
3639}
3640
3641#[cfg(feature = "python")]
3642pub fn register_rvi_module(m: &Bound<'_, pyo3::types::PyModule>) -> PyResult<()> {
3643 m.add_function(wrap_pyfunction!(rvi_py, m)?)?;
3644 m.add_function(wrap_pyfunction!(rvi_batch_py, m)?)?;
3645 m.add_class::<RviStreamPy>()?;
3646 Ok(())
3647}
3648
3649#[cfg(all(feature = "python", feature = "cuda"))]
3650use crate::cuda::oscillators::CudaRvi;
3651#[cfg(all(feature = "python", feature = "cuda"))]
3652use crate::utilities::dlpack_cuda::{make_device_array_py, DeviceArrayF32Py};
3653
3654#[cfg(all(feature = "python", feature = "cuda"))]
3655#[pyfunction(name = "rvi_cuda_batch_dev")]
3656#[pyo3(signature = (data_f32, period_range, ma_len_range, matype_range, devtype_range, device_id=0))]
3657pub fn rvi_cuda_batch_dev_py<'py>(
3658 py: Python<'py>,
3659 data_f32: numpy::PyReadonlyArray1<'py, f32>,
3660 period_range: (usize, usize, usize),
3661 ma_len_range: (usize, usize, usize),
3662 matype_range: (usize, usize, usize),
3663 devtype_range: (usize, usize, usize),
3664 device_id: usize,
3665) -> PyResult<(DeviceArrayF32Py, Bound<'py, PyDict>)> {
3666 use crate::cuda::cuda_available;
3667 use numpy::{IntoPyArray, PyArrayMethods};
3668 if !cuda_available() {
3669 return Err(PyValueError::new_err("CUDA not available"));
3670 }
3671 let d = data_f32.as_slice()?;
3672 let sweep = RviBatchRange {
3673 period: period_range,
3674 ma_len: ma_len_range,
3675 matype: matype_range,
3676 devtype: devtype_range,
3677 };
3678 let (inner, combos) = py.allow_threads(|| {
3679 let cuda = CudaRvi::new(device_id).map_err(|e| PyValueError::new_err(e.to_string()))?;
3680 cuda.rvi_batch_dev(d, &sweep)
3681 .map_err(|e| PyValueError::new_err(e.to_string()))
3682 })?;
3683 let dict = PyDict::new(py);
3684 dict.set_item(
3685 "periods",
3686 combos
3687 .iter()
3688 .map(|p| p.period.unwrap() as u64)
3689 .collect::<Vec<_>>()
3690 .into_pyarray(py),
3691 )?;
3692 dict.set_item(
3693 "ma_lens",
3694 combos
3695 .iter()
3696 .map(|p| p.ma_len.unwrap() as u64)
3697 .collect::<Vec<_>>()
3698 .into_pyarray(py),
3699 )?;
3700 dict.set_item(
3701 "matypes",
3702 combos
3703 .iter()
3704 .map(|p| p.matype.unwrap() as u64)
3705 .collect::<Vec<_>>()
3706 .into_pyarray(py),
3707 )?;
3708 dict.set_item(
3709 "devtypes",
3710 combos
3711 .iter()
3712 .map(|p| p.devtype.unwrap() as u64)
3713 .collect::<Vec<_>>()
3714 .into_pyarray(py),
3715 )?;
3716 let handle = make_device_array_py(device_id, inner)?;
3717 Ok((handle, dict))
3718}
3719
3720#[cfg(all(feature = "python", feature = "cuda"))]
3721#[pyfunction(name = "rvi_cuda_many_series_one_param_dev")]
3722#[pyo3(signature = (data_tm_f32, cols, rows, period, ma_len, matype, devtype, device_id=0))]
3723pub fn rvi_cuda_many_series_one_param_dev_py(
3724 py: Python<'_>,
3725 data_tm_f32: numpy::PyReadonlyArray1<'_, f32>,
3726 cols: usize,
3727 rows: usize,
3728 period: usize,
3729 ma_len: usize,
3730 matype: usize,
3731 devtype: usize,
3732 device_id: usize,
3733) -> PyResult<DeviceArrayF32Py> {
3734 use crate::cuda::cuda_available;
3735 if !cuda_available() {
3736 return Err(PyValueError::new_err("CUDA not available"));
3737 }
3738 let tm = data_tm_f32.as_slice()?;
3739 let params = RviParams {
3740 period: Some(period),
3741 ma_len: Some(ma_len),
3742 matype: Some(matype),
3743 devtype: Some(devtype),
3744 };
3745 let inner = py.allow_threads(|| {
3746 let cuda = CudaRvi::new(device_id).map_err(|e| PyValueError::new_err(e.to_string()))?;
3747 cuda.rvi_many_series_one_param_time_major_dev(tm, cols, rows, ¶ms)
3748 .map_err(|e| PyValueError::new_err(e.to_string()))
3749 })?;
3750 Ok(make_device_array_py(device_id, inner)?)
3751}