Refactor PCE graph PostAlgoPathValidator step 3 40/104340/12
authorguillaume.lambert <guillaume.lambert@orange.com>
Thu, 9 Feb 2023 10:50:30 +0000 (11:50 +0100)
committerguillaume.lambert <guillaume.lambert@orange.com>
Wed, 1 Mar 2023 12:48:35 +0000 (13:48 +0100)
checkOSNR() method is very long and has a hich cyclomatic complexity.
The main reason is that it proposes a common heuristic for both AZ & ZA
directions to compute the OSNR. This creates a lot of checks in this
function to identify the direction currently used.
Those multiple checks also affects the code overall efficiency.
- dispatch checkOSNR() content in two methods checkOSNRaz()
  and checkOSNRaz() to check the current direction only once.

The second reason is that it contains a for-loop where the iterator
value is hidden to decorrelate the indexes of the verticles retrieved
by the treatment
and also to back-pedal in the ZA direction case, what is now useless.
- unhide pathElement and merge it to the n iterator
- use decrement operator in the ZA direction for-loop case

Once that done, checkOSNRaz() and checkOSNRaz() looks very similar.
And it may look clever to regroup them again by inverting the order in
the edges list rather than using a decrement operator for the ZA
direction. But this is technically impossible w/o reintroducing many
direction checks because links are retrieved with the getter
link().getOppositeLink() in the ZA direction rather than simply
link() in the AZ direction...

Also the first two iterations and the last iteration of the loops
require specific treatments since they are at the edges of the line,
what causes checks at each iteration.
These other checks affect the code overall efficiency too.
- split the for-loop in three parts to avoid checking iterator value
  at each iteration
- raise an error for unsupported cases (degree at the edges and
  back to back xponder) and remove related code

The third reason is that the for-loop use a switch case to identify
each node type. Each block contains a different treatment to compute
the signal impairments & characteristics.
- create various intermediate functions to compute signal
  characteristics at each step and reduce case-blocks size.

JIRA: TRNSPRTPCE-725
Signed-off-by: guillaume.lambert <guillaume.lambert@orange.com>
Change-Id: I9cd2370d42d74892f8788a9991827066c2b4fcaa

pce/src/main/java/org/opendaylight/transportpce/pce/graph/PostAlgoPathValidator.java

index 53d45122e33f57808b5c46a65f57cf6bdb9385f3..fe7dd48a9b772446551d08d6b800484617458918 100644 (file)
@@ -66,7 +66,6 @@ public class PostAlgoPathValidator {
     @SuppressFBWarnings(
         value = "SF_SWITCH_FALLTHROUGH",
         justification = "intentional fallthrough")
-
     public PceResult checkPath(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
             Map<LinkId, PceLink> allPceLinks, PceResult pceResult, PceConstraints pceHardConstraints,
             String serviceType) {
@@ -377,6 +376,19 @@ public class PostAlgoPathValidator {
             OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(tsList.size() - 1).toString()))));
     }
 
+    private double checkOSNR(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
+            Map<LinkId, PceLink> allPceLinks, String serviceType, String direction, CatalogUtils cu) {
+        switch (direction) {
+            case StringConstants.SERVICE_DIRECTION_AZ:
+                return checkOSNRaz(path, allPceNodes, allPceLinks, serviceType, cu);
+            case StringConstants.SERVICE_DIRECTION_ZA:
+                return checkOSNRza(path, allPceNodes, allPceLinks, serviceType, cu);
+            default:
+                LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported direction {}", direction);
+                return 0.0;
+        }
+    }
+
     /**
      * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
      *
@@ -384,14 +396,11 @@ public class PostAlgoPathValidator {
      * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
      * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
      * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
-     * @param direction                 The direction used to scan provided path in a direct or reverse way.
      * @param cu                        CatalogUtils instance.
      * @return the calculated margin according to the Transponder performances and path impairments.
      */
