gam_models/gamlss/binomial/
wiggle_workspace.rs1use super::*;
6
7pub(crate) struct BinomialLocationScaleWiggleHessianWorkspace {
11 pub(crate) family: BinomialLocationScaleWiggleFamily,
12 pub(crate) block_states: Vec<ParameterBlockState>,
13 pub(crate) x_t: Arc<Array2<f64>>,
14 pub(crate) x_ls: Arc<Array2<f64>>,
15 pub(crate) pieces: BinomialWiggleOrder2Rows,
16}
17
18impl BinomialLocationScaleWiggleHessianWorkspace {
19 pub(crate) fn new(
20 family: BinomialLocationScaleWiggleFamily,
21 block_states: Vec<ParameterBlockState>,
22 x_t: Array2<f64>,
23 x_ls: Array2<f64>,
24 ) -> Result<Self, String> {
25 let pieces = family.wiggle_order2_rows(&block_states)?;
26 Ok(Self {
27 family,
28 block_states,
29 x_t: Arc::new(x_t),
30 x_ls: Arc::new(x_ls),
31 pieces,
32 })
33 }
34
35 pub(crate) fn apply_outer_subsample(
47 &mut self,
48 rows: &[crate::outer_subsample::WeightedOuterRow],
49 ) {
50 let n = self.pieces.coeff_tt.len();
51 let mut mask_tt = Array1::<f64>::zeros(n);
52 let mut mask_tl = Array1::<f64>::zeros(n);
53 let mut mask_ll = Array1::<f64>::zeros(n);
54 let mut mask_tw_b = Array1::<f64>::zeros(n);
55 let mut mask_tw_d = Array1::<f64>::zeros(n);
56 let mut mask_lw_b = Array1::<f64>::zeros(n);
57 let mut mask_lw_d = Array1::<f64>::zeros(n);
58 let mut mask_ww = Array1::<f64>::zeros(n);
59 for r in rows {
60 let i = r.index;
61 let w = r.weight;
62 mask_tt[i] = self.pieces.coeff_tt[i] * w;
63 mask_tl[i] = self.pieces.coeff_tl[i] * w;
64 mask_ll[i] = self.pieces.coeff_ll[i] * w;
65 mask_tw_b[i] = self.pieces.coeff_tw_b[i] * w;
66 mask_tw_d[i] = self.pieces.coeff_tw_d[i] * w;
67 mask_lw_b[i] = self.pieces.coeff_lw_b[i] * w;
68 mask_lw_d[i] = self.pieces.coeff_lw_d[i] * w;
69 mask_ww[i] = self.pieces.coeff_ww[i] * w;
70 }
71 self.pieces.coeff_tt = mask_tt;
72 self.pieces.coeff_tl = mask_tl;
73 self.pieces.coeff_ll = mask_ll;
74 self.pieces.coeff_tw_b = mask_tw_b;
75 self.pieces.coeff_tw_d = mask_tw_d;
76 self.pieces.coeff_lw_b = mask_lw_b;
77 self.pieces.coeff_lw_d = mask_lw_d;
78 self.pieces.coeff_ww = mask_ww;
79 }
80}
81
82impl ExactNewtonJointHessianWorkspace for BinomialLocationScaleWiggleHessianWorkspace {
83 fn warm_up_outer_caches_for_mode(
84 &self,
85 eval_mode: gam_problem::EvalMode,
86 ) -> Result<(), String> {
87 match eval_mode {
88 gam_problem::EvalMode::ValueOnly
89 | gam_problem::EvalMode::ValueAndGradient
90 | gam_problem::EvalMode::ValueGradientHessian => Ok(()),
91 }
92 }
93
94 fn hessian_dense(&self) -> Result<Option<Array2<f64>>, String> {
95 let dense = self
103 .pieces
104 .assemble_dense(self.x_t.as_ref(), self.x_ls.as_ref())?;
105 Ok(Some(dense))
106 }
107
108 fn hessian_matvec_available(&self) -> bool {
109 true
110 }
111
112 fn hessian_matvec(&self, v: &Array1<f64>) -> Result<Option<Array1<f64>>, String> {
113 let pt = self.x_t.ncols();
114 let pls = self.x_ls.ncols();
115 let pw = self.pieces.b0.ncols();
116 let total = pt + pls + pw;
117 if v.len() != total {
118 return Err(GamlssError::DimensionMismatch {
119 reason: format!(
120 "BinomialLocationScaleWiggle matvec dimension mismatch: got {}, expected {}",
121 v.len(),
122 total
123 ),
124 }
125 .into());
126 }
127 let v_t = v.slice(s![0..pt]);
128 let v_ls = v.slice(s![pt..pt + pls]);
129 let v_w = v.slice(s![pt + pls..total]);
130
131 let u_t = self.x_t.dot(&v_t);
132 let u_ls = self.x_ls.dot(&v_ls);
133 let u_b = self.pieces.b0.dot(&v_w);
134 let u_d = self.pieces.d0.dot(&v_w);
135
136 let r_t = &self.pieces.coeff_tt * &u_t
137 + &self.pieces.coeff_tl * &u_ls
138 + &self.pieces.coeff_tw_b * &u_b
139 + &self.pieces.coeff_tw_d * &u_d;
140 let r_ls = &self.pieces.coeff_tl * &u_t
141 + &self.pieces.coeff_ll * &u_ls
142 + &self.pieces.coeff_lw_b * &u_b
143 + &self.pieces.coeff_lw_d * &u_d;
144 let r_b = &self.pieces.coeff_tw_b * &u_t
145 + &self.pieces.coeff_lw_b * &u_ls
146 + &self.pieces.coeff_ww * &u_b;
147 let r_d = &self.pieces.coeff_tw_d * &u_t + &self.pieces.coeff_lw_d * &u_ls;
148
149 let out_t = fast_atv(self.x_t.as_ref(), &r_t);
150 let out_ls = fast_atv(self.x_ls.as_ref(), &r_ls);
151 let out_w = fast_atv(&self.pieces.b0, &r_b) + &fast_atv(&self.pieces.d0, &r_d);
152
153 let mut out = Array1::<f64>::zeros(total);
154 out.slice_mut(s![0..pt]).assign(&out_t);
155 out.slice_mut(s![pt..pt + pls]).assign(&out_ls);
156 out.slice_mut(s![pt + pls..total]).assign(&out_w);
157 Ok(Some(out))
158 }
159
160 fn hessian_diagonal(&self) -> Result<Option<Array1<f64>>, String> {
161 let pt = self.x_t.ncols();
162 let pls = self.x_ls.ncols();
163 let pw = self.pieces.b0.ncols();
164 let total = pt + pls + pw;
165 let mut diag = Array1::<f64>::zeros(total);
166 let n = self.pieces.coeff_tt.len();
167 for j in 0..pt {
168 let col = self.x_t.column(j);
169 let mut acc = 0.0;
170 for i in 0..n {
171 let v = col[i];
172 acc += self.pieces.coeff_tt[i] * v * v;
173 }
174 diag[j] = acc;
175 }
176 for j in 0..pls {
177 let col = self.x_ls.column(j);
178 let mut acc = 0.0;
179 for i in 0..n {
180 let v = col[i];
181 acc += self.pieces.coeff_ll[i] * v * v;
182 }
183 diag[pt + j] = acc;
184 }
185 for j in 0..pw {
186 let col = self.pieces.b0.column(j);
187 let mut acc = 0.0;
188 for i in 0..n {
189 let v = col[i];
190 acc += self.pieces.coeff_ww[i] * v * v;
191 }
192 diag[pt + pls + j] = acc;
193 }
194 Ok(Some(diag))
195 }
196
197 fn directional_derivative(
198 &self,
199 d_beta_flat: &Array1<f64>,
200 ) -> Result<Option<Array2<f64>>, String> {
201 self.family
202 .exact_newton_joint_hessian_directional_derivative(&self.block_states, d_beta_flat)
203 }
204
205 fn directional_derivative_operator(
206 &self,
207 d_beta_flat: &Array1<f64>,
208 ) -> Result<Option<Arc<dyn gam_problem::HyperOperator>>, String> {
209 self.family.bls_wiggle_directional_operator(
210 &self.block_states,
211 self.x_t.clone(),
212 self.x_ls.clone(),
213 d_beta_flat,
214 )
215 }
216
217 fn second_directional_derivative(
218 &self,
219 d_beta_u_flat: &Array1<f64>,
220 d_beta_v_flat: &Array1<f64>,
221 ) -> Result<Option<Array2<f64>>, String> {
222 self.family
223 .exact_newton_joint_hessiansecond_directional_derivative(
224 &self.block_states,
225 d_beta_u_flat,
226 d_beta_v_flat,
227 )
228 }
229
230 fn second_directional_derivative_operator(
231 &self,
232 d_beta_u: &Array1<f64>,
233 d_beta_v: &Array1<f64>,
234 ) -> Result<Option<Arc<dyn gam_problem::HyperOperator>>, String> {
235 self.family.bls_wiggle_second_directional_operator(
236 &self.block_states,
237 self.x_t.clone(),
238 self.x_ls.clone(),
239 d_beta_u,
240 d_beta_v,
241 )
242 }
243}
244
245impl CustomFamilyGenerative for BinomialLocationScaleWiggleFamily {
246 fn generativespec(
247 &self,
248 block_states: &[ParameterBlockState],
249 ) -> Result<GenerativeSpec, String> {
250 validate_block_count::<GamlssError>(
251 "BinomialLocationScaleWiggleFamily",
252 3,
253 block_states.len(),
254 )?;
255 let eta_t = &block_states[Self::BLOCK_T].eta;
256 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
257 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
258 if eta_t.len() != self.y.len() || eta_ls.len() != self.y.len() || etaw.len() != self.y.len()
259 {
260 return Err(GamlssError::DimensionMismatch {
261 reason: "BinomialLocationScaleWiggleFamily generative size mismatch".to_string(),
262 }
263 .into());
264 }
265 let mean = gamlss_rowwise_map_result(self.y.len(), |i| {
266 let sigma = exp_sigma_from_eta_scalar(eta_ls[i]);
267 let q0 = binomial_location_scale_q0(eta_t[i], sigma);
268 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, q0 + etaw[i])
269 .map_err(|e| format!("location-scale inverse-link evaluation failed: {e}"))?;
270 Ok(jet.mu)
271 })?;
272 Ok(GenerativeSpec {
273 mean,
274 noise: NoiseModel::Bernoulli,
275 })
276 }
277}