Skip to content
Snippets Groups Projects
test_esipp_problem.py 260 KiB
Newer Older
  • Learn to ignore specific revisions
  • Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
    # imports
    
    # standard
    import math
    
    # local
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
    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:
    
        # *************************************************************************
        # *************************************************************************
    
        
        @pytest.mark.parametrize(
            "use_sos_arcs, arc_sos_weight_key", 
            [(True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_NONE),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_CAP),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_COST),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_SPEC_CAP),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_SPEC_COST),
             (False, None)]
            )
        def test_single_network_single_arc_problem(self, use_sos_arcs, arc_sos_weight_key):
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
            
            # 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()
                },
            )
    
            # 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={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)
    
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                solver_options={},
    
                use_sos_arcs=use_sos_arcs,
                arc_sos_weight_key=arc_sos_weight_key,
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                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,
            )
    
            # *********************************************************************
            # *********************************************************************
            
            # validation
    
            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
            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, q, 0)]),
                1.0,
                abs_tol=1e-6,
            )
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 1)]),
                0.0,
                abs_tol=1e-6,
            )
            assert math.isclose(
                pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 2)]),
                2.0,
                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,
            )
    
            # capex should be four
            assert math.isclose(pyo.value(ipp.instance.var_capex), 4.0, abs_tol=1e-3)
    
            # sdncf should be -5.7
            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)
            
        # *************************************************************************
        # *************************************************************************
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
        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)
            
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                solver_options={},
    
                # use_sos_arcs=use_sos_arcs,
                # arc_sos_weight_key=arc_sos_weight_key,
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                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)
    
        # *************************************************************************
        # *************************************************************************
    
    
        @pytest.mark.parametrize(
            "use_sos_arcs, arc_sos_weight_key", 
            [(True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_NONE),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_CAP),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_COST),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_SPEC_CAP),
             (True, InfrastructurePlanningProblem.SOS1_ARC_WEIGHTS_SPEC_COST),
             (False, None)]
            )
        def test_single_network_single_arc_problem_simpler(self, use_sos_arcs, arc_sos_weight_key):
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
            
            # 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 = "thatimpnode"
            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 = "thatnodea"
            mynet.add_source_sink_node(
                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},
            )
    
            # 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)
    
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                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"] == 16 # 20
            assert ipp.results["Problem"][0]["Number of variables"] == 15 # 19
            assert ipp.results["Problem"][0]["Number of nonzeros"] == 28 # 36
            
            # *********************************************************************
            # *********************************************************************
    
            # 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
            )
    
            # 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)
    
            # no sos, regular time intervals
    
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                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)]),
                2.5,
                abs_tol=0.01,
            )
    
            # capex should be four
    
            assert math.isclose(pyo.value(ipp.instance.var_capex), 4.5, abs_tol=1e-3)
    
            # sdncf_q[0] should be -5.7
    
            assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[0]), -5.7, abs_tol=1e-3)
    
            # the objective function should be -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)
    
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                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)
    
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                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)
    
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                solver_options={},
                perform_analysis=False,
                plot_results=False,  # True,
                print_solver_output=False,
                networks={"mynet": mynet},
                converters={},
                time_frame=tf,
                static_losses_mode=True,  # just to reach a line,
                mandatory_arcs=[],
                max_number_parallel_arcs={},
                assessment_weights=assessment_weights,
                simplify_problem=True
            )
            
            assert ipp.has_peak_total_assessments()
            assert ipp.results["Problem"][0]["Number of constraints"] == 28 # 42
            assert ipp.results["Problem"][0]["Number of variables"] == 25 # 38
            assert ipp.results["Problem"][0]["Number of nonzeros"] == 51 # 87
    
            # *********************************************************************
    
            # validation
            # arc amplitude should be two
            assert math.isclose(
                pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_IMP, node_A, 0)]),
                2.5,
                abs_tol=0.01,
            )
            # capex should be four
            assert math.isclose(pyo.value(ipp.instance.var_capex), 4.5, abs_tol=1e-3)
            # sdncf_q[0] should be -5.7
            # assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[0]), -5.7, abs_tol=1e-3)
            # the objective function should be -10.80213032963115 (or -10.8027723516153)
            assert math.isclose(pyo.value(ipp.instance.obj_f), -10.80213032963115, abs_tol=3e-3)
            
        # *************************************************************************
        # *************************************************************************
        
        # problem with two symmetrical nodes and one undirected arc
        # problem with symmetrical nodes and one undirected arc with diff. tech.
        # problem with symmetrical nodes and one undirected arc, irregular steps
        # same problem as the previous one, except with interface variables
        # problem with two symmetrical nodes and one undirected arc, w/ simple sos1
            
        def test_isolated_undirected_network(self):
            
            q = 0
            tf = EconomicTimeFrame(
                discount_rate=3.5/100,
                reporting_periods={q: (0,)},
                reporting_period_durations={q: (365 * 24 * 3600,)},
                time_intervals={q: (0,1,2,3)},
                time_interval_durations={q: (1,1,1,1)},
            )
        
            # 4 nodes: one import, one export, two supply/demand nodes
            mynet = Network()
        
            # other nodes
            node_A = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(
                node_key=node_A,
                # base_flow=[1, -1, 0.5, -0.5],
                base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
            )
            node_B = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(
                node_key=node_B,
                # base_flow=[-1, 1, -0.5, 0.5],
                base_flow={(0, 0): -1, (0, 1): 1, (0, 2): -0.5, (0, 3): 0.5},
            )
        
            # add arcs
            # undirected arc
            arc_tech_AB = ArcsWithoutStaticLosses(
                name="any",
                # efficiency=[1, 1, 1, 1],
                efficiency={(0, 0): 1, (0, 1): 1, (0, 2): 1, (0, 3): 1},
                efficiency_reverse=None,
                capacity=[0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
                minimum_cost=[10, 10.1, 10.2, 10.3, 10.4, 10.5],
                specific_capacity_cost=1,
                capacity_is_instantaneous=False,
            )
            arc_key_AB_und = mynet.add_undirected_arc(
                node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
            )
        
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                solver_options={},
                perform_analysis=False,
                plot_results=False,  # True,
                print_solver_output=False,
                time_frame=tf,
                networks={"mynet": mynet},
                static_losses_mode=True,  # just to reach a line,
                mandatory_arcs=[],
                max_number_parallel_arcs={}
            )
            
            assert ipp.has_peak_total_assessments() # TODO: make sure this is true
            assert ipp.results["Problem"][0]["Number of constraints"] == 34
            assert ipp.results["Problem"][0]["Number of variables"] == 28
            assert ipp.results["Problem"][0]["Number of nonzeros"] == 105
        
            # *********************************************************************
            # *********************************************************************
        
            # validation
        
            # the arc should be installed since it is the only feasible solution
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            # there should be no opex (imports or exports), only capex from arcs
            assert pyo.value(ipp.instance.var_sdncf_q[q]) == 0
            assert pyo.value(ipp.instance.var_capex) > 0
            assert (
                pyo.value(
                    ipp.instance.var_capex_arc_gllj[("mynet", node_A, node_B, arc_key_AB_und)]
                )
                > 0
            )
            
        # *************************************************************************
        # *************************************************************************
        
        def test_isolated_undirected_network_diff_tech(self):
            
            # time frame
            q = 0
            tf = EconomicTimeFrame(
                discount_rate=3.5/100,
                reporting_periods={q: (0,)},
                reporting_period_durations={q: (365 * 24 * 3600,)},
                time_intervals={q: (0,1,2,3)},
                time_interval_durations={q: (1,1,1,1)},
            )
        
            # 4 nodes: one import, one export, two supply/demand nodes
            mynet = Network()
        
            # other nodes
            node_A = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(
                node_key=node_A,
                # base_flow=[1, -1, 0.5, -0.5]
                base_flow={(0, 0): 1, (0, 1): -1, (0, 2): 0.5, (0, 3): -0.5},
            )
        
            node_B = generate_pseudo_unique_key(mynet.nodes())
            mynet.add_source_sink_node(
                node_key=node_B,
                # base_flow=[-1.25, 1, -0.625, 0.5]
                base_flow={(0, 0): -1.25, (0, 1): 1.0, (0, 2): -0.625, (0, 3): 0.5},
            )
        
            # add arcs
            # undirected arc
            arc_tech_AB = ArcsWithoutStaticLosses(
                name="any",
                # efficiency=[0.8, 1.0, 0.8, 1.0],
                efficiency={(0, 0): 0.8, (0, 1): 1.0, (0, 2): 0.8, (0, 3): 1.0},
                efficiency_reverse=None,
                capacity=[1.25, 2.5],
                minimum_cost=[10, 15],
                specific_capacity_cost=1,
                capacity_is_instantaneous=False,
            )
            arc_key_AB_und = mynet.add_undirected_arc(
                node_key_a=node_A, node_key_b=node_B, arcs=arc_tech_AB
            )
        
            # no sos, regular time intervals
    
    Pedro L. Magalhães's avatar
    Pedro L. Magalhães committed
                solver_options={},
                perform_analysis=False,
                plot_results=False,
                print_solver_output=False,
                time_frame=tf,
                networks={"mynet": mynet},
                static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP,
                mandatory_arcs=[],
                max_number_parallel_arcs={}
            )
            
            assert ipp.has_peak_total_assessments()
            assert ipp.results["Problem"][0]["Number of constraints"] == 34
            assert ipp.results["Problem"][0]["Number of variables"] == 24
            assert ipp.results["Problem"][0]["Number of nonzeros"] == 77
        
            # *********************************************************************
            # *********************************************************************
        
            # validation
        
            # the arc should be installed since it is the only feasible solution
            assert (
                True
                in ipp.networks["mynet"]
                .edges[(node_A, node_B, arc_key_AB_und)][Network.KEY_ARC_TECH]
                .options_selected
            )
        
            # there should be no opex (imports or exports), only capex from arcs
            assert pyo.value(ipp.instance.var_sdncf_q[q]) == 0
            assert pyo.value(ipp.instance.var_capex) > 0
            assert (
                pyo.value(