-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathexecute.R
378 lines (343 loc) · 15.6 KB
/
execute.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#' Run a MODFLOW model
#'
#' \code{rmf_execute} runs a MODFLOW model.
#'
#' @param path path to name file; typically '*.nam'
#' @param code name of the MODFLOW variant to use
#' @param par vector of parameter value file parameter values to run the model with
#' @param verbose logical; should the terminal output be printed to the R console? Defaults to TRUE
#' @param convergence logical; should convergence be checked? If TRUE (default), a logical is returned
#' @param cvg_message character denoting the message in the terminal output used to check for convergence
#' @return if convergence = TRUE, a logical depending on whether the model converged. Otherwise NULL.
#' @rdname rmf_execute
#' @method rmf_execute character
#' @export
#' @seealso \code{\link{rmf_install}}
rmf_execute.character <- function(
path,
code = 2005,
par = NULL,
verbose = TRUE,
convergence = TRUE,
cvg_message = 'Normal termination'
) {
code <- rmf_find(code)
# get directory and filename
dir <- dirname(path)
file <- basename(path)
# set initial parameters if provided
if(!is.null(par)) {
nam <- rmf_read_nam(path)
if('PVAL' %in% nam$ftype) {
pvl <- rmf_read_pvl(file.path(dir, nam$fname[which(nam$ftype == 'PVAL')]))
# copy and write original pvl
rmf_write_pvl(pvl, file = file.path(dir, paste('original', nam$fname[which(nam$ftype=='PVAL')], sep = '_')))
pvl$parval <- par
rmf_write_pvl(pvl, file = file.path(dir, nam$fname[which(nam$ftype == 'PVAL')]))
} else {
warning('Model does not have PVL input file. Ignoring par argument', call. = FALSE)
}
}
# run modflow
out <- processx::run(code, file, wd = dir, echo = verbose)
if(convergence) {
cvg <- grepl(cvg_message, out$stdout)
if (verbose) return(invisible(cvg))
return(cvg)
}
return(invisible())
}
#' Run a MODFLOW model
#'
#' \code{execute} runs a MODFLOW model.
#'
#' @param modflow modflow object
#' @param code name of the MODFLOW variant to use
#' @param par vector of parameter value file parameter values to run the model with
#'
#' @rdname rmf_execute
#' @method rmf_execute modflow
#' @export
rmf_execute.modflow <- function(
modflow,
code = 2005,
par = NULL
) {
code <- rmf_find(code)
# temporary directory
old <- setwd(tempdir())
on.exit(setwd(old), add = TRUE)
# write all files
rmf_write_nam(modflow$nam, file = 'input.nam')
for(i in 1:nrow(modflow$nam)) {
if(modflow$nam$ftype[i] %in% c('HOB','PVAL','DIS','ZONE','MULT','BAS6','HUF2','OC','WEL','GHB','PCG','KDEP','LPF')) {
object_class <- c('hob','pvl','dis','zon','mlt','bas','huf','oc','wel','ghb','pcg','kdep','lpf')[which(c('HOB','PVAL','DIS','ZONE','MULT','BAS6','HUF2','OC','WEL','GHB','PCG','KDEP','LPF') == modflow$nam$ftype[i])]
if(object_class %in% 'lpf') {
get(paste0('rmf_write_',object_class))(modflow[[object_class]], file = modflow$nam$fname[i], dis = modflow$dis)
} else {
get(paste0('rmf_write_',object_class))(modflow[[object_class]], file = modflow$nam$fname[i])
}
}
}
# run modflow
rmf_execute('input.nam', code = code, par = par)
# read all output
}
#' Generic function to execute a MODFLOW model
#'
#' @rdname rmf_execute
#' @export
rmf_execute <- function(...) {
UseMethod('rmf_execute')
}
#' Run a MODFLOW model optimization, based on the parameter value file and the head predictions output file
#'
#' \code{rmf_optimize} runs a MODFLOW optimization.
#'
#' @param path path to name file; typically '*.nam'
#' @param code name of the MODFLOW variant to use
#' @param par initial parameter values (for all or only included parameters); current parameter value file values are used if par is not provided (default)
#' @param include logical vector indicating which parameters in the parameter value file to include in the optimization
#' @param lower lower parameter bounds
#' @param upper upper parameter bounds
#' @param control list of control arguments
#' @param logtrans logical vector indicating which parameters should be logtransformed
#' @param verbose logical; should the terminal output be printed to the R console? Defaults to TRUE
#' @param cost character denoting which statistic should be used to compute the cost. Possible values are those returned by \code{\link{rmf_performance}}. Defaults to 'ssq'
#' @param out_file optional file path to write cost value, convergence and parameter values to for each iteration
#' @param cvg_message character denoting the message in the terminal output used to check for convergence
#' @param ... further arguments provided to \code{optim}
#'
#' @details Only works with models using MODFLOW parameters and having a head predictions output file as defined in the HOB object
#' The only method currently available is "L-BFGS-B". See \code{\link{optim}}
#' @return \code{optim} results with the full list of parameters
#' @export
#' @seealso \code{\link{optim}}, \code{\link{rmf_execute}}, \code{\link{rmf_create_pvl}} & \code{\link{rmf_create_hob}}
rmf_optimize <- function(
path,
code = 2005,
par = NULL,
include = NULL,
logtrans = NULL,
lower = 0, # TODO think about this default value ...
upper = Inf,
verbose = TRUE,
cost = 'ssq',
out_file = NULL,
control = list(),
cvg_message = 'Normal termination',
...
) {
code <- rmf_find(code)
dir <- dirname(path)
nam <- rmf_read_nam(path)
if(!('PVAL' %in% nam$ftype) || !('HOB' %in% nam$ftype)) stop('rmf_optimize only works with models having PVL and HOB input', call. = FALSE)
pvl <- rmf_read_pvl(file.path(dir, nam$fname[which(nam$ftype=='PVAL')]))
hob <- rmf_read_hob(file.path(dir, nam$fname[which(nam$ftype=='HOB')]))
if(hob$iuhobsv == 0 || toupper(nam$ftype[which(nam$nunit == hob$iuhobsv)]) != 'DATA') {
stop('rmf_optimize only works with models having a head predictions output file. This can be created by setting the iuhobsv argument of the HOB file to a non-zero value', call. = FALSE)
}
# copy and write original pvl
rmf_write_pvl(pvl, file = file.path(dir, paste('original', nam$fname[which(nam$ftype=='PVAL')], sep = '_')))
if(is.null(par)) par <- pvl$parval
if(is.null(include)) include <- rep(TRUE,length(par))
if(is.infinite(lower)) lower <- rep(lower,pvl$np)
if(is.infinite(upper)) upper <- rep(upper,pvl$np)
if(!is.null(logtrans)) {
par[which(logtrans)] <- log(par[which(logtrans)])
lower[which(logtrans)] <- log(lower[which(logtrans)])
upper[which(logtrans)] <- log(upper[which(logtrans)])
if('parscale' %in% names(control)) {
control$parscale[which(trans[include])] <- log(control$parscale[which(trans[include])])
}
}
# if par only has values for include = TRUE
if(length(par) != length(pvl$parval)) {
par2 <- par
par <- pvl$parval
par[which(include)] <- par2
}
optim_modflow <- function(par_include) {
# adjust values
pvl$parval <- par
pvl$parval[which(include)] <- par_include
if(!is.null(logtrans)) pvl$parval[which(logtrans)] <- exp(pvl$parval[which(logtrans)])
# write values
rmf_write_pvl(pvl, file = file.path(dir, nam$fname[which(nam$ftype=='PVAL')]))
# run modflow
cvg <- rmf_execute(file = file, executable = executable, version = version, verbose = verbose, cvg_message = cvg_message, convergence = TRUE)
# get cost
if(cvg) {
hpr <- rmf_read_hpr(file.path(dir, nam$fname[which(nam$nunit==hob$iuhobsv)]))
cost_value <- rmf_performance(hpr)[cost]
} else { # return large cost when not converging
cost_value <- ifelse(!is.null(control$fnscale) && control$fnscale < 0, -1e100, 1e100) # maximization/minimization
}
# print
if(verbose) cat(paste(cost, '=', format(cost_value,scientific=TRUE,digits=4), 'converged =', as.character(cvg), 'parval =', paste(format(pvl$parval[include],scientific=TRUE,digits=4), collapse=' '),'\n'))
if(!is.null(out_file)) cat(paste(cost, '=', format(cost_value,scientific=TRUE,digits=4), 'converged =', as.character(cvg), 'parval =', paste(format(pvl$parval[include],scientific=TRUE,digits=4), collapse=' '),'\n'), file = out_file, append = TRUE)
return(cost_value)
}
# optimize; L-BGFS-B only
opt <- optim(par[which(include)], optim_modflow, method = 'L-BFGS-B', lower = lower[which(include)], upper = upper[which(include)], control = control, ...)
opt$included <- include
return(opt)
}
#' Run a MODFLOW model sensitivity analysis, based on the parameter value file and the head predictions output file
#'
#' \code{rmf_analyze} performs a MODFLOW model sensitivity analysis.
#'
#' @param path path to name file; typically '*.nam'
#' @param code name of the MODFLOW variant to use
#' @param par central parameter values (for all or only included parameters); parameter value file values are used if par is not provided (default)
#' @param include logical vector indicating which parameters in the parameter value file to include in the sensitivity analysis
#' @param ... optional arguments passed to \code{rmf_execute}
#' @details Only works with models using MODFLOW parameters and having a head predictions output file as defined in the HOB object
#' @return object of class \code{sen} which is a list with dimensionless scaled sensitivities (dss) and composite scaled sensitivies (css)
#' @export
#' @seealso \code{\link{rmf_execute}}, \code{\link{rmf_create_pvl}} & \code{\link{rmf_create_hob}}
rmf_analyze <- function(path,
code = "2005",
par = NULL,
include = NULL,
...) {
code <- rmf_find(code)
dir <- dirname(path)
nam <- rmf_read_nam(path)
if(!('PVAL' %in% nam$ftype) || !('HOB' %in% nam$ftype)) stop('rmf_analyze only works with models having PVL and HOB input', call. = FALSE)
pvl <- rmf_read_pvl(file.path(dir, nam$fname[which(nam$ftype=='PVAL')]))
hob <- rmf_read_hob(file.path(dir, nam$fname[which(nam$ftype=='HOB')]))
if(hob$iuhobsv == 0 || toupper(nam$ftype[which(nam$nunit == hob$iuhobsv)]) != 'DATA') {
stop('rmf_analyze only works with models having a head predictions output file. This can be created by setting the iuhobsv argument of the HOB file to a non-zero value', call. = FALSE)
}
pvl_org <- pvl
if(is.null(par)) par <- pvl$parval
if(is.null(include)) include <- rep(TRUE,length(par))
# if par only has values for include = TRUE
if(length(par) != length(pvl$parval)) {
par2 <- par
par <- pvl$parval
par[which(include)] <- par2
}
# copy and write original pvl
rmf_write_pvl(pvl_org, file = file.path(dir, paste('original', nam$fname[which(nam$ftype=='PVAL')], sep = '_')))
# initial run
rmf_execute(path = path, code = code, par = par, version = version, convergence = FALSE, ...)
hpr_orig <- rmf_read_hpr(file.path(dir, nam$fname[which(nam$nunit == hob$iuhobsv)]))
sens <- list()
sens$dss <- matrix(NA, nrow = length(hob$obsnam), ncol = length(pvl$parval))
sens$css <- pvl$parval*NA
# dss & css
for(i in which(include)) {
pvl$parval <- par
pvl$parval[i] <- pvl$parval[i]*1.01
rmf_write_pvl(pvl, file = file.path(dir, nam$fname[which(nam$ftype == 'PVAL')]))
rmf_execute(file = file, executable = executable, par = NULL, version = version, convergence = FALSE, ...)
hpr <- rmf_read_hpr(file.path(dir, nam$fname[which(nam$nunit == hob$iuhobsv)]))
sens$dss[,i] <- (hpr$simulated - hpr_orig$simulated)/(0.01)
sens$css[i] <- sqrt(sum(sens$dss[,i]^2)/hob$nh)
}
# write original pvl and run
rmf_write_pvl(pvl_org, file = file.path(dir, nam$fname[which(nam$ftype=='PVAL')]))
rmf_execute(file = file, executable = executable, par = NULL, version = version, convergence = FALSE, ...)
sens$parnam <- pvl$parnam
class(sens) <- 'sen'
return(sens)
}
#' Find paths to executables
#'
#' \code{rmf_find} finds the path to external software executables.
#'
#' The function first looks for the executable in the current working
#' directory. If not there, it looks in \file{C:/WRDAPP/}, where the
#' software might have been installed by \code{\link{rmf_install}}. If the
#' executable cannot be found, the system path variable is used in an attempt
#' to locate it. If it still cannot be located, you should first install the
#' software or add the folder with the executable to the system path.
#'
#' @param name Character. Name of the software. Currently supported values are \code{"MODFLOW-2005"}, \code{"MODFLOW-OWHM"}, \code{"MODFLOW-NWT"}, \code{"MODFLOW-LGR"} and \code{"MODFLOW-CFP"}.
#' @param precision Character. Can be \code{"single"} or \code{"double"}. Only relevant for MODFLOW-2005.
#'
#' @return Path to the executable.
#' @export
#'
#' @examples
#' rmf_find("MODFLOW-2005")
rmf_find <- function(
name = "MODFLOW-2005",
precision = "single"
) {
# TODO automatically select 32 bit executables if available, otherwise throw
# error on 32 bit systems
# TODO check if executable provided in name exists in current wd, not name
# of executable that we are using in RMODFLOW!
if (grepl("2005", name)) {
if (grepl("dbl", name)) precision <- "double"
name <- "MODFLOW-2005"
executable <- ifelse(precision == "single", "mf2005.exe", "mf2005dbl.exe")
folder <- ""
rmf_install_bin_folder <- paste0(getOption("RMODFLOW.path"), "/", name, "/bin/")
if (!file.exists(executable)) {
if (file.exists(paste0(rmf_install_bin_folder, executable))) {
folder <- rmf_install_bin_folder
} else if (Sys.which(executable) == "") {
stop("Path to ", name, " executable not found.")
}
}
return(paste0(folder, executable))
} else if (grepl("owhm", name, ignore.case = TRUE)) {
name <- "MODFLOW-OWHM"
executable <- "MF_OWHM.exe"
folder <- ""
rmf_install_bin_folder <- paste0(getOption("RMODFLOW.path"), "/", name, "/bin/")
if (!file.exists(executable)) {
if (file.exists(paste0(rmf_install_bin_folder, executable))) {
folder <- rmf_install_bin_folder
} else if (Sys.which(executable) == "") {
stop("Path to ", name, " executable not found.")
}
}
return(paste0(folder, executable))
} else if (grepl("nwt", name, ignore.case = TRUE)) {
name <- "MODFLOW-NWT"
executable <- "MODFLOW-NWT_64.exe"
folder <- ""
rmf_install_bin_folder <- paste0(getOption("RMODFLOW.path"), "/", name, "/bin/")
if (!file.exists(executable)) {
if (file.exists(paste0(rmf_install_bin_folder, executable))) {
folder <- rmf_install_bin_folder
} else if (Sys.which(executable) == "") {
stop("Path to ", name, " executable not found.")
}
}
return(paste0(folder, executable))
} else if (grepl("lgr", name, ignore.case = TRUE)) {
name <- "MODFLOW-LGR"
executable <- "mflgr.exe"
folder <- ""
rmf_install_bin_folder <- paste0(getOption("RMODFLOW.path"), "/", name, "/bin/")
if (!file.exists(executable)) {
if (file.exists(paste0(rmf_install_bin_folder, executable))) {
folder <- rmf_install_bin_folder
} else if (Sys.which(executable) == "") {
stop("Path to ", name, " executable not found.")
}
}
return(paste0(folder, executable))
} else if (grepl("cfp", name, ignore.case = TRUE)) {
name <- "MODFLOW-CFP"
executable <- "mf2005cfp.exe"
folder <- ""
rmf_install_bin_folder <- paste0(getOption("RMODFLOW.path"), "/", name, "/")
if (!file.exists(executable)) {
if (file.exists(paste0(rmf_install_bin_folder, executable))) {
folder <- rmf_install_bin_folder
} else if (Sys.which(executable) == "") {
stop("Path to ", name, " executable not found.")
}
}
return(paste0(folder, executable))
} else {
stop("Finding paths to the executables of software other than MODFLOW-2005, MODFLOW-OWHM, MODFLOW-NWT, MODFLOW-LGR or MODFLOW-CFP is currently not supported.")
}
}