1use super::{FieldGain, Medium, NonlinearStep, StepRule, StepUpdate, manakov_rotation, mul, scale, signal_bandwidth};
5use crate::fft;
6use crate::{C64, cis};
7use comnum_core::{Error, Result, ensure, same_len};
8
9#[derive(Clone, Copy, Debug, PartialEq)]
17pub struct ManakovSplitStep {
18 pub medium: Medium,
20 pub rule: StepRule,
22 pub nonlinear: NonlinearStep,
24 pub max_iterations: usize,
26 pub tolerance: f64,
28}
29
30#[derive(Clone, Copy, Debug)]
37pub struct Birefringence<'a> {
38 pub section_km: f64,
40 pub rotations: &'a [[C64; 4]],
42 pub dgd_s_per_km: &'a [f64],
44}
45
46#[derive(Clone, Copy, Default)]
49pub struct PropagationOptions<'a> {
50 pub gain: Option<FieldGain<'a>>,
53 pub birefringence: Option<Birefringence<'a>>,
55}
56
57impl std::fmt::Debug for PropagationOptions<'_> {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 f.debug_struct("PropagationOptions")
60 .field("gain", &self.gain.map(|_| "FieldGain"))
61 .field("birefringence", &self.birefringence)
62 .finish()
63 }
64}
65
66struct Work {
68 omega: Vec<f64>,
70 p0: Vec<f64>,
72 p1: Vec<f64>,
74 phi: Vec<f64>,
76 lin: Vec<C64>,
78 half1: Vec<Vec<C64>>,
80 half2: Vec<Vec<C64>>,
81 gain: Vec<f64>,
82 h: Vec<Vec<C64>>,
85 f: Vec<Vec<C64>>,
86 c: Vec<Vec<C64>>,
87}
88
89impl ManakovSplitStep {
90 pub fn new(medium: Medium) -> ManakovSplitStep {
97 ManakovSplitStep {
98 medium,
99 rule: StepRule::FWM_DEFAULT,
100 nonlinear: NonlinearStep::Midpoint,
101 max_iterations: 10,
102 tolerance: 1e-5,
103 }
104 }
105
106 pub fn run(&self, x: &mut [C64], y: &mut [C64], length_km: f64, fs: f64) -> Result<usize> {
113 self.run_fields(&mut [x, y], length_km, fs, &PropagationOptions::default())
114 }
115
116 pub fn run_with_gain(
123 &self,
124 x: &mut [C64],
125 y: &mut [C64],
126 length_km: f64,
127 fs: f64,
128 gain: FieldGain<'_>,
129 ) -> Result<usize> {
130 self.run_fields(&mut [x, y], length_km, fs, &PropagationOptions { gain: Some(gain), birefringence: None })
131 }
132
133 pub fn run_fields(
148 &self,
149 fields: &mut [&mut [C64]],
150 length_km: f64,
151 fs: f64,
152 options: &PropagationOptions<'_>,
153 ) -> Result<usize> {
154 let nf = fields.len();
155 if nf == 0 || nf > 2 {
156 return Err(Error::Shape(format!("a propagation takes one or two fields, got {nf}")));
157 }
158 let n = fields[0].len();
159 if nf == 2 {
160 same_len(n, fields[1].len(), "the polarisations")?;
161 }
162 self.rule_parameter()?;
163 ensure(length_km.is_finite(), || format!("the length must be finite, got {length_km} km"))?;
164 if let Some(b) = &options.birefringence {
165 check_birefringence(b, nf, length_km)?;
166 }
167 if n == 0 || length_km <= 0.0 {
168 return Ok(0);
169 }
170 if let (StepRule::Fixed(hf), NonlinearStep::Midpoint) = (self.rule, self.nonlinear) {
171 return Ok(self.run_fixed_midpoint(fields, length_km, fs, options, hf));
172 }
173 let p = fft::plan(n);
174 let zero = C64::new(0.0, 0.0);
175 let mut w = Work {
176 omega: fft::angular_frequencies(n, fs),
177 p0: vec![0.0; n],
178 p1: vec![0.0; n],
179 phi: vec![0.0; n],
180 lin: vec![zero; n],
181 half1: vec![vec![zero; n]; nf],
182 half2: vec![vec![zero; n]; nf],
183 gain: vec![0.0; n],
184 h: vec![vec![zero; n]; nf],
185 f: vec![vec![zero; n]; nf],
186 c: fields.iter().map(|u| u.to_vec()).collect(),
187 };
188 let k = if nf == 2 { 8.0 / 9.0 } else { 1.0 } * self.medium.gamma_w_km;
189 let bandwidth = match self.rule {
190 StepRule::Fwm { bandwidth_hz: Some(b), .. } => b,
191 StepRule::Fwm { bandwidth_hz: None, .. } => {
192 let views: Vec<&[C64]> = fields.iter().map(|u| &**u).collect();
193 signal_bandwidth(&views, fs)
194 }
195 _ => 0.0,
196 };
197 let mut z = 0.0;
198 let mut steps = 0;
199 let mut previous = None;
200 let mut section: Option<usize> = None;
202 while z < length_km {
203 let mut stretch_end = length_km;
204 if let Some(b) = &options.birefringence {
205 let current = ((z / b.section_km) * (1.0 + 1e-12)).floor() as usize;
206 let current = current.min(b.rotations.len() - 1);
207 if section != Some(current) {
208 let (a, rest) = fields.split_at_mut(1);
211 rotate(&b.rotations[current], a[0], rest[0]);
212 let (a, rest) = w.c.split_at_mut(1);
213 rotate(&b.rotations[current], &mut a[0], &mut rest[0]);
214 section = Some(current);
215 }
216 stretch_end = (((current + 1) as f64) * b.section_km).min(length_km);
217 }
218 total_power_of(fields, &mut w.p0);
219 total_power_of(&w.c, &mut w.p1);
220 trapezoid(&w.p0, &w.p1, k, &mut w.phi);
221 let h = self.step_length(&w.phi, z, stretch_end - z, previous, bandwidth)?;
222 previous = Some(h);
223 self.half_steps(&mut w, z, h, section, options);
224 for (o, u) in w.h.iter_mut().zip(fields.iter()) {
225 o.copy_from_slice(u);
226 }
227 for (u, half) in w.h.iter_mut().zip(&w.half1) {
228 p.forward(u);
229 mul(u, half);
230 p.inverse(u);
231 }
232 if self.nonlinear == NonlinearStep::Midpoint {
233 kerr_rotation(&mut w.h, k * h);
235 for (u, half) in w.h.iter_mut().zip(&w.half2) {
236 p.forward(u);
237 mul(u, half);
238 p.inverse(u);
239 }
240 for ((u, c), hh) in fields.iter_mut().zip(w.c.iter_mut()).zip(&w.h) {
241 u.copy_from_slice(hh);
242 c.copy_from_slice(hh);
243 }
244 } else {
245 for it in 0..self.max_iterations.max(1) {
246 for (o, v) in w.f.iter_mut().zip(&w.h) {
247 for ((a, b), ph) in o.iter_mut().zip(v).zip(&w.phi) {
248 *a = b * cis(ph * h);
249 }
250 }
251 for (u, half) in w.f.iter_mut().zip(&w.half2) {
252 p.forward(u);
253 mul(u, half);
254 p.inverse(u);
255 }
256 let diff: f64 =
257 w.f.iter().zip(&w.c).flat_map(|(a, b)| a.iter().zip(b)).map(|(u, v)| (u - v).norm_sqr()).sum();
258 let norm: f64 = w.c.iter().flatten().map(C64::norm_sqr).sum();
259 std::mem::swap(&mut w.c, &mut w.f);
260 if norm == 0.0 || (diff / norm).sqrt() < self.tolerance || it + 1 == self.max_iterations {
261 break;
262 }
263 total_power_of(&w.c, &mut w.p1);
264 trapezoid(&w.p0, &w.p1, k, &mut w.phi);
265 }
266 for (u, c) in fields.iter_mut().zip(&w.c) {
267 u.copy_from_slice(c);
268 }
269 }
270 z += h;
271 steps += 1;
272 }
273 Ok(steps)
274 }
275
276 fn run_fixed_midpoint(
282 &self,
283 fields: &mut [&mut [C64]],
284 length_km: f64,
285 fs: f64,
286 options: &PropagationOptions<'_>,
287 hf: f64,
288 ) -> usize {
289 let (nf, n) = (fields.len(), fields[0].len());
290 let p = fft::plan(n);
291 let zero = C64::new(0.0, 0.0);
292 let mut w = Work {
293 omega: fft::angular_frequencies(n, fs),
294 p0: Vec::new(),
295 p1: Vec::new(),
296 phi: Vec::new(),
297 lin: vec![zero; n],
298 half1: vec![vec![zero; n]; nf],
299 half2: vec![vec![zero; n]; nf],
300 gain: vec![0.0; n],
301 h: fields.iter().map(|u| u.to_vec()).collect(),
302 f: Vec::new(),
303 c: Vec::new(),
304 };
305 let k = if nf == 2 { 8.0 / 9.0 } else { 1.0 } * self.medium.gamma_w_km;
306 let (n_stretches, stretch_km) = match &options.birefringence {
308 Some(b) => (b.rotations.len(), b.section_km),
309 None => (1, length_km),
310 };
311 for u in &mut w.h {
312 p.forward(u);
313 }
314 let mut steps = 0;
315 for s in 0..n_stretches {
316 let z_start = s as f64 * stretch_km;
317 let z_end = if s + 1 == n_stretches { length_km } else { (s + 1) as f64 * stretch_km };
318 if let Some(b) = &options.birefringence {
319 let (a, rest) = w.h.split_at_mut(1);
322 rotate(&b.rotations[s], &mut a[0], &mut rest[0]);
323 }
324 let (n_steps, h) = equal_steps(z_end - z_start, hf);
325 let section = options.birefringence.is_some().then_some(s);
326 self.half_steps(&mut w, z_start, h, section, &PropagationOptions { gain: None, ..*options });
328 for i in 0..n_steps {
329 let z = z_start + i as f64 * h;
330 for (u, half) in w.h.iter_mut().zip(&w.half1) {
331 mul(u, half);
332 }
333 if let Some(g) = options.gain {
334 g(z, z + h / 2.0, &mut w.gain);
335 for u in &mut w.h {
336 scale(u, &w.gain);
337 }
338 }
339 for u in &mut w.h {
340 p.inverse(u);
341 }
342 kerr_rotation_scalar_order(&mut w.h, k, h);
343 for (u, half) in w.h.iter_mut().zip(&w.half2) {
344 p.forward(u);
345 mul(u, half);
346 }
347 if let Some(g) = options.gain {
348 g(z + h / 2.0, z + h, &mut w.gain);
349 for u in &mut w.h {
350 scale(u, &w.gain);
351 }
352 }
353 steps += 1;
354 }
355 }
356 for (u, spectrum) in fields.iter_mut().zip(w.h.iter_mut()) {
357 p.inverse(spectrum);
358 u.copy_from_slice(spectrum);
359 }
360 steps
361 }
362
363 fn half_steps(&self, w: &mut Work, z: f64, h: f64, section: Option<usize>, options: &PropagationOptions<'_>) {
366 self.medium.half_step_into(&w.omega, h, &mut w.lin);
367 let nf = w.half1.len();
368 let dgd = match (options.birefringence, section) {
369 (Some(b), Some(s)) => b.dgd_s_per_km[s],
370 _ => 0.0,
371 };
372 for i in 0..nf {
373 let sign = if i == 0 { -1.0 } else { 1.0 };
375 for ((a, b), (l, wk)) in w.half1[i].iter_mut().zip(w.half2[i].iter_mut()).zip(w.lin.iter().zip(&w.omega)) {
376 let d = if dgd == 0.0 { *l } else { l * cis(sign * 0.25 * wk * dgd * h) };
377 *a = d;
378 *b = d;
379 }
380 }
381 if let Some(g) = options.gain {
382 for (half, z0, z1) in [(&mut w.half1, z, z + h / 2.0), (&mut w.half2, z + h / 2.0, z + h)] {
383 g(z0, z1, &mut w.gain);
384 for v in half.iter_mut() {
385 for (o, b) in v.iter_mut().zip(&w.gain) {
386 *o *= b;
387 }
388 }
389 }
390 }
391 }
392
393 fn rule_parameter(&self) -> Result<f64> {
396 let (step, what) = match self.rule {
397 StepRule::Fixed(h) => (h, "fixed step (km)"),
398 StepRule::NonlinearPhase(r) => (r, "nonlinear rotation (rad)"),
399 StepRule::Fwm { phase_rad, bandwidth_hz, max_nl_phase_rad, .. } => {
400 if let Some(b) = bandwidth_hz {
401 ensure(b > 0.0 && b.is_finite(), || format!("the bandwidth must be positive, got {b} Hz"))?;
402 }
403 if let Some(r) = max_nl_phase_rad {
404 ensure(r > 0.0, || format!("the nonlinear rotation must be positive, got {r}"))?;
405 }
406 (phase_rad, "four-wave-mixing phase (rad)")
407 }
408 };
409 ensure(step > 0.0, || format!("the {what} must be positive, got {step}"))?;
410 Ok(step)
411 }
412
413 fn fwm_first_step(&self, phase_rad: f64, b: f64) -> f64 {
416 let w = 2.0 * std::f64::consts::PI * b;
417 let h = phase_rad / (self.medium.beta2_s2_km.abs() * w * w);
418 if h.is_finite() { h } else { f64::INFINITY }
419 }
420
421 pub(super) fn step_length(
428 &self,
429 phi: &[f64],
430 z: f64,
431 rest: f64,
432 previous: Option<f64>,
433 bandwidth: f64,
434 ) -> Result<f64> {
435 let h = match self.rule {
436 StepRule::Fixed(hf) => equal_steps(rest, hf).1,
437 StepRule::NonlinearPhase(rot) => {
438 let m = phi.iter().copied().fold(0.0, f64::max);
439 if m > 0.0 && rest >= rot / m { rot / m } else { rest }
440 }
441 StepRule::Fwm { phase_rad, update, max_nl_phase_rad, .. } => {
442 let a = self.medium.alpha_np_km;
443 let h = match previous {
444 None => self.fwm_first_step(phase_rad, bandwidth),
445 Some(hk) if a <= 0.0 => hk,
446 Some(hk) => match update {
447 StepUpdate::ConstantLocalError => hk * (a * hk / 3.0).exp(),
448 StepUpdate::NonlinearPhase => {
449 let l = (1.0 - (-a * hk).exp()) / a * (a * hk).exp();
451 if a * l < 1.0 { -(1.0 - a * l).ln() / a } else { f64::INFINITY }
452 }
453 },
454 };
455 let m = phi.iter().copied().fold(0.0, f64::max);
456 let nl = match max_nl_phase_rad {
457 Some(rot) if m > 0.0 => rot / m,
458 _ => f64::INFINITY,
459 };
460 h.min(nl).min(rest)
461 }
462 };
463 if z + h <= z || h.is_nan() {
466 return Err(Error::Numerical(format!("the split step vanishes at z = {z} km (step {h} km)")));
467 }
468 Ok(h)
469 }
470}
471
472fn equal_steps(rest: f64, hf: f64) -> (usize, f64) {
476 let ratio = rest / hf;
477 let whole = ratio.round();
478 if whole >= 1.0 && (whole - ratio).abs() <= 1e-9 * ratio {
479 (whole as usize, hf)
480 } else {
481 let n = ratio.ceil().max(1.0);
482 (n as usize, rest / n)
483 }
484}
485
486fn check_birefringence(b: &Birefringence<'_>, nf: usize, length_km: f64) -> Result<()> {
489 if nf != 2 {
490 return Err(Error::Shape("a birefringence needs two polarisations".into()));
491 }
492 let n = b.rotations.len();
493 let covers = b.section_km > 0.0 && ((n as f64) * b.section_km - length_km).abs() <= 1e-9 * length_km.max(1.0);
494 if n == 0 || b.dgd_s_per_km.len() != n || !covers {
495 return Err(Error::Shape(format!(
496 "{n} sections of {} km and {} DGDs for a length of {length_km} km",
497 b.section_km,
498 b.dgd_s_per_km.len()
499 )));
500 }
501 Ok(())
502}
503
504fn rotate(m: &[C64; 4], x: &mut [C64], y: &mut [C64]) {
506 for (u, v) in x.iter_mut().zip(y.iter_mut()) {
507 let (a, b) = (m[0] * *u + m[1] * *v, m[2] * *u + m[3] * *v);
508 *u = a;
509 *v = b;
510 }
511}
512
513fn total_power_of<T: AsRef<[C64]>>(fields: &[T], out: &mut [f64]) {
515 out.fill(0.0);
516 for f in fields {
517 for (o, v) in out.iter_mut().zip(f.as_ref()) {
518 *o += v.norm_sqr();
519 }
520 }
521}
522
523fn kerr_rotation_scalar_order(fields: &mut [Vec<C64>], k: f64, h: f64) {
527 match fields {
528 [x] => {
529 for v in x.iter_mut() {
530 *v *= cis(k * v.norm_sqr() * h);
531 }
532 }
533 [x, y] => manakov_rotation(x, y, k * h),
534 _ => {}
535 }
536}
537
538fn kerr_rotation(fields: &mut [Vec<C64>], k: f64) {
541 match fields {
542 [x] => {
543 for v in x.iter_mut() {
544 *v *= cis(k * v.norm_sqr());
545 }
546 }
547 [x, y] => manakov_rotation(x, y, k),
548 _ => {}
549 }
550}
551
552fn trapezoid(p0: &[f64], p1: &[f64], k: f64, phi: &mut [f64]) {
554 for (f, (u, v)) in phi.iter_mut().zip(p0.iter().zip(p1)) {
555 *f = k * (u + v) / 2.0;
556 }
557}