From 099d1b7c69d68f9e7fb0071bd4931f92f573d5c1 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 12:15:39 +0100 Subject: [PATCH 1/8] [wip] Update ramping constraint file Closes #964 --- src/constraints/create.jl | 66 ++++- .../ramping-and-unit-commitment.jl | 246 +++++++++--------- src/model-preparation.jl | 36 ++- 3 files changed, 211 insertions(+), 137 deletions(-) diff --git a/src/constraints/create.jl b/src/constraints/create.jl index d9700e52..d91218b4 100644 --- a/src/constraints/create.jl +++ b/src/constraints/create.jl @@ -15,8 +15,12 @@ function compute_constraints_indices(connection) :capacity_outgoing, :capacity_outgoing_non_investable_storage_with_binary, :capacity_outgoing_investable_storage_with_binary, + :limit_units_on, :ramping_with_unit_commitment, + :max_output_flow_with_basic_unit_commitment, + :max_ramp_with_unit_commitment, :ramping_without_unit_commitment, + :max_ramp_without_unit_commitment, :balance_storage_rep_period, :balance_storage_over_clustered_year, :min_energy_over_clustered_year, @@ -190,6 +194,13 @@ function _create_constraints_tables(connection) ", ) + DuckDB.query( + connection, + "CREATE OR REPLACE TABLE cons_limit_units_on AS + SELECT * FROM var_units_on + ", + ) + DuckDB.query( connection, "CREATE OR REPLACE TEMP SEQUENCE id START 1; @@ -202,10 +213,46 @@ function _create_constraints_tables(connection) ON t_high.asset = asset.asset WHERE asset.type in ('producer', 'conversion') - AND asset.unit_commitment = true; + AND asset.unit_commitment + ", + ) + + DuckDB.query( + connection, + "CREATE OR REPLACE TEMP SEQUENCE id START 1; + CREATE OR REPLACE TABLE cons_max_output_flow_with_basic_unit_commitment AS + SELECT + nextval('id') AS index, + t_high.* + FROM t_highest_assets_and_out_flows AS t_high + LEFT JOIN asset + ON t_high.asset = asset.asset + WHERE + asset.type in ('producer', 'conversion') + AND asset.unit_commitment + AND asset.unit_commitment_method = 'basic' + ", + ) + + DuckDB.query( + connection, + "CREATE OR REPLACE TEMP SEQUENCE id START 1; + CREATE OR REPLACE TABLE cons_max_ramp_with_unit_commitment AS + SELECT + nextval('id') AS index, + t_high.* + FROM t_highest_assets_and_out_flows AS t_high + LEFT JOIN asset + ON t_high.asset = asset.asset + WHERE + asset.type in ('producer', 'conversion') + AND asset.ramping + AND asset.unit_commitment + AND asset.unit_commitment_method = 'basic' ", ) + # TODO: Should there be a `AND NOT asset.unit_commitment` in the WHERE here? DuckDB.query( connection, "CREATE OR REPLACE TEMP SEQUENCE id START 1; @@ -220,6 +267,23 @@ function _create_constraints_tables(connection) asset.type in ('producer', 'storage', 'conversion')", ) + DuckDB.query( + connection, + "CREATE OR REPLACE TEMP SEQUENCE id START 1; + CREATE OR REPLACE TABLE cons_max_ramp_without_unit_commitment AS + SELECT + nextval('id') AS index, + t_high.* + FROM t_highest_out_flows AS t_high + LEFT JOIN asset + ON t_high.asset = asset.asset + WHERE + asset.type in ('producer', 'storage', 'conversion') + AND asset.ramping + AND asset.unit_commitment_method != 'basic' + ", + ) + DuckDB.query( connection, "CREATE OR REPLACE TABLE cons_balance_storage_rep_period AS diff --git a/src/constraints/ramping-and-unit-commitment.jl b/src/constraints/ramping-and-unit-commitment.jl index 9060b1be..df31f508 100644 --- a/src/constraints/ramping-and-unit-commitment.jl +++ b/src/constraints/ramping-and-unit-commitment.jl @@ -18,13 +18,12 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) ## unpack from constraints cons_with = constraints[:ramping_with_unit_commitment] cons_without = constraints[:ramping_without_unit_commitment] - outgoing_flow = cons_without.expressions[:outgoing] + # outgoing_flow = cons_without.expressions[:outgoing] ## Expressions used by the ramping and unit commitment constraints # - Expression to have the product of the profile and the capacity paramters profile_times_capacity = Dict( - key => begin - table_name = Symbol("ramping_$(key)_unit_commitment") + table_name => begin cons = constraints[table_name] [ profile_aggregation( @@ -37,148 +36,163 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) 1.0, ) * graph[row.asset].capacity for row in eachrow(cons.indices) ] - end for key in (:with, :without) + end for table_name in ( + :ramping_with_unit_commitment, + :ramping_without_unit_commitment, + :max_ramp_without_unit_commitment, + :max_ramp_with_unit_commitment, + :max_output_flow_with_basic_unit_commitment, + ) ) # - Flow that is above the minimum operating point of the asset - flow_above_min_operating_point = - model[:flow_above_min_operating_point] = [ - @expression( - model, - cons_with.expressions[:outgoing][row.index] - - profile_times_capacity[:with][row.index] * - graph[row.asset].min_operating_point * - cons_with.expressions[:units_on][row.index] - ) for row in eachrow(cons_with.indices) - ] + for table_name in ( + :ramping_with_unit_commitment, + :max_output_flow_with_basic_unit_commitment, + :max_ramp_with_unit_commitment, + ) + cons = constraints[table_name] + attach_expression!( + cons, + :flow_above_min_operating_point, + [ + @expression( + model, + cons.expressions[:outgoing][row.index] - + profile_times_capacity[table_name][row.index] * + graph[row.asset].min_operating_point * + cons.expressions[:units_on][row.index] + ) for row in eachrow(cons.indices) + ], + ) + end ## Unit Commitment Constraints (basic implementation - more advanced will be added in 2025) # - Limit to the units on (i.e. commitment) - # TODO: When this becomes a TulipaConstraint, attach `:limit_units_on` - model[:limit_units_on] = [ - @constraint( - model, - units_on ≤ accumulated_units[accumulated_units_lookup[(row.asset, row.year)]], - base_name = "limit_units_on[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) for (units_on, row) in - zip(variables[:units_on].container, eachrow(variables[:units_on].indices)) - ] - - # - Minimum output flow above the minimum operating point attach_constraint!( model, - cons_with, - :min_output_flow_with_unit_commitment, + constraints[:limit_units_on], + :limit_units_on, [ @constraint( model, - flow_above_min_operating_point[row.index] ≥ 0, - base_name = "min_output_flow_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) for row in eachrow(cons_with.indices) + units_on ≤ accumulated_units[accumulated_units_lookup[(row.asset, row.year)]], + base_name = "limit_units_on[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) for (units_on, row) in + zip(variables[:units_on].container, eachrow(constraints[:limit_units_on].indices)) ], ) - # - Maximum output flow above the minimum operating point + # - Minimum output flow above the minimum operating point attach_constraint!( model, cons_with, - :max_output_flow_with_unit_commitment, + :min_output_flow_with_unit_commitment, [ @constraint( model, - flow_above_min_operating_point[row.index] ≤ - (1 - graph[row.asset].min_operating_point) * - profile_times_capacity[:with][row.index] * - cons_with.expressions[:units_on][row.index], - base_name = "max_output_flow_with_basic_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) for row in eachrow(cons_with.indices) if row.asset ∈ Auc_basic + cons_with.expressions[:flow_above_min_operating_point][row.index] ≥ 0, + base_name = "min_output_flow_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) for row in eachrow(cons_with.indices) ], ) - ## Ramping Constraints with unit commitment - # Note: We start ramping constraints from the second timesteps_block - # We filter and group the dataframe per asset and representative period - df_grouped = DataFrames.groupby(cons_with.indices, [:asset, :year, :rep_period]) - - # get the units on column to get easier the index - 1, i.e., the previous one - units_on = cons_with.expressions[:units_on] - - #- Maximum ramp-up rate limit to the flow above the operating point when having unit commitment variables - for ((a, y, rp), sub_df) in pairs(df_grouped) - if !(a ∈ Ar && a ∈ Auc_basic) - continue - end - model[Symbol("max_ramp_up_with_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - flow_above_min_operating_point[row.index] - - flow_above_min_operating_point[row.index-1] ≤ - graph[row.asset].max_ramp_up * - row.min_outgoing_flow_duration * - profile_times_capacity[:with][row.index] * - units_on[row.index], - base_name = "max_ramp_up_with_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 - ] - end - - # - Maximum ramp-down rate limit to the flow above the operating point when having unit commitment variables - for ((a, y, rp), sub_df) in pairs(df_grouped) - if !(a ∈ Ar && a ∈ Auc_basic) - continue - end - model[Symbol("max_ramp_down_with_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - flow_above_min_operating_point[row.index] - - flow_above_min_operating_point[row.index-1] ≥ - -graph[row.asset].max_ramp_down * - row.min_outgoing_flow_duration * - profile_times_capacity[:with][row.index] * - units_on[row.index-1], - base_name = "max_ramp_down_with_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 - ] + # - Maximum output flow above the minimum operating point + let + table_name = :max_output_flow_with_basic_unit_commitment + cons = constraints[table_name] + attach_constraint!( + model, + cons, + table_name, + [ + @constraint( + model, + cons.expressions[:flow_above_min_operating_point][row.index] ≤ + (1 - graph[row.asset].min_operating_point) * + profile_times_capacity[table_name][row.index] * + cons.expressions[:units_on][row.index], + base_name = "max_output_flow_with_basic_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) for row in eachrow(cons.indices) + ], + ) end - ## Ramping Constraints without unit commitment - # Note: We start ramping constraints from the second timesteps_block - # We filter and group the dataframe per asset and representative period that does not have the unit_commitment methods - df_grouped = DataFrames.groupby(cons_without.indices, [:asset, :year, :rep_period]) + let + ## Ramping Constraints with unit commitment + # Note: We start ramping constraints from the second timesteps_block + # We filter and group the dataframe per asset and representative period + table_name = :max_ramp_with_unit_commitment + cons = constraints[table_name] + # get the units on column to get easier the index - 1, i.e., the previous one + units_on = cons.expressions[:units_on] + + for ((a, y, rp), sub_df) in + pairs(DataFrames.groupby(cons.indices, [:asset, :year, :rep_period])) + #- Maximum ramp-up rate limit to the flow above the operating point when having unit commitment variables + model[Symbol("max_ramp_up_with_unit_commitment_$(a)_$(y)_$(rp)")] = [ + @constraint( + model, + cons.expressions[:flow_above_min_operating_point][row.index] - + cons.expressions[:flow_above_min_operating_point][row.index-1] ≤ + graph[row.asset].max_ramp_up * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index] * + units_on[row.index], + base_name = "max_ramp_up_with_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" + ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 + ] - # - Maximum ramp-up rate limit to the flow (no unit commitment variables) - for ((a, y, rp), sub_df) in pairs(df_grouped) - if !(a ∈ Ar) || a ∈ Auc # !(a ∈ Ar \ Auc) = !(a ∈ Ar ∩ Aucᶜ) = !(a ∈ Ar && a ∉ Auc) = a ∉ Ar || a ∈ Auc - continue + # - Maximum ramp-down rate limit to the flow above the operating point when having unit commitment variables + model[Symbol("max_ramp_down_with_unit_commitment_$(a)_$(y)_$(rp)")] = [ + @constraint( + model, + cons.expressions[:flow_above_min_operating_point][row.index] - + cons.expressions[:flow_above_min_operating_point][row.index-1] ≥ + -graph[row.asset].max_ramp_down * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index] * + units_on[row.index-1], + base_name = "max_ramp_down_with_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" + ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 + ] end - model[Symbol("max_ramp_up_without_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - outgoing_flow[row.index] - outgoing_flow[row.index-1] ≤ - graph[row.asset].max_ramp_up * - row.min_outgoing_flow_duration * - profile_times_capacity[:without][row.index], - base_name = "max_ramp_up_without_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for - (k, row) in enumerate(eachrow(sub_df)) if k > 1 && outgoing_flow[row.index] != 0 - ] end - # - Maximum ramp-down rate limit to the flow (no unit commitment variables) - for ((a, y, rp), sub_df) in pairs(df_grouped) - if !(a ∈ Ar) || a ∈ Auc # !(a ∈ Ar \ Auc) = !(a ∈ Ar ∩ Aucᶜ) = !(a ∈ Ar && a ∉ Auc) = a ∉ Ar || a ∈ Auc - continue + let + table_name = :max_ramp_without_unit_commitment + cons = constraints[table_name] + ## Ramping Constraints without unit commitment + # Note: We start ramping constraints from the second timesteps_block + # We filter and group the dataframe per asset and representative period that does not have the unit_commitment methods + + for ((a, y, rp), sub_df) in + pairs(DataFrames.groupby(cons.indices, [:asset, :year, :rep_period])) + # - Maximum ramp-up rate limit to the flow (no unit commitment variables) + model[Symbol("max_ramp_up_without_unit_commitment_$(a)_$(y)_$(rp)")] = [ + @constraint( + model, + cons.expressions[:outgoing][row.index] - + cons.expressions[:outgoing][row.index-1] ≤ + graph[row.asset].max_ramp_up * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index], + base_name = "max_ramp_up_without_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" + ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 + ] + + # - Maximum ramp-down rate limit to the flow (no unit commitment variables) + model[Symbol("max_ramp_down_without_unit_commitment_$(a)_$(y)_$(rp)")] = [ + @constraint( + model, + cons.expressions[:outgoing][row.index] - + cons.expressions[:outgoing][row.index-1] ≥ + -graph[row.asset].max_ramp_down * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index], + base_name = "max_ramp_down_without_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" + ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 + ] end - model[Symbol("max_ramp_down_without_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - outgoing_flow[row.index] - outgoing_flow[row.index-1] ≥ - -graph[row.asset].max_ramp_down * - row.min_outgoing_flow_duration * - profile_times_capacity[:without][row.index], - base_name = "max_ramp_down_without_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for - (k, row) in enumerate(eachrow(sub_df)) if k > 1 && outgoing_flow[row.index] != 0 - ] end end diff --git a/src/model-preparation.jl b/src/model-preparation.jl index d14bce36..8b78446d 100644 --- a/src/model-preparation.jl +++ b/src/model-preparation.jl @@ -366,6 +366,7 @@ function add_expressions_to_constraints!( use_highest_resolution = true, multiply_by_duration = false, ) + for table_name in ( :capacity_incoming, :capacity_incoming_non_investable_storage_with_binary, @@ -390,19 +391,16 @@ function add_expressions_to_constraints!( expression_workspace, ) end - @timeit to "add_expression_terms_intra_rp_constraints!" add_expression_terms_intra_rp_constraints!( - constraints[:ramping_without_unit_commitment], - variables[:flow], - expression_workspace, - representative_periods, - graph; - use_highest_resolution = true, - multiply_by_duration = false, - add_min_outgoing_flow_duration = true, + + for table_name in ( + :ramping_without_unit_commitment, + :ramping_with_unit_commitment, + :max_ramp_with_unit_commitment, + :max_ramp_without_unit_commitment, + :max_output_flow_with_basic_unit_commitment, ) - if !isempty(constraints[:ramping_with_unit_commitment].indices) @timeit to "add_expression_terms_intra_rp_constraints!" add_expression_terms_intra_rp_constraints!( - constraints[:ramping_with_unit_commitment], + constraints[table_name], variables[:flow], expression_workspace, representative_periods, @@ -439,16 +437,14 @@ function add_expressions_to_constraints!( variables[:is_charging], expression_workspace, ) - if !isempty(constraints[:ramping_with_unit_commitment].indices) - @timeit to "add_expression_units_on_terms_intra_rp_constraints!" add_expression_units_on_terms_intra_rp_constraints!( - constraints[:ramping_with_unit_commitment], - variables[:units_on], - expression_workspace, - ) - end - if !isempty(constraints[:ramping_without_unit_commitment].indices) + for table_name in ( + :ramping_without_unit_commitment, + :ramping_with_unit_commitment, + :max_output_flow_with_basic_unit_commitment, + :max_ramp_with_unit_commitment, + ) @timeit to "add_expression_units_on_terms_intra_rp_constraints!" add_expression_units_on_terms_intra_rp_constraints!( - constraints[:ramping_without_unit_commitment], + constraints[table_name], variables[:units_on], expression_workspace, ) From 2bae6d910b81415151fc9bf775854bcfdcee1696 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 12:32:39 +0100 Subject: [PATCH 2/8] [wip] Further cleaning --- .../ramping-and-unit-commitment.jl | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/constraints/ramping-and-unit-commitment.jl b/src/constraints/ramping-and-unit-commitment.jl index df31f508..f8b6814c 100644 --- a/src/constraints/ramping-and-unit-commitment.jl +++ b/src/constraints/ramping-and-unit-commitment.jl @@ -7,19 +7,11 @@ Adds the ramping constraints for producer and conversion assets where ramping = """ function add_ramping_constraints!(model, variables, constraints, graph, sets) # unpack from sets - Ar = sets[:Ar] - Auc = sets[:Auc] - Auc_basic = sets[:Auc_basic] accumulated_units_lookup = sets[:accumulated_units_lookup] ## unpack from model accumulated_units = model[:accumulated_units] - ## unpack from constraints - cons_with = constraints[:ramping_with_unit_commitment] - cons_without = constraints[:ramping_without_unit_commitment] - # outgoing_flow = cons_without.expressions[:outgoing] - ## Expressions used by the ramping and unit commitment constraints # - Expression to have the product of the profile and the capacity paramters profile_times_capacity = Dict( @@ -69,33 +61,41 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) ## Unit Commitment Constraints (basic implementation - more advanced will be added in 2025) # - Limit to the units on (i.e. commitment) - attach_constraint!( - model, - constraints[:limit_units_on], - :limit_units_on, - [ - @constraint( - model, - units_on ≤ accumulated_units[accumulated_units_lookup[(row.asset, row.year)]], - base_name = "limit_units_on[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) for (units_on, row) in - zip(variables[:units_on].container, eachrow(constraints[:limit_units_on].indices)) - ], - ) + let + table_name = :limit_units_on + cons = constraints[table_name] + attach_constraint!( + model, + cons, + :limit_units_on, + [ + @constraint( + model, + units_on ≤ accumulated_units[accumulated_units_lookup[(row.asset, row.year)]], + base_name = "limit_units_on[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) for (units_on, row) in zip(variables[:units_on].container, eachrow(cons.indices)) + ], + ) + end # - Minimum output flow above the minimum operating point - attach_constraint!( - model, - cons_with, - :min_output_flow_with_unit_commitment, - [ - @constraint( - model, - cons_with.expressions[:flow_above_min_operating_point][row.index] ≥ 0, - base_name = "min_output_flow_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) for row in eachrow(cons_with.indices) - ], - ) + let + table_name = :ramping_with_unit_commitment + cons = constraints[table_name] + attach_constraint!( + model, + cons, + :min_output_flow_with_unit_commitment, + [ + @constraint( + model, + flow_above_min_operating_point ≥ 0, + base_name = "min_output_flow_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) for (row, flow_above_min_operating_point) in + zip(eachrow(cons.indices), cons.expressions[:flow_above_min_operating_point]) + ], + ) + end # - Maximum output flow above the minimum operating point let From ab4b414dab3e53b03dc6ad50d4f5792b14fb8fdb Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 13:39:53 +0100 Subject: [PATCH 3/8] [ramp] Remove loop over groups to use the attach_constraint function --- .../ramping-and-unit-commitment.jl | 150 +++++++++++------- 1 file changed, 94 insertions(+), 56 deletions(-) diff --git a/src/constraints/ramping-and-unit-commitment.jl b/src/constraints/ramping-and-unit-commitment.jl index f8b6814c..fb4973df 100644 --- a/src/constraints/ramping-and-unit-commitment.jl +++ b/src/constraints/ramping-and-unit-commitment.jl @@ -127,36 +127,55 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) # get the units on column to get easier the index - 1, i.e., the previous one units_on = cons.expressions[:units_on] - for ((a, y, rp), sub_df) in - pairs(DataFrames.groupby(cons.indices, [:asset, :year, :rep_period])) - #- Maximum ramp-up rate limit to the flow above the operating point when having unit commitment variables - model[Symbol("max_ramp_up_with_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - cons.expressions[:flow_above_min_operating_point][row.index] - - cons.expressions[:flow_above_min_operating_point][row.index-1] ≤ - graph[row.asset].max_ramp_up * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index] * - units_on[row.index], - base_name = "max_ramp_up_with_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 - ] + # - Maximum ramp-up rate limit to the flow above the operating point when having unit commitment variables + attach_constraint!( + model, + constraints[table_name], + :max_ramp_up_with_unit_commitment, + [ + begin + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:flow_above_min_operating_point][row.index] - + cons.expressions[:flow_above_min_operating_point][row.index-1] ≤ + graph[row.asset].max_ramp_up * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index] * + units_on[row.index], + base_name = "max_ramp_up_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) + end + end for row in eachrow(cons.indices) + ], + ) - # - Maximum ramp-down rate limit to the flow above the operating point when having unit commitment variables - model[Symbol("max_ramp_down_with_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - cons.expressions[:flow_above_min_operating_point][row.index] - - cons.expressions[:flow_above_min_operating_point][row.index-1] ≥ - -graph[row.asset].max_ramp_down * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index] * - units_on[row.index-1], - base_name = "max_ramp_down_with_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 - ] - end + # - Maximum ramp-down rate limit to the flow above the operating point when having unit commitment variables + attach_constraint!( + model, + constraints[table_name], + :max_ramp_down_with_unit_commitment, + [ + begin + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:flow_above_min_operating_point][row.index] - + cons.expressions[:flow_above_min_operating_point][row.index-1] ≥ + -graph[row.asset].max_ramp_down * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index] * + units_on[row.index-1], + base_name = "max_ramp_down_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) + end + end for row in eachrow(cons.indices) + ], + ) end let @@ -166,33 +185,52 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) # Note: We start ramping constraints from the second timesteps_block # We filter and group the dataframe per asset and representative period that does not have the unit_commitment methods - for ((a, y, rp), sub_df) in - pairs(DataFrames.groupby(cons.indices, [:asset, :year, :rep_period])) - # - Maximum ramp-up rate limit to the flow (no unit commitment variables) - model[Symbol("max_ramp_up_without_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - cons.expressions[:outgoing][row.index] - - cons.expressions[:outgoing][row.index-1] ≤ - graph[row.asset].max_ramp_up * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index], - base_name = "max_ramp_up_without_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 - ] + # - Maximum ramp-up rate limit to the flow (no unit commitment variables) + attach_constraint!( + model, + constraints[table_name], + :max_ramp_up_without_unit_commitment, + [ + begin + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:outgoing][row.index] - + cons.expressions[:outgoing][row.index-1] ≤ + graph[row.asset].max_ramp_up * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index], + base_name = "max_ramp_up_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) + end + end for row in eachrow(cons.indices) + ], + ) + #base_name = "max_ramp_down_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - # - Maximum ramp-down rate limit to the flow (no unit commitment variables) - model[Symbol("max_ramp_down_without_unit_commitment_$(a)_$(y)_$(rp)")] = [ - @constraint( - model, - cons.expressions[:outgoing][row.index] - - cons.expressions[:outgoing][row.index-1] ≥ - -graph[row.asset].max_ramp_down * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index], - base_name = "max_ramp_down_without_unit_commitment[$a,$y,$rp,$(row.time_block_start):$(row.time_block_end)]" - ) for (k, row) in enumerate(eachrow(sub_df)) if k > 1 - ] - end + attach_constraint!( + model, + constraints[table_name], + :max_ramp_up_without_unit_commitment, + [ + begin + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:outgoing][row.index] - + cons.expressions[:outgoing][row.index-1] ≥ + -graph[row.asset].max_ramp_down * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index], + base_name = "max_ramp_down_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) + end + end for row in eachrow(cons.indices) + ], + ) end end From cc210954757f3bc303a5b809b5717fac6d0db67d Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 13:50:37 +0100 Subject: [PATCH 4/8] [ramp] Last clean up --- .../ramping-and-unit-commitment.jl | 125 +++++++++--------- 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/src/constraints/ramping-and-unit-commitment.jl b/src/constraints/ramping-and-unit-commitment.jl index fb4973df..17e99311 100644 --- a/src/constraints/ramping-and-unit-commitment.jl +++ b/src/constraints/ramping-and-unit-commitment.jl @@ -50,11 +50,12 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) [ @expression( model, - cons.expressions[:outgoing][row.index] - + outgoing_flow - profile_times_capacity[table_name][row.index] * graph[row.asset].min_operating_point * - cons.expressions[:units_on][row.index] - ) for row in eachrow(cons.indices) + units_on + ) for (row, outgoing_flow, units_on) in + zip(eachrow(cons.indices), cons.expressions[:outgoing], cons.expressions[:units_on]) ], ) end @@ -108,12 +109,16 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) [ @constraint( model, - cons.expressions[:flow_above_min_operating_point][row.index] ≤ + flow_above_min_operating_point ≤ (1 - graph[row.asset].min_operating_point) * profile_times_capacity[table_name][row.index] * - cons.expressions[:units_on][row.index], + units_on, base_name = "max_output_flow_with_basic_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) for row in eachrow(cons.indices) + ) for (row, flow_above_min_operating_point, units_on) in zip( + eachrow(cons.indices), + cons.expressions[:flow_above_min_operating_point], + cons.expressions[:units_on], + ) ], ) end @@ -133,21 +138,19 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) constraints[table_name], :max_ramp_up_with_unit_commitment, [ - begin - if row.time_block_start == 1 - @constraint(model, 0 == 0) # Placeholder for case k = 1 - else - @constraint( - model, - cons.expressions[:flow_above_min_operating_point][row.index] - - cons.expressions[:flow_above_min_operating_point][row.index-1] ≤ - graph[row.asset].max_ramp_up * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index] * - units_on[row.index], - base_name = "max_ramp_up_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) - end + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:flow_above_min_operating_point][row.index] - + cons.expressions[:flow_above_min_operating_point][row.index-1] ≤ + graph[row.asset].max_ramp_up * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index] * + units_on[row.index], + base_name = "max_ramp_up_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) end for row in eachrow(cons.indices) ], ) @@ -158,21 +161,19 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) constraints[table_name], :max_ramp_down_with_unit_commitment, [ - begin - if row.time_block_start == 1 - @constraint(model, 0 == 0) # Placeholder for case k = 1 - else - @constraint( - model, - cons.expressions[:flow_above_min_operating_point][row.index] - - cons.expressions[:flow_above_min_operating_point][row.index-1] ≥ - -graph[row.asset].max_ramp_down * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index] * - units_on[row.index-1], - base_name = "max_ramp_down_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) - end + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:flow_above_min_operating_point][row.index] - + cons.expressions[:flow_above_min_operating_point][row.index-1] ≥ + -graph[row.asset].max_ramp_down * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index] * + units_on[row.index-1], + base_name = "max_ramp_down_with_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) end for row in eachrow(cons.indices) ], ) @@ -191,20 +192,18 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) constraints[table_name], :max_ramp_up_without_unit_commitment, [ - begin - if row.time_block_start == 1 - @constraint(model, 0 == 0) # Placeholder for case k = 1 - else - @constraint( - model, - cons.expressions[:outgoing][row.index] - - cons.expressions[:outgoing][row.index-1] ≤ - graph[row.asset].max_ramp_up * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index], - base_name = "max_ramp_up_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) - end + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:outgoing][row.index] - + cons.expressions[:outgoing][row.index-1] ≤ + graph[row.asset].max_ramp_up * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index], + base_name = "max_ramp_up_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) end for row in eachrow(cons.indices) ], ) @@ -215,20 +214,18 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) constraints[table_name], :max_ramp_up_without_unit_commitment, [ - begin - if row.time_block_start == 1 - @constraint(model, 0 == 0) # Placeholder for case k = 1 - else - @constraint( - model, - cons.expressions[:outgoing][row.index] - - cons.expressions[:outgoing][row.index-1] ≥ - -graph[row.asset].max_ramp_down * - row.min_outgoing_flow_duration * - profile_times_capacity[table_name][row.index], - base_name = "max_ramp_down_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" - ) - end + if row.time_block_start == 1 + @constraint(model, 0 == 0) # Placeholder for case k = 1 + else + @constraint( + model, + cons.expressions[:outgoing][row.index] - + cons.expressions[:outgoing][row.index-1] ≥ + -graph[row.asset].max_ramp_down * + row.min_outgoing_flow_duration * + profile_times_capacity[table_name][row.index], + base_name = "max_ramp_down_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" + ) end for row in eachrow(cons.indices) ], ) From 9cb03422ebe4b81865260b3286868dd656156c9c Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 14:21:01 +0100 Subject: [PATCH 5/8] Change use of let block --- .../ramping-and-unit-commitment.jl | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/constraints/ramping-and-unit-commitment.jl b/src/constraints/ramping-and-unit-commitment.jl index 17e99311..2c24cd4d 100644 --- a/src/constraints/ramping-and-unit-commitment.jl +++ b/src/constraints/ramping-and-unit-commitment.jl @@ -62,9 +62,7 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) ## Unit Commitment Constraints (basic implementation - more advanced will be added in 2025) # - Limit to the units on (i.e. commitment) - let - table_name = :limit_units_on - cons = constraints[table_name] + let table_name = :limit_units_on, cons = constraints[table_name] attach_constraint!( model, cons, @@ -80,9 +78,7 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) end # - Minimum output flow above the minimum operating point - let - table_name = :ramping_with_unit_commitment - cons = constraints[table_name] + let table_name = :ramping_with_unit_commitment, cons = constraints[table_name] attach_constraint!( model, cons, @@ -99,9 +95,7 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) end # - Maximum output flow above the minimum operating point - let - table_name = :max_output_flow_with_basic_unit_commitment - cons = constraints[table_name] + let table_name = :max_output_flow_with_basic_unit_commitment, cons = constraints[table_name] attach_constraint!( model, cons, @@ -123,12 +117,10 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) ) end - let + let table_name = :max_ramp_with_unit_commitment, cons = constraints[table_name] ## Ramping Constraints with unit commitment # Note: We start ramping constraints from the second timesteps_block # We filter and group the dataframe per asset and representative period - table_name = :max_ramp_with_unit_commitment - cons = constraints[table_name] # get the units on column to get easier the index - 1, i.e., the previous one units_on = cons.expressions[:units_on] @@ -179,9 +171,7 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) ) end - let - table_name = :max_ramp_without_unit_commitment - cons = constraints[table_name] + let table_name = :max_ramp_without_unit_commitment, cons = constraints[table_name] ## Ramping Constraints without unit commitment # Note: We start ramping constraints from the second timesteps_block # We filter and group the dataframe per asset and representative period that does not have the unit_commitment methods From 7b87bf2fe01ffc8b9227f5e5e27cdb2d775bbb86 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 17:53:48 +0100 Subject: [PATCH 6/8] Update src/constraints/ramping-and-unit-commitment.jl Co-authored-by: Diego Alejandro Tejada Arango <12887482+datejada@users.noreply.github.com> --- src/constraints/ramping-and-unit-commitment.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/constraints/ramping-and-unit-commitment.jl b/src/constraints/ramping-and-unit-commitment.jl index 2c24cd4d..39451925 100644 --- a/src/constraints/ramping-and-unit-commitment.jl +++ b/src/constraints/ramping-and-unit-commitment.jl @@ -197,7 +197,6 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets) end for row in eachrow(cons.indices) ], ) - #base_name = "max_ramp_down_without_unit_commitment[$(row.asset),$(row.year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]" attach_constraint!( model, From f12df6fa87fb9912da7961f60bb6490cfc89b550 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 18:17:19 +0100 Subject: [PATCH 7/8] Exclude assets with unit_commitment = false for constraints without unit_commitment --- src/constraints/create.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/constraints/create.jl b/src/constraints/create.jl index d91218b4..0424116b 100644 --- a/src/constraints/create.jl +++ b/src/constraints/create.jl @@ -264,7 +264,9 @@ function _create_constraints_tables(connection) LEFT JOIN asset ON t_high.asset = asset.asset WHERE - asset.type in ('producer', 'storage', 'conversion')", + asset.type in ('producer', 'storage', 'conversion') + AND NOT asset.unit_commitment + ", ) DuckDB.query( @@ -280,6 +282,7 @@ function _create_constraints_tables(connection) WHERE asset.type in ('producer', 'storage', 'conversion') AND asset.ramping + AND NOT asset.unit_commitment AND asset.unit_commitment_method != 'basic' ", ) From 5cbddfc5658e6da8ebc568653cacd858de0d3075 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 13 Dec 2024 18:17:55 +0100 Subject: [PATCH 8/8] Clean old comment --- src/constraints/create.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/constraints/create.jl b/src/constraints/create.jl index 0424116b..36e793ee 100644 --- a/src/constraints/create.jl +++ b/src/constraints/create.jl @@ -252,7 +252,6 @@ function _create_constraints_tables(connection) ", ) - # TODO: Should there be a `AND NOT asset.unit_commitment` in the WHERE here? DuckDB.query( connection, "CREATE OR REPLACE TEMP SEQUENCE id START 1;