mirror of
				https://github.com/Telecominfraproject/oopt-gnpy.git
				synced 2025-11-02 19:18:02 +00:00 
			
		
		
		
	exception handling add some info on which element (uid) is causing a problem this can help the user to identify where he must correct the input files Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
		
			
				
	
	
		
			905 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			905 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python3
 | 
						|
# -*- coding: utf-8 -*-
 | 
						|
 | 
						|
"""
 | 
						|
gnpy.core.request
 | 
						|
=================
 | 
						|
 | 
						|
This module contains path request functionality.
 | 
						|
 | 
						|
This functionality allows the user to provide a JSON request
 | 
						|
file in accordance with a Yang model for requesting path
 | 
						|
computations and returns path results in terms of path
 | 
						|
and feasibility
 | 
						|
 | 
						|
See: draft-ietf-teas-yang-path-computation-01.txt
 | 
						|
"""
 | 
						|
 | 
						|
from sys import exit
 | 
						|
from collections import namedtuple
 | 
						|
from logging import getLogger, basicConfig, CRITICAL, DEBUG, INFO
 | 
						|
from networkx import (dijkstra_path, NetworkXNoPath, all_simple_paths)
 | 
						|
from networkx.utils import pairwise 
 | 
						|
from numpy import mean
 | 
						|
from gnpy.core.service_sheet import convert_service_sheet, Request_element, Element
 | 
						|
from gnpy.core.elements import Transceiver, Roadm, Edfa, Fused
 | 
						|
from gnpy.core.network import set_roadm_loss
 | 
						|
from gnpy.core.utils import db2lin, lin2db
 | 
						|
from gnpy.core.info import create_input_spectral_information, SpectralInformation, Channel, Power
 | 
						|
from copy import copy, deepcopy
 | 
						|
from csv import writer
 | 
						|
from math import ceil
 | 
						|
 | 
						|
logger = getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
RequestParams = namedtuple('RequestParams','request_id source destination trx_type'+
 | 
						|
' trx_mode nodes_list loose_list spacing power nb_channel frequency format baud_rate OSNR bit_rate roll_off tx_osnr cost path_bandwidth')
 | 
						|
DisjunctionParams = namedtuple('DisjunctionParams','disjunction_id relaxable link_diverse node_diverse disjunctions_req')
 | 
						|
 | 
						|
class Path_request:
 | 
						|
    def __init__(self, *args, **params):
 | 
						|
        params = RequestParams(**params)
 | 
						|
        self.request_id = params.request_id
 | 
						|
        self.source     = params.source
 | 
						|
        self.destination = params.destination
 | 
						|
        self.tsp        = params.trx_type
 | 
						|
        self.tsp_mode   = params.trx_mode
 | 
						|
        self.baud_rate  = params.baud_rate
 | 
						|
        self.nodes_list = params.nodes_list
 | 
						|
        self.loose_list = params.loose_list
 | 
						|
        self.spacing    = params.spacing
 | 
						|
        self.power      = params.power
 | 
						|
        self.nb_channel = params.nb_channel
 | 
						|
        self.frequency  = params.frequency
 | 
						|
        self.format     = params.format
 | 
						|
        self.OSNR       = params.OSNR
 | 
						|
        self.bit_rate   = params.bit_rate
 | 
						|
        self.roll_off   = params.roll_off
 | 
						|
        self.tx_osnr    = params.tx_osnr
 | 
						|
        self.cost       = params.cost
 | 
						|
        self.path_bandwidth  = params.path_bandwidth
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return '\n\t'.join([  f'{type(self).__name__} {self.request_id}',
 | 
						|
                            f'source:       {self.source}',
 | 
						|
                            f'destination:  {self.destination}'])
 | 
						|
    def __repr__(self):
 | 
						|
        if self.baud_rate is not None:
 | 
						|
            temp = self.baud_rate * 1e-9
 | 
						|
            temp2 = self.bit_rate * 1e-9
 | 
						|
        else:
 | 
						|
            temp = self.baud_rate
 | 
						|
            temp2 = self.bit_rate
 | 
						|
 | 
						|
        return '\n\t'.join([  f'{type(self).__name__} {self.request_id}',
 | 
						|
                            f'source: \t{self.source}',
 | 
						|
                            f'destination:\t{self.destination}',
 | 
						|
                            f'trx type:\t{self.tsp}',
 | 
						|
                            f'trx mode:\t{self.tsp_mode}',
 | 
						|
                            f'baud_rate:\t{temp} Gbaud',
 | 
						|
                            f'bit_rate:\t{temp2} Gb/s',
 | 
						|
                            f'spacing:\t{self.spacing * 1e-9} GHz',
 | 
						|
                            f'power:  \t{round(lin2db(self.power)+30,2)} dBm',
 | 
						|
                            f'nb channels: \t{self.nb_channel}',
 | 
						|
                            f'path_bandwidth: \t{round(self.path_bandwidth * 1e-9,2)} Gbit/s',
 | 
						|
                            f'nodes-list:\t{self.nodes_list}',
 | 
						|
                            f'loose-list:\t{self.loose_list}'
 | 
						|
                            '\n'])
 | 
						|
class Disjunction:
 | 
						|
    def __init__(self, *args, **params):
 | 
						|
        params = DisjunctionParams(**params)
 | 
						|
        self.disjunction_id = params.disjunction_id
 | 
						|
        self.relaxable = params.relaxable
 | 
						|
        self.link_diverse = params.link_diverse
 | 
						|
        self.node_diverse = params.node_diverse
 | 
						|
        self.disjunctions_req = params.disjunctions_req
 | 
						|
        
 | 
						|
    def __str__(self):
 | 
						|
        return '\n\t'.join([f'relaxable:    {self.relaxable}',
 | 
						|
                            f'link-diverse:       {self.link_diverse}',
 | 
						|
                            f'node-diverse:  {self.node_diverse}',
 | 
						|
                            f'request-id-numbers: {self.disjunctions_req}']
 | 
						|
                            )
 | 
						|
    def __repr__(self):
 | 
						|
        return '\n\t'.join([  f'{type(self).__name__} {self.disjunction_id}',
 | 
						|
                            f'relaxable:    {self.relaxable}',
 | 
						|
                            f'link-diverse:       {self.link_diverse}',
 | 
						|
                            f'node-diverse:  {self.node_diverse}',
 | 
						|
                            f'request-id-numbers: {self.disjunctions_req}'
 | 
						|
                            '\n'])
 | 
						|
 | 
						|
