New to pyomo and to linear optimisation generally. I have an energy allocation problem where I have a fixed amount of energy over a timeframe, I have defined the relevant timeframe as a set:
model.t = pyomo.Set(initialize = (supply_unix_ts.index))
And the total amount of energy available for each time period is defined as a parameter:
model.available_supply = pyomo.Param(model.t, initialize = supply_unix_ts.to_dict(), domain=pyomo.NonNegativeReals)
I then have some requests to consume the available energy defined as another set:
model.r = pyomo.Set(initialize = request_ids)
Each request has some specific parameters associated with it, specifically:
- The total amount of energy needed (kWh)
- The maximum power the request can be provided with (kW)
- The earliest time that the request can start to receive energy and the latest time the request can receive energy by (this interval is different for each request and is a subset of t)
model.request_earliest_start_time = pyomo.Param(model.r, initialize = request_earliest_start_times_dict, domain=pyomo.NonNegativeReals) #parameter for earliest start times
model.request_latest_end_time = pyomo.Param(model.r, initialize = request_latest_end_time_dict, domain=pyomo.NonNegativeReals) #parameter for latest end times
model.request_max_power_dict = pyomo.Param(model.r, initialize = request_max_power_dict, domain=pyomo.NonNegativeReals) #parameter for max power
model.request_energy_needed_dict = pyomo.Param(model.r, initialize = request_energy_needed_dict, domain=pyomo.NonNegativeReals)
I also have another parameter for the sampling period which I need to convert between power and energy in some places to follow.
model.sampling_period_dict = pyomo.Param(model.t, initialize = sampling_period_dict, domain=pyomo.NonNegativeReals)
My objective is to maximise the utilisation of the available energy, not all requests need to be satisfied.
def obj_rule(model):
return sum(model.booked_supply[t] for t in model.t())
model.obj = pyomo.Objective(rule = obj_rule, sense = pyomo.maximize)
The decision variables I have are as follows, two of which are binary:
model.booked_supply = pyomo.Var(model.t, domain = pyomo.NonNegativeReals) #total supply that is used for a specific time
model.request_status = pyomo.Var(model.t, model.r, domain = pyomo.Binary) #if request is allocated energy during time period t
model.request_satisfied = pyomo.Var(model.r, domain = pyomo.Binary) #if request is fully satisfied
I have implemented a constraint to ensure that the booked supply (model.booked_supply) cannot be greater than the available supply for each time period (model.available_supply), and a constraint to set the booked supply equal to the sum of the requests satisfied for each time period.
The problem that I am having is implementing a constraint to say that the total amount of energy allocated to a request should be equal to the total amount of energy needed by that request (model.request_energy_needed_dict), within the interval specified by the request (i.e. between model.request_earliest_start_time and model.request_latest_end_time) i.e. this constraint: Math form of constraint
Where [E_r,L_r] is the time interval relevant for the request.
I have tried looking into lots of different approaches but without any success, since I am new I am unclear about both what the best approach is, and the required syntax to implement this (and if I have defined everything correctly in pyomo).
My two main approaches have been:
Instead of defining [E_r and L_r] (i.e. model.request_earliest_start_time and model.request_latest_end_time ) as parameters, instead try to define these as subsets of the model.t Set and then implement an individual Constraint for each request using the relevant subset. The problem with this is that I have potentially 1000s of requests, I have seen it is possible to dynamically create constraints using ConstraintList but have not seen a way to dynamically create large numbers of individual sets like a SetList (doesn't seem to exist?).
I hoped it would be possible to implement this in a single constraint, but so far have struggled to write down how this should work. Some very naive attempts are below which have generated various errors...
def requestMaxEnergyConstraint(model, t, r):
return sum(model.request_confirmed_power[r,t]*model.request_satisfied[r,t]*model.sampling_period_dict[t] for t in model.t if (t > model.request_earliest_start_time[r] and t < model.request_latest_end_time[r]))== model.request_max_energy_dict[r]*model.request_satisfied[r]
def requestMaxEnergyConstraint(model, t, r):
maxEnergy=0
for r in model.r:
for t in model.t:
if((t > model.request_earliest_start_time[r,t]) and t < (model.request_earliest_start_time[r,t])):
maxEnergy += model.request_confirmed_power[r,t]
return maxEnergy == model.request_max_energy_dict[r]*model.request_satisfied[r]
Any advice (ideally a worked example) would be really appreciated on how this can be achieved.
Here are a couple of concepts that I think will help you out....
First, I'm quite sure you are going to need to index your power delivery variable by both request and timeslice because you will need to know in your constraints how much power is going to individual requests in each timeslice.
You should also use an indexed set, which is kinda like a python dictionary, where you have a set of sets that is indexed by a set, in your case, you could go for the set of periods by request or vice-versa. Or alternatively, you could just hold the min/max period in 2 different parameters, and create a list of eligible time periods within a constraint by filtering or such.
If you go the way shown below, then you can make a flat set of just the request - time slot pairs that are "legal" for consideration. This has a HUGE benefit if your model grows because you will be omitting all of the ineligible pairs within the power supply variable, which will be "sparse" instead of the full
R x Tset.Anyhow, there are probably a couple variants of this, but this gets it done pretty efficiently:
Code:
Output: