Skip to content
Snippets Groups Projects
test_esipp_problem.py 269 KiB
Newer Older
  • Learn to ignore specific revisions
  • # 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