1use crate::{BasisError, PenaltyMatrix};
13use ndarray::{Array1, Array2, ArrayView1, ArrayViewMut1};
14use std::any::Any;
15use std::ops::Range;
16use std::sync::Arc;
17
18#[derive(Clone)]
19pub struct CustomFamilyBlockPsiDerivative {
20 pub penalty_index: Option<usize>,
21 pub x_psi: Array2<f64>,
22 pub s_psi: Array2<f64>,
23 pub s_psi_components: Option<Vec<(usize, Array2<f64>)>>,
24 pub s_psi_penalty_components: Option<Vec<(usize, PenaltyMatrix)>>,
25 pub x_psi_psi: Option<Vec<Array2<f64>>>,
26 pub s_psi_psi: Option<Vec<Array2<f64>>>,
27 pub s_psi_psi_components: Option<Vec<Vec<(usize, Array2<f64>)>>>,
28 pub s_psi_psi_penalty_components: Option<Vec<Vec<(usize, PenaltyMatrix)>>>,
29 pub implicit_operator: Option<Arc<dyn CustomFamilyPsiDerivativeOperator>>,
30 pub implicit_axis: usize,
31 pub implicit_group_id: Option<usize>,
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum CustomFamilyHyperAxis {
48 DesignPenalty {
49 block: usize,
50 derivative_index: usize,
51 },
52 Family {
53 family_axis: usize,
54 },
55}
56
57#[derive(Clone)]
64pub struct CustomFamilyHyperLayout {
65 design_derivative_blocks: Vec<Vec<CustomFamilyBlockPsiDerivative>>,
66 family_axes: Vec<usize>,
67 values: Array1<f64>,
68 design_axis_count: usize,
69 axis_count: usize,
70}
71
72impl CustomFamilyHyperLayout {
73 pub fn new(
80 design_derivative_blocks: Vec<Vec<CustomFamilyBlockPsiDerivative>>,
81 family_axes: Vec<usize>,
82 values: Array1<f64>,
83 ) -> Result<Self, String> {
84 for (expected, &actual) in family_axes.iter().enumerate() {
85 if actual != expected {
86 return Err(format!(
87 "custom-family hyper layout family axes must be contiguous and ordered: \
88 position {expected} carries family axis {actual}"
89 ));
90 }
91 }
92 let design_axis_count = design_derivative_blocks.iter().try_fold(
93 0usize,
94 |count, derivatives| {
95 count.checked_add(derivatives.len()).ok_or_else(|| {
96 "custom-family hyper layout design-axis count exceeds usize".to_string()
97 })
98 },
99 )?;
100 let axis_count = design_axis_count
101 .checked_add(family_axes.len())
102 .ok_or_else(|| "custom-family hyper layout axis count exceeds usize".to_string())?;
103 if values.len() != axis_count {
104 return Err(format!(
105 "custom-family hyper layout value length mismatch: got {}, expected {axis_count}",
106 values.len()
107 ));
108 }
109 if let Some((axis, value)) = values
110 .iter()
111 .copied()
112 .enumerate()
113 .find(|(_, value)| !value.is_finite())
114 {
115 return Err(format!(
116 "custom-family hyper layout axis {axis} has non-finite value {value}"
117 ));
118 }
119 Ok(Self {
120 design_derivative_blocks,
121 family_axes,
122 values,
123 design_axis_count,
124 axis_count,
125 })
126 }
127
128 pub fn block_count(&self) -> usize {
129 self.design_derivative_blocks.len()
130 }
131
132 pub fn design_axis_count(&self) -> usize {
133 self.design_axis_count
134 }
135
136 pub fn family_axis_count(&self) -> usize {
137 self.family_axes.len()
138 }
139
140 pub fn len(&self) -> usize {
141 self.axis_count
142 }
143
144 pub fn is_empty(&self) -> bool {
145 self.len() == 0
146 }
147
148 pub fn design_derivative_blocks(&self) -> &[Vec<CustomFamilyBlockPsiDerivative>] {
149 &self.design_derivative_blocks
150 }
151
152 pub fn family_axes(&self) -> &[usize] {
153 &self.family_axes
154 }
155
156 pub fn values(&self) -> &Array1<f64> {
161 &self.values
162 }
163
164 pub fn axis(&self, global_index: usize) -> Option<CustomFamilyHyperAxis> {
166 if global_index < self.design_axis_count {
167 let mut remaining = global_index;
168 return self
169 .design_derivative_blocks
170 .iter()
171 .enumerate()
172 .find_map(|(block, derivatives)| {
173 if remaining < derivatives.len() {
174 Some(CustomFamilyHyperAxis::DesignPenalty {
175 block,
176 derivative_index: remaining,
177 })
178 } else {
179 remaining -= derivatives.len();
180 None
181 }
182 });
183 }
184 let family_offset = global_index.checked_sub(self.design_axis_count)?;
185 self.family_axes
186 .get(family_offset)
187 .copied()
188 .map(|family_axis| CustomFamilyHyperAxis::Family { family_axis })
189 }
190
191 pub fn design_derivative(
192 &self,
193 global_index: usize,
194 ) -> Option<(usize, usize, &CustomFamilyBlockPsiDerivative)> {
195 match self.axis(global_index)? {
196 CustomFamilyHyperAxis::DesignPenalty {
197 block,
198 derivative_index,
199 } => self
200 .design_derivative_blocks
201 .get(block)?
202 .get(derivative_index)
203 .map(|derivative| (block, derivative_index, derivative)),
204 CustomFamilyHyperAxis::Family { .. } => None,
205 }
206 }
207
208 pub fn family_axis(&self, global_index: usize) -> Option<usize> {
209 match self.axis(global_index)? {
210 CustomFamilyHyperAxis::Family { family_axis } => Some(family_axis),
211 CustomFamilyHyperAxis::DesignPenalty { .. } => None,
212 }
213 }
214}
215
216pub type SharedCustomFamilyHyperLayout = Arc<CustomFamilyHyperLayout>;
217
218impl CustomFamilyBlockPsiDerivative {
219 pub fn new(
222 penalty_index: Option<usize>,
223 x_psi: Array2<f64>,
224 s_psi: Array2<f64>,
225 s_psi_components: Option<Vec<(usize, Array2<f64>)>>,
226 x_psi_psi: Option<Vec<Array2<f64>>>,
227 s_psi_psi: Option<Vec<Array2<f64>>>,
228 s_psi_psi_components: Option<Vec<Vec<(usize, Array2<f64>)>>>,
229 ) -> Self {
230 Self {
231 penalty_index,
232 x_psi,
233 s_psi,
234 s_psi_components,
235 s_psi_penalty_components: None,
236 x_psi_psi,
237 s_psi_psi,
238 s_psi_psi_components,
239 s_psi_psi_penalty_components: None,
240 implicit_operator: None,
241 implicit_axis: 0,
242 implicit_group_id: None,
243 }
244 }
245}
246
247pub trait CustomFamilyPsiDerivativeOperator: Send + Sync + Any {
248 fn as_any(&self) -> &dyn Any;
249 fn n_data(&self) -> usize;
250 fn p_out(&self) -> usize;
251 fn transpose_mul(
252 &self,
253 axis: usize,
254 v: &ArrayView1<'_, f64>,
255 ) -> Result<Array1<f64>, BasisError>;
256 fn forward_mul(&self, axis: usize, u: &ArrayView1<'_, f64>) -> Result<Array1<f64>, BasisError>;
257 fn transpose_mul_second_diag(
258 &self,
259 axis: usize,
260 v: &ArrayView1<'_, f64>,
261 ) -> Result<Array1<f64>, BasisError>;
262 fn transpose_mul_second_cross(
263 &self,
264 axis_d: usize,
265 axis_e: usize,
266 v: &ArrayView1<'_, f64>,
267 ) -> Result<Array1<f64>, BasisError>;
268 fn forward_mul_second_diag(
269 &self,
270 axis: usize,
271 u: &ArrayView1<'_, f64>,
272 ) -> Result<Array1<f64>, BasisError>;
273 fn forward_mul_second_cross(
274 &self,
275 axis_d: usize,
276 axis_e: usize,
277 u: &ArrayView1<'_, f64>,
278 ) -> Result<Array1<f64>, BasisError>;
279 fn row_chunk_first(&self, axis: usize, rows: Range<usize>) -> Result<Array2<f64>, BasisError>;
280 fn row_vector_first_into(
285 &self,
286 axis: usize,
287 row: usize,
288 mut out: ArrayViewMut1<'_, f64>,
289 ) -> Result<(), BasisError> {
290 let chunk = self.row_chunk_first(axis, row..row + 1)?;
291 out.assign(&chunk.row(0));
292 Ok(())
293 }
294 fn row_chunk_second_diag(
295 &self,
296 axis: usize,
297 rows: Range<usize>,
298 ) -> Result<Array2<f64>, BasisError>;
299 fn row_chunk_second_cross(
300 &self,
301 axis_d: usize,
302 axis_e: usize,
303 rows: Range<usize>,
304 ) -> Result<Array2<f64>, BasisError>;
305
306 fn as_materializable(&self) -> Option<&dyn MaterializablePsiDerivativeOperator> {
312 None
313 }
314}
315
316pub trait MaterializablePsiDerivativeOperator: CustomFamilyPsiDerivativeOperator {
321 fn materialize_first(&self, axis: usize) -> Result<Array2<f64>, BasisError>;
322}
323
324#[derive(Clone, Copy, Debug, PartialEq, Eq)]
325pub enum JointHessianSourcePreference {
326 Dense,
327 Operator,
328}
329
330#[derive(Clone, Copy, Debug, PartialEq, Eq)]
351pub enum MaterializationIntent {
352 InnerSolve,
354 LogdetFactorization,
356 OuterEvaluation,
359 OuterGradient,
361}