1use std::collections::HashSet;
2
3use crate::{
4 LinearOp, LinearOpTranspose, Matrix, MatrixSparsity, NonLinearOp, NonLinearOpAdjoint,
5 NonLinearOpJacobian, NonLinearOpSens, NonLinearOpSensAdjoint, Scalar, Vector, VectorIndex,
6};
7use num_traits::{One, Zero};
8
9use self::{coloring::nonzeros2graph, greedy_coloring::color_graph_greedy};
10
11pub mod coloring;
12pub mod graph;
13pub mod greedy_coloring;
14
15macro_rules! gen_find_non_zeros_nonlinear {
16 ($name:ident, $op_fn:ident, $op_trait:ident) => {
17 pub fn $name<F: NonLinearOp + $op_trait + ?Sized>(
22 op: &F,
23 x: &F::V,
24 t: F::T,
25 ) -> Vec<(usize, usize)> {
26 let mut v = F::V::zeros(op.nstates(), op.context().clone());
27 let mut col = F::V::zeros(op.nout(), op.context().clone());
28 let mut triplets = Vec::with_capacity(op.nstates());
29 for j in 0..op.nstates() {
30 v.set_index(j, F::T::NAN);
31 op.$op_fn(x, t, &v, &mut col);
32 for i in 0..op.nout() {
33 if col.get_index(i).is_nan() {
34 triplets.push((i, j));
35 }
36 col.set_index(i, F::T::zero());
37 }
38 col.fill(F::T::zero());
45 v.set_index(j, F::T::zero());
46 }
47 triplets
48 }
49 };
50}
51
52gen_find_non_zeros_nonlinear!(
53 find_jacobian_non_zeros,
54 jac_mul_inplace,
55 NonLinearOpJacobian
56);
57gen_find_non_zeros_nonlinear!(
58 find_adjoint_non_zeros,
59 jac_transpose_mul_inplace,
60 NonLinearOpAdjoint
61);
62gen_find_non_zeros_nonlinear!(find_sens_non_zeros, sens_mul_inplace, NonLinearOpSens);
63gen_find_non_zeros_nonlinear!(
64 find_sens_adjoint_non_zeros,
65 sens_transpose_mul_inplace,
66 NonLinearOpSensAdjoint
67);
68
69macro_rules! gen_find_non_zeros_linear {
70 ($name:ident, $op_fn:ident $(, $op_trait:tt )?) => {
71 pub fn $name<F: LinearOp + ?Sized $(+ $op_trait)?>(op: &F, t: F::T) -> Vec<(usize, usize)> {
76 let mut v = F::V::zeros(op.nstates(), op.context().clone());
77 let mut col = F::V::zeros(op.nout(), op.context().clone());
78 let mut triplets = Vec::with_capacity(op.nstates());
79 for j in 0..op.nstates() {
80 v.set_index(j, F::T::NAN);
81 op.$op_fn(&v, t, &mut col);
82 for i in 0..op.nout() {
83 if col.get_index(i).is_nan() {
84 triplets.push((i, j));
85 }
86 col.set_index(i, F::T::zero());
87 }
88 v.set_index(j, F::T::zero());
95 }
96 triplets
97 }
98 };
99}
100
101gen_find_non_zeros_linear!(find_matrix_non_zeros, call_inplace);
102gen_find_non_zeros_linear!(
103 find_transpose_non_zeros,
104 call_transpose_inplace,
105 LinearOpTranspose
106);
107
108#[derive(Clone)]
109pub struct JacobianColoring<M: Matrix> {
110 dst_indices_per_color: Vec<<M::V as Vector>::Index>,
111 src_indices_per_color: Vec<<M::V as Vector>::Index>,
112 input_indices_per_color: Vec<<M::V as Vector>::Index>,
113}
114
115impl<M: Matrix> JacobianColoring<M> {
116 pub fn new(sparsity: &impl MatrixSparsity<M>, non_zeros: &[(usize, usize)], ctx: M::C) -> Self {
117 let ncols = sparsity.ncols();
118 let graph = nonzeros2graph(non_zeros, ncols);
119 let coloring = color_graph_greedy(&graph);
120 let max_color = coloring.iter().max().copied().unwrap_or(0);
121 let mut dst_indices_per_color = Vec::new();
122 let mut src_indices_per_color = Vec::new();
123 let mut input_indices_per_color = Vec::new();
124 for c in 1..=max_color {
125 let mut rows = Vec::new();
126 let mut cols = Vec::new();
127 let mut indices = Vec::new();
128 for (i, j) in non_zeros {
129 if coloring[*j] == c {
130 rows.push(*i);
131 cols.push(*j);
132 indices.push((*i, *j));
133 }
134 }
135 let dst_indices = sparsity.get_index(indices.as_slice(), ctx.clone());
136 let src_indices = <M::V as Vector>::Index::from_vec(rows, ctx.clone());
137 let unique_cols: HashSet<_> = HashSet::from_iter(cols.iter().cloned());
138 let unique_cols = unique_cols.into_iter().collect::<Vec<_>>();
139 let input_indices = <M::V as Vector>::Index::from_vec(unique_cols, ctx.clone());
140 dst_indices_per_color.push(dst_indices);
141 src_indices_per_color.push(src_indices);
142 input_indices_per_color.push(input_indices);
143 }
144 Self {
145 dst_indices_per_color,
146 src_indices_per_color,
147 input_indices_per_color,
148 }
149 }
150
151 pub fn jacobian_inplace<F: NonLinearOpJacobian<M = M, V = M::V, T = M::T, C = M::C>>(
162 &self,
163 op: &F,
164 x: &F::V,
165 t: F::T,
166 y: &mut F::M,
167 ) {
168 let mut v = F::V::zeros(op.nstates(), op.context().clone());
169 let mut col = F::V::zeros(op.nout(), op.context().clone());
170 for c in 0..self.dst_indices_per_color.len() {
171 let input = &self.input_indices_per_color[c];
172 let dst_indices = &self.dst_indices_per_color[c];
173 let src_indices = &self.src_indices_per_color[c];
174 v.assign_at_indices(input, F::T::one());
175 op.jac_mul_inplace(x, t, &v, &mut col);
176 y.set_data_with_indices(dst_indices, src_indices, &col);
177 v.assign_at_indices(input, F::T::zero());
178 }
179 }
180
181 pub fn adjoint_inplace<F: NonLinearOpAdjoint<M = M, V = M::V, T = M::T, C = M::C>>(
182 &self,
183 op: &F,
184 x: &F::V,
185 t: F::T,
186 y: &mut F::M,
187 ) {
188 let mut v = F::V::zeros(op.nstates(), op.context().clone());
189 let mut col = F::V::zeros(op.nout(), op.context().clone());
190 for c in 0..self.dst_indices_per_color.len() {
191 let input = &self.input_indices_per_color[c];
192 let dst_indices = &self.dst_indices_per_color[c];
193 let src_indices = &self.src_indices_per_color[c];
194 v.assign_at_indices(input, F::T::one());
195 op.jac_transpose_mul_inplace(x, t, &v, &mut col);
196 y.set_data_with_indices(dst_indices, src_indices, &col);
197 v.assign_at_indices(input, F::T::zero());
198 }
199 }
200
201 pub fn sens_adjoint_inplace<F: NonLinearOpSensAdjoint<M = M, V = M::V, T = M::T, C = M::C>>(
202 &self,
203 op: &F,
204 x: &F::V,
205 t: F::T,
206 y: &mut F::M,
207 ) {
208 let mut v = F::V::zeros(op.nstates(), op.context().clone());
209 let mut col = F::V::zeros(op.nout(), op.context().clone());
210 for c in 0..self.dst_indices_per_color.len() {
211 let input = &self.input_indices_per_color[c];
212 let dst_indices = &self.dst_indices_per_color[c];
213 let src_indices = &self.src_indices_per_color[c];
214 v.assign_at_indices(input, F::T::one());
215 op.sens_transpose_mul_inplace(x, t, &v, &mut col);
216 y.set_data_with_indices(dst_indices, src_indices, &col);
217 v.assign_at_indices(input, F::T::zero());
218 }
219 }
220
221 pub fn matrix_inplace<F: LinearOp<M = M, V = M::V, T = M::T, C = M::C>>(
222 &self,
223 op: &F,
224 t: F::T,
225 y: &mut F::M,
226 ) {
227 let mut v = F::V::zeros(op.nstates(), op.context().clone());
228 let mut col = F::V::zeros(op.nout(), op.context().clone());
229 for c in 0..self.dst_indices_per_color.len() {
230 let input = &self.input_indices_per_color[c];
231 let dst_indices = &self.dst_indices_per_color[c];
232 let src_indices = &self.src_indices_per_color[c];
233 v.assign_at_indices(input, F::T::one());
234 op.call_inplace(&v, t, &mut col);
235 y.set_data_with_indices(dst_indices, src_indices, &col);
236 v.assign_at_indices(input, F::T::zero());
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243
244 use crate::jacobian::{find_jacobian_non_zeros, JacobianColoring};
245 use crate::matrix::dense_nalgebra_serial::NalgebraMat;
246 use crate::matrix::Matrix;
247 use crate::op::linear_closure::LinearClosure;
248 use crate::op::ParameterisedOp;
249 use crate::vector::Vector;
250 use crate::{
251 jacobian::{coloring::nonzeros2graph, greedy_coloring::color_graph_greedy},
252 op::closure::Closure,
253 LinearOp, Op,
254 };
255 use crate::{scale, FaerSparseMat, NonLinearOpJacobian};
256 use num_traits::{One, Zero};
257 use std::ops::MulAssign;
258
259 #[allow(clippy::type_complexity)]
260 fn helper_triplets2op_nonlinear<'a, M: Matrix + 'a>(
261 triplets: &'a [(usize, usize, M::T)],
262 p: &'a M::V,
263 nrows: usize,
264 ncols: usize,
265 ) -> Closure<
266 M,
267 impl Fn(&M::V, &M::V, M::T, &mut M::V) + use<'a, M>,
268 impl Fn(&M::V, &M::V, M::T, &M::V, &mut M::V) + use<'a, M>,
269 > {
270 let nstates = ncols;
271 let nout = nrows;
272 let f = move |x: &M::V, y: &mut M::V| {
273 for (i, j, v) in triplets {
274 y.set_index(*i, y.get_index(*i) + x.get_index(*j) * *v);
275 }
276 };
277 let mut ret = Closure::new(
278 move |x: &M::V, _p: &M::V, _t, y: &mut M::V| {
279 y.fill(M::T::zero());
280 f(x, y);
281 },
282 move |_x: &M::V, _p: &M::V, _t, v, y: &mut M::V| {
283 y.fill(M::T::zero());
284 f(v, y);
285 },
286 nstates,
287 nout,
288 p.len(),
289 p.context().clone(),
290 );
291 let y0 = M::V::zeros(nstates, p.context().clone());
292 let t0 = M::T::zero();
293 ret.calculate_sparsity(&y0, t0, p);
294 ret
295 }
296
297 #[allow(clippy::type_complexity)]
298 fn helper_triplets2op_linear<'a, M: Matrix + 'a>(
299 triplets: &'a [(usize, usize, M::T)],
300 p: &'a M::V,
301 nrows: usize,
302 ncols: usize,
303 ) -> LinearClosure<M, impl Fn(&M::V, &M::V, M::T, M::T, &mut M::V) + use<'a, M>> {
304 let nstates = ncols;
305 let nout = nrows;
306 let f = move |x: &M::V, y: &mut M::V| {
307 for (i, j, v) in triplets {
308 y.set_index(*i, y.get_index(*i) + x.get_index(*j) * *v);
309 }
310 };
311 let mut ret = LinearClosure::new(
312 move |x: &M::V, _p: &M::V, _t, beta, y: &mut M::V| {
313 y.mul_assign(scale(beta));
314 f(x, y);
315 },
316 nstates,
317 nout,
318 p.len(),
319 p.context().clone(),
320 );
321 let t0 = M::T::zero();
322 ret.calculate_sparsity(t0, p);
323 ret
324 }
325
326 fn find_non_zeros<M: Matrix>() {
327 let test_triplets = vec![
328 vec![(0, 0, M::T::one()), (1, 1, M::T::one())],
329 vec![
330 (0, 0, M::T::one()),
331 (0, 1, M::T::one()),
332 (1, 1, M::T::one()),
333 ],
334 vec![(1, 1, M::T::one())],
335 vec![
336 (0, 0, M::T::one()),
337 (1, 0, M::T::one()),
338 (0, 1, M::T::one()),
339 (1, 1, M::T::one()),
340 ],
341 ];
342 let p = M::V::zeros(0, Default::default());
343 for triplets in test_triplets {
344 let op = helper_triplets2op_nonlinear::<M>(triplets.as_slice(), &p, 2, 2);
345 let op = ParameterisedOp::new(&op, &p);
346 let non_zeros =
347 find_jacobian_non_zeros(&op, &M::V::zeros(2, p.context().clone()), M::T::zero());
348 let expect = triplets
349 .iter()
350 .map(|(i, j, _v)| (*i, *j))
351 .collect::<Vec<_>>();
352 assert_eq!(non_zeros, expect);
353 }
354 }
355
356 #[test]
357 fn find_non_zeros_dmatrix() {
358 find_non_zeros::<NalgebraMat<f64>>();
359 }
360
361 #[test]
362 fn find_non_zeros_faer_sparse() {
363 find_non_zeros::<FaerSparseMat<f64>>();
364 }
365
366 fn build_coloring<M: Matrix>() {
367 let test_triplets = [
368 vec![(0, 0, M::T::one()), (1, 1, M::T::one())],
369 vec![
370 (0, 0, M::T::one()),
371 (0, 1, M::T::one()),
372 (1, 1, M::T::one()),
373 ],
374 vec![(1, 1, M::T::one())],
375 vec![
376 (0, 0, M::T::one()),
377 (1, 0, M::T::one()),
378 (0, 1, M::T::one()),
379 (1, 1, M::T::one()),
380 ],
381 ];
382 let expect = vec![vec![1, 1], vec![1, 2], vec![1, 1], vec![1, 2]];
383 let p = M::V::zeros(0, Default::default());
384 for (triplets, expect) in test_triplets.iter().zip(expect) {
385 let op = helper_triplets2op_nonlinear::<M>(triplets.as_slice(), &p, 2, 2);
386 let op = ParameterisedOp::new(&op, &p);
387 let non_zeros =
388 find_jacobian_non_zeros(&op, &M::V::zeros(2, p.context().clone()), M::T::zero());
389 let ncols = op.nstates();
390 let graph = nonzeros2graph(non_zeros.as_slice(), ncols);
391 let coloring = color_graph_greedy(&graph);
392
393 assert_eq!(coloring, expect);
394 }
395 }
396
397 #[test]
398 fn build_coloring_dmatrix() {
399 build_coloring::<NalgebraMat<f64>>();
400 }
401
402 #[test]
403 fn build_coloring_faer_sparse() {
404 build_coloring::<FaerSparseMat<f64>>();
405 }
406
407 fn matrix_coloring<M: Matrix>() {
408 let test_triplets = vec![
409 vec![
410 (0, 0, M::T::one()),
411 (1, 1, M::T::one()),
412 (2, 2, M::T::one()),
413 ],
414 vec![(0, 0, M::T::one()), (1, 1, M::T::one())],
415 vec![
416 (0, 0, M::T::from(0.9)),
417 (1, 0, M::T::from(2.0)),
418 (1, 1, M::T::from(1.1)),
419 (2, 2, M::T::from(1.4)),
420 ],
421 ];
422 let n = 3;
423
424 let p = M::V::zeros(0, Default::default());
426 for triplets in test_triplets.iter() {
427 let op = helper_triplets2op_nonlinear::<M>(triplets.as_slice(), &p, n, n);
428 let op = ParameterisedOp::new(&op, &p);
429 let y0 = M::V::zeros(n, p.context().clone());
430 let t0 = M::T::zero();
431 let nonzeros = triplets
432 .iter()
433 .map(|(i, j, _v)| (*i, *j))
434 .collect::<Vec<_>>();
435 let coloring = JacobianColoring::new(
436 &op.jacobian_sparsity().unwrap(),
437 &nonzeros,
438 p.context().clone(),
439 );
440 let mut jac = M::new_from_sparsity(3, 3, op.jacobian_sparsity(), p.context().clone());
441 coloring.jacobian_inplace(&op, &y0, t0, &mut jac);
442 let mut gemv1 = M::V::zeros(n, p.context().clone());
443 let v = M::V::from_element(3, M::T::one(), p.context().clone());
444 op.jac_mul_inplace(&y0, t0, &v, &mut gemv1);
445 let mut gemv2 = M::V::zeros(n, p.context().clone());
446 jac.gemv(M::T::one(), &v, M::T::zero(), &mut gemv2);
447 gemv1.assert_eq_st(&gemv2, M::T::from(1e-10));
448 }
449
450 let p = M::V::zeros(0, p.context().clone());
452 for triplets in test_triplets {
453 let op = helper_triplets2op_linear::<M>(triplets.as_slice(), &p, n, n);
454 let op = ParameterisedOp::new(&op, &p);
455 let t0 = M::T::zero();
456 let nonzeros = triplets
457 .iter()
458 .map(|(i, j, _v)| (*i, *j))
459 .collect::<Vec<_>>();
460 let coloring =
461 JacobianColoring::new(&op.sparsity().unwrap(), &nonzeros, p.context().clone());
462 let mut jac = M::new_from_sparsity(3, 3, op.sparsity(), p.context().clone());
463 coloring.matrix_inplace(&op, t0, &mut jac);
464 let mut gemv1 = M::V::zeros(n, p.context().clone());
465 let v = M::V::from_element(3, M::T::one(), p.context().clone());
466 op.gemv_inplace(&v, t0, M::T::zero(), &mut gemv1);
467 let mut gemv2 = M::V::zeros(n, p.context().clone());
468 jac.gemv(M::T::one(), &v, M::T::zero(), &mut gemv2);
469 gemv1.assert_eq_st(&gemv2, M::T::from(1e-10));
470 }
471 }
472
473 #[test]
474 fn matrix_coloring_dmatrix() {
475 matrix_coloring::<NalgebraMat<f64>>();
476 }
477
478 #[test]
479 fn matrix_coloring_faer_sparse() {
480 matrix_coloring::<FaerSparseMat<f64>>();
481 }
482}