def requests_from_json(json_data, equipment): """Extract list of requests from data parsed from JSON""" requests_list = [] for req in json_data['path-request']: # init all params from request params = {} params['request_id'] = req['request-id'] params['source'] = req['source'] params['bidir'] = req['bidirectional'] params['destination'] = req['destination'] params['trx_type'] = req['path-constraints']['te-bandwidth']['trx_type'] params['trx_mode'] = req['path-constraints']['te-bandwidth']['trx_mode'] params['format'] = params['trx_mode'] params['spacing'] = req['path-constraints']['te-bandwidth']['spacing'] try: nd_list = req['explicit-route-objects']['route-object-include-exclude'] except KeyError: nd_list = [] params['nodes_list'] = [n['num-unnum-hop']['node-id'] for n in nd_list] params['loose_list'] = [n['num-unnum-hop']['hop-type'] for n in nd_list] # recover trx physical param (baudrate, ...) from type and mode # in trx_mode_params optical power is read from equipment['SI']['default'] and # nb_channel is computed based on min max frequency and spacing trx_params = trx_mode_params(equipment, params['trx_type'], params['trx_mode'], True) params.update(trx_params) # print(trx_params['min_spacing']) # optical power might be set differently in the request. if it is indicated then the # params['power'] is updated try: if req['path-constraints']['te-bandwidth']['output-power']: params['power'] = req['path-constraints']['te-bandwidth']['output-power'] except KeyError: pass # same process for nb-channel f_min = params['f_min'] f_max_from_si = params['f_max'] try: if req['path-constraints']['te-bandwidth']['max-nb-of-channel'] is not None: nch = req['path-constraints']['te-bandwidth']['max-nb-of-channel'] params['nb_channel'] = nch spacing = params['spacing'] params['f_max'] = automatic_fmax(f_min, spacing, nch) else: params['nb_channel'] = automatic_nch(f_min, f_max_from_si, params['spacing']) except KeyError: params['nb_channel'] = automatic_nch(f_min, f_max_from_si, params['spacing']) _check_one_request(params, f_max_from_si) try: params['path_bandwidth'] = req['path-constraints']['te-bandwidth']['path_bandwidth'] except KeyError: pass requests_list.append(PathRequest(**params)) return requests_list
def create_rq(equipment, srce, dest, bdir, node_list, loose_list, rqid='test_request'): ''' create the usual request list according to parameters ''' requests_list = [] params = { 'request_id': rqid, 'source': srce, 'bidir': bdir, 'destination': dest, 'trx_type': 'Voyager', 'trx_mode': 'mode 1', 'spacing': 50000000000.0, 'nodes_list': node_list, 'loose_list': loose_list, 'path_bandwidth': 100.0e9, 'power': 1.0, 'effective_freq_slot': None, } params['format'] = params['trx_mode'] trx_params = trx_mode_params(equipment, params['trx_type'], params['trx_mode'], True) params.update(trx_params) f_min = params['f_min'] f_max_from_si = params['f_max'] params['nb_channel'] = automatic_nch(f_min, f_max_from_si, params['spacing']) requests_list.append(PathRequest(**params)) return requests_list
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') expected = load_json(expected_json_output) results = compare_services(expected, from_xls) assert not results.requests.missing assert not results.requests.extra assert not results.requests.different assert not results.synchronizations.missing assert not results.synchronizations.extra assert not results.synchronizations.different
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) expected = load_json(expected_json_output) results = compare_networks(expected, actual) assert not results.elements.missing assert not results.elements.extra assert not results.elements.different assert not results.connections.missing assert not results.connections.extra assert not results.connections.different
def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=False): """return the trx and SI parameters from eqpt_config for a given type_variety and mode (ie format)""" trx_params = {} default_si_data = equipment['SI']['default'] try: trxs = equipment['Transceiver'] # if called from path_requests_run.py, trx_mode is filled with None when not specified by user # if called from transmission_main.py, trx_mode is '' if trx_mode is not None: mode_params = next(mode for trx in trxs if trx == trx_type_variety for mode in trxs[trx].mode if mode['format'] == trx_mode) trx_params = {**mode_params} # sanity check: spacing baudrate must be smaller than min spacing if trx_params['baud_rate'] > trx_params['min_spacing']: raise EquipmentConfigError(f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" ' + f'has baud rate {trx_params["baud_rate"]*1e-9} GHz greater than min_spacing {trx_params["min_spacing"]*1e-9}.') else: mode_params = {"format": "undetermined", "baud_rate": None, "OSNR": None, "bit_rate": None, "roll_off": None, "tx_osnr": None, "min_spacing": None, "cost": None} trx_params = {**mode_params} trx_params['f_min'] = equipment['Transceiver'][trx_type_variety].frequency['min'] trx_params['f_max'] = equipment['Transceiver'][trx_type_variety].frequency['max'] # TODO: novel automatic feature maybe unwanted if spacing is specified # trx_params['spacing'] = _automatic_spacing(trx_params['baud_rate']) # temp = trx_params['spacing'] # print(f'spacing {temp}') except StopIteration: if error_message: raise EquipmentConfigError(f'Could not find transponder "{trx_type_variety}" with mode "{trx_mode}" in equipment library') else: # default transponder charcteristics # mainly used with transmission_main_example.py trx_params['f_min'] = default_si_data.f_min trx_params['f_max'] = default_si_data.f_max trx_params['baud_rate'] = default_si_data.baud_rate trx_params['spacing'] = default_si_data.spacing trx_params['OSNR'] = None trx_params['bit_rate'] = None trx_params['cost'] = None trx_params['roll_off'] = default_si_data.roll_off trx_params['tx_osnr'] = default_si_data.tx_osnr trx_params['min_spacing'] = None nch = automatic_nch(trx_params['f_min'], trx_params['f_max'], trx_params['spacing']) trx_params['nb_channel'] = nch print(f'There are {nch} channels propagating') trx_params['power'] = db2lin(default_si_data.power_dbm) * 1e-3 return trx_params
def propagation(input_power, network, sim_path, initial_slot, num_slots, eqpt): """ Calculate and output SNR based on inputs input_power: Power in decibels network: Network created from GNPy topology sim_path: list of nodes service will travel through num_slots: number of slots in service eqpt: equipment library for GNPy """ # Values to create Spectral Information object spacing = 12.5e9 min_freq = 195942783006536 + initial_slot * spacing max_freq = min_freq + (num_slots - 1) * spacing p = input_power p = db2lin(p) * 1e-3 si = create_input_spectral_information(min_freq, max_freq, 0.15, 32e9, p, spacing) p_total_db = input_power + lin2db( automatic_nch(min_freq, max_freq, spacing)) build_network(network, eqpt, input_power, p_total_db) # Store network elements transceivers = { n.uid: n for n in network.nodes() if isinstance(n, Transceiver) } fibers = {n.uid: n for n in network.nodes() if isinstance(n, Fiber)} edfas = {n.uid: n for n in network.nodes() if isinstance(n, Edfa)} # Recreate path in the GNPy network using node list from simulator path = [] for index, node in enumerate(sim_path): # add transceiver to path path.append(transceivers[node]) # add fiber connecting transceivers to path, unless source transceiver is last in path if index + 1 < len(sim_path): fiber_str = f"Fiber ({node} \u2192 {sim_path[index+1]})" for uid in fibers: # add all fibers to path even if they are split up if uid[0:len(fiber_str)] == fiber_str: path.append(fibers[uid]) # add amplifier to path, if necessary edfa = f"Edfa0_{uid}" fiber_neighbors = [ n.uid for n in neighbors(network, fibers[uid]) ] if edfa in edfas and edfa in fiber_neighbors: path.append(edfas[edfa]) # Calculate effects of physical layer impairments for el in path: si = el(si) destination_node = path[-1] return destination_node.snr
def setup(equipment): """ common setup for tests: builds network, equipment and oms only once """ network = load_network(NETWORK_FILENAME, equipment) spectrum = equipment['SI']['default'] p_db = spectrum.power_dbm p_total_db = p_db + lin2db(automatic_nch(spectrum.f_min, spectrum.f_max, spectrum.spacing)) build_network(network, equipment, p_db, p_total_db) oms_list = build_oms_list(network, equipment) return network, oms_list
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing): """ Creates a fixed slot width spectral information with flat power """ nb_channel = automatic_nch(f_min, f_max, spacing) frequency = [(f_min + spacing * i) for i in range(1, nb_channel + 1)] return create_arbitrary_spectral_information(frequency, slot_width=spacing, signal=power, baud_rate=baud_rate, roll_off=roll_off)
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), 0, 0) for f in range(1, nb_channel + 1) ]) return si
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_target_pch_out_db_global(case): """ check that per degree attributes are correctly created with global values if none are given """ json_network = setup_per_degree(case) per_degree = {} for elem in json_network['elements']: if 'type' in elem.keys() and elem['type'] == 'Roadm' and 'params' in elem.keys() \ and 'per_degree_pch_out_db' in elem['params']: # records roadms that have a per degree target per_degree[elem['uid']] = { k: v for k, v in elem['params']['per_degree_pch_out_db'].items() } 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) data = network_to_json(network) for elem in data['elements']: if 'type' in elem.keys() and elem['type'] == 'Roadm': # check that power target attributes exist and are filled with correct values # first check that global 'target_pch_out_db' is correctly filled assert elem['params']['target_pch_out_db'] == equipment['Roadm'][ 'default'].target_pch_out_db for degree, power in elem['params']['per_degree_pch_out_db'].items( ): if elem['uid'] not in per_degree.keys(): # second: check that per degree 'target_pch_out_db' is correctly filled with global value # when there was no per degree specification on network input assert power == equipment['Roadm'][ 'default'].target_pch_out_db else: if degree not in per_degree[elem['uid']].keys(): # third: check that per degree 'target_pch_out_db' is correctly filled with global value # on degrees that had no specification when other degrees are filled assert power == equipment['Roadm'][ 'default'].target_pch_out_db else: # fourth: check that per degree 'target_pch_out_db' is correctly filled with specified values assert power == per_degree[elem['uid']][degree]
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 _check_one_request(params, f_max_from_si): """Checks that the requested parameters are consistant (spacing vs nb channel vs transponder mode...)""" f_min = params['f_min'] f_max = params['f_max'] max_recommanded_nb_channels = automatic_nch(f_min, f_max, params['spacing']) if params['baud_rate'] is not None: # implicitly means that a mode is defined with min_spacing if params['min_spacing'] > params['spacing']: msg = f'Request {params["request_id"]} has spacing below transponder ' +\ f'{params["trx_type"]} {params["trx_mode"]} min spacing value ' +\ f'{params["min_spacing"]*1e-9}GHz.\nComputation stopped' print(msg) _logger.critical(msg) raise ServiceError(msg) if f_max > f_max_from_si: msg = f'''Requested channel number {params["nb_channel"]}, baud rate {params["baud_rate"]} GHz and requested spacing {params["spacing"]*1e-9}GHz is not consistent with frequency range {f_min*1e-12} THz, {f_max*1e-12} THz, min recommanded spacing {params["min_spacing"]*1e-9}GHz. max recommanded nb of channels is {max_recommanded_nb_channels}.''' _logger.critical(msg) raise ServiceError(msg)
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 _check_one_request(params, f_max_from_si): """Checks that the requested parameters are consistant (spacing vs nb channel vs transponder mode...)""" f_min = params['f_min'] f_max = params['f_max'] max_recommanded_nb_channels = automatic_nch(f_min, f_max, params['spacing']) if params['baud_rate'] is not None: # implicitly means that a mode is defined with min_spacing if params['min_spacing'] > params['spacing']: msg = f'Request {params["request_id"]} has spacing below transponder ' +\ f'{params["trx_type"]} {params["trx_mode"]} min spacing value ' +\ f'{params["min_spacing"]*1e-9}GHz.\nComputation stopped' print(msg) _logger.critical(msg) raise ServiceError(msg) if f_max > f_max_from_si: msg = f'''Requested channel number {params["nb_channel"]}, baud rate {params["baud_rate"]} GHz and requested spacing {params["spacing"]*1e-9}GHz is not consistent with frequency range {f_min*1e-12} THz, {f_max*1e-12} THz, min recommanded spacing {params["min_spacing"]*1e-9}GHz. max recommanded nb of channels is {max_recommanded_nb_channels}.''' _logger.critical(msg) raise ServiceError(msg) # Transponder mode already selected; will it fit to the requested bandwidth? if params['trx_mode'] is not None and params['effective_freq_slot'] is not None \ and params['effective_freq_slot']['M'] is not None: _, requested_m = compute_spectrum_slot_vs_bandwidth( params['path_bandwidth'], params['spacing'], params['bit_rate']) # params['effective_freq_slot']['M'] value should be bigger than the computed requested_m (simple estimate) # TODO: elaborate a more accurate estimate with nb_wl * tx_osnr + possibly guardbands in case of # superchannel closed packing. if requested_m > params['effective_freq_slot']['M']: msg = f'requested M {params["effective_freq_slot"]["M"]} number of slots for request' +\ f'{params["request_id"]} should be greater than {requested_m} to support request' +\ f'{params["path_bandwidth"] * 1e-9} Gbit/s with {params["trx_type"]} {params["trx_mode"]}' _logger.critical(msg) raise ServiceError(msg)
def create_rq(equipment, srce, dest, bdir, nd_list, ls_list): """ create the usual request list according to parameters """ requests_list = [] params = {} params['request_id'] = 'test_request' params['source'] = srce params['bidir'] = bdir params['destination'] = dest params['trx_type'] = 'Voyager' params['trx_mode'] = 'mode 1' params['format'] = params['trx_mode'] params['spacing'] = 50000000000.0 params['nodes_list'] = nd_list params['loose_list'] = ls_list trx_params = trx_mode_params(equipment, params['trx_type'], params['trx_mode'], True) params.update(trx_params) params['power'] = 1.0 f_min = params['f_min'] f_max_from_si = params['f_max'] params['nb_channel'] = automatic_nch(f_min, f_max_from_si, params['spacing']) params['path_bandwidth'] = 100000000000.0 requests_list.append(PathRequest(**params)) return requests_list
def test_roadm_target_power(prev_node_type, effective_pch_out_db, power_dbm): ''' Check that egress power of roadm is equal to target power if input power is greater than target power else, that it is equal to input power. Use a simple two hops A-B-C topology for the test where the prev_node in ROADM B is either an amplifier or a fused, so that the target power can not be met in this last case. ''' equipment = load_equipment(EQPT_LIBRARY_NAME) json_network = load_json(TEST_DIR / 'data/twohops_roadm_power_test.json') prev_node = next(n for n in json_network['elements'] if n['uid'] == 'west edfa in node B to ila2') json_network['elements'].remove(prev_node) if prev_node_type == 'edfa': prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Edfa'} elif prev_node_type == 'fused': prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Fused'} prev_node['params'] = {'loss': 0} json_network['elements'].append(prev_node) network = network_from_json(json_network, equipment) p_total_db = power_dbm + lin2db( automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max, equipment['SI']['default'].spacing)) build_network(network, equipment, power_dbm, p_total_db) params = { 'request_id': 0, 'trx_type': '', 'trx_mode': '', 'source': 'trx node A', 'destination': 'trx node C', 'bidir': False, 'nodes_list': ['trx node C'], 'loose_list': ['strict'], 'format': '', 'path_bandwidth': 100e9, 'effective_freq_slot': None, } trx_params = trx_mode_params(equipment) params.update(trx_params) req = PathRequest(**params) req.power = db2lin(power_dbm - 30) path = compute_constrained_path(network, req) si = create_input_spectral_information(req.f_min, req.f_max, req.roll_off, req.baud_rate, req.power, req.spacing) for i, el in enumerate(path): if isinstance(el, Roadm): carriers_power_in_roadm = min([ c.power.signal + c.power.nli + c.power.ase for c in si.carriers ]) si = el(si, degree=path[i + 1].uid) if el.uid == 'roadm node B': print('input', carriers_power_in_roadm) # if previous was an EDFA, power level at ROADM input is enough for the ROADM to apply its # target power (as specified in equipment ie -20 dBm) # if it is a Fused, the input power to the ROADM is smaller than the target power, and the # ROADM cannot apply this target. In this case, it is assumed that the ROADM has 0 dB loss # so the output power will be the same as the input power, which for this particular case # corresponds to -22dBm + power_dbm # next step (for ROADM modelling) will be to apply a minimum loss for ROADMs ! if prev_node_type == 'edfa': assert el.effective_pch_out_db == effective_pch_out_db if prev_node_type == 'fused': # then output power == input_power == effective_pch_out_db + power_dbm assert effective_pch_out_db + power_dbm == \ pytest.approx(lin2db(carriers_power_in_roadm * 1e3), rel=1e-3) assert el.effective_pch_out_db == effective_pch_out_db + power_dbm for carrier in si.carriers: print(carrier.power.signal + carrier.power.nli + carrier.power.ase) power = carrier.power.signal + carrier.power.nli + carrier.power.ase if prev_node_type == 'edfa': # edfa prev_node sets input power to roadm to a high enough value: # Check that egress power of roadm is equal to target power assert power == pytest.approx( db2lin(effective_pch_out_db - 30), rel=1e-3) elif prev_node_type == 'fused': # fused prev_node does reamplfy power after fiber propagation, so input power # to roadm is low. # Check that egress power of roadm is equalized to the min carrier input power. assert power == pytest.approx(carriers_power_in_roadm, rel=1e-3) else: si = el(si)
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 test_restrictions(restrictions, equipment): ''' test that restriction is correctly applied if provided in eqpt_config and if no Edfa type were provided in the network json ''' # add restrictions equipment['Roadm']['default'].restrictions = restrictions # build network json_network = load_json(NETWORK_FILE_NAME) network = network_from_json(json_network, equipment) amp_nodes_nobuild_uid = [ nd.uid for nd in network.nodes() if isinstance(nd, Edfa) and isinstance(next(network.predecessors(nd)), Roadm) ] preamp_nodes_nobuild_uid = [ nd.uid for nd in network.nodes() if isinstance(nd, Edfa) and isinstance(next(network.successors(nd)), Roadm) ] amp_nodes_nobuild = { nd.uid: nd for nd in network.nodes() if isinstance(nd, Edfa) and isinstance(next(network.predecessors(nd)), Roadm) } preamp_nodes_nobuild = { nd.uid: nd for nd in network.nodes() if isinstance(nd, Edfa) and isinstance(next(network.successors(nd)), Roadm) } # roadm dict with restrictions before build roadms = {nd.uid: nd for nd in network.nodes() if isinstance(nd, Roadm)} # 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) amp_nodes = [ nd for nd in network.nodes() if isinstance(nd, Edfa) and isinstance(next(network.predecessors(nd)), Roadm) and next(network.predecessors(nd)).restrictions['booster_variety_list'] ] preamp_nodes = [ nd for nd in network.nodes() if isinstance(nd, Edfa) and isinstance(next(network.successors(nd)), Roadm) and next(network.successors(nd)).restrictions['preamp_variety_list'] ] # check that previously existing amp are not changed for amp in amp_nodes: if amp.uid in amp_nodes_nobuild_uid: print(amp.uid, amp.params.type_variety) if not amp.params.type_variety == amp_nodes_nobuild[ amp.uid].params.type_variety: raise AssertionError() for amp in preamp_nodes: if amp.uid in preamp_nodes_nobuild_uid: if not amp.params.type_variety == preamp_nodes_nobuild[ amp.uid].params.type_variety: raise AssertionError() # check that restrictions are correctly applied for amp in amp_nodes: if amp.uid not in amp_nodes_nobuild_uid: # and if roadm had no restrictions before build: if restrictions['booster_variety_list'] and \ not roadms[next(network.predecessors(amp)).uid]\ .restrictions['booster_variety_list']: if amp.params.type_variety not in restrictions[ 'booster_variety_list']: raise AssertionError() for amp in preamp_nodes: if amp.uid not in preamp_nodes_nobuild_uid: if restrictions['preamp_variety_list'] and\ not roadms[next(network.successors(amp)).uid].restrictions['preamp_variety_list']: if amp.params.type_variety not in restrictions[ 'preamp_variety_list']: raise AssertionError()
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 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 test_roadm_target_power(prev_node_type, effective_pch_out_db): ''' Check that egress power of roadm is equal to target power if input power is greater than target power else, that it is equal to input power. Use a simple two hops A-B-C topology for the test where the prev_node in ROADM B is either an amplifier or a fused, so that the target power can not be met in this last case. ''' equipment = load_equipment(EQPT_LIBRARY_NAME) json_network = load_json(TEST_DIR / 'data/twohops_roadm_power_test.json') prev_node = next(n for n in json_network['elements'] if n['uid'] == 'west edfa in node B to ila2') json_network['elements'].remove(prev_node) if prev_node_type == 'edfa': prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Edfa'} elif prev_node_type == 'fused': prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Fused'} prev_node['params'] = {'loss': 0} json_network['elements'].append(prev_node) network = network_from_json(json_network, 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) params = {} params['request_id'] = 0 params['trx_type'] = '' params['trx_mode'] = '' params['source'] = 'trx node A' params['destination'] = 'trx node C' params['bidir'] = False params['nodes_list'] = ['trx node C'] params['loose_list'] = ['strict'] params['format'] = '' params['path_bandwidth'] = 100e9 trx_params = trx_mode_params(equipment) params.update(trx_params) req = PathRequest(**params) path = compute_constrained_path(network, req) si = create_input_spectral_information(req.f_min, req.f_max, req.roll_off, req.baud_rate, req.power, req.spacing) for i, el in enumerate(path): if isinstance(el, Roadm): carriers_power_in_roadm = min([ c.power.signal + c.power.nli + c.power.ase for c in si.carriers ]) si = el(si, degree=path[i + 1].uid) if el.uid == 'roadm node B': print('input', carriers_power_in_roadm) assert el.effective_pch_out_db == effective_pch_out_db for carrier in si.carriers: print(carrier.power.signal + carrier.power.nli + carrier.power.ase) power = carrier.power.signal + carrier.power.nli + carrier.power.ase if prev_node_type == 'edfa': # edfa prev_node sets input power to roadm to a high enough value: # Check that egress power of roadm is equal to target power assert power == pytest.approx( db2lin(effective_pch_out_db - 30), rel=1e-3) elif prev_node_type == 'fused': # fused prev_node does reamplfy power after fiber propagation, so input power # to roadm is low. # Check that egress power of roadm is equalized to the min carrier input power. assert power == pytest.approx(carriers_power_in_roadm, rel=1e-3) else: si = el(si)