diff --git a/src/topupopt/problems/esipp/blocks/lambda_.py b/src/topupopt/problems/esipp/blocks/lambda_.py index 7cb66668206fc10acc26a501ad92a148f8ef9256..4a2d7223328d519f038aaddf4765eee2fc109cb6 100644 --- a/src/topupopt/problems/esipp/blocks/lambda_.py +++ b/src/topupopt/problems/esipp/blocks/lambda_.py @@ -49,29 +49,29 @@ def price_lambda_block( # ********************************************************************* # set of points - b.set_P = pyo.Set(initialize=[i for i in range(len(b.set_S)+1)]) + b.set_O = 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, + def init_param_x_o(b, o): + return 0 if o == 0 else sum(b.param_v_max_s[i] for i in range(o)) + b.param_x_o = pyo.Param( + b.set_O, within=pyo.NonNegativeReals, - initialize=init_param_x_p + initialize=init_param_x_o ) # 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] + def init_param_y_o(b, o): + return 0 if o == 0 else (b.param_x_o[o]-b.param_x_o[o-1])*b.param_p_s[o-1]+b.param_y_o[o-1] # sum(b.param_p_s[i] for i in range(p+1)) - b.param_y_p = pyo.Param( - b.set_P, + b.param_y_o = pyo.Param( + b.set_O, within=pyo.NonNegativeReals, - initialize=init_param_y_p + initialize=init_param_y_o ) # interpoint weights - b.var_weights_p = pyo.Var(b.set_P, within=pyo.UnitInterval) + b.var_weights_o = pyo.Var(b.set_O, within=pyo.UnitInterval) # TODO: consider replacing None in the bounds with inf in the parameter @@ -87,7 +87,7 @@ def price_lambda_block( # ********************************************************************* def rule_constr_stick_to_line(b): - return sum(b.var_weights_p[p] for p in b.set_P) == 1 + return sum(b.var_weights_o[p] for p in b.set_O) == 1 b.constr_stick_to_line = pyo.Constraint(rule=rule_constr_stick_to_line) # ********************************************************************* @@ -97,12 +97,12 @@ def price_lambda_block( def rule_constr_y_equation(b): if (g,l) in b.parent_block().set_GL_imp: return ( - sum(b.var_weights_p[p]*b.param_y_p[p] for p in b.set_P) + sum(b.var_weights_o[p]*b.param_y_o[p] for p in b.set_O) == b.parent_block().var_ifc_glqpk[(g,l,q,p,k)] ) else: return ( - sum(b.var_weights_p[p]*b.param_y_p[p] for p in b.set_P) + sum(b.var_weights_o[p]*b.param_y_o[p] for p in b.set_O) == b.parent_block().var_efr_glqpk[(g,l,q,p,k)] ) b.constr_y_equation = pyo.Constraint(rule=rule_constr_y_equation) @@ -111,12 +111,12 @@ def price_lambda_block( 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) + sum(b.var_weights_o[p]*b.param_x_o[p] for p in b.set_O) == b.var_trans_flows ) else: return ( - sum(b.var_weights_p[p]*b.param_x_p[p] for p in b.set_P) + sum(b.var_weights_o[p]*b.param_x_o[p] for p in b.set_O) == b.var_trans_flows ) b.constr_x_equation = pyo.Constraint(rule=rule_constr_x_equation) @@ -148,7 +148,7 @@ def price_lambda_block( # declare SOS2 def rule_constr_sos2_weights(b): - return [b.var_weights_p[p] for p in b.set_P] + return [b.var_weights_o[p] for p in b.set_O] b.constr_sos2_weights = pyo.SOSConstraint(rule=rule_constr_sos2_weights, sos=2) # ********************************************************************* @@ -180,8 +180,6 @@ def price_lambda_no_block( enable_initialisation: bool = True ): - raise NotImplementedError - # auxiliary set for pyomo model.set_GLQPK = model.set_GL_exp_imp*model.set_QPK @@ -208,24 +206,21 @@ def price_lambda_no_block( # parameters # resource prices - model.param_p_glqpks = pyo.Param(model.set_GLQPKS, within=pyo.NonNegativeReals) # price function convexity - model.param_price_function_is_convex = pyo.Param( model.set_GLQPK, within=pyo.Boolean ) # maximum resource volumes for each prices - model.param_v_max_glqpks = pyo.Param( model.set_GLQPKS, within=pyo.NonNegativeReals, # default=math.inf ) - + # ************************************************************************* # ************************************************************************* @@ -233,131 +228,166 @@ def price_lambda_no_block( # ************************************************************************* # ************************************************************************* + + # set of points + def init_set_O(m, g, l, q, p, k): + return [i for i in range(len(m.set_S[(g,l,q,p,k)])+1)] + # return 0 if o == 0 else sum(m.param_v_max_glqpks[i] for i in range(o)) + model.set_O = pyo.Set( + model.set_GLQPK, + initialize=(init_set_O if enable_initialisation else None) + ) + + + # set of GLQKS tuples + def init_set_GLQPKO(m): + return ( + (g, l, q, p, k, o) + # for (g,l) in m.set_GL_exp_imp + # for (q,k) in m.set_QK + for (g, l, q, p, k) in m.set_O + for o in m.set_O[(g, l, q, p, k)] + ) + model.set_GLQPKO = pyo.Set( + dimen=6, initialize=(init_set_GLQPKO if enable_initialisation else None) + ) + + # set of x points + def init_param_x_glqpko(m, g, l, q, p, k, o): + return 0 if o == 0 else sum(m.param_v_max_glqpks[(g,l,q,p,k,i)] for i in range(o)) + model.param_x_glqpko = pyo.Param( + model.set_GLQPKO, + within=pyo.NonNegativeReals, + initialize=init_param_x_glqpko + ) + + # set of y points + def init_param_y_glqpko(m, g, l, q, p, k, o): + return ( + 0 + if o == 0 else + (m.param_x_glqpko[(g,l,q,p,k,o)] + -m.param_x_glqpko[(g,l,q,p,k,o-1)])* + m.param_p_glqpks[(g,l,q,p,k,o-1)]+ + m.param_y_glqpko[(g,l,q,p,k,o-1)] + ) + model.param_y_glqpko = pyo.Param( + model.set_GLQPKO, + within=pyo.NonNegativeReals, + initialize=init_param_y_glqpko + ) - # import and export flows - def bounds_var_trans_flows_glqpks(m, g, l, q, p, k, s): - # return (0, m.param_v_max_glqpks[(g, l, q, p, k, s)]) - if (g, l, q, p, k, s) in m.param_v_max_glqpks: - # predefined finite capacity - return (0, m.param_v_max_glqpks[(g, l, q, p, k, s)]) - else: - # infinite capacity + # interpoint weights + model.var_weights_glqpko = pyo.Var( + model.set_GLQPKO, + within=pyo.UnitInterval + ) + + # TODO: consider replacing None in the bounds with inf in the parameter + + # transshipment flows + def bounds_var_trans_flows_glqpk(m, g, l, q, p, k): + try: + return (0, sum(m.param_v_max_glqpks[(g, l, q, p, k, s)] + for s in m.set_S[(g, l, q, p, k)])) + except Exception: return (0, None) - model.var_trans_flows_glqpks = pyo.Var( - model.set_GLQPKS, within=pyo.NonNegativeReals, bounds=bounds_var_trans_flows_glqpks - ) + model.var_trans_flows_glqpk = pyo.Var( + model.set_GLQPK, + bounds=bounds_var_trans_flows_glqpk + ) # ************************************************************************* # ************************************************************************* + + def rule_constr_stick_to_line(m, g, l, q, p, k): + return sum(m.var_weights_glqpko[(g,l,q,p,k,o)] + for o in m.set_O[(g,l,q,p,k)]) == 1 + model.constr_stick_to_line = pyo.Constraint( + model.set_GLQPK, + rule=rule_constr_stick_to_line + ) + + # ************************************************************************* + # ************************************************************************* - # import flow costs and export flow revenues - def rule_constr_trans_monetary_flows(m, g, l, q, p, k): + # import flow costs and export flow revenues (y equation) + def rule_constr_y_equation(m, g, l, q, p, k): if (g,l) in m.set_GL_imp: return ( - sum( - m.var_trans_flows_glqpks[(g, l, q, p, k, s)] - * m.param_p_glqpks[(g, l, q, p, k, s)] - for s in m.set_S[(g, l, q, p, k)] - ) - == m.var_ifc_glqpk[(g, l, q, p, k)] + sum(m.var_weights_glqpko[(g,l,q,p,k,o)]* + m.param_y_glqpko[(g,l,q,p,k,o)] + for o in m.set_O[(g,l,q,p,k)]) + == m.var_ifc_glqpk[(g,l,q,p,k)] ) else: return ( - sum( - m.var_trans_flows_glqpks[(g, l, q, p, k, s)] - * m.param_p_glqpks[(g, l, q, p, k, s)] - for s in m.set_S[(g, l, q, p, k)] - ) - == m.var_efr_glqpk[(g, l, q, p, k)] + sum(m.var_weights_glqpko[(g,l,q,p,k,o)]* + m.param_y_glqpko[(g,l,q,p,k,o)] + for o in m.set_O[(g,l,q,p,k)]) + == m.var_efr_glqpk[(g,l,q,p,k)] ) - model.constr_trans_monetary_flows = pyo.Constraint( - model.set_GLQPK, rule=rule_constr_trans_monetary_flows - ) + model.constr_y_equation = pyo.Constraint( + model.set_GLQPK, + rule=rule_constr_y_equation + ) - # imported and exported flows - def rule_constr_trans_flows(m, g, l, q, p, k): + # imported and exported flows (x equation) + def rule_constr_x_equation(m, g, l, q, p, k): + if (g,l) in m.set_GL_imp: + return ( + sum(m.var_weights_glqpko[(g,l,q,p,k,o)]*m.param_x_glqpko[(g,l,q,p,k,o)] for o in m.set_O[(g,l,q,p,k)]) + == m.var_trans_flows_glqpk[(g,l,q,p,k)] + ) + else: + return ( + sum(m.var_weights_glqpko[(g,l,q,p,k,o)]*m.param_x_glqpko[(g,l,q,p,k,o)] for o in m.set_O[(g,l,q,p,k)]) + == m.var_trans_flows_glqpk[(g,l,q,p,k)] + ) + model.constr_x_equation = pyo.Constraint( + model.set_GLQPK, + rule=rule_constr_x_equation + ) + + # imported and exported flows (system equation) + def rule_constr_sys_equation(m, g, l, q, p, k): if (g,l) in m.set_GL_imp: return sum( - m.var_v_glljqk[(g, l, l_star, j, q, k)] + m.var_v_glljqk[(g,l,l_star,j,q,k)] for l_star in m.set_L[g] if l_star not in m.set_L_imp[g] - for j in m.set_J[(g, l, l_star)] # only directed arcs - ) == sum(m.var_trans_flows_glqpks[(g, l, q, p, k, s)] for s in m.set_S[(g, l, q, p, k)]) + for j in m.set_J[(g,l,l_star)] # only directed arcs + ) == m.var_trans_flows_glqpk[(g,l,q,p,k)] else: return sum( - m.var_v_glljqk[(g, l_star, l, j, q, k)] - * m.param_eta_glljqk[(g, l_star, l, j, q, k)] + m.var_v_glljqk[(g,l_star,l,j,q,k)] + * m.param_eta_glljqk[(g,l_star,l,j,q,k)] for l_star in m.set_L[g] if l_star not in m.set_L_exp[g] - for j in m.set_J[(g, l_star, l)] # only directed arcs - ) == sum(m.var_trans_flows_glqpks[(g, l, q, p, k, s)] for s in m.set_S[(g, l, q, p, k)]) - - model.constr_trans_flows = pyo.Constraint( - model.set_GLQPK, rule=rule_constr_trans_flows - ) + for j in m.set_J[(g,l_star,l)] # only directed arcs + ) == m.var_trans_flows_glqpk[(g,l,q,p,k)] + model.constr_sys_equation = pyo.Constraint( + model.set_GLQPK, + rule=rule_constr_sys_equation + ) # ************************************************************************* # ************************************************************************* # non-convex price functions - # TODO: remove these variables from the model if they are not needed - # delta variables - model.var_active_segment_glqpks = pyo.Var( - model.set_GLQPKS, within=pyo.Binary - ) - - # segments must be empty if the respective delta variable is zero - def rule_constr_empty_segment_if_delta_zero(m, g, l, q, p, k, s): - if len(m.set_S[(g,l,q,p,k)]) == 1 or m.param_price_function_is_convex[(g,l,q,p,k)]: - # single segment, skip - # convex, skip - return pyo.Constraint.Skip - return ( - m.var_trans_flows_glqpks[(g,l,q,p,k,s)] <= - m.param_v_max_glqpks[(g,l,q,p,k,s)]* - m.var_active_segment_glqpks[(g,l,q,p,k,s)] - ) - model.constr_empty_segment_if_delta_zero = pyo.Constraint( - model.set_GLQPKS, 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(m, g, l, q, p, k, s): - if s == len(m.set_S[(g,l,q,p,k)])-1 or m.param_price_function_is_convex[(g,l,q,p,k)]: - # last segment, skip - # convex, skip - return pyo.Constraint.Skip - return ( - m.var_active_segment_glqpks[(g,l,q,p,k,s)] >= - m.var_active_segment_glqpks[(g,l,q,p,k,s+1)] - ) - model.constr_delta_summing_logic = pyo.Constraint( - model.set_GLQPKS, 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(m, g, l, q, p, k, s): - if s == len(m.set_S[(g,l,q,p,k)])-1 or m.param_price_function_is_convex[(g,l,q,p,k)]: - # last segment, skip - # convex, skip - return pyo.Constraint.Skip - return ( - m.var_trans_flows_glqpks[(g,l,q,p,k,s)] >= - m.var_active_segment_glqpks[(g,l,q,p,k,s+1)]* - m.param_v_max_glqpks[(g,l,q,p,k,s)] - ) - # return ( - # m.var_if_glqpks[(g,l,q,p,k,s)]/m.param_v_max_glqpks[(g,l,q,p,k,s)] >= - # m.var_active_segment_glqpks[(g,l,q,p,k,s+1)] - # ) - # return ( - # m.param_v_max_glqpks[(g,l,q,p,k,s)]-m.var_if_glqpks[(g,l,q,p,k,s)] <= - # m.param_v_max_glqpks[(g,l,q,p,k,s)]*(1- m.var_active_segment_glqpks[(g,l,q,p,k,s+1)]) - # ) - model.constr_fill_up_segment_before_next = pyo.Constraint( - model.set_GLQPKS, rule=rule_constr_fill_up_segment_before_next - ) + + # declare SOS2 + def rule_constr_sos2_weights(m, g, l, q, p, k): + if m.param_price_function_is_convex[(g,l,q,p,k)]: + return pyo.SOSConstraint.Skip + return [m.var_weights_glqpko[(g,l,q,p,k,o)] for o in m.set_O[(g,l,q,p,k)]] + model.constr_sos2_weights = pyo.SOSConstraint( + model.set_GLQPK, + rule=rule_constr_sos2_weights, + sos=2) + + # ************************************************************************* + # ************************************************************************* # ***************************************************************************** # ***************************************************************************** \ No newline at end of file diff --git a/tests/test_esipp_prices.py b/tests/test_esipp_prices.py index 39dbeb938f72ec5d6dba80d6b35dba247d88a877..813fc0eb311b712ef6b4b85f62869e6e1cc45f74 100644 --- a/tests/test_esipp_prices.py +++ b/tests/test_esipp_prices.py @@ -163,8 +163,7 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* - # TODO: test a nonconvex price function in which only the first segment is used - # e.g. using a flow 0.5 in the following example + @pytest.mark.parametrize( "use_prices_block, node_price_model", [(True, NODE_PRICE_OTHER),