Skip to content

Commit 1773088

Browse files
committed
Add functions for validation of data preparation for rolling horizon
Add validation of input in the rolling horizon function. Add function to prepare the input to get in the rolling horizon and to get out. Add function to save the solution in the relevant tables. Part of #1365
1 parent e77f50b commit 1773088

File tree

2 files changed

+234
-6
lines changed

2 files changed

+234
-6
lines changed

src/rolling-horizon/rolling-horizon.jl

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export run_rolling_horizon
22

33
include("create.jl")
44
include("update.jl")
5+
include("utils.jl")
56

67
"""
78
energy_problem = run_rolling_horizon(
@@ -56,10 +57,12 @@ function run_rolling_horizon(
5657
show_log = true,
5758
save_rolling_solution = false,
5859
)
59-
## TODO: Replace TODOs with the actual implementation of the functions
60-
6160
# Validation that the input data must satisfy to run rolling horizon
62-
## TODO: Call function to validate rolling horizon input
61+
@timeit to "Validate rolling horizon input" validate_rolling_horizon_input(
62+
connection,
63+
move_forward,
64+
opt_window_length,
65+
)
6366

6467
horizon_length = get_single_element_from_query_and_ensure_its_only_one(
6568
DuckDB.query(connection, "SELECT max(timestep) FROM profiles_rep_periods"),
@@ -93,7 +96,12 @@ function run_rolling_horizon(
9396
)
9497
]
9598

96-
## TODO: Call function to prepare table for rolling horizon to simulate different input size
99+
@timeit to "Prepare table for rolling horizon" prepare_rolling_horizon_tables!(
100+
connection,
101+
variable_tables,
102+
save_rolling_solution,
103+
opt_window_length,
104+
)
97105

98106
energy_problem = @timeit to "Create internal EnergyProblem for rolling horizon" EnergyProblem(
99107
connection;
@@ -151,7 +159,15 @@ function run_rolling_horizon(
151159
break
152160
end
153161

154-
## TODO: Call function to save window solution
162+
@timeit to "Save window solution" save_solution_into_tables!(
163+
energy_problem,
164+
variable_tables,
165+
window_id,
166+
move_forward,
167+
window_start,
168+
horizon_length,
169+
save_rolling_solution,
170+
)
155171

156172
energy_problem.solved = false
157173
end
@@ -164,7 +180,10 @@ function run_rolling_horizon(
164180
full_energy_problem.rolling_horizon_energy_problem = energy_problem
165181

166182
# Undo the changes to rep_periods_data and year_data
167-
## TODO: Call function to undo change to input tables
183+
@timeit to "undo changes to rolling horizon tables" prepare_tables_to_leave_rolling_horizon!(
184+
connection,
185+
variable_tables,
186+
)
168187

169188
# Export solution
170189
if output_folder != ""

src/rolling-horizon/utils.jl

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
2+
"""
3+
validate_rolling_horizon_input(connection, move_forward, opt_window)
4+
5+
Validation of the rolling horizon input:
6+
- opt_window_length ≥ move_forward
7+
- Only one representative period per year
8+
- Only 'uniform' partitions are allowed
9+
- Only partitions that exactly divide opt_window_length are allowed
10+
"""
11+
function validate_rolling_horizon_input(connection, move_forward, opt_window_length)
12+
@assert opt_window_length >= move_forward
13+
for row in DuckDB.query(
14+
connection,
15+
"SELECT year, max(rep_period) as num_rep_periods
16+
FROM rep_periods_data
17+
GROUP BY year",
18+
)
19+
@assert row.num_rep_periods == 1
20+
end
21+
partition_tables = [
22+
row.table_name for row in
23+
DuckDB.query(connection, "FROM duckdb_tables() WHERE table_name LIKE '%_partitions'")
24+
]
25+
26+
for table_name in partition_tables
27+
for row in DuckDB.query(connection, "FROM $table_name")
28+
@assert row.specification == "uniform" "Only 'uniform' specification is accepted"
29+
partition = tryparse(Int, row.partition)
30+
@assert !isnothing(partition) "Invalid partition"
31+
@assert opt_window_length % partition == 0
32+
end
33+
end
34+
35+
return
36+
end
37+
38+
"""
39+
prepare_rolling_horizon_tables!(connection, variable_tables, save_rolling_solution, opt_window_length)
40+
41+
Modify and create tables to prepare to start the rolling horizon execution.
42+
The changes are:
43+
44+
- Stored the original variable tables as `full_var_%` per variable table
45+
- Add `solution` column to each full variable table.
46+
- If `save_rolling_solution`, create the `rolling_solution_var_%` tables per variable table.
47+
- Backup `rep_periods_data` and `year_data` into `full_rep_periods_data` and `full_year_data`.
48+
- Modify `rep_periods_data` and `year_data` to use `opt_window_length` as `num_timesteps`/`length`, respectively.
49+
"""
50+
function prepare_rolling_horizon_tables!(
51+
connection,
52+
variable_tables,
53+
save_rolling_solution,
54+
opt_window_length,
55+
)
56+
# Preparing the table to save the rolling solution
57+
for table in variable_tables
58+
# Add a column solution to no-rolling variable tables
59+
DuckDB.execute(connection, "ALTER TABLE $table ADD COLUMN IF NOT EXISTS solution FLOAT8")
60+
61+
if save_rolling_solution
62+
# Save solutions with a table linking the window to the id of the (no-rolling) variable
63+
DuckDB.execute(
64+
connection,
65+
"""
66+
CREATE OR REPLACE TABLE rolling_solution_$table (
67+
window_id INTEGER,
68+
var_id INTEGER,
69+
solution FLOAT8,
70+
);
71+
""",
72+
)
73+
end
74+
end
75+
76+
# Create backup tables for rep_periods_data, year_data, and asset_milestone
77+
# and full tables for the variables
78+
backup_tables = ["rep_periods_data", "year_data"]
79+
for table_name in [backup_tables; variable_tables]
80+
DuckDB.query(
81+
connection,
82+
"CREATE OR REPLACE TABLE full_$table_name AS
83+
SELECT *, NULL AS solution
84+
FROM $table_name",
85+
)
86+
end
87+
88+
# Modify tables that keep horizon information to limit the horizon to the rolling window
89+
DuckDB.query(connection, "UPDATE rep_periods_data SET num_timesteps = $opt_window_length")
90+
DuckDB.query(connection, "UPDATE year_data SET length = $opt_window_length")
91+
92+
return
93+
end
94+
95+
"""
96+
save_solution_into_tables!(energy_problem, variable_tables, window_id, move_forward, window_start, horizon_length, save_rolling_solution)
97+
98+
Save the current rolling horizon solution from the model into the connection.
99+
This involves:
100+
- Calling [`save_solution!`](@ref) to copy the internal solution from the JuMP model to the connection.
101+
- Copying the solution from the internal variables to the full variables for the `move_forward` sub-window.
102+
- If `save_rolling_solution`, save the complete rolling solution in the table `rolling_solution_var_%` per variable table.
103+
"""
104+
function save_solution_into_tables!(
105+
energy_problem,
106+
variable_tables,
107+
window_id,
108+
move_forward,
109+
window_start,
110+
horizon_length,
111+
save_rolling_solution,
112+
)
113+
@timeit to "Save internal rolling horizon solution to connection" save_solution!(
114+
energy_problem,
115+
compute_duals = false,
116+
)
117+
# Save rolling solution of each variable
118+
for table_name in variable_tables
119+
# Guessing which columns should be used as key for matching with the larger no-rolling variables
120+
# TODO: Use the schema for this matching?
121+
key_columns = [
122+
row.column_name for row in DuckDB.query(
123+
energy_problem.db_connection,
124+
"""
125+
FROM duckdb_columns
126+
WHERE table_name = '$table_name'
127+
AND column_name IN ('asset', 'from_asset', 'to_asset',
128+
'milestone_year', 'commission_year', 'year', 'rep_period')
129+
""",
130+
)
131+
]
132+
133+
# Construct the WHERE condition matching the keys
134+
where_condition =
135+
join(["full_$table_name.$key = $table_name.$key" for key in key_columns], " AND ")
136+
137+
# Save solution to full table
138+
DuckDB.query(
139+
energy_problem.db_connection,
140+
"""
141+
UPDATE full_$table_name
142+
SET solution = $table_name.solution
143+
FROM $table_name WHERE $where_condition
144+
AND full_$table_name.time_block_start = ($(window_start - 1) + $table_name.time_block_start - 1) % $horizon_length + 1
145+
AND $table_name.time_block_end <= $move_forward -- only save the move_forward window
146+
""",
147+
)
148+
149+
if save_rolling_solution
150+
# Store the solution in the corresponding rolling_solution_$table_name
151+
# This also uses the `where_condition`, but to join the no-rolling and rolling variable tables
152+
DuckDB.query(
153+
energy_problem.db_connection,
154+
"""
155+
WITH cte_var_solution AS (
156+
SELECT
157+
$window_id as window_id,
158+
full_$table_name.id as var_id, -- the ids are from the main model
159+
$table_name.solution
160+
FROM $table_name
161+
LEFT JOIN full_$table_name
162+
ON $where_condition -- this condition should match
163+
AND full_$table_name.time_block_start = ($(window_start - 1) + $table_name.time_block_start - 1) % $horizon_length + 1
164+
)
165+
INSERT INTO rolling_solution_$table_name
166+
SELECT *
167+
FROM cte_var_solution
168+
""",
169+
)
170+
end
171+
end
172+
173+
return
174+
end
175+
176+
"""
177+
prepare_tables_to_leave_rolling_horizon!(connection, variable_tables)
178+
179+
Undo some of the changes done by [`prepare_rolling_horizon_tables`] to go back to the original input data.
180+
This involves:
181+
- Revert `rep_periods_data` and `year_data` to their original values.
182+
- Drop the internal variable tables and replace them with the full variable tables.
183+
"""
184+
function prepare_tables_to_leave_rolling_horizon!(connection, variable_tables)
185+
DuckDB.query(
186+
connection,
187+
"UPDATE rep_periods_data
188+
SET num_timesteps = full_rep_periods_data.num_timesteps
189+
FROM full_rep_periods_data
190+
WHERE rep_periods_data.year = full_rep_periods_data.year
191+
AND rep_periods_data.rep_period = full_rep_periods_data.rep_period",
192+
)
193+
DuckDB.query(
194+
connection,
195+
"UPDATE year_data
196+
SET length = full_year_data.length
197+
FROM full_year_data
198+
WHERE year_data.year = full_year_data.year
199+
",
200+
)
201+
202+
# Drop the rolling horizon variable tables and rename the full_var_% tables
203+
for table_name in variable_tables
204+
DuckDB.query(connection, "DROP TABLE IF EXISTS $table_name")
205+
DuckDB.query(connection, "ALTER TABLE full_$table_name RENAME TO $table_name")
206+
end
207+
208+
return
209+
end

0 commit comments

Comments
 (0)