1use super::{Block, BuildContext, Extension, FIBER_DOC, FiberSpec, Frame, Mapping};
4use comnum_channel::optical::edfa::Edfa;
5use comnum_channel::optical::fiber::FiberParams;
6use comnum_channel::optical::fiber::linear_fiber_channel;
7use comnum_channel::optical::fiber::{Amplification, Precision, Ssfm};
8use comnum_core::Error;
9use comnum_core::Result;
10use comnum_core::ensure;
11use comnum_core::rng::Rng;
12use comnum_dsp::compensation::cd;
13use comnum_dsp::compensation::dbp::NonlinearLength;
14use comnum_math::nlse::{StepDistribution, StepRule};
15use serde_json::Value as Json;
16
17pub(super) fn blocks() -> Vec<Extension> {
19 vec![
20 builtin!(
21 per_polarization "fiber",
22 "Linear fibre (loss and dispersion, OptiCommPy linearFiberChannel).",
23 FiberBlock,
24 needs [Samples],
25 gives [],
26 |q, _, ctx| {
27 let fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
28 let length_km = q.length_km.unwrap_or(fiber.total_km());
29 Ok(Box::new(LinearFiber { length_km, fiber, compensate: q.compensate_loss }))
30 },
31 ),
32 builtin!(
33 per_polarization "ssfm", "Split-step Fourier propagation over the link, one polarisation.", SsfmBlock, needs [Samples], gives [],
34 |q, _, ctx| Ok(Box::new(SplitStep::new(&q, ctx)?)),
35 ),
36 builtin!(
37 per_polarization "edfa", "Erbium-doped fibre amplifier with ASE.", EdfaBlock, needs [Samples], gives [],
38 |q, _, _| Ok(Box::new(EdfaStage(Edfa { gain_db: q.gain_db, nf_db: q.nf_db, fc_hz: q.fc_hz }))),
39 ),
40 builtin!(
41 per_polarization "cdc", "Chromatic-dispersion compensation in the frequency domain.", CdcBlock, needs [Samples], gives [],
42 |q, _, ctx| {
43 let fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
44 Ok(Box::new(Cdc { length_km: q.length_km.unwrap_or(fiber.total_km()), fiber }))
45 },
46 ),
47 builtin!(
48 per_polarization "dbp", "Digital back-propagation by the split-step method.", DbpBlock, needs [Samples], gives [],
49 |q, _, ctx| Ok(Box::new(Dbp::new(&q, ctx)?)),
50 ),
51 ]
52}
53
54pub(super) fn fiber_json(f: Option<&Mapping<FiberSpec>>) -> Option<&Json> {
56 f.map(|m| &m.value)
57}
58
59block_params! {
60 struct FiberBlock {
61 fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
62 length_km: Option<f64> [absent "total"] "km" >= 0, "length; the whole link by default";
63 compensate_loss: bool [true], "restore the power with an ideal gain e^(αL/2)";
64 }
65}
66
67struct LinearFiber {
68 fiber: FiberParams,
69 length_km: f64,
70 compensate: bool,
71}
72
73impl Block for LinearFiber {
74 fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
75 let mut y = linear_fiber_channel(&s.samples, &self.fiber, self.length_km, s.fs());
76 if self.compensate {
77 let g = (self.fiber.alpha_np_km() / 2.0 * self.length_km).exp();
78 for v in &mut y {
79 *v *= g;
80 }
81 }
82 s.samples = y;
83 Ok(())
84 }
85}
86
87block_params! {
88 pub(super) struct AmplifierSettings {
90 amplification: String ["edfa"] one_of ["edfa", "ideal", "none"],
91 "edfa (gain αL, ASE), ideal (noiseless gain) or none; with fiber.raman, the amplifier restores every frequency";
92 noise: bool [true], "draw the ASE of the EDFAs and the spontaneous Raman noise";
93 }
94}
95
96impl AmplifierSettings {
97 pub(super) fn amplification(&self) -> Result<Amplification> {
99 self.amplification.parse()
100 }
101
102 pub(super) fn noise(&self) -> bool {
104 self.noise
105 }
106}
107
108block_params! {
109 struct SsfmBlock {
110 fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
111 step_km: f64 [1.0] "km" > 0, "step";
112 steps_per_span: Option<usize> [absent] >= 1, "this number of logarithmic steps per span instead of step_km (Bosco et al. 2000, eq. (6): every step carries the same integrated power, short at the start of the span; double precision)";
113 amplifiers: AmplifierSettings [flatten], "the amplifiers after every span";
114 precision: String ["f64"] one_of ["f64", "f32"],
115 "f64, or f32 (about twice as fast, relative error about 1e-6 √steps; not with Raman)";
116 check_sampling: bool [true], "refuse a signal whose sampling rate or block cannot hold the four-wave mixing and the walk-off of the link (Musetti et al. 2018, Appendix B)";
117 }
118}
119
120struct SplitStep {
121 ssfm: Ssfm,
122 noise: bool,
123 precision: Precision,
124}
125
126impl SplitStep {
127 fn new(q: &SsfmBlock, ctx: &BuildContext) -> Result<SplitStep> {
128 let fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
129 let ssfm = Ssfm {
130 fiber,
131 step_km: q.step_km,
132 amplification: q.amplifiers.amplification()?,
133 raman: ctx.raman(fiber_json(q.fiber.as_ref()))?,
134 rule: q.steps_per_span.map(StepRule::Logarithmic),
135 check_sampling: q.check_sampling,
136 };
137 let precision: Precision = q.precision.parse().map_err(|e: Error| e.context("ssfm"))?;
138 ensure(!(precision == Precision::F32 && ssfm.raman.is_some()), || {
139 "ssfm: the Raman amplification is simulated in double precision only".into()
140 })?;
141 ensure(!(precision == Precision::F32 && ssfm.rule.is_some()), || {
142 "ssfm: the logarithmic steps (steps_per_span) are simulated in double precision only".into()
143 })?;
144 Ok(SplitStep { ssfm, noise: q.amplifiers.noise(), precision })
145 }
146}
147
148impl Block for SplitStep {
149 fn apply(&self, s: &mut Frame, rng: &mut Rng) -> Result<()> {
150 let rng = if self.noise { Some(rng) } else { None };
151 let fs = s.fs();
152 self.ssfm.propagate_with(&mut s.samples, fs, rng, self.precision)?;
153 Ok(())
154 }
155}
156
157block_params! {
158 struct EdfaBlock {
159 gain_db: f64 [required] "dB", "gain";
160 nf_db: f64 [4.5] "dB", "noise figure";
161 fc_hz: f64 [193.1e12] "Hz" > 0, "carrier frequency";
162 }
163}
164
165struct EdfaStage(Edfa);
166
167impl Block for EdfaStage {
168 fn apply(&self, s: &mut Frame, rng: &mut Rng) -> Result<()> {
169 let fs = s.fs();
170 self.0.apply(&mut s.samples, fs, rng)
171 }
172}
173
174block_params! {
177 struct CdcBlock {
178 fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
179 length_km: Option<f64> [absent "total"] "km", "compensated length";
180 }
181}
182
183struct Cdc {
184 fiber: FiberParams,
185 length_km: f64,
186}
187
188impl Block for Cdc {
189 fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
190 s.samples = cd::cdc(&s.samples, self.fiber.beta2_s2_km(), self.length_km, s.fs());
191 Ok(())
192 }
193}
194
195block_params! {
196 struct DbpBlock {
197 fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
198 steps_per_span: usize [1] >= 1, "symmetric steps per span";
199 step_distribution: String ["uniform"] one_of ["uniform", "logarithmic", "modified_logarithmic"],
200 "the steps of a span: equal, logarithmic (Bosco et al. 2000: every step carries the same integrated power of the forward span; better with 2 to 8 steps per span, worse with many), or modified logarithmic (Zhang et al. 2013: the attenuation times step_factor)";
201 step_factor: f64 [0.4] > 0, "the adjusting factor of the modified logarithmic steps (0.4 recommended by Zhang et al. 2013)";
202 nonlinear_length: String ["effective"] one_of ["effective", "step"],
203 "the nonlinear length of a coarse step: its effective length (the power integrated over the step) or the step itself (the midpoint rule of Häger & Pfister 2021)";
204 amplification: String ["edfa"] one_of ["edfa", "ideal", "none"], "the amplification of the forward link";
205 nonlinear: bool [true], "false: linear back-propagation (dispersion compensation and loss)";
206 nonlinear_scale: f64 [1.0] >= 0, "ξ: multiplies the nonlinear phase (optimised DBP with few steps, Ip & Kahn 2008)";
207 }
208}
209
210struct Dbp {
211 ssfm: Ssfm,
212 steps: usize,
213 xi: f64,
214 distribution: StepDistribution,
215 nonlinear_length: NonlinearLength,
216}
217
218impl Dbp {
219 fn new(q: &DbpBlock, ctx: &BuildContext) -> Result<Dbp> {
220 let mut fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
221 if !q.nonlinear {
222 fiber.gamma_w_km = 0.0;
223 }
224 let ssfm = Ssfm {
225 fiber,
226 step_km: 1.0,
227 amplification: q.amplification.parse()?,
228 raman: None,
229 rule: None,
230 check_sampling: true,
231 };
232 Ok(Dbp {
233 ssfm,
234 steps: q.steps_per_span,
235 xi: q.nonlinear_scale,
236 distribution: match q.step_distribution.parse().map_err(|e: Error| e.context("dbp"))? {
237 StepDistribution::ModifiedLogarithmic(_) => StepDistribution::ModifiedLogarithmic(q.step_factor),
238 d => d,
239 },
240 nonlinear_length: q.nonlinear_length.parse().map_err(|e: Error| e.context("dbp"))?,
241 })
242 }
243}
244
245impl Block for Dbp {
246 fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
247 let dbp = comnum_dsp::compensation::dbp::Dbp {
248 distribution: self.distribution,
249 nonlinear_length: self.nonlinear_length,
250 ..self.ssfm.dbp(self.steps, self.xi)
251 };
252 s.samples = dbp.run(&s.samples, s.fs())?;
253 Ok(())
254 }
255}