Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jd/misc fixes #1111

Merged
merged 7 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/tutorials/adding_new_problem_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ my_model = DecisionModel{MyCustomDecisionProblem}(
These methods can be defined optionally for your problem. By default for problems subtyped from `DecisionProblem` these checks are not executed. If the problems are subtyped from `DefaultDecisionProblem` these checks are always conducted with PowerSimulations defaults and require compliance with those defaults to pass. In any case, these can be overloaded when necessary depending on the problem requirements.

1. `validate_template`
2. `validate_time_series`
2. `validate_time_series!`
3. `reset!`
4. `solve_impl!`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,75 @@ function _add_pwl_constraint!(
return
end

"""
Implement the constraints for PWL variables for Compact form. That is:

```math
\\sum_{k\\in\\mathcal{K}} P_k^{max} \\delta_{k,t} = p_t + P_min * u_t \\\\
\\sum_{k\\in\\mathcal{K}} \\delta_{k,t} = on_t
```
"""
function _add_pwl_constraint!(
container::OptimizationContainer,
component::T,
::U,
break_points::Vector{Float64},
sos_status::SOSStatusVariable,
period::Int,
) where {T <: PSY.Component, U <: PowerAboveMinimumVariable}
variables = get_variable(container, U(), T)
const_container = lazy_container_addition!(
container,
PieceWiseLinearCostConstraint(),
T,
axes(variables)...,
)
len_cost_data = length(break_points)
jump_model = get_jump_model(container)
pwl_vars = get_variable(container, PieceWiseLinearCostVariable(), T)
name = PSY.get_name(component)

if sos_status == SOSStatusVariable.NO_VARIABLE
bin = 1.0
@debug "Using Piecewise Linear cost function but no variable/parameter ref for ON status is passed. Default status will be set to online (1.0)" _group =
LOG_GROUP_COST_FUNCTIONS

elseif sos_status == SOSStatusVariable.PARAMETER
param = get_default_on_parameter(component)
bin = get_parameter(container, param, T).parameter_array[name, period]
@debug "Using Piecewise Linear cost function with parameter OnStatusParameter, $T" _group =
LOG_GROUP_COST_FUNCTIONS
elseif sos_status == SOSStatusVariable.VARIABLE
var = get_default_on_variable(component)
bin = get_variable(container, var, T)[name, period]
@debug "Using Piecewise Linear cost function with variable OnVariable $T" _group =
LOG_GROUP_COST_FUNCTIONS
else
@assert false
end
P_min = PSY.get_active_power_limits(component).min

const_container[name, period] = JuMP.@constraint(
jump_model,
bin * P_min + variables[name, period] ==
sum(pwl_vars[name, ix, period] * break_points[ix] for ix in 1:len_cost_data)
)

const_normalization_container = lazy_container_addition!(
container,
PieceWiseLinearCostConstraint(),
T,
axes(variables)...;
meta = "normalization",
)

const_normalization_container[name, period] = JuMP.@constraint(
jump_model,
sum(pwl_vars[name, i, period] for i in 1:len_cost_data) == bin
)
return
end

"""
Implement the SOS for PWL variables. That is:

Expand Down Expand Up @@ -313,20 +382,7 @@ function _add_pwl_term!(
return
end

compact_status = validate_compact_pwl_data(component, data, base_power)
if !uses_compact_power(component, V()) && compact_status == COMPACT_PWL_STATUS.VALID
error(
"The data provided is not compatible with formulation $V. Use a formulation compatible with Compact Cost Functions",
)
# data = _convert_to_full_variable_cost(data, component)
elseif uses_compact_power(component, V()) && compact_status != COMPACT_PWL_STATUS.VALID
@warn(
"The cost data provided is not in compact form. Will attempt to convert. Errors may occur."
)
data = convert_to_compact_variable_cost(data)
else
@debug uses_compact_power(component, V()) compact_status name T V
end
# Compact PWL data does not exists anymore

cost_is_convex = PSY.is_convex(data)
break_points = PSY.get_x_coords(data)
Expand Down Expand Up @@ -383,10 +439,7 @@ function _add_pwl_term!(
)
end

if validate_compact_pwl_data(component, data, base_power) == COMPACT_PWL_STATUS.VALID
error("The data provided is not compatible with formulation $V. \\
Use a formulation compatible with Compact Cost Functions")
end
# Compact PWL data does not exists anymore

if slopes[1] != 0.0
@debug "PWL has no 0.0 intercept for generator $(component_name)"
Expand Down
39 changes: 1 addition & 38 deletions src/operation/decision_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function DecisionModel{M}(
DecisionModelStore(),
Dict{String, Any}(),
)
validate_time_series(model)
PSI.validate_time_series!(model)
return model
end

Expand Down Expand Up @@ -244,7 +244,6 @@ end

get_problem_type(::DecisionModel{M}) where {M <: DecisionProblem} = M
validate_template(::DecisionModel{<:DecisionProblem}) = nothing
validate_time_series(::DecisionModel{<:DecisionProblem}) = nothing

# Probably could be more efficient by storing the info in the internal
function get_current_time(model::DecisionModel)
Expand Down Expand Up @@ -275,42 +274,6 @@ function init_model_store_params!(model::DecisionModel)
return
end

function validate_time_series(model::DecisionModel{<:DefaultDecisionProblem})
sys = get_system(model)
settings = get_settings(model)
available_resolutions = PSY.get_time_series_resolutions(sys)

if get_resolution(settings) == UNSET_RESOLUTION && length(available_resolutions) != 1
throw(
IS.ConflictingInputsError(
"Data contains multiple resolutions, the resolution keyword argument must be added to the Model. Time Series Resolutions: $(available_resolutions)",
),
)
elseif get_resolution(settings) != UNSET_RESOLUTION && length(available_resolutions) > 1
if get_resolution(settings) ∉ available_resolutions
throw(
IS.ConflictingInputsError(
"Resolution $(get_resolution(settings)) is not available in the system data. Time Series Resolutions: $(available_resolutions)",
),
)
end
else
set_resolution!(settings, first(available_resolutions))
end

if get_horizon(settings) == UNSET_HORIZON
set_horizon!(settings, PSY.get_forecast_horizon(sys))
end

counts = PSY.get_time_series_counts(sys)
if counts.forecast_count < 1
error(
"The system does not contain forecast data. A DecisionModel can't be built.",
)
end
return
end

function build_pre_step!(model::DecisionModel{<:DecisionProblem})
TimerOutputs.@timeit BUILD_PROBLEMS_TIMER "Build pre-step" begin
validate_template(model)
Expand Down
47 changes: 2 additions & 45 deletions src/operation/emulation_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function EmulationModel{M}(
resolution = resolution,
)
model = EmulationModel{M}(template, sys, settings, jump_model; name = name)
validate_time_series(model)
validate_time_series!(model)
return model
end

Expand Down Expand Up @@ -231,7 +231,7 @@ end

get_problem_type(::EmulationModel{M}) where {M <: EmulationProblem} = M
validate_template(::EmulationModel{<:EmulationProblem}) = nothing
validate_time_series(::EmulationModel{<:EmulationProblem}) = nothing
validate_time_series!(::EmulationModel{<:EmulationProblem}) = nothing

function get_current_time(model::EmulationModel)
execution_count = get_execution_count(model)
Expand Down Expand Up @@ -262,49 +262,6 @@ function init_model_store_params!(model::EmulationModel)
return
end

function validate_time_series(model::EmulationModel{<:DefaultEmulationProblem})
sys = get_system(model)
counts = PSY.get_time_series_counts(sys)
if counts.static_time_series_count < 1
error(
"The system does not contain Static TimeSeries data. An Emulation model can't be formulated.",
)
end
counts = PSY.get_time_series_counts(sys)

if counts.forecast_count < 1
error(
"The system does not contain time series data. A EmulationModel can't be built.",
)
end

settings = get_settings(model)
available_resolutions = PSY.get_time_series_resolutions(sys)

if get_resolution(settings) == UNSET_RESOLUTION && length(available_resolutions) != 1
throw(
IS.ConflictingInputsError(
"Data contains multiple resolutions, the resolution keyword argument must be added to the Model. Time Series Resolutions: $(available_resolutions)",
),
)
elseif get_resolution(settings) != UNSET_RESOLUTION && length(available_resolutions) > 1
if get_resolution(settings) ∉ available_resolutions
throw(
IS.ConflictingInputsError(
"Resolution $(get_resolution(settings)) is not available in the system data. Time Series Resolutions: $(available_resolutions)",
),
)
end
else
set_resolution!(settings, first(available_resolutions))
end

if get_horizon(settings) == UNSET_HORIZON
set_horizon!(settings, get_resolution(settings))
end
return
end

function build_pre_step!(model::EmulationModel)
TimerOutputs.@timeit BUILD_PROBLEMS_TIMER "Build pre-step" begin
validate_template(model)
Expand Down
39 changes: 39 additions & 0 deletions src/operation/operation_model_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,42 @@ function serialize_optimization_model(model::OperationModel, save_path::String)
)
return
end

function validate_time_series!(model::OperationModel)
sys = get_system(model)
settings = get_settings(model)
available_resolutions = PSY.get_time_series_resolutions(sys)

if get_resolution(settings) == UNSET_RESOLUTION && length(available_resolutions) != 1
throw(
IS.ConflictingInputsError(
"Data contains multiple resolutions, the resolution keyword argument must be added to the Model. Time Series Resolutions: $(available_resolutions)",
),
)
elseif get_resolution(settings) != UNSET_RESOLUTION && length(available_resolutions) >= 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
elseif get_resolution(settings) != UNSET_RESOLUTION && length(available_resolutions) >= 1
elseif get_resolution(settings) != UNSET_RESOLUTION &&
length(available_resolutions) >= 1

if get_resolution(settings) ∉ available_resolutions
throw(
IS.ConflictingInputsError(
"Resolution $(get_resolution(settings)) is not available in the system data. Time Series Resolutions: $(available_resolutions)",
),
)
end
set_resolution!(settings, first(available_resolutions))
else
IS.@assert_op get_resolution(settings) == UNSET_RESOLUTION
@info "Resolution not set, using $(first(available_resolutions)) from the system data"
set_resolution!(settings, first(available_resolutions))
end

if get_horizon(settings) == UNSET_HORIZON
set_horizon!(settings, PSY.get_forecast_horizon(sys))
end

counts = PSY.get_time_series_counts(sys)
if counts.forecast_count < 1
error(
"The system does not contain forecast data. A DecisionModel can't be built.",
)
end
return
end
2 changes: 1 addition & 1 deletion src/parameters/update_parameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ function update_container_parameter_values!(
model::OperationModel,
key::ParameterKey{T, U},
input::DatasetContainer{InMemoryDataset},
) where {T <: ObjectiveFunctionParameter, U <: PSY.Service}
) where {T <: ParameterType, U <: PSY.Service}
# Note: Do not instantite a new key here because it might not match the param keys in the container
# if the keys have strings in the meta fields
parameter_array = get_parameter_array(optimization_container, key)
Expand Down
6 changes: 2 additions & 4 deletions src/simulation/simulation_models.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,8 @@ function determine_horizons!(models::SimulationModels)
if horizon == UNSET_HORIZON
sys = get_system(model)
horizon = PSY.get_forecast_horizon(sys)
# TODO: PSY to return horizon in TimePeriod
resolution = get_resolution(settings)
set_horizon!(settings, horizon * resolution)
horizons[get_name(model)] = horizon * resolution
set_horizon!(settings, horizon)
horizons[get_name(model)] = horizon
else
horizons[get_name(model)] = horizon
end
Expand Down
74 changes: 0 additions & 74 deletions src/utils/powersystems_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -335,77 +335,3 @@ function _get_piecewise_incrementalcurve_per_system_unit(
y_coords_normalized = y_coords .* system_base_power
return PSY.PiecewiseStepData(x_coords_normalized, y_coords_normalized)
end

##################################################
############### Auxiliary Methods ################
##################################################

# These conversions are not properly done for the new models
function convert_to_compact_variable_cost(
var_cost::PSY.PiecewiseLinearData,
p_min::Float64,
no_load_cost::Float64,
)
points = PSY.get_points(var_cost)
new_points = [(pp - p_min, c - no_load_cost) for (pp, c) in points]
return PSY.PiecewiseLinearData(new_points)
end

# These conversions are not properly done for the new models
function convert_to_compact_variable_cost(
var_cost::PSY.PiecewiseStepData,
p_min::Float64,
no_load_cost::Float64,
)
x = PSY.get_x_coords(var_cost)
y = vcat(PSY.get_y_coords(var_cost), PSY.get_y_coords(var_cost)[end])
points = [(x[i], y[i]) for i in length(x)]
new_points = [(x = pp - p_min, y = c - no_load_cost) for (pp, c) in points]
return PSY.PiecewiseLinearData(new_points)
end

# TODO: This method needs to be corrected to account for actual StepData. The TestData is point wise
function convert_to_compact_variable_cost(var_cost::PSY.PiecewiseStepData)
p_min, no_load_cost = (PSY.get_x_coords(var_cost)[1], PSY.get_y_coords(var_cost)[1])
return convert_to_compact_variable_cost(var_cost, p_min, no_load_cost)
end

function convert_to_compact_variable_cost(var_cost::PSY.PiecewiseLinearData)
p_min, no_load_cost = first(PSY.get_points(var_cost))
return convert_to_compact_variable_cost(var_cost, p_min, no_load_cost)
end

function _validate_compact_pwl_data(
min::Float64,
max::Float64,
cost_data::PSY.PiecewiseStepData,
base_power::Float64,
)
data = PSY.get_x_coords(cost_data)
if isapprox(max - min, last(data) / base_power) && iszero(first(data))
return COMPACT_PWL_STATUS.VALID
else
return COMPACT_PWL_STATUS.INVALID
end
end

function validate_compact_pwl_data(
d::PSY.ThermalGen,
data::PSY.PiecewiseStepData,
base_power::Float64,
)
min = PSY.get_active_power_limits(d).min
max = PSY.get_active_power_limits(d).max
return _validate_compact_pwl_data(min, max, data, base_power)
end

function validate_compact_pwl_data(
d::PSY.Component,
::PSY.PiecewiseLinearData,
::Float64,
)
@warn "Validation of compact pwl data is not implemented for $(typeof(d))."
return COMPACT_PWL_STATUS.UNDETERMINED
end

get_breakpoint_upper_bounds = PSY.get_x_lengths
2 changes: 1 addition & 1 deletion test/test_utils/mock_operation_models.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function mock_construct_device!(
set_device_model!(problem.template, model)
template = PSI.get_template(problem)
PSI.finalize_template!(template, PSI.get_system(problem))
PSI.validate_time_series(problem)
PSI.validate_time_series!(problem)
PSI.init_optimization_container!(
PSI.get_optimization_container(problem),
PSI.get_network_model(template),
Expand Down
Loading