1#![allow(dead_code)]
2
3extern crate special;
4
5use crate::param::io::*;
6use crate::param::traits::*;
7use nalgebra::{DMatrix, DVector};
8use rayon::prelude::*;
9
10#[derive(Debug, Clone)]
11pub struct GammaMatrix {
12 num_rows: usize,
13 num_columns: usize,
14 a0: f32,
18 b0: f32,
19 row_prior: Option<(DVector<f32>, DVector<f32>)>,
22 a_stat: DMatrix<f32>,
26 b_stat: DMatrix<f32>,
27 estimated_mean: DMatrix<f32>,
31 estimated_sd: DMatrix<f32>,
32 estimated_log_mean: DMatrix<f32>,
33 estimated_log_sd: DMatrix<f32>,
34}
35
36impl ParamIo for GammaMatrix {
37 type Mat = DMatrix<f32>;
38}
39
40impl TwoStatParam for GammaMatrix {
41 type Mat = DMatrix<f32>;
42 type Scalar = f32;
43
44 fn new(dims: (usize, usize), a: Self::Scalar, b: Self::Scalar) -> Self {
45 Self {
46 num_rows: dims.0,
47 num_columns: dims.1,
48 a0: a,
49 b0: b,
50 row_prior: None,
51 a_stat: DMatrix::from_element(dims.0, dims.1, a),
52 b_stat: DMatrix::from_element(dims.0, dims.1, b),
53 estimated_mean: DMatrix::zeros(dims.0, dims.1),
57 estimated_sd: DMatrix::zeros(0, 0),
63 estimated_log_mean: DMatrix::zeros(0, 0),
64 estimated_log_sd: DMatrix::zeros(0, 0),
65 }
66 }
67
68 fn add_stat(&mut self, add_a: &Self::Mat, add_b: &Self::Mat) {
69 self.a_stat += add_a;
70 self.b_stat += add_b;
71 }
72 fn update_stat(&mut self, update_a: &Self::Mat, update_b: &Self::Mat) {
73 self.reset_stat();
74 self.add_stat(update_a, update_b);
75 }
76 fn reset_stat(&mut self) {
77 match &self.row_prior {
78 None => {
79 self.a_stat.fill(self.a0);
80 self.b_stat.fill(self.b0);
81 }
82 Some((a0, b0)) => {
83 for mut col in self.a_stat.column_iter_mut() {
86 col.copy_from(a0);
87 }
88 for mut col in self.b_stat.column_iter_mut() {
89 col.copy_from(b0);
90 }
91 }
92 }
93 }
94 fn update_stat_col(&mut self, update_a: &Self::Mat, update_b: &Self::Mat, k: usize) {
95 match &self.row_prior {
96 None => {
97 self.a_stat
98 .column_mut(k)
99 .copy_from(&update_a.map(|x| x + self.a0));
100 self.b_stat
101 .column_mut(k)
102 .copy_from(&update_b.map(|x| x + self.b0));
103 }
104 Some((a0, b0)) => {
105 let mut a = self.a_stat.column_mut(k);
106 a.copy_from(update_a);
107 a += a0;
108 let mut b = self.b_stat.column_mut(k);
109 b.copy_from(update_b);
110 b += b0;
111 }
112 }
113 }
114
115 fn map_calibrate_mean(&mut self) {
127 self.estimated_mean = self.a_stat.zip_map(&self.b_stat, |a, b| a / b);
128 }
129 fn map_calibrate_sd(&mut self) {
130 self.estimated_sd = self.a_stat.zip_map(&self.b_stat, |a, b| a.sqrt() / b);
131 }
132 fn map_calibrate_log_mean(&mut self) {
133 use special::Gamma;
134 self.estimated_log_mean = self
135 .a_stat
136 .zip_map(&self.b_stat, |a, b| a.digamma() - b.ln());
137 }
138 fn map_calibrate_log_sd(&mut self) {
139 use special::Gamma;
152 self.estimated_log_sd = self.a_stat.map(|a| a.trigamma().sqrt());
153 }
154}
155
156impl Inference for GammaMatrix {
157 type Mat = DMatrix<f32>;
158 type Scalar = f32;
159
160 fn posterior_mean(&self) -> &Self::Mat {
161 &self.estimated_mean
162 }
163
164 fn posterior_sd(&self) -> &Self::Mat {
165 &self.estimated_sd
166 }
167
168 fn posterior_log_mean(&self) -> &Self::Mat {
169 &self.estimated_log_mean
170 }
171
172 fn posterior_log_sd(&self) -> &Self::Mat {
173 &self.estimated_log_sd
174 }
175
176 fn posterior_sample(&self) -> anyhow::Result<Self::Mat> {
177 use rand_distr::{Distribution, Gamma};
178 let eps = 1e-8;
179
180 let sampled = self
181 .a_stat
182 .as_slice()
183 .par_iter()
184 .zip(self.b_stat.as_slice().par_iter())
185 .map_init(rand::rng, |rng, (&a, &b)| -> anyhow::Result<f32> {
186 let shape = a + eps;
187 let scale = (b + eps).recip();
188 let pdf = Gamma::new(shape, scale)?;
189 Ok(pdf.sample(rng))
190 })
191 .collect::<anyhow::Result<Vec<_>>>()?;
192
193 Ok(Self::Mat::from_vec(self.nrows(), self.ncols(), sampled))
194 }
195
196 fn posterior_sample_seeded(&self, seed: u64) -> anyhow::Result<Self::Mat> {
197 let sampled = super::traits::gamma_sample_seeded(
198 self.a_stat.as_slice(),
199 self.b_stat.as_slice(),
200 seed,
201 )?;
202 Ok(Self::Mat::from_vec(self.nrows(), self.ncols(), sampled))
203 }
204
205 fn posterior_log_sample(&self, seed: u64) -> anyhow::Result<Self::Mat> {
206 use rand::rngs::SmallRng;
207 use rand::SeedableRng;
208 use rand_distr::{Distribution, StandardNormal};
209
210 const CHUNK: usize = 1024;
213 let m_slice = self.estimated_log_mean.as_slice();
214 let s_slice = self.estimated_log_sd.as_slice();
215 let mut sampled = vec![0.0f32; m_slice.len()];
216 sampled
217 .par_chunks_mut(CHUNK)
218 .enumerate()
219 .for_each(|(ci, out)| {
220 let mut rng =
221 SmallRng::seed_from_u64(seed ^ (ci as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15));
222 let base = ci * CHUNK;
223 for (k, o) in out.iter_mut().enumerate() {
224 let z: f32 = StandardNormal.sample(&mut rng);
225 *o = m_slice[base + k] + s_slice[base + k] * z;
226 }
227 });
228
229 Ok(Self::Mat::from_vec(self.nrows(), self.ncols(), sampled))
230 }
231
232 fn nrows(&self) -> usize {
233 self.num_rows
234 }
235
236 fn ncols(&self) -> usize {
237 self.num_columns
238 }
239}
240
241fn stack_field<F>(
245 blocks: &[GammaMatrix],
246 nrows: usize,
247 ncols: usize,
248 enabled: bool,
249 sel: F,
250) -> DMatrix<f32>
251where
252 F: Fn(&GammaMatrix) -> &DMatrix<f32>,
253{
254 if !enabled || blocks.is_empty() || sel(&blocks[0]).nrows() == 0 {
255 return DMatrix::zeros(0, 0);
256 }
257 let mut out = DMatrix::zeros(nrows, ncols);
258 let mut r0 = 0;
259 for b in blocks {
260 let src = sel(b);
261 out.rows_mut(r0, src.nrows()).copy_from(src);
262 r0 += src.nrows();
263 }
264 out
265}
266
267impl GammaMatrix {
268 pub fn release_stats(&mut self) {
274 self.a_stat = DMatrix::zeros(0, 0);
275 self.b_stat = DMatrix::zeros(0, 0);
276 }
277
278 pub fn sparsify_mean_to_support(&mut self, numerator: &DMatrix<f32>) {
287 debug_assert_eq!(self.estimated_mean.shape(), numerator.shape());
288 self.estimated_mean
289 .iter_mut()
290 .zip(numerator.iter())
291 .for_each(|(m, &n)| {
292 if n == 0.0 {
293 *m = 0.0;
294 }
295 });
296 }
297
298 #[must_use]
307 pub fn has_data_support(&self, row: usize, col: usize) -> bool {
308 self.a_stat[(row, col)] > self.a0_at(row)
309 }
310
311 #[must_use]
323 pub fn evidence_mean(&self, row: usize, col: usize) -> f32 {
324 let a = self.a_stat[(row, col)] - self.a0_at(row);
325 let b = self.b_stat[(row, col)] - self.b0_at(row);
326 if a > 0.0 && b > 0.0 {
327 a / b
328 } else {
329 0.0
330 }
331 }
332
333 #[inline]
335 fn a0_at(&self, row: usize) -> f32 {
336 self.row_prior.as_ref().map_or(self.a0, |(a, _)| a[row])
337 }
338
339 #[inline]
341 fn b0_at(&self, row: usize) -> f32 {
342 self.row_prior.as_ref().map_or(self.b0, |(_, b)| b[row])
343 }
344
345 pub fn with_row_prior(dims: (usize, usize), a0: &DVector<f32>, b0: &DVector<f32>) -> Self {
348 let mut out = Self::new(dims, 0.0, 0.0);
349 out.set_row_prior(a0, b0);
350 out.reset_stat();
351 out
352 }
353
354 pub fn set_row_prior(&mut self, a0: &DVector<f32>, b0: &DVector<f32>) {
357 assert_eq!(a0.len(), self.num_rows, "row prior a0 length != rows");
358 assert_eq!(b0.len(), self.num_rows, "row prior b0 length != rows");
359 self.row_prior = Some((a0.clone(), b0.clone()));
360 }
361
362 #[must_use]
364 pub fn row_prior(&self) -> Option<(&DVector<f32>, &DVector<f32>)> {
365 self.row_prior.as_ref().map(|(a, b)| (a, b))
366 }
367
368 pub fn vconcat(blocks: Vec<GammaMatrix>, stack_stats: bool) -> Self {
378 assert!(!blocks.is_empty(), "vconcat of empty block list");
379 let ncols = blocks[0].num_columns;
380 let a0 = blocks[0].a0;
381 let b0 = blocks[0].b0;
382 let nrows: usize = blocks.iter().map(|b| b.num_rows).sum();
383 let row_prior = if blocks[0].row_prior.is_some() {
384 assert!(
385 blocks.iter().all(|b| b.row_prior.is_some()),
386 "vconcat: blocks mix scalar and row priors"
387 );
388 let a = DVector::from_iterator(
389 nrows,
390 blocks
391 .iter()
392 .flat_map(|b| b.row_prior.as_ref().expect("checked").0.iter().copied()),
393 );
394 let b = DVector::from_iterator(
395 nrows,
396 blocks
397 .iter()
398 .flat_map(|b| b.row_prior.as_ref().expect("checked").1.iter().copied()),
399 );
400 Some((a, b))
401 } else {
402 assert!(
403 blocks
404 .iter()
405 .all(|b| b.row_prior.is_none() && b.a0 == a0 && b.b0 == b0),
406 "vconcat: blocks must share hyper-params"
407 );
408 None
409 };
410 let a_stat = stack_field(&blocks, nrows, ncols, stack_stats, |g| &g.a_stat);
411 let b_stat = stack_field(&blocks, nrows, ncols, stack_stats, |g| &g.b_stat);
412 let estimated_mean = stack_field(&blocks, nrows, ncols, true, |g| &g.estimated_mean);
413 let estimated_sd = stack_field(&blocks, nrows, ncols, true, |g| &g.estimated_sd);
414 let estimated_log_mean =
415 stack_field(&blocks, nrows, ncols, true, |g| &g.estimated_log_mean);
416 let estimated_log_sd = stack_field(&blocks, nrows, ncols, true, |g| &g.estimated_log_sd);
417 Self {
418 num_rows: nrows,
419 num_columns: ncols,
420 a0,
421 b0,
422 row_prior,
423 a_stat,
424 b_stat,
425 estimated_mean,
426 estimated_sd,
427 estimated_log_mean,
428 estimated_log_sd,
429 }
430 }
431}