CseCracCreatorTest.java

/*
 * Copyright (c) 2021, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.powsybl.openrao.data.crac.io.cse;

import com.powsybl.iidm.network.Country;
import com.powsybl.iidm.network.Network;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.Instant;
import com.powsybl.openrao.data.crac.api.RaUsageLimits;
import com.powsybl.openrao.data.crac.api.RemedialAction;
import com.powsybl.openrao.data.crac.api.State;
import com.powsybl.openrao.data.crac.api.usagerule.OnConstraint;
import com.powsybl.openrao.data.crac.api.usagerule.OnFlowConstraintInCountry;
import com.powsybl.openrao.data.crac.api.usagerule.OnInstant;
import com.powsybl.openrao.data.crac.api.usagerule.UsageMethod;
import com.powsybl.openrao.data.crac.api.usagerule.UsageRule;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnec;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.openrao.data.crac.api.networkaction.NetworkAction;
import com.powsybl.openrao.data.crac.api.networkaction.SwitchPair;
import com.powsybl.openrao.data.crac.api.parameters.CracCreationParameters;
import com.powsybl.openrao.data.crac.api.parameters.JsonCracCreationParameters;
import com.powsybl.openrao.data.crac.api.range.RangeType;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeAction;
import com.powsybl.openrao.data.crac.io.commons.api.ElementaryCreationContext;
import com.powsybl.openrao.data.crac.io.commons.api.ImportStatus;
import com.powsybl.openrao.data.crac.io.commons.api.stdcreationcontext.BranchCnecCreationContext;
import com.powsybl.openrao.data.crac.io.commons.api.stdcreationcontext.InjectionRangeActionCreationContext;
import com.powsybl.openrao.data.crac.io.cse.criticalbranch.CseCriticalBranchCreationContext;
import com.powsybl.openrao.data.crac.io.cse.parameters.CseCracCreationParameters;
import com.powsybl.openrao.data.crac.io.cse.remedialaction.CsePstCreationContext;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Alexandre Montigny {@literal <alexandre.montigny at rte-france.com>}
 */
class CseCracCreatorTest {
    private static final double DOUBLE_TOLERANCE = 0.01;
    private static final String PREVENTIVE_INSTANT_ID = "preventive";
    private static final String OUTAGE_INSTANT_ID = "outage";
    private static final String CURATIVE_INSTANT_ID = "curative";

    private CracCreationParameters parameters = new CracCreationParameters();
    private Crac importedCrac;
    private CseCracCreationContext cracCreationContext;
    private Instant preventiveInstant;
    private Instant outageInstant;
    private Instant curativeInstant;

    private void setUp(String cracFileName, String networkFileName) throws IOException {
        Network network = Network.read(networkFileName, getClass().getResourceAsStream(networkFileName));
        InputStream is = getClass().getResourceAsStream(cracFileName);
        cracCreationContext = (CseCracCreationContext) Crac.readWithContext(cracFileName, is, network, parameters);
        importedCrac = cracCreationContext.getCrac();
        preventiveInstant = importedCrac.getInstant(PREVENTIVE_INSTANT_ID);
        outageInstant = importedCrac.getInstant(OUTAGE_INSTANT_ID);
        curativeInstant = importedCrac.getInstant(CURATIVE_INSTANT_ID);
    }

    private void setUp(String cracFileName) throws IOException {
        setUp(cracFileName, "/networks/TestCase12Nodes_with_Xnodes.uct");
    }

    private void setUpWithHvdcNetwork(String cracFileName) throws IOException {
        setUp(cracFileName, "/networks/TestCase16NodesWithUcteHvdc.uct");
    }

    private void setUpWithTransformer(String cracFileName) throws IOException {
        setUp(cracFileName, "/networks/TestCase12NodesTransformer.uct");
    }

    private void assertOutageNotImported(String name, ImportStatus importStatus) {
        ElementaryCreationContext context = cracCreationContext.getOutageCreationContext(name);
        assertNotNull(context);
        assertFalse(context.isImported());
        assertEquals(importStatus, context.getImportStatus());
    }

    private void assertCriticalBranchNotImported(String name, ImportStatus importStatus) {
        BranchCnecCreationContext context = cracCreationContext.getBranchCnecCreationContext(name);
        assertNotNull(context);
        assertFalse(context.isImported());
        assertEquals(importStatus, context.getImportStatus());
        assertTrue(context.getCreatedCnecsIds().isEmpty());
        assertTrue(context.getCreatedObjectsIds().isEmpty());
    }

    private void assertRemedialActionNotImported(String name, ImportStatus importStatus) {
        ElementaryCreationContext context = cracCreationContext.getRemedialActionCreationContext(name);
        assertNotNull(context);
        assertFalse(context.isImported());
        assertEquals(importStatus, context.getImportStatus());
        assertNull(context.getCreatedObjectId());
    }

