treams_core/smatrix/
array.rs1mod saved;
6
7use faer::MatRef;
8use nalgebra::DMatrix;
9
10use super::{Blocks, checked_dimension, dimension};
11use crate::{
12 Complex, Error, Result,
13 linalg::{product, product_adjoint_right, product_views, view},
14 numerics::finite,
15};
16
17pub type Channels = [DMatrix<Complex>; 4];
23
24#[derive(Debug)]
27pub struct FromArrayResidual {
28 response: DMatrix<Complex>,
29 channels: Channels,
30 scattered: [DMatrix<Complex>; 2],
31}
32
33pub fn from_array(
41 response: DMatrix<Complex>,
42 channels: Channels,
43) -> Result<(Blocks, FromArrayResidual)> {
44 let d = response.nrows();
45 let c = channels[0].ncols();
46 if d == 0
47 || !response.is_square()
48 || c == 0
49 || response.iter().any(|&v| !finite(v))
50 || channels
51 .iter()
52 .any(|a| a.shape() != (d, c) || a.iter().any(|&v| !finite(v)))
53 {
54 return Err(Error::InvalidInput(
55 "require a finite square response and four matching multipole-by-plane channel arrays"
56 .into(),
57 ));
58 }
59 let scattered = std::array::from_fn(|direction| product(&response, &channels[direction]));
60 let mut value = std::array::from_fn(|b| {
61 product_views(
62 view(&channels[2 + b / 2]).transpose(),
63 view(&scattered[b % 2]),
64 )
65 });
66 for block in [0, 3] {
67 for i in 0..c {
68 value[block][(i, i)] += 1.0;
69 }
70 }
71 dimension(&value).map_err(|_| Error::NonFinite("non-finite array scattering matrix".into()))?;
72 Ok((
73 value,
74 FromArrayResidual {
75 response,
76 channels,
77 scattered,
78 },
79 ))
80}
81
82#[derive(Debug)]
84pub struct FromArrayGradient {
85 pub response: DMatrix<Complex>,
87 pub channels: Channels,
89}
90
91impl FromArrayResidual {
92 #[must_use]
95 pub fn shape(&self) -> (usize, usize) {
96 (self.channels[0].ncols(), self.channels[0].ncols())
97 }
98
99 #[must_use]
101 pub fn input_shape(&self) -> (usize, usize) {
102 self.channels[0].shape()
103 }
104
105 pub fn pushforward(&self, response: &DMatrix<Complex>, channels: &Channels) -> Result<Blocks> {
107 if response.shape() != self.response.shape()
108 || response.iter().any(|&z| !finite(z))
109 || channels
110 .iter()
111 .any(|a| a.shape() != self.input_shape() || a.iter().any(|&z| !finite(z)))
112 {
113 return Err(Error::InvalidInput(
114 "invalid array scattering tangent".into(),
115 ));
116 }
117 let scattered: [DMatrix<Complex>; 2] = std::array::from_fn(|direction| {
118 product(response, &self.channels[direction])
119 + product(&self.response, &channels[direction])
120 });
121 Ok(std::array::from_fn(|b| {
122 product_views(
123 view(&channels[2 + b / 2]).transpose(),
124 view(&self.scattered[b % 2]),
125 ) + product_views(
126 view(&self.channels[2 + b / 2]).transpose(),
127 view(&scattered[b % 2]),
128 )
129 }))
130 }
131
132 pub fn pullback(&self, cotangent: &Blocks) -> Result<FromArrayGradient> {
134 let c = checked_dimension(
135 cotangent,
136 self.channels[0].ncols(),
137 "invalid array scattering cotangent shape",
138 )?;
139 let conjugate =
142 |a: &DMatrix<Complex>, b: MatRef<'_, Complex>| product_views(view(a).conjugate(), b);
143 let mut response = DMatrix::zeros(self.response.nrows(), self.response.ncols());
144 let mut channels: Channels =
145 std::array::from_fn(|_| DMatrix::zeros(self.response.nrows(), c));
146 for direction in 0..2 {
147 let adjoint = conjugate(&self.channels[2], view(&cotangent[direction]))
148 + conjugate(&self.channels[3], view(&cotangent[2 + direction]));
149 response += product_adjoint_right(&adjoint, &self.channels[direction]);
150 channels[direction] = product_views(view(&self.response).adjoint(), view(&adjoint));
151 channels[2 + direction] = conjugate(
152 &self.scattered[0],
153 view(&cotangent[2 * direction]).transpose(),
154 ) + conjugate(
155 &self.scattered[1],
156 view(&cotangent[2 * direction + 1]).transpose(),
157 );
158 }
159 Ok(FromArrayGradient { response, channels })
160 }
161}