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 serde_json::Value as Json;
14
15/// The blocks of this module: their documentation and their builders.
16pub(super) fn blocks() -> Vec<Extension> {
17    vec![
18        builtin!(
19            per_polarization "fiber",
20            "Linear fibre (loss and dispersion, OptiCommPy linearFiberChannel).",
21            FiberBlock,
22            needs [Samples],
23            gives [],
24            |q, _, ctx| {
25                let fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
26                let length_km = q.length_km.unwrap_or(fiber.total_km());
27                Ok(Box::new(LinearFiber { length_km, fiber, compensate: q.compensate_loss }))
28            },
29        ),
30        builtin!(
31            per_polarization "ssfm", "Split-step Fourier propagation over the link, one polarisation.", SsfmBlock, needs [Samples], gives [],
32            |q, _, ctx| Ok(Box::new(SplitStep::new(&q, ctx)?)),
33        ),
34        builtin!(
35            per_polarization "edfa", "Erbium-doped fibre amplifier with ASE.", EdfaBlock, needs [Samples], gives [],
36            |q, _, _| Ok(Box::new(EdfaStage(Edfa { gain_db: q.gain_db, nf_db: q.nf_db, fc_hz: q.fc_hz }))),
37        ),
38        builtin!(
39            per_polarization "cdc", "Chromatic-dispersion compensation in the frequency domain.", CdcBlock, needs [Samples], gives [],
40            |q, _, ctx| {
41                let fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
42                Ok(Box::new(Cdc { length_km: q.length_km.unwrap_or(fiber.total_km()), fiber }))
43            },
44        ),
45        builtin!(
46            per_polarization "dbp", "Digital back-propagation by the split-step method.", DbpBlock, needs [Samples], gives [],
47            |q, _, ctx| Ok(Box::new(Dbp::new(&q, ctx)?)),
48        ),
49    ]
50}
51
52/// The fibre of a block as given, merged over that of the specification by the context.
53pub(super) fn fiber_json(f: Option<&Mapping<FiberSpec>>) -> Option<&Json> {
54    f.map(|m| &m.value)
55}
56
57block_params! {
58    struct FiberBlock {
59        fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
60        length_km: Option<f64> [absent "total"] "km" >= 0, "length; the whole link by default";
61        compensate_loss: bool [true], "restore the power with an ideal gain e^(αL/2)";
62    }
63}
64
65struct LinearFiber {
66    fiber: FiberParams,
67    length_km: f64,
68    compensate: bool,
69}
70
71impl Block for LinearFiber {
72    fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
73        let mut y = linear_fiber_channel(&s.samples, &self.fiber, self.length_km, s.fs());
74        if self.compensate {
75            let g = (self.fiber.alpha_np_km() / 2.0 * self.length_km).exp();
76            for v in &mut y {
77                *v *= g;
78            }
79        }
80        s.samples = y;
81        Ok(())
82    }
83}
84
85block_params! {
86    /// The amplifiers after every span: the parameters shared by `ssfm` and `manakov`.
87    pub(super) struct AmplifierSettings {
88        amplification: String ["edfa"] one_of ["edfa", "ideal", "none"],
89            "edfa (gain αL, ASE), ideal (noiseless gain) or none; with fiber.raman, the amplifier restores every frequency";
90        noise: bool [true], "draw the ASE of the EDFAs and the spontaneous Raman noise";
91    }
92}
93
94impl AmplifierSettings {
95    /// The amplification after every span.
96    pub(super) fn amplification(&self) -> Result<Amplification> {
97        self.amplification.parse()
98    }
99
100    /// Whether the amplifiers and the Raman pumps add their noise.
101    pub(super) fn noise(&self) -> bool {
102        self.noise
103    }
104}
105
106block_params! {
107    struct SsfmBlock {
108        fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
109        step_km: f64 [1.0] "km" > 0, "step";
110        amplifiers: AmplifierSettings [flatten], "the amplifiers after every span";
111        precision: String ["f64"] one_of ["f64", "f32"],
112            "f64, or f32 (about twice as fast, relative error about 1e-6 √steps; not with Raman)";
113        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)";
114    }
115}
116
117struct SplitStep {
118    ssfm: Ssfm,
119    noise: bool,
120    precision: Precision,
121}
122
123impl SplitStep {
124    fn new(q: &SsfmBlock, ctx: &BuildContext) -> Result<SplitStep> {
125        let fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
126        let ssfm = Ssfm {
127            fiber,
128            step_km: q.step_km,
129            amplification: q.amplifiers.amplification()?,
130            raman: ctx.raman(fiber_json(q.fiber.as_ref()))?,
131            rule: None,
132            check_sampling: q.check_sampling,
133        };
134        let precision: Precision = q.precision.parse().map_err(|e: Error| e.context("ssfm"))?;
135        ensure(!(precision == Precision::F32 && ssfm.raman.is_some()), || {
136            "ssfm: the Raman amplification is simulated in double precision only".into()
137        })?;
138        Ok(SplitStep { ssfm, noise: q.amplifiers.noise(), precision })
139    }
140}
141
142impl Block for SplitStep {
143    fn apply(&self, s: &mut Frame, rng: &mut Rng) -> Result<()> {
144        let rng = if self.noise { Some(rng) } else { None };
145        let fs = s.fs();
146        self.ssfm.propagate_with(&mut s.samples, fs, rng, self.precision)?;
147        Ok(())
148    }
149}
150
151block_params! {
152    struct EdfaBlock {
153        gain_db: f64 [required] "dB", "gain";
154        nf_db: f64 [4.5] "dB", "noise figure";
155        fc_hz: f64 [193.1e12] "Hz" > 0, "carrier frequency";
156    }
157}
158
159struct EdfaStage(Edfa);
160
161impl Block for EdfaStage {
162    fn apply(&self, s: &mut Frame, rng: &mut Rng) -> Result<()> {
163        let fs = s.fs();
164        self.0.apply(&mut s.samples, fs, rng)
165    }
166}
167
168// ------------------------------------------------------------------------ receiver
169
170block_params! {
171    struct CdcBlock {
172        fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
173        length_km: Option<f64> [absent "total"] "km", "compensated length";
174    }
175}
176
177struct Cdc {
178    fiber: FiberParams,
179    length_km: f64,
180}
181
182impl Block for Cdc {
183    fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
184        s.samples = cd::cdc(&s.samples, self.fiber.beta2_s2_km(), self.length_km, s.fs());
185        Ok(())
186    }
187}
188
189block_params! {
190    struct DbpBlock {
191        fiber: Option<Mapping<FiberSpec>> [absent "spec fiber"], FIBER_DOC;
192        steps_per_span: usize [1] >= 1, "symmetric steps per span";
193        amplification: String ["edfa"] one_of ["edfa", "ideal", "none"], "the amplification of the forward link";
194        nonlinear: bool [true], "false: linear back-propagation (dispersion compensation and loss)";
195        nonlinear_scale: f64 [1.0] >= 0, "ξ: multiplies the nonlinear phase (optimised DBP with few steps, Ip & Kahn 2008)";
196    }
197}
198
199struct Dbp {
200    ssfm: Ssfm,
201    steps: usize,
202    xi: f64,
203}
204
205impl Dbp {
206    fn new(q: &DbpBlock, ctx: &BuildContext) -> Result<Dbp> {
207        let mut fiber = ctx.fiber(fiber_json(q.fiber.as_ref()))?;
208        if !q.nonlinear {
209            fiber.gamma_w_km = 0.0;
210        }
211        let ssfm = Ssfm {
212            fiber,
213            step_km: 1.0,
214            amplification: q.amplification.parse()?,
215            raman: None,
216            rule: None,
217            check_sampling: true,
218        };
219        Ok(Dbp { ssfm, steps: q.steps_per_span, xi: q.nonlinear_scale })
220    }
221}
222
223impl Block for Dbp {
224    fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
225        s.samples = self.ssfm.backpropagate_scaled(&s.samples, s.fs(), self.steps, self.xi)?;
226        Ok(())
227    }
228}