1use super::*;
6
7pub struct SurvivalMarginalSlopeFamilyScalars {
18 pub q0_i: Vec<f64>,
19 pub q1_i: Vec<f64>,
20 pub qd1_i: Vec<f64>,
21 pub g_i: Vec<f64>,
22 pub c_i: Vec<f64>,
23 pub s: f64,
24 pub z_i: Vec<f64>,
25}
26
27impl SurvivalMarginalSlopeFamilyScalars {
28 pub fn new(
30 q0_i: Vec<f64>,
31 q1_i: Vec<f64>,
32 qd1_i: Vec<f64>,
33 g_i: Vec<f64>,
34 s: f64,
35 z_i: Vec<f64>,
36 ) -> Self {
37 let c_i: Vec<f64> = g_i
38 .iter()
39 .map(|&g| (1.0 + (s * g).powi(2)).sqrt())
40 .collect();
41 Self {
42 q0_i,
43 q1_i,
44 qd1_i,
45 g_i,
46 c_i,
47 s,
48 z_i,
49 }
50 }
51}
52
53pub struct LogslopeBlockJacobian {
71 pub(crate) design: Arc<Array2<f64>>,
76 pub(crate) z: Vec<f64>,
78 pub(crate) s: f64,
80}
81
82impl LogslopeBlockJacobian {
83 pub fn new(design: impl Into<Arc<Array2<f64>>>, z: Vec<f64>, s: f64) -> Self {
84 Self {
85 design: design.into(),
86 z,
87 s,
88 }
89 }
90}
91
92impl crate::custom_family::BlockEffectiveJacobian for LogslopeBlockJacobian {
93 fn effective_jacobian_rows(
94 &self,
95 state: &crate::custom_family::FamilyLinearizationState<'_>,
96 rows: std::ops::Range<usize>,
97 ) -> Result<Array2<f64>, String> {
98 let n = self.design.nrows();
99 let p = self.design.ncols();
100 let rows = rows.start.min(n)..rows.end.min(n);
101 let chunk = rows.end - rows.start;
102 let s = if state.probit_frailty_scale > 0.0 && state.probit_frailty_scale.is_finite() {
109 state.probit_frailty_scale
110 } else {
111 self.s
112 };
113
114 let beta = state.beta;
119 let p_use = p.min(beta.len());
120 let mut g_rows = vec![0.0_f64; chunk];
121 for i in rows.clone() {
122 let local_i = i - rows.start;
123 for j in 0..p_use {
124 g_rows[local_i] += self.design[[i, j]] * beta[j];
125 }
126 }
127
128 let scalars: Option<&SurvivalMarginalSlopeFamilyScalars> = state
133 .family_scalars
134 .as_ref()
135 .and_then(|a| a.downcast_ref::<SurvivalMarginalSlopeFamilyScalars>());
136
137 let any_nonzero_g = g_rows.iter().any(|&gi| gi != 0.0);
138 if any_nonzero_g && scalars.is_none() {
139 return Err("survival marginal-slope logslope block requires \
140 SurvivalMarginalSlopeFamilyScalars when beta != 0 \
141 (g_i != 0 for at least one row); got family_scalars: None. \
142 The caller must compute per-row (q0, q1, qd1) at the current \
143 beta and pass them via FamilyLinearizationState::family_scalars."
144 .to_string());
145 }
146
147 let mut jac = Array2::<f64>::zeros((3 * chunk, p));
148
149 for i in rows.clone() {
150 let local_i = i - rows.start;
151 let g = g_rows[local_i];
155 let (q0, q1, qd1, c) = match scalars {
156 Some(sc) => (sc.q0_i[i], sc.q1_i[i], sc.qd1_i[i], sc.c_i[i]),
157 None => {
158 (0.0_f64, 0.0_f64, 0.0_f64, 1.0_f64)
161 }
162 };
163 let z_i = self.z[i];
164 let sg_over_c = if g == 0.0 { 0.0 } else { s * s * g / c };
165 let coeff_eta0 = q0 * sg_over_c + s * z_i;
166 let coeff_eta1 = q1 * sg_over_c + s * z_i;
167 let coeff_ad1 = qd1 * sg_over_c;
168
169 for j in 0..p {
170 let g_ij = self.design[[i, j]];
171 jac[[local_i, j]] = coeff_eta0 * g_ij;
172 jac[[chunk + local_i, j]] = coeff_eta1 * g_ij;
173 jac[[2 * chunk + local_i, j]] = coeff_ad1 * g_ij;
174 }
175 }
176 Ok(jac)
177 }
178
179 fn n_outputs(&self) -> usize {
180 3
181 }
182}
183
184pub struct MarginalBlockJacobian {
197 pub(crate) design: Arc<Array2<f64>>,
200}
201
202impl MarginalBlockJacobian {
203 pub fn new(design: impl Into<Arc<Array2<f64>>>) -> Self {
204 Self {
205 design: design.into(),
206 }
207 }
208}
209
210impl crate::custom_family::BlockEffectiveJacobian for MarginalBlockJacobian {
211 fn effective_jacobian_rows(
212 &self,
213 state: &crate::custom_family::FamilyLinearizationState<'_>,
214 rows: std::ops::Range<usize>,
215 ) -> Result<Array2<f64>, String> {
216 let n = self.design.nrows();
217 let p = self.design.ncols();
218 let rows = rows.start.min(n)..rows.end.min(n);
219 let chunk = rows.end - rows.start;
220
221 let scalars: Option<&SurvivalMarginalSlopeFamilyScalars> = state
228 .family_scalars
229 .as_ref()
230 .and_then(|a| a.downcast_ref::<SurvivalMarginalSlopeFamilyScalars>());
231
232 let beta_nonzero = state.beta.iter().any(|&b| b != 0.0);
233 if beta_nonzero && scalars.is_none() {
234 return Err("survival marginal-slope marginal block requires \
235 SurvivalMarginalSlopeFamilyScalars when beta != 0 (c_i != 1 in general); \
236 got family_scalars: None. The caller must populate per-row c_i via \
237 FamilyLinearizationState::family_scalars."
238 .to_string());
239 }
240
241 let mut jac = Array2::<f64>::zeros((3 * chunk, p));
242
243 for i in rows.clone() {
244 let local_i = i - rows.start;
245 let c = match scalars {
246 Some(sc) => sc.c_i[i],
247 None => 1.0_f64,
249 };
250 for j in 0..p {
251 let m_ij = c * self.design[[i, j]];
252 jac[[local_i, j]] = m_ij;
253 jac[[chunk + local_i, j]] = m_ij;
254 }
256 }
257 Ok(jac)
258 }
259
260 fn n_outputs(&self) -> usize {
261 3
262 }
263}
264
265pub struct TimeBlockJacobian {
278 pub(crate) design_entry: Arc<Array2<f64>>,
280 pub(crate) design_exit: Arc<Array2<f64>>,
281 pub(crate) design_deriv: Arc<Array2<f64>>,
282}
283
284impl TimeBlockJacobian {
285 pub fn new(
286 design_entry: impl Into<Arc<Array2<f64>>>,
287 design_exit: impl Into<Arc<Array2<f64>>>,
288 design_deriv: impl Into<Arc<Array2<f64>>>,
289 ) -> Self {
290 Self {
291 design_entry: design_entry.into(),
292 design_exit: design_exit.into(),
293 design_deriv: design_deriv.into(),
294 }
295 }
296}
297
298impl crate::custom_family::BlockEffectiveJacobian for TimeBlockJacobian {
299 fn effective_jacobian_rows(
300 &self,
301 state: &crate::custom_family::FamilyLinearizationState<'_>,
302 rows: std::ops::Range<usize>,
303 ) -> Result<Array2<f64>, String> {
304 let n = self.design_entry.nrows();
305 let p = self.design_entry.ncols();
306 let rows = rows.start.min(n)..rows.end.min(n);
307 let chunk = rows.end - rows.start;
308
309 if self.design_exit.nrows() != n || self.design_deriv.nrows() != n {
310 return Err(format!(
311 "TimeBlockJacobian: design row count mismatch \
312 entry={n} exit={} deriv={}",
313 self.design_exit.nrows(),
314 self.design_deriv.nrows(),
315 ));
316 }
317 if self.design_exit.ncols() != p || self.design_deriv.ncols() != p {
318 return Err(format!(
319 "TimeBlockJacobian: design col count mismatch \
320 entry={p} exit={} deriv={}",
321 self.design_exit.ncols(),
322 self.design_deriv.ncols(),
323 ));
324 }
325
326 let scalars: Option<&SurvivalMarginalSlopeFamilyScalars> = state
331 .family_scalars
332 .as_ref()
333 .and_then(|a| a.downcast_ref::<SurvivalMarginalSlopeFamilyScalars>());
334
335 let beta_nonzero = state.beta.iter().any(|&b| b != 0.0);
336 if beta_nonzero && scalars.is_none() {
337 return Err("survival marginal-slope time block requires \
338 SurvivalMarginalSlopeFamilyScalars when beta != 0 (c_i != 1 in general); \
339 got family_scalars: None. The caller must populate per-row c_i via \
340 FamilyLinearizationState::family_scalars."
341 .to_string());
342 }
343
344 let mut jac = Array2::<f64>::zeros((3 * chunk, p));
345
346 for i in rows.clone() {
347 let local_i = i - rows.start;
348 let c = match scalars {
349 Some(sc) => sc.c_i[i],
350 None => 1.0_f64,
352 };
353 for j in 0..p {
354 jac[[local_i, j]] = c * self.design_entry[[i, j]];
355 jac[[chunk + local_i, j]] = c * self.design_exit[[i, j]];
356 jac[[2 * chunk + local_i, j]] = c * self.design_deriv[[i, j]];
357 }
358 }
359 Ok(jac)
360 }
361
362 fn n_outputs(&self) -> usize {
363 3
364 }
365}
366
367pub struct SmsTimewiggleTimeJacobian {
404 pub(crate) design_entry: Arc<Array2<f64>>,
405 pub(crate) design_exit: Arc<Array2<f64>>,
406 pub(crate) design_deriv: Arc<Array2<f64>>,
407 pub(crate) design_marginal: Arc<Array2<f64>>,
408 pub(crate) design_logslope: Arc<Array2<f64>>,
409 pub(crate) offset_entry: Arc<Array1<f64>>,
410 pub(crate) offset_exit: Arc<Array1<f64>>,
411 pub(crate) offset_deriv: Arc<Array1<f64>>,
412 pub(crate) marginal_offset: Arc<Array1<f64>>,
416 pub(crate) time_wiggle_knots: Array1<f64>,
417 pub(crate) time_wiggle_degree: usize,
418 pub(crate) p_time: usize,
420 pub(crate) p_tw: usize,
422 pub(crate) p_m: usize,
424 pub(crate) p_g: usize,
426 pub(crate) probit_scale: f64,
428}
429
430impl SmsTimewiggleTimeJacobian {
431 pub fn new(
433 design_entry: Arc<Array2<f64>>,
434 design_exit: Arc<Array2<f64>>,
435 design_deriv: Arc<Array2<f64>>,
436 design_marginal: Arc<Array2<f64>>,
437 design_logslope: Arc<Array2<f64>>,
438 offset_entry: Arc<Array1<f64>>,
439 offset_exit: Arc<Array1<f64>>,
440 offset_deriv: Arc<Array1<f64>>,
441 marginal_offset: Arc<Array1<f64>>,
442 time_wiggle_knots: Array1<f64>,
443 time_wiggle_degree: usize,
444 p_tw: usize,
445 p_m: usize,
446 p_g: usize,
447 probit_scale: f64,
448 ) -> Self {
449 let p_time = design_entry.ncols();
450 Self {
451 design_entry,
452 design_exit,
453 design_deriv,
454 design_marginal,
455 design_logslope,
456 offset_entry,
457 offset_exit,
458 offset_deriv,
459 marginal_offset,
460 time_wiggle_knots,
461 time_wiggle_degree,
462 p_time,
463 p_tw,
464 p_m,
465 p_g,
466 probit_scale,
467 }
468 }
469}
470
471impl crate::custom_family::BlockEffectiveJacobian for SmsTimewiggleTimeJacobian {
472 fn effective_jacobian_rows(
473 &self,
474 state: &crate::custom_family::FamilyLinearizationState<'_>,
475 rows: std::ops::Range<usize>,
476 ) -> Result<Array2<f64>, String> {
477 let n = self.design_entry.nrows();
478 let p = self.p_time;
479 let rows = rows.start.min(n)..rows.end.min(n);
480 let chunk = rows.end - rows.start;
481 let p_base = p.saturating_sub(self.p_tw);
482
483 let beta = state.beta;
484 let beta_t = if beta.len() >= p { &beta[..p] } else { beta };
486 let beta_t_base = &beta_t[..p_base.min(beta_t.len())];
487 let zero_tw: Vec<f64>;
498 let beta_tw: &[f64] = if beta_t.len() >= p_base + self.p_tw {
499 &beta_t[p_base..p_base + self.p_tw]
500 } else {
501 zero_tw = vec![0.0; self.p_tw];
502 &zero_tw
503 };
504 let beta_m = {
506 let s = p;
507 let e = (s + self.p_m).min(beta.len());
508 if e > s { &beta[s..e] } else { &[][..] }
509 };
510 let beta_g = {
512 let s = p + self.p_m;
513 let e = (s + self.p_g).min(beta.len());
514 if e > s { &beta[s..e] } else { &[][..] }
515 };
516
517 let sc = self.probit_scale;
518 let knots = &self.time_wiggle_knots;
519 let degree = self.time_wiggle_degree;
520
521 let mut jac = Array2::<f64>::zeros((3 * chunk, p));
522
523 for i in rows.clone() {
524 let local_i = i - rows.start;
525 let g_i: f64 = beta_g
527 .iter()
528 .enumerate()
529 .filter(|&(j, _)| j < self.design_logslope.ncols())
530 .map(|(j, &b)| self.design_logslope[[i, j]] * b)
531 .sum();
532 let c_i = (1.0_f64 + (sc * g_i).powi(2)).sqrt();
533
534 let eta_m: f64 = beta_m
536 .iter()
537 .enumerate()
538 .filter(|&(j, _)| j < self.design_marginal.ncols())
539 .map(|(j, &b)| self.design_marginal[[i, j]] * b)
540 .sum();
541
542 let h0: f64 = self.offset_entry[i]
546 + eta_m
547 + self.marginal_offset[i]
548 + (0..p_base.min(beta_t_base.len()).min(self.design_entry.ncols()))
549 .map(|j| self.design_entry[[i, j]] * beta_t_base[j])
550 .sum::<f64>();
551 let h1: f64 = self.offset_exit[i]
552 + eta_m
553 + self.marginal_offset[i]
554 + (0..p_base.min(beta_t_base.len()).min(self.design_exit.ncols()))
555 .map(|j| self.design_exit[[i, j]] * beta_t_base[j])
556 .sum::<f64>();
557 let d_raw: f64 = self.offset_deriv[i]
558 + (0..p_base.min(beta_t_base.len()).min(self.design_deriv.ncols()))
559 .map(|j| self.design_deriv[[i, j]] * beta_t_base[j])
560 .sum::<f64>();
561
562 let beta_tw_view = ndarray::ArrayView1::from(beta_tw);
563 let eg = sms_tw_first_order_geom(
564 ndarray::ArrayView1::from(&[h0][..]),
565 beta_tw_view,
566 knots,
567 degree,
568 )?;
569 let xg = sms_tw_first_order_geom(
570 ndarray::ArrayView1::from(&[h1][..]),
571 beta_tw_view,
572 knots,
573 degree,
574 )?;
575
576 let (entry_dq, exit_dq, exit_d2q, entry_basis, exit_basis, exit_basis_d1) =
577 match (eg, xg) {
578 (Some(eg), Some(xg)) => (
579 eg.dq_dq0[0],
580 xg.dq_dq0[0],
581 xg.d2q_dq02[0],
582 Some(eg.basis),
583 Some(xg.basis),
584 Some(xg.basis_d1),
585 ),
586 _ => (1.0_f64, 1.0_f64, 0.0_f64, None, None, None),
587 };
588
589 for j in 0..p_base.min(self.design_entry.ncols()) {
591 let xe = self.design_entry[[i, j]];
592 let xx = self.design_exit[[i, j]];
593 let xd = self.design_deriv[[i, j]];
594 jac[[local_i, j]] = c_i * entry_dq * xe;
595 jac[[chunk + local_i, j]] = c_i * exit_dq * xx;
596 jac[[2 * chunk + local_i, j]] = c_i * (exit_d2q * d_raw * xx + exit_dq * xd);
597 }
598
599 for local_idx in 0..self.p_tw {
601 let col = p_base + local_idx;
602 let b0 = entry_basis.as_ref().map_or(0.0, |b| b[[0, local_idx]]);
603 let b1 = exit_basis.as_ref().map_or(0.0, |b| b[[0, local_idx]]);
604 let bd1 = exit_basis_d1.as_ref().map_or(0.0, |b| b[[0, local_idx]]);
605 jac[[local_i, col]] = c_i * b0;
606 jac[[chunk + local_i, col]] = c_i * b1;
607 jac[[2 * chunk + local_i, col]] = c_i * bd1 * d_raw;
608 }
609 }
610 Ok(jac)
611 }
612
613 fn n_outputs(&self) -> usize {
614 3
615 }
616}
617
618pub struct SmsTimewiggleMarginalJacobian {
621 pub(crate) design_entry: Arc<Array2<f64>>,
622 pub(crate) design_exit: Arc<Array2<f64>>,
623 pub(crate) design_deriv: Arc<Array2<f64>>,
624 pub(crate) design_marginal: Arc<Array2<f64>>,
625 pub(crate) design_logslope: Arc<Array2<f64>>,
626 pub(crate) offset_entry: Arc<Array1<f64>>,
627 pub(crate) offset_exit: Arc<Array1<f64>>,
628 pub(crate) offset_deriv: Arc<Array1<f64>>,
629 pub(crate) marginal_offset: Arc<Array1<f64>>,
632 pub(crate) time_wiggle_knots: Array1<f64>,
633 pub(crate) time_wiggle_degree: usize,
634 pub(crate) p_time: usize,
635 pub(crate) p_tw: usize,
636 pub(crate) p_g: usize,
637 pub(crate) probit_scale: f64,
638}
639
640impl SmsTimewiggleMarginalJacobian {
641 pub fn new(
643 design_entry: Arc<Array2<f64>>,
644 design_exit: Arc<Array2<f64>>,
645 design_deriv: Arc<Array2<f64>>,
646 design_marginal: Arc<Array2<f64>>,
647 design_logslope: Arc<Array2<f64>>,
648 offset_entry: Arc<Array1<f64>>,
649 offset_exit: Arc<Array1<f64>>,
650 offset_deriv: Arc<Array1<f64>>,
651 marginal_offset: Arc<Array1<f64>>,
652 time_wiggle_knots: Array1<f64>,
653 time_wiggle_degree: usize,
654 p_time: usize,
655 p_tw: usize,
656 p_g: usize,
657 probit_scale: f64,
658 ) -> Self {
659 Self {
660 design_entry,
661 design_exit,
662 design_deriv,
663 design_marginal,
664 design_logslope,
665 offset_entry,
666 offset_exit,
667 offset_deriv,
668 marginal_offset,
669 time_wiggle_knots,
670 time_wiggle_degree,
671 p_time,
672 p_tw,
673 p_g,
674 probit_scale,
675 }
676 }
677}
678
679impl crate::custom_family::BlockEffectiveJacobian for SmsTimewiggleMarginalJacobian {
680 fn effective_jacobian_rows(
681 &self,
682 state: &crate::custom_family::FamilyLinearizationState<'_>,
683 rows: std::ops::Range<usize>,
684 ) -> Result<Array2<f64>, String> {
685 let n = self.design_marginal.nrows();
686 let p_m = self.design_marginal.ncols();
687 let rows = rows.start.min(n)..rows.end.min(n);
688 let chunk = rows.end - rows.start;
689 let p_t = self.p_time;
690 let p_base = p_t.saturating_sub(self.p_tw);
691
692 let beta = state.beta;
693 let beta_t = if beta.len() >= p_t {
694 &beta[..p_t]
695 } else {
696 beta
697 };
698 let beta_t_base = &beta_t[..p_base.min(beta_t.len())];
699 let beta_tw = if beta_t.len() > p_base {
700 &beta_t[p_base..]
701 } else {
702 &[][..]
703 };
704 let beta_m = {
705 let s = p_t;
706 let e = (s + p_m).min(beta.len());
707 if e > s { &beta[s..e] } else { &[][..] }
708 };
709 let beta_g = {
710 let s = p_t + p_m;
711 let e = (s + self.p_g).min(beta.len());
712 if e > s { &beta[s..e] } else { &[][..] }
713 };
714
715 let sc = self.probit_scale;
716 let knots = &self.time_wiggle_knots;
717 let degree = self.time_wiggle_degree;
718
719 let mut jac = Array2::<f64>::zeros((3 * chunk, p_m));
720
721 for i in rows.clone() {
722 let local_i = i - rows.start;
723 let g_i: f64 = beta_g
724 .iter()
725 .enumerate()
726 .filter(|&(j, _)| j < self.design_logslope.ncols())
727 .map(|(j, &b)| self.design_logslope[[i, j]] * b)
728 .sum();
729 let c_i = (1.0_f64 + (sc * g_i).powi(2)).sqrt();
730
731 let eta_m: f64 = beta_m
732 .iter()
733 .enumerate()
734 .filter(|&(j, _)| j < p_m)
735 .map(|(j, &b)| self.design_marginal[[i, j]] * b)
736 .sum();
737
738 let h0: f64 = self.offset_entry[i]
741 + eta_m
742 + self.marginal_offset[i]
743 + (0..p_base.min(beta_t_base.len()).min(self.design_entry.ncols()))
744 .map(|j| self.design_entry[[i, j]] * beta_t_base[j])
745 .sum::<f64>();
746 let h1: f64 = self.offset_exit[i]
747 + eta_m
748 + self.marginal_offset[i]
749 + (0..p_base.min(beta_t_base.len()).min(self.design_exit.ncols()))
750 .map(|j| self.design_exit[[i, j]] * beta_t_base[j])
751 .sum::<f64>();
752 let d_raw: f64 = self.offset_deriv[i]
753 + (0..p_base.min(beta_t_base.len()).min(self.design_deriv.ncols()))
754 .map(|j| self.design_deriv[[i, j]] * beta_t_base[j])
755 .sum::<f64>();
756
757 let beta_tw_view = ndarray::ArrayView1::from(beta_tw);
758 let eg = sms_tw_first_order_geom(
759 ndarray::ArrayView1::from(&[h0][..]),
760 beta_tw_view,
761 knots,
762 degree,
763 )?;
764 let xg = sms_tw_first_order_geom(
765 ndarray::ArrayView1::from(&[h1][..]),
766 beta_tw_view,
767 knots,
768 degree,
769 )?;
770
771 let (entry_dq, exit_dq, exit_d2q) = match (eg, xg) {
772 (Some(eg), Some(xg)) => (eg.dq_dq0[0], xg.dq_dq0[0], xg.d2q_dq02[0]),
773 _ => (1.0_f64, 1.0_f64, 0.0_f64),
774 };
775
776 for j in 0..p_m {
777 let m_ij = self.design_marginal[[i, j]];
778 jac[[local_i, j]] = c_i * entry_dq * m_ij;
779 jac[[chunk + local_i, j]] = c_i * exit_dq * m_ij;
780 jac[[2 * chunk + local_i, j]] = c_i * exit_d2q * d_raw * m_ij;
781 }
782 }
783 Ok(jac)
784 }
785
786 fn n_outputs(&self) -> usize {
787 3
788 }
789}
790
791pub(crate) fn sms_tw_first_order_geom(
798 h0: ndarray::ArrayView1<'_, f64>,
799 beta_tw: ndarray::ArrayView1<'_, f64>,
800 knots: &Array1<f64>,
801 degree: usize,
802) -> Result<Option<SurvivalTimeWiggleFirstOrderGeometry>, String> {
803 if beta_tw.is_empty() {
804 return Ok(None);
805 }
806 let basis = monotone_wiggle_basis_with_derivative_order(h0, knots, degree, 0)?;
807 let basis_d1 = monotone_wiggle_basis_with_derivative_order(h0, knots, degree, 1)?;
808 let basis_d2 = monotone_wiggle_basis_with_derivative_order(h0, knots, degree, 2)?;
809 if basis.ncols() != beta_tw.len()
810 || basis_d1.ncols() != beta_tw.len()
811 || basis_d2.ncols() != beta_tw.len()
812 {
813 return Err(format!(
814 "sms_tw_first_order_geom: basis/beta_tw width mismatch \
815 B/B'/B''={}/{}/{} beta_tw={}",
816 basis.ncols(),
817 basis_d1.ncols(),
818 basis_d2.ncols(),
819 beta_tw.len(),
820 ));
821 }
822 let dq_dq0 = fast_av(&basis_d1, &beta_tw) + 1.0;
823 let d2q_dq02 = fast_av(&basis_d2, &beta_tw);
824 Ok(Some(SurvivalTimeWiggleFirstOrderGeometry {
825 basis,
826 basis_d1,
827 basis_d2,
828 dq_dq0,
829 d2q_dq02,
830 }))
831}