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