コード例 #1
0
ファイル: json_io.py プロジェクト: mohammadsaadraza/oopt-gnpy
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
コード例 #2
0
ファイル: test_disjunction.py プロジェクト: notilos/oopt-gnpy
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
コード例 #3
0
ファイル: test_parser.py プロジェクト: tqhuy812/oopt-gnpy-1
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
コード例 #4
0
ファイル: test_parser.py プロジェクト: tqhuy812/oopt-gnpy-1
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
コード例 #5
0
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
コード例 #6
0
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
コード例 #7
0
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
コード例 #8
0
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)
コード例 #9
0
ファイル: info.py プロジェクト: roshan-joyce-fujitsu/gnpy
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
コード例 #10
0
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
コード例 #11
0
ファイル: test_parser.py プロジェクト: tqhuy812/oopt-gnpy-1
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]
コード例 #12
0
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()
コード例 #13
0
ファイル: json_io.py プロジェクト: mohammadsaadraza/oopt-gnpy
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)
コード例 #14
0
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)
コード例 #15
0
ファイル: json_io.py プロジェクト: notilos/oopt-gnpy
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)
コード例 #16
0
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
コード例 #17
0
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)
コード例 #18
0
ファイル: test_parser.py プロジェクト: tqhuy812/oopt-gnpy-1
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])
コード例 #19
0
ファイル: test_parser.py プロジェクト: tqhuy812/oopt-gnpy-1
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')
コード例 #20
0
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()
コード例 #21
0
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)
コード例 #22
0
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
コード例 #23
0
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)