diff --git a/src/topupopt/problems/esipp/blocks/lambda.py b/src/topupopt/problems/esipp/blocks/lambda_.py similarity index 75% rename from src/topupopt/problems/esipp/blocks/lambda.py rename to src/topupopt/problems/esipp/blocks/lambda_.py index c09943f583629ecae62edc3382a163b0d681b7a9..7cb66668206fc10acc26a501ad92a148f8ef9256 100644 --- a/src/topupopt/problems/esipp/blocks/lambda.py +++ b/src/topupopt/problems/esipp/blocks/lambda_.py @@ -25,11 +25,6 @@ def price_lambda_block( # set of price segments b.set_S = pyo.Set() - - # TODO: introduce a set of price segments for non-convex tariffs - # def init_set_S_nonconvex(b, s): - # return (s for s in b.set_S if b.param_price_function_is_convex) - # b.set_S_nonconvex = pyo.Set(within=b.set_S, initialize=init_set_S_nonconvex) # ********************************************************************* # ********************************************************************* @@ -53,66 +48,88 @@ def price_lambda_block( # ********************************************************************* # ********************************************************************* - # TODO: consider replacing None in the bounds with inf in the parameter - - # import and export flows - def bounds_var_trans_flows_s(b, s): - if s in b.param_v_max_s: - # predefined finite capacity - return (0, b.param_v_max_s[s]) - else: - # infinite capacity - return (0, None) - b.var_trans_flows_s = pyo.Var( - b.set_S, - within=pyo.NonNegativeReals, - bounds=bounds_var_trans_flows_s - ) + # set of points + b.set_P = pyo.Set(initialize=[i for i in range(len(b.set_S)+1)]) + + # set of x points + def init_param_x_p(b, p): + return 0 if p == 0 else sum(b.param_v_max_s[i] for i in range(p)) + b.param_x_p = pyo.Param( + b.set_P, + within=pyo.NonNegativeReals, + initialize=init_param_x_p + ) + + # set of y points + def init_param_y_p(b, p): + return 0 if p == 0 else (b.param_x_p[p]-b.param_x_p[p-1])*b.param_p_s[p-1]+b.param_y_p[p-1] + # sum(b.param_p_s[i] for i in range(p+1)) + b.param_y_p = pyo.Param( + b.set_P, + within=pyo.NonNegativeReals, + initialize=init_param_y_p + ) - # import and export flows - def bounds_var_trans_flows_s(b, s): - if s in b.param_v_max_s: - # predefined finite capacity - return (0, b.param_v_max_s[s]) - else: - # infinite capacity + # interpoint weights + b.var_weights_p = pyo.Var(b.set_P, within=pyo.UnitInterval) + + # TODO: consider replacing None in the bounds with inf in the parameter + + # transshipment flows + def bounds_var_trans_flows(b): + try: + return (0, sum(b.param_v_max_s[s] for s in b.set_S)) + except Exception: return (0, None) - b.var_trans_flows_s = pyo.Var( - b.set_S, - within=pyo.NonNegativeReals, - bounds=bounds_var_trans_flows_s - ) + b.var_trans_flows = pyo.Var(bounds=bounds_var_trans_flows) + + # ********************************************************************* + # ********************************************************************* + + def rule_constr_stick_to_line(b): + return sum(b.var_weights_p[p] for p in b.set_P) == 1 + b.constr_stick_to_line = pyo.Constraint(rule=rule_constr_stick_to_line) # ********************************************************************* # ********************************************************************* - # import flow costs and export flow revenues - def rule_constr_trans_monetary_flows(b): + # import flow costs and export flow revenues (y equation) + def rule_constr_y_equation(b): if (g,l) in b.parent_block().set_GL_imp: return ( - sum(b.var_trans_flows_s[s]*b.param_p_s[s] - for s in b.set_S) + sum(b.var_weights_p[p]*b.param_y_p[p] for p in b.set_P) == b.parent_block().var_ifc_glqpk[(g,l,q,p,k)] ) else: return ( - sum(b.var_trans_flows_s[s]*b.param_p_s[s] - for s in b.set_S) + sum(b.var_weights_p[p]*b.param_y_p[p] for p in b.set_P) == b.parent_block().var_efr_glqpk[(g,l,q,p,k)] ) - b.constr_trans_monetary_flows = pyo.Constraint( - rule=rule_constr_trans_monetary_flows - ) + b.constr_y_equation = pyo.Constraint(rule=rule_constr_y_equation) + + # imported and exported flows (x equation) + def rule_constr_x_equation(b): + if (g,l) in b.parent_block().set_GL_imp: + return ( + sum(b.var_weights_p[p]*b.param_x_p[p] for p in b.set_P) + == b.var_trans_flows + ) + else: + return ( + sum(b.var_weights_p[p]*b.param_x_p[p] for p in b.set_P) + == b.var_trans_flows + ) + b.constr_x_equation = pyo.Constraint(rule=rule_constr_x_equation) - # imported and exported flows - def rule_constr_trans_flows(b): + # imported and exported flows (system equation) + def rule_constr_sys_equation(b): if (g,l) in b.parent_block().set_GL_imp: return sum( b.parent_block().var_v_glljqk[(g,l,l_star,j,q,k)] for l_star in b.parent_block().set_L[g] if l_star not in b.parent_block().set_L_imp[g] for j in b.parent_block().set_J[(g,l,l_star)] # only directed arcs - ) == sum(b.var_trans_flows_s[s] for s in b.set_S) + ) == b.var_trans_flows else: return sum( b.parent_block().var_v_glljqk[(g,l_star,l,j,q,k)] @@ -120,8 +137,8 @@ def price_lambda_block( for l_star in b.parent_block().set_L[g] if l_star not in b.parent_block().set_L_exp[g] for j in b.parent_block().set_J[(g,l_star,l)] # only directed arcs - ) == sum(b.var_trans_flows_s[s] for s in b.set_S) - b.constr_trans_flows = pyo.Constraint(rule=rule_constr_trans_flows) + ) == b.var_trans_flows + b.constr_sys_equation = pyo.Constraint(rule=rule_constr_sys_equation) # ********************************************************************* # ********************************************************************* @@ -129,52 +146,10 @@ def price_lambda_block( # non-convex price functions if not b.param_price_function_is_convex: - # delta variables - b.var_active_segment_s = pyo.Var(b.set_S, within=pyo.Binary) - - # segments must be empty if the respective delta variable is zero - def rule_constr_empty_segment_if_delta_zero(b, s): - if len(b.set_S) == 1 or b.param_price_function_is_convex: - # single segment, skip - # convex, skip - return pyo.Constraint.Skip - return ( - b.var_trans_flows_s[s] <= - b.param_v_max_s[s]*b.var_active_segment_s[s] - ) - b.constr_empty_segment_if_delta_zero = pyo.Constraint( - b.set_S, rule=rule_constr_empty_segment_if_delta_zero - ) - - # if delta var is one, previous ones must be one too - # if delta var is zero, the next ones must also be zero - def rule_constr_delta_summing_logic(b, s): - if s == len(b.set_S)-1 or b.param_price_function_is_convex: - # last segment, skip - # convex, skip - return pyo.Constraint.Skip - return ( - b.var_active_segment_s[s] >= - b.var_active_segment_s[s+1] - ) - b.constr_delta_summing_logic = pyo.Constraint( - b.set_S, rule=rule_constr_delta_summing_logic - ) - - # if a segment is not completely used, the next ones must remain empty - def rule_constr_fill_up_segment_before_next(b, s): - if s == len(b.set_S)-1 or b.param_price_function_is_convex: - # last segment, skip - # convex, skip - return pyo.Constraint.Skip - return ( - b.var_trans_flows_s[s] >= - b.var_active_segment_s[s+1]* - b.param_v_max_s[s] - ) - b.constr_fill_up_segment_before_next = pyo.Constraint( - b.set_S, rule=rule_constr_fill_up_segment_before_next - ) + # declare SOS2 + def rule_constr_sos2_weights(b): + return [b.var_weights_p[p] for p in b.set_P] + b.constr_sos2_weights = pyo.SOSConstraint(rule=rule_constr_sos2_weights, sos=2) # ********************************************************************* # ********************************************************************* diff --git a/src/topupopt/problems/esipp/blocks/prices.py b/src/topupopt/problems/esipp/blocks/prices.py index f4f946902c9cc7c569fa566a6e19c40a0b902a9f..4951fc78c17af35574e75bfbbf1b704c6b4f6b14 100644 --- a/src/topupopt/problems/esipp/blocks/prices.py +++ b/src/topupopt/problems/esipp/blocks/prices.py @@ -3,6 +3,7 @@ import pyomo.environ as pyo from .other import price_other_block, price_other_no_block from .delta import price_delta_block, price_delta_no_block +from .lambda_ import price_lambda_block, price_lambda_no_block # ***************************************************************************** # ***************************************************************************** @@ -29,7 +30,7 @@ def add_price_functions( if use_blocks: # with blocks if node_price_model == NODE_PRICE_LAMBDA: - raise NotImplementedError + price_lambda_block(model, **kwargs) elif node_price_model == NODE_PRICE_DELTA: price_delta_block(model, **kwargs) else: @@ -37,7 +38,7 @@ def add_price_functions( else: # without blocks if node_price_model == NODE_PRICE_LAMBDA: - raise NotImplementedError + price_lambda_no_block(model, **kwargs) elif node_price_model == NODE_PRICE_DELTA: price_delta_no_block(model, **kwargs) else: diff --git a/src/topupopt/problems/esipp/utils.py b/src/topupopt/problems/esipp/utils.py index bffc0b94be97a0c9892c9a3420fb92e2a8eece82..9c5cdbdf4102ae37ff2af1dd0b74f1274a076b6d 100644 --- a/src/topupopt/problems/esipp/utils.py +++ b/src/topupopt/problems/esipp/utils.py @@ -98,7 +98,7 @@ def statistics(ipp: InfrastructurePlanningProblem, ) if prices_in_block: # imports - if ipp.node_price_model == NODE_PRICE_DELTA: + if ipp.node_price_model == NODE_PRICE_DELTA or ipp.node_price_model == NODE_PRICE_LAMBDA: # imports imports_qpk = { qpk: pyo.value( @@ -152,7 +152,7 @@ def statistics(ipp: InfrastructurePlanningProblem, } else: # not in a block - if ipp.node_price_model == NODE_PRICE_DELTA: + if ipp.node_price_model == NODE_PRICE_DELTA or ipp.node_price_model == NODE_PRICE_LAMBDA: # imports imports_qpk = { qpk: pyo.value( diff --git a/tests/test_esipp_prices.py b/tests/test_esipp_prices.py index 2652bd64451e91152af4a5861a561a0042e524b8..39dbeb938f72ec5d6dba80d6b35dba247d88a877 100644 --- a/tests/test_esipp_prices.py +++ b/tests/test_esipp_prices.py @@ -221,6 +221,7 @@ class TestESIPPProblem: # no sos, regular time intervals ipp = build_solve_ipp( + solver='scip' if node_price_model == NODE_PRICE_LAMBDA else 'glpk', solver_options={}, perform_analysis=False, plot_results=False, # True, @@ -351,6 +352,7 @@ class TestESIPPProblem: # no sos, regular time intervals ipp = build_solve_ipp( + solver='scip' if node_price_model == NODE_PRICE_LAMBDA else 'glpk', solver_options={}, perform_analysis=False, plot_results=False, # True, @@ -680,6 +682,7 @@ class TestESIPPProblem: # no sos, regular time intervals ipp = build_solve_ipp( + solver='scip' if node_price_model == NODE_PRICE_LAMBDA else 'glpk', solver_options={}, perform_analysis=False, plot_results=False, # True,