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 load_common_data(equipment_filename, topology_filename, simulation_filename, save_raw_network_filename): '''Load common configuration from JSON files''' try: equipment = load_equipment(equipment_filename) network = load_network(topology_filename, equipment) if save_raw_network_filename is not None: save_network(network, save_raw_network_filename) print(f'{ansi_escapes.blue}Raw network (no optimizations) saved to {save_raw_network_filename}{ansi_escapes.reset}') sim_params = SimParams(**load_json(simulation_filename)) if simulation_filename is not None else None if not sim_params: if next((node for node in network if isinstance(node, RamanFiber)), None) is not None: print(f'{ansi_escapes.red}Invocation error:{ansi_escapes.reset} ' f'RamanFiber requires passing simulation params via --sim-params') sys.exit(1) else: Simulation.set_params(sim_params) except exceptions.EquipmentConfigError as e: print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') sys.exit(1) 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) except exceptions.ParametersError as e: print(f'{ansi_escapes.red}Simulation parameters error:{ansi_escapes.reset} {e}') sys.exit(1) except exceptions.ServiceError as e: print(f'{ansi_escapes.red}Service error:{ansi_escapes.reset} {e}') sys.exit(1) return (equipment, network)
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 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)