diff --git a/src/InfrastructureOptimizationModels.jl b/src/InfrastructureOptimizationModels.jl index 9ee5860..ec65634 100644 --- a/src/InfrastructureOptimizationModels.jl +++ b/src/InfrastructureOptimizationModels.jl @@ -216,10 +216,7 @@ export set_hvdc_network_model! # Extension points for downstream packages (e.g., PowerOperationsModels) # These functions have fallback implementations in IOM but are meant to be # extended with device-specific methods in POM -export construct_device! -export construct_service! export add_variables! -export add_constraints! export add_to_expression! export add_constant_to_jump_expression! export add_proportional_to_jump_expression! @@ -232,7 +229,6 @@ export add_pwl_linking_constraint! export add_pwl_normalization_constraint! export add_pwl_sos2_constraint! export get_pwl_cost_expression -export add_to_objective_function! export process_market_bid_parameters! ## Outputs interfaces @@ -319,9 +315,6 @@ export get_multiplier_array export get_parameter_column_refs export get_service_name export get_default_time_series_type -export get_expression_multiplier -export get_variable_multiplier -export get_multiplier_value export add_expression_container! # Initial condition infrastructure (extension points for POM) @@ -391,7 +384,7 @@ export process_import_export_parameters!, process_market_bid_parameters! # Types export AreaControlError # Extension point functions -export add_variable!, requires_initialization +export add_service_variables!, requires_initialization # End bulk-added # more extension points @@ -439,7 +432,6 @@ export add_constraints_container!, add_variable_cost! export add_initial_condition_container! export has_initial_condition_value, set_ic_quantity!, get_last_recorded_value export set_initial_conditions_model_container!, get_initial_conditions_model_container -export get_initial_conditions_device_model export _validate_warm_start_support, _add_services_to_device_model! export get_component_type, get_component_name, add_jump_parameter # Template/model access @@ -657,6 +649,4 @@ include("utils/jump_utils.jl") include("utils/powersystems_utils.jl") include("utils/time_series_utils.jl") include("utils/datetime_utils.jl") -include("utils/generate_valid_formulations.jl") - end diff --git a/src/common_models/add_auxiliary_variable.jl b/src/common_models/add_auxiliary_variable.jl index a104a30..e2bc8f0 100644 --- a/src/common_models/add_auxiliary_variable.jl +++ b/src/common_models/add_auxiliary_variable.jl @@ -1,29 +1,17 @@ -# FIXME The only difference between the signature and the definition are plural -# vs singular (add_variableS) and type vs instance (2nd argument). seems redundant/confusing. -# also, identical to add_variable.jl except for AuxVariableType vs VariableType. -""" -Add variables to the OptimizationContainer for any component. -""" -function add_variables!( - container::OptimizationContainer, - ::Type{T}, - devices::Union{Vector{U}, IS.FlattenIteratorWrapper{U}}, - formulation::Union{AbstractDeviceFormulation, AbstractServiceFormulation}, -) where {T <: AuxVariableType, U <: PSY.Component} - add_variable!(container, T(), devices, formulation) - return -end - @doc raw""" Default implementation of adding auxiliary variable to the model. """ -function add_variable!( +function add_variables!( container::OptimizationContainer, - var_type::AuxVariableType, + ::Type{T}, devices::U, formulation, -) where {U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}} where {D <: PSY.Component} +) where { + T <: AuxVariableType, + U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, +} where {D <: IS.InfrastructureSystemsComponent} @assert !isempty(devices) + var_type = T() time_steps = get_time_steps(container) add_aux_variable_container!( container, diff --git a/src/common_models/add_variable.jl b/src/common_models/add_variable.jl index 72e902a..128b5f9 100644 --- a/src/common_models/add_variable.jl +++ b/src/common_models/add_variable.jl @@ -1,31 +1,3 @@ -""" -Add variables to the OptimizationContainer for any component. -""" -function add_variables!( - container::OptimizationContainer, - ::Type{T}, - devices::Union{Vector{U}, IS.FlattenIteratorWrapper{U}}, - formulation::Union{AbstractServiceFormulation, AbstractDeviceFormulation}, -) where {T <: VariableType, U <: PSY.Component} - add_variable!(container, T(), devices, formulation) - return -end - -""" -Add variables to the OptimizationContainer for a service. -""" -function add_variables!( - container::OptimizationContainer, - ::Type{T}, - service::U, - contributing_devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}}, - formulation::AbstractReservesFormulation, -) where {T <: VariableType, U <: PSY.AbstractReserve, V <: PSY.Component} - # PERF: compilation hotspot. Switch to TSC. - add_service_variable!(container, T(), service, contributing_devices, formulation) - return -end - @doc raw""" Adds a variable to the optimization model and to the affine expressions contained in the optimization_container model according to the specified sign. Based on the inputs, the variable can @@ -59,9 +31,9 @@ If binary = true: * initial_value : Provides the function over device to obtain the warm start value """ -function add_variable!( +function add_variables!( container::OptimizationContainer, - variable_type::T, + ::Type{T}, devices::U, formulation, ) where { @@ -69,6 +41,7 @@ function add_variable!( U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, } where {D <: IS.InfrastructureSystemsComponent} @assert !isempty(devices) + variable_type = T() time_steps = get_time_steps(container) settings = get_settings(container) binary = get_variable_binary(variable_type, D, formulation) @@ -103,9 +76,12 @@ function add_variable!( return end -function add_service_variable!( +""" +Add variables to the OptimizationContainer for a service. +""" +function add_service_variables!( container::OptimizationContainer, - variable_type::T, + ::Type{T}, service::U, contributing_devices::V, formulation::AbstractServiceFormulation, @@ -115,6 +91,7 @@ function add_service_variable!( V <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, } where {D <: PSY.Component} @assert !isempty(contributing_devices) + variable_type = T() time_steps = get_time_steps(container) binary = get_variable_binary(variable_type, U, formulation) diff --git a/src/common_models/interfaces.jl b/src/common_models/interfaces.jl index 5409a98..f9a3e2d 100644 --- a/src/common_models/interfaces.jl +++ b/src/common_models/interfaces.jl @@ -1,93 +1,3 @@ -############################### -###### construct_device! ###### -############################### - -_to_string(::Type{ArgumentConstructStage}) = "ArgumentConstructStage" -_to_string(::Type{ModelConstructStage}) = "ModelConstructStage" - -""" -Stub implementation: downstream modules should implement `construct_device!` for -`ArgumentConstructStage` and for `ModelConstructStage` stages separately. -""" -function construct_device!( - ::OptimizationContainer, - ::IS.ComponentContainer, - ::M, - model::DeviceModel{D, F}, - network_model::NetworkModel{S}, -) where { - M <: ConstructStage, - D <: IS.InfrastructureSystemsComponent, - F <: AbstractDeviceFormulation, - S, -} - error( - "construct_device! not implemented for device type $D with formulation $F " * - "at $(_to_string(M)). Implement this method to add variables and expressions.", - ) -end - -# for some reason this one doesn't print the stage name. -function construct_service!( - ::OptimizationContainer, - ::IS.ComponentContainer, - ::ConstructStage, - model::ServiceModel{S, F}, - devices_template::Dict{Symbol, DeviceModel}, - incompatible_device_types::Set{<:DataType}, - network_model::NetworkModel{N}, -) where {S <: PSY.Service, F <: AbstractServiceFormulation, N} - error( - "construct_service! not implemented for service type $S with formulation $F. " * - "Implement this method in PowerOperationsModels.", - ) -end - -############################### -###### add_foo functions ###### -############################### - -# previously called objective_function!, but renamed to be more consistent with others. -""" -Add objective function contributions for devices. -""" -function add_to_objective_function!( - ::OptimizationContainer, - ::Union{Vector{U}, IS.FlattenIteratorWrapper{U}}, - ::DeviceModel{U, F}, - ::Type{S}, -) where { - U <: IS.InfrastructureSystemsComponent, - F <: AbstractDeviceFormulation, - S <: AbstractPowerModel, -} - error( - "add_to_objective_function! not implemented for device type $U with formulation $F and power model $S.", - ) - return -end - -""" -Add constraints to the optimization container. Stub implementation. -""" -function add_constraints!( - ::OptimizationContainer, - ::Type{T}, - devices::Union{Vector{U}, IS.FlattenIteratorWrapper{U}}, - model::DeviceModel{U, F}, - network_model::NetworkModel{S}, -) where { - T <: ConstraintType, - U <: IS.InfrastructureSystemsComponent, - F <: AbstractDeviceFormulation, - S, -} - error( - "add_constraints! not implemented for constraint type $T, " * - "device type $U with formulation $F. Implement this method to add constraints.", - ) -end - """ Extension point: Add parameters to the optimization container. Concrete implementations are in PowerOperationsModels. @@ -111,75 +21,6 @@ end ###### get_foo functions ###### ############################### -# Variable multipliers: default to 1.0 - -""" -Get the multiplier for a variable type when adding to an expression. -Default implementation returns 1.0. Override for specific variable/device/formulation combinations. -""" -get_variable_multiplier( - ::VariableType, - ::Type{<:IS.InfrastructureSystemsComponent}, - ::AbstractDeviceFormulation, -) = 1.0 - -# Expression multipliers: error by default. -""" -Get the multiplier for an expression type based on parameter type. -""" -function get_expression_multiplier( - ::P, - ::Type{T}, - ::D, - ::F, -) where { - P <: ParameterType, - T <: ExpressionType, - D <: IS.InfrastructureSystemsComponent, - F <: AbstractDeviceFormulation, -} - error( - "get_expression_multiplier not implemented for parameter $P, expression $T, " * - "device $D, formulation $F. Implement this method in PowerOperationsModels.", - ) -end - -# parameter multipliers: time series defaults to 1.0, other types error by default. - -""" -Extension point: Get multiplier value for a time series parameter. -This scales the time series values for each device. -""" -function get_multiplier_value( - ::T, - ::U, - ::F, -) where { - T <: TimeSeriesParameter, - U <: IS.InfrastructureSystemsComponent, - F <: AbstractDeviceFormulation, -} - return 1.0 # Default: no scaling -end - -""" -Get the multiplier value for a parameter type. -""" -function get_multiplier_value( - ::P, - ::D, - ::F, -) where { - P <: ParameterType, - D <: IS.InfrastructureSystemsComponent, - F <: AbstractDeviceFormulation, -} - error( - "get_multiplier_value not implemented for parameter $P, device $D, formulation $F. " * - "Implement this method in PowerOperationsModels.", - ) -end - # stuff associated to a formulation: attributes, time series names """ Extension point: Get default attributes for a device formulation. @@ -320,7 +161,7 @@ Extension point: Convert raw startup cost to a scalar value. Device-specific implementations (e.g., for StartUpStages, MultiStartVariable) are in POM. """ function start_up_cost( - cost::Any, # could be NamedTuple, StartUpStages, AffExpr, or Float. + cost::Any, # could be NamedTuple, StartUpStages, AffExpr, or Float. ::Type{T}, ::V, ::F, @@ -335,22 +176,6 @@ function start_up_cost( ) end -""" -Extension point: Add power flow evaluation data to the container. -Default: no-op (handles the common case of no power flow evaluators). -""" -function add_power_flow_data!( - ::OptimizationContainer, - evaluators::Vector{<:AbstractPowerFlowEvaluationModel}, - ::IS.ComponentContainer, -) - if !isempty(evaluators) - error( - "Power flow in-the-loop with the new IOM-POM-PSI split isn't working yet.", - ) - end -end - """ Extension point: Solve the power flow model. Default: error. Concrete implementations require PowerFlows integration. @@ -381,7 +206,7 @@ get_min_max_limits( """ Extension point: variable cost. -The one exception where it isn't just `get_variable(cost)`: storage devices, where we +The one exception where it isn't just `get_variable(cost)`: storage devices, where we need to map `ActivePower{In/Out}` to {charge/discharge} variable cost. """ function variable_cost( @@ -400,22 +225,6 @@ variable_cost( ::AbstractDeviceFormulation, ) = 0.0 -""" -Extension point: Get the device model to use for initialization. -The FixedOutput case returns the model as-is; all other formulations must be -implemented in PowerOperationsModels. -""" -get_initial_conditions_device_model( - ::OperationModel, - model::DeviceModel{T, FixedOutput}, -) where {T <: PSY.Device} = model - -get_initial_conditions_device_model( - ::OperationModel, - ::DeviceModel{T, D}, -) where {T <: PSY.Device, D <: AbstractDeviceFormulation} = - error("`get_initial_conditions_device_model` must be implemented for $T and $D") - """ Extension point: get the initial condition type for a given constraint, device, and formulation. Concrete implementations in POM. Used for ramp constraints. @@ -433,7 +242,7 @@ Update parameter values in the optimization container from the given input data. This is an extension point — concrete implementations should be defined in PowerOperationsModels (or PowerSimulations for simulation-specific variants). -Only called in `emulation_model.jl`: that file's contents and this function should +Only called in `emulation_model.jl`: that file's contents and this function should likely be moved to POM or PSI. """ function update_container_parameter_values! end diff --git a/src/utils/generate_valid_formulations.jl b/src/utils/generate_valid_formulations.jl deleted file mode 100644 index 01a9796..0000000 --- a/src/utils/generate_valid_formulations.jl +++ /dev/null @@ -1,101 +0,0 @@ -""" -Generate valid combinations of device_type/formulation and service_type/formulation. -Return vectors of dictionaries with Julia types. - -# Arguments - - - `sys::Union{Nothing, System}`: If set, only include component types present in the system. -""" -function generate_formulation_combinations(sys = nothing) - combos = Dict( - "device_formulations" => generate_device_formulation_combinations(), - "service_formulations" => generate_service_formulation_combinations(), - ) - - filter_formulation_combinations!(combos, sys) - return combos -end - -filter_formulation_combinations!(combos, ::Nothing) = nothing - -function filter_formulation_combinations!(combos, sys::PSY.System) - device_types = Set(PSY.get_existing_device_types(sys)) - service_types = - Set((x for x in PSY.get_existing_component_types(sys) if x <: PSY.Service)) - filter!(x -> x["device_type"] in device_types, combos["device_formulations"]) - filter!(x -> x["service_type"] in service_types, combos["service_formulations"]) -end - -""" -Generate valid combinations of device_type/formulation and service_type/formulation. -Return vectors of dictionaries with Julia types encoded as strings. - -# Arguments - - - `sys::Union{Nothing, System}`: If set, only include component types present in the system. -""" -function serialize_formulation_combinations(sys = nothing) - combos = generate_formulation_combinations(sys) - for (i, combo) in enumerate(combos["device_formulations"]) - for key in keys(combo) - combos["device_formulations"][i][key] = string(nameof(combo[key])) - end - end - for (i, combo) in enumerate(combos["service_formulations"]) - for key in keys(combo) - combos["service_formulations"][i][key] = string(nameof(combo[key])) - end - end - - sort!(combos["device_formulations"]; by = x -> x["device_type"]) - sort!(combos["service_formulations"]; by = x -> x["service_type"]) - return combos -end - -""" -Generate valid combinations of device_type/formulation and service_type/formulation and write -the output to a JSON file. - -# Arguments - - - `sys::Union{Nothing, System}`: If set, only include component types present in the system. -""" -function write_formulation_combinations(filename::AbstractString, sys = nothing) - open(filename, "w") do io - JSON3.pretty(io, serialize_formulation_combinations(sys)) - end - @info(" to $filename") -end - -function generate_device_formulation_combinations() - combos = [] - for (d, f) in Iterators.product( - IS.get_all_concrete_subtypes(PSY.Device), - IS.get_all_concrete_subtypes(AbstractDeviceFormulation), - ) - # DynamicBranches are not supported in PSI but they are still considered <: PSY.Device since in - # PSY 1.0 we haven't introduced the notion of AbstractDynamicBranches. - if d <: PSY.DynamicBranch - continue - end - if !isempty(methodswith(DeviceModel{d, f}, construct_device!; supertypes = true)) - push!(combos, Dict{String, Any}("device_type" => d, "formulation" => f)) - end - end - - return combos -end - -function generate_service_formulation_combinations() - combos = [] - for (d, f) in Iterators.product( - IS.get_all_concrete_subtypes(PSY.Service), - IS.get_all_concrete_subtypes(AbstractServiceFormulation), - ) - if !isempty(methodswith(ServiceModel{d, f}, construct_service!; supertypes = true)) - push!(combos, Dict{String, Any}("service_type" => d, "formulation" => f)) - end - end - - return combos -end diff --git a/test/test_pwl_methods.jl b/test/test_pwl_methods.jl index 9436df2..bf23404 100644 --- a/test/test_pwl_methods.jl +++ b/test/test_pwl_methods.jl @@ -149,8 +149,8 @@ function setup_pwl_constraint_test(; formulation = TestPWLFormulation() model = IOM.DeviceModel(MockThermalGen, TestPWLFormulation) - IOM.add_variable!(container, TestOriginalVariable(), devices, formulation) - IOM.add_variable!(container, TestApproximatedVariable(), devices, formulation) + IOM.add_variables!(container, TestOriginalVariable, devices, formulation) + IOM.add_variables!(container, TestApproximatedVariable, devices, formulation) IOM.add_sparse_pwl_interpolation_variables!( container, TestInterpolationVariable(), devices, model, num_segments, )