1#![allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9
10use std::sync::Arc;
11
12use antecedent_core::{
13 CausalSchemaBuilder, Lag, MeasurementSpec, RoleHint, SmallRoleSet, ValueType, VariableId,
14};
15
16use crate::column::{Float64Column, OwnedColumn, ValidityBitmap};
17use crate::dataset::TimeSeriesData;
18use crate::error::DataError;
19use crate::storage::OwnedColumnarStorage;
20use crate::temporal::{SamplingRegularity, TimeIndex};
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
24pub struct KnownLaggedParent {
25 pub source: VariableId,
27 pub source_lag: Lag,
29 pub target: VariableId,
31}
32
33#[derive(Clone, Debug)]
35pub struct LaggedLinearPair {
36 pub n: usize,
38 pub coef: f64,
40 pub lag: u32,
42 pub seed: u64,
44}
45
46impl Default for LaggedLinearPair {
47 fn default() -> Self {
48 Self { n: 500, coef: 0.8, lag: 1, seed: 1 }
49 }
50}
51
52impl LaggedLinearPair {
53 pub fn simulate(&self) -> Result<(TimeSeriesData, KnownLaggedParent), DataError> {
59 if self.n < self.lag as usize + 2 {
60 return Err(DataError::InvalidArgument {
61 message: "series too short for configured lag".into(),
62 });
63 }
64 let mut b = CausalSchemaBuilder::new();
65 b.add_variable(
66 "x",
67 ValueType::Continuous,
68 SmallRoleSet::from_hint(RoleHint::Context),
69 None,
70 None,
71 MeasurementSpec::default(),
72 )
73 .map_err(|e| DataError::Schema(e.to_string()))?;
74 b.add_variable(
75 "y",
76 ValueType::Continuous,
77 SmallRoleSet::from_hint(RoleHint::Context),
78 None,
79 None,
80 MeasurementSpec::default(),
81 )
82 .map_err(|e| DataError::Schema(e.to_string()))?;
83 let schema = b.build().map_err(|e| DataError::Schema(e.to_string()))?;
84
85 let mut x = vec![0.0; self.n];
86 let mut y = vec![0.0; self.n];
87 for t in 0..self.n {
88 x[t] = det_noise(self.seed, t as u64, 1);
89 let x_lag = if t >= self.lag as usize { x[t - self.lag as usize] } else { 0.0 };
90 y[t] = self.coef * x_lag + 0.2 * det_noise(self.seed, t as u64, 2);
91 }
92
93 let cols = vec![
94 OwnedColumn::Float64(Float64Column::new(
95 VariableId::from_raw(0),
96 Arc::from(x),
97 ValidityBitmap::all_valid(self.n),
98 )?),
99 OwnedColumn::Float64(Float64Column::new(
100 VariableId::from_raw(1),
101 Arc::from(y),
102 ValidityBitmap::all_valid(self.n),
103 )?),
104 ];
105 let storage = OwnedColumnarStorage::try_new(schema, cols, None, None)?;
106 let data = TimeSeriesData::try_new(
107 storage,
108 TimeIndex {
109 regularity: SamplingRegularity::Regular { interval_ns: 1 },
110 length: self.n,
111 },
112 )?;
113 let parent = KnownLaggedParent {
114 source: VariableId::from_raw(0),
115 source_lag: Lag::from_raw(self.lag),
116 target: VariableId::from_raw(1),
117 };
118 Ok((data, parent))
119 }
120}
121
122fn det_noise(seed: u64, t: u64, stream: u64) -> f64 {
123 let mut z = seed
125 .wrapping_add(t.wrapping_mul(0x9E37_79B9_7F4A_7C15))
126 .wrapping_add(stream.wrapping_mul(0xD6E8_FEB8_6659_FD93));
127 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
128 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
129 z ^= z >> 31;
130 let u = (z >> 11) as f64 / ((1u64 << 53) as f64);
131 u * 2.0 - 1.0
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::table::TableView;
138
139 #[test]
140 fn lag1_pair_recovers_length_and_parent() {
141 let (data, parent) = LaggedLinearPair::default().simulate().unwrap();
142 assert_eq!(data.row_count(), 500);
143 assert_eq!(parent.source_lag.raw(), 1);
144 assert_eq!(parent.source.raw(), 0);
145 assert_eq!(parent.target.raw(), 1);
146 }
147}