MODFLOW 6  version 6.6.0.dev0
USGS Modular Hydrologic Model
mf6xmi.F90
Go to the documentation of this file.
1 !> @brief This module contains the eXtended Model Interface
2 !!
3 !! In this module we expose functionality in the MODFLOW 6 shared library,
4 !! that is _beyond_ the basic model interface: https://bmi-spec.readthedocs.io/en/latest/.
5 !! The main extensions are
6 !!
7 !! - Controlling the kernel at a finer granularity, isolating the call to the linear solve
8 !! of the system. This way, the interface can be used to set up a non-linear coupling
9 !! with, for example, an external unsaturated zone model.
10 !! - Expose the concept of subcomponents, which in case of MODFLOW 6 are 'Numerical Solution'
11 !! objects, each of which represents a separate linear system to solve. An example here
12 !! would be a transport model (GWT) coupled to a groundwater model (GWF).
13 !!
14 !! The common BMI control flow is
15 !!
16 !! ~~~{.py}
17 !! initialize()
18 !!
19 !! while t < t_end:
20 !! update()
21 !!
22 !! finalize()
23 !! ~~~
24 !!
25 !! With the XMI you can now also use it as:
26 !!
27 !! ~~~{.py}
28 !! initialize()
29 !!
30 !! while(t < t_end):
31 !!
32 !! prepare_time_step()
33 !!
34 !! # modify some values here
35 !!
36 !! do_time_step()
37 !!
38 !! # and maybe something here as well
39 !!
40 !! finalize_time_step()
41 !!
42 !! finalize()
43 !! ~~~
44 !!
45 !! Or, when you want to isolate the call to the linear solve, a
46 !! typical application could look like this:
47 !!
48 !! ~~~{.py}
49 !! initialize()
50 !!
51 !! while t < t_end:
52 !!
53 !! prepare_time_step()
54 !!
55 !! for i_sol in solutions:
56 !!
57 !! prepare_solve(i_sol)
58 !!
59 !! while k_iter < max_iter:
60 !!
61 !! # exchange coupled variables
62 !! exchange_data()
63 !!
64 !! # the MODFLOW linear solve:
65 !! solve()
66 !!
67 !! # maybe solve some other, external model here:
68 !! solveExternalModel()
69 !!
70 !! # and exchange back
71 !! exchange_data()
72 !!
73 !! # check for convergence
74 !! convergence_check()
75 !!
76 !! finalize_solve(i_sol)
77 !!
78 !! finalize_time_step()
79 !!
80 !! finalize()
81 !! ~~~
82 !!
83 !! Note that the last example can only work when there is a single Solution Group defined.
84 !! This will typically not be a problem, though, as applications with multiple Solution Groups
85 !! should be quite rare.
86 !<
87 module mf6xmi
88  use mf6bmi
89  use mf6bmiutil
90  use mf6bmierror
91  use mf6coremodule
92  use kindmodule
93  use iso_c_binding, only: c_int, c_char, c_double
94  implicit none
95 
96  integer(I4B), pointer :: iterationcounter => null() !< the counter for the outer iteration loop, initialized in xmi_prepare_iteration()
97 
98 contains
99 
100 #if defined(__WITH_MPI__)
101  function xmi_initialize_mpi(mpi_comm) result(bmi_status) &
102  bind(C, name="initialize_mpi")
103  use mpiworldmodule
105  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_initialize_mpi
106  ! -- dummy variables
107  integer(kind=c_int) :: mpi_comm !< the Fortran communicator (as an integer)
108  integer(kind=c_int) :: bmi_status !< BMI status code
109  ! -- local variables
110  type(mpiworldtype), pointer :: mpi_world => null()
111 
112  ! set parallel
113  mpi_world => get_mpi_world()
114  call mpi_world%set_comm(mpi_comm)
115  simulation_mode = 'PARALLEL'
116 
117  ! regular initialize
118  bmi_status = bmi_initialize()
119 
120  end function xmi_initialize_mpi
121 #endif
122 
123  !> @brief Prepare a single time step
124  !!
125  !! The routine takes the time step \p dt as an argument. However, MODFLOW (currently)
126  !! does not allow to alter this value after initialization, so it is ignored
127  !! here.
128  !<
129  function xmi_prepare_time_step(dt) result(bmi_status) &
130  bind(C, name="prepare_time_step")
131  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_prepare_time_step
132  ! -- dummy variables
133  real(kind=c_double), intent(in) :: dt !< time step
134  integer(kind=c_int) :: bmi_status !< BMI status code
135 
136  call mf6preparetimestep()
137  bmi_status = bmi_success
138 
139  end function xmi_prepare_time_step
140 
141  !> @brief Perform a single time step
142  !!
143  !! It does so by looping over all solution groups, and calling
144  !! the calculate function on all solutions in there.
145  !<
146  function xmi_do_time_step() result(bmi_status) bind(C, name="do_time_step")
147  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_do_time_step
148  ! -- dummy variables
149  integer(kind=c_int) :: bmi_status !< BMI status code
150 
151  call mf6dotimestep()
152  bmi_status = bmi_success
153 
154  end function xmi_do_time_step
155 
156  !> @brief Finalize the time step
157  !!
158  !! This will mostly write output and messages. It is essential
159  !! to call this to finish the time step.
160  !<
161  function xmi_finalize_time_step() result(bmi_status) &
162  bind(C, name="finalize_time_step")
163  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_finalize_time_step
164  ! -- dummy variables
165  integer(kind=c_int) :: bmi_status !< BMI status code
166  ! -- local variables
167  logical :: hasconverged
168 
169  hasconverged = mf6finalizetimestep()
170  if (hasconverged) then
171  bmi_status = bmi_success
172  else
173  write (bmi_last_error, fmt_general_err) 'simulation failed to converge'
175  bmi_status = bmi_failure
176  end if
177 
178  end function xmi_finalize_time_step
179 
180  !> @brief This will get the number of Numerical Solutions in the simulation
181  !!
182  !! For most applications, this number will be equal to 1. Note that this part
183  !! of the XMI only works when the simulation is defined with a single
184  !! Solution Group. (If you don't know what a Solution Group is, then
185  !! you are most likely not using more than one...)
186  !<
187  function xmi_get_subcomponent_count(count) result(bmi_status) &
188  bind(C, name="get_subcomponent_count")
189  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_get_subcomponent_count
190  ! -- modules
191  use listsmodule, only: solutiongrouplist
192  use simvariablesmodule, only: istdout
193  ! -- dummy variables
194  integer(kind=c_int), intent(out) :: count !< number of solutions
195  integer(kind=c_int) :: bmi_status !< BMI status code
196  ! -- local variables
197  class(solutiongrouptype), pointer :: sgp
198 
199  ! the following is true for all calls at this level (subcomponent)
200  if (solutiongrouplist%Count() /= 1) then
201  write (istdout, *) &
202  'Error: BMI does not support the use of multiple solution groups'
203  count = -1
204  bmi_status = bmi_failure
205  return
206  end if
207 
208  sgp => getsolutiongroupfromlist(solutiongrouplist, 1)
209  count = sgp%nsolutions
210  bmi_status = bmi_success
211 
212  end function xmi_get_subcomponent_count
213 
214  !> @brief Prepare for solving the system
215  !!
216  !! This preparation mostly consists of advancing the solutions, models, and exchanges
217  !! in the simulation. The index \p subcomponent_idx runs from 1 to the value returned
218  !! by xmi_get_subcomponent_count().
219  !<
220  function xmi_prepare_solve(subcomponent_idx) result(bmi_status) &
221  bind(C, name="prepare_solve")
222  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_prepare_solve
223  ! -- modules
224  use listsmodule, only: solutiongrouplist
226  use simvariablesmodule, only: istdout
227  ! -- dummy variables
228  integer(kind=c_int) :: subcomponent_idx !< index of the subcomponent (i.e. Numerical Solution)
229  integer(kind=c_int) :: bmi_status !< BMI status code
230  ! -- local variables
231  class(basesolutiontype), pointer :: bs
232 
233  ! people might not call 'xmi_get_subcomponent_count' first, so let's repeat this:
234  if (solutiongrouplist%Count() /= 1) then
235  write (istdout, *) &
236  'Error: BMI does not support the use of multiple solution groups'
237  bmi_status = bmi_failure
238  return
239  end if
240 
241  ! get the solution we are running
242  bs => getsolution(subcomponent_idx)
243 
244  ! *_ad (model, exg, sln)
245  call bs%prepareSolve()
246 
247  ! reset counter
248  allocate (iterationcounter)
249  iterationcounter = 0
250 
251  bmi_status = bmi_success
252 
253  end function xmi_prepare_solve
254 
255  !> @brief Build and solve the linear system
256  !!
257  !! The solve is called on the Numerical Solution indicated by the value of \p subcomponent_idx,
258  !! which runs from 1 to the value returned by xmi_get_subcomponent_count(). Before calling
259  !! this, a matching call to xmi_prepare_solve() should be done.
260  !<
261  function xmi_solve(subcomponent_idx, has_converged) result(bmi_status) &
262  bind(C, name="solve")
263  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_solve
264  ! -- modules
268  ! -- dummy variables
269  integer(kind=c_int), intent(in) :: subcomponent_idx !< index of the subcomponent (i.e. Numerical Solution)
270  integer(kind=c_int), intent(out) :: has_converged !< equal to 1 for convergence, 0 otherwise
271  integer(kind=c_int) :: bmi_status !< BMI status code
272  ! -- local variables
273  class(basesolutiontype), pointer :: bs
274 
275  ! get the numerical solution we are running
276  bs => getsolution(subcomponent_idx)
277 
278  ! execute the nth iteration
280  call bs%solve(iterationcounter)
281 
282  ! the following check is equivalent to that in NumericalSolution%sln_ca
283  select type (bs)
284  class is (numericalsolutiontype)
285  if (bs%icnvg == 1) then
286  has_converged = 1
287  else
288  has_converged = 0
289  end if
290  class is (explicitsolutiontype)
291  has_converged = 1
292  end select
293 
294  bmi_status = bmi_success
295 
296  end function xmi_solve
297 
298  !> @brief Finalize the solve of the system
299  !!
300  !! This will determine convergence, reports, calculate flows and budgets, and more...
301  !! It should always follow after a call to xmi_prepare_solve() and xmi_solve().
302  !!
303  !! @param[in] subcomponent_idx the index of the subcomponent (Numerical Solution)
304  !! @return bmi_status the BMI status code
305  !<
306  function xmi_finalize_solve(subcomponent_idx) result(bmi_status) &
307  bind(C, name="finalize_solve")
308  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_finalize_solve
309  ! -- modules
311  ! -- dummy variables
312  integer(kind=c_int), intent(in) :: subcomponent_idx !< index of the subcomponent (i.e. Numerical Solution)
313  integer(kind=c_int) :: bmi_status !< BMI status code
314  ! -- local variables
315  class(basesolutiontype), pointer :: bs
316  integer(I4B) :: hasconverged
317 
318  ! get the numerical solution we are running
319  bs => getsolution(subcomponent_idx)
320 
321  ! hasConverged is equivalent to the isgcnvg variable which is initialized to 1,
322  ! see the body of the picard loop in SolutionGroupType%sgp_ca
323  hasconverged = 1
324 
325  ! finish up
326  call bs%finalizeSolve(iterationcounter, hasconverged, 0)
327 
328  ! check convergence on solution
329  if (.not. hasconverged == 1) then
330  write (bmi_last_error, fmt_fail_cvg_sol) subcomponent_idx
332  end if
333 
334  ! non-convergence is no reason to crash the API:
335  bmi_status = bmi_success
336 
337  ! clear this for safety
338  deallocate (iterationcounter)
339 
340  end function xmi_finalize_solve
341 
342  !> @brief Get the version string for this component
343  !<
344  function xmi_get_version(mf_version) result(bmi_status) &
345  bind(C, name="get_version")
346  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_get_version
347  ! -- modules
349  ! -- dummy variables
350  character(kind=c_char), intent(inout) :: mf_version(bmi_lenversion)
351  integer(kind=c_int) :: bmi_status !< BMI status code
352  ! -- local variables
353  character(len=BMI_LENVERSION) :: vstr
354 
355  if (idevelopmode == 1) then
356  vstr = versionnumber//'-dev'
357  else
358  vstr = versionnumber
359  end if
360  mf_version = string_to_char_array(vstr, len_trim(vstr))
361  bmi_status = bmi_success
362 
363  end function xmi_get_version
364 
365  !> @brief Get the full address string for a variable
366  !!
367  !! This routine constructs the full address string of a variable using the
368  !! exact same logic as the internal memory manager. This routine
369  !! should always be used when accessing a variable through the BMI
370  !! to assure compatibility with future versions of the library.
371  !<
372  function get_var_address(c_component_name, c_subcomponent_name, &
373  c_var_name, c_var_address) &
374  result(bmi_status) bind(C, name="get_var_address")
375  !DIR$ ATTRIBUTES DLLEXPORT :: get_var_address
376  ! -- modules
380  ! -- dummy variables
381  character(kind=c_char), intent(in) :: c_component_name(*) !< name of the component (a Model or Solution)
382  character(kind=c_char), intent(in) :: c_subcomponent_name(*) !< name of the subcomponent (Package), or an empty
383  !! string'' when not applicable
384  character(kind=c_char), intent(in) :: c_var_name(*) !< name of the variable
385  character(kind=c_char), intent(out) :: c_var_address(bmi_lenvaraddress) !< full address of the variable
386  integer(kind=c_int) :: bmi_status !< BMI status code
387  ! -- local variables
388  character(len=LENCOMPONENTNAME) :: component_name
389  character(len=LENCOMPONENTNAME) :: subcomponent_name
390  character(len=LENVARNAME) :: variable_name
391  character(len=LENMEMPATH) :: mem_path
392  character(len=LENMEMADDRESS) :: mem_address
393 
394  ! convert to Fortran strings
395  component_name = char_array_to_string(c_component_name, &
396  strlen(c_component_name, &
397  lencomponentname + 1))
398  subcomponent_name = char_array_to_string(c_subcomponent_name, &
399  strlen(c_subcomponent_name, &
400  lencomponentname + 1))
401  variable_name = char_array_to_string(c_var_name, &
402  strlen(c_var_name, &
403  lenvarname + 1))
404 
405  ! create memory address
406  if (subcomponent_name == '') then
407  mem_path = create_mem_path(component_name)
408  else
409  mem_path = create_mem_path(component_name, subcomponent_name)
410  end if
411  mem_address = create_mem_address(mem_path, variable_name)
412 
413  ! convert to c string:
414  c_var_address(1:len(trim(mem_address)) + 1) = &
415  string_to_char_array(trim(mem_address), len(trim(mem_address)))
416 
417  bmi_status = bmi_success
418 
419  end function get_var_address
420 
421 end module mf6xmi
This module contains simulation constants.
Definition: Constants.f90:9
integer(i4b), parameter lencomponentname
maximum length of a component name
Definition: Constants.f90:18
integer(i4b), parameter lenmemaddress
maximum length of the full memory address, including variable name
Definition: Constants.f90:31
integer(i4b), parameter lenvarname
maximum length of a variable name
Definition: Constants.f90:17
integer(i4b), parameter lenmempath
maximum length of the memory path
Definition: Constants.f90:27
Explicit Solution Module.
This module defines variable data types.
Definition: kind.f90:8
type(listtype), public solutiongrouplist
Definition: mf6lists.f90:22
character(len=lenmemaddress) function create_mem_address(mem_path, var_name)
returns the address string of the memory object
character(len=lenmempath) function create_mem_path(component, subcomponent, context)
returns the path to the memory object
This module contains the MODFLOW 6 BMI.
Definition: mf6bmi.f90:18
integer(kind=c_int) function bmi_initialize()
Initialize the computational core.
Definition: mf6bmi.f90:67
Detailed error information for the BMI.
Definition: mf6bmiError.f90:6
character(len= *), parameter fmt_general_err
Definition: mf6bmiError.f90:22
integer, parameter bmi_failure
BMI status code for failure (taken from bmi.f90, CSDMS)
Definition: mf6bmiError.f90:12
character(len=lenerrmessage) bmi_last_error
module variable containing the last error as a Fortran string
Definition: mf6bmiError.f90:20
subroutine report_bmi_error(err_msg)
Sets the last BMI error message and copies it to an exported C-string.
Definition: mf6bmiError.f90:47
character(len= *), parameter fmt_fail_cvg_sol
Definition: mf6bmiError.f90:37
integer, parameter bmi_success
BMI status code for success (taken from bmi.f90, CSDMS)
Definition: mf6bmiError.f90:13
This module contains helper routines and parameters for the MODFLOW 6 BMI.
Definition: mf6bmiUtil.f90:4
integer(c_int), bind(C, name="BMI_LENVERSION") bmi_lenversion
length of version string, e.g. '6.3.1' or '6.4.1-dev'
Definition: mf6bmiUtil.f90:42
class(basesolutiontype) function, pointer getsolution(subcomponent_idx)
Get the solution object for this index.
Definition: mf6bmiUtil.f90:219
pure character(kind=c_char, len=1) function, dimension(length+1) string_to_char_array(string, length)
Convert Fortran string to C-style character string.
Definition: mf6bmiUtil.f90:149
pure integer(i4b) function strlen(char_array, max_len)
Returns the string length without the trailing null character.
Definition: mf6bmiUtil.f90:113
pure character(len=length) function char_array_to_string(char_array, length)
Convert C-style string to Fortran character string.
Definition: mf6bmiUtil.f90:133
integer(c_int), bind(C, name="BMI_LENVARADDRESS") bmi_lenvaraddress
max. length for the variable's address C-string
Definition: mf6bmiUtil.f90:34
Core MODFLOW 6 module.
Definition: mf6core.f90:8
subroutine mf6dotimestep()
Run time step.
Definition: mf6core.f90:588
subroutine mf6preparetimestep()
Read and prepare time step.
Definition: mf6core.f90:473
logical(lgp) function mf6finalizetimestep()
Finalize time step.
Definition: mf6core.f90:670
This module contains the eXtended Model Interface.
Definition: mf6xmi.F90:87
integer(kind=c_int) function xmi_get_version(mf_version)
Get the version string for this component.
Definition: mf6xmi.F90:346
integer(i4b), pointer iterationcounter
the counter for the outer iteration loop, initialized in xmi_prepare_iteration()
Definition: mf6xmi.F90:96
integer(kind=c_int) function xmi_solve(subcomponent_idx, has_converged)
Build and solve the linear system.
Definition: mf6xmi.F90:263
integer(kind=c_int) function xmi_prepare_time_step(dt)
Prepare a single time step.
Definition: mf6xmi.F90:131
integer(kind=c_int) function xmi_prepare_solve(subcomponent_idx)
Prepare for solving the system.
Definition: mf6xmi.F90:222
integer(kind=c_int) function xmi_get_subcomponent_count(count)
This will get the number of Numerical Solutions in the simulation.
Definition: mf6xmi.F90:189
integer(kind=c_int) function xmi_finalize_time_step()
Finalize the time step.
Definition: mf6xmi.F90:163
integer(kind=c_int) function xmi_finalize_solve(subcomponent_idx)
Finalize the solve of the system.
Definition: mf6xmi.F90:308
integer(kind=c_int) function xmi_do_time_step()
Perform a single time step.
Definition: mf6xmi.F90:147
integer(kind=c_int) function get_var_address(c_component_name, c_subcomponent_name, c_var_name, c_var_address)
Get the full address string for a variable.
Definition: mf6xmi.F90:375
type(mpiworldtype) function, pointer, public get_mpi_world()
Definition: MpiWorld.f90:32
This module contains simulation variables.
Definition: SimVariables.f90:9
character(len=linelength) simulation_mode
integer(i4b) istdout
unit number for stdout
This module contains version information.
Definition: version.f90:7
integer(i4b), parameter idevelopmode
Definition: version.f90:19
character(len= *), parameter versionnumber
Definition: version.f90:20
Manages and solves explicit models.