Autonomous impairment aware path computation
[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.RoundingMode;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.BitSet;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.concurrent.ExecutionException;
21 import java.util.stream.Collectors;
22 import org.jgrapht.GraphPath;
23 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
24 import org.opendaylight.transportpce.common.InstanceIdentifiers;
25 import org.opendaylight.transportpce.common.ResponseCodes;
26 import org.opendaylight.transportpce.common.StringConstants;
27 import org.opendaylight.transportpce.common.catalog.CatalogConstant;
28 import org.opendaylight.transportpce.common.catalog.CatalogConstant.CatalogNodeType;
29 import org.opendaylight.transportpce.common.catalog.CatalogUtils;
30 import org.opendaylight.transportpce.common.fixedflex.GridConstant;
31 import org.opendaylight.transportpce.common.fixedflex.GridUtils;
32 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
33 import org.opendaylight.transportpce.pce.constraints.PceConstraints;
34 import org.opendaylight.transportpce.pce.constraints.PceConstraints.ResourcePair;
35 import org.opendaylight.transportpce.pce.networkanalyzer.PceLink;
36 import org.opendaylight.transportpce.pce.networkanalyzer.PceNode;
37 import org.opendaylight.transportpce.pce.networkanalyzer.PceResult;
38 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignment;
39 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignmentBuilder;
40 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.topology.rev211210.TerminationPoint1;
41 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.topology.rev211210.networks.network.node.termination.point.XpdrNetworkAttributes;
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
70     public PceResult checkPath(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
71             Map<LinkId, PceLink> allPceLinks, PceResult pceResult, PceConstraints pceHardConstraints,
72             String serviceType) {
73         LOG.info("path = {}", path);
74         // check if the path is empty
75         if (path.getEdgeList().isEmpty()) {
76             pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
77             return pceResult;
78         }
79         int spectralWidthSlotNumber = GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP
80             .getOrDefault(serviceType, GridConstant.NB_SLOTS_100G);
81         SpectrumAssignment spectrumAssignment = null;
82         //variable to deal with 1GE (Nb=1) and 10GE (Nb=10) cases
83         switch (serviceType) {
84             case StringConstants.SERVICE_TYPE_OTUC2:
85             case StringConstants.SERVICE_TYPE_OTUC3:
86             case StringConstants.SERVICE_TYPE_OTUC4:
87             case StringConstants.SERVICE_TYPE_400GE:
88                 spectralWidthSlotNumber = GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP
89                     .getOrDefault(serviceType, GridConstant.NB_SLOTS_400G);
90             //fallthrough
91             case StringConstants.SERVICE_TYPE_100GE_T:
92             case StringConstants.SERVICE_TYPE_OTU4:
93                 spectrumAssignment = getSpectrumAssignment(path, allPceNodes, spectralWidthSlotNumber);
94                 pceResult.setServiceType(serviceType);
95                 if (spectrumAssignment.getBeginIndex().equals(Uint16.valueOf(0))
96                         && spectrumAssignment.getStopIndex().equals(Uint16.valueOf(0))) {
97                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
98                     pceResult.setLocalCause(PceResult.LocalCause.NO_PATH_EXISTS);
99                     return pceResult;
100                 }
101                 if (spectrumAssignment.getFlexGrid()) {
102                     LOG.debug("Spectrum assignment flexgrid mode");
103                     pceResult.setResultWavelength(GridConstant.IRRELEVANT_WAVELENGTH_NUMBER);
104                 } else {
105                     LOG.debug("Spectrum assignment fixedgrid mode");
106                     pceResult.setResultWavelength(
107                             GridUtils.getWaveLengthIndexFromSpectrumAssigment(spectrumAssignment.getBeginIndex()
108                                 .toJava()));
109                 }
110                 pceResult.setMinFreq(GridUtils.getStartFrequencyFromIndex(spectrumAssignment.getBeginIndex().toJava()));
111                 pceResult.setMaxFreq(GridUtils.getStopFrequencyFromIndex(spectrumAssignment.getStopIndex().toJava()));
112                 LOG.debug("In PostAlgoPathValidator: spectrum assignment found {} {}", spectrumAssignment, path);
113
114                 // Check the OSNR
115                 CatalogUtils cu = new CatalogUtils(networkTransactionService);
116                 if (cu.isCatalogFilled()) {
117                     double margin1 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
118                             StringConstants.SERVICE_DIRECTION_AZ, cu);
119                     double margin2 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
120                             StringConstants.SERVICE_DIRECTION_ZA, cu);
121                     if (margin1 < 0 || margin2 < 0 || margin1 == Double.NEGATIVE_INFINITY
122                             || margin2 == Double.NEGATIVE_INFINITY) {
123                         pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
124                         pceResult.setLocalCause(PceResult.LocalCause.OUT_OF_SPEC_OSNR);
125                         return pceResult;
126                     }
127                     this.tpceCalculatedMargin = Math.min(margin1, margin2);
128                     LOG.info(
129                         "In PostAlgoPathValidator: Minimum margin estimated by tpce on AtoZ and ZtoA path is of  {} dB",
130                         this.tpceCalculatedMargin);
131                 } else {
132                     this.tpceCalculatedMargin = 0.0;
133                     LOG.info("In PostAlgoPathValidator: Operational mode Catalog not filled, delegate OSNR calculation"
134                         + " to GNPy and margin set to 0");
135                 }
136
137                 // Check if MaxLatency is defined in the hard constraints
138                 if ((pceHardConstraints.getMaxLatency() != -1)
139                         && (!checkLatency(pceHardConstraints.getMaxLatency(), path))) {
140                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
141                     pceResult.setLocalCause(PceResult.LocalCause.TOO_HIGH_LATENCY);
142                     return pceResult;
143                 }
144
145                 // Check if nodes are included in the hard constraints
146                 if (!checkInclude(path, pceHardConstraints)) {
147                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
148                     pceResult.setLocalCause(PceResult.LocalCause.HD_NODE_INCLUDE);
149                     return pceResult;
150                 }
151
152                 // TODO here other post algo validations can be added
153                 // more data can be sent to PceGraph module via PceResult structure if required
154
155                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
156                 pceResult.setLocalCause(PceResult.LocalCause.NONE);
157                 break;
158             case StringConstants.SERVICE_TYPE_100GE_M:
159             case StringConstants.SERVICE_TYPE_10GE:
160             case StringConstants.SERVICE_TYPE_1GE:
161                 Map<String, Integer> tribSlotNbMap = Map.of(
162                     StringConstants.SERVICE_TYPE_100GE_M, 20,
163                     StringConstants.SERVICE_TYPE_10GE, 8,
164                     StringConstants.SERVICE_TYPE_1GE, 1);
165                 int tribSlotNb = tribSlotNbMap.get(serviceType);
166                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
167                 pceResult.setServiceType(serviceType);
168                 Map<String, List<Uint16>> tribSlot = chooseTribSlot(path, allPceNodes, tribSlotNb);
169                 Map<String, Uint16> tribPort = chooseTribPort(path, allPceNodes, tribSlot, tribSlotNb);
170                 List<OpucnTribSlotDef> resultTribPortTribSlot = getMinMaxTpTs(tribPort, tribSlot);
171
172                 if (resultTribPortTribSlot.get(0) != null && resultTribPortTribSlot.get(1) != null) {
173                     pceResult.setResultTribPortTribSlot(resultTribPortTribSlot);
174                     pceResult.setRC(ResponseCodes.RESPONSE_OK);
175                     LOG.info("In PostAlgoPathValidator: found TribPort {} - tribSlot {} - tribSlotNb {}",
176                         tribPort, tribSlot, tribSlotNb);
177                 }
178                 break;
179             case StringConstants.SERVICE_TYPE_ODU4:
180             case StringConstants.SERVICE_TYPE_ODUC2:
181             case StringConstants.SERVICE_TYPE_ODUC3:
182             case StringConstants.SERVICE_TYPE_ODUC4:
183             case StringConstants.SERVICE_TYPE_100GE_S:
184                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
185                 pceResult.setServiceType(serviceType);
186                 LOG.info("In PostAlgoPathValidator: ODU4/ODUCn path found {}", path);
187                 break;
188             default:
189                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
190                 LOG.warn("In PostAlgoPathValidator checkPath: unsupported serviceType {} found {}",
191                     serviceType, path);
192                 break;
193         }
194         return pceResult;
195     }
196
197     // Check the latency
198     private boolean checkLatency(Long maxLatency, GraphPath<String, PceGraphEdge> path) {
199         double latency = 0;
200
201         for (PceGraphEdge edge : path.getEdgeList()) {
202             if (edge.link() == null || edge.link().getLatency() == null) {
203                 LOG.warn("- In checkLatency: the link {} does not contain latency field",
204                     edge.link().getLinkId().getValue());
205                 return false;
206             }
207             latency += edge.link().getLatency();
208             LOG.debug("- In checkLatency: latency of {} = {} units", edge.link().getLinkId().getValue(), latency);
209         }
210         return (latency < maxLatency);
211     }
212
213     // Check the inclusion if it is defined in the hard constraints
214     private boolean checkInclude(GraphPath<String, PceGraphEdge> path, PceConstraints pceHardConstraintsInput) {
215         List<ResourcePair> listToInclude = pceHardConstraintsInput.getListToInclude()
216             .stream().sorted((rp1, rp2) -> rp1.getName().compareTo(rp2.getName()))
217             .collect(Collectors.toList());
218         if (listToInclude.isEmpty()) {
219             return true;
220         }
221
222         List<PceGraphEdge> pathEdges = path.getEdgeList();
223         LOG.debug(" in checkInclude vertex list: [{}]", path.getVertexList());
224
225         List<String> listOfElementsSubNode = new ArrayList<>();
226         listOfElementsSubNode.add(pathEdges.get(0).link().getsourceNetworkSupNodeId());
227         listOfElementsSubNode.addAll(listOfElementsBuild(pathEdges, PceConstraints.ResourceType.NODE,
228             pceHardConstraintsInput));
229
230         List<String> listOfElementsCLLI = new ArrayList<>();
231         listOfElementsCLLI.add(pathEdges.get(0).link().getsourceCLLI());
232         listOfElementsCLLI.addAll(listOfElementsBuild(pathEdges, PceConstraints.ResourceType.CLLI,
233             pceHardConstraintsInput));
234
235         List<String> listOfElementsSRLG = new ArrayList<>();
236         // first link is XPONDEROUTPUT, no SRLG for it
237         listOfElementsSRLG.add("NONE");
238         listOfElementsSRLG.addAll(listOfElementsBuild(pathEdges, PceConstraints.ResourceType.SRLG,
239             pceHardConstraintsInput));
240
241         // validation: check each type for each element
242         List<String> listNodeToInclude = listToInclude
243                 .stream().filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
244                 .map(ResourcePair::getName).collect(Collectors.toList());
245         List<String> listSrlgToInclude = listToInclude
246                 .stream().filter(rp -> PceConstraints.ResourceType.SRLG.equals(rp.getType()))
247                 .map(ResourcePair::getName).collect(Collectors.toList());
248         List<String> listClliToInclude = listToInclude
249                 .stream().filter(rp -> PceConstraints.ResourceType.CLLI.equals(rp.getType()))
250                 .map(ResourcePair::getName).collect(Collectors.toList());
251
252         return listOfElementsSubNode.containsAll(listNodeToInclude)
253                 && listOfElementsSRLG.containsAll(listSrlgToInclude)
254                 && listOfElementsCLLI.containsAll(listClliToInclude);
255     }
256
257     private List<String> listOfElementsBuild(List<PceGraphEdge> pathEdges, PceConstraints.ResourceType type,
258         PceConstraints pceHardConstraints) {
259
260         List<String> listOfElements = new ArrayList<>();
261         for (PceGraphEdge link : pathEdges) {
262             switch (type) {
263                 case NODE:
264                     listOfElements.add(link.link().getdestNetworkSupNodeId());
265                     break;
266                 case CLLI:
267                     listOfElements.add(link.link().getdestCLLI());
268                     break;
269                 case SRLG:
270                     if (link.link().getlinkType() != OpenroadmLinkType.ROADMTOROADM) {
271                         listOfElements.add("NONE");
272                         break;
273                     }
274                     // srlg of link is List<Long>. But in this algo we need string representation of
275                     // one SRLG
276                     // this should be any SRLG mentioned in include constraints if any of them if
277                     // mentioned
278                     boolean found = false;
279                     for (Long srlg : link.link().getsrlgList()) {
280                         String srlgStr = String.valueOf(srlg);
281                         if (pceHardConstraints.getSRLGnames().contains(srlgStr)) {
282                             listOfElements.add(srlgStr);
283                             LOG.info("listOfElementsBuild. FOUND SRLG {} in link {}", srlgStr, link.link());
284                             found = true;
285                         }
286                     }
287                     if (!found) {
288                         // there is no specific srlg to include. thus add to list just the first one
289                         listOfElements.add("NONE");
290                     }
291                     break;
292                 default:
293                     LOG.debug("listOfElementsBuild unsupported resource type");
294             }
295         }
296         return listOfElements;
297     }
298
299     private Map<String, Uint16> chooseTribPort(GraphPath<String,
300         PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, Map<String, List<Uint16>> tribSlotMap, int nbSlot) {
301         LOG.debug("In choosetribPort: edgeList = {} ", path.getEdgeList());
302         Map<String, Uint16> tribPortMap = new HashMap<>();
303
304         for (PceGraphEdge edge : path.getEdgeList()) {
305             NodeId linkSrcNode = edge.link().getSourceId();
306             String linkSrcTp = edge.link().getSourceTP().getValue();
307             NodeId linkDestNode = edge.link().getDestId();
308             String linkDestTp = edge.link().getDestTP().getValue();
309             PceNode pceOtnNodeSrc = allPceNodes.get(linkSrcNode);
310             PceNode pceOtnNodeDest = allPceNodes.get(linkDestNode);
311             List<Uint16> srcTpnPool = pceOtnNodeSrc.getAvailableTribPorts().get(linkSrcTp);
312             List<Uint16> destTpnPool = pceOtnNodeDest.getAvailableTribPorts().get(linkDestTp);
313             List<Uint16> commonEdgeTpnPool = new ArrayList<>();
314             for (Uint16 srcTpn : srcTpnPool) {
315                 if (destTpnPool.contains(srcTpn)) {
316                     commonEdgeTpnPool.add(srcTpn);
317                 }
318             }
319
320             if (!commonEdgeTpnPool.isEmpty()) {
321                 Integer startTribSlot = tribSlotMap.values().stream().findFirst().get().get(0).toJava();
322                 Integer tribPort = (int) Math.ceil((double)startTribSlot / nbSlot);
323                 for (Uint16 commonTribPort : commonEdgeTpnPool) {
324                     if (tribPort.equals(commonTribPort.toJava())) {
325                         tribPortMap.put(edge.link().getLinkId().getValue(), commonTribPort);
326                     }
327                 }
328             }
329         }
330         tribPortMap.forEach((k,v) -> LOG.info("TribPortMap : k = {}, v = {}", k, v));
331         return tribPortMap;
332     }
333
334     private Map<String, List<Uint16>> chooseTribSlot(GraphPath<String,
335         PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, int nbSlot) {
336         LOG.debug("In choosetribSlot: edgeList = {} ", path.getEdgeList());
337         Map<String, List<Uint16>> tribSlotMap = new HashMap<>();
338
339         for (PceGraphEdge edge : path.getEdgeList()) {
340             NodeId linkSrcNode = edge.link().getSourceId();
341             String linkSrcTp = edge.link().getSourceTP().getValue();
342             NodeId linkDestNode = edge.link().getDestId();
343             String linkDestTp = edge.link().getDestTP().getValue();
344             PceNode pceOtnNodeSrc = allPceNodes.get(linkSrcNode);
345             PceNode pceOtnNodeDest = allPceNodes.get(linkDestNode);
346             List<Uint16> srcTsPool = pceOtnNodeSrc.getAvailableTribSlots().get(linkSrcTp);
347             List<Uint16> destTsPool = pceOtnNodeDest.getAvailableTribSlots().get(linkDestTp);
348             List<Uint16> commonEdgeTsPoolList = new ArrayList<>();
349             List<Uint16> tribSlotList = new ArrayList<>();
350             for (Uint16 integer : srcTsPool) {
351                 if (destTsPool.contains(integer)) {
352                     commonEdgeTsPoolList.add(integer);
353                 }
354             }
355             Collections.sort(commonEdgeTsPoolList);
356             List<Uint16> commonGoodStartEdgeTsPoolList = new ArrayList<>();
357             for (Uint16 startEdgeTsPool : commonEdgeTsPoolList) {
358                 if (Integer.valueOf(1).equals(startEdgeTsPool.toJava() % nbSlot)
359                         || nbSlot == 1) {
360                     commonGoodStartEdgeTsPoolList.add(startEdgeTsPool);
361                 }
362             }
363             Collections.sort(commonGoodStartEdgeTsPoolList);
364             boolean goodTsList = false;
365             for (Uint16 goodStartTsPool : commonGoodStartEdgeTsPoolList) {
366                 int goodStartIndex = commonEdgeTsPoolList.indexOf(Uint16.valueOf(goodStartTsPool.intValue()));
367                 if (!goodTsList && commonEdgeTsPoolList.size() - goodStartIndex >= nbSlot) {
368                     for (int i = 0; i < nbSlot; i++) {
369                         if (!commonEdgeTsPoolList.get(goodStartIndex + i)
370                                 .equals(Uint16.valueOf(goodStartTsPool.toJava() + i))) {
371                             goodTsList = false;
372                             tribSlotList.clear();
373                             break;
374                         }
375                         tribSlotList.add(commonEdgeTsPoolList.get(goodStartIndex + i));
376                         goodTsList = true;
377                     }
378                 }
379             }
380             tribSlotMap.put(edge.link().getLinkId().getValue(), tribSlotList);
381         }
382         tribSlotMap.forEach((k,v) -> LOG.info("TribSlotMap : k = {}, v = {}", k, v));
383         return tribSlotMap;
384     }
385
386     private List<OpucnTribSlotDef> getMinMaxTpTs(Map<String, Uint16> tribPort, Map<String, List<Uint16>> tribSlot) {
387         String tribport = tribPort.values().toArray()[0].toString();
388         @SuppressWarnings("unchecked")
389         List<Uint16> tsList = (List<Uint16>) tribSlot.values().toArray()[0];
390         OpucnTribSlotDef minOpucnTs = OpucnTribSlotDef
391             .getDefaultInstance(String.join(".", tribport, tsList.get(0).toString()));
392         OpucnTribSlotDef maxOpucnTs = OpucnTribSlotDef
393             .getDefaultInstance(String.join(".", tribport, tsList.get(tsList.size() - 1).toString()));
394         List<OpucnTribSlotDef> minmaxTpTsList = new ArrayList<>();
395         minmaxTpTsList.add(minOpucnTs);
396         minmaxTpTsList.add(maxOpucnTs);
397         return minmaxTpTsList;
398     }
399
400     /**
401      * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
402      *
403      * @param path                      the AtoZ path provided by the PCE.
404      * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
405      * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
406      * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
407      * @param direction                 The direction used to scan provided path in a direct or reverse way.
408      * @param cu                        CatalogUtils instance.
409      * @return the calculated margin according to the Transponder performances and path impairments.
410      */
411     @SuppressWarnings("deprecation")
412     @edu.umd.cs.findbugs.annotations.SuppressWarnings("DLS_DEAD_LOCAL_STORE")
413     private double checkOSNR(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
414         Map<LinkId, PceLink> allPceLinks, String serviceType, String direction, CatalogUtils cu) {
415         double spacing = 50.0;
416         double calcPdl2 = 0;
417         double calcOsnrdB = 0;
418         double calcCd = 0;
419         double calcPmd2 = 0;
420         double calcOnsrLin = 0.0001;
421         double margin = 0;
422         double pwrIn = -60.0;
423         double pwrOut = -60.0;
424         int pathElement = 0;
425         int increment = 1;
426         int offsetLink = 0;
427         boolean transponderPresent = false;
428         if ((StringConstants.SERVICE_DIRECTION_ZA).equals(direction)) {
429             increment = - 1;
430             offsetLink = -1;
431         }
432         CatalogNodeType cnt;
433         List<String> vertices = path.getVertexList();
434         List<PceGraphEdge> edges = path.getEdgeList();
435         String opMode = "";
436         // LOOP that scans the different Nodes/Links of the path and calculates
437         // associated degradations
438         // using CatalogUtils primitives to retrieve physical parameters and make a
439         // first level calculation
440         Map<String, Double> impairments = new HashMap<>();
441         for (int n = 0; n < vertices.size(); n++) {
442             InstanceIdentifier<TerminationPoint1> nwTpIid;
443             PceNode nextNode = null;
444             if ((StringConstants.SERVICE_DIRECTION_AZ).equals(direction)) {
445                 pathElement = n ;
446             } else {
447                 pathElement = vertices.size() - n - 1;
448             }
449             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
450             if (((pathElement != vertices.size() - 1) && (StringConstants.SERVICE_DIRECTION_AZ).equals(direction))
451                     || ((pathElement != 0) && (StringConstants.SERVICE_DIRECTION_ZA).equals(direction))) {
452                 nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement + increment)));
453             }
454             LOG.debug("loop of check OSNR, n = {} Path Element = {}", n, pathElement);
455             switch (currentNode.getORNodeType()) {
456                 case XPONDER:
457                     transponderPresent = true;
458                     String nwTpId = "";
459                     if (((pathElement == 0) && (StringConstants.SERVICE_DIRECTION_AZ).equals(direction))
460                             || ((pathElement == (vertices.size() - 1)) && (StringConstants.SERVICE_DIRECTION_ZA)
461                                 .equals(direction))) {
462                         //First Xponder of the path TX side
463                         nwTpId = getAppropriatePceLink((pathElement + offsetLink), edges, allPceLinks, direction)
464                             .getSourceTP().getValue();
465                     } else {
466                         // last Xponder of the path (RX side)
467                         nwTpId = getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks, direction)
468                         .getDestTP().getValue();
469                     }
470                     nwTpIid = InstanceIdentifiers.createNetworkTerminationPoint1IIDBuilder(
471                             vertices.get(pathElement), nwTpId);
472                     LOG.debug("loop of check OSNR : XPDR, n = {} Path Element = {}", n, pathElement);
473                     try {
474                         if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
475                                 .get().isPresent()) {
476                             XpdrNetworkAttributes xna = networkTransactionService
477                                 .read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
478                                 .get().get().getXpdrNetworkAttributes();
479                             // If the operational mode of the Xponder is not consistent or
480                             // if the operational mode of the Xponder is not declared in the topology
481                             // (Network TP)
482                             if (currentNode.getXponderOperationalMode(xna).contentEquals(StringConstants.UNKNOWN_MODE)
483                                     || currentNode.getXponderOperationalMode(xna) == null
484                                     || currentNode.getXponderOperationalMode(xna).isEmpty()) {
485                                 // Operational mode is retrieved from the service Type assuming it is supported
486                                 // by the Xponder
487                                 opMode = cu.getPceOperationalModeFromServiceType(
488                                     CatalogConstant.CatalogNodeType.TSP, serviceType);
489                             } else {
490                                 // Operational mode is found as an attribute of the network TP
491                                 opMode = currentNode.getXponderOperationalMode(xna);
492                             }
493                             LOG.debug("Transponder {} corresponding to path Element {} in the path has {} operational "
494                                     + "mode", currentNode.getNodeId().getValue(), pathElement, opMode);
495                         } else {
496                             LOG.error("Issue accessing the XponderNetworkAttributes of {} for Transponder {}"
497                                 + " corresponding to path Element {} in the path ",
498                                 nwTpId, currentNode.getNodeId().getValue(), pathElement);
499                             opMode = cu.getPceOperationalModeFromServiceType(
500                                 CatalogConstant.CatalogNodeType.TSP, serviceType);
501                             LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
502                                 + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
503                         }
504                     } catch (InterruptedException | ExecutionException e1) {
505                         opMode = cu.getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
506                             serviceType);
507                         LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
508                             + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
509                     }
510                     // If TSP is the last of the path
511                     if (((pathElement == (vertices.size() - 1))
512                             && (StringConstants.SERVICE_DIRECTION_AZ).equals(direction))
513                             || ((pathElement == 0) && (StringConstants.SERVICE_DIRECTION_ZA).equals(direction))) {
514                         LOG.debug("Loop n = {}, Step5.1, XPDR, tries calculating Margin, just before call", n);
515                         // Check that accumulated degradations are compatible with TSP performances
516                         // According to OpenROADM spec :
517                         // margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2),
518                         //              getOsnrDbfromOnsrLin(calcOnsrLin));
519                         // Calculation modified for pdl according to calculation in Julia's Tool
520                         margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2),
521                             (Math.sqrt(calcPdl2)), getOsnrDbfromOnsrLin(calcOnsrLin));
522                         LOG.info("Loop n = {}, XPDR, calcosnrdB= {}", n, getOsnrDbfromOnsrLin(calcOnsrLin));
523                     } else {
524                         // TSP is first element of the path . To correctly evaluate the TX OOB OSNR from
525                         // its operational mode, we need to know the type of ADD/DROP Mux it is
526                         // connected to
527                         String adnMode = "";
528                         // If the operational mode of the ADD/DROP MUX is not consistent or
529                         // if the operational mode of the ADD/DROP MUX is not declared in the topology
530                         // (Network TP)
531                         if (StringConstants.UNKNOWN_MODE.equals(nextNode.getOperationalMode())
532                                 || nextNode.getOperationalMode() == null
533                                 || nextNode.getOperationalMode().isEmpty()) {
534                             // Operational mode is set by default to standard opMode for ADD SRGs
535                             adnMode = CatalogConstant.MWWRCORE;
536                         } else {
537                             // Operational mode is found in SRG attributes of the Node
538                             adnMode = nextNode.getOperationalMode();
539                         }
540                         LOG.debug("Transponder {} corresponding to path Element {} in the path is connected to SRG "
541                             + "which has {} operational mode", currentNode.getNodeId().getValue(), pathElement,
542                             adnMode);
543                         // Retrieve the Tx ONSR of the Xponder which results from IB and OOB OSNR
544                         // contributions
545                         calcOnsrLin = cu.getPceTxTspParameters(opMode, adnMode);
546                         // Retrieve the spacing associated with Xponder operational mode that is needed
547                         // to calculate OSNR
548                         spacing = cu.getPceTxTspChannelSpacing(opMode);
549                         LOG.info("Transponder {} corresponding to path Element {} in the path has a TX OSNR of {} dB",
550                             currentNode.getNodeId().getValue(), pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
551                     }
552                     break;
553                 case SRG:
554                     String srgMode = "";
555                     // If the operational mode of the ADD/DROP MUX is not consistent or
556                     // if the operational mode of the ADD/DROP MUX is not declared in the topology
557                     // (Network TP)
558                     if (StringConstants.UNKNOWN_MODE.equals(currentNode.getOperationalMode())
559                             || currentNode.getOperationalMode() == null
560                             || currentNode.getOperationalMode().isEmpty()) {
561                         // Operational mode is set by default to standard opMode for ADD/DROP SRGs
562                         srgMode = CatalogConstant.MWWRCORE;
563                     } else {
564                         // Operational mode is found in SRG attributes of the Node
565                         srgMode = currentNode.getOperationalMode();
566                     }
567                     cnt = CatalogConstant.CatalogNodeType.DROP;
568                     LOG.debug("loop of check OSNR : SRG, n = {} Path Element = {}", n, pathElement);
569                     if ((pathElement <= 1) && (StringConstants.SERVICE_DIRECTION_AZ).equals(direction)
570                             || (pathElement >= vertices.size() - 2)
571                             && (StringConstants.SERVICE_DIRECTION_ZA).equals(direction)) {
572                         // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
573                         // Xponder to Xponder) node element of the path is the ADD SRG.
574                         if (!(getAppropriatePceLink((pathElement + offsetLink), edges, allPceLinks, direction)
575                                 .getlinkType() == OpenroadmLinkType.ADDLINK)) {
576                             LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
577                                 currentNode.getNodeId().toString(), pathElement + offsetLink);
578                         }
579                         cnt = CatalogConstant.CatalogNodeType.ADD;
580                         pwrIn = 0.0;
581                         pwrOut = cu.getPceRoadmAmpOutputPower(cnt, srgMode,
582                             getAppropriatePceLink((pathElement + 1 + offsetLink * 3), edges, allPceLinks, direction)
583                             .getspanLoss(), spacing,
584                             getAppropriatePceLink((pathElement + 1 + offsetLink * 3), edges, allPceLinks, direction)
585                             .getpowerCorrection());
586                         LOG.debug("loop of check OSNR : SRG, n = {} link {} Pout = {}",
587                             pathElement, pathElement + 1 + offsetLink * 3, pwrOut);
588                     } else {
589                         // Other case is DROP, for which cnt is unchanged (.DROP)
590                         if (!(getAppropriatePceLink((pathElement - 1 - offsetLink), edges, allPceLinks, direction)
591                                 .getlinkType() == OpenroadmLinkType.DROPLINK)) {
592                             LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
593                                 currentNode.getNodeId().toString(), pathElement - 1 - offsetLink);
594                         }
595                         pwrIn = pwrOut - getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
596                             direction).getspanLoss();
597                         // Calculate degradation accumulated across incoming Link and add them to
598                         // accumulated impairments
599                         calcCd += getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
600                             direction).getcd();
601                         LOG.info("loop of check OSNR : SRG, n = {} CD on preceeding link {} = {} ps", pathElement,
602                             pathElement - offsetLink * 3 - 2, getAppropriatePceLink((pathElement - offsetLink * 3 - 2),
603                                 edges, allPceLinks, direction).getcd());
604                         calcPmd2 += getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
605                             direction).getpmd2();
606                         // This also includes Non Linear Contribution from the path
607                         calcOnsrLin += cu.calculateNLonsrContribution(pwrOut, getAppropriatePceLink((pathElement
608                             - offsetLink * 3 - 2), edges, allPceLinks, direction).getLength(), spacing);
609                     }
610                     //calculation of the SRG contribution either for Add and Drop
611                     impairments = cu.getPceRoadmAmpParameters(cnt, srgMode,
612                         pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
613                     calcCd = impairments.get("CD").doubleValue();
614                     calcPmd2 = impairments.get("DGD2").doubleValue();
615                     calcPdl2 = impairments.get("PDL2").doubleValue();
616                     calcOnsrLin = impairments.get("ONSRLIN").doubleValue();
617                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
618                         return -1.0;
619                     }
620                     if (pathElement > 1) {
621                         // If SRG is not the first or the second element of the Path, it is the DROP
622                         // side.
623                         // After accumulated degradations are calculated, we also need to calculate
624                         // resulting OSNR in dB to pass it to the method that verifies end Xponder
625                         // performances are compatible with degradations experienced on the path
626                         try {
627                             calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
628                             LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
629                             LOG.info("Loop n = {}, DROP, calcOsnrdB= {}", n, calcOsnrdB);
630                         } catch (ArithmeticException e) {
631                             LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
632                                 path.getEdgeList().size());
633                             return -1.0;
634                         }
635                     }
636                     if (CatalogConstant.CatalogNodeType.ADD.equals(cnt)) {
637                         // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
638                         // The Degree is not considered. This means we must bypass the add-link (ADD)
639                         // and the next node (Degree) which are not considered in the impairments.
640                         n++;
641                     }
642                     impairments.clear();
643                     break;
644                 case DEGREE:
645                     if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
646                         //This is the case of DROP, ROADM degree is not considered
647                         break;
648                     }
649                     LOG.info("loop of check OSNR : DEGREE, n = {} Path Element = {}", n, pathElement);
650                     cnt = CatalogConstant.CatalogNodeType.EXPRESS;
651                     String degree1Mode = "";
652                     String degree2Mode = "";
653                     // If the operational mode of the Degree is not consistent or if the operational
654                     // mode is not declared in the topology
655                     if (StringConstants.UNKNOWN_MODE.equals(currentNode.getOperationalMode())
656                             || currentNode.getOperationalMode() == null
657                             || currentNode.getOperationalMode().isEmpty()) {
658                         // Operational mode is set by default to standard opMode for Degree
659                         degree1Mode = CatalogConstant.MWMWCORE;
660                     } else {
661                         // Operational mode is found in degree-attributes of the Node
662                         degree1Mode = currentNode.getOperationalMode();
663                     }
664                     // Same for next node which is the second degree of a ROADM node
665                     if (StringConstants.UNKNOWN_MODE.equals(nextNode.getOperationalMode())
666                             || nextNode.getOperationalMode() == null
667                             || nextNode.getOperationalMode().isEmpty()) {
668                         degree2Mode = CatalogConstant.MWMWCORE;
669                     } else {
670                         degree2Mode = currentNode.getOperationalMode();
671                     }
672                     // At that time OpenROADM provides only one spec for the ROADM nodes
673                     if (!degree1Mode.equals(degree2Mode)) {
674                         LOG.info("Unsupported Hybrid ROADM configuration with Degree1 {} of {} operational mode"
675                             + "and Degree2 {} of {} operational mode. Will by default use operational mode"
676                             + "of Degree2", currentNode.getNodeId().toString(),
677                             degree1Mode, nextNode.getNodeId().toString(), degree2Mode);
678                     }
679                     pwrIn = pwrOut - getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks,
680                         direction).getspanLoss();
681                     // Calculate degradation accumulated across incoming Link and add them to
682                     // accumulated impairments
683                     calcCd += getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks, direction)
684                         .getcd();
685                     calcPmd2 += getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks, direction)
686                         .getpmd2();
687                     // This also includes Non Linear Contribution from the path
688                     calcOnsrLin += cu.calculateNLonsrContribution(pwrOut, getAppropriatePceLink((pathElement
689                         - offsetLink - 1), edges, allPceLinks, direction).getLength(), spacing);
690                     // Calculate output power for next span (Output of degree 2)
691                     pwrOut = cu.getPceRoadmAmpOutputPower(cnt, degree2Mode, getAppropriatePceLink((pathElement
692                         + 3 * offsetLink + 1), edges, allPceLinks, direction).getspanLoss(), spacing,
693                         getAppropriatePceLink((pathElement + 3 * offsetLink + 1), edges, allPceLinks, direction)
694                         .getpowerCorrection());
695                     // Adds to accumulated impairments the degradation associated with the Express
696                     // path of ROADM : Degree1, express link, Degree2
697                     impairments = cu.getPceRoadmAmpParameters(cnt, degree2Mode,
698                         pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
699                     calcCd = impairments.get("CD").doubleValue();
700                     calcPmd2 = impairments.get("DGD2").doubleValue();
701                     calcPdl2 = impairments.get("PDL2").doubleValue();
702                     calcOnsrLin = impairments.get("ONSRLIN").doubleValue();
703                     LOG.debug("Loop n = {}, DEGREE, calcOsnrdB= {}", n, getOsnrDbfromOnsrLin(calcOnsrLin));
704                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
705                         return -1.0;
706                     }
707                     // increment pathElement so that in next step we will not point to Degree2 but
708                     // next node
709                     n++;
710                     LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
711                         + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId().toString(),
712                         nextNode.getNodeId().toString(), calcCd, calcPmd2, calcPdl2, getOsnrDbfromOnsrLin(calcOnsrLin));
713                     break;
714                 default:
715                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
716             }
717         }
718         LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOsnrdB = {} "
719             + "dB and ONSR dB exterapolated from calcosnrlin = {}"
720             + " including non linear contributions",
721             calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcOsnrdB, getOsnrDbfromOnsrLin(calcOnsrLin));
722         if (!transponderPresent) {
723             LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
724                 + "that optical tunnel degradations are compatible with external transponder performances");
725             return 0.0;
726         }
727         LOG.info("In checkOSNR: Transponder Operational mode {} results in a residual margin of {} dB, according "
728             + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
729             opMode, margin - SYS_MARGIN, SYS_MARGIN);
730         String validationMessage = "INVALIDATED";
731         if ((margin - SYS_MARGIN) >= 0) {
732             validationMessage = "VALIDATED";
733         }
734         if ((StringConstants.SERVICE_DIRECTION_AZ).equals(direction)) {
735             LOG.info("- In checkOSNR: A to Z Path from {} to {} {}", vertices.get(0),
736                 vertices.get(vertices.size() - 1), validationMessage);
737         } else {
738             LOG.info("- In checkOSNR: Z to A Path from {} to {} {}", vertices.get(vertices.size() - 1),
739                 vertices.get(0), validationMessage);
740         }
741         return (margin - SYS_MARGIN);
742     }
743
744     // Method to provide either regular link (AtoZ) or Opposite link (ZtoA) in the list of PceGraphEdges
745     private PceLink getAppropriatePceLink(Integer pathEltNber, List<PceGraphEdge> edges,
746             Map<LinkId, PceLink> allPceLinks, String direction) {
747         if ((StringConstants.SERVICE_DIRECTION_AZ).equals(direction)) {
748             // Returns regular link.
749             return edges.get(pathEltNber).link();
750         }
751             //For Z to A direction, must return the opposite link
752         return allPceLinks.get(new LinkId(edges.get(pathEltNber).link()
753             .getOppositeLink()));
754     }
755
756     private double getOsnrDbfromOnsrLin(double onsrLu) {
757         return (10 * Math.log10(1 / onsrLu));
758     }
759
760     /**
761      * Get spectrum assignment for path.
762      *
763      * @param path                    the path for which we get spectrum assignment.
764      * @param allPceNodes             all optical nodes.
765      * @param spectralWidthSlotNumber number of slot for spectral width. Depends on
766      *                                service type.
767      * @return a spectrum assignment object which contains begin and end index. If
768      *         no spectrum assignment found, beginIndex = stopIndex = 0
769      */
770     private SpectrumAssignment getSpectrumAssignment(GraphPath<String, PceGraphEdge> path,
771             Map<NodeId, PceNode> allPceNodes, int spectralWidthSlotNumber) {
772         byte[] freqMap = new byte[GridConstant.NB_OCTECTS];
773         Arrays.fill(freqMap, (byte) GridConstant.AVAILABLE_SLOT_VALUE);
774         BitSet result = BitSet.valueOf(freqMap);
775         boolean isFlexGrid = true;
776         LOG.debug("Processing path {} with length {}", path, path.getLength());
777         BitSet pceNodeFreqMap;
778         for (PceGraphEdge edge : path.getEdgeList()) {
779             LOG.debug("Processing source {} ", edge.link().getSourceId());
780             if (allPceNodes.containsKey(edge.link().getSourceId())) {
781                 PceNode pceNode = allPceNodes.get(edge.link().getSourceId());
782                 LOG.debug("Processing PCE node {}", pceNode);
783                 if (StringConstants.OPENROADM_DEVICE_VERSION_1_2_1.equals(pceNode.getVersion())) {
784                     LOG.debug("Node {}: version is {} and slot width granularity is {} -> fixed grid mode",
785                         pceNode.getNodeId(), pceNode.getVersion(), pceNode.getSlotWidthGranularity());
786                     isFlexGrid = false;
787                 }
788                 if ((pceNode.getSlotWidthGranularity().setScale(0, RoundingMode.CEILING)
789                         .equals(GridConstant.SLOT_WIDTH_50))
790                         && (pceNode.getCentralFreqGranularity().setScale(0, RoundingMode.CEILING)
791                         .equals(GridConstant.SLOT_WIDTH_50))) {
792                     LOG.debug("Node {}: version is {} with slot width granularity  {} and central "
793                             + "frequency granularity is {} -> fixed grid mode",
794                         pceNode.getNodeId(), pceNode.getVersion(), pceNode.getSlotWidthGranularity(),
795                         pceNode.getCentralFreqGranularity());
796                     isFlexGrid = false;
797                 }
798                 pceNodeFreqMap = pceNode.getBitSetData();
799                 LOG.debug("Pce node bitset {}", pceNodeFreqMap);
800                 if (pceNodeFreqMap != null) {
801                     result.and(pceNodeFreqMap);
802                     LOG.debug("intermediate bitset {}", result);
803                 }
804             }
805         }
806         LOG.debug("Bitset result {}", result);
807         return computeBestSpectrumAssignment(result, spectralWidthSlotNumber, isFlexGrid);
808     }
809
810     /**
811      * Compute spectrum assignment from spectrum occupation for spectral width.
812      *
813      * @param spectrumOccupation      the spectrum occupation BitSet.
814      * @param spectralWidthSlotNumber the nb slots for spectral width.
815      * @param isFlexGrid              true if flexible grid, false otherwise.
816      * @return a spectrum assignment object which contains begin and stop index. If
817      *         no spectrum assignment found, beginIndex = stopIndex = 0
818      */
819     private SpectrumAssignment computeBestSpectrumAssignment(BitSet spectrumOccupation, int spectralWidthSlotNumber,
820             boolean isFlexGrid) {
821         SpectrumAssignmentBuilder spectrumAssignmentBldr = new SpectrumAssignmentBuilder()
822             .setBeginIndex(Uint16.valueOf(0))
823             .setStopIndex(Uint16.valueOf(0))
824             .setFlexGrid(isFlexGrid);
825         BitSet referenceBitSet = new BitSet(spectralWidthSlotNumber);
826         referenceBitSet.set(0, spectralWidthSlotNumber);
827         int nbSteps = isFlexGrid ? spectralWidthSlotNumber : 1;
828         //higher is the frequency, smallest is the wavelength number
829         //in operational, the allocation is done through wavelength starting from the smallest
830         //so we have to loop from the last element of the spectrum occupation
831         for (int i = spectrumOccupation.size(); i >= spectralWidthSlotNumber; i -= nbSteps) {
832             if (spectrumOccupation.get(i - spectralWidthSlotNumber, i).equals(referenceBitSet)) {
833                 spectrumAssignmentBldr.setBeginIndex(Uint16.valueOf(i - spectralWidthSlotNumber));
834                 spectrumAssignmentBldr.setStopIndex(Uint16.valueOf(i - 1));
835                 break;
836             }
837         }
838         return spectrumAssignmentBldr.build();
839     }
840
841     public Double getTpceCalculatedMargin() {
842         return tpceCalculatedMargin;
843     }
844 }