diff --git a/gnpy/core/equipment.py b/gnpy/core/equipment.py index d69e0d8e1..50bb20b85 100644 --- a/gnpy/core/equipment.py +++ b/gnpy/core/equipment.py @@ -33,6 +33,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F + f' mode "{trx_params["format"]}" has baud rate' + f' {trx_params["baud_rate"] * 1e-9:.3f} GHz greater than min_spacing' + f' {trx_params["min_spacing"] * 1e-9:.3f}.') + trx_params['equalization_offset_db'] = trx_params.get('equalization_offset_db', 0) else: mode_params = {"format": "undetermined", "baud_rate": None, @@ -42,7 +43,8 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F "roll_off": None, "tx_osnr": None, "min_spacing": None, - "cost": None} + "cost": None, + "equalization_offset_db": 0} 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'] @@ -68,6 +70,7 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F trx_params['roll_off'] = default_si_data.roll_off trx_params['tx_osnr'] = default_si_data.tx_osnr trx_params['min_spacing'] = None + trx_params['equalization_offset_db'] = 0 trx_params['power'] = db2lin(default_si_data.power_dbm) * 1e-3 diff --git a/gnpy/core/info.py b/gnpy/core/info.py index 8046c24ad..e0667b97c 100644 --- a/gnpy/core/info.py +++ b/gnpy/core/info.py @@ -322,14 +322,15 @@ def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, fl raise -def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing, tx_osnr, ref_carrier=None): +def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing, tx_osnr, delta_pdb=0, + ref_carrier=None): """Creates a fixed slot width spectral information with flat power. all arguments are scalar values""" number_of_channels = automatic_nch(f_min, f_max, spacing) frequency = [(f_min + spacing * i) for i in range(1, number_of_channels + 1)] p_span0 = watt2dbm(power) p_spani = watt2dbm(power) - delta_pdb_per_channel = zeros(number_of_channels) + delta_pdb_per_channel = delta_pdb * ones(number_of_channels) label = [f'{baud_rate * 1e-9 :.2f}G' for i in range(number_of_channels)] return create_arbitrary_spectral_information(frequency, slot_width=spacing, signal=power, baud_rate=baud_rate, roll_off=roll_off, delta_pdb_per_channel=delta_pdb_per_channel, diff --git a/gnpy/tools/cli_examples.py b/gnpy/tools/cli_examples.py index ba0e22665..ee0d5f93a 100644 --- a/gnpy/tools/cli_examples.py +++ b/gnpy/tools/cli_examples.py @@ -331,7 +331,6 @@ def path_requests_run(args=None): # 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: diff --git a/gnpy/tools/json_io.py b/gnpy/tools/json_io.py index 7abd97954..a07376ccd 100644 --- a/gnpy/tools/json_io.py +++ b/gnpy/tools/json_io.py @@ -133,6 +133,7 @@ def __init__(self, **kwargs): for mode_params in self.mode: penalties = mode_params.get('penalties') mode_params['penalties'] = {} + mode_params['equalization_offset_db'] = mode_params.get('equalization_offset_db', 0) if not penalties: continue for impairment in ('chromatic_dispersion', 'pmd', 'pdl'): @@ -574,7 +575,6 @@ def requests_from_json(json_data, equipment): # 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: diff --git a/gnpy/topology/request.py b/gnpy/topology/request.py index 10a9b6ed8..ab303755a 100644 --- a/gnpy/topology/request.py +++ b/gnpy/topology/request.py @@ -35,14 +35,14 @@ RequestParams = namedtuple('RequestParams', 'request_id source destination bidir trx_type' ' trx_mode nodes_list loose_list spacing power nb_channel f_min' ' f_max format baud_rate OSNR penalties bit_rate' - ' roll_off tx_osnr min_spacing cost path_bandwidth effective_freq_slot') + ' roll_off tx_osnr min_spacing cost path_bandwidth effective_freq_slot' + ' equalization_offset_db') DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link_diverse' ' node_diverse disjunctions_req') class PathRequest: """the class that contains all attributes related to a request""" - def __init__(self, *args, **params): params = RequestParams(**params) self.request_id = params.request_id @@ -72,6 +72,7 @@ def __init__(self, *args, **params): self.N = params.effective_freq_slot['N'] self.M = params.effective_freq_slot['M'] self.initial_spectrum = None + self.offset_db = params.equalization_offset_db def __str__(self): return '\n\t'.join([f'{type(self).__name__} {self.request_id}', @@ -351,7 +352,8 @@ def propagate(path, req, equipment): else: si = create_input_spectral_information( f_min=req.f_min, f_max=req.f_max, roll_off=req.roll_off, baud_rate=req.baud_rate, - power=req.power, spacing=req.spacing, tx_osnr=req.tx_osnr, ref_carrier=ref_carrier(equipment)) + power=req.power, spacing=req.spacing, tx_osnr=req.tx_osnr, delta_pdb=req.offset_db, + ref_carrier=ref_carrier(equipment)) for i, el in enumerate(path): if isinstance(el, Roadm): si = el(si, degree=path[i+1].uid) @@ -369,20 +371,21 @@ def propagate(path, req, equipment): def propagate_and_optimize_mode(path, req, equipment): # if mode is unknown : loops on the modes starting from the highest baudrate fiting in the - # step 1: create an ordered list of modes based on baudrate - baudrate_to_explore = list(set([this_mode['baud_rate'] - for this_mode in equipment['Transceiver'][req.tsp].mode - if float(this_mode['min_spacing']) <= req.spacing])) + # step 1: create an ordered list of modes based on baudrate and power offset + # order higher baudrate with higher power offset first + baudrate_offset_to_explore = list(set([(this_mode['baud_rate'], this_mode['equalization_offset_db']) + for this_mode in equipment['Transceiver'][req.tsp].mode + if float(this_mode['min_spacing']) <= req.spacing])) # TODO be carefull on limits cases if spacing very close to req spacing eg 50.001 50.000 - baudrate_to_explore = sorted(baudrate_to_explore, reverse=True) - if baudrate_to_explore: + baudrate_offset_to_explore = sorted(baudrate_offset_to_explore, reverse=True) + if baudrate_offset_to_explore: # at least 1 baudrate can be tested wrt spacing - for this_br in baudrate_to_explore: + for (this_br, this_offset) in baudrate_offset_to_explore: modes_to_explore = [this_mode for this_mode in equipment['Transceiver'][req.tsp].mode - if this_mode['baud_rate'] == this_br and - float(this_mode['min_spacing']) <= req.spacing] + if this_mode['baud_rate'] == this_br + and float(this_mode['min_spacing']) <= req.spacing] modes_to_explore = sorted(modes_to_explore, - key=lambda x: x['bit_rate'], reverse=True) + key=lambda x: (x['bit_rate'], x['equalization_offset_db']), reverse=True) # step2: computes propagation for each baudrate: stop and select the first that passes # TODO: the case of roll off is not included: for now use SI one # TODO: if the loop in mode optimization does not have a feasible path, then bugs @@ -394,6 +397,7 @@ def propagate_and_optimize_mode(path, req, equipment): spc_info = create_input_spectral_information(f_min=req.f_min, f_max=req.f_max, roll_off=equipment['SI']['default'].roll_off, baud_rate=this_br, power=req.power, spacing=req.spacing, + delta_pdb=this_offset, tx_osnr=req.tx_osnr, ref_carrier=ref_carrier(equipment)) for i, el in enumerate(path): if isinstance(el, Roadm): diff --git a/tests/data/eqpt_config.json b/tests/data/eqpt_config.json index 8dbb5b895..8585c12e2 100644 --- a/tests/data/eqpt_config.json +++ b/tests/data/eqpt_config.json @@ -217,7 +217,7 @@ "tx_osnr": 45, "min_spacing": 75e9, "cost":1 - }, + }, { "format": "mode 4", "baud_rate": 66e9, diff --git a/tests/test_equalization.py b/tests/test_equalization.py index a7275372d..4e8061b86 100644 --- a/tests/test_equalization.py +++ b/tests/test_equalization.py @@ -12,6 +12,7 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal, assert_raises from numpy import array +from copy import deepcopy from gnpy.core.utils import lin2db, automatic_nch, dbm2watt, power_dbm_to_psd_mw_ghz, watt2dbm, psd2powerdbm from gnpy.core.network import build_network @@ -20,8 +21,9 @@ ReferenceCarrier from gnpy.core.equipment import trx_mode_params from gnpy.core.exceptions import ConfigurationError -from gnpy.tools.json_io import network_from_json, load_equipment, load_network, _spectrum_from_json, load_json -from gnpy.topology.request import PathRequest, compute_constrained_path, propagate +from gnpy.tools.json_io import network_from_json, load_equipment, load_network, _spectrum_from_json, load_json, \ + Transceiver, requests_from_json +from gnpy.topology.request import PathRequest, compute_constrained_path, propagate, propagate_and_optimize_mode TEST_DIR = Path(__file__).parent @@ -583,3 +585,253 @@ def test_power_option(req_power): assert_array_equal(infos_expected.pmd, infos_actual.pmd) assert_array_equal(infos_expected.channel_number, infos_actual.channel_number) assert_array_equal(infos_expected.number_of_channels, infos_actual.number_of_channels) + + +def transceiver(slot_width, value): + return { + "type_variety": "test_offset", + "frequency": { + "min": 191.3e12, + "max": 196.1e12 + }, + "mode": [ + { + "format": "mode 1", + "baud_rate": 64e9, + "OSNR": 18, + "bit_rate": 100e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": 75e9, + "cost": 1 + }, + { + "format": "mode 3", + "baud_rate": 64e9, + "OSNR": 18, + "bit_rate": 100e9, + "roll_off": 0.15, + "tx_osnr": 40, + "min_spacing": slot_width, + "equalization_offset_db": value, + "cost": 1 + } + ] + } + + +def some_requests(): + route = { + "route-object-include-exclude": [ + { + "explicit-route-usage": "route-include-ero", + "index": 0, + "num-unnum-hop": { + "node-id": "trx Brest_KLA", + "link-tp-id": "link-tp-id is not used", + "hop-type": "STRICT" + } + }, + { + "explicit-route-usage": "route-include-ero", + "index": 1, + "num-unnum-hop": { + "node-id": "trx Vannes_KBE", + "link-tp-id": "link-tp-id is not used", + "hop-type": "STRICT" + } + } + ] + } + return { + "path-request": [{ + "request-id": "2", + "source": "trx Brest_KLA", + "destination": "trx Vannes_KBE", + "src-tp-id": "trx Brest_KLA", + "dst-tp-id": "trx Vannes_KBE", + "bidirectional": False, + "path-constraints": { + "te-bandwidth": { + "technology": "flexi-grid", + "trx_type": "test_offset", + "trx_mode": "mode 1", + "spacing": 75000000000.0, + "path_bandwidth": 100000000000.0 + } + }, + "explicit-route-objects": route + }, { + "request-id": "3", + "source": "trx Brest_KLA", + "destination": "trx Vannes_KBE", + "src-tp-id": "trx Brest_KLA", + "dst-tp-id": "trx Vannes_KBE", + "bidirectional": False, + "path-constraints": { + "te-bandwidth": { + "technology": "flexi-grid", + "trx_type": "test_offset", + "trx_mode": "mode 3", + "spacing": 87500000000.0, + "path_bandwidth": 100000000000.0 + } + }, + "explicit-route-objects": route + }, { + "request-id": "4", + "source": "trx Brest_KLA", + "destination": "trx Vannes_KBE", + "src-tp-id": "trx Brest_KLA", + "dst-tp-id": "trx Vannes_KBE", + "bidirectional": False, + "path-constraints": { + "te-bandwidth": { + "technology": "flexi-grid", + "trx_type": "test_offset", + "trx_mode": "mode 1", + "spacing": 87500000000.0, + "path_bandwidth": 100000000000.0 + } + }, + "explicit-route-objects": route + }] + } + + +@pytest.mark.parametrize('slot_width, value', [(75e9, lin2db(75 / 87.5)), + (87.5e9, lin2db(75 / 87.5))]) +def test_power_offset_trx_equalization_psw(slot_width, value): + """Check that the equalization with the offset is giving the same result as with reference slot_width + Check that larger slot width but no offset takes larger slot width for equalization + """ + equipment = load_equipment(EQPT_FILENAME) + trx = transceiver(slot_width, value) + equipment['Transceiver'][trx['type_variety']] = Transceiver(**trx) + setattr(equipment['Roadm']['default'], 'target_pch_out_db', None) + setattr(equipment['Roadm']['default'], 'target_out_mWperSlotWidth', power_dbm_to_psd_mw_ghz(-20, 50e9)) + network = net_setup(equipment) + json_data = some_requests() + ref_request, request, other = requests_from_json(json_data, equipment) + # ref_request (_expected) has no offset, equalization on 75GH basis + path_expected = compute_constrained_path(network, ref_request) + _ = propagate(path_expected, ref_request, equipment) + roadm1_expected = deepcopy(path_expected[1]) + # request has an offset either defined in power and a larger slot width. + # The defined offset is "equalize as if it was a 75 GHz channel" although slot_width is 87.5GHz + path = compute_constrained_path(network, request) + _ = propagate(path, request, equipment) + roadm1 = deepcopy(path[1]) + # the other request has a larger slot width (spacing) but no offset. so equalization uses this slot width + path_other = compute_constrained_path(network, other) + _ = propagate(path, other, equipment) + roadm1_other = path_other[1] + # check the first frequency since all cariers have the same equalization + # Check that the power is equalized as if it was for a 75GHz channel (mode 1) instead of a 87.5GHz + assert roadm1.pch_out_dbm[0] == roadm1_expected.pch_out_dbm[0] + # Check that equalization instead uses 87.5GHz basis + assert roadm1_other.pch_out_dbm[0] == roadm1_expected.pch_out_dbm[0] + lin2db(87.5 / 75) + + +@pytest.mark.parametrize('slot_width, value', [(75e9, lin2db(75 / 50)), + (87.5e9, lin2db(75 / 50))]) +def test_power_offset_trx_equalization_p(slot_width, value): + """Check that the constant power equalization with the offset is applied + """ + equipment = load_equipment(EQPT_FILENAME) + trx = transceiver(slot_width, value) + equipment['Transceiver'][trx['type_variety']] = Transceiver(**trx) + setattr(equipment['Roadm']['default'], 'target_pch_out_db', -20) + network = net_setup(equipment) + json_data = some_requests() + ref_request, request, _ = requests_from_json(json_data, equipment) + path_expected = compute_constrained_path(network, ref_request) + _ = propagate(path_expected, ref_request, equipment) + roadm1_expected = deepcopy(path_expected[1]) + path = compute_constrained_path(network, request) + _ = propagate(path, request, equipment) + roadm1 = deepcopy(path[1]) + assert roadm1.pch_out_dbm[0] == roadm1_expected.pch_out_dbm[0] + lin2db(75 / 50) + + +@pytest.mark.parametrize('equalization, target_value', + [('target_pch_out_db', -20), + ('target_psd_out_mWperGHz', power_dbm_to_psd_mw_ghz(-20, 64e9)), + ('target_out_mWperSlotWidth', power_dbm_to_psd_mw_ghz(-20, 50e9))]) +@pytest.mark.parametrize('slot_width, value, expected_mode', [(75e9, 3.0, 'mode 3')]) +def test_power_offset_automatic_mode_selection(slot_width, value, equalization, + target_value, expected_mode): + """Check that the same result is obtained if the mode is user defined or if it is + automatically selected + """ + equipment = load_equipment(EQPT_FILENAME) + trx = transceiver(slot_width, value) + equipment['Transceiver'][trx['type_variety']] = Transceiver(**trx) + setattr(equipment['Roadm']['default'], 'target_pch_out_db', None) + setattr(equipment['Roadm']['default'], equalization, target_value) + network = net_setup(equipment) + route = { + "route-object-include-exclude": [ + { + "explicit-route-usage": "route-include-ero", + "index": 0, + "num-unnum-hop": { + "node-id": "trx Brest_KLA", + "link-tp-id": "link-tp-id is not used", + "hop-type": "STRICT" + } + }, + { + "explicit-route-usage": "route-include-ero", + "index": 1, + "num-unnum-hop": { + "node-id": "trx Vannes_KBE", + "link-tp-id": "link-tp-id is not used", + "hop-type": "STRICT" + } + } + ] + } + json_data = { + "path-request": [{ + "request-id": "imposed_mode", + "source": "trx Brest_KLA", + "destination": "trx Vannes_KBE", + "src-tp-id": "trx Brest_KLA", + "dst-tp-id": "trx Vannes_KBE", + "bidirectional": False, + "path-constraints": { + "te-bandwidth": { + "technology": "flexi-grid", + "trx_type": "test_offset", + "trx_mode": "mode 3", + "spacing": 75000000000.0, + "path_bandwidth": 100000000000.0 + } + }, + "explicit-route-objects": route + }, { + "request-id": "free_mode", + "source": "trx Brest_KLA", + "destination": "trx Vannes_KBE", + "src-tp-id": "trx Brest_KLA", + "dst-tp-id": "trx Vannes_KBE", + "bidirectional": False, + "path-constraints": { + "te-bandwidth": { + "technology": "flexi-grid", + "trx_type": "test_offset", + "spacing": 75000000000.0, + "path_bandwidth": 100000000000.0 + } + }, + "explicit-route-objects": route + }]} + imposed_req, free_req, = requests_from_json(json_data, equipment) + assert free_req.tsp_mode is None + path_expected = compute_constrained_path(network, imposed_req) + _ = propagate(path_expected, imposed_req, equipment) + path = compute_constrained_path(network, free_req) + _, mode = propagate_and_optimize_mode(path, free_req, equipment) + assert mode['format'] == expected_mode + assert_allclose(path_expected[-1].snr_01nm, path[-1].snr_01nm, rtol=1e-5) diff --git a/tests/test_parser.py b/tests/test_parser.py index b80d2fa9b..02d41c0e1 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -355,7 +355,8 @@ def test_excel_ila_constraints(source, destination, route_list, hoptype, expecte 'nb_channel': 0, 'power': 0, 'path_bandwidth': 0, - 'effective_freq_slot': None + 'effective_freq_slot': None, + 'equalization_offset_db': 0 } request = PathRequest(**params) diff --git a/tests/test_spectrum_assignment.py b/tests/test_spectrum_assignment.py index e017bdef2..60c524b49 100644 --- a/tests/test_spectrum_assignment.py +++ b/tests/test_spectrum_assignment.py @@ -288,7 +288,8 @@ def request_set(): 'min_spacing': 37.5e9, 'nb_channel': None, 'power': 0, - 'path_bandwidth': 800e9} + 'path_bandwidth': 800e9, + 'equalization_offset_db': 0} def test_freq_slot_exist(setup, equipment, request_set):