From 0a7502f625d4cef33397a4f0d484580f4415930a Mon Sep 17 00:00:00 2001 From: Truls Flatberg Date: Mon, 29 Sep 2025 16:22:37 +0200 Subject: [PATCH 1/3] Enhance discount_avg function to support resolution parameter --- src/discount.jl | 19 ++++++++++--------- test/runtests.jl | 5 +++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/discount.jl b/src/discount.jl index 5677cf6..f247068 100644 --- a/src/discount.jl +++ b/src/discount.jl @@ -76,16 +76,15 @@ function discount(disc::Discounter, t::TimePeriod; type = "start", timeunit_to_y return discount(t, disc.ts, disc.discount_rate; type, timeunit_to_year) end -function discount_avg(discount_rate, start_year, duration_years) - if discount_rate > 0 - δ = 1 / (1 + discount_rate) - m = - (δ^start_year - δ^(start_year + duration_years)) / log(1 + discount_rate) / - duration_years - return m - else - return 1.0 +function discount_avg(discount_rate, start_year, duration_years; resolution::Int = 0) + discount_rate == 0 && return 1.0 + δ = 1 / (1 + discount_rate) + if resolution == 0 + return (δ^start_year - δ^(start_year + duration_years)) / log(1 + discount_rate) / + duration_years end + return sum(δ^(start_year + i / resolution) for i in 0:(resolution*duration_years-1)) / + (resolution * duration_years) end function discount_start(discount_rate, start_year) @@ -107,6 +106,8 @@ function discount( return discount_start(discount_rate, start_year) elseif type == "avg" return discount_avg(discount_rate, start_year, duration_years) + elseif type == "avg_year" + return discount_avg(discount_rate, start_year, duration_years; resolution = 1) end end diff --git a/test/runtests.jl b/test/runtests.jl index 23e4704..f9f1d54 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1335,6 +1335,11 @@ end @test discount(disc, t) == δ^(i - 1) end + for sp in strat_periods(uniform_years) + @test discount(sp, uniform_years, 0.06; type = "avg") < + discount(sp, uniform_years, 0.06; type = "avg_year") + end + @test sum(objective_weight(t, disc) for t in uniform_years) ≈ 8.435 atol = 1e-3 uniform_day = SimpleTimes(24, 1) From 4cce5fb9825dc7a1ff8fa96cbf186c479debd05a Mon Sep 17 00:00:00 2001 From: Truls Flatberg Date: Tue, 30 Sep 2025 22:05:14 +0200 Subject: [PATCH 2/3] Improve doc and tests, refactor and remove resolution parameter --- src/discount.jl | 47 +++++++++++++++++++++++++++-------------------- test/runtests.jl | 24 +++++++++++++++++------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/discount.jl b/src/discount.jl index f247068..d28c69a 100644 --- a/src/discount.jl +++ b/src/discount.jl @@ -56,9 +56,13 @@ end Calculates the discount factor to be used for a time period `t` using a fixed 'discount_rate`. There are two types of discounting -available, either discounting to the start of the time period -or calculating an approximate value for the average discount factor -over the whole time period (`type="avg"`). +available, either discounting to the start of the strategic period +containing the time period (`type="start"`) or calculating an approximate +value for the average discount factor over the whole strategic period. +The average can be calculated either as a continuous average (`type="avg"`) or +as a discrete average that discounts to the start of each year (`type="avg_year"`). +The `timeunit_to_year` parameter is used to convert the time units of +strategic periods in the time structure to years (default value = 1.0). """ function discount( t::TimePeriod, @@ -76,15 +80,15 @@ function discount(disc::Discounter, t::TimePeriod; type = "start", timeunit_to_y return discount(t, disc.ts, disc.discount_rate; type, timeunit_to_year) end -function discount_avg(discount_rate, start_year, duration_years; resolution::Int = 0) - discount_rate == 0 && return 1.0 +function discount_avg(discount_rate, start_year, duration_years) δ = 1 / (1 + discount_rate) - if resolution == 0 - return (δ^start_year - δ^(start_year + duration_years)) / log(1 + discount_rate) / - duration_years - end - return sum(δ^(start_year + i / resolution) for i in 0:(resolution*duration_years-1)) / - (resolution * duration_years) + return (δ^start_year - δ^(start_year + duration_years)) / log(1 + discount_rate) / + duration_years +end + +function discount_avg_year(discount_rate, start_year, duration_years) + δ = 1 / (1 + discount_rate) + return sum(δ^(start_year + i) for i in 0:(duration_years-1)) / duration_years end function discount_start(discount_rate, start_year) @@ -99,16 +103,19 @@ function discount( type = "start", timeunit_to_year = 1.0, ) - start_year = _to_year(_start_strat(sp, ts), timeunit_to_year) - duration_years = _to_year(duration_strat(sp), timeunit_to_year) - - if type == "start" - return discount_start(discount_rate, start_year) - elseif type == "avg" - return discount_avg(discount_rate, start_year, duration_years) - elseif type == "avg_year" - return discount_avg(discount_rate, start_year, duration_years; resolution = 1) + discount_factor = 1.0 + if discount_rate > 0 + start_year = _to_year(_start_strat(sp, ts), timeunit_to_year) + duration_years = _to_year(duration_strat(sp), timeunit_to_year) + if type == "start" + discount_factor = discount_start(discount_rate, start_year) + elseif type == "avg" + discount_factor = discount_avg(discount_rate, start_year, duration_years) + elseif type == "avg_year" + discount_factor = discount_avg_year(discount_rate, start_year, duration_years) + end end + return discount_factor end """ diff --git a/test/runtests.jl b/test/runtests.jl index f9f1d54..565751c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1335,20 +1335,30 @@ end @test discount(disc, t) == δ^(i - 1) end - for sp in strat_periods(uniform_years) - @test discount(sp, uniform_years, 0.06; type = "avg") < - discount(sp, uniform_years, 0.06; type = "avg_year") - end + @test sum( + discount(sp, uniform_years, 0.06; type = "avg_year") for + sp in strat_periods(uniform_years) + ) ≈ 7.8017 atol = 1e-3 @test sum(objective_weight(t, disc) for t in uniform_years) ≈ 8.435 atol = 1e-3 uniform_day = SimpleTimes(24, 1) - periods = TwoLevel(10, 8760, uniform_day) + periods = TwoLevel(10, 5 * 8760, uniform_day) + + @test sum( + objective_weight(sp, periods, 0.04; timeunit_to_year = 1 / 8760, type = "start") for + sp in strat_periods(periods) + ) ≈ 4.825 atol = 1e-3 + + @test sum( + objective_weight(sp, periods, 0.04; timeunit_to_year = 1 / 8760, type = "avg_year") + for sp in strat_periods(periods) + ) ≈ 4.468 atol = 1e-3 @test sum( - objective_weight(sp, periods, 0.04; timeunit_to_year = 1 / 8760) for + objective_weight(sp, periods, 0.04; timeunit_to_year = 1 / 8760, type = "avg") for sp in strat_periods(periods) - ) ≈ 8.435 atol = 1e-3 + ) ≈ 4.382 atol = 1e-3 uniform_day = SimpleTimes(24, 1u"hr") periods_unit = TwoLevel(10, 365.125u"d", uniform_day) From 811ac53d0824f9be4e12a551d7b6e0fb3adccda8 Mon Sep 17 00:00:00 2001 From: Truls Flatberg Date: Tue, 30 Sep 2025 22:18:46 +0200 Subject: [PATCH 3/3] Extend doc for objective_weight --- src/discount.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/discount.jl b/src/discount.jl index d28c69a..c2e20ac 100644 --- a/src/discount.jl +++ b/src/discount.jl @@ -123,7 +123,14 @@ end Returns an overall weight to be used for a time period `t` in the objective function considering both discounting, -probability and possible multiplicity. +probability and possible multiplicity. There are two types of discounting +available, either discounting to the start of the strategic period +containing the time period (`type="start"`) or calculating an approximate +value for the average discount factor over the whole strategic period. +The average can be calculated either as a continuous average (`type="avg"`) or +as a discrete average that discounts to the start of each year (`type="avg_year"`). +The `timeunit_to_year` parameter is used to convert the time units of +strategic periods in the time structure to years (default value = 1.0). """ function objective_weight( t::TimePeriod,