The source files for all examples can be found in /test.

Re-ordering days in a year

This script illustrates how to re-order days in a year, for example in order to go from 10 representative days to a continuous year of 365 (representative) days.

First we create an instance of a PeriodsFinder. Here we are using the default .yaml configuration file, but keep in mind that a lot of the following lines of code could be avoided by creating your own custom file.

using RepresentativePeriodsFinder;
RPF = RepresentativePeriodsFinder;
config_file = RPF.datadir("default.yaml");
pf = PeriodsFinder(config_file, populate_entries=true);
[ Info: Adding LFW...
[ Info: Interpolating missing values...
[ Info: Adding Residual Load...
[ Info: Interpolating missing values...
[ Info: Resampling...
[ Info: Adding Load...
[ Info: Interpolating missing values...
[ Info: Adding LFS...
[ Info: Interpolating missing values...

Change the result directory name.

script_name = splitext(splitdir(@__FILE__)[2])[1];
result_dir = joinpath(@__DIR__, "results", script_name);
pf.config["results"]["result_dir"] = result_dir;

Turn off automatic plot creation.

pf.config["results"]["create_plots"] = false;

We will only concern ourselves with mapping load, just to make things simple.

delete!(pf.config["time_series"], "LFW");
delete!(pf.config["time_series"], "LFS",);
delete!(pf.config["time_series"], "Residual Load");

Next we define the total number of periods and the number of representative periods. This script was run without access to a professional solver such as CPLEX or Gurobi, so we will limit ourselves to creating a "year" of 20 days. Further on we will use an alternative formulation which will be able to create a full year of 365 days.

pf.config["method"]["options"]["total_periods"] = 20;
pf.config["method"]["options"]["representative_periods"] = 10;

We define our representative periods, where each element in the vector is a day in the year.

rep_periods = [1, 3, 4, 5, 7, 12, 15, 16, 18, 19];

We populate the results of the selection variable, pf.u, with our representative days. This is the only step that (currently) cannot be done within the .yaml file.

pf.u = zeros(Bool, 20);
pf.u[rep_periods] .= 1 ;

The following lines define the selection and ordering method, specifically we will use an optimisation method which minimises only the absolute (as opposed to squared) time series error.

delete!(pf.config["method"], "clustering");
delete!(pf.config["method"]["optimization"], "duration_curve_error");
pf.config["method"]["optimization"]["time_series_error"]["type"] = "absolute";

We set binary ordering to false, which means that a non-representative day can be represented by a linear combination of representative days.

pf.config["method"]["optimization"]["binary_ordering"] = false;

We set the mandatory periods to be selected to our representative periods.

pf.config["method"]["options"]["mandatory_periods"] = rep_periods;

Setup the optimizer and solve to re-order the days. Importantly don't forget to pass the keyword argument reset=false!

using Ipopt, JuMP;
opt = optimizer_with_attributes(Ipopt.Optimizer, "max_iter" => 100);
find_representative_periods(pf, optimizer=opt, reset=false);
┌ Warning: Weight for error ord_err_1 is 0 and it will not impact period selection.
└ @ RepresentativePeriodsFinder /builds/UCM/representativeperiodsfinder.jl/src/util/get.jl:128

******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit https://github.com/coin-or/Ipopt
******************************************************************************

This is Ipopt version 3.14.4, running with linear solver MUMPS 5.4.1.

Number of nonzeros in equality constraint Jacobian...:      420
Number of nonzeros in inequality constraint Jacobian.:    10780
Number of nonzeros in Lagrangian Hessian.............:        0

Total number of variables............................:      690
                     variables with only lower bounds:      690
                variables with lower and upper bounds:        0
                     variables with only upper bounds:        0
Total number of equality constraints.................:       31
Total number of inequality constraints...............:     1181
        inequality constraints with only lower bounds:      970
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:      211

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  1.2555188e+01 1.99e+01 3.04e+00  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  1.5161980e+01 1.86e+01 1.25e+01  -1.0 2.02e+00    -  5.12e-03 6.64e-02f  1
   2  1.9563632e+01 1.67e+01 1.12e+01  -1.0 2.76e+00    -  7.04e-02 9.88e-02f  1
   3  3.2460555e+01 1.24e+01 8.34e+00  -1.0 2.72e+00    -  2.62e-01 2.58e-01f  1
   4  5.1853264e+01 7.84e+00 5.27e+00  -1.0 2.33e+00    -  4.58e-01 3.69e-01f  1
   5  6.7258695e+01 5.52e+00 5.94e+00  -1.0 1.97e+00    -  5.01e-01 2.96e-01f  1
   6  1.0312496e+02 2.09e+00 9.95e+00  -1.0 8.94e-01    -  8.35e-01 6.21e-01f  1
   7  1.5742099e+02 1.97e-02 8.06e-01  -1.0 4.91e-01    -  9.81e-01 9.91e-01f  1
   8  5.1926780e+01 1.79e-03 4.48e+02  -1.7 5.91e-01    -  6.73e-01 9.09e-01f  1
   9  4.1378251e+01 2.77e-09 2.00e-07  -1.7 4.51e-01    -  1.00e+00 1.00e+00f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  10  2.1600149e+01 2.04e-09 4.87e+00  -2.5 3.03e-01    -  8.06e-01 7.66e-01f  1
  11  1.7720571e+01 3.86e-10 9.00e+01  -2.5 1.33e-01    -  7.15e-01 8.11e-01f  1
  12  1.6879957e+01 7.19e-10 1.01e+02  -2.5 6.33e-02    -  6.49e-01 1.00e+00f  1
  13  1.6873420e+01 4.35e-10 2.83e-08  -2.5 1.89e-02    -  1.00e+00 1.00e+00f  1
  14  1.4514591e+01 1.42e-10 1.34e+01  -3.8 9.35e-02    -  6.05e-01 6.74e-01f  1
  15  1.4063704e+01 2.34e-10 3.66e+00  -3.8 1.25e-01    -  5.91e-01 4.39e-01f  1
  16  1.3689554e+01 7.83e-11 2.12e+01  -3.8 1.07e-01    -  6.91e-01 6.65e-01f  1
  17  1.3506395e+01 2.31e-10 1.49e+00  -3.8 3.69e-02    -  8.61e-01 1.00e+00f  1
  18  1.3506027e+01 7.11e-15 1.50e-09  -3.8 9.95e-03    -  1.00e+00 1.00e+00f  1
  19  1.3377581e+01 3.51e-11 2.24e-01  -5.7 1.66e-02    -  7.19e-01 7.27e-01f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  20  1.3346898e+01 2.78e-11 5.65e+00  -5.7 9.97e-03    -  8.67e-01 6.53e-01f  1
  21  1.3332143e+01 2.29e-12 7.57e+04  -5.7 6.28e-03    -  1.13e-01 9.18e-01f  1
  22  1.3330838e+01 2.08e-11 1.85e-11  -5.7 6.38e-04    -  1.00e+00 1.00e+00f  1
  23  1.3330829e+01 7.11e-15 1.85e-11  -5.7 7.50e-04    -  1.00e+00 1.00e+00h  1
  24  1.3329027e+01 7.04e-12 4.94e+00  -8.6 5.21e-04    -  9.10e-01 8.40e-01f  1
  25  1.3328791e+01 4.83e-11 1.14e+01  -8.6 6.20e-04    -  8.39e-01 6.88e-01f  1
  26  1.3328705e+01 9.07e-12 8.91e+00  -8.6 2.29e-04    -  5.35e-01 8.12e-01f  1
In iteration 26, 1 Slack too small, adjusting variable bound
  27  1.3328694e+01 3.93e-12 3.90e+00  -8.6 4.36e-05    -  7.27e-01 5.67e-01f  1
In iteration 27, 1 Slack too small, adjusting variable bound
  28  1.3328688e+01 8.09e-11 1.15e+00  -8.6 1.89e-05    -  7.42e-01 7.06e-01h  1
  29  1.3328686e+01 3.55e-15 1.38e+00  -8.6 5.56e-06    -  5.43e-01 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  30  1.3328686e+01 2.15e-15 2.66e-14  -8.6 6.16e-08    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 30

                                   (scaled)                 (unscaled)
Objective...............:   1.3328686337693618e+01    1.3328686337693618e+01
Dual infeasibility......:   2.6645352591003757e-14    2.6645352591003757e-14
Constraint violation....:   2.1521381349761983e-15    2.1521381349761983e-15
Variable bound violation:   9.9189549019547981e-09    9.9189549019547981e-09
Complementarity.........:   6.5341399297206729e-09    6.5341399297206729e-09
Overall NLP error.......:   6.5341399297206729e-09    6.5341399297206729e-09


Number of objective function evaluations             = 31
Number of objective gradient evaluations             = 31
Number of equality constraint evaluations            = 31
Number of inequality constraint evaluations          = 31
Number of equality constraint Jacobian evaluations   = 1
Number of inequality constraint Jacobian evaluations = 1
Number of Lagrangian Hessian evaluations             = 1
Total seconds in IPOPT                               = 0.619

EXIT: Optimal Solution Found.

We can plot the resulting synthetic time series. Note how days 1 and 3, which are representative, match up exactly while other days must be approximated.

using Dates;
timestamps = Dict("Load" => DateTime(1970,1,1):Hour(1):DateTime(1970,1,7));
create_synthetic_time_series_plot(pf, "Load"; timestamps=timestamps)

We can also check out the heatmap, which shows how representative days are mapped to non-representative days throughout the year:

create_ordering_heatmap(pf)

We can also write out the this synthetic time series to pf.config["results"]["result_dir"].

write_out_synthetic_timeseries(pf, timestamps=timestamps)

This last optimisation was done by solving a linear problem. We can also do this by solving a binary problem, where each representative day is mapped to a non-representative day as opposed to using a linear combination of days. The advantage of this is that we can define fancier error functions.

Change the total number of days and re-define our representative periods:

pf.config["method"]["options"]["total_periods"] = 365;
rep_periods = [i for i in 1:35:350];
pf.config["method"]["options"]["mandatory_periods"] = rep_periods;
pf.u = zeros(Bool, 365);
pf.u[rep_periods] .= 1;

For reasons not worth getting into, we need to re-populate pf:

populate_entries!(pf);
[ Info: Adding Load...
[ Info: Interpolating missing values...

We get rid of the time series error entry:

delete!(pf.config["method"]["optimization"], "time_series_error");

Now we define our ordering error function, which is the error associated with representing a day x with another day y.

pf.config["method"]["options"]["ordering_error"] = Dict(
    "ord_err_1" => Dict(
        "function" => (x,y) -> sum((x .- y).^2),
        "weight" => 1.0
    )
);

Here we've used the 2-norm error, but we could have specified anything we wanted.

We set binary ordering to true:

pf.config["method"]["optimization"]["binary_ordering"] = true;

Setup the optimizer and solve to re-order the days.

using Cbc;
opt = optimizer_with_attributes(Cbc.Optimizer, "seconds" => 60);
find_representative_periods(pf, optimizer=opt, reset=false);

Let's inspect the heatmap again

result_dir = joinpath(@__DIR__, "results", script_name * "_binary");
pf.config["results"]["result_dir"] = result_dir;
create_ordering_heatmap(pf)

Note how we have solid colors now, since we don't represent days by linear combinations of representative days.


This page was generated using Literate.jl.