Refactor PCE graph PostAlgoPathValidator step 3
[transportpce.git] / pce / src / main / java / org / opendaylight / transportpce / pce / graph / PostAlgoPathValidator.java
1 /*
2  * Copyright © 2017 AT&T, 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
9 package org.opendaylight.transportpce.pce.graph;
10
11 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
12 import java.math.BigDecimal;
13 import java.math.RoundingMode;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.BitSet;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.concurrent.ExecutionException;
22 import java.util.stream.Collectors;
23 import org.jgrapht.GraphPath;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.transportpce.common.InstanceIdentifiers;
26 import org.opendaylight.transportpce.common.ResponseCodes;
27 import org.opendaylight.transportpce.common.StringConstants;
28 import org.opendaylight.transportpce.common.catalog.CatalogConstant;
29 import org.opendaylight.transportpce.common.catalog.CatalogConstant.CatalogNodeType;
30 import org.opendaylight.transportpce.common.catalog.CatalogUtils;
31 import org.opendaylight.transportpce.common.fixedflex.GridConstant;
32 import org.opendaylight.transportpce.common.fixedflex.GridUtils;
33 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
34 import org.opendaylight.transportpce.pce.constraints.PceConstraints;
35 import org.opendaylight.transportpce.pce.constraints.PceConstraints.ResourcePair;
36 import org.opendaylight.transportpce.pce.networkanalyzer.PceLink;
37 import org.opendaylight.transportpce.pce.networkanalyzer.PceNode;
38 import org.opendaylight.transportpce.pce.networkanalyzer.PceResult;
39 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignment;
40 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignmentBuilder;
41 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.topology.rev211210.TerminationPoint1;
42 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev211210.OpenroadmLinkType;
43 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev211210.OpenroadmNodeType;
44 import org.opendaylight.yang.gen.v1.http.org.openroadm.otn.common.types.rev210924.OpucnTribSlotDef;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.rev180226.NodeId;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.topology.rev180226.LinkId;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
48 import org.opendaylight.yangtools.yang.common.Uint16;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class PostAlgoPathValidator {
53     /* Logging. */
54     private static final Logger LOG = LoggerFactory.getLogger(PostAlgoPathValidator.class);
55
56     public static final Long CONST_OSNR = 1L;
57     public static final double SYS_MARGIN = 0;
58     private Double tpceCalculatedMargin = 0.0;
59     private final NetworkTransactionService networkTransactionService;
60
61     public PostAlgoPathValidator(NetworkTransactionService networkTransactionService) {
62         this.networkTransactionService = networkTransactionService;
63     }
64
65     @SuppressWarnings("fallthrough")
66     @SuppressFBWarnings(
67         value = "SF_SWITCH_FALLTHROUGH",
68         justification = "intentional fallthrough")
69     public PceResult checkPath(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
70             Map<LinkId, PceLink> allPceLinks, PceResult pceResult, PceConstraints pceHardConstraints,
71             String serviceType) {
72         LOG.info("path = {}", path);
73         // check if the path is empty
74         if (path.getEdgeList().isEmpty()) {
75             pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
76             return pceResult;
77         }
78         int spectralWidthSlotNumber =
79             GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_100G);
80         SpectrumAssignment spectrumAssignment = null;
81         //variable to deal with 1GE (Nb=1) and 10GE (Nb=10) cases
82         switch (serviceType) {
83             case StringConstants.SERVICE_TYPE_OTUC2:
84             case StringConstants.SERVICE_TYPE_OTUC3:
85             case StringConstants.SERVICE_TYPE_OTUC4:
86             case StringConstants.SERVICE_TYPE_400GE:
87                 spectralWidthSlotNumber =
88                     GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_400G);
89             //fallthrough
90             case StringConstants.SERVICE_TYPE_100GE_T:
91             case StringConstants.SERVICE_TYPE_OTU4:
92                 spectrumAssignment = getSpectrumAssignment(path, allPceNodes, spectralWidthSlotNumber);
93                 pceResult.setServiceType(serviceType);
94                 if (spectrumAssignment.getBeginIndex().equals(Uint16.valueOf(0))
95                         && spectrumAssignment.getStopIndex().equals(Uint16.valueOf(0))) {
96                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
97                     pceResult.setLocalCause(PceResult.LocalCause.NO_PATH_EXISTS);
98                     return pceResult;
99                 }
100                 if (spectrumAssignment.getFlexGrid()) {
101                     LOG.debug("Spectrum assignment flexgrid mode");
102                     pceResult.setResultWavelength(GridConstant.IRRELEVANT_WAVELENGTH_NUMBER);
103                 } else {
104                     LOG.debug("Spectrum assignment fixedgrid mode");
105                     pceResult.setResultWavelength(
106                         GridUtils.getWaveLengthIndexFromSpectrumAssigment(spectrumAssignment.getBeginIndex().toJava()));
107                 }
108                 pceResult.setMinFreq(GridUtils.getStartFrequencyFromIndex(spectrumAssignment.getBeginIndex().toJava()));
109                 pceResult.setMaxFreq(GridUtils.getStopFrequencyFromIndex(spectrumAssignment.getStopIndex().toJava()));
110                 LOG.debug("In PostAlgoPathValidator: spectrum assignment found {} {}", spectrumAssignment, path);
111
112                 // Check the OSNR
113                 CatalogUtils cu = new CatalogUtils(networkTransactionService);
114                 if (cu.isCatalogFilled()) {
115                     double margin1 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
116                             StringConstants.SERVICE_DIRECTION_AZ, cu);
117                     double margin2 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
118                             StringConstants.SERVICE_DIRECTION_ZA, cu);
119                     if (margin1 < 0 || margin2 < 0 || margin1 == Double.NEGATIVE_INFINITY
120                             || margin2 == Double.NEGATIVE_INFINITY) {
121                         pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
122                         pceResult.setLocalCause(PceResult.LocalCause.OUT_OF_SPEC_OSNR);
123                         return pceResult;
124                     }
125                     this.tpceCalculatedMargin = Math.min(margin1, margin2);
126                     LOG.info(
127                         "In PostAlgoPathValidator: Minimum margin estimated by tpce on AtoZ and ZtoA path is of  {} dB",
128                         this.tpceCalculatedMargin);
129                 } else {
130                     this.tpceCalculatedMargin = 0.0;
131                     LOG.info("In PostAlgoPathValidator: Operational mode Catalog not filled, delegate OSNR calculation"
132                         + " to GNPy and margin set to 0");
133                 }
134                 // Check if MaxLatency is defined in the hard constraints
135                 if (pceHardConstraints.getMaxLatency() != -1
136                         && !checkLatency(pceHardConstraints.getMaxLatency(), path)) {
137                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
138                     pceResult.setLocalCause(PceResult.LocalCause.TOO_HIGH_LATENCY);
139                     return pceResult;
140                 }
141                 // Check if nodes are included in the hard constraints
142                 if (!checkInclude(path, pceHardConstraints)) {
143                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
144                     pceResult.setLocalCause(PceResult.LocalCause.HD_NODE_INCLUDE);
145                     return pceResult;
146                 }
147                 // TODO here other post algo validations can be added
148                 // more data can be sent to PceGraph module via PceResult structure if required
149                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
150                 pceResult.setLocalCause(PceResult.LocalCause.NONE);
151                 return pceResult;
152             case StringConstants.SERVICE_TYPE_100GE_M:
153             case StringConstants.SERVICE_TYPE_10GE:
154             case StringConstants.SERVICE_TYPE_1GE:
155                 int tribSlotNb = Map.of(
156                         StringConstants.SERVICE_TYPE_100GE_M, 20,
157                         StringConstants.SERVICE_TYPE_10GE, 8,
158                         StringConstants.SERVICE_TYPE_1GE, 1)
159                     .get(serviceType);
160                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
161                 pceResult.setServiceType(serviceType);
162                 Map<String, List<Uint16>> tribSlot = chooseTribSlot(path, allPceNodes, tribSlotNb);
163                 Map<String, Uint16> tribPort = chooseTribPort(path, allPceNodes, tribSlot, tribSlotNb);
164                 List<OpucnTribSlotDef> resultTribPortTribSlot = getMinMaxTpTs(tribPort, tribSlot);
165                 if (resultTribPortTribSlot.get(0) != null && resultTribPortTribSlot.get(1) != null) {
166                     pceResult.setResultTribPortTribSlot(resultTribPortTribSlot);
167                     pceResult.setRC(ResponseCodes.RESPONSE_OK);
168                     LOG.info("In PostAlgoPathValidator: found TribPort {} - tribSlot {} - tribSlotNb {}",
169                         tribPort, tribSlot, tribSlotNb);
170                 }
171                 return pceResult;
172             case StringConstants.SERVICE_TYPE_ODU4:
173             case StringConstants.SERVICE_TYPE_ODUC2:
174             case StringConstants.SERVICE_TYPE_ODUC3:
175             case StringConstants.SERVICE_TYPE_ODUC4:
176             case StringConstants.SERVICE_TYPE_100GE_S:
177                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
178                 pceResult.setServiceType(serviceType);
179                 LOG.info("In PostAlgoPathValidator: ODU4/ODUCn path found {}", path);
180                 return pceResult;
181             default:
182                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
183                 LOG.warn("In PostAlgoPathValidator checkPath: unsupported serviceType {} found {}",
184                     serviceType, path);
185                 return pceResult;
186         }
187     }
188
189     // Check the latency
190     private boolean checkLatency(Long maxLatency, GraphPath<String, PceGraphEdge> path) {
191         double latency = 0;
192         for (PceGraphEdge edge : path.getEdgeList()) {
193             if (edge.link() == null || edge.link().getLatency() == null) {
194                 LOG.warn("- In checkLatency: the link {} does not contain latency field",
195                     edge.link().getLinkId().getValue());
196                 return false;
197             }
198             latency += edge.link().getLatency();
199             LOG.debug("- In checkLatency: latency of {} = {} units", edge.link().getLinkId().getValue(), latency);
200         }
201         return (latency < maxLatency);
202     }
203
204     // Check the inclusion if it is defined in the hard constraints
205     private boolean checkInclude(GraphPath<String, PceGraphEdge> path, PceConstraints pceHardConstraintsInput) {
206         List<ResourcePair> listToInclude = pceHardConstraintsInput.getListToInclude()
207             .stream().sorted((rp1, rp2) -> rp1.getName().compareTo(rp2.getName()))
208             .collect(Collectors.toList());
209         if (listToInclude.isEmpty()) {
210             return true;
211         }
212         List<PceGraphEdge> pathEdges = path.getEdgeList();
213         LOG.debug(" in checkInclude vertex list: [{}]", path.getVertexList());
214         List<String> listOfElementsSubNode = new ArrayList<>();
215         listOfElementsSubNode.add(pathEdges.get(0).link().getsourceNetworkSupNodeId());
216         listOfElementsSubNode.addAll(
217             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.NODE, pceHardConstraintsInput));
218         List<String> listOfElementsCLLI = new ArrayList<>();
219         listOfElementsCLLI.add(pathEdges.get(0).link().getsourceCLLI());
220         listOfElementsCLLI.addAll(
221             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.CLLI, pceHardConstraintsInput));
222         List<String> listOfElementsSRLG = new ArrayList<>();
223         // first link is XPONDEROUTPUT, no SRLG for it
224         listOfElementsSRLG.add("NONE");
225         listOfElementsSRLG.addAll(
226             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.SRLG, pceHardConstraintsInput));
227         // validation: check each type for each element
228         return listOfElementsSubNode.containsAll(
229                 listToInclude
230                     .stream().filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
231                     .map(ResourcePair::getName).collect(Collectors.toList()))
232             && listOfElementsSRLG.containsAll(
233                 listToInclude
234                     .stream().filter(rp -> PceConstraints.ResourceType.SRLG.equals(rp.getType()))
235                     .map(ResourcePair::getName).collect(Collectors.toList()))
236             && listOfElementsCLLI.containsAll(
237                 listToInclude
238                     .stream().filter(rp -> PceConstraints.ResourceType.CLLI.equals(rp.getType()))
239                     .map(ResourcePair::getName).collect(Collectors.toList()));
240     }
241
242     private List<String> listOfElementsBuild(List<PceGraphEdge> pathEdges, PceConstraints.ResourceType type,
243             PceConstraints pceHardConstraints) {
244         List<String> listOfElements = new ArrayList<>();
245         for (PceGraphEdge link : pathEdges) {
246             switch (type) {
247                 case NODE:
248                     listOfElements.add(link.link().getdestNetworkSupNodeId());
249                     break;
250                 case CLLI:
251                     listOfElements.add(link.link().getdestCLLI());
252                     break;
253                 case SRLG:
254                     if (link.link().getlinkType() != OpenroadmLinkType.ROADMTOROADM) {
255                         listOfElements.add("NONE");
256                         break;
257                     }
258                     // srlg of link is List<Long>. But in this algo we need string representation of
259                     // one SRLG
260                     // this should be any SRLG mentioned in include constraints if any of them if
261                     // mentioned
262                     boolean found = false;
263                     for (Long srlg : link.link().getsrlgList()) {
264                         String srlgStr = String.valueOf(srlg);
265                         if (pceHardConstraints.getSRLGnames().contains(srlgStr)) {
266                             listOfElements.add(srlgStr);
267                             LOG.info("listOfElementsBuild. FOUND SRLG {} in link {}", srlgStr, link.link());
268                             found = true;
269                         }
270                     }
271                     if (!found) {
272                         // there is no specific srlg to include. thus add to list just the first one
273                         listOfElements.add("NONE");
274                     }
275                     break;
276                 default:
277                     LOG.debug("listOfElementsBuild unsupported resource type");
278             }
279         }
280         return listOfElements;
281     }
282
283     private Map<String, Uint16> chooseTribPort(GraphPath<String,
284             PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, Map<String, List<Uint16>> tribSlotMap, int nbSlot) {
285         LOG.debug("In choosetribPort: edgeList = {} ", path.getEdgeList());
286         Map<String, Uint16> tribPortMap = new HashMap<>();
287         for (PceGraphEdge edge : path.getEdgeList()) {
288             List<Uint16> srcTpnPool =
289                 allPceNodes
290                     .get(edge.link().getSourceId())
291                     .getAvailableTribPorts()
292                     .get(edge.link().getSourceTP().getValue());
293             List<Uint16> destTpnPool =
294                 allPceNodes
295                     .get(edge.link().getDestId())
296                     .getAvailableTribPorts()
297                     .get(edge.link().getDestTP().getValue());
298             List<Uint16> commonEdgeTpnPool = new ArrayList<>();
299             for (Uint16 srcTpn : srcTpnPool) {
300                 if (destTpnPool.contains(srcTpn)) {
301                     commonEdgeTpnPool.add(srcTpn);
302                 }
303             }
304             if (commonEdgeTpnPool.isEmpty()) {
305                 continue;
306             }
307             Integer startTribSlot = tribSlotMap.values().stream().findFirst().get().get(0).toJava();
308             Integer tribPort = (int) Math.ceil((double)startTribSlot / nbSlot);
309             for (Uint16 commonTribPort : commonEdgeTpnPool) {
310                 if (tribPort.equals(commonTribPort.toJava())) {
311                     tribPortMap.put(edge.link().getLinkId().getValue(), commonTribPort);
312                 }
313             }
314         }
315         tribPortMap.forEach((k,v) -> LOG.info("TribPortMap : k = {}, v = {}", k, v));
316         return tribPortMap;
317     }
318
319     private Map<String, List<Uint16>> chooseTribSlot(GraphPath<String,
320             PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, int nbSlot) {
321         LOG.debug("In choosetribSlot: edgeList = {} ", path.getEdgeList());
322         Map<String, List<Uint16>> tribSlotMap = new HashMap<>();
323         for (PceGraphEdge edge : path.getEdgeList()) {
324             List<Uint16> srcTsPool =
325                 allPceNodes
326                     .get(edge.link().getSourceId())
327                     .getAvailableTribSlots()
328                     .get(edge.link().getSourceTP().getValue());
329             List<Uint16> destTsPool =
330                 allPceNodes
331                     .get(edge.link().getDestId())
332                     .getAvailableTribSlots()
333                     .get(edge.link().getDestTP().getValue());
334             List<Uint16> commonEdgeTsPoolList = new ArrayList<>();
335             List<Uint16> tribSlotList = new ArrayList<>();
336             for (Uint16 integer : srcTsPool) {
337                 if (destTsPool.contains(integer)) {
338                     commonEdgeTsPoolList.add(integer);
339                 }
340             }
341             Collections.sort(commonEdgeTsPoolList);
342             List<Uint16> commonGoodStartEdgeTsPoolList = new ArrayList<>();
343             for (Uint16 startEdgeTsPool : commonEdgeTsPoolList) {
344                 if (Integer.valueOf(1).equals(startEdgeTsPool.toJava() % nbSlot) || nbSlot == 1) {
345                     commonGoodStartEdgeTsPoolList.add(startEdgeTsPool);
346                 }
347             }
348             Collections.sort(commonGoodStartEdgeTsPoolList);
349             boolean goodTsList = false;
350             for (Uint16 goodStartTsPool : commonGoodStartEdgeTsPoolList) {
351                 int goodStartIndex = commonEdgeTsPoolList.indexOf(Uint16.valueOf(goodStartTsPool.intValue()));
352                 if (!goodTsList && commonEdgeTsPoolList.size() - goodStartIndex >= nbSlot) {
353                     for (int i = 0; i < nbSlot; i++) {
354                         if (!commonEdgeTsPoolList.get(goodStartIndex + i)
355                                 .equals(Uint16.valueOf(goodStartTsPool.toJava() + i))) {
356                             goodTsList = false;
357                             tribSlotList.clear();
358                             break;
359                         }
360                         tribSlotList.add(commonEdgeTsPoolList.get(goodStartIndex + i));
361                         goodTsList = true;
362                     }
363                 }
364             }
365             tribSlotMap.put(edge.link().getLinkId().getValue(), tribSlotList);
366         }
367         tribSlotMap.forEach((k,v) -> LOG.info("TribSlotMap : k = {}, v = {}", k, v));
368         return tribSlotMap;
369     }
370
371     private List<OpucnTribSlotDef> getMinMaxTpTs(Map<String, Uint16> tribPort, Map<String, List<Uint16>> tribSlot) {
372         String tribport = tribPort.values().toArray()[0].toString();
373         List<Uint16> tsList = (List<Uint16>) tribSlot.values().toArray()[0];
374         return new ArrayList<>(List.of(
375             OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(0).toString())),
376             OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(tsList.size() - 1).toString()))));
377     }
378
379     private double checkOSNR(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
380             Map<LinkId, PceLink> allPceLinks, String serviceType, String direction, CatalogUtils cu) {
381         switch (direction) {
382             case StringConstants.SERVICE_DIRECTION_AZ:
383                 return checkOSNRaz(path, allPceNodes, allPceLinks, serviceType, cu);
384             case StringConstants.SERVICE_DIRECTION_ZA:
385                 return checkOSNRza(path, allPceNodes, allPceLinks, serviceType, cu);
386             default:
387                 LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported direction {}", direction);
388                 return 0.0;
389         }
390     }
391
392     /**
393      * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
394      *
395      * @param path                      the AtoZ path provided by the PCE.
396      * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
397      * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
398      * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
399      * @param cu                        CatalogUtils instance.
400      * @return the calculated margin according to the Transponder performances and path impairments.
401      */
402     private double checkOSNRaz(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
403             Map<LinkId, PceLink> allPceLinks, String serviceType, CatalogUtils cu) {
404         double spacing = 50.0;
405         double calcPdl2 = 0;
406         double calcOsnrdB = 0;
407         double calcCd = 0;
408         double calcPmd2 = 0;
409         double calcOnsrLin = 0.0001;
410         double margin = 0;
411         double pwrIn = -60.0;
412         double pwrOut = -60.0;
413         boolean transponderPresent = false;
414         List<String> vertices = path.getVertexList();
415         List<PceGraphEdge> edges = path.getEdgeList();
416         // LOOP that scans the different Nodes/Links of the path and calculates
417         // associated degradations
418         // using CatalogUtils primitives to retrieve physical parameters and make a
419         // first level calculation
420         int bypassDegree = 0;
421         for (int pathElement = 0; pathElement < 2; pathElement++) {
422             bypassDegree = 0;
423             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
424             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement + 1)));
425             LOG.debug("loop of check OSNR direction AZ, Path Element = {}", pathElement);
426             switch (currentNode.getORNodeType()) {
427                 case XPONDER:
428                     LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
429                     transponderPresent = true;
430                     Map<String, Double> results = calcXpdrOSNR(cu,
431                         pathElement == 0
432                             // First transponder on the Path (TX side) / Last Xponder of the path (RX side)
433                             ? edges.get(pathElement).link().getSourceTP().getValue()
434                             : edges.get(pathElement - 1).link().getDestTP().getValue(),
435                         serviceType, currentNode, nextNode, vertices.get(pathElement), pathElement);
436                     calcOnsrLin = results.get("calcOnsrLin");
437                     spacing = results.get("spacing");
438                     break;
439                 case SRG:
440                     LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", pathElement);
441                     // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
442                     // Xponder to Xponder) node element of the path is the ADD SRG.
443                     if (edges.get(pathElement).link().getlinkType() != OpenroadmLinkType.ADDLINK) {
444                         LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
445                             currentNode.getNodeId(), pathElement);
446                     }
447                     pwrIn = 0.0;
448                     Map<String, Double> impairments = calcAddContrib(
449                         cu, currentNode, edges.get(pathElement + 1).link(),
450                         calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
451                     calcCd = impairments.get("calcCd");
452                     calcPmd2 = impairments.get("calcPmd2");
453                     calcPdl2 = impairments.get("calcPdl2");
454                     calcOnsrLin = impairments.get("calcOnsrLin");
455                     pwrOut = impairments.get("pwrOut");
456                     LOG.debug("loop of check OSNR direction AZ: SRG, pathElement = {} link {} Pout = {}",
457                         pathElement, pathElement + 1, pwrOut);
458                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
459                         return -1.0;
460                     }
461                     // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
462                     // The Degree is not considered. This means we must bypass the add-link (ADD)
463                     // and the next node (Degree) which are not considered in the impairments.
464                     pathElement++;
465                     bypassDegree = 1;
466                     break;
467                 case DEGREE:
468                 default:
469                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
470             }
471         }
472         for (int pathElement = 2 + bypassDegree; pathElement < vertices.size() - 1; pathElement++) {
473             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
474             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement + 1)));
475             LOG.debug("loop of check OSNR direction AZ: Path Element = {}", pathElement);
476             switch (currentNode.getORNodeType()) {
477                 case SRG:
478                     LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", pathElement);
479                     // Other case is DROP, for which cnt is unchanged (.DROP)
480                     if (edges.get(pathElement - 1).link().getlinkType() != OpenroadmLinkType.DROPLINK) {
481                         LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
482                             currentNode.getNodeId(), pathElement - 1);
483                     }
484                     PceLink pceLink = edges.get(pathElement - 2).link();
485                     LOG.info("loop of check OSNR : SRG, pathElement = {} CD on preceeding link {} = {} ps",
486                         pathElement, pathElement - 2, pceLink.getcd());
487                     Map<String, Double> impairments = calcDropContrib(
488                         cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
489                     calcCd = impairments.get("calcCd");
490                     calcPmd2 = impairments.get("calcPmd2");
491                     calcPdl2 = impairments.get("calcPdl2");
492                     calcOnsrLin = impairments.get("calcOnsrLin");
493                     pwrIn = impairments.get("pwrIn");
494                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
495                         return -1.0;
496                     }
497                     // If SRG is not the first or the second element of the Path, it is the DROP
498                     // side.
499                     // After accumulated degradations are calculated, we also need to calculate
500                     // resulting OSNR in dB to pass it to the method that verifies end Xponder
501                     // performances are compatible with degradations experienced on the path
502                     try {
503                         calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
504                         LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
505                         LOG.info("Loop pathElement = {}, DROP, calcOsnrdB= {}", pathElement, calcOsnrdB);
506                     } catch (ArithmeticException e) {
507                         LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
508                             path.getEdgeList().size());
509                         return -1.0;
510                     }
511                     break;
512                 case DEGREE:
513                     if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
514                         //This is the case of DROP, ROADM degree is not considered
515                         break;
516                     }
517                     LOG.info("loop of check OSNR direction AZ: DEGREE, Path Element = {}", pathElement);
518                     Map<String, Double> impairments0 =  calcBypassContrib(
519                             cu, currentNode, nextNode,
520                             edges.get(pathElement - 1).link(), edges.get(pathElement + 1).link(),
521                             pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
522                     calcCd = impairments0.get("CD").doubleValue();
523                     calcPmd2 = impairments0.get("DGD2").doubleValue();
524                     calcPdl2 = impairments0.get("PDL2").doubleValue();
525                     calcOnsrLin = impairments0.get("ONSRLIN").doubleValue();
526                     //TODO rename impariments0 var and/or adapt catalog utils
527                     pwrIn = impairments0.get("pwrIn").doubleValue();
528                     pwrOut = impairments0.get("pwrOut").doubleValue();
529                     LOG.debug(
530                         "Loop pathElement= {}, DEGREE, calcOsnrdB= {}", pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
531                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
532                         return -1.0;
533                     }
534                     // increment pathElement so that in next step we will not point to Degree2 but
535                     // next node
536                     pathElement++;
537                     LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
538                         + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId(),
539                         nextNode.getNodeId(), calcCd, calcPmd2, calcPdl2, getOsnrDbfromOnsrLin(calcOnsrLin));
540                     break;
541                 case XPONDER:
542                     LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
543                     LOG.error("unsupported back to back transponder configuration");
544                     return -1.0;
545                 default:
546                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
547             }
548         }
549         PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(vertices.size() - 1)));
550         LOG.debug("loop of check OSNR, Path Element = {}", vertices.size() - 1);
551         switch (currentNode.getORNodeType()) {
552             case XPONDER:
553                 LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", vertices.size() - 1);
554                 transponderPresent = true;
555                 // TSP is the last of the path
556                 margin = getLastXpdrMargin(cu, edges.get(vertices.size() - 2).link().getDestTP().getValue(),
557                     serviceType, currentNode, vertices.get(vertices.size() - 1), vertices.size() - 1,
558                     calcCd, calcPmd2, calcPdl2, calcOnsrLin);
559                 break;
560             case SRG:
561                 LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", vertices.size() - 1);
562                 // Other case is DROP, for which cnt is unchanged (.DROP)
563                 if (edges.get(vertices.size() - 2).link().getlinkType() != OpenroadmLinkType.DROPLINK) {
564                     LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
565                         currentNode.getNodeId(), vertices.size() - 2);
566                 }
567                 PceLink pceLink = edges.get(vertices.size() - 3).link();
568                 LOG.info("loop of check OSNR : SRG, pathElement = {} CD on preceeding link {} = {} ps",
569                     vertices.size() - 1, vertices.size() - 3, pceLink.getcd());
570                 Map<String, Double> impairments = calcDropContrib(
571                     cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
572                 calcCd = impairments.get("calcCd");
573                 calcPmd2 = impairments.get("calcPmd2");
574                 calcPdl2 = impairments.get("calcPdl2");
575                 calcOnsrLin = impairments.get("calcOnsrLin");
576                 //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
577                 if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
578                     return -1.0;
579                 }
580                 // If SRG is not the first or the second element of the Path, it is the DROP
581                 // side.
582                 // After accumulated degradations are calculated, we also need to calculate
583                 // resulting OSNR in dB to pass it to the method that verifies end Xponder
584                 // performances are compatible with degradations experienced on the path
585                 try {
586                     calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
587                     LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
588                     LOG.info("Loop pathElement = {}, DROP, calcOsnrdB= {}", vertices.size() - 1, calcOsnrdB);
589                 } catch (ArithmeticException e) {
590                     LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
591                         path.getEdgeList().size());
592                     return -1.0;
593                 }
594                 break;
595             case DEGREE:
596             default:
597                 LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain last element");
598         }
599         LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOsnrdB = {} "
600             + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
601             calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcOsnrdB, getOsnrDbfromOnsrLin(calcOnsrLin));
602         if (!transponderPresent) {
603             LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
604                 + "that optical tunnel degradations are compatible with external transponder performances");
605             return 0.0;
606         }
607         double delta = margin - SYS_MARGIN;
608         LOG.info("In checkOSNR: Transponder Operational mode results in a residual margin of {} dB, according "
609             + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
610             delta, SYS_MARGIN);
611         String validationMessage = delta >= 0 ? "VALIDATED" : "INVALIDATED";
612         LOG.info("- In checkOSNR: A to Z Path from {} to {} {}",
613                 vertices.get(0), vertices.get(vertices.size() - 1), validationMessage);
614         return delta;
615     }
616
617     /**
618      * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
619      *
620      * @param path                      the AtoZ path provided by the PCE.
621      * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
622      * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
623      * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
624      * @param cu                        CatalogUtils instance.
625      * @return the calculated margin according to the Transponder performances and path impairments.
626      */
627     private double checkOSNRza(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
628             Map<LinkId, PceLink> allPceLinks, String serviceType, CatalogUtils cu) {
629         double spacing = 50.0;
630         double calcPdl2 = 0;
631         double calcOsnrdB = 0;
632         double calcCd = 0;
633         double calcPmd2 = 0;
634         double calcOnsrLin = 0.0001;
635         double margin = 0;
636         double pwrIn = -60.0;
637         double pwrOut = -60.0;
638         boolean transponderPresent = false;
639         List<String> vertices = path.getVertexList();
640         List<PceGraphEdge> edges = path.getEdgeList();
641         // LOOP that scans the different Nodes/Links of the path and calculates
642         // associated degradations
643         // using CatalogUtils primitives to retrieve physical parameters and make a
644         // first level calculation
645         int bypassDegree = 0;
646         for (int pathElement = vertices.size() - 1; pathElement > vertices.size() - 3; pathElement--) {
647             bypassDegree = 0;
648             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
649             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement - 1)));
650             LOG.debug("loop of check OSNR direction ZA:  Path Element = {}", pathElement);
651             switch (currentNode.getORNodeType()) {
652                 case XPONDER:
653                     LOG.debug("loop of check OSNR direction ZA: XPDR, Path Element = {}", pathElement);
654                     transponderPresent = true;
655                     Map<String, Double> results = calcXpdrOSNR(cu,
656                         pathElement == vertices.size() - 1
657                             // First transponder on the Path (TX side) / Last Xponder of the path (RX side)
658                             ? getOppPceLink(pathElement - 1, edges, allPceLinks).getSourceTP().getValue()
659                             : getOppPceLink((pathElement), edges, allPceLinks).getDestTP().getValue(),
660                         serviceType, currentNode, nextNode, vertices.get(pathElement), pathElement);
661                     calcOnsrLin = results.get("calcOnsrLin");
662                     spacing = results.get("spacing");
663                     break;
664                 case SRG:
665                     LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = {}", pathElement);
666                     // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
667                     // Xponder to Xponder) node element of the path is the ADD SRG.
668                     if (getOppPceLink(pathElement - 1, edges, allPceLinks).getlinkType() != OpenroadmLinkType.ADDLINK) {
669                         LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
670                             currentNode.getNodeId(), pathElement - 1);
671                     }
672                     pwrIn = 0.0;
673
674                     Map<String, Double> impairments = calcAddContrib(
675                         cu, currentNode, getOppPceLink(pathElement - 2, edges, allPceLinks),
676                         calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
677                     calcCd = impairments.get("calcCd");
678                     calcPmd2 = impairments.get("calcPmd2");
679                     calcPdl2 = impairments.get("calcPdl2");
680                     calcOnsrLin = impairments.get("calcOnsrLin");
681                     pwrOut = impairments.get("pwrOut");
682                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
683                         return -1.0;
684                     }
685                     // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
686                     // The Degree is not considered. This means we must bypass the add-link (ADD)
687                     // and the next node (Degree) which are not considered in the impairments.
688                     pathElement--;
689                     bypassDegree = 1;
690                     break;
691                 case DEGREE:
692                 default:
693                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
694             }
695         }
696         for (int pathElement = vertices.size() - 3 - bypassDegree; pathElement > 0; pathElement--) {
697             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
698             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement - 1)));
699             LOG.debug("loop of check OSNR direction ZA: Path Element = {}", pathElement);
700             switch (currentNode.getORNodeType()) {
701                 case SRG:
702                     LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = {}", pathElement);
703                     if (getOppPceLink(pathElement, edges, allPceLinks).getlinkType() != OpenroadmLinkType.DROPLINK) {
704                         LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
705                             currentNode.getNodeId(), pathElement);
706                     }
707                     PceLink pceLink = getOppPceLink(pathElement + 1, edges, allPceLinks);
708                     LOG.info("loop of check OSNR direction ZA: SRG, path Element = {} CD on preceeding link {} = {} ps",
709                         pathElement, pathElement + 1, pceLink.getcd());
710                     Map<String, Double> impairments = calcDropContrib(
711                         cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
712                     calcCd = impairments.get("calcCd");
713                     calcPmd2 = impairments.get("calcPmd2");
714                     calcPdl2 = impairments.get("calcPdl2");
715                     calcOnsrLin = impairments.get("calcOnsrLin");
716                     pwrIn = impairments.get("pwrIn");
717                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
718                         return -1.0;
719                     }
720                     // If SRG is not the first or the second element of the Path, it is the DROP
721                     // side.
722                     // After accumulated degradations are calculated, we also need to calculate
723                     // resulting OSNR in dB to pass it to the method that verifies end Xponder
724                     // performances are compatible with degradations experienced on the path
725                     try {
726                         calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
727                         LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
728                         LOG.info("Loop Path Element = {}, DROP, calcOsnrdB= {}", pathElement, calcOsnrdB);
729                     } catch (ArithmeticException e) {
730                         LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
731                             path.getEdgeList().size());
732                         return -1.0;
733                     }
734                     break;
735                 case DEGREE:
736                     if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
737                         //This is the case of DROP, ROADM degree is not considered
738                         break;
739                     }
740                     LOG.info("loop of check OSNR direction ZA: DEGREE, Path Element = {}", pathElement);
741                     Map<String, Double> impairments0 =  calcBypassContrib(
742                         cu, currentNode, nextNode,
743                         getOppPceLink(pathElement, edges, allPceLinks),
744                         getOppPceLink(pathElement - 2, edges, allPceLinks),
745                         pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
746                     calcCd = impairments0.get("CD").doubleValue();
747                     calcPmd2 = impairments0.get("DGD2").doubleValue();
748                     calcPdl2 = impairments0.get("PDL2").doubleValue();
749                     calcOnsrLin = impairments0.get("ONSRLIN").doubleValue();
750                     //TODO rename impariments0 var and/or adapt catalog utils
751                     pwrIn = impairments0.get("pwrIn").doubleValue();
752                     pwrOut = impairments0.get("pwrOut").doubleValue();
753                     LOG.debug("Loop Path Element = {}, DEGREE, calcOsnrdB= {}",
754                             pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
755                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
756                         return -1.0;
757                     }
758                     // increment pathElement so that in next step we will not point to Degree2 but
759                     // next node
760                     pathElement--;
761                     LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
762                         + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId(),
763                         nextNode.getNodeId(), calcCd, calcPmd2, calcPdl2, getOsnrDbfromOnsrLin(calcOnsrLin));
764                     break;
765                 case XPONDER:
766                     LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
767                     LOG.error("unsupported back to back transponder configuration");
768                     return -1.0;
769                 default:
770                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
771             }
772         }
773         PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(0)));
774         LOG.debug("loop of check OSNR direction ZA: Path Element = 0");
775         switch (currentNode.getORNodeType()) {
776             case XPONDER:
777                 LOG.debug("loop of check OSNR direction ZA: XPDR, Path Element = 0");
778                 transponderPresent = true;
779                 // TSP is the last of the path
780                 margin = getLastXpdrMargin(cu, getOppPceLink(0, edges, allPceLinks).getDestTP().getValue(),
781                     serviceType, currentNode, vertices.get(0), 0, calcCd, calcPmd2, calcPdl2, calcOnsrLin);
782                 break;
783             case SRG:
784                 LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = 0");
785                 if (getOppPceLink(0, edges, allPceLinks).getlinkType() != OpenroadmLinkType.DROPLINK) {
786                     LOG.error("Error processing Node {} for which input link 0 is not a DROPLINK Type",
787                         currentNode.getNodeId());
788                 }
789                 PceLink pceLink = getOppPceLink(1, edges, allPceLinks);
790                 LOG.info("loop of check OSNR direction ZA: SRG, path Element = 0 CD on preceeding link 1 = {} ps",
791                     pceLink.getcd());
792                 Map<String, Double> impairments = calcDropContrib(
793                     cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
794                 calcCd = impairments.get("calcCd");
795                 calcPmd2 = impairments.get("calcPmd2");
796                 calcPdl2 = impairments.get("calcPdl2");
797                 calcOnsrLin = impairments.get("calcOnsrLin");
798                 //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
799                 if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
800                     return -1.0;
801                 }
802                 // If SRG is not the first or the second element of the Path, it is the DROP
803                 // side.
804                 // After accumulated degradations are calculated, we also need to calculate
805                 // resulting OSNR in dB to pass it to the method that verifies end Xponder
806                 // performances are compatible with degradations experienced on the path
807                 try {
808                     calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
809                     LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
810                     LOG.info("Loop Path Element = 0, DROP, calcOsnrdB= {}", calcOsnrdB);
811                 } catch (ArithmeticException e) {
812                     LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
813                         path.getEdgeList().size());
814                     return -1.0;
815                 }
816                 break;
817             case DEGREE:
818             default:
819                 LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain last element");
820         }
821         LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOsnrdB = {} "
822             + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
823             calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcOsnrdB, getOsnrDbfromOnsrLin(calcOnsrLin));
824         if (!transponderPresent) {
825             LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
826                 + "that optical tunnel degradations are compatible with external transponder performances");
827             return 0.0;
828         }
829         double delta = margin - SYS_MARGIN;
830         LOG.info("In checkOSNR: Transponder Operational mode results in a residual margin of {} dB, according "
831             + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
832             delta, SYS_MARGIN);
833         String validationMessage = delta >= 0 ? "VALIDATED" : "INVALIDATED";
834         LOG.info("- In checkOSNR: Z to A Path from {} to {} {}",
835                 vertices.get(vertices.size() - 1), vertices.get(0), validationMessage);
836         return delta;
837     }
838
839     private String setOpMode(String opMode, String defaultMode) {
840         return
841             opMode == null || opMode.isEmpty() || opMode.contentEquals(StringConstants.UNKNOWN_MODE)
842                 ? defaultMode
843                 : opMode;
844     }
845
846     private PceLink getOppPceLink(Integer pathEltNber, List<PceGraphEdge> edges,
847             Map<LinkId, PceLink> allPceLinks) {
848         return allPceLinks.get(new LinkId(edges.get(pathEltNber).link().getOppositeLink()));
849     }
850
851     private String getXpdrOpMode(String nwTpId, String vertice, int pathElement, PceNode currentNode,
852             String serviceType, CatalogUtils cu) {
853         InstanceIdentifier<TerminationPoint1> nwTpIid =
854             InstanceIdentifiers.createNetworkTerminationPoint1IIDBuilder(vertice, nwTpId);
855         String opMode = cu.getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP, serviceType);
856         try {
857             if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, nwTpIid).get().isPresent()) {
858                 // If the operational mode of the Xponder is not consistent nor declared in the topology (Network TP)
859                 opMode = setOpMode(
860                     currentNode.getXponderOperationalMode(
861                         networkTransactionService
862                                 .read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
863                                 .get().get().getXpdrNetworkAttributes()),
864                     // Operational mode is found as an attribute of the network TP
865                     opMode);
866                     // Operational mode is retrieved from the service Type assuming it is supported
867                     // by the Xponder
868                 LOG.debug(
869                     "Transponder {} corresponding to path Element {} in the path has {} operational mode",
870                     currentNode.getNodeId().getValue(), pathElement, opMode);
871                 return opMode;
872             }
873         } catch (InterruptedException | ExecutionException e1) {
874             LOG.error("Issue accessing the XponderNetworkAttributes of {} for Transponder {}"
875                 + " corresponding to path Element {} in the path ",
876                 nwTpId, currentNode.getNodeId().getValue(), pathElement);
877         }
878         LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
879             + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
880         return opMode;
881     }
882
883     private double getLastXpdrMargin(
884             CatalogUtils cu, String nwTpId, String serviceType, PceNode currentNode, String vertice, int pathElement,
885             double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin) {
886         LOG.debug("Loop Path Element = {}, Step5.1, XPDR, tries calculating Margin, just before call", pathElement);
887         // Check that accumulated degradations are compatible with TSP performances
888         // According to OpenROADM spec :
889         // margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2),
890         //              getOsnrDbfromOnsrLin(calcOnsrLin));
891         // Calculation modified for pdl according to calculation in Julia's Tool
892         double calcosnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
893         LOG.info("Loop Path Element = {}, XPDR, calcosnrdB= {}", pathElement, calcosnrdB);
894         return cu.getPceRxTspParameters(
895             getXpdrOpMode(nwTpId, vertice, pathElement, currentNode, serviceType, cu),
896             calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcosnrdB);
897     }
898
899     private Map<String, Double> calcXpdrOSNR(
900             CatalogUtils cu, String nwTpId, String serviceType,
901             PceNode currentNode, PceNode nextNode, String vertice, int pathElement) {
902         // If the Xponder operational mode (setOpMode Arg1) is not consistent nor declared in the topology (Network TP)
903         // Operational mode is retrieved from the service Type assuming it is supported by the Xponder (setOpMode Arg2)
904         String opMode = getXpdrOpMode(nwTpId, vertice, pathElement, currentNode, serviceType, cu);
905         // If the operational mode of the ADD/DROP MUX is not consistent nor declared in the topology (Network TP)
906         // Operational mode is set by default to standard opMode for ADD SRGs
907         String adnMode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWWRCORE);
908         double calcOnsrLin = cu.getPceTxTspParameters(opMode, adnMode);
909         LOG.debug(
910             "Transponder {} corresponding to path Element {} is connected to SRG which has {} operational mode",
911             currentNode.getNodeId().getValue(), pathElement, adnMode);
912         LOG.info("Transponder {} corresponding to path Element {} in the path has a TX OSNR of {} dB",
913             currentNode.getNodeId().getValue(), pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
914         // Return the Tx ONSR of the Xponder which results from IB and OOB OSNR contributions
915         // and the spacing associated with Xponder operational mode that is needed to calculate OSNR
916         return Map.of(
917             "spacing", cu.getPceTxTspChannelSpacing(opMode),
918             "calcOnsrLin", calcOnsrLin);
919     }
920
921     private Map<String, Double> calcDropContrib(
922             CatalogUtils cu, PceNode currentNode, PceLink pceLink,
923             double pwrOut, double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
924         //calculation of the SRG contribution for Drop
925         Map<String, Double> impairments =
926             calcLineDegradation(cu, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
927         double pwrIn = impairments.get("pwrIn");
928         impairments = cu.getPceRoadmAmpParameters(
929             CatalogConstant.CatalogNodeType.DROP,
930             setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE),
931         // If the operational mode of the ADD/DROP MUX is not consistent or not declared in the topology (Network TP)
932         // Operational mode is set by default to standard opMode for ADD/DROP SRGs
933             pwrIn,
934             impairments.get("calcCd").doubleValue(),
935             impairments.get("calcPmd2").doubleValue(),
936             calcPdl2,
937             impairments.get("calcOnsrLin").doubleValue(),
938             spacing);
939         return Map.of(
940             "calcCd", impairments.get("CD"),
941             "calcPmd2", impairments.get("DGD2"),
942             "calcPdl2", impairments.get("PDL2"),
943             "calcOnsrLin", impairments.get("ONSRLIN"),
944             "pwrIn", pwrIn);
945     }
946
947     private Map<String, Double> calcAddContrib(
948             CatalogUtils cu, PceNode currentNode, PceLink pceLink,
949             double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
950         //calculation of the SRG contribution for Add
951         String srgMode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE);
952         // If the operational mode of the ADD/DROP MUX is not consistent or is not declared in the topology (Network TP)
953         // Operational mode is set by default to standard opMode for ADD/DROP SRGs
954         CatalogNodeType cnt = CatalogConstant.CatalogNodeType.ADD;
955         double pwrOut = cu.getPceRoadmAmpOutputPower(
956                 cnt, srgMode, pceLink.getspanLoss(), spacing, pceLink.getpowerCorrection());
957         //calculation of the SRG contribution either for Add and Drop
958         Map<String, Double> impairments = cu.getPceRoadmAmpParameters(
959             cnt, srgMode, 0, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
960         return Map.of(
961             "calcCd", impairments.get("CD"),
962             "calcPmd2", impairments.get("DGD2"),
963             "calcPdl2", impairments.get("PDL2"),
964             "calcOnsrLin", impairments.get("ONSRLIN"),
965             "pwrOut", pwrOut);
966     }
967
968     private Map<String, Double> calcBypassContrib(
969             CatalogUtils cu, PceNode currentNode, PceNode nextNode, PceLink pceLink0, PceLink pceLink1,
970             double pwrOut, double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
971         // If the operational mode of the Degree is not consistent or declared in the topology
972         // Operational mode is set by default to standard opMode for Degree
973         String degree1Mode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWMWCORE);
974         // Same for next node which is the second degree of a ROADM node
975         String degree2Mode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWMWCORE);
976         // At that time OpenROADM provides only one spec for the ROADM nodes
977         if (!degree1Mode.equals(degree2Mode)) {
978             LOG.warn("Unsupported Hybrid ROADM configuration with Degree1 {} of {} operational mode and Degree2 "
979                 + "{} of {} operational mode. Will by default use operational mode of Degree2",
980                 currentNode.getNodeId(), degree1Mode, nextNode.getNodeId(), degree2Mode);
981         }
982         Map<String, Double> impairments = calcLineDegradation(
983             cu, pceLink0, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
984         calcCd = impairments.get("calcCd");
985         calcPmd2 = impairments.get("calcPmd2");
986         calcOnsrLin = impairments.get("calcOnsrLin");
987         CatalogNodeType cnt0 = CatalogConstant.CatalogNodeType.EXPRESS;
988         double pwrIn = impairments.get("pwrIn");
989         pwrOut = cu.getPceRoadmAmpOutputPower(
990             cnt0, degree2Mode, pceLink1.getspanLoss(), spacing, pceLink1.getpowerCorrection());
991         // Adds to accumulated impairments the degradation associated with the Express
992         // path of ROADM : Degree1, express link, Degree2
993         impairments = cu.getPceRoadmAmpParameters(
994             cnt0, degree2Mode, pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
995         impairments.put("pwrIn", pwrIn);
996         impairments.put("pwrOut", pwrOut);
997         return impairments;
998     }
999     //TODO these methods might be more indicated in a catalog utils refactoring
1000
1001     private Map<String, Double> calcLineDegradation(
1002             CatalogUtils cu, PceLink pceLink,
1003             double pwrOut, double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
1004         // Calculate degradation accumulated across incoming Link and add them to
1005         // accumulated impairments
1006         // This also includes Non Linear Contribution from the path
1007         return Map.of(
1008             "pwrIn", pwrOut - pceLink.getspanLoss(),
1009             "calcCd", calcCd + pceLink.getcd(),
1010             "calcPmd2", calcPmd2 + pceLink.getpmd2(),
1011             "calcOnsrLin", calcOnsrLin + cu.calculateNLonsrContribution(pwrOut, pceLink.getLength(), spacing));
1012     }
1013
1014     private double getOsnrDbfromOnsrLin(double onsrLu) {
1015         return 10 * Math.log10(1 / onsrLu);
1016     }
1017
1018     /**
1019      * Get spectrum assignment for path.
1020      *
1021      * @param path                    the path for which we get spectrum assignment.
1022      * @param allPceNodes             all optical nodes.
1023      * @param spectralWidthSlotNumber number of slot for spectral width. Depends on
1024      *                                service type.
1025      * @return a spectrum assignment object which contains begin and end index. If
1026      *         no spectrum assignment found, beginIndex = stopIndex = 0
1027      */
1028     private SpectrumAssignment getSpectrumAssignment(GraphPath<String, PceGraphEdge> path,
1029             Map<NodeId, PceNode> allPceNodes, int spectralWidthSlotNumber) {
1030         byte[] freqMap = new byte[GridConstant.NB_OCTECTS];
1031         Arrays.fill(freqMap, (byte) GridConstant.AVAILABLE_SLOT_VALUE);
1032         BitSet result = BitSet.valueOf(freqMap);
1033         boolean isFlexGrid = true;
1034         LOG.debug("Processing path {} with length {}", path, path.getLength());
1035         for (PceGraphEdge edge : path.getEdgeList()) {
1036             NodeId srcNodeId = edge.link().getSourceId();
1037             LOG.debug("Processing source {} ", srcNodeId);
1038             if (allPceNodes.containsKey(srcNodeId)) {
1039                 PceNode pceNode = allPceNodes.get(srcNodeId);
1040                 LOG.debug("Processing PCE node {}", pceNode);
1041                 String pceNodeVersion = pceNode.getVersion();
1042                 NodeId pceNodeId = pceNode.getNodeId();
1043                 BigDecimal sltWdthGran = pceNode.getSlotWidthGranularity();
1044                 BigDecimal ctralFreqGran = pceNode.getCentralFreqGranularity();
1045                 if (StringConstants.OPENROADM_DEVICE_VERSION_1_2_1.equals(pceNodeVersion)) {
1046                     LOG.debug("Node {}: version is {} and slot width granularity is {} -> fixed grid mode",
1047                         pceNodeId, pceNodeVersion, sltWdthGran);
1048                     isFlexGrid = false;
1049                 }
1050                 if (sltWdthGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)
1051                         && ctralFreqGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)) {
1052                     LOG.debug("Node {}: version is {} with slot width granularity {} and central "
1053                             + "frequency granularity is {} -> fixed grid mode",
1054                         pceNodeId, pceNodeVersion, sltWdthGran, ctralFreqGran);
1055                     isFlexGrid = false;
1056                 }
1057                 BitSet pceNodeFreqMap = pceNode.getBitSetData();
1058                 LOG.debug("Pce node bitset {}", pceNodeFreqMap);
1059                 if (pceNodeFreqMap != null) {
1060                     result.and(pceNodeFreqMap);
1061                     LOG.debug("intermediate bitset {}", result);
1062                 }
1063             }
1064         }
1065         LOG.debug("Bitset result {}", result);
1066         return computeBestSpectrumAssignment(result, spectralWidthSlotNumber, isFlexGrid);
1067     }
1068
1069     /**
1070      * Compute spectrum assignment from spectrum occupation for spectral width.
1071      *
1072      * @param spectrumOccupation      the spectrum occupation BitSet.
1073      * @param spectralWidthSlotNumber the nb slots for spectral width.
1074      * @param isFlexGrid              true if flexible grid, false otherwise.
1075      * @return a spectrum assignment object which contains begin and stop index. If
1076      *         no spectrum assignment found, beginIndex = stopIndex = 0
1077      */
1078     private SpectrumAssignment computeBestSpectrumAssignment(
1079             BitSet spectrumOccupation, int spectralWidthSlotNumber, boolean isFlexGrid) {
1080         SpectrumAssignmentBuilder spectrumAssignmentBldr = new SpectrumAssignmentBuilder()
1081             .setBeginIndex(Uint16.valueOf(0))
1082             .setStopIndex(Uint16.valueOf(0))
1083             .setFlexGrid(isFlexGrid);
1084         BitSet referenceBitSet = new BitSet(spectralWidthSlotNumber);
1085         referenceBitSet.set(0, spectralWidthSlotNumber);
1086         //higher is the frequency, smallest is the wavelength number
1087         //in operational, the allocation is done through wavelength starting from the smallest
1088         //so we have to loop from the last element of the spectrum occupation
1089         for (int i = spectrumOccupation.size(); i >= spectralWidthSlotNumber;
1090                 i -= isFlexGrid ? spectralWidthSlotNumber : 1) {
1091             if (spectrumOccupation.get(i - spectralWidthSlotNumber, i).equals(referenceBitSet)) {
1092                 spectrumAssignmentBldr.setBeginIndex(Uint16.valueOf(i - spectralWidthSlotNumber));
1093                 spectrumAssignmentBldr.setStopIndex(Uint16.valueOf(i - 1));
1094                 break;
1095             }
1096         }
1097         return spectrumAssignmentBldr.build();
1098     }
1099
1100     public Double getTpceCalculatedMargin() {
1101         return tpceCalculatedMargin;
1102     }
1103 }