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 serde_json::Value as Json;
14
15pub(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
52pub(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 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 pub(super) fn amplification(&self) -> Result<Amplification> {
97 self.amplification.parse()
98 }
99
100 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
168block_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}