Primitives to retrieve parameters from Catalog 18/101918/14
authororenais <olivier.renais@orange.com>
Fri, 29 Jul 2022 15:31:40 +0000 (17:31 +0200)
committerguillaume.lambert <guillaume.lambert@orange.com>
Tue, 9 Aug 2022 09:30:20 +0000 (11:30 +0200)
- add a CatalogUtils and a Catalogconstant class in common module
  with primitives to retrieve parameters from Catalog
- add a related Penalty comparator serializable class
- add related Junit with a sample apidocCatalog json file
  that sticks to latest version of OpenROADM optical specification v5.1

JIRA: TRNSPRTPCE-518
Signed-off-by: orenais <olivier.renais@orange.com>
Change-Id: I8e898d86d44ea9a8f3a58eb581c3fe6515534221

common/pom.xml
common/src/main/java/org/opendaylight/transportpce/common/catalog/CatalogConstant.java [new file with mode: 0644]
common/src/main/java/org/opendaylight/transportpce/common/catalog/CatalogUtils.java [new file with mode: 0644]
common/src/main/java/org/opendaylight/transportpce/common/catalog/PenaltiesComparator.java [new file with mode: 0644]
common/src/test/java/org/opendaylight/transportpce/common/catalog/CatalogUtilsTest.java [new file with mode: 0644]
common/src/test/resources/apidocCatalog10_1OptSpecV5_1.json [new file with mode: 0644]

index 3adffdf97e499e1a6f48e298a0d1daee621927dc..ad5162fb248fe8863c49eb5c340b4b1d2c6ca3cf 100644 (file)
       <artifactId>transportpce-ordmodels-device</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>${project.groupId}.ordmodels</groupId>
+      <artifactId>transportpce-ordmodels-service</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>${project.groupId}.ordmodels</groupId>
       <artifactId>transportpce-ordmodels-network</artifactId>
