1mod saved;
6
7use std::ops::AddAssign;
8
9use faer::MatRef;
10use nalgebra::DMatrix;
11
12use super::{
13 Blocks, checked_dimension, dimension,
14 solve::{AdjointSolve, InternalSolve},
15};
16use crate::{
17 Complex, Error, Result,
18 linalg::{product, product_adjoint_right, product_views, view, view_mut},
19};
20
21#[derive(Debug)]
27pub struct AddResidual {
28 lower: [DMatrix<Complex>; 2],
31 upper: [DMatrix<Complex>; 2],
34 solve: InternalSolve,
36 down: DMatrix<Complex>,
38}
39
40pub fn add(lower: Blocks, upper: Blocks) -> Result<(Blocks, AddResidual)> {
44 let n = dimension(&lower)?;
45 if dimension(&upper)? != n {
46 return Err(Error::InvalidInput("S matrix dimensions must match".into()));
47 }
48 let [l0, l1, mut l2, l3] = lower;
49 let [u0, mut u1, u2, u3] = upper;
50 let mut rhs = DMatrix::zeros(n, 2 * n);
51 rhs.columns_mut(0, n).copy_from(&l0);
52 rhs.columns_mut(n, n).copy_from(&product(&l1, &u3));
53 let solve = InternalSolve::direct(view(&l1), view(&u2), rhs)?;
57 let up = view(&solve.value);
58 let mut down = product_views(view(&u2), up);
59 down.columns_mut(n, n).add_assign(&u3);
60 let down_view = view(&down);
61 let value = [
62 product_views(view(&u0), up.subcols(0, n)),
63 {
64 accumulate_product(&mut u1, view(&u0), up.subcols(n, n));
65 u1
66 },
67 {
68 accumulate_product(&mut l2, view(&l3), down_view.subcols(0, n));
69 l2
70 },
71 product_views(view(&l3), down_view.subcols(n, n)),
72 ];
73 dimension(&value).map_err(|_| Error::Singular)?;
74 Ok((
75 value,
76 AddResidual {
77 lower: [l1, l3],
78 upper: [u0, u2],
79 solve,
80 down,
81 },
82 ))
83}
84
85fn accumulate_product(
87 target: &mut DMatrix<Complex>,
88 left: MatRef<'_, Complex>,
89 right: MatRef<'_, Complex>,
90) {
91 let (m, n, k) = (target.nrows(), target.ncols(), left.ncols());
92 crate::threads::product(m, n, k, |par| {
93 faer::linalg::matmul::matmul(
94 view_mut(target),
95 faer::Accum::Add,
96 left,
97 right,
98 Complex::new(1.0, 0.0),
99 par,
100 );
101 });
102}
103
104#[derive(Debug)]
106pub struct AddGradient {
107 pub lower: Blocks,
109 pub upper: Blocks,
111}
112
113impl AddResidual {
114 #[must_use]
116 pub fn shape(&self) -> (usize, usize) {
117 (self.down.nrows(), self.down.nrows())
118 }
119
120 pub fn pushforward(&self, lower: &Blocks, upper: &Blocks) -> Result<Blocks> {
123 let n = self.down.nrows();
124 checked_dimension(lower, n, "invalid lower S matrix tangent")?;
125 checked_dimension(upper, n, "invalid upper S matrix tangent")?;
126 let up = view(&self.solve.value);
127 let mut direct = product_views(view(&upper[2]), up);
128 direct.columns_mut(n, n).add_assign(&upper[3]);
129 let mut rhs = product_views(view(&lower[1]), view(&self.down))
130 + product_views(view(&self.lower[0]), view(&direct));
131 rhs.columns_mut(0, n).add_assign(&lower[0]);
132 let tangent_up =
133 self.solve
134 .solve_forward(view(&self.lower[0]), view(&self.upper[1]), rhs)?;
135 let tangent_down = direct + product(&self.upper[1], &tangent_up);
136 let mut top = product_views(view(&upper[0]), up) + product(&self.upper[0], &tangent_up);
137 top.columns_mut(n, n).add_assign(&upper[1]);
138 let mut bottom = product(&lower[3], &self.down) + product(&self.lower[1], &tangent_down);
139 bottom.columns_mut(0, n).add_assign(&lower[2]);
140 Ok([
141 top.columns(0, n).into_owned(),
142 top.columns(n, n).into_owned(),
143 bottom.columns(0, n).into_owned(),
144 bottom.columns(n, n).into_owned(),
145 ])
146 }
147
148 pub fn pullback(&self, cotangent: &Blocks) -> Result<AddGradient> {
150 let n = checked_dimension(
151 cotangent,
152 self.down.nrows(),
153 "invalid S matrix cotangent shape",
154 )?;
155 let [reflection, transmission] = &self.lower;
156 let [top_transmission, bottom_reflection] = &self.upper;
157 let mut top = DMatrix::zeros(n, 2 * n);
158 top.columns_mut(0, n).copy_from(&cotangent[0]);
159 top.columns_mut(n, n).copy_from(&cotangent[1]);
160 let mut bottom = DMatrix::zeros(n, 2 * n);
161 bottom.columns_mut(0, n).copy_from(&cotangent[2]);
162 bottom.columns_mut(n, n).copy_from(&cotangent[3]);
163 let down = product_views(view(transmission).adjoint(), view(&bottom));
164 let adjoint = product_views(view(top_transmission).adjoint(), view(&top))
165 + product_views(view(bottom_reflection).adjoint(), view(&down));
166 let AdjointSolve { adjoint, value: up } =
167 self.solve
168 .solve_adjoint(view(reflection), view(bottom_reflection), adjoint)?;
169 let incident = down + product_views(view(reflection).adjoint(), view(&adjoint));
170 let lower = [
171 adjoint.columns(0, n).into_owned(),
172 product_adjoint_right(&adjoint, &self.down),
173 cotangent[2].clone(),
174 product_adjoint_right(&bottom, &self.down),
175 ];
176 let upper = [
177 product_adjoint_right(&top, up),
178 cotangent[1].clone(),
179 product_adjoint_right(&incident, up),
180 incident.columns(n, n).into_owned(),
181 ];
182 Ok(AddGradient { lower, upper })
183 }
184}