mirror of
				https://github.com/Telecominfraproject/oopt-gnpy.git
				synced 2025-10-30 17:47:50 +00:00 
			
		
		
		
	 bad893bf86
			
		
	
	bad893bf86
	
	
	
		
			
			this sort of verification should be covered by automatic tests since this is a verification of the correct behaviour of the spectrum_selection function Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com> Change-Id: I76dd3bcad74085e1cd36ecb6503dad0271b61b80
		
			
				
	
	
		
			431 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python3
 | ||
| # -*- coding: utf-8 -*-
 | ||
| 
 | ||
| """
 | ||
| gnpy.topology.spectrum_assignment
 | ||
| =================================
 | ||
| 
 | ||
| This module contains the :class:`Oms` and :class:`Bitmap` classes and methods to
 | ||
| select and assign spectrum. The :func:`spectrum_selection` function identifies the free
 | ||
| slots and :func:`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
 | ||
| from gnpy.core.exceptions import ServiceError, SpectrumError
 | ||
| from gnpy.topology.request import compute_spectrum_slot_vs_bandwidth
 | ||
| 
 | ||
| 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)
 | ||
|         elif len(bitmap) == len(self.freq_index):
 | ||
|             self.bitmap = bitmap
 | ||
|         else:
 | ||
|             raise SpectrumError(f'bitmap is not consistant with f_min{f_min} - n: {n_min} and f_max{f_max}- n :{n_max}')
 | ||
| 
 | ||
|     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 between 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 = []
 | ||
|         self.nb_channels = 0
 | ||
|         self.service_list = []
 | ||
|     # TODO
 | ||
| 
 | ||
|     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, elem):
 | ||
|         """ records oms elements
 | ||
|         """
 | ||
|         self.el_id_list.append(elem.uid)
 | ||
|         self.el_list.append(elem)
 | ||
| 
 | ||
|     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 enable 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
 | ||
|         """
 | ||
|         if not isinstance(nvalue, int):
 | ||
|             raise SpectrumError(f'N must be a signed integer, got {nvalue}')
 | ||
|         if not isinstance(mvalue, int):
 | ||
|             raise SpectrumError(f'M must be an integer, got {mvalue}')
 | ||
|         if mvalue <= 0:
 | ||
|             raise SpectrumError(f'M must be positive, got {mvalue}')
 | ||
|         if nvalue > self.spectrum_bitmap.freq_index_max:
 | ||
|             raise SpectrumError(f'N {nvalue} over the upper spectrum boundary')
 | ||
|         if nvalue < self.spectrum_bitmap.freq_index_min:
 | ||
|             raise SpectrumError(f'N {nvalue} below the lower spectrum boundary')
 | ||
|         startn, stopn = mvalue_to_slots(nvalue, mvalue)
 | ||
|         if stopn > self.spectrum_bitmap.n_max:
 | ||
|             raise SpectrumError(f'N {nvalue}, M {mvalue} over the N spectrum bitmap bounds')
 | ||
|         if startn <= self.spectrum_bitmap.n_min:
 | ||
|             raise SpectrumError(f'N {nvalue}, M {mvalue} below the N spectrum bitmap bounds')
 | ||
|         self.spectrum_bitmap.bitmap[self.spectrum_bitmap.geti(startn):self.spectrum_bitmap.geti(stopn) + 1] = [0] * (stopn - startn + 1)
 | ||
| 
 | ||
|     def add_service(self, service_id, nb_wl):
 | ||
|         """ record service and mark spectrum as occupied
 | ||
|         """
 | ||
|         self.service_list.append(service_id)
 | ||
|         self.nb_channels += nb_wl
 | ||
| 
 | ||
| 
 | ||
| def frequency_to_n(freq, grid=0.00625e12):
 | ||
|     """ converts frequency into the n value (ITU grid)
 | ||
|         reference to Recommendation G.694.1 (02/12), Figure I.3
 | ||
|         https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
 | ||
| 
 | ||
|     >>> frequency_to_n(193.1375e12)
 | ||
|     6
 | ||
|     >>> frequency_to_n(193.225e12)
 | ||
|     20
 | ||
| 
 | ||
|     """
 | ||
|     return (int)((freq - 193.1e12) / grid)
 | ||
| 
 | ||
| 
 | ||
| def nvalue_to_frequency(nvalue, grid=0.00625e12):
 | ||
|     """ converts n value into a frequency
 | ||
|         reference to Recommendation G.694.1 (02/12), Table 1
 | ||
|         https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
 | ||
| 
 | ||
|     >>> nvalue_to_frequency(6)
 | ||
|     193137500000000.0
 | ||
|     >>> nvalue_to_frequency(-1, 0.1e12)
 | ||
|     193000000000000.0
 | ||
| 
 | ||
|     """
 | ||
|     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
 | ||