diff --git a/common/src/main/java/org/opendaylight/transportpce/common/catalog/CatalogConstant.java b/common/src/main/java/org/opendaylight/transportpce/common/catalog/CatalogConstant.java
new file mode 100644 (file)
index 0000000..423ab69
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2022 Orange, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.transportpce.common.catalog;
+
+
+
+/**
+ * Util class for Catalog constants and impairment calculation.
+ */
+
+
+public final class CatalogConstant {
+
+    public static final String ORW100GSC = "OR-W-100G-SC";
+    public static final String ORW100GOFEC316GBD = "OR-W-100G-oFEC-31.6Gbd";
+    public static final String ORW200GOFEC316GBD = "OR-W-200G-oFEC-31.6Gbd";
+    public static final String ORW200GOFEC631GBD = "OR-W-200G-oFEC-63.1Gbd";
+    public static final String ORW300GOFEC631GBD = "OR-W-300G-oFEC-63.1Gbd";
+    public static final String ORW400GOFEC631GBD = "OR-W-400G-oFEC-63.1Gbd";
+    public static final String MWWRCORE = "MW-WR-core";
+    public static final String MWMWCORE = "MW-MW-core";
+    public static final String MWISTANDARD = "MWi-standard";
+    public static final String MWILOWNOISE = "MWi-low-noise";
+    public static final double NLCONSTANTC1 = -2.0;
+    public static final double NLCONSTANTC0UPTO875 = 43.4;
+    public static final double NLCONSTANTC0UPTO1000 = 45.4;
+    public static final double NLCONSTANTC0UPTO1125 = 46.1;
+    public static final double NLCONSTANTC0UPTO1625 = 48.6;
+    public static final double NLCONSTANTC0GT1625 = 60.0;
+    public static final double NLCONSTANTCE = 11.33;
+    public static final double NLCONSTANTEX = -0.09;
+
+    public enum CatalogNodeType { ADD, DROP, EXPRESS, AMP, TSP }
+
+    private CatalogConstant() {
+    }
+}
diff --git a/common/src/main/java/org/opendaylight/transportpce/common/catalog/CatalogUtils.java b/common/src/main/java/org/opendaylight/transportpce/common/catalog/CatalogUtils.java
new file mode 100644 (file)
index 0000000..0fab6de
--- /dev/null
@@ -0,0 +1,752 @@
+/*
+ * Copyright © 2022 Orange, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.transportpce.common.catalog;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.transportpce.common.StringConstants;
+import org.opendaylight.transportpce.common.network.NetworkTransactionService;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.ImpairmentType;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.amplifier.parameters.Amplifier;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.OpenroadmOperationalModes;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.SpecificOperationalModes;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.Amplifiers;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.Roadms;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.XpondersPluggables;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.xponders.pluggables.XponderPluggableOpenroadmOperationalMode;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.xponders.pluggables.XponderPluggableOpenroadmOperationalModeKey;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.specific.operational.modes.SpecificOperationalMode;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.specific.operational.modes.SpecificOperationalModeKey;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.common.amplifier.drop.parameters.OpenroadmOperationalMode;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.common.amplifier.drop.parameters.OpenroadmOperationalModeKey;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.Add;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.add.AddOpenroadmOperationalMode;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.add.AddOpenroadmOperationalModeKey;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.drop.parameters.Drop;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.express.parameters.Express;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.Penalties;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.PenaltiesKey;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.TXOOBOsnrKey;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.OperationalModeCatalog;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class for Catalog. Following methods are used to retrieve parameters
+ * from the specification catalog. They point to either openROADM or specific
+ * operational modes. They provide to the PCE, the OLM and the Renderer, the
+ * required parameters to calculate impairments and set output power levels
+ * according to the specifications.
+ *
+ */
+public class CatalogUtils {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CatalogUtils.class);
+    private final PenaltiesComparator penaltiesComparator = new PenaltiesComparator();
+    private NetworkTransactionService networkTransactionService;
+
+    public CatalogUtils(NetworkTransactionService networkTransactionService) {
+        this.networkTransactionService = networkTransactionService;
+    }
+
+    /**
+     * Following method returns default OperationalModeId for devices that do not expose them.
+     *
+     * @param catalogNodeType
+     *            identifies type of nodes in the catalog
+     * @param serviceType
+     *            allows for Xponder selecting default mode according to the rate
+     *
+     * @return a default operational mode that corresponds to initial specifications
+     *
+     */
+    public String getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType catalogNodeType,
+            String serviceType) {
+        String operationalModeId = "";
+
+        switch (catalogNodeType) {
+            case ADD:
+            case DROP:
+                operationalModeId = CatalogConstant.MWWRCORE;
+                break;
+            case EXPRESS:
+                operationalModeId = CatalogConstant.MWMWCORE;
+                break;
+            case AMP:
+                operationalModeId = CatalogConstant.MWISTANDARD;
+                break;
+            case TSP:
+                if (StringConstants.SERVICE_TYPE_100GE_T.contentEquals(serviceType)
+                        || StringConstants.SERVICE_TYPE_OTU4.contentEquals(serviceType)) {
+                    operationalModeId = CatalogConstant.ORW100GSC;
+                }
+                if (StringConstants.SERVICE_TYPE_OTUC2.contentEquals(serviceType)) {
+                    operationalModeId = CatalogConstant.ORW200GOFEC316GBD;
+                }
+                if (StringConstants.SERVICE_TYPE_OTUC3.contentEquals(serviceType)) {
+                    operationalModeId = CatalogConstant.ORW300GOFEC631GBD;
+                }
+                if ((StringConstants.SERVICE_TYPE_OTUC4.contentEquals(serviceType))
+                        || (StringConstants.SERVICE_TYPE_400GE.contentEquals(serviceType))) {
+                    operationalModeId = CatalogConstant.ORW400GOFEC631GBD;
+                }
+                break;
+            default:
+                LOG.warn("Unsupported catalogNodeType {}", catalogNodeType);
+                break;
+        }
+        return operationalModeId;
+    }
+
+    /**
+     * This method retrieves channel-spacing associated with a Xponder TX.
+     *
+     * @param operationalModeId
+     *            operational-mode-Id of the Xponder (OR or Specific)
+     *
+     * @return the channel spacing used to correct OSNR contribution values from
+     *         ROADMs and amplifiers
+     * @throws RuntimeException
+     *             if operationalModeId is not described in the catalog
+     */
+
+    public double getPceTxTspChannelSpacing(String operationalModeId) {
+        double spacing = 0.0;
+        double rolloff = 0.2;
+        XponderPluggableOpenroadmOperationalMode orTspOM = null;
+        SpecificOperationalMode speTspOM = null;
+
+        if (operationalModeId.startsWith("OR")) {
+            InstanceIdentifier<XponderPluggableOpenroadmOperationalMode> omCatalogIid = InstanceIdentifier
+                .builder(OperationalModeCatalog.class)
+                .child(OpenroadmOperationalModes.class)
+                .child(XpondersPluggables.class)
+                .child(XponderPluggableOpenroadmOperationalMode.class,
+                    new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
+                .build();
+            try {
+                Optional<XponderPluggableOpenroadmOperationalMode> omOptional = networkTransactionService
+                    .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
+                if (omOptional.isPresent()) {
+                    orTspOM = omOptional.get();
+                    if ((orTspOM.getMaxRollOff() == null) || (orTspOM.getMaxRollOff().doubleValue() == 0)) {
+                        if (CatalogConstant.ORW100GSC.contentEquals(operationalModeId)) {
+                            spacing = 50.0;
+                            LOG.info("Operational Mode {} associated channel spacing is {}",
+                                operationalModeId, spacing);
+
+                        } else {
+                            spacing = (Math.ceil(
+                                orTspOM.getBaudRate().doubleValue() * (1 + 0.2) / 12.5)) * 12.5;
+                            LOG.warn("Did not succeed in retrieving rolloff factor from Operational Mode {},"
+                                + " used default value of 0.2  --> Please check operational mode catalog,"
+                                + " Rolloff factor is a mandatory parameter", operationalModeId);
+                        }
+                    } else {
+                        spacing = (Math.ceil(
+                            orTspOM.getBaudRate().doubleValue() * (1 + orTspOM.getMaxRollOff().doubleValue()) / 12.5))
+                            * 12.5;
+                    }
+                    LOG.info("Operational Mode {} associated channel spacing is {}",
+                        operationalModeId, spacing);
+                }
+            } catch (InterruptedException | ExecutionException e) {
+                LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
+                throw new RuntimeException(
+                    "Operational mode not populated in Catalog : "
+                        + omCatalogIid + " :" + e);
+            } finally {
+                networkTransactionService.close();
+            }
+
+        } else {
+            // In other cases, means the mode is a non OpenROADM specific Operational Mode
+            InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
+                .builder(OperationalModeCatalog.class)
+                .child(SpecificOperationalModes.class)
+                .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
+                .build();
+            try {
+                var somOptional = networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid)
+                    .get();
+                if (somOptional.isPresent()) {
+                    speTspOM = somOptional.get();
+                    if ((speTspOM.getMaxRollOff() == null) || (speTspOM.getMaxRollOff().doubleValue() == 0)) {
+                        spacing = (Math.ceil(speTspOM.getBaudRate().doubleValue() * (1 + 0.2) / 12.5)) * 12.5;
+                        LOG.warn("Did not succeed in retrieving rolloff factor from Operational Mode {},"
+                            + " used default value of 0.2  --> Please check operational mode catalog,"
+                            + " Rolloff factor is a mandatory parameter", operationalModeId);
+                    } else {
+                        spacing = (Math.ceil(
+                            speTspOM.getBaudRate().doubleValue() * (1 + speTspOM.getMaxRollOff().doubleValue()) / 12.5))
+                            * 12.5;
+                    }
+                    LOG.info("Operational Mode {} associated channel spacing is {}",
+                        operationalModeId, spacing);
+                }
+            } catch (InterruptedException | ExecutionException e) {
+                LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
+                throw new RuntimeException(
+                    "Operational mode not populated in Catalog : "
+                        + omCatalogIid + " :" + e);
+            } finally {
+                networkTransactionService.close();
+            }
+        }
+        return spacing;
+    }
+
+    /**
+     * This method retrieves performance parameters associated with a Xponder TX.
+     *
+     * @param operationalModeId
+     *            operational-mode-Id of the Xponder (OR or Specific)
+     * @param addDropMuxOperationalModeId
+     *            operational-mode-Id of the Add-Drop bloc the XponderTX is
+     *            associated to (conditions TX-OOB OSNR value)
+     *
+     * @return the linear Optical Noise to signal Ratio
+     * @throws RuntimeException
+     *             if operationalModeId is not described in the catalog
+     */
+    public double getPceTxTspParameters(String operationalModeId, String addDropMuxOperationalModeId) {
+        double txOnsrLin = 0.0;
+        XponderPluggableOpenroadmOperationalMode orTspOM = null;
+        SpecificOperationalMode speTspOM = null;
+
+        if (operationalModeId.startsWith("OR")) {
+            InstanceIdentifier<XponderPluggableOpenroadmOperationalMode> omCatalogIid = InstanceIdentifier
+                .builder(OperationalModeCatalog.class)
+                .child(OpenroadmOperationalModes.class)
+                .child(XpondersPluggables.class)
+                .child(XponderPluggableOpenroadmOperationalMode.class,
+                    new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
+                .build();
+            try {
+                Optional<XponderPluggableOpenroadmOperationalMode> omOptional = networkTransactionService
+                    .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
+                if (omOptional.isPresent()) {
+                    orTspOM = omOptional.get();
+                    LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orTspOM);
+                    TXOOBOsnrKey key = new TXOOBOsnrKey(addDropMuxOperationalModeId);
+                    if (orTspOM.getMinTXOsnr() != null) {
+                        txOnsrLin = 1.0 / (Math.pow(10.0, (orTspOM.getMinTXOsnr().getValue().doubleValue() / 10.0)));
+                    }
+                    if (orTspOM.nonnullTXOOBOsnr().get(key) != null
+                            && orTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrSingleChannelValue() != null) {
+                        // To 1/(Xponder Min TX OSNR lin) Add 1/(Xponder TX OOB OSNR Single channel lin)
+                        txOnsrLin = txOnsrLin + 1.0 / (Math.pow(10.0, (orTspOM.nonnullTXOOBOsnr().get(key)
+                            .getMinOOBOsnrSingleChannelValue().getValue().doubleValue() / 10.0)));
+                    }
+                    if (orTspOM.getTXOOBOsnr() != null && orTspOM.nonnullTXOOBOsnr().get(key)
+                            .getMinOOBOsnrMultiChannelValue() != null) {
+                        // To resulting 1/(OSNR lin) Add 1/(Xponder TX OOB OSNR Multi channel lin)
+                        // contribution
+                        txOnsrLin = txOnsrLin + 1.0 / (Math.pow(10.0, (orTspOM.nonnullTXOOBOsnr().get(key)
+                            .getMinOOBOsnrMultiChannelValue().getValue().doubleValue() / 10.0)));
+                    }
+                }
+            } catch (InterruptedException | ExecutionException e) {
+                LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
+                throw new RuntimeException(
+                    "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
+                        + e);
+            } finally {
+                networkTransactionService.close();
+            }
+
+        } else {
+            // In other cases, means the mode is a non OpenROADM specific Operational Mode
+            InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
+                .builder(OperationalModeCatalog.class)
+                .child(SpecificOperationalModes.class)
+                .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
+                .build();
+            try {
+                var somOptional = networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid)
+                    .get();
+                if (somOptional.isPresent()) {
+                    speTspOM = somOptional.get();
+                    LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", speTspOM);
+                    TXOOBOsnrKey key = new TXOOBOsnrKey(addDropMuxOperationalModeId);
+                    if (speTspOM.getMinTXOsnr() != null) {
+                        txOnsrLin = 1.0 / (Math.pow(10.0, (speTspOM.getMinTXOsnr().getValue().doubleValue() / 10.0)));
+                    }
+                    if (speTspOM.nonnullTXOOBOsnr().get(key) != null
+                            && speTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrSingleChannelValue() != null) {
+                        // Add to 1/(Transponder Min TX OSNR lin) 1/(Transponder TX OOB OSNR Single
+                        // channel lin)
+                        txOnsrLin = txOnsrLin + 1.0 / (Math.pow(10.0, (speTspOM.nonnullTXOOBOsnr().get(key)
+                            .getMinOOBOsnrSingleChannelValue().getValue().doubleValue() / 10.0)));
+                    }
+                    if (speTspOM.nonnullTXOOBOsnr().get(key) != null
+                            && speTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrMultiChannelValue() != null) {
+                        // Add to resulting 1/(OSNR lin) 1/(Transponder TX OOB OSNR Multi channel lin)
+                        // contribution
+                        txOnsrLin = txOnsrLin + 1.0 / (Math.pow(10.0, (speTspOM.nonnullTXOOBOsnr().get(key)
+                            .getMinOOBOsnrMultiChannelValue().getValue().doubleValue() / 10.0)));
+                    }
+                }
+            } catch (InterruptedException | ExecutionException e) {
+                LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
+                throw new RuntimeException(
+                    "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
+                        + e);
+            } finally {
+                networkTransactionService.close();
+            }
+        }
+        return txOnsrLin;
+    }
+
+    /**
+     * This method retrieves performance parameters associated with a Xponder RX.
+     * It calls getRxTspPenalty to evaluate the penalty associated with CD/PMD/PDL
+     * It compares expected OSNR with the OSNR resulting from the line degradation,
+     * and finally calculates and return the resulting margin.
+     *
+     * @param operationalModeId
+     *            operational-mode-Id of the Xponder (OR or Specific)
+     * @param calcCd
+     *            accumulated chromatic dispersion across the line
+     * @param calcPmd
+     *            accumulated Polarization mode dispersion across the line
+     * @param calcPdl
+     *            accumulated Polarization Dependant Loss across the line
+     * @param calcOsnrdB
+     *            Optical Signal to Noise Ratio (dB)resulting from the transmission
+     *            on the line, that shall include the Non Linear contribution
+     *
+     * @return the margin on the service path
+     * @throws RuntimeException
+     *             if operationalModeId is not described in the catalog
+     */
+    public double getPceRxTspParameters(String operationalModeId, double calcCd, double calcPmd,
+            double calcPdl, double calcOsnrdB) {
+        HashMap<String, Double> impairments = new HashMap<>();
+        double totalPenalty = 0.0;
+        double penalty ;
+        double rxOsnrdB = 0.0;
+        double margin = -9999.9;
+        XponderPluggableOpenroadmOperationalMode orTspOM = null;
+        SpecificOperationalMode speTspOM = null;
+        Map<PenaltiesKey, Penalties> penaltiesMap = null;
+        if (operationalModeId.startsWith("OR")) {
+            var omCatalogIid = InstanceIdentifier
+                .builder(OperationalModeCatalog.class)
+                .child(OpenroadmOperationalModes.class)
+                .child(XpondersPluggables.class)
+                .child(XponderPluggableOpenroadmOperationalMode.class,
+                    new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
+                .build();
+            try {
+                Optional<XponderPluggableOpenroadmOperationalMode> omOptional = networkTransactionService
+                    .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
+                if (omOptional.isPresent()) {
+                    orTspOM = omOptional.get();
+                    LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orTspOM);
+                    if (orTspOM.getMinRXOsnrTolerance() != null) {
+                        rxOsnrdB = orTspOM.getMinRXOsnrTolerance().getValue().doubleValue();
+                    }
+                    penaltiesMap = orTspOM.getPenalties();
+                }
+            } catch (InterruptedException | ExecutionException e) {
+                LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
+                throw new RuntimeException(
+                    "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
+                        + e);
+            } finally {
+                networkTransactionService.close();
+            }
+        } else {
+            // In other cases, means the mode is a non OpenROADM specific Operational Mode
+            // InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
+            var omCatalogIid = InstanceIdentifier
+                .builder(OperationalModeCatalog.class)
+                .child(SpecificOperationalModes.class)
+                .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
+                .build();
+            try {
+                Optional<SpecificOperationalMode> somOptional = networkTransactionService
+                    .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
+                if (somOptional.isPresent()) {
+                    speTspOM = somOptional.get();
+                    LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", speTspOM);
+                    if (speTspOM.getMinRXOsnrTolerance() != null) {
+                        rxOsnrdB = speTspOM.getMinRXOsnrTolerance().getValue().doubleValue();
+                    }
+                    penaltiesMap = speTspOM.getPenalties();
+                }
+            } catch (InterruptedException | ExecutionException e) {
+                LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
+                throw new RuntimeException(
+                    "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
+                        + e);
+            } finally {
+                networkTransactionService.close();
+            }
+        }
+        if (penaltiesMap != null) {
+            penalty = getRxTspPenalty(calcCd, ImpairmentType.CDPsNm, penaltiesMap);
+            impairments.put("CDpenalty", penalty);
+            totalPenalty = totalPenalty + penalty;
+            penalty = getRxTspPenalty(calcPmd, ImpairmentType.PMDPs, penaltiesMap);
+            impairments.put("PMD Penalty", penalty);
+            totalPenalty = totalPenalty + penalty;
+            penalty = getRxTspPenalty(calcPdl, ImpairmentType.PDLDB, penaltiesMap);
+            impairments.put("PDL penalty", penalty);
+            totalPenalty = totalPenalty + penalty;
+            // For Future work since at that time we have no way to calculate the following
+            // parameters,even if penalties are defined in the OpenROADM specifications
+            //
+            // impairments.put("Colorless Drop Adjacent Xtalk Penalty", getRxTspPenalty(TBD,
+            // ImpairmentType.ColorlessDropAdjacentChannelCrosstalkGHz, penalitiesMap));
+            // impairments.put("XTalk total Power Penalty", getRxTspPenalty(TBD,
+            // ImpairmentType.CrossTalkTotalPowerDB, penalitiesMap));
+            // impairments.put("Power penalty", getRxTspPenalty(TBD,
+            // ImpairmentType.PowerDBm, penalitiesMap));
+            LOG.info("Penalty resulting from CD, PMD and PDL is {} dB with following contributions {}",
+                totalPenalty, impairments);
+            margin = calcOsnrdB - totalPenalty - rxOsnrdB;
+            LOG.info("According to RX TSP Specification and calculated impairments Margin is {} dB ", margin);
+            if (margin < 0) {
+                LOG.info("Negative margin shall result in PCE rejecting the analyzed path");
+            }
+        } else {
+            LOG.info("Unable to calculate margin as penaltyMap can not be retrieved : Operational mode not populated");
+        }
+        return margin;
+    }
+
+    /**
+     * This generic method is called from getPceRxTspParameters to provide the
+     * Penalties associated with CD, PMD and DGD for Xponder. It scans a penalty
+     * list that includes penalty values corresponding to an interval between an
+     * upper and a lower boundary for each of the above parameters.
+     *
+     * @param impairmentType
+     *            : the type of impairment (CD/PMD/DGD)
+     * @param calculatedParameter
+     *            calculated accumulated value on the line for the impairment
+     * @param penaltiesMap
+     *            the global map of penalties retrieved by getPceRxTspParameters
+     *            from the Xponder operational mode
+     *
+     * @return the penalty associated with accumulated impairment if it is in the
+     *         range specified in the table, a value that will lead to reject the
+     *         path if this is not the case
+     */
+
+    private double getRxTspPenalty(double calculatedParameter, ImpairmentType impairmentType,
+        Map<PenaltiesKey, Penalties> penalitiesMap) {
+        Penalties penalty = penalitiesMap.values().stream()
+            // We only keep penalties corresponding to the calculated Parameter
+            .filter(val -> val.getParameterAndUnit().getName().equals(impairmentType.getName()))
+            // we sort it according to the comparator (based on up-to-boundary)
+            .sorted(penaltiesComparator)
+            // keep only items for which up to boundary is greater than calculatedParameter
+            .filter(val -> val.getUpToBoundary().doubleValue() >= calculatedParameter)
+            // takes the immediate greater or equal value
+            .findFirst().orElse(null);
+
+        if (penalty == null) {
+            //means a boundary that is greater than calculatedParameter couldn't be found
+            // Out of specification!
+            return 9999.9;
+        }
+        // In spec, return penalty associated with calculatedParameter
+        return penalty.getPenaltyValue().getValue().doubleValue();
+    }
+
+    /**
+     * This method retrieves performance parameters associated with ROADMs and
+     * Amplifiers. It calculates the contribution of the node to the degradation of
+     * the signal for CD, DGD, PDL, and OSNR which is calculated through a
+     * polynomial fit described in the catalog. It finally corrects the accumulated
+     * values for these parameters and return them.
+     *
+     * @param catalogNodeType
+     *            crossed node path type (ADD/DROP/EXPRESS/AMP)
+     * @param operationalModeId
+     *            operational-mode-Id of the Node (OpenROADM only)
+     * @param calcCd
+     *            accumulated chromatic dispersion across the line
+     * @param calcDgd2
+     *            Square of accumulated Group velocity dispersion across the line
+     * @param calcPdl2
+     *            Square of the accumulated Polarization Dependant Loss across the
+     *            line
+     * @param pwrIn
+     *            Input power required to calculate OSNR contribution of the node =
+     *            f(pwrIn)
+     * @param calcOnsrLin
+     *            Linear Optical Noise to Signal Ratio resulting from the
+     *            transmission on the line, that shall include the Non Linear
+     *            contribution
+     * @param spacing
+     *            Interchannel spacing used for correction to calculate OSNR
+     *            contribution of the node
+     *
+     * @return Impairment, a map that provides corrected values for all calculated
+     *         parameters which includes the contribution of the node
+     *         (CD/DGD2/PDL2/ONSRLin)
+     * @throws RuntimeException
+     *             if operationalModeId is not described in the catalog
+     */
+
+    public Map<String, Double> getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType catalogNodeType,
+        String operationalModeId, double pwrIn, double calcCd, double calcDgd2, double calcPdl2,
+        double calcOnsrLin, double spacing) {
+
+        Map<String, Double> impairments = new HashMap<>();
+        double pdl2 = calcPdl2;
+        double dgd2 = calcDgd2;
+        double cd = calcCd;
+        double onsrLin = calcOnsrLin;
+        boolean supportedMode = true;
+
+        switch (catalogNodeType) {
+            case ADD:
+                var omCatalogIid = InstanceIdentifier
+                    .builder(OperationalModeCatalog.class)
+                    .child(OpenroadmOperationalModes.class)
+                    .child(Roadms.class)
+                    .child(Add.class)
+                    .child(AddOpenroadmOperationalMode.class, new AddOpenroadmOperationalModeKey(operationalModeId))
+                    .build();
+                try {
+                    var omOptional = networkTransactionService.read(LogicalDatastoreType.CONFIGURATION,
+                        omCatalogIid).get();
+                    if (omOptional.isPresent()) {
+                        var orAddOM = omOptional.get();
+                        LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAddOM);
+                        networkTransactionService.close();
+                        onsrLin = onsrLin
+                            + Math.pow(10, (-orAddOM.getIncrementalOsnr().getValue().doubleValue()
+                            - Math.log10(spacing / 50.0)) / 10.0);
+                        cd = cd + orAddOM.getMaxIntroducedCd().doubleValue();
+                        pdl2 = pdl2 + Math.pow(orAddOM.getMaxIntroducedPdl().getValue().doubleValue(), 2.0);
+                        dgd2 = dgd2 + Math.pow(orAddOM.getMaxIntroducedDgd().doubleValue(), 2.0);
+                    } else {
+                        supportedMode = false;
+                    }
+                } catch (InterruptedException | ExecutionException e) {
+                    onsrLin = 1;
+                    LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
+                        omCatalogIid);
+                    throw new RuntimeException(
+                        "readMdSal: Error reading from operational store, Operational Mode Catalog : "
+                            + omCatalogIid + " :" + e);
+                } finally {
+                    networkTransactionService.close();
+                }
+                break;
+
+            case DROP:
+                var omCatalogIid1 = InstanceIdentifier
+                    .builder(OperationalModeCatalog.class)
+                    .child(OpenroadmOperationalModes.class)
+                    .child(Roadms.class)
+                    .child(Drop.class)
+                    .child(OpenroadmOperationalMode.class, new OpenroadmOperationalModeKey(operationalModeId))
+                    .build();
+                try {
+                    var omOptional = networkTransactionService
+                        .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid1)
+                        .get();
+                    if (omOptional.isPresent()) {
+                        var orDropOM = omOptional.get();
+                        LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orDropOM);
+                        networkTransactionService.close();
+                        cd = cd + orDropOM.getMaxIntroducedCd().doubleValue();
+                        pdl2 = pdl2 + Math.pow(orDropOM.getMaxIntroducedPdl().getValue().doubleValue(), 2.0);
+                        dgd2 = dgd2 + Math.pow(orDropOM.getMaxIntroducedDgd().doubleValue(), 2);
+                        onsrLin = onsrLin + Math.pow(10,
+                            -(orDropOM.getOsnrPolynomialFit().getA().doubleValue() * Math.pow(pwrIn, 3)
+                                + orDropOM.getOsnrPolynomialFit().getB().doubleValue() * Math.pow(pwrIn, 2)
+                                + orDropOM.getOsnrPolynomialFit().getC().doubleValue() * pwrIn
+                                + orDropOM.getOsnrPolynomialFit().getD().doubleValue()
+                                + 10 * Math.log10(spacing / 50.0)) / 10);
+                    } else {
+                        supportedMode = false;
+                    }
+                } catch (InterruptedException | ExecutionException e) {
+                    onsrLin = 1;
+                    supportedMode = false;
+                    LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
+                        omCatalogIid1);
+                    throw new RuntimeException(
+                        "readMdSal: Error reading from operational store, Operational Mode Catalog : "
+                            + omCatalogIid1 + " :" + e);
+                } finally {
+                    networkTransactionService.close();
+                }
+                break;
+
+            case EXPRESS:
+                var omCatalogIid2 = InstanceIdentifier
+                    .builder(OperationalModeCatalog.class)
+                    .child(OpenroadmOperationalModes.class)
+                    .child(Roadms.class)
+                    .child(Express.class)
+                    .child(
+                        org.opendaylight.yang.gen.v1.http
+                        .org.openroadm.operational.mode.catalog.rev211210
+                        .operational.mode.roadm.express.parameters.express.OpenroadmOperationalMode.class,
+                        new org.opendaylight.yang.gen.v1.http
+                        .org.openroadm.operational.mode.catalog.rev211210
+                        .operational.mode.roadm.express.parameters.express.OpenroadmOperationalModeKey(
+                            operationalModeId))
+                    .build();
+                try {
+                    var omOptional = networkTransactionService
+                        .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid2)
+                        .get();
+                    if (omOptional.isPresent()) {
+                        var orExpressOM = omOptional.get();
+                        LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}",
+                            orExpressOM);
+                        cd = cd + orExpressOM.getMaxIntroducedCd().doubleValue();
+                        pdl2 = pdl2 + Math.pow(orExpressOM.getMaxIntroducedPdl().getValue().doubleValue(), 2.0);
+                        dgd2 = dgd2 + Math.pow(orExpressOM.getMaxIntroducedDgd().doubleValue(), 2.0);
+                        onsrLin = onsrLin + Math.pow(10,
+                            -(orExpressOM.getOsnrPolynomialFit().getA().doubleValue() * Math.pow(pwrIn, 3)
+                                + orExpressOM.getOsnrPolynomialFit().getB().doubleValue() * Math.pow(pwrIn, 2)
+                                + orExpressOM.getOsnrPolynomialFit().getC().doubleValue() * pwrIn
+                                + orExpressOM.getOsnrPolynomialFit().getD().doubleValue()
+                                + 10 * Math.log10(spacing / 50.0)) / 10);
+                    } else {
+                        supportedMode = false;
+                    }
+                } catch (InterruptedException | ExecutionException e) {
+                    onsrLin = 1;
+                    supportedMode = false;
+                    LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
+                        omCatalogIid2);
+                    throw new RuntimeException(
+                        "readMdSal: Error reading from operational store, Operational Mode Catalog : "
+                            + omCatalogIid2 + " :" + e);
+                } finally {
+                    networkTransactionService.close();
+                }
+                break;
+
+            case AMP:
+                var omCatalogIid3 = InstanceIdentifier
+                    .builder(OperationalModeCatalog.class)
+                    .child(OpenroadmOperationalModes.class)
+                    .child(Amplifiers.class)
+                    .child(Amplifier.class)
+                    .child(OpenroadmOperationalMode.class, new OpenroadmOperationalModeKey(operationalModeId))
+                    .build();
+                try {
+                    var omOptional = networkTransactionService
+                        .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid3)
+                        .get();
+                    if (omOptional.isPresent()) {
+                        var orAmpOM = omOptional.get();
+                        LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAmpOM);
+                        networkTransactionService.close();
+                        cd = cd + orAmpOM.getMaxIntroducedCd().doubleValue();
+                        pdl2 = pdl2 + Math.pow(orAmpOM.getMaxIntroducedPdl().getValue().doubleValue(), 2.0);
+                        dgd2 = dgd2 + Math.pow(orAmpOM.getMaxIntroducedDgd().doubleValue(), 2.0);
+                        onsrLin = onsrLin + Math.pow(10,
+                            -(orAmpOM.getOsnrPolynomialFit().getA().doubleValue() * Math.pow(pwrIn, 3)
+                                + orAmpOM.getOsnrPolynomialFit().getB().doubleValue() * Math.pow(pwrIn, 2)
+                                + orAmpOM.getOsnrPolynomialFit().getC().doubleValue() * pwrIn
+                                + orAmpOM.getOsnrPolynomialFit().getD().doubleValue()
+                                + 10 * Math.log10(spacing / 50.0)) / 10);
+
+                    } else {
+                        supportedMode = false;
+                    }
+                } catch (InterruptedException | ExecutionException e) {
+                    onsrLin = 1;
+                    supportedMode = false;
+                    LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
+                        omCatalogIid3);
+                    throw new RuntimeException(
+                        "readMdSal: Error reading from operational store, Operational Mode Catalog : "
+                            + omCatalogIid3 + " :" + e);
+                } finally {
+                    networkTransactionService.close();
+                }
+                break;
+            default:
+                LOG.warn("Unsupported catalogNodeType {}", catalogNodeType);
+                break;
+        }
+
+        if (supportedMode) {
+            impairments.put("CD", cd);
+            impairments.put("DGD2", dgd2);
+            impairments.put("PDL2", pdl2);
+            impairments.put("ONSRLIN", onsrLin);
+
+            LOG.info("Accumulated CD is {} ps, DGD2 is {} ps and PDL2 is {} dB", cd, Math.sqrt(dgd2), Math.sqrt(pdl2));
+            LOG.info("Resulting OSNR is {} dB", 10 * Math.log10(1 / onsrLin));
+
+        } else {
+            LOG.error("Operational Mode {} passed to getPceRoadmAmpParameters does not correspond to an OpenROADM mode"
+                + "Parameters for amplifier and/or ROADMs can not be derived from specific-operational-modes.",
+                operationalModeId);
+        }
+
+        return impairments;
+    }
+
+    /**
+     * Non linear contribution computation.
+     * Public method calculating non linear contribution among the path from
+     * launched power and span length Formula comes from
+     * OpenROADM_OSNR_Calculation_20220610 Tool The resulting contribution shall be
+     * calculated for each fiber span and summed up
+     * @param launchedPowerdB
+     *            The power launched in the span (shall account for Optical Distribution
+     *            Frame loss)
+     * @param spanLength
+     *            Length of the span in km
+     * @param spacing
+     *            OpenROADM power and osnr contribution calculations are based on
+     *            spacing between channels : the Media Channel (MC) width
+     *
+     * @return nonLinearOnsrContributionLin
+     *         The inverse of the NL OSNR contribution converted from dB to linear value
+     */
+    public double calculateNLonsrContribution(double launchedPowerdB, double spanLength, double spacing) {
+        double constanteC0 = 0 ;
+        if (spacing > 162.5) {
+            constanteC0 = CatalogConstant.NLCONSTANTC0GT1625;
+        }
+        else if (spacing > 112.5) {
+            constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1625;
+        }
+        else if (spacing > 100.0) {
+            constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1125;
+        }
+        else if (spacing > 87.5) {
+            constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1000;
+        }
+        else {
+            constanteC0 = CatalogConstant.NLCONSTANTC0UPTO875;
+        }
+
+        double nonLinearOnsrContributionLin = Math.pow(10.0, -(launchedPowerdB * CatalogConstant.NLCONSTANTC1
+            + constanteC0
+            + CatalogConstant.NLCONSTANTCE * Math.exp(CatalogConstant.NLCONSTANTEX * spanLength)) / 10);
+        LOG.info(" OSNR Non Linear contribution is {} dB", launchedPowerdB * CatalogConstant.NLCONSTANTC1
+            + constanteC0
+            + CatalogConstant.NLCONSTANTCE * Math.exp(CatalogConstant.NLCONSTANTEX * spanLength));
+        return nonLinearOnsrContributionLin;
+    }
+
+}
diff --git a/common/src/main/java/org/opendaylight/transportpce/common/catalog/PenaltiesComparator.java b/common/src/main/java/org/opendaylight/transportpce/common/catalog/PenaltiesComparator.java
new file mode 100644 (file)
index 0000000..55f805d
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2022 Orange, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.transportpce.common.catalog;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.Penalties;
+
+@SuppressWarnings("serial")
+@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
+    value = "SE_NO_SERIALVERSIONID",
+    justification = "https://github.com/rzwitserloot/lombok/wiki/WHY-NOT:-serialVersionUID")
+public class PenaltiesComparator implements Comparator<Penalties>, Serializable {
+    @Override
+    public int compare(Penalties o1, Penalties o2) {
+        return Double.compare(o1.getUpToBoundary().doubleValue(), o2.getUpToBoundary().doubleValue());
+    }
+}
diff --git a/common/src/test/java/org/opendaylight/transportpce/common/catalog/CatalogUtilsTest.java b/common/src/test/java/org/opendaylight/transportpce/common/catalog/CatalogUtilsTest.java
new file mode 100644 (file)
index 0000000..e7e0ed7
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright © 2022 Orange, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.common.catalog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.transportpce.common.StringConstants;
+import org.opendaylight.transportpce.common.network.NetworkTransactionImpl;
+import org.opendaylight.transportpce.common.network.NetworkTransactionService;
+import org.opendaylight.transportpce.common.network.RequestProcessor;
+import org.opendaylight.transportpce.test.AbstractTest;
+import org.opendaylight.transportpce.test.converter.DataObjectConverter;
+import org.opendaylight.transportpce.test.converter.JSONDataObjectConverter;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.OperationalModeCatalog;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CatalogUtilsTest extends AbstractTest {
+    private static final Logger LOG = LoggerFactory.getLogger(CatalogUtilsTest.class);
+    private static final String CATALOG_FILE = "src/test/resources/apidocCatalog10_1OptSpecV5_1.json";
+    private static OperationalModeCatalog omCatalog;
+    private static Map<String, Double> outputImpairments = new HashMap<>();
+
+    //
+    @BeforeClass
+    public static void setUp() throws InterruptedException,
+        ExecutionException {
+        DataObjectConverter dataObjectConverter = JSONDataObjectConverter
+            .createWithDataStoreUtil(getDataStoreContextUtil());
+        try (Reader reader = new FileReader(CATALOG_FILE, StandardCharsets.UTF_8)) {
+            NormalizedNode normalizedNode = dataObjectConverter
+                .transformIntoNormalizedNode(reader).get();
+            omCatalog = (OperationalModeCatalog) getDataStoreContextUtil()
+                .getBindingDOMCodecServices().fromNormalizedNode(YangInstanceIdentifier
+                    .of(OperationalModeCatalog.QNAME), normalizedNode)
+                .getValue();
+            @NonNull
+            WriteTransaction newWriteOnlyTransaction = getDataBroker().newWriteOnlyTransaction();
+            newWriteOnlyTransaction
+                .put(LogicalDatastoreType.CONFIGURATION,
+                    InstanceIdentifier.create(OperationalModeCatalog.class),
+                    omCatalog);
+            newWriteOnlyTransaction.commit().get();
+        } catch (IOException e) {
+            LOG.error("Cannot load OpenROADM part of Operational Mode Catalog ", e);
+            fail("Cannot load openROADM operational modes ");
+        }
+    }
+
+    @Test
+    public void catalogPrimitivesTest() {
+        RequestProcessor reqProc = new RequestProcessor(getDataBroker());
+        NetworkTransactionService netTransServ = new NetworkTransactionImpl(reqProc);
+        CatalogUtils catalogUtils = new CatalogUtils(netTransServ);
+        assertEquals("Checking retrieval of Operational Mode from Node Type ADD",
+            CatalogConstant.MWWRCORE,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.ADD,
+                StringConstants.SERVICE_TYPE_100GE_T));
+        assertEquals("Checking retrieval of Operational Mode from Node Type DROP",
+            CatalogConstant.MWWRCORE,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.DROP,
+                StringConstants.SERVICE_TYPE_100GE_T));
+        assertEquals("Checking retrieval of Operational Mode from Node Type EXPRESS",
+            CatalogConstant.MWMWCORE,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.EXPRESS,
+                StringConstants.SERVICE_TYPE_100GE_T));
+        assertEquals("Checking retrieval of Operational Mode from Node Type AMP",
+            CatalogConstant.MWISTANDARD,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.AMP,
+                StringConstants.SERVICE_TYPE_100GE_T));
+        assertEquals("Checking retrieval of Operational Mode from Node Type and service Type 100GE",
+            CatalogConstant.ORW100GSC,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
+                StringConstants.SERVICE_TYPE_100GE_T));
+        assertEquals("Checking retrieval of Operational Mode from Node Type and service Type OTU4",
+            CatalogConstant.ORW100GSC,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
+                StringConstants.SERVICE_TYPE_OTU4));
+        assertEquals("Checking retrieval of Operational Mode from Node Type and service Type OTUC2",
+            CatalogConstant.ORW200GOFEC316GBD,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
+                StringConstants.SERVICE_TYPE_OTUC2));
+        assertEquals("Checking retrieval of Operational Mode from Node Type and service Type OTUC3",
+            CatalogConstant.ORW300GOFEC631GBD,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
+                StringConstants.SERVICE_TYPE_OTUC3));
+        assertEquals("Checking retrieval of Operational Mode from Node Type and service Type 400GE",
+            CatalogConstant.ORW400GOFEC631GBD,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
+                StringConstants.SERVICE_TYPE_400GE));
+        assertEquals("Checking retrieval of Operational Mode from Node Type and service Type OTUC4",
+            CatalogConstant.ORW400GOFEC631GBD,
+            catalogUtils.getPceTxTspOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
+                StringConstants.SERVICE_TYPE_OTUC4));
+        assertEquals("Checking retrieval of channel spacing from Operational Mode 100G SC FEC",
+            50.0,
+            catalogUtils.getPceTxTspChannelSpacing(CatalogConstant.ORW100GSC),0.005);
+        assertEquals("Checking retrieval of channel spacing from Operational Mode 100G OFEC 31.6",
+            50.0,
+            catalogUtils.getPceTxTspChannelSpacing(CatalogConstant.ORW100GOFEC316GBD),0.005);
+        assertEquals("Checking retrieval of channel spacing from Operational Mode 200G OFEC 31.6",
+            50.0,
+            catalogUtils.getPceTxTspChannelSpacing(CatalogConstant.ORW200GOFEC316GBD),0.005);
+        assertEquals("Checking retrieval of channel spacing from Operational Mode 200G OFEC 63.1",
+            87.5,
+            catalogUtils.getPceTxTspChannelSpacing(CatalogConstant.ORW200GOFEC631GBD),0.005);
+        assertEquals("Checking retrieval of channel spacing from Operational Mode 300G OFEC 63.1 GBd",
+            87.5,
+            catalogUtils.getPceTxTspChannelSpacing(CatalogConstant.ORW300GOFEC631GBD),0.005);
+        assertEquals("Checking retrieval of channel spacing from Operational Mode 400G OFEC 63.1 Gbd",
+            87.5,
+            catalogUtils.getPceTxTspChannelSpacing(CatalogConstant.ORW400GOFEC631GBD),0.005);
+        assertEquals("Checking 100GSCFEC ONSR Lin",
+            1345.6,
+            catalogUtils.getPceTxTspParameters(CatalogConstant.ORW100GSC, CatalogConstant.MWWRCORE) * 1000000.0,
+            0.5);
+        assertEquals("Checking 100G OFEC 31.6 Gbauds ONSR Lin",
+            450.7,
+            catalogUtils.getPceTxTspParameters(CatalogConstant.ORW100GOFEC316GBD, CatalogConstant.MWWRCORE) * 1000000.0,
+            0.5);
+        assertEquals("Checking 200G OFEC 31.6 Gbauds ONSR Lin",
+            450.7,
+            catalogUtils.getPceTxTspParameters(CatalogConstant.ORW200GOFEC316GBD, CatalogConstant.MWWRCORE) * 1000000.0,
+            0.5);
+        assertEquals("Checking 200G OFEC 63.1 Gbauds ONSR Lin",
+            450.7,
+            catalogUtils.getPceTxTspParameters(CatalogConstant.ORW200GOFEC631GBD, CatalogConstant.MWWRCORE) * 1000000.0,
+            0.5);
+        assertEquals("Checking 300G OFEC 63.1 Gbauds ONSR Lin",
+            450.7,
+            catalogUtils.getPceTxTspParameters(CatalogConstant.ORW300GOFEC631GBD, CatalogConstant.MWWRCORE) * 1000000.0,
+            0.5);
+        assertEquals("Checking 400G OFEC 63.1 Gbauds ONSR Lin",
+            450.7,
+            catalogUtils.getPceTxTspParameters(CatalogConstant.ORW400GOFEC631GBD, CatalogConstant.MWWRCORE) * 1000000.0,
+            0.5);
+        assertEquals("Checking ONSR Lin = 0 for non valid OM",
+            0.0, catalogUtils.getPceTxTspParameters("SPE-non-existing-mode", CatalogConstant.MWWRCORE) * 1000000.0,
+            0.0);
+        assertEquals("Checking 100GSCFEC RX margin OOR due to CD",
+            -9996.9, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW100GSC, 18001.0, 0.0, 0.0, 20.0), 0.5);
+        assertEquals("Checking 100GSCFEC RX margin OOR due to PMD",
+            -9996.9, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW100GSC, 0.0, 30.1, 0.0, 20.0), 0.5);
+        assertEquals("Checking 100GSCFEC RX margin OOR due to PDL",
+            -9996.9, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW100GSC, 0.0, 0.0, 6.1, 20.0), 0.5);
+        assertEquals("Checking 100GSCFEC RX margin in Range at max tolerated penalty",
+            3.0, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW100GSC, 17999.0, 29.9, 5.9, 20.0), 0.05);
+        assertEquals("Checking 400G OFEC 63.1 Gbauds RX margin OOR due to CD",
+            -9996.9, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW400GOFEC631GBD, 12001.0, 0.0, 0.0, 27.0),
+            0.5);
+        assertEquals("Checking 400G OFEC 63.1 Gbauds RX margin OOR due to PMD",
+            -9996.9, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW400GOFEC631GBD, 0.0, 20.1, 0.0, 27.0),
+            0.5);
+        assertEquals("Checking 400G OFEC 63.1 Gbauds RX margin OOR due to PDL",
+            -9996.9, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW400GOFEC631GBD, 0.0, 0.0, 4.1, 27.0),
+            0.5);
+        assertEquals("Checking 400G OFEC 63.1 Gbauds RX margin in Range at max tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW400GOFEC631GBD, 11999.0, 19.9, 3.9, 28.0),
+            0.05);
+        assertEquals("Checking 400G OFEC 63.1 Gbauds RX margin in Range at intermediate tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW400GOFEC631GBD, 3999.0, 9.9, 1.9, 25.5),
+            0.05);
+        assertEquals("Checking 400G OFEC 63.1 Gbauds RX margin in Range at min tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW400GOFEC631GBD, 3999.0, 9.9, 0.9, 25.0),
+            0.05);
+        assertEquals("Checking 300G OFEC 63.1 Gbauds RX margin in Range at max tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW300GOFEC631GBD, 17999.0, 24.9, 3.9, 25.0),
+            0.05);
+        assertEquals("Checking 300G OFEC 63.1 Gbauds RX margin in Range at min tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW300GOFEC631GBD, 3999.0, 9.9, 0.9, 22.0),
+            0.05);
+        assertEquals("Checking 200G OFEC 63.1 Gbauds RX margin in Range at max tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW200GOFEC631GBD, 23999.0, 24.9, 3.9, 21.0),
+            0.05);
+        assertEquals("Checking 200G OFEC 63.1 Gbauds RX margin in Range at min tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW200GOFEC631GBD, 3999.0, 9.9, 0.9, 18.0),
+            0.05);
+        assertEquals("Checking 200G OFEC 31.6 Gbauds RX margin in Range at max tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW200GOFEC316GBD, 23999.0, 29.9, 3.9, 24.5),
+            0.05);
+        assertEquals("Checking 200G OFEC 31.6 Gbauds RX margin in Range at min tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW200GOFEC316GBD, 3999.0, 9.9, 0.9, 21.5),
+            0.05);
+        assertEquals("Checking 100G OFEC 31.6 Gbauds RX margin in Range at max tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW100GOFEC316GBD, 47999.0, 29.9, 3.9, 16.0),
+            0.05);
+        assertEquals("Checking 100G OFEC 31.6 Gbauds RX margin in Range at min tolerated penalty",
+            0.5, catalogUtils.getPceRxTspParameters(CatalogConstant.ORW100GOFEC316GBD, 3999.0, 9.9, 0.9, 13.0),
+            0.05);
+        assertEquals("Checking Margin negative for non valid OM",
+            -9999.9, catalogUtils.getPceRxTspParameters("SPE-non-existing-mode", 0.0, 0.0, 0.0, 30.0), 0.05);
+        outputImpairments.put("CD", 1025.0);
+        outputImpairments.put("DGD2", 18.0);
+        outputImpairments.put("PDL2", 6.25);
+        outputImpairments.put("ONSRLIN", 0.0016307685044580744);
+        // check how to add Delta on an object<String, Double>
+        assertEquals("Checking ROADM Express path contribution to impairments ",
+            outputImpairments, catalogUtils.getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType.EXPRESS,
+            CatalogConstant.MWMWCORE,-15.0, 1000.0, 9.0, 4.0, 0.001000, 50.0));
+        outputImpairments.put("ONSRLIN", 0.0013604391454046139);
+        assertEquals("Checking ROADM Express path contribution to impairments with 87.5 GHz spacing ",
+            outputImpairments, catalogUtils.getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType.EXPRESS,
+            CatalogConstant.MWMWCORE,-15.0, 1000.0, 9.0, 4.0, 0.001000, 87.5));
+        outputImpairments.put("ONSRLIN", 0.0015011872336272727);
+        assertEquals("Checking ROADM Add path contribution to impairments ",
+            outputImpairments, catalogUtils.getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType.ADD,
+            CatalogConstant.MWWRCORE, -15.0, 1000.0, 9.0, 4.0, 0.001, 50.0));
+        outputImpairments.put("ONSRLIN", 0.0016307685044580744);
+        assertEquals("Checking ROADM Drop path contribution to impairments ",
+            outputImpairments, catalogUtils.getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType.DROP,
+            CatalogConstant.MWWRCORE, -15.0, 1000.0, 9.0, 4.0, 0.001, 50.0));
+        outputImpairments.put("ONSRLIN", 0.0015010372326658573);
+        assertEquals("Checking Amp path contribution to impairments ",
+            outputImpairments, catalogUtils.getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType.AMP,
+            CatalogConstant.MWISTANDARD, -15.0, 1025.0, 9.0, 5.76, 0.001, 50.0));
+        assertEquals("Checking empty map returned in case wrong Operational mode provided  ",
+            true, catalogUtils.getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType.AMP,
+                "ThisIsNotAValidMode", -15.0,1000.0, 0.0, 0.0, 0.001, 50.0).isEmpty());
+        outputImpairments.put("ONSRLIN", 1.0);
+        assertEquals("Checking empty map returned in case wrong Operational mode provided  ",
+            true, catalogUtils.getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType.AMP,
+            "OR-InvalidMode", -15.0, 1025.0, 18.0, 6.25, 0.001, 50.0).isEmpty());
+        assertEquals("Checking Non Linear contribution calculation  ", 0.000114266642501745,
+            catalogUtils.calculateNLonsrContribution(2, 70, 87.5), 0.000000005);
+    }
+}
\ No newline at end of file
diff --git a/common/src/test/resources/apidocCatalog10_1OptSpecV5_1.json b/common/src/test/resources/apidocCatalog10_1OptSpecV5_1.json
new file mode 100644 (file)
index 0000000..413c6f5
--- /dev/null
@@ -0,0 +1,662 @@
+{
+    "operational-mode-catalog": {
+        "openroadm-operational-modes": {
+            "grid-parameters": {
+                "min-central-frequency": "191.32500000",
+                "max-central-frequency": "196.12500000",
+                "central-frequency-granularity": "12.50000",
+                "min-spacing": "37.50000"
+            },
+            "xponders-pluggables": {
+                "xponder-pluggable-openroadm-operational-mode": [
+                    {
+                        "openroadm-operational-mode-id": "OR-W-100G-SC",
+                        "baud-rate": "28.0",
+                        "line-rate": "111.8",
+                        "modulation-format": "dp-qpsk",
+                        "min-TX-osnr": "33.000",
+                        "TX-OOB-osnr": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-OOB-osnr-multi-channel-value": "31.000",
+                            "min-OOB-osnr-single-channel-value": "43.000"
+                        },
+                        "output-power-range": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-output-power": "-5.000",
+                            "max-output-power": "0.000"
+                        },
+                        "min-RX-osnr-tolerance": "17.000",
+                        "min-input-power-at-RX-osnr": "-22.000",
+                        "max-input-power": "1.000",
+                        "channel-width": "40.00000",
+                        "fec-type": "org-openroadm-common-types:scfec",
+                        "penalties": [
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "18000",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "6",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "30",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-22",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "cross-talk-total-power-dB",
+                                "up-to-boundary": "15",
+                                "penalty-value": "0.200"
+                            },
+                            {
+                                "parameter-and-unit": "colorless-drop-adjacent-channel-crosstalk-GHz",
+                                "up-to-boundary": "4",
+                                "penalty-value": "0.200"
+                            }
+                        ]
+                    },
+                    {
+                        "openroadm-operational-mode-id": "OR-W-100G-oFEC-31.6Gbd",
+                        "baud-rate": "31.6",
+                        "line-rate": "126.3",
+                        "modulation-format": "dp-qpsk",
+                        "min-TX-osnr": "37.000",
+                        "TX-OOB-osnr": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-OOB-osnr-multi-channel-value": "36.000"
+                        },
+                        "output-power-range": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-output-power": "-5.000",
+                            "max-output-power": "0.000"
+                        },
+                        "min-RX-osnr-tolerance": "12.000",
+                        "min-input-power-at-RX-osnr": "-18.000",
+                        "max-input-power": "1.000",
+                        "channel-width": "37.88400",
+                        "fec-type": "org-openroadm-common-types:ofec",
+                        "min-roll-off": "0.05",
+                        "max-roll-off": "0.20",
+                        "penalties": [
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "4000",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "48000",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "1",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "2",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "4",
+                                "penalty-value": "2.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "6",
+                                "penalty-value": "4.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "10",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "30",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-18",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-20",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "cross-talk-total-power-dB",
+                                "up-to-boundary": "15",
+                                "penalty-value": "0.200"
+                            },
+                            {
+                                "parameter-and-unit": "colorless-drop-adjacent-channel-crosstalk-GHz",
+                                "up-to-boundary": "4",
+                                "penalty-value": "0.200"
+                            }
+                        ]
+                    },
+                    {
+                        "openroadm-operational-mode-id": "OR-W-200G-oFEC-31.6Gbd",
+                        "baud-rate": "31.6",
+                        "line-rate": "252.6",
+                        "modulation-format": "dp-qam16",
+                        "min-TX-osnr": "37.000",
+                        "TX-OOB-osnr": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-OOB-osnr-multi-channel-value": "36.000"
+                        },
+                        "output-power-range": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-output-power": "-5.000",
+                            "max-output-power": "0.000"
+                        },
+                        "min-RX-osnr-tolerance": "20.500",
+                        "min-input-power-at-RX-osnr": "-16.000",
+                        "max-input-power": "1.000",
+                        "channel-width": "37.88400",
+                        "fec-type": "org-openroadm-common-types:ofec",
+                        "min-roll-off": "0.05",
+                        "max-roll-off": "0.20",
+                        "penalties": [
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "4000",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "24000",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "1",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "2",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "4",
+                                "penalty-value": "2.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "6",
+                                "penalty-value": "4.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "10",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "30",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-16",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-18",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-20",
+                                "penalty-value": "2.000"
+                            },
+                            {
+                                "parameter-and-unit": "cross-talk-total-power-dB",
+                                "up-to-boundary": "15",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "colorless-drop-adjacent-channel-crosstalk-GHz",
+                                "up-to-boundary": "4",
+                                "penalty-value": "0.500"
+                            }
+                        ]
+                    },
+                    {
+                        "openroadm-operational-mode-id": "OR-W-200G-oFEC-63.1Gbd",
+                        "baud-rate": "63.1",
+                        "line-rate": "252.6",
+                        "modulation-format": "dp-qpsk",
+                        "min-TX-osnr": "37.000",
+                        "TX-OOB-osnr": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-OOB-osnr-multi-channel-value": "36.000"
+                        },
+                        "output-power-range": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-output-power": "-5.000",
+                            "max-output-power": "0.000"
+                        },
+                        "min-RX-osnr-tolerance": "17.000",
+                        "min-input-power-at-RX-osnr": "-18.000",
+                        "max-input-power": "1.000",
+                        "channel-width": "75.72000",
+                        "fec-type": "org-openroadm-common-types:ofec",
+                        "min-roll-off": "0.05",
+                        "max-roll-off": "0.20",
+                        "penalties": [
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "4000",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "24000",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "1",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "2",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "4",
+                                "penalty-value": "2.500"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "10",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "25",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-18",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-20",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "cross-talk-total-power-dB",
+                                "up-to-boundary": "15",
+                                "penalty-value": "0.300"
+                            },
+                            {
+                                "parameter-and-unit": "colorless-drop-adjacent-channel-crosstalk-GHz",
+                                "up-to-boundary": "4",
+                                "penalty-value": "0.500"
+                            }
+                        ]
+                    },
+                    {
+                        "openroadm-operational-mode-id": "OR-W-300G-oFEC-63.1Gbd",
+                        "baud-rate": "63.1",
+                        "line-rate": "378.8",
+                        "modulation-format": "dp-qam16",
+                        "min-TX-osnr": "37.000",
+                        "TX-OOB-osnr": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-OOB-osnr-multi-channel-value": "36.000"
+                        },
+                        "output-power-range": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-output-power": "-5.000",
+                            "max-output-power": "0.000"
+                        },
+                        "min-RX-osnr-tolerance": "21.000",
+                        "min-input-power-at-RX-osnr": "-16.000",
+                        "max-input-power": "1.000",
+                        "channel-width": "75.72000",
+                        "fec-type": "org-openroadm-common-types:ofec",
+                        "min-roll-off": "0.05",
+                        "max-roll-off": "0.20",
+                        "penalties": [
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "4000",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "18000",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "1.00",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "2.00",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "4.00",
+                                "penalty-value": "2.500"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "10.00",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "25.00",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-16.00",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-18.00",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-20.00",
+                                "penalty-value": "2.000"
+                            },
+                            {
+                                "parameter-and-unit": "cross-talk-total-power-dB",
+                                "up-to-boundary": "15.00",
+                                "penalty-value": "0.300"
+                            },
+                            {
+                                "parameter-and-unit": "colorless-drop-adjacent-channel-crosstalk-GHz",
+                                "up-to-boundary": "4.0",
+                                "penalty-value": "0.500"
+                            }
+                        ]
+                    },
+                    {
+                        "openroadm-operational-mode-id": "OR-W-400G-oFEC-63.1Gbd",
+                        "baud-rate": "63.1",
+                        "line-rate": "505.1",
+                        "modulation-format": "dp-qam8",
+                        "min-TX-osnr": "37.000",
+                        "TX-OOB-osnr": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-OOB-osnr-multi-channel-value": "36.000"
+                        },
+                        "output-power-range": {
+                            "WR-openroadm-operational-mode-id": "MW-WR-core",
+                            "min-output-power": "-5.000",
+                            "max-output-power": "0.000"
+                        },
+                        "min-RX-osnr-tolerance": "24.000",
+                        "min-input-power-at-RX-osnr": "-14.000",
+                        "max-input-power": "1.000",
+                        "channel-width": "75.72000",
+                        "fec-type": "org-openroadm-common-types:ofec",
+                        "min-roll-off": "0.05",
+                        "max-roll-off": "0.20",
+                        "penalties": [
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "4000",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "CD-ps/nm",
+                                "up-to-boundary": "12000",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "1.00",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "2.00",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "PDL-dB",
+                                "up-to-boundary": "4.00",
+                                "penalty-value": "2.500"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "10.00",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "PMD-ps",
+                                "up-to-boundary": "20.00",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-14",
+                                "penalty-value": "0.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-16.00",
+                                "penalty-value": "1.000"
+                            },
+                            {
+                                "parameter-and-unit": "power-dBm",
+                                "up-to-boundary": "-18.00",
+                                "penalty-value": "2.000"
+                            },
+                            {
+                                "parameter-and-unit": "cross-talk-total-power-dB",
+                                "up-to-boundary": "13.00",
+                                "penalty-value": "0.300"
+                            },
+                            {
+                                "parameter-and-unit": "cross-talk-total-power-dB",
+                                "up-to-boundary": "15.00",
+                                "penalty-value": "0.500"
+                            },
+                            {
+                                "parameter-and-unit": "colorless-drop-adjacent-channel-crosstalk-GHz",
+                                "up-to-boundary": "4.10",
+                                "penalty-value": "0.500"
+                            }
+                        ]
+                    }
+                ]
+            },
+            "roadms": {
+                "Express": {
+                    "openroadm-operational-mode": {
+                        "openroadm-operational-mode-id": "MW-MW-core",
+                        "per-channel-Pin-min": "-21.000",
+                        "per-channel-Pin-max": "-9.000",
+                        "max-introduced-pdl": "1.500",
+                        "max-introduced-dgd": "3.00",
+                        "max-introduced-cd": "25.00",
+                        "osnr-polynomial-fit": {
+                            "A": "-0.00059520",
+                            "B": "-0.06250000",
+                            "C": "-1.07100000",
+                            "D": "27.99000000"
+                        },
+                        "mask-power-vs-pin": [
+                            {
+                                "lower-boundary": "0",
+                                "upper-boundary": "6",
+                                "C": "1.00000000",
+                                "D": "-9.00000000",
+                                "fiber-type": "smf"
+                            },
+                            {
+                                "lower-boundary": "6",
+                                "upper-boundary": "8",
+                                "C": "-0.00000000",
+                                "D": "-3.00000000",
+                                "fiber-type": "smf"
+                            },
+                            {
+                                "lower-boundary": "8",
+                                "upper-boundary": "23",
+                                "C": "0.33333334",
+                                "D": "-5.66666667",
+                                "fiber-type": "smf"
+                            },
+                            {
+                                "lower-boundary": "23",
+                                "upper-boundary": "27",
+                                "C": "0.00000000",
+                                "D": "2.00000000",
+                                "fiber-type": "smf"
+                            }
+                        ]
+                    }
+                },
+                "Add": {
+                    "add-openroadm-operational-mode": {
+                        "openroadm-operational-mode-id": "MW-WR-core",
+                        "incremental-osnr": "33.000",
+                        "per-channel-Pin-min": "-6.000",
+                        "per-channel-Pin-max": "3.000",
+                        "max-introduced-pdl": "1.500",
+                        "max-introduced-dgd": "3.00",
+                        "max-introduced-cd": "25.00",
+                        "mask-power-vs-pin": [
+                            {
+                                "lower-boundary": "0",
+                                "upper-boundary": "6",
+                                "C": "1.00000000",
+                                "D": "-9.00000000",
+                                "fiber-type": "smf"
+                            },
+                            {
+                                "lower-boundary": "6",
+                                "upper-boundary": "8",
+                                "C": "-0.00000000",
+                                "D": "-3.00000000",
+                                "fiber-type": "smf"
+                            },
+                            {
+                                "lower-boundary": "8",
+                                "upper-boundary": "23",
+                                "C": "0.33333334",
+                                "D": "-5.66666667",
+                                "fiber-type": "smf"
+                            },
+                            {
+                                "lower-boundary": "23",
+                                "upper-boundary": "27",
+                                "C": "0.00000000",
+                                "D": "2.00000000",
+                                "fiber-type": "smf"
+                            }
+                        ]
+                    }
+                },
+                "Drop": {
+                    "openroadm-operational-mode": {
+                        "openroadm-operational-mode-id": "MW-WR-core",
+                        "per-channel-Pin-min": "-25.000",
+                        "per-channel-Pin-max": "-9.000",
+                        "max-introduced-pdl": "1.500",
+                        "max-introduced-dgd": "3.00",
+                        "max-introduced-cd": "25.00",
+                        "osnr-polynomial-fit": {
+                            "A": "-0.00059520",
+                            "B": "-0.06250000",
+                            "C": "-1.07100000",
+                            "D": "27.99000000"
+                        },
+                        "per-channel-Pout-min": "-22.000",
+                        "per-channel-Pout-max": "1.000"
+                    }
+                }
+            },
+            "amplifiers": {
+                "Amplifier": {
+                    "min-gain": "0.000",
+                    "max-gain": "27.000",
+                    "max-extended-gain": "31.000",
+                    "mask-gain-ripple-vs-tilt": [
+                        {
+                            "lower-boundary": "-4",
+                            "upper-boundary": "-1",
+                            "C": "-0.50",
+                            "D": "1.00"
+                        },
+                        {
+                            "lower-boundary": "-1",
+                            "upper-boundary": "0",
+                            "C": "0.50",
+                            "D": "2.00"
+                        }
+                    ],
+                    "openroadm-operational-mode": [
+                        {
+                            "openroadm-operational-mode-id": "MWi-standard",
+                            "per-channel-Pin-min": "-31.000",
+                            "per-channel-Pin-max": "-9.000",
+                            "max-introduced-pdl": "0.70",
+                            "max-introduced-dgd": "3.00",
+                            "max-introduced-cd": "0.00",
+                            "osnr-polynomial-fit": {
+                                "A": "-0.00059520",
+                                "B": "-0.06250000",
+                                "C": "-1.07100000",
+                                "D": "28.99000000"
+                            },
+                            "per-channel-Pout-min": "-9.000",
+                            "per-channel-Pout-max": "2.000"
+                        },
+                        {
+                            "openroadm-operational-mode-id": "MWi-low-noise",
+                            "per-channel-Pin-min": "-31.000",
+                            "per-channel-Pin-max": "-9.000",
+                            "max-introduced-pdl": "0.700",
+                            "max-introduced-dgd": "3.00",
+                            "max-introduced-cd": "0.00",
+                            "osnr-polynomial-fit": {
+                                "A": "-0.00081040",
+                                "B": "-0.06221000",
+                                "C": "-0.58890000",
+                                "D": "37.62000000"
+                            },
+                            "per-channel-Pout-min": "-9.000",
+                            "per-channel-Pout-max": "2.000"
+                        }
+                    ]
+                }
+            }
+        }
+    }
+}