Skip to content

Commit e3de2bc

Browse files
authored
Merge pull request #262 from Deltares/starttime
Starttime
2 parents 7790afd + 98765cf commit e3de2bc

24 files changed

+123
-148
lines changed

docs/src/changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
### Changed
1818
- The time values returned in the BMI interface are no longer in seconds since 1970, but in
1919
seconds since the model start time. This is more in line with standard BMI practices.
20+
- The `starttime` was defined one model timestep `Δt` ahead of the actual model time (the
21+
initial conditions timestamp (state time)). As a consequence this was also the case for
22+
the current model time. To allow for an easier interpretation of Wflow time handling,
23+
either through BMI or directly, the `starttime` is now equal to the state time, resulting
24+
in current model times without an offset.
2025

2126
### Added
2227
- For (regulated) lakes with rating curve of type 1 (H-Q table), lake `storage` above the

docs/src/user_guide/step2_settings_file.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ The filepaths that are provided in this file are relative to the location of the
66
or to `dir_input` and `dir_output` if they are given.
77

88
## General time info
9-
Time information is optional. When left out, each time step in the forcing NetCDF will be
10-
calculated. If you wish to calculate a subset of this time range, or a different timestep,
11-
you can specify a `starttime`, `endtime` and `timestepsecs` yourself. The `time_units`
12-
optional information is used by the `writer` of the model, for model output in netCDF
13-
format. The `calendar` option allows you to calculate in one of the different [CF
14-
conventions calendars](http://cfconventions.org/cf-conventions/cf-conventions.html#calendar)
15-
provided by the [CFTime.jl package](https://juliageo.org/CFTime.jl/latest/), such as
16-
`"360_day"`. This is useful if you want to calculate climate scenarios which are sometimes
17-
provided in these alternative calendars.
9+
Time information is optional. When left out, for each timestamp in the forcing NetCDF Wflow
10+
will do computations, except for the first forcing timestamp that is considered equal to the
11+
initial conditions of the Wflow model (state time). If you wish to calculate a subset of
12+
this time range, or a different timestep, you can specify a `starttime`, `endtime` and
13+
`timestepsecs` yourself. The `starttime` is defined as the model state time. In the TOML
14+
file settings below the `starttime` is 2000-01-01T00:00:00 (state time) and the first update
15+
(and output) of the Wflow model is at 2000-01-02T00:00:00. The `time_units` optional
16+
information is used by the `writer` of the model, for model output in netCDF format. The
17+
`calendar` option allows you to calculate in one of the different [CF conventions
18+
calendars](http://cfconventions.org/cf-conventions/cf-conventions.html#calendar) provided by
19+
the [CFTime.jl package](https://juliageo.org/CFTime.jl/latest/), such as `"360_day"`. This
20+
is useful if you want to calculate climate scenarios which are sometimes provided in these
21+
alternative calendars.
1822

1923
```toml
2024
calendar = "standard" # optional, this is default value

src/Wflow.jl

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,40 +40,41 @@ function Clock(config)
4040
calendar = get(config, "calendar", "standard")::String
4141
starttime = cftime(config.starttime, calendar)
4242
Δt = Second(config.timestepsecs)
43-
Clock(starttime, 1, Δt)
43+
Clock(starttime, 0, Δt)
4444
end
4545

4646
function Clock(config, reader)
4747
nctimes = reader.dataset["time"][:]
48-
# if the config file does not have a start or endtime, folow the NetCDF times
49-
# and add them to the config
48+
5049
# if the timestep is not given, use the difference between NetCDF time 1 and 2
50+
timestepsecs = get(config, "timestepsecs", nothing)
51+
if timestepsecs === nothing
52+
timestepsecs = Dates.value(Second(nctimes[2] - nctimes[1]))
53+
config.timestepsecs = timestepsecs
54+
end
55+
Δt = Second(timestepsecs)
56+
57+
# if the config file does not have a start or endtime, follow the NetCDF times
58+
# and add them to the config
5159
starttime = get(config, "starttime", nothing)
5260
if starttime === nothing
53-
starttime = first(nctimes)
61+
starttime = first(nctimes) - Δt
5462
config.starttime = starttime
5563
end
5664
endtime = get(config, "endtime", nothing)
5765
if endtime === nothing
5866
endtime = last(nctimes)
5967
config.endtime = endtime
6068
end
61-
timestepsecs = get(config, "timestepsecs", nothing)
62-
if timestepsecs === nothing
63-
timestepsecs = Dates.value(Second(nctimes[2] - nctimes[1]))
64-
config.timestepsecs = timestepsecs
65-
end
6669

6770
calendar = get(config, "calendar", "standard")::String
68-
starttime = cftime(config.starttime, calendar)
69-
Δt = Second(timestepsecs)
70-
7171
fews_run = get(config, "fews_run", false)::Bool
7272
if fews_run
73-
starttime = starttime + Δt
73+
config.starttime = starttime + Δt
7474
end
75-
76-
Clock(starttime, 1, Δt)
75+
starttime = cftime(config.starttime, calendar)
76+
77+
Clock(starttime, 0, Δt)
7778
end
7879

7980
include("io.jl")
@@ -192,31 +193,42 @@ function run(config::Config)
192193
run(model)
193194
end
194195

196+
function run_timestep(model::Model; update_func = update, write_model_output = true)
197+
advance!(model.clock)
198+
load_dynamic_input!(model)
199+
model = update_func(model)
200+
if write_model_output
201+
write_output(model)
202+
end
203+
return model
204+
end
205+
195206
function run(model::Model; close_files = true)
196207
@unpack network, config, writer, clock = model
197208

198209
model_type = config.model.type::String
199210

200211
# determine timesteps to run
201212
calendar = get(config, "calendar", "standard")::String
213+
@warn string(
214+
"The definition of `starttime` has changed (equal to model state time).\n Please",
215+
" update your settings TOML file by subtracting one model timestep Δt from the",
216+
" `starttime`, if it was used with a Wflow version up to v0.6.3.",
217+
)
202218
starttime = clock.time
203219
Δt = clock.Δt
204220
endtime = cftime(config.endtime, calendar)
205-
times = range(starttime, endtime, step = Δt)
221+
times = range(starttime + Δt, endtime, step = Δt)
206222

207223
@info "Run information" model_type starttime Δt endtime nthreads()
208224
runstart_time = now()
209225
@progress for (i, time) in enumerate(times)
210226
@debug "Starting timestep." time i now()
211-
load_dynamic_input!(model)
212-
model = update(model)
227+
model = run_timestep(model)
213228
end
214229
@info "Simulation duration: $(canonicalize(now() - runstart_time))"
215230

216231
# write output state NetCDF
217-
# undo the clock advance at the end of the last iteration, since there won't
218-
# be a next step, and then the output state falls on the correct time
219-
rewind!(clock)
220232
if haskey(config, "state") && haskey(config.state, "path_output")
221233
@info "Write output states to NetCDF file `$(model.writer.state_nc_path)`."
222234
end

src/bmi.jl

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,35 +48,34 @@ Update the model for a single timestep.
4848
- `run = nothing`: to update a model partially.
4949
"""
5050
function BMI.update(model::Model; run = nothing)
51-
@unpack network, config = model
51+
@unpack clock, network, config = model
5252
if isnothing(run)
53-
update_func = update
53+
model = run_timestep(model)
5454
elseif run == "sbm_until_recharge"
55-
update_func = update_until_recharge
55+
model = run_timestep(
56+
model,
57+
update_func = update_until_recharge,
58+
write_model_output = false,
59+
)
5660
elseif run == "sbm_after_subsurfaceflow"
57-
update_func = update_after_subsurfaceflow
61+
model = run_timestep(model, update_func = update_after_subsurfaceflow)
5862
end
59-
load_dynamic_input!(model)
60-
return update_func(model)
63+
return model
6164
end
6265

6366
function BMI.update_until(model::Model, time::Float64)
64-
@unpack network, config = model
67+
@unpack clock, network, config = model
6568
curtime = BMI.get_current_time(model)
66-
n_iter = Int(max(0, (time - curtime) / model.clock.Δt.value))
67-
end_time = curtime + n_iter * config.timestepsecs
68-
@info "Updating model until $end_time."
69-
for _ = 1:n_iter
70-
load_dynamic_input!(model)
71-
update(model)
69+
n = Int(max(0, (time - curtime) / model.clock.Δt.value))
70+
for _ = 1:n
71+
model = run_timestep(model)
7272
end
7373
return model
7474
end
7575

7676
"Write state output to netCDF and close files."
7777
function BMI.finalize(model::Model)
7878
@unpack config, writer, clock = model
79-
rewind!(clock)
8079
write_netcdf_timestep(model, writer.state_dataset, writer.state_parameters)
8180
reset_clock!(model.clock, config)
8281
close_files(model, delete_output = false)

src/flextopo_model.jl

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -720,10 +720,5 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:FlextopoModel}
720720

721721
surface_routing(model)
722722

723-
write_output(model)
724-
725-
# update the clock
726-
advance!(clock)
727-
728723
return model
729724
end

src/hbv_model.jl

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -426,10 +426,5 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:HbvModel}
426426

427427
surface_routing(model)
428428

429-
write_output(model)
430-
431-
# update the clock
432-
advance!(clock)
433-
434429
return model
435430
end

src/sbm_gwf_model.jl

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -476,10 +476,5 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel}
476476
ssf_toriver[inds_riv] = -lateral.subsurface.river.flux ./ lateral.river.Δt
477477
surface_routing(model, ssf_toriver = ssf_toriver)
478478

479-
write_output(model)
480-
481-
# update the clock
482-
advance!(clock)
483-
484479
return model
485480
end

src/sbm_model.jl

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -496,10 +496,5 @@ function update_after_subsurfaceflow(
496496
ssf_toriver = lateral.subsurface.to_river ./ tosecond(basetimestep)
497497
surface_routing(model, ssf_toriver = ssf_toriver)
498498

499-
write_output(model)
500-
501-
# update the clock
502-
advance!(clock)
503-
504499
return model
505500
end

src/sediment_model.jl

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,5 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel}
199199
update(lateral.river, network.river, config)
200200
end
201201

202-
write_output(model)
203-
204-
# update the clock
205-
advance!(clock)
206-
207202
return model
208203
end

test/bmi.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml")
1313
@test BMI.get_time_step(model) == 86400.0
1414
@test BMI.get_start_time(model) == 0.0
1515
@test BMI.get_current_time(model) == 0.0
16-
@test BMI.get_end_time(model) == 30 * 86400.0
16+
@test BMI.get_end_time(model) == 31 * 86400.0
1717
end
1818

1919
@testset "model information functions" begin

test/flextopo_config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
casename = "wflow_meuse"
22
calendar = "proleptic_gregorian"
3-
starttime = "2010-01-01T00:00:00"
3+
starttime = "2009-12-31T00:00:00"
44
endtime = "2010-07-01T00:00:00"
55
time_units = "days since 1900-01-01 00:00:00"
66
timestepsecs = 86400

test/hbv_config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
calendar = "proleptic_gregorian"
77
endtime = 2000-02-01T00:00:00
8-
starttime = 2000-01-01T00:00:00
8+
starttime = 1999-12-31T00:00:00
99
time_units = "days since 1900-01-01 00:00:00"
1010
timestepsecs = 86400
1111

test/io.jl

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ config = Wflow.Config(tomlpath)
1818
@test dirname(config) == dirname(tomlpath)
1919

2020
# test if the values are parsed as expected
21-
@test config.starttime === DateTime(2000, 1, 2)
21+
@test config.starttime === DateTime(2000, 1, 1)
2222
@test config.endtime === DateTime(2000, 2)
2323
@test config.output.path == "output_moselle.nc"
2424
@test config.output isa Wflow.Config
@@ -71,11 +71,11 @@ end
7171
pop!(Dict(config), "timestepsecs")
7272
clock = Wflow.Clock(config, reader)
7373

74-
@test clock.time == DateTimeProlepticGregorian(2000, 1, 2)
75-
@test clock.iteration == 1
74+
@test clock.time == DateTimeProlepticGregorian(2000, 1, 1)
75+
@test clock.iteration == 0
7676
@test clock.Δt == Second(Day(1))
7777
# test that the missing keys have been added to the config
78-
@test config.starttime == DateTime(2000, 1, 2)
78+
@test config.starttime == DateTime(2000, 1, 1)
7979
@test config.endtime == DateTime(2001, 1, 1)
8080
@test config.timestepsecs == 86400
8181

@@ -87,7 +87,7 @@ end
8787

8888
clock = Wflow.Clock(config, reader)
8989
@test clock.time == DateTimeStandard(2003, 4, 5)
90-
@test clock.iteration == 1
90+
@test clock.iteration == 0
9191
@test clock.Δt == Second(Hour(1))
9292

9393
close(ds)
@@ -98,43 +98,43 @@ end
9898
# 29 days in this February due to leap year
9999
starttime = DateTimeStandard(2000, 2, 28)
100100
Δt = Day(1)
101-
clock = Wflow.Clock(starttime, 1, Second(Δt))
101+
clock = Wflow.Clock(starttime, 0, Second(Δt))
102102

103103
Wflow.advance!(clock)
104104
Wflow.advance!(clock)
105105
@test clock.time == DateTimeStandard(2000, 3, 1)
106-
@test clock.iteration == 3
106+
@test clock.iteration == 2
107107
@test clock.Δt == Δt
108108

109109
Wflow.rewind!(clock)
110110
@test clock.time == DateTimeStandard(2000, 2, 29)
111-
@test clock.iteration == 2
111+
@test clock.iteration == 1
112112
@test clock.Δt == Δt
113113

114114
config = Wflow.Config(
115115
Dict("starttime" => starttime, "timestepsecs" => Dates.value(Second(Δt))),
116116
)
117117
Wflow.reset_clock!(clock, config)
118118
@test clock.time == starttime
119-
@test clock.iteration == 1
119+
@test clock.iteration == 0
120120
@test clock.Δt == Δt
121121
end
122122

123123
@testset "Clock{DateTime360Day}" begin
124124
# 30 days in each month
125125
starttime = DateTime360Day(2000, 2, 29)
126126
Δt = Day(1)
127-
clock = Wflow.Clock(starttime, 1, Second(Δt))
127+
clock = Wflow.Clock(starttime, 0, Second(Δt))
128128

129129
Wflow.advance!(clock)
130130
Wflow.advance!(clock)
131131
@test clock.time == DateTime360Day(2000, 3, 1)
132-
@test clock.iteration == 3
132+
@test clock.iteration == 2
133133
@test clock.Δt == Δt
134134

135135
Wflow.rewind!(clock)
136136
@test clock.time == DateTime360Day(2000, 2, 30)
137-
@test clock.iteration == 2
137+
@test clock.iteration == 1
138138
@test clock.Δt == Δt
139139

140140
config = Wflow.Config(
@@ -147,7 +147,7 @@ end
147147
Wflow.reset_clock!(clock, config)
148148
@test clock.time isa DateTime360Day
149149
@test string(clock.time) == "2020-02-29T00:00:00"
150-
@test clock.iteration == 1
150+
@test clock.iteration == 0
151151
@test clock.Δt == Δt
152152
end
153153

@@ -193,6 +193,7 @@ config.input["path_forcing"] = abs_path_forcing
193193
@test isabspath(config.input.path_forcing)
194194

195195
model = Wflow.initialize_sbm_model(config)
196+
Wflow.advance!(model.clock)
196197
Wflow.load_dynamic_input!(model)
197198

198199
@unpack vertical, clock, reader, writer = model
@@ -273,6 +274,7 @@ config.input.vertical.c = Dict(
273274
)
274275

275276
model = Wflow.initialize_sbm_model(config)
277+
Wflow.advance!(model.clock)
276278
Wflow.load_dynamic_input!(model)
277279

278280
@testset "changed parameter values" begin
@@ -429,11 +431,11 @@ end
429431
# test Clock{DateTimeNoLeap}
430432
clock = Wflow.Clock(config, reader)
431433
@test clock.time isa DateTimeNoLeap
432-
@test clock.time == DateTimeNoLeap(2000, 1, 2)
434+
@test clock.time == DateTimeNoLeap(2000, 1, 1)
433435

434436
starttime = DateTimeNoLeap(2000, 2, 28)
435437
Δt = Day(1)
436-
clock = Wflow.Clock(starttime, 1, Second(Δt))
438+
clock = Wflow.Clock(starttime, 0, Second(Δt))
437439
Wflow.advance!(clock)
438440
@test clock.time == DateTimeNoLeap(2000, 3, 1)
439441
end

0 commit comments

Comments
 (0)