Skip to content
Snippets Groups Projects
test_esipp_problem.py 269 KiB
Newer Older
  • Learn to ignore specific revisions
  •         # 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,
                arc_sos_weight_key=None,
                arc_use_real_variables_if_possible=False,
                use_sos_sense=False,
                sense_sos_weight_key=None,
                sense_use_real_variables_if_possible=False,
                sense_use_arc_interfaces=False,
                perform_analysis=False,
                plot_results=False,  # True,
                print_solver_output=False,
                static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_ARR,
                time_frame=tf,
                networks={"mynet": mynet},
                mandatory_arcs=[],
                max_number_parallel_arcs={},
                arc_groups_dict=arc_groups_dict
            )
        
            # *********************************************************************
        
            # overview
    
            
            (imports_qpk, 
             exports_qpk, 
             balance_qpk, 
             import_costs_qpk, 
             export_revenue_qpk, 
             ncf_qpk, 
             aggregate_static_demand_qpk,
             aggregate_static_supply_qpk,
             aggregate_static_balance_qpk) = statistics(ipp)
    
            
            q = 0
            capex_ind = 0.75
            capex_group = 1.5
            imp_ind = 1.9882352941176473
            imp_group = 2.2  # {'mynet': 2.2245012886626414}
            sdncf_group = -6.184560251632995  # -6.2386008960367345
            sdncf_ind = -5.274445281858455
            sdext_group = 0
            sdext_ind = 0
            # losses_ind = 0
            # losses_group = 0
            obj_ind = sdncf_ind + sdext_ind - capex_ind
            obj_group = sdncf_group + sdext_group - capex_group
        
            assert capex_ind < capex_group
            assert imp_ind < imp_group
            assert obj_ind > obj_group
        
            # all arcs have to be installed
    
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp1_node_key, node_A, arc_key_I1A)][Network.KEY_ARC_TECH]
                .options_selected
            )
    
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp2_node_key, node_A, arc_key_I2A)][Network.KEY_ARC_TECH]
                .options_selected
            )
    
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp3_node_key, node_A, arc_key_I3A)][Network.KEY_ARC_TECH]
                .options_selected
            )
    
            # the same option has to be selected in all arcs
    
            h1 = (
                ipp.networks["mynet"]
                .edges[(imp1_node_key, node_A, arc_key_I1A)][Network.KEY_ARC_TECH]
                .options_selected.index(True)
            )
            h2 = (
                ipp.networks["mynet"]
                .edges[(imp2_node_key, node_A, arc_key_I2A)][Network.KEY_ARC_TECH]
                .options_selected.index(True)
            )
            h3 = (
                ipp.networks["mynet"]
                .edges[(imp3_node_key, node_A, arc_key_I3A)][Network.KEY_ARC_TECH]
                .options_selected.index(True)
            )
            assert h1 == h2
            assert h1 == h3
    
            # the capex have to be higher than those of the best individual arc
            abs_tol = 1e-3
            assert math.isclose(
                pyo.value(ipp.instance.var_capex), capex_group, abs_tol=abs_tol
            )
    
            # there should be no exports
    
            abs_tol = 1e-3
            exports_qp = sum(exports_qpk[(q, 0, k)] for k in tf.time_intervals[q])
            assert math.isclose(exports_qp, 0, abs_tol=abs_tol)
    
    
            # the imports should be higher than with individual arcs
            abs_tol = 1e-3
    
            imports_qp = sum(imports_qpk[qpk] for qpk in tf.qpk() if qpk[1] == 0)
            assert math.isclose(imports_qp, imp_group, abs_tol=abs_tol)
    
    
            # the operating results should be lower than with an individual arc
    
            abs_tol = 1e-3
            assert math.isclose(
                pyo.value(ipp.instance.var_sdncf_q[q]), sdncf_group, abs_tol=abs_tol
            )
    
            # the externalities should be zero
    
            abs_tol = 1e-3
            assert math.isclose(pyo.value(ipp.instance.var_sdext_q[q]), 0, abs_tol=abs_tol)
    
            # the objective function should be -6.3639758220728595-1.5
    
            abs_tol = 1e-3
            assert math.isclose(pyo.value(ipp.instance.obj_f), obj_group, abs_tol=abs_tol)
    
            # the imports should be greater than or equal to the losses for all arx
    
            losses_model = sum(
                pyo.value(
                    ipp.instance.var_w_glljqk[
                        ("mynet", imp1_node_key, node_A, arc_key_I1A, q, k)
                    ]
                )
                + pyo.value(
                    ipp.instance.var_w_glljqk[
                        ("mynet", imp2_node_key, node_A, arc_key_I2A, q, k)
                    ]
                )
                + pyo.value(
                    ipp.instance.var_w_glljqk[
                        ("mynet", imp3_node_key, node_A, arc_key_I3A, q, k)
                    ]
                )
                for k in range(tf.number_time_intervals(q))
            )
    
            losses_data = sum(
                static_loss_IA1[(h1, q, k)]
                + static_loss_IA2[(h2, q, k)]
                + static_loss_IA3[(h3, q, k)]
                for k in range(tf.number_time_intervals(q))
            )
    
            assert math.isclose(losses_model, losses_data, abs_tol=abs_tol)
    
    
            assert imports_qp >= losses_model
    
            
        # *************************************************************************
        # *************************************************************************
        
        def test_arc_groups_individual_ref(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
            )
            
            # do not use arc groups
            arc_groups_dict = {}
        
            # 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,
                arc_sos_weight_key=None,
                arc_use_real_variables_if_possible=False,
                use_sos_sense=False,
                sense_sos_weight_key=None,
                sense_use_real_variables_if_possible=False,
                sense_use_arc_interfaces=False,
                perform_analysis=False,
                plot_results=False,  # True,
                print_solver_output=False,
                time_frame=tf,
                static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_ARR,
                networks={"mynet": mynet},
                mandatory_arcs=[],
                max_number_parallel_arcs={},
                arc_groups_dict=arc_groups_dict
            )
        
            # **************************************************************************
        
            # overview
    
            (imports_qpk, 
             exports_qpk, 
             balance_qpk, 
             import_costs_qpk, 
             export_revenue_qpk, 
             ncf_qpk, 
             aggregate_static_demand_qpk,
             aggregate_static_supply_qpk,
             aggregate_static_balance_qpk) = statistics(ipp)
    
            
            q = 0
            capex_ind = 0.75
            capex_group = 1.5
            imp_ind = 1.9882352941176473
            imp_group = 2.2  # {'mynet': 2.2245012886626414}
            sdncf_group = -6.184560251632995  # -6.2386008960367345
            sdncf_ind = -5.274445281858455
            sdext_group = 0
            sdext_ind = 0
            # losses_ind = 0
            # losses_group = 0
            obj_ind = sdncf_ind + sdext_ind - capex_ind
            obj_group = sdncf_group + sdext_group - capex_group
        
            assert capex_ind < capex_group
            assert imp_ind < imp_group
            assert obj_ind > obj_group
        
            # at least one arc has to be installed
    
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp1_node_key, node_A, arc_key_I1A)][Network.KEY_ARC_TECH]
                .options_selected
                or True
                in ipp.networks["mynet"]
                .edges[(imp2_node_key, node_A, arc_key_I2A)][Network.KEY_ARC_TECH]
                .options_selected
                or True
                in ipp.networks["mynet"]
                .edges[(imp3_node_key, node_A, arc_key_I3A)][Network.KEY_ARC_TECH]
                .options_selected
            )
    
            # the capex have to be lower than with a group of arcs
    
            abs_tol = 1e-3
    
            assert math.isclose(
                pyo.value(ipp.instance.var_capex), capex_ind, abs_tol=abs_tol
            )
    
            # there should be no exports
    
            abs_tol = 1e-3
            exports_qp = sum(exports_qpk[(q, 0, k)] for k in tf.time_intervals[q])
            assert math.isclose(exports_qp, 0, abs_tol=abs_tol)
    
    
            # the imports should be lower than with a group of arcs
            abs_tol = 1e-3
    
            imports_qp = sum(imports_qpk[qpk] for qpk in tf.qpk() if qpk[1] == 0)
            assert math.isclose(imports_qp, imp_ind, abs_tol=abs_tol)
    
    
            # the operating results should be lower than with an individual arc
            abs_tol = 1e-3
            assert math.isclose(
                pyo.value(ipp.instance.var_sdncf_q[q]), sdncf_ind, abs_tol=abs_tol
            )
    
            # the externalities should be zero
            abs_tol = 1e-3
            assert math.isclose(
                pyo.value(ipp.instance.var_sdext_q[q]), sdext_ind, abs_tol=abs_tol
            )
    
            # the objective function should be -6.3639758220728595-1.5
            abs_tol = 1e-3
            assert math.isclose(pyo.value(ipp.instance.obj_f), obj_ind, abs_tol=abs_tol)
    
            # the imports should be greater than or equal to the losses for all arx
            losses_model = sum(
                pyo.value(
                    ipp.instance.var_w_glljqk[
                        ("mynet", imp1_node_key, node_A, arc_key_I1A, q, k)
                    ]
                )
                + pyo.value(
                    ipp.instance.var_w_glljqk[
                        ("mynet", imp2_node_key, node_A, arc_key_I2A, q, k)
                    ]
                )
                + pyo.value(
                    ipp.instance.var_w_glljqk[
                        ("mynet", imp3_node_key, node_A, arc_key_I3A, q, k)
                    ]
                )
                for k in range(tf.number_time_intervals(q))
            )
    
    
            assert imports_qp >= losses_model
    
            
        # *************************************************************************
        # *************************************************************************
        
        def test_arc_groups_individual_undirected(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: one import node, four regular nodes
            mynet = Network()
    
            # import nodes
    
            mynet.add_import_node(
                node_key=imp_node_key,
                prices={
                    qpk: ResourcePrice(prices=1 + qpk[2], volumes=None)
                    for qpk in tf.qpk()
                },
            )
    
            # A
            node_A = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(node_key=node_A, base_flow={(q, 0): 0.0, (q, 1): 1.0})
    
            # B
            node_B = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(node_key=node_B, base_flow={(q, 0): 1.0, (q, 1): -0.5})
    
            # C
            node_C = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(node_key=node_C, base_flow={(q, 0): 0.0, (q, 1): 0.5})
    
            # D
            node_D = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(node_key=node_D, base_flow={(q, 0): 0.5, (q, 1): -0.25})
    
            # **************************************************************************
    
            # add arcs
    
            # IA
            mynet.add_preexisting_directed_arc(
                node_key_a=imp_node_key,
                node_key_b=node_A,
                efficiency=None,
                static_loss=None,
                capacity=1.5,
                capacity_is_instantaneous=False,
            )
    
            # IC
            mynet.add_preexisting_directed_arc(
                node_key_a=imp_node_key,
                node_key_b=node_C,
                efficiency=None,
                static_loss=None,
                capacity=1.5,
                capacity_is_instantaneous=False,
            )
    
            # AB
            efficiency_AB = {
                (q, 0): 1.00,
                (q, 1): 0.85,
            }
            efficiency_BA = {
                (q, 0): 0.95,
                (q, 1): 0.80,
            }
            static_loss_AB = {
                (0, q, 0): 0.20,
                (0, q, 1): 0.25,
                (1, q, 0): 0.25,
                (1, q, 1): 0.30,
                (2, q, 0): 0.30,
                (2, q, 1): 0.35,
            }
    
            arcs_AB = Arcs(
                name="AB",
                efficiency=efficiency_AB,
                efficiency_reverse=efficiency_BA,
                static_loss=static_loss_AB,
                capacity=(0.85, 1.5, 2.5),
                minimum_cost=(1, 2, 3),
                specific_capacity_cost=0,
                capacity_is_instantaneous=False,
                validate=True,
            )
    
            arc_key_AB = mynet.add_undirected_arc(
                node_key_a=node_A, node_key_b=node_B, arcs=arcs_AB
            )
    
            # CD
            efficiency_CD = {
                (q, 0): 1.00,
                (q, 1): 0.85,
            }
            efficiency_DC = {(q, 0): 0.95, (q, 1): 0.80}
            static_loss_CD = {
                (0, q, 0): 0.010,
                (0, q, 1): 0.015,
                (1, q, 0): 0.015,
                (1, q, 1): 0.020,
                (2, q, 0): 0.020,
                (2, q, 1): 0.025,
            }
            arcs_CD = Arcs(
                name="CD",
                efficiency=efficiency_CD,
                efficiency_reverse=efficiency_DC,
                static_loss=static_loss_CD,
                capacity=(0.85, 1.5, 2.5),
                minimum_cost=(1, 2, 3),
                specific_capacity_cost=0,
                capacity_is_instantaneous=False,
                validate=True,
            )
            arc_key_CD = mynet.add_undirected_arc(
                node_key_a=node_C, node_key_b=node_D, arcs=arcs_CD
            )
    
            # arc groups
            arc_groups_dict = {
                0: (
                    ("mynet", node_A, node_B, arc_key_AB),
                    ("mynet", node_C, node_D, arc_key_CD),
                )
            }
    
            # identify node types
            mynet.identify_node_types()
    
            # solver settings
            solver_options = {}
            solver_options["relative_mip_gap"] = 0
            solver_options["absolute_mip_gap"] = 1e-4
    
            # no sos, regular time intervals
            
            for static_losses_mode in InfrastructurePlanningProblem.STATIC_LOSS_MODES:
                # reset decisions if necessary
                if True in mynet.edges[(node_A, node_B, arc_key_AB)][Network.KEY_ARC_TECH].options_selected:
                    mynet.edges[(node_A, node_B, arc_key_AB)][
                        Network.KEY_ARC_TECH].options_selected[
                            mynet.edges[(node_A, node_B, arc_key_AB)][
                                Network.KEY_ARC_TECH].options_selected.index(True)
                            ] = False
                if True in mynet.edges[(node_C, node_D, arc_key_CD)][Network.KEY_ARC_TECH].options_selected:
                    mynet.edges[(node_C, node_D, arc_key_CD)][
                        Network.KEY_ARC_TECH].options_selected[
                            mynet.edges[(node_C, node_D, arc_key_CD)][
                                Network.KEY_ARC_TECH].options_selected.index(True)
                            ] = False
                            
                ipp = self.build_solve_ipp(
                    solver_options=solver_options,
                    use_sos_arcs=False,
                    arc_sos_weight_key=None,
                    arc_use_real_variables_if_possible=False,
                    use_sos_sense=False,
                    sense_sos_weight_key=None,
                    sense_use_real_variables_if_possible=False,
                    sense_use_arc_interfaces=False,
                    perform_analysis=False,
                    plot_results=False,  # True,
                    print_solver_output=False,
                    networks={"mynet": mynet},
                    time_frame=tf,
                    static_losses_mode=static_losses_mode,
                    mandatory_arcs=[],
                    max_number_parallel_arcs={},
                    arc_groups_dict=arc_groups_dict
                )
                
                # overview
    
                (imports_qpk, 
                 exports_qpk, 
                 balance_qpk, 
                 import_costs_qpk, 
                 export_revenue_qpk, 
                 ncf_qpk, 
                 aggregate_static_demand_qpk,
                 aggregate_static_supply_qpk,
                 aggregate_static_balance_qpk) = statistics(ipp)
    
        
                capex_ind = 3
                capex_group = 4
        
                imp_ind = 2.912
                imp_group = 2.9210000000000003
                
                sdncf_group = -7.745053560176434
        
                sdnext_group = 0
                
                obj_group = sdnext_group + sdncf_group - capex_group
        
                losses_ind = sum(
                    static_loss_AB[(1, q, k)] + static_loss_CD[(0, q, k)]
                    for k in range(tf.number_time_intervals(q))
                )
                losses_group = sum(
                    static_loss_AB[(1, q, k)] + static_loss_CD[(1, q, k)]
                    for k in range(tf.number_time_intervals(q))
                )
        
                losses_model = sum(
                    pyo.value(
                        ipp.instance.var_w_glljqk[("mynet", node_A, node_B, arc_key_AB, q, k)]
                    )
                    + pyo.value(
                        ipp.instance.var_w_glljqk[("mynet", node_C, node_D, arc_key_CD, q, k)]
                    )
                    for k in range(tf.number_time_intervals(q))
                )
        
                assert capex_group > capex_ind
                # # assert math.isclose(losses_group, losses_ind, abs_tol=1e-3)
                assert losses_group > losses_ind
                assert imp_group > imp_ind
        
                # all arcs have to be installed
        
                assert (
                    True
                    in ipp.networks["mynet"]
                    .edges[(node_A, node_B, arc_key_AB)][Network.KEY_ARC_TECH]
                    .options_selected
                )
        
                assert (
                    True
                    in ipp.networks["mynet"]
                    .edges[(node_C, node_D, arc_key_CD)][Network.KEY_ARC_TECH]
                    .options_selected
                )
        
                # the same option has to be selected in all arcs
        
                h1 = (
                    ipp.networks["mynet"]
                    .edges[(node_A, node_B, arc_key_AB)][Network.KEY_ARC_TECH]
                    .options_selected.index(True)
                )
        
                h2 = (
                    ipp.networks["mynet"]
                    .edges[(node_C, node_D, arc_key_CD)][Network.KEY_ARC_TECH]
                    .options_selected.index(True)
                )
        
                assert h1 == h2
        
                # the capex have to be higher than those of the best individual arc
                abs_tol = 1e-3
                assert math.isclose(
                    pyo.value(ipp.instance.var_capex), capex_group, abs_tol=abs_tol
                )
        
                # there should be no exports
    
                abs_tol = 1e-3
                exports_qp = sum(exports_qpk[(q, 0, k)] for k in tf.time_intervals[q])
                assert math.isclose(exports_qp, 0, abs_tol=abs_tol)
    
        
                # the imports should be higher than with individual arcs
                abs_tol = 1e-3
    
                imports_qp = sum(imports_qpk[qpk] for qpk in tf.qpk() if qpk[1] == 0)
                assert math.isclose(imports_qp, imp_group, abs_tol=abs_tol)
    
                assert imp_group > imp_ind
        
                # the operating results should be lower than with an individual arc
                abs_tol = 1e-3
                assert math.isclose(
                    pyo.value(ipp.instance.var_sdncf_q[q]), sdncf_group, abs_tol=abs_tol
                )
        
                # the externalities should be zero
                abs_tol = 1e-3
                assert math.isclose(
                    pyo.value(ipp.instance.var_sdext_q[q]), sdnext_group, abs_tol=abs_tol
                )
        
                # the objective function should be -6.3639758220728595-1.5
                abs_tol = 1e-3
                assert math.isclose(pyo.value(ipp.instance.obj_f), obj_group, abs_tol=abs_tol)
        
                # the imports should be greater than or equal to the losses for all arx
                losses_model = sum(
                    pyo.value(
                        ipp.instance.var_w_glljqk[("mynet", node_A, node_B, arc_key_AB, q, k)]
                    )
                    + pyo.value(
                        ipp.instance.var_w_glljqk[("mynet", node_C, node_D, arc_key_CD, q, k)]
                    )
                    for k in range(tf.number_time_intervals(q))
                )
        
                losses_data = sum(
                    static_loss_AB[(h1, q, k)] + static_loss_CD[(h2, q, k)]
                    for k in range(tf.number_time_intervals(q))
                )
        
                assert math.isclose(losses_model, losses_data, abs_tol=abs_tol)
        
                assert math.isclose(losses_data, losses_group, abs_tol=abs_tol)
                
        # *************************************************************************
        # *************************************************************************
        
        def test_arc_groups_individual_undirected_ref(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: one import node, four regular nodes
            mynet = Network()
            
            # import nodes
    
            mynet.add_import_node(
                node_key=imp_node_key,
                prices={
                    qpk: ResourcePrice(prices=1 + qpk[2], volumes=None)
                    for qpk in tf.qpk()