From e519a3bc39be1bbd7734c25cfe6e67d4eb0073e9 Mon Sep 17 00:00:00 2001 From: Alessio Ferrari Date: Fri, 24 May 2019 10:36:29 +0200 Subject: [PATCH] add class RamanFiber --- gnpy/core/elements.py | 209 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/gnpy/core/elements.py b/gnpy/core/elements.py index d4ebf6c7..7d194c90 100644 --- a/gnpy/core/elements.py +++ b/gnpy/core/elements.py @@ -417,6 +417,215 @@ class Fiber(Node): self.carriers_out = carriers return spectral_info.update(carriers=carriers, pref=pref) +RamanFiberParams = namedtuple('RamanFiberParams', 'type_variety length loss_coef length_units \ + att_in con_in con_out dispersion gamma') + +class RamanFiber(Node): + def __init__(self, *args, params=None, **kwargs): + if params is None: + params = {} + if 'con_in' not in params: + # if not defined in the network json connector loss in/out + # the None value will be updated in network.py[build_network] + # with default values from eqpt_config.json[Spans] + params['con_in'] = None + params['con_out'] = None + if 'att_in' not in params: + #fixed attenuator for padding + params['att_in'] = 0 + + super().__init__(*args, params=RamanFiberParams(**params), **kwargs) + self.type_variety = self.params.type_variety + self.length = self.params.length * UNITS[self.params.length_units] # in m + self.loss_coef = self.params.loss_coef * 1e-3 # lineic loss dB/m + self.lin_loss_coef = self.params.loss_coef / (20 * log10(exp(1))) + self.att_in = self.params.att_in + self.con_in = self.params.con_in + self.con_out = self.params.con_out + self.dispersion = self.params.dispersion # s/m/m + self.gamma = self.params.gamma # 1/W/m + self.pch_out_db = None + self.carriers_in = None + self.carriers_out = None + # TODO|jla: discuss factor 2 in the linear lineic attenuation + + @property + def to_json(self): + return {'uid' : self.uid, + 'type' : type(self).__name__, + 'type_variety' : self.type_variety, + 'params' : { + #have to specify each because namedtupple cannot be updated :( + 'type_variety' : self.type_variety, + 'length' : self.length/UNITS[self.params.length_units], + 'loss_coef' : self.loss_coef*1e3, + 'length_units' : self.params.length_units, + 'att_in' : self.att_in, + 'con_in' : self.con_in, + 'con_out' : self.con_out + }, + 'metadata' : { + 'location': self.metadata['location']._asdict() + } + } + + def __repr__(self): + return f'{type(self).__name__}(uid={self.uid!r}, length={round(self.length*1e-3,1)!r}km, loss={round(self.loss,1)!r}dB)' + + def __str__(self): + return '\n'.join([f'{type(self).__name__} {self.uid}', + f' type_variety: {self.type_variety}', + f' length (km): {round(self.length*1e-3):.2f}', + f' pad att_in (dB): {self.att_in:.2f}', + f' total loss (dB): {self.loss:.2f}', + f' (includes conn loss (dB) in: {self.con_in:.2f} out: {self.con_out:.2f})', + f' (conn loss out includes EOL margin defined in eqpt_config.json)', + f' pch out (dBm): {self.pch_out_db!r}']) + + @property + def fiber_loss(self): + # dB fiber loss, not including padding attenuator + return self.loss_coef * self.length + self.con_in + self.con_out + + @property + def loss(self): + #total loss incluiding padding att_in: useful for polymorphism with roadm loss + return self.loss_coef * self.length + self.con_in + self.con_out + self.att_in + + @property + def passive(self): + return True + + @property + def lin_attenuation(self): + return db2lin(self.length * self.loss_coef) + + @property + def effective_length(self): + _, alpha = self.dbkm_2_lin() + leff = (1 - exp(-2 * alpha * self.length)) / (2 * alpha) + return leff + + @property + def asymptotic_length(self): + _, alpha = self.dbkm_2_lin() + aleff = 1 / (2 * alpha) + return aleff + + def carriers(self, loc, attr): + """retrieve carriers information + loc = (in, out) of the class element + attr = (ase, nli, signal, total) power information""" + if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')): + yield None + return + power_dict = { + 'nli': 'nonlinear_interference', + 'ase': 'amplified_spontaneous_emission' + } + attr = power_dict.get(attr, attr) + loc_attr = 'carriers_'+loc + for c in getattr(self, loc_attr) : + if attr == 'total': + yield c.power.ase+c.power.nli+c.power.signal + else: + yield c.power._asdict().get(attr, None) + + def beta2(self, ref_wavelength=None): + """ Returns beta2 from dispersion parameter. + Dispersion is entered in ps/nm/km. + Disperion can be a numpy array or a single value. If a + value ref_wavelength is not entered 1550e-9m will be assumed. + ref_wavelength can be a numpy array. + """ + # TODO|jla: discuss beta2 as method or attribute + wl = 1550e-9 if ref_wavelength is None else ref_wavelength + D = abs(self.dispersion) + b2 = (wl ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] + return b2 # s/Hz/m + + def dbkm_2_lin(self): + """ calculates the linear loss coefficient + """ + # alpha_pcoef is linear loss coefficient in dB/km^-1 + # alpha_acoef is linear loss field amplitude coefficient in m^-1 + alpha_pcoef = self.loss_coef + alpha_acoef = alpha_pcoef / (2 * 10 * log10(exp(1))) + return alpha_pcoef, alpha_acoef + + def _psi(self, carrier, interfering_carrier): + """ Calculates eq. 123 from arXiv:1209.0394. + """ + if carrier.num_chan == interfering_carrier.num_chan: # SCI + psi = arcsinh(0.5 * pi**2 * self.asymptotic_length + * abs(self.beta2()) * carrier.baud_rate**2) + else: # XCI + delta_f = carrier.freq - interfering_carrier.freq + psi = arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) + * carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate)) + psi -= arcsinh(pi**2 * self.asymptotic_length * abs(self.beta2()) + * carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate)) + + return psi + + def _gn_analytic(self, carrier, *carriers): + """ Computes the nonlinear interference power on a single carrier. + The method uses eq. 120 from arXiv:1209.0394. + :param carrier: the signal under analysis + :param carriers: the full WDM comb + :return: carrier_nli: the amount of nonlinear interference in W on the under analysis + """ + + g_nli = 0 + for interfering_carrier in carriers: + psi = self._psi(carrier, interfering_carrier) + g_nli += (interfering_carrier.power.signal/interfering_carrier.baud_rate)**2 \ + * (carrier.power.signal/carrier.baud_rate) * psi + + g_nli *= (16 / 27) * (self.gamma * self.effective_length)**2 \ + / (2 * pi * abs(self.beta2()) * self.asymptotic_length) + + carrier_nli = carrier.baud_rate * g_nli + return carrier_nli + + def propagate(self, *carriers): + + # apply connector_att_in on all carriers before computing gn analytics premiere partie pas bonne + attenuation = db2lin(self.con_in + self.att_in) + + chan = [] + for carrier in carriers: + pwr = carrier.power + pwr = pwr._replace(signal=pwr.signal/attenuation, + nonlinear_interference=pwr.nli/attenuation, + amplified_spontaneous_emission=pwr.ase/attenuation) + carrier = carrier._replace(power=pwr) + chan.append(carrier) + + carriers = tuple(f for f in chan) + + # propagate in the fiber and apply attenuation out + attenuation = db2lin(self.con_out) + for carrier in carriers: + pwr = carrier.power + carrier_nli = self._gn_analytic(carrier, *carriers) + pwr = pwr._replace(signal=pwr.signal/self.lin_attenuation/attenuation, + nonlinear_interference=(pwr.nli+carrier_nli)/self.lin_attenuation/attenuation, + amplified_spontaneous_emission=pwr.ase/self.lin_attenuation/attenuation) + yield carrier._replace(power=pwr) + + def update_pref(self, pref): + self.pch_out_db = round(pref.pi - self.loss, 2) + return pref._replace(p_span0=pref.p0, p_spani=self.pch_out_db) + + def __call__(self, spectral_info): + self.carriers_in = spectral_info.carriers + carriers = tuple(self.propagate(*spectral_info.carriers)) + pref = self.update_pref(spectral_info.pref) + self.carriers_out = carriers + return spectral_info.update(carriers=carriers, pref=pref) + + class EdfaParams: def __init__(self, **params): self.update_params(params)