Skip to content
Snippets Groups Projects
test_esipp_problem.py 269 KiB
Newer Older
# imports

# standard
import math

# local
# import numpy as np
# import networkx as nx
import pyomo.environ as pyo
# import src.topupopt.problems.esipp.utils as utils
from src.topupopt.data.misc.utils import generate_pseudo_unique_key
from src.topupopt.problems.esipp.problem import InfrastructurePlanningProblem
from src.topupopt.problems.esipp.network import Arcs, Network
from src.topupopt.problems.esipp.network import ArcsWithoutStaticLosses
from src.topupopt.problems.esipp.resource import ResourcePrice
# from src.topupopt.problems.esipp.utils import compute_cost_volume_metrics
from src.topupopt.problems.esipp.utils import statistics
from src.topupopt.problems.esipp.time import EconomicTimeFrame
# from src.topupopt.problems.esipp.converter import Converter

# *****************************************************************************
# *****************************************************************************

Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
class TestESIPPProblem:
    
    solver = 'glpk'
    # solver = 'scip'
    # solver = 'cbc'
    
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        self,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        solver_options: dict = None,
        use_sos_arcs: bool = False,
        arc_sos_weight_key: str = (InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_NONE),
        arc_use_real_variables_if_possible: bool = False,
        use_sos_sense: bool = False,
        sense_sos_weight_key: int = (
            InfrastructurePlanningProblem.SOS1_SENSE_WEIGHT_NOMINAL_HIGHER
        ),
        sense_use_real_variables_if_possible: bool = False,
        sense_use_arc_interfaces: bool = False,
        perform_analysis: bool = False,
        plot_results: bool = False,
        print_solver_output: bool = False,
        time_frame: EconomicTimeFrame = None,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        networks: dict = None,
        converters: dict = None,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        static_losses_mode=None,
        mandatory_arcs: list = None,
        max_number_parallel_arcs: dict = None,
        arc_groups_dict: dict = None,
        init_aux_sets: bool = False,
        # discount_rates: dict = None,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        assessment_weights: dict = None,
        simplify_problem: bool = False,
    ):
        if type(solver) == type(None):
            solver = self.solver
        if type(assessment_weights) != dict:
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            assessment_weights = {}  # default

        if type(converters) != dict:
            converters = {}
        # one interval twice as long as the average is worth twice
        # one interval half as long as the average is worth half
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        #     [time_period_duration/average_time_interval_duration
        #       for time_period_duration in intraperiod_time_interval_duration]
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed

        time_weights = None  # nothing yet

        normalised_time_interval_duration = None  # nothing yet

        ipp = InfrastructurePlanningProblem(
            # discount_rates=discount_rates,
            time_frame=time_frame,
            # reporting_periods=time_frame.reporting_periods,
            # time_intervals=time_frame.time_interval_durations,
            time_weights=time_weights,
            normalised_time_interval_duration=normalised_time_interval_duration,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            assessment_weights=assessment_weights,
        )

        for netkey, net in networks.items():
            ipp.add_network(network_key=netkey, network=net)
        # add converters

        for cvtkey, cvt in converters.items():
            ipp.add_converter(converter_key=cvtkey, converter=cvt)

        if type(mandatory_arcs) == list:
            for full_arc_key in mandatory_arcs:
                ipp.make_arc_mandatory(full_arc_key[0], full_arc_key[1:])
        #         for arc_key in ipp.networks[network_key].edges(keys=True):
        #             if ipp.networks[network_key].edges[arc_key][
        #                     Network.KEY_ARC_TECH].has_been_selected():
        #             ipp.make_arc_mandatory(network_key, arc_key)
        # set up the use of sos for arc selection
        if use_sos_arcs:
            for network_key in ipp.networks:
                for arc_key in ipp.networks[network_key].edges(keys=True):
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                    if (
                        ipp.networks[network_key]
                        .edges[arc_key][Network.KEY_ARC_TECH]
                        .has_been_selected()
                    ):
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                        network_key,
                        arc_key,
                        use_real_variables_if_possible=(
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                            arc_use_real_variables_if_possible
                        ),
                        sos1_weight_method=arc_sos_weight_key,
                    )

        # set up the use of sos for flow sense determination
        if use_sos_sense:
            for network_key in ipp.networks:
                for arc_key in ipp.networks[network_key].edges(keys=True):
                    if not ipp.networks[network_key].edges[arc_key][
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                        Network.KEY_ARC_UND
                    ]:
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                        network_key,
                        arc_key,
                        use_real_variables_if_possible=(
                            sense_use_real_variables_if_possible
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                        ),
                        use_interface_variables=sense_use_arc_interfaces,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                        sos1_weight_method=sense_sos_weight_key,
                    )

        elif sense_use_arc_interfaces:  # set up the use of arc interfaces w/o sos1
            for network_key in ipp.networks:
                for arc_key in ipp.networks[network_key].edges(keys=True):
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                    if (
                        ipp.networks[network_key]
                        .edges[arc_key][Network.KEY_ARC_TECH]
                        .has_been_selected()
                    ):
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed

                    ipp.use_interface_variables_for_arc_selection(network_key, arc_key)

        if static_losses_mode == ipp.STATIC_LOSS_MODE_ARR:
            ipp.place_static_losses_arrival_node()
        elif static_losses_mode == ipp.STATIC_LOSS_MODE_DEP:
            ipp.place_static_losses_departure_node()
        elif static_losses_mode == ipp.STATIC_LOSS_MODE_US:
            ipp.place_static_losses_upstream()
        elif static_losses_mode == ipp.STATIC_LOSS_MODE_DS:
            ipp.place_static_losses_downstream()
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            raise ValueError("Unknown static loss modelling mode.")

        # *********************************************************************
        if type(arc_groups_dict) != type(None):
            for key in arc_groups_dict:
                ipp.create_arc_group(arc_groups_dict[key])
        # *********************************************************************
        for key in max_number_parallel_arcs:
            ipp.set_maximum_number_parallel_arcs(
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
                network_key=key[0],
                node_a=key[1],
                node_b=key[2],
                limit=max_number_parallel_arcs[key],
            )

        # *********************************************************************
            ipp.simplify_peak_total_assessments()
        # *********************************************************************
        # instantiate (disable the default case v-a-v fixed losses)
        # ipp.instantiate(place_fixed_losses_upstream_if_possible=False)
        ipp.instantiate(initialise_ancillary_sets=init_aux_sets)
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        ipp.optimise(
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            solver_options=solver_options,
            output_options={},
            print_solver_output=print_solver_output,
        )

        # *********************************************************************
        # *********************************************************************
    # *************************************************************************
    # *************************************************************************
    def test_single_network_single_arc_problem(self):
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0, 1)},
            reporting_period_durations={q: (365 * 24 * 3600, 365 * 24 * 3600)},
            time_intervals={q: (0, 1, 2)},
            time_interval_durations={q: (1, 1, 1)},
        # 2 nodes: one import, one regular
        mynet = Network()
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed

        # import node
        node_IMP = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            node_key=node_IMP,
                qpk: ResourcePrice(prices=1.0, volumes=None)
                for qpk in tf.qpk()
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            node_key=node_A,
            base_flow={(q, 0): 0.50, (q, 1): 0.00, (q, 2): 1.00},
        )

Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            name="any",
            efficiency={qk: 0.5 for qk in tf.qk()},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            specific_capacity_cost=1,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            validate=False,
        )
        mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA)

        # identify node types
        mynet.identify_node_types()
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            plot_results=False,  # True,
            time_frame=tf,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            networks={"mynet": mynet},
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            simplify_problem=False,
        )

        # *********************************************************************
        # *********************************************************************
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 24
        assert ipp.results["Problem"][0]["Number of variables"] == 22
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 49

        # the arc should be installed since it is required for feasibility
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_IMP, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )

        # the flows should be 1.0, 0.0 and 2.0
        assert math.isclose(
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 0)]),
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            abs_tol=1e-6,
        )
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 1)]),
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            abs_tol=1e-6,
        )
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 2)]),
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            abs_tol=1e-6,
        )

        # arc amplitude should be two
        assert math.isclose(
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_IMP, node_A, 0)]),
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            abs_tol=0.01,
        )

        # capex should be four
        assert math.isclose(pyo.value(ipp.instance.var_capex), 4.0, abs_tol=1e-3)
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[q]), -5.7, abs_tol=1e-3)

        # the objective function should be -9.7
        assert math.isclose(pyo.value(ipp.instance.obj_f), -9.7, abs_tol=1e-3)
        
    # *************************************************************************
    # *************************************************************************

    def test_single_network_two_arcs_problem(self):
        
        # TODO: test simplifying this problem
        
        # assessment
        q = 0

        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0, 1)},
            reporting_period_durations={q: (365 * 24 * 3600, 365 * 24 * 3600)},
            time_intervals={q: (0, 1, 2)},
            time_interval_durations={q: (1, 1, 1)},
        )

        # 2 nodes: one import, one regular
        mynet = Network()
        
        # import node
        node_IMP = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=node_IMP,
            prices={
                qpk: ResourcePrice(prices=1.0, volumes=None)
                for qpk in tf.qpk()
            }
        )
        
        # export node
        node_EXP = generate_pseudo_unique_key(mynet.nodes())        
        mynet.add_export_node(
            node_key=node_EXP,
            prices={
                qpk: ResourcePrice(prices=0.5, volumes=None)
                for qpk in tf.qpk()
            }
        )

        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            base_flow={(q, 0): 0.50, (q, 1): -1.50, (q, 2): 1.00},
        )

        # arc IA
        arc_tech_IA = Arcs(
            name="any",
            efficiency={qk: 0.5 for qk in tf.qk()},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            validate=False,
        )
        mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA)
        
        # arc AE
        arc_tech_AE = Arcs(
            name="any",
            efficiency={qk: 0.8 for qk in tf.qk()},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            validate=False,
        )
        mynet.add_directed_arc(node_key_a=node_A, node_key_b=node_EXP, arcs=arc_tech_AE)

        # identify node types
        mynet.identify_node_types()
        
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
            simplify_problem=False,
        )

        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 42
        assert ipp.results["Problem"][0]["Number of variables"] == 40
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 95

        # *********************************************************************
        # *********************************************************************

        # validation
        # the arc should be installed since it is required for feasibility
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_IMP, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )

        # flows
        true_v_glljqk = {
            ("mynet", node_IMP, node_A, 0, q, 0): 1,
            ("mynet", node_IMP, node_A, 0, q, 1): 0,
            ("mynet", node_IMP, node_A, 0, q, 2): 2,
            ("mynet", node_A, node_EXP, 0, q, 0): 0,
            ("mynet", node_A, node_EXP, 0, q, 1): 1.5,
            ("mynet", node_A, node_EXP, 0, q, 2): 0
            }
        for key, v in true_v_glljqk.items():
            assert math.isclose(pyo.value(ipp.instance.var_v_glljqk[key]), v, abs_tol=1e-6)

        # arc amplitude should be two
        assert math.isclose(
            pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_IMP, node_A, 0)]),
            2.0,
            abs_tol=0.01,
        )
        assert math.isclose(
            pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_A, node_EXP, 0)]),
            1.5,
            abs_tol=0.01,
        )

        # capex should be four
        assert math.isclose(pyo.value(ipp.instance.var_capex), 7.5, abs_tol=1e-3)

        # sdncf should be -5.7+0.6*(0.966+0.934)
        assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[q]), -5.7+0.6*(0.966+0.934), abs_tol=1e-3)

        # the objective function should be -9.7+0.6*(0.966+0.934)
        assert math.isclose(pyo.value(ipp.instance.obj_f), -9.7+0.6*(0.966+0.934)-3.5, abs_tol=1e-3)
    # *************************************************************************
    # *************************************************************************
    def test_single_network_single_arc_problem_simpler(self):
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0, 1)},
            reporting_period_durations={q: (365 * 24 * 3600, 365 * 24 * 3600)},
            time_intervals={q: (0, 1, 2)},
            time_interval_durations={q: (1, 1, 1)},
        # 2 nodes: one import, one regular
        mynet = Network()
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed

        # import node
        node_IMP = "thatimpnode"
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            node_key=node_IMP,
                qpk: ResourcePrice(prices=1.0, volumes=None)
                for qpk in tf.qpk()
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        node_A = "thatnodea"
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            node_key=node_A,
            # base_flow=[0.5, 0.0, 1.0],
            base_flow={(q, 0): 0.50, (q, 1): 0.00, (q, 2): 1.00},
        )

Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            name="any",
            efficiency={qk: 0.5 for qk in tf.qk()},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            specific_capacity_cost=1,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            validate=False,
        )
        mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA)

        # identify node types
        mynet.identify_node_types()
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            plot_results=False,  # True,
            time_frame=tf,
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            networks={"mynet": mynet},
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
            simplify_problem=True,
        )

        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 16 # 20
        assert ipp.results["Problem"][0]["Number of variables"] == 15 # 19
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 28 # 36
        # *********************************************************************
        # *********************************************************************
        # the arc should be installed since it is required for feasibility