|         reference to Recommendation G.694.1 (02/12), Figure I.3
 | ||
|         https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
 | ||
| 
 | ||
|     >>> nval, mval = slots_to_m(6, 20)
 | ||
|     >>> nval
 | ||
|     13
 | ||
|     >>> mval
 | ||
|     7
 | ||
| 
 | ||
|     """
 | ||
|     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
 | ||
|         spectrum(13,7) is (193137500000000.0, 193225000000000.0)
 | ||
|         reference to Recommendation G.694.1 (02/12), Figure I.3
 | ||
|         https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
 | ||
| 
 | ||
|     >>> fstart, fstop = m_to_freq(13, 7)
 | ||
|     >>> fstart
 | ||
|     193137500000000.0
 | ||
|     >>> fstop
 | ||
|     193225000000000.0
 | ||
| 
 | ||
|     """
 | ||
|     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)
 | ||
| 
 | ||
|                 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)
 | ||
|     reversed_oms(oms_list)
 | ||
|     return oms_list
 | ||
| 
 | ||
| 
 | ||
| def reversed_oms(oms_list):
 | ||
|     """ identifies reversed OMS
 | ||
|         only applicable for non parallel OMS
 | ||
|     """
 | ||
|     for oms in oms_list:
 | ||
|         has_reversed = False
 | ||
|         for this_o in oms_list:
 | ||
|             if (oms.el_id_list[0] == this_o.el_id_list[-1] and
 | ||
|                     oms.el_id_list[-1] == this_o.el_id_list[0]):
 | ||
|                 oms.reversed_oms = this_o
 | ||
|                 has_reversed = True
 | ||
|                 break
 | ||
|         if not has_reversed:
 | ||
|             oms.reversed_oms = None
 | ||
| 
 | ||
| 
 | ||
| def bitmap_sum(band1, band2):
 | ||
|     """mark 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"""
 | ||
| 
 | ||
|     # 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
 | ||
|     if not path_oms:
 | ||
|         candidate = (None, None, None)
 | ||
|         return candidate, path_oms
 | ||
|     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)
 | ||
|     else:
 | ||
|         raise ServiceError('Only first_fit spectrum assignment policy is implemented.')
 | ||
| 
 | ||
| 
 | ||
| def pth_assign_spectrum(pths, rqs, oms_list, rpths):
 | ||
|     """ basic first fit assignment
 | ||
|         if reversed path are provided, means that occupation is bidir
 | ||
|     """
 | ||
|     for pth, rq, rpth in zip(pths, rqs, rpths):
 | ||
|         # computes the number of channels required
 | ||
|         if hasattr(rq, 'blocking_reason'):
 | ||
|             rq.N = None
 | ||
|             rq.M = None
 | ||
|         else:
 | ||
|             nb_wl, requested_m = compute_spectrum_slot_vs_bandwidth(rq.path_bandwidth,
 | ||
|                                                                     rq.spacing, rq.bit_rate)
 | ||
|             if getattr(rq, 'M', None) is not None:
 | ||
|                 # Consistency check between the requested M and path_bandwidth
 | ||
|                 # M value should be bigger than the computed requested_m (simple estimate)
 | ||
|                 # TODO: elaborate a more accurate estimate with nb_wl * tx_osnr + possibly guardbands in case of
 | ||
|                 # superchannel closed packing.
 | ||
|                 if requested_m > rq.M:
 | ||
|                     rq.N = None
 | ||
|                     rq.M = None
 | ||
|                     rq.blocking_reason = 'NOT_ENOUGH_RESERVED_SPECTRUM'
 | ||
|                     # need to stop here for this request and not go though spectrum selection process with requested_m
 | ||
|                     continue
 | ||
|                 # use the req.M even if requested_m is smaller
 | ||
|                 requested_m = rq.M
 | ||
|             requested_n = getattr(rq, 'N', None)
 | ||
|             (center_n, startn, stopn), path_oms = spectrum_selection(pth + rpth, oms_list, requested_m,
 | ||
|                                                                      requested_n)
 | ||
|             # if requested n and m concern already occupied spectrum the previous function returns a None candidate
 | ||
|             # if not None, center_n and start, stop frequencies are applicable to all oms of pth
 | ||
|             # checks that spectrum is not None else indicate blocking reason
 | ||
|             if center_n is not None:
 | ||
|                 for oms_elem in path_oms:
 | ||
|                     oms_list[oms_elem].assign_spectrum(center_n, requested_m)
 | ||
|                     oms_list[oms_elem].add_service(rq.request_id, nb_wl)
 | ||
|                 rq.N = center_n
 | ||
|                 rq.M = requested_m
 | ||
|             else:
 | ||
|                 rq.N = None
 | ||
|                 rq.M = None
 | ||
|                 rq.blocking_reason = 'NO_SPECTRUM'
 |