I am making use of CVXPY and CVXOPT to solve a problem that allows us to control the running of motors in the cheapest possible ways.
We have existing constraints which consist of, cost, volume, and level which need to be maintained (all of which work, and is only to give a further definition of the problem at hand). We use this to minimize the cost and provide an optimized approach.
Recently, we wish to add a new constraint that will allow for a cool-off period. Which we can set. So if the selection value is 1, the next two periods should be a 0 if this makes sense. Essentially, if the pump runs for a period it should be off for the next two to cool down.
Please see below for a current function that is being used. Currently, no cool-off constraint has been implemented, and wish to get a push in the right direction of how best to implement such a solution.
def optimiser(self, cost_, volume_,v_min,flow_,min_level,max_level,initial_level,period_lengths,out_flow_, errors) :
"""
This function optimizes the regime possible combinations using convex optimization. We assign and define the problem and uses `GLPK_MI` to solve our problem.
Parameters
----------
cost_
Numpy Array
volume_
Numpy Array
v_min
Float -> minimum volume
flow_
Numpy Array
min_level
Float -> minimum level
max_level
Float -> maximum level
initial_level
Float -> initial level
period_lengths
Integer -> number of period lengths
out_flow_
Numpy Array
errors
Exceptions -> error handling
Returns
----------
selection
Solved problem with the best possible combination for pumping regime.
"""
FACTOR = 0.001*1800*self.RESERVOIR_VOLUME
input_flow_matrix=np.zeros((max(period_lengths),len(period_lengths)))
for i,l in enumerate(period_lengths):
input_flow_matrix[:l,i]=1
selection = cp.Variable(shape=cost_.shape,boolean=True)
assignment_constraint = cp.sum(selection,axis=1) == 1
input_flow_= cp.sum(cp.multiply(flow_,selection),axis=1)
input_flow_vector=cp.vec(cp.multiply(input_flow_matrix,np.ones((max(period_lengths), 1)) @ cp.reshape(input_flow_,(1,len(period_lengths)))))
res_flow= (input_flow_vector-cp.vec(out_flow_))
res_level=cp.cumsum(res_flow) * FACTOR + initial_level
volume_= cp.sum(cp.multiply(volume_,selection))
volume_constraint = volume_ >= v_min
min_level_constraint = res_level >= min_level
max_level_constraint = res_level <= max_level
if errors is LevelTooLowError:
constraints = [assignment_constraint, max_level_constraint, volume_constraint]
elif errors is LevelTooHighError:
constraints = [assignment_constraint, min_level_constraint, volume_constraint]
elif errors is MaxVolumeExceededError:
constraints = [assignment_constraint, min_level_constraint, volume_constraint]
else:
constraints = [assignment_constraint, max_level_constraint, min_level_constraint, volume_constraint]
cost_ = cp.sum(cp.multiply(cost_,selection))
assign_prob = cp.Problem(cp.Minimize(cost_),constraints)
assign_prob.solve(solver=cp.GLPK_MI, verbose=False)
return selection
Sample of data which we work with
Here is our cost_
array
[[0. 0.8 ]
[0. 0.8 ]
[0. 0.8 ]
[0. 0.8 ]
[0. 0.8 ]
[0. 0.8 ]
[0. 0.8 ]
[0. 0.8 ]
[0. 0.9 ]
[0. 0.9 ]
[0. 0.9 ]
[0. 0.9 ]
[0. 0.9 ]
[0. 0.9 ]
[0. 0.9 ]
[0. 0.9 ]
[0. 1.35]
[0. 1.35]
[0. 1.35]
[0. 1.8 ]
[0. 1.2 ]]
And here is our volume_
array
[[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 34200.]
[ 0. 51300.]
[ 0. 51300.]
[ 0. 51300.]
[ 0. 68400.]
[ 0. 51300.]]
I hope my problem has been well defined for anyone to understand what I wish to do at the moment. If anything else is needed, I can happily provide this for anyone.
Thanks in advance.
Something like this is sometimes used in power generation. A generator may have to cool down before allowing it to be turned on again.
Let's assume we have a binary variable x[t]
indicating if the generator is "on" at period t i.e.
x[t] = 1 if machine is "on"
0 "off"
Then we want to require:
if x[t-1]=1 and x[t]=0 => x[t+1] = 0
This reads as: if turned off at t, then also stay off at t+1. This can be written as a linear constraint:
x[t+1] <= x[t] - x[t-1] + 1 for all periods t
If we read "if the pump runs for a period it should be off for the next two to cool down" as indicating that in addition, the maximum up-time is 1, then we can simply write:
x[t-1] + x[t] + x[t+1] <= 1 for all t
This will allow patterns like [0,0,0],[1,0,0],[0,1,0],[0,0,1]
but forbids patterns like [1,1,1],[0,1,1],[1,1,0],[1,0,1]
.