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 {
68 let h = S::EPSILON.cbrt() * (S::ONE + t.abs());
69 (self.eval(t + h) - self.eval(t - h)) / (S::TWO * h)
70 }
71}
72
73#[derive(Clone, Debug)]
91pub struct Harmonic<S: Scalar> {
92 pub amplitude: S,
93 pub frequency: S,
94 pub phase: S,
95}
96
97impl<S: Scalar> Harmonic<S> {
98 pub fn new(amplitude: S, frequency: S, phase: S) -> Self {
105 Self {
106 amplitude,
107 frequency,
108 phase,
109 }
110 }
111}
112
113impl<S: Scalar> Signal<S> for Harmonic<S> {
114 #[inline]
115 fn eval(&self, t: S) -> S {
116 let omega_t = S::TWO * S::PI * self.frequency * t + self.phase;
117 self.amplitude * omega_t.sin()
118 }
119
120 #[inline]
121 fn eval_derivative(&self, t: S) -> S {
122 let omega = S::TWO * S::PI * self.frequency;
123 let omega_t = omega * t + self.phase;
124 self.amplitude * omega * omega_t.cos()
125 }
126}
127
128#[derive(Clone, Debug)]
147pub struct Step<S: Scalar> {
148 pub magnitude: S,
149 pub time: S,
150 pub smoothing: Option<S>,
152}
153
154impl<S: Scalar> Step<S> {
155 pub fn new(magnitude: S, time: S) -> Self {
157 Self {
158 magnitude,
159 time,
160 smoothing: None,
161 }
162 }
163
164 pub fn smooth(magnitude: S, time: S, smoothing: S) -> Self {
169 Self {
170 magnitude,
171 time,
172 smoothing: Some(smoothing),
173 }
174 }
175}
176
177impl<S: Scalar> Signal<S> for Step<S> {
178 fn eval(&self, t: S) -> S {
179 match self.smoothing {
180 None => {
181 if t >= self.time {
182 self.magnitude
183 } else {
184 S::ZERO
185 }
186 }
187 Some(k) => {
188 let x = (t - self.time) / k;
190 self.magnitude * S::HALF * (S::ONE + x.tanh())
191 }
192 }
193 }
194}
195
196#[derive(Clone, Debug)]
216pub struct Ramp<S: Scalar> {
217 pub start_value: S,
218 pub end_value: S,
219 pub start_time: S,
220 pub end_time: S,
221}
222
223impl<S: Scalar> Ramp<S> {
224 pub fn new(start_value: S, end_value: S, start_time: S, end_time: S) -> Self {
226 Self {
227 start_value,
228 end_value,
229 start_time,
230 end_time,
231 }
232 }
233
234 pub fn from_rate(rate: S, start_time: S, end_time: S) -> Self {
236 let duration = end_time - start_time;
237 Self {
238 start_value: S::ZERO,
239 end_value: rate * duration,
240 start_time,
241 end_time,
242 }
243 }
244}
245
246impl<S: Scalar> Signal<S> for Ramp<S> {
247 fn eval(&self, t: S) -> S {
248 if t <= self.start_time {
249 self.start_value
250 } else if t >= self.end_time {
251 self.end_value
252 } else {
253 let alpha = (t - self.start_time) / (self.end_time - self.start_time);
254 self.start_value + alpha * (self.end_value - self.start_value)
255 }
256 }
257
258 fn eval_derivative(&self, t: S) -> S {
259 if t > self.start_time && t < self.end_time {
260 (self.end_value - self.start_value) / (self.end_time - self.start_time)
261 } else {
262 S::ZERO
263 }
264 }
265}
266
267#[derive(Clone, Debug)]
286pub struct Pulse<S: Scalar> {
287 pub magnitude: S,
288 pub start: S,
289 pub duration: S,
290}
291
292impl<S: Scalar> Pulse<S> {
293 pub fn new(magnitude: S, start: S, duration: S) -> Self {
295 Self {
296 magnitude,
297 start,
298 duration,
299 }
300 }
301}
302
303impl<S: Scalar> Signal<S> for Pulse<S> {
304 fn eval(&self, t: S) -> S {
305 if t >= self.start && t <= self.start + self.duration {
306 self.magnitude
307 } else {
308 S::ZERO
309 }
310 }
311}
312
313#[derive(Clone, Debug)]
332pub struct Chirp<S: Scalar> {
333 pub amplitude: S,
334 pub f0: S, pub f1: S, pub t1: S, }
338
339impl<S: Scalar> Chirp<S> {
340 pub fn new(amplitude: S, f0: S, f1: S, t1: S) -> Self {
342 Self {
343 amplitude,
344 f0,
345 f1,
346 t1,
347 }
348 }
349}
350
351impl<S: Scalar> Signal<S> for Chirp<S> {
352 fn eval(&self, t: S) -> S {
353 if t < S::ZERO || t > self.t1 {
354 return S::ZERO;
355 }
356 let k = (self.f1 - self.f0) / self.t1;
359 let phase = S::TWO * S::PI * (self.f0 * t + S::HALF * k * t * t);
360 self.amplitude * phase.sin()
361 }
362}
363
364#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
370pub enum Interpolation {
371 #[default]
373 Linear,
374 Nearest,
376 Cubic,
378}
379
380#[derive(Clone, Debug)]
403pub struct Tabulated<S: Scalar> {
404 pub times: Vec<S>,
405 pub values: Vec<S>,
406 pub interp: Interpolation,
407 spline_d2: Option<Vec<S>>,
410}
411
412impl<S: Scalar> Tabulated<S> {
413 pub fn new(times: Vec<S>, values: Vec<S>, interp: Interpolation) -> Self {
418 assert_eq!(
419 times.len(),
420 values.len(),
421 "times and values must have same length"
422 );
423 assert!(!times.is_empty(), "times must not be empty");
424
425 let spline_d2 = if interp == Interpolation::Cubic && times.len() >= 2 {
427 Some(Self::compute_spline_coefficients(×, &values))
428 } else {
429 None
430 };
431
432 Self {
433 times,
434 values,
435 interp,
436 spline_d2,
437 }
438 }
439
440 pub fn from_pairs(pairs: &[(S, S)], interp: Interpolation) -> Self {
442 let (times, values): (Vec<_>, Vec<_>) = pairs.iter().cloned().unzip();
443 Self::new(times, values, interp)
444 }
445
446 fn compute_spline_coefficients(times: &[S], values: &[S]) -> Vec<S> {
451 let n = times.len();
452 if n < 2 {
453 return vec![S::ZERO; n];
454 }
455 if n == 2 {
456 return vec![S::ZERO, S::ZERO];
458 }
459
460 let mut d2 = vec![S::ZERO; n];
466
467 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);
473 for i in 0..n - 1 {
474 h.push(times[i + 1] - times[i]);
475 }
476
477 for i in 1..n - 1 {
479 let h_prev = h[i - 1];
480 let h_curr = h[i];
481
482 let diag = S::TWO * (h_prev + h_curr);
484
485 let slope_curr = (values[i + 1] - values[i]) / h_curr;
487 let slope_prev = (values[i] - values[i - 1]) / h_prev;
488 let rhs = S::from_f64(6.0) * (slope_curr - slope_prev);
489
490 let idx = i - 1;
491 if idx == 0 {
492 c_prime[idx] = h_curr / diag;
494 d_prime[idx] = rhs / diag;
495 } else {
496 let m = diag - h_prev * c_prime[idx - 1];
497 c_prime[idx] = h_curr / m;
498 d_prime[idx] = (rhs - h_prev * d_prime[idx - 1]) / m;
499 }
500 }
501
502 let last_idx = n - 3;
505 d2[n - 2] = d_prime[last_idx];
506
507 for i in (1..n - 2).rev() {
508 let idx = i - 1;
509 d2[i] = d_prime[idx] - c_prime[idx] * d2[i + 1];
510 }
511
512 d2[0] = S::ZERO;
514 d2[n - 1] = S::ZERO;
515
516 d2
517 }
518
519 pub fn value_at(&self, t: S) -> S {
521 self.interpolate_at(t)
522 }
523
524 fn interpolate_at(&self, t: S) -> S {
526 let n = self.times.len();
527
528 if t <= self.times[0] {
530 return self.values[0];
531 }
532 if t >= self.times[n - 1] {
533 return self.values[n - 1];
534 }
535
536 let i = self.find_interval(t);
537 let t0 = self.times[i];
538 let t1 = self.times[i + 1];
539 let v0 = self.values[i];
540 let v1 = self.values[i + 1];
541
542 match self.interp {
543 Interpolation::Nearest => {
544 if t - t0 < t1 - t {
545 v0
546 } else {
547 v1
548 }
549 }
550 Interpolation::Linear => {
551 let alpha = (t - t0) / (t1 - t0);
552 v0 + alpha * (v1 - v0)
553 }
554 Interpolation::Cubic => self.cubic_spline_interp(t, i),
555 }
556 }
557
558 fn cubic_spline_interp(&self, t: S, i: usize) -> S {
560 let d2 = match &self.spline_d2 {
561 Some(d2) => d2,
562 None => {
563 let t0 = self.times[i];
565 let t1 = self.times[i + 1];
566 let alpha = (t - t0) / (t1 - t0);
567 return self.values[i] + alpha * (self.values[i + 1] - self.values[i]);
568 }
569 };
570
571 let t0 = self.times[i];
572 let t1 = self.times[i + 1];
573 let h = t1 - t0;
574 let y0 = self.values[i];
575 let y1 = self.values[i + 1];
576 let d2_0 = d2[i];
577 let d2_1 = d2[i + 1];
578
579 let u = (t - t0) / h;
584 let one_minus_u = S::ONE - u;
585
586 let u3 = u * u * u;
588 let omu3 = one_minus_u * one_minus_u * one_minus_u;
589
590 let h2_6 = h * h / S::from_f64(6.0);
592
593 one_minus_u * y0 + u * y1 + h2_6 * ((u3 - u) * d2_1 + (omu3 - one_minus_u) * d2_0)
595 }
596
597 fn find_interval(&self, t: S) -> usize {
599 let n = self.times.len();
600 if t <= self.times[0] {
601 return 0;
602 }
603 if t >= self.times[n - 1] {
604 return n - 2;
605 }
606 let mut lo = 0;
608 let mut hi = n - 1;
609 while hi - lo > 1 {
610 let mid = (lo + hi) / 2;
611 if t < self.times[mid] {
612 hi = mid;
613 } else {
614 lo = mid;
615 }
616 }
617 lo
618 }
619}
620
621impl<S: Scalar> Signal<S> for Tabulated<S> {
622 fn eval(&self, t: S) -> S {
623 self.interpolate_at(t)
624 }
625}
626
627#[cfg(feature = "std")]
659#[derive(Clone, Debug)]
660pub struct FromFile<S: Scalar> {
661 tabulated: Tabulated<S>,
663 path: std::string::String,
665}
666
667#[cfg(feature = "std")]
668impl<S: Scalar + std::str::FromStr> FromFile<S> {
669 pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<Self, std::string::String> {
678 Self::load_with_interpolation(path, Interpolation::Linear)
679 }
680
681 pub fn load_with_interpolation<P: AsRef<std::path::Path>>(
683 path: P,
684 interp: Interpolation,
685 ) -> Result<Self, std::string::String> {
686 use std::fs::File;
687 use std::io::{BufRead, BufReader};
688
689 let path_ref = path.as_ref();
690 let file = File::open(path_ref)
691 .map_err(|e| format!("Failed to open file '{}': {}", path_ref.display(), e))?;
692
693 let reader = BufReader::new(file);
694 let mut times = Vec::new();
695 let mut values = Vec::new();
696
697 for (line_num, line_result) in reader.lines().enumerate() {
698 let line =
699 line_result.map_err(|e| format!("Failed to read line {}: {}", line_num + 1, e))?;
700
701 let line = line.trim();
702
703 if line.is_empty() || line.starts_with('#') {
705 continue;
706 }
707
708 let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
710 if parts.len() < 2 {
711 return Err(format!(
712 "Line {} has fewer than 2 columns: '{}'",
713 line_num + 1,
714 line
715 ));
716 }
717
718 let t: S = parts[0].parse().map_err(|_| {
719 format!(
720 "Failed to parse time on line {}: '{}'",
721 line_num + 1,
722 parts[0]
723 )
724 })?;
725 let v: S = parts[1].parse().map_err(|_| {
726 format!(
727 "Failed to parse value on line {}: '{}'",
728 line_num + 1,
729 parts[1]
730 )
731 })?;
732
733 times.push(t);
734 values.push(v);
735 }
736
737 if times.is_empty() {
738 return Err("No data found in file".to_string());
739 }
740
741 let tabulated = Tabulated::new(times, values, interp);
742 let path_string = path_ref.to_string_lossy().into_owned();
743
744 Ok(Self {
745 tabulated,
746 path: path_string,
747 })
748 }
749
750 pub fn from_csv_string(
752 content: &str,
753 interp: Interpolation,
754 ) -> Result<Self, std::string::String> {
755 let mut times = Vec::new();
756 let mut values = Vec::new();
757
758 for (line_num, line) in content.lines().enumerate() {
759 let line = line.trim();
760
761 if line.is_empty() || line.starts_with('#') {
763 continue;
764 }
765
766 let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
768 if parts.len() < 2 {
769 return Err(format!(
770 "Line {} has fewer than 2 columns: '{}'",
771 line_num + 1,
772 line
773 ));
774 }
775
776 let t: S = parts[0].parse().map_err(|_| {
777 format!(
778 "Failed to parse time on line {}: '{}'",
779 line_num + 1,
780 parts[0]
781 )
782 })?;
783 let v: S = parts[1].parse().map_err(|_| {
784 format!(
785 "Failed to parse value on line {}: '{}'",
786 line_num + 1,
787 parts[1]
788 )
789 })?;
790
791 times.push(t);
792 values.push(v);
793 }
794
795 if times.is_empty() {
796 return Err("No data found in content".to_string());
797 }
798
799 let tabulated = Tabulated::new(times, values, interp);
800
801 Ok(Self {
802 tabulated,
803 path: "<from_string>".to_string(),
804 })
805 }
806
807 pub fn len(&self) -> usize {
809 self.tabulated.times.len()
810 }
811
812 pub fn is_empty(&self) -> bool {
814 self.tabulated.times.is_empty()
815 }
816
817 pub fn time_range(&self) -> (S, S) {
819 let times = &self.tabulated.times;
820 (times[0], times[times.len() - 1])
821 }
822
823 pub fn path(&self) -> &str {
825 &self.path
826 }
827
828 pub fn as_tabulated(&self) -> &Tabulated<S> {
830 &self.tabulated
831 }
832}
833
834#[cfg(feature = "std")]
835impl<S: Scalar + std::str::FromStr> Signal<S> for FromFile<S> {
836 fn eval(&self, t: S) -> S {
837 self.tabulated.eval(t)
838 }
839
840 fn eval_derivative(&self, t: S) -> S {
841 self.tabulated.eval_derivative(t)
842 }
843}
844
845#[derive(Clone, Debug)]
867pub struct Piecewise<S: Scalar> {
868 segments: Vec<(S, S)>,
870 default: S,
872}
873
874impl<S: Scalar> Piecewise<S> {
875 pub fn new(segments: Vec<(S, S)>, default: S) -> Self {
881 Self { segments, default }
882 }
883}
884
885impl<S: Scalar> Signal<S> for Piecewise<S> {
886 fn eval(&self, t: S) -> S {
887 for &(end_time, value) in &self.segments {
888 if t < end_time {
889 return value;
890 }
891 }
892 self.default
893 }
894}
895
896#[derive(Clone, Debug)]
902pub struct Sum<S: Scalar, A: Signal<S>, B: Signal<S>> {
903 pub a: A,
904 pub b: B,
905 _marker: core::marker::PhantomData<S>,
906}
907
908impl<S: Scalar, A: Signal<S>, B: Signal<S>> Sum<S, A, B> {
909 pub fn new(a: A, b: B) -> Self {
910 Self {
911 a,
912 b,
913 _marker: core::marker::PhantomData,
914 }
915 }
916}
917
918impl<S: Scalar, A: Signal<S>, B: Signal<S>> Signal<S> for Sum<S, A, B> {
919 #[inline]
920 fn eval(&self, t: S) -> S {
921 self.a.eval(t) + self.b.eval(t)
922 }
923
924 #[inline]
925 fn eval_derivative(&self, t: S) -> S {
926 self.a.eval_derivative(t) + self.b.eval_derivative(t)
927 }
928}
929
930#[derive(Clone, Debug)]
932pub struct Product<S: Scalar, A: Signal<S>, B: Signal<S>> {
933 pub a: A,
934 pub b: B,
935 _marker: core::marker::PhantomData<S>,
936}
937
938impl<S: Scalar, A: Signal<S>, B: Signal<S>> Product<S, A, B> {
939 pub fn new(a: A, b: B) -> Self {
940 Self {
941 a,
942 b,
943 _marker: core::marker::PhantomData,
944 }
945 }
946}
947
948impl<S: Scalar, A: Signal<S>, B: Signal<S>> Signal<S> for Product<S, A, B> {
949 #[inline]
950 fn eval(&self, t: S) -> S {
951 self.a.eval(t) * self.b.eval(t)
952 }
953
954 #[inline]
955 fn eval_derivative(&self, t: S) -> S {
956 self.a.eval_derivative(t) * self.b.eval(t) + self.a.eval(t) * self.b.eval_derivative(t)
958 }
959}
960
961#[derive(Clone, Debug)]
963pub struct Scaled<S: Scalar, Inner: Signal<S>> {
964 pub scale: S,
965 pub inner: Inner,
966}
967
968impl<S: Scalar, Inner: Signal<S>> Scaled<S, Inner> {
969 pub fn new(scale: S, inner: Inner) -> Self {
970 Self { scale, inner }
971 }
972}
973
974impl<S: Scalar, Inner: Signal<S>> Signal<S> for Scaled<S, Inner> {
975 #[inline]
976 fn eval(&self, t: S) -> S {
977 self.scale * self.inner.eval(t)
978 }
979
980 #[inline]
981 fn eval_derivative(&self, t: S) -> S {
982 self.scale * self.inner.eval_derivative(t)
983 }
984}
985
986#[derive(Clone, Debug)]
992pub struct Constant<S: Scalar> {
993 pub value: S,
994}
995
996impl<S: Scalar> Constant<S> {
997 pub fn new(value: S) -> Self {
998 Self { value }
999 }
1000}
1001
1002impl<S: Scalar> Signal<S> for Constant<S> {
1003 #[inline]
1004 fn eval(&self, _t: S) -> S {
1005 self.value
1006 }
1007
1008 #[inline]
1009 fn eval_derivative(&self, _t: S) -> S {
1010 S::ZERO
1011 }
1012}
1013
1014#[derive(Clone, Debug, Default)]
1020pub struct Zero<S: Scalar> {
1021 _marker: core::marker::PhantomData<S>,
1022}
1023
1024impl<S: Scalar> Zero<S> {
1025 pub fn new() -> Self {
1026 Self {
1027 _marker: core::marker::PhantomData,
1028 }
1029 }
1030}
1031
1032impl<S: Scalar> Signal<S> for Zero<S> {
1033 #[inline]
1034 fn eval(&self, _t: S) -> S {
1035 S::ZERO
1036 }
1037
1038 #[inline]
1039 fn eval_derivative(&self, _t: S) -> S {
1040 S::ZERO
1041 }
1042}
1043
1044impl<S: Scalar> Signal<S> for Box<dyn Signal<S>> {
1049 fn eval(&self, t: S) -> S {
1050 (**self).eval(t)
1051 }
1052
1053 fn eval_derivative(&self, t: S) -> S {
1054 (**self).eval_derivative(t)
1055 }
1056}
1057
1058#[cfg(test)]
1063mod tests {
1064 use super::*;
1065
1066 const TOL: f64 = 1e-10;
1067
1068 #[test]
1069 fn test_harmonic() {
1070 let h = Harmonic::new(2.0, 1.0, 0.0); assert!(h.eval(0.0).abs() < TOL);
1074
1075 assert!((h.eval(0.25) - 2.0).abs() < TOL);
1077
1078 assert!(h.eval(0.5).abs() < TOL);
1080
1081 assert!((h.eval(0.75) + 2.0).abs() < TOL);
1083 }
1084
1085 #[test]
1086 fn test_harmonic_derivative() {
1087 let h = Harmonic::new(1.0, 1.0, 0.0); let deriv = h.eval_derivative(0.0);
1092 assert!((deriv - 2.0 * core::f64::consts::PI).abs() < TOL);
1093 }
1094
1095 #[test]
1096 fn test_step_sharp() {
1097 let s = Step::new(1.0, 2.0);
1098
1099 assert!(s.eval(1.0).abs() < TOL);
1100 assert!(s.eval(1.999).abs() < TOL);
1101 assert!((s.eval(2.0) - 1.0).abs() < TOL);
1102 assert!((s.eval(3.0) - 1.0).abs() < TOL);
1103 }
1104
1105 #[test]
1106 fn test_step_smooth() {
1107 let s = Step::smooth(1.0, 2.0, 0.1);
1108
1109 assert!(s.eval(0.0).abs() < 0.01);
1111
1112 assert!((s.eval(2.0) - 0.5).abs() < TOL);
1114
1115 assert!((s.eval(4.0) - 1.0).abs() < 0.01);
1117 }
1118
1119 #[test]
1120 fn test_ramp() {
1121 let r = Ramp::new(0.0, 2.0, 1.0, 3.0);
1122
1123 assert!(r.eval(0.5).abs() < TOL);
1125
1126 assert!(r.eval(1.0).abs() < TOL);
1128
1129 assert!((r.eval(2.0) - 1.0).abs() < TOL);
1131
1132 assert!((r.eval(3.0) - 2.0).abs() < TOL);
1134
1135 assert!((r.eval(4.0) - 2.0).abs() < TOL);
1137 }
1138
1139 #[test]
1140 fn test_ramp_derivative() {
1141 let r = Ramp::new(0.0, 4.0, 1.0, 3.0); assert!((r.eval_derivative(2.0) - 2.0).abs() < TOL);
1145
1146 assert!(r.eval_derivative(0.5).abs() < TOL);
1148 assert!(r.eval_derivative(4.0).abs() < TOL);
1149 }
1150
1151 #[test]
1152 fn test_pulse() {
1153 let p = Pulse::new(5.0, 1.0, 2.0);
1154
1155 assert!(p.eval(0.5).abs() < TOL);
1157
1158 assert!((p.eval(1.0) - 5.0).abs() < TOL);
1160 assert!((p.eval(2.0) - 5.0).abs() < TOL);
1161 assert!((p.eval(3.0) - 5.0).abs() < TOL);
1162
1163 assert!(p.eval(3.5).abs() < TOL);
1165 }
1166
1167 #[test]
1168 fn test_chirp() {
1169 let c = Chirp::new(1.0, 1.0, 10.0, 5.0);
1170
1171 assert!(c.eval(-1.0).abs() < TOL);
1173 assert!(c.eval(6.0).abs() < TOL);
1174
1175 assert!(c.eval(2.5).abs() <= 1.0 + TOL);
1177
1178 assert!(c.eval(0.0).abs() < TOL);
1180 }
1181
1182 #[test]
1183 fn test_tabulated_linear() {
1184 let times = vec![0.0, 1.0, 2.0, 3.0];
1185 let values = vec![0.0, 2.0, 1.0, 3.0];
1186 let t = Tabulated::new(times, values, Interpolation::Linear);
1187
1188 assert!(t.eval(0.0).abs() < TOL);
1190 assert!((t.eval(1.0) - 2.0).abs() < TOL);
1191 assert!((t.eval(2.0) - 1.0).abs() < TOL);
1192 assert!((t.eval(3.0) - 3.0).abs() < TOL);
1193
1194 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);
1200 assert!((t.eval(5.0) - 3.0).abs() < TOL);
1201 }
1202
1203 #[test]
1204 fn test_tabulated_nearest() {
1205 let times = vec![0.0, 1.0, 2.0];
1206 let values = vec![0.0, 1.0, 2.0];
1207 let t = Tabulated::new(times, values, Interpolation::Nearest);
1208
1209 assert!(t.eval(0.3).abs() < TOL);
1211
1212 assert!((t.eval(0.6) - 1.0).abs() < TOL);
1214 assert!((t.eval(1.4) - 1.0).abs() < TOL);
1215
1216 assert!((t.eval(1.6) - 2.0).abs() < TOL);
1218 }
1219
1220 #[test]
1221 fn test_tabulated_cubic_spline() {
1222 let times = vec![0.0, 1.0, 2.0, 3.0, 4.0];
1224 let values = vec![0.0, 1.0, 0.0, 1.0, 0.0];
1225 let t = Tabulated::new(times.clone(), values.clone(), Interpolation::Cubic);
1226
1227 for (i, (&ti, &vi)) in times.iter().zip(values.iter()).enumerate() {
1229 let y = t.value_at(ti);
1230 assert!(
1231 (y - vi).abs() < 1e-10,
1232 "Failed at point {}: expected {}, got {}",
1233 i,
1234 vi,
1235 y
1236 );
1237 }
1238
1239 let mid_12 = t.value_at(1.5);
1242 assert!(
1243 mid_12 < 1.0 && mid_12 > -0.5,
1244 "Cubic spline midpoint 1.5 out of range: {}",
1245 mid_12
1246 );
1247
1248 let eps = 0.001;
1251 let deriv_left = (t.value_at(1.5) - t.value_at(1.5 - eps)) / eps;
1252 let deriv_right = (t.value_at(1.5 + eps) - t.value_at(1.5)) / eps;
1253 assert!(
1254 (deriv_left - deriv_right).abs() < 0.1,
1255 "Derivative discontinuity at 1.5: left={}, right={}",
1256 deriv_left,
1257 deriv_right
1258 );
1259 }
1260
1261 #[test]
1262 fn test_tabulated_cubic_vs_linear() {
1263 let times = vec![0.0, 1.0, 2.0, 3.0];
1265 let values = vec![0.0, 1.0, 0.5, 1.5];
1266
1267 let linear = Tabulated::new(times.clone(), values.clone(), Interpolation::Linear);
1268 let cubic = Tabulated::new(times.clone(), values.clone(), Interpolation::Cubic);
1269
1270 for (&ti, &vi) in times.iter().zip(values.iter()) {
1272 assert!((linear.value_at(ti) - vi).abs() < 1e-10);
1273 assert!((cubic.value_at(ti) - vi).abs() < 1e-10);
1274 }
1275
1276 let lin_mid = linear.value_at(1.5);
1279 let cub_mid = cubic.value_at(1.5);
1280
1281 assert!((lin_mid - 0.75).abs() < 1e-10);
1283
1284 assert!(
1286 cub_mid > 0.0 && cub_mid < 2.0,
1287 "Cubic value {} out of reasonable range",
1288 cub_mid
1289 );
1290 }
1291
1292 #[test]
1293 fn test_piecewise() {
1294 let p = Piecewise::new(vec![(1.0, 10.0), (3.0, 20.0), (5.0, 30.0)], 0.0);
1295
1296 assert!((p.eval(0.5) - 10.0).abs() < TOL);
1297 assert!((p.eval(2.0) - 20.0).abs() < TOL);
1298 assert!((p.eval(4.0) - 30.0).abs() < TOL);
1299 assert!(p.eval(6.0).abs() < TOL);
1300 }
1301
1302 #[test]
1303 fn test_sum() {
1304 let a = Constant::new(3.0);
1305 let b = Constant::new(5.0);
1306 let sum = Sum::new(a, b);
1307
1308 assert!((sum.eval(0.0) - 8.0).abs() < TOL);
1309 assert!((sum.eval(100.0) - 8.0).abs() < TOL);
1310 }
1311
1312 #[test]
1313 fn test_product() {
1314 let a = Constant::new(3.0);
1315 let b = Constant::new(5.0);
1316 let prod = Product::new(a, b);
1317
1318 assert!((prod.eval(0.0) - 15.0).abs() < TOL);
1319 }
1320
1321 #[test]
1322 fn test_scaled() {
1323 let h = Harmonic::new(1.0, 1.0, 0.0);
1324 let scaled = Scaled::new(2.0, h);
1325
1326 assert!((scaled.eval(0.25) - 2.0).abs() < TOL);
1328 }
1329
1330 #[test]
1341 fn test_signal_derivative_f32() {
1342 let times: Vec<f32> = vec![0.0, 1.0, 2.0, 3.0, 4.0];
1345 let values: Vec<f32> = vec![0.0, 2.0, 4.0, 6.0, 8.0];
1346 let s = Tabulated::new(times, values, Interpolation::Linear);
1347 let d = s.eval_derivative(2.0_f32);
1348 assert!(d != 0.0, "FD step quantised to zero on f32");
1349 assert!(
1350 (d - 2.0_f32).abs() < 1e-2,
1351 "central-FD derivative on f32 too inaccurate: d={}",
1352 d
1353 );
1354 }
1355
1356 #[test]
1357 fn test_constant_and_zero() {
1358 let c = Constant::new(42.0);
1359 let z: Zero<f64> = Zero::new();
1360
1361 assert!((c.eval(0.0) - 42.0).abs() < TOL);
1362 assert!((c.eval(1000.0) - 42.0).abs() < TOL);
1363 assert!(c.eval_derivative(0.0).abs() < TOL);
1364
1365 assert!(z.eval(0.0).abs() < TOL);
1366 assert!(z.eval(1000.0).abs() < TOL);
1367 }
1368
1369 #[test]
1370 fn test_composite_signals() {
1371 let harmonic = Harmonic::new(2.0, 1.0, 0.0);
1373 let step = Step::new(1.0, 0.5);
1374 let composite = Sum::new(harmonic, step);
1375
1376 assert!(composite.eval(0.0).abs() < TOL);
1378
1379 assert!((composite.eval(0.25) - 2.0).abs() < TOL);
1381
1382 assert!((composite.eval(0.75) + 1.0).abs() < TOL);
1384 }
1385
1386 #[cfg(feature = "std")]
1387 #[test]
1388 fn test_from_file_csv_string() {
1389 let csv = r#"
1390# Test data
13910.0, 0.0
13921.0, 2.0
13932.0, 1.0
13943.0, 3.0
1395"#;
1396 let signal: super::FromFile<f64> =
1397 super::FromFile::from_csv_string(csv, Interpolation::Linear).unwrap();
1398
1399 assert_eq!(signal.len(), 4);
1400 assert!(!signal.is_empty());
1401
1402 let (t_min, t_max) = signal.time_range();
1403 assert!(t_min.abs() < TOL);
1404 assert!((t_max - 3.0).abs() < TOL);
1405
1406 assert!(signal.eval(0.0).abs() < TOL);
1408 assert!((signal.eval(1.0) - 2.0).abs() < TOL);
1409 assert!((signal.eval(0.5) - 1.0).abs() < TOL); }
1411
1412 #[cfg(feature = "std")]
1413 #[test]
1414 fn test_from_file_empty_error() {
1415 let csv = "# Only comments\n# No data\n";
1416 let result: Result<super::FromFile<f64>, _> =
1417 super::FromFile::from_csv_string(csv, Interpolation::Linear);
1418 assert!(result.is_err());
1419 }
1420
1421 #[cfg(feature = "std")]
1422 #[test]
1423 fn test_from_file_parse_error() {
1424 let csv = "0.0, abc\n"; let result: Result<super::FromFile<f64>, _> =
1426 super::FromFile::from_csv_string(csv, Interpolation::Linear);
1427 assert!(result.is_err());
1428 }
1429
1430 #[cfg(feature = "std")]
1431 #[test]
1432 fn test_from_file_too_few_columns() {
1433 let csv = "0.0\n"; let result: Result<super::FromFile<f64>, _> =
1435 super::FromFile::from_csv_string(csv, Interpolation::Linear);
1436 assert!(result.is_err());
1437 }
1438}