Skip to content

Commit

Permalink
Create coefficients field in TulipaConstraint and remove graph from r…
Browse files Browse the repository at this point in the history
…amping constraints
  • Loading branch information
abelsiqueira committed Dec 19, 2024
1 parent 7847593 commit 0a0ea7a
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 30 deletions.
95 changes: 69 additions & 26 deletions src/constraints/ramping-and-unit-commitment.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,37 @@ export add_ramping_and_unit_commitment_constraints!
Adds the ramping constraints for producer and conversion assets where ramping = true in assets_data
"""
function add_ramping_constraints!(model, variables, constraints, graph, sets)
function add_ramping_constraints!(connection, model, variables, constraints, profiles, sets)
# unpack from sets
accumulated_units_lookup = sets[:accumulated_units_lookup]

## unpack from model
accumulated_units = model[:accumulated_units]

indices_dict = Dict(
table_name => _append_ramping_data_to_indices(connection, table_name) 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,
)
)

## 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(
table_name => begin
cons = constraints[table_name]
indices = indices_dict[table_name]
[
profile_aggregation(
Statistics.mean,
graph[row.asset].rep_periods_profiles,
row.year,
row.year,
("availability", row.rep_period),
_profile_aggregate(
profiles.rep_period,
(row.profile_name, row.year, row.rep_period),
row.time_block_start:row.time_block_end,
Statistics.mean,
1.0,
) * graph[row.asset].capacity for row in eachrow(cons.indices)
) * row.capacity for row in indices
]
end for table_name in (
:ramping_with_unit_commitment,
Expand All @@ -44,6 +53,7 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
:max_ramp_with_unit_commitment,
)
cons = constraints[table_name]
indices = indices_dict[table_name]
attach_expression!(
cons,
:flow_above_min_operating_point,
Expand All @@ -52,10 +62,10 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
model,
outgoing_flow -
profile_times_capacity[table_name][row.index] *
graph[row.asset].min_operating_point *
row.min_operating_point *
units_on
) for (row, outgoing_flow, units_on) in
zip(eachrow(cons.indices), cons.expressions[:outgoing], cons.expressions[:units_on])
zip(indices, cons.expressions[:outgoing], cons.expressions[:units_on])
],
)
end
Expand All @@ -79,6 +89,7 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)

# - Minimum output flow above the minimum operating point
let table_name = :ramping_with_unit_commitment, cons = constraints[table_name]
indices = indices_dict[table_name]
attach_constraint!(
model,
cons,
Expand All @@ -89,13 +100,14 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
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])
zip(indices, cons.expressions[:flow_above_min_operating_point])
],
)
end

# - Maximum output flow above the minimum operating point
let table_name = :max_output_flow_with_basic_unit_commitment, cons = constraints[table_name]
indices = indices_dict[table_name]
attach_constraint!(
model,
cons,
Expand All @@ -104,12 +116,12 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
@constraint(
model,
flow_above_min_operating_point
(1 - graph[row.asset].min_operating_point) *
(1 - row.min_operating_point) *
profile_times_capacity[table_name][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, flow_above_min_operating_point, units_on) in zip(
eachrow(cons.indices),
indices,
cons.expressions[:flow_above_min_operating_point],
cons.expressions[:units_on],
)
Expand All @@ -118,6 +130,7 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
end

let table_name = :max_ramp_with_unit_commitment, cons = constraints[table_name]
indices = indices_dict[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
Expand All @@ -137,13 +150,14 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
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 *
row.max_ramp_up *
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)
end for (row, min_outgoing_flow_duration) in
zip(indices, cons.coefficients[:min_outgoing_flow_duration])
],
)

Expand All @@ -160,18 +174,20 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
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 *
-row.max_ramp_down *
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)
end for (row, min_outgoing_flow_duration) in
zip(indices, cons.coefficients[:min_outgoing_flow_duration])
],
)
end

let table_name = :max_ramp_without_unit_commitment, cons = constraints[table_name]
indices = indices_dict[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
Expand All @@ -189,12 +205,13 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
model,
cons.expressions[:outgoing][row.index] -
cons.expressions[:outgoing][row.index-1]
graph[row.asset].max_ramp_up *
row.min_outgoing_flow_duration *
row.max_ramp_up *
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)
end for (row, min_outgoing_flow_duration) in
zip(indices, cons.coefficients[:min_outgoing_flow_duration])
],
)

Expand All @@ -210,13 +227,39 @@ function add_ramping_constraints!(model, variables, constraints, graph, sets)
model,
cons.expressions[:outgoing][row.index] -
cons.expressions[:outgoing][row.index-1]
-graph[row.asset].max_ramp_down *
row.min_outgoing_flow_duration *
-row.max_ramp_down *
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)
end for (row, min_outgoing_flow_duration) in
zip(indices, cons.coefficients[:min_outgoing_flow_duration])
],
)
end
end

function _append_ramping_data_to_indices(connection, table_name)
return DuckDB.query(
connection,
"SELECT
cons.*,
asset.capacity,
asset.min_operating_point,
asset.max_ramp_up,
asset.max_ramp_down,
assets_profiles.profile_name
FROM cons_$table_name AS cons
LEFT JOIN asset
ON cons.asset = asset.asset
LEFT JOIN asset_commission
ON cons.asset = asset_commission.asset
AND cons.year = asset_commission.commission_year
LEFT OUTER JOIN assets_profiles
ON cons.asset = assets_profiles.asset
AND cons.year = assets_profiles.commission_year
AND assets_profiles.profile_type = 'availability'
ORDER BY cons.index
",
)
end
3 changes: 2 additions & 1 deletion src/create-model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,11 @@ function create_model(

if !isempty(constraints[:ramping_with_unit_commitment].indices)
@timeit to "add_ramping_constraints!" add_ramping_constraints!(
connection,
model,
variables,
constraints,
graph,
profiles,
sets,
)
end
Expand Down
14 changes: 11 additions & 3 deletions src/model-preparation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,15 @@ function add_expression_terms_intra_rp_constraints!(
conditions_to_add_min_outgoing_flow_duration =
add_min_outgoing_flow_duration && case.expr_key == :outgoing
if conditions_to_add_min_outgoing_flow_duration
# TODO: What to do about this?
cons.indices[!, :min_outgoing_flow_duration] .= 1
# TODO: Evaluate what to do with this
# Originally, this was a column attach to the indices Assuming the
# indices will be DuckDB tables, that would be problematic,
# although possible However, that would be the only place that
# DuckDB tables are changed after creation - notice that
# constraints create new tables when a new column is necessary The
# current solution is to attach as a coefficient, a new field of
# TulipaConstraint created just for this purpose
attach_coefficient!(cons, :min_outgoing_flow_duration, ones(num_rows))
end
grouped_flows = DataFrames.groupby(flow.indices, [:year, :rep_period, case.asset_match])
for ((year, rep_period, asset), sub_df) in pairs(grouped_cons)
Expand Down Expand Up @@ -105,7 +112,8 @@ function add_expression_terms_intra_rp_constraints!(
cons.expressions[case.expr_key][row.index] =
agg(@view workspace[row.time_block_start:row.time_block_end])
if conditions_to_add_min_outgoing_flow_duration
row[:min_outgoing_flow_duration] = outgoing_flow_durations
cons.coefficients[:min_outgoing_flow_duration][row.index] =
outgoing_flow_durations
end
end
end
Expand Down
18 changes: 18 additions & 0 deletions src/structures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ mutable struct TulipaConstraint
num_rows::Int
constraint_names::Vector{Symbol}
expressions::Dict{Symbol,Vector{JuMP.AffExpr}}
coefficients::Dict{Symbol,Vector{Float64}} # TODO: This was created only because of min_outgoing_flow_duration
duals::Dict{Symbol,Vector{Float64}}

function TulipaConstraint(connection, table_name::String)
Expand All @@ -71,6 +72,7 @@ mutable struct TulipaConstraint
Symbol[],
Dict(),
Dict(),
Dict(),
)
end
end
Expand Down Expand Up @@ -161,6 +163,22 @@ end
# return nothing
# end

"""
attach_coefficient!(cons, name, container)
Attach a coefficient named `name` stored in `container`.
This checks that the `container` length matches the stored `indices` number of rows.
"""
function attach_coefficient!(cons::TulipaConstraint, name::Symbol, container)
if length(container) != cons.num_rows
error(

Check warning on line 174 in src/structures.jl

View check run for this annotation

Codecov / codecov/patch

src/structures.jl#L174

Added line #L174 was not covered by tests
"The number of coefficients does not match the number of rows in the indices of $name",
)
end
cons.coefficients[name] = container
return nothing
end

"""
Structure to hold the data of one representative period.
"""
Expand Down

0 comments on commit 0a0ea7a

Please sign in to comment.