Search code examples
constraintspyomo

Pyomo constraint issue: not returning constrained result


I setup a constraint that does not constraint the solver in pyomo.

The constraint is the following:

def revenue_positive(model,t):
        for t in model.T:
            return (model.D[t] * model.P[t]) >= 0
    
    model.positive_revenue = Constraint(model.T, rule=revenue_positive)

while the model parameters are:

model = ConcreteModel()
model.T = Set(doc='quarter of year', initialize=df.index.tolist(), ordered=True)
model.P = Param(model.T, initialize=df['price'].to_dict(), within=Any, doc='Price for each quarter')

model.C = Var(model.T, domain=NonNegativeReals)
model.D = Var(model.T, domain=NonNegativeReals)

income = sum(df.loc[t, 'price'] * model.D[t] for t in model.T)
expenses = sum(df.loc[t, 'price'] * model.C[t] for t in model.T)
profit = income - expenses
model.objective = Objective(expr=profit, sense=maximize)
    
# Solve the model
solver = SolverFactory('cbc')
solver.solve(model)

df dataframe is:

df                   time_stamp  price Status  imbalance  Difference Situation  ... week month  hour_of_day  day_of_week  day_of_year  yearly_quarter
quarter                                                                     ...                                                                  
0       2021-01-01 00:00:00  64.84  Final         16          -3   Deficit  ...   00     1            0            4            1               1
1       2021-01-01 00:15:00  13.96  Final         38           2   Surplus  ...   00     1            0            4            1               1
2       2021-01-01 00:30:00  12.40  Final         46           1   Surplus  ...   00     1            0            4            1               1
3       2021-01-01 00:45:00   7.70  Final         65          14   Surplus  ...   00     1            0            4            1               1
4       2021-01-01 01:00:00  64.25  Final          3          -9   Deficit  ...   00     1            1            4            1               1

The objective is to constraint the solver not to accept a negative revenue. As such it does not work as the solver passes 6 negative revenue values through. Looking at the indices with negative revenue, it appears the system chooses to sell at a negative price to buy later at a price even "more" negative, so from an optimization standpoint, it is ok. I would like to check the difference in results if we prohibit the solver to do that. Any input is welcome as after many searches on the web, still not the right way to write it correctly.

I did a pprint() of the constraint that returned:

positive_revenue : Size=35040, Index=T, Active=True

UPDATE following new constraint code:

def revenue_positive(model,t):
        return model.D[t] * model.P[t] >= 0
        
model.positive_revenue = Constraint(model.T, rule=revenue_positive)

Return the following error:

ERROR: Rule failed when generating expression for constraint positive_revenue
    with index 283: ValueError: Invalid constraint expression. The constraint
    expression resolved to a trivial Boolean (True) instead of a Pyomo object.
    Please modify your rule to return Constraint.Feasible instead of True.

    Error thrown for Constraint 'positive_revenue[283]'
ERROR: Constructing component 'positive_revenue' from data=None failed:
    ValueError: Invalid constraint expression. The constraint expression
    resolved to a trivial Boolean (True) instead of a Pyomo object. Please
    modify your rule to return Constraint.Feasible instead of True.

    Error thrown for Constraint 'positive_revenue[283]'
Traceback (most recent call last):
  File "/home/olivier/Desktop/Elia - BESS/run_imbalance.py", line 25, in <module>
    results_df = optimize_year(df)
  File "/home/olivier/Desktop/Elia - BESS/battery_model_imbalance.py", line 122, in optimize_year
    model.positive_revenue = Constraint(model.T, rule=revenue_positive)
  File "/home/olivier/anaconda3/lib/python3.9/site-packages/pyomo/core/base/block.py", line 542, in __setattr__
    self.add_component(name, val)
  File "/home/olivier/anaconda3/lib/python3.9/site-packages/pyomo/core/base/block.py", line 1087, in add_component
    val.construct(data)
  File "/home/olivier/anaconda3/lib/python3.9/site-packages/pyomo/core/base/constraint.py", line 781, in construct
    self._setitem_when_not_present(
  File "/home/olivier/anaconda3/lib/python3.9/site-packages/pyomo/core/base/indexed_component.py", line 778, in _setitem_when_not_present
    obj.set_value(value)
  File "/home/olivier/anaconda3/lib/python3.9/site-packages/pyomo/core/base/constraint.py", line 506, in set_value
    raise ValueError(
ValueError: Invalid constraint expression. The constraint expression resolved to a trivial Boolean (True) instead of a Pyomo object. Please modify your rule to return Constraint.Feasible instead of True.

Error thrown for Constraint 'positive_revenue[283]'

Solution

  • So there are 2 issues w/ your constraint. It isn't clear if one is a cut & paste issue or not.

    1. The function call to make the constraint appears to be indented and inside of your function after the return statement, making it unreachable code. Could be just the spacing in your post.

    2. You are incorrectly adding a loop inside of your function. You are passing in the parameter t as a function argument and then you are blowing it away with the for loop, which only executes for the first value of t in T then hits the return statement. Remove the loop. When you use the rule= structure in pyomo it will call the rule for each instance of the set that you are using in the Constraint(xx, rule=) structure.

    So I think you should have:

    def revenue_positive(model, t):
      return model.D[t] * model.P[t] >= 0
        
    model.positive_revenue = Constraint(model.T, rule=revenue_positive)
    

    Updated re: the error you added.

    The error cites the 283rd index. My bet is that price[283] is zero, so you are multiplying by a zero and killing your variable.

    You could add a check within the function that checks if the price is zero, and in that case, just return pyo.Constraint.Feasible, which is the trivial return that doesn't influence the model (or crash)