diff --git a/examples/path_requests_run.py b/examples/path_requests_run.py index a376e818..d12ab584 100755 --- a/examples/path_requests_run.py +++ b/examples/path_requests_run.py @@ -21,18 +21,14 @@ from numpy import mean from gnpy.core import ansi_escapes from gnpy.core.utils import automatic_nch from gnpy.core.network import build_network -from gnpy.core.elements import Roadm from gnpy.core.utils import lin2db from gnpy.core.exceptions import (ConfigurationError, EquipmentConfigError, NetworkTopologyError, ServiceError, DisjunctionError) -from gnpy.topology.request import (ResultElement, - propagate, jsontocsv, compute_path_dsjctn, - requests_aggregation, propagate_and_optimize_mode, - BLOCKING_NOPATH, BLOCKING_NOMODE, - find_reversed_path, correct_json_route_list) +from gnpy.topology.request import (ResultElement, jsontocsv, compute_path_dsjctn, requests_aggregation, + BLOCKING_NOPATH, correct_json_route_list, + deduplicate_disjunctions, compute_path_with_disjunction) from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum from gnpy.tools.json_io import load_equipment, load_network, load_requests, save_network, requests_from_json, disjunctions_from_json -from copy import deepcopy from math import ceil #EQPT_LIBRARY_FILENAME = Path(__file__).parent / 'eqpt_config.json' @@ -57,124 +53,6 @@ PARSER.add_argument('-v', '--verbose', action='count', default=0,\ PARSER.add_argument('-o', '--output', type=Path) -def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist): - """ use a list but a dictionnary might be helpful to find path based on request_id - TODO change all these req, dsjct, res lists into dict ! - """ - path_res_list = [] - reversed_path_res_list = [] - propagated_reversed_path_res_list = [] - - for i, pathreq in enumerate(pathreqlist): - - # 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 - 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}') - - # pathlist[i] contains the whole path information for request i - # last element is a transciver and where the result of the propagation is - # recorded. - # Important Note: since transceivers attached to roadms are actually logical - # elements to simulate performance, several demands having the same destination - # may use the same transponder for the performance simulation. This is why - # we use deepcopy: to ensure that each propagation is recorded and not overwritten - total_path = deepcopy(pathlist[i]) - print(f'Computed path (roadms):{[e.uid for e in total_path if isinstance(e, Roadm)]}') - # for debug - # print(f'{pathreq.baud_rate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}') - if total_path: - if pathreq.baud_rate is not None: - # means that at this point the mode was entered/forced by user and thus a - # baud_rate was defined - total_path = propagate(total_path, pathreq, equipment) - temp_snr01nm = round(mean(total_path[-1].snr+lin2db(pathreq.baud_rate/(12.5e9))), 2) - if temp_snr01nm < pathreq.OSNR: - msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\ - f' {pathreq.source} to {pathreq.destination} does not pass with' +\ - f' {pathreq.tsp_mode}\n\tcomputedSNR in 0.1nm = {temp_snr01nm} ' +\ - f'- required osnr {pathreq.OSNR}' - print(msg) - LOGGER.warning(msg) - pathreq.blocking_reason = 'MODE_NOT_FEASIBLE' - else: - total_path, mode = propagate_and_optimize_mode(total_path, pathreq, equipment) - # if no baudrate satisfies spacing, no mode is returned and the last explored mode - # a warning is shown in the propagate_and_optimize_mode - # propagate_and_optimize_mode function returns the mode with the highest bitrate - # that passes. if no mode passes, then a attribute blocking_reason is added on - # pathreq that contains the reason for blocking: 'NO_PATH', 'NO_FEASIBLE_MODE', ... - try: - if pathreq.blocking_reason in BLOCKING_NOPATH: - total_path = [] - elif pathreq.blocking_reason in BLOCKING_NOMODE: - pathreq.baud_rate = mode['baud_rate'] - pathreq.tsp_mode = mode['format'] - pathreq.format = mode['format'] - pathreq.OSNR = mode['OSNR'] - pathreq.tx_osnr = mode['tx_osnr'] - pathreq.bit_rate = mode['bit_rate'] - # other blocking reason should not appear at this point - except AttributeError: - pathreq.baud_rate = mode['baud_rate'] - pathreq.tsp_mode = mode['format'] - pathreq.format = mode['format'] - pathreq.OSNR = mode['OSNR'] - pathreq.tx_osnr = mode['tx_osnr'] - pathreq.bit_rate = mode['bit_rate'] - - # reversed path is needed for correct spectrum assignment - reversed_path = find_reversed_path(pathlist[i]) - if pathreq.bidir: - # only propagate if bidir is true, but needs the reversed path anyway for - # correct spectrum assignment - rev_p = deepcopy(reversed_path) - - print(f'\n\tPropagating Z to A direction {pathreq.destination} to {pathreq.source}') - print(f'\tPath (roadsm) {[r.uid for r in rev_p if isinstance(r,Roadm)]}\n') - propagated_reversed_path = propagate(rev_p, pathreq, equipment) - temp_snr01nm = round(mean(propagated_reversed_path[-1].snr +\ - lin2db(pathreq.baud_rate/(12.5e9))), 2) - if temp_snr01nm < pathreq.OSNR: - msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\ - f' {pathreq.source} to {pathreq.destination} does not pass with' +\ - f' {pathreq.tsp_mode}\n' +\ - f'\tcomputedSNR in 0.1nm = {temp_snr01nm} - required osnr {pathreq.OSNR}' - print(msg) - LOGGER.warning(msg) - # TODO selection of mode should also be on reversed direction !! - pathreq.blocking_reason = 'MODE_NOT_FEASIBLE' - else: - propagated_reversed_path = [] - else: - msg = 'Total path is empty. No propagation' - print(msg) - LOGGER.info(msg) - reversed_path = [] - propagated_reversed_path = [] - - path_res_list.append(total_path) - reversed_path_res_list.append(reversed_path) - propagated_reversed_path_res_list.append(propagated_reversed_path) - # print to have a nice output - print('') - return path_res_list, reversed_path_res_list, propagated_reversed_path_res_list - -def correct_disjn(disjn): - """ clean disjunctions to remove possible repetition - """ - local_disjn = disjn.copy() - for elem in local_disjn: - for dis_elem in local_disjn: - if set(elem.disjunctions_req) == set(dis_elem.disjunctions_req) and\ - elem.disjunction_id != dis_elem.disjunction_id: - local_disjn.remove(dis_elem) - return local_disjn - - def path_result_json(pathresult): """ create the response dictionnary """ @@ -242,7 +120,7 @@ def main(args): print(dsjn) # need to warn or correct in case of wrong disjunction form # disjunction must not be repeated with same or different ids - dsjn = correct_disjn(dsjn) + dsjn = deduplicate_disjunctions(dsjn) # Aggregate demands with same exact constraints print(f'{ansi_escapes.blue}Aggregating similar requests{ansi_escapes.reset}') @@ -261,8 +139,7 @@ def main(args): 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) + 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. diff --git a/gnpy/topology/request.py b/gnpy/topology/request.py index 77e1302e..a4a1429a 100644 --- a/gnpy/topology/request.py +++ b/gnpy/topology/request.py @@ -1110,3 +1110,122 @@ def correct_json_route_list(network, pathreqlist): raise ServiceError(msg) return pathreqlist + + +def deduplicate_disjunctions(disjn): + """ clean disjunctions to remove possible repetition + """ + local_disjn = disjn.copy() + for elem in local_disjn: + for dis_elem in local_disjn: + if set(elem.disjunctions_req) == set(dis_elem.disjunctions_req) and \ + elem.disjunction_id != dis_elem.disjunction_id: + local_disjn.remove(dis_elem) + return local_disjn + + +def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist): + """ use a list but a dictionnary might be helpful to find path based on request_id + TODO change all these req, dsjct, res lists into dict ! + """ + path_res_list = [] + reversed_path_res_list = [] + propagated_reversed_path_res_list = [] + + for i, pathreq in enumerate(pathreqlist): + + # 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 + 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}') + + # pathlist[i] contains the whole path information for request i + # last element is a transciver and where the result of the propagation is + # recorded. + # Important Note: since transceivers attached to roadms are actually logical + # elements to simulate performance, several demands having the same destination + # may use the same transponder for the performance simulation. This is why + # we use deepcopy: to ensure that each propagation is recorded and not overwritten + total_path = deepcopy(pathlist[i]) + print(f'Computed path (roadms):{[e.uid for e in total_path if isinstance(e, Roadm)]}') + # for debug + # print(f'{pathreq.baud_rate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}') + if total_path: + if pathreq.baud_rate is not None: + # means that at this point the mode was entered/forced by user and thus a + # baud_rate was defined + total_path = propagate(total_path, pathreq, equipment) + temp_snr01nm = round(mean(total_path[-1].snr+lin2db(pathreq.baud_rate/(12.5e9))), 2) + if temp_snr01nm < pathreq.OSNR: + msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\ + f' {pathreq.source} to {pathreq.destination} does not pass with' +\ + f' {pathreq.tsp_mode}\n\tcomputedSNR in 0.1nm = {temp_snr01nm} ' +\ + f'- required osnr {pathreq.OSNR}' + print(msg) + LOGGER.warning(msg) + pathreq.blocking_reason = 'MODE_NOT_FEASIBLE' + else: + total_path, mode = propagate_and_optimize_mode(total_path, pathreq, equipment) + # if no baudrate satisfies spacing, no mode is returned and the last explored mode + # a warning is shown in the propagate_and_optimize_mode + # propagate_and_optimize_mode function returns the mode with the highest bitrate + # that passes. if no mode passes, then a attribute blocking_reason is added on + # pathreq that contains the reason for blocking: 'NO_PATH', 'NO_FEASIBLE_MODE', ... + try: + if pathreq.blocking_reason in BLOCKING_NOPATH: + total_path = [] + elif pathreq.blocking_reason in BLOCKING_NOMODE: + pathreq.baud_rate = mode['baud_rate'] + pathreq.tsp_mode = mode['format'] + pathreq.format = mode['format'] + pathreq.OSNR = mode['OSNR'] + pathreq.tx_osnr = mode['tx_osnr'] + pathreq.bit_rate = mode['bit_rate'] + # other blocking reason should not appear at this point + except AttributeError: + pathreq.baud_rate = mode['baud_rate'] + pathreq.tsp_mode = mode['format'] + pathreq.format = mode['format'] + pathreq.OSNR = mode['OSNR'] + pathreq.tx_osnr = mode['tx_osnr'] + pathreq.bit_rate = mode['bit_rate'] + + # reversed path is needed for correct spectrum assignment + reversed_path = find_reversed_path(pathlist[i]) + if pathreq.bidir: + # only propagate if bidir is true, but needs the reversed path anyway for + # correct spectrum assignment + rev_p = deepcopy(reversed_path) + + print(f'\n\tPropagating Z to A direction {pathreq.destination} to {pathreq.source}') + print(f'\tPath (roadsm) {[r.uid for r in rev_p if isinstance(r,Roadm)]}\n') + propagated_reversed_path = propagate(rev_p, pathreq, equipment) + temp_snr01nm = round(mean(propagated_reversed_path[-1].snr +\ + lin2db(pathreq.baud_rate/(12.5e9))), 2) + if temp_snr01nm < pathreq.OSNR: + msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\ + f' {pathreq.source} to {pathreq.destination} does not pass with' +\ + f' {pathreq.tsp_mode}\n' +\ + f'\tcomputedSNR in 0.1nm = {temp_snr01nm} - required osnr {pathreq.OSNR}' + print(msg) + LOGGER.warning(msg) + # TODO selection of mode should also be on reversed direction !! + pathreq.blocking_reason = 'MODE_NOT_FEASIBLE' + else: + propagated_reversed_path = [] + else: + msg = 'Total path is empty. No propagation' + print(msg) + LOGGER.info(msg) + reversed_path = [] + propagated_reversed_path = [] + + path_res_list.append(total_path) + reversed_path_res_list.append(reversed_path) + propagated_reversed_path_res_list.append(propagated_reversed_path) + # print to have a nice output + print('') + return path_res_list, reversed_path_res_list, propagated_reversed_path_res_list diff --git a/tests/test_parser.py b/tests/test_parser.py index 16555903..ad3903b9 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -25,13 +25,12 @@ from copy import deepcopy from gnpy.core.utils import automatic_nch, lin2db from gnpy.core.network import build_network from gnpy.core.exceptions import ServiceError -from gnpy.topology.request import (jsontocsv, requests_aggregation, compute_path_dsjctn, - ResultElement, PathRequest) +from gnpy.topology.request import (jsontocsv, requests_aggregation, compute_path_dsjctn, deduplicate_disjunctions, + compute_path_with_disjunction, ResultElement, PathRequest) from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum from gnpy.tools.convert import convert_file from gnpy.tools.json_io import load_network, save_network, load_equipment, requests_from_json, disjunctions_from_json from gnpy.tools.service_sheet import convert_service_sheet, correct_xls_route_list -from examples.path_requests_run import correct_disjn, compute_path_with_disjunction TEST_DIR = Path(__file__).parent DATA_DIR = TEST_DIR / 'data' @@ -309,7 +308,7 @@ def test_json_response_generation(xls_input, expected_response_file): oms_list = build_oms_list(network, equipment) rqs = requests_from_json(data, equipment) dsjn = disjunctions_from_json(data) - dsjn = correct_disjn(dsjn) + dsjn = deduplicate_disjunctions(dsjn) rqs, dsjn = requests_aggregation(rqs, dsjn) pths = compute_path_dsjctn(network, equipment, rqs, dsjn) propagatedpths, reversed_pths, reversed_propagatedpths = \ diff --git a/tests/test_spectrum_assignment.py b/tests/test_spectrum_assignment.py index 505ff8bf..eefff42e 100644 --- a/tests/test_spectrum_assignment.py +++ b/tests/test_spectrum_assignment.py @@ -19,11 +19,10 @@ from gnpy.core.network import build_network from gnpy.core.utils import lin2db, automatic_nch from gnpy.core.elements import Roadm, Transceiver from gnpy.core.exceptions import SpectrumError -from gnpy.topology.request import compute_path_dsjctn, find_reversed_path +from gnpy.topology.request import compute_path_dsjctn, find_reversed_path, deduplicate_disjunctions from gnpy.topology.spectrum_assignment import (build_oms_list, align_grids, nvalue_to_frequency, bitmap_sum, Bitmap, spectrum_selection, pth_assign_spectrum) from gnpy.tools.json_io import load_equipment, load_network, requests_from_json, disjunctions_from_json -from examples.path_requests_run import correct_disjn TEST_DIR = Path(__file__).parent DATA_DIR = TEST_DIR / 'data' @@ -275,7 +274,7 @@ def test_reversed_direction(equipment, setup, requests, services): """ network, oms_list = setup dsjn = disjunctions_from_json(services) - dsjn = correct_disjn(dsjn) + dsjn = deduplicate_disjunctions(dsjn) paths = compute_path_dsjctn(network, equipment, requests, dsjn) rev_pths = [] for pth in paths: