edfa.py (6476B)
1 # 2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 # SPDX-License-Identifier: Apache-2.0 4 # 5 6 """ 7 This module defines a model for an Erbium-Doped Fiber Amplifier. 8 """ 9 import tensorflow as tf 10 from tensorflow.keras.layers import Layer 11 import sionna 12 13 class EDFA(Layer): 14 # pylint: disable=line-too-long 15 r"""EDFA(g=4.0,f=7.0,f_c=193.55e12,dt=1e-12,with_dual_polarization=False,dtype=tf.complex64,**kwargs) 16 17 Layer implementing a model of an Erbium-Doped Fiber Amplifier 18 19 Amplifies the optical input signal by a given gain and adds 20 amplified spontaneous emission (ASE) noise. 21 22 The noise figure including the noise due to beating of signal and 23 spontaneous emission is :math:`F_\mathrm{ASE,shot} =\frac{\mathrm{SNR} 24 _\mathrm{in}}{\mathrm{SNR}_\mathrm{out}}`, 25 where ideally the detector is limited by shot noise only, and 26 :math:`\text{SNR}` is the signal-to-noise-ratio. Shot noise is 27 neglected here but is required to derive the noise power of the amplifier, as 28 otherwise the input SNR is infinitely large. Hence, for the input SNR, 29 it follows [A2012]_ that 30 :math:`\mathrm{SNR}_\mathrm{in}=\frac{P}{2hf_cW}`, where :math:`h` denotes 31 Planck's constant, :math:`P` is the signal power, and :math:`W` the 32 considered bandwidth. 33 The output SNR is decreased by ASE noise induced by the amplification. 34 Note that shot noise is applied after the amplifier and is hence not 35 amplified. It results that :math:`\mathrm{SNR}_\mathrm{out}=\frac{GP}{\left 36 (4\rho_\mathrm{ASE}+2hf_c\right)W}`, where :math:`G` is the 37 parametrized gain. 38 Hence, one can write the former equation as :math:`F_\mathrm{ASE,shot} = 2 39 n_\mathrm{sp} \left(1-G^{-1}\right) + G^{-1}`. 40 Dropping shot noise again results in :math:`F = 2 n_\mathrm{sp} \left(1-G^ 41 {-1}\right)=2 n_\mathrm{sp} \frac{G-1}{G}`. 42 43 For a transparent link, e.g., the required gain per span is :math:`G = 44 \exp\left(\alpha \ell \right)`. 45 The spontaneous emission factor is :math:`n_\mathrm{sp}=\frac{F} 46 {2}\frac{G}{G-1}`. 47 According to [A2012]_ and [EKWFG2010]_ combined with [BGT2000]_ and [GD1991]_, 48 the noise power spectral density of the EDFA per state of 49 polarization is obtained as :math:`\rho_\mathrm{ASE}^{(1)} = n_\mathrm{sp}\left 50 (G-1\right) h f_c=\frac{1}{2}G F h f_c`. 51 At simulation frequency :math:`f_\mathrm{sim}`, the noise has a power of 52 :math:`P_\mathrm{ASE}^{(1)}=\sigma_\mathrm{n,ASE}^2=2\rho_\mathrm{ASE}^{(1)} 53 \cdot f_\mathrm{sim}`, 54 where the factor :math:`2` accounts for the unpolarized noise (for dual 55 polarization the factor is :math:`1` per polarization). 56 Here, the notation :math:`()^{(1)}` means that this is the noise introduced by a 57 single EDFA. 58 59 This class inherits from the Keras `Layer` class and can be used as layer in 60 a Keras model. 61 62 Example 63 -------- 64 65 Setting-up: 66 67 >>> edfa = EDFA( 68 >>> g=4.0, 69 >>> f=2.0, 70 >>> f_c=193.55e12, 71 >>> dt=1.0e-12, 72 >>> with_dual_polarization=False) 73 74 Running: 75 76 >>> # x is the optical input signal 77 >>> y = EDFA(x) 78 79 Parameters 80 ---------- 81 g : float 82 Amplifier gain (linear domain). Defaults to 4.0. 83 84 f : float 85 Noise figure (linear domain). Defaults to 7.0. 86 87 f_c : float 88 Carrier frequency :math:`f_\mathrm{c}` in :math:`(\text{Hz})`. 89 Defaults to 193.55e12. 90 91 dt : float 92 Time step :math:`\Delta_t` in :math:`(\text{s})`. 93 Defaults to 1e-12. 94 95 with_dual_polarization : bool 96 Considers axis [-2] as x- and y-polarization and applies the noise 97 per polarization. Defaults to `False`. 98 99 dtype : tf.complex 100 Defines the datatype for internal calculations and the output 101 dtype. Defaults to `tf.complex64`. 102 103 Input 104 ----- 105 x : Tensor, tf.complex 106 Optical input signal 107 108 Output 109 ------- 110 y : Tensor with same shape as ``x``, ``dtype`` 111 Amplifier output 112 """ 113 def __init__( 114 self, 115 g=4.0, 116 f=7.0, 117 f_c=193.55e12, 118 dt=1e-12, 119 with_dual_polarization=False, 120 dtype=tf.complex64, 121 **kwargs): 122 123 super().__init__(dtype=dtype, **kwargs) 124 125 self._dtype = dtype 126 self._cdtype = tf.as_dtype(dtype) 127 self._rdtype = tf.as_dtype(dtype).real_dtype 128 129 self._g = tf.cast(g, self._rdtype) 130 self._f = tf.cast(f, self._rdtype) 131 self._f_c = tf.cast(f_c, self._rdtype) 132 self._dt = tf.cast(dt, self._rdtype) 133 134 assert isinstance(with_dual_polarization, bool), \ 135 "with_dual_polarization must be bool." 136 self._with_dual_polarization = with_dual_polarization 137 138 # Spontaneous emission factor 139 if self._g == 1.0: 140 self._n_sp = tf.cast(0.0, self._rdtype) 141 else: 142 self._n_sp = self._f / tf.cast( 143 2.0, self._rdtype) * self._g / ( 144 self._g - tf.cast(1.0, self._rdtype)) 145 146 self._rho_n_ase = tf.cast( 147 self._n_sp * (self._g - tf.cast(1.0, self._rdtype)) * 148 sionna.constants.H * self._f_c, 149 self._rdtype) # Noise density in (W/Hz) 150 self._p_n_ase = tf.cast( 151 2.0, self._rdtype) * self._rho_n_ase * tf.cast( 152 1.0, self._rdtype) / (self._dt) # Noise power in (W) 153 154 if self._with_dual_polarization: 155 self._p_n_ase = self._p_n_ase / tf.cast(2.0, self._rdtype) 156 157 def call(self, inputs): 158 if self._with_dual_polarization: 159 tf.assert_equal(tf.shape(inputs)[-2], 2) 160 161 x = tf.cast(inputs, self._cdtype) 162 163 # Calculate noise signal with given noise power 164 n = tf.complex( 165 sionna.config.tf_rng.normal( 166 tf.shape(x), 167 tf.cast(0.0, self._rdtype), 168 tf.sqrt(self._p_n_ase / tf.cast(2.0, self._rdtype)), 169 self._rdtype), 170 sionna.config.tf_rng.normal( 171 tf.shape(x), 172 tf.cast(0.0, self._rdtype), 173 tf.sqrt(self._p_n_ase / tf.cast(2.0, self._rdtype)), 174 self._rdtype)) 175 176 # Amplify signal 177 x = x * tf.cast(tf.sqrt(self._g), self._cdtype) 178 179 # Add noise signal 180 y = x + n 181 182 return y