Pedro L. Magalhães's avatar
Pedro L. Magalhães committed
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_IMP, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )

        # capex should be four
        assert math.isclose(pyo.value(ipp.instance.var_capex), 4.0, abs_tol=1e-3)
        # the objective function should be -9.7
        assert math.isclose(pyo.value(ipp.instance.obj_f), -9.7, abs_tol=1e-3)
    # *************************************************************************
    # *************************************************************************
    
    def test_problem_two_scenarios(self):
        
        # number_intraperiod_time_intervals = 4
        nominal_discount_rate = 0.035
        assessment_weights = {0: 0.7, 1: 0.3}
            
        tf = EconomicTimeFrame(
            discount_rate=nominal_discount_rate,
            reporting_periods={0: (0, 1), 1: (0, 1, 2)}, 
            reporting_period_durations={0: (1, 1), 1: (1, 1, 1)}, # does not matter 
            time_intervals={0: (0, 1, 2), 1: (0, 1)}, 
            time_interval_durations={0: (1, 1, 1), 1: (1, 1)}, 
            )

        # 2 nodes: one import, one regular
        mynet = Network()

        node_IMP = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=node_IMP,
            prices={
                qpk: ResourcePrice(prices=1.0, volumes=None)
                for qpk in tf.qpk()
            },
        )

        # other nodes

        node_A = generate_pseudo_unique_key(mynet.nodes())

        mynet.add_source_sink_node(
            node_key=node_A,
            base_flow={
                (0, 0): 0.50,
                (0, 1): 0.00,
                (0, 2): 1.00,
                (1, 0): 1.25,
                (1, 1): 0.30,
            },
        )

        # arc IA

        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[0.5, 0.5, 0.5],
            efficiency={(0, 0): 0.5, (0, 1): 0.5, (0, 2): 0.5, (1, 0): 0.5, (1, 1): 0.5},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            validate=False,
        )

        mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA)

        # identify node types

        mynet.identify_node_types()

        # no sos, regular time intervals

        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            networks={"mynet": mynet},
            converters={},
            time_frame=tf,
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
            assessment_weights=assessment_weights,
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 42
        assert ipp.results["Problem"][0]["Number of variables"] == 38
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 87

        # *********************************************************************

        # validation

        # the arc should be installed since it is the only feasible solution

        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_IMP, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )

        # the flows should be 1.0, 0.0 and 2.0
        true_v_glljqk = {
            ("mynet", node_IMP, node_A, 0, 0, 0): 1,
            ("mynet", node_IMP, node_A, 0, 0, 1): 0,
            ("mynet", node_IMP, node_A, 0, 0, 2): 2,
            ("mynet", node_IMP, node_A, 0, 1, 0): 2.5,
            ("mynet", node_IMP, node_A, 0, 1, 1): 0.6,
            }
        
        for key, v in true_v_glljqk.items():
            assert math.isclose(pyo.value(ipp.instance.var_v_glljqk[key]), v, abs_tol=1e-6)
        # arc amplitude should be two
        assert math.isclose(
            pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_IMP, node_A, 0)]),
            abs_tol=0.01,
        )

        # capex should be four

        assert math.isclose(pyo.value(ipp.instance.var_capex), 4.5, abs_tol=1e-3)

        # sdncf_q[0] should be -5.7

        assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[0]), -5.7, abs_tol=1e-3)

        # the objective function should be -9.7

        assert math.isclose(pyo.value(ipp.instance.obj_f), -11.096, abs_tol=3e-3)
        
    # *************************************************************************
    # *************************************************************************
    
    def test_problem_two_scenarios_simpler(self):
        
        # number_intraperiod_time_intervals = 4
        nominal_discount_rate = 0.035
        assessment_weights = {0: 0.7, 1: 0.3}
        tf = EconomicTimeFrame(
            discount_rate=nominal_discount_rate,
            reporting_periods={0: (0, 1), 1: (0, 1, 2)}, 
            reporting_period_durations={0: (1, 1), 1: (1, 1, 1)}, # does not matter 
            time_intervals={0: (0, 1, 2), 1: (0, 1)}, 
            time_interval_durations={0: (1, 1, 1), 1: (1, 1)}, 
            )
        
        # 2 nodes: one import, one regular
        mynet = Network()
        node_IMP = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=node_IMP,
            prices={
                qpk: ResourcePrice(prices=1.0, volumes=None)
                for qpk in tf.qpk()
            },
        )

        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            base_flow={
                (0, 0): 0.50,
                (0, 1): 0.00,
                (0, 2): 1.00,
                (1, 0): 1.25,
                (1, 1): 0.30,
            },
        )

        # arc IA
        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[0.5, 0.5, 0.5],
            efficiency={(0, 0): 0.5, (0, 1): 0.5, (0, 2): 0.5, (1, 0): 0.5, (1, 1): 0.5},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            validate=False,
        )
        mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA)

        # identify node types
        mynet.identify_node_types()

        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            networks={"mynet": mynet},
            converters={},
            time_frame=tf,
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
            assessment_weights=assessment_weights,
            simplify_problem=True
        )
        
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 28 # 42
        assert ipp.results["Problem"][0]["Number of variables"] == 25 # 38
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 51 # 87

        # *********************************************************************
        # validation
        # capex should be 4.5
        assert math.isclose(pyo.value(ipp.instance.var_capex), 4.5, abs_tol=1e-3)
        # the objective function should be -11.096
        assert math.isclose(pyo.value(ipp.instance.obj_f), -11.096, abs_tol=3e-3)
        
    # *************************************************************************
    # *************************************************************************
    
    def test_problem_two_scenarios_two_discount_rates(self):
        
        # two discount rates
        assessment_weights = {0: 0.7, 1: 0.3}
        tf = EconomicTimeFrame(
            discount_rates_q={0: (0.035, 0.035), 1: (0.1, 0.1, 0.1)},
            reporting_periods={0: (0, 1), 1: (0, 1, 2)}, 
            reporting_period_durations={0: (1, 1), 1: (1, 1, 1)}, # does not matter 
            time_intervals={0: (0, 1, 2), 1: (0, 1)}, 
            time_interval_durations={0: (1, 1, 1), 1: (1, 1)}, 
            )

        # 2 nodes: one import, one regular
        mynet = Network()
        node_IMP = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=node_IMP,
            prices={
                qpk: ResourcePrice(prices=1.0, volumes=None)
                for qpk in tf.qpk()
            },
        )

        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            base_flow={
                (0, 0): 0.50,
                (0, 1): 0.00,
                (0, 2): 1.00,
                (1, 0): 1.25,
                (1, 1): 0.30,
            },
        )

        # arc IA
        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[0.5, 0.5, 0.5],
            efficiency={(0, 0): 0.5, (0, 1): 0.5, (0, 2): 0.5, (1, 0): 0.5, (1, 1): 0.5},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            validate=False,
        )
        mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA)

        # identify node types
        mynet.identify_node_types()

        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            networks={"mynet": mynet},
            converters={},
            time_frame=tf,
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
            assessment_weights=assessment_weights,
        )        
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 42
        assert ipp.results["Problem"][0]["Number of variables"] == 38
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 87

        # *********************************************************************

        # validation
        # the arc should be installed since it is the only feasible solution
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_IMP, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )
        # the flows should be 1.0, 0.0 and 2.0
        assert math.isclose(
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, 0, 0)]),
            1.0,
            abs_tol=1e-6,
        )
        assert math.isclose(
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, 0, 1)]),
            0.0,
            abs_tol=1e-6,
        )
        assert math.isclose(
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, 0, 2)]),
            2.0,
            abs_tol=1e-6,
        )
        assert math.isclose(
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, 1, 0)]),
            2.5,
            abs_tol=1e-6,
        )
        assert math.isclose(
            pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, 1, 1)]),
            0.6,
            abs_tol=1e-6,
        )
        # arc amplitude should be two
        assert math.isclose(
            pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_IMP, node_A, 0)]),
            2.5,
            abs_tol=0.01,
        )

        # capex should be 4.5
        assert math.isclose(pyo.value(ipp.instance.var_capex), 4.5, abs_tol=1e-3)
        # sdncf_q[0] should be -5.7
        assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[0]), -5.7, abs_tol=1e-3)
        # the objective function should be -10.80213032963115
        assert math.isclose(pyo.value(ipp.instance.obj_f), -10.80213032963115, abs_tol=3e-3)
        
    # *************************************************************************
    # *************************************************************************
    
    def test_problem_two_scenarios_two_discount_rates_simpler(self):
        
        # two discount rates
        assessment_weights = {0: 0.7, 1: 0.3}
        tf = EconomicTimeFrame(
            discount_rates_q={0: (0.035, 0.035), 1: (0.1, 0.1, 0.1)},
            reporting_periods={0: (0, 1), 1: (0, 1, 2)}, 
            reporting_period_durations={0: (1, 1), 1: (1, 1, 1)}, # does not matter 
            time_intervals={0: (0, 1, 2), 1: (0, 1)}, 
            time_interval_durations={0: (1, 1, 1), 1: (1, 1)}, 
            )

        # 2 nodes: one import, one regular
        mynet = Network()
        node_IMP = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=node_IMP,
            prices={
                qpk: ResourcePrice(prices=1.0, volumes=None)
                for qpk in tf.qpk()
            },
        )

        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            base_flow={
                (0, 0): 0.50,
                (0, 1): 0.00,
                (0, 2): 1.00,
                (1, 0): 1.25,
                (1, 1): 0.30,
            },
        )

        # arc IA
        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[0.5, 0.5, 0.5],
            efficiency={(0, 0): 0.5, (0, 1): 0.5, (0, 2): 0.5, (1, 0): 0.5, (1, 1): 0.5},
            efficiency_reverse=None,
            static_loss=None,
            capacity=[3],
            minimum_cost=[2],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            validate=False,
        )
        mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA)

        # identify node types
        mynet.identify_node_types()

        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            networks={"mynet": mynet},
            converters={},
            time_frame=tf,
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
            assessment_weights=assessment_weights,
            simplify_problem=True
        )
        
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 28 # 42
        assert ipp.results["Problem"][0]["Number of variables"] == 25 # 38
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 51 # 87

        # *********************************************************************

        # validation
        # arc amplitude should be two
        assert math.isclose(
            pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_IMP, node_A, 0)]),
            2.5,
            abs_tol=0.01,
        )
        # capex should be four
        assert math.isclose(pyo.value(ipp.instance.var_capex), 4.5, abs_tol=1e-3)
        # sdncf_q[0] should be -5.7
        # assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[0]), -5.7, abs_tol=1e-3)
        # the objective function should be -10.80213032963115 (or -10.8027723516153)
        assert math.isclose(pyo.value(ipp.instance.obj_f), -10.80213032963115, abs_tol=3e-3)
        
    # *************************************************************************
    # *************************************************************************
    
    # problem with two symmetrical nodes and one undirected arc
    # problem with symmetrical nodes and one undirected arc with diff. tech.
    # problem with symmetrical nodes and one undirected arc, irregular steps
    # same problem as the previous one, except with interface variables
    # problem with two symmetrical nodes and one undirected arc, w/ simple sos1
        
    def test_isolated_undirected_network(self):
        
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,)},
            reporting_period_durations={q: (365 * 24 * 3600,)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
        mynet = Network()
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5],
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )
        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1, 1, -0.5, 0.5],
            base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
        )
    
        # add arcs
        # undirected arc
        arc_tech_AB = ArcsWithoutStaticLosses(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            efficiency_reverse=None,
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_key_AB_und = mynet.add_undirected_arc(
            node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
        )
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=True,  # just to reach a line,
            mandatory_arcs=[],
            max_number_parallel_arcs={}
        )
        
        assert ipp.has_peak_total_assessments() # TODO: make sure this is true
        assert ipp.results["Problem"][0]["Number of constraints"] == 34
        assert ipp.results["Problem"][0]["Number of variables"] == 28
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 105
    
        # *********************************************************************
        # *********************************************************************
    
        # validation
    
        # the arc should be installed since it is the only feasible solution
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
            .options_selected
        )
    
        # there should be no opex (imports or exports), only capex from arcs
        assert pyo.value(ipp.instance.var_sdncf_q[q]) == 0
        assert pyo.value(ipp.instance.var_capex) > 0
        assert (
            pyo.value(
                ipp.instance.var_capex_arc_gllj[("mynet", node_A, node_B, arc_key_AB_und)]
            )
            > 0
        )
        
    # *************************************************************************
    # *************************************************************************
    
    def test_isolated_undirected_network_diff_tech(self):
        
        # time frame
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,)},
            reporting_period_durations={q: (365 * 24 * 3600,)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
        mynet = Network()
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5]
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )
    
        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1.25, 1, -0.625, 0.5]
            base_flow={(0, 0): -1.25, (0, 1): 1.0, (0, 2): -0.625, (0, 3): 0.5},
        )
    
        # add arcs
        # undirected arc
        arc_tech_AB = ArcsWithoutStaticLosses(
            name="any",
            # efficiency=[0.8, 1.0, 0.8, 1.0],
            efficiency={(0, 0): 0.8, (0, 1): 1.0, (0, 2): 0.8, (0, 3): 1.0},
            efficiency_reverse=None,
            capacity=[1.25, 2.5],
            minimum_cost=[10, 15],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_key_AB_und = mynet.add_undirected_arc(
            node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
        )
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
            mandatory_arcs=[],
            max_number_parallel_arcs={}
        )
        
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 34
        assert ipp.results["Problem"][0]["Number of variables"] == 24
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 77
    
        # *********************************************************************
        # *********************************************************************
    
        # validation
    
        # the arc should be installed since it is the only feasible solution
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
            .options_selected
        )
    
        # there should be no opex (imports or exports), only capex from arcs
        assert pyo.value(ipp.instance.var_sdncf_q[q]) == 0
        assert pyo.value(ipp.instance.var_capex) > 0
        assert (
            pyo.value(
                ipp.instance.var_capex_arc_gllj[("mynet", node_A, node_B, arc_key_AB_und)]
            )
            > 0
        )
    
        # *********************************************************************
        # *********************************************************************
        
    # *************************************************************************
    # *************************************************************************
    
    # preexisting, reference
    # capacity is instantaneous
    # use dedicated method for preexisting arcs
    # capacity is instantaneous, using dedicated method
    # use different technologies for the undirected arc
    # use different technologies for the undirected arc, capacity is instant.
    # use different technologies for the undirected arc, using specific method
    # same as before but assuming the capacity is instantaneous

    def test_isolated_preexisting_undirected_network(self):
        capacity_is_instantaneous = False
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,)},
            reporting_period_durations={q: (365 * 24 * 3600,)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
        mynet = Network()
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5]
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )
        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1, 1, -0.5, 0.5],
            base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
        )
    
        # add arcs
        # isotropic
        mynet.add_preexisting_undirected_arc(
            node_key_a=node_A,
            node_key_b=node_B,
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            efficiency_reverse=None,
            static_loss=None,
            capacity=1.0,
            capacity_is_instantaneous=capacity_is_instantaneous,
        # identify node types
        mynet.identify_node_types()
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
            mandatory_arcs=[],
            max_number_parallel_arcs={}
        )
        
        # validation
        # there should be no opex (imports or exports) and no capex
        assert pyo.value(ipp.instance.var_sdncf_q[q]) == 0
        assert pyo.value(ipp.instance.var_capex) == 0
                        
    # *************************************************************************
    # *************************************************************************

    def test_isolated_preexisting_undirected_network_diff_tech(self):
        
        capacity_is_instantaneous = False
        
        # assessment
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,)},
            reporting_period_durations={q: (365 * 24 * 3600,)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
        mynet = Network()
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5]
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )

        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1, 1, -0.5, 0.5],
            base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
        )
    
        # add arcs
        # anisotropic
        mynet.add_preexisting_undirected_arc(
            node_key_a=node_A,
            node_key_b=node_B,
            # efficiency=[0.9, 1, 0.9, 1],
            efficiency={(0, 0): 0.9, (0, 1): 1, (0, 2): 0.9, (0, 3): 1},
            capacity=1.0,
            capacity_is_instantaneous=capacity_is_instantaneous,
            # efficiency_reverse=[1, 0.9, 1, 0.9],
            efficiency_reverse={(0, 0): 1, (0, 1): 0.9, (0, 2): 1, (0, 3): 0.9},
            static_loss=None,
        )
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
            mandatory_arcs=[],
            max_number_parallel_arcs={}
        )
        
        # validation
        # there should be no opex (imports or exports) and no capex
        assert pyo.value(ipp.instance.var_sdncf_q[q]) == 0
        assert pyo.value(ipp.instance.var_capex) == 0
    
    # *************************************************************************
    # *************************************************************************
        
    def test_nonisolated_undirected_network(self):
        
        # scenario
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0, 1)},
            reporting_period_durations={q: (365 * 24 * 3600, 365 * 24 * 3600)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
    
        mynet = Network()
    
        # import node    
        mynet.add_import_node(
            node_key=imp_node_key,
            prices={
                qpk: ResourcePrice(prices=1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # export node    
        mynet.add_export_node(
            node_key=exp_node_key,
            prices={
                qpk: ResourcePrice(prices=0.1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5]
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )
        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1, 1, -0.5, 0.5]
            base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
        )
    
        # add arcs
    
        # import arc
        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
        )
        mynet.add_directed_arc(
            node_key_a=imp_node_key, node_key_b=node_A, arcs=arc_tech_IA
        )
    
        # export arc
    
        arc_tech_BE = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
        )
        mynet.add_directed_arc(
            node_key_a=node_B, node_key_b=exp_node_key, arcs=arc_tech_BE
        )
    
        # undirected arc
        arc_tech_AB = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10.0, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
        )
        arc_key_AB_und = mynet.add_undirected_arc(
            node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
        )
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
            mandatory_arcs=[],
            max_number_parallel_arcs={}
        )
        
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 80
        assert ipp.results["Problem"][0]["Number of variables"] == 84
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 253
    
        # **************************************************************************
    
        # validation
        # network is still isolated
        # the import arc was not installed
    
        assert (
            True
            not in ipp.networks["mynet"]
            .edges[(imp_node_key, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )
    
        # the export arc was not installed
    
        assert (
            True
            not in ipp.networks["mynet"]
            .edges[(node_B, exp_node_key, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )
    
        # the undirected arc was installed
    
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
            .options_selected
        )
    
        # the opex should be zero
    
        assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[q]), 0, abs_tol=1e-6)
    
        # the capex should be positive
    
        assert pyo.value(ipp.instance.var_capex) > 0
    
    
        # *********************************************************************
        # *********************************************************************
        
    # *************************************************************************
    # *************************************************************************
            
    def test_nonisolated_undirected_network_diff_tech(self):
        
        # scenario
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,1)},
            reporting_period_durations={q: (365 * 24 * 3600,365 * 24 * 3600)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
        mynet = Network()
    
        # import node
        mynet.add_import_node(
            node_key=imp_node_key,
            prices={
                qpk: ResourcePrice(prices=1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # export node
        mynet.add_export_node(
            node_key=exp_node_key,
            prices={
                qpk: ResourcePrice(prices=0.1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5]
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )
    
        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1, 1, -0.5, 0.5]
            base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
        )
    
        # add arcs
        # import arc
        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
        )
        mynet.add_directed_arc(
            node_key_a=imp_node_key, node_key_b=node_A, arcs=arc_tech_IA
        )
    
        # export arc
        arc_tech_BE = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
        )
        mynet.add_directed_arc(
            node_key_a=node_B, node_key_b=exp_node_key, arcs=arc_tech_BE
        )
        
        # undirected arc
        arc_tech_AB = Arcs(
            name="any",
            # efficiency=[0.95, 0.95, 0.95, 0.95],
            efficiency={(0, 0): 0.95, (0, 1): 0.95, (0, 2): 0.95, (0, 3): 0.95},
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
            # efficiency_reverse=[0.85, 0.85, 0.85, 0.85],
            efficiency_reverse={(0, 0): 0.85, (0, 1): 0.85, (0, 2): 0.85, (0, 3): 0.85},
            static_loss=None,
            validate=False,
        )
        arc_key_AB_und = mynet.add_undirected_arc(
            node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
        )
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
            mandatory_arcs=[],
            max_number_parallel_arcs={}
        )
        
        assert ipp.has_peak_total_assessments()
        assert ipp.results["Problem"][0]["Number of constraints"] == 80
        assert ipp.results["Problem"][0]["Number of variables"] == 84
        assert ipp.results["Problem"][0]["Number of nonzeros"] == 253
    
        # *********************************************************************
        # *********************************************************************
    
        # validation
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
            .options_selected
        )

        # the directed arc from the import should also be installed since node
        # B cannot fullfil all the demand since it has an efficiency of 0.85<1
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(imp_node_key, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )

        # there should be no opex (imports or exports), only capex from arcs
        assert pyo.value(ipp.instance.var_sdncf_q[q]) < 0
        assert pyo.value(ipp.instance.var_capex) > 0
        assert (
            pyo.value(
                ipp.instance.var_capex_arc_gllj[
                    ("mynet", node_A, node_B, arc_key_AB_und)
                ]
            )
            > 0
        )
        assert (
            pyo.value(
                ipp.instance.var_capex_arc_gllj[("mynet", imp_node_key, node_A, 0)]
            )
            > 0
        )
    
        # *********************************************************************
        # *********************************************************************
    # *************************************************************************
    # *************************************************************************
    
    def test_nonisolated_network_preexisting_directed_arcs(self):
        
        # time frame
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,1)},
            reporting_period_durations={q: (365 * 24 * 3600,365 * 24 * 3600)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
        mynet = Network()
    
        # import node
        mynet.add_import_node(
            node_key=imp_node_key,
            prices={
                qpk: ResourcePrice(prices=1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # export node
        mynet.add_export_node(
            node_key=exp_node_key,
            prices={
                qpk: ResourcePrice(prices=0.1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5],
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )
        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1, 1, -0.5, 0.5],
            base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
        )
    
        # add arcs
        # import arc
        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_tech_IA.options_selected[0] = True
        mynet.add_directed_arc(node_key_a=imp_node_key, node_key_b=node_A, arcs=arc_tech_IA)
    
        # export arc
        arc_tech_BE = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_tech_BE.options_selected[0] = True
        mynet.add_directed_arc(node_key_a=node_B, node_key_b=exp_node_key, arcs=arc_tech_BE)
    
        # undirected arc
        # isotropic arc
        arc_tech_AB = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10.0, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_key_AB_und = mynet.add_undirected_arc(
            node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
        )
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            solver_options={},
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
        )
    
        # *********************************************************************
    
        # validation
        # network is still isolated
        # the undirected arc was installed
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
            .options_selected
        )

        # the opex should be zero
        assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[q]), 0, abs_tol=1e-3)

        # the capex should be positive
        assert pyo.value(ipp.instance.var_capex) > 0
    
        # *********************************************************************
        # *********************************************************************
        
    # *************************************************************************
    # *************************************************************************
    
    def test_nonisolated_network_preexisting_directed_arcs_diff_tech(self):
        
        # time frame
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,1)},
            reporting_period_durations={q: (365 * 24 * 3600,365 * 24 * 3600)},
            time_intervals={q: (0,1,2,3)},
            time_interval_durations={q: (1,1,1,1)},
        )
    
        # 4 nodes: one import, one export, two supply/demand nodes
        mynet = Network()
    
        # import node
        mynet.add_import_node(
            node_key=imp_node_key,
            prices={
                qpk: ResourcePrice(prices=1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # export node
        mynet.add_export_node(
            node_key=exp_node_key,
            prices={
                qpk: ResourcePrice(prices=0.1+i*0.05, volumes=None)
                for i, qpk in enumerate(tf.qpk())
            },
        )
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_A,
            # base_flow=[1, -1, 0.5, -0.5],
            base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
        )
        node_B = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(
            node_key=node_B,
            # base_flow=[-1, 1, -0.5, 0.5],
            base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
        )
    
        # add arcs
        # import arc
        arc_tech_IA = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_tech_IA.options_selected[0] = True
        mynet.add_directed_arc(node_key_a=imp_node_key, node_key_b=node_A, arcs=arc_tech_IA)
    
        # export arc
        arc_tech_BE = Arcs(
            name="any",
            # efficiency=[1, 1, 1, 1],
            efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
            efficiency_reverse=None,
            static_loss=None,
            validate=False,
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_tech_BE.options_selected[0] = True
        mynet.add_directed_arc(node_key_a=node_B, node_key_b=exp_node_key, arcs=arc_tech_BE)
    
        # undirected arc
        # anisotropic arc
        arc_tech_AB = Arcs(
            name="any",
            # efficiency=[0.95, 0.95, 0.95, 0.95],
            efficiency={(0, 0): 0.95, (0, 1): 0.95, (0, 2): 0.95, (0, 3): 0.95},
            capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
            minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
            # efficiency_reverse=[0.85, 0.85, 0.85, 0.85],
            efficiency_reverse={(0, 0): 0.85, (0, 1): 0.85, (0, 2): 0.85, (0, 3): 0.85},
            static_loss=None,
            validate=False,
            specific_capacity_cost=1,
            capacity_is_instantaneous=False,
        )
        arc_key_AB_und = mynet.add_undirected_arc(
            node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
        )
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        ipp = self.build_solve_ipp(
            perform_analysis=False,
            plot_results=False,  # True,
            print_solver_output=False,
            time_frame=tf,
            networks={"mynet": mynet},
            static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
            mandatory_arcs=[],
            max_number_parallel_arcs={},
        )
    
        # **************************************************************************
    
        # validation
        # the undirected arc should be installed since it is cheaper tham imp.
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
            .options_selected
        )
    
        # the directed arc from the import should also be installed since node
        # B cannot fullfil all the demand since it has an efficiency of 0.85<1
    
        assert (
            True
            in ipp.networks["mynet"]
            .edges[(imp_node_key, node_A, 0)][Network.KEY_ARC_TECH]
            .options_selected
        )
    
        # there should be no opex (imports or exports), only capex from arcs
        assert pyo.value(ipp.instance.var_sdncf_q[q]) < 0
        assert pyo.value(ipp.instance.var_capex) > 0
        assert (
            pyo.value(
                ipp.instance.var_capex_arc_gllj[
                    ("mynet", node_A, node_B, arc_key_AB_und)
                ]
            )
            > 0
        )
    
        # *********************************************************************
        # *********************************************************************
        
    # *************************************************************************
    # *************************************************************************
    
    def test_arc_groups_individual(self):
        
        # time frame
        q = 0
        tf = EconomicTimeFrame(
            discount_rate=3.5/100,
            reporting_periods={q: (0,1)},
            reporting_period_durations={q: (365 * 24 * 3600,365 * 24 * 3600)},
            time_intervals={q: (0,1)},
            time_interval_durations={q: (1,1)},
        )
    
        # 4 nodes: two import nodes, two supply/demand nodes
        mynet = Network()
    
        # **************************************************************************
    
        # import nodes
        imp1_node_key = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=imp1_node_key,
            prices={
                qpk: ResourcePrice(prices=qpk[2] + 1, volumes=None)
                for qpk in tf.qpk()
            },
        )
        imp2_node_key = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=imp2_node_key,
            prices={
                qpk: ResourcePrice(prices=3 - qpk[2], volumes=None)
                for qpk in tf.qpk()
            },
        )
        imp3_node_key = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_import_node(
            node_key=imp3_node_key,
            prices={
                qpk: ResourcePrice(prices=1 + 2 * qpk[2], volumes=None)
                for qpk in tf.qpk()
            },
        )
    
        # **************************************************************************
    
        # other nodes
        node_A = generate_pseudo_unique_key(mynet.nodes())
        mynet.add_source_sink_node(node_key=node_A, base_flow={(q, 0): 1.0, (q, 1): 0.5})
    
        # add arcs
        efficiency_IA1 = {
            (q, 0): 1.00,
            (q, 1): 0.85,
        }
        efficiency_IA2 = {
            (q, 0): 0.95,
            (q, 1): 0.90,
        }
        efficiency_IA3 = {
            (q, 0): 0.90,
            (q, 1): 0.95,
        }
    
        # I1A
        static_loss_IA1 = {
            (0, q, 0): 0.10,
            (0, q, 1): 0.10,
            (1, q, 0): 0.15,
            (1, q, 1): 0.15,
            (2, q, 0): 0.20,
            (2, q, 1): 0.20,
        }
    
        # I2A
        static_loss_IA2 = {
            (0, q, 0): 0.20,
            (0, q, 1): 0.20,
            (1, q, 0): 0.05,
            (1, q, 1): 0.05,
            (2, q, 0): 0.10,
            (2, q, 1): 0.10,
        }
    
        # I3A
        static_loss_IA3 = {
            (0, q, 0): 0.15,
            (0, q, 1): 0.15,
            (1, q, 0): 0.10,
            (1, q, 1): 0.10,
            (2, q, 0): 0.05,
            (2, q, 1): 0.05,
        }
        arcs_I1A = Arcs(
            name="IA1",
            efficiency=efficiency_IA1,
            efficiency_reverse=None,
            static_loss=static_loss_IA1,
            capacity=(
                0.5,
                0.75,
                1.2,
            ),
            minimum_cost=(
                0.2,
                0.5,
                0.75,
            ),
            specific_capacity_cost=0,
            capacity_is_instantaneous=False,
            validate=True,
        )
        arc_key_I1A = mynet.add_directed_arc(
            node_key_a=imp1_node_key, node_key_b=node_A, arcs=arcs_I1A
        )
    
        arcs_I2A = Arcs(
            name="IA2",
            efficiency=efficiency_IA2,
            efficiency_reverse=None,
            static_loss=static_loss_IA2,
            capacity=(
                0.5,
                0.75,
                1.2,
            ),
            minimum_cost=(
                0.2,
                0.5,
                0.75,
            ),
            specific_capacity_cost=0,
            capacity_is_instantaneous=False,
            validate=True,
        )
    
        arc_key_I2A = mynet.add_directed_arc(
            node_key_a=imp2_node_key, node_key_b=node_A, arcs=arcs_I2A
        )
    
        arcs_I3A = Arcs(
            name="IA3",
            efficiency=efficiency_IA3,
            efficiency_reverse=None,
            static_loss=static_loss_IA3,
            capacity=(
                0.5,
                0.75,
                1.2,
            ),
            minimum_cost=(
                0.2,
                0.5,
                0.75,
            ),
            specific_capacity_cost=0,
            capacity_is_instantaneous=False,
            validate=True,
        )
    
        arc_key_I3A = mynet.add_directed_arc(
            node_key_a=imp3_node_key, node_key_b=node_A, arcs=arcs_I3A
        )
    
        arc_groups_dict = {
            0: (
                ("mynet", imp1_node_key, node_A, arc_key_I1A),
                ("mynet", imp2_node_key, node_A, arc_key_I2A),
                ("mynet", imp3_node_key, node_A, arc_key_I3A),
            )
        }
    
        # identify node types
        mynet.identify_node_types()
    
        # no sos, regular time intervals
        solver_options = {}
        solver_options["relative_mip_gap"] = 0
        solver_options["absolute_mip_gap"] = 1e-4
    
        ipp = self.build_solve_ipp(
            solver_options=solver_options,
            use_sos_arcs=False,
Loading
Loading full blame…