TieLineTest.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/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.iidm.network.impl;

import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.NoEquipmentNetworkFactory;
import java.time.ZonedDateTime;

import com.powsybl.iidm.network.util.TieLineUtil;
import org.junit.jupiter.api.Test;

import com.powsybl.iidm.network.util.LinkData;
import com.powsybl.iidm.network.util.SV;
import com.powsybl.iidm.network.util.LinkData.BranchAdmittanceMatrix;

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

import org.apache.commons.math3.complex.Complex;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 * @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
 */
class TieLineTest {

    @Test
    void tieLineTest0() {

        // Line1 from node1 to boundaryNode, Line2 from boundaryNode to node2
        CaseSv caseSv0 = createCase0();
        Network n = createNetworkWithTieLine(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.ONE, caseSv0);
        Branch<?> branch = n.getBranch("TWO + ONE");
        assertTrue(branch instanceof TieLine);

        TieLine tieLine = (TieLine) branch;
        SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.ONE).otherSide(tieLine);
        SV isv2 = initialSv2(caseSv0, initialModelCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE);
        assertTrue(compare(sv2, caseSv0.node2, caseSv0.line2, TwoSides.ONE, isv2));

        SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.TWO).otherSide(tieLine);
        SV isv1 = initialSv1(caseSv0, initialModelCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE);
        assertTrue(compare(sv1, caseSv0.node1, caseSv0.line1, TwoSides.TWO, isv1));

        SV isvHalf1 = initialHalf1SvBoundary(caseSv0, initialModelCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO);
        assertTrue(compare(caseSv0.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU()));
        assertTrue(compare(caseSv0.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA()));
        assertTrue(compare(getP(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP()));
        assertTrue(compare(getQ(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ()));
        assertTrue(compare(getI(caseSv0.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));

        SV isvHalf2 = initialHalf2SvBoundary(caseSv0, initialModelCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE);
        assertTrue(compare(caseSv0.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU()));
        assertTrue(compare(caseSv0.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA()));
        assertTrue(compare(getP(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP()));
        assertTrue(compare(getQ(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ()));
        assertTrue(compare(getI(caseSv0.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI()));
    }

    @Test
    void tieLineTest1() {

        // Line1 from node1 to boundaryNode, Line2 from node2 to boundaryNode
        CaseSv caseSv1 = createCase1();
        Network n = createNetworkWithTieLine(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.TWO, caseSv1);
        TieLine tieLine = n.getTieLine("TWO + TWO");

        SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.ONE).otherSide(tieLine);
        SV isv2 = initialSv2(caseSv1, initialModelCase(TwoSides.TWO, TwoSides.TWO), TwoSides.TWO, TwoSides.TWO);
        assertTrue(compare(sv2, caseSv1.node2, caseSv1.line2, TwoSides.TWO, isv2));

        SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.TWO).otherSide(tieLine);
        SV isv1 = initialSv1(caseSv1, initialModelCase(TwoSides.TWO, TwoSides.TWO), TwoSides.TWO, TwoSides.TWO);
        assertTrue(compare(sv1, caseSv1.node1, caseSv1.line1, TwoSides.TWO, isv1));

        SV isvHalf1 = initialHalf1SvBoundary(caseSv1, initialModelCase(TwoSides.TWO, TwoSides.TWO), TwoSides.TWO);
        assertTrue(compare(caseSv1.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU()));
        assertTrue(compare(caseSv1.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA()));
        assertTrue(compare(getP(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP()));
        assertTrue(compare(getQ(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ()));
        assertTrue(compare(getI(caseSv1.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));

        SV isvHalf2 = initialHalf2SvBoundary(caseSv1, initialModelCase(TwoSides.TWO, TwoSides.TWO), TwoSides.TWO);
        assertTrue(compare(caseSv1.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU()));
        assertTrue(compare(caseSv1.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA()));
        assertTrue(compare(getP(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP()));
        assertTrue(compare(getQ(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ()));
        assertTrue(compare(getI(caseSv1.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI()));
    }

    @Test
    void tieLineTest2() {

        // Line1 from boundaryNode to node1, Line2 from boundaryNode to node2
        CaseSv caseSv2 = createCase2();
        Network n = createNetworkWithTieLine(NetworkFactory.findDefault(), TwoSides.ONE, TwoSides.ONE, caseSv2);
        TieLine tieLine = n.getTieLine("ONE + ONE");

        SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.ONE).otherSide(tieLine);
        SV isv2 = initialSv2(caseSv2, initialModelCase(TwoSides.ONE, TwoSides.ONE), TwoSides.ONE, TwoSides.ONE);
        assertTrue(compare(sv2, caseSv2.node2, caseSv2.line2, TwoSides.ONE, isv2));

        SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.TWO).otherSide(tieLine);
        SV isv1 = initialSv1(caseSv2, initialModelCase(TwoSides.ONE, TwoSides.ONE), TwoSides.ONE, TwoSides.ONE);
        assertTrue(compare(sv1, caseSv2.node1, caseSv2.line1, TwoSides.ONE, isv1));

        SV isvHalf1 = initialHalf1SvBoundary(caseSv2, initialModelCase(TwoSides.ONE, TwoSides.ONE), TwoSides.ONE);
        assertTrue(compare(caseSv2.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU()));
        assertTrue(compare(caseSv2.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA()));
        assertTrue(compare(getP(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP()));
        assertTrue(compare(getQ(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ()));
        assertTrue(compare(getI(caseSv2.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));

        SV isvHalf2 = initialHalf2SvBoundary(caseSv2, initialModelCase(TwoSides.ONE, TwoSides.ONE), TwoSides.ONE);
        assertTrue(compare(caseSv2.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU()));
        assertTrue(compare(caseSv2.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA()));
        assertTrue(compare(getP(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP()));
        assertTrue(compare(getQ(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ()));
        assertTrue(compare(getI(caseSv2.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI()));
    }

    @Test
    void tieLineTest3() {

        // Line1 from boundaryNode to node1, Line2 from node2 to boundaryNode
        CaseSv caseSv3 = createCase3();
        Network n = createNetworkWithTieLine(NetworkFactory.findDefault(), TwoSides.ONE, TwoSides.TWO, caseSv3);
        TieLine tieLine = n.getTieLine("ONE + TWO");

        SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.ONE).otherSide(tieLine);
        SV isv2 = initialSv2(caseSv3, initialModelCase(TwoSides.ONE, TwoSides.TWO), TwoSides.ONE, TwoSides.TWO);
        assertTrue(compare(sv2, caseSv3.node2, caseSv3.line2, TwoSides.TWO, isv2));

        SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.TWO).otherSide(tieLine);
        SV isv1 = initialSv1(caseSv3, initialModelCase(TwoSides.ONE, TwoSides.TWO), TwoSides.ONE, TwoSides.TWO);
        assertTrue(compare(sv1, caseSv3.node1, caseSv3.line1, TwoSides.ONE, isv1));

        SV isvHalf1 = initialHalf1SvBoundary(caseSv3, initialModelCase(TwoSides.ONE, TwoSides.TWO), TwoSides.ONE);
        assertTrue(compare(caseSv3.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU()));
        assertTrue(compare(caseSv3.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA()));
        assertTrue(compare(getP(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP()));
        assertTrue(compare(getQ(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ()));
        assertTrue(compare(getI(caseSv3.line1, TwoSides.ONE), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));

        SV isvHalf2 = initialHalf2SvBoundary(caseSv3, initialModelCase(TwoSides.ONE, TwoSides.TWO), TwoSides.TWO);
        assertTrue(compare(caseSv3.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU()));
        assertTrue(compare(caseSv3.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA()));
        assertTrue(compare(getP(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP()));
        assertTrue(compare(getQ(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ()));
        assertTrue(compare(getI(caseSv3.line2, TwoSides.TWO), tieLine.getDanglingLine2().getBoundary().getI(), isvHalf2.getI()));
    }

    @Test
    void tieLineWithDifferentNominalVoltageAtEndsTest() {

        // Line1 from node1 to boundaryNode, Line2 from boundaryNode to node2
        CaseSv caseSv = createCaseDifferentNominalVoltageAtEnds();
        Network n = createNetworkWithTieLineWithDifferentNominalVoltageAtEnds(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.ONE, caseSv);
        TieLine tieLine = n.getTieLine("TWO + ONE");

        SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.ONE).otherSide(tieLine);
        SV isv2 = initialSv2(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE);
        assertTrue(compare(sv2, caseSv.node2, caseSv.line2, TwoSides.ONE, isv2));

        SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(),
            tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(),
            TwoSides.TWO).otherSide(tieLine);
        SV isv1 = initialSv1(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE);
        assertTrue(compare(sv1, caseSv.node1, caseSv.line1, TwoSides.TWO, isv1));

        SV isvHalf1 = initialHalf1SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO);
        assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU()));
        assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA()));
        assertTrue(compare(getP(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP()));
        assertTrue(compare(getQ(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ()));
        assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));

        SV isvHalf2 = initialHalf2SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE);
        assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU()));
        assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA()));
        assertTrue(compare(getP(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP()));
        assertTrue(compare(getQ(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ()));
        assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));
    }

    @Test
    void tieLineWithNaNVoltagesTest() {

        // Line1 from node1 to boundaryNode, Line2 from boundaryNode to node2
        CaseSv caseSv = createCaseNaNVoltages();
        Network n = createNetworkWithTieLineWithDifferentNominalVoltageAtEnds(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.ONE, caseSv);
        TieLine tieLine = n.getTieLine("TWO + ONE");

        SV sv2 = new SV(tieLine.getDanglingLine1().getTerminal().getP(), tieLine.getDanglingLine1().getTerminal().getQ(),
                tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(),
                tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(),
                TwoSides.ONE).otherSide(tieLine);
        SV isv2 = initialSv2(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE);
        assertTrue(compare(sv2, caseSv.node2, caseSv.line2, TwoSides.ONE, isv2));

        SV sv1 = new SV(tieLine.getDanglingLine2().getTerminal().getP(), tieLine.getDanglingLine2().getTerminal().getQ(),
                tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(),
                tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(),
                TwoSides.TWO).otherSide(tieLine);
        SV isv1 = initialSv1(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO, TwoSides.ONE);
        assertTrue(compare(sv1, caseSv.node1, caseSv.line1, TwoSides.TWO, isv1));

        SV isvHalf1 = initialHalf1SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.TWO);
        assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine1().getBoundary().getV(), isvHalf1.getU()));
        assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine1().getBoundary().getAngle(), isvHalf1.getA()));
        assertTrue(compare(getP(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getP(), isvHalf1.getP()));
        assertTrue(compare(getQ(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getQ(), isvHalf1.getQ()));
        assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));

        SV isvHalf2 = initialHalf2SvBoundary(caseSv, initialModelDifferentVlCase(TwoSides.TWO, TwoSides.ONE), TwoSides.ONE);
        assertTrue(compare(caseSv.nodeBoundary.v, tieLine.getDanglingLine2().getBoundary().getV(), isvHalf2.getU()));
        assertTrue(compare(caseSv.nodeBoundary.a, tieLine.getDanglingLine2().getBoundary().getAngle(), isvHalf2.getA()));
        assertTrue(compare(getP(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getP(), isvHalf2.getP()));
        assertTrue(compare(getQ(caseSv.line2, TwoSides.ONE), tieLine.getDanglingLine2().getBoundary().getQ(), isvHalf2.getQ()));
        assertTrue(compare(getI(caseSv.line1, TwoSides.TWO), tieLine.getDanglingLine1().getBoundary().getI(), isvHalf1.getI()));
    }

    @Test
    void testDefaultValuesTieLine() {

        Network network = NoEquipmentNetworkFactory.create();

        Substation s1 = network.newSubstation()
                .setId("S1")
                .add();
        VoltageLevel s1vl1 = s1.newVoltageLevel()
                .setId("S1VL1")
                .setNominalV(1.0)
                .setLowVoltageLimit(0.95)
                .setHighVoltageLimit(1.05)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();

        createBus(s1vl1, "S1VL1-BUS");

        Substation s2 = network.newSubstation()
                .setId("S2")
                .add();
        VoltageLevel s2vl1 = s2.newVoltageLevel()
                .setId("S2VL1")
                .setNominalV(1.0)
                .setLowVoltageLimit(0.95)
                .setHighVoltageLimit(1.05)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();

        createBus(s2vl1, "S2VL1-BUS");

        TwoSides boundarySide1 = TwoSides.ONE;
        TwoSides boundarySide2 = TwoSides.TWO;
        DanglingLine dl1 = s1vl1.newDanglingLine()
                .setId(boundarySide1.name())
                .setName(boundarySide1.name())
                .setP0(0.0)
                .setQ0(0.0)
                .setR(1.0)
                .setX(2.0)
                .setBus("S1VL1-BUS")
                .setPairingKey("key")
                .add();
        DanglingLine dl2 = s2vl1.newDanglingLine()
                .setId(boundarySide2.name())
                .setName(boundarySide2.name())
                .setP0(0.0)
                .setQ0(0.0)
                .setR(1.0)
                .setX(2.0)
                .setBus("S2VL1-BUS")
                .setPairingKey("key")
                .add();

        TieLine tieLine = network.newTieLine()
                .setId(boundarySide1.name() + " + " + boundarySide2.name())
                .setName(boundarySide1.name() + " + " + boundarySide2.name())
                .setDanglingLine1(dl1.getId())
                .setDanglingLine2(dl2.getId())
                .add();

        assertEquals(0.0, tieLine.getDanglingLine1().getG(), 0.0);
        assertEquals(0.0, tieLine.getDanglingLine1().getB(), 0.0);
        assertEquals(0.0, tieLine.getDanglingLine2().getG(), 0.0);
        assertEquals(0.0, tieLine.getDanglingLine2().getB(), 0.0);

        assertSame(s1vl1, tieLine.getDanglingLine1().getTerminal().getVoltageLevel());
        assertSame(s2vl1, tieLine.getDanglingLine2().getTerminal().getVoltageLevel());
    }

    @Test
    void tieLineTestZeroImpedanceDl1() {
        // Line1 from node1 to boundaryNode, Line2 from node2 to boundaryNode
        CaseSv caseSv0 = createCase0();
        Network n = createNetworkWithTieLine(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.TWO, caseSv0);
        Branch<?> branch = n.getBranch("TWO + TWO");
        assertTrue(branch instanceof TieLine);

        TieLine tieLine = (TieLine) branch;
        tieLine.getDanglingLine1().setR(0.0).setX(0.0).setG(0.0).setB(0.0);

        double tol = 1.0e-6;
        assertEquals(tieLine.getDanglingLine2().getR(), tieLine.getR(), tol);
        assertEquals(tieLine.getDanglingLine2().getX(), tieLine.getX(), tol);
        assertEquals(tieLine.getDanglingLine2().getG(), tieLine.getG2(), tol);
        assertEquals(tieLine.getDanglingLine2().getB(), tieLine.getB2(), tol);
        assertEquals(0.0, tieLine.getG1(), tol);
        assertEquals(0.0, tieLine.getB1(), tol);

        assertEquals(tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(), TieLineUtil.getBoundaryV(tieLine.getDanglingLine1(), tieLine.getDanglingLine2()), tol);
        assertEquals(tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(), TieLineUtil.getBoundaryAngle(tieLine.getDanglingLine1(), tieLine.getDanglingLine2()), tol);
    }

    @Test
    void tieLineTestZeroImpedanceDl2() {
        // Line1 from node1 to boundaryNode, Line2 from node2 to boundaryNode
        CaseSv caseSv0 = createCase0();
        Network n = createNetworkWithTieLine(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.TWO, caseSv0);
        Branch<?> branch = n.getBranch("TWO + TWO");
        assertTrue(branch instanceof TieLine);

        TieLine tieLine = (TieLine) branch;
        tieLine.getDanglingLine2().setR(0.0).setX(0.0).setG(0.0).setB(0.0);

        double tol = 1.0e-6;
        assertEquals(tieLine.getDanglingLine1().getR(), tieLine.getR(), tol);
        assertEquals(tieLine.getDanglingLine1().getX(), tieLine.getX(), tol);
        assertEquals(tieLine.getDanglingLine1().getG(), tieLine.getG1(), tol);
        assertEquals(tieLine.getDanglingLine1().getB(), tieLine.getB1(), tol);
        assertEquals(0.0, tieLine.getG2(), tol);
        assertEquals(0.0, tieLine.getB2(), tol);

        assertEquals(tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getV(), TieLineUtil.getBoundaryV(tieLine.getDanglingLine1(), tieLine.getDanglingLine2()), tol);
        assertEquals(tieLine.getDanglingLine2().getTerminal().getBusView().getBus().getAngle(), TieLineUtil.getBoundaryAngle(tieLine.getDanglingLine1(), tieLine.getDanglingLine2()), tol);
    }

    @Test
    void tieLineTestZeroImpedanceDl1AndDl2() {
        // Line1 from node1 to boundaryNode, Line2 from node2 to boundaryNode
        CaseSv caseSv0 = createCase0();
        Network n = createNetworkWithTieLine(NetworkFactory.findDefault(), TwoSides.TWO, TwoSides.TWO, caseSv0);
        Branch<?> branch = n.getBranch("TWO + TWO");
        assertTrue(branch instanceof TieLine);

        TieLine tieLine = (TieLine) branch;
        tieLine.getDanglingLine1().setR(0.0).setX(0.0).setG(0.0).setB(0.0);
        tieLine.getDanglingLine2().setR(0.0).setX(0.0).setG(0.0).setB(0.0);

        double tol = 1.0e-6;
        assertEquals(0.0, tieLine.getR(), tol);
        assertEquals(0.0, tieLine.getX(), tol);
        assertEquals(0.0, tieLine.getG1(), tol);
        assertEquals(0.0, tieLine.getB1(), tol);
        assertEquals(0.0, tieLine.getG2(), tol);
        assertEquals(0.0, tieLine.getB2(), tol);

        assertEquals(tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getV(), TieLineUtil.getBoundaryV(tieLine.getDanglingLine1(), tieLine.getDanglingLine2()), tol);
        assertEquals(tieLine.getDanglingLine1().getTerminal().getBusView().getBus().getAngle(), TieLineUtil.getBoundaryAngle(tieLine.getDanglingLine1(), tieLine.getDanglingLine2()), tol);
    }

    private static Network createNetworkWithTieLine(NetworkFactory networkFactory,
                                                    TwoSides boundarySide1, TwoSides boundarySide2, CaseSv caseSv) {

        Network network = networkFactory.createNetwork("TieLine-BusBreaker", "test");
        network.setCaseDate(ZonedDateTime.parse("2017-06-25T17:43:00.000+01:00"));
        network.setForecastDistance(0);

        Substation s1 = network.newSubstation()
                .setId("S1")
                .add();
        VoltageLevel s1vl1 = s1.newVoltageLevel()
                .setId("S1VL1")
                .setNominalV(1.0)
                .setLowVoltageLimit(0.95)
                .setHighVoltageLimit(1.05)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();

        createBus(s1vl1, "S1VL1-BUS");

        Substation s2 = network.newSubstation()
            .setId("S2")
            .add();
        VoltageLevel s2vl1 = s2.newVoltageLevel()
            .setId("S2VL1")
            .setNominalV(1.0)
            .setLowVoltageLimit(0.95)
            .setHighVoltageLimit(1.05)
            .setTopologyKind(TopologyKind.BUS_BREAKER)
            .add();

        createBus(s2vl1, "S2VL1-BUS");

        // The initial parameters for AcLineSegment 1 are R = 0.019, X = 0.059, G1 = 0.02, B1 = 0.075, G2 = 0.03, B2 = 0.065
        // The initial parameters for AcLineSegment 2 are R = 0.038, X = 0.118, G1 = 0.015,B1 = 0.050, G2 = 0.025,B2 = 0.080
        // AcLinesegment 1 must be reoriented if boundary side is at end 1
        // AcLinesegment 2 must be reoriented if boundary side is at end 2
        // Current model does not allow shunt admittances at both ends, so it does not make sense to reorient the AcLineSegments

        DanglingLine dl1 = s1vl1.newDanglingLine()
                .setBus("S1VL1-BUS")
                .setId(boundarySide1.name())
                .setEnsureIdUnicity(true)
                .setName(boundarySide1.name())
                .setP0(0.0)
                .setQ0(0.0)
                .setR(0.019)
                .setX(0.059)
                .setG(0.05)
                .setB(0.14)
                .add();
        DanglingLine dl2 = s2vl1.newDanglingLine()
                .setBus("S2VL1-BUS")
                .setId(boundarySide2.name())
                .setEnsureIdUnicity(true)
                .setName(boundarySide2.name())
                .setP0(0.0)
                .setQ0(0.0)
                .setR(0.038)
                .setX(0.118)
                .setG(0.04)
                .setB(0.13)
                .setPairingKey("key")
                .add();

        TieLine tieLine = network.newTieLine()
            .setId(boundarySide1.name() + " + " + boundarySide2.name())
            .setName(boundarySide1.name() + " + " + boundarySide2.name())
            .setDanglingLine1(dl1.getId())
            .setDanglingLine2(dl2.getId())
            .add();
        tieLine.getDanglingLine1().getTerminal().getBusView().getBus().setV(caseSv.node1.v);
        tieLine.getDanglingLine1().getTerminal().getBusView().getBus().setAngle(caseSv.node1.a);
        tieLine.getDanglingLine1().getTerminal().setP(getOtherSideP(caseSv.line1, boundarySide1));
        tieLine.getDanglingLine1().getTerminal().setQ(getOtherSideQ(caseSv.line1, boundarySide1));

        tieLine.getDanglingLine2().getTerminal().getBusView().getBus().setV(caseSv.node2.v);
        tieLine.getDanglingLine2().getTerminal().getBusView().getBus().setAngle(caseSv.node2.a);
        tieLine.getDanglingLine2().getTerminal().setP(getOtherSideP(caseSv.line2, boundarySide2));
        tieLine.getDanglingLine2().getTerminal().setQ(getOtherSideQ(caseSv.line2, boundarySide2));

        return network;
    }

    private static Network createNetworkWithTieLineWithDifferentNominalVoltageAtEnds(NetworkFactory networkFactory,
                                                                                     TwoSides boundarySide1, TwoSides boundarySide2, CaseSv caseSv) {

        Network network = networkFactory.createNetwork("TieLine-BusBreaker", "test");
        network.setCaseDate(ZonedDateTime.parse("2017-06-25T17:43:00.000+01:00"));
        network.setForecastDistance(0);

        Substation s1 = network.newSubstation()
                .setId("S1")
                .add();
        VoltageLevel s1vl1 = s1.newVoltageLevel()
                .setId("S1VL1")
                .setNominalV(138.0)
                .setLowVoltageLimit(110.0)
                .setHighVoltageLimit(150.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();

        createBus(s1vl1, "S1VL1-BUS");

        Substation s2 = network.newSubstation()
            .setId("S2")
            .add();
        VoltageLevel s2vl1 = s2.newVoltageLevel()
            .setId("S2VL1")
            .setNominalV(220.0)
            .setLowVoltageLimit(195.0)
            .setHighVoltageLimit(240.0)
            .setTopologyKind(TopologyKind.BUS_BREAKER)
            .add();

        createBus(s2vl1, "S2VL1-BUS");

        // The initial parameters for AcLineSegment 1 are R = 2.1672071999999996, X = 9.5543748, G1 = 0.0, B1 = 1.648813274522159E-4, G2 = 0.0, B2 = 1.648813274522159E-4
        // AcLinesegment 1 must be reoriented if boundary side is at end 1
        // Current model does not allow shunt admittances at both ends, so it does not make sense to reorient it

        // The initial parameters for AcLineSegment 2 are R = 3.1513680000000006, X = 14.928011999999999, G1 = 0.008044414674299755, B1 = -0.03791520949675112, G2 = -0.005046041932060755, B2 = 0.023978278075869598
        // AcLinesegment 2 must be reoriented if boundary side is at end 2
        // Current model does not allow shunt admittances at both ends, so it does not make sense to reorient it
        DanglingLine dl1 = s1vl1.newDanglingLine()
                .setBus("S1VL1-BUS")
                .setId(boundarySide1.name())
                .setName(boundarySide1.name())
                .setP0(0.0)
                .setQ0(0.0)
                .setR(2.1672071999999996)
                .setX(9.5543748)
                .setG(0.0)
                .setB(0.00032976265)
                .setPairingKey("key")
                .add();
        DanglingLine dl2 = s2vl1.newDanglingLine()
                .setBus("S2VL1-BUS")
                .setId(boundarySide2.name())
                .setName(boundarySide2.name())
                .setP0(0.0)
                .setQ0(0.0)
                .setR(3.1513680000000006)
                .setX(14.928011999999999)
                .setG(0.00299837274)
                .setB(-0.01393693142)
                .add();

        TieLine tieLine = network.newTieLine()
                .setId(boundarySide1.name() + " + " + boundarySide2.name())
                .setName(boundarySide1.name() + " + " + boundarySide2.name())
                .setDanglingLine1(dl1.getId())
                .setDanglingLine2(dl2.getId())
                .add();

        tieLine.getDanglingLine1().getTerminal().getBusView().getBus().setV(caseSv.node1.v);
        tieLine.getDanglingLine1().getTerminal().getBusView().getBus().setAngle(caseSv.node1.a);
        tieLine.getDanglingLine1().getTerminal().setP(getOtherSideP(caseSv.line1, boundarySide1));
        tieLine.getDanglingLine1().getTerminal().setQ(getOtherSideQ(caseSv.line1, boundarySide1));

        tieLine.getDanglingLine2().getTerminal().getBusView().getBus().setV(caseSv.node2.v);
        tieLine.getDanglingLine2().getTerminal().getBusView().getBus().setAngle(caseSv.node2.a);
        tieLine.getDanglingLine2().getTerminal().setP(getOtherSideP(caseSv.line2, boundarySide2));
        tieLine.getDanglingLine2().getTerminal().setQ(getOtherSideQ(caseSv.line2, boundarySide2));

        return network;
    }

    private static void createBus(VoltageLevel voltageLevel, String id) {
        voltageLevel.getBusBreakerView()
            .newBus()
            .setName(id)
            .setId(id)
            .add();
    }

    private static double getOtherSideP(LineSv line, TwoSides boundarySide) {
        if (boundarySide == TwoSides.ONE) {
            return line.p2;
        } else {
            return line.p1;
        }
    }

    private static double getOtherSideQ(LineSv line, TwoSides boundarySide) {
        if (boundarySide == TwoSides.ONE) {
            return line.q2;
        } else {
            return line.q1;
        }
    }

    private static double getP(LineSv line, TwoSides boundarySide) {
        if (boundarySide == TwoSides.ONE) {
            return line.p1;
        } else {
            return line.p2;
        }
    }

    private static double getQ(LineSv line, TwoSides boundarySide) {
        if (boundarySide == TwoSides.ONE) {
            return line.q1;
        } else {
            return line.q2;
        }
    }

    private static double getI(LineSv line, TwoSides boundarySide) {
        if (boundarySide == TwoSides.ONE) {
            return line.getI1();
        } else {
            return line.getI2();
        }
    }

    // We define an error by value to adjust the case. The error is calculated by difference between
    // the calculated value with both models, the initial model of the case and the current model of the danglingLine
    // Errors are due to the danglingLine model (it does not allow shunt admittance at both ends)
    private static boolean compare(SV sv, NodeSv nodeSv, LineSv lineSv, TwoSides boundarySide, SV initialModelSv) {
        double tol = 0.00001;
        double errorP = initialModelSv.getP() - sv.getP();
        double errorQ = initialModelSv.getQ() - sv.getQ();
        double errorU = initialModelSv.getU() - sv.getU();
        double errorA = initialModelSv.getA() - sv.getA();
        if (Math.abs(sv.getP() - getOtherSideP(lineSv, boundarySide)) > tol + Math.abs(errorP)) {
            return false;
        }
        if (Math.abs(sv.getQ() - getOtherSideQ(lineSv, boundarySide)) > tol + Math.abs(errorQ)) {
            return false;
        }
        if (Math.abs(sv.getU() - nodeSv.v) > tol + Math.abs(errorU)) {
            return false;
        }
        if (Math.abs(sv.getA() - nodeSv.a) > tol + Math.abs(errorA)) {
            return false;
        }
        return true;
    }

    // We define an error to adjust the case. The error is calculated by difference between
    // the calculated value with both models, the initial model of the case and the current model of the danglingLine
    // Errors are due to the danglingLine model (it does not allow shunt admittance at both ends)
    private static boolean compare(double expected, double actual, double initialActual) {
        double tol = 0.00001;
        double error = initialActual - actual;
        if (Math.abs(actual - expected) > tol + Math.abs(error)) {
            return false;
        }
        return true;
    }

    // Line1 from node1 to nodeBoundary, Line2 from nodeBoundary to node2
    private static CaseSv createCase0() {
        NodeSv node1 = new NodeSv(1.06000000, Math.toDegrees(0.0));
        NodeSv nodeBoundary = new NodeSv(1.05913402, Math.toDegrees(-0.01700730));
        NodeSv node2 = new NodeSv(1.04546576, Math.toDegrees(-0.04168907));

        LineSv line1 = new LineSv(0.32101578, -0.16210107, -0.26328124, 0.00991455, node1.v, nodeBoundary.v);
        LineSv line2 = new LineSv(0.26328124, -0.00991455, -0.21700000, -0.12700000, nodeBoundary.v, node2.v);
        return new CaseSv(node1, node2, nodeBoundary, line1, line2);
    }

    // Line1 from node1 to nodeBoundary, Line2 from node2 to nodeBoundary
    private static CaseSv createCase1() {
        NodeSv node1 = new NodeSv(1.06000000, Math.toDegrees(0.0));
        NodeSv nodeBoundary = new NodeSv(1.05916756, Math.toDegrees(-0.01702560));
        NodeSv node2 = new NodeSv(1.04216358, Math.toDegrees(-0.03946400));

        LineSv line1 = new LineSv(0.32116645, -0.16274609, -0.26342655, 0.01056498, node1.v, nodeBoundary.v);
        LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26342655, -0.01056498, node2.v, nodeBoundary.v);
        return new CaseSv(node1, node2, nodeBoundary, line1, line2);
    }

    // Line1 from nodeBoundary to node1, Line2 from nodeBoundary to node2
    private static CaseSv createCase2() {
        NodeSv node1 = new NodeSv(1.06000000, Math.toDegrees(0.0));
        NodeSv nodeBoundary = new NodeSv(1.05998661, Math.toDegrees(-0.01660626));
        NodeSv node2 = new NodeSv(1.04634503, Math.toDegrees(-0.04125738));

        LineSv line1 = new LineSv(-0.26335112, 0.01016197, 0.32106283, -0.16270573, nodeBoundary.v, node1.v);
        LineSv line2 = new LineSv(0.26335112, -0.01016197, -0.21700000, -0.12700000, nodeBoundary.v, node2.v);
        return new CaseSv(node1, node2, nodeBoundary, line1, line2);
    }

    // Line1 from nodeBoundary to node1, Line2 from node2 to nodeBoundary
    private static CaseSv createCase3() {
        NodeSv node1 = new NodeSv(1.06000000, Math.toDegrees(0.0));
        NodeSv nodeBoundary = new NodeSv(1.06002014, Math.toDegrees(-0.01662448));
        NodeSv node2 = new NodeSv(1.04304009, Math.toDegrees(-0.03903205));

        LineSv line1 = new LineSv(-0.26349561, 0.01081185, 0.32121215, -0.16335034, nodeBoundary.v, node1.v);
        LineSv line2 = new LineSv(-0.21700000, -0.12700000, 0.26349561, -0.01081185, node2.v, nodeBoundary.v);
        return new CaseSv(node1, node2, nodeBoundary, line1, line2);
    }

    // Line1 from node1 to nodeBoundary, Line2 from nodeBoundary to node2
    // Different nominal voltage at node1 and node2
    private static CaseSv createCaseDifferentNominalVoltageAtEnds() {
        NodeSv node1 = new NodeSv(145.2861673277147, Math.toDegrees(-0.01745197));
        NodeSv nodeBoundary = new NodeSv(145.42378472578227, Math.toDegrees(-0.02324020));
        NodeSv node2 = new NodeSv(231.30269602522478, Math.toDegrees(-0.02818192));

        LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712, node1.v, nodeBoundary.v);
        LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000, nodeBoundary.v, node2.v);
        return new CaseSv(node1, node2, nodeBoundary, line1, line2);
    }

    // Line1 from node1 to nodeBoundary, Line2 from nodeBoundary to node2
    // Different nominal voltage at node1 and node2
    // NaN values for voltages - simulates DC LF
    private static CaseSv createCaseNaNVoltages() {
        NodeSv node1 = new NodeSv(Double.NaN, Math.toDegrees(-0.01745197));
        NodeSv nodeBoundary = new NodeSv(Double.NaN, Math.toDegrees(-0.02324020));
        NodeSv node2 = new NodeSv(Double.NaN, Math.toDegrees(-0.02818192));

        LineSv line1 = new LineSv(11.729938, -8.196614, -11.713527, 1.301712, node1.v, nodeBoundary.v);
        LineSv line2 = new LineSv(11.713527, -1.301712, -11.700000, -6.700000, nodeBoundary.v, node2.v);
        return new CaseSv(node1, node2, nodeBoundary, line1, line2);
    }

    private static final class CaseSv {
        private final NodeSv node1;
        private final NodeSv node2;
        private final NodeSv nodeBoundary;
        private final LineSv line1;
        private final LineSv line2;

        private CaseSv(NodeSv node1, NodeSv node2, NodeSv nodeBoundary, LineSv line1, LineSv line2) {
            this.node1 = node1;
            this.node2 = node2;
            this.nodeBoundary = nodeBoundary;
            this.line1 = line1;
            this.line2 = line2;
        }
    }

    private static final class NodeSv {
        private final double v;
        private final double a;

        private NodeSv(double v, double a) {
            this.v = v;
            this.a = a;
        }
    }

    private static final class LineSv {
        private final double p1;
        private final double q1;
        private final double p2;
        private final double q2;
        private final double v1;
        private final double v2;

        private LineSv(double p1, double q1, double p2, double q2, double v1, double v2) {
            this.p1 = p1;
            this.q1 = q1;
            this.p2 = p2;
            this.q2 = q2;
            this.v1 = v1;
            this.v2 = v2;
        }

        private double getI1() {
            return Math.hypot(p1, q1) / (Math.sqrt(3.) * v1 / 1000);
        }

        private double getI2() {
            return Math.hypot(p2, q2) / (Math.sqrt(3.) * v2 / 1000);
        }
    }

    private static SV initialSv1(CaseSv initialCase, TieLineInitialModel tlim, TwoSides half1Boundary, TwoSides half2Boundary) {
        return new SV(getOtherSideP(initialCase.line2, half2Boundary),
            getOtherSideQ(initialCase.line2, half2Boundary),
            initialCase.node2.v, initialCase.node2.a, TwoSides.TWO).otherSide(tlim.tieLine.r, tlim.tieLine.x,
                tlim.tieLine.g1, tlim.tieLine.b1, tlim.tieLine.g2, tlim.tieLine.b2, 1.0, 0.0);
    }

    private static SV initialSv2(CaseSv initialCase, TieLineInitialModel tlim, TwoSides half1Boundary, TwoSides half2Boundary) {
        return new SV(getOtherSideP(initialCase.line1, half1Boundary),
            getOtherSideQ(initialCase.line1, half1Boundary),
            initialCase.node1.v, initialCase.node1.a, TwoSides.ONE).otherSide(tlim.tieLine.r, tlim.tieLine.x,
                tlim.tieLine.g1, tlim.tieLine.b1, tlim.tieLine.g2, tlim.tieLine.b2, 1.0, 0.0);
    }

    private static SV initialHalf1SvBoundary(CaseSv initialCase, TieLineInitialModel tlim, TwoSides half1Boundary) {
        return new SV(getOtherSideP(initialCase.line1, half1Boundary),
            getOtherSideQ(initialCase.line1, half1Boundary),
            initialCase.node1.v, initialCase.node1.a,
            half1Boundary.equals(TwoSides.ONE) ? TwoSides.TWO : TwoSides.ONE).otherSide(tlim.half1.r,
                tlim.half1.x, tlim.half1.g1, tlim.half1.b1, tlim.half1.g2, tlim.half1.b2, 1.0, 0.0);
    }

    private static SV initialHalf2SvBoundary(CaseSv initialCase, TieLineInitialModel tlim, TwoSides half2Boundary) {
        return new SV(getOtherSideP(initialCase.line2, half2Boundary),
            getOtherSideQ(initialCase.line2, half2Boundary),
            initialCase.node2.v, initialCase.node2.a,
            half2Boundary.equals(TwoSides.ONE) ? TwoSides.TWO : TwoSides.ONE).otherSide(tlim.half2.r,
                tlim.half2.x, tlim.half2.g1, tlim.half2.b1, tlim.half2.g2, tlim.half2.b2, 1.0, 0.0);
    }

    private static TieLineInitialModel initialModelCase(TwoSides half1Boundary, TwoSides half2Boundary) {
        return new TieLineInitialModel(new LineInitialModel(0.019, 0.059, 0.02, 0.075, 0.03, 0.065), half1Boundary,
            new LineInitialModel(0.038, 0.118, 0.015, 0.050, 0.025, 0.080), half2Boundary);
    }

    private static TieLineInitialModel initialModelDifferentVlCase(TwoSides half1Boundary, TwoSides half2Boundary) {
        return new TieLineInitialModel(
            new LineInitialModel(2.1672071999999996, 9.5543748, 0.0, 1.648813274522159E-4, 0.0, 1.648813274522159E-4), half1Boundary,
            new LineInitialModel(3.1513680000000006, 14.928011999999999, 0.008044414674299755, -0.03791520949675112,
                -0.005046041932060755, 0.023978278075869598), half2Boundary);
    }

    private static final class TieLineInitialModel {
        private final LineInitialModel half1;
        private final LineInitialModel half2;
        private final LineInitialModel tieLine;

        private TieLineInitialModel(LineInitialModel half1, TwoSides half1Boundary, LineInitialModel half2, TwoSides half2Boundary) {
            this.half1 = half1;
            this.half2 = half2;

            BranchAdmittanceMatrix adm1 = LinkData.calculateBranchAdmittance(half1.r, half1.x, 1.0, 0.0, 1.0, 0.0,
                new Complex(half1.g1, half1.b1), new Complex(half1.g2, half1.b2));
            BranchAdmittanceMatrix adm2 = LinkData.calculateBranchAdmittance(half2.r, half2.x, 1.0, 0.0, 1.0, 0.0,
                new Complex(half2.g1, half2.b1), new Complex(half2.g2, half2.b2));

            BranchAdmittanceMatrix adm = LinkData.kronChain(adm1, half1Boundary, adm2, half2Boundary);
            this.tieLine = new LineInitialModel(adm.y12().negate().reciprocal().getReal(),
                adm.y12().negate().reciprocal().getImaginary(),
                adm.y11().add(adm.y12()).getReal(),
                adm.y11().add(adm.y12()).getImaginary(),
                adm.y22().add(adm.y21()).getReal(),
                adm.y22().add(adm.y21()).getImaginary());
        }
    }

    private static final class LineInitialModel {
        private final double r;
        private final double x;
        private final double g1;
        private final double b1;
        private final double g2;
        private final double b2;

        private LineInitialModel(double r, double x, double g1, double b1, double g2, double b2) {
            this.r = r;
            this.x = x;
            this.g1 = g1;
            this.b1 = b1;
            this.g2 = g2;
            this.b2 = b2;
        }
    }
}