diff --git a/src/operation/problem_template.jl b/src/operation/problem_template.jl index 8b8039e1..4af94ac9 100644 --- a/src/operation/problem_template.jl +++ b/src/operation/problem_template.jl @@ -135,11 +135,29 @@ function finalize_template!(template::AbstractProblemTemplate, args...) ) end +""" + share_template_references!(template_::AbstractProblemTemplate, template::AbstractProblemTemplate) + +Extension point for template types whose build-time copy must share some objects with +the caller's `template` by reference instead of by `deepcopy` — for instance objects a +build mutates that callers then inspect on the instance they passed in. The model +constructors call it on the fresh copy `template_`; the default shares nothing. +""" +function share_template_references!(::AbstractProblemTemplate, ::AbstractProblemTemplate) + return +end + +function _deepcopy_template(template::AbstractProblemTemplate) + template_ = _deepcopy_sharing_network_data(template) + share_template_references!(template_, template) + return template_ +end + # Deep-copy a template while sharing the network source and derived data by reference: # both can hold PNM matrices whose solver caches carry raw factorization handles and # deliberately error on deepcopy (PNM #312). Sharing is safe because instantiation # replaces the copy's `network_data` outright rather than mutating the original's. -function _deepcopy_template(template::AbstractProblemTemplate) +function _deepcopy_sharing_network_data(template::AbstractProblemTemplate) network_model = get_network_model(template) network_model === nothing && return deepcopy(template) source = network_model.network_source diff --git a/test/InfrastructureOptimizationModelsTests.jl b/test/InfrastructureOptimizationModelsTests.jl index ad28b334..890cc378 100644 --- a/test/InfrastructureOptimizationModelsTests.jl +++ b/test/InfrastructureOptimizationModelsTests.jl @@ -115,6 +115,7 @@ function run_tests() # TODO outputs_by_time.jl # TODO service_model.jl include(joinpath(TEST_DIR, "test_settings.jl")) + include(joinpath(TEST_DIR, "test_problem_template.jl")) # standard_variables_expressions.jl: low complexity # time_series_parameter_types.jl: low complexity diff --git a/test/test_problem_template.jl b/test/test_problem_template.jl new file mode 100644 index 00000000..b0b98ff2 --- /dev/null +++ b/test/test_problem_template.jl @@ -0,0 +1,43 @@ +""" +Unit tests for the template deep-copy path and its `share_template_references!` hook. +""" + +using Test +using InfrastructureOptimizationModels + +# Minimal templates: one shares a field through the hook, one relies on the default. +mutable struct SharingTestTemplate <: IOM.AbstractProblemTemplate + shared::Vector{Int} + cloned::Vector{Int} +end +IOM.get_network_model(::SharingTestTemplate) = nothing +function IOM.share_template_references!( + template_::SharingTestTemplate, + template::SharingTestTemplate, +) + template_.shared = template.shared + return +end + +mutable struct DefaultCopyTestTemplate <: IOM.AbstractProblemTemplate + data::Vector{Int} +end +IOM.get_network_model(::DefaultCopyTestTemplate) = nothing + +@testset "Template deep copy" begin + @testset "share_template_references! runs on the copy" begin + template = SharingTestTemplate([1], [2]) + template_ = IOM._deepcopy_template(template) + @test template_ !== template + @test template_.shared === template.shared + @test template_.cloned == template.cloned + @test template_.cloned !== template.cloned + end + + @testset "default hook shares nothing" begin + template = DefaultCopyTestTemplate([1]) + template_ = IOM._deepcopy_template(template) + @test template_.data == template.data + @test template_.data !== template.data + end +end