antecedent_core/query/
population.rs1use std::collections::BTreeMap;
6use std::sync::Arc;
7
8use crate::ids::DistributionRef;
9
10use super::error::QueryError;
11use super::target::{PredicateExpr, TargetPopulation};
12
13#[derive(Clone, Debug, Default)]
15pub struct PopulationRegistry {
16 predicates: BTreeMap<Arc<str>, Arc<[usize]>>,
17 distributions: BTreeMap<u32, Arc<[f64]>>,
18}
19
20impl PopulationRegistry {
21 #[must_use]
23 pub fn new() -> Self {
24 Self::default()
25 }
26
27 pub fn insert_predicate(&mut self, name: impl Into<Arc<str>>, rows: impl Into<Arc<[usize]>>) {
29 self.predicates.insert(name.into(), rows.into());
30 }
31
32 pub fn insert_distribution(&mut self, id: DistributionRef, weights: impl Into<Arc<[f64]>>) {
34 self.distributions.insert(id.raw(), weights.into());
35 }
36
37 #[must_use]
39 pub fn predicate(&self, name: &str) -> Option<&[usize]> {
40 self.predicates.get(name).map(std::convert::AsRef::as_ref)
41 }
42
43 #[must_use]
45 pub fn distribution(&self, id: DistributionRef) -> Option<&[f64]> {
46 self.distributions.get(&id.raw()).map(std::convert::AsRef::as_ref)
47 }
48}
49
50#[derive(Clone, Debug)]
52pub struct PopulationSelection {
53 pub keep: Arc<[bool]>,
55 pub weights: Option<Arc<[f64]>>,
57}
58
59impl TargetPopulation {
60 pub fn resolve(
69 &self,
70 n: usize,
71 treatment: Option<&[f64]>,
72 registry: Option<&PopulationRegistry>,
73 ) -> Result<PopulationSelection, QueryError> {
74 match self {
75 Self::AllObserved => {
76 Ok(PopulationSelection { keep: Arc::from(vec![true; n]), weights: None })
77 }
78 Self::Treated | Self::Untreated => {
79 let t = treatment.ok_or(QueryError::PopulationNeedsTreatment)?;
80 if t.len() != n {
81 return Err(QueryError::PopulationLengthMismatch {
82 expected: n,
83 actual: t.len(),
84 });
85 }
86 let want_treated = matches!(self, Self::Treated);
87 let mut keep = vec![false; n];
88 for (i, &ti) in t.iter().enumerate() {
89 let is_t = (ti - 1.0).abs() <= 1e-12;
90 let is_c = ti.abs() <= 1e-12;
91 if !is_t && !is_c {
92 return Err(QueryError::PopulationNonBinaryTreatment);
93 }
94 keep[i] = if want_treated { is_t } else { is_c };
95 }
96 Ok(PopulationSelection { keep: Arc::from(keep), weights: None })
97 }
98 Self::Environment(_) => Err(QueryError::PopulationEnvironmentUnsupported),
99 Self::Predicate(expr) => {
100 let rows: &[usize] = match expr {
101 PredicateExpr::Rows(rows) => rows.as_ref(),
102 PredicateExpr::Named(name) => {
103 let reg = registry.ok_or(QueryError::PopulationRegistryRequired)?;
104 reg.predicate(name.as_ref()).ok_or_else(|| {
105 QueryError::UnknownPredicateName { name: Arc::clone(name) }
106 })?
107 }
108 };
109 let mut keep = vec![false; n];
110 for &r in rows {
111 if r >= n {
112 return Err(QueryError::PopulationRowOutOfRange { row: r, n });
113 }
114 keep[r] = true;
115 }
116 Ok(PopulationSelection { keep: Arc::from(keep), weights: None })
117 }
118 Self::CustomDistribution(id) => {
119 let reg = registry.ok_or(QueryError::PopulationRegistryRequired)?;
120 let weights = reg
121 .distribution(*id)
122 .ok_or(QueryError::UnknownDistributionRef { id: id.raw() })?;
123 if weights.len() != n {
124 return Err(QueryError::PopulationLengthMismatch {
125 expected: n,
126 actual: weights.len(),
127 });
128 }
129 if weights.iter().any(|w| !w.is_finite() || *w < 0.0) {
130 return Err(QueryError::InvalidPopulationWeights);
131 }
132 Ok(PopulationSelection {
133 keep: Arc::from(vec![true; n]),
134 weights: Some(Arc::from(weights.to_vec())),
135 })
136 }
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::ids::DistributionRef;
145
146 #[test]
147 fn resolves_rows_and_named_and_weights() {
148 let mut reg = PopulationRegistry::new();
149 reg.insert_predicate("cohort", [0usize, 2]);
150 reg.insert_distribution(DistributionRef::from_raw(1), [0.5, 0.0, 1.5]);
151
152 let rows = TargetPopulation::Predicate(PredicateExpr::rows([1usize]))
153 .resolve(3, None, None)
154 .unwrap();
155 assert_eq!(rows.keep.as_ref(), &[false, true, false]);
156
157 let named = TargetPopulation::Predicate(PredicateExpr::named("cohort"))
158 .resolve(3, None, Some(®))
159 .unwrap();
160 assert_eq!(named.keep.as_ref(), &[true, false, true]);
161
162 let w = TargetPopulation::CustomDistribution(DistributionRef::from_raw(1))
163 .resolve(3, None, Some(®))
164 .unwrap();
165 assert_eq!(w.weights.as_ref().unwrap().as_ref(), &[0.5, 0.0, 1.5]);
166 }
167}