mirror of
https://github.com/Telecominfraproject/oopt-gnpy.git
synced 2025-11-01 18:47:48 +00:00
Creation of the OMS structure to record spectrum assignment
oms_list contains the list of OMS and each OMS contains the list of uid of each crossed elements (ordered) each element is updated with the oms_id to which it belongs each oms contains a bitmap with frequency slots according to frequency min max defined in eqpt_config.json (SI) and in case oms are defined elsewhere, there is an alignment of grids to ease computation the build_OMS_list function builds the OMS list and implements oms attributes in all network elements Commit also contains basic functions to handle spectrum bitmap and indexes Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
This commit is contained in:
326
gnpy/core/spectrum_assignment.py
Normal file
326
gnpy/core/spectrum_assignment.py
Normal file
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
gnpy.core.spectrum_assignment
|
||||
=============================
|
||||
|
||||
This module contains the Oms and Bitmap classes and the different method to
|
||||
select and assign spectrum. Spectrum_selection function identifies the free
|
||||
slots and select_candidate selects the candidate spectrum according to
|
||||
strategy: for example first fit
|
||||
oms records its elements, and elements are updated with an oms to have
|
||||
element/oms correspondace
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
from logging import getLogger
|
||||
from math import ceil
|
||||
from gnpy.core.elements import Roadm, Transceiver
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
class Bitmap:
|
||||
""" records the spectrum occupation
|
||||
"""
|
||||
def __init__(self, f_min, f_max, grid, guardband=0.15e12, bitmap=None):
|
||||
# n is the min index including guardband. Guardband is require to be sure
|
||||
# that a channel can be assigned with center frequency fmin (means that its
|
||||
# slot occupation goes below freq_index_min
|
||||
n_min = frequency_to_n(f_min-guardband, grid)
|
||||
n_max = frequency_to_n(f_max+guardband, grid) - 1
|
||||
self.n_min = n_min
|
||||
self.n_max = n_max
|
||||
self.freq_index_min = frequency_to_n(f_min)
|
||||
self.freq_index_max = frequency_to_n(f_max)
|
||||
self.freq_index = list(range(n_min, n_max+1))
|
||||
if bitmap is None:
|
||||
self.bitmap = [1] * (n_max-n_min+1)
|
||||
else:
|
||||
if len(bitmap) == len(self.freq_index):
|
||||
self.bitmap = bitmap
|
||||
else:
|
||||
msg = f'bitmap is not consistant with f_min{f_min} - n :' +\
|
||||
f'{n_min} and f_max{f_max}- n :{n_max}'
|
||||
LOGGER.critical(msg)
|
||||
exit()
|
||||
def getn(self, i):
|
||||
""" converts the n (itu grid) into a local index
|
||||
"""
|
||||
return self.freq_index[i]
|
||||
def geti(self, nvalue):
|
||||
""" converts the local index into n (itu grid)
|
||||
"""
|
||||
return self.freq_index.index(nvalue)
|
||||
def insert_left(self, newbitmap):
|
||||
""" insert bitmap on the left to align oms bitmaps if their start frequencies are different
|
||||
"""
|
||||
self.bitmap = newbitmap + self.bitmap
|
||||
temp = list(range(self.n_min-len(newbitmap), self.n_min))
|
||||
self.freq_index = temp + self.freq_index
|
||||
self.n_min = self.freq_index[0]
|
||||
def insert_right(self, newbitmap):
|
||||
""" insert bitmap on the right to align oms bitmaps if their stop frequencies are different
|
||||
"""
|
||||
self.bitmap = self.bitmap + newbitmap
|
||||
self.freq_index = self.freq_index + list(range(self.n_max, self.n_max+len(newbitmap)))
|
||||
self.n_max = self.freq_index[-1]
|
||||
|
||||
# +'grid available_slots f_min f_max services_list')
|
||||
OMSParams = namedtuple('OMSParams', 'oms_id el_id_list el_list')
|
||||
|
||||
class OMS:
|
||||
""" OMS class is the logical container that represent a link betwoeen two adjacent ROADMs and
|
||||
records the crossed elements and the occupied spectrum
|
||||
"""
|
||||
def __init__(self, *args, **params):
|
||||
params = OMSParams(**params)
|
||||
self.oms_id = params.oms_id
|
||||
self.el_id_list = params.el_id_list
|
||||
self.el_list = params.el_list
|
||||
self.spectrum_bitmap = []
|
||||
def __str__(self):
|
||||
return '\n\t'.join([f'{type(self).__name__} {self.oms_id}',
|
||||
f'{self.el_id_list[0]} - {self.el_id_list[-1]}'])
|
||||
def __repr__(self):
|
||||
return '\n\t'.join([f'{type(self).__name__} {self.oms_id}',
|
||||
f'{self.el_id_list[0]} - {self.el_id_list[-1]}', '\n'])
|
||||
|
||||
def add_element(self, el):
|
||||
self.el_id_list.append(el.uid)
|
||||
self.el_list.append(el)
|
||||
|
||||
def update_spectrum(self, f_min, f_max, guardband=0.15e12, existing_spectrum=None,
|
||||
grid=0.00625e12):
|
||||
""" frequencies expressed in Hz
|
||||
"""
|
||||
if existing_spectrum is None:
|
||||
# add some 150 GHz margin to eable a center channel on f_min
|
||||
# use ITU-T G694.1
|
||||
# Flexible DWDM grid definition
|
||||
# For the flexible DWDM grid, the allowed frequency slots have a nominal
|
||||
# central frequency (in THz) defined by:
|
||||
# 193.1 + n × 0.00625 where n is a positive or negative integer including 0
|
||||
# and 0.00625 is the nominal central frequency granularity in THz
|
||||
# and a slot width defined by:
|
||||
# 12.5 × m where m is a positive integer and 12.5 is the slot width granularity in
|
||||
# GHz.
|
||||
# Any combination of frequency slots is allowed as long as no two frequency
|
||||
# slots overlap.
|
||||
|
||||
# TODO : add explaination on that / parametrize ....
|
||||
self.spectrum_bitmap = Bitmap(f_min, f_max, grid, guardband)
|
||||
# print(len(self.spectrum_bitmap.bitmap))
|
||||
|
||||
def assign_spectrum(self, nvalue, mvalue):
|
||||
""" change oms spectrum to mark spectrum assigned
|
||||
"""
|
||||
# print("assign_spectrum")
|
||||
# print(f'n , m :{n},{m}')
|
||||
if (nvalue is None or mvalue is None or isinstance(nvalue, float)
|
||||
or isinstance(mvalue, float) or mvalue == 0):
|
||||
msg = f'could not assign None values'
|
||||
LOGGER.critical(msg)
|
||||
exit()
|
||||
startn, stopn = mvalue_to_slots(nvalue, mvalue)
|
||||
# print(f'startn stop n {startn} , {stopn}')
|
||||
# assumes that guardbands are sufficient to ensure that assigning a center channel
|
||||
# at fmin or fmax is OK is startn > self.spectrum_bitmap.n_min
|
||||
if (nvalue <= self.spectrum_bitmap.freq_index_max and
|
||||
nvalue >= self.spectrum_bitmap.freq_index_min and
|
||||
stopn <= self.spectrum_bitmap.n_max and
|
||||
startn > self.spectrum_bitmap.n_min):
|
||||
# verification that both length are identical
|
||||
self.spectrum_bitmap.bitmap[self.spectrum_bitmap.geti(startn):self.spectrum_bitmap.geti(stopn)+1] = [0] * (stopn-startn+1)
|
||||
return True
|
||||
else:
|
||||
msg = f'Could not assign n {nvalue}, m {mvalue} values:' +\
|
||||
f' one or several slots are not available'
|
||||
LOGGER.info(msg)
|
||||
return False
|
||||
|
||||
def frequency_to_n(freq, grid=0.00625e12):
|
||||
""" converts frequency into the n value (ITU grid)
|
||||
"""
|
||||
return (int)((freq-193.1e12)/grid)
|
||||
|
||||
def nvalue_to_frequency(nvalue, grid=0.00625e12):
|
||||
""" converts n value into a frequency
|
||||
"""
|
||||
return 193.1e12 + nvalue * grid
|
||||
|
||||
def mvalue_to_slots(nvalue, mvalue):
|
||||
""" convert center n an m into start and stop n
|
||||
"""
|
||||
startn = nvalue - mvalue
|
||||
stopn = nvalue + mvalue -1
|
||||
return startn, stopn
|
||||
|
||||
def slots_to_m(startn, stopn):
|
||||
""" converts the start and stop n values to the center n and m value
|
||||
"""
|
||||
nvalue = (int)((startn+stopn+1)/2)
|
||||
mvalue = (int)((stopn-startn+1)/2)
|
||||
return nvalue, mvalue
|
||||
|
||||
def m_to_freq(nvalue, mvalue, grid=0.00625e12):
|
||||
""" converts m into frequency range
|
||||
"""
|
||||
startn, stopn = mvalue_to_slots(nvalue, mvalue)
|
||||
fstart = nvalue_to_frequency(startn, grid)
|
||||
fstop = nvalue_to_frequency(stopn+1, grid)
|
||||
return fstart, fstop
|
||||
|
||||
def align_grids(oms_list):
|
||||
""" used to apply same grid to all oms : same starting n, stop n and slot size
|
||||
out of grid slots are set to 0
|
||||
"""
|
||||
n_min = min([o.spectrum_bitmap.n_min for o in oms_list])
|
||||
n_max = max([o.spectrum_bitmap.n_max for o in oms_list])
|
||||
for this_o in oms_list:
|
||||
if (this_o.spectrum_bitmap.n_min - n_min) > 0:
|
||||
this_o.spectrum_bitmap.insert_left([0] * (this_o.spectrum_bitmap.n_min - n_min))
|
||||
if (n_max - this_o.spectrum_bitmap.n_max) > 0:
|
||||
this_o.spectrum_bitmap.insert_right([0] * (n_max - this_o.spectrum_bitmap.n_max))
|
||||
return oms_list
|
||||
|
||||
def build_oms_list(network, equipment):
|
||||
""" initialization of OMS list in the network
|
||||
an oms is build reading all intermediate nodes between two adjacent ROADMs
|
||||
each element within the list is being added an oms and oms_id to record the
|
||||
oms it belongs to.
|
||||
the function supports different spectrum width and supposes that the whole network
|
||||
works with the min range among OMSs
|
||||
"""
|
||||
oms_id = 0
|
||||
oms_list = []
|
||||
for node in [n for n in network.nodes() if isinstance(n, Roadm)]:
|
||||
for edge in network.edges([node]):
|
||||
if not isinstance(edge[1], Transceiver):
|
||||
nd_in = edge[0] # nd_in is a Roadm
|
||||
try:
|
||||
nd_in.oms_list.append(oms_id)
|
||||
except AttributeError:
|
||||
nd_in.oms_list = []
|
||||
nd_in.oms_list.append(oms_id)
|
||||
nd_out = edge[1]
|
||||
|
||||
params = {}
|
||||
params['oms_id'] = oms_id
|
||||
params['el_id_list'] = []
|
||||
params['el_list'] = []
|
||||
oms = OMS(**params)
|
||||
oms.add_element(nd_in)
|
||||
while not isinstance(nd_out, Roadm):
|
||||
oms.add_element(nd_out)
|
||||
# add an oms_id in the element
|
||||
nd_out.oms_id = oms_id
|
||||
nd_out.oms = oms
|
||||
n_temp = nd_out
|
||||
nd_out = next(n[1] for n in network.edges([n_temp]) if n[1].uid != nd_in.uid)
|
||||
nd_in = n_temp
|
||||
|
||||
oms.add_element(nd_out)
|
||||
# nd_out is a Roadm
|
||||
try:
|
||||
nd_out.oms_list.append(oms_id)
|
||||
except AttributeError:
|
||||
nd_out.oms_list = []
|
||||
nd_out.oms_list.append(oms_id)
|
||||
|
||||
# print(f'coucou2 {oms.oms_id} {oms.el_id_list[0]} {oms.el_id_list[-1]}')
|
||||
# for e in oms.el_id_list:
|
||||
# print(f' {e}')
|
||||
|
||||
# TODO do not forget to correct next line !
|
||||
# to test different grids
|
||||
# TODO move this to test
|
||||
if oms_id < 3:
|
||||
oms.update_spectrum(equipment['SI']['default'].f_min,
|
||||
equipment['SI']['default'].f_max, grid=0.00625e12)
|
||||
else:
|
||||
oms.update_spectrum(equipment['SI']['default'].f_min,
|
||||
equipment['SI']['default'].f_max, grid=0.00625e12)
|
||||
# oms.assign_spectrum(13,7) gives back (193137500000000.0, 193225000000000.0)
|
||||
# as in the example in the standard
|
||||
# oms.assign_spectrum(13,7)
|
||||
|
||||
oms_list.append(oms)
|
||||
oms_id += 1
|
||||
oms_list = align_grids(oms_list)
|
||||
return oms_list
|
||||
|
||||
def bitmap_sum(band1, band2):
|
||||
""" a functions that marks occupied bitmap by 0 if the slot is occupied in band1 or in band2
|
||||
"""
|
||||
res = []
|
||||
for i, elem in enumerate(band1):
|
||||
if band2[i] * elem == 0:
|
||||
res.append(0)
|
||||
else:
|
||||
res.append(1)
|
||||
return res
|
||||
|
||||
def spectrum_selection(pth, oms_list, requested_m, requested_n=None):
|
||||
""" collects spectrum availability and call the select_candidate function
|
||||
# step 1 collects pth spectrum availability
|
||||
# step 2 if n is not None try to assign the spectrum
|
||||
# if the spectrum is not available then sends back an "error"
|
||||
# if n is None selects candidate spectrum
|
||||
# select spectrum that fits the policy ( first fit, random, ABP...)
|
||||
# step3 returns the selection
|
||||
"""
|
||||
|
||||
# use indexes instead of ITU-T n values
|
||||
path_oms = []
|
||||
for elem in pth:
|
||||
if not isinstance(elem, Roadm) and not isinstance(elem, Transceiver):
|
||||
# only edfa, fused and fibers have oms_id attribute
|
||||
path_oms.append(elem.oms_id)
|
||||
# remove duplicate oms_id, order is not important
|
||||
path_oms = list(set(path_oms))
|
||||
# assuming all oms have same freq index
|
||||
freq_index = oms_list[path_oms[0]].spectrum_bitmap.freq_index
|
||||
freq_index_min = oms_list[path_oms[0]].spectrum_bitmap.freq_index_min
|
||||
freq_index_max = oms_list[path_oms[0]].spectrum_bitmap.freq_index_max
|
||||
|
||||
freq_availability = oms_list[path_oms[0]].spectrum_bitmap.bitmap
|
||||
for oms in path_oms[1:]:
|
||||
freq_availability = bitmap_sum(oms_list[oms].spectrum_bitmap.bitmap, freq_availability)
|
||||
if requested_n is None:
|
||||
# avoid slots reserved on the edge 0.15e-12 on both sides -> 24
|
||||
candidates = [(freq_index[i]+requested_m, freq_index[i], freq_index[i]+2*requested_m-1)
|
||||
for i in range(len(freq_availability))
|
||||
if freq_availability[i:i+2*requested_m] == [1] * (2*requested_m)
|
||||
and freq_index[i] >= freq_index_min
|
||||
and freq_index[i+2*requested_m-1] <= freq_index_max]
|
||||
|
||||
candidate = select_candidate(candidates, policy='first_fit')
|
||||
else:
|
||||
i = oms_list[path_oms[0]].spectrum_bitmap.geti(requested_n)
|
||||
# print(f'N {requested_n} i {i}')
|
||||
# print(freq_availability[i-m:i+m] )
|
||||
# print(freq_index[i-m:i+m])
|
||||
if (freq_availability[i-requested_m:i+requested_m] == [1] * (2*requested_m) and
|
||||
freq_index[i-requested_m] >= freq_index_min
|
||||
and freq_index[i+requested_m-1] <= freq_index_max):
|
||||
# candidate is the triplet center_n, startn and stopn
|
||||
candidate = (requested_n, requested_n-requested_m, requested_n+requested_m-1)
|
||||
else:
|
||||
candidate = (None, None, None)
|
||||
# print("coucou11")
|
||||
# print(candidate)
|
||||
# print(freq_availability[321:321+2*m])
|
||||
# a = [i+321 for i in range(2*m)]
|
||||
# print(a)
|
||||
# print(candidate)
|
||||
return candidate, path_oms
|
||||
|
||||
def select_candidate(candidates, policy):
|
||||
""" selects a candidate among all available spectrum
|
||||
"""
|
||||
if policy == 'first_fit':
|
||||
if candidates:
|
||||
return candidates[0]
|
||||
else:
|
||||
return (None, None, None)
|
||||
Reference in New Issue
Block a user