odow / SDDP.jl

Stochastic Dual Dynamic Programming in Julia
https://sddp.dev
Other
289 stars 60 forks source link

Use historical data to train a model? #583

Closed jdkworld closed 1 year ago

jdkworld commented 1 year ago

Hi Oscar,

I am still working on optimizing an energy system with photovoltaics, grid power, electrolyser, hydrogen storage and hydrogen fueling station for cars using SDDP. I have implemented stochasticity for the hydrogen demand.

Now I want to add stochasticity for solar irradiation / PV generation. Solar irradiation can be modeled as a Markov Chain but it is very complex to identify the right model. Therefore, I prefer to simply use the previous 5 years of solar data to sample from. How can I train the model using historical data?

This is my current model:

model = SDDP.LinearPolicyGraph(
    stages = length(optdata.timestamp),
    sense = :Min,
    lower_bound = 0,
    optimizer = HiGHS.Optimizer,
) do sp, t
    # State variables
    @variable(sp, 0 <= m_st <= 110, SDDP.State, initial_value = 10)
    # Control variables
    @variables(sp, begin
        0.0 <= mdot_el <= 2.84
        0 <= mdot_magic <= 40
        P_grid_imp >= 0
        P_grid_exp <= 0
        mdot_exp
    end)
    # Transition function and constraints
    @expressions(sp, begin
        P_el, -64 * mdot_el + 4 #-P_el == mdot_el * 39.4 / 0.6 + 0.64
        P_comp450, -5.8 * mdot_el
    end)
    @constraints(sp, begin
        m_st.out == m_st.in + mdot_el + mdot_exp + mdot_magic
        (P_grid_imp + P_grid_exp) + optdata.P_pv[t] + P_el + P_comp450 + mdot_exp * 2.54 == 0
    end)
    P = [h2uncertainty.prob0[t], h2uncertainty.prob4[t]]
    SDDP.parameterize(sp, [0, -4], P) do w
        JuMP.fix(mdot_exp, w)
    end
    # Stage-objective
    @stageobjective(sp, optdata.C_dayahead[t] * (P_grid_imp + P_grid_exp) + optdata.C_fixedgrid[t] * P_grid_imp + 200 * mdot_magic)
end

So I want to replace optdata.P_pv[t] by a stochastic variable.

I thought of doing the following:

P = [0.2, 0.2, 0.2, 0.2, 0.2]
W = [pvdata.year1[t], pvdata.year2[t], pvdata.year3[t], pvdata.year4[t], pvdata.year5[t]]
SDDP.parameterize(sp, W, P) do w
    JuMP.fix(P_pv, w)
end

However, this is not possible since solar irradiation data is stagewise-dependent.

Potential alternative options:

Thanks, Josien

odow commented 1 year ago

You can use the sampling_scheme argument to train: https://odow.github.io/SDDP.jl/stable/apireference/#SDDP.train. So something like:

scenarios = [
    [(t, w) for (t, w) in enumerate(pvdata.year1)],
    [(t, w) for (t, w) in enumerate(pvdata.year2)],
]
SDDP.train(model; sampling_scheme = SDDP.Historical(scenarios))

This might not be doing what you think it's doing though. The model is still assuming that the radiation is independent between weeks. And you'll just keep resampling the same 5 trajectories, which means that the policy will perform poorly if you simulate it out-of-sample on a future year's realization.

If you just have five years of weekly data, and you haven't fitted a statistical model, then multistage stochastic optimization is probably the wrong framework.

Thinking about it though, solar radiation probably is independent from week to week? Have to attempted to quantify the level of inter-week correlation in the data? I'd train with the defaults, and then simulate the 5 historical scenarios to see how they perform.

jdkworld commented 1 year ago

Thank you for your answer, Oscar.

I now managed to get 20 years of historical solar data. My solar data is on an hourly resolution and I would like to run SDDP on an hourly resolution, i.e. not weekly. Solar irradiation is (kind of) independent between weather spells, but the problem is that these weather spells can last anywhere from a few hours to a few weeks. So it is hard to separate them.

Just to clarify your answer: if I use SDDP.Historical(scenarios), the training algorithm will still "jump" between scenarios when going from one timestep to the next? I.e. the hours will still be considered stagewise-independent?

After reading you answer and reading more deeply into SDDP, I start to understand the principles more (please correct me if I am wrong). In basic SDDP, only stagewise-independent uncertainty is possible. Stagewise-dependent uncertainty can be included when the system is modeled as a Markov chain, and this can be optimized with SDDP. (This is good, because solar irradiation can be modeled as Markov process!). Furthermore, there are a number of "tricks" to include stagewise-independence, such as auto-regression and objective states.

Unfortunately I do not have the time to do a Markov solar model, but I will try out several heuristic methods that may not be optimal, but still better than what exists:

odow commented 1 year ago

if I use SDDP.Historical(scenarios), the training algorithm will still "jump" between scenarios when going from one timestep to the next? I.e. the hours will still be considered stagewise-independent?

So there are two parts to the training:

  1. Picking points in state-space at which to refine the value function
  2. Refining the value function

If you use Historical, then we'll use it to pick good points in state-space for (1), but we'll still assume stagewise independence and ignore the historical samples for (2).

In basic SDDP, only stagewise-independent uncertainty is possible. Stagewise-dependent uncertainty can be included when the system is modeled as a Markov chain, and this can be optimized with SDDP. (This is good, because solar irradiation can be modeled as Markov process!). Furthermore, there are a number of "tricks" to include stagewise-independence, such as auto-regression and objective states.

Correct. See https://ieeexplore.ieee.org/abstract/document/9546644 for something that is close to what you're after.

but I will try out several heuristic methods that may not be optimal, but still better than what exists:

I would just train with the defaults, and then simulate your 20 years of historical data to see how the policy performs.