1mod saved;
6
7use nalgebra::DMatrix;
8
9use super::{Blocks, checked_dimension, dimension};
10use crate::{
11 Complex, Error, Result,
12 linalg::{
13 self, SolveGradient, SolveResidual, product, product_adjoint_right, product_views, view,
14 },
15 numerics::{finite, ratio},
16};
17
18#[derive(Debug)]
21pub struct PeriodicResidual {
22 top: DMatrix<Complex>,
23 reflection: DMatrix<Complex>,
24 solve: SolveResidual,
25}
26
27pub fn periodic(blocks: Blocks) -> Result<(DMatrix<Complex>, PeriodicResidual)> {
31 let n = dimension(&blocks)?;
32 let [up, reflection_up, reflection_down, down] = blocks;
33 let mut top = DMatrix::zeros(n, 2 * n);
34 top.columns_mut(0, n).copy_from(&up);
35 top.columns_mut(n, n).copy_from(&reflection_up);
36 let mut rhs = -product(&reflection_down, &top);
37 for i in 0..n {
38 rhs[(i, n + i)] += 1.0;
39 }
40 let solve = linalg::solve_owned(down, rhs)?;
41 let mut value = DMatrix::zeros(2 * n, 2 * n);
42 value.rows_mut(0, n).copy_from(&top);
43 value.rows_mut(n, n).copy_from(solve.value());
44 Ok((
45 value,
46 PeriodicResidual {
47 top,
48 reflection: reflection_down,
49 solve,
50 },
51 ))
52}
53
54impl PeriodicResidual {
55 #[must_use]
58 pub fn shape(&self) -> (usize, usize) {
59 (2 * self.top.nrows(), 2 * self.top.nrows())
60 }
61
62 pub fn pushforward(&self, blocks: &Blocks) -> Result<DMatrix<Complex>> {
64 let n = self.top.nrows();
65 checked_dimension(blocks, n, "invalid periodic transfer tangent")?;
66 let mut top = DMatrix::zeros(n, 2 * n);
67 top.columns_mut(0, n).copy_from(&blocks[0]);
68 top.columns_mut(n, n).copy_from(&blocks[1]);
69 let rhs = -product(&blocks[2], &self.top) - product(&self.reflection, &top);
70 let bottom = self.solve.pushforward(&blocks[3], rhs)?;
71 let mut value = DMatrix::zeros(2 * n, 2 * n);
72 value.rows_mut(0, n).copy_from(&top);
73 value.rows_mut(n, n).copy_from(&bottom);
74 Ok(value)
75 }
76
77 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<Blocks> {
79 let n = self.top.nrows();
80 if cotangent.shape() != self.shape() || cotangent.iter().any(|&z| !finite(z)) {
81 return Err(Error::InvalidInput(
82 "invalid periodic transfer cotangent".into(),
83 ));
84 }
85 let SolveGradient {
86 operator: down,
87 rhs,
88 } = self.solve.pullback(cotangent.rows(n, n).into_owned())?;
89 let reflection = -product_adjoint_right(&rhs, &self.top);
90 let top =
91 cotangent.rows(0, n) - product_views(view(&self.reflection).adjoint(), view(&rhs));
92 Ok([
93 top.columns(0, n).into_owned(),
94 top.columns(n, n).into_owned(),
95 reflection,
96 down,
97 ])
98 }
99}
100
101#[derive(Debug)]
104pub struct BandsResidual {
105 periodic: PeriodicResidual,
106 eigen: linalg::EigResidual,
107 period: f64,
108 wavenumbers: Vec<Complex>,
109}
110
111pub fn bands(blocks: Blocks, period: f64) -> Result<BandsResidual> {
115 if !period.is_finite() || period <= 0.0 {
116 return Err(Error::InvalidInput(
117 "band period must be finite and positive".into(),
118 ));
119 }
120 let (transfer, periodic) = periodic(blocks)?;
121 let eigen = linalg::eig(&transfer)?;
122 let wavenumbers: Vec<_> = eigen
123 .values()
124 .iter()
125 .map(|v| -Complex::i() * v.ln() / period)
126 .collect();
127 if wavenumbers.iter().any(|&z| !finite(z)) {
128 return Err(Error::NonFinite(
129 "zero or non-finite Bloch multiplier".into(),
130 ));
131 }
132 Ok(BandsResidual {
133 periodic,
134 eigen,
135 period,
136 wavenumbers,
137 })
138}
139
140#[derive(Debug)]
142pub struct BandsGradient {
143 pub blocks: Blocks,
145 pub period: f64,
147}
148
149impl BandsResidual {
150 #[must_use]
153 pub fn wavenumbers(&self) -> &[Complex] {
154 &self.wavenumbers
155 }
156
157 #[must_use]
159 pub const fn vectors(&self) -> &DMatrix<Complex> {
160 self.eigen.vectors()
161 }
162
163 pub fn pushforward(
166 &self,
167 blocks: &Blocks,
168 period: f64,
169 ) -> Result<(Vec<Complex>, DMatrix<Complex>)> {
170 if !period.is_finite() {
171 return Err(Error::InvalidInput("invalid band period tangent".into()));
172 }
173 let transfer = self.periodic.pushforward(blocks)?;
174 let logarithm_derivatives: Vec<_> = self
175 .eigen
176 .values()
177 .iter()
178 .map(|&v| ratio(-Complex::i() / self.period, v))
179 .collect();
180 let (values, vectors) = self.eigen.pushforward(&transfer)?;
181 let wavenumbers = values
182 .iter()
183 .zip(&logarithm_derivatives)
184 .zip(&self.wavenumbers)
185 .map(|((&value, &derivative), &wavenumber)| {
186 derivative * value - wavenumber * (period / self.period)
187 })
188 .collect();
189 Ok((wavenumbers, vectors))
190 }
191
192 pub fn pullback(
194 &self,
195 wavenumbers: &[Complex],
196 vectors: DMatrix<Complex>,
197 ) -> Result<BandsGradient> {
198 if wavenumbers.len() != self.wavenumbers.len() || wavenumbers.iter().any(|&z| !finite(z)) {
199 return Err(Error::InvalidInput(
200 "invalid Bloch wavenumber cotangent".into(),
201 ));
202 }
203 let period = -wavenumbers
204 .iter()
205 .zip(&self.wavenumbers)
206 .map(|(g, k)| (g.conj() * k).re)
207 .sum::<f64>()
208 / self.period;
209 let values: Vec<_> = wavenumbers
210 .iter()
211 .zip(self.eigen.values())
212 .map(|(g, l)| g * ratio(-Complex::i() / self.period, *l).conj())
213 .collect();
214 let matrix = self.eigen.pullback(&values, vectors)?;
215 Ok(BandsGradient {
216 blocks: self.periodic.pullback(&matrix)?,
217 period,
218 })
219 }
220}