diff --git a/src/algorithms/sequential_algorithm.jl b/src/algorithms/sequential_algorithm.jl index ffdb173..fe473a3 100644 --- a/src/algorithms/sequential_algorithm.jl +++ b/src/algorithms/sequential_algorithm.jl @@ -35,7 +35,7 @@ function write_results_to_main_container(container::MultiOptimizationContainer) main_container_data_field = getproperty(container, field) for (key, src) in subproblem_data_field if src isa JuMP.Containers.SparseAxisArray - @warn "Skip SparseAxisArray" field key + # @warn "Skip SparseAxisArray" field key continue end num_dims = ndims(src) @@ -45,7 +45,7 @@ function write_results_to_main_container(container::MultiOptimizationContainer) data = PSI.jump_value.(src) catch e if e isa UndefRefError - @warn "Skip UndefRefError for" field key + #@warn "Skip UndefRefError for" field key continue end rethrow() @@ -68,7 +68,6 @@ function write_results_to_main_container(container::MultiOptimizationContainer) end _write_parameter_results_to_main_container(container, subproblem) end - # Parameters need a separate approach due to the way the containers work return end diff --git a/src/multi_optimization_container.jl b/src/multi_optimization_container.jl index f321432..8331eef 100644 --- a/src/multi_optimization_container.jl +++ b/src/multi_optimization_container.jl @@ -42,28 +42,28 @@ function MultiOptimizationContainer( ) return MultiOptimizationContainer{T}(; - main_problem = PSI.OptimizationContainer(sys, settings, nothing, U), - subproblems = subproblems, - time_steps = 1:1, - resolution = IS.time_period_conversion(resolution), - settings = settings, - settings_copy = PSI.copy_for_serialization(settings), - variables = Dict{PSI.VariableKey, AbstractArray}(), - aux_variables = Dict{PSI.AuxVarKey, AbstractArray}(), - duals = Dict{PSI.ConstraintKey, AbstractArray}(), - constraints = Dict{PSI.ConstraintKey, AbstractArray}(), - objective_function = PSI.ObjectiveFunction(), - expressions = Dict{PSI.ExpressionKey, AbstractArray}(), - parameters = Dict{PSI.ParameterKey, PSI.ParameterContainer}(), - primal_values_cache = PSI.PrimalValuesCache(), - initial_conditions = Dict{PSI.ICKey, Vector{PSI.InitialCondition}}(), - initial_conditions_data = PSI.InitialConditionsData(), - base_power = PSY.get_base_power(sys), - optimizer_stats = PSI.OptimizerStats(), - built_for_recurrent_solves = false, - metadata = PSI.OptimizationContainerMetadata(), - default_time_series_type = U, - mpi_info = nothing, + main_problem=PSI.OptimizationContainer(sys, settings, nothing, U), + subproblems=subproblems, + time_steps=1:1, + resolution=IS.time_period_conversion(resolution), + settings=settings, + settings_copy=PSI.copy_for_serialization(settings), + variables=Dict{PSI.VariableKey, AbstractArray}(), + aux_variables=Dict{PSI.AuxVarKey, AbstractArray}(), + duals=Dict{PSI.ConstraintKey, AbstractArray}(), + constraints=Dict{PSI.ConstraintKey, AbstractArray}(), + objective_function=PSI.ObjectiveFunction(), + expressions=Dict{PSI.ExpressionKey, AbstractArray}(), + parameters=Dict{PSI.ParameterKey, PSI.ParameterContainer}(), + primal_values_cache=PSI.PrimalValuesCache(), + initial_conditions=Dict{PSI.ICKey, Vector{PSI.InitialCondition}}(), + initial_conditions_data=PSI.InitialConditionsData(), + base_power=PSY.get_base_power(sys), + optimizer_stats=PSI.OptimizerStats(), + built_for_recurrent_solves=false, + metadata=PSI.OptimizationContainerMetadata(), + default_time_series_type=U, + mpi_info=nothing, ) end @@ -163,6 +163,7 @@ function init_optimization_container!( PSI.LOG_GROUP_OPTIMIZATION_CONTAINER subproblem.settings = deepcopy(settings) PSI.init_optimization_container!(subproblem, network_model, sys) + subproblem.built_for_recurrent_solves = true end _finalize_jump_model!(container, settings) return diff --git a/src/multiproblem_template.jl b/src/multiproblem_template.jl index e4b287b..06e8603 100644 --- a/src/multiproblem_template.jl +++ b/src/multiproblem_template.jl @@ -47,6 +47,26 @@ function get_sub_problem_keys(template::MultiProblemTemplate) return sort!(collect(keys(get_sub_templates(template)))) end +function PSI.get_component_types(template::MultiProblemTemplate)::Vector{DataType} + base_template = template.base_template + return vcat( + get_component_type.(values(get_device_models(base_template))), + get_component_type.(values(get_branch_models(base_template))), + get_component_type.(values(get_service_models(base_template))), + ) +end + +function PSI.get_model(template::MultiProblemTemplate, ::Type{T}) where {T <: PSY.Device} + base_template = template.base_template + if T <: PSY.Branch + return get(base_template.branches, Symbol(T), nothing) + elseif T <: PSY.Device + return get(base_template.devices, Symbol(T), nothing) + else + error("Component $T not present in the template") + end +end + """ Sets the network model in a template. """ @@ -92,8 +112,9 @@ function PSI.set_device_model!( ) PSI.set_device_model!(template.base_template, model) for (id, sub_template) in get_sub_templates(template) - PSI.set_subsystem!(model, id) - PSI.set_device_model!(sub_template, model) + new_model = deepcopy(model) + PSI.set_subsystem!(new_model, id) + PSI.set_device_model!(sub_template, new_model) end return end @@ -104,8 +125,9 @@ function PSI.set_device_model!( ) PSI.set_device_model!(template.base_template, model) for (id, sub_template) in get_sub_templates(template) - PSI.set_subsystem!(model, id) - PSI.set_device_model!(sub_template, PSI.DeviceModel(component_type, formulation)) + new_model = deepcopy(model) + PSI.set_subsystem!(new_model, id) + PSI.set_device_model!(sub_template, new_model) end return end @@ -123,10 +145,10 @@ function PSI.set_service_model!( PSI.set_service_model!( template.base_template, service_name, - ServiceModel(service_type, formulation; use_service_name = true), + ServiceModel(service_type, formulation; use_service_name=true), ) for (id, sub_template) in get_sub_templates(template) - service_model = ServiceModel(service_type, formulation; use_service_name = true) + service_model = ServiceModel(service_type, formulation; use_service_name=true) PSI.set_subsystem!(service_model, id) PSI.set_service_model!(sub_template, service_name, service_model) end diff --git a/src/problems/multi_region_problem.jl b/src/problems/multi_region_problem.jl index 22f62ae..867c4e5 100644 --- a/src/problems/multi_region_problem.jl +++ b/src/problems/multi_region_problem.jl @@ -3,7 +3,7 @@ struct MultiRegionProblem <: PSI.DecisionProblem end function PSI.DecisionModel{MultiRegionProblem}( template::MultiProblemTemplate, sys::PSY.System, - ::Union{Nothing, JuMP.Model} = nothing; + ::Union{Nothing, JuMP.Model}=nothing; kwargs..., ) name = Symbol(get(kwargs, :name, nameof(MultiRegionProblem))) @@ -126,11 +126,7 @@ function _make_parameter_attributes(subproblem_parameters) if !haskey(data, key) data[key] = deepcopy(val.attributes) else - existing = data[key] - if val.attributes.name != existing.name - error("Mismatch in attributes name: $key $val $(existing.name)") - end - _merge_attributes!(existing, val.attributes) + _merge_attributes!(data[key], val.attributes) end end end @@ -138,7 +134,7 @@ function _make_parameter_attributes(subproblem_parameters) return data end -function _merge_attributes(attributes::T, other::T) where {T <: PSI.ParameterAttributes} +function _merge_attributes!(attributes::T, other::T) where {T <: PSI.ParameterAttributes} for field in fieldnames(T) val1 = getproperty(attributes, field) val2 = getproperty(other, field) @@ -151,6 +147,9 @@ function _merge_attributes(attributes::T, other::T) where {T <: PSI.ParameterAtt end function _merge_attributes!(attributes::T, other::T) where {T <: PSI.TimeSeriesAttributes} + if attributes.name != other.name + error("Mismatch in attributes name: $(attributes.name) $(other.name)") + end intersection = intersect( keys(attributes.component_name_to_ts_uuid), keys(other.component_name_to_ts_uuid), @@ -185,9 +184,9 @@ function _make_parameter_arrays(subproblem_parameters, field_name) end function _make_array_joined_by_axes( - a1::JuMP.Containers.DenseAxisArray{Float64, 2}, - a2::JuMP.Containers.DenseAxisArray{Float64, 2}, -) + a1::JuMP.Containers.DenseAxisArray{T, 2}, + a2::JuMP.Containers.DenseAxisArray{U, 2}, +) where {T <: Union{Float64, JuMP.VariableRef}, U <: Union{Float64, JuMP.VariableRef}} ax1 = axes(a1) ax2 = axes(a2) if ax1[2] != ax2[2] @@ -209,6 +208,8 @@ function PSI.build_impl!(model::PSI.DecisionModel{MultiRegionProblem}) handle_initial_conditions!(model) PSI.build_model!(model) _map_containers(model) + container = PSI.get_optimization_container(model) + container.built_for_recurrent_solves = true # Might need custom implementation for this container type # serialize_metadata!(get_optimization_container(model), get_output_dir(model)) PSI.log_values(PSI.get_settings(model)) @@ -261,3 +262,111 @@ function PSI.solve_impl!(model::PSI.DecisionModel{MultiRegionProblem}) end function PSI._check_numerical_bounds(model::PSI.DecisionModel{MultiRegionProblem}) end + +### Simulation Related methods ### +# These code blocks are duplicative from PSI, refactoring might be required on the PSI side to +# avoid duplication. + +function PSI._add_feedforward_to_model( + sim_model::PSI.DecisionModel{MultiRegionProblem}, + ff::T, + ::Type{U}, +) where {T <: PSI.AbstractAffectFeedforward, U <: PSY.Device} + template = PSI.get_template(sim_model) + for (id, sub_template) in get_sub_templates(template) + device_model = PSI.get_model(sub_template, PSI.get_component_type(ff)) + if device_model === nothing + model_name = PSI.get_name(sim_model) + throw( + IS.ConflictingInputsError( + "Device model $(PSI.get_component_type(ff)) not found in model $model_name", + ), + ) + end + @info "attaching $T to $(PSI.get_component_type(ff)) to Template $id" + PSI.attach_feedforward!(device_model, ff) + end + return +end + +function PSI._add_feedforward_to_model( + sim_model::PSI.DecisionModel{MultiRegionProblem}, + ff::T, + ::Type{U}, +) where {T <: PSI.AbstractAffectFeedforward, U <: PSY.Service} + template = PSI.get_template(sim_model) + name_provided = PSI.get_feedforward_meta(ff) != PSI.NO_SERVICE_NAME_PROVIDED + for (id, sub_template) in get_sub_templates(template) + if name_provided + service_model = PSI.get_model( + sub_template, + PSI.get_component_type(ff), + PSI.get_feedforward_meta(ff), + ) + if service_model === nothing + throw( + IS.ConflictingInputsError( + "Service model $(get_component_type(ff)) not found in model $(get_name(sim_model))", + ), + ) + end + @info "attaching $T to $(PSI.get_component_type(ff)) $(PSI.get_feedforward_meta(ff)) to Template $id" + PSI.attach_feedforward!(service_model, ff) + else + service_found = false + for (key, model) in PSI.get_service_models(sub_template) + if key[2] == Symbol(PSI.get_component_type(ff)) + service_found = true + @info "attaching $T to $(PSI.get_component_type(ff))" + PSI.attach_feedforward!(model, ff) + end + end + end + end + return +end + +function PSI.update_parameters!( + model::PSI.DecisionModel{MultiRegionProblem}, + decision_states::PSI.DatasetContainer{PSI.InMemoryDataset}, +) + container = PSI.get_optimization_container(model) + for (ix, subproblem) in container.subproblems + @info "Updating subproblem $ix" + PSI.cost_function_unsynch(subproblem) + for key in keys(PSI.get_parameters(subproblem)) + PSI.update_container_parameter_values!(subproblem, model, key, decision_states) + end + end + + if !PSI.is_synchronized(model) + for subproblem in values(container.subproblems) + PSI.update_objective_function!(subproblem) + obj_func = PSI.get_objective_expression(subproblem) + PSI.set_synchronized_status(obj_func, true) + end + end + return +end + +""" +Default problem update function for most problems with no customization +""" +function PSI.update_model!( + model::PSI.DecisionModel{MultiRegionProblem}, + sim::PSI.Simulation, +) + PSI.update_model!( + model, + PSI.get_simulation_state(sim), + PSI.get_ini_cond_chronology(sim), + ) + #= + if get_rebuild_model(model) + container = get_optimization_container(model) + reset_optimization_model!(container) + build_impl!(container, get_template(model), get_system(model)) + end + =# + return +end diff --git a/test/Project.toml b/test/Project.toml index 8cbe0c2..9a225ef 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,8 +1,10 @@ [deps] +HydroPowerSimulations = "fc1677e0-6ad7-4515-bf3a-bd6bf20a0b1b" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" PowerSimulations = "e690365d-45e2-57bb-ac84-44ba829e73c4" PowerSimulationsDecomposition = "bed98974-b02a-5e2f-9ee0-a103f5c450dd" PowerSystemCaseBuilder = "f00506e0-b84f-492a-93c2-c0a9afc4364e" PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" +StorageSystemsSimulations = "e2f1a126-19d0-4674-9252-42b2384f8e3c" Xpress = "9e70acf3-d6c9-5be6-b5bd-4e2c73e3e054"