class Result_element(Element):
 | 
						|
    def __init__(self,path_request,computed_path):
 | 
						|
        self.path_id = path_request.request_id
 | 
						|
        self.path_request = path_request
 | 
						|
        self.computed_path = computed_path
 | 
						|
        hop_type = []
 | 
						|
        if len(computed_path)>0 :
 | 
						|
            for e in computed_path :
 | 
						|
                if isinstance(e, Transceiver) :
 | 
						|
                    hop_type.append(' - '.join([path_request.tsp,path_request.tsp_mode]))
 | 
						|
                else:
 | 
						|
                    hop_type.append('not recorded')
 | 
						|
        else:
 | 
						|
            # TODO differentiate empty path in case not feasible because of tsp or not feasible because
 | 
						|
            # ther is no path connecting the nodes (whatever the tsp)
 | 
						|
            mode = 'not feasible with this transponder'
 | 
						|
            hop_type = ' - '.join([path_request.tsp,mode])
 | 
						|
        self.hop_type = hop_type
 | 
						|
    uid = property(lambda self: repr(self))
 | 
						|
    @property
 | 
						|
    def pathresult(self):
 | 
						|
        if not self.computed_path:
 | 
						|
            return {
 | 
						|
                   'path-id': self.path_id,
 | 
						|
                   'path-properties':{
 | 
						|
                       'path-metric': [
 | 
						|
                           {
 | 
						|
                           'metric-type': 'SNR@bandwidth',
 | 
						|
                           'accumulative-value': 'None'
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'SNR@0.1nm',
 | 
						|
                           'accumulative-value': 'None'
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'OSNR@bandwidth',
 | 
						|
                           'accumulative-value': 'None'
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'OSNR@0.1nm',
 | 
						|
                           'accumulative-value': 'None'
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'reference_power',
 | 
						|
                           'accumulative-value': self.path_request.power
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'path_bandwidth',
 | 
						|
                           'accumulative-value': self.path_request.path_bandwidth
 | 
						|
                           }
 | 
						|
                        ],
 | 
						|
                        'path-srlgs': {
 | 
						|
                            'usage': 'not used yet',
 | 
						|
                            'values': 'not used yet'
 | 
						|
                        },
 | 
						|
                        'path-route-objects': [
 | 
						|
                            {
 | 
						|
                            'path-route-object': {
 | 
						|
                                'index': 0,
 | 
						|
                                'unnumbered-hop': {
 | 
						|
                                    'node-id': self.path_request.source,
 | 
						|
                                    'link-tp-id': self.path_request.source,
 | 
						|
                                    'hop-type':  self.hop_type,
 | 
						|
                                    'direction': 'not used'
 | 
						|
                                },
 | 
						|
                                'label-hop': {
 | 
						|
                                    'te-label': {
 | 
						|
                                        'generic': 'not used yet',
 | 
						|
                                        'direction': 'not used yet'
 | 
						|
                                        }
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                            'path-route-object': {
 | 
						|
                                'index': 1,
 | 
						|
                                'unnumbered-hop': {
 | 
						|
                                    'node-id': self.path_request.destination,
 | 
						|
                                    'link-tp-id': self.path_request.destination,
 | 
						|
                                    'hop-type':  self.hop_type,
 | 
						|
                                    'direction': 'not used'
 | 
						|
                                },
 | 
						|
                                'label-hop': {
 | 
						|
                                    'te-label': {
 | 
						|
                                        'generic': 'not used yet',
 | 
						|
                                        'direction': 'not used yet'
 | 
						|
                                        }
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                            ]
 | 
						|
                    }
 | 
						|
                }
 | 
						|
        else:
 | 
						|
            return {
 | 
						|
                   'path-id': self.path_id,
 | 
						|
                   'path-properties':{
 | 
						|
                       'path-metric': [
 | 
						|
                           {
 | 
						|
                           'metric-type': 'SNR@bandwidth',
 | 
						|
                           'accumulative-value': round(mean(self.computed_path[-1].snr),2)
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'SNR@0.1nm',
 | 
						|
                           'accumulative-value': round(mean(self.computed_path[-1].snr+lin2db(self.path_request.baud_rate/12.5e9)),2)
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'OSNR@bandwidth',
 | 
						|
                           'accumulative-value': round(mean(self.computed_path[-1].osnr_ase),2)
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'OSNR@0.1nm',
 | 
						|
                           'accumulative-value': round(mean(self.computed_path[-1].osnr_ase_01nm),2)
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'reference_power',
 | 
						|
                           'accumulative-value': self.path_request.power
 | 
						|
                           },
 | 
						|
                           {
 | 
						|
                           'metric-type': 'path_bandwidth',
 | 
						|
                           'accumulative-value': self.path_request.path_bandwidth
 | 
						|
                           }
 | 
						|
                        ],
 | 
						|
                        'path-srlgs': {
 | 
						|
                            'usage': 'not used yet',
 | 
						|
                            'values': 'not used yet'
 | 
						|
                        },
 | 
						|
                        'path-route-objects': [
 | 
						|
                            {
 | 
						|
                            'path-route-object': {
 | 
						|
                                'index': self.computed_path.index(n),
 | 
						|
                                'unnumbered-hop': {
 | 
						|
                                    'node-id': n.uid,
 | 
						|
                                    'link-tp-id': n.uid,
 | 
						|
                                    'hop-type': self.hop_type[self.computed_path.index(n)],
 | 
						|
                                    'direction': 'not used'
 | 
						|
                                },
 | 
						|
                                'label-hop': {
 | 
						|
                                    'te-label': {
 | 
						|
                                        'generic': 'not used yet',
 | 
						|
                                        'direction': 'not used yet'
 | 
						|
                                        }
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            } for n in self.computed_path
 | 
						|
                            ]
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
    @property
 | 
						|
    def json(self):
 | 
						|
        return self.pathresult
 | 
						|
 | 
						|
def compute_constrained_path(network, req):
 | 
						|
    trx = [n for n in network.nodes() if isinstance(n, Transceiver)]
 | 
						|
    roadm = [n for n in network.nodes() if isinstance(n, Roadm)]
 | 
						|
    edfa = [n for n in network.nodes() if isinstance(n, Edfa)]
 | 
						|
    anytypenode = [n for n in network.nodes()]
 | 
						|
 | 
						|
    source = next(el for el in trx if el.uid == req.source)
 | 
						|
 | 
						|
    # This method ensures that the constraint can be satisfied without loops
 | 
						|
    # except when it is not possible : eg if constraints makes a loop
 | 
						|
    # It requires that the source, dest and nodes are correct (no error in the names)
 | 
						|
    destination = next(el for el in trx if el.uid == req.destination)
 | 
						|
    nodes_list = []
 | 
						|
    for n in req.nodes_list :
 | 
						|
        # for debug excel print(n)
 | 
						|
        nodes_list.append(next(el for el in anytypenode if el.uid == n))
 | 
						|
    # nodes_list contains at least the destination
 | 
						|
    if nodes_list is None :
 | 
						|
        msg = f'Request {req.request_id} problem in the constitution of nodes_list: should at least include destination'
 | 
						|
        logger.critical(msg)
 | 
						|
        exit()
 | 
						|
    if req.nodes_list[-1] != req.destination:
 | 
						|
        msg = f'Request {req.request_id} malformed list of nodes: last node should be destination trx'
 | 
						|
        logger.critical(msg)
 | 
						|
        exit()
 | 
						|
 | 
						|
    if len(nodes_list) == 1  :
 | 
						|
        try :
 | 
						|
            total_path = dijkstra_path(network, source, destination)
 | 
						|
        except NetworkXNoPath:
 | 
						|
            msg = f'Request {req.request_id} could not find a path from {source.uid} to node : {destination.uid} in network topology'
 | 
						|
            logger.critical(msg)
 | 
						|
            print(msg)
 | 
						|
            total_path = []        
 | 
						|
    else : 
 | 
						|
        all_simp_pths = list(all_simple_paths(network,source=source,\
 | 
						|
            target=destination, cutoff=120))
 | 
						|
        candidate = []
 | 
						|
        for p in all_simp_pths :
 | 
						|
            if ispart(nodes_list, p) :
 | 
						|
                # print(f'selection{[el.uid for el in p if el in roadm]}')
 | 
						|
                candidate.append(p)
 | 
						|
        # select the shortest path (in nb of hops)
 | 
						|
        if len(candidate)>0 :
 | 
						|
            candidate.sort(key=lambda x: len(x))
 | 
						|
            total_path = candidate[0]
 | 
						|
        else:
 | 
						|
            if req.loose_list[1] == 'loose':
 | 
						|
                print(f'Request {req.request_id} could not find a path crossing {nodes_list} in network topology')
 | 
						|
                print(f'constraint ignored')
 | 
						|
                total_path = dijkstra_path(network, source, destination)
 | 
						|
            else:
 | 
						|
                msg = f'Request {req.request_id} could not find a path crossing {nodes_list}.\nNo path computed'
 | 
						|
                logger.critical(msg)
 | 
						|
                print(msg)
 | 
						|
                total_path = []
 | 
						|
 | 
						|
    # obsolete method: this does not guaranty to avoid loops or correct results
 | 
						|
    # Here is the demonstration :
 | 
						|
    #         1     1
 | 
						|
    # eg    a----b-----c
 | 
						|
    #       |1   |0.5  |1
 | 
						|
    #       e----f--h--g
 | 
						|
    #         1  0.5 0.5
 | 
						|
    # if I have to compute a to g with constraint f-c
 | 
						|
    # result will be a concatenation of: a-b-f and f-b-c and c-g
 | 
						|
    # which means a loop. 
 | 
						|
    # if to avoid loops I iteratively suppress edges of the segmenst in the topo
 | 
						|
    # segment 1 = a-b-f
 | 
						|
    #               1
 | 
						|
    # eg    a    b-----c
 | 
						|
    #       |1         |1
 | 
						|
    #       e----f--h--g
 | 
						|
    #         1  0.5 0.5
 | 
						|
    # then 
 | 
						|
    # segment 2 = f-h-g-c
 | 
						|
    #               1
 | 
						|
    # eg    a    b-----c
 | 
						|
    #       |1          
 | 
						|
    #       e----f  h  g
 | 
						|
    #         1  
 | 
						|
    # then there is no more path to g destination
 | 
						|
    #
 | 
						|
    # 
 | 
						|
    # total_path = [source]
 | 
						|
 | 
						|
    # for n in req.nodes_list:
 | 
						|
    #     try :
 | 
						|
    #         node = next(el for el in trx if el.uid == n)
 | 
						|
    #     except StopIteration:
 | 
						|
    #         try:
 | 
						|
    #             node = next(el for el in anytypenode if el.uid == n)
 | 
						|
    #         except StopIteration:
 | 
						|
    #             try:
 | 
						|
    #                 # TODO this test is not giving good results: full name of the 
 | 
						|
    #                 # amp is required to avoid ambiguity on the direction
 | 
						|
    #                 node = next(el for el in anytypenode 
 | 
						|
    #                     if n in el.uid)
 | 
						|
    #             except StopIteration:
 | 
						|
    #                 msg = f'could not find node : {n} in network topology: \
 | 
						|
    #                     not a trx, roadm, edfa, fiber or fused element'
 | 
						|
    #                 logger.critical(msg)
 | 
						|
    #                 raise ValueError(msg)
 | 
						|
    #     # extend path list without repeating source -> skip first element in the list
 | 
						|
    #     try:
 | 
						|
    #         # to avoid looping back: use an alternate graph were current path edges and vertex are suppressed
 | 
						|
 | 
						|
    #         total_path.extend(dijkstra_path(network, source, node)[1:])
 | 
						|
    #         source = node
 | 
						|
    #     except NetworkXNoPath:
 | 
						|
    #         if req.loose_list[req.nodes_list.index(n)] == 'loose':
 | 
						|
    #             print(f'could not find a path from {source.uid} to loose node : {n} in network topology')
 | 
						|
    #             print(f'node  {n} is skipped')
 | 
						|
    #         else:
 | 
						|
    #             msg = f'could not find a path from {source.uid} to node : {n} in network topology'
 | 
						|
    #             logger.critical(msg)
 | 
						|
    #             print(msg)
 | 
						|
    #             total_path = []
 | 
						|
 | 
						|
    return total_path
 | 
						|
 | 
						|
def propagate(path, req, equipment, show=False):
 | 
						|
    #update roadm loss in case of power sweep (power mode only)
 | 
						|
    set_roadm_loss(path, equipment, lin2db(req.power*1e3))
 | 
						|
    si = create_input_spectral_information(
 | 
						|
        req.frequency['min'], req.roll_off, req.baud_rate,
 | 
						|
        req.power, req.spacing, req.nb_channel, req.tx_osnr)
 | 
						|
    for el in path:
 | 
						|
        si = el(si)
 | 
						|
        if show :
 | 
						|
            print(el)
 | 
						|
    return path
 | 
						|
 | 
						|
def propagate_and_optimize_mode(path, req, equipment, show=False):
 | 
						|
    #update roadm loss in case of power sweep (power mode only)
 | 
						|
    set_roadm_loss(path, equipment, lin2db(req.power*1e3))
 | 
						|
    # if mode is unknown : loops on the modes starting from the highest baudrate fiting in the
 | 
						|
    # spacing. TODO add a min_spacing attribute in transceivers. for now just using baudrate*1.1
 | 
						|
    # step 1: create an ordered list of modes based on baudrate
 | 
						|
    baudrate_to_explore = list(set([m['baud_rate'] for m in equipment['Transceiver'][req.tsp].mode 
 | 
						|
        if float(m['baud_rate'])+12.5e9< req.spacing]))
 | 
						|
    baudrate_to_explore = sorted(baudrate_to_explore, reverse=True)
 | 
						|
    if baudrate_to_explore : 
 | 
						|
        # at least 1 baudrate can be tested wrt spacing
 | 
						|
        for b in baudrate_to_explore :
 | 
						|
            modes_to_explore = [m for m in equipment['Transceiver'][req.tsp].mode 
 | 
						|
                if m['baud_rate'] == b]
 | 
						|
            modes_to_explore = sorted(modes_to_explore, 
 | 
						|
                key = lambda x: x['bit_rate'], reverse=True)
 | 
						|
            # print(modes_to_explore)
 | 
						|
            # step2 : computes propagation for each baudrate: stop and select the first that passes
 | 
						|
            found_a_feasible_mode = False
 | 
						|
            # TODO : the case of roll of is not included: for now use SI one
 | 
						|
            # TODO : if the loop in mode optimization does not have a feasible path, then bugs
 | 
						|
            for m in modes_to_explore :
 | 
						|
                si = create_input_spectral_information(
 | 
						|
                req.frequency['min'], equipment['SI']['default'].roll_off,
 | 
						|
                b, req.power, req.spacing, req.nb_channel, m['tx_osnr'])
 | 
						|
                for el in path:
 | 
						|
                    si = el(si)
 | 
						|
                if show :
 | 
						|
                    print(el)
 | 
						|
                if path[-1].snr is not None:
 | 
						|
                    if round(mean(path[-1].snr+lin2db(b/(12.5e9))),2) > m['OSNR'] :
 | 
						|
                        found_a_feasible_mode = True
 | 
						|
                        return path, m
 | 
						|
                else:  
 | 
						|
                    return [], None
 | 
						|
        # only get to this point if no baudrate/mode satisfies OSNR requirement
 | 
						|
 | 
						|
        # returns the last propagated path and mode
 | 
						|
        msg = f'Warning! Request {req.request_id}: no mode satisfies path SNR requirement.\n'
 | 
						|
        print(msg)
 | 
						|
        logger.info(msg)
 | 
						|
        return [],None
 | 
						|
    else :
 | 
						|
    #  no baudrate satisfying spacing
 | 
						|
        msg = f'Warning! Request {req.request_id}: no baudrate satisfies spacing requirement.\n'
 | 
						|
        print(msg)
 | 
						|
        logger.info(msg)
 | 
						|
        return [], None
 | 
						|
 | 
						|
 | 
						|
def jsontocsv(json_data,equipment,fileout):
 | 
						|
    # read json path result file in accordance with:
 | 
						|
    # Yang model for requesting Path Computation
 | 
						|
    # draft-ietf-teas-yang-path-computation-01.txt.
 | 
						|
    # and write results in an CSV file
 | 
						|
 | 
						|
    mywriter = writer(fileout)
 | 
						|
    mywriter.writerow(('path-id','source','destination','path_bandwidth','Pass?',\
 | 
						|
        'nb of tsp pairs','total cost','transponder-type','transponder-mode',\
 | 
						|
        'OSNR@0.1nm','SNR@0.1nm','SNR@bandwidth','baud rate (Gbaud)',\
 | 
						|
        'input power (dBm)','path'))
 | 
						|
    tspjsondata = equipment['Transceiver']
 | 
						|
    #print(tspjsondata)
 | 
						|
    for p in json_data['path']:
 | 
						|
        path_id     = p['path-id']
 | 
						|
        source      = p['path-properties']['path-route-objects'][0]\
 | 
						|
        ['path-route-object']['unnumbered-hop']['node-id']
 | 
						|
        destination = p['path-properties']['path-route-objects'][-1]\
 | 
						|
        ['path-route-object']['unnumbered-hop']['node-id']
 | 
						|
        # selects only roadm nodes
 | 
						|
        pth        = ' | '.join([ e['path-route-object']['unnumbered-hop']['node-id']
 | 
						|
                 for e in p['path-properties']['path-route-objects'] 
 | 
						|
                 if e['path-route-object']['unnumbered-hop']['node-id'].startswith('roadm') or e['path-route-object']['unnumbered-hop']['node-id'].startswith('Edfa')])
 | 
						|
 | 
						|
        [tsp,mode] = p['path-properties']['path-route-objects'][0]\
 | 
						|
        ['path-route-object']['unnumbered-hop']['hop-type'].split(' - ')
 | 
						|
 | 
						|
        # find the min  acceptable OSNR, baud rate from the eqpt library based on tsp (tupe) and mode (format)
 | 
						|
        # loading equipment already tests the existence of tsp type and mode:
 | 
						|
        if mode !='not feasible with this transponder' :
 | 
						|
            [minosnr, baud_rate, bit_rate, cost] = next([m['OSNR'] , m['baud_rate'] , m['bit_rate'], m['cost']]  
 | 
						|
                for m in equipment['Transceiver'][tsp].mode if  m['format']==mode)
 | 
						|
        # else:
 | 
						|
        #     [minosnr, baud_rate, bit_rate] = ['','','','']
 | 
						|
        output_snr = next(e['accumulative-value'] 
 | 
						|
            for e in p['path-properties']['path-metric'] if e['metric-type'] == 'SNR@0.1nm')
 | 
						|
        output_snrbandwidth = next(e['accumulative-value']
 | 
						|
            for e in p['path-properties']['path-metric'] if e['metric-type'] == 'SNR@bandwidth')
 | 
						|
        output_osnr = next(e['accumulative-value']
 | 
						|
            for e in p['path-properties']['path-metric'] if e['metric-type'] == 'OSNR@0.1nm')
 | 
						|
        output_osnrbandwidth = next(e['accumulative-value']
 | 
						|
            for e in p['path-properties']['path-metric'] if e['metric-type'] == 'OSNR@bandwidth')
 | 
						|
        power = next(e['accumulative-value']
 | 
						|
            for e in p['path-properties']['path-metric'] if e['metric-type'] == 'reference_power')
 | 
						|
        path_bandwidth = next(e['accumulative-value']
 | 
						|
            for e in p['path-properties']['path-metric'] if e['metric-type'] == 'path_bandwidth')
 | 
						|
        if isinstance(output_snr, str):
 | 
						|
            isok = False
 | 
						|
            nb_tsp = 0
 | 
						|
            pthbdbw = round(path_bandwidth*1e-9,2)
 | 
						|
            rosnr = ''
 | 
						|
            rsnr = ''
 | 
						|
            rsnrb = ''
 | 
						|
            br = ''
 | 
						|
            pw = ''
 | 
						|
            total_cost = ''
 | 
						|
        else:
 | 
						|
            isok   = output_snr >= minosnr
 | 
						|
            nb_tsp = ceil(path_bandwidth / bit_rate)
 | 
						|
            pthbdbw = round(path_bandwidth*1e-9,2)
 | 
						|
            rosnr  = round(output_osnr,2)
 | 
						|
            rsnr   = round(output_snr,2)
 | 
						|
            rsnrb  = round(output_snrbandwidth,2)
 | 
						|
            br     = round(baud_rate*1e-9,2) 
 | 
						|
            pw     = round(lin2db(power)+30,2)
 | 
						|
            total_cost = nb_tsp * cost
 | 
						|
        mywriter.writerow((path_id,
 | 
						|
            source,
 | 
						|
            destination,
 | 
						|
            pthbdbw,
 | 
						|
            isok,
 | 
						|
            nb_tsp,
 | 
						|
            total_cost,
 | 
						|
            tsp,
 | 
						|
            mode,
 | 
						|
            rosnr,
 | 
						|
            rsnr,
 | 
						|
            rsnrb,
 | 
						|
            br,
 | 
						|
            pw,
 | 
						|
            pth
 | 
						|
            ))
 | 
						|
 | 
						|
 | 
						|
def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
 | 
						|
    # pathreqlist is a list of Path_request objects
 | 
						|
    # disjunctions_list a list of Disjunction objects
 | 
						|
    
 | 
						|
    # given a network, a list of requests with the set of disjunction features between
 | 
						|
    # request, the function computes the set of path satisfying : first the disjunction
 | 
						|
    # constraint and second the routing constraint if the request include an explicit 
 | 
						|
    # set of elements to pass through.
 | 
						|
    # the algorithm used allows to specify disjunction for demands not sharing source or
 | 
						|
    # destination.
 | 
						|
    # a request might be declared as disjoint from several requests
 | 
						|
    # it is a iterative process:
 | 
						|
    # first computes a list of all shortest path (this may add computation time)
 | 
						|
    # second elaborate the set of path solution for each synchronization vector
 | 
						|
    # third select only the candidates that satisfy all synchronization vectors they belong to
 | 
						|
    # fourth apply route constraints : remove candidate path that do not satisfy the constraint
 | 
						|
    # fifth select the first candidate among the set of candidates.
 | 
						|
    # the example network used in comments has been added to the set of data tests files
 | 
						|
 | 
						|
    # define the list to be returned
 | 
						|
    path_res_list = []
 | 
						|
 | 
						|
    # all disjctn must be computed at once together to avoid blocking
 | 
						|
    #         1     1
 | 
						|
    # eg    a----b-----c
 | 
						|
    #       |1   |0.5  |1
 | 
						|
    #       e----f--h--g
 | 
						|
    #         1  0.5 0.5
 | 
						|
    # if I have to compute a to g and a to h 
 | 
						|
    # I must not compute a-b-f-h-g, otherwise there is no disjoint path remaining for a to h
 | 
						|
    # instead I should list all most disjoint path and select the one that have the less
 | 
						|
    # number of commonalities
 | 
						|
    #     \     path abfh  aefh   abcgh 
 | 
						|
    #      \___cost   2     2.5    3.5
 | 
						|
    #   path| cost  
 | 
						|
    #  abfhg|  2.5    x      x      x
 | 
						|
    #  abcg |  3      x             x
 | 
						|
    #  aefhg|  3      x      x      x
 | 
						|
    # from this table abcg and aefh have no common links and should be preferred 
 | 
						|
    # even they are not the shortest paths
 | 
						|
 | 
						|
    # build the list of pathreqlist elements not concerned by disjunction
 | 
						|
    global_disjunctions_list = [e for d in disjunctions_list for e in d.disjunctions_req ]
 | 
						|
    pathreqlist_simple = [e for e in pathreqlist if e.request_id not in global_disjunctions_list]
 | 
						|
    pathreqlist_disjt = [e for e in pathreqlist if e.request_id in global_disjunctions_list]
 | 
						|
 | 
						|
    # use a mirror class to record path and the corresponding requests
 | 
						|
    class Pth:
 | 
						|
        def __init__(self, req, pth, simplepth):
 | 
						|
            self.req = req
 | 
						|
            self.pth = pth
 | 
						|
            self.simplepth = simplepth
 | 
						|
 | 
						|
    # step 1
 | 
						|
    # for each remaining request compute a set of simple path
 | 
						|
    allpaths = {}
 | 
						|
    rqs = {}
 | 
						|
    simple_rqs = {}
 | 
						|
    simple_rqs_reversed = {}
 | 
						|
    for pathreq in pathreqlist_disjt :
 | 
						|
        all_simp_pths = list(all_simple_paths(network,\
 | 
						|
            source=next(el for el in network.nodes() if el.uid == pathreq.source),\
 | 
						|
            target=next(el for el in network.nodes() if el.uid == pathreq.destination),\
 | 
						|
            cutoff=80))
 | 
						|
        # sort them
 | 
						|
        all_simp_pths = sorted(all_simp_pths, key=lambda path: len(path))
 | 
						|
        # reversed direction paths required to check disjunction on both direction
 | 
						|
        all_simp_pths_reversed = []
 | 
						|
        for pth in all_simp_pths:
 | 
						|
            all_simp_pths_reversed.append(find_reversed_path(pth,network))
 | 
						|
        rqs[pathreq.request_id] = all_simp_pths 
 | 
						|
        temp =[]
 | 
						|
        for p in all_simp_pths :
 | 
						|
            # build a short list representing each roadm+direction with the first item
 | 
						|
            # start enumeration at 1 to avoid Trx in the list
 | 
						|
            s = [e.uid for i,e in enumerate(p[1:-1]) \
 | 
						|
                if (isinstance(e,Roadm) | (isinstance(p[i],Roadm) ))] 
 | 
						|
            temp.append(s)
 | 
						|
            # id(s) is unique even if path is the same: two objects with same 
 | 
						|
            # path have two different ids
 | 
						|
            allpaths[id(s)] = Pth(pathreq,p,s)
 | 
						|
        simple_rqs[pathreq.request_id] = temp
 | 
						|
        temp =[]
 | 
						|
        for p in all_simp_pths_reversed :
 | 
						|
            # build a short list representing each roadm+direction with the first item
 | 
						|
            # start enumeration at 1 to avoid Trx in the list
 | 
						|
            temp.append([e.uid for i,e in enumerate(p[1:-1]) \
 | 
						|
                if (isinstance(e,Roadm) | (isinstance(p[i],Roadm) ))] )
 | 
						|
        simple_rqs_reversed[pathreq.request_id] = temp
 | 
						|
    # step 2 
 | 
						|
    # for each set of requests that need to be disjoint
 | 
						|
    # select the disjoint path combination
 | 
						|
 | 
						|
    candidates = {}
 | 
						|
    for d in disjunctions_list :
 | 
						|
        dlist = d.disjunctions_req.copy()
 | 
						|
        # each line of dpath is one combination of path that satisfies disjunction
 | 
						|
        dpath = []
 | 
						|
        for i,p in enumerate(simple_rqs[dlist[0]]):
 | 
						|
            dpath.append([p])
 | 
						|
            # allpaths[id(p)].d_id = d.disjunction_id
 | 
						|
        # in each loop, dpath is updated with a path for rq that satisfies 
 | 
						|
        # disjunction with each path in dpath
 | 
						|
        # for example, assume set of requests in the vector (disjunction_list) is  {rq1,rq2, rq3}
 | 
						|
        # rq1  p1: abfhg
 | 
						|
        #      p2: aefhg
 | 
						|
        #      p3: abcg
 | 
						|
        # rq2  p8: bf
 | 
						|
        # rq3  p4: abcgh
 | 
						|
        #      p6: aefh
 | 
						|
        #      p7: abfh
 | 
						|
        # initiate with rq1
 | 
						|
        #  dpath = [[p1]
 | 
						|
        #           [p2]
 | 
						|
        #           [p3]]
 | 
						|
        #  after first loop:
 | 
						|
        #  dpath = [[p1 p8]
 | 
						|
        #           [p3 p8]]
 | 
						|
        #  since p2 and p8 are not disjoint
 | 
						|
        #  after second loop:
 | 
						|
        #  dpath = [ p3 p8 p6 ]
 | 
						|
        #  since p1 and p4 are not disjoint 
 | 
						|
        #        p1 and p7 are not disjoint
 | 
						|
        #        p3 and p4 are not disjoint
 | 
						|
        #        p3 and p7 are not disjoint
 | 
						|
 | 
						|
        for e1 in dlist[1:] :
 | 
						|
            temp = []
 | 
						|
            for j,p1 in enumerate(simple_rqs[e1]):
 | 
						|
                # allpaths[id(p1)].d_id = d.disjunction_id
 | 
						|
                # can use index j in simple_rqs_reversed because index 
 | 
						|
                # of direct and reversed paths have been kept identical
 | 
						|
                p1_reversed = simple_rqs_reversed[e1][j]
 | 
						|
                # print(p1_reversed)
 | 
						|
                # print('\n\n')
 | 
						|
                for k,c in enumerate(dpath) :
 | 
						|
                    # print(f' c: \t{c}')
 | 
						|
                    temp2 = c.copy()
 | 
						|
                    all_disjoint = 0
 | 
						|
                    for p in c :
 | 
						|
                        all_disjoint += isdisjoint(p1,p)+ isdisjoint(p1_reversed,p)
 | 
						|
                    if all_disjoint ==0:
 | 
						|
                        temp2.append(p1)
 | 
						|
                        temp.append(temp2)
 | 
						|
                            # print(f' coucou {e1}: \t{temp}')
 | 
						|
            dpath = temp
 | 
						|
        # print(dpath)
 | 
						|
        candidates[d.disjunction_id] = dpath
 | 
						|
 | 
						|
    # for i in disjunctions_list  :
 | 
						|
    #     print(f'\n{candidates[i.disjunction_id]}')
 | 
						|
 | 
						|
    # step 3
 | 
						|
    # now for each request, select the path that satisfies all disjunctions
 | 
						|
    # path must be in candidates[id] for all concerned ids
 | 
						|
    # for example, assume set of sync vectors (disjunction groups) is
 | 
						|
    #   s1 = {rq1 rq2}   s2 = {rq1 rq3}
 | 
						|
    #   candidate[s1] = [[p1 p8]
 | 
						|
    #                    [p3 p8]]
 | 
						|
    #   candidate[s2] = [[p3 p6]]
 | 
						|
    #   for rq1 p3 should be preferred
 | 
						|
 | 
						|
 | 
						|
    for pathreq in pathreqlist_disjt:
 | 
						|
        concerned_d_id = [d.disjunction_id for d in disjunctions_list if pathreq.request_id in d.disjunctions_req]
 | 
						|
        # for each set of solution, verify that the same path is used for the same request
 | 
						|
        candidate_paths = simple_rqs[pathreq.request_id]
 | 
						|
        # print('coucou')
 | 
						|
        # print(pathreq.request_id)
 | 
						|
        for p in candidate_paths :
 | 
						|
            iscandidate = 0
 | 
						|
            for sol in concerned_d_id :
 | 
						|
                test = 1
 | 
						|
                # for each solution test if p is part of the solution
 | 
						|
                # if yes, then p can remain a candidate
 | 
						|
                for i,m in enumerate(candidates[sol]) :
 | 
						|
                    if p in m:
 | 
						|
                        if allpaths[id(m[m.index(p)])].req.request_id == pathreq.request_id :
 | 
						|
                            test = 0
 | 
						|
                            break
 | 
						|
                iscandidate += test
 | 
						|
            if iscandidate != 0:
 | 
						|
                for l in concerned_d_id :
 | 
						|
                    for m in candidates[l] :
 | 
						|
                        if p in m :
 | 
						|
                            candidates[l].remove(m)
 | 
						|
 | 
						|
#    for i in disjunctions_list  :
 | 
						|
#        print(i.disjunction_id)
 | 
						|
#        print(f'\n{candidates[i.disjunction_id]}')
 | 
						|
 | 
						|
    # step 4 apply route constraints : remove candidate path that do not satisfy the constraint
 | 
						|
    # only in  the case of disjounction: the simple path is processed in request.compute_constrained_path
 | 
						|
    # TODO : keep a version without the loose constraint
 | 
						|
    for d in disjunctions_list  :
 | 
						|
        temp = []
 | 
						|
        for j,sol in enumerate(candidates[d.disjunction_id]) :
 | 
						|
            testispartok = True
 | 
						|
            for i,p in enumerate(sol) :
 | 
						|
                # print(f'test {allpaths[id(p)].req.request_id}')
 | 
						|
                # print(f'length of route {len(allpaths[id(p)].req.nodes_list)}')
 | 
						|
                if allpaths[id(p)].req.nodes_list :
 | 
						|
                    # if p does not containt the ordered list node, remove sol from the candidate
 | 
						|
                    # except if this was the last solution: then check if the constraint is loose or not
 | 
						|
                    if not ispart(allpaths[id(p)].req.nodes_list, p) : 
 | 
						|
                        # print(f'nb of solutions {len(temp)}')
 | 
						|
                        if j < len(candidates[d.disjunction_id])-1 :
 | 
						|
                            msg = f'removing {sol}'
 | 
						|
                            logger.info(msg)
 | 
						|
                            testispartok = False
 | 
						|
                            #break
 | 
						|
                        else:
 | 
						|
                            if 'loose' in allpaths[id(p)].req.loose_list:
 | 
						|
                                logger.info(f'Could not apply route constraint'+
 | 
						|
                                    f'{allpaths[id(p)].req.nodes_list} on request {allpaths[id(p)].req.request_id}')
 | 
						|
                            else :
 | 
						|
                                logger.info(f'removing last solution from candidate paths\n{sol}')
 | 
						|
                                testispartok = False
 | 
						|
            if testispartok :
 | 
						|
                temp.append(sol)
 | 
						|
        candidates[d.disjunction_id] = temp
 | 
						|
 | 
						|
    # step 5 select the first combination that works
 | 
						|
    pathreslist_disjoint = {}
 | 
						|
    for d in disjunctions_list  :
 | 
						|
        test_sol = True
 | 
						|
        while test_sol:
 | 
						|
            # print('coucou')
 | 
						|
            if candidates[d.disjunction_id] :
 | 
						|
                for p in candidates[d.disjunction_id][0]:
 | 
						|
                    if allpaths[id(p)].req in pathreqlist_disjt: 
 | 
						|
                        # print(f'selected path :{p} for req {allpaths[id(p)].req.request_id}')
 | 
						|
                        pathreslist_disjoint[allpaths[id(p)].req] = allpaths[id(p)].pth
 | 
						|
                        pathreqlist_disjt.remove(allpaths[id(p)].req)
 | 
						|
                        candidates = remove_candidate(candidates, allpaths, allpaths[id(p)].req, p)
 | 
						|
                        test_sol = False
 | 
						|
            else:
 | 
						|
                msg = f'No disjoint path found with added constraint'
 | 
						|
                logger.critical(msg)
 | 
						|
                print(f'{msg}\nComputation stopped.')
 | 
						|
                # TODO in this case: replay step 5  with the candidate without constraints
 | 
						|
                exit()
 | 
						|
    
 | 
						|
    # for i in disjunctions_list  :
 | 
						|
    #     print(i.disjunction_id)
 | 
						|
    #     print(f'\n{candidates[i.disjunction_id]}')
 | 
						|
 | 
						|
    # list the results in the same order as initial pathreqlist        
 | 
						|
    for req in pathreqlist :
 | 
						|
        req.nodes_list.append(req.destination)
 | 
						|
        # we assume that the destination is a strict constraint
 | 
						|
        req.loose_list.append('strict')
 | 
						|
        if req in pathreqlist_simple:
 | 
						|
            path_res_list.append(compute_constrained_path(network, req))
 | 
						|
        else:
 | 
						|
            path_res_list.append(pathreslist_disjoint[req])
 | 
						|
    return path_res_list
 | 
						|
 | 
						|
def isdisjoint(p1,p2) :
 | 
						|
    # returns 0 if disjoint
 | 
						|
    edge1 = list(pairwise(p1))
 | 
						|
    edge2 = list(pairwise(p2))
 | 
						|
    for e in edge1 :
 | 
						|
        if e in edge2 :
 | 
						|
            return 1
 | 
						|
    return 0
 | 
						|
 | 
						|
def find_reversed_path(p,network) :
 | 
						|
    # select of intermediate roadms and find the path between them
 | 
						|
    # note that this function may not give an exact result in case of multiple
 | 
						|
    # links between two adjacent nodes. 
 | 
						|
    # TODO add some indication on elements to indicate from which other they 
 | 
						|
    # are the reversed direction
 | 
						|
    reversed_roadm_path = list(reversed([e for e in p if isinstance (e,Roadm)]))
 | 
						|
    source = p[-1]
 | 
						|
    destination = p[0]
 | 
						|
    total_path = [source]
 | 
						|
    for node in reversed_roadm_path :
 | 
						|
        total_path.extend(dijkstra_path(network, source, node)[1:])
 | 
						|
        source = node
 | 
						|
    total_path.append(destination)
 | 
						|
    return total_path
 | 
						|
 | 
						|
def ispart(a,b) :
 | 
						|
    # the functions takes two paths a and b and retrns True
 | 
						|
    # if all a elements are part of b and in the same order
 | 
						|
    j = 0
 | 
						|
    for i, el in enumerate(a):
 | 
						|
        if el in b :
 | 
						|
            if b.index(el) >= j :
 | 
						|
                j = b.index(el)
 | 
						|
            else: 
 | 
						|
                return False
 | 
						|
        else:
 | 
						|
            return False
 | 
						|
    return True
 | 
						|
 | 
						|
def remove_candidate(candidates, allpaths, rq, pth) :
 | 
						|
    # print(f'coucou {rq.request_id}')
 | 
						|
    for key, candidate  in candidates.items() :
 | 
						|
        temp = candidate.copy()
 | 
						|
        for i,sol in enumerate(candidate) :
 | 
						|
            for p in sol :
 | 
						|
                if allpaths[id(p)].req.request_id == rq.request_id :
 | 
						|
                    if id(p) != id(pth) :
 | 
						|
                        temp.remove(sol)
 | 
						|
                        break
 | 
						|
        candidates[key] = temp
 | 
						|
    return candidates
 | 
						|
 | 
						|
def compare_reqs(req1,req2,disjlist) :
 | 
						|
    dis1 = [d for d in disjlist if req1.request_id in d.disjunctions_req]
 | 
						|
    dis2 = [d for d in disjlist if req2.request_id in d.disjunctions_req]
 | 
						|
    same_disj = False
 | 
						|
    if dis1 and dis2 :
 | 
						|
        temp1 = []
 | 
						|
        for d in dis1:
 | 
						|
            temp1.extend(d.disjunctions_req)
 | 
						|
            temp1.remove(req1.request_id)
 | 
						|
        temp2 = []
 | 
						|
        for d in dis2:
 | 
						|
            temp2.extend(d.disjunctions_req)
 | 
						|
            temp2.remove(req2.request_id)
 | 
						|
        if set(temp1) == set(temp2) :
 | 
						|
            same_disj = True
 | 
						|
    elif not dis2 and not dis1:
 | 
						|
        same_disj = True
 | 
						|
 | 
						|
    if req1.source     == req2.source and \
 | 
						|
        req1.destination == req2.destination and  \
 | 
						|
        req1.tsp        == req2.tsp and \
 | 
						|
        req1.tsp_mode   == req2.tsp_mode and \
 | 
						|
        req1.baud_rate  == req2.baud_rate and \
 | 
						|
        req1.nodes_list == req2.nodes_list and \
 | 
						|
        req1.loose_list == req2.loose_list and \
 | 
						|
        req1.spacing    == req2.spacing and \
 | 
						|
        req1.power      == req2.power and \
 | 
						|
        req1.nb_channel == req2.nb_channel and \
 | 
						|
        req1.frequency  == req2.frequency and \
 | 
						|
        req1.format     == req2.format and \
 | 
						|
        req1.OSNR       == req2.OSNR and \
 | 
						|
        req1.roll_off   == req2.roll_off and \
 | 
						|
        same_disj :
 | 
						|
        return True
 | 
						|
    else:
 | 
						|
        return False
 | 
						|
 | 
						|
def requests_aggregation(pathreqlist,disjlist) :
 | 
						|
    # this function aggregates requests so that if several requests
 | 
						|
    # exist between same source and destination and with same transponder type
 | 
						|
    # todo maybe add conditions on mode ??, spacing ...
 | 
						|
    # currently if undefined takes the default values
 | 
						|
    local_list = pathreqlist.copy()
 | 
						|
    for req in pathreqlist:
 | 
						|
        for r in local_list : 
 | 
						|
            if  req.request_id != r.request_id and compare_reqs(req, r, disjlist):
 | 
						|
                # aggregate
 | 
						|
                r.path_bandwidth += req.path_bandwidth
 | 
						|
                temp_r_id = r.request_id
 | 
						|
                r.request_id = ' | '.join((r.request_id,req.request_id))
 | 
						|
                # remove request from list
 | 
						|
                local_list.remove(req)
 | 
						|
                # todo change also disjunction req with new demand
 | 
						|
 | 
						|
                for d in disjlist :
 | 
						|
                    if req.request_id in d.disjunctions_req :
 | 
						|
                        d.disjunctions_req.remove(req.request_id)
 | 
						|
                        d.disjunctions_req.append(r.request_id)
 | 
						|
                for d in disjlist :        
 | 
						|
                    if temp_r_id in d.disjunctions_req :
 | 
						|
                        disjlist.remove(d)
 | 
						|
                break
 | 
						|
    return local_list, disjlist
 |