-    @SuppressWarnings("deprecation")
-    @edu.umd.cs.findbugs.annotations.SuppressWarnings("DLS_DEAD_LOCAL_STORE")
-    private double checkOSNR(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
-            Map<LinkId, PceLink> allPceLinks, String serviceType, String direction, CatalogUtils cu) {
+    private double checkOSNRaz(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
+            Map<LinkId, PceLink> allPceLinks, String serviceType, CatalogUtils cu) {
         double spacing = 50.0;
         double calcPdl2 = 0;
         double calcOsnrdB = 0;
@@ -401,264 +410,416 @@ public class PostAlgoPathValidator {
         double margin = 0;
         double pwrIn = -60.0;
         double pwrOut = -60.0;
-        int increment = 1;
-        int offsetLink = 0;
         boolean transponderPresent = false;
-        if (direction.equals(StringConstants.SERVICE_DIRECTION_ZA)) {
-            increment = - 1;
-            offsetLink = -1;
-        }
-        CatalogNodeType cnt;
         List<String> vertices = path.getVertexList();
         List<PceGraphEdge> edges = path.getEdgeList();
-        String opMode = "";
         // LOOP that scans the different Nodes/Links of the path and calculates
         // associated degradations
         // using CatalogUtils primitives to retrieve physical parameters and make a
         // first level calculation
-        Map<String, Double> impairments = new HashMap<>();
-        for (int n = 0; n < vertices.size(); n++) {
-            int pathElement = direction.equals(StringConstants.SERVICE_DIRECTION_AZ) ? n : vertices.size() - n - 1;
+        int bypassDegree = 0;
+        for (int pathElement = 0; pathElement < 2; pathElement++) {
+            bypassDegree = 0;
             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
-            PceNode nextNode =
-                pathElement != vertices.size() - 1 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
-                        || pathElement != 0 && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)
-                    ? allPceNodes.get(new NodeId(vertices.get(pathElement + increment)))
-                    : null;
-            LOG.debug("loop of check OSNR, n = {} Path Element = {}", n, pathElement);
+            PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement + 1)));
+            LOG.debug("loop of check OSNR direction AZ, Path Element = {}", pathElement);
             switch (currentNode.getORNodeType()) {
                 case XPONDER:
+                    LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
                     transponderPresent = true;
-                    String nwTpId =
-                            pathElement == 0 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
-                                    || pathElement == vertices.size() - 1
-                                        && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)
-                                ? getAppropriatePceLink((pathElement + offsetLink), edges, allPceLinks, direction)
-                                    .getSourceTP()
-                                    .getValue()
-                        // last Xponder of the path (RX side)
-                                : getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks, direction)
-                                    .getDestTP()
-                                    .getValue();
-                    InstanceIdentifier<TerminationPoint1> nwTpIid =
-                        InstanceIdentifiers.createNetworkTerminationPoint1IIDBuilder(vertices.get(pathElement), nwTpId);
-                    LOG.debug("loop of check OSNR : XPDR, n = {} Path Element = {}", n, pathElement);
+                    Map<String, Double> results = calcXpdrOSNR(cu,
+                        pathElement == 0
+                            // First transponder on the Path (TX side) / Last Xponder of the path (RX side)
+                            ? edges.get(pathElement).link().getSourceTP().getValue()
+                            : edges.get(pathElement - 1).link().getDestTP().getValue(),
+                        serviceType, currentNode, nextNode, vertices.get(pathElement), pathElement);
+                    calcOnsrLin = results.get("calcOnsrLin");
+                    spacing = results.get("spacing");
+                    break;
+                case SRG:
+                    LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", pathElement);
+                    // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
+                    // Xponder to Xponder) node element of the path is the ADD SRG.
+                    if (edges.get(pathElement).link().getlinkType() != OpenroadmLinkType.ADDLINK) {
+                        LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
+                            currentNode.getNodeId(), pathElement);
+                    }
+                    pwrIn = 0.0;
+                    Map<String, Double> impairments = calcAddContrib(
+                        cu, currentNode, edges.get(pathElement + 1).link(),
+                        calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                    calcCd = impairments.get("calcCd");
+                    calcPmd2 = impairments.get("calcPmd2");
+                    calcPdl2 = impairments.get("calcPdl2");
+                    calcOnsrLin = impairments.get("calcOnsrLin");
+                    pwrOut = impairments.get("pwrOut");
+                    LOG.debug("loop of check OSNR direction AZ: SRG, pathElement = {} link {} Pout = {}",
+                        pathElement, pathElement + 1, pwrOut);
+                    if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
+                        return -1.0;
+                    }
+                    // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
+                    // The Degree is not considered. This means we must bypass the add-link (ADD)
+                    // and the next node (Degree) which are not considered in the impairments.
+                    pathElement++;
+                    bypassDegree = 1;
+                    break;
+                case DEGREE:
+                default:
+                    LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
+            }
+        }
+        for (int pathElement = 2 + bypassDegree; pathElement < vertices.size() - 1; pathElement++) {
+            PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
+            PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement + 1)));
+            LOG.debug("loop of check OSNR direction AZ: Path Element = {}", pathElement);
+            switch (currentNode.getORNodeType()) {
+                case SRG:
+                    LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", pathElement);
+                    // Other case is DROP, for which cnt is unchanged (.DROP)
+                    if (edges.get(pathElement - 1).link().getlinkType() != OpenroadmLinkType.DROPLINK) {
+                        LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
+                            currentNode.getNodeId(), pathElement - 1);
+                    }
+                    PceLink pceLink = edges.get(pathElement - 2).link();
+                    LOG.info("loop of check OSNR : SRG, pathElement = {} CD on preceeding link {} = {} ps",
+                        pathElement, pathElement - 2, pceLink.getcd());
+                    Map<String, Double> impairments = calcDropContrib(
+                        cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                    calcCd = impairments.get("calcCd");
+                    calcPmd2 = impairments.get("calcPmd2");
+                    calcPdl2 = impairments.get("calcPdl2");
+                    calcOnsrLin = impairments.get("calcOnsrLin");
+                    pwrIn = impairments.get("pwrIn");
+                    if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
+                        return -1.0;
+                    }
+                    // If SRG is not the first or the second element of the Path, it is the DROP
+                    // side.
+                    // After accumulated degradations are calculated, we also need to calculate
+                    // resulting OSNR in dB to pass it to the method that verifies end Xponder
+                    // performances are compatible with degradations experienced on the path
                     try {
-                        if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
-                                .get().isPresent()) {
-// If the Xponder operational mode (setOpMode Arg1) is not consistent or not declared in the topology (Network TP)
-// Operational mode is retrieved from the service Type assuming it is supported by the Xponder (setOpMode Arg2)
-                            opMode = setOpMode(
-                                currentNode.getXponderOperationalMode(
-                                    networkTransactionService
-                                            .read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
-                                            .get().get().getXpdrNetworkAttributes()),
-                                // Operational mode is found as an attribute of the network TP
-                                cu.getPceOperationalModeFromServiceType(
-                                    CatalogConstant.CatalogNodeType.TSP, serviceType));
-                                // Operational mode is retrieved from the service Type assuming it is supported
-                                // by the Xponder
-                            LOG.debug("Transponder {} corresponding to path Element {} in the path has {} operational "
-                                    + "mode", currentNode.getNodeId().getValue(), pathElement, opMode);
-                        } else {
-                            LOG.error("Issue accessing the XponderNetworkAttributes of {} for Transponder {}"
-                                + " corresponding to path Element {} in the path ",
-                                nwTpId, currentNode.getNodeId().getValue(), pathElement);
-                            opMode = cu.getPceOperationalModeFromServiceType(
-                                CatalogConstant.CatalogNodeType.TSP, serviceType);
-                            LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
-                                + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
-                        }
-                    } catch (InterruptedException | ExecutionException e1) {
-                        opMode = cu.getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
-                            serviceType);
-                        LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
-                            + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
+                        calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
+                        LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
+                        LOG.info("Loop pathElement = {}, DROP, calcOsnrdB= {}", pathElement, calcOsnrdB);
+                    } catch (ArithmeticException e) {
+                        LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
+                            path.getEdgeList().size());
+                        return -1.0;
                     }
