Update release in docs/conf.yaml
[transportpce.git] / pce / src / main / java / org / opendaylight / transportpce / pce / graph / PostAlgoPathValidator.java
index c05eaa09124526fa49371a9bc99a4fbe5ff2923e..53353aeae0415acfb29aa9867e4f4d2de734be67 100644 (file)
@@ -9,29 +9,45 @@
 package org.opendaylight.transportpce.pce.graph;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import org.jgrapht.GraphPath;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.transportpce.common.InstanceIdentifiers;
 import org.opendaylight.transportpce.common.ResponseCodes;
 import org.opendaylight.transportpce.common.StringConstants;
+import org.opendaylight.transportpce.common.catalog.CatalogConstant;
+import org.opendaylight.transportpce.common.catalog.CatalogConstant.CatalogNodeType;
+import org.opendaylight.transportpce.common.catalog.CatalogUtils;
 import org.opendaylight.transportpce.common.fixedflex.GridConstant;
 import org.opendaylight.transportpce.common.fixedflex.GridUtils;
+import org.opendaylight.transportpce.common.network.NetworkTransactionService;
 import org.opendaylight.transportpce.pce.constraints.PceConstraints;
 import org.opendaylight.transportpce.pce.constraints.PceConstraints.ResourcePair;
+import org.opendaylight.transportpce.pce.networkanalyzer.PceLink;
 import org.opendaylight.transportpce.pce.networkanalyzer.PceNode;
 import org.opendaylight.transportpce.pce.networkanalyzer.PceResult;
-import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignment;
-import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignmentBuilder;
-import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev211210.OpenroadmLinkType;
-import org.opendaylight.yang.gen.v1.http.org.openroadm.otn.common.types.rev181130.OpucnTribSlotDef;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev240205.PceConstraintMode;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev240205.SpectrumAssignment;
+import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev240205.SpectrumAssignmentBuilder;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.network.topology.rev230526.TerminationPoint1;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev230526.OpenroadmLinkType;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev230526.OpenroadmNodeType;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.otn.common.types.rev210924.OpucnTribSlotDef;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.rev180226.NodeId;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.topology.rev180226.LinkId;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.Uint16;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,26 +56,33 @@ public class PostAlgoPathValidator {
     /* Logging. */
     private static final Logger LOG = LoggerFactory.getLogger(PostAlgoPathValidator.class);
 
-    private static final double MIN_OSNR_W100G = 17;
-    private static final double TRX_OSNR = 33;
-    private static final double ADD_OSNR = 30;
     public static final Long CONST_OSNR = 1L;
     public static final double SYS_MARGIN = 0;
 
+    private Double tpceCalculatedMargin = 0.0;
+    private final NetworkTransactionService networkTransactionService;
+    private final BitSet spectrumConstraint;
+
+    public PostAlgoPathValidator(NetworkTransactionService networkTransactionService, BitSet spectrumConstraint) {
+        this.networkTransactionService = networkTransactionService;
+        this.spectrumConstraint = spectrumConstraint;
+    }
+
     @SuppressWarnings("fallthrough")
     @SuppressFBWarnings(
         value = "SF_SWITCH_FALLTHROUGH",
         justification = "intentional fallthrough")
     public PceResult checkPath(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
-        PceResult pceResult, PceConstraints pceHardConstraints, String serviceType) {
-
+            Map<LinkId, PceLink> allPceLinks, PceResult pceResult, PceConstraints pceHardConstraints,
+            String serviceType, PceConstraintMode mode) {
+        LOG.info("path = {}", path);
         // check if the path is empty
         if (path.getEdgeList().isEmpty()) {
             pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
             return pceResult;
         }
-        int spectralWidthSlotNumber = GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP
-            .getOrDefault(serviceType, GridConstant.NB_SLOTS_100G);
+        int spectralWidthSlotNumber =
+            GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_100G);
         SpectrumAssignment spectrumAssignment = null;
         //variable to deal with 1GE (Nb=1) and 10GE (Nb=10) cases
         switch (serviceType) {
@@ -67,8 +90,8 @@ public class PostAlgoPathValidator {
             case StringConstants.SERVICE_TYPE_OTUC3:
             case StringConstants.SERVICE_TYPE_OTUC4:
             case StringConstants.SERVICE_TYPE_400GE:
-                spectralWidthSlotNumber = GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP
-                    .getOrDefault(serviceType, GridConstant.NB_SLOTS_400G);
+                spectralWidthSlotNumber =
+                    GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_400G);
             //fallthrough
             case StringConstants.SERVICE_TYPE_100GE_T:
             case StringConstants.SERVICE_TYPE_OTU4:
@@ -86,62 +109,72 @@ public class PostAlgoPathValidator {
                 } else {
                     LOG.debug("Spectrum assignment fixedgrid mode");
                     pceResult.setResultWavelength(
-                            GridUtils.getWaveLengthIndexFromSpectrumAssigment(spectrumAssignment.getBeginIndex()
-                                .toJava()));
+                        GridUtils.getWaveLengthIndexFromSpectrumAssigment(spectrumAssignment.getBeginIndex().toJava()));
                 }
                 pceResult.setMinFreq(GridUtils.getStartFrequencyFromIndex(spectrumAssignment.getBeginIndex().toJava()));
                 pceResult.setMaxFreq(GridUtils.getStopFrequencyFromIndex(spectrumAssignment.getStopIndex().toJava()));
                 LOG.debug("In PostAlgoPathValidator: spectrum assignment found {} {}", spectrumAssignment, path);
 
                 // Check the OSNR
-                if (!checkOSNR(path)) {
-                    pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
-                    pceResult.setLocalCause(PceResult.LocalCause.OUT_OF_SPEC_OSNR);
-                    return pceResult;
+                CatalogUtils cu = new CatalogUtils(networkTransactionService);
+                if (cu.isCatalogFilled()) {
+                    double margin1 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
+                            StringConstants.SERVICE_DIRECTION_AZ, cu);
+                    double margin2 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
+                            StringConstants.SERVICE_DIRECTION_ZA, cu);
+                    if (margin1 < 0 || margin2 < 0 || margin1 == Double.NEGATIVE_INFINITY
+                            || margin2 == Double.NEGATIVE_INFINITY) {
+                        pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
+                        pceResult.setLocalCause(PceResult.LocalCause.OUT_OF_SPEC_OSNR);
+                        return pceResult;
+                    }
+                    this.tpceCalculatedMargin = Math.min(margin1, margin2);
+                    LOG.info(
+                        "In PostAlgoPathValidator: Minimum margin estimated by tpce on AtoZ and ZtoA path is of  {} dB",
+                        this.tpceCalculatedMargin);
+                } else {
+                    this.tpceCalculatedMargin = 0.0;
+                    LOG.info("In PostAlgoPathValidator: Operational mode Catalog not filled, delegate OSNR calculation"
+                        + " to GNPy and margin set to 0");
                 }
