From aea56482c289aaf864fa3d76c9b6f6b56b30dbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Thu, 11 Dec 2025 13:16:39 +0100 Subject: [PATCH 1/2] Store original resource keys to file for MultipleBuildingTypes, and test the overwrite_saved_data feature --- src/datastructures.jl | 6 +- test/test_CSPandPV.jl | 189 +++++++++++++++++++++-------------------- test/test_buildings.jl | 149 ++++++++++++++++---------------- test/utils.jl | 2 +- 4 files changed, 178 insertions(+), 168 deletions(-) diff --git a/src/datastructures.jl b/src/datastructures.jl index c53cc30..b7aee2e 100644 --- a/src/datastructures.jl +++ b/src/datastructures.jl @@ -498,14 +498,14 @@ function MultipleBuildingTypes( # Filter demands based on buildings and time period. Also convert the format of demands from # Dict{String, Vector{Dict}} to Dict{Resource, Vector{Float64}} and scale results to MW for building ∈ buildings - temp = Dict{Any,Vector{Any}}(val => Any[] for val ∈ values(resources_map)) + temp = Dict{Any,Vector{Any}}(val => Any[] for val ∈ keys(resources_map)) for v ∈ demands_orig_format[building] date = DateTime(v["Datetime"], "yyyy-mm-dd HH:MM") if time_start <= date && date <= time_end for (res, res_val) ∈ v if !(res ∈ ["Datetime", "Variable cost [€]", "Emissions [KgCO2]"]) - push!(temp[resources_map[res]], res_val/1e6) # Scale power_outputs to MW + push!(temp[res], res_val/1e6) # Scale power_outputs to MW end end end @@ -520,7 +520,7 @@ function MultipleBuildingTypes( # Sum the demands for all building types for val ∈ values(demands) for (res, demand) ∈ val - cap_vec[res] += demand + cap_vec[resources_map[res]] += demand end end diff --git a/test/test_CSPandPV.jl b/test/test_CSPandPV.jl index 1a357e9..6b171c2 100644 --- a/test/test_CSPandPV.jl +++ b/test/test_CSPandPV.jl @@ -3,97 +3,102 @@ using JSON using Dates @testset "CSPandPV" begin - case, modeltype = simple_graph_csp_pv() - - csp_and_pv_plant = get_node(case, "CSP and PV plant") # The MultipleBuildingTypes node - 𝒫 = setdiff(get_products(case), [CO2]) - - # Run the model - m = EMB.run_model(case, modeltype, OPTIMIZER) - - # Extraction of the time structure - 𝒯 = get_time_struct(case) - - # Run of the general tests - general_tests(m) - - # Test that curtailment is correctly with respect to the profile. - @test sum(value.(m[:solar_curtailment][csp_and_pv_plant, t, Power]) > 0.0 for t ∈ 𝒯) == - 102 - - # Test constraints from EMB.constraints_capacity - @test sum( - value.(m[:solar_cap_use][csp_and_pv_plant, t, p]) ≤ - EMB.capacity(csp_and_pv_plant, t, p) for t ∈ 𝒯, p ∈ 𝒫 - ) == length(𝒯) * length(𝒫) - - @test sum( - value.(m[:solar_cap_use][csp_and_pv_plant, t, p]) + - value.(m[:solar_curtailment][csp_and_pv_plant, t, p]) ≈ - EMRP.profile(csp_and_pv_plant, t, p) * - EMB.capacity(csp_and_pv_plant, t, p) for t ∈ 𝒯, p ∈ 𝒫 - ) == length(𝒯) * length(𝒫) - - @test sum( - sum(value.(m[:solar_curtailment][csp_and_pv_plant, t, p]) for p ∈ 𝒫) ≈ - value(m[:curtailment][csp_and_pv_plant, t]) for t ∈ 𝒯 - ) == length(𝒯) - - # Test constraints frmo EMB.constraints_flow_out - @test sum( - value(m[:flow_out][csp_and_pv_plant, t, p]) ≈ - value(m[:solar_cap_use][csp_and_pv_plant, t, p]) * outputs(csp_and_pv_plant, p) for - t ∈ 𝒯, p ∈ 𝒫 - ) == length(𝒯) * length(𝒫) - - # Test constraints from EMB.constraints_opex_var - 𝒯ᴵⁿᵛ = strategic_periods(𝒯) - @test sum( - value(m[:opex_var][csp_and_pv_plant, t_inv]) ≈ - sum( - value(m[:solar_cap_use][csp_and_pv_plant, t, p]) * - EMB.opex_var(csp_and_pv_plant, t, p) * - scale_op_sp(t_inv, t) for t ∈ t_inv, p ∈ outputs(csp_and_pv_plant) - ) for t_inv ∈ 𝒯ᴵⁿᵛ - ) == length(𝒯ᴵⁿᵛ) - - # Test constraints from EMB.constraints_opex_fixed - @test sum( - value(m[:opex_fixed][csp_and_pv_plant, t_inv]) ≈ - sum( - EMB.opex_fixed(csp_and_pv_plant, t_inv, p) * - EMB.capacity(csp_and_pv_plant, first(t_inv), p) for - p ∈ outputs(csp_and_pv_plant) - ) for t_inv ∈ 𝒯ᴵⁿᵛ - ) == length(𝒯ᴵⁿᵛ) - - # Test that the EMB function has_capacity is false for the CSPandPV node. - @test !EMB.has_capacity(csp_and_pv_plant) - - # Test the utility functions - for p ∈ 𝒫 - # Capacity - @test EMB.capacity(csp_and_pv_plant, p) isa TimeProfile - @test EMB.capacity(csp_and_pv_plant)[p] == EMB.capacity(csp_and_pv_plant, p) - @test EMB.capacity(csp_and_pv_plant, first(𝒯), p) == - EMB.capacity(csp_and_pv_plant, p)[first(𝒯)] - - # OPEX variable - @test EMB.opex_var(csp_and_pv_plant, p) isa TimeProfile - @test EMB.opex_var(csp_and_pv_plant)[p] == EMB.opex_var(csp_and_pv_plant, p) - @test EMB.opex_var(csp_and_pv_plant, first(𝒯), p) == - EMB.opex_var(csp_and_pv_plant, p)[first(𝒯)] - - # OPEX fixed - @test EMB.opex_fixed(csp_and_pv_plant, p) isa TimeProfile - @test EMB.opex_fixed(csp_and_pv_plant)[p] == EMB.opex_fixed(csp_and_pv_plant, p) - @test EMB.opex_fixed(csp_and_pv_plant, first(𝒯ᴵⁿᵛ), p) == - EMB.opex_fixed(csp_and_pv_plant, p)[first(𝒯ᴵⁿᵛ)] - - # Profile - @test EMRP.profile(csp_and_pv_plant, p) isa TimeProfile - @test EMRP.profile(csp_and_pv_plant)[p] == EMRP.profile(csp_and_pv_plant, p) - @test EMRP.profile(csp_and_pv_plant, first(𝒯), p) == - EMRP.profile(csp_and_pv_plant, p)[first(𝒯)] + for i ∈ 1:2 # Run the test two times to also test running from stored files (from first run) + case, modeltype = simple_graph_csp_pv() + + csp_and_pv_plant = get_node(case, "CSP and PV plant") # The MultipleBuildingTypes node + 𝒫 = setdiff(get_products(case), [CO2]) + + # Run the model + m = EMB.run_model(case, modeltype, OPTIMIZER) + + # Extraction of the time structure + 𝒯 = get_time_struct(case) + + # Run of the general tests + general_tests(m) + + # Test that curtailment is correctly with respect to the profile. + @test sum( + value.(m[:solar_curtailment][csp_and_pv_plant, t, Power]) > 0.0 for t ∈ 𝒯 + ) == + 102 + + # Test constraints from EMB.constraints_capacity + @test sum( + value.(m[:solar_cap_use][csp_and_pv_plant, t, p]) ≤ + EMB.capacity(csp_and_pv_plant, t, p) for t ∈ 𝒯, p ∈ 𝒫 + ) == length(𝒯) * length(𝒫) + + @test sum( + value.(m[:solar_cap_use][csp_and_pv_plant, t, p]) + + value.(m[:solar_curtailment][csp_and_pv_plant, t, p]) ≈ + EMRP.profile(csp_and_pv_plant, t, p) * + EMB.capacity(csp_and_pv_plant, t, p) for t ∈ 𝒯, p ∈ 𝒫 + ) == length(𝒯) * length(𝒫) + + @test sum( + sum(value.(m[:solar_curtailment][csp_and_pv_plant, t, p]) for p ∈ 𝒫) ≈ + value(m[:curtailment][csp_and_pv_plant, t]) for t ∈ 𝒯 + ) == length(𝒯) + + # Test constraints frmo EMB.constraints_flow_out + @test sum( + value(m[:flow_out][csp_and_pv_plant, t, p]) ≈ + value(m[:solar_cap_use][csp_and_pv_plant, t, p]) * outputs(csp_and_pv_plant, p) + for + t ∈ 𝒯, p ∈ 𝒫 + ) == length(𝒯) * length(𝒫) + + # Test constraints from EMB.constraints_opex_var + 𝒯ᴵⁿᵛ = strategic_periods(𝒯) + @test sum( + value(m[:opex_var][csp_and_pv_plant, t_inv]) ≈ + sum( + value(m[:solar_cap_use][csp_and_pv_plant, t, p]) * + EMB.opex_var(csp_and_pv_plant, t, p) * + scale_op_sp(t_inv, t) for t ∈ t_inv, p ∈ outputs(csp_and_pv_plant) + ) for t_inv ∈ 𝒯ᴵⁿᵛ + ) == length(𝒯ᴵⁿᵛ) + + # Test constraints from EMB.constraints_opex_fixed + @test sum( + value(m[:opex_fixed][csp_and_pv_plant, t_inv]) ≈ + sum( + EMB.opex_fixed(csp_and_pv_plant, t_inv, p) * + EMB.capacity(csp_and_pv_plant, first(t_inv), p) for + p ∈ outputs(csp_and_pv_plant) + ) for t_inv ∈ 𝒯ᴵⁿᵛ + ) == length(𝒯ᴵⁿᵛ) + + # Test that the EMB function has_capacity is false for the CSPandPV node. + @test !EMB.has_capacity(csp_and_pv_plant) + + # Test the utility functions + for p ∈ 𝒫 + # Capacity + @test EMB.capacity(csp_and_pv_plant, p) isa TimeProfile + @test EMB.capacity(csp_and_pv_plant)[p] == EMB.capacity(csp_and_pv_plant, p) + @test EMB.capacity(csp_and_pv_plant, first(𝒯), p) == + EMB.capacity(csp_and_pv_plant, p)[first(𝒯)] + + # OPEX variable + @test EMB.opex_var(csp_and_pv_plant, p) isa TimeProfile + @test EMB.opex_var(csp_and_pv_plant)[p] == EMB.opex_var(csp_and_pv_plant, p) + @test EMB.opex_var(csp_and_pv_plant, first(𝒯), p) == + EMB.opex_var(csp_and_pv_plant, p)[first(𝒯)] + + # OPEX fixed + @test EMB.opex_fixed(csp_and_pv_plant, p) isa TimeProfile + @test EMB.opex_fixed(csp_and_pv_plant)[p] == EMB.opex_fixed(csp_and_pv_plant, p) + @test EMB.opex_fixed(csp_and_pv_plant, first(𝒯ᴵⁿᵛ), p) == + EMB.opex_fixed(csp_and_pv_plant, p)[first(𝒯ᴵⁿᵛ)] + + # Profile + @test EMRP.profile(csp_and_pv_plant, p) isa TimeProfile + @test EMRP.profile(csp_and_pv_plant)[p] == EMRP.profile(csp_and_pv_plant, p) + @test EMRP.profile(csp_and_pv_plant, first(𝒯), p) == + EMRP.profile(csp_and_pv_plant, p)[first(𝒯)] + end end end diff --git a/test/test_buildings.jl b/test/test_buildings.jl index 89fdd00..d323fba 100644 --- a/test/test_buildings.jl +++ b/test/test_buildings.jl @@ -1,89 +1,94 @@ @testset "MultipleBuildingTypes" begin - case, modeltype = simple_graph_buildings() + for i ∈ 1:2 # Run the test two times to also test running from stored files (from first run) + case, modeltype = simple_graph_buildings() - buildings = get_node(case, "Buildings") # The MultipleBuildingTypes node - products = get_products(case) - building_res = products[1:(end-1)] # All resources except CO2 - CO2 = products[end] # The CO2 resource + buildings = get_node(case, "Buildings") # The MultipleBuildingTypes node + products = get_products(case) + building_res = products[1:(end-1)] # All resources except CO2 + CO2 = products[end] # The CO2 resource - # Run the model - m = EMB.run_model(case, modeltype, OPTIMIZER) + # Run the model + m = EMB.run_model(case, modeltype, OPTIMIZER) - # Extraction of the time structure - 𝒯 = get_time_struct(case) + # Extraction of the time structure + 𝒯 = get_time_struct(case) - # Run of the general tests - general_tests(m) + # Run of the general tests + general_tests(m) - @test all( - value.(m[:buildings_surplus][buildings, t, p]) == 0.0 for t ∈ 𝒯, p ∈ building_res - ) - @test all( - value.(m[:buildings_deficit][buildings, t, p]) == 0.0 for t ∈ 𝒯, p ∈ building_res - ) - @test all(value.(m[:emissions_total][t, CO2]) > 0.001 for t ∈ 𝒯) + @test all( + value.(m[:buildings_surplus][buildings, t, p]) == 0.0 for + t ∈ 𝒯, p ∈ building_res + ) + @test all( + value.(m[:buildings_deficit][buildings, t, p]) == 0.0 for + t ∈ 𝒯, p ∈ building_res + ) + @test all(value.(m[:emissions_total][t, CO2]) > 0.001 for t ∈ 𝒯) - # Test that the EMB function has_capacity is false for the MultipleBuildingTypes node. - @test !EMB.has_capacity(buildings) + # Test that the EMB function has_capacity is false for the MultipleBuildingTypes node. + @test !EMB.has_capacity(buildings) - # Test constraints from EMB.constraints_capacity - @test all( - value.(m[:flow_in][buildings, t, p]) / inputs(buildings, p) + - value.(m[:buildings_deficit][buildings, t, p]) == - EMB.capacity(buildings, t, p) + value.(m[:buildings_surplus][buildings, t, p]) - for t ∈ 𝒯, p ∈ inputs(buildings) - ) + # Test constraints from EMB.constraints_capacity + @test all( + value.(m[:flow_in][buildings, t, p]) / inputs(buildings, p) + + value.(m[:buildings_deficit][buildings, t, p]) == + EMB.capacity(buildings, t, p) + value.(m[:buildings_surplus][buildings, t, p]) + for t ∈ 𝒯, p ∈ inputs(buildings) + ) - @test all( - sum(value.(m[:buildings_deficit][buildings, t, p]) for p ∈ inputs(buildings)) == - value.(m[:sink_deficit][buildings, t]) - for t ∈ 𝒯 - ) + @test all( + sum(value.(m[:buildings_deficit][buildings, t, p]) for p ∈ inputs(buildings)) == + value.(m[:sink_deficit][buildings, t]) + for t ∈ 𝒯 + ) - @test all( - sum(value.(m[:buildings_surplus][buildings, t, p]) for p ∈ inputs(buildings)) == - value.(m[:sink_surplus][buildings, t]) - for t ∈ 𝒯 - ) + @test all( + sum(value.(m[:buildings_surplus][buildings, t, p]) for p ∈ inputs(buildings)) == + value.(m[:sink_surplus][buildings, t]) + for t ∈ 𝒯 + ) - # Test constraints from EMB.constraints_opex_var - 𝒯ᴵⁿᵛ = strategic_periods(𝒯) - @test all( - value.(m[:opex_var][buildings, t_inv]) == - sum( - ( - value.(m[:buildings_surplus][buildings, t, p]) * - EMB.surplus_penalty(buildings, t, p) + - value.(m[:buildings_deficit][buildings, t, p]) * - EMB.deficit_penalty(buildings, t, p) - ) * scale_op_sp(t_inv, t) for t ∈ t_inv, p ∈ inputs(buildings) + # Test constraints from EMB.constraints_opex_var + 𝒯ᴵⁿᵛ = strategic_periods(𝒯) + @test all( + value.(m[:opex_var][buildings, t_inv]) == + sum( + ( + value.(m[:buildings_surplus][buildings, t, p]) * + EMB.surplus_penalty(buildings, t, p) + + value.(m[:buildings_deficit][buildings, t, p]) * + EMB.deficit_penalty(buildings, t, p) + ) * scale_op_sp(t_inv, t) for t ∈ t_inv, p ∈ inputs(buildings) + ) + for t_inv ∈ 𝒯ᴵⁿᵛ ) - for t_inv ∈ 𝒯ᴵⁿᵛ - ) - # Test the utility functions for MultipleBuildingTypes - for p ∈ building_res - # Capacity - @test EMB.capacity(buildings) isa Dict - @test EMB.capacity(buildings, p) isa TimeProfile - @test EMB.capacity(buildings)[p] == EMB.capacity(buildings, p) - @test EMB.capacity(buildings, first(𝒯), p) == EMB.capacity(buildings, p)[first(𝒯)] + # Test the utility functions for MultipleBuildingTypes + for p ∈ building_res + # Capacity + @test EMB.capacity(buildings) isa Dict + @test EMB.capacity(buildings, p) isa TimeProfile + @test EMB.capacity(buildings)[p] == EMB.capacity(buildings, p) + @test EMB.capacity(buildings, first(𝒯), p) == + EMB.capacity(buildings, p)[first(𝒯)] - # Surplus penalty - @test EMB.surplus_penalty(buildings) isa Dict - @test EMB.surplus_penalty(buildings, p) isa TimeProfile - @test EMB.surplus_penalty(buildings)[p] == EMB.surplus_penalty(buildings, p) - @test EMB.surplus_penalty(buildings, first(𝒯), p) == - EMB.surplus_penalty(buildings, p)[first(𝒯)] + # Surplus penalty + @test EMB.surplus_penalty(buildings) isa Dict + @test EMB.surplus_penalty(buildings, p) isa TimeProfile + @test EMB.surplus_penalty(buildings)[p] == EMB.surplus_penalty(buildings, p) + @test EMB.surplus_penalty(buildings, first(𝒯), p) == + EMB.surplus_penalty(buildings, p)[first(𝒯)] - # Deficit penalty - @test EMB.deficit_penalty(buildings) isa Dict - @test EMB.deficit_penalty(buildings, p) isa TimeProfile - @test EMB.deficit_penalty(buildings)[p] == EMB.deficit_penalty(buildings, p) - @test EMB.deficit_penalty(buildings, first(𝒯), p) == - EMB.deficit_penalty(buildings, p)[first(𝒯)] - end + # Deficit penalty + @test EMB.deficit_penalty(buildings) isa Dict + @test EMB.deficit_penalty(buildings, p) isa TimeProfile + @test EMB.deficit_penalty(buildings)[p] == EMB.deficit_penalty(buildings, p) + @test EMB.deficit_penalty(buildings, first(𝒯), p) == + EMB.deficit_penalty(buildings, p)[first(𝒯)] + end - # Test has_capacity utility function - @test EMB.has_capacity(buildings) == false + # Test has_capacity utility function + @test EMB.has_capacity(buildings) == false + end end diff --git a/test/utils.jl b/test/utils.jl index d84b9f8..9a30c9d 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -195,7 +195,7 @@ function simple_graph_buildings(; cap_p = nothing, penalty_deficit, # deficit penalty for the node in €/MWh; data = [EmissionsEnergy()], data_location = joinpath(pkgdir(EMLI), "test", "data", "buildings"), - overwrite_saved_data = true, + overwrite_saved_data = false, ) else buildings = MultipleBuildingTypes( From 9ac296e0b76fb5c4473db9db7233a7670a1fb62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vegard=20Ven=C3=A5s?= Date: Thu, 11 Dec 2025 13:22:35 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/test_CSPandPV.jl | 2 +- test/test_buildings.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_CSPandPV.jl b/test/test_CSPandPV.jl index 6b171c2..1e35d1f 100644 --- a/test/test_CSPandPV.jl +++ b/test/test_CSPandPV.jl @@ -3,7 +3,7 @@ using JSON using Dates @testset "CSPandPV" begin - for i ∈ 1:2 # Run the test two times to also test running from stored files (from first run) + for _ ∈ 1:2 # Run the test two times to also test running from stored files (from first run) case, modeltype = simple_graph_csp_pv() csp_and_pv_plant = get_node(case, "CSP and PV plant") # The MultipleBuildingTypes node diff --git a/test/test_buildings.jl b/test/test_buildings.jl index d323fba..2f8ec37 100644 --- a/test/test_buildings.jl +++ b/test/test_buildings.jl @@ -1,5 +1,5 @@ @testset "MultipleBuildingTypes" begin - for i ∈ 1:2 # Run the test two times to also test running from stored files (from first run) + for _ ∈ 1:2 # Run the test two times to also test running from stored files (from first run) case, modeltype = simple_graph_buildings() buildings = get_node(case, "Buildings") # The MultipleBuildingTypes node