Search code examples
pythonmodelinglinear-programmingpyomo

Pyomo optimal solution found but number of solutions 0 (hydropower simulation)


I am looking to simulate the hydropower generation in Norway, taking into account all the cascade dependencies (for an hourly resolution). To beginn with I have set up an optimisation problem in Pyomo for a set of three plants and three reservoirs for one day. For the moment I want to maximise profit over all hours and all plant/reservoirs calculated as: electricity price[$/MWh]*turbine output[MW]*3600[s=1h] + volume[m3]*energy equivalent[MWh/m3]*electricity price[$/MWh]

My constraints are:

Hourly load = generated power must equal load at each hour

Water balance = storage levels updated properly at each timestep

Start and end storage levels = at 60% of max storage a beginning and end of simulation period (this is in the water balance constraint)

QUESTIONS:

1) why does the print statement print out 0.0 for cascade_inflow and discharged_flow: because this is printed while the model is created and not while solving it?

2A) Termination condition is optimal and I have a value for the objective function, but number of solutions is 0: I think the problem lies in the constraint water balance, I will post the results further down for 6 hours. Do I need to somehow set upper and lower bound contraints for the water balance? edit: if I run the script from the command line with pyomo solve --solver=glpk script.py input.dat I get a solution displayed..?!

2B) The water constraint function does not behave properly. If I look at the results then the jump in volume from time-step 1 to 2 is not feasible. What is wrong there? Is the way I am adding the cascade flows incorrect, or does the variable m.volume just do what it wants?

3) Might it be a better idea to create a network flow problem? There are example codes for these kinds of problems in the Pyomo Gallery. But I am not sure yet how to model the nodes as reservoirs. (I will most probably make a new post for this as soon as I have tried to implement a script).

4) (This is my first post: anything I did wrong or should do better next time?)

Code (reading parameters left out)

from pyomo.environ import *
from pyomo.opt import SolverFactory

opt = SolverFactory("glpk")

# Initiate model instance
m = AbstractModel()

# Define the variable power for each time step
m.turbine = Var(m.stage, m.res, initialize=0, bounds=turbine_bounds, within=NonNegativeReals)
# Define the variable volume for each time step
m.volume = Var(m.stage, m.res, initialize=volume_Init,  bounds=volume_bounds, within=NonNegativeReals)
# Define the variable spill flow for each time step
m.spilledFlow = Var(m.stage, m.res, initialize=0, bounds=spill_bounds, within=NonNegativeReals)


# Constrain total power generated over day
def hourly_load_rule(model, t):
    return model.P_load*model.hourly_demand[t] <= sum(model.turbine[t, res] for res in model.res) <= model.P_load*model.hourly_demand[t]
m.hourly_load = Constraint(m.stage, rule=hourly_load_rule)


# Water balance equation
def water_balance_rule(model, t, r):
    if t == model.T:  # final volume is same as initial volume at 60%
        return model.volume[t, r] == model.max_Vol[r]*0.6
    elif t > 1:
        cascade_inflow = 0
        for stor in model.res:
            # connectMat is a matrix that has a 1 where there is a connection and 0 where there is not
            cascade_inflow = cascade_inflow + model.connectMat[stor, r]*(model.turbine[t, stor]/model.slope[stor]+model.spilledFlow[t, stor])
            if model.connectMat[stor, r] == 1:
                print(stor, r, t, value(cascade_inflow))  # this always prints out 0.0 for cascade_inflow
         discharged_flow = model.turbine[t, r]/model.slope[r]  # model.turbine is in MW: divide by slope to get discharge flow [m3/s]
         print(value(discharged_flow))  # this always prints out 0.0
         return model.volume[t, r] == model.volume[t-1, r]+(cascade_inflow+model.inflow[t, r]-model.spilledFlow[t, r]-discharged_flow)*model.secPerTimeStep
    else:
        # start volume constrained to 60% of max volume
        return model.volume[t, r] == model.max_Vol[r]*0.6
