# [Scheduling callbacks and output writers](@id callback_schedules)

Callbacks and output writers in `Simulation` actuate on objects that are subtypes of `AbstractSchedule`.
Schedules are small callable objects that return `true` when an action should fire and `false` otherwise.
This page collects the built-in schedules, when to use them, and how to combine them for more complex behavior.

!!! tip "Aligned time steps"
    `Simulation` automatically shortens the next time step so that callback and output events scheduled
    by model time happen exactly when requested. Set `align_time_step = false` in the `Simulation`
    constructor to disable this.

For the examples here, we use the following simple simulation and progress:

```@example schedules
using Oceananigans
grid = RectilinearGrid(size=(1, 1, 1), extent=(1, 1, 1))
model = NonhydrostaticModel(grid)
simulation = Simulation(model, Δt=0.1, stop_time=2.5, verbose=false)
dummy(sim) = @info string("Iter: ", iteration(sim), " -- I was called at t = ", time(sim),
                          " and wall time = ", prettytime(sim.run_wall_time))
```

During the examples below, we use `Oceananigans.Simulations.reset!(simulation)` so
that we reuse the simulation in the next example without requiring to recreate it.

## Basic schedules

### `IterationInterval`

[`IterationInterval`](@ref Oceananigans.OutputWriters.IterationInterval) actuates every `interval` iterations:

```@example schedules
schedule = IterationInterval(11)
add_callback!(simulation, dummy, schedule, name=:dummy)
run!(simulation)
```

Use the `offset` keyword argument to shift the trigger so that, for example,

```@example schedules
Oceananigans.Simulations.reset!(simulation)
simulation.stop_time = 2.5

schedule = IterationInterval(7; offset=-2)
add_callback!(simulation, dummy, schedule, name=:dummy)
run!(simulation)
```

Notice that other than iteration 0, the callback is actuated on iterations 5, 12, 19, ...

Above, we have overwritten the original callback called "dummy" with the
new one with the offset schedule. An alternative way is to construct the
[`Callback`](@ref) manually and add it in the simulation:

```@example schedules
simulation.callbacks[:dummy] = Callback(dummy, schedule)
```

### `TimeInterval`

[`TimeInterval`](@ref Oceananigans.OutputWriters.TimeInterval) actuates every `interval` of model time,
in units corresponding to `model.clock.time`. For example,

```@example schedules
Oceananigans.Simulations.reset!(simulation)
simulation.stop_time = 2.5

schedule = TimeInterval(1.11)
add_callback!(simulation, dummy, schedule, name=:dummy)
run!(simulation)
```

When `model.clock.time isa AbstractTime` such as `DateTime`, then `interval` can be `Dates.Period`:

```@example schedules
using Dates

start_time = DateTime(2025, 1, 1)
clock = Clock(time = start_time)
datetime_model = NonhydrostaticModel(grid; clock)

stop_time = start_time + Dates.Minute(3)
datetime_simulation = Simulation(datetime_model; Δt=Dates.Second(25), stop_time, verbose=false)

schedule = TimeInterval(Dates.Minute(1))
add_callback!(datetime_simulation, dummy, schedule)
run!(datetime_simulation)
```

### `WallTimeInterval`

[`WallTimeInterval`](@ref Oceananigans.OutputWriters.WallTimeInterval) uses wall-clock seconds instead of model time.
This is mostly useful for writing checkpoints to disk after consuming a fixed amount of computational resources.
For example, using the previous `simulation` without the

```@example schedules
Oceananigans.Simulations.reset!(simulation)
simulation.stop_time = 2.5

schedule = WallTimeInterval(0.005)
add_callback!(simulation, dummy, schedule, name=:dummy)
run!(simulation)
```

### `SpecifiedTimes`

[`SpecifiedTimes`](@ref Oceananigans.OutputWriters.SpecifiedTimes) actuates when `model.clock.time` reaches
the given values. The constructor accepts numeric times or `Dates.DateTime` values and sorts them automatically.
This schedule is helpful for pre-planned save points or events tied to specific model times.

```@example schedules
Oceananigans.Simulations.reset!(simulation)
simulation.stop_time = 2.5

schedule = SpecifiedTimes(0.2, 1.5, 2.1)
add_callback!(simulation, dummy, schedule, name=:dummy)
run!(simulation)
```

### Arbitrary functions of model

Any function of `model` that returns a `Bool` can be used as a schedule:

```@example schedules
Oceananigans.Simulations.reset!(simulation)
simulation.stop_time = 2.5

after_two(model) = model.clock.time > 2
add_callback!(simulation, dummy, after_two, name=:dummy)
run!(simulation)
```

## Combining schedules

Some applications benefit from running extra steps immediately after an event or from combining multiple criteria.

### `ConsecutiveIterations`

[`ConsecutiveIterations`](@ref Oceananigans.OutputWriters.ConsecutiveIterations) actuates when the parent schedule does and for the next `N` iterations.
For example, averaging callbacks often need data at the scheduled time and immediately afterwards.

```@example schedules
Oceananigans.Simulations.reset!(simulation)
simulation.stop_time = 2.5

times = SpecifiedTimes(0.55, 1.5, 2.12)
schedule = ConsecutiveIterations(times)
add_callback!(simulation, dummy, schedule, name=:dummy)
run!(simulation)
```

### `AndSchedule` and `OrSchedule`

Use [`AndSchedule`](@ref Oceananigans.OutputWriters.AndSchedule) when an action should fire only if _every_ child
schedule actuates in the same iteration. Use [`OrSchedule`](@ref Oceananigans.OutputWriters.OrSchedule) when _any one_ of
the child schedules should trigger the action. Both accept any mix of `AbstractSchedule`s, so you can require,
for example, output every hour *and* every 1000 iterations:

```@example schedules
Oceananigans.Simulations.reset!(simulation)
simulation.stop_time = 2.5

after_one_point_seven(model) = model.clock.time > 1.7
schedule = AndSchedule(IterationInterval(2), after_one_point_seven)
add_callback!(simulation, dummy, schedule, name=:dummy)
run!(simulation)
```

!!! warning "Stateful schedules"
    Stateful schedules such as `TimeInterval`, `SpecifiedTimes`, and `ConsecutiveIterations` store
    their own counters, so we need to create a _fresh_ instance (or call `copy`) for each callback or
    output writer that needs an identical pattern.

## Output-specific schedules

Some schedules only apply to output writers because they keep extra state or require file access.

### `AveragedTimeInterval`

[`AveragedTimeInterval`](@ref Oceananigans.OutputWriters.AveragedTimeInterval) asks an output writer to accumulate
data over a sliding time window before writing. The window ends at each actuation time, runs for `window` seconds,
and samples every `stride` iterations inside the window.

### `AveragedSpecifiedTimes`

[`AveragedSpecifiedTimes`](@ref Oceananigans.OutputWriters.AveragedSpecifiedTimes) behaves like
[`SpecifiedTimes`](@ref Oceananigans.OutputWriters.SpecifiedTimes) but with a trailing averaging window.
Pass either a `SpecifiedTimes` instance or raw times.

### `FileSizeLimit`

[`FileSizeLimit`](@ref Oceananigans.OutputWriters.FileSizeLimit) actuates when the target file grows beyond `size_limit` bytes.
Output writers update the internal path automatically so you usually only pass the size limit. Combine it with `OrSchedule`
to rotate files when either the clock reaches a value or the file becomes too large.
