69769705805ae58e668c5e3150fa0212dd7107e8
[transportpce.git] / common / src / main / java / org / opendaylight / transportpce / common / catalog / CatalogUtils.java
1 /*
2  * Copyright © 2022 Orange, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.transportpce.common.catalog;
9
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Optional;
14 import java.util.concurrent.ExecutionException;
15 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
16 import org.opendaylight.transportpce.common.StringConstants;
17 import org.opendaylight.transportpce.common.catalog.CatalogConstant.CatalogNodeType;
18 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
19 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.link.types.rev191129.RatioDB;
20 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.ImpairmentType;
21 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.amplifier.parameters.Amplifier;
22 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.OpenroadmOperationalModes;
23 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.SpecificOperationalModes;
24 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.Amplifiers;
25 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.Roadms;
26 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.XpondersPluggables;
27 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.xponders.pluggables.XponderPluggableOpenroadmOperationalMode;
28 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.xponders.pluggables.XponderPluggableOpenroadmOperationalModeKey;
29 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.specific.operational.modes.SpecificOperationalMode;
30 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.specific.operational.modes.SpecificOperationalModeKey;
31 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.common.amplifier.drop.parameters.OpenroadmOperationalMode;
32 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.common.amplifier.drop.parameters.OpenroadmOperationalModeKey;
33 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.Add;
34 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.add.AddOpenroadmOperationalMode;
35 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.add.AddOpenroadmOperationalModeKey;
36 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.drop.parameters.Drop;
37 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.express.parameters.Express;
38 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.Penalties;
39 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.PenaltiesKey;
40 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.TXOOBOsnrKey;
41 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.power.mask.MaskPowerVsPin;
42 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.power.mask.MaskPowerVsPinKey;
43 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.OperationalModeCatalog;
44 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * Utility class for Catalog. Following methods are used to retrieve parameters
50  * from the specification catalog. They point to either openROADM or specific
51  * operational modes. They provide to the PCE, the OLM and the Renderer, the
52  * required parameters to calculate impairments and set output power levels
53  * according to the specifications.
54  *
55  */
56 public class CatalogUtils {
57
58     private static final Logger LOG = LoggerFactory.getLogger(CatalogUtils.class);
59
60     private static final String OPMODE_MISMATCH_MSG =
61         "Operational Mode {} passed to getPceRoadmAmpParameters does not correspond to an OpenROADM mode"
62         + "Parameters for amplifier and/or ROADMs can not be derived from specific-operational-modes.";
63     private static final Map<CatalogConstant.CatalogNodeType, String> CATALOGNODETYPE_OPERATIONMODEID_MAP = Map.of(
64         CatalogConstant.CatalogNodeType.ADD, CatalogConstant.MWWRCORE,
65         CatalogConstant.CatalogNodeType.DROP, CatalogConstant.MWWRCORE,
66         CatalogConstant.CatalogNodeType.EXPRESS, CatalogConstant.MWMWCORE,
67         CatalogConstant.CatalogNodeType.AMP, CatalogConstant.MWISTANDARD);
68     private static final Map<String, String> TSP_DEFAULT_OM_MAP = Map.of(
69         StringConstants.SERVICE_TYPE_100GE_T, CatalogConstant.ORW100GSC,
70         StringConstants.SERVICE_TYPE_OTU4, CatalogConstant.ORW100GSC,
71         StringConstants.SERVICE_TYPE_OTUC2,  CatalogConstant.ORW200GOFEC316GBD,
72         StringConstants.SERVICE_TYPE_OTUC3, CatalogConstant.ORW300GOFEC631GBD,
73         StringConstants.SERVICE_TYPE_OTUC4, CatalogConstant.ORW400GOFEC631GBD,
74         StringConstants.SERVICE_TYPE_400GE, CatalogConstant.ORW400GOFEC631GBD);
75
76     private final PenaltiesComparator penaltiesComparator = new PenaltiesComparator();
77     private NetworkTransactionService networkTransactionService;
78
79     public CatalogUtils(NetworkTransactionService networkTransactionService) {
80         this.networkTransactionService = networkTransactionService;
81     }
82
83     /**
84      * Following method returns default OperationalModeId for devices that do not
85      * expose them.
86      *
87      * @param catalogNodeType
88      *            identifies type of nodes in the catalog
89      * @param serviceType
90      *            allows for Xponder selecting default mode according to the rate
91      *
92      * @return a default operational mode that corresponds to initial specifications
93      *
94      */
95     public String getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType catalogNodeType,
96             String serviceType) {
97         if (CATALOGNODETYPE_OPERATIONMODEID_MAP.containsKey(catalogNodeType)) {
98             return CATALOGNODETYPE_OPERATIONMODEID_MAP.get(catalogNodeType);
99         }
100         if (!catalogNodeType.equals(CatalogConstant.CatalogNodeType.TSP)) {
101             LOG.warn("Unsupported catalogNodeType {}", catalogNodeType);
102             return "";
103         }
104         if (!TSP_DEFAULT_OM_MAP.containsKey(serviceType)) {
105             LOG.warn("Unsupported serviceType {} for TSP catalogNodeType", serviceType);
106             return "";
107         }
108         return TSP_DEFAULT_OM_MAP.get(serviceType);
109     }
110
111     /**
112      * This method retrieves channel-spacing associated with a Xponder TX.
113      *
114      * @param operationalModeId
115      *            operational-mode-Id of the Xponder (OR or Specific)
116      *
117      * @return the channel spacing used to correct OSNR contribution values from
118      *         ROADMs and amplifiers
119      * @throws RuntimeException
120      *             if operationalModeId is not described in the catalog
121      */
122
123     public double getPceTxTspChannelSpacing(String operationalModeId) {
124         double baudRate;
125         double maxRollOff;
126         if (operationalModeId.startsWith("OR")) {
127             InstanceIdentifier<XponderPluggableOpenroadmOperationalMode> omCatalogIid = InstanceIdentifier
128                 .builder(OperationalModeCatalog.class)
129                 .child(OpenroadmOperationalModes.class)
130                 .child(XpondersPluggables.class)
131                 .child(XponderPluggableOpenroadmOperationalMode.class,
132                     new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
133                 .build();
134             try {
135                 Optional<XponderPluggableOpenroadmOperationalMode> omOptional =
136                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
137                 if (omOptional.isEmpty()) {
138                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
139                     return 0.0;
140                 }
141                 XponderPluggableOpenroadmOperationalMode orTspOM = omOptional.get();
142                 maxRollOff = orTspOM.getMaxRollOff() == null ? 0 : orTspOM.getMaxRollOff().doubleValue();
143                 baudRate = orTspOM.getBaudRate().doubleValue();
144             } catch (InterruptedException | ExecutionException e) {
145                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
146                 throw new RuntimeException("Operational mode not populated in Catalog : " + omCatalogIid + " :" + e);
147             } finally {
148                 networkTransactionService.close();
149             }
150         } else {
151             // In other cases, means the mode is a non OpenROADM specific Operational Mode
152             InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
153                 .builder(OperationalModeCatalog.class)
154                 .child(SpecificOperationalModes.class)
155                 .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
156                 .build();
157             try {
158                 var somOptional =
159                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
160                 if (somOptional.isEmpty()) {
161                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
162                     return 0.0;
163                 }
164                 SpecificOperationalMode speTspOM = somOptional.get();
165                 maxRollOff = speTspOM.getMaxRollOff() == null ? 0 : speTspOM.getMaxRollOff().doubleValue();
166                 baudRate = speTspOM.getBaudRate().doubleValue();
167             } catch (InterruptedException | ExecutionException e) {
168                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
169                 throw new RuntimeException("Operational mode not populated in Catalog : " + omCatalogIid + " :" + e);
170             } finally {
171                 networkTransactionService.close();
172             }
173         }
174         if (maxRollOff == 0) {
175             if (CatalogConstant.ORW100GSC.contentEquals(operationalModeId)) {
176             // OR 100G SCFEC is the only case where rolloff factor is not mandatory in the catalog
177                 LOG.info("Operational Mode {} associated channel spacing is 50.0", operationalModeId);
178                 return 50.0;
179             }
180             LOG.warn("Missing rolloff factor (mandatory in Catalog) from Operational Mode {}: use default=0.2",
181                 operationalModeId);
182             maxRollOff = 0.2;
183         }
184         double spacing = 12.5 * Math.ceil(baudRate * (1 + maxRollOff) / 12.5);
185         LOG.info("Operational Mode {} associated channel spacing is {}", operationalModeId, spacing);
186         return spacing;
187     }
188
189     /**
190      * This method retrieves performance parameters associated with a Xponder TX.
191      *
192      * @param operationalModeId
193      *            operational-mode-Id of the Xponder (OR or Specific)
194      * @param addDropMuxOperationalModeId
195      *            operational-mode-Id of the Add-Drop bloc the XponderTX is
196      *            associated to (conditions TX-OOB OSNR value)
197      *
198      * @return the linear Optical Noise to signal Ratio
199      * @throws RuntimeException
200      *             if operationalModeId is not described in the catalog
201      */
202     public double getPceTxTspParameters(String operationalModeId, String addDropMuxOperationalModeId) {
203         double txOnsrLin = 0.0;
204         XponderPluggableOpenroadmOperationalMode orTspOM = null;
205         SpecificOperationalMode speTspOM = null;
206         RatioDB minOOBOsnrSingleChannelValue;
207         RatioDB minOOBOsnrMultiChannelValue;
208         if (operationalModeId.startsWith("OR")) {
209             InstanceIdentifier<XponderPluggableOpenroadmOperationalMode> omCatalogIid = InstanceIdentifier
210                 .builder(OperationalModeCatalog.class)
211                 .child(OpenroadmOperationalModes.class)
212                 .child(XpondersPluggables.class)
213                 .child(XponderPluggableOpenroadmOperationalMode.class,
214                     new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
215                 .build();
216             try {
217                 Optional<XponderPluggableOpenroadmOperationalMode> omOptional =
218                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
219                 if (omOptional.isEmpty()) {
220                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
221                     return 0.0;
222                 }
223                 orTspOM = omOptional.get();
224                 LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orTspOM);
225                 TXOOBOsnrKey key = new TXOOBOsnrKey(addDropMuxOperationalModeId);
226                 if (orTspOM.getMinTXOsnr() != null) {
227                     txOnsrLin = 1.0 / Math.pow(10.0, orTspOM.getMinTXOsnr().getValue().doubleValue() / 10.0);
228                 }
229                 if (orTspOM.nonnullTXOOBOsnr().get(key) == null) {
230                     return txOnsrLin;
231                 }
232                 minOOBOsnrSingleChannelValue = orTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrSingleChannelValue();
233                 minOOBOsnrMultiChannelValue =  orTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrMultiChannelValue();
234             } catch (InterruptedException | ExecutionException e) {
235                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
236                 throw new RuntimeException(
237                     "readMdSal: Error reading from operational store, Operational Mode Catalog : "
238                         + omCatalogIid + " :" + e);
239             } finally {
240                 networkTransactionService.close();
241             }
242         } else {
243             // In other cases, means the mode is a non OpenROADM specific Operational Mode
244             InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
245                 .builder(OperationalModeCatalog.class)
246                 .child(SpecificOperationalModes.class)
247                 .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
248                 .build();
249             try {
250                 var somOptional =
251                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
252                 if (somOptional.isEmpty()) {
253                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
254                     return 0.0;
255                 }
256                 speTspOM = somOptional.get();
257                 LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", speTspOM);
258                 TXOOBOsnrKey key = new TXOOBOsnrKey(addDropMuxOperationalModeId);
259                 if (speTspOM.getMinTXOsnr() != null) {
260                     txOnsrLin = 1.0 / Math.pow(10.0, speTspOM.getMinTXOsnr().getValue().doubleValue() / 10.0);
261                 }
262                 if (speTspOM.nonnullTXOOBOsnr().get(key) == null) {
263                     return txOnsrLin;
264                 }
265                 minOOBOsnrSingleChannelValue = speTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrSingleChannelValue();
266                 minOOBOsnrMultiChannelValue = speTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrMultiChannelValue();
267             } catch (InterruptedException | ExecutionException e) {
268                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
269                 throw new RuntimeException(
270                     "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
271                         + e);
272             } finally {
273                 networkTransactionService.close();
274             }
275         }
276         if (minOOBOsnrSingleChannelValue != null) {
277             txOnsrLin += 1.0 / Math.pow(10.0, minOOBOsnrSingleChannelValue.getValue().doubleValue() / 10.0);
278         }
279         if (minOOBOsnrMultiChannelValue != null) {
280             txOnsrLin += 1.0 / Math.pow(10.0, minOOBOsnrMultiChannelValue.getValue().doubleValue() / 10.0);
281         }
282         return txOnsrLin;
283     }
284
285     /**
286      * This method retrieves performance parameters associated with a Xponder RX.
287      * It calls getRxTspPenalty to evaluate the penalty associated with CD/PMD/PDL
288      * It compares expected OSNR with the OSNR resulting from the line degradation,
289      * and finally calculates and return the resulting margin.
290      *
291      * @param operationalModeId
292      *            operational-mode-Id of the Xponder (OR or Specific)
293      * @param calcCd
294      *            accumulated chromatic dispersion across the line
295      * @param calcPmd
296      *            accumulated Polarization mode dispersion across the line
297      * @param calcPdl
298      *            accumulated Polarization Dependant Loss across the line
299      * @param calcOsnrdB
300      *            Optical Signal to Noise Ratio (dB)resulting from the transmission
301      *            on the line, that shall include the Non Linear contribution
302      *
303      * @return the margin on the service path
304      * @throws RuntimeException
305      *             if operationalModeId is not described in the catalog
306      */
307     public double getPceRxTspParameters(String operationalModeId, double calcCd, double calcPmd,
308             double calcPdl, double calcOsnrdB) {
309         double rxOsnrdB = 0.0;
310         XponderPluggableOpenroadmOperationalMode orTspOM = null;
311         SpecificOperationalMode speTspOM = null;
312         Map<PenaltiesKey, Penalties> penaltiesMap = null;
313         if (operationalModeId.startsWith("OR")) {
314             var omCatalogIid = InstanceIdentifier
315                 .builder(OperationalModeCatalog.class)
316                 .child(OpenroadmOperationalModes.class)
317                 .child(XpondersPluggables.class)
318                 .child(XponderPluggableOpenroadmOperationalMode.class,
319                     new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
320                 .build();
321             try {
322                 Optional<XponderPluggableOpenroadmOperationalMode> omOptional = networkTransactionService
323                     .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
324                 if (omOptional.isPresent()) {
325                     orTspOM = omOptional.get();
326                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orTspOM);
327                     if (orTspOM.getMinRXOsnrTolerance() != null) {
328                         rxOsnrdB = orTspOM.getMinRXOsnrTolerance().getValue().doubleValue();
329                     }
330                     penaltiesMap = orTspOM.getPenalties();
331                 }
332             } catch (InterruptedException | ExecutionException e) {
333                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
334                 throw new RuntimeException(
335                     "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
336                         + e);
337             } finally {
338                 networkTransactionService.close();
339             }
340         } else {
341             // In other cases, means the mode is a non OpenROADM specific Operational Mode
342             // InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
343             var omCatalogIid = InstanceIdentifier
344                 .builder(OperationalModeCatalog.class)
345                 .child(SpecificOperationalModes.class)
346                 .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
347                 .build();
348             try {
349                 Optional<SpecificOperationalMode> somOptional = networkTransactionService
350                     .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
351                 if (somOptional.isPresent()) {
352                     speTspOM = somOptional.get();
353                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", speTspOM);
354                     if (speTspOM.getMinRXOsnrTolerance() != null) {
355                         rxOsnrdB = speTspOM.getMinRXOsnrTolerance().getValue().doubleValue();
356                     }
357                     penaltiesMap = speTspOM.getPenalties();
358                 }
359             } catch (InterruptedException | ExecutionException e) {
360                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
361                 throw new RuntimeException(
362                     "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
363                         + e);
364             } finally {
365                 networkTransactionService.close();
366             }
367         }
368         if (penaltiesMap == null) {
369             LOG.error("Unable to calculate margin as penaltyMap can not be retrieved : Operational mode not populated");
370             return -9999.9;
371         }
372         HashMap<String, Double> impairments = new HashMap<>();
373         double penalty = getRxTspPenalty(calcCd, ImpairmentType.CDPsNm, penaltiesMap);
374         impairments.put("CDpenalty", penalty);
375         double totalPenalty = penalty;
376         penalty = getRxTspPenalty(calcPmd, ImpairmentType.PMDPs, penaltiesMap);
377         impairments.put("PMD Penalty", penalty);
378         totalPenalty += penalty;
379         // Calculation according to OpenROADM specification
380         // penalty = getRxTspPenalty(calcPdl, ImpairmentType.PDLDB, penaltiesMap);
381         // Calculation modified according to Julia's Tool
382         penalty = calcPdl / 2;
383         impairments.put("PDL penalty", penalty);
384         totalPenalty += penalty;
385         // TODO for Future work since at that time we have no way to calculate the following
386         // parameters,even if penalties are defined in the OpenROADM specifications
387         //
388         // impairments.put("Colorless Drop Adjacent Xtalk Penalty", getRxTspPenalty(TBD,
389         // ImpairmentType.ColorlessDropAdjacentChannelCrosstalkGHz, penalitiesMap));
390         // impairments.put("XTalk total Power Penalty", getRxTspPenalty(TBD,
391         // ImpairmentType.CrossTalkTotalPowerDB, penalitiesMap));
392         // impairments.put("Power penalty", getRxTspPenalty(TBD,
393         // ImpairmentType.PowerDBm, penalitiesMap));
394         LOG.info("Penalty resulting from CD, PMD and PDL is {} dB with following contributions {}",
395             totalPenalty, impairments);
396         double margin = calcOsnrdB - totalPenalty - rxOsnrdB;
397         LOG.info("According to RX TSP Specification and calculated impairments Margin is {} dB ", margin);
398         if (margin < 0) {
399             LOG.warn("Negative margin shall result in PCE rejecting the analyzed path");
400         }
401         return margin;
402     }
403
404     /**
405      * This generic method is called from getPceRxTspParameters to provide the
406      * Penalties associated with CD, PMD and DGD for Xponder. It scans a penalty
407      * list that includes penalty values corresponding to an interval between an
408      * upper and a lower boundary for each of the above parameters.
409      *
410      * @param impairmentType
411      *            : the type of impairment (CD/PMD/DGD)
412      * @param calculatedParameter
413      *            calculated accumulated value on the line for the impairment
414      * @param penaltiesMap
415      *            the global map of penalties retrieved by getPceRxTspParameters
416      *            from the Xponder operational mode
417      *
418      * @return the penalty associated with accumulated impairment if it is in the
419      *         range specified in the table, a value that will lead to reject the
420      *         path if this is not the case
421      */
422
423     private double getRxTspPenalty(double calculatedParameter, ImpairmentType impairmentType,
424             Map<PenaltiesKey, Penalties> penalitiesMap) {
425         Penalties penalty = penalitiesMap.values().stream()
426             // We only keep penalties corresponding to the calculated Parameter
427             .filter(val -> val.getParameterAndUnit().getName().equals(impairmentType.getName()))
428             // we sort it according to the comparator (based on up-to-boundary)
429             .sorted(penaltiesComparator)
430             // keep only items for which up to boundary is greater than calculatedParameter
431             .filter(val -> val.getUpToBoundary().doubleValue() >= calculatedParameter)
432             // takes the immediate greater or equal value
433             .findFirst().orElse(null);
434         if (penalty == null) {
435             //means a boundary that is greater than calculatedParameter couldn't be found
436             // Out of specification!
437             return 9999.9;
438         }
439         // In spec, return penalty associated with calculatedParameter
440         LOG.info("Penalty for {} is {} dB", impairmentType, penalty.getPenaltyValue().getValue().doubleValue());
441         return penalty.getPenaltyValue().getValue().doubleValue();
442     }
443
444     /**
445      * This method retrieves performance parameters associated with ROADMs and
446      * Amplifiers. It calculates the contribution of the node to the degradation of
447      * the signal for CD, DGD, PDL, and OSNR which is calculated through a
448      * polynomial fit described in the catalog. It finally corrects the accumulated
449      * values for these parameters and return them.
450      *
451      * @param catalogNodeType
452      *            crossed node path type (ADD/DROP/EXPRESS/AMP)
453      * @param operationalModeId
454      *            operational-mode-Id of the Node (OpenROADM only)
455      * @param cd
456      *            accumulated chromatic dispersion across the line
457      * @param dgd2
458      *            Square of accumulated Group velocity dispersion across the line
459      * @param pdl2
460      *            Square of the accumulated Polarization Dependant Loss across the
461      *            line
462      * @param pwrIn
463      *            Input power required to calculate OSNR contribution of the node =
464      *            f(pwrIn)
465      * @param onsrLin
466      *            Linear Optical Noise to Signal Ratio resulting from the
467      *            transmission on the line, that shall include the Non Linear
468      *            contribution
469      * @param spacing
470      *            Interchannel spacing used for correction to calculate OSNR
471      *            contribution of the node
472      *
473      * @return Impairment, a map that provides corrected values for all calculated
474      *         parameters which includes the contribution of the node
475      *         (CD/DGD2/PDL2/ONSRLin)
476      * @throws RuntimeException
477      *             if operationalModeId is not described in the catalog
478      */
479
480     public Map<String, Double> getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType catalogNodeType,
481             String operationalModeId, double pwrIn, double cd, double dgd2, double pdl2,
482             double onsrLin, double spacing) {
483         double maxIntroducedCd;
484         double maxIntroducedPdl;
485         double maxIntroducedDgd;
486         List<Double> osnrPolynomialFits;
487         switch (catalogNodeType) {
488             case ADD:
489                 var omCatalogIid = InstanceIdentifier
490                     .builder(OperationalModeCatalog.class)
491                     .child(OpenroadmOperationalModes.class)
492                     .child(Roadms.class)
493                     .child(Add.class)
494                     .child(AddOpenroadmOperationalMode.class, new AddOpenroadmOperationalModeKey(operationalModeId))
495                     .build();
496                 try {
497                     var omOptional =
498                         networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
499                     if (omOptional.isEmpty()) {
500                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
501                         return new HashMap<>();
502                     }
503                     var orAddOM = omOptional.get();
504                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAddOM);
505                     networkTransactionService.close();
506                     maxIntroducedCd = orAddOM.getMaxIntroducedCd().doubleValue();
507                     // As per current OpenROADM Spec
508                     //maxIntroducedPdl = orAddOM.getMaxIntroducedPdl().getValue().doubleValue();
509                     // Applying calculation as provided in Julia's tool
510                     maxIntroducedPdl = Math.sqrt(0.2 * 0.2 + 0.4 * 0.4);
511                     maxIntroducedDgd = orAddOM.getMaxIntroducedDgd().doubleValue();
512                     osnrPolynomialFits = List.of(orAddOM.getIncrementalOsnr().getValue().doubleValue());
513                 } catch (InterruptedException | ExecutionException e) {
514                     onsrLin = 1;
515                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
516                         omCatalogIid);
517                     throw new RuntimeException(
518                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
519                             + omCatalogIid + " :" + e);
520                 } finally {
521                     networkTransactionService.close();
522                 }
523                 break;
524
525             case DROP:
526                 var omCatalogIid1 = InstanceIdentifier
527                     .builder(OperationalModeCatalog.class)
528                     .child(OpenroadmOperationalModes.class)
529                     .child(Roadms.class)
530                     .child(Drop.class)
531                     .child(OpenroadmOperationalMode.class, new OpenroadmOperationalModeKey(operationalModeId))
532                     .build();
533                 try {
534                     var omOptional =
535                         networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid1).get();
536                     if (omOptional.isEmpty()) {
537                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
538                         return new HashMap<>();
539                     }
540                     var orDropOM = omOptional.get();
541                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orDropOM);
542                     networkTransactionService.close();
543                     maxIntroducedCd = orDropOM.getMaxIntroducedCd().doubleValue();
544                     // As per current OpenROADM Spec
545                     // maxIntroducedPdl = orDropOM.getMaxIntroducedPdl().getValue().doubleValue();
546                     // Applying calculation as provided in Julia's tool
547                     maxIntroducedPdl = Math.sqrt(0.2 * 0.2 + 0.4 * 0.4);
548                     maxIntroducedDgd = orDropOM.getMaxIntroducedDgd().doubleValue();
549                     osnrPolynomialFits = List.of(
550                         orDropOM.getOsnrPolynomialFit().getD().doubleValue(),
551                         orDropOM.getOsnrPolynomialFit().getC().doubleValue(),
552                         orDropOM.getOsnrPolynomialFit().getB().doubleValue(),
553                         orDropOM.getOsnrPolynomialFit().getA().doubleValue());
554                 } catch (InterruptedException | ExecutionException e) {
555                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
556                         omCatalogIid1);
557                     throw new RuntimeException(
558                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
559                             + omCatalogIid1 + " :" + e);
560                 } finally {
561                     networkTransactionService.close();
562                 }
563                 break;
564
565             case EXPRESS:
566                 var omCatalogIid2 = InstanceIdentifier
567                     .builder(OperationalModeCatalog.class)
568                     .child(OpenroadmOperationalModes.class)
569                     .child(Roadms.class)
570                     .child(Express.class)
571                     .child(
572                         org.opendaylight.yang.gen.v1.http
573                             .org.openroadm.operational.mode.catalog.rev211210
574                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalMode.class,
575                         new org.opendaylight.yang.gen.v1.http
576                             .org.openroadm.operational.mode.catalog.rev211210
577                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalModeKey(
578                                 operationalModeId))
579                     .build();
580                 try {
581                     var omOptional = networkTransactionService
582                         .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid2)
583                         .get();
584                     if (omOptional.isEmpty()) {
585                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
586                         return new HashMap<>();
587                     }
588                     var orExpressOM = omOptional.get();
589                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orExpressOM);
590                     maxIntroducedCd = orExpressOM.getMaxIntroducedCd().doubleValue();
591                     // As per current OpenROADM Spec
592                     // maxIntroducedPdl = orExpressOM.getMaxIntroducedPdl().getValue().doubleValue();
593                     // Applying calculation as provided in Julia's tool
594                     maxIntroducedPdl = Math.sqrt(2 * 0.2 * 0.2 + 2 * 0.4 * 0.4);
595                     maxIntroducedDgd = orExpressOM.getMaxIntroducedDgd().doubleValue();
596                     osnrPolynomialFits = List.of(
597                         orExpressOM.getOsnrPolynomialFit().getD().doubleValue(),
598                         orExpressOM.getOsnrPolynomialFit().getC().doubleValue(),
599                         orExpressOM.getOsnrPolynomialFit().getB().doubleValue(),
600                         orExpressOM.getOsnrPolynomialFit().getA().doubleValue());
601                 } catch (InterruptedException | ExecutionException e) {
602                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
603                         omCatalogIid2);
604                     throw new RuntimeException(
605                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
606                             + omCatalogIid2 + " :" + e);
607                 } finally {
608                     networkTransactionService.close();
609                 }
610                 break;
611
612             case AMP:
613                 var omCatalogIid3 = InstanceIdentifier
614                     .builder(OperationalModeCatalog.class)
615                     .child(OpenroadmOperationalModes.class)
616                     .child(Amplifiers.class)
617                     .child(Amplifier.class)
618                     .child(OpenroadmOperationalMode.class, new OpenroadmOperationalModeKey(operationalModeId))
619                     .build();
620                 try {
621                     var omOptional = networkTransactionService
622                         .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid3)
623                         .get();
624                     if (omOptional.isEmpty()) {
625                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
626                         return new HashMap<>();
627                     }
628                     var orAmpOM = omOptional.get();
629                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAmpOM);
630                     networkTransactionService.close();
631                     maxIntroducedCd = orAmpOM.getMaxIntroducedCd().doubleValue();
632                     // As per current OpenROADM Spec
633                     // maxIntroducedPdl = orAmpOM.getMaxIntroducedPdl().getValue().doubleValue();
634                     // Applying calculation as provided in Julia's tool
635                     maxIntroducedPdl = 0.2;
636                     maxIntroducedDgd = orAmpOM.getMaxIntroducedDgd().doubleValue();
637                     osnrPolynomialFits = List.of(
638                         orAmpOM.getOsnrPolynomialFit().getD().doubleValue(),
639                         orAmpOM.getOsnrPolynomialFit().getC().doubleValue(),
640                         orAmpOM.getOsnrPolynomialFit().getB().doubleValue(),
641                         orAmpOM.getOsnrPolynomialFit().getA().doubleValue());
642                 } catch (InterruptedException | ExecutionException e) {
643                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
644                         omCatalogIid3);
645                     throw new RuntimeException(
646                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
647                             + omCatalogIid3 + " :" + e);
648                 } finally {
649                     networkTransactionService.close();
650                 }
651                 break;
652             default:
653                 LOG.error("Unsupported catalogNodeType {}", catalogNodeType);
654                 return new HashMap<>();
655         }
656         cd += maxIntroducedCd;
657         pdl2 += Math.pow(maxIntroducedPdl, 2.0);
658         dgd2 += Math.pow(maxIntroducedDgd, 2.0);
659         double pwrFact = 1;
660         double contrib = 0;
661         // We correct PwrIn to the value corresponding to a 50 GHz Bandwidth, because OpenROADM spec (polynomial fit)
662         // is based on power in 50GHz Bandwidth
663         pwrIn -= 10 * Math.log10(spacing / 50.0);
664         if (catalogNodeType != CatalogNodeType.ADD) {
665             // For add, incremental OSNR is defined for Noiseless input, BW Correction (contrib) does not apply
666             contrib = 10 * Math.log10(spacing / 50.0);
667         }
668         for (double fit : osnrPolynomialFits) {
669             contrib += pwrFact * fit;
670             pwrFact *= pwrIn;
671             // Using a for loop with multiplication instead of Math.pow optimizes the computation.
672         }
673         // Double is not strictly spoken a Mathematics commutative group because
674         // computers design limits their bits representation size.
675         // As a result, the order of arithmetic operation matters.
676         // In a sum, smallest numbers should be introduced first for a maximum of
677         // precision. In other words, the sum
678         //    10 * Math.log10(spacing / 50.0)
679         //    + osnrPolynomialFits.get(0)
680         //    + osnrPolynomialFits.get(1) * pwrIn
681         //    + osnrPolynomialFits.get(2) * Math.pow(pwrIn, 2)
682         //    + osnrPolynomialFits.get(3) * Math.pow(pwrIn, 3)
683         // is not equal to its reverse form
684         //    osnrPolynomialFits.get(3) * Math.pow(pwrIn, 3)
685         //    + osnrPolynomialFits.get(2) * Math.pow(pwrIn, 2)
686         //    + osnrPolynomialFits.get(1) * pwrIn
687         //    + osnrPolynomialFits.get(0)
688         //    + 10 * Math.log10(spacing / 50.0)
689         // and the more precise first form should be preferred here.
690         onsrLin += Math.pow(10, -contrib / 10);
691         Map<String, Double> impairments = new HashMap<>();
692         impairments.put("CD", cd);
693         impairments.put("DGD2", dgd2);
694         impairments.put("PDL2", pdl2);
695         impairments.put("ONSRLIN", onsrLin);
696         LOG.info("Accumulated CD is {} ps, DGD is {} ps and PDL is {} dB", cd, Math.sqrt(dgd2), Math.sqrt(pdl2));
697         LOG.info("Resulting OSNR is {} dB", 10 * Math.log10(1 / onsrLin));
698         return impairments;
699     }
700
701     /**
702      * This method calculates power that shall be applied at the output of ROADMs and
703      * Amplifiers. It retrieves the mask-power-vs-Pin and calculates target output
704      * power from the span loss
705      *
706      * @param catalogNodeType
707      *            crossed node path type (ADD/EXPRESS/AMP)
708      * @param operationalModeId
709      *            operational-mode-Id of the Node (OpenROADM only)
710      * @param spanLoss
711      *            spanLoss at the output of the ROADM
712      * @param powerCorrection
713      *            correction to be applied to the calculated power according to fiber type
714      * @param spacing
715      *            Interchannel spacing used for correction to calculate output power
716      * @return outputPower
717      *         Corrected output power calculated according to channel spacing
718      * @throws RuntimeException
719      *             if operationalModeId is not described in the catalog
720      */
721     public double getPceRoadmAmpOutputPower(CatalogConstant.CatalogNodeType catalogNodeType,
722             String operationalModeId, double spanLoss, double spacing, double powerCorrection) {
723         double pout = 99999.0;
724         switch (catalogNodeType) {
725             case ADD:
726                 var omCatalogIid = InstanceIdentifier
727                     .builder(OperationalModeCatalog.class)
728                     .child(OpenroadmOperationalModes.class)
729                     .child(Roadms.class)
730                     .child(Add.class)
731                     .child(AddOpenroadmOperationalMode.class, new AddOpenroadmOperationalModeKey(operationalModeId))
732                     .build();
733                 try {
734                     var omOptional =
735                         networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
736                     if (omOptional.isEmpty()) {
737                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
738                         return pout;
739                     }
740                     var orAddOM = omOptional.get();
741                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAddOM);
742                     networkTransactionService.close();
743                     var mask = orAddOM.getMaskPowerVsPin();
744                     for (Map.Entry<MaskPowerVsPinKey, MaskPowerVsPin> pw : mask.entrySet()) {
745                         if (spanLoss >= pw.getKey().getLowerBoundary().doubleValue()
746                             && spanLoss <= pw.getKey().getUpperBoundary().doubleValue()) {
747                             pout = pw.getValue().getC().doubleValue() * spanLoss + pw.getValue().getD().doubleValue()
748                                 + powerCorrection + 10 * Math.log10(spacing / 50.0);
749                             LOG.info("Calculated target Output power is {} dB in {} Bandwidth", pout, spacing);
750                             return pout;
751                         }
752                     }
753                     LOG.info("Did not succeed in calculating target Output power, SpanLoss {} dB is out of range",
754                         spanLoss);
755                 } catch (InterruptedException | ExecutionException e) {
756                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
757                         omCatalogIid);
758                     throw new RuntimeException(
759                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
760                             + omCatalogIid + " :" + e);
761                 } finally {
762                     networkTransactionService.close();
763                 }
764                 break;
765
766             case EXPRESS:
767                 var omCatalogIid2 = InstanceIdentifier
768                     .builder(OperationalModeCatalog.class)
769                     .child(OpenroadmOperationalModes.class)
770                     .child(Roadms.class)
771                     .child(Express.class)
772                     .child(
773                         org.opendaylight.yang.gen.v1.http
774                             .org.openroadm.operational.mode.catalog.rev211210
775                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalMode.class,
776                         new org.opendaylight.yang.gen.v1.http
777                             .org.openroadm.operational.mode.catalog.rev211210
778                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalModeKey(
779                                 operationalModeId))
780                     .build();
781                 try {
782                     var omOptional = networkTransactionService
783                         .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid2)
784                         .get();
785                     if (omOptional.isEmpty()) {
786                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
787                         return pout;
788                     }
789                     var orExpressOM = omOptional.get();
790                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orExpressOM);
791                     var mask = orExpressOM.getMaskPowerVsPin();
792                     for (Map.Entry<MaskPowerVsPinKey, MaskPowerVsPin> pw : mask.entrySet()) {
793                         if (spanLoss >= pw.getKey().getLowerBoundary().doubleValue()
794                                 && spanLoss <= pw.getKey().getUpperBoundary().doubleValue()) {
795                             pout = pw.getValue().getC().doubleValue() * spanLoss + pw.getValue().getD().doubleValue()
796                                 + powerCorrection + 10 * Math.log10(spacing / 50.0);
797                             LOG.info("Calculated target Output power is {} dB in {} Bandwidth", pout, spacing);
798                             return pout;
799                         }
800                     }
801                     LOG.info("Did not succeed in calculating target Output power, SpanLoss {} dB is out of range",
802                         spanLoss);
803                 } catch (InterruptedException | ExecutionException e) {
804                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
805                         omCatalogIid2);
806                     throw new RuntimeException(
807                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
808                             + omCatalogIid2 + " :" + e);
809                 } finally {
810                     networkTransactionService.close();
811                 }
812                 break;
813
814             default:
815                 LOG.error("Unsupported catalogNodeType {}", catalogNodeType);
816         }
817         return pout;
818     }
819
820     /**
821      * Non linear contribution computation.
822      * Public method calculating non linear contribution among the path from
823      * launched power and span length Formula comes from
824      * OpenROADM_OSNR_Calculation_20220610 Tool The resulting contribution shall be
825      * calculated for each fiber span and summed up
826      * @param launchedPowerdB
827      *            The power launched in the span (shall account for Optical Distribution
828      *            Frame loss)
829      * @param spanLength
830      *            Length of the span in km
831      * @param spacing
832      *            OpenROADM power and osnr contribution calculations are based on
833      *            spacing between channels : the Media Channel (MC) width
834      *
835      * @return nonLinearOnsrContributionLin
836      *         The inverse of the NL OSNR contribution converted from dB to linear value
837      */
838     public double calculateNLonsrContribution(double launchedPowerdB, double spanLength, double spacing) {
839         double constanteC0 = 0 ;
840         if (spacing > 162.5) {
841             constanteC0 = CatalogConstant.NLCONSTANTC0GT1625;
842         } else if (spacing > 112.5) {
843             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1625;
844         } else if (spacing > 100.0) {
845             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1125;
846         } else if (spacing > 87.5) {
847             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1000;
848         } else {
849             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO875;
850         }
851         double nonLinearOnsrContributionLinDb = launchedPowerdB * CatalogConstant.NLCONSTANTC1
852             + constanteC0 + CatalogConstant.NLCONSTANTCE * Math.exp(CatalogConstant.NLCONSTANTEX * spanLength);
853         LOG.info(" OSNR Non Linear contribution is {} dB", nonLinearOnsrContributionLinDb);
854         return Math.pow(10.0, -nonLinearOnsrContributionLinDb / 10);
855     }
856
857     public boolean isCatalogFilled() {
858         var omCatalogIid = InstanceIdentifier
859             .builder(OperationalModeCatalog.class)
860             .child(OpenroadmOperationalModes.class)
861             .child(Roadms.class)
862             .child(Add.class)
863             .child(AddOpenroadmOperationalMode.class, new AddOpenroadmOperationalModeKey(CatalogConstant.MWWRCORE))
864             .build();
865         try {
866             if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get().isEmpty()) {
867                 LOG.error("Operational Mode catalog is not filled");
868                 return false;
869             }
870             networkTransactionService.close();
871             return true;
872         } catch (InterruptedException | ExecutionException e) {
873             LOG.error("readMdSal: Error reading Operational Mode Catalog, catalog not filled");
874             throw new RuntimeException(
875                 "readMdSal: Error reading from operational store, Operational Mode Catalog not filled" + e);
876         } finally {
877             networkTransactionService.close();
878         }
879     }
880
881 }