    private void assertHvdcRangeActionImported(String name, Map<String, String> networkElements, String groupId) {
        InjectionRangeActionCreationContext context = (InjectionRangeActionCreationContext) cracCreationContext.getRemedialActionCreationContext(name);
        assertTrue(context.isImported());
        assertEquals(networkElements, context.getNativeNetworkElementIds());
        assertFalse(context.isAltered());
        assertNotNull(context.getCreatedObjectId());
        assertNotNull(importedCrac.getInjectionRangeAction(context.getCreatedObjectId()));
        assertEquals(groupId, importedCrac.getInjectionRangeAction(context.getCreatedObjectId()).getGroupId().orElseThrow());
    }

    @Test
    void createCrac() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertTrue(cracCreationContext.isCreationSuccessful());
        assertEquals(null, cracCreationContext.getTimeStamp());
        assertEquals("/networks/TestCase12Nodes_with_Xnodes", cracCreationContext.getNetworkName());
    }

    @Test
    void createCracWithParameters() throws IOException {
        RaUsageLimits raUsageLimits = new RaUsageLimits();
        raUsageLimits.setMaxRa(4);
        parameters.addRaUsageLimitsForInstant("preventive", raUsageLimits);
        setUp("/cracs/cse_crac_1.xml");
        assertTrue(cracCreationContext.isCreationSuccessful());
        assertNull(cracCreationContext.getTimeStamp());
        assertEquals("/networks/TestCase12Nodes_with_Xnodes", cracCreationContext.getNetworkName());
        assertEquals(4, cracCreationContext.getCrac().getRaUsageLimits(preventiveInstant).getMaxRa());
    }

    @Test
    void createCracWithHvdcBasicTest() throws IOException {
        parameters.addExtension(CseCracCreationParameters.class, new CseCracCreationParameters());
        parameters.getExtension(CseCracCreationParameters.class).setRangeActionGroupsAsString(List.of("PRA_HVDC + CRA_HVDC", "PRA_HVDC + CRA_HVDC_2"));
        setUpWithHvdcNetwork("/cracs/cse_crac_with_hvdc.xml");
        assertTrue(cracCreationContext.isCreationSuccessful());
        assertEquals(3, importedCrac.getInjectionRangeActions().size());

        assertHvdcRangeActionImported("PRA_HVDC", Map.of("BBE2AA12", "BBE2AA12_generator", "FFR3AA12", "FFR3AA12_generator"), "PRA_HVDC + CRA_HVDC");
        assertHvdcRangeActionImported("CRA_HVDC", Map.of("BBE2AA12", "BBE2AA12_generator", "FFR3AA12", "FFR3AA12_generator"), "PRA_HVDC + CRA_HVDC");

        assertOutageNotImported("fake_contingency_because_we_have_to", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
        assertCriticalBranchNotImported("fake_because_we_have_to - AAAAAA11 - BBBBBB11 - null", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
        assertRemedialActionNotImported("CRA_HVDC_fake", ImportStatus.NOT_YET_HANDLED_BY_OPEN_RAO);
        assertRemedialActionNotImported("WEIRD_HVDC_WITH_2_HVDCNODES", ImportStatus.INCONSISTENCY_IN_DATA);
        assertRemedialActionNotImported("HVDC_WITH_NON_OPPOSITE_GENERATORS", ImportStatus.INCONSISTENCY_IN_DATA);
    }

    @Test
    void createCracWithHvdcWithNoCracCreationParameters() throws IOException {
        parameters.addExtension(CseCracCreationParameters.class, new CseCracCreationParameters());
        setUpWithHvdcNetwork("/cracs/cse_crac_with_hvdc.xml");
        assertTrue(cracCreationContext.isCreationSuccessful());
        assertTrue(importedCrac.getInjectionRangeAction("PRA_HVDC").getGroupId().isEmpty());
        assertTrue(importedCrac.getInjectionRangeAction("CRA_HVDC").getGroupId().isEmpty());
        assertTrue(importedCrac.getInjectionRangeAction("CRA_HVDC_2").getGroupId().isEmpty());
        assertEquals("FR", importedCrac.getInjectionRangeAction("PRA_HVDC").getOperator());
        // range from CRAC
        assertEquals(-100, importedCrac.getInjectionRangeAction("PRA_HVDC").getRanges().get(0).getMin(), 1e-1);
        assertEquals(2000, importedCrac.getInjectionRangeAction("PRA_HVDC").getRanges().get(0).getMax(), 1e-1);
        //range from network
        assertEquals(-500, importedCrac.getInjectionRangeAction("PRA_HVDC").getRanges().get(1).getMin(), 1e-1);
        assertEquals(800, importedCrac.getInjectionRangeAction("PRA_HVDC").getRanges().get(1).getMax(), 1e-1);
        assertEquals("AVAILABLE", importedCrac.getInjectionRangeAction("PRA_HVDC").getUsageRules().iterator().next().getUsageMethod().toString());
        assertEquals(2, importedCrac.getInjectionRangeAction("PRA_HVDC").getInjectionDistributionKeys().size());
        assertEquals(-1., importedCrac.getInjectionRangeAction("PRA_HVDC").getInjectionDistributionKeys().entrySet().stream().filter(e -> e.getKey().getId().equals("BBE2AA12_generator")).findAny().orElseThrow().getValue(), 1e-3);
        assertEquals(1., importedCrac.getInjectionRangeAction("PRA_HVDC").getInjectionDistributionKeys().entrySet().stream().filter(e -> e.getKey().getId().equals("FFR3AA12_generator")).findAny().orElseThrow().getValue(), 1e-3);
    }

    @Test
    void createCracWithHvdcWithCracCreationParameters() throws IOException {
        parameters.addExtension(CseCracCreationParameters.class, new CseCracCreationParameters());
        parameters.getExtension(CseCracCreationParameters.class).setRangeActionGroupsAsString(List.of("PRA_HVDC + CRA_HVDC"));
        setUpWithHvdcNetwork("/cracs/cse_crac_with_hvdc.xml");
        assertTrue(cracCreationContext.isCreationSuccessful());
        assertEquals("PRA_HVDC + CRA_HVDC", importedCrac.getInjectionRangeAction("PRA_HVDC").getGroupId().get());
        assertEquals(importedCrac.getInjectionRangeAction("CRA_HVDC").getGroupId().get(), importedCrac.getInjectionRangeAction("PRA_HVDC").getGroupId().get());
        assertEquals("FR", importedCrac.getInjectionRangeAction("PRA_HVDC").getOperator());
        assertEquals(2000, importedCrac.getInjectionRangeAction("PRA_HVDC").getRanges().get(0).getMax(), 1e-1);
        assertEquals(-100, importedCrac.getInjectionRangeAction("PRA_HVDC").getRanges().get(0).getMin(), 1e-1);
        assertEquals("AVAILABLE", importedCrac.getInjectionRangeAction("PRA_HVDC").getUsageRules().iterator().next().getUsageMethod().toString());
    }

    @Test
    void createContingencies() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertEquals(2, importedCrac.getContingencies().size());
    }

    @Test
    void createPreventiveCnecs() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertEquals(3, importedCrac.getCnecs(importedCrac.getPreventiveState()).size());
        BranchCnecCreationContext cnec1context = cracCreationContext.getBranchCnecCreationContext("basecase_branch_1 - NNL2AA1  - NNL3AA1  - basecase");
        assertTrue(cnec1context.isBaseCase());
        assertTrue(cnec1context.isImported());
        assertFalse(cnec1context.isDirectionInvertedInNetwork());
        assertTrue(cnec1context.getContingencyId().isEmpty());
        assertEquals(1, cnec1context.getCreatedCnecsIds().size());
        assertEquals("basecase_branch_1 - NNL2AA1 ->NNL3AA1  - preventive", cnec1context.getCreatedCnecsIds().get(PREVENTIVE_INSTANT_ID));
    }

    @Test
    void checkOptimizedParameterAccordingToSelected() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        BranchCnecCreationContext cnec1context = cracCreationContext.getBranchCnecCreationContext("basecase_branch_1 - NNL2AA1  - NNL3AA1  - basecase");
        BranchCnecCreationContext cnec2context = cracCreationContext.getBranchCnecCreationContext("basecase_branch_2 - NNL1AA1  - NNL3AA1  - basecase");
        BranchCnecCreationContext cnec3context = cracCreationContext.getBranchCnecCreationContext("basecase_branch_3 - NNL1AA1  - NNL2AA1  - basecase");
        assertFalse(((CseCriticalBranchCreationContext) cnec1context).isSelected());
        assertTrue(((CseCriticalBranchCreationContext) cnec2context).isSelected());
        assertTrue(((CseCriticalBranchCreationContext) cnec3context).isSelected());
        assertFalse(importedCrac.getCnec(cnec1context.getCreatedCnecsIds().get(PREVENTIVE_INSTANT_ID)).isOptimized());
        assertTrue(importedCrac.getCnec(cnec2context.getCreatedCnecsIds().get(PREVENTIVE_INSTANT_ID)).isOptimized());
        assertTrue(importedCrac.getCnec(cnec3context.getCreatedCnecsIds().get(PREVENTIVE_INSTANT_ID)).isOptimized());
    }

    @Test
    void createCurativeCnecs() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        BranchCnecCreationContext cnec2context = cracCreationContext.getBranchCnecCreationContext("French line 1 - FFR1AA1  - FFR2AA1  - outage_1");
        assertFalse(cnec2context.isBaseCase());
        assertTrue(cnec2context.isImported());
        assertFalse(cnec2context.isDirectionInvertedInNetwork());
        assertEquals("outage_1", cnec2context.getContingencyId().get());
        assertEquals(2, cnec2context.getCreatedCnecsIds().size());
        assertEquals("French line 1 - FFR1AA1 ->FFR2AA1   - outage_1 - outage", cnec2context.getCreatedCnecsIds().get(OUTAGE_INSTANT_ID));
        assertEquals("French line 1 - FFR1AA1 ->FFR2AA1   - outage_1 - curative", cnec2context.getCreatedCnecsIds().get(CURATIVE_INSTANT_ID));
    }

    @Test
    void doNotCreateAbsentFromNetworkCnec() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertCriticalBranchNotImported("French line 2 - FFRFAK2  - FFRFAK1  - outage_2", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
    }

    @Test
    void createNetworkActions() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertEquals(2, importedCrac.getNetworkActions().size());
    }

    @Test
    void createRangeActions() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertEquals(1, importedCrac.getRangeActions().size());
    }

    @Test
    void doNotCreateAbsentFromNetworkContingency() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertOutageNotImported("outage_3", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);

    }

    @Test
    void doNotCreateAbsentFromNetworkPstRangeAction() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertRemedialActionNotImported("cra_4", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
    }

    @Test
    void doNotCreateAbsentFromNetworkTopologyAction() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertRemedialActionNotImported("cra_5", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
    }

    @Test
    void doNotCreateAbsentFromNetworkInjectionSetpointCurative() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        assertRemedialActionNotImported("cra_6", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
    }

    @Test
    void doNotCreateAbsentFromNetworkInjectionSetpointPreventive() throws IOException {
        setUp("/cracs/cse_crac_2.xml");
        assertRemedialActionNotImported("cra_1", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
    }

    @Test
    void doNotCreateInjectionSetpointWithOneAbsentFromNetworkNode() throws IOException {
        setUp("/cracs/cse_crac_2.xml");
        assertRemedialActionNotImported("cra_3", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
    }

    @Test
    void createInjectionSetpointWithWildcard() throws IOException {
        setUp("/cracs/cse_crac_2.xml");
        ElementaryCreationContext raContext = cracCreationContext.getRemedialActionCreationContext("cra_4");
        assertTrue(raContext.isImported());
        NetworkAction na = cracCreationContext.getCrac().getNetworkAction("cra_4");
        assertEquals(2, na.getNetworkElements().size());
        assertTrue(na.getNetworkElements().stream().anyMatch(el -> el.getId().equals("FFR3AA1 _generator")));
        assertTrue(na.getNetworkElements().stream().anyMatch(el -> el.getId().equals("FFR2AA1 _generator")));
    }

    @Test
    void cracCreationContextReport() throws IOException {
        setUp("/cracs/cse_crac_1.xml");
        List<String> creationReport = cracCreationContext.getCreationReport().getReport();
        assertFalse(creationReport.isEmpty());
        assertEquals(6, creationReport.size());
    }

    @Test
    void cracCreationContextReport2() throws IOException {
        setUp("/cracs/cse_crac_2.xml");
        List<String> creationReport = cracCreationContext.getCreationReport().getReport();
        assertFalse(creationReport.isEmpty());
        assertEquals(5, creationReport.size());
    }

    @Test
    void testRaOnConstraint() throws IOException {
        setUp("/cracs/cse_crac_onConstraint.xml");

        State preventiveState = importedCrac.getPreventiveState();
        State outageState = importedCrac.getState(importedCrac.getContingency("outage_1"), outageInstant);
        State curativeState = importedCrac.getState(importedCrac.getContingency("outage_1"), curativeInstant);

        FlowCnec outageCnec = importedCrac.getFlowCnec("French line 1 - FFR1AA1 ->FFR2AA1   - outage_1 - outage");
        FlowCnec curativeCnec = importedCrac.getFlowCnec("French line 1 - FFR1AA1 ->FFR2AA1   - outage_1 - curative");

        // PRA
        RemedialAction<?> ra = importedCrac.getRangeAction("PST_pra_3_BBE2AA1  BBE3AA1  1");
        assertEquals(2, ra.getUsageRules().size());
        List<UsageRule> usageRuleList = ra.getUsageRules().stream().toList();

        UsageRule usageRule1 = usageRuleList.get(0);
        UsageRule usageRule2 = usageRuleList.get(1);
        assertTrue(usageRule1 instanceof OnConstraint<?>);
        assertTrue(usageRule2 instanceof OnConstraint<?>);
        assertEquals(preventiveInstant, usageRule1.getInstant());
        assertEquals(preventiveInstant, usageRule2.getInstant());
        assertTrue(((OnConstraint<?>) usageRule1).getCnec().equals(outageCnec) || ((OnConstraint<?>) usageRule2).getCnec().equals(outageCnec));
        assertTrue(((OnConstraint<?>) usageRule1).getCnec().equals(curativeCnec) || ((OnConstraint<?>) usageRule2).getCnec().equals(curativeCnec));
        System.out.println(usageRule1.getUsageMethod(preventiveState));
        System.out.println(usageRule2.getUsageMethod(preventiveState));
        assertEquals(UsageMethod.AVAILABLE, usageRule1.getUsageMethod(preventiveState));
        assertEquals(UsageMethod.AVAILABLE, usageRule2.getUsageMethod(preventiveState));
        assertEquals(UsageMethod.UNDEFINED, usageRule1.getUsageMethod(outageState));
        assertEquals(UsageMethod.UNDEFINED, usageRule2.getUsageMethod(outageState));
        assertEquals(UsageMethod.UNDEFINED, usageRule1.getUsageMethod(curativeState));
        assertEquals(UsageMethod.UNDEFINED, usageRule2.getUsageMethod(curativeState));

        // CRA
        ra = importedCrac.getNetworkAction("cra_1");
        assertEquals(1, ra.getUsageRules().size());
        usageRule1 = ra.getUsageRules().iterator().next();
        assertTrue(usageRule1 instanceof OnConstraint<?>);
        assertSame(curativeCnec, ((OnConstraint<?>) usageRule1).getCnec());
        assertEquals(curativeInstant, usageRule1.getInstant());
        assertEquals(UsageMethod.UNDEFINED, usageRule1.getUsageMethod(preventiveState));
        assertEquals(UsageMethod.UNDEFINED, usageRule1.getUsageMethod(outageState));
        assertEquals(UsageMethod.AVAILABLE, usageRule1.getUsageMethod(curativeState));
    }

    @Test
    void testPercentageThresholdsOnLeftSide() throws IOException {
        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_SIDE_ONE);
        setUp("/cracs/cse_crac_pct_limit.xml");

        FlowCnec flowCnec1 = importedCrac.getFlowCnec("basecase_branch_1 - NNL2AA1 ->NNL3AA1  - preventive");
        assertEquals(1, flowCnec1.getThresholds().size());
        assertEquals(0.7, flowCnec1.getThresholds().iterator().next().max().get(), DOUBLE_TOLERANCE);
        assertTrue(flowCnec1.getThresholds().iterator().next().min().isEmpty());
        assertEquals(0.7 * 5000., flowCnec1.getUpperBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);

        FlowCnec flowCnec2 = importedCrac.getFlowCnec("basecase_branch_2 - NNL1AA1 ->NNL3AA1  - preventive");
        assertEquals(1, flowCnec2.getThresholds().size());
        assertEquals(-1., flowCnec2.getThresholds().iterator().next().min().get(), DOUBLE_TOLERANCE);
        assertTrue(flowCnec2.getThresholds().iterator().next().max().isEmpty());
        assertEquals(-5000., flowCnec2.getLowerBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);

        FlowCnec flowCnec3 = importedCrac.getFlowCnec("basecase_branch_3 - NNL1AA1 ->NNL2AA1  - preventive");
        assertEquals(1, flowCnec3.getThresholds().size());
        assertEquals(-0.2, flowCnec3.getThresholds().iterator().next().min().get(), DOUBLE_TOLERANCE);
        assertEquals(0.2, flowCnec3.getThresholds().iterator().next().max().get(), DOUBLE_TOLERANCE);
        assertEquals(-0.2 * 5000., flowCnec3.getLowerBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
        assertEquals(0.2 * 5000., flowCnec3.getUpperBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
    }

    @Test
    void testPercentageThresholdsOnRightSide() throws IOException {
        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_SIDE_TWO);
        setUp("/cracs/cse_crac_pct_limit.xml");

        FlowCnec flowCnec1 = importedCrac.getFlowCnec("basecase_branch_1 - NNL2AA1 ->NNL3AA1  - preventive");
        assertEquals(1, flowCnec1.getThresholds().size());
        assertEquals(0.7, flowCnec1.getThresholds().iterator().next().max().get(), DOUBLE_TOLERANCE);
        assertTrue(flowCnec1.getThresholds().iterator().next().min().isEmpty());
        assertEquals(0.7 * 5000., flowCnec1.getUpperBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);

        FlowCnec flowCnec2 = importedCrac.getFlowCnec("basecase_branch_2 - NNL1AA1 ->NNL3AA1  - preventive");
        assertEquals(1, flowCnec2.getThresholds().size());
        assertEquals(-1., flowCnec2.getThresholds().iterator().next().min().get(), DOUBLE_TOLERANCE);
        assertTrue(flowCnec2.getThresholds().iterator().next().max().isEmpty());
        assertEquals(-5000., flowCnec2.getLowerBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);

        FlowCnec flowCnec3 = importedCrac.getFlowCnec("basecase_branch_3 - NNL1AA1 ->NNL2AA1  - preventive");
        assertEquals(1, flowCnec3.getThresholds().size());
        assertEquals(-0.2, flowCnec3.getThresholds().iterator().next().min().get(), DOUBLE_TOLERANCE);
        assertEquals(0.2, flowCnec3.getThresholds().iterator().next().max().get(), DOUBLE_TOLERANCE);
        assertEquals(-0.2 * 5000., flowCnec3.getLowerBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
        assertEquals(0.2 * 5000., flowCnec3.getUpperBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
    }

    @Test
    void testPercentageThresholdsOnBothSides() throws IOException {
        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_BOTH_SIDES);
        setUp("/cracs/cse_crac_pct_limit.xml");

        FlowCnec flowCnec1 = importedCrac.getFlowCnec("basecase_branch_1 - NNL2AA1 ->NNL3AA1  - preventive");
        assertEquals(2, flowCnec1.getThresholds().size());
        assertEquals(0.7 * 5000., flowCnec1.getUpperBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
        assertEquals(0.7 * 5000., flowCnec1.getUpperBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);

        FlowCnec flowCnec2 = importedCrac.getFlowCnec("basecase_branch_2 - NNL1AA1 ->NNL3AA1  - preventive");
        assertEquals(2, flowCnec2.getThresholds().size());
        assertEquals(-5000., flowCnec2.getLowerBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
        assertEquals(-5000., flowCnec2.getLowerBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);

        FlowCnec flowCnec3 = importedCrac.getFlowCnec("basecase_branch_3 - NNL1AA1 ->NNL2AA1  - preventive");
        assertEquals(2, flowCnec3.getThresholds().size());
        assertEquals(-0.2 * 5000., flowCnec3.getLowerBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
        assertEquals(0.2 * 5000., flowCnec3.getUpperBound(TwoSides.ONE, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
        assertEquals(-0.2 * 5000., flowCnec3.getLowerBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
        assertEquals(0.2 * 5000., flowCnec3.getUpperBound(TwoSides.TWO, Unit.AMPERE).get(), DOUBLE_TOLERANCE);
    }

    @Test
    void testRaOnConstraintInSpecificCountry() throws IOException {
        setUp("/cracs/cse_crac_onConstraintInSpecificCountry.xml");

        // cra_1
        RemedialAction<?> cra1 = importedCrac.getNetworkAction("cra_1");
        assertEquals(1, cra1.getUsageRules().size()); // one OnConstraint on CNEC 1
        Iterator<UsageRule> iterator1 = cra1.getUsageRules().iterator();
        UsageRule crac1UsageRule0 = iterator1.next();
        assertTrue(crac1UsageRule0 instanceof OnConstraint<?>);
        // cra_2
        RemedialAction<?> cra2 = importedCrac.getNetworkAction("cra_2");
        assertEquals(2, cra2.getUsageRules().size()); // one OnInstant, one OnConstraint on CNEC 1
        List<UsageRule> usageRules2List = cra2.getUsageRules().stream().sorted(Comparator.comparing(ur -> ur.getClass().getName())).toList();
        assertTrue(usageRules2List.get(0) instanceof OnConstraint<?>);
        assertTrue(usageRules2List.get(1) instanceof OnInstant);
        // cra_3
        RemedialAction<?> cra3 = importedCrac.getNetworkAction("cra_3");
        assertEquals(2, cra3.getUsageRules().size()); // 1 OnConstraint on CNEC 1 and 1 on country FR
        List<UsageRule> usageRules3List = cra3.getUsageRules().stream().sorted(Comparator.comparing(ur -> ur.getClass().getName())).toList();
        assertTrue(usageRules3List.get(0) instanceof OnConstraint<?>);
        assertTrue(usageRules3List.get(1) instanceof OnFlowConstraintInCountry);
        assertEquals(Country.FR, ((OnFlowConstraintInCountry) usageRules3List.get(1)).getCountry());
        // cra_4
        RemedialAction<?> cra4 = importedCrac.getNetworkAction("cra_4");
        assertEquals(1, cra4.getUsageRules().size()); // on country NL
        Iterator<UsageRule> iterator4 = cra4.getUsageRules().iterator();
        UsageRule crac4UsageRule0 = iterator4.next();
        assertTrue(crac4UsageRule0 instanceof OnFlowConstraintInCountry);
        assertEquals(curativeInstant, crac4UsageRule0.getInstant());
        assertEquals(Country.NL, ((OnFlowConstraintInCountry) crac4UsageRule0).getCountry());
        // cra_5
        RemedialAction<?> cra5 = importedCrac.getNetworkAction("cra_5");
        assertEquals(1, cra5.getUsageRules().size()); // on country FR
        Iterator<UsageRule> iterator5 = cra5.getUsageRules().iterator();
        UsageRule crac5UsageRule0 = iterator5.next();
        assertTrue(crac5UsageRule0 instanceof OnFlowConstraintInCountry);
        assertEquals(curativeInstant, crac5UsageRule0.getInstant());
        assertEquals(Country.FR, ((OnFlowConstraintInCountry) crac5UsageRule0).getCountry());
        // cra_6
        assertTrue(importedCrac.getNetworkAction("cra_6").getUsageRules().isEmpty());
    }

    @Test
    void testInvertPstRangeAction() throws IOException {
        setUp("/cracs/cse_crac_inverted_pst.xml");

        // ra_1 should not be inverted
        assertTrue(cracCreationContext.getRemedialActionCreationContext("ra_1") instanceof CsePstCreationContext);
        CsePstCreationContext pstContext = (CsePstCreationContext) cracCreationContext.getRemedialActionCreationContext("ra_1");
        assertTrue(pstContext.isImported());
        assertFalse(pstContext.isAltered());
        assertEquals("ra_1", pstContext.getNativeObjectId());
        assertEquals("PST_ra_1_BBE2AA1  BBE3AA1  1", pstContext.getCreatedObjectId());
        assertFalse(pstContext.isInverted());
        assertFalse(pstContext.isAltered());
        assertEquals("BBE2AA1  BBE3AA1  1", pstContext.getNativeNetworkElementId());
        PstRangeAction pstRangeAction = importedCrac.getPstRangeAction(pstContext.getCreatedObjectId());
        assertEquals("BBE2AA1  BBE3AA1  1", pstRangeAction.getNetworkElement().getId());
        assertEquals(3, pstRangeAction.getInitialTap());
        assertEquals(RangeType.ABSOLUTE, pstRangeAction.getRanges().get(0).getRangeType());
        assertEquals(-2, pstRangeAction.getRanges().get(0).getMinTap());
        assertEquals(10, pstRangeAction.getRanges().get(0).getMaxTap());

        // ra_2 should be inverted but range remains the same (just aligns on network direction)
        assertTrue(cracCreationContext.getRemedialActionCreationContext("ra_2") instanceof CsePstCreationContext);
        pstContext = (CsePstCreationContext) cracCreationContext.getRemedialActionCreationContext("ra_2");
        assertTrue(pstContext.isImported());
        assertEquals("ra_2", pstContext.getNativeObjectId());
        assertEquals("PST_ra_2_BBE2AA1  BBE3AA1  1", pstContext.getCreatedObjectId());
        assertFalse(pstContext.isInverted());
        assertFalse(pstContext.isAltered());
        assertEquals("BBE3AA1  BBE2AA1  1", pstContext.getNativeNetworkElementId());
        pstRangeAction = importedCrac.getPstRangeAction(pstContext.getCreatedObjectId());
        assertEquals("BBE2AA1  BBE3AA1  1", pstRangeAction.getNetworkElement().getId());
        assertEquals(3, pstRangeAction.getInitialTap());
        assertEquals(RangeType.ABSOLUTE, pstRangeAction.getRanges().get(0).getRangeType());
        assertEquals(-2, pstRangeAction.getRanges().get(0).getMinTap());
        assertEquals(10, pstRangeAction.getRanges().get(0).getMaxTap());
    }

    @Test
    void testImportBusBarChange() throws IOException {
        parameters = JsonCracCreationParameters.read(getClass().getResourceAsStream("/parameters/CseCracCreationParameters_15_10_1_5.json"));
        setUp("/cracs/cseCrac_ep15us10-1case5.xml", "/networks/TestCase12Nodes_forCSE_3nodes.uct");

        assertEquals(2, importedCrac.getNetworkActions().size());
        assertTrue(cracCreationContext.getRemedialActionCreationContext("RA1").isImported());
        assertTrue(cracCreationContext.getRemedialActionCreationContext("RA2").isImported());

        NetworkAction ra1 = importedCrac.getNetworkAction("RA1");
        assertEquals(1, ra1.getElementaryActions().size());
        assertTrue(ra1.getElementaryActions().iterator().next() instanceof SwitchPair);
        SwitchPair switchPair = (SwitchPair) ra1.getElementaryActions().iterator().next();
        assertEquals("BBE1AA1X BBE1AA11 1", switchPair.getSwitchToOpen().getId());
        assertEquals("BBE1AA1X BBE1AA12 1", switchPair.getSwitchToClose().getId());

        NetworkAction ra2 = importedCrac.getNetworkAction("RA2");
        assertEquals(1, ra2.getElementaryActions().size());
        assertTrue(ra2.getElementaryActions().iterator().next() instanceof SwitchPair);
        switchPair = (SwitchPair) ra2.getElementaryActions().iterator().next();
        assertEquals("BBE1AA1X BBE1AA12 1", switchPair.getSwitchToOpen().getId());
        assertEquals("BBE1AA1X BBE1AA11 1", switchPair.getSwitchToClose().getId());
    }

    @Test
    void testImportBusBarChangeWithMissingSwitch() throws IOException {
        parameters = JsonCracCreationParameters.read(getClass().getResourceAsStream("/parameters/CseCracCreationParameters_15_10_1_3.json"));
        setUp("/cracs/cseCrac_ep15us10-1case1.xml", "/networks/TestCase12Nodes_forCSE.uct");
        assertRemedialActionNotImported("Bus bar ok test", ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK);
    }

    @Test
    void testImportBusBarChangeWithMissingParameter() throws IOException {
        parameters = JsonCracCreationParameters.read(getClass().getResourceAsStream("/parameters/CseCracCreationParameters_15_10_1_4.json"));
        setUp("/cracs/cseCrac_ep15us10-1case1.xml", "/networks/TestCase12Nodes_forCSE.uct");
        assertRemedialActionNotImported("Bus bar ok test", ImportStatus.INCOMPLETE_DATA);
    }

    @Test
    void testImportEmptyRa() throws IOException {
        setUp("/cracs/cse_crac_empty_ra.xml");
        assertNotNull(cracCreationContext.getCrac());
        assertTrue(cracCreationContext.getCrac().getRemedialActions().isEmpty());
        assertEquals(1, cracCreationContext.getRemedialActionCreationContexts().size());
    }

    private void assertHasOneThreshold(String cnecId, TwoSides side) {
        FlowCnec cnec = importedCrac.getFlowCnec(cnecId);
        assertEquals(1, cnec.getThresholds().size());
        assertEquals(side, cnec.getThresholds().iterator().next().getSide());
    }

    private void assertHasTwoThresholds(String cnecId) {
        FlowCnec cnec = importedCrac.getFlowCnec(cnecId);
        assertEquals(2, cnec.getThresholds().size());
        assertTrue(cnec.getThresholds().stream().anyMatch(branchThreshold -> branchThreshold.getSide().equals(TwoSides.ONE)));
        assertTrue(cnec.getThresholds().stream().anyMatch(branchThreshold -> branchThreshold.getSide().equals(TwoSides.TWO)));
    }

    @Test
    void testImportThresholdOnHalfLine() throws IOException {
        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_BOTH_SIDES);
        setUp("/cracs/cse_crac_halflines.xml");
        assertHasOneThreshold("basecase_branch_1 - FFR2AA1 ->X_DEFR1  - preventive", TwoSides.TWO);
        assertHasOneThreshold("basecase_branch_2 - DDE2AA1 ->X_NLDE1  - preventive", TwoSides.ONE);

        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_SIDE_TWO);
        setUp("/cracs/cse_crac_halflines.xml");
        assertHasOneThreshold("basecase_branch_1 - FFR2AA1 ->X_DEFR1  - preventive", TwoSides.TWO);
        assertHasOneThreshold("basecase_branch_2 - DDE2AA1 ->X_NLDE1  - preventive", TwoSides.ONE);

        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_SIDE_ONE);
        setUp("/cracs/cse_crac_halflines.xml");
        assertHasOneThreshold("basecase_branch_1 - FFR2AA1 ->X_DEFR1  - preventive", TwoSides.TWO);
        assertHasOneThreshold("basecase_branch_2 - DDE2AA1 ->X_NLDE1  - preventive", TwoSides.ONE);
    }

    @Test
    void testTransformerCnecThresholds() throws IOException {
        // basecase_branch_1 is in A, threshold should be defined on high voltage level side
        // basecase_branch_2 is in %Imax, thresholds should be created depending on default monitored side

        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_BOTH_SIDES);
        setUpWithTransformer("/cracs/cse_crac_transformer_cnec.xml");
        assertHasOneThreshold("basecase_branch_1 - BBE2AA1 ->BBE3AA2  - preventive", TwoSides.ONE);
        assertHasTwoThresholds("basecase_branch_2 - BBE2AA1 ->BBE3AA2  - preventive");

        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_SIDE_TWO);
        setUpWithTransformer("/cracs/cse_crac_transformer_cnec.xml");
        assertHasOneThreshold("basecase_branch_1 - BBE2AA1 ->BBE3AA2  - preventive", TwoSides.ONE);
        assertHasOneThreshold("basecase_branch_2 - BBE2AA1 ->BBE3AA2  - preventive", TwoSides.TWO);

        parameters.setDefaultMonitoredLineSide(CracCreationParameters.MonitoredLineSide.MONITOR_LINES_ON_SIDE_ONE);
        setUpWithTransformer("/cracs/cse_crac_transformer_cnec.xml");
        assertHasOneThreshold("basecase_branch_1 - BBE2AA1 ->BBE3AA2  - preventive", TwoSides.ONE);
        assertHasOneThreshold("basecase_branch_2 - BBE2AA1 ->BBE3AA2  - preventive", TwoSides.ONE);
    }

    @Test
    void createCracWithAuto() throws IOException {
        setUp("/cracs/cse_crac_auto.xml");
        assertRemedialActionNotImported("ara_1", ImportStatus.NOT_YET_HANDLED_BY_OPEN_RAO);
        assertEquals(9, importedCrac.getFlowCnecs().size());
        assertFalse(cracCreationContext.getCreationReport().getReport().contains("[ADDED] CNEC \"French line 1 - FFR1AA1 ->FFR2AA1   - outage_1 - auto\" has no associated automaton. It will be cloned on the OUTAGE instant in order to be secured during preventive RAO."));
    }
}