def propagate_and_optimize_mode(path, req, equipment, show=False): # if mode is unknown : loops on the modes starting from the highest baudrate fiting in the # step 1: create an ordered list of modes based on baudrate baudrate_to_explore = list(set([m['baud_rate'] for m in equipment['Transceiver'][req.tsp].mode if float(m['min_spacing'])<= req.spacing])) # TODO be carefull on limits cases if spacing very close to req spacing eg 50.001 50.000 baudrate_to_explore = sorted(baudrate_to_explore, reverse=True) if baudrate_to_explore : # at least 1 baudrate can be tested wrt spacing for b in baudrate_to_explore : modes_to_explore = [m for m in equipment['Transceiver'][req.tsp].mode if m['baud_rate'] == b and float(m['min_spacing'])<= req.spacing] modes_to_explore = sorted(modes_to_explore, key = lambda x: x['bit_rate'], reverse=True) # print(modes_to_explore) # step2 : computes propagation for each baudrate: stop and select the first that passes found_a_feasible_mode = False # TODO : the case of roll of is not included: for now use SI one # TODO : if the loop in mode optimization does not have a feasible path, then bugs si = create_input_spectral_information( req.f_min, req.f_max, equipment['SI']['default'].roll_off, b, req.power, req.spacing) for el in path: si = el(si) if show: print(el) for m in modes_to_explore : if path[-1].snr is not None: path[-1].update_snr(m['tx_osnr'], equipment['Roadm']['default'].add_drop_osnr) if round(min(path[-1].snr+lin2db(b/(12.5e9))),2) > m['OSNR'] : found_a_feasible_mode = True return path, m else: return [], None # only get to this point if no baudrate/mode satisfies OSNR requirement # returns the last propagated path and mode msg = f'\tWarning! Request {req.request_id}: no mode satisfies path SNR requirement.\n' print(msg) logger.info(msg) return [],None else : # no baudrate satisfying spacing msg = f'\tWarning! Request {req.request_id}: no baudrate satisfies spacing requirement.\n' print(msg) logger.info(msg) return [], None
def propagate(self, pref, *carriers): #pin_target and loss are read from eqpt_config.json['Roadm'] #all ingress channels in xpress are set to this power level #but add channels are not, so we define an effective loss #in the case of add channels self.effective_pch_out_db = min(pref.p_spani, self.params.target_pch_out_db) self.effective_loss = pref.p_spani - self.effective_pch_out_db carriers_power = array([c.power.signal +c.power.nli+c.power.ase for c in carriers]) carriers_att = list(map(lambda x : lin2db(x*1e3)-self.params.target_pch_out_db, carriers_power)) exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default = 0) carriers_att = list(map(lambda x: db2lin(x+exceeding_att), carriers_att)) for carrier_att, carrier in zip(carriers_att, carriers) : pwr = carrier.power pwr = pwr._replace( signal = pwr.signal/carrier_att, nli = pwr.nli/carrier_att, ase = pwr.ase/carrier_att) yield carrier._replace(power=pwr)
def test_excel_service_json_generation(xls_input, expected_json_output): """ test services creation """ equipment = load_equipment(eqpt_filename) network = load_network(DATA_DIR / 'testTopology.xls', equipment) # Build the network once using the default power defined in SI in eqpt config p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) from_xls = read_service_sheet(xls_input, equipment, network, network_filename=DATA_DIR / 'testTopology.xls') assert from_xls == load_json(expected_json_output)
def test_setup(): ''' common setup for tests: builds network, equipment and oms only once ''' equipment = load_equipment(EQPT_LIBRARY_NAME) network = load_network(NETWORK_FILE_NAME, equipment) # Build the network once using the default power defined in SI in eqpt config # power density : db2linp(ower_dbm': 0)/power_dbm': 0 * nb channels as defined by # spacing, f_min and f_max p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) build_oms_list(network, equipment) return network, equipment
def test_compare_nf_models(gain, setup_edfa_variable_gain, si): """ compare the 2 amplifier models (polynomial and estimated from nf_min and max) => nf_model vs nf_poly_fit for intermediate gain values: between gain_min and gain_flatmax some discrepancy is expected but target < 0.5dB => unitary test for Edfa._calc_nf (and Edfa.interpol_params)""" edfa = setup_edfa_variable_gain frequencies = array([c.frequency for c in si.carriers]) pin = array( [c.power.signal + c.power.nli + c.power.ase for c in si.carriers]) pin = pin / db2lin(gain) baud_rates = array([c.baud_rate for c in si.carriers]) edfa.operational.gain_target = gain pref = Pref(0, -gain, lin2db(len(frequencies))) edfa.interpol_params(frequencies, pin, baud_rates, pref) nf_model = edfa.nf[0] edfa.interpol_params(frequencies, pin, baud_rates, pref) nf_poly = edfa.nf[0] assert pytest.approx(nf_model, abs=0.5) == nf_poly
def test_compare_nf_models(gain, setup_edfa_variable_gain, si): """ compare the 2 amplifier models (polynomial and estimated from nf_min and max) => nf_model vs nf_poly_fit for intermediate gain values: between gain_min and gain_flatmax some discrepancy is expected but target < 0.5dB => unitary test for Edfa._calc_nf (and Edfa.interpol_params)""" edfa = setup_edfa_variable_gain frequencies = array([c.frequency for c in si.carriers]) pin = array( [c.power.signal + c.power.nli + c.power.ase for c in si.carriers]) pin = pin / db2lin(gain) baud_rates = array([c.baud_rate for c in si.carriers]) edfa.operational.gain_target = gain # edfa is variable gain type pref = Pref(0, -gain, lin2db(len(frequencies))) edfa.interpol_params(frequencies, pin, baud_rates, pref) nf_model = edfa.nf[0] # change edfa type variety to a polynomial el_config = { "uid": "Edfa1", "operational": { "gain_target": gain, "tilt_target": 0 }, "metadata": { "location": { "region": "", "latitude": 2, "longitude": 0 } } } equipment = load_equipment(eqpt_library) extra_params = equipment['Edfa']['CienaDB_medium_gain'] temp = el_config.setdefault('params', {}) temp = merge_amplifier_restrictions(temp, extra_params.__dict__) el_config['params'] = temp edfa = Edfa(**el_config) # edfa is variable gain type edfa.interpol_params(frequencies, pin, baud_rates, pref) nf_poly = edfa.nf[0] print(nf_poly, nf_model) assert pytest.approx(nf_model, abs=0.5) == nf_poly
def test_no_amp_feature(node_uid): ''' Check that booster is not placed on a roadm if fused is specified test_parser covers partly this behaviour. This test should guaranty that the feature is preserved even if convert is changed ''' equipment = load_equipment(EQPT_LIBRARY_NAME) json_network = load_json(NETWORK_FILE_NAME) for elem in json_network['elements']: if elem['uid'] == node_uid: #replace edfa node by a fused node in the topology elem['type'] = 'Fused' elem.pop('type_variety') elem.pop('operational') elem['params'] = {'loss': 0} next_node_uid = next(conn['to_node'] for conn in json_network['connections'] \ if conn['from_node'] == node_uid) previous_node_uid = next(conn['from_node'] for conn in json_network['connections'] \ if conn['to_node'] == node_uid) network = network_from_json(json_network, equipment) # Build the network once using the default power defined in SI in eqpt config # power density : db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by # spacing, f_min and f_max p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,\ equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) node = next(nd for nd in network.nodes() if nd.uid == node_uid) next_node = next(network.successors(node)) previous_node = next(network.predecessors(node)) if not isinstance(node, Fused): raise AssertionError() if not node.params.loss == 0.0: raise AssertionError() if not next_node_uid == next_node.uid: raise AssertionError() if not previous_node_uid == previous_node.uid: raise AssertionError()
def _calc_nf(self): """nf calculation based on 2 models: self.params.nf_model.enabled from json import: True => 2 stages amp modelling based on precalculated nf1, nf2 and delta_p in build_OA_json False => polynomial fit based on self.params.nf_fit_coeff""" #TODO : tbd alarm rising or input VOA padding in case #gain_min > gain_target TBD: pad = max(self.params.gain_min - self.operational.gain_target, 0) gain_target = self.operational.gain_target + pad dg = gain_target - self.params.gain_flatmax # ! <0 if self.params.nf_model.enabled: g1a = gain_target - self.params.nf_model.delta_p + dg nf_avg = lin2db( db2lin(self.params.nf_model.nf1) + db2lin(self.params.nf_model.nf2) / db2lin(g1a)) else: nf_avg = np.polyval(self.params.nf_fit_coeff, dg) nf_array = self.interpol_nf_ripple + nf_avg + pad #input VOA = 1 for 1 NF degradation return nf_array
def update_snr(self, *args): """ snr_added in 0.1nm compute SNR penalties such as transponder Tx_osnr or Roadm add_drop_osnr only applied in request.py / propagate on the last Trasceiver node of the path all penalties are added in a single call because to avoid uncontrolled cumul """ # use raw_values so that the added SNR penalties are not cumulated snr_added = 0 for s in args: snr_added += db2lin(-s) snr_added = -lin2db(snr_added) self.osnr_ase = list(map(lambda x, y: snr_sum(x, y, snr_added), self.raw_osnr_ase, self.baud_rate)) self.snr = list(map(lambda x, y: snr_sum(x, y, snr_added), self.raw_snr, self.baud_rate)) self.osnr_ase_01nm = list(map(lambda x: snr_sum(x, 12.5e9, snr_added), self.raw_osnr_ase_01nm)) self.snr_01nm = list(map(lambda x: snr_sum(x, 12.5e9, snr_added), self.raw_snr_01nm))
def calc_average_gsnr_opt(): """ Function to print the average GSNR for all channels for several .mat data files """ path_data = Path( '/mnt/Bruno_Data/GoogleDrive/Material_Academico/PoliTo_WON/Research/Simulations_Data/Results/' 'JOCN_Power_Optimization/C_L_S/Future_scenarios_analyze/Profiles') list_folder = [file for file in os.listdir(path_data)] for folder in list_folder: print(folder) list_files = [file for file in os.listdir(path_data / folder)] for file in list_files: data = loadmat(path_data / (folder + '/' + file)) gsnr = np.transpose(data['GSNR']) print('Name of profile: {}'.format(data['name'][0])) print('Average GSNR: {} dB'.format(np.mean(lin2db(gsnr)))) print('\n')
def test_disjunction(net, eqpt, serv): data = load_requests(serv, eqpt) equipment = load_equipment(eqpt) network = load_network(net, equipment) # Build the network once using the default power defined in SI in eqpt config # power density : db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by # spacing, f_min and f_max p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,\ equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) rqs = requests_from_json(data, equipment) rqs = correct_route_list(network, rqs) dsjn = disjunctions_from_json(data) pths = compute_path_dsjctn(network, equipment, rqs, dsjn) print(dsjn) dsjn_list = [d.disjunctions_req for d in dsjn] # assumes only pairs in dsjn list test = True for e in dsjn_list: rqs_id_list = [r.request_id for r in rqs] p1 = pths[rqs_id_list.index(e[0])][1:-1] p2 = pths[rqs_id_list.index(e[1])][1:-1] if isdisjoint(p1, p2) + isdisjoint(p1, find_reversed_path( p2, network)) > 0: test = False print( f'Computed path (roadms):{[e.uid for e in p1 if isinstance(e, Roadm)]}\n' ) print( f'Computed path (roadms):{[e.uid for e in p2 if isinstance(e, Roadm)]}\n' ) break print(dsjn_list) assert test
def test_auto_design_generation_fromjson(tmpdir, json_input, power_mode): """test that autodesign creates same file as an input file already autodesigned """ equipment = load_equipment(eqpt_filename) network = load_network(json_input, equipment) # in order to test the Eqpt sheet and load gain target, # change the power-mode to False (to be in gain mode) equipment['Span']['default'].power_mode = power_mode # Build the network once using the default power defined in SI in eqpt config p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) actual_json_output = tmpdir / json_input.with_name( json_input.stem + '_auto_design').with_suffix('.json').name save_network(network, actual_json_output) actual = load_json(actual_json_output) unlink(actual_json_output) assert actual == load_json(json_input)
def calc_best_gsnr(data_path, output_folder=None, thr=0.01, copy_files=False): """ Save a list of profiles with best GSNR average for a threshold value of difference """ config_path = data_path / 'config_file.json' if not output_folder: output_folder = data_path.parent / 'results/results_{}'.format(thr) if not os.path.isdir(output_folder): os.makedirs(output_folder) # Create list of .mat files (combinations) and load configuration file list_data_files = [file for file in os.listdir(data_path) if file.endswith('.mat')] config = json.load(open(config_path, 'r')) bands = config['spectral_config']['bands'] # Calculation only for the channels under test ordered_list = [] files_bar = tqdm(iterable=list_data_files, desc='Calculating gsnr average for all combinations') for file_name in files_bar: data = DataQot.load_qot_mat(data_path / file_name) calc_gsnr_lin = [gsnr for i, gsnr in enumerate(data.gsnr) if i in data.cut_index] calc_gsnr_db = [lin2db(gsnr) for gsnr in calc_gsnr_lin] gsnr_average = np.mean(calc_gsnr_db) ordered_list.append([data.name, gsnr_average]) # Order list of combinations by GSNR average ordered_list.sort(key=lambda value: value[1], reverse=True) thr_gsnr = ordered_list[0][1] - (ordered_list[0][1] * thr) # Remove items with smaller GSNR average than the threshold ordered_list = [item for item in ordered_list if item[1] >= thr_gsnr] print('{} profiles with acceptable GSNR average'.format(len(ordered_list))) # Save best GSNR list and profiles with open((output_folder / 'Best_GSNR_average_{}.txt'.format(thr)), 'w') as best_combs: for comb in ordered_list: best_combs.write('{}: {}\n'.format(comb[0], comb[1])) if copy_files: shutil.copy2((data_path / (comb[0] + '.mat')), output_folder)
def _calc_nf(self, avg=False): """nf calculation based on 2 models: self.params.nf_model.enabled from json import: True => 2 stages amp modelling based on precalculated nf1, nf2 and delta_p in build_OA_json False => polynomial fit based on self.params.nf_fit_coeff""" # TODO|jla: TBD alarm rising or input VOA padding in case # gain_min > gain_target TBD: pad = max(self.params.gain_min - self.effective_gain, 0) self.att_in = pad gain_target = self.effective_gain + pad dg = max(self.params.gain_flatmax - gain_target, 0) if self.params.type_def == 'variable_gain': g1a = gain_target - self.params.nf_model.delta_p - dg nf_avg = lin2db( db2lin(self.params.nf_model.nf1) + db2lin(self.params.nf_model.nf2) / db2lin(g1a)) elif self.params.type_def == 'fixed_gain': nf_avg = self.params.nf_model.nf0 else: nf_avg = polyval(self.params.nf_fit_coeff, -dg) if avg: return nf_avg + pad else: return self.interpol_nf_ripple + nf_avg + pad # input VOA = 1 for 1 NF degradation
def _calc_nf(self, avg=False): """nf calculation based on 2 models: self.params.nf_model.enabled from json import: True => 2 stages amp modelling based on precalculated nf1, nf2 and delta_p in build_OA_json False => polynomial fit based on self.params.nf_fit_coeff""" # gain_min > gain_target TBD: if self.params.type_def == 'dual_stage': g1 = self.params.preamp_gain_flatmax g2 = self.effective_gain - g1 nf1_avg, pad = self._nf(self.params.preamp_type_def, self.params.preamp_nf_model, self.params.preamp_nf_fit_coeff, self.params.preamp_gain_min, self.params.preamp_gain_flatmax, g1) # no padding expected for the 1stage because g1 = gain_max nf2_avg, pad = self._nf(self.params.booster_type_def, self.params.booster_nf_model, self.params.booster_nf_fit_coeff, self.params.booster_gain_min, self.params.booster_gain_flatmax, g2) nf_avg = lin2db(db2lin(nf1_avg) + db2lin(nf2_avg - g1)) # no padding expected for the 1stage because g1 = gain_max pad = 0 else: nf_avg, pad = self._nf(self.params.type_def, self.params.nf_model, self.params.nf_fit_coeff, self.params.gain_min, self.params.gain_flatmax, self.effective_gain) self.att_in = pad # not used to attenuate carriers, only used in _repr_ and _str_ if avg: return nf_avg else: return self.interpol_nf_ripple + nf_avg # input VOA = 1 for 1 NF degradation
def calc_average_gsnr(): path_data = Path( '/mnt/Bruno_Data/GoogleDrive/Material_Academico/PoliTo_WON/research/Simulations_Data/Results/' 'JOCN_Power_Optimization/Compare_GSNR') config_path = path_data / 'config_file_cl.json' config = json.load(open(config_path, 'r')) bands = config['spectral_config']['bands'] data = loadmat( path_data / 'jocn_2020_l_c_offset_l=0.5_tilt_l=0.1_offset_c=1.0_tilt_c=-0.3.mat') num_ch_band = 0 for band in bands: bands[band]['comp_channels_ind'] = [ (ch + num_ch_band - 1) for ch in bands[band]['comp_channels'] ] num_ch_band += bands[band]['nb_channels'] gsnr_per_band = 0.0 total_gsnr, freq_list = [], [] for band in bands: gsnr_per_band = [ lin2db(gsnr) for i, gsnr in enumerate(data['GSNR'][0]) if i in bands[band]['comp_channels_ind'] ] total_gsnr.extend(gsnr_per_band) freq_list.extend([ freq for i, freq in enumerate(data['frequencies'][0]) if i in bands[band]['comp_channels_ind'] ]) print('Average GSNR for {} band is {}'.format(band, np.mean(gsnr_per_band))) print('Average GSNR is {}'.format(np.mean(total_gsnr))) array = np.array([freq_list, total_gsnr]) np.save(path_data / 'C+L.npy', array)
def propagate(self, pref, *carriers, degree): # pin_target and loss are read from eqpt_config.json['Roadm'] # all ingress channels in xpress are set to this power level # but add channels are not, so we define an effective loss # in the case of add channels # find the target power on this degree: # if a target power has been defined for this degree use it else use the global one. # if the input power is lower than the target one, use the input power instead because # a ROADM doesn't amplify, it can only attenuate # TODO maybe add a minimum loss for the ROADM per_degree_pch = self.per_degree_pch_out_db[degree] if degree in self.per_degree_pch_out_db.keys() else self.params.target_pch_out_db self.effective_pch_out_db = min(pref.p_spani, per_degree_pch) self.effective_loss = pref.p_spani - self.effective_pch_out_db carriers_power = array([c.power.signal + c.power.nli + c.power.ase for c in carriers]) carriers_att = list(map(lambda x: lin2db(x * 1e3) - per_degree_pch, carriers_power)) exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default=0) carriers_att = list(map(lambda x: db2lin(x + exceeding_att), carriers_att)) for carrier_att, carrier in zip(carriers_att, carriers): pwr = carrier.power pwr = pwr._replace(signal=pwr.signal / carrier_att, nli=pwr.nli / carrier_att, ase=pwr.ase / carrier_att) pmd = sqrt(carrier.pmd**2 + self.params.pmd**2) yield carrier._replace(power=pwr, pmd=pmd)
def path_metric(pth, req): """ creates the metrics dictionary """ return [{ 'metric-type': 'SNR-bandwidth', 'accumulative-value': round(mean(pth[-1].snr), 2) }, { 'metric-type': 'SNR-0.1nm', 'accumulative-value': round(mean(pth[-1].snr + lin2db(req.baud_rate / 12.5e9)), 2) }, { 'metric-type': 'OSNR-bandwidth', 'accumulative-value': round(mean(pth[-1].osnr_ase), 2) }, { 'metric-type': 'OSNR-0.1nm', 'accumulative-value': round(mean(pth[-1].osnr_ase_01nm), 2) }, { 'metric-type': 'reference_power', 'accumulative-value': req.power }, { 'metric-type': 'path_bandwidth', 'accumulative-value': req.path_bandwidth }]
def test_auto_design_generation_fromxlsgainmode(tmpdir, xls_input, expected_json_output): """ tests generation of topology json test that the build network gives correct results in gain mode """ equipment = load_equipment(eqpt_filename) network = load_network(xls_input, equipment) # in order to test the Eqpt sheet and load gain target, # change the power-mode to False (to be in gain mode) equipment['Span']['default'].power_mode = False # Build the network once using the default power defined in SI in eqpt config p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) actual_json_output = tmpdir / xls_input.with_name( xls_input.stem + '_auto_design').with_suffix('.json').name save_network(network, actual_json_output) actual = load_json(actual_json_output) unlink(actual_json_output) assert actual == load_json(expected_json_output)
except EquipmentConfigError as e: print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') exit(1) except NetworkTopologyError as e: print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}') exit(1) except ConfigurationError as e: print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') exit(1) # Build the network once using the default power defined in SI in eqpt config # TODO power density : db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by # spacing, f_min and f_max p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,\ equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) save_network(args.network_filename, network) rqs = requests_from_json(data, equipment) # check that request ids are unique. Non unique ids, may # mess the computation : better to stop the computation all_ids = [r.request_id for r in rqs] if len(all_ids) != len(set(all_ids)): for a in list(set(all_ids)): all_ids.remove(a) msg = f'Requests id {all_ids} are not unique' logger.critical(msg) exit() rqs = correct_route_list(network, rqs)
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing): # pref in dB : convert power lin into power in dB pref = lin2db(power * 1e3) nb_channel = automatic_nch(f_min, f_max, spacing) si = SpectralInformation(pref=Pref(pref, pref, lin2db(nb_channel)), carriers=[ Channel(f, (f_min + spacing * f), baud_rate, roll_off, Power(power, 0, 0)) for f in range(1, nb_channel + 1) ]) return si if __name__ == '__main__': pref = lin2db(power * 1e3) si = SpectralInformation( Pref(pref, pref), Channel( 1, 193.95e12, 32e9, 0.15, # 193.95 THz, 32 Gbaud Power(1e-3, 1e-6, 1e-6)), # 1 mW, 1uW, 1uW Channel( 1, 195.95e12, 32e9, 0.15, # 195.95 THz, 32 Gbaud Power(1.2e-3, 1e-6, 1e-6)), # 1.2 mW, 1uW, 1uW )
def test_excel_ila_constraints(source, destination, route_list, hoptype, expected_correction): """ add different kind of constraints to test all correct_route cases """ service_xls_input = DATA_DIR / 'testTopology.xls' network_json_input = DATA_DIR / 'testTopology_auto_design_expected.json' equipment = load_equipment(eqpt_filename) network = load_network(network_json_input, equipment) # increase length of one span to trigger automatic fiber splitting included by autodesign # so that the test also covers this case next(node for node in network.nodes() if node.uid == 'fiber (Brest_KLA → Quimper)-').length = 200000 next(node for node in network.nodes() if node.uid == 'fiber (Quimper → Brest_KLA)-').length = 200000 default_si = equipment['SI']['default'] p_db = default_si.power_dbm p_total_db = p_db + lin2db( automatic_nch(default_si.f_min, default_si.f_max, default_si.spacing)) build_network(network, equipment, p_db, p_total_db) # create params for a request based on input nodes_list = route_list.split(' | ') if route_list is not None else [] params = { 'request_id': '0', 'source': source, 'bidir': False, 'destination': destination, 'trx_type': '', 'trx_mode': '', 'format': '', 'spacing': '', 'nodes_list': nodes_list, 'loose_list': [hoptype for node in nodes_list] if route_list is not None else '', 'f_min': 0, 'f_max': 0, 'baud_rate': 0, 'OSNR': None, 'bit_rate': None, 'cost': None, 'roll_off': 0, 'tx_osnr': 0, 'min_spacing': None, 'nb_channel': 0, 'power': 0, 'path_bandwidth': 0, } request = PathRequest(**params) if expected_correction != 'Fail': [request] = correct_xls_route_list(service_xls_input, network, [request]) assert request.nodes_list == expected_correction else: with pytest.raises(ServiceError): [request] = correct_xls_route_list(service_xls_input, network, [request])
def test_json_response_generation(xls_input, expected_response_file): """ tests if json response is correctly generated for all combinations of requests """ equipment = load_equipment(eqpt_filename) network = load_network(xls_input, equipment) p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) data = read_service_sheet(xls_input, equipment, network) # change one of the request with bidir option to cover bidir case as well data['path-request'][2]['bidirectional'] = True oms_list = build_oms_list(network, equipment) rqs = requests_from_json(data, equipment) dsjn = disjunctions_from_json(data) dsjn = deduplicate_disjunctions(dsjn) rqs, dsjn = requests_aggregation(rqs, dsjn) pths = compute_path_dsjctn(network, equipment, rqs, dsjn) propagatedpths, reversed_pths, reversed_propagatedpths = \ compute_path_with_disjunction(network, equipment, rqs, pths) pth_assign_spectrum(pths, rqs, oms_list, reversed_pths) result = [] for i, pth in enumerate(propagatedpths): # test ServiceError handling : when M is zero at this point, the # json result should not be created if there is no blocking reason if i == 1: my_rq = deepcopy(rqs[i]) my_rq.M = 0 with pytest.raises(ServiceError): ResultElement(my_rq, pth, reversed_propagatedpths[i]).json my_rq.blocking_reason = 'NO_SPECTRUM' ResultElement(my_rq, pth, reversed_propagatedpths[i]).json result.append(ResultElement(rqs[i], pth, reversed_propagatedpths[i])) temp = {'response': [n.json for n in result]} expected = load_json(expected_response_file) for i, response in enumerate(temp['response']): if i == 2: # compare response must be False because z-a metric is missing # (request with bidir option to cover bidir case) assert not compare_response(expected['response'][i], response) print(f'response {response["response-id"]} should not match') expected['response'][2]['path-properties']['z-a-path-metric'] = [{ 'metric-type': 'SNR-bandwidth', 'accumulative-value': 22.809999999999999 }, { 'metric-type': 'SNR-0.1nm', 'accumulative-value': 26.890000000000001 }, { 'metric-type': 'OSNR-bandwidth', 'accumulative-value': 26.239999999999998 }, { 'metric-type': 'OSNR-0.1nm', 'accumulative-value': 30.32 }, { 'metric-type': 'reference_power', 'accumulative-value': 0.0012589254117941673 }, { 'metric-type': 'path_bandwidth', 'accumulative-value': 60000000000.0 }] # test should be OK now else: assert compare_response(expected['response'][i], response) print(f'response {response["response-id"]} is not correct')
def _gain_profile(self, pin): """ Pin : input power / channel in W :param gain_ripple: design flat gain :param dgt: design gain tilt :param Pin: total input power in W :param gp: Average gain setpoint in dB units :param gtp: gain tilt setting :type gain_ripple: numpy.ndarray :type dgt: numpy.ndarray :type Pin: numpy.ndarray :type gp: float :type gtp: float :return: gain profile in dBm :rtype: numpy.ndarray AMPLIFICATION USING INPUT PROFILE INPUTS: gain_ripple - vector of length number of channels or spectral slices DGT - vector of length number of channels or spectral slices Pin - input powers vector of length number of channels or spectral slices Gp - provisioned gain length 1 GTp - provisioned tilt length 1 OUTPUT: amp gain per channel or spectral slice NOTE: there is no checking done for violations of the total output power capability of the amp. EDIT OF PREVIOUS NOTE: power violation now added in interpol_params Ported from Matlab version written by David Boerges at Ciena. Based on: R. di Muro, "The Er3+ fiber gain coefficient derived from a dynamic gain tilt technique", Journal of Lightwave Technology, Vol. 18, Iss. 3, Pp. 343-347, 2000. """ err_tolerance = 1.0e-11 simple_opt = True # TODO check what param should be used (currently length(dgt)) nchan = np.arange(len(self.interpol_dgt)) # TODO find a way to use these or lose them. Primarily we should have # a way to determine if exceeding the gain or output power of the amp tot_in_power_db = lin2db(np.sum(pin * 1e3)) # ! Pin expressed in W # Linear fit to get the p = np.polyfit(nchan, self.interpol_dgt, 1) dgt_slope = p[0] # Calculate the target slope- Currently assumes equal spaced channels # TODO make it so that supports arbitrary channel spacing. targ_slope = self.operational.tilt_target / (len(nchan) - 1) # 1st estimate of DGT scaling if abs(dgt_slope) > 0.001: # add check for div 0 due to flat dgt dgts1 = targ_slope / dgt_slope else: dgts1 = 0 # when simple_opt is true code makes 2 attempts to compute gain and # the internal voa value. This is currently here to provide direct # comparison with original Matlab code. Will be removed. # TODO replace with loop if simple_opt: # 1st estimate of Er gain & voa loss g1st = np.array(self.interpol_gain_ripple) + self.params.gain_flatmax + \ np.array(self.interpol_dgt) * dgts1 voa = lin2db(np.mean(db2lin(g1st))) - self.operational.gain_target # 2nd estimate of Amp ch gain using the channel input profile g2nd = g1st - voa pout_db = lin2db(np.sum(pin * 1e3 * db2lin(g2nd))) dgts2 = self.operational.gain_target - (pout_db - tot_in_power_db) # Center estimate of amp ch gain xcent = dgts2 gcent = g1st - voa + np.array(self.interpol_dgt) * xcent pout_db = lin2db(np.sum(pin * 1e3 * db2lin(gcent))) gavg_cent = pout_db - tot_in_power_db # Lower estimate of Amp ch gain deltax = np.max(g1st) - np.min(g1st) # ! if no ripple deltax = 0 => xlow = xcent: div 0 # add check for flat gain response : if abs( deltax ) > 0.05: #enough ripple to consider calculation and avoid div 0 xlow = dgts2 - deltax glow = g1st - voa + np.array(self.interpol_dgt) * xlow pout_db = lin2db(np.sum(pin * 1e3 * db2lin(glow))) gavg_low = pout_db - tot_in_power_db # Upper gain estimate xhigh = dgts2 + deltax ghigh = g1st - voa + np.array(self.interpol_dgt) * xhigh pout_db = lin2db(np.sum(pin * 1e3 * db2lin(ghigh))) gavg_high = pout_db - tot_in_power_db # compute slope slope1 = (gavg_low - gavg_cent) / (xlow - xcent) slope2 = (gavg_cent - gavg_high) / (xcent - xhigh) if np.abs(self.operational.gain_target - gavg_cent) <= err_tolerance: dgts3 = xcent elif self.operational.gain_target < gavg_cent: dgts3 = xcent - (gavg_cent - self.operational.gain_target) / slope1 else: dgts3 = xcent + (-gavg_cent + self.operational.gain_target) / slope2 gprofile = g1st - voa + np.array(self.interpol_dgt) * dgts3 else: #not enough ripple gprofile = g1st - voa else: #simple_opt gprofile = None return gprofile
def test_automaticmodefeature(net, eqpt, serv, expected_mode): equipment = load_equipment(eqpt) network = load_network(net, equipment) data = load_requests(serv, eqpt, bidir=False, network=network, network_filename=net) # Build the network once using the default power defined in SI in eqpt config # power density : db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by # spacing, f_min and f_max p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, p_db, p_total_db) rqs = requests_from_json(data, equipment) rqs = correct_json_route_list(network, rqs) dsjn = [] pths = compute_path_dsjctn(network, equipment, rqs, dsjn) path_res_list = [] for i, pathreq in enumerate(rqs): # use the power specified in requests but might be different from the one specified for design # the power is an optional parameter for requests definition # if optional, use the one defines in eqt_config.json p_db = lin2db(pathreq.power * 1e3) p_total_db = p_db + lin2db(pathreq.nb_channel) print(f'request {pathreq.request_id}') print(f'Computing path from {pathreq.source} to {pathreq.destination}') # adding first node to be clearer on the output print(f'with path constraint: {[pathreq.source]+pathreq.nodes_list}') total_path = pths[i] print( f'Computed path (roadms):{[e.uid for e in total_path if isinstance(e, Roadm)]}\n' ) # for debug # print(f'{pathreq.baud_rate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}') if pathreq.baud_rate is not None: print(pathreq.format) path_res_list.append(pathreq.format) total_path = propagate(total_path, pathreq, equipment) else: total_path, mode = propagate_and_optimize_mode( total_path, pathreq, equipment) # if no baudrate satisfies spacing, no mode is returned and an empty path is returned # a warning is shown in the propagate_and_optimize_mode if mode is not None: print(mode['format']) path_res_list.append(mode['format']) else: print('nok') path_res_list.append('nok') print(path_res_list) assert path_res_list == expected_mode
def jsontocsv(json_data, equipment, fileout): # read json path result file in accordance with: # Yang model for requesting Path Computation # draft-ietf-teas-yang-path-computation-01.txt. # and write results in an CSV file mywriter = writer(fileout) mywriter.writerow(('response-id','source','destination','path_bandwidth','Pass?',\ 'nb of tsp pairs','total cost','transponder-type','transponder-mode',\ 'OSNR-0.1nm','SNR-0.1nm','SNR-bandwidth','baud rate (Gbaud)',\ 'input power (dBm)','path')) tspjsondata = equipment['Transceiver'] #print(tspjsondata) for pth_el in json_data['response']: path_id = pth_el['response-id'] try: if pth_el['no-path']: source = '' destination = '' tsp = '' mode = '' isok = False nb_tsp = 0 pthbdbw = '' rosnr = '' rsnr = '' rsnrb = '' br = '' pw = '' total_cost = '' pth = '' except KeyError: source = pth_el['path-properties']['path-route-objects'][0]\ ['path-route-object']['num-unnum-hop']['node-id'] destination = pth_el['path-properties']['path-route-objects'][-2]\ ['path-route-object']['num-unnum-hop']['node-id'] # selects only roadm nodes temp = [] for e in pth_el['path-properties']['path-route-objects']: try: temp.append( e['path-route-object']['num-unnum-hop']['node-id']) except KeyError: pass pth = ' | '.join(temp) temp_tsp = pth_el['path-properties']['path-route-objects'][1]\ ['path-route-object']['transponder'] tsp = temp_tsp['transponder-type'] mode = temp_tsp['transponder-mode'] # find the min acceptable OSNR, baud rate from the eqpt library based on tsp (tupe) and mode (format) # loading equipment already tests the existence of tsp type and mode: if mode != 'not feasible with this transponder': [minosnr, baud_rate, bit_rate, cost] = next( [m['OSNR'], m['baud_rate'], m['bit_rate'], m['cost']] for m in equipment['Transceiver'][tsp].mode if m['format'] == mode) # else: # [minosnr, baud_rate, bit_rate] = ['','','',''] output_snr = next(e['accumulative-value'] for e in pth_el['path-properties']['path-metric'] if e['metric-type'] == 'SNR-0.1nm') output_snrbandwidth = next( e['accumulative-value'] for e in pth_el['path-properties']['path-metric'] if e['metric-type'] == 'SNR-bandwidth') output_osnr = next( e['accumulative-value'] for e in pth_el['path-properties']['path-metric'] if e['metric-type'] == 'OSNR-0.1nm') output_osnrbandwidth = next( e['accumulative-value'] for e in pth_el['path-properties']['path-metric'] if e['metric-type'] == 'OSNR-bandwidth') power = next(e['accumulative-value'] for e in pth_el['path-properties']['path-metric'] if e['metric-type'] == 'reference_power') path_bandwidth = next( e['accumulative-value'] for e in pth_el['path-properties']['path-metric'] if e['metric-type'] == 'path_bandwidth') if isinstance(output_snr, str): isok = False nb_tsp = 0 pthbdbw = round(path_bandwidth * 1e-9, 2) rosnr = '' rsnr = '' rsnrb = '' br = '' pw = '' total_cost = '' else: isok = output_snr >= minosnr nb_tsp = ceil(path_bandwidth / bit_rate) pthbdbw = round(path_bandwidth * 1e-9, 2) rosnr = round(output_osnr, 2) rsnr = round(output_snr, 2) rsnrb = round(output_snrbandwidth, 2) br = round(baud_rate * 1e-9, 2) pw = round(lin2db(power) + 30, 2) total_cost = nb_tsp * cost mywriter.writerow( (path_id, source, destination, pthbdbw, isok, nb_tsp, total_cost, tsp, mode, rosnr, rsnr, rsnrb, br, pw, pth))
def pathresult(self): if not self.computed_path: return { 'response-id': self.path_id, 'no-path': "Response without path information, due to failure performing the path computation" } else: index = 0 pro_list = [] for element in self.computed_path: temp = { 'path-route-object': { 'index': index, 'num-unnum-hop': { 'node-id': element.uid, 'link-tp-id': element.uid, # TODO change index in order to insert transponder attribute } } } pro_list.append(temp) index += 1 if isinstance(element, Transceiver): temp = { 'path-route-object': { 'index': index, 'transponder': { 'transponder-type': self.path_request.tsp, 'transponder-mode': self.path_request.tsp_mode, } } } pro_list.append(temp) index += 1 response = { 'response-id': self.path_id, 'path-properties':{ 'path-metric': [ { 'metric-type': 'SNR-bandwidth', 'accumulative-value': round(mean(self.computed_path[-1].snr), 2) }, { 'metric-type': 'SNR-0.1nm', 'accumulative-value': round(mean(self.computed_path[-1]. snr + \ lin2db(self.path_request.baud_rate/12.5e9)), 2) }, { 'metric-type': 'OSNR-bandwidth', 'accumulative-value': round(mean(self.computed_path[-1].osnr_ase), 2) }, { 'metric-type': 'OSNR-0.1nm', 'accumulative-value': round(mean(self.computed_path[-1].osnr_ase_01nm), 2) }, { 'metric-type': 'reference_power', 'accumulative-value': self.path_request.power }, { 'metric-type': 'path_bandwidth', 'accumulative-value': self.path_request.path_bandwidth } ], 'path-route-objects': pro_list } } return response
def update_pref(self, pref, *carriers): pch_out_db = lin2db( mean([carrier.power.signal for carrier in carriers])) + 30 self.pch_out_db = round(pch_out_db, 2) return pref._replace(p_span0=pref.p_span0, p_spani=self.pch_out_db)
def path_requests_run(args=None): parser = argparse.ArgumentParser( description= 'Compute performance for a list of services provided in a json file or an excel sheet', epilog=_help_footer, formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) _add_common_options(parser, network_default=_examples_dir / 'meshTopologyExampleV2.xls') parser.add_argument('service_filename', nargs='?', type=Path, metavar='SERVICES-REQUESTS.(json|xls|xlsx)', default=_examples_dir / 'meshTopologyExampleV2.xls', help='Input service file') parser.add_argument('-bi', '--bidir', action='store_true', help='considers that all demands are bidir') parser.add_argument( '-o', '--output', type=Path, metavar=_help_fname_json_csv, help='Store satisifed requests into a JSON or CSV file') args = parser.parse_args(args if args is not None else sys.argv[1:]) _setup_logging(args) _logger.info( f'Computing path requests {args.service_filename} into JSON format') print( f'{ansi_escapes.blue}Computing path requests {os.path.relpath(args.service_filename)} into JSON format{ansi_escapes.reset}' ) (equipment, network) = load_common_data(args.equipment, args.topology, args.sim_params, args.save_network_before_autodesign) # Build the network once using the default power defined in SI in eqpt config # TODO power density: db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by # spacing, f_min and f_max p_db = equipment['SI']['default'].power_dbm p_total_db = p_db + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) try: build_network(network, equipment, p_db, p_total_db) except exceptions.NetworkTopologyError as e: print( f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}' ) sys.exit(1) except exceptions.ConfigurationError as e: print( f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') sys.exit(1) if args.save_network is not None: save_network(network, args.save_network) print( f'{ansi_escapes.blue}Network (after autodesign) saved to {args.save_network}{ansi_escapes.reset}' ) oms_list = build_oms_list(network, equipment) try: data = load_requests(args.service_filename, equipment, bidir=args.bidir, network=network, network_filename=args.topology) rqs = requests_from_json(data, equipment) except exceptions.ServiceError as e: print(f'{ansi_escapes.red}Service error:{ansi_escapes.reset} {e}') sys.exit(1) # check that request ids are unique. Non unique ids, may # mess the computation: better to stop the computation all_ids = [r.request_id for r in rqs] if len(all_ids) != len(set(all_ids)): for item in list(set(all_ids)): all_ids.remove(item) msg = f'Requests id {all_ids} are not unique' _logger.critical(msg) sys.exit() rqs = correct_json_route_list(network, rqs) # pths = compute_path(network, equipment, rqs) dsjn = disjunctions_from_json(data) print(f'{ansi_escapes.blue}List of disjunctions{ansi_escapes.reset}') print(dsjn) # need to warn or correct in case of wrong disjunction form # disjunction must not be repeated with same or different ids dsjn = deduplicate_disjunctions(dsjn) # Aggregate demands with same exact constraints print( f'{ansi_escapes.blue}Aggregating similar requests{ansi_escapes.reset}') rqs, dsjn = requests_aggregation(rqs, dsjn) # TODO export novel set of aggregated demands in a json file print( f'{ansi_escapes.blue}The following services have been requested:{ansi_escapes.reset}' ) print(rqs) print( f'{ansi_escapes.blue}Computing all paths with constraints{ansi_escapes.reset}' ) try: pths = compute_path_dsjctn(network, equipment, rqs, dsjn) except exceptions.DisjunctionError as this_e: print( f'{ansi_escapes.red}Disjunction error:{ansi_escapes.reset} {this_e}' ) sys.exit(1) print( f'{ansi_escapes.blue}Propagating on selected path{ansi_escapes.reset}') propagatedpths, reversed_pths, reversed_propagatedpths = compute_path_with_disjunction( network, equipment, rqs, pths) # Note that deepcopy used in compute_path_with_disjunction returns # a list of nodes which are not belonging to network (they are copies of the node objects). # so there can not be propagation on these nodes. pth_assign_spectrum(pths, rqs, oms_list, reversed_pths) print(f'{ansi_escapes.blue}Result summary{ansi_escapes.reset}') header = [ 'req id', ' demand', ' snr@bandwidth A-Z (Z-A)', ' [email protected] A-Z (Z-A)', ' Receiver minOSNR', ' mode', ' Gbit/s', ' nb of tsp pairs', 'N,M or blocking reason' ] data = [] data.append(header) for i, this_p in enumerate(propagatedpths): rev_pth = reversed_propagatedpths[i] if rev_pth and this_p: psnrb = f'{round(mean(this_p[-1].snr),2)} ({round(mean(rev_pth[-1].snr),2)})' psnr = f'{round(mean(this_p[-1].snr_01nm), 2)}' +\ f' ({round(mean(rev_pth[-1].snr_01nm),2)})' elif this_p: psnrb = f'{round(mean(this_p[-1].snr),2)}' psnr = f'{round(mean(this_p[-1].snr_01nm),2)}' try: if rqs[i].blocking_reason in BLOCKING_NOPATH: line = [ f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} :', f'-', f'-', f'-', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9,2)}', f'-', f'{rqs[i].blocking_reason}' ] else: line = [ f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} : ', psnrb, psnr, f'-', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9, 2)}', f'-', f'{rqs[i].blocking_reason}' ] except AttributeError: line = [ f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} : ', psnrb, psnr, f'{rqs[i].OSNR + equipment["SI"]["default"].sys_margins}', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9,2)}', f'{ceil(rqs[i].path_bandwidth / rqs[i].bit_rate) }', f'({rqs[i].N},{rqs[i].M})' ] data.append(line) col_width = max(len(word) for row in data for word in row[2:]) # padding firstcol_width = max(len(row[0]) for row in data) # padding secondcol_width = max(len(row[1]) for row in data) # padding for row in data: firstcol = ''.join(row[0].ljust(firstcol_width)) secondcol = ''.join(row[1].ljust(secondcol_width)) remainingcols = ''.join( word.center(col_width, ' ') for word in row[2:]) print(f'{firstcol} {secondcol} {remainingcols}') print( f'{ansi_escapes.yellow}Result summary shows mean SNR and OSNR (average over all channels){ansi_escapes.reset}' ) if args.output: result = [] # assumes that list of rqs and list of propgatedpths have same order for i, pth in enumerate(propagatedpths): result.append( ResultElement(rqs[i], pth, reversed_propagatedpths[i])) temp = _path_result_json(result) if args.output.suffix.lower() == '.json': save_json(temp, args.output) print( f'{ansi_escapes.blue}Saved JSON to {args.output}{ansi_escapes.reset}' ) elif args.output.suffix.lower() == '.csv': with open(args.output, "w", encoding='utf-8') as fcsv: jsontocsv(temp, equipment, fcsv) print( f'{ansi_escapes.blue}Saved CSV to {args.output}{ansi_escapes.reset}' ) else: print( f'{ansi_escapes.red}Cannot save output: neither JSON nor CSV file{ansi_escapes.reset}' ) sys.exit(1)
def transmission_main_example(args=None): parser = argparse.ArgumentParser( description= 'Send a full spectrum load through the network from point A to point B', epilog=_help_footer, formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) _add_common_options(parser, network_default=_examples_dir / 'edfa_example_network.json') parser.add_argument('--show-channels', action='store_true', help='Show final per-channel OSNR summary') parser.add_argument('-pl', '--plot', action='store_true') parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes') parser.add_argument('-po', '--power', default=0, help='channel ref power in dBm') parser.add_argument('source', nargs='?', help='source node') parser.add_argument('destination', nargs='?', help='destination node') args = parser.parse_args(args if args is not None else sys.argv[1:]) _setup_logging(args) (equipment, network) = load_common_data(args.equipment, args.topology, args.sim_params, args.save_network_before_autodesign) if args.plot: plot_baseline(network) transceivers = { n.uid: n for n in network.nodes() if isinstance(n, Transceiver) } if not transceivers: sys.exit('Network has no transceivers!') if len(transceivers) < 2: sys.exit('Network has only one transceiver!') if args.list_nodes: for uid in transceivers: print(uid) sys.exit() # First try to find exact match if source/destination provided if args.source: source = transceivers.pop(args.source, None) valid_source = True if source else False else: source = None _logger.info('No source node specified: picking random transceiver') if args.destination: destination = transceivers.pop(args.destination, None) valid_destination = True if destination else False else: destination = None _logger.info( 'No destination node specified: picking random transceiver') # If no exact match try to find partial match if args.source and not source: # TODO code a more advanced regex to find nodes match source = next( (transceivers.pop(uid) for uid in transceivers if args.source.lower() in uid.lower()), None) if args.destination and not destination: # TODO code a more advanced regex to find nodes match destination = next((transceivers.pop(uid) for uid in transceivers if args.destination.lower() in uid.lower()), None) # If no partial match or no source/destination provided pick random if not source: source = list(transceivers.values())[0] del transceivers[source.uid] if not destination: destination = list(transceivers.values())[0] _logger.info(f'source = {args.source!r}') _logger.info(f'destination = {args.destination!r}') params = {} params['request_id'] = 0 params['trx_type'] = '' params['trx_mode'] = '' params['source'] = source.uid params['destination'] = destination.uid params['bidir'] = False params['nodes_list'] = [destination.uid] params['loose_list'] = ['strict'] params['format'] = '' params['path_bandwidth'] = 0 trx_params = trx_mode_params(equipment) # Randomly generate input power input_power = round(random.random(), 2) input_power = -2 + (input_power * (8)) args.power = input_power if args.power: trx_params['power'] = db2lin(float(args.power)) * 1e-3 params.update(trx_params) req = PathRequest(**params) power_mode = equipment['Span']['default'].power_mode print('\n'.join([ f'Power mode is set to {power_mode}', f'=> it can be modified in eqpt_config.json - Span' ])) pref_ch_db = lin2db(req.power * 1e3) # reference channel power / span (SL=20dB) pref_total_db = pref_ch_db + lin2db( req.nb_channel) # reference total power / span (SL=20dB) try: build_network(network, equipment, pref_ch_db, pref_total_db) except exceptions.NetworkTopologyError as e: print( f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}' ) sys.exit(1) except exceptions.ConfigurationError as e: print( f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') sys.exit(1) path = compute_constrained_path(network, req) spans = [ s.params.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber) ] print( f'\nThere are {len(spans)} fiber spans over {sum(spans)/1000:.0f} km between {source.uid} ' f'and {destination.uid}') print(f'\nNow propagating between {source.uid} and {destination.uid}:') try: p_start, p_stop, p_step = equipment['SI']['default'].power_range_db p_num = abs(int(round( (p_stop - p_start) / p_step))) + 1 if p_step != 0 else 1 power_range = list(linspace(p_start, p_stop, p_num)) except TypeError: print( 'invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]' ) power_range = [0] if not power_mode: # power cannot be changed in gain mode power_range = [0] for dp_db in power_range: req.power = db2lin(pref_ch_db + dp_db) * 1e-3 if power_mode: print( f'\nPropagating with input power = {ansi_escapes.cyan}{lin2db(req.power*1e3):.2f} dBm{ansi_escapes.reset}:' ) else: print( f'\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually' ) infos = propagate(path, req, equipment) if len(power_range) == 1: for elem in path: print(elem) if power_mode: print( f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f} dBm:' ) else: print(f'\nTransmission results:') # print('-------------') # print(destination.snr_01nm) # print('-------------') print( f' Final SNR total (0.1 nm): {ansi_escapes.cyan}{mean(destination.snr_01nm):.02f} dB{ansi_escapes.reset}' ) else: print(path[-1]) if args.save_network is not None: save_network(network, args.save_network) print( f'{ansi_escapes.blue}Network (after autodesign) saved to {args.save_network}{ansi_escapes.reset}' ) if args.show_channels: print('\nThe total SNR per channel at the end of the line is:') print('{:>5}{:>26}{:>26}{:>28}{:>28}{:>28}'.format( 'Ch. #', 'Channel frequency (THz)', 'Channel power (dBm)', 'OSNR ASE (signal bw, dB)', 'SNR NLI (signal bw, dB)', 'SNR total (signal bw, dB)')) # print(dir(info)) for final_carrier, ch_osnr, ch_snr_nl, ch_snr in zip( infos.carriers, path[-1].osnr_ase, path[-1].osnr_nli, path[-1].snr): ch_freq = final_carrier.frequency * 1e-12 ch_power = lin2db(final_carrier.power.signal * 1e3) print('{:5}{:26.2f}{:26.2f}{:28.2f}{:28.2f}{:28.2f}'.format( final_carrier.channel_number, round(ch_freq, 2), round(ch_power, 2), round(ch_osnr, 2), round(ch_snr_nl, 2), round(ch_snr, 2))) if not args.source: print(f'\n(No source node specified: picked {source.uid})') elif not valid_source: print( f'\n(Invalid source node {args.source!r} replaced with {source.uid})' ) if not args.destination: print(f'\n(No destination node specified: picked {destination.uid})') elif not valid_destination: print( f'\n(Invalid destination node {args.destination!r} replaced with {destination.uid})' ) if args.plot: plot_results(network, path, source, destination) # MY ADDITION # just to see what the different contributions of ASE and NLI are # return input_power, path[-1].osnr_ase, path[-1].osnr_nli, path[-1].snr # to test Raman return input_power, destination.snr_01nm, mean(destination.snr_01nm)