diff --git a/mnoptical/edfa_params.py b/mnoptical/edfa_params.py index 2db8a020..b2b842f0 100644 --- a/mnoptical/edfa_params.py +++ b/mnoptical/edfa_params.py @@ -1,5 +1,3 @@ - - ripple_functions = { "linear": [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, @@ -37,5 +35,96 @@ 0.135829787234041, 0.141276595744682, 0.148425531914892, 0.157617021276597, 0.169531914893618, 0.185872340425533, 0.205617021276598, 0.227744680851064, 0.252255319148937, 0.278468085106383, 0.303659574468085, 0.328170212765958, 0.34927659574468, 0.369929078014184, 0.382978723404253 - ] + ], + + #rd1-co1 booster amplifier in the COSMOS testbed - version1 + "CM": [ + 0.11304348, 0.01304348, 0.01304348, 0.11304348, 0.01304348, + -0.08695652, 0.01304348, 0.11304348, 0.11304348, 0.11304348, + 0.11304348, 0.21304348, 0.01304348, 0.11304348, 0.01304348, + 0.11304348, 0.01304348, 0.11304348, 0.11304348, 0.11304348, + -0.08695652, 0.01304348, 0.01304348, 0.01304348, 0.11304348, + 0.11304348, 0.11304348, 0.11304348, 0.01304348, 0.11304348, + 0.11304348, 0.11304348, 0.01304348, 0.01304348, 0.11304348, + 0.01304348, 0.01304348, 0.01304348, -0.08695652, -0.08695652, + -0.18695652, -0.08695652, -0.08695652, -0.18695652, -0.08695652, + 0.01304348, 0.01304348, -0.08695652, 0.01304348, 0.01304348, + 0.01304348, 0.01304348, 0.11304348, 0.11304348, 0.11304348, + 0.21304348, 0.31304348, 0.21304348, 0.21304348, 0.21304348, + 0.21304348, 0.31304348, 0.41304348, 0.31304348, 0.31304348, + 0.21304348, 0.21304348, 0.11304348, 0.11304348, 0.11304348, + -0.08695652, -0.18695652, -0.18695652, -0.18695652, -0.18695652, + -0.28695652, -0.28695652, -0.18695652, -0.18695652, -0.38695652, + -0.28695652, -0.38695652, -0.38695652, -0.28695652, -0.38695652, + -0.38695652, -0.28695652, -0.28695652, -0.28695652, -0.18695652, + -0.08695652, 0.01304348 + + ], + + # rd1-co1 booster amplifier in the COSMOS testbed + "CM2": [0.163, 0.063, 0.063, 0.063, 0.063, 0.063, 0.163, 0.163, + 0.163, 0.163, 0.163, 0.163, 0.063, 0.263, 0.163, 0.263, + 0.163, 0.063, 0.263, 0.163, 0.163, 0.063, 0.163, 0.163, + 0.163, 0.263, 0.363, 0.263, 0.263, 0.363, 0.163, 0.263, + 0.163, 0.263, 0.163, 0.163, 0.263, 0.163, 0.063, 0.063, + 0.063, -0.037, -0.037, -0.037, -0.037, -0.137, 0.063, 0.063, + -0.037, -0.037, -0.037, 0.063, 0.063, 0.163, 0.063, 0.063, + 0.163, 0.163, 0.263, 0.163, 0.263, 0.263, 0.163, 0.163, + 0.263, 0.163, 0.063, 0.063, 0.063, -0.037, -0.037, -0.137, + -0.237, -0.337, -0.337, -0.437, -0.337, -0.337, -0.437, -0.437, + -0.337, -0.437, -0.437, -0.537, -0.537, -0.537, -0.637, -0.537, + -0.537, -0.637], + + + # rd2-co1 pre-amplifier in the COSMOS testbed + "CM3": [0.487, 0.487, 0.387, 0.387, 0.387, 0.487, 0.587, 0.487, 0.487, + 0.487, 0.387, 0.487, 0.387, 0.487, 0.487, 0.587, 0.487, 0.487, + 0.587, 0.587, 0.587, 0.587, 0.487, 0.387, 0.587, 0.487, 0.487, + 0.487, 0.387, 0.387, 0.387, 0.387, 0.387, 0.487, 0.587, 0.587, + 0.487, 0.587, 0.587, 0.587, 0.587, 0.687, 0.587, 0.587, 0.587, + 0.587, 0.687, 0.687, 0.687, 0.687, 0.687, 0.687, 0.587, 0.687, + 0.787, 0.887, 0.787, 0.787, 0.787, 0.787, 0.887, 0.787, 0.787, + 0.687, 0.687, 0.587, 0.687, 0.587, 0.487, 0.587, 0.487, 0.387, + 0.387, 0.387, 0.287, 0.387, 0.387, 0.487, 0.487, 0.387, 0.487, + 0.587, 0.587, 0.487, 0.487, 0.487, 0.387, 0.587, 0.587, 0.687], + + # In-Line amplifier in OpenIreland's ROADM2 + "CM4": [0.28374269, 0.18374269, 0.08374269, -0.01625731, -0.11625731, + -0.01625731, 0.08374269, 0.08374269, -0.01625731, -0.01625731, + 0.08374269, 0.08374269, 0.28374269, -0.01625731, -0.01625731, + 0.08374269, 0.08374269, 0.08374269, 0.08374269, 0.08374269, + -0.11625731, -0.01625731, -0.01625731, 0.08374269, 0.08374269, + -0.01625731, -0.11625731, -0.01625731, -0.01625731, 0.18374269, + 0.18374269, 0.08374269, 0.08374269, 0.18374269, 0.08374269, + 0.18374269, 0.08374269, -0.01625731, -0.01625731, -0.01625731, + 0.08374269, -0.01625731, -0.11625731, -0.11625731, -0.11625731, + -0.11625731, -0.11625731, -0.01625731, -0.11625731, -0.21625731, + -0.21625731, -0.01625731, -0.11625731, -0.11625731, -0.01625731, + -0.11625731, -0.01625731, -0.01625731, 0.18374269, 0.08374269, + -0.01625731, 0.08374269, 0.08374269, 0.08374269, 0.18374269, + 0.18374269, 0.08374269, 0.08374269, 0.08374269, -0.01625731, + 0.08374269, -0.01625731, -0.01625731, -0.01625731, -0.01625731, + -0.01625731, -0.01625731, -0.11625731, -0.31625731, -0.21625731, + -0.11625731, -0.21625731, -0.11625731, -0.21625731, -0.11625731, + -0.21625731, -0.11625731, -0.21625731, -0.01625731, -0.11625731] + +} + +# wavelength dependent gain values +fibre_spectral_attenuation = { + "SMF": [0.2087 , 0.20876, 0.20883, 0.20889, 0.20895, 0.20901, 0.20907, + 0.20913, 0.20919, 0.20925, 0.20932, 0.20938, 0.20944, 0.2095 , + 0.20956, 0.20962, 0.20968, 0.20974, 0.2098 , 0.20986, 0.20992, + 0.20998, 0.21005, 0.21011, 0.21017, 0.21023, 0.21029, 0.21035, + 0.21041, 0.21047, 0.21053, 0.21059, 0.21065, 0.21071, 0.21077, + 0.21083, 0.21089, 0.21095, 0.21102, 0.2111 , 0.21118, 0.21126, + 0.21134, 0.21142, 0.2115 , 0.21158, 0.21166, 0.21174, 0.21182, + 0.2119 , 0.21198, 0.21206, 0.21214, 0.21222, 0.21229, 0.21237, + 0.21245, 0.21253, 0.21261, 0.21269, 0.21277, 0.21285, 0.21293, + 0.21301, 0.21309, 0.21317, 0.21325, 0.21333, 0.2134 , 0.21348, + 0.21356, 0.21364, 0.21372, 0.2138 , 0.21388, 0.21396, 0.21404, + 0.21411, 0.21419, 0.21427, 0.21435, 0.21443, 0.21451, 0.21459, + 0.21466, 0.21474, 0.21482, 0.2149 , 0.21498, 0.21508, 0.2152 , + 0.21532, 0.21543 + ] } diff --git a/mnoptical/link.py b/mnoptical/link.py index f5ae532d..a798667e 100644 --- a/mnoptical/link.py +++ b/mnoptical/link.py @@ -3,6 +3,8 @@ from pprint import pprint from numpy import errstate from mnoptical.node import LineTerminal, Roadm, Amplifier +from mnoptical.edfa_params import fibre_spectral_attenuation +import math SpanTuple = namedtuple('Span', 'span amplifier') @@ -171,6 +173,7 @@ def propagate(self, is_last_port=False, safe_switch=False): first_span.propagate(is_last_port=is_last_port, safe_switch=safe_switch) + class Span(object): ids = 1 @@ -186,10 +189,10 @@ def __init__(self, fibre_type='SMF', length=20.0, debugger=False): Span.ids += 1 self.fibre_type = fibre_type self.length = length * km - self.fibre_attenuation = 0.22 / km # fiber attenuation in decibels/km - self.alpha = self.fibre_attenuation / (20 * np.log10(np.e)) # linear value fibre attenuation + self.fibre_attenuation = (self.get_fibre_spectral_attenuation())[::-1] + self.alpha = (self.fibre_attenuation) / (20 * np.log10(np.e)) # fibre attenuation self.effective_length = (1 - np.exp(-2 * self.alpha * self.length)) / (2 * self.alpha) - self.non_linear_coefficient = 1.27 / km # gamma fiber non-linearity coefficient [W^-1 km^-1] + self.non_linear_coefficient = 1.2 / km # gamma fiber non-linearity coefficient [W^-1 km^-1] self.dispersion = 1.67e-05 self.dispersion_coefficient = self.beta2() # B_2 dispersion coefficient [ps^2 km^-1] self.dispersion_slope = 0.1452 * (ps ** 3 / km) # B_3 dispersion slope in (ps^3 km^-1) @@ -198,7 +201,8 @@ def __init__(self, fibre_type='SMF', length=20.0, debugger=False): self.raman_amplification_band = 15 * THz # Raman amplification band ~15THz # Raman coefficient self.raman_coefficient = self.raman_gain / (2 * self.effective_area * self.raman_amplification_band) - + #self.raman_coefficient = (8.2e-17 / 2) or (7.87e-17 / 2) for 50 or 25 km spans + self.optical_signals = [] self.link = None self.prev_component = None @@ -214,7 +218,26 @@ def __repr__(self): def reset(self): self.optical_signals = [] - def attenuation(self): + def get_fibre_spectral_attenuation(self): + """ + Retrieve the WDL of the single mode fibre + :return: WDL of the SMF + """ + self.attenuation_values = list(fibre_spectral_attenuation['SMF']) + + for i in range(0, len(self.attenuation_values)): + self.attenuation_values[i] = self.attenuation_values[i] / km + # self.attenuation_values[i] = 0.22 / km # for linear attenuation + + return self.attenuation_values + + def attenuation(self, signal_index=1): + """ + Returns the attenuation value for each wavelength by the signal's index + :param signal_index: + """ + self.attenuation_values = self.get_fibre_spectral_attenuation() + self.fibre_attenuation = self.attenuation_values[92 - signal_index] return db_to_abs(self.fibre_attenuation * self.length) def beta2(self, ref_wavelength=1550e-9): @@ -275,11 +298,15 @@ def propagate(self, is_last_port=False, safe_switch=False): if self.link.srs_effect: if len(self.optical_signals) > 1: self.srs_effect_model() + # self.Zirngibl_General_Method() + # self.Sylvestre_SRS_model() + # self.Bigo_SRS_model() + # self.capacity_length_product() for optical_signal in self.optical_signals: - power_out = optical_signal.loc_out_to_state[self]['power'] / self.attenuation() - ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] / self.attenuation() - nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] / self.attenuation() + power_out = optical_signal.loc_out_to_state[self]['power'] / self.attenuation(optical_signal.index) + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] / self.attenuation(optical_signal.index) + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] / self.attenuation(optical_signal.index) self.include_optical_signal_out(optical_signal, power=power_out, ase_noise=ase_noise_out, nli_noise=nli_noise_out) @@ -318,8 +345,10 @@ def propagate(self, is_last_port=False, safe_switch=False): def srs_effect_model(self): """ Computation taken from : M. Zirngibl Analytical model of Raman gain effects in massive - wavelength division multiplexed transmission systems, 1998. - Equations 7,8. + wavelength division multiplexed transmission systems, 1998. - Equation 10. + - Most accurate for uniform loading cases. """ + min_wavelength_index = 90 max_wavelength_index = 0 min_signal = None @@ -340,22 +369,213 @@ def srs_effect_model(self): total_power = 0 # Total input power calculated by following loop for optical_signal in self.optical_signals: total_power += optical_signal.loc_out_to_state[self]['power'] - + i = 0 # Calculate delta P for each channel for optical_signal in self.optical_signals: frequency = optical_signal.frequency - with errstate(divide='ignore'): - r1 = beta * total_power * effective_length * (frequency_max - frequency_min) * math.e ** ( - beta * total_power * effective_length * (frequency - frequency_min)) # term 1 - r2 = math.e ** (beta * total_power * effective_length * (frequency_max - frequency_min)) - 1 # term 2 + with errstate(divide='ignore'): + i = i+1 + r1 = beta * total_power * effective_length[i] * (frequency_max - frequency_min) * math.e ** ( + beta * total_power * effective_length[i] * (frequency - frequency_min)) # term 1 + r2 = math.e ** (beta * total_power * effective_length[i] * (frequency_max - frequency_min)) - 1 # term 2 delta_p = float(r1 / r2) - power_out = optical_signal.loc_out_to_state[self]['power'] * delta_p - ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] * delta_p - nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] * delta_p + power_out = optical_signal.loc_out_to_state[self]['power'] / delta_p + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] / delta_p + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] / delta_p self.include_optical_signal_out(optical_signal, power=power_out, ase_noise=ase_noise_out, nli_noise=nli_noise_out) + def Zirngibl_General_Method(self): + """ + The general form of the SRS model as proposed by M. Zirngibl in + Analytical model of Raman gain effects in massive wavelength division + multiplexed transmission systems, 1998." - Equation 7 + - Most accurate for non-uniform loading cases. + """ + + beta = self.raman_coefficient # (mWHz)^-1 + effective_length = self.effective_length # SMF effective length (km) + alpha = self.alpha # fibre attenuation in dB/km + + power_array = [] # to store the power level of each channel + channels_frequency = [] # to store the frequency of each channel + total_power = 0 + i = 0 + counter = 1 + + # Iterate through all the signals + for optical_signal in self.optical_signals: + current_ch_power = optical_signal.loc_out_to_state[self]['power'] + # Sum the power across the channels + total_power += current_ch_power + power_array.append(current_ch_power) + + # Store each channel's frequency + frequency = (optical_signal.frequency) + channels_frequency.append(frequency) + + # Calculate delta P for each channel + for optical_signal in self.optical_signals: + num = power_array[i] * (total_power) * math.e ** (- alpha[i] * (self.length)) + den = 0 + for j in range(0, len(power_array)): + den = power_array[j] * math.e ** (beta * total_power * effective_length[i] * (channels_frequency[j] - channels_frequency[i])) + den_sum += den + i += 1 + + with errstate(divide='ignore'): + delta_p = float(num / den_sum) + + counter += 1 + + # Update the current values + power_out = optical_signal.loc_out_to_state[self]['power'] / delta_p + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] / delta_p + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] / delta_p + self.include_optical_signal_out(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) + + def capacity_length_product(self, system_capacity=4450e9, amplifier_spacing=50e3): + """ + CLP analytical model by Hyun Deok Kim and Chang-Hee Lee in + Capacities of WDM Transmission Systems and Networks Limited by + Stimulated Raman Scattering, 2001. - Equations 1 and 2 + """ + max_wavelength_index = 0 + + # get the frequency of the shortest wavelength + max_signal = None + no_of_signals = 0 + for optical_signal in self.optical_signals: + if optical_signal.index > max_wavelength_index: + max_signal = optical_signal + max_wavelength_index = optical_signal.index + no_of_signals += 1 + frequency_max = max_signal.frequency # shortest wavelength + + # calculate K + NF = 5.6 # dB + plank_constant = 6.62607015e-34 # (Joule second) + photon_energy = (plank_constant * frequency_max) # shortest wavelength + fibre_attenuation = 0.22 / 1000 # dB/km + optical_resolution_bandwidth = 12.5e9 # Hz + amplifier_spacing = 50e3 # m + exp_val = np.exp(fibre_attenuation * amplifier_spacing) - 1 + # K is a constant for the defined expression + K = (NF * photon_energy * optical_resolution_bandwidth * exp_val) + K = np.power(K, -1) + effective_area = 72e-6 # um + + # transmission_length, L -> No of spans * length of each span + length = self.length * 2 + fibre_attenuation = 0.22 / 1000 + alpha = fibre_attenuation / (20 * np.log10(np.e)) # linear attenuation + effective_length = (1 - np.exp(- alpha * length)) / (alpha) + channel_spacing = 50e9 # Hz + transmission_bandwidth = channel_spacing * (no_of_signals - 1) + raman_gain_coef = 5.9e-27 # slope of the Raman gain profile + bit_rate = 10e9 # bps + CLPs = [] + OSNR_values = [10, 20, 30, 40, 50, 60, 70] + for OSNR in OSNR_values: + n1 = (2.6 * effective_area) / (raman_gain_coef * effective_length) + n2 = bit_rate / OSNR + n3 = system_capacity / transmission_bandwidth + CLP = amplifier_spacing * np.sqrt(K * n1 * n2 * n3) + CLPs.append(CLP) + + def Sylvestre_SRS_model(self): + """ + SRS model proposed in 'Raman-Induced Power Tilt in Arbitrarily + Large Wavelength-Division-MUltiplexed Systems' by T. Sylvestre et al., 2005 + - Equation 6 + """ + F_j = [-1.5690, 5.6979, -6.5340, 4.7408, -2.0005, 0.1426, 5.9511, -3.2483, -2.4842, 0.4010] + omega_j = [0.0532, 4.1560, 5.2066, 6.4445, 7.2368, 14.9782, 15.7841, 16.3591, 18.2431, 24.4251] + delta_j = [1.2745, 7.4252, 5.2615, 4.8111, 3.7719, 0.6586, 4.2635, 2.2120, 5.4013, 1.3313] + + for i in range(0, len(F_j)): + omega_j[i] = omega_j[i] * (2 * math.pi) + delta_j[i] = delta_j[i] * (2 * math.pi) + + channel_spacing = 0.05 # THz + delta_w = 2 * math.pi * channel_spacing + + length = self.length / km + total_power = 0 # Total input power calculated by following loop + no_of_channels = 0 + for optical_signal in self.optical_signals: + total_power += optical_signal.loc_out_to_state[self]['power'] + no_of_channels += 1 + average_input_power = total_power / no_of_channels + + f_R = 0.18 # f_R raman_polarisation + non_linearity = 0.78 # W^-1 km^-1 at 1550nm + effective_length = self.effective_length # SMF effective distance + n_R = 10 + ind = 0 + + # Calculate delta P for each channel + for optical_signal in self.optical_signals: + total_sum = 0 + # change no_of_channels to the signal.index + # repeat iteratively for each individual channel + for k in range(1, optical_signal.index - 1): + for j in range(1, n_R): + numerator = k * delta_w * F_j[j] * omega_j[j] * (delta_j[j]**2) + denominator1 = ((delta_j[j] ** 2) + (omega_j[j] ** 2))**2 + denominator2 = (k * delta_w) ** 2 + denominator3 = ((k * delta_w) ** 2) + (2 * (delta_j[j] ** 2)) - (2 * (omega_j[j] ** 2)) + + denominator = denominator1 + (denominator2 * denominator3) + div = numerator / denominator + total_sum += div + delta_p = 34.74 * average_input_power * effective_length[ind] * non_linearity * f_R * total_sum + ind += 1 + delta_p_linear = db_to_abs(delta_p) # convert to linear + + # Update the current values + power_out = optical_signal.loc_out_to_state[self]['power'] / delta_p_linear + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] * delta_p_linear + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] * delta_p_linear + self.include_optical_signal_out(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) + + def Bigo_SRS_model(self): + """ + SRS model proposed in the paper tilted: + 'Experimental Investigation of Stimulated Raman Scattering + Limitation on WDM Transmission Over Various Types of Fiber + Infrastructures' by S.Bigo et al., 1999 - Equation 1 + - Simplified form of the srs_effect_model() function + """ + + channel_spacing = 50e9 + total_power = 0 + no_of_channels = 0 + for optical_signal in self.optical_signals: + # sum the total power across the channels + total_power += optical_signal.loc_out_to_state[self]['power'] + no_of_channels += 1 + + average_input_power = total_power / no_of_channels # average channel power + g_Aeff = 8.2e-17 # estimated Raman gain coefficient for a 50 km span in COSMOS + #g_Aeff = 7.87e-17 # estimated Raman gain coefficient for a 25 km span in COSMOS + + # Calculate delta P for each channel using the model + for optical_signal in self.optical_signals: + + delta_p = 2.17 * (g_Aeff) * average_input_power * no_of_channels * (optical_signal.index - 1) * self.effective_length[optical_signal.index] * channel_spacing + delta_p_linear = db_to_abs(delta_p) # convert to linear + + # Update the current values + power_out = optical_signal.loc_out_to_state[self]['power'] / delta_p_linear + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] / delta_p_linear + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] / delta_p_linear + self.include_optical_signal_out(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) + def output_nonlinear_noise(self): """ Compute GN model and updates state data structures @@ -384,6 +604,8 @@ def gn_model(self): gamma = self.non_linear_coefficient effective_length = self.effective_length asymptotic_length = 1 / (2 * alpha) + + index = 0 for optical_signal in self.optical_signals: channel_under_test = optical_signal.index symbol_rate_cut = optical_signal.symbol_rate @@ -392,16 +614,20 @@ def gn_model(self): g_cut = pwr_cut / bw_cut # G is the flat PSD per channel power (per polarization) g_nli = 0 + i = 0 for ch in self.optical_signals: + symbol_rate_ch = ch.symbol_rate bw_ch = symbol_rate_ch pwr_ch = ch.loc_out_to_state[self]['power'] g_ch = pwr_ch / bw_ch # G is the flat PSD per channel power (per polarization) - psi = self.psi_factor(optical_signal, ch, beta2=beta2, asymptotic_length=asymptotic_length) + psi = self.psi_factor(optical_signal, ch, beta2=beta2, asymptotic_length=asymptotic_length[i]) g_nli += g_ch ** 2 * g_cut * psi + i += 1 with errstate(divide='ignore'): - g_nli *= (16.0 / 27.0) * (gamma * effective_length) ** 2 / (2 * np.pi * abs(beta2) * asymptotic_length) + g_nli *= (16.0 / 27.0) * (gamma * effective_length[index]) ** 2 / (2 * np.pi * abs(beta2) * asymptotic_length[index]) + index = index + 1 signal_under_test = index_to_signal[channel_under_test] nonlinear_noise_struct[signal_under_test] = g_nli * bw_cut @@ -427,3 +653,4 @@ def psi_factor(carrier, interfering_carrier, beta2, asymptotic_length): psi -= np.arcsinh(np.pi ** 2 * asymptotic_length * abs(beta2) * bw_cut * (delta_f - 0.5 * bw_ch)) return psi + diff --git a/tests/SRS_Test.py b/tests/SRS_Test.py new file mode 100644 index 00000000..93676919 --- /dev/null +++ b/tests/SRS_Test.py @@ -0,0 +1,145 @@ +""" +This script can be used to check the SRS effect in the link class +in the EDFA class. + +This script requires the SRS flag in the link.py script to be turned on. +It uses the flat spectrum which ensures the end-to-end tilt at the input +of each amplifier is the same at its output. There is no amplifier tilt. + +""" + +from mnoptical.topo.linear_params import LinearTopology +from scipy.optimize import curve_fit +import numpy as np +import math +import unittest + +power_dBm = 0 +span_length_km = 50 +span_no = 3 +hop_no = 2 + +wdg_id = 'linear' +signal_no = 90 + +net = LinearTopology.build(power_dBm=power_dBm, span_length_km=span_length_km, + span_no=span_no, hop_no=hop_no, signal_no=signal_no, + wdg_id='linear') + + +# convert the watts values to dBm +def wattsTodBm(value): + conv = 10 * np.log10(value) + 30 + return conv + + +# basis function for end to end tilt +def basisFunction(x, m, b): + return m * x + b + + +# calculate the end to end tilt +def computeEndToEndTilt(values): + data_len = len(values) + x = np.arange(1, data_len+1, 1) + y = values + popt, _ = curve_fit(basisFunction, x, y) + # summarize the parameter values + a, b = popt + x_line = np.arange(min(x), max(x), 1) + y_line = basisFunction(x_line, a, b) + tilt = max(y_line) - min(y_line) + return tilt + + +def configure_terminals(): + tx = net.name_to_node['tx'] + rx = net.name_to_node['rx'] + for p, c in enumerate(range(1, signal_no + 1), start=1): + # configure transmitter terminal + tx_transceiver = tx.id_to_transceivers[c] + tx.assoc_tx_to_channel(tx_transceiver, c, out_port=p) + # configure receiver terminal + rx_transceiver = rx.id_to_transceivers[1] + rx.assoc_rx_to_channel(rx_transceiver, c, in_port=1) + + +def configure_roadms(): + in_port = out_port = 0 + for i, r in enumerate(net.roadms): + + if i == 0: + # case for first ROADM + for in_port, c in enumerate(range(1, signal_no + 1), start=1): + r.install_switch_rule(in_port, out_port, c) + elif i == len(net.roadms) - 1: + in_port = 0 + # case for last ROADM + for out_port, c in enumerate(range(1, signal_no + 1), start=1): + r.install_switch_rule(in_port, 1, c) + else: + # case for intermediate ROADM + in_port = out_port = 0 + for c in range(1, signal_no + 1): + r.install_switch_rule(in_port, out_port, c) + + # returns two ROADM objects + return net.roadms[0], net.roadms[1] + + +def launch_transmission(): + tx = net.name_to_node['tx'] + tx.turn_on() + + +configure_terminals() +r = configure_roadms() +launch_transmission() +optical_signals = r[1].get_optical_signals() + +# declare some arrays to store the computed values +r2_preamp_in = [] +amp1_in = [] +amp2_in = [] +preamp_in = [] + +# read the monitored values +for i in range(0, len(optical_signals)): + for k, v in list(optical_signals[i].loc_in_to_state.items()): + if str(k) == "": # amp1's input + amp1_in.append(wattsTodBm(v['power'])) + elif str(k) == "": # amp2's input + amp2_in.append(wattsTodBm(v['power'])) + elif str(k) == "": # input of R2's pre-amp + preamp_in.append(wattsTodBm(v['power'])) + + +# compute the end-to-end tilt for each input +amp1_in_dBm_tilt = computeEndToEndTilt(amp1_in) +amp2_in_dBm_tilt = computeEndToEndTilt(amp2_in) +preamp_in_dBm_tilt = computeEndToEndTilt(preamp_in) + +# round the values to 2 decimal places +amp1_in_dBm_tilt = round(amp1_in_dBm_tilt, 2) +amp2_in_dBm_tilt = round(amp2_in_dBm_tilt, 2) +preamp_in_dBm_tilt = round(preamp_in_dBm_tilt, 2) + +# combine the results for each input +actual_result = [amp1_in_dBm_tilt, amp2_in_dBm_tilt, preamp_in_dBm_tilt] + +print("amp1_in_dBm_tilt: ", amp1_in_dBm_tilt) +print("amp2_in_dBm_tilt: ", amp2_in_dBm_tilt) +print("preamp_in_dBm_tilt: ", preamp_in_dBm_tilt) + + +# This class defines a unit test to validate the results obtained +# in the SRS test above +class TestSRS(unittest.TestCase): + + def test_SRS(self): + expected_result = [0.62, 1.33, 2.15] + self.assertEqual(actual_result, expected_result) + +if __name__ == '__main__': + unittest.main() +