1mod saved;
7
8use faer::MatRef;
9use nalgebra::DMatrix;
10use rayon::prelude::*;
11
12use super::{
13 Blocks, StoredBlock, any_nonfinite, checked_dimension,
14 solve::{AdjointSolve, InternalSolve},
15};
16use crate::{
17 Complex, Error, Result,
18 linalg::{product, product_adjoint_right, product_views, view},
19 numerics::finite,
20};
21
22#[derive(Debug)]
29pub struct IlluminateResidual {
30 lower: [StoredBlock; 4],
31 upper: [StoredBlock; 4],
32 incoming: [DMatrix<Complex>; 2],
33 solve: InternalSolve,
34 down: DMatrix<Complex>,
35}
36
37const PARALLEL_SNAPSHOT_ROWS: usize = 128;
40
41const WORKER_SNAPSHOT_ROWS: usize = 512;
44
45const PARALLEL_SCAN_ROWS: usize = 256;
48
49pub fn illuminate(
57 lower: [MatRef<'_, Complex>; 4],
58 upper: [MatRef<'_, Complex>; 4],
59 incoming: [DMatrix<Complex>; 2],
60) -> Result<([DMatrix<Complex>; 4], IlluminateResidual)> {
61 let store = |blocks: [MatRef<'_, Complex>; 4]| {
64 let [a, b, c, d] = blocks;
65 let ((a, b), (c, d)) = crate::threads::join(
66 || crate::threads::join(|| StoredBlock::copy_view(a), || StoredBlock::copy_view(b)),
67 || crate::threads::join(|| StoredBlock::copy_view(c), || StoredBlock::copy_view(d)),
68 );
69 [a, b, c, d]
70 };
71 let threshold = if crate::threads::current_num_threads() > 1 {
75 PARALLEL_SNAPSHOT_ROWS
76 } else {
77 WORKER_SNAPSHOT_ROWS
78 };
79 let (lower, upper) = if lower[0].nrows() >= threshold {
80 crate::threads::join(|| store(lower), || store(upper))
81 } else {
82 (
83 lower.map(StoredBlock::copy_view),
84 upper.map(StoredBlock::copy_view),
85 )
86 };
87 let (top, bottom, down, solve) = illumination_fields(
88 lower.each_ref().map(StoredBlock::view),
89 upper.each_ref().map(StoredBlock::view),
90 incoming.each_ref().map(view),
91 )?;
92 let value = [top, bottom, solve.value.clone(), down.clone()];
93 Ok((
94 value,
95 IlluminateResidual {
96 lower,
97 upper,
98 incoming,
99 solve,
100 down,
101 },
102 ))
103}
104
105pub fn illuminate_value(
111 lower: [MatRef<'_, Complex>; 4],
112 upper: [MatRef<'_, Complex>; 4],
113 incoming: [MatRef<'_, Complex>; 2],
114) -> Result<[DMatrix<Complex>; 4]> {
115 let (top, bottom, down, solve) = illumination_fields(lower, upper, incoming)?;
116 Ok([top, bottom, solve.value, down])
117}
118
119type InternalFields = (
121 DMatrix<Complex>,
122 DMatrix<Complex>,
123 DMatrix<Complex>,
124 InternalSolve,
125);
126
127fn illumination_fields(
129 lower: [MatRef<'_, Complex>; 4],
130 upper: [MatRef<'_, Complex>; 4],
131 incoming: [MatRef<'_, Complex>; 2],
132) -> Result<InternalFields> {
133 let n = lower[0].nrows();
134 let p = incoming[0].ncols();
135 let nonfinite = |a: &MatRef<'_, Complex>| any_nonfinite(*a);
136 if n == 0
137 || p == 0
138 || lower.iter().chain(&upper).any(|a| a.shape() != (n, n))
139 || incoming.iter().any(|a| a.shape() != (n, p))
140 || if n >= PARALLEL_SCAN_ROWS {
141 crate::threads::install(|| {
142 lower
143 .par_iter()
144 .chain(upper.par_iter())
145 .chain(incoming.par_iter())
146 .any(nonfinite)
147 })
148 } else {
149 lower.iter().chain(&upper).chain(&incoming).any(nonfinite)
150 }
151 {
152 return Err(Error::InvalidInput(
153 "require matching finite S matrices and mode-by-illumination inputs".into(),
154 ));
155 }
156 let direct = product_views(upper[3], incoming[1]);
157 let rhs = product_views(lower[0], incoming[0]) + product_views(lower[1], view(&direct));
158 let solve = InternalSolve::new(lower[1], upper[2], rhs)?;
159 let down = product_views(upper[2], view(&solve.value)) + direct;
160 let top = product_views(upper[0], view(&solve.value)) + product_views(upper[1], incoming[1]);
161 let bottom = product_views(lower[2], incoming[0]) + product_views(lower[3], view(&down));
162 if top
163 .iter()
164 .chain(bottom.iter())
165 .chain(down.iter())
166 .any(|&z| !finite(z))
167 {
168 return Err(Error::Singular);
169 }
170 Ok((top, bottom, down, solve))
171}
172
173#[derive(Debug)]
175pub struct IlluminateGradient {
176 pub lower: Blocks,
178 pub upper: Blocks,
180 pub incoming: [DMatrix<Complex>; 2],
182}
183
184impl IlluminateResidual {
185 #[must_use]
187 pub fn shape(&self) -> (usize, usize) {
188 self.solve.value.shape()
189 }
190
191 pub fn pushforward(
194 &self,
195 lower: &Blocks,
196 upper: &Blocks,
197 incoming: &[DMatrix<Complex>; 2],
198 ) -> Result<[DMatrix<Complex>; 4]> {
199 let (n, _) = self.shape();
200 checked_dimension(lower, n, "invalid lower illumination S matrix tangent")?;
201 checked_dimension(upper, n, "invalid upper illumination S matrix tangent")?;
202 if incoming
203 .iter()
204 .any(|a| a.shape() != self.shape() || a.iter().any(|&z| !finite(z)))
205 {
206 return Err(Error::InvalidInput(
207 "invalid illumination input tangent".into(),
208 ));
209 }
210 let up = &self.solve.value;
211 let direct = product(&upper[2], up)
212 + product(&upper[3], &self.incoming[1])
213 + product_views(self.upper[3].view(), view(&incoming[1]));
214 let rhs = product(&lower[0], &self.incoming[0])
215 + product_views(self.lower[0].view(), view(&incoming[0]))
216 + product(&lower[1], &self.down)
217 + product_views(self.lower[1].view(), view(&direct));
218 let tangent_up =
219 self.solve
220 .solve_forward(self.lower[1].view(), self.upper[2].view(), rhs)?;
221 let tangent_down = direct + product_views(self.upper[2].view(), view(&tangent_up));
222 let top = product(&upper[0], up)
223 + product_views(self.upper[0].view(), view(&tangent_up))
224 + product(&upper[1], &self.incoming[1])
225 + product_views(self.upper[1].view(), view(&incoming[1]));
226 let bottom = product(&lower[2], &self.incoming[0])
227 + product_views(self.lower[2].view(), view(&incoming[0]))
228 + product(&lower[3], &self.down)
229 + product_views(self.lower[3].view(), view(&tangent_down));
230 Ok([top, bottom, tangent_up, tangent_down])
231 }
232
233 pub fn pullback(&self, cotangent: &[DMatrix<Complex>; 4]) -> Result<IlluminateGradient> {
235 if cotangent
236 .iter()
237 .any(|a| a.shape() != self.shape() || a.iter().any(|&z| !finite(z)))
238 {
239 return Err(Error::InvalidInput(
240 "invalid field coefficient cotangent".into(),
241 ));
242 }
243 let down =
244 &cotangent[3] + product_views(self.lower[3].view().adjoint(), view(&cotangent[1]));
245 let up = &cotangent[2]
246 + product_views(self.upper[0].view().adjoint(), view(&cotangent[0]))
247 + product_views(self.upper[2].view().adjoint(), view(&down));
248 let AdjointSolve {
249 adjoint: rhs,
250 value: internal_up,
251 } = self
252 .solve
253 .solve_adjoint(self.lower[1].view(), self.upper[2].view(), up)?;
254 let direct = down + product_views(self.lower[1].view().adjoint(), view(&rhs));
255 let incoming = [
256 product_views(self.lower[2].view().adjoint(), view(&cotangent[1]))
257 + product_views(self.lower[0].view().adjoint(), view(&rhs)),
258 product_views(self.upper[1].view().adjoint(), view(&cotangent[0]))
259 + product_views(self.upper[3].view().adjoint(), view(&direct)),
260 ];
261 let lower = [
262 product_adjoint_right(&rhs, &self.incoming[0]),
263 product_adjoint_right(&rhs, &self.down),
264 product_adjoint_right(&cotangent[1], &self.incoming[0]),
265 product_adjoint_right(&cotangent[1], &self.down),
266 ];
267 let upper = [
268 product_adjoint_right(&cotangent[0], internal_up),
269 product_adjoint_right(&cotangent[0], &self.incoming[1]),
270 product_adjoint_right(&direct, internal_up),
271 product_adjoint_right(&direct, &self.incoming[1]),
272 ];
273 Ok(IlluminateGradient {
274 lower,
275 upper,
276 incoming,
277 })
278 }
279}