m.water_balance = Constraint(m.stage, m.res, rule=water_balance_rule)


# Revenue = Objective function (sum over all hours and all plants/reservoirs)
def revenue_rule(model):
    return sum(sum(model.el_price[i]*model.turbine[i, j]*model.secPerTimeStep+model.volume[i, j]*model.energy_stored[j]*model.el_price[i] for i in model.stage) for j in model.res)
m.obj = Objective(rule=revenue_rule, sense=maximize)

instance = m.create("three_input_stack.dat")

results = opt.solve(instance)

instance.display()

results.write()

Results

0.0
(1, 2, 2, 0.0)
0.0
(2, 3, 2, 0.0)
0.0
0.0
(1, 2, 3, 0.0)
0.0
(2, 3, 3, 0.0)
0.0
0.0
(1, 2, 4, 0.0)
0.0
(2, 3, 4, 0.0)
0.0
0.0
(1, 2, 5, 0.0)
0.0
(2, 3, 5, 0.0)
0.0
Model unknown

  Variables:
    turbine : Size=18, Index=turbine_index
        Key    : Lower : Value          : Upper : Fixed : Stale : Domain
        (1, 1) :     0 :           3.31 :  3.31 : False : False : NonNegativeReals
        (1, 2) :     0 :           3.71 :   5.9 : False : False : NonNegativeReals
        (1, 3) :     0 :            0.0 :   9.0 : False : False : NonNegativeReals
        (2, 1) :     0 :            0.8 :  3.31 : False : False : NonNegativeReals
        (2, 2) :     0 :            5.9 :   5.9 : False : False : NonNegativeReals
        (2, 3) :     0 :            0.0 :   9.0 : False : False : NonNegativeReals
        (3, 1) :     0 :            0.0 :  3.31 : False : False : NonNegativeReals
        (3, 2) :     0 : 0.242202133966 :   5.9 : False : False : NonNegativeReals
        (3, 3) :     0 :  6.31779786603 :   9.0 : False : False : NonNegativeReals
        (4, 1) :     0 :            0.0 :  3.31 : False : False : NonNegativeReals
        (4, 2) :     0 :            0.0 :   5.9 : False : False : NonNegativeReals
        (4, 3) :     0 :            6.5 :   9.0 : False : False : NonNegativeReals
        (5, 1) :     0 :            0.0 :  3.31 : False : False : NonNegativeReals
        (5, 2) :     0 :         1.9665 :   5.9 : False : False : NonNegativeReals
        (5, 3) :     0 :         4.6535 :   9.0 : False : False : NonNegativeReals
        (6, 1) :     0 :           3.31 :  3.31 : False : False : NonNegativeReals
        (6, 2) :     0 :           3.83 :   5.9 : False : False : NonNegativeReals
        (6, 3) :     0 :            0.0 :   9.0 : False : False : NonNegativeReals
    volume : Size=18, Index=volume_index
        Key    : Lower : Value         : Upper      : Fixed : Stale : Domain
        (1, 1) :   0.0 :    39600000.0 : 66000000.0 : False : False : NonNegativeReals
        (1, 2) :   0.0 :    10020000.0 : 16700000.0 : False : False : NonNegativeReals
        (1, 3) :   0.0 :     1260000.0 :  2100000.0 : False : False : NonNegativeReals
        (2, 1) :   0.0 : 32149783.0468 : 66000000.0 : False : False : NonNegativeReals
        (2, 2) :   0.0 : 16684216.9532 : 16700000.0 : False : False : NonNegativeReals
        (2, 3) :   0.0 :     2100000.0 :  2100000.0 : False : False : NonNegativeReals
        (3, 1) :   0.0 : 32167783.0468 : 66000000.0 : False : False : NonNegativeReals
        (3, 2) :   0.0 :    16700000.0 : 16700000.0 : False : False : NonNegativeReals
        (3, 3) :   0.0 :     2100000.0 :  2100000.0 : False : False : NonNegativeReals
        (4, 1) :   0.0 : 32185783.0468 : 66000000.0 : False : False : NonNegativeReals
        (4, 2) :   0.0 :    16700000.0 : 16700000.0 : False : False : NonNegativeReals
        (4, 3) :   0.0 :     2100000.0 :  2100000.0 : False : False : NonNegativeReals
        (5, 1) :   0.0 : 32203783.0468 : 66000000.0 : False : False : NonNegativeReals
        (5, 2) :   0.0 :    16700000.0 : 16700000.0 : False : False : NonNegativeReals
        (5, 3) :   0.0 :     2100000.0 :  2100000.0 : False : False : NonNegativeReals
        (6, 1) :   0.0 :    39600000.0 : 66000000.0 : False : False : NonNegativeReals
        (6, 2) :   0.0 :    10020000.0 : 16700000.0 : False : False : NonNegativeReals
        (6, 3) :   0.0 :     1260000.0 :  2100000.0 : False : False : NonNegativeReals
    spilledFlow : Size=18, Index=spilledFlow_index
        Key    : Lower : Value         : Upper   : Fixed : Stale : Domain
        (1, 1) :   0.0 :             0 : 10000.0 : False :  True : NonNegativeReals
        (1, 2) :   0.0 :             0 : 10000.0 : False :  True : NonNegativeReals
        (1, 3) :   0.0 :             0 : 10000.0 : False :  True : NonNegativeReals
        (2, 1) :   0.0 : 2069.67087236 : 10000.0 : False : False : NonNegativeReals
        (2, 2) :   0.0 : 213.332062039 : 10000.0 : False : False : NonNegativeReals
        (2, 3) :   0.0 :           0.0 : 10000.0 : False : False : NonNegativeReals
        (3, 1) :   0.0 :           0.0 : 10000.0 : False : False : NonNegativeReals
        (3, 2) :   0.0 :           0.0 : 10000.0 : False : False : NonNegativeReals
        (3, 3) :   0.0 :           0.0 : 10000.0 : False : False : NonNegativeReals
        (4, 1) :   0.0 :           0.0 : 10000.0 : False : False : NonNegativeReals
        (4, 2) :   0.0 :           5.0 : 10000.0 : False : False : NonNegativeReals
        (4, 3) :   0.0 : 4.22222222222 : 10000.0 : False : False : NonNegativeReals
        (5, 1) :   0.0 :           0.0 : 10000.0 : False : False : NonNegativeReals
        (5, 2) :   0.0 :           0.0 : 10000.0 : False : False : NonNegativeReals
        (5, 3) :   0.0 : 5.86355555556 : 10000.0 : False : False : NonNegativeReals
        (6, 1) :   0.0 :             0 : 10000.0 : False :  True : NonNegativeReals
        (6, 2) :   0.0 :             0 : 10000.0 : False :  True : NonNegativeReals
        (6, 3) :   0.0 :             0 : 10000.0 : False :  True : NonNegativeReals

  Objectives:
    obj : Size=1, Index=None, Active=True
        Key  : Active : Value
        None :   True : 39250323.6272

  Constraints:
    hourly_load : Size=6
        Key : Lower : Body : Upper
          1 :  7.02 : 7.02 :  7.02
          2 :   6.7 :  6.7 :   6.7
          3 :  6.56 : 6.56 :  6.56
          4 :   6.5 :  6.5 :   6.5
          5 :  6.62 : 6.62 :  6.62
          6 :  7.14 : 7.14 :  7.14
    water_balance : Size=18
         Key    : Lower      : Body               : Upper
         (1, 1) : 39600000.0 :         39600000.0 : 39600000.0
         (1, 2) : 10020000.0 :         10020000.0 : 10020000.0
         (1, 3) :  1260000.0 :          1260000.0 :  1260000.0
         (2, 1) :        0.0 :  2.14204192162e-08 :        0.0
         (2, 2) :        0.0 : -2.23517417908e-08 :        0.0
         (2, 3) :        0.0 : -5.82076609135e-10 :        0.0
         (3, 1) :        0.0 :                0.0 :        0.0
         (3, 2) :        0.0 :  1.55250745593e-08 :        0.0
         (3, 3) :        0.0 : -7.13669123797e-09 :        0.0
         (4, 1) :        0.0 :                0.0 :        0.0
         (4, 2) :        0.0 : -7.35411731512e-11 :        0.0
         (4, 3) :        0.0 : -6.39488462184e-12 :        0.0
         (5, 1) :        0.0 :                0.0 :        0.0
         (5, 2) :        0.0 : -5.49960077478e-10 :        0.0
         (5, 3) :        0.0 : -1.79056769412e-10 :        0.0
         (6, 1) : 39600000.0 :         39600000.0 : 39600000.0
         (6, 2) : 10020000.0 :         10020000.0 : 10020000.0
         (6, 3) :  1260000.0 :          1260000.0 :  1260000.0
