diff --git a/src/topupopt/problems/esipp/blocks/prices.py b/src/topupopt/problems/esipp/blocks/prices.py index 870a71e1b44036dad5c13cb7dd29f2265cf81d98..2af7aa3b810b0c280b2d1a2bbdf1397f37766448 100644 --- a/src/topupopt/problems/esipp/blocks/prices.py +++ b/src/topupopt/problems/esipp/blocks/prices.py @@ -8,14 +8,19 @@ import math def add_price_functions( model: pyo.AbstractModel, + use_blocks: bool = False, **kwargs ): # ************************************************************************* # ************************************************************************* - # price_other(model, **kwargs) - price_other_block(model, **kwargs) + if use_blocks: + # blocks + price_other_block(model, **kwargs) + else: + # no blocks + price_other(model, **kwargs) # ************************************************************************* # ************************************************************************* @@ -132,7 +137,7 @@ def price_other_block( # ********************************************************************* # non-convex price functions - + # TODO: fix the variables if the function is convex # delta variables b.var_active_segment_s = pyo.Var(b.set_S, within=pyo.Binary) diff --git a/src/topupopt/problems/esipp/model.py b/src/topupopt/problems/esipp/model.py index ade66bb19829abf84f8a77e28e596bfd4f5d0f69..a378cc8edd15b74a2cea1004c118e276c8cebbf8 100644 --- a/src/topupopt/problems/esipp/model.py +++ b/src/topupopt/problems/esipp/model.py @@ -14,6 +14,7 @@ def create_model( enable_default_values: bool = True, enable_validation: bool = True, enable_initialisation: bool = True, + use_prices_block: bool = False ): # TODO: test initialisation @@ -2156,7 +2157,7 @@ def create_model( ) # prices - add_price_functions(model) + add_price_functions(model, use_blocks=use_prices_block) # ************************************************************************* # ************************************************************************* diff --git a/src/topupopt/problems/esipp/problem.py b/src/topupopt/problems/esipp/problem.py index c05b664c4791445e143c22a403d5bead1659a1f7..ced3fb7209d23097b9ed7161f22c4dd014b5be9a 100644 --- a/src/topupopt/problems/esipp/problem.py +++ b/src/topupopt/problems/esipp/problem.py @@ -89,7 +89,8 @@ class InfrastructurePlanningProblem(EnergySystem): converters: dict = None, prepare_model: bool = True, validate_inputs: bool = True, - node_price_model = NODE_PRICE_DELTA + node_price_model = NODE_PRICE_DELTA, + use_prices_block: bool = True #False ): # TODO: switch to False when everything is more mature # ********************************************************************* @@ -122,6 +123,15 @@ class InfrastructurePlanningProblem(EnergySystem): # self.number_time_intervals = { # q: len(time_frame.time_intervals[q]) for q in self.time_frame.assessments # } + + # ********************************************************************* + # ********************************************************************* + + # options + self.use_prices_block = use_prices_block + + # ********************************************************************* + # ********************************************************************* # initialise @@ -1625,28 +1635,22 @@ class InfrastructurePlanningProblem(EnergySystem): raise ValueError( "The method to determine the SOS1 weights was not recognised." ) - if method == self.SOS1_ARC_WEIGHTS_CAP: sos1_weights = tuple(capacity for capacity in arcs.capacity) - elif method == self.SOS1_ARC_WEIGHTS_COST: sos1_weights = tuple(cost for cost in arcs.minimum_cost) - elif method == self.SOS1_ARC_WEIGHTS_SPEC_CAP: sos1_weights = tuple( cap / cost for cap, cost in zip(arcs.capacity, arcs.minimum_cost) ) - elif method == self.SOS1_ARC_WEIGHTS_SPEC_COST: sos1_weights = tuple( cost / cap for cap, cost in zip(arcs.capacity, arcs.minimum_cost) ) - else: # SOS1_ARC_WEIGHTS_NONE return None # make sure they are unique - if verify_weights: for weight in sos1_weights: if sos1_weights.count(weight) >= 2: # TODO: reach this point @@ -1655,7 +1659,6 @@ class InfrastructurePlanningProblem(EnergySystem): + "special ordered sets of type 1 (SOS1)," + " since some weights are not unique." ) - return sos1_weights # ************************************************************************* @@ -1663,10 +1666,8 @@ class InfrastructurePlanningProblem(EnergySystem): def prepare(self, name = None): """Sets up the problem model with which instances can be built.""" - # create pyomo model (AbstractModel) - - self.model = create_model(name) + self.model = create_model(name, use_prices_block=self.use_prices_block) # ************************************************************************* # ************************************************************************* @@ -3296,6 +3297,8 @@ class InfrastructurePlanningProblem(EnergySystem): # ********************************************************************* # produce a dictionary with the data for the problem + + # TODO: built the dict as a function of the price submodel data_dict = { None: { diff --git a/src/topupopt/problems/esipp/utils.py b/src/topupopt/problems/esipp/utils.py index 4047696c1311ed28b21307510000cd5c8638cbf9..08f6f612f55dae9f730c097dbfb027f6dd812fa1 100644 --- a/src/topupopt/problems/esipp/utils.py +++ b/src/topupopt/problems/esipp/utils.py @@ -69,6 +69,7 @@ def statistics(ipp: InfrastructurePlanningProblem, other_node_keys: tuple = None): "Returns flow statistics using the optimisation results." + prices_in_block = ipp.use_prices_block if type(import_node_keys) == type(None): # pick all import nodes @@ -94,36 +95,67 @@ def statistics(ipp: InfrastructurePlanningProblem, for node_key in net.nodes() if Network.KEY_NODE_BASE_FLOW in net.nodes[node_key] ) - - # imports - imports_qpk = { - qpk: pyo.value( - sum( - ipp.instance.var_trans_flows_glqpks[(g,l_imp,*qpk, s)] - for g, l_imp in import_node_keys - # for g in ipp.networks - # for l_imp in ipp.networks[g].import_nodes - for s in ipp.instance.set_S[(g,l_imp,*qpk)] + if prices_in_block: + # imports + imports_qpk = { + qpk: pyo.value( + sum( + ipp.instance.block_prices[(g,l_imp,*qpk)].var_trans_flows_s[s] + for g, l_imp in import_node_keys + # for g in ipp.networks + # for l_imp in ipp.networks[g].import_nodes + for s in ipp.instance.block_prices[(g,l_imp,*qpk)].set_S + ) + *ipp.instance.param_c_time_qpk[qpk] ) - *ipp.instance.param_c_time_qpk[qpk] - ) - for qpk in ipp.time_frame.qpk() - } - - # exports - exports_qpk = { - qpk: pyo.value( - sum( - ipp.instance.var_trans_flows_glqpks[(g,l_exp,*qpk, s)] - for g, l_exp in export_node_keys - # for g in ipp.networks - # for l_exp in ipp.networks[g].export_nodes - for s in ipp.instance.set_S[(g,l_exp,*qpk)] + for qpk in ipp.time_frame.qpk() + } + + # exports + exports_qpk = { + qpk: pyo.value( + sum( + ipp.instance.block_prices[(g,l_exp,*qpk)].var_trans_flows_s[s] + for g, l_exp in export_node_keys + # for g in ipp.networks + # for l_exp in ipp.networks[g].export_nodes + for s in ipp.instance.block_prices[(g,l_exp,*qpk)].set_S + ) + *ipp.instance.param_c_time_qpk[qpk] ) - *ipp.instance.param_c_time_qpk[qpk] - ) - for qpk in ipp.time_frame.qpk() - } + for qpk in ipp.time_frame.qpk() + } + else: + # not in a block + # imports + imports_qpk = { + qpk: pyo.value( + sum( + ipp.instance.var_trans_flows_glqpks[(g,l_imp,*qpk, s)] + for g, l_imp in import_node_keys + # for g in ipp.networks + # for l_imp in ipp.networks[g].import_nodes + for s in ipp.instance.set_S[(g,l_imp,*qpk)] + ) + *ipp.instance.param_c_time_qpk[qpk] + ) + for qpk in ipp.time_frame.qpk() + } + + # exports + exports_qpk = { + qpk: pyo.value( + sum( + ipp.instance.var_trans_flows_glqpks[(g,l_exp,*qpk, s)] + for g, l_exp in export_node_keys + # for g in ipp.networks + # for l_exp in ipp.networks[g].export_nodes + for s in ipp.instance.set_S[(g,l_exp,*qpk)] + ) + *ipp.instance.param_c_time_qpk[qpk] + ) + for qpk in ipp.time_frame.qpk() + } # balance balance_qpk = { qpk: imports_qpk[qpk]-exports_qpk[qpk] diff --git a/tests/test_esipp_prices.py b/tests/test_esipp_prices.py index 9f3bf25f9793362c5c6084dd9fd5dfd15fdf162e..a7c6d59d9f707a88b8d6f9b66b150c4d2b187d37 100644 --- a/tests/test_esipp_prices.py +++ b/tests/test_esipp_prices.py @@ -2,12 +2,14 @@ # standard import math +import pytest # 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 @@ -54,6 +56,7 @@ class TestESIPPProblem: # discount_rates: dict = None, assessment_weights: dict = None, simplify_problem: bool = False, + use_prices_block: bool = False ): if type(solver) == type(None): solver = self.solver @@ -90,6 +93,7 @@ class TestESIPPProblem: time_weights=time_weights, normalised_time_interval_duration=normalised_time_interval_duration, assessment_weights=assessment_weights, + use_prices_block=use_prices_block ) # add networks and systems @@ -243,7 +247,8 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - def test_problem_increasing_imp_prices(self): + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_problem_increasing_imp_prices(self, use_prices_block): # assessment q = 0 @@ -298,7 +303,8 @@ class TestESIPPProblem: static_losses_mode=True, # just to reach a line, mandatory_arcs=[], max_number_parallel_arcs={}, - simplify_problem=False + simplify_problem=False, + use_prices_block=use_prices_block ) assert not ipp.has_peak_total_assessments() @@ -345,7 +351,8 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - def test_problem_decreasing_imp_prices(self): + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_problem_decreasing_imp_prices(self, use_prices_block): # assessment q = 0 @@ -400,7 +407,8 @@ class TestESIPPProblem: static_losses_mode=True, # just to reach a line, mandatory_arcs=[], max_number_parallel_arcs={}, - simplify_problem=False + simplify_problem=False, + use_prices_block=use_prices_block ) assert not ipp.has_peak_total_assessments() @@ -447,7 +455,8 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - def test_problem_decreasing_imp_prices_infinite_capacity(self): + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_problem_decreasing_imp_prices_infinite_capacity(self, use_prices_block): # assessment q = 0 @@ -514,7 +523,8 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - def test_problem_decreasing_exp_prices(self): + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_problem_decreasing_exp_prices(self, use_prices_block): # assessment q = 0 # time @@ -620,7 +630,8 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - def test_problem_increasing_exp_prices(self): + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_problem_increasing_exp_prices(self, use_prices_block): # assessment q = 0 # time @@ -681,7 +692,7 @@ class TestESIPPProblem: max_number_parallel_arcs={}, simplify_problem=False, ) - ipp.instance.pprint() + assert not ipp.has_peak_total_assessments() assert ipp.results["Problem"][0]["Number of constraints"] == 14 # 10 before nonconvex block assert ipp.results["Problem"][0]["Number of variables"] == 13 # 11 before nonconvex block @@ -726,7 +737,8 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - def test_problem_increasing_exp_prices_infinite_capacity(self): + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_problem_increasing_exp_prices_infinite_capacity(self, use_prices_block): # assessment q = 0 # time @@ -789,6 +801,7 @@ class TestESIPPProblem: mandatory_arcs=[], max_number_parallel_arcs={}, simplify_problem=False, + use_prices_block=use_prices_block ) except Exception: error_raised = True @@ -797,7 +810,8 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - def test_problem_increasing_imp_decreasing_exp_prices(self): + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_problem_increasing_imp_decreasing_exp_prices(self, use_prices_block): # scenario q = 0 # time @@ -885,6 +899,7 @@ class TestESIPPProblem: max_number_parallel_arcs={}, simplify_problem=False, # discount_rates={0: (0.0,)}, + use_prices_block=use_prices_block ) assert not ipp.has_peak_total_assessments() @@ -960,8 +975,9 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - - def test_direct_imp_exp_network_higher_exp_prices(self): + + @pytest.mark.parametrize("use_prices_block", [True, False]) + def test_direct_imp_exp_network_higher_exp_prices(self, use_prices_block): # time frame q = 0 @@ -1031,7 +1047,8 @@ class TestESIPPProblem: time_frame=tf, static_losses_mode=InfrastructurePlanningProblem.STATIC_LOSS_MODE_DEP, mandatory_arcs=[], - max_number_parallel_arcs={} + max_number_parallel_arcs={}, + use_prices_block=use_prices_block ) # export prices are higher: it makes sense to install the arc since the @@ -1043,7 +1060,7 @@ class TestESIPPProblem: .edges[(imp_node_key, exp_node_key, 0)][Network.KEY_ARC_TECH] .options_selected ) - + # overview (imports_qpk, exports_qpk,