-
                 // Check if MaxLatency is defined in the hard constraints
-                if ((pceHardConstraints.getMaxLatency() != -1)
-                        && (!checkLatency(pceHardConstraints.getMaxLatency(), path))) {
+                if (pceHardConstraints.getMaxLatency() != -1
+                        && !checkLatency(pceHardConstraints.getMaxLatency(), path)) {
                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
                     pceResult.setLocalCause(PceResult.LocalCause.TOO_HIGH_LATENCY);
                     return pceResult;
                 }
-
                 // Check if nodes are included in the hard constraints
-                if (!checkInclude(path, pceHardConstraints)) {
+                if (!checkInclude(path, pceHardConstraints, mode)) {
                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
                     pceResult.setLocalCause(PceResult.LocalCause.HD_NODE_INCLUDE);
                     return pceResult;
                 }
-
                 // TODO here other post algo validations can be added
                 // more data can be sent to PceGraph module via PceResult structure if required
-
                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
                 pceResult.setLocalCause(PceResult.LocalCause.NONE);
-                break;
+                return pceResult;
             case StringConstants.SERVICE_TYPE_100GE_M:
             case StringConstants.SERVICE_TYPE_10GE:
             case StringConstants.SERVICE_TYPE_1GE:
-                Map<String, Integer> tribSlotNbMap = Map.of(
-                    StringConstants.SERVICE_TYPE_100GE_M, 20,
-                    StringConstants.SERVICE_TYPE_10GE, 8,
-                    StringConstants.SERVICE_TYPE_1GE, 1);
-                int tribSlotNb = tribSlotNbMap.get(serviceType);
+                int tribSlotNb = Map.of(
+                        StringConstants.SERVICE_TYPE_100GE_M, 20,
+                        StringConstants.SERVICE_TYPE_10GE, 8,
+                        StringConstants.SERVICE_TYPE_1GE, 1)
+                    .get(serviceType);
                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
                 pceResult.setServiceType(serviceType);
                 Map<String, List<Uint16>> tribSlot = chooseTribSlot(path, allPceNodes, tribSlotNb);
                 Map<String, Uint16> tribPort = chooseTribPort(path, allPceNodes, tribSlot, tribSlotNb);
                 List<OpucnTribSlotDef> resultTribPortTribSlot = getMinMaxTpTs(tribPort, tribSlot);
-
                 if (resultTribPortTribSlot.get(0) != null && resultTribPortTribSlot.get(1) != null) {
                     pceResult.setResultTribPortTribSlot(resultTribPortTribSlot);
                     pceResult.setRC(ResponseCodes.RESPONSE_OK);
                     LOG.info("In PostAlgoPathValidator: found TribPort {} - tribSlot {} - tribSlotNb {}",
                         tribPort, tribSlot, tribSlotNb);
                 }
-                break;
+                return pceResult;
             case StringConstants.SERVICE_TYPE_ODU4:
             case StringConstants.SERVICE_TYPE_ODUC2:
             case StringConstants.SERVICE_TYPE_ODUC3:
@@ -150,20 +183,18 @@ public class PostAlgoPathValidator {
                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
                 pceResult.setServiceType(serviceType);
                 LOG.info("In PostAlgoPathValidator: ODU4/ODUCn path found {}", path);
-                break;
+                return pceResult;
             default:
                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
                 LOG.warn("In PostAlgoPathValidator checkPath: unsupported serviceType {} found {}",
                     serviceType, path);
-                break;
+                return pceResult;
         }
-        return pceResult;
     }
 
     // Check the latency
     private boolean checkLatency(Long maxLatency, GraphPath<String, PceGraphEdge> path) {
         double latency = 0;
-
         for (PceGraphEdge edge : path.getEdgeList()) {
             if (edge.link() == null || edge.link().getLatency() == null) {
                 LOG.warn("- In checkLatency: the link {} does not contain latency field",
@@ -177,53 +208,55 @@ public class PostAlgoPathValidator {
     }
 
     // Check the inclusion if it is defined in the hard constraints
-    private boolean checkInclude(GraphPath<String, PceGraphEdge> path, PceConstraints pceHardConstraintsInput) {
-        List<ResourcePair> listToInclude = pceHardConstraintsInput.getListToInclude()
-            .stream().sorted((rp1, rp2) -> rp1.getName().compareTo(rp2.getName()))
-            .collect(Collectors.toList());
+    //TODO: remove this checkstyle false positive warning when the checkstyle bug will be fixed
+    @SuppressWarnings("MissingSwitchDefault")
+    private boolean checkInclude(GraphPath<String, PceGraphEdge> path, PceConstraints pceHardConstraintsInput,
+            PceConstraintMode mode) {
+        List<ResourcePair> listToInclude = pceHardConstraintsInput.getListToInclude();
         if (listToInclude.isEmpty()) {
             return true;
         }
-
         List<PceGraphEdge> pathEdges = path.getEdgeList();
         LOG.debug(" in checkInclude vertex list: [{}]", path.getVertexList());
-
+        LOG.debug("listToInclude = {}", listToInclude);
         List<String> listOfElementsSubNode = new ArrayList<>();
         listOfElementsSubNode.add(pathEdges.get(0).link().getsourceNetworkSupNodeId());
-        listOfElementsSubNode.addAll(listOfElementsBuild(pathEdges, PceConstraints.ResourceType.NODE,
-            pceHardConstraintsInput));
-
+        listOfElementsSubNode.addAll(
+            listOfElementsBuild(pathEdges, PceConstraints.ResourceType.NODE, pceHardConstraintsInput));
         List<String> listOfElementsCLLI = new ArrayList<>();
         listOfElementsCLLI.add(pathEdges.get(0).link().getsourceCLLI());
-        listOfElementsCLLI.addAll(listOfElementsBuild(pathEdges, PceConstraints.ResourceType.CLLI,
-            pceHardConstraintsInput));
-
+        listOfElementsCLLI.addAll(
+            listOfElementsBuild(pathEdges, PceConstraints.ResourceType.CLLI, pceHardConstraintsInput));
         List<String> listOfElementsSRLG = new ArrayList<>();
         // first link is XPONDEROUTPUT, no SRLG for it
         listOfElementsSRLG.add("NONE");
-        listOfElementsSRLG.addAll(listOfElementsBuild(pathEdges, PceConstraints.ResourceType.SRLG,
-            pceHardConstraintsInput));
-
+        listOfElementsSRLG.addAll(
+            listOfElementsBuild(pathEdges, PceConstraints.ResourceType.SRLG, pceHardConstraintsInput));
         // validation: check each type for each element
-        List<String> listNodeToInclude = listToInclude
-                .stream().filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
-                .map(ResourcePair::getName).collect(Collectors.toList());
-        List<String> listSrlgToInclude = listToInclude
-                .stream().filter(rp -> PceConstraints.ResourceType.SRLG.equals(rp.getType()))
-                .map(ResourcePair::getName).collect(Collectors.toList());
-        List<String> listClliToInclude = listToInclude
-                .stream().filter(rp -> PceConstraints.ResourceType.CLLI.equals(rp.getType()))
-                .map(ResourcePair::getName).collect(Collectors.toList());
-
-        return listOfElementsSubNode.containsAll(listNodeToInclude)
-                && listOfElementsSRLG.containsAll(listSrlgToInclude)
-                && listOfElementsCLLI.containsAll(listClliToInclude);
+        LOG.debug("listOfElementsSubNode = {}", listOfElementsSubNode);
+        return switch (mode) {
+            case Loose -> listOfElementsSubNode
+                .containsAll(listToInclude.stream()
+                    .filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
+                    .map(ResourcePair::getName).collect(Collectors.toList()));
+            case Strict -> listOfElementsSubNode
+                .equals(listToInclude.stream()
+                    .filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
+                    .map(ResourcePair::getName).collect(Collectors.toList()));
+        }
+            && listOfElementsSRLG.containsAll(
+                listToInclude
+                    .stream().filter(rp -> PceConstraints.ResourceType.SRLG.equals(rp.getType()))
+                    .map(ResourcePair::getName).collect(Collectors.toList()))
+            && listOfElementsCLLI.containsAll(
+                listToInclude
+                    .stream().filter(rp -> PceConstraints.ResourceType.CLLI.equals(rp.getType()))
+                    .map(ResourcePair::getName).collect(Collectors.toList()));
     }
 
     private List<String> listOfElementsBuild(List<PceGraphEdge> pathEdges, PceConstraints.ResourceType type,
-        PceConstraints pceHardConstraints) {
-
-        List<String> listOfElements = new ArrayList<>();
+            PceConstraints pceHardConstraints) {
+        Set<String> listOfElements = new LinkedHashSet<>();
         for (PceGraphEdge link : pathEdges) {
             switch (type) {
                 case NODE:
@@ -259,37 +292,38 @@ public class PostAlgoPathValidator {
                     LOG.debug("listOfElementsBuild unsupported resource type");
             }
         }
-        return listOfElements;
+        return new ArrayList<>(listOfElements);
     }
 
     private Map<String, Uint16> chooseTribPort(GraphPath<String,
-        PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, Map<String, List<Uint16>> tribSlotMap, int nbSlot) {
+            PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, Map<String, List<Uint16>> tribSlotMap, int nbSlot) {
         LOG.debug("In choosetribPort: edgeList = {} ", path.getEdgeList());
         Map<String, Uint16> tribPortMap = new HashMap<>();
-
         for (PceGraphEdge edge : path.getEdgeList()) {
-            NodeId linkSrcNode = edge.link().getSourceId();
-            String linkSrcTp = edge.link().getSourceTP().getValue();
-            NodeId linkDestNode = edge.link().getDestId();
-            String linkDestTp = edge.link().getDestTP().getValue();
-            PceNode pceOtnNodeSrc = allPceNodes.get(linkSrcNode);
-            PceNode pceOtnNodeDest = allPceNodes.get(linkDestNode);
-            List<Uint16> srcTpnPool = pceOtnNodeSrc.getAvailableTribPorts().get(linkSrcTp);
-            List<Uint16> destTpnPool = pceOtnNodeDest.getAvailableTribPorts().get(linkDestTp);
+            List<Uint16> srcTpnPool =
+                allPceNodes
+                    .get(edge.link().getSourceId())
+                    .getAvailableTribPorts()
+                    .get(edge.link().getSourceTP().getValue());
+            List<Uint16> destTpnPool =
+                allPceNodes
+                    .get(edge.link().getDestId())
+                    .getAvailableTribPorts()
+                    .get(edge.link().getDestTP().getValue());
             List<Uint16> commonEdgeTpnPool = new ArrayList<>();
             for (Uint16 srcTpn : srcTpnPool) {
                 if (destTpnPool.contains(srcTpn)) {
                     commonEdgeTpnPool.add(srcTpn);
                 }
             }
-
-            if (!commonEdgeTpnPool.isEmpty()) {
-                Integer startTribSlot = tribSlotMap.values().stream().findFirst().get().get(0).toJava();
-                Integer tribPort = (int) Math.ceil((double)startTribSlot / nbSlot);
-                for (Uint16 commonTribPort : commonEdgeTpnPool) {
-                    if (tribPort.equals(commonTribPort.toJava())) {
-                        tribPortMap.put(edge.link().getLinkId().getValue(), commonTribPort);
-                    }
+            if (commonEdgeTpnPool.isEmpty()) {
+                continue;
+            }
+            Integer startTribSlot = tribSlotMap.values().stream().findFirst().orElseThrow().get(0).toJava();
+            Integer tribPort = (int) Math.ceil((double)startTribSlot / nbSlot);
+            for (Uint16 commonTribPort : commonEdgeTpnPool) {
+                if (tribPort.equals(commonTribPort.toJava())) {
+                    tribPortMap.put(edge.link().getLinkId().getValue(), commonTribPort);
                 }
             }
         }
@@ -298,19 +332,20 @@ public class PostAlgoPathValidator {
     }
 
     private Map<String, List<Uint16>> chooseTribSlot(GraphPath<String,
-        PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, int nbSlot) {
+            PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, int nbSlot) {
         LOG.debug("In choosetribSlot: edgeList = {} ", path.getEdgeList());
         Map<String, List<Uint16>> tribSlotMap = new HashMap<>();
-
         for (PceGraphEdge edge : path.getEdgeList()) {
-            NodeId linkSrcNode = edge.link().getSourceId();
-            String linkSrcTp = edge.link().getSourceTP().getValue();
-            NodeId linkDestNode = edge.link().getDestId();
-            String linkDestTp = edge.link().getDestTP().getValue();
-            PceNode pceOtnNodeSrc = allPceNodes.get(linkSrcNode);
-            PceNode pceOtnNodeDest = allPceNodes.get(linkDestNode);
-            List<Uint16> srcTsPool = pceOtnNodeSrc.getAvailableTribSlots().get(linkSrcTp);
-            List<Uint16> destTsPool = pceOtnNodeDest.getAvailableTribSlots().get(linkDestTp);
+            List<Uint16> srcTsPool =
+                allPceNodes
+                    .get(edge.link().getSourceId())
+                    .getAvailableTribSlots()
+                    .get(edge.link().getSourceTP().getValue());
+            List<Uint16> destTsPool =
+                allPceNodes
+                    .get(edge.link().getDestId())
+                    .getAvailableTribSlots()
+                    .get(edge.link().getDestTP().getValue());
             List<Uint16> commonEdgeTsPoolList = new ArrayList<>();
             List<Uint16> tribSlotList = new ArrayList<>();
             for (Uint16 integer : srcTsPool) {
@@ -321,8 +356,7 @@ public class PostAlgoPathValidator {
             Collections.sort(commonEdgeTsPoolList);
             List<Uint16> commonGoodStartEdgeTsPoolList = new ArrayList<>();
             for (Uint16 startEdgeTsPool : commonEdgeTsPoolList) {
-                if (Integer.valueOf(1).equals(startEdgeTsPool.toJava() % nbSlot)
-                        || nbSlot == 1) {
+                if (Integer.valueOf(1).equals(startEdgeTsPool.toJava() % nbSlot) || nbSlot == 1) {
                     commonGoodStartEdgeTsPoolList.add(startEdgeTsPool);
                 }
             }
@@ -351,53 +385,609 @@ public class PostAlgoPathValidator {
 
     private List<OpucnTribSlotDef> getMinMaxTpTs(Map<String, Uint16> tribPort, Map<String, List<Uint16>> tribSlot) {
         String tribport = tribPort.values().toArray()[0].toString();
-        @SuppressWarnings("unchecked")
-        List<Uint16> tsList = (List<Uint16>) tribSlot.values().toArray()[0];
-        OpucnTribSlotDef minOpucnTs = OpucnTribSlotDef
-            .getDefaultInstance(String.join(".", tribport, tsList.get(0).toString()));
-        OpucnTribSlotDef maxOpucnTs = OpucnTribSlotDef
-            .getDefaultInstance(String.join(".", tribport, tsList.get(tsList.size() - 1).toString()));
-        List<OpucnTribSlotDef> minmaxTpTsList = new ArrayList<>();
-        minmaxTpTsList.add(minOpucnTs);
-        minmaxTpTsList.add(maxOpucnTs);
-        return minmaxTpTsList;
+        @SuppressWarnings("unchecked") List<Uint16> tsList = (List<Uint16>) tribSlot.values().toArray()[0];
+        return new ArrayList<>(List.of(
+            OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(0).toString())),
+            OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(tsList.size() - 1).toString()))));
     }
 
-    // Check the path OSNR
-    private boolean checkOSNR(GraphPath<String, PceGraphEdge> path) {
-        double linkOsnrDb;
-        double osnrDb = 0;
-        LOG.info("- In checkOSNR: OSNR of the transmitter = {} dB", TRX_OSNR);
-        LOG.info("- In checkOSNR: add-path incremental OSNR = {} dB", ADD_OSNR);
-        double inverseLocalOsnr = getInverseOsnrLinkLu(TRX_OSNR) + getInverseOsnrLinkLu(ADD_OSNR);
-        for (PceGraphEdge edge : path.getEdgeList()) {
-            if (edge.link().getlinkType() == OpenroadmLinkType.ROADMTOROADM) {
-                // link OSNR in dB
-                linkOsnrDb = edge.link().getosnr();
-                LOG.info("- In checkOSNR: OSNR of {} = {} dB", edge.link().getLinkId().getValue(), linkOsnrDb);
-                // 1 over the local OSNR, in linear units
-                inverseLocalOsnr += getInverseOsnrLinkLu(linkOsnrDb);
+    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.
+     *
+     * @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 checkOSNRaz(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
+            Map<LinkId, PceLink> allPceLinks, String serviceType, CatalogUtils cu) {
+        Map<String, Double> signal = new HashMap<>(
+            Map.of(
+                "spacing", Double.valueOf(50.0),
+                "calcPdl2", Double.valueOf(0),
+                "calcCd", Double.valueOf(0),
+                "calcPmd2", Double.valueOf(0),
+                "calcOnsrLin", Double.valueOf(0.0001),
+                "pwrIn", Double.valueOf(-60.0),
+                "pwrOut", Double.valueOf(-60.0)));
+        double calcOnsrdB = 0;
+        double margin = 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 = 0; pathElement < 2; 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 AZ, Path Element = {}", pathElement);
+            switch (currentNode.getORNodeType()) {
+                case XPONDER:
+                    LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
+                    transponderPresent = true;
+                    calcXpdrOSNR(cu, signal,
+                        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);
+                    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);
+                    }
+                    signal.put("pwrIn", Double.valueOf(0));
+                    calcAddContrib(cu, signal, currentNode, edges.get(pathElement + 1).link());
+                    LOG.debug("loop of check OSNR direction AZ: SRG, pathElement = {} link {} Pout = {}",
+                        pathElement, pathElement + 1, signal.get("pwrOut"));
+                    double calcOnsr = signal.get("calcOnsrLin").doubleValue();
+                    if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == 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());
+                    calcDropContrib(cu, signal, currentNode, pceLink);
+                    double calcOnsr = signal.get("calcOnsrLin").doubleValue();
+                    if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == 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 {
+                        calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
+                        LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
+                        LOG.info("Loop pathElement = {}, DROP, calcOnsrdB= {}", pathElement, calcOnsrdB);
+                    } 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:
+                    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);
+                    calcBypassContrib(cu, signal, currentNode, nextNode,
+                        edges.get(pathElement - 1).link(), edges.get(pathElement + 1).link());
+                    double calcOnsrLin = signal.get("calcOnsrLin").doubleValue();
+                    LOG.debug(
+                        "Loop pathElement= {}, DEGREE, calcOnsrdB= {}", 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(),
+                        signal.get("calcCd"), signal.get("calcPmd2"), signal.get("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, signal, edges.get(vertices.size() - 2).link().getDestTP().getValue(),
+                    serviceType, currentNode, vertices.get(vertices.size() - 1), vertices.size() - 1);
+                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());
+                calcDropContrib(cu, signal, currentNode, pceLink);
+                double calcOnsr = signal.get("calcOnsrLin").doubleValue();
+                //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
+                if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == 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 {
+                    calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
+                    LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
+                    LOG.info("Loop pathElement = {}, DROP, calcOnsrdB= {}", vertices.size() - 1, calcOnsrdB);
+                } 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 calcOnsrdB = {} "
+            + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
+            signal.get("calcCd"), Math.sqrt(signal.get("calcPmd2").doubleValue()),
+            Math.sqrt(signal.get("calcPdl2").doubleValue()), calcOnsrdB,
+            getOsnrDbfromOnsrLin(signal.get("calcOnsrLin").doubleValue()));
+        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) {
+        Map<String, Double> signal = new HashMap<>(
+            Map.of(
+                "spacing", Double.valueOf(50.0),
+                "calcPdl2", Double.valueOf(0),
+                "calcCd", Double.valueOf(0),
+                "calcPmd2", Double.valueOf(0),
+                "calcOnsrLin", Double.valueOf(0.0001),
+                "pwrIn", Double.valueOf(-60.0),
+                "pwrOut", Double.valueOf(-60.0)));
+        double calcOnsrdB = 0;
+        double margin = 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;
+                    calcXpdrOSNR(cu, signal,
+                        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);
+                    break;
+                case SRG:
+                    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);
+                    }
+                    signal.put("pwrIn", Double.valueOf(0));
+                    calcAddContrib(cu, signal, currentNode, getOppPceLink(pathElement - 2, edges, allPceLinks));
+                    double calcOnsr = signal.get("calcOnsrLin").doubleValue();
+                    if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == 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 = 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);
+                    }
+                    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());
+                    calcDropContrib(cu, signal, currentNode, pceLink);
+                    double calcOnsr = signal.get("calcOnsrLin").doubleValue();
+                    if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == 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 {
+                        calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
+                        LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
+                        LOG.info("Loop Path Element = {}, DROP, calcOnsrdB= {}", pathElement, calcOnsrdB);
+                    } 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:
+                    if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
+                        //This is the case of DROP, ROADM degree is not considered
+                        break;
+                    }
+                    LOG.info("loop of check OSNR direction ZA: DEGREE, Path Element = {}", pathElement);
+                    calcBypassContrib(cu, signal, currentNode, nextNode,
+                        getOppPceLink(pathElement, edges, allPceLinks),
+                        getOppPceLink(pathElement - 2, edges, allPceLinks));
+                    double calcOnsrLin = signal.get("calcOnsrLin").doubleValue();
+                    LOG.debug("Loop Path Element = {}, DEGREE, calcOnsrdB= {}",
+                            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(),
+                        signal.get("calcCd"), signal.get("calcPmd2"), signal.get("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, signal, getOppPceLink(0, edges, allPceLinks).getDestTP().getValue(),
+                    serviceType, currentNode, vertices.get(0), 0);
+                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());
+                calcDropContrib(cu, signal, currentNode, pceLink);
+                double calcOnsr = signal.get("calcOnsrLin").doubleValue();
+                //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
+                if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == 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 {
+                    calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
+                    LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
+                    LOG.info("Loop Path Element = 0, DROP, calcOnsrdB= {}", calcOnsrdB);
+                } 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 calcOnsrdB = {} "
+            + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
+            signal.get("calcCd"), Math.sqrt(signal.get("calcPmd2").doubleValue()),
+            Math.sqrt(signal.get("calcPdl2").doubleValue()), calcOnsrdB,
+            getOsnrDbfromOnsrLin(signal.get("calcOnsrLin").doubleValue()));
+        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: Z to A Path from {} to {} {}",
+                vertices.get(vertices.size() - 1), vertices.get(0), validationMessage);
+        return delta;
+    }
+
+    private String setOpMode(String opMode, String defaultMode) {
+        return
+            opMode == null || opMode.isEmpty() || opMode.contentEquals(StringConstants.UNKNOWN_MODE)
+                ? defaultMode
+                : opMode;
+    }
+
+    private PceLink getOppPceLink(Integer pathEltNber, List<PceGraphEdge> edges,
+            Map<LinkId, PceLink> allPceLinks) {
+        return allPceLinks.get(new LinkId(edges.get(pathEltNber).link().getOppositeLink()));
+    }
+
+    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 {
-            osnrDb = getOsnrDb(1 / inverseLocalOsnr);
-        } catch (ArithmeticException e) {
-            LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}", path.getEdgeList().size());
-            return false;
+            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().orElseThrow().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);
+        }
+        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, Map<String, Double> signal,
+            String nwTpId, String serviceType, PceNode currentNode, String vertice, int pathElement) {
+        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 calcOnsrdB = getOsnrDbfromOnsrLin(signal.get("calcOnsrLin").doubleValue());
+        LOG.info("Loop Path Element = {}, XPDR, calcosnrdB= {}", pathElement, calcOnsrdB);
+        return cu.getPceRxTspParameters(
+            getXpdrOpMode(nwTpId, vertice, pathElement, currentNode, serviceType, cu),
+            signal.get("calcCd").doubleValue(),
+            Math.sqrt(signal.get("calcPmd2").doubleValue()),
+            Math.sqrt(signal.get("calcPdl2").doubleValue()),
+            calcOnsrdB);
+    }
+
+    private void calcXpdrOSNR(
+            CatalogUtils cu, Map<String, Double> signal, 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
+        signal.put("spacing", Double.valueOf(cu.getPceTxTspChannelSpacing(opMode)));
+        signal.put("calcOnsrLin", Double.valueOf(calcOnsrLin));
+    }
+
+    private void calcDropContrib(
+            CatalogUtils cu, Map<String, Double> signal, PceNode currentNode, PceLink pceLink) {
+        //calculation of the SRG contribution for Drop
+        calcLineDegradation(cu, signal, pceLink);
+        Map<String, Double> 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
+            signal.get("pwrIn").doubleValue(),
+            signal.get("calcCd").doubleValue(),
+            signal.get("calcPmd2").doubleValue(),
+            signal.get("calcPdl2").doubleValue(),
+            signal.get("calcOnsrLin").doubleValue(),
+            signal.get("spacing").doubleValue());
+        signal.putAll(
+            Map.of(
+                "calcCd", impairments.get("CD"),
+                "calcPmd2", impairments.get("DGD2"),
+                "calcPdl2", impairments.get("PDL2"),
+                "calcOnsrLin", impairments.get("ONSRLIN")));
+    }
+
+    private void calcAddContrib(
+            CatalogUtils cu, Map<String, Double> signal, PceNode currentNode, PceLink pceLink) {
+        //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(), signal.get("spacing").doubleValue(), pceLink.getpowerCorrection());
+        //calculation of the SRG contribution either for Add and Drop
+        Map<String, Double> impairments = cu.getPceRoadmAmpParameters(cnt, srgMode, 0,
+            signal.get("calcCd").doubleValue(), signal.get("calcPmd2").doubleValue(),
+            signal.get("calcPdl2").doubleValue(),
+            signal.get("calcOnsrLin").doubleValue(), signal.get("spacing").doubleValue());
+        signal.putAll(
+            Map.of(
+                "calcCd", impairments.get("CD"),
+                "calcPmd2", impairments.get("DGD2"),
+                "calcPdl2", impairments.get("PDL2"),
+                "calcOnsrLin", impairments.get("ONSRLIN"),
+                "pwrOut", Double.valueOf(pwrOut)));
+    }
+
+    private void calcBypassContrib(CatalogUtils cu, Map<String, Double> signal,
+            PceNode currentNode, PceNode nextNode, PceLink pceLink0, PceLink pceLink1) {
+        // 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);
         }
