diff --git a/scripts/coverage/Project.toml b/scripts/coverage/Project.toml new file mode 100644 index 00000000..a7b2880f --- /dev/null +++ b/scripts/coverage/Project.toml @@ -0,0 +1,7 @@ +[deps] +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" +CoverageTools = "c36e975a-824b-4404-a568-ef97ca766997" + +[compat] +Coverage = "1" +CoverageTools = "1" diff --git a/scripts/generate_lcov.jl b/scripts/generate_lcov.jl index d16dd23a..0092a990 100644 --- a/scripts/generate_lcov.jl +++ b/scripts/generate_lcov.jl @@ -5,7 +5,7 @@ # # Usage: # 1. Run tests with coverage: julia --project=. -e 'using TestEnv; TestEnv.activate(); include("test/load_tests.jl"); InfrastructureOptimizationModelsTests.run_tests()' -# 2. Generate lcov: julia --project=. -e 'using TestEnv; TestEnv.activate(); include("scripts/generate_lcov.jl")' +# 2. Generate lcov: julia --project=scripts/coverage -e 'using Pkg; Pkg.instantiate(); include("scripts/generate_lcov.jl")' using CoverageTools using Coverage diff --git a/scripts/test_with_coverage.sh b/scripts/test_with_coverage.sh index f32ac543..251ccee2 100755 --- a/scripts/test_with_coverage.sh +++ b/scripts/test_with_coverage.sh @@ -14,9 +14,7 @@ julia --project=. --code-coverage -e ' ' echo "==> Generating lcov.info..." -julia --project=. -e ' - using TestEnv; TestEnv.activate() - include("scripts/generate_lcov.jl") -' +# Coverage.jl caps HTTP at 1.x while the OpenAPI packages need 2.x, so it has its own env. +julia --project=scripts/coverage -e 'using Pkg; Pkg.instantiate(); include("scripts/generate_lcov.jl")' echo "==> Done. lcov.info written to $(pwd)/lcov.info" diff --git a/src/utils/component_utils.jl b/src/utils/component_utils.jl index f45e118c..8b537acc 100644 --- a/src/utils/component_utils.jl +++ b/src/utils/component_utils.jl @@ -73,6 +73,22 @@ end ########### Cost Function Utilities ############## ################################################## +""" + _system_base_ratio(unit_system, system_base_power, device_base_power) -> Float64 + +The x-axis ratio `x_from = ratio * x_su` from `unit_system` to the system base, the +base arithmetic `IS.convert_cost_coefficient` no longer resolves itself. +""" +_system_base_ratio(::IS.SystemBaseUnit, ::Float64, ::Float64) = 1.0 +_system_base_ratio( + ::IS.ComponentBaseUnit, + system_base_power::Float64, + device_base_power::Float64, +) = + system_base_power / device_base_power +_system_base_ratio(::IS.NaturalUnit, system_base_power::Float64, ::Float64) = + system_base_power + """ Proportional (slope) cost coefficient normalized to system base. """ @@ -82,8 +98,8 @@ get_proportional_cost_per_system_unit( system_base_power::Float64, device_base_power::Float64, ) = IS.convert_cost_coefficient( - cost_term, unit_system, IS.SU, - system_base_power, device_base_power, + cost_term, + _system_base_ratio(unit_system, system_base_power, device_base_power), ) """ @@ -95,8 +111,9 @@ get_quadratic_cost_per_system_unit( system_base_power::Float64, device_base_power::Float64, ) = IS.convert_cost_coefficient( - cost_term, unit_system, IS.SU, - system_base_power, device_base_power, 2, + cost_term, + _system_base_ratio(unit_system, system_base_power, device_base_power), + 2, ) """ @@ -110,8 +127,9 @@ function get_piecewise_pointcurve_per_system_unit( device_base_power::Float64, ) x_ratio = IS.convert_cost_coefficient( - 1.0, unit_system, IS.SU, - system_base_power, device_base_power, -1, + 1.0, + _system_base_ratio(unit_system, system_base_power, device_base_power), + -1, ) points = cost_component.points points_normalized = similar(points) @@ -165,14 +183,9 @@ function get_piecewise_curve_per_system_unit( system_base_power::Float64, device_base_power::Float64, ) - x_ratio = IS.convert_cost_coefficient( - 1.0, unit_system, IS.SU, - system_base_power, device_base_power, -1, - ) - y_ratio = IS.convert_cost_coefficient( - 1.0, unit_system, IS.SU, - system_base_power, device_base_power, 1, - ) + ratio = _system_base_ratio(unit_system, system_base_power, device_base_power) + x_ratio = IS.convert_cost_coefficient(1.0, ratio, -1) + y_ratio = IS.convert_cost_coefficient(1.0, ratio, 1) return x_coords .* x_ratio, y_coords .* y_ratio end diff --git a/test/InfrastructureOptimizationModelsTests.jl b/test/InfrastructureOptimizationModelsTests.jl index 890cc378..bded2c1c 100644 --- a/test/InfrastructureOptimizationModelsTests.jl +++ b/test/InfrastructureOptimizationModelsTests.jl @@ -122,6 +122,7 @@ function run_tests() # --- objective_function/ subfolder --- # import_export.jl: commented out include(joinpath(TEST_DIR, "test_cost_term_helpers.jl")) + include(joinpath(TEST_DIR, "test_cost_unit_conversion.jl")) include(joinpath(TEST_DIR, "test_linear_curve.jl")) # value_curve_cost.jl: needs more work include(joinpath(TEST_DIR, "test_piecewise_linear.jl")) diff --git a/test/Project.toml b/test/Project.toml index c84ef001..87fc4ac4 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -2,8 +2,6 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" -CoverageTools = "c36e975a-824b-4404-a568-ef97ca766997" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataFramesMeta = "1313f7d8-7da2-5740-9ea0-a2ca25f37964" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/test/test_cost_unit_conversion.jl b/test/test_cost_unit_conversion.jl new file mode 100644 index 00000000..3f3eb957 --- /dev/null +++ b/test/test_cost_unit_conversion.jl @@ -0,0 +1,97 @@ +""" +Unit tests for the cost-coefficient base conversions in src/utils/component_utils.jl. +`IS.convert_cost_coefficient` takes a bare x-axis ratio; the helpers derive it from the +curve's unit system and the two base powers. +""" + +@testset "Cost coefficient conversion to system base" begin + system_base = 100.0 + device_base = 20.0 + + @testset "proportional term" begin + @test IOM.get_proportional_cost_per_system_unit( + 3.0, + IS.SU, + system_base, + device_base, + ) == 3.0 + # \$/MW -> \$/(system pu MW): one system pu is 100 MW + @test IOM.get_proportional_cost_per_system_unit( + 3.0, + IS.NU, + system_base, + device_base, + ) == 300.0 + # \$/(device pu MW) -> \$/(system pu MW): one system pu is five device pu + @test IOM.get_proportional_cost_per_system_unit( + 3.0, + IS.CU, + system_base, + device_base, + ) == 15.0 + end + + @testset "quadratic term scales with the square of the ratio" begin + @test IOM.get_quadratic_cost_per_system_unit( + 2.0, + IS.SU, + system_base, + device_base, + ) == 2.0 + @test IOM.get_quadratic_cost_per_system_unit( + 2.0, + IS.NU, + system_base, + device_base, + ) == 2.0e4 + @test IOM.get_quadratic_cost_per_system_unit( + 2.0, + IS.CU, + system_base, + device_base, + ) == 50.0 + end + + @testset "point curve rescales x only" begin + curve = IS.PiecewiseLinearData([(x = 0.0, y = 0.0), (x = 20.0, y = 100.0)]) + natural = IOM.get_piecewise_pointcurve_per_system_unit( + curve, + IS.NU, + system_base, + device_base, + ) + @test IS.get_x_coords(natural) == [0.0, 0.2] + @test IS.get_y_coords(natural) == [0.0, 100.0] + device = IOM.get_piecewise_pointcurve_per_system_unit( + curve, + IS.CU, + system_base, + device_base, + ) + @test IS.get_x_coords(device) == [0.0, 4.0] + @test IOM.get_piecewise_pointcurve_per_system_unit( + curve, + IS.SU, + system_base, + device_base, + ) === curve + end + + @testset "step curve rescales x down and y up" begin + curve = IS.PiecewiseStepData([0.0, 20.0], [5.0]) + natural = + IOM.get_piecewise_curve_per_system_unit(curve, IS.NU, system_base, device_base) + @test IS.get_x_coords(natural) == [0.0, 0.2] + @test IS.get_y_coords(natural) == [500.0] + device = + IOM.get_piecewise_curve_per_system_unit(curve, IS.CU, system_base, device_base) + @test IS.get_x_coords(device) == [0.0, 4.0] + @test IS.get_y_coords(device) == [25.0] + @test IOM.get_piecewise_curve_per_system_unit( + curve, + IS.SU, + system_base, + device_base, + ) === curve + end +end