Skip to content
Snippets Groups Projects
test_esipp_problem.py 269 KiB
Newer Older
  • Learn to ignore specific revisions
  •             abs_tol=abs_tol,
            )
                    
        # *************************************************************************
        # *************************************************************************
        
        def test_directed_arc_static_upstream_pre(self):
            
            # time
            number_intervals = 1
            number_periods = 2
            
            # 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,)},
                time_interval_durations={q: (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={
                    (q, p, k): ResourcePrice(prices=1, volumes=None)
                    for p in range(number_periods)
                    for k in range(number_intervals)
                },
            )
            imp2_node_key = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_import_node(
                node_key=imp2_node_key,
                prices={
                    (q, p, k): ResourcePrice(prices=2, volumes=None)
                    for p in range(number_periods)
                    for k in range(number_intervals)
                },
            )
        
            # other nodes
            node_A = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_waypoint_node(node_key=node_A)
            node_B = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(
                node_key=node_B,
                base_flow={
                    (q, 0): 1.0,
                },
            )
        
            # add arcs
            # I1A
            mynet.add_preexisting_directed_arc(
                node_key_a=imp1_node_key,
                node_key_b=node_A,
                efficiency=None,
                static_loss=None,
                capacity=1,
                capacity_is_instantaneous=False,
            )
            
            # I2B
            mynet.add_preexisting_directed_arc(
                node_key_a=imp2_node_key,
                node_key_b=node_B,
                efficiency=None,
                static_loss=None,
                capacity=0.1,
                capacity_is_instantaneous=False,
            )
            
            # AB
            mynet.add_preexisting_directed_arc(
                node_key_a=node_A,
                node_key_b=node_B,
                efficiency=None,
                static_loss={(0, q, 0): 0.1},
                capacity=1,
                capacity_is_instantaneous=False,
            )
        
            # identify node types
            mynet.identify_node_types()
        
            # no sos, regular time intervals
        
            ipp = self.build_solve_ipp(
    
                solver='cbc', # TODO: make this work with other solvers
    
                solver_options={},
                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={}
            )
            
            # all arcs should be installed (they are not new)
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp1_node_key, node_A, 0)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp2_node_key, node_B, 0)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(node_A, node_B, 0)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            # 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)
    
        
            # there should be imports
            abs_tol = 1e-6
    
            imports_qp = sum(imports_qpk[qpk] for qpk in tf.qpk() if qpk[1] == 0)
            assert math.isclose(imports_qp, 1.1, abs_tol=abs_tol)
    
        
            # there should be no exports
            abs_tol = 1e-6
    
    
            exports_qp = sum(exports_qpk[(q, 0, k)] for k in tf.time_intervals[q])
            export_revenue_qp = sum(export_revenue_qpk[(q, 0, k)] for k in tf.time_intervals[q])
            assert math.isclose(exports_qp, 0, abs_tol=abs_tol)
    
        
            # interval 0: flow through IA1 must be 1
            abs_tol = 1e-6
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", imp1_node_key, node_A, 0, 0, 0)]),
                1,
                abs_tol=abs_tol,
            )
        
            # interval 0: flow through AB must be 0.9
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", node_A, node_B, 0, 0, 0)]),
                0.9,
                abs_tol=abs_tol,
            )
        
            # interval 0: flow through IB2 must be 0.1
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", imp2_node_key, node_B, 0, 0, 0)]),
                0.1,
                abs_tol=abs_tol,
            )
    
        # *************************************************************************
        # *************************************************************************
        
        def test_directed_arc_static_downstream_new(self):
    
            # time
            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,)},
                time_interval_durations={q: (1,)},
            )
    
            number_intervals = 1
    
            # 4 nodes: one import, one export, two supply/demand nodes
    
            # import node    
    
            mynet.add_import_node(
                node_key=imp_node_key,
                prices={
    
                    (q, p, k): ResourcePrice(prices=1 + 0.1, volumes=None)
    
                    for p in range(number_periods)
                    for k in range(number_intervals)
                },
            )
        
            # other nodes    
    
            mynet.add_source_sink_node(node_key=node_A, base_flow={(q, 0): 1.0})
    
            # add arcs    
            # IA1    
            mynet.add_preexisting_directed_arc(
                node_key_a=imp_node_key,
                node_key_b=node_A,
                efficiency={(q, 0): 0.9},
                static_loss={(q, 0, 0): 0.1},
                capacity=0.5,
    
                capacity_is_instantaneous=False,
            )
    
            # IA2
            arcs_ia2 = Arcs(
                name="IA2",
                efficiency=None,
                efficiency_reverse=None,
                static_loss=None,
                capacity=tuple([1.2]),
                minimum_cost=tuple([0.1]),
                specific_capacity_cost=0,
                capacity_is_instantaneous=False,
                validate=True,
            )
            mynet.add_directed_arc(node_key_a=imp_node_key, node_key_b=node_A, arcs=arcs_ia2)
        
    
            # identify node types    
    
            # no sos, regular time intervals    
    
            ipp = self.build_solve_ipp(
    
                solver='cbc', # TODO: make this work with other solvers
    
                solver_options={},
                plot_results=False,  # True,
                print_solver_output=False,
                networks={"mynet": mynet},
                time_frame=tf,
    
                # static_losses_mode=True,
                static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_ARR,
    
                max_number_parallel_arcs={},
    
        
            # **************************************************************************
        
    
            # all arcs should be installed (they are not new)
    
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp_node_key, node_A, 0)][Network.KEY_ARC_TECH]
                .options_selected
            )
    
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp_node_key, node_A, 1)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            # 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)
    
        
            # there should be imports
            abs_tol = 1e-6
    
            imports_qp = sum(imports_qpk[qpk] for qpk in tf.qpk() if qpk[1] == 0)
            assert math.isclose(imports_qp, (1.0 + 0.1), abs_tol=abs_tol)
    
    
            exports_qp = sum(exports_qpk[(q, 0, k)] for k in tf.time_intervals[q])
            export_revenue_qp = sum(export_revenue_qpk[(q, 0, k)] for k in tf.time_intervals[q])
            assert math.isclose(exports_qp, 0, abs_tol=abs_tol)
    
            # flow through IA1 must be 0.1
    
            abs_tol = 1e-6
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", imp_node_key, node_A, 0, 0, 0)]),
    
            # flow through IA2 must be 1.0
    
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", imp_node_key, node_A, 1, 0, 0)]),
                1.0,
                abs_tol=abs_tol,
            )
            
        # *************************************************************************
        # *************************************************************************
        
        def test_directed_arc_static_downstream_pre(self):
            
            # time
            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,)},
                time_interval_durations={q: (1,)},
            )
            number_intervals = 1
            number_periods = 2
        
            # 4 nodes: one import, one export, two supply/demand nodes
            mynet = Network()
        
            # import node    
    
            mynet.add_import_node(
                node_key=imp_node_key,
                prices={
                    (q, p, k): ResourcePrice(prices=1 + 0.1, volumes=None)
                    for p in range(number_periods)
                    for k in range(number_intervals)
                },
            )
        
            # other nodes    
    
            mynet.add_source_sink_node(node_key=node_A, base_flow={(q, 0): 1.0})
        
            # add arcs    
            # IA1    
            mynet.add_preexisting_directed_arc(
                node_key_a=imp_node_key,
                node_key_b=node_A,
                efficiency={(q, 0): 0.9},
                static_loss={(q, 0, 0): 0.1},
                capacity=0.5,
                capacity_is_instantaneous=False,
            )
        
            # IA2    
            mynet.add_preexisting_directed_arc(
                node_key_a=imp_node_key,
                node_key_b=node_A,
                efficiency=None,
                static_loss=None,
                capacity=1.2,
                capacity_is_instantaneous=False,
            )
        
            # identify node types    
            mynet.identify_node_types()
        
            # no sos, regular time intervals    
            ipp = self.build_solve_ipp(
    
                solver='cbc', # TODO: make this work with other solvers
    
                solver_options={},
                plot_results=False,  # True,
                print_solver_output=False,
                networks={"mynet": mynet},
                time_frame=tf,
    
                # static_losses_mode=True,
                static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_ARR,
    
                mandatory_arcs=[],
                max_number_parallel_arcs={},
            )
        
            # **************************************************************************
        
            # all arcs should be installed (they are not new)
        
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp_node_key, node_A, 0)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(imp_node_key, node_A, 1)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            # 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)
    
        
            # there should be imports
            abs_tol = 1e-6
    
            imports_qp = sum(imports_qpk[qpk] for qpk in tf.qpk() if qpk[1] == 0)
            assert math.isclose(imports_qp, (1.0 + 0.1), abs_tol=abs_tol)
    
    
            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)
    
        
            # flow through IA1 must be 0.1
            abs_tol = 1e-6
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", imp_node_key, node_A, 0, 0, 0)]),
                0.1,
                abs_tol=abs_tol,
            )
        
            # flow through IA2 must be 1.0
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", imp_node_key, node_A, 1, 0, 0)]),
                1.0,
                abs_tol=abs_tol,
            )
            
        # *************************************************************************
        # *************************************************************************
    
        # def test_problem_converter_sink(self):
            
        #     # 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)},
        #         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()
        #         },
        #     )
    
        #     # 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): 0.00, (q, 2): 1.00},
        #     )
    
        #     # arc IA
        #     arc_tech_IA = Arcs(
        #         name="any",
        #         # efficiency=[0.5, 0.5, 0.5],
        #         efficiency={(q, 0): 0.5, (q, 1): 0.5, (q, 2): 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()
    
        #     # converter
        #     a_nnk = {
        #         (0, 0, 0): 0.95,
        #         (0, 0, 1): 0.95,
        #         (0, 0, 2): 0.95,
        #     }
        #     b_nmk = {
        #         (0, 0, 0): 3,
        #         (0, 0, 1): 3,
        #         (0, 0, 2): 3
        #         }
        #     x_n0 = {0: 18}
    
        #     # get the signals
        #     inputs, states, outputs = get_two_node_model_signals(
        #         tf.number_time_intervals(q)
        #         )
    
        #     # create a discretised dynamic system from dictionaries
        #     dds = dynsys.DiscretisedDynamicSystem(
        #         a_nnk=a_nnk,
        #         b_nmk=b_nmk,
        #         x_n0=x_n0,
        #         time_frame=tf
        #         )
    
        #     # create a converter
        #     cvt = Converter(
        #         time_frame=tf,
        #         dds=dds,
        #         turn_key_cost=3,
        #         inputs=inputs,
        #         states=states,
        #         outputs=outputs,
        #     )
    
        #     # 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},
        #         converters={"mycvt": cvt},
        #         static_losses_mode=False,
        #         mandatory_arcs=[],
        #         max_number_parallel_arcs={},
        #         # init_aux_sets=init_aux_sets,
        #         simplify_problem=False,
        #     )
    
        #     assert not 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
    
        #     # *********************************************************************
        #     # *********************************************************************
    
        #     # validation
            
        #     # if uC,M 1,q,0 = 0, then xC,N 1,q,1 = 17.1     # infeasible
        #     # if uC,M 1,q,0 = 1, then xC,N 1,q,1 = 20.1.    # only feasible option
        #     # if uC,M 1,q,1 = 0, then xC,N 1,q,2 = 19.095   # only feasible option
        #     # if uC,M 1,q,1 = 1, then xC,N 1,q,2 = 22.095   # infeasible
        #     # if uC,M 1,q,2 = 0, then xC,N 1,q,3 = 18.14025 # feasible
        #     # if uC,M 1,q,2 = 1, then xC,N 1,q,3 = 21.14025 # feasible
            
        #     true_u_imqk = {
        #         ('mycvt', 0, q, 0): 1,
        #         ('mycvt', 0, q, 1): 0,
        #         ('mycvt', 0, q, 2): 0, # could also be 1
        #         }
            
        #     true_x_inqk = {
        #         ('mycvt', 0, q, 0): 20.1,
        #         ('mycvt', 0, q, 1): 19.095,
        #         ('mycvt', 0, q, 2): 18.14025, # could also be 21.14025
        #         }
            
        #     # check the inputs
        #     for imqk, u in true_u_imqk.items():
        #         assert math.isclose(
        #             pyo.value(ipp.instance.var_u_imqk[imqk]),
        #             u,
        #             abs_tol=1e-6,
        #         ) 
            
        #     # check the states
        #     for inqk, x in true_x_inqk.items():
        #         assert math.isclose(
        #             pyo.value(ipp.instance.var_x_inqk[inqk]),
        #             x,
        #             abs_tol=1e-6,
        #         )
        
        
            
        # *************************************************************************
        # *************************************************************************
        
        # TODO: test non-simplifiable problems with time varying prices on select assessments
        # TODO: test non-simplifiable problems with volume varying prices on select assessments
    
        
            
        # *************************************************************************
        # *************************************************************************
        
        def test_problem_with_tree_network(self):
            
            # 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,)},
                time_interval_durations={q: (1,)},
            )
    
            # 2 nodes: one import, one regular
    
            mynet = Network(network_type=Network.NET_TYPE_TREE)
    
    
            # import node
            node_IMP = "thatimpnode"
            mynet.add_import_node(
                node_key=node_IMP,
                prices={
                    qpk: ResourcePrice(prices=1.0, volumes=None)
                    for qpk in tf.qpk()
                },
            )
    
            # node A
            node_A = "thatnodea"
            mynet.add_source_sink_node(
                node_key=node_A,
                base_flow={(q, 0): 0.50},
            )
            # node B
            node_B = "thatnodeb"
            mynet.add_source_sink_node(
                node_key=node_B,
                base_flow={(q, 0): 0.25},
            )
            # node C
            node_C = "thatnodec"
            mynet.add_source_sink_node(
                node_key=node_C,
                base_flow={(q, 0): 1.25},
            )
            
            list_imp_arcs = [
                (node_IMP, node_A), # IA
                (node_IMP, node_B), # IB
                (node_IMP, node_C), # IC
                ]
            for i, node_pair in enumerate(list_imp_arcs):
            
                # import arcs: IA, IB, IC
                new_arc = Arcs(
                    name="IA",
                    efficiency=None,
                    efficiency_reverse=None,
                    static_loss=None,
                    capacity=[2],
                    minimum_cost=[6],
                    specific_capacity_cost=i,
                    capacity_is_instantaneous=False,
                    validate=False,
                )
                mynet.add_directed_arc(*node_pair, arcs=new_arc)        
            
            # arcs: AB, BA, BC, CB, AC, CA
            
            list_other_arcs = [
                (node_A, node_B), # AB
                (node_B, node_A), # BA
                (node_B, node_C), # BC
                (node_C, node_B), # CB
                (node_A, node_C), # AC
                (node_C, node_A), # CA
                ]
            
            for node_pair in list_other_arcs:
                # arc
                new_arc_tech = Arcs(
                    name="any",
                    efficiency=None,
                    efficiency_reverse=None,
                    static_loss=None,
                    capacity=[3],
                    minimum_cost=[2],
                    specific_capacity_cost=0,
                    capacity_is_instantaneous=False,
                    validate=False,
                )
                mynet.add_directed_arc(*node_pair, arcs=new_arc_tech)
    
            # 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=True,
            )
            assert ipp.has_peak_total_assessments()
    
            assert ipp.results["Problem"][0]["Number of constraints"] == 61
    
            assert ipp.results["Problem"][0]["Number of variables"] == 53 
            assert ipp.results["Problem"][0]["Number of nonzeros"] == 143
            
            # *********************************************************************
            # *********************************************************************
    
            # validation
    
            # only the IA arc should be installed
            true_imp_arcs_selected = [True, False, False]
            for node_pair, true_arc_decision in zip(list_imp_arcs, true_imp_arcs_selected):
                assert (
                    true_arc_decision
                    in ipp.networks["mynet"]
                    .edges[(*node_pair, 0)][Network.KEY_ARC_TECH]
                    .options_selected
                )
            # only two arcs between A, B and C can be installed
            arcs_selected = tuple(
                1
                for node_pair in list_other_arcs
                if True in ipp.networks["mynet"]
                .edges[(*node_pair, 0)][Network.KEY_ARC_TECH]
                .options_selected
                )
            assert sum(arcs_selected) == 2
            # the network must be tree-shaped
            assert ipp.networks["mynet"].has_tree_topology()
            # capex
            assert math.isclose(pyo.value(ipp.instance.var_capex), 10.0, abs_tol=1e-3)
            # the objective function
            assert math.isclose(pyo.value(ipp.instance.obj_f), -1.193236715e+01, abs_tol=1e-3)
    
            
            
        # *************************************************************************
        # *************************************************************************
        
        def test_problem_with_reverse_tree_network(self):
            
            # 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,)},
                time_interval_durations={q: (1,)},
            )
    
            # 2 nodes: one import, one regular
    
            mynet = Network(network_type=Network.NET_TYPE_REV_TREE)
            
    
            # export node
            node_EXP = "thatexpnode"
            mynet.add_export_node(
                node_key=node_EXP,
                prices={
                    qpk: ResourcePrice(prices=1.0, volumes=None)
                    for qpk in tf.qpk()
                },
            )
    
            # node A
            node_A = "thatnodea"
            mynet.add_source_sink_node(
                node_key=node_A,
                base_flow={(q, 0): -0.50},
            )
            # node B
            node_B = "thatnodeb"
            mynet.add_source_sink_node(
                node_key=node_B,
                base_flow={(q, 0): -0.25},
            )
            # node C
            node_C = "thatnodec"
            mynet.add_source_sink_node(
                node_key=node_C,
                base_flow={(q, 0): -1.25},
            )
            
    
                (node_A, node_EXP), # AE
                (node_B, node_EXP), # BE
                (node_C, node_EXP), # CE
                ]
    
            for i, node_pair in enumerate(list_exp_arcs):
    
            
                # import arcs: AE, BE, CE
                new_arc = Arcs(
                    name="arc_"+str(node_pair),
                    efficiency=None,
                    efficiency_reverse=None,
                    static_loss=None,
                    capacity=[2],
                    minimum_cost=[6],
                    specific_capacity_cost=i,
                    capacity_is_instantaneous=False,
                    validate=False,
                )
                mynet.add_directed_arc(*node_pair, arcs=new_arc)        
            
            # arcs: AB, BA, BC, CB, AC, CA
            
            list_other_arcs = [
                (node_A, node_B), # AB
                (node_B, node_A), # BA
                (node_B, node_C), # BC
                (node_C, node_B), # CB
                (node_A, node_C), # AC
                (node_C, node_A), # CA
                ]
            
            for node_pair in list_other_arcs:
                # arc
                new_arc_tech = Arcs(
                    name="any",
                    efficiency=None,
                    efficiency_reverse=None,
                    static_loss=None,
                    capacity=[3],
                    minimum_cost=[2],
                    specific_capacity_cost=0,
                    capacity_is_instantaneous=False,
                    validate=False,
                )
                mynet.add_directed_arc(*node_pair, arcs=new_arc_tech)
    
            # 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=True,
            )
            assert ipp.has_peak_total_assessments()
    
            assert ipp.results["Problem"][0]["Number of constraints"] == 61
    
            assert ipp.results["Problem"][0]["Number of variables"] == 53 
    
            assert ipp.results["Problem"][0]["Number of nonzeros"] == 143 # 
    
            
            # *********************************************************************
            # *********************************************************************
    
            # validation
    
    
            # only the AE arc should be installed
            true_exp_arcs_selected = [True, False, False]
            for node_pair, true_arc_decision in zip(list_exp_arcs, true_exp_arcs_selected):
    
                assert (
                    true_arc_decision
                    in ipp.networks["mynet"]
                    .edges[(*node_pair, 0)][Network.KEY_ARC_TECH]
                    .options_selected
                )
            # only two arcs between A, B and C can be installed
            arcs_selected = tuple(
                1
                for node_pair in list_other_arcs
                if True in ipp.networks["mynet"]
                .edges[(*node_pair, 0)][Network.KEY_ARC_TECH]
                .options_selected
                )
            assert sum(arcs_selected) == 2
            # the network must be tree-shaped
            assert ipp.networks["mynet"].has_tree_topology()
            # capex
            assert math.isclose(pyo.value(ipp.instance.var_capex), 10.0, abs_tol=1e-3)
            # the objective function
            assert math.isclose(pyo.value(ipp.instance.obj_f), -(10+(-11.93236715+10)), abs_tol=1e-3)
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
    
    # *****************************************************************************
    
    # *****************************************************************************