From ef56a633d574046b2838bb699185457be702bd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20L=2E=20Magalh=C3=A3es?= <pmlpm@posteo.de> Date: Sat, 27 Jul 2024 00:18:38 +0200 Subject: [PATCH] Both ways of using price functions are implemented and tested. --- src/topupopt/problems/esipp/blocks/prices.py | 11 ++- src/topupopt/problems/esipp/model.py | 3 +- src/topupopt/problems/esipp/problem.py | 25 +++--- src/topupopt/problems/esipp/utils.py | 88 +++++++++++++------- tests/test_esipp_prices.py | 45 ++++++---- 5 files changed, 115 insertions(+), 57 deletions(-) diff --git a/src/topupopt/problems/esipp/blocks/prices.py b/src/topupopt/problems/esipp/blocks/prices.py index 870a71e..2af7aa3 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 ade66bb..a378cc8 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 c05b664..ced3fb7 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 4047696..08f6f61 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 9f3bf25..a7c6d59 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, -- GitLab