1#[cfg(not(feature = "std"))]
43use alloc::{boxed::Box, vec::Vec};
44
45use crate::Scalar;
46
47pub trait Signal<S: Scalar>: Send + Sync {
52 fn eval(&self, t: S) -> S;
54
55 fn eval_derivative(&self, t: S) -> S {
58 let h = S::from_f64(1e-8);
59 (self.eval(t + h) - self.eval(t - h)) / (S::TWO * h)
60 }
61}
62
63#[derive(Clone, Debug)]
81pub struct Harmonic<S: Scalar> {
82 pub amplitude: S,
83 pub frequency: S,
84 pub phase: S,
85}
86
87impl<S: Scalar> Harmonic<S> {
88 pub fn new(amplitude: S, frequency: S, phase: S) -> Self {
95 Self {
96 amplitude,
97 frequency,
98 phase,
99 }
100 }
101}
102
103impl<S: Scalar> Signal<S> for Harmonic<S> {
104 #[inline]
105 fn eval(&self, t: S) -> S {
106 let omega_t = S::TWO * S::PI * self.frequency * t + self.phase;
107 self.amplitude * omega_t.sin()
108 }
109
110 #[inline]
111 fn eval_derivative(&self, t: S) -> S {
112 let omega = S::TWO * S::PI * self.frequency;
113 let omega_t = omega * t + self.phase;
114 self.amplitude * omega * omega_t.cos()
115 }
116}
117
118#[derive(Clone, Debug)]
137pub struct Step<S: Scalar> {
138 pub magnitude: S,
139 pub time: S,
140 pub smoothing: Option<S>,
142}
143
144impl<S: Scalar> Step<S> {
145 pub fn new(magnitude: S, time: S) -> Self {
147 Self {
148 magnitude,
149 time,
150 smoothing: None,
151 }
152 }
153
154 pub fn smooth(magnitude: S, time: S, smoothing: S) -> Self {
159 Self {
160 magnitude,
161 time,
162 smoothing: Some(smoothing),
163 }
164 }
165}
166
167impl<S: Scalar> Signal<S> for Step<S> {
168 fn eval(&self, t: S) -> S {
169 match self.smoothing {
170 None => {
171 if t >= self.time {
172 self.magnitude
173 } else {
174 S::ZERO
175 }
176 }
177 Some(k) => {
178 let x = (t - self.time) / k;
180 self.magnitude * S::HALF * (S::ONE + x.tanh())
181 }
182 }
183 }
184}
185
186#[derive(Clone, Debug)]
206pub struct Ramp<S: Scalar> {
207 pub start_value: S,
208 pub end_value: S,
209 pub start_time: S,
210 pub end_time: S,
211}
212
213impl<S: Scalar> Ramp<S> {
214 pub fn new(start_value: S, end_value: S, start_time: S, end_time: S) -> Self {
216 Self {
217 start_value,
218 end_value,
219 start_time,
220 end_time,
221 }
222 }
223
224 pub fn from_rate(rate: S, start_time: S, end_time: S) -> Self {
226 let duration = end_time - start_time;
227 Self {
228 start_value: S::ZERO,
229 end_value: rate * duration,
230 start_time,
231 end_time,
232 }
233 }
234}
235
236impl<S: Scalar> Signal<S> for Ramp<S> {
237 fn eval(&self, t: S) -> S {
238 if t <= self.start_time {
239 self.start_value
240 } else if t >= self.end_time {
241 self.end_value
242 } else {
243 let alpha = (t - self.start_time) / (self.end_time - self.start_time);
244 self.start_value + alpha * (self.end_value - self.start_value)
245 }
246 }
247
248 fn eval_derivative(&self, t: S) -> S {
249 if t > self.start_time && t < self.end_time {
250 (self.end_value - self.start_value) / (self.end_time - self.start_time)
251 } else {
252 S::ZERO
253 }
254 }
255}
256
257#[derive(Clone, Debug)]
276pub struct Pulse<S: Scalar> {
277 pub magnitude: S,
278 pub start: S,
279 pub duration: S,
280}
281
282impl<S: Scalar> Pulse<S> {
283 pub fn new(magnitude: S, start: S, duration: S) -> Self {
285 Self {
286 magnitude,
287 start,
288 duration,
289 }
290 }
291}
292
293impl<S: Scalar> Signal<S> for Pulse<S> {
294 fn eval(&self, t: S) -> S {
295 if t >= self.start && t <= self.start + self.duration {
296 self.magnitude
297 } else {
298 S::ZERO
299 }
300 }
301}
302
303#[derive(Clone, Debug)]
322pub struct Chirp<S: Scalar> {
323 pub amplitude: S,
324 pub f0: S, pub f1: S, pub t1: S, }
328
329impl<S: Scalar> Chirp<S> {
330 pub fn new(amplitude: S, f0: S, f1: S, t1: S) -> Self {
332 Self {
333 amplitude,
334 f0,
335 f1,
336 t1,
337 }
338 }
339}
340
341impl<S: Scalar> Signal<S> for Chirp<S> {
342 fn eval(&self, t: S) -> S {
343 if t < S::ZERO || t > self.t1 {
344 return S::ZERO;
345 }
346 let k = (self.f1 - self.f0) / self.t1;
349 let phase = S::TWO * S::PI * (self.f0 * t + S::HALF * k * t * t);
350 self.amplitude * phase.sin()
351 }
352}
353
354#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
360pub enum Interpolation {
361 #[default]
363 Linear,
364 Nearest,
366 Cubic,
368}
369
370#[derive(Clone, Debug)]
393pub struct Tabulated<S: Scalar> {
394 pub times: Vec<S>,
395 pub values: Vec<S>,
396 pub interp: Interpolation,
397 spline_d2: Option<Vec<S>>,
400}
401
402impl<S: Scalar> Tabulated<S> {
403 pub fn new(times: Vec<S>, values: Vec<S>, interp: Interpolation) -> Self {
408 assert_eq!(
409 times.len(),
410 values.len(),
411 "times and values must have same length"
412 );
413 assert!(!times.is_empty(), "times must not be empty");
414
415 let spline_d2 = if interp == Interpolation::Cubic && times.len() >= 2 {
417 Some(Self::compute_spline_coefficients(×, &values))
418 } else {
419 None
420 };
421
422 Self {
423 times,
424 values,
425 interp,
426 spline_d2,
427 }
428 }
429
430 pub fn from_pairs(pairs: &[(S, S)], interp: Interpolation) -> Self {
432 let (times, values): (Vec<_>, Vec<_>) = pairs.iter().cloned().unzip();
433 Self::new(times, values, interp)
434 }
435
436 fn compute_spline_coefficients(times: &[S], values: &[S]) -> Vec<S> {
441 let n = times.len();
442 if n < 2 {
443 return vec![S::ZERO; n];
444 }
445 if n == 2 {
446 return vec![S::ZERO, S::ZERO];
448 }
449
450 let mut d2 = vec![S::ZERO; n];
456
457 let mut c_prime = vec![S::ZERO; n - 2]; let mut d_prime = vec![S::ZERO; n - 2]; let mut h = Vec::with_capacity(n - 1);
463 for i in 0..n - 1 {
464 h.push(times[i + 1] - times[i]);
465 }
466
467 for i in 1..n - 1 {
469 let h_prev = h[i - 1];
470 let h_curr = h[i];
471
472 let diag = S::TWO * (h_prev + h_curr);
474
475 let slope_curr = (values[i + 1] - values[i]) / h_curr;
477 let slope_prev = (values[i] - values[i - 1]) / h_prev;
478 let rhs = S::from_f64(6.0) * (slope_curr - slope_prev);
479
480 let idx = i - 1;
481 if idx == 0 {
482 c_prime[idx] = h_curr / diag;
484 d_prime[idx] = rhs / diag;
485 } else {
486 let m = diag - h_prev * c_prime[idx - 1];
487 c_prime[idx] = h_curr / m;
488 d_prime[idx] = (rhs - h_prev * d_prime[idx - 1]) / m;
489 }
490 }
491
492 let last_idx = n - 3;
495 d2[n - 2] = d_prime[last_idx];
496
497 for i in (1..n - 2).rev() {
498 let idx = i - 1;
499 d2[i] = d_prime[idx] - c_prime[idx] * d2[i + 1];
500 }
501
502 d2[0] = S::ZERO;
504 d2[n - 1] = S::ZERO;
505
506 d2
507 }
508
509 pub fn value_at(&self, t: S) -> S {
511 self.interpolate_at(t)
512 }
513
514 fn interpolate_at(&self, t: S) -> S {
516 let n = self.times.len();
517
518 if t <= self.times[0] {
520 return self.values[0];
521 }
522 if t >= self.times[n - 1] {
523 return self.values[n - 1];
524 }
525
526 let i = self.find_interval(t);
527 let t0 = self.times[i];
528 let t1 = self.times[i + 1];
529 let v0 = self.values[i];
530 let v1 = self.values[i + 1];
531
532 match self.interp {
533 Interpolation::Nearest => {
534 if t - t0 < t1 - t {
535 v0
536 } else {
537 v1
538 }
539 }
540 Interpolation::Linear => {
541 let alpha = (t - t0) / (t1 - t0);
542 v0 + alpha * (v1 - v0)
543 }
544 Interpolation::Cubic => self.cubic_spline_interp(t, i),
545 }
546 }
547
548 fn cubic_spline_interp(&self, t: S, i: usize) -> S {
550 let d2 = match &self.spline_d2 {
551 Some(d2) => d2,
552 None => {
553 let t0 = self.times[i];
555 let t1 = self.times[i + 1];
556 let alpha = (t - t0) / (t1 - t0);
557 return self.values[i] + alpha * (self.values[i + 1] - self.values[i]);
558 }
559 };
560
561 let t0 = self.times[i];
562 let t1 = self.times[i + 1];
563 let h = t1 - t0;
564 let y0 = self.values[i];
565 let y1 = self.values[i + 1];
566 let d2_0 = d2[i];
567 let d2_1 = d2[i + 1];
568
569 let u = (t - t0) / h;
574 let one_minus_u = S::ONE - u;
575
576 let u3 = u * u * u;
578 let omu3 = one_minus_u * one_minus_u * one_minus_u;
579
580 let h2_6 = h * h / S::from_f64(6.0);
582
583 one_minus_u * y0 + u * y1 + h2_6 * ((u3 - u) * d2_1 + (omu3 - one_minus_u) * d2_0)
585 }
586
587 fn find_interval(&self, t: S) -> usize {
589 let n = self.times.len();
590 if t <= self.times[0] {
591 return 0;
592 }
593 if t >= self.times[n - 1] {
594 return n - 2;
595 }
596 let mut lo = 0;
598 let mut hi = n - 1;
599 while hi - lo > 1 {
600 let mid = (lo + hi) / 2;
601 if t < self.times[mid] {
602 hi = mid;
603 } else {
604 lo = mid;
605 }
606 }
607 lo
608 }
609}
610
611impl<S: Scalar> Signal<S> for Tabulated<S> {
612 fn eval(&self, t: S) -> S {
613 self.interpolate_at(t)
614 }
615}
616
617#[cfg(feature = "std")]
649#[derive(Clone, Debug)]
650pub struct FromFile<S: Scalar> {
651 tabulated: Tabulated<S>,
653 path: std::string::String,
655}
656
657#[cfg(feature = "std")]
658impl<S: Scalar + std::str::FromStr> FromFile<S> {
659 pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<Self, std::string::String> {
668 Self::load_with_interpolation(path, Interpolation::Linear)
669 }
670
671 pub fn load_with_interpolation<P: AsRef<std::path::Path>>(
673 path: P,
674 interp: Interpolation,
675 ) -> Result<Self, std::string::String> {
676 use std::fs::File;
677 use std::io::{BufRead, BufReader};
678
679 let path_ref = path.as_ref();
680 let file = File::open(path_ref)
681 .map_err(|e| format!("Failed to open file '{}': {}", path_ref.display(), e))?;
682
683 let reader = BufReader::new(file);
684 let mut times = Vec::new();
685 let mut values = Vec::new();
686
687 for (line_num, line_result) in reader.lines().enumerate() {
688 let line =
689 line_result.map_err(|e| format!("Failed to read line {}: {}", line_num + 1, e))?;
690
691 let line = line.trim();
692
693 if line.is_empty() || line.starts_with('#') {
695 continue;
696 }
697
698 let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
700 if parts.len() < 2 {
701 return Err(format!(
702 "Line {} has fewer than 2 columns: '{}'",
703 line_num + 1,
704 line
705 ));
706 }
707
708 let t: S = parts[0].parse().map_err(|_| {
709 format!(
710 "Failed to parse time on line {}: '{}'",
711 line_num + 1,
712 parts[0]
713 )
714 })?;
715 let v: S = parts[1].parse().map_err(|_| {
716 format!(
717 "Failed to parse value on line {}: '{}'",
718 line_num + 1,
719 parts[1]
720 )
721 })?;
722
723 times.push(t);
724 values.push(v);
725 }
726
727 if times.is_empty() {
728 return Err("No data found in file".to_string());
729 }
730
731 let tabulated = Tabulated::new(times, values, interp);
732 let path_string = path_ref.to_string_lossy().into_owned();
733
734 Ok(Self {
735 tabulated,
736 path: path_string,
737 })
738 }
739
740 pub fn from_csv_string(
742 content: &str,
743 interp: Interpolation,
744 ) -> Result<Self, std::string::String> {
745 let mut times = Vec::new();
746 let mut values = Vec::new();
747
748 for (line_num, line) in content.lines().enumerate() {
749 let line = line.trim();
750
751 if line.is_empty() || line.starts_with('#') {
753 continue;
754 }
755
756 let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
758 if parts.len() < 2 {
759 return Err(format!(
760 "Line {} has fewer than 2 columns: '{}'",
761 line_num + 1,
762 line
763 ));
764 }
765
766 let t: S = parts[0].parse().map_err(|_| {
767 format!(
768 "Failed to parse time on line {}: '{}'",
769 line_num + 1,
770 parts[0]
771 )
772 })?;
773 let v: S = parts[1].parse().map_err(|_| {
774 format!(
775 "Failed to parse value on line {}: '{}'",
776 line_num + 1,
777 parts[1]
778 )
779 })?;
780
781 times.push(t);
782 values.push(v);
783 }
784
785 if times.is_empty() {
786 return Err("No data found in content".to_string());
787 }
788
789 let tabulated = Tabulated::new(times, values, interp);
790
791 Ok(Self {
792 tabulated,
793 path: "<from_string>".to_string(),
794 })
795 }
796
797 pub fn len(&self) -> usize {
799 self.tabulated.times.len()
800 }
801
802 pub fn is_empty(&self) -> bool {
804 self.tabulated.times.is_empty()
805 }
806
807 pub fn time_range(&self) -> (S, S) {
809 let times = &self.tabulated.times;
810 (times[0], times[times.len() - 1])
811 }
812
813 pub fn path(&self) -> &str {
815 &self.path
816 }
817
818 pub fn as_tabulated(&self) -> &Tabulated<S> {
820 &self.tabulated
821 }
822}
823
824#[cfg(feature = "std")]
825impl<S: Scalar + std::str::FromStr> Signal<S> for FromFile<S> {
826 fn eval(&self, t: S) -> S {
827 self.tabulated.eval(t)
828 }
829
830 fn eval_derivative(&self, t: S) -> S {
831 self.tabulated.eval_derivative(t)
832 }
833}
834
835#[derive(Clone, Debug)]
857pub struct Piecewise<S: Scalar> {
858 segments: Vec<(S, S)>,
860 default: S,
862}
863
864impl<S: Scalar> Piecewise<S> {
865 pub fn new(segments: Vec<(S, S)>, default: S) -> Self {
871 Self { segments, default }
872 }
873}
874
875impl<S: Scalar> Signal<S> for Piecewise<S> {
876 fn eval(&self, t: S) -> S {
877 for &(end_time, value) in &self.segments {
878 if t < end_time {
879 return value;
880 }
881 }
882 self.default
883 }
884}
885
886#[derive(Clone, Debug)]
892pub struct Sum<S: Scalar, A: Signal<S>, B: Signal<S>> {
893 pub a: A,
894 pub b: B,
895 _marker: core::marker::PhantomData<S>,
896}
897
898impl<S: Scalar, A: Signal<S>, B: Signal<S>> Sum<S, A, B> {
899 pub fn new(a: A, b: B) -> Self {
900 Self {
901 a,
902 b,
903 _marker: core::marker::PhantomData,
904 }
905 }
906}
907
908impl<S: Scalar, A: Signal<S>, B: Signal<S>> Signal<S> for Sum<S, A, B> {
909 #[inline]
910 fn eval(&self, t: S) -> S {
911 self.a.eval(t) + self.b.eval(t)
912 }
913
914 #[inline]
915 fn eval_derivative(&self, t: S) -> S {
916 self.a.eval_derivative(t) + self.b.eval_derivative(t)
917 }
918}
919
920#[derive(Clone, Debug)]
922pub struct Product<S: Scalar, A: Signal<S>, B: Signal<S>> {
923 pub a: A,
924 pub b: B,
925 _marker: core::marker::PhantomData<S>,
926}
927
928impl<S: Scalar, A: Signal<S>, B: Signal<S>> Product<S, A, B> {
929 pub fn new(a: A, b: B) -> Self {
930 Self {
931 a,
932 b,
933 _marker: core::marker::PhantomData,
934 }
935 }
936}
937
938impl<S: Scalar, A: Signal<S>, B: Signal<S>> Signal<S> for Product<S, A, B> {
939 #[inline]
940 fn eval(&self, t: S) -> S {
941 self.a.eval(t) * self.b.eval(t)
942 }
943
944 #[inline]
945 fn eval_derivative(&self, t: S) -> S {
946 self.a.eval_derivative(t) * self.b.eval(t) + self.a.eval(t) * self.b.eval_derivative(t)
948 }
949}
950
951#[derive(Clone, Debug)]
953pub struct Scaled<S: Scalar, Inner: Signal<S>> {
954 pub scale: S,
955 pub inner: Inner,
956}
957
958impl<S: Scalar, Inner: Signal<S>> Scaled<S, Inner> {
959 pub fn new(scale: S, inner: Inner) -> Self {
960 Self { scale, inner }
961 }
962}
963
964impl<S: Scalar, Inner: Signal<S>> Signal<S> for Scaled<S, Inner> {
965 #[inline]
966 fn eval(&self, t: S) -> S {
967 self.scale * self.inner.eval(t)
968 }
969
970 #[inline]
971 fn eval_derivative(&self, t: S) -> S {
972 self.scale * self.inner.eval_derivative(t)
973 }
974}
975
976#[derive(Clone, Debug)]
982pub struct Constant<S: Scalar> {
983 pub value: S,
984}
985
986impl<S: Scalar> Constant<S> {
987 pub fn new(value: S) -> Self {
988 Self { value }
989 }
990}
991
992impl<S: Scalar> Signal<S> for Constant<S> {
993 #[inline]
994 fn eval(&self, _t: S) -> S {
995 self.value
996 }
997
998 #[inline]
999 fn eval_derivative(&self, _t: S) -> S {
1000 S::ZERO
1001 }
1002}
1003
1004#[derive(Clone, Debug, Default)]
1010pub struct Zero<S: Scalar> {
1011 _marker: core::marker::PhantomData<S>,
1012}
1013
1014impl<S: Scalar> Zero<S> {
1015 pub fn new() -> Self {
1016 Self {
1017 _marker: core::marker::PhantomData,
1018 }
1019 }
1020}
1021
1022impl<S: Scalar> Signal<S> for Zero<S> {
1023 #[inline]
1024 fn eval(&self, _t: S) -> S {
1025 S::ZERO
1026 }
1027
1028 #[inline]
1029 fn eval_derivative(&self, _t: S) -> S {
1030 S::ZERO
1031 }
1032}
1033
1034impl<S: Scalar> Signal<S> for Box<dyn Signal<S>> {
1039 fn eval(&self, t: S) -> S {
1040 (**self).eval(t)
1041 }
1042
1043 fn eval_derivative(&self, t: S) -> S {
1044 (**self).eval_derivative(t)
1045 }
1046}
1047
1048#[cfg(test)]
1053mod tests {
1054 use super::*;
1055
1056 const TOL: f64 = 1e-10;
1057
1058 #[test]
1059 fn test_harmonic() {
1060 let h = Harmonic::new(2.0, 1.0, 0.0); assert!(h.eval(0.0).abs() < TOL);
1064
1065 assert!((h.eval(0.25) - 2.0).abs() < TOL);
1067
1068 assert!(h.eval(0.5).abs() < TOL);
1070
1071 assert!((h.eval(0.75) + 2.0).abs() < TOL);
1073 }
1074
1075 #[test]
1076 fn test_harmonic_derivative() {
1077 let h = Harmonic::new(1.0, 1.0, 0.0); let deriv = h.eval_derivative(0.0);
1082 assert!((deriv - 2.0 * core::f64::consts::PI).abs() < TOL);
1083 }
1084
1085 #[test]
1086 fn test_step_sharp() {
1087 let s = Step::new(1.0, 2.0);
1088
1089 assert!(s.eval(1.0).abs() < TOL);
1090 assert!(s.eval(1.999).abs() < TOL);
1091 assert!((s.eval(2.0) - 1.0).abs() < TOL);
1092 assert!((s.eval(3.0) - 1.0).abs() < TOL);
1093 }
1094
1095 #[test]
1096 fn test_step_smooth() {
1097 let s = Step::smooth(1.0, 2.0, 0.1);
1098
1099 assert!(s.eval(0.0).abs() < 0.01);
1101
1102 assert!((s.eval(2.0) - 0.5).abs() < TOL);
1104
1105 assert!((s.eval(4.0) - 1.0).abs() < 0.01);
1107 }
1108
1109 #[test]
1110 fn test_ramp() {
1111 let r = Ramp::new(0.0, 2.0, 1.0, 3.0);
1112
1113 assert!(r.eval(0.5).abs() < TOL);
1115
1116 assert!(r.eval(1.0).abs() < TOL);
1118
1119 assert!((r.eval(2.0) - 1.0).abs() < TOL);
1121
1122 assert!((r.eval(3.0) - 2.0).abs() < TOL);
1124
1125 assert!((r.eval(4.0) - 2.0).abs() < TOL);
1127 }
1128
1129 #[test]
1130 fn test_ramp_derivative() {
1131 let r = Ramp::new(0.0, 4.0, 1.0, 3.0); assert!((r.eval_derivative(2.0) - 2.0).abs() < TOL);
1135
1136 assert!(r.eval_derivative(0.5).abs() < TOL);
1138 assert!(r.eval_derivative(4.0).abs() < TOL);
1139 }
1140
1141 #[test]
1142 fn test_pulse() {
1143 let p = Pulse::new(5.0, 1.0, 2.0);
1144
1145 assert!(p.eval(0.5).abs() < TOL);
1147
1148 assert!((p.eval(1.0) - 5.0).abs() < TOL);
1150 assert!((p.eval(2.0) - 5.0).abs() < TOL);
1151 assert!((p.eval(3.0) - 5.0).abs() < TOL);
1152
1153 assert!(p.eval(3.5).abs() < TOL);
1155 }
1156
1157 #[test]
1158 fn test_chirp() {
1159 let c = Chirp::new(1.0, 1.0, 10.0, 5.0);
1160
1161 assert!(c.eval(-1.0).abs() < TOL);
1163 assert!(c.eval(6.0).abs() < TOL);
1164
1165 assert!(c.eval(2.5).abs() <= 1.0 + TOL);
1167
1168 assert!(c.eval(0.0).abs() < TOL);
1170 }
1171
1172 #[test]
1173 fn test_tabulated_linear() {
1174 let times = vec![0.0, 1.0, 2.0, 3.0];
1175 let values = vec![0.0, 2.0, 1.0, 3.0];
1176 let t = Tabulated::new(times, values, Interpolation::Linear);
1177
1178 assert!(t.eval(0.0).abs() < TOL);
1180 assert!((t.eval(1.0) - 2.0).abs() < TOL);
1181 assert!((t.eval(2.0) - 1.0).abs() < TOL);
1182 assert!((t.eval(3.0) - 3.0).abs() < TOL);
1183
1184 assert!((t.eval(0.5) - 1.0).abs() < TOL); assert!((t.eval(1.5) - 1.5).abs() < TOL); assert!(t.eval(-1.0).abs() < TOL);
1190 assert!((t.eval(5.0) - 3.0).abs() < TOL);
1191 }
1192
1193 #[test]
1194 fn test_tabulated_nearest() {
1195 let times = vec![0.0, 1.0, 2.0];
1196 let values = vec![0.0, 1.0, 2.0];
1197 let t = Tabulated::new(times, values, Interpolation::Nearest);
1198
1199 assert!(t.eval(0.3).abs() < TOL);
1201
1202 assert!((t.eval(0.6) - 1.0).abs() < TOL);
1204 assert!((t.eval(1.4) - 1.0).abs() < TOL);
1205
1206 assert!((t.eval(1.6) - 2.0).abs() < TOL);
1208 }
1209
1210 #[test]
1211 fn test_tabulated_cubic_spline() {
1212 let times = vec![0.0, 1.0, 2.0, 3.0, 4.0];
1214 let values = vec![0.0, 1.0, 0.0, 1.0, 0.0];
1215 let t = Tabulated::new(times.clone(), values.clone(), Interpolation::Cubic);
1216
1217 for (i, (&ti, &vi)) in times.iter().zip(values.iter()).enumerate() {
1219 let y = t.value_at(ti);
1220 assert!(
1221 (y - vi).abs() < 1e-10,
1222 "Failed at point {}: expected {}, got {}",
1223 i,
1224 vi,
1225 y
1226 );
1227 }
1228
1229 let mid_12 = t.value_at(1.5);
1232 assert!(
1233 mid_12 < 1.0 && mid_12 > -0.5,
1234 "Cubic spline midpoint 1.5 out of range: {}",
1235 mid_12
1236 );
1237
1238 let eps = 0.001;
1241 let deriv_left = (t.value_at(1.5) - t.value_at(1.5 - eps)) / eps;
1242 let deriv_right = (t.value_at(1.5 + eps) - t.value_at(1.5)) / eps;
1243 assert!(
1244 (deriv_left - deriv_right).abs() < 0.1,
1245 "Derivative discontinuity at 1.5: left={}, right={}",
1246 deriv_left,
1247 deriv_right
1248 );
1249 }
1250
1251 #[test]
1252 fn test_tabulated_cubic_vs_linear() {
1253 let times = vec![0.0, 1.0, 2.0, 3.0];
1255 let values = vec![0.0, 1.0, 0.5, 1.5];
1256
1257 let linear = Tabulated::new(times.clone(), values.clone(), Interpolation::Linear);
1258 let cubic = Tabulated::new(times.clone(), values.clone(), Interpolation::Cubic);
1259
1260 for (&ti, &vi) in times.iter().zip(values.iter()) {
1262 assert!((linear.value_at(ti) - vi).abs() < 1e-10);
1263 assert!((cubic.value_at(ti) - vi).abs() < 1e-10);
1264 }
1265
1266 let lin_mid = linear.value_at(1.5);
1269 let cub_mid = cubic.value_at(1.5);
1270
1271 assert!((lin_mid - 0.75).abs() < 1e-10);
1273
1274 assert!(
1276 cub_mid > 0.0 && cub_mid < 2.0,
1277 "Cubic value {} out of reasonable range",
1278 cub_mid
1279 );
1280 }
1281
1282 #[test]
1283 fn test_piecewise() {
1284 let p = Piecewise::new(vec![(1.0, 10.0), (3.0, 20.0), (5.0, 30.0)], 0.0);
1285
1286 assert!((p.eval(0.5) - 10.0).abs() < TOL);
1287 assert!((p.eval(2.0) - 20.0).abs() < TOL);
1288 assert!((p.eval(4.0) - 30.0).abs() < TOL);
1289 assert!(p.eval(6.0).abs() < TOL);
1290 }
1291
1292 #[test]
1293 fn test_sum() {
1294 let a = Constant::new(3.0);
1295 let b = Constant::new(5.0);
1296 let sum = Sum::new(a, b);
1297
1298 assert!((sum.eval(0.0) - 8.0).abs() < TOL);
1299 assert!((sum.eval(100.0) - 8.0).abs() < TOL);
1300 }
1301
1302 #[test]
1303 fn test_product() {
1304 let a = Constant::new(3.0);
1305 let b = Constant::new(5.0);
1306 let prod = Product::new(a, b);
1307
1308 assert!((prod.eval(0.0) - 15.0).abs() < TOL);
1309 }
1310
1311 #[test]
1312 fn test_scaled() {
1313 let h = Harmonic::new(1.0, 1.0, 0.0);
1314 let scaled = Scaled::new(2.0, h);
1315
1316 assert!((scaled.eval(0.25) - 2.0).abs() < TOL);
1318 }
1319
1320 #[test]
1321 fn test_constant_and_zero() {
1322 let c = Constant::new(42.0);
1323 let z: Zero<f64> = Zero::new();
1324
1325 assert!((c.eval(0.0) - 42.0).abs() < TOL);
1326 assert!((c.eval(1000.0) - 42.0).abs() < TOL);
1327 assert!(c.eval_derivative(0.0).abs() < TOL);
1328
1329 assert!(z.eval(0.0).abs() < TOL);
1330 assert!(z.eval(1000.0).abs() < TOL);
1331 }
1332
1333 #[test]
1334 fn test_composite_signals() {
1335 let harmonic = Harmonic::new(2.0, 1.0, 0.0);
1337 let step = Step::new(1.0, 0.5);
1338 let composite = Sum::new(harmonic, step);
1339
1340 assert!(composite.eval(0.0).abs() < TOL);
1342
1343 assert!((composite.eval(0.25) - 2.0).abs() < TOL);
1345
1346 assert!((composite.eval(0.75) + 1.0).abs() < TOL);
1348 }
1349
1350 #[cfg(feature = "std")]
1351 #[test]
1352 fn test_from_file_csv_string() {
1353 let csv = r#"
1354# Test data
13550.0, 0.0
13561.0, 2.0
13572.0, 1.0
13583.0, 3.0
1359"#;
1360 let signal: super::FromFile<f64> =
1361 super::FromFile::from_csv_string(csv, Interpolation::Linear).unwrap();
1362
1363 assert_eq!(signal.len(), 4);
1364 assert!(!signal.is_empty());
1365
1366 let (t_min, t_max) = signal.time_range();
1367 assert!(t_min.abs() < TOL);
1368 assert!((t_max - 3.0).abs() < TOL);
1369
1370 assert!(signal.eval(0.0).abs() < TOL);
1372 assert!((signal.eval(1.0) - 2.0).abs() < TOL);
1373 assert!((signal.eval(0.5) - 1.0).abs() < TOL); }
1375
1376 #[cfg(feature = "std")]
1377 #[test]
1378 fn test_from_file_empty_error() {
1379 let csv = "# Only comments\n# No data\n";
1380 let result: Result<super::FromFile<f64>, _> =
1381 super::FromFile::from_csv_string(csv, Interpolation::Linear);
1382 assert!(result.is_err());
1383 }
1384
1385 #[cfg(feature = "std")]
1386 #[test]
1387 fn test_from_file_parse_error() {
1388 let csv = "0.0, abc\n"; let result: Result<super::FromFile<f64>, _> =
1390 super::FromFile::from_csv_string(csv, Interpolation::Linear);
1391 assert!(result.is_err());
1392 }
1393
1394 #[cfg(feature = "std")]
1395 #[test]
1396 fn test_from_file_too_few_columns() {
1397 let csv = "0.0\n"; let result: Result<super::FromFile<f64>, _> =
1399 super::FromFile::from_csv_string(csv, Interpolation::Linear);
1400 assert!(result.is_err());
1401 }
1402}