From 5fcbcfa20de354a499309860f406eb69e6139263 Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Mon, 20 Oct 2025 18:08:26 -0700 Subject: [PATCH 01/12] first commit for array averagedspecifiedtimes --- src/OutputWriters/windowed_time_average.jl | 58 +++++++++++++++++++--- src/Utils/prettytime.jl | 1 + src/Utils/schedules.jl | 2 + src/Utils/times_and_datetimes.jl | 8 +++ 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index c0c98d2692..73d8617a24 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -91,14 +91,12 @@ function (sch::AveragedTimeInterval)(model) return scheduled end initialize_schedule!(sch::AveragedTimeInterval, clock) = nothing -outside_window(sch::AveragedTimeInterval, clock) = clock.time <= next_actuation_time(sch) - sch.window +outside_window(sch::AveragedTimeInterval, clock) = clock.time <= next_actuation_time(sch) - sch.window end_of_window(sch::AveragedTimeInterval, clock) = clock.time >= next_actuation_time(sch) TimeInterval(sch::AveragedTimeInterval) = TimeInterval(sch.interval) Base.copy(sch::AveragedTimeInterval) = AveragedTimeInterval(sch.interval, window=sch.window, stride=sch.stride) - - """ mutable struct AveragedSpecifiedTimes <: AbstractSchedule @@ -144,6 +142,11 @@ function end_of_window(schedule::AveragedSpecifiedTimes, clock) return clock.time >= next_time end +TimeInterval(sch::AveragedSpecifiedTimes) = TimeInterval(sch.specified_times.times) +Base.copy(sch::AveragedSpecifiedTimes) = AveragedSpecifiedTimes(copy(sch.specified_times); window=sch.window, stride=sch.stride) + +next_actuation_time(sch::AveragedSpecifiedTimes) = Oceananigans.Utils.next_actuation_time(sch.specified_times) + ##### ##### WindowedTimeAverage ##### @@ -261,6 +264,40 @@ function advance_time_average!(wta::WindowedTimeAverage, model) return nothing end +function advance_time_average!(wta::SpecifiedWindowedTimeAverage, model) + + unscheduled = model.clock.iteration == 0 || outside_window(wta.schedule, model.clock) + if !(unscheduled) + if !(wta.schedule.collecting) + # Zero out result to begin new accumulation window + wta.result .= 0 + + # Begin collecting window-averaged increments + wta.schedule.collecting = true + + wta.window_start_time = next_actuation_time(wta.schedule) - wta.schedule.window + wta.previous_collection_time = wta.window_start_time + wta.window_start_iteration = model.clock.iteration - 1 + # @info "t $(prettytime(model.clock.time)), next actuation time: $(prettytime(next_actuation_time(wta.schedule))), window $(prettytime(wta.schedule.window))" + end + + if end_of_window(wta.schedule, model.clock) + accumulate_result!(wta, model) + # Save averaging start time and the initial data collection time + wta.schedule.collecting = false + wta.schedule.specified_times.previous_actuation += 1 + + elseif mod(model.clock.iteration - wta.window_start_iteration, stride(wta)) == 0 + accumulate_result!(wta, model) + else + # Off stride, so do nothing. + end + + end + return nothing +end + + # So it can be used as a Diagnostic run_diagnostic!(wta::WindowedTimeAverage, model) = advance_time_average!(wta, model) @@ -271,8 +308,14 @@ Base.summary(schedule::AveragedTimeInterval) = string("AveragedTimeInterval(", "stride=", schedule.stride, ", ", "interval=", prettytime(schedule.interval), ")") +Base.summary(schedule::AveragedSpecifiedTimes) = string("AveragedSpecifiedTimes(", + "window=", prettytime(schedule.window), ", ", + "stride=", schedule.stride, ", ", + "times=", schedule.specified_times, ")") + show_averaging_schedule(schedule) = "" show_averaging_schedule(schedule::AveragedTimeInterval) = string(" averaged on ", summary(schedule)) +show_averaging_schedule(schedule::AveragedSpecifiedTimes) = string(" averaged on ", summary(schedule)) output_averaging_schedule(output::WindowedTimeAverage) = output.schedule @@ -282,6 +325,8 @@ output_averaging_schedule(output::WindowedTimeAverage) = output.schedule time_average_outputs(schedule, outputs, model) = schedule, outputs # fallback +const AveragedTimeSchedule = Union{AveragedTimeInterval, AveragedSpecifiedTimes} + """ time_average_outputs(schedule::AveragedTimeInterval, outputs, model, field_slicer) @@ -290,17 +335,16 @@ Wrap each `output` in a `WindowedTimeAverage` on the time-averaged `schedule` an Returns the `TimeInterval` associated with `schedule` and a `NamedTuple` or `Dict` of the wrapped outputs. """ -function time_average_outputs(schedule::AveragedTimeInterval, outputs::Dict, model) +function time_average_outputs(schedule::AveragedTimeSchedule, outputs::Dict, model) averaged_outputs = Dict(name => WindowedTimeAverage(output, model; schedule=copy(schedule)) for (name, output) in outputs) return TimeInterval(schedule), averaged_outputs end -function time_average_outputs(schedule::AveragedTimeInterval, outputs::NamedTuple, model) +function time_average_outputs(schedule::AveragedTimeSchedule, outputs::NamedTuple, model) averaged_outputs = NamedTuple(name => WindowedTimeAverage(outputs[name], model; schedule=copy(schedule)) for name in keys(outputs)) return TimeInterval(schedule), averaged_outputs -end - +end \ No newline at end of file diff --git a/src/Utils/prettytime.jl b/src/Utils/prettytime.jl index ac71dbd676..f2e3e20f85 100644 --- a/src/Utils/prettytime.jl +++ b/src/Utils/prettytime.jl @@ -64,3 +64,4 @@ function prettytimeunits(t, longform=true) end prettytime(dt::AbstractTime) = "$dt" +prettytime(t::Array) = prettytime.(t) diff --git a/src/Utils/schedules.jl b/src/Utils/schedules.jl index d7c72763c6..88bab3269c 100644 --- a/src/Utils/schedules.jl +++ b/src/Utils/schedules.jl @@ -234,6 +234,8 @@ function specified_times_str(st) return string(str, "]") end +Base.copy(st::SpecifiedTimes) = SpecifiedTimes(copy(st.times), st.previous_actuation) + ##### ##### ConsecutiveIterations ##### diff --git a/src/Utils/times_and_datetimes.jl b/src/Utils/times_and_datetimes.jl index 7521c64115..a2c0df07f8 100644 --- a/src/Utils/times_and_datetimes.jl +++ b/src/Utils/times_and_datetimes.jl @@ -29,11 +29,19 @@ end @inline add_time_interval(base::AbstractTime, interval::Number, count=1) = base + seconds_to_nanosecond(interval * count) @inline add_time_interval(base::AbstractTime, interval::Period, count=1) = base + count * interval +@inline add_time_interval(base::Number, interval::Array{<:Number}, count=1) = interval[count] + function period_type(interval::Number) FT = Oceananigans.defaults.FloatType return FT end +function period_type(interval::Array{<:Number}) + FT = Oceananigans.defaults.FloatType + return Array{FT, 1} +end + period_type(interval::Dates.Period) = typeof(interval) time_type(interval::Number) = typeof(interval) time_type(interval::Dates.Period) = Dates.DateTime +time_type(interval::Array{<:Number}) = eltype(interval) From 5457bc51d2557cbb921086ff01372a766cb4f593 Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Tue, 21 Oct 2025 16:39:37 -0700 Subject: [PATCH 02/12] allow window to be arrays --- src/OutputWriters/windowed_time_average.jl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index 73d8617a24..6f76b78612 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -102,18 +102,23 @@ Base.copy(sch::AveragedTimeInterval) = AveragedTimeInterval(sch.interval, window A schedule for averaging over windows that precede SpecifiedTimes. """ -mutable struct AveragedSpecifiedTimes <: AbstractSchedule +mutable struct AveragedSpecifiedTimes{W} <: AbstractSchedule specified_times :: SpecifiedTimes - window :: Float64 + window :: W stride :: Int collecting :: Bool end +const VaryingWindowAveragedSpecifiedTimes = AveragedSpecifiedTimes{Vector{Float64}} + AveragedSpecifiedTimes(specified_times::SpecifiedTimes; window, stride=1) = AveragedSpecifiedTimes(specified_times, window, stride, false) AveragedSpecifiedTimes(times; kw...) = AveragedSpecifiedTimes(SpecifiedTimes(times); kw...) +get_next_window(schedule::VaryingWindowAveragedSpecifiedTimes) = schedule.window[schedule.specified_times.previous_actuation + 1] +get_next_window(schedule::AveragedSpecifiedTimes) = schedule.window + function (schedule::AveragedSpecifiedTimes)(model) time = model.clock.time @@ -121,7 +126,7 @@ function (schedule::AveragedSpecifiedTimes)(model) next > length(schedule.specified_times.times) && return false next_time = schedule.specified_times.times[next] - window = schedule.window + window = get_next_window(schedule) schedule.collecting || time >= next_time - window end @@ -132,7 +137,8 @@ function outside_window(schedule::AveragedSpecifiedTimes, clock) next = schedule.specified_times.previous_actuation + 1 next > length(schedule.specified_times.times) && return true next_time = schedule.specified_times.times[next] - return clock.time < next_time - schedule.window + window = get_next_window(schedule) + return clock.time < next_time - window end function end_of_window(schedule::AveragedSpecifiedTimes, clock) @@ -171,7 +177,7 @@ stride(wta::SpecifiedWindowedTimeAverage) = wta.schedule.stride WindowedTimeAverage(operand, model=nothing; schedule) Returns an object for computing running averages of `operand` over `schedule.window` and -recurring on `schedule.interval`, where `schedule` is an `AveragedTimeInterval`. +recurring on `schedule.interval`, where `schedule` is an `AveragedTimeInterval` or `AveragedSpecifiedTimes`. During the collection period, averages are computed every `schedule.stride` iteration. `operand` may be a `Oceananigans.Field` or a function that returns an array or scalar. @@ -275,7 +281,7 @@ function advance_time_average!(wta::SpecifiedWindowedTimeAverage, model) # Begin collecting window-averaged increments wta.schedule.collecting = true - wta.window_start_time = next_actuation_time(wta.schedule) - wta.schedule.window + wta.window_start_time = next_actuation_time(wta.schedule) - get_next_window(wta.schedule) wta.previous_collection_time = wta.window_start_time wta.window_start_iteration = model.clock.iteration - 1 # @info "t $(prettytime(model.clock.time)), next actuation time: $(prettytime(next_actuation_time(wta.schedule))), window $(prettytime(wta.schedule.window))" From 96872aa73e57d83e42f488c934ff5e304b3718ad Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Tue, 21 Oct 2025 17:52:25 -0700 Subject: [PATCH 03/12] Enhance AveragedSpecifiedTimes to support vector windows and add validation for overlapping windows --- src/OutputWriters/windowed_time_average.jl | 52 +++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index 6f76b78612..c174e820d8 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -102,7 +102,7 @@ Base.copy(sch::AveragedTimeInterval) = AveragedTimeInterval(sch.interval, window A schedule for averaging over windows that precede SpecifiedTimes. """ -mutable struct AveragedSpecifiedTimes{W} <: AbstractSchedule +mutable struct AveragedSpecifiedTimes{W <: Union{Float64, Vector{Float64}}} <: AbstractSchedule specified_times :: SpecifiedTimes window :: W stride :: Int @@ -114,7 +114,55 @@ const VaryingWindowAveragedSpecifiedTimes = AveragedSpecifiedTimes{Vector{Float6 AveragedSpecifiedTimes(specified_times::SpecifiedTimes; window, stride=1) = AveragedSpecifiedTimes(specified_times, window, stride, false) -AveragedSpecifiedTimes(times; kw...) = AveragedSpecifiedTimes(SpecifiedTimes(times); kw...) +AveragedSpecifiedTimes(times; window, kw...) = AveragedSpecifiedTimes(times, window; kw...) +# AveragedSpecifiedTimes(times; window::Float64; kw...) = AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) + +function AveragedSpecifiedTimes(times, window::Vector{Float64}; kw...) + length(window) == length(times) || throw(ArgumentError("When providing a vector of windows, its length $(length(window)) must match the number of specified times $(length(times)).")) + perm = sortperm(times) + sorted_times = times[perm] + sorted_window = window[perm] + time_diff = diff(vcat(0, sorted_times)) + + @info "timediff", time_diff + @info "sortedwindow", sorted_window + + any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) + + return AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) +end + +function AveragedSpecifiedTimes(times, window::Float64; kw...) + perm = sortperm(times) + sorted_times = times[perm] + time_diff = diff(vcat(0, sorted_times)) + + any(time_diff .- window .< -eps(typeof(window))) && throw(ArgumentError("Averaging window $window is too large and causes overlapping windows. Ensure that for each specified time tᵢ, tᵢ - window ≥ tᵢ₋₁.")) + + return AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) +end + +# function AveragedSpecifiedTimes(times; window, kw...) +# perm = sortperm(times) +# sorted_times = times[perm] +# time_diff = diff(vcat(0, sorted_times)) + +# if window isa Vector{Float64} +# length(window) == length(times) || throw(ArgumentError("When providing a vector of windows, its length $(length(window)) must match the number of specified times $(length(times)).")) + +# sorted_window = window[perm] +# @info "timediff", time_diff +# @info "sortedwindow", sorted_window + +# any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) +# elseif window isa Number +# any(time_diff .- window .< -eps(typeof(window))) && throw(ArgumentError("Averaging window $window is too large and causes overlapping windows. Ensure that for each specified time tᵢ, tᵢ - window ≥ tᵢ₋₁.")) +# else +# throw(ArgumentError("window must be a Float64 or a Vector{Float64}, got $(typeof(window))")) +# end + +# return AveragedSpecifiedTimes(SpecifiedTimes(times); window=window, kw...) +# end get_next_window(schedule::VaryingWindowAveragedSpecifiedTimes) = schedule.window[schedule.specified_times.previous_actuation + 1] get_next_window(schedule::AveragedSpecifiedTimes) = schedule.window From 61d29ac359fe2b37231e899a01da140c92655550 Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Tue, 21 Oct 2025 18:02:59 -0700 Subject: [PATCH 04/12] Refactor AveragedSpecifiedTimes to ensure non-overlapping windows and improve time sorting --- src/OutputWriters/windowed_time_average.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index c174e820d8..a557f60420 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -129,12 +129,11 @@ function AveragedSpecifiedTimes(times, window::Vector{Float64}; kw...) any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) - return AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) + return AveragedSpecifiedTimes(SpecifiedTimes(times); window=sorted_window, kw...) end function AveragedSpecifiedTimes(times, window::Float64; kw...) - perm = sortperm(times) - sorted_times = times[perm] + sorted_times = sort(times) time_diff = diff(vcat(0, sorted_times)) any(time_diff .- window .< -eps(typeof(window))) && throw(ArgumentError("Averaging window $window is too large and causes overlapping windows. Ensure that for each specified time tᵢ, tᵢ - window ≥ tᵢ₋₁.")) From c20c9b09e550a21970d7fc7960b8d9eba9d1c7f4 Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Tue, 21 Oct 2025 18:03:25 -0700 Subject: [PATCH 05/12] Fix AveragedSpecifiedTimes to use sorted times for averaging windows --- src/OutputWriters/windowed_time_average.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index a557f60420..c7de95978f 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -129,7 +129,7 @@ function AveragedSpecifiedTimes(times, window::Vector{Float64}; kw...) any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) - return AveragedSpecifiedTimes(SpecifiedTimes(times); window=sorted_window, kw...) + return AveragedSpecifiedTimes(SpecifiedTimes(sorted_times); window=sorted_window, kw...) end function AveragedSpecifiedTimes(times, window::Float64; kw...) From 35c41a517021ea1f0a8751d7f9c2611717425dea Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Tue, 21 Oct 2025 18:09:27 -0700 Subject: [PATCH 06/12] Remove debug logging from AveragedSpecifiedTimes function --- src/OutputWriters/windowed_time_average.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index c7de95978f..d1f67f1855 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -115,7 +115,6 @@ AveragedSpecifiedTimes(specified_times::SpecifiedTimes; window, stride=1) = AveragedSpecifiedTimes(specified_times, window, stride, false) AveragedSpecifiedTimes(times; window, kw...) = AveragedSpecifiedTimes(times, window; kw...) -# AveragedSpecifiedTimes(times; window::Float64; kw...) = AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) function AveragedSpecifiedTimes(times, window::Vector{Float64}; kw...) length(window) == length(times) || throw(ArgumentError("When providing a vector of windows, its length $(length(window)) must match the number of specified times $(length(times)).")) @@ -124,9 +123,6 @@ function AveragedSpecifiedTimes(times, window::Vector{Float64}; kw...) sorted_window = window[perm] time_diff = diff(vcat(0, sorted_times)) - @info "timediff", time_diff - @info "sortedwindow", sorted_window - any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) return AveragedSpecifiedTimes(SpecifiedTimes(sorted_times); window=sorted_window, kw...) From 6d168250410165f3cc61b319cf87415ec7d6a769 Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Tue, 21 Oct 2025 18:14:40 -0700 Subject: [PATCH 07/12] update commented out method --- src/OutputWriters/windowed_time_average.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index d1f67f1855..9cd1e25ecf 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -150,13 +150,13 @@ end # @info "sortedwindow", sorted_window # any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) +# return AveragedSpecifiedTimes(SpecifiedTimes(sorted_times); window=sorted_window, kw...) # elseif window isa Number # any(time_diff .- window .< -eps(typeof(window))) && throw(ArgumentError("Averaging window $window is too large and causes overlapping windows. Ensure that for each specified time tᵢ, tᵢ - window ≥ tᵢ₋₁.")) +# return AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) # else # throw(ArgumentError("window must be a Float64 or a Vector{Float64}, got $(typeof(window))")) # end - -# return AveragedSpecifiedTimes(SpecifiedTimes(times); window=window, kw...) # end get_next_window(schedule::VaryingWindowAveragedSpecifiedTimes) = schedule.window[schedule.specified_times.previous_actuation + 1] From 6ea30f5eb0580d3d7c931e6f743f4791fafcfdea Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Wed, 22 Oct 2025 16:02:52 -0700 Subject: [PATCH 08/12] Refactor AveragedSpecifiedTimes to support varying window types and improve overlap validation --- src/OutputWriters/windowed_time_average.jl | 48 +++++++++------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index 9cd1e25ecf..146972a4b4 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -2,6 +2,7 @@ using Oceananigans.Diagnostics: AbstractDiagnostic using Oceananigans.OutputWriters: fetch_output using Oceananigans.Utils: AbstractSchedule, prettytime using Oceananigans.TimeSteppers: Clock +using Dates: Period import Oceananigans: run_diagnostic! import Oceananigans.Utils: TimeInterval, SpecifiedTimes @@ -102,63 +103,54 @@ Base.copy(sch::AveragedTimeInterval) = AveragedTimeInterval(sch.interval, window A schedule for averaging over windows that precede SpecifiedTimes. """ -mutable struct AveragedSpecifiedTimes{W <: Union{Float64, Vector{Float64}}} <: AbstractSchedule +mutable struct AveragedSpecifiedTimes{W} <: AbstractSchedule specified_times :: SpecifiedTimes window :: W stride :: Int collecting :: Bool end -const VaryingWindowAveragedSpecifiedTimes = AveragedSpecifiedTimes{Vector{Float64}} +const VaryingWindowAveragedSpecifiedTimes = AveragedSpecifiedTimes{<:Vector} AveragedSpecifiedTimes(specified_times::SpecifiedTimes; window, stride=1) = AveragedSpecifiedTimes(specified_times, window, stride, false) AveragedSpecifiedTimes(times; window, kw...) = AveragedSpecifiedTimes(times, window; kw...) -function AveragedSpecifiedTimes(times, window::Vector{Float64}; kw...) +function determine_epsilon(eltype) + if eltype <: AbstractFloat + return eps(eltype) + elseif eltype <: Period + return Second(0) + else + return 0 + end +end + +function AveragedSpecifiedTimes(times, window::Vector; kw...) length(window) == length(times) || throw(ArgumentError("When providing a vector of windows, its length $(length(window)) must match the number of specified times $(length(times)).")) perm = sortperm(times) sorted_times = times[perm] sorted_window = window[perm] time_diff = diff(vcat(0, sorted_times)) - any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) + epsilon = determine_epsilon(eltype(window)) + any(time_diff .- sorted_window .< -epsilon) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) return AveragedSpecifiedTimes(SpecifiedTimes(sorted_times); window=sorted_window, kw...) end -function AveragedSpecifiedTimes(times, window::Float64; kw...) +function AveragedSpecifiedTimes(times, window::Union{<:Number, <:Period}; kw...) sorted_times = sort(times) time_diff = diff(vcat(0, sorted_times)) - any(time_diff .- window .< -eps(typeof(window))) && throw(ArgumentError("Averaging window $window is too large and causes overlapping windows. Ensure that for each specified time tᵢ, tᵢ - window ≥ tᵢ₋₁.")) + epsilon = determine_epsilon(typeof(window)) + + any(time_diff .- window .< -epsilon) && throw(ArgumentError("Averaging window $window is too large and causes overlapping windows. Ensure that for each specified time tᵢ, tᵢ - window ≥ tᵢ₋₁.")) return AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) end -# function AveragedSpecifiedTimes(times; window, kw...) -# perm = sortperm(times) -# sorted_times = times[perm] -# time_diff = diff(vcat(0, sorted_times)) - -# if window isa Vector{Float64} -# length(window) == length(times) || throw(ArgumentError("When providing a vector of windows, its length $(length(window)) must match the number of specified times $(length(times)).")) - -# sorted_window = window[perm] -# @info "timediff", time_diff -# @info "sortedwindow", sorted_window - -# any(time_diff .- sorted_window .< -eps(eltype(window))) && throw(ArgumentError("Averaging windows overlap. Ensure that for each specified time tᵢ, tᵢ - windowᵢ ≥ tᵢ₋₁.")) -# return AveragedSpecifiedTimes(SpecifiedTimes(sorted_times); window=sorted_window, kw...) -# elseif window isa Number -# any(time_diff .- window .< -eps(typeof(window))) && throw(ArgumentError("Averaging window $window is too large and causes overlapping windows. Ensure that for each specified time tᵢ, tᵢ - window ≥ tᵢ₋₁.")) -# return AveragedSpecifiedTimes(SpecifiedTimes(times); window, kw...) -# else -# throw(ArgumentError("window must be a Float64 or a Vector{Float64}, got $(typeof(window))")) -# end -# end - get_next_window(schedule::VaryingWindowAveragedSpecifiedTimes) = schedule.window[schedule.specified_times.previous_actuation + 1] get_next_window(schedule::AveragedSpecifiedTimes) = schedule.window From 56e6e55bb1d8a17f68244639a68d4c237310d17a Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Wed, 22 Oct 2025 16:05:22 -0700 Subject: [PATCH 09/12] Refactor AveragedTimeInterval struct to support generic types for interval and first actuation time --- src/OutputWriters/windowed_time_average.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index 146972a4b4..34034f496a 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -13,11 +13,11 @@ import Oceananigans.Fields: location, indices, set! Container for parameters that configure and handle time-averaged output. """ -mutable struct AveragedTimeInterval <: AbstractSchedule - interval :: Float64 - window :: Float64 +mutable struct AveragedTimeInterval{I, T} <: AbstractSchedule + interval :: I + window :: I stride :: Int - first_actuation_time :: Float64 + first_actuation_time :: T actuations :: Int collecting :: Bool end From b3f1541e7ce9b62029a2dfcc17277624dfaed24e Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Wed, 22 Oct 2025 16:21:37 -0700 Subject: [PATCH 10/12] make averagedspecifiedtimes concrete --- src/OutputWriters/windowed_time_average.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index 34034f496a..814b965fb9 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -103,8 +103,8 @@ Base.copy(sch::AveragedTimeInterval) = AveragedTimeInterval(sch.interval, window A schedule for averaging over windows that precede SpecifiedTimes. """ -mutable struct AveragedSpecifiedTimes{W} <: AbstractSchedule - specified_times :: SpecifiedTimes +mutable struct AveragedSpecifiedTimes{S<:SpecifiedTimes, W} <: AbstractSchedule + specified_times :: S window :: W stride :: Int collecting :: Bool From 0efdbc82ccfe6b32d40a52245ac357ab0354c2b8 Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Thu, 23 Oct 2025 19:01:21 -0700 Subject: [PATCH 11/12] change dispatch method --- src/OutputWriters/windowed_time_average.jl | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index 814b965fb9..a15ab91b1a 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -110,22 +110,16 @@ mutable struct AveragedSpecifiedTimes{S<:SpecifiedTimes, W} <: AbstractSchedule collecting :: Bool end -const VaryingWindowAveragedSpecifiedTimes = AveragedSpecifiedTimes{<:Vector} +const VaryingWindowAveragedSpecifiedTimes = AveragedSpecifiedTimes{<:Any, <:Vector} AveragedSpecifiedTimes(specified_times::SpecifiedTimes; window, stride=1) = AveragedSpecifiedTimes(specified_times, window, stride, false) AveragedSpecifiedTimes(times; window, kw...) = AveragedSpecifiedTimes(times, window; kw...) -function determine_epsilon(eltype) - if eltype <: AbstractFloat - return eps(eltype) - elseif eltype <: Period - return Second(0) - else - return 0 - end -end +determine_epsilon(eltype) = 0 +determine_epsilon(::Type{T}) where T <: AbstractFloat = eps(T) +determine_epsilon(::Period) = Second(0) function AveragedSpecifiedTimes(times, window::Vector; kw...) length(window) == length(times) || throw(ArgumentError("When providing a vector of windows, its length $(length(window)) must match the number of specified times $(length(times)).")) @@ -152,7 +146,7 @@ function AveragedSpecifiedTimes(times, window::Union{<:Number, <:Period}; kw...) end get_next_window(schedule::VaryingWindowAveragedSpecifiedTimes) = schedule.window[schedule.specified_times.previous_actuation + 1] -get_next_window(schedule::AveragedSpecifiedTimes) = schedule.window +get_next_window(schedule) = schedule.window function (schedule::AveragedSpecifiedTimes)(model) time = model.clock.time From 3ab8e0e54b971be3cea337a3fb6b06b5b94fb5c0 Mon Sep 17 00:00:00 2001 From: Xin Kai Lee Date: Thu, 23 Oct 2025 19:04:18 -0700 Subject: [PATCH 12/12] change to fallback --- src/OutputWriters/windowed_time_average.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OutputWriters/windowed_time_average.jl b/src/OutputWriters/windowed_time_average.jl index a15ab91b1a..90cb6387d4 100644 --- a/src/OutputWriters/windowed_time_average.jl +++ b/src/OutputWriters/windowed_time_average.jl @@ -134,7 +134,7 @@ function AveragedSpecifiedTimes(times, window::Vector; kw...) return AveragedSpecifiedTimes(SpecifiedTimes(sorted_times); window=sorted_window, kw...) end -function AveragedSpecifiedTimes(times, window::Union{<:Number, <:Period}; kw...) +function AveragedSpecifiedTimes(times, window; kw...) sorted_times = sort(times) time_diff = diff(vcat(0, sorted_times))