mirror of
				https://github.com/Telecominfraproject/oopt-gnpy.git
				synced 2025-11-04 03:57:47 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			651 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			651 lines
		
	
	
		
			28 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 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
 | 
						|
 | 
						|
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')
 | 
						|
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
 | 
						|
 | 
						|
    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):
 | 
						|
        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{self.baud_rate * 1e-9} Gbaud',
 | 
						|
                            f'bit_rate:\t{self.bit_rate * 1e-9} Gb/s',
 | 
						|
                            f'spacing:\t{self.spacing * 1e-9} GHz',
 | 
						|
                            f'power:  \t{round(lin2db(self.power)+30,2)} dBm'
 | 
						|
                            '\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'relaxable:    {self.relaxable}',
 | 
						|
                            f'link-diverse:       {self.link_diverse}',
 | 
						|
                            f'node-diverse:  {self.node_diverse}',
 | 
						|
                            f'request-id-numbers: {self.disjunctions_req}']
 | 
						|
                            )
 | 
						|
 | 
						|
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 = []
 | 
						|
        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')
 | 
						|
        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
 | 
						|
                           }
 | 
						|
                        ],
 | 
						|
                        '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': ' - '.join([self.path_request.tsp, self.path_request.tsp_mode]),
 | 
						|
                                    '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': ' - '.join([self.path_request.tsp, self.path_request.tsp_mode]),
 | 
						|
                                    '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
 | 
						|
                           }
 | 
						|
                        ],
 | 
						|
                        '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)]
 | 
						|
    source = next(el for el in trx if el.uid == req.source)
 | 
						|
    # start the path with its source
 | 
						|
    # TODO : avoid loops due to constraints , guess name based on string,
 | 
						|
    # avoid crashing if on req is not correct
 | 
						|
    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 roadm 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 edfa 
 | 
						|
                        if el.uid.find(f'{n}'))
 | 
						|
                except StopIteration:
 | 
						|
                    msg = f'could not find node : {n} in network topology: \
 | 
						|
                        not a trx, roadm, edfa or fused element'
 | 
						|
                    logger.critical(msg)
 | 
						|
                    raise ValueError(msg)
 | 
						|
        # extend path list without repeating source -> skip first element in the list
 | 
						|
        try:
 | 
						|
            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)
 | 
						|
    for el in path:
 | 
						|
        si = el(si)
 | 
						|
        if show :
 | 
						|
            print(el)
 | 
						|
    return path
 | 
						|
 | 
						|
 | 
						|
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','transponder-type',\
 | 
						|
        'transponder-mode','baud rate (Gbaud)', 'input power (dBm)','path',\
 | 
						|
        'OSNR@bandwidth','OSNR@0.1nm','SNR@bandwidth','SNR@0.1nm','Pass?'))
 | 
						|
    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']
 | 
						|
        pth        = ' | '.join([ e['path-route-object']['unnumbered-hop']['node-id']
 | 
						|
                 for e in p['path-properties']['path-route-objects']])
 | 
						|
 | 
						|
        [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:
 | 
						|
        [minosnr, baud_rate] = next([m['OSNR'] , m['baud_rate']]  
 | 
						|
            for m in equipment['Transceiver'][tsp].mode if  m['format']==mode)
 | 
						|
        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')
 | 
						|
        if isinstance(output_snr, str):
 | 
						|
            isok = ''
 | 
						|
        else:
 | 
						|
            isok = output_snr >= minosnr
 | 
						|
        mywriter.writerow((path_id,
 | 
						|
            source,
 | 
						|
            destination,
 | 
						|
            tsp,
 | 
						|
            mode,
 | 
						|
            baud_rate*1e-9,
 | 
						|
            round(lin2db(power)+30,2),
 | 
						|
            pth,
 | 
						|
            output_osnrbandwidth,
 | 
						|
            output_osnr,
 | 
						|
            output_snrbandwidth,
 | 
						|
            output_snr,
 | 
						|
            isok
 | 
						|
            ))
 | 
						|
 | 
						|
 | 
						|
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)))
 | 
						|
        # 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:
 | 
						|
            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) :
 | 
						|
    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 |