Background references: [1] Generating constraints with conditional summing over several pyomo subsets
[2] Debugging why some requests are unexpectedly not satisfied in pyomo
I am getting a correct solution for the problem detailed above with the solutions provided by @Airsquid, however I am facing long runtimes for the case where there is a small amount of energy supply available (relative to the number of requests).
I have added some early stopping settings, which is fine for the majority of cases as I don't necessarily need the totally optimal solution just one which is good enough. solver.options = { 'sec': 600, 'threads':6, 'ratio': 5} #early stopping settings
I wanted to try to refactor the code per the suggestion given by @Airsquid in [2]: "In that case, you "know everything about the request" just from the start variable. So, you could do a big refactor and probably get rid of some of the helper variables. If it starts in period 5 and the request is for 10 periods with a certain fixed amount of power, You don't need a variable to know if it is running in period 8--it is! Similarly for the allocation within the period. You'd still need a variable to keep track of how much power is allocated to each request."
Since I am still new to linear programming and pyomo, I am having some problems with formulating the syntax for this.
I have only been able to think of two approaches to do this:
Approach 1: Define the value of the m.dispatch[t,r] variable between the time when m.start[t,r] == 1 and m.start[t+request_length_periods,r], a crude attempt at the syntax for this is below! I know this is not correct and why it is not correct but I don't know what the solution for it is.
@m.Constraint(m.windows_flat)
def request_length(m, t, r):
request_length_periods = int(m.duration_periods[r])
request_periods = set(list(range(t,t+request_length_periods+1)))
return (m.dispatch[t,r] == m.request_power_size[r] for t in request_periods if m.start[t,r] == 1)
This unsuprisingly gives me an error:
ERROR: Rule failed when generating expression for Constraint request_length
with index (910192, 88): ValueError: Constraint 'request_length[910192,88]'
does not have a proper value. Found '<generator object
generalOptimisation.<locals>.request_length.<locals>.<genexpr> at
0x168022b20>' Expecting a tuple or relational expression. Examples:
sum(model.costs) == model.income (0, model.price[item], 50)
ERROR: Constructing component 'request_length' from data=None failed:
ValueError: Constraint 'request_length[910192,88]' does not have a proper
value. Found '<generator object
I know how I have defined request_periods is not correct, but I don't know how to define this set without knowing when m.start[t,r] == 1?
Approach 2: m.start[t,r] *(m.dispatch[t,r]...m.dispatch[t+request_length_periods,r]) == 1 (I don't know what the syntax is for this in Pyomo and I know this would make the solution nonlinear)
If anyone has any suggestions on how to formulate this correctly per @Airsquid's suggestion, I would be very grateful, currently I am planning to relax the early stopping further to work around this which is not entirely desirable.
Any input on the early stopping settings is also welcome - perhaps the system time out at 600 seconds is not realistic!
Here's a refactor of your original code that uses 1 binary variable for the dispatch. There's a couple of things in here that could probably be refactored a bit, but it shows one approach to this. It also makes the assumption that the power delivered to each request is constant across periods.
Captured a rather unique result after a few pings (shown)
Code
Output [truncated]