gam_models/gamlss/gaussian/
log_link.rs1use super::*;
6
7pub struct PoissonLogFamily {
8 pub y: Array1<f64>,
9 pub weights: Array1<f64>,
10}
11
12impl PoissonLogFamily {
13 pub const BLOCK_ETA: usize = 0;
14
15 pub fn parameternames() -> &'static [&'static str] {
16 &["eta"]
17 }
18
19 pub fn parameter_links() -> &'static [ParameterLink] {
20 &[ParameterLink::Log]
21 }
22
23 pub fn metadata() -> FamilyMetadata {
24 FamilyMetadata {
25 name: "poisson_log",
26 parameternames: Self::parameternames(),
27 parameter_links: Self::parameter_links(),
28 }
29 }
30}
31
32pub(crate) struct DiagonalIrlsRow {
39 pub(crate) log_lik_increment: f64,
41 pub(crate) observed_weight: f64,
43 pub(crate) working_step: f64,
47}
48
49trait LogLinkDiagonalIrlsFamily {
54 fn family_label(&self) -> &'static str;
56
57 fn y(&self) -> &Array1<f64>;
59 fn prior_weights(&self) -> &Array1<f64>;
60
61 fn validate_self(&self) -> Result<(), String> {
64 Ok(())
65 }
66
67 fn validate_yi(&self, yi: f64, idx: usize) -> Result<(), String>;
71
72 fn row_kernel(&self, yi: f64, e_clamped: f64, m: f64, prior_w: f64) -> DiagonalIrlsRow;
75}
76
77fn evaluate_log_link_diagonal_irls<F: LogLinkDiagonalIrlsFamily + ?Sized>(
82 family: &F,
83 block_states: &[ParameterBlockState],
84) -> Result<FamilyEvaluation, String> {
85 let label = family.family_label();
86 let eta = &expect_single_block(block_states, label)?.eta;
87 let y = family.y();
88 let prior_weights = family.prior_weights();
89 let n = y.len();
90 if eta.len() != n || prior_weights.len() != n {
91 return Err(GamlssError::DimensionMismatch {
92 reason: format!("{label} input size mismatch"),
93 }
94 .into());
95 }
96 family.validate_self()?;
97
98 let mut ll = 0.0;
99 let mut z = Array1::<f64>::zeros(n);
100 let mut w = Array1::<f64>::zeros(n);
101
102 for i in 0..n {
103 let yi = y[i];
104 family.validate_yi(yi, i)?;
105 let e_raw = eta[i];
106 let e = e_raw.clamp(-ETA_HARD_CLAMP, ETA_HARD_CLAMP);
107 let active_clamp = e != e_raw;
108 let m = saturated_exp_eta(e_raw);
109 let prior_w = prior_weights[i];
110 let row = family.row_kernel(yi, e, m, prior_w);
111 ll += row.log_lik_increment;
112 if prior_w == 0.0 || active_clamp {
113 w[i] = 0.0;
114 z[i] = e_raw;
115 } else {
116 w[i] = floor_positiveweight(row.observed_weight, MIN_WEIGHT);
117 z[i] = e + row.working_step;
118 }
119 }
120
121 Ok(FamilyEvaluation {
122 log_likelihood: ll,
123 blockworking_sets: vec![BlockWorkingSet::diagonal_checked(z, w)?],
124 })
125}
126
127impl LogLinkDiagonalIrlsFamily for PoissonLogFamily {
128 fn family_label(&self) -> &'static str {
129 "PoissonLogFamily"
130 }
131 fn y(&self) -> &Array1<f64> {
132 &self.y
133 }
134 fn prior_weights(&self) -> &Array1<f64> {
135 &self.weights
136 }
137 fn validate_yi(&self, yi: f64, idx: usize) -> Result<(), String> {
138 if !yi.is_finite() || yi < 0.0 {
139 return Err(GamlssError::InvalidInput {
140 reason: format!(
141 "PoissonLogFamily requires non-negative finite y; found y[{idx}]={yi}"
142 ),
143 }
144 .into());
145 }
146 Ok::<(), _>(())
147 }
148 #[inline]
149 fn row_kernel(&self, yi: f64, e_clamped: f64, m: f64, prior_w: f64) -> DiagonalIrlsRow {
150 let log_lik_increment = prior_w * (yi * e_clamped - m);
152 let dmu = m.max(MIN_DERIV);
153 let var = m.max(MIN_PROB);
154 DiagonalIrlsRow {
155 log_lik_increment,
156 observed_weight: prior_w * (dmu * dmu / var),
157 working_step: (yi - m) / signedwith_floor(dmu, MIN_DERIV),
159 }
160 }
161}
162
163impl CustomFamily for PoissonLogFamily {
164 fn joint_jeffreys_term_required(&self) -> bool {
168 true
169 }
170
171 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
172 evaluate_log_link_diagonal_irls(self, block_states)
173 }
174
175 fn exact_newton_joint_gradient_evaluation(
176 &self,
177 block_states: &[ParameterBlockState],
178 specs: &[ParameterBlockSpec],
179 ) -> Result<Option<ExactNewtonJointGradientEvaluation>, String> {
180 let eval = self.evaluate(block_states)?;
185 gamlss_joint_gradient_from_working_sets(&eval, specs, block_states).map(Some)
186 }
187}
188
189impl CustomFamilyGenerative for PoissonLogFamily {
190 fn generativespec(
191 &self,
192 block_states: &[ParameterBlockState],
193 ) -> Result<GenerativeSpec, String> {
194 let eta = &expect_single_block(block_states, "PoissonLogFamily")?.eta;
195 let mean = gamlss_rowwise_map(eta.len(), |i| saturated_exp_eta(eta[i]));
196 Ok(GenerativeSpec {
197 mean,
198 noise: NoiseModel::Poisson,
199 })
200 }
201}
202
203#[derive(Clone)]
205pub struct GammaLogFamily {
206 pub y: Array1<f64>,
207 pub weights: Array1<f64>,
208 pub shape: f64,
209}
210
211impl GammaLogFamily {
212 pub const BLOCK_ETA: usize = 0;
213
214 pub fn parameternames() -> &'static [&'static str] {
215 &["eta"]
216 }
217
218 pub fn parameter_links() -> &'static [ParameterLink] {
219 &[ParameterLink::Log]
220 }
221
222 pub fn metadata() -> FamilyMetadata {
223 FamilyMetadata {
224 name: "gamma_log",
225 parameternames: Self::parameternames(),
226 parameter_links: Self::parameter_links(),
227 }
228 }
229}
230
231impl LogLinkDiagonalIrlsFamily for GammaLogFamily {
232 fn family_label(&self) -> &'static str {
233 "GammaLogFamily"
234 }
235 fn y(&self) -> &Array1<f64> {
236 &self.y
237 }
238 fn prior_weights(&self) -> &Array1<f64> {
239 &self.weights
240 }
241 fn validate_self(&self) -> Result<(), String> {
242 if !self.shape.is_finite() || self.shape <= 0.0 {
243 return Err(GamlssError::NonFinite {
244 reason: "GammaLogFamily shape must be finite and > 0".to_string(),
245 }
246 .into());
247 }
248 Ok(())
249 }
250 fn validate_yi(&self, yi: f64, idx: usize) -> Result<(), String> {
251 if !yi.is_finite() || yi <= 0.0 {
252 return Err(GamlssError::InvalidInput {
253 reason: format!("GammaLogFamily requires positive finite y; found y[{idx}]={yi}"),
254 }
255 .into());
256 }
257 Ok::<(), _>(())
258 }
259 #[inline]
260 fn row_kernel(&self, yi: f64, e_clamped: f64, m: f64, prior_w: f64) -> DiagonalIrlsRow {
261 assert!(e_clamped.is_finite());
262 assert!((e_clamped.exp() - m).abs() <= 1.0e-8 * m.abs().max(1.0));
263 let log_lik_increment = prior_w * (-self.shape * (yi / m + m.ln()));
265 let observed_weight = prior_w * self.shape * yi / m;
270 let score = prior_w * self.shape * (yi / m - 1.0);
271 let w_floored = observed_weight.max(MIN_WEIGHT);
278 DiagonalIrlsRow {
279 log_lik_increment,
280 observed_weight,
281 working_step: score / w_floored,
282 }
283 }
284}
285
286impl CustomFamily for GammaLogFamily {
287 fn joint_jeffreys_term_required(&self) -> bool {
291 true
292 }
293
294 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
295 evaluate_log_link_diagonal_irls(self, block_states)
296 }
297
298 fn exact_newton_joint_gradient_evaluation(
299 &self,
300 block_states: &[ParameterBlockState],
301 specs: &[ParameterBlockSpec],
302 ) -> Result<Option<ExactNewtonJointGradientEvaluation>, String> {
303 let eval = self.evaluate(block_states)?;
308 gamlss_joint_gradient_from_working_sets(&eval, specs, block_states).map(Some)
309 }
310
311 fn diagonalworking_weights_directional_derivative(
312 &self,
313 block_states: &[ParameterBlockState],
314 block_idx: usize,
315 d_eta: &Array1<f64>,
316 ) -> Result<Option<Array1<f64>>, String> {
317 if block_idx != Self::BLOCK_ETA {
318 return Ok(None);
319 }
320 let eta = &expect_single_block(block_states, "GammaLogFamily")?.eta;
321 let n = self.y.len();
322 if eta.len() != n || self.weights.len() != n || d_eta.len() != n {
323 return Err(GamlssError::DimensionMismatch {
324 reason: "GammaLogFamily input size mismatch".to_string(),
325 }
326 .into());
327 }
328 if !self.shape.is_finite() || self.shape <= 0.0 {
329 return Err(GamlssError::NonFinite {
330 reason: "GammaLogFamily shape must be finite and > 0".to_string(),
331 }
332 .into());
333 }
334
335 let mut dw = Array1::<f64>::zeros(n);
336 for i in 0..n {
337 let yi = self.y[i];
338 if !yi.is_finite() || yi <= 0.0 {
339 return Err(GamlssError::InvalidInput {
340 reason: format!("GammaLogFamily requires positive finite y; found y[{i}]={yi}"),
341 }
342 .into());
343 }
344 let e_raw = eta[i];
345 let e = e_raw.clamp(-ETA_HARD_CLAMP, ETA_HARD_CLAMP);
346 if self.weights[i] == 0.0 || e != e_raw {
347 dw[i] = 0.0;
348 continue;
349 }
350 let m = safe_exp(e).max(MIN_WEIGHT);
351 let observed_weight = self.weights[i] * self.shape * yi / m;
352 if observed_weight <= MIN_WEIGHT {
355 dw[i] = 0.0;
356 } else {
357 dw[i] = -observed_weight * d_eta[i];
358 }
359 }
360 Ok(Some(dw))
361 }
362}
363
364impl CustomFamilyGenerative for GammaLogFamily {
365 fn generativespec(
366 &self,
367 block_states: &[ParameterBlockState],
368 ) -> Result<GenerativeSpec, String> {
369 let eta = &expect_single_block(block_states, "GammaLogFamily")?.eta;
370 let mean = gamlss_rowwise_map(eta.len(), |i| saturated_exp_eta(eta[i]));
371 let shape = ndarray::Array1::from_elem(mean.len(), self.shape);
372 Ok(GenerativeSpec {
373 mean,
374 noise: NoiseModel::Gamma { shape },
375 })
376 }
377}