def test_n_m_requests(setup, equipment, n, m, final_n, final_m, blocking_reason, request_set): """ test that various N and M values for a request end up with the correct path assgnment """ network, oms_list = setup # add an occupation on one of the span of the expected path OMS list on both directions # as defined by its offsets within the OMS list: [17, 20, 13, 22] and reversed path [19, 16, 21, 26] expected_path = [17, 20, 13, 22] expected_oms = [13, 16, 17, 19, 20, 21, 22, 26] some_oms = oms_list[expected_oms[3]] some_oms.assign_spectrum( -30, 32 ) # means that spectrum is occupied from indexes -62 to 1 on reversed path params = request_set params['effective_freq_slot'] = {'N': n, 'M': m} rqs = [PathRequest(**params)] paths = compute_path_dsjctn(network, equipment, rqs, []) # check that the computed path is the expected one (independant of blocking issues due to spectrum) path_oms = list( set([ e.oms_id for e in paths[0] if not isinstance(e, (Transceiver, Roadm)) ])) assert path_oms == expected_path # function to be tested: pth_assign_spectrum(paths, rqs, oms_list, [find_reversed_path(paths[0])]) # check that spectrum is correctly assigned assert rqs[0].N == final_n assert rqs[0].M == final_m assert getattr(rqs[0], 'blocking_reason', None) == blocking_reason
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 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 test_freq_slot_exist(setup, equipment, request_set): """ test that assignment works even if effective_freq_slot is not populated """ network, oms_list = setup params = request_set params['effective_freq_slot'] = None rqs = [PathRequest(**params)] paths = compute_path_dsjctn(network, equipment, rqs, []) pth_assign_spectrum(paths, rqs, oms_list, [find_reversed_path(paths[0])]) assert rqs[0].N == -256 assert rqs[0].M == 32
def test_inconsistant_freq_slot(setup, equipment, request_set): """ test that an inconsistant M correctly raises an error """ network, oms_list = setup params = request_set # minimum required nb of slots is 32 (800Gbit/100Gbit/s channels each occupying 50GHz ie 4 slots) params['effective_freq_slot'] = {'N': 0, 'M': 4} with pytest.raises(ServiceError): _check_one_request(params, 196.05e12) params['trx_mode'] = None rqs = [PathRequest(**params)] paths = compute_path_dsjctn(network, equipment, rqs, []) pth_assign_spectrum(paths, rqs, oms_list, [find_reversed_path(paths[0])]) assert rqs[0].blocking_reason == 'NOT_ENOUGH_RESERVED_SPECTRUM'
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_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 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)
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_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)