MODFLOW 6  version 6.5.0.dev2
MODFLOW 6 Code Documentation
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(numericalsolutiontype), pointer :: ns
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 numerical solution we are running
242  ns => getsolution(subcomponent_idx)
243 
244  ! *_ad (model, exg, sln)
245  call ns%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
266  ! -- dummy variables
267  integer(kind=c_int), intent(in) :: subcomponent_idx !< index of the subcomponent (i.e. Numerical Solution)
268  integer(kind=c_int), intent(out) :: has_converged !< equal to 1 for convergence, 0 otherwise
269  integer(kind=c_int) :: bmi_status !< BMI status code
270  ! -- local variables
271  class(numericalsolutiontype), pointer :: ns
272 
273  ! get the numerical solution we are running
274  ns => getsolution(subcomponent_idx)
275 
276  ! execute the nth iteration
278  call ns%solve(iterationcounter)
279 
280  ! the following check is equivalent to that in NumericalSolution%sln_ca
281  if (ns%icnvg == 1) then
282  has_converged = 1
283  else
284  has_converged = 0
285  end if
286 
287  bmi_status = bmi_success
288 
289  end function xmi_solve
290 
291  !> @brief Finalize the solve of the system
292  !!
293  !! This will determine convergence, reports, calculate flows and budgets, and more...
294  !! It should always follow after a call to xmi_prepare_solve() and xmi_solve().
295  !!
296  !! @param[in] subcomponent_idx the index of the subcomponent (Numerical Solution)
297  !! @return bmi_status the BMI status code
298  !<
299  function xmi_finalize_solve(subcomponent_idx) result(bmi_status) &
300  bind(C, name="finalize_solve")
301  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_finalize_solve
302  ! -- modules
304  ! -- dummy variables
305  integer(kind=c_int), intent(in) :: subcomponent_idx !< index of the subcomponent (i.e. Numerical Solution)
306  integer(kind=c_int) :: bmi_status !< BMI status code
307  ! -- local variables
308  class(numericalsolutiontype), pointer :: ns
309  integer(I4B) :: hasconverged
310 
311  ! get the numerical solution we are running
312  ns => getsolution(subcomponent_idx)
313 
314  ! hasConverged is equivalent to the isgcnvg variable which is initialized to 1,
315  ! see the body of the picard loop in SolutionGroupType%sgp_ca
316  hasconverged = 1
317 
318  ! finish up
319  call ns%finalizeSolve(iterationcounter, hasconverged, 0)
320 
321  ! check convergence on solution
322  if (.not. hasconverged == 1) then
323  write (bmi_last_error, fmt_fail_cvg_sol) subcomponent_idx
325  end if
326 
327  ! non-convergence is no reason to crash the API:
328  bmi_status = bmi_success
329 
330  ! clear this for safety
331  deallocate (iterationcounter)
332 
333  end function xmi_finalize_solve
334 
335  !> @brief Get the version string for this component
336  !<
337  function xmi_get_version(mf_version) result(bmi_status) &
338  bind(C, name="get_version")
339  !DIR$ ATTRIBUTES DLLEXPORT :: xmi_get_version
340  ! -- modules
342  ! -- dummy variables
343  character(kind=c_char), intent(out) :: mf_version(bmi_lenversion)
344  integer(kind=c_int) :: bmi_status !< BMI status code
345  ! -- local variables
346  character(len=BMI_LENVERSION) :: vstr
347 
348  if (idevelopmode == 1) then
349  vstr = versionnumber//'-dev'
350  else
351  vstr = versionnumber
352  end if
353  mf_version = string_to_char_array(vstr, len_trim(vstr))
354  bmi_status = bmi_success
355 
356  end function xmi_get_version
357 
358  !> @brief Get the full address string for a variable
359  !!
360  !! This routine constructs the full address string of a variable using the
361  !! exact same logic as the internal memory manager. This routine
362  !! should always be used when accessing a variable through the BMI
363  !! to assure compatibility with future versions of the library.
364  !<
365  function get_var_address(c_component_name, c_subcomponent_name, &
366  c_var_name, c_var_address) &
367  result(bmi_status) bind(C, name="get_var_address")
368  !DIR$ ATTRIBUTES DLLEXPORT :: get_var_address
369  ! -- modules
373  ! -- dummy variables
374  character(kind=c_char), intent(in) :: c_component_name(*) !< name of the component (a Model or Solution)
375  character(kind=c_char), intent(in) :: c_subcomponent_name(*) !< name of the subcomponent (Package), or an empty
376  !! string'' when not applicable
377  character(kind=c_char), intent(in) :: c_var_name(*) !< name of the variable
378  character(kind=c_char), intent(out) :: c_var_address(bmi_lenvaraddress) !< full address of the variable
379  integer(kind=c_int) :: bmi_status !< BMI status code
380  ! -- local variables
381  character(len=LENCOMPONENTNAME) :: component_name
382  character(len=LENCOMPONENTNAME) :: subcomponent_name
383  character(len=LENVARNAME) :: variable_name
384  character(len=LENMEMPATH) :: mem_path
385  character(len=LENMEMADDRESS) :: mem_address
386 
387  ! convert to Fortran strings
388  component_name = char_array_to_string(c_component_name, &
389  strlen(c_component_name, &
390  lencomponentname + 1))
391  subcomponent_name = char_array_to_string(c_subcomponent_name, &
392  strlen(c_subcomponent_name, &
393  lencomponentname + 1))
394  variable_name = char_array_to_string(c_var_name, &
395  strlen(c_var_name, &
396  lenvarname + 1))
397 
398  ! create memory address
399  if (subcomponent_name == '') then
400  mem_path = create_mem_path(component_name)
401  else
402  mem_path = create_mem_path(component_name, subcomponent_name)
403  end if
404  mem_address = create_mem_address(mem_path, variable_name)
405 
406  ! convert to c string:
407  c_var_address(1:len(trim(mem_address)) + 1) = &
408  string_to_char_array(trim(mem_address), len(trim(mem_address)))
409 
410  bmi_status = bmi_success
411 
412  end function get_var_address
413 
414 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:30
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:26
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:66
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
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
class(numericalsolutiontype) function, pointer getsolution(subcomponent_idx)
Get the solution object for this index.
Definition: mf6bmiUtil.f90:219
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:601
subroutine mf6preparetimestep()
Read and prepare time step.
Definition: mf6core.f90:490
logical(lgp) function mf6finalizetimestep()
Finalize time step.
Definition: mf6core.f90:686
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:339
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:301
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:368
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