Skip to main content

antecedent_data/
table.rs

1//! [`TableView`] trait — public causal table API (ADR 0004).
2//!
3//! SPDX-License-Identifier: MIT OR Apache-2.0
4
5use antecedent_core::{CausalSchema, VariableId};
6
7use crate::column::ColumnView;
8use crate::error::DataError;
9
10/// Lossy `i64` → `f64` for the float analysis path (mantissa cannot hold all `i64`).
11fn analysis_f64_from_i64(v: i64) -> f64 {
12    #[allow(clippy::cast_precision_loss)]
13    {
14        v as f64
15    }
16}
17
18/// Borrowed table access used by algorithms.
19pub trait TableView {
20    /// Immutable causal schema.
21    fn schema(&self) -> &CausalSchema;
22
23    /// Number of rows.
24    fn row_count(&self) -> usize;
25
26    /// Column view for `id`.
27    ///
28    /// # Errors
29    ///
30    /// Unknown variable or type issues.
31    fn column(&self, id: VariableId) -> Result<ColumnView<'_>, DataError>;
32
33    /// Copy a column into an owned `f64` buffer.
34    ///
35    /// Native `Float64` columns are copied as-is. `Int64` and `Boolean` columns
36    /// are coerced to `f64` (`true` → `1.0`, `false` → `0.0`); invalid rows become
37    /// `NaN`. Other column kinds (categorical, timestamp, fixed vector) error.
38    ///
39    /// # Errors
40    ///
41    /// Unknown variable or unsupported column type.
42    fn float64_values(&self, id: VariableId) -> Result<Vec<f64>, DataError> {
43        match self.column(id)? {
44            ColumnView::Float64(c) => Ok(c.values.to_vec()),
45            ColumnView::Int64(c) => {
46                let mut out = Vec::with_capacity(c.values.len());
47                for (i, &v) in c.values.iter().enumerate() {
48                    out.push(if c.validity.is_valid(i) {
49                        analysis_f64_from_i64(v)
50                    } else {
51                        f64::NAN
52                    });
53                }
54                Ok(out)
55            }
56            ColumnView::Boolean(c) => {
57                let mut out = Vec::with_capacity(c.values.len());
58                for (i, &v) in c.values.iter().enumerate() {
59                    out.push(if c.validity.is_valid(i) { f64::from(v) } else { f64::NAN });
60                }
61                Ok(out)
62            }
63            _ => Err(DataError::TypeMismatch {
64                id,
65                expected: "float64 (or coercible int64/boolean)",
66            }),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use std::sync::Arc;
74
75    use antecedent_core::{
76        CausalSchemaBuilder, MeasurementSpec, RoleHint, SmallRoleSet, ValueType, VariableId,
77    };
78
79    use super::*;
80    use crate::column::{BooleanColumn, Float64Column, Int64Column, OwnedColumn, ValidityBitmap};
81    use crate::dataset::TabularData;
82    use crate::storage::OwnedColumnarStorage;
83
84    fn schema_n(n: usize) -> antecedent_core::CausalSchema {
85        let mut b = CausalSchemaBuilder::new();
86        for i in 0..n {
87            b.add_variable(
88                format!("v{i}"),
89                ValueType::Continuous,
90                SmallRoleSet::from_hint(RoleHint::Context),
91                None,
92                None,
93                MeasurementSpec::default(),
94            )
95            .unwrap();
96        }
97        b.build().unwrap()
98    }
99
100    #[test]
101    fn float64_values_coerces_int64_and_boolean() {
102        let schema = schema_n(3);
103        let cols = vec![
104            OwnedColumn::Float64(
105                Float64Column::new(
106                    VariableId::from_raw(0),
107                    Arc::from([1.5_f64, 2.5]),
108                    ValidityBitmap::all_valid(2),
109                )
110                .unwrap(),
111            ),
112            OwnedColumn::Int64(
113                Int64Column::new(
114                    VariableId::from_raw(1),
115                    Arc::<[i64]>::from([3_i64, 4]),
116                    ValidityBitmap::all_valid(2),
117                )
118                .unwrap(),
119            ),
120            OwnedColumn::Boolean(
121                BooleanColumn::new(
122                    VariableId::from_raw(2),
123                    Arc::<[u8]>::from([1_u8, 0]),
124                    ValidityBitmap::all_valid(2),
125                )
126                .unwrap(),
127            ),
128        ];
129        let storage = OwnedColumnarStorage::try_new(schema, cols, None, None).unwrap();
130        let data = TabularData::new(storage);
131        assert_eq!(data.float64_values(VariableId::from_raw(0)).unwrap(), vec![1.5, 2.5]);
132        assert_eq!(data.float64_values(VariableId::from_raw(1)).unwrap(), vec![3.0, 4.0]);
133        assert_eq!(data.float64_values(VariableId::from_raw(2)).unwrap(), vec![1.0, 0.0]);
134    }
135}