-                    // If TSP is the last of the path
-                    if (pathElement == vertices.size() - 1 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
-                            || pathElement == 0 && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)) {
-                        LOG.debug("Loop n = {}, Step5.1, XPDR, tries calculating Margin, just before call", n);
-                        // Check that accumulated degradations are compatible with TSP performances
-                        // According to OpenROADM spec :
-                        // margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2),
-                        //              getOsnrDbfromOnsrLin(calcOnsrLin));
-                        // Calculation modified for pdl according to calculation in Julia's Tool
-                        margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2),
-                            (Math.sqrt(calcPdl2)), getOsnrDbfromOnsrLin(calcOnsrLin));
-                        LOG.info("Loop n = {}, XPDR, calcosnrdB= {}", n, getOsnrDbfromOnsrLin(calcOnsrLin));
-                    } else {
-                        // TSP is first element of the path . To correctly evaluate the TX OOB OSNR from
-                        // its operational mode, we need to know the type of ADD/DROP Mux it is
-                        // connected to
-                        String adnMode = "";
-                        // If the operational mode of the ADD/DROP MUX is not consistent or
-                        // if the operational mode of the ADD/DROP MUX is not declared in the topology
-                        // (Network TP)
-                            // Operational mode is set by default to standard opMode for ADD SRGs
-                        adnMode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWWRCORE);
-                            // Operational mode is found in SRG attributes of the Node
-                        LOG.debug("Transponder {} corresponding to path Element {} in the path is connected to SRG "
-                            + "which has {} operational mode", currentNode.getNodeId().getValue(), pathElement,
-                            adnMode);
-                        // Retrieve the Tx ONSR of the Xponder which results from IB and OOB OSNR
-                        // contributions
-                        calcOnsrLin = cu.getPceTxTspParameters(opMode, adnMode);
-                        // Retrieve the spacing associated with Xponder operational mode that is needed
-                        // to calculate OSNR
-                        spacing = cu.getPceTxTspChannelSpacing(opMode);
-                        LOG.info("Transponder {} corresponding to path Element {} in the path has a TX OSNR of {} dB",
-                            currentNode.getNodeId().getValue(), pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
+                    break;
+                case DEGREE:
+                    if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
+                        //This is the case of DROP, ROADM degree is not considered
+                        break;
                     }