-        LOG.info("In checkOSNR: OSNR of the path is {} dB", osnrDb);
-        return ((osnrDb + SYS_MARGIN) > MIN_OSNR_W100G);
+        calcLineDegradation(cu, signal, pceLink0);
+        CatalogNodeType cnt = CatalogConstant.CatalogNodeType.EXPRESS;
+        double pwrOut = cu.getPceRoadmAmpOutputPower(cnt, degree2Mode, pceLink1.getspanLoss(),
+            signal.get("spacing").doubleValue(), pceLink1.getpowerCorrection());
+        // Adds to accumulated impairments the degradation associated with the Express
+        // path of ROADM : Degree1, express link, Degree2
+        Map<String, Double> impairments = cu.getPceRoadmAmpParameters(cnt, degree2Mode,
+            signal.get("pwrIn").doubleValue(), signal.get("calcCd").doubleValue(),
+            signal.get("calcPmd2").doubleValue(), signal.get("calcPdl2").doubleValue(),
+            signal.get("calcOnsrLin").doubleValue(), signal.get("spacing").doubleValue());
+        signal.putAll(
+            Map.of(
+                "calcCd", impairments.get("CD"),
+                "calcPmd2", impairments.get("DGD2"),
+                "calcPdl2", impairments.get("PDL2"),
+                "calcOnsrLin", impairments.get("ONSRLIN"),
+                "pwrOut", Double.valueOf(pwrOut)));
     }