# ==========================================================
# = Solver Results                                         =
# ==========================================================
# ----------------------------------------------------------
#   Problem Information
# ----------------------------------------------------------
Problem: 
- Name: unknown
  Lower bound: 39250323.6272
  Upper bound: 39250323.6272
  Number of objectives: 1
  Number of constraints: 25
  Number of variables: 49
  Number of nonzeros: 89
  Sense: maximize
# ----------------------------------------------------------
#   Solver Information
# ----------------------------------------------------------
Solver: 
- Status: ok
  Termination condition: optimal
  Statistics: 
    Branch and bound: 
      Number of bounded subproblems: 0
      Number of created subproblems: 0
  Error rc: 0
  Time: 0.0750000476837
# ----------------------------------------------------------
#   Solution Information
# ----------------------------------------------------------
Solution: 
- number of solutions: 0
  number of solutions displayed: 0

Input File

param secPerTimeStep:=3600;
param T:=6;
param numReservoirs:=3;
param connectMat:=
1   1   0
1   2   1
1   3   0
2   1   0
2   2   0
2   3   1
3   1   0
3   2   0
3   3   0;
param el_price:=
1   242.16
2   242.09
3   239.3
4   231.52
5   224.25
6   219.77;
param inflow:=
1   1   5
2   1   5
3   1   5
4   1   5
5   1   5
6   1   5
1   2   5
2   2   5
3   2   5
4   2   5
5   2   5
6   2   5
1   3   5
2   3   5
3   3   5
4   3   5
5   3   5
6   3   5;
param min_Vol:=
1   0.0
2   0.0
3   0.0;
param max_Vol:=
1   66000000.0
2   16700000.0
3   2100000.0;
param min_Turb_gen:=
1   0
2   0
3   0;
param max_Turb_gen:=
1   3.31
2   5.9
3   9.0;
param min_spill:=
1   0.0
2   0.0
3   0.0;
param max_spill:=
1   10000.0
2   10000.0
3   10000.0;
param min_discharge:=
1   0.0
2   0.0
3   0.0;
param max_discharge:=
1   20.0
2   15.0
3   8.0;
param slope:=
1   0.1655
2   0.3933
3   1.125;
param energy_stored:=
1   0.000046
2   0.000109
3   0.00031;
param hourly_demand:=
1   0.0351
2   0.0335
3   0.0328
4   0.0325
5   0.0331
6   0.0357;
param P_load:=200;

Solution

  • 1) Zeros are always printed because m.tubine is initialized to 0. During model construction (which is when the print statements are executed) the expressions evaluate to 0.

    2A) The result is automatically loaded back into the model after calling the solver, so the command instance.display() is printing the results. The results object is only used if you want to delay loading the solutions (which is an option you can set when calling the solver).

    2B) Not sure. You can always run the command instance.water_balance.pprint() to confirm that the constraint expressions are what you expect them to be.