Skip to main content

gam_problem/
test_support.rs

1//! Parameter-block test fixtures shared across the workspace.
2//!
3//! These fixtures build [`ParameterBlockSpec`] values, so they live in the crate
4//! that owns that type rather than in a downstream test-support crate. Following
5//! the workspace convention for `test_support` modules, this is a plain
6//! always-compiled `pub mod`: feature gates and `#[cfg(test)]` module gates are
7//! banned here, and a `cfg(test)` module would be invisible to downstream
8//! crates' test builds anyway. The contents are `pub`, so they are reachable
9//! (no dead-code lint) yet only ever called from `#[cfg(test)]` code.
10
11use crate::PenaltyMatrix;
12use crate::block_spec::ParameterBlockSpec;
13use gam_linalg::matrix::{DenseDesignMatrix, DesignMatrix};
14use ndarray::{Array1, Array2, array};
15
16/// A minimal two-block binomial location–scale problem: `n = 7` Bernoulli
17/// responses with an intercept-only threshold block and an intercept-only
18/// log-σ block, each carrying a unit ridge penalty.
19pub struct BinomialLocationScaleBaseFixture {
20    pub n: usize,
21    pub y: Array1<f64>,
22    pub weights: Array1<f64>,
23    pub threshold_design: DesignMatrix,
24    pub log_sigma_design: DesignMatrix,
25    pub threshold_spec: ParameterBlockSpec,
26    pub log_sigma_spec: ParameterBlockSpec,
27}
28
29pub fn binomial_location_scale_base_fixture() -> BinomialLocationScaleBaseFixture {
30    let n = 7usize;
31    let y = Array1::from_vec(vec![0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]);
32    let weights = Array1::from_vec(vec![1.0; n]);
33    let threshold_design =
34        DesignMatrix::Dense(DenseDesignMatrix::from(Array2::from_elem((n, 1), 1.0)));
35    let log_sigma_design =
36        DesignMatrix::Dense(DenseDesignMatrix::from(Array2::from_elem((n, 1), 1.0)));
37    let threshold_spec = ParameterBlockSpec {
38        name: "threshold".to_string(),
39        design: threshold_design.clone(),
40        offset: Array1::zeros(n),
41        penalties: vec![PenaltyMatrix::Dense(Array2::eye(1))],
42        nullspace_dims: vec![],
43        initial_log_lambdas: array![0.0],
44        initial_beta: Some(array![0.2]),
45        gauge_priority: 100,
46        jacobian_callback: None,
47        stacked_design: None,
48        stacked_offset: None,
49    };
50    let log_sigma_spec = ParameterBlockSpec {
51        name: "log_sigma".to_string(),
52        design: log_sigma_design.clone(),
53        offset: Array1::zeros(n),
54        penalties: vec![PenaltyMatrix::Dense(Array2::eye(1))],
55        nullspace_dims: vec![],
56        initial_log_lambdas: array![-0.2],
57        initial_beta: Some(array![-0.1]),
58        gauge_priority: 100,
59        jacobian_callback: None,
60        stacked_design: None,
61        stacked_offset: None,
62    };
63    BinomialLocationScaleBaseFixture {
64        n,
65        y,
66        weights,
67        threshold_design,
68        log_sigma_design,
69        threshold_spec,
70        log_sigma_spec,
71    }
72}
73
74/// An unpenalized block wrapping a dense design under the default gauge
75/// priority.
76pub fn spec_from_dense(name: &str, design: Array2<f64>) -> ParameterBlockSpec {
77    let n = design.nrows();
78    ParameterBlockSpec {
79        name: name.to_string(),
80        design: DesignMatrix::Dense(DenseDesignMatrix::from(design)),
81        offset: Array1::<f64>::zeros(n),
82        penalties: Vec::new(),
83        nullspace_dims: Vec::new(),
84        initial_log_lambdas: Array1::<f64>::zeros(0),
85        initial_beta: None,
86        gauge_priority: 100,
87        jacobian_callback: None,
88        stacked_design: None,
89        stacked_offset: None,
90    }
91}
92
93/// [`spec_from_dense`] with an explicit gauge priority, for fixtures that pin
94/// which block wins a shared-affine gauge contest.
95pub fn spec_from_dense_with_priority(
96    name: &str,
97    design: Array2<f64>,
98    priority: u8,
99) -> ParameterBlockSpec {
100    let mut s = spec_from_dense(name, design);
101    s.gauge_priority = priority;
102    s
103}