Skip to content
7 changes: 7 additions & 0 deletions scripts/coverage/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[deps]
Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037"
CoverageTools = "c36e975a-824b-4404-a568-ef97ca766997"

[compat]
Coverage = "1"
CoverageTools = "1"
2 changes: 1 addition & 1 deletion scripts/generate_lcov.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions scripts/test_with_coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions scripts/units_dispatch_profile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ function make_container(devices)
end

# Build N components, evenly distributed across the 3 unit systems. The cost
# *curves* are concretely typed (CostCurve{LinearCurve, NaturalUnit/SU/DU}) but
# *curves* are concretely typed (CostCurve{LinearCurve, NaturalUnit/SU/CU}) but
# the abstractly-typed `MockThermalGen` storage means upstream callers see a
# UnionAll. This is the realistic shape of consuming code.
function make_workload()
devices = MockThermalGen[]
curves = Any[] # heterogeneous on U, simulates abstract field upstream
units = (IS.NaturalUnit(), IS.SystemBaseUnit(), IS.DeviceBaseUnit())
units = (IS.NaturalUnit(), IS.SystemBaseUnit(), IS.ComponentBaseUnit())
for i in 1:N_COMPONENTS
u = units[mod1(i, 3)]
push!(devices, make_mock_thermal("g$i"; base_power = 50.0 + i))
Expand Down
20 changes: 19 additions & 1 deletion src/operation/problem_template.jl
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,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
Expand Down
41 changes: 27 additions & 14 deletions src/utils/component_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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),
)

"""
Expand All @@ -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,
)

"""
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions test/InfrastructureOptimizationModelsTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ 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

# --- 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"))
Expand Down
2 changes: 0 additions & 2 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
97 changes: 97 additions & 0 deletions test/test_cost_unit_conversion.jl
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions test/test_linear_curve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ end
# Cost: 30 $/p.u.h in device base units
cost_curve = IS.CostCurve(
IS.LinearCurve(30.0),
IS.DeviceBaseUnit(),
IS.ComponentBaseUnit(),
)

InfrastructureOptimizationModels.add_variable_cost_to_objective!(
Expand Down Expand Up @@ -312,7 +312,7 @@ end
),
DEVICE_BASE = IS.CostCurve(
IS.LinearCurve(rate * device_base),
IS.DeviceBaseUnit(),
IS.ComponentBaseUnit(),
),
)

Expand Down
43 changes: 43 additions & 0 deletions test/test_problem_template.jl
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions test/test_quadratic_curve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ end

cost_curve = IS.CostCurve(
IS.QuadraticCurve(1.0, 20.0, 0.0),
IS.DeviceBaseUnit(),
IS.ComponentBaseUnit(),
)

InfrastructureOptimizationModels.add_variable_cost_to_objective!(
Expand Down Expand Up @@ -404,7 +404,7 @@ end
),
DEVICE_BASE = IS.CostCurve(
IS.QuadraticCurve(a * device_base^2, b * device_base, 0.0),
IS.DeviceBaseUnit(),
IS.ComponentBaseUnit(),
),
)

Expand Down
2 changes: 1 addition & 1 deletion test/test_ts_value_curve_objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ end
for (unit_system, expected_slope_factor, expected_bp_factor) in [
(IS.NaturalUnit(), system_base, 1.0 / system_base),
(
IS.DeviceBaseUnit(),
IS.ComponentBaseUnit(),
1.0 / (device_base / system_base),
device_base / system_base,
),
Expand Down
Loading