1pub type ColumnId = u32;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum Sizing {
12 FixedClusters { n_clusters: u32 },
13 FixedSize { cluster_size: u32 },
14}
15
16impl Sizing {
17 pub fn atom(&self) -> usize {
19 match self {
20 Sizing::FixedClusters { n_clusters } => (*n_clusters).max(1) as usize,
21 Sizing::FixedSize { cluster_size } => (*cluster_size).max(1) as usize,
22 }
23 }
24 pub fn n_clusters_at(&self, n: usize) -> usize {
25 match self {
26 Sizing::FixedClusters { n_clusters } => (*n_clusters).max(1) as usize,
27 Sizing::FixedSize { cluster_size } => n / (*cluster_size).max(1) as usize,
28 }
29 }
30 pub fn cluster_of_row(&self, i: usize) -> usize {
31 match self {
32 Sizing::FixedClusters { n_clusters } => i % (*n_clusters).max(1) as usize,
33 Sizing::FixedSize { cluster_size } => i / (*cluster_size).max(1) as usize,
34 }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct SlopeTerm {
41 pub column: ColumnId,
42 pub variance: f64,
43 pub corr_with_intercept: f64,
44 pub corr_with: Vec<f64>,
45}
46
47#[derive(Debug, Clone, PartialEq)]
49pub struct Grouping {
50 pub relation: GroupingRelation,
51 pub tau_squared: f64,
52 pub slopes: Vec<SlopeTerm>,
53}
54
55impl Grouping {
56 pub fn re_correlation_matrix(&self) -> (usize, Vec<f64>) {
61 re_correlation_from_slopes(&self.slopes)
62 }
63}
64
65fn re_correlation_from_slopes(slopes: &[SlopeTerm]) -> (usize, Vec<f64>) {
72 let q = 1 + slopes.len();
73 let mut r = vec![0.0; q * q];
74 for d in 0..q {
75 r[d * q + d] = 1.0;
76 }
77 for (k, s) in slopes.iter().enumerate() {
78 r[k + 1] = s.corr_with_intercept; r[(k + 1) * q] = s.corr_with_intercept; for (i, &cik) in s.corr_with.iter().enumerate() {
81 r[(i + 1) * q + (k + 1)] = cik;
82 r[(k + 1) * q + (i + 1)] = cik;
83 }
84 }
85 (q, r)
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub enum GroupingRelation {
90 Crossed { n_clusters: u32 },
91 NestedWithin { n_per_parent: u32 },
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum Estimator {
97 Ols,
98 Glm,
99 Mle,
100}
101
102#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
104pub enum WaldSe {
105 #[default]
106 Hessian,
107 Rx,
108}
109
110#[derive(Debug, Clone, PartialEq)]
112pub struct ModelSpec {
113 pub sizing: Sizing,
114 pub tau_squared: f64,
115 pub slopes: Vec<SlopeTerm>,
116 pub extra_groupings: Vec<Grouping>,
117 pub estimator: Estimator,
118 pub wald_se: WaldSe,
119}
120
121impl ModelSpec {
122 pub fn re_correlation_matrix(&self) -> (usize, Vec<f64>) {
129 re_correlation_from_slopes(&self.slopes)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn model_spec_constructs_and_reports_q() {
139 let spec = ModelSpec {
140 sizing: Sizing::FixedClusters { n_clusters: 30 },
141 tau_squared: 0.5,
142 slopes: vec![SlopeTerm {
143 column: 1,
144 variance: 0.2,
145 corr_with_intercept: 0.1,
146 corr_with: vec![],
147 }],
148 extra_groupings: vec![Grouping {
149 relation: GroupingRelation::Crossed { n_clusters: 12 },
150 tau_squared: 0.3,
151 slopes: vec![],
152 }],
153 estimator: Estimator::Mle,
154 wald_se: WaldSe::Hessian,
155 };
156 assert_eq!(1 + spec.slopes.len(), 2);
158 assert_eq!(spec.sizing.atom(), 30);
159 }
160}