+    //TODO these methods might be more indicated in a catalog utils refactoring
 
-    private double getOsnrDb(double osnrLu) {
-        return (10 * Math.log10(osnrLu));
+    private void calcLineDegradation(CatalogUtils cu, Map<String, Double> signal, PceLink pceLink) {
+        // Calculate degradation accumulated across incoming Link and add them to
+        // accumulated impairments
+        // This also includes Non Linear Contribution from the path
+        signal.putAll(Map.of(
+            "pwrIn", Double.valueOf(signal.get("pwrOut").doubleValue() - pceLink.getspanLoss()),
+            "calcCd", Double.valueOf(signal.get("calcCd").doubleValue() + pceLink.getcd()),
+            "calcPmd2", Double.valueOf(signal.get("calcPmd2").doubleValue() + pceLink.getpmd2()),
+            "calcOnsrLin", Double.valueOf(
+                signal.get("calcOnsrLin").doubleValue()
+                + cu.calculateNLonsrContribution(
+                    signal.get("pwrOut").doubleValue(), pceLink.getLength(), signal.get("spacing").doubleValue()))));
     }
 
-    private double getInverseOsnrLinkLu(double linkOsnrDb) {
-        // 1 over the link OSNR, in linear units
-        double linkOsnrLu = Math.pow(10, (linkOsnrDb / 10.0));
-        LOG.debug("In retrieveosnr: the inverse of link osnr is {} (Linear Unit)", linkOsnrLu);
-        return (CONST_OSNR / linkOsnrLu);
+    private double getOsnrDbfromOnsrLin(double osnrLu) {
+        return 10 * Math.log10(1 / osnrLu);
     }
 
     /**
@@ -418,34 +1008,52 @@ public class PostAlgoPathValidator {
         boolean isFlexGrid = true;
         LOG.debug("Processing path {} with length {}", path, path.getLength());
         BitSet pceNodeFreqMap;
+        Set<PceNode> pceNodes = new LinkedHashSet<>();
+
         for (PceGraphEdge edge : path.getEdgeList()) {
-            LOG.debug("Processing source {} ", edge.link().getSourceId());
-            if (allPceNodes.containsKey(edge.link().getSourceId())) {
-                PceNode pceNode = allPceNodes.get(edge.link().getSourceId());
-                LOG.debug("Processing PCE node {}", pceNode);
-                if (StringConstants.OPENROADM_DEVICE_VERSION_1_2_1.equals(pceNode.getVersion())) {
-                    LOG.debug("Node {}: version is {} and slot width granularity is {} -> fixed grid mode",
-                        pceNode.getNodeId(), pceNode.getVersion(), pceNode.getSlotWidthGranularity());
-                    isFlexGrid = false;
-                }
-                if ((pceNode.getSlotWidthGranularity().setScale(0, RoundingMode.CEILING)
-                        .equals(GridConstant.SLOT_WIDTH_50))
-                        && (pceNode.getCentralFreqGranularity().setScale(0, RoundingMode.CEILING)
-                        .equals(GridConstant.SLOT_WIDTH_50))) {
-                    LOG.debug("Node {}: version is {} with slot width granularity  {} and central "
-                            + "frequency granularity is {} -> fixed grid mode",
-                        pceNode.getNodeId(), pceNode.getVersion(), pceNode.getSlotWidthGranularity(),
-                        pceNode.getCentralFreqGranularity());
-                    isFlexGrid = false;
-                }
-                pceNodeFreqMap = pceNode.getBitSetData();
-                LOG.debug("Pce node bitset {}", pceNodeFreqMap);
-                if (pceNodeFreqMap != null) {
-                    result.and(pceNodeFreqMap);
-                    LOG.debug("intermediate bitset {}", result);
-                }
+            NodeId srcId = edge.link().getSourceId();
+            NodeId dstId = edge.link().getDestId();
+            LOG.debug("Processing {} to {}", srcId.getValue(), dstId.getValue());
+            if (allPceNodes.containsKey(srcId)) {
+                pceNodes.add(allPceNodes.get(srcId));
+            }
+            if (allPceNodes.containsKey(dstId)) {
+                pceNodes.add(allPceNodes.get(dstId));
             }
         }
+
+        for (PceNode pceNode : pceNodes) {
+            LOG.debug("Processing PCE node {}", pceNode);
+            pceNodeFreqMap = pceNode.getBitSetData();
+            LOG.debug("Pce node bitset {}", pceNodeFreqMap);
+            if (pceNodeFreqMap != null) {
+                result.and(pceNodeFreqMap);
+                LOG.debug("intermediate bitset {}", result);
+            }
+            String pceNodeVersion = pceNode.getVersion();
+            BigDecimal sltWdthGran = pceNode.getSlotWidthGranularity();
+            if (StringConstants.OPENROADM_DEVICE_VERSION_1_2_1.equals(pceNodeVersion)) {
+                LOG.debug("Node {}: version is {} with slot width granularity {} - fixed grid mode",
+                    pceNode.getNodeId(), pceNodeVersion, sltWdthGran);
+                isFlexGrid = false;
+                continue;
+            }
+            if (!sltWdthGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)) {
+                continue;
+            }
+            BigDecimal ctralFreqGran = pceNode.getCentralFreqGranularity();
+            if (!ctralFreqGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)) {
+                continue;
+            }
+            LOG.debug(
+                "Node {}: version is {} with slot width and central frequency granularities {} {} - fixed grid mode",
+                pceNode.getNodeId(), pceNodeVersion, sltWdthGran, ctralFreqGran);
+            isFlexGrid = false;
+        }
+        if (spectrumConstraint != null) {
+            result.and(spectrumConstraint);
+        }
+
         LOG.debug("Bitset result {}", result);
         return computeBestSpectrumAssignment(result, spectralWidthSlotNumber, isFlexGrid);
     }
@@ -459,19 +1067,19 @@ public class PostAlgoPathValidator {
      * @return a spectrum assignment object which contains begin and stop index. If
      *         no spectrum assignment found, beginIndex = stopIndex = 0
      */
-    private SpectrumAssignment computeBestSpectrumAssignment(BitSet spectrumOccupation, int spectralWidthSlotNumber,
-            boolean isFlexGrid) {
+    private SpectrumAssignment computeBestSpectrumAssignment(
+            BitSet spectrumOccupation, int spectralWidthSlotNumber, boolean isFlexGrid) {
         SpectrumAssignmentBuilder spectrumAssignmentBldr = new SpectrumAssignmentBuilder()
             .setBeginIndex(Uint16.valueOf(0))
             .setStopIndex(Uint16.valueOf(0))
             .setFlexGrid(isFlexGrid);
         BitSet referenceBitSet = new BitSet(spectralWidthSlotNumber);
         referenceBitSet.set(0, spectralWidthSlotNumber);
-        int nbSteps = isFlexGrid ? spectralWidthSlotNumber : 1;
         //higher is the frequency, smallest is the wavelength number
         //in operational, the allocation is done through wavelength starting from the smallest
         //so we have to loop from the last element of the spectrum occupation
-        for (int i = spectrumOccupation.size(); i >= spectralWidthSlotNumber; i -= nbSteps) {
+        for (int i = spectrumOccupation.size(); i >= spectralWidthSlotNumber;
+                i -= isFlexGrid ? spectralWidthSlotNumber : 1) {
             if (spectrumOccupation.get(i - spectralWidthSlotNumber, i).equals(referenceBitSet)) {
                 spectrumAssignmentBldr.setBeginIndex(Uint16.valueOf(i - spectralWidthSlotNumber));
                 spectrumAssignmentBldr.setStopIndex(Uint16.valueOf(i - 1));
@@ -480,4 +1088,8 @@ public class PostAlgoPathValidator {
         }
         return spectrumAssignmentBldr.build();
     }
+
+    public Double getTpceCalculatedMargin() {
+        return tpceCalculatedMargin;
+    }
 }