+                    LOG.info("loop of check OSNR direction AZ: DEGREE, Path Element = {}", pathElement);
+                    Map<String, Double> impairments0 =  calcBypassContrib(
+                            cu, currentNode, nextNode,
+                            edges.get(pathElement - 1).link(), edges.get(pathElement + 1).link(),
+                            pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                    calcCd = impairments0.get("CD").doubleValue();
+                    calcPmd2 = impairments0.get("DGD2").doubleValue();
+                    calcPdl2 = impairments0.get("PDL2").doubleValue();
+                    calcOnsrLin = impairments0.get("ONSRLIN").doubleValue();
+                    //TODO rename impariments0 var and/or adapt catalog utils
+                    pwrIn = impairments0.get("pwrIn").doubleValue();
+                    pwrOut = impairments0.get("pwrOut").doubleValue();
+                    LOG.debug(
+                        "Loop pathElement= {}, DEGREE, calcOsnrdB= {}", pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
+                    if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
+                        return -1.0;
+                    }
+                    // increment pathElement so that in next step we will not point to Degree2 but
+                    // next node
+                    pathElement++;
+                    LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
+                        + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId(),
+                        nextNode.getNodeId(), calcCd, calcPmd2, calcPdl2, getOsnrDbfromOnsrLin(calcOnsrLin));
+                    break;
+                case XPONDER:
+                    LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
+                    LOG.error("unsupported back to back transponder configuration");
+                    return -1.0;
+                default:
+                    LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
+            }
+        }
+        PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(vertices.size() - 1)));
+        LOG.debug("loop of check OSNR, Path Element = {}", vertices.size() - 1);
+        switch (currentNode.getORNodeType()) {
+            case XPONDER:
+                LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", vertices.size() - 1);
+                transponderPresent = true;
+                // TSP is the last of the path
+                margin = getLastXpdrMargin(cu, edges.get(vertices.size() - 2).link().getDestTP().getValue(),
+                    serviceType, currentNode, vertices.get(vertices.size() - 1), vertices.size() - 1,
+                    calcCd, calcPmd2, calcPdl2, calcOnsrLin);
+                break;
+            case SRG:
+                LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", vertices.size() - 1);
+                // Other case is DROP, for which cnt is unchanged (.DROP)
+                if (edges.get(vertices.size() - 2).link().getlinkType() != OpenroadmLinkType.DROPLINK) {
+                    LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
+                        currentNode.getNodeId(), vertices.size() - 2);
+                }
+                PceLink pceLink = edges.get(vertices.size() - 3).link();
+                LOG.info("loop of check OSNR : SRG, pathElement = {} CD on preceeding link {} = {} ps",
+                    vertices.size() - 1, vertices.size() - 3, pceLink.getcd());
+                Map<String, Double> impairments = calcDropContrib(
+                    cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                calcCd = impairments.get("calcCd");
+                calcPmd2 = impairments.get("calcPmd2");
+                calcPdl2 = impairments.get("calcPdl2");
+                calcOnsrLin = impairments.get("calcOnsrLin");
+                //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
+                if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
+                    return -1.0;
+                }
+                // If SRG is not the first or the second element of the Path, it is the DROP
+                // side.
+                // After accumulated degradations are calculated, we also need to calculate
+                // resulting OSNR in dB to pass it to the method that verifies end Xponder
+                // performances are compatible with degradations experienced on the path
+                try {
+                    calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
+                    LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
+                    LOG.info("Loop pathElement = {}, DROP, calcOsnrdB= {}", vertices.size() - 1, calcOsnrdB);
+                } catch (ArithmeticException e) {
+                    LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
+                        path.getEdgeList().size());
+                    return -1.0;
+                }
+                break;
+            case DEGREE:
+            default:
+                LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain last element");
+        }
+        LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOsnrdB = {} "
+            + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
+            calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcOsnrdB, getOsnrDbfromOnsrLin(calcOnsrLin));
+        if (!transponderPresent) {
+            LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
+                + "that optical tunnel degradations are compatible with external transponder performances");
+            return 0.0;
+        }
+        double delta = margin - SYS_MARGIN;
+        LOG.info("In checkOSNR: Transponder Operational mode results in a residual margin of {} dB, according "
+            + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
+            delta, SYS_MARGIN);
+        String validationMessage = delta >= 0 ? "VALIDATED" : "INVALIDATED";
+        LOG.info("- In checkOSNR: A to Z Path from {} to {} {}",
+                vertices.get(0), vertices.get(vertices.size() - 1), validationMessage);
+        return delta;
+    }
+
+    /**
+     * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
+     *
+     * @param path                      the AtoZ path provided by the PCE.
+     * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
+     * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
+     * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
+     * @param cu                        CatalogUtils instance.
+     * @return the calculated margin according to the Transponder performances and path impairments.
+     */
+    private double checkOSNRza(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
+            Map<LinkId, PceLink> allPceLinks, String serviceType, CatalogUtils cu) {
+        double spacing = 50.0;
+        double calcPdl2 = 0;
+        double calcOsnrdB = 0;
+        double calcCd = 0;
+        double calcPmd2 = 0;
+        double calcOnsrLin = 0.0001;
+        double margin = 0;
+        double pwrIn = -60.0;
+        double pwrOut = -60.0;
+        boolean transponderPresent = false;
+        List<String> vertices = path.getVertexList();
+        List<PceGraphEdge> edges = path.getEdgeList();
+        // LOOP that scans the different Nodes/Links of the path and calculates
+        // associated degradations
+        // using CatalogUtils primitives to retrieve physical parameters and make a
+        // first level calculation
+        int bypassDegree = 0;
+        for (int pathElement = vertices.size() - 1; pathElement > vertices.size() - 3; pathElement--) {
+            bypassDegree = 0;
+            PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
+            PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement - 1)));
+            LOG.debug("loop of check OSNR direction ZA:  Path Element = {}", pathElement);
+            switch (currentNode.getORNodeType()) {
+                case XPONDER:
+                    LOG.debug("loop of check OSNR direction ZA: XPDR, Path Element = {}", pathElement);
+                    transponderPresent = true;
+                    Map<String, Double> results = calcXpdrOSNR(cu,
+                        pathElement == vertices.size() - 1
+                            // First transponder on the Path (TX side) / Last Xponder of the path (RX side)
+                            ? getOppPceLink(pathElement - 1, edges, allPceLinks).getSourceTP().getValue()
+                            : getOppPceLink((pathElement), edges, allPceLinks).getDestTP().getValue(),
+                        serviceType, currentNode, nextNode, vertices.get(pathElement), pathElement);
+                    calcOnsrLin = results.get("calcOnsrLin");
+                    spacing = results.get("spacing");
                     break;
                 case SRG:
-// If the operational mode of the ADD/DROP MUX is not consistent or is not declared in the topology (Network TP)
-// Operational mode is set by default to standard opMode for ADD/DROP SRGs
-                    String srgMode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE);
-                    cnt = CatalogConstant.CatalogNodeType.DROP;
-                    LOG.debug("loop of check OSNR : SRG, n = {} Path Element = {}", n, pathElement);
-                    if (pathElement <= 1 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
-                            || pathElement >= vertices.size() - 2
-                                && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)) {
-                        // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
-                        // Xponder to Xponder) node element of the path is the ADD SRG.
-                        if (getAppropriatePceLink(pathElement + offsetLink, edges, allPceLinks, direction)
-                                .getlinkType() != OpenroadmLinkType.ADDLINK) {
-                            LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
-                                currentNode.getNodeId().toString(), pathElement + offsetLink);
-                        }
-                        cnt = CatalogConstant.CatalogNodeType.ADD;
-                        pwrIn = 0.0;
-                        pwrOut = cu.getPceRoadmAmpOutputPower(cnt, srgMode,
-                            getAppropriatePceLink((pathElement + 1 + offsetLink * 3), edges, allPceLinks, direction)
-                                .getspanLoss(),
-                            spacing,
-                            getAppropriatePceLink((pathElement + 1 + offsetLink * 3), edges, allPceLinks, direction)
-                                .getpowerCorrection());
-                        LOG.debug("loop of check OSNR : SRG, n = {} link {} Pout = {}",
-                            pathElement, pathElement + 1 + offsetLink * 3, pwrOut);
-                    } else {
-                        // Other case is DROP, for which cnt is unchanged (.DROP)
-                        if (getAppropriatePceLink(pathElement - 1 - offsetLink, edges, allPceLinks, direction)
-                                .getlinkType() != OpenroadmLinkType.DROPLINK) {
-                            LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
-                                currentNode.getNodeId().toString(), pathElement - 1 - offsetLink);
-                        }
-                        pwrIn = pwrOut - getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
-                            direction).getspanLoss();
-                        // Calculate degradation accumulated across incoming Link and add them to
-                        // accumulated impairments
-                        calcCd += getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
-                            direction).getcd();
-                        LOG.info("loop of check OSNR : SRG, n = {} CD on preceeding link {} = {} ps", pathElement,
-                            pathElement - offsetLink * 3 - 2, getAppropriatePceLink((pathElement - offsetLink * 3 - 2),
-                                edges, allPceLinks, direction).getcd());
-                        calcPmd2 += getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
-                            direction).getpmd2();
-                        // This also includes Non Linear Contribution from the path
-                        calcOnsrLin += cu.calculateNLonsrContribution(pwrOut, getAppropriatePceLink((pathElement
-                            - offsetLink * 3 - 2), edges, allPceLinks, direction).getLength(), spacing);
+                    LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = {}", pathElement);
+                    // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
+                    // Xponder to Xponder) node element of the path is the ADD SRG.
+                    if (getOppPceLink(pathElement - 1, edges, allPceLinks).getlinkType() != OpenroadmLinkType.ADDLINK) {
+                        LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
+                            currentNode.getNodeId(), pathElement - 1);
                     }
-                    //calculation of the SRG contribution either for Add and Drop
-                    impairments = cu.getPceRoadmAmpParameters(cnt, srgMode,
-                        pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
-                    calcCd = impairments.get("CD").doubleValue();
-                    calcPmd2 = impairments.get("DGD2").doubleValue();
-                    calcPdl2 = impairments.get("PDL2").doubleValue();
-                    calcOnsrLin = impairments.get("ONSRLIN").doubleValue();
+                    pwrIn = 0.0;
+
+                    Map<String, Double> impairments = calcAddContrib(
+                        cu, currentNode, getOppPceLink(pathElement - 2, edges, allPceLinks),
+                        calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                    calcCd = impairments.get("calcCd");
+                    calcPmd2 = impairments.get("calcPmd2");
+                    calcPdl2 = impairments.get("calcPdl2");
+                    calcOnsrLin = impairments.get("calcOnsrLin");
+                    pwrOut = impairments.get("pwrOut");
                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
                         return -1.0;
                     }
-                    if (pathElement > 1) {
-                        // If SRG is not the first or the second element of the Path, it is the DROP
-                        // side.
-                        // After accumulated degradations are calculated, we also need to calculate
-                        // resulting OSNR in dB to pass it to the method that verifies end Xponder
-                        // performances are compatible with degradations experienced on the path
-                        try {
-                            calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
-                            LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
-                            LOG.info("Loop n = {}, DROP, calcOsnrdB= {}", n, calcOsnrdB);
-                        } catch (ArithmeticException e) {
-                            LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
-                                path.getEdgeList().size());
-                            return -1.0;
-                        }
+                    // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
+                    // The Degree is not considered. This means we must bypass the add-link (ADD)
+                    // and the next node (Degree) which are not considered in the impairments.
+                    pathElement--;
+                    bypassDegree = 1;
+                    break;
+                case DEGREE:
+                default:
+                    LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
+            }
+        }
+        for (int pathElement = vertices.size() - 3 - bypassDegree; pathElement > 0; pathElement--) {
+            PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
+            PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement - 1)));
+            LOG.debug("loop of check OSNR direction ZA: Path Element = {}", pathElement);
+            switch (currentNode.getORNodeType()) {
+                case SRG:
+                    LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = {}", pathElement);
+                    if (getOppPceLink(pathElement, edges, allPceLinks).getlinkType() != OpenroadmLinkType.DROPLINK) {
+                        LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
+                            currentNode.getNodeId(), pathElement);
                     }
-                    if (CatalogConstant.CatalogNodeType.ADD.equals(cnt)) {
-                        // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
-                        // The Degree is not considered. This means we must bypass the add-link (ADD)
-                        // and the next node (Degree) which are not considered in the impairments.
-                        n++;
+                    PceLink pceLink = getOppPceLink(pathElement + 1, edges, allPceLinks);
+                    LOG.info("loop of check OSNR direction ZA: SRG, path Element = {} CD on preceeding link {} = {} ps",
+                        pathElement, pathElement + 1, pceLink.getcd());
+                    Map<String, Double> impairments = calcDropContrib(
+                        cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                    calcCd = impairments.get("calcCd");
+                    calcPmd2 = impairments.get("calcPmd2");
+                    calcPdl2 = impairments.get("calcPdl2");
+                    calcOnsrLin = impairments.get("calcOnsrLin");
+                    pwrIn = impairments.get("pwrIn");
+                    if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
+                        return -1.0;
+                    }
+                    // If SRG is not the first or the second element of the Path, it is the DROP
+                    // side.
+                    // After accumulated degradations are calculated, we also need to calculate
+                    // resulting OSNR in dB to pass it to the method that verifies end Xponder
+                    // performances are compatible with degradations experienced on the path
+                    try {
+                        calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
+                        LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
+                        LOG.info("Loop Path Element = {}, DROP, calcOsnrdB= {}", pathElement, calcOsnrdB);
+                    } catch (ArithmeticException e) {
+                        LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
+                            path.getEdgeList().size());
+                        return -1.0;
                     }
-                    impairments.clear();
                     break;
                 case DEGREE:
                     if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
                         //This is the case of DROP, ROADM degree is not considered
                         break;
                     }
-                    LOG.info("loop of check OSNR : DEGREE, n = {} Path Element = {}", n, pathElement);
-                    cnt = CatalogConstant.CatalogNodeType.EXPRESS;
-                    String degree1Mode = "";
-                    String degree2Mode = "";
-// If the operational mode of the Degree is not consistent or declared in the topology
-// Operational mode is set by default to standard opMode for Degree
-                    degree1Mode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWMWCORE);
-                    // Same for next node which is the second degree of a ROADM node
-                    degree2Mode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWMWCORE);
-                    // At that time OpenROADM provides only one spec for the ROADM nodes
-                    if (!degree1Mode.equals(degree2Mode)) {
-                        LOG.info("Unsupported Hybrid ROADM configuration with Degree1 {} of {} operational mode"
-                            + "and Degree2 {} of {} operational mode. Will by default use operational mode"
-                            + "of Degree2", currentNode.getNodeId().toString(),
-                            degree1Mode, nextNode.getNodeId().toString(), degree2Mode);
-                    }
-                    pwrIn = pwrOut - getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks,
-                        direction).getspanLoss();
-                    // Calculate degradation accumulated across incoming Link and add them to
-                    // accumulated impairments
-                    calcCd +=
-                        getAppropriatePceLink(pathElement - offsetLink - 1, edges, allPceLinks, direction).getcd();
-                    calcPmd2 +=
-                        getAppropriatePceLink(pathElement - offsetLink - 1, edges, allPceLinks, direction).getpmd2();
-                    // This also includes Non Linear Contribution from the path
-                    calcOnsrLin += cu.calculateNLonsrContribution(pwrOut,
-                        getAppropriatePceLink(pathElement - offsetLink - 1, edges, allPceLinks, direction).getLength(),
-                        spacing);
-                    // Calculate output power for next span (Output of degree 2)
-                    pwrOut = cu.getPceRoadmAmpOutputPower(cnt, degree2Mode,
-                        getAppropriatePceLink(pathElement + 3 * offsetLink + 1, edges, allPceLinks, direction)
-                            .getspanLoss(),
-                        spacing,
-                        getAppropriatePceLink(pathElement + 3 * offsetLink + 1, edges, allPceLinks, direction)
-                            .getpowerCorrection());
-                    // Adds to accumulated impairments the degradation associated with the Express
-                    // path of ROADM : Degree1, express link, Degree2
-                    impairments = cu.getPceRoadmAmpParameters(cnt, degree2Mode,
-                        pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
-                    calcCd = impairments.get("CD").doubleValue();
-                    calcPmd2 = impairments.get("DGD2").doubleValue();
-                    calcPdl2 = impairments.get("PDL2").doubleValue();
-                    calcOnsrLin = impairments.get("ONSRLIN").doubleValue();
-                    LOG.debug("Loop n = {}, DEGREE, calcOsnrdB= {}", n, getOsnrDbfromOnsrLin(calcOnsrLin));
+                    LOG.info("loop of check OSNR direction ZA: DEGREE, Path Element = {}", pathElement);
+                    Map<String, Double> impairments0 =  calcBypassContrib(
+                        cu, currentNode, nextNode,
+                        getOppPceLink(pathElement, edges, allPceLinks),
+                        getOppPceLink(pathElement - 2, edges, allPceLinks),
+                        pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                    calcCd = impairments0.get("CD").doubleValue();
+                    calcPmd2 = impairments0.get("DGD2").doubleValue();
+                    calcPdl2 = impairments0.get("PDL2").doubleValue();
+                    calcOnsrLin = impairments0.get("ONSRLIN").doubleValue();
+                    //TODO rename impariments0 var and/or adapt catalog utils
+                    pwrIn = impairments0.get("pwrIn").doubleValue();
+                    pwrOut = impairments0.get("pwrOut").doubleValue();
+                    LOG.debug("Loop Path Element = {}, DEGREE, calcOsnrdB= {}",
+                            pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
                         return -1.0;
                     }
                     // increment pathElement so that in next step we will not point to Degree2 but
                     // next node
-                    n++;
+                    pathElement--;
                     LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
-                        + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId().toString(),
-                        nextNode.getNodeId().toString(), calcCd, calcPmd2, calcPdl2, getOsnrDbfromOnsrLin(calcOnsrLin));
+                        + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId(),
+                        nextNode.getNodeId(), calcCd, calcPmd2, calcPdl2, getOsnrDbfromOnsrLin(calcOnsrLin));
                     break;
+                case XPONDER:
+                    LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
+                    LOG.error("unsupported back to back transponder configuration");
+                    return -1.0;
                 default:
                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
             }
         }
+        PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(0)));
+        LOG.debug("loop of check OSNR direction ZA: Path Element = 0");
+        switch (currentNode.getORNodeType()) {
+            case XPONDER:
+                LOG.debug("loop of check OSNR direction ZA: XPDR, Path Element = 0");
+                transponderPresent = true;
+                // TSP is the last of the path
+                margin = getLastXpdrMargin(cu, getOppPceLink(0, edges, allPceLinks).getDestTP().getValue(),
+                    serviceType, currentNode, vertices.get(0), 0, calcCd, calcPmd2, calcPdl2, calcOnsrLin);
+                break;
+            case SRG:
+                LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = 0");
+                if (getOppPceLink(0, edges, allPceLinks).getlinkType() != OpenroadmLinkType.DROPLINK) {
+                    LOG.error("Error processing Node {} for which input link 0 is not a DROPLINK Type",
+                        currentNode.getNodeId());
+                }
+                PceLink pceLink = getOppPceLink(1, edges, allPceLinks);
+                LOG.info("loop of check OSNR direction ZA: SRG, path Element = 0 CD on preceeding link 1 = {} ps",
+                    pceLink.getcd());
+                Map<String, Double> impairments = calcDropContrib(
+                    cu, currentNode, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+                calcCd = impairments.get("calcCd");
+                calcPmd2 = impairments.get("calcPmd2");
+                calcPdl2 = impairments.get("calcPdl2");
+                calcOnsrLin = impairments.get("calcOnsrLin");
+                //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
+                if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
+                    return -1.0;
+                }
+                // If SRG is not the first or the second element of the Path, it is the DROP
+                // side.
+                // After accumulated degradations are calculated, we also need to calculate
+                // resulting OSNR in dB to pass it to the method that verifies end Xponder
+                // performances are compatible with degradations experienced on the path
+                try {
+                    calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
+                    LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
+                    LOG.info("Loop Path Element = 0, DROP, calcOsnrdB= {}", calcOsnrdB);
+                } catch (ArithmeticException e) {
+                    LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
+                        path.getEdgeList().size());
+                    return -1.0;
+                }
+                break;
+            case DEGREE:
+            default:
+                LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain last element");
+        }
         LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOsnrdB = {} "
-            + "dB and ONSR dB exterapolated from calcosnrlin = {}"
-            + " including non linear contributions",
+            + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
             calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcOsnrdB, getOsnrDbfromOnsrLin(calcOnsrLin));
         if (!transponderPresent) {
             LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
@@ -666,17 +827,12 @@ public class PostAlgoPathValidator {
             return 0.0;
         }
         double delta = margin - SYS_MARGIN;
-        LOG.info("In checkOSNR: Transponder Operational mode {} results in a residual margin of {} dB, according "
+        LOG.info("In checkOSNR: Transponder Operational mode results in a residual margin of {} dB, according "
             + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
-            opMode, delta, SYS_MARGIN);
+            delta, SYS_MARGIN);
         String validationMessage = delta >= 0 ? "VALIDATED" : "INVALIDATED";
-        if (direction.equals(StringConstants.SERVICE_DIRECTION_AZ)) {
-            LOG.info("- In checkOSNR: A to Z Path from {} to {} {}",
-                vertices.get(0), vertices.get(vertices.size() - 1), validationMessage);
-        } else {
-            LOG.info("- In checkOSNR: Z to A Path from {} to {} {}",
+        LOG.info("- In checkOSNR: Z to A Path from {} to {} {}",
                 vertices.get(vertices.size() - 1), vertices.get(0), validationMessage);
-        }
         return delta;
     }
 
@@ -687,20 +843,176 @@ public class PostAlgoPathValidator {
                 : opMode;
     }
 
+    private PceLink getOppPceLink(Integer pathEltNber, List<PceGraphEdge> edges,
+            Map<LinkId, PceLink> allPceLinks) {
+        return allPceLinks.get(new LinkId(edges.get(pathEltNber).link().getOppositeLink()));
+    }
 
-    // Method to provide either regular link (AtoZ) or Opposite link (ZtoA) in the list of PceGraphEdges
-    private PceLink getAppropriatePceLink(Integer pathEltNber, List<PceGraphEdge> edges,
-            Map<LinkId, PceLink> allPceLinks, String direction) {
-        if ((StringConstants.SERVICE_DIRECTION_AZ).equals(direction)) {
-            // Returns regular link.
-            return edges.get(pathEltNber).link();
+    private String getXpdrOpMode(String nwTpId, String vertice, int pathElement, PceNode currentNode,
+            String serviceType, CatalogUtils cu) {
+        InstanceIdentifier<TerminationPoint1> nwTpIid =
+            InstanceIdentifiers.createNetworkTerminationPoint1IIDBuilder(vertice, nwTpId);
+        String opMode = cu.getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP, serviceType);
+        try {
+            if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, nwTpIid).get().isPresent()) {
+                // If the operational mode of the Xponder is not consistent nor declared in the topology (Network TP)
+                opMode = setOpMode(
+                    currentNode.getXponderOperationalMode(
+                        networkTransactionService
+                                .read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
+                                .get().get().getXpdrNetworkAttributes()),
+                    // Operational mode is found as an attribute of the network TP
+                    opMode);
+                    // Operational mode is retrieved from the service Type assuming it is supported
+                    // by the Xponder
+                LOG.debug(
+                    "Transponder {} corresponding to path Element {} in the path has {} operational mode",
+                    currentNode.getNodeId().getValue(), pathElement, opMode);
+                return opMode;
+            }
+        } catch (InterruptedException | ExecutionException e1) {
+            LOG.error("Issue accessing the XponderNetworkAttributes of {} for Transponder {}"
+                + " corresponding to path Element {} in the path ",
+                nwTpId, currentNode.getNodeId().getValue(), pathElement);
         }
-            //For Z to A direction, must return the opposite link
-        return allPceLinks.get(new LinkId(edges.get(pathEltNber).link().getOppositeLink()));
+        LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
+            + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
+        return opMode;
+    }
+
+    private double getLastXpdrMargin(
+            CatalogUtils cu, String nwTpId, String serviceType, PceNode currentNode, String vertice, int pathElement,
+            double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin) {
+        LOG.debug("Loop Path Element = {}, Step5.1, XPDR, tries calculating Margin, just before call", pathElement);
+        // Check that accumulated degradations are compatible with TSP performances
+        // According to OpenROADM spec :
+        // margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2),
+        //              getOsnrDbfromOnsrLin(calcOnsrLin));
+        // Calculation modified for pdl according to calculation in Julia's Tool
+        double calcosnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
+        LOG.info("Loop Path Element = {}, XPDR, calcosnrdB= {}", pathElement, calcosnrdB);
+        return cu.getPceRxTspParameters(
+            getXpdrOpMode(nwTpId, vertice, pathElement, currentNode, serviceType, cu),
+            calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcosnrdB);
+    }
+
+    private Map<String, Double> calcXpdrOSNR(
+            CatalogUtils cu, String nwTpId, String serviceType,
+            PceNode currentNode, PceNode nextNode, String vertice, int pathElement) {
+        // If the Xponder operational mode (setOpMode Arg1) is not consistent nor declared in the topology (Network TP)
+        // Operational mode is retrieved from the service Type assuming it is supported by the Xponder (setOpMode Arg2)
+        String opMode = getXpdrOpMode(nwTpId, vertice, pathElement, currentNode, serviceType, cu);
+        // If the operational mode of the ADD/DROP MUX is not consistent nor declared in the topology (Network TP)
+        // Operational mode is set by default to standard opMode for ADD SRGs
+        String adnMode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWWRCORE);
+        double calcOnsrLin = cu.getPceTxTspParameters(opMode, adnMode);
+        LOG.debug(
+            "Transponder {} corresponding to path Element {} is connected to SRG which has {} operational mode",
+            currentNode.getNodeId().getValue(), pathElement, adnMode);
+        LOG.info("Transponder {} corresponding to path Element {} in the path has a TX OSNR of {} dB",
+            currentNode.getNodeId().getValue(), pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
+        // Return the Tx ONSR of the Xponder which results from IB and OOB OSNR contributions
+        // and the spacing associated with Xponder operational mode that is needed to calculate OSNR
+        return Map.of(
+            "spacing", cu.getPceTxTspChannelSpacing(opMode),
+            "calcOnsrLin", calcOnsrLin);
+    }
+
+    private Map<String, Double> calcDropContrib(
+            CatalogUtils cu, PceNode currentNode, PceLink pceLink,
+            double pwrOut, double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
+        //calculation of the SRG contribution for Drop
+        Map<String, Double> impairments =
+            calcLineDegradation(cu, pceLink, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+        double pwrIn = impairments.get("pwrIn");
+        impairments = cu.getPceRoadmAmpParameters(
+            CatalogConstant.CatalogNodeType.DROP,
+            setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE),
+        // If the operational mode of the ADD/DROP MUX is not consistent or not declared in the topology (Network TP)
+        // Operational mode is set by default to standard opMode for ADD/DROP SRGs
+            pwrIn,
+            impairments.get("calcCd").doubleValue(),
+            impairments.get("calcPmd2").doubleValue(),
+            calcPdl2,
+            impairments.get("calcOnsrLin").doubleValue(),
+            spacing);
+        return Map.of(
+            "calcCd", impairments.get("CD"),
+            "calcPmd2", impairments.get("DGD2"),
+            "calcPdl2", impairments.get("PDL2"),
+            "calcOnsrLin", impairments.get("ONSRLIN"),
+            "pwrIn", pwrIn);
+    }
+
+    private Map<String, Double> calcAddContrib(
+            CatalogUtils cu, PceNode currentNode, PceLink pceLink,
+            double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
+        //calculation of the SRG contribution for Add
+        String srgMode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE);
+        // If the operational mode of the ADD/DROP MUX is not consistent or is not declared in the topology (Network TP)
+        // Operational mode is set by default to standard opMode for ADD/DROP SRGs
+        CatalogNodeType cnt = CatalogConstant.CatalogNodeType.ADD;
+        double pwrOut = cu.getPceRoadmAmpOutputPower(
+                cnt, srgMode, pceLink.getspanLoss(), spacing, pceLink.getpowerCorrection());
+        //calculation of the SRG contribution either for Add and Drop
+        Map<String, Double> impairments = cu.getPceRoadmAmpParameters(
+            cnt, srgMode, 0, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+        return Map.of(
+            "calcCd", impairments.get("CD"),
+            "calcPmd2", impairments.get("DGD2"),
+            "calcPdl2", impairments.get("PDL2"),
+            "calcOnsrLin", impairments.get("ONSRLIN"),
+            "pwrOut", pwrOut);
+    }
+
+    private Map<String, Double> calcBypassContrib(
+            CatalogUtils cu, PceNode currentNode, PceNode nextNode, PceLink pceLink0, PceLink pceLink1,
+            double pwrOut, double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
+        // If the operational mode of the Degree is not consistent or declared in the topology
+        // Operational mode is set by default to standard opMode for Degree
+        String degree1Mode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWMWCORE);
+        // Same for next node which is the second degree of a ROADM node
+        String degree2Mode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWMWCORE);
+        // At that time OpenROADM provides only one spec for the ROADM nodes
+        if (!degree1Mode.equals(degree2Mode)) {
+            LOG.warn("Unsupported Hybrid ROADM configuration with Degree1 {} of {} operational mode and Degree2 "
+                + "{} of {} operational mode. Will by default use operational mode of Degree2",
+                currentNode.getNodeId(), degree1Mode, nextNode.getNodeId(), degree2Mode);
+        }
+        Map<String, Double> impairments = calcLineDegradation(
+            cu, pceLink0, pwrOut, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+        calcCd = impairments.get("calcCd");
+        calcPmd2 = impairments.get("calcPmd2");
+        calcOnsrLin = impairments.get("calcOnsrLin");
+        CatalogNodeType cnt0 = CatalogConstant.CatalogNodeType.EXPRESS;
+        double pwrIn = impairments.get("pwrIn");
+        pwrOut = cu.getPceRoadmAmpOutputPower(
+            cnt0, degree2Mode, pceLink1.getspanLoss(), spacing, pceLink1.getpowerCorrection());
+        // Adds to accumulated impairments the degradation associated with the Express
+        // path of ROADM : Degree1, express link, Degree2
+        impairments = cu.getPceRoadmAmpParameters(
+            cnt0, degree2Mode, pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
+        impairments.put("pwrIn", pwrIn);
+        impairments.put("pwrOut", pwrOut);
+        return impairments;
+    }
+    //TODO these methods might be more indicated in a catalog utils refactoring
+
+    private Map<String, Double> calcLineDegradation(
+            CatalogUtils cu, PceLink pceLink,
+            double pwrOut, double calcCd, double calcPmd2, double calcPdl2, double calcOnsrLin, double spacing) {
+        // Calculate degradation accumulated across incoming Link and add them to
+        // accumulated impairments
+        // This also includes Non Linear Contribution from the path
+        return Map.of(
+            "pwrIn", pwrOut - pceLink.getspanLoss(),
+            "calcCd", calcCd + pceLink.getcd(),
+            "calcPmd2", calcPmd2 + pceLink.getpmd2(),
+            "calcOnsrLin", calcOnsrLin + cu.calculateNLonsrContribution(pwrOut, pceLink.getLength(), spacing));
     }
 
     private double getOsnrDbfromOnsrLin(double onsrLu) {
-        return (10 * Math.log10(1 / onsrLu));
+        return 10 * Math.log10(1 / onsrLu);
     }
 
     /**