Skip to main content

comnum_spec/blocks/
fiber.rs

1//! Single-polarisation fibre propagation and its compensation: linear fibre, split-step Fourier, EDFA, dispersion compensation, digital back-propagation.
2
3use 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
17/// The blocks of this module: their documentation and their builders.
18pub(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
54/// The fibre of a block as given, merged over that of the specification by the context.
55pub(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    /// The amplifiers after every span: the parameters shared by `ssfm` and `manakov`.
89    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    /// The amplification after every span.
98    pub(super) fn amplification(&self) -> Result<Amplification> {
99        self.amplification.parse()
100    }
101
102    /// Whether the amplifiers and the Raman pumps add their noise.
103    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
174// ------------------------------------------------------------------------ receiver
175
176block_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}