diff --git a/README.md b/README.md index 5032d1d..6d2461c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,28 @@ This package is currently in pre-alpha development, and is not suitable for use, There are several jupyter notebook examples of using pypestutils for a structured and quadtree Freyberg model. These notebooks rely on both [flopy](https://github.com/modflowpy/flopy) and [pyEMU](https://github.com/pypest/pyemu) to help with visualization and processing. +The use the low-level python interface to the shared fortran library, you create a `PESTUTILSLIB` instance and then can directly call the shared library routines: + +``` +from pypestutils.pestutilslib import PestUtilsLib +lib = PestUtilsLib() #the constructor searches for the shared lib +grid_info = lib.install_mf6_grid_from_file("grid","freyberg6.disv.grb") +easting,northing,elev = lib.get_cell_centres_mf6("grid",grid_info["ncells"]) +``` + +The higher-level helper functions obsecure the calls the fortran library and string together multiple low-level function calls: + +``` +import pypestutils.helpers as helpers +grid_info = helpers.get_grid_info_from_file("freyberg6.disv.grb") +``` + +## Documentation + +The documentation for pypestutils can be found [here](docs/index.html) + +The documentation for the shared FORTRAN library can be found [here](docs/pestutilslib/fortran_library_documentation.md) + ## Installation ### Dependencies diff --git a/docs/ctypes_declarations.html b/docs/ctypes_declarations.html new file mode 100644 index 0000000..203a009 --- /dev/null +++ b/docs/ctypes_declarations.html @@ -0,0 +1,1180 @@ + + + + + + +pypestutils.ctypes_declarations API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.ctypes_declarations

+
+
+

Low-level Fortran-Python ctypes functions.

+
+ +Expand source code + +
"""Low-level Fortran-Python ctypes functions."""
+from __future__ import annotations
+
+from ctypes import ARRAY, CDLL, POINTER, c_char, c_double, c_int
+
+from numpy.ctypeslib import ndpointer
+
+# Cache variables by uppercase dimvar name
+_dimvar_cache = {}
+_char_array_cache = {}
+# other lengths not defined in dimvar
+_misc_lengths = {
+    "LENVARTYPE": 17,
+    "LENFLOWTYPE": 17,
+}
+
+
+def get_dimvar_int(lib: CDLL, name: str) -> int:
+    """Get dimvar constant integer from library instance.
+
+    Parameters
+    ----------
+    lib : CDLL
+        Ctypes library instance.
+    name : str
+        Uppercase name of variable in dimvar or other custom name.
+
+    Returns
+    -------
+    int
+
+    Raises
+    ------
+    ValueError
+        If name is not defined in lib object.
+    """
+    if name in _dimvar_cache:
+        return _dimvar_cache[name]
+    elif name in _misc_lengths:
+        # Special consideration for constants not specified by dimvar
+        return _misc_lengths[name]
+    c_var = c_int.in_dll(lib, name)
+    _dimvar_cache[name] = c_var.value
+    return c_var.value
+
+
+def get_char_array(lib: CDLL, name: str):
+    """Get c_char Array with a fixed size from dimvar.
+
+    Parameters
+    ----------
+    lib : CDLL
+        Ctypes library instance.
+    name : str
+        Uppercase name of variable in dimvar or other custom name.
+    """
+    if name in _char_array_cache:
+        return _char_array_cache[name]
+    size = get_dimvar_int(lib, name)
+    array_type = ARRAY(c_char, size)
+    _char_array_cache[name] = array_type
+    return array_type
+
+
+def prototype(lib) -> None:
+    """Add ctypes prototypes for each function in pestutils.
+
+    Parameters
+    ----------
+    lib : CDLL
+        Ctypes library instance, which is modified in-place
+    """
+    # Generate c_char Array types based on dimvar sizes
+    filename_t = get_char_array(lib, "LENFILENAME")
+    message_t = get_char_array(lib, "LENMESSAGE")
+    gridname_t = get_char_array(lib, "LENGRIDNAME")
+    vartype_t = get_char_array(lib, "LENVARTYPE")
+    flowtype_t = get_char_array(lib, "LENFLOWTYPE")
+
+    # inquire_modflow_binary_file_specs(
+    #   filein,fileout,isim,itype,iprec,narray,ntime)
+    lib.inquire_modflow_binary_file_specs.argtypes = (
+        POINTER(filename_t),  # filein, in
+        POINTER(filename_t),  # fileout, in
+        POINTER(c_int),  # isim, in
+        POINTER(c_int),  # itype, in
+        POINTER(c_int),  # iprec, out
+        POINTER(c_int),  # narray, out
+        POINTER(c_int),  # ntime, out
+    )
+    lib.inquire_modflow_binary_file_specs.restype = c_int
+
+    # retrieve_error_message(errormessage)
+    lib.retrieve_error_message.argtypes = (POINTER(message_t),)  # errormessage, out
+    lib.retrieve_error_message.restype = c_int
+
+    # install_structured_grid(
+    #   gridname,ncol,nrow,nlay,icorner,e0,n0,rotation,delr,delc)
+    lib.install_structured_grid.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # ncol, in
+        POINTER(c_int),  # nrow, in
+        POINTER(c_int),  # nlay, in
+        POINTER(c_int),  # icorner, in
+        POINTER(c_double),  # e0, in
+        POINTER(c_double),  # n0, in
+        POINTER(c_double),  # rotation, in
+        ndpointer(c_double, ndim=1, flags="F"),  # delr(ncol), in
+        ndpointer(c_double, ndim=1, flags="F"),  # delc(nrow), in
+    )
+    lib.install_structured_grid.restype = c_int
+
+    # get_cell_centres_structured(gridname,ncpl,cellx,celly)
+    lib.get_cell_centres_structured.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # ncpl, in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # cellx(ncells), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # celly(ncells), out
+    )
+    lib.get_cell_centres_structured.restype = c_int
+
+    # uninstall_structured_grid(gridname)
+    lib.uninstall_structured_grid.argtypes = (POINTER(gridname_t),)  # gridname, in
+    lib.uninstall_structured_grid.restype = c_int
+
+    # free_all_memory()
+    lib.free_all_memory.argtypes = ()
+    lib.free_all_memory.restype = c_int
+
+    # interp_from_structured_grid(
+    #   gridname,depvarfile,isim,iprec,ntime,vartype,interpthresh,nointerpval,
+    #   npts,ecoord,ncoord,layer,nproctime,simtime,simstate)
+    lib.interp_from_structured_grid.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(filename_t),  # depvarfile, in
+        POINTER(c_int),  # isim, in
+        POINTER(c_int),  # iprec, in
+        POINTER(c_int),  # ntime, in
+        POINTER(vartype_t),  # vartype, in
+        POINTER(c_double),  # interpthresh, in
+        POINTER(c_double),  # nointerpval, in
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecoord, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncoord, in
+        ndpointer(c_int, ndim=1, flags="F"),  # layer, in
+        POINTER(c_int),  # nproctime, out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # simtime(ntime), out
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # simstate(ntime,npts), out
+    )
+    lib.interp_from_structured_grid.restype = c_int
+
+    # interp_to_obstime(
+    #   nsimtime,nproctime,npts,simtime,simval,interpthresh,how_extrap,
+    #   time_extrap,nointerpval,nobs,obspoint,obstime,obssimval)
+    lib.interp_to_obstime.argtypes = (
+        POINTER(c_int),  # nsimtime, in
+        POINTER(c_int),  # nproctime, in
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # simtime(nsimtime), in
+        ndpointer(c_double, ndim=2, flags="F"),  # simval(nsimtime,npts), in
+        POINTER(c_double),  # interpthresh, in
+        POINTER(c_char),  # how_extrap, in
+        POINTER(c_double),  # time_extrap, in
+        POINTER(c_double),  # nointerpval, in
+        POINTER(c_int),  # nobs, in
+        ndpointer(c_int, ndim=1, flags="F"),  # obspoint(nobs), in
+        ndpointer(c_double, ndim=1, flags="F"),  # obstime(nobs), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # obssimval(nobs), out
+    )
+    lib.interp_to_obstime.restype = c_int
+
+    # install_mf6_grid_from_file(
+    #   gridname,grbfile,idis,ncells,ndim1,ndim2,ndim3)
+    lib.install_mf6_grid_from_file.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(filename_t),  # grbfile, in
+        POINTER(c_int),  # idis, out
+        POINTER(c_int),  # ncells, out
+        POINTER(c_int),  # ndim1, out
+        POINTER(c_int),  # ndim2, out
+        POINTER(c_int),  # ndim3, out
+    )
+    lib.install_mf6_grid_from_file.restype = c_int
+
+    # get_cell_centres_mf6(gridname,ncells,cellx,celly,cellz)
+    lib.get_cell_centres_mf6.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # ncells, in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # cellx(ncells), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # celly(ncells), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # cellz(ncells), out
+    )
+    lib.get_cell_centres_mf6.restype = c_int
+
+    # uninstall_mf6_grid(gridname)
+    lib.uninstall_mf6_grid.argtypes = (POINTER(gridname_t),)  # gridname, in
+    lib.uninstall_mf6_grid.restype = c_int
+
+    # calc_mf6_interp_factors(
+    #   gridname,npts,ecoord,ncoord,layer,factorfile,
+    #   factorfiletype,blnfile,interp_success)
+    lib.calc_mf6_interp_factors.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecoord, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncoord, in
+        ndpointer(c_int, ndim=1, flags="F"),  # layer, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(filename_t),  # blnfile, in
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # interp_success, out
+    )
+    lib.calc_mf6_interp_factors.restype = c_int
+
+    # interp_from_mf6_depvar_file(
+    #   depvarfile,factorfile,factorfiletype,ntime,vartype,interpthresh,
+    #   reapportion,nointerpval,npts,nproctime,simtime,simstate)
+    lib.interp_from_mf6_depvar_file.argtypes = (
+        POINTER(filename_t),  # depvarfile, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # ntime, in
+        POINTER(vartype_t),  # vartype(17), in
+        POINTER(c_double),  # interpthresh, in
+        POINTER(c_int),  # reapportion, in
+        POINTER(c_double),  # nointerpval, in
+        POINTER(c_int),  # npts, in
+        POINTER(c_int),  # nproctime, out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # simtime(ntime), out
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # simstate(ntime,npts), out
+    )
+    lib.interp_from_mf6_depvar_file.restype = c_int
+
+    # extract_flows_from_cbc_file(
+    #   cbcfile,flowtype,isim,iprec,ncell,izone,nzone,numzone,zonenumber,
+    #   ntime,nproctime,timestep,stressperiod,simtime,simflow)
+    lib.extract_flows_from_cbc_file.argtypes = (
+        POINTER(filename_t),  # cbcfile, in
+        POINTER(flowtype_t),  # flowtype, in
+        POINTER(c_int),  # isim, in
+        POINTER(c_int),  # iprec, in
+        POINTER(c_int),  # ncell, in
+        ndpointer(c_int, ndim=1, flags="F"),  # izone(ncell), in
+        POINTER(c_int),  # nzone, in
+        POINTER(c_int),  # numzone, out
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # zonenumber(nzone), out
+        POINTER(c_int),  # ntime, in
+        POINTER(c_int),  # nproctime, out
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # timestep(ntime), out
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # stressperiod(ntime), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # simtime(ntime), out
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # simflow(ntime,nzone), out
+    )
+    lib.extract_flows_from_cbc_file.restype = c_int
+
+    # calc_kriging_factors_2d(
+    #   npts,ecs,ncs,zns,mpts,ect,nct,znt,vartype,krigtype,aa,anis,bearing,
+    #   searchrad,maxpts,minpts,factorfile,factorfiletype,icount_interp)
+    lib.calc_kriging_factors_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        POINTER(c_int),  # vartype, in
+        POINTER(c_int),  # krigtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        POINTER(c_double),  # searchrad, in
+        POINTER(c_int),  # maxpts, in
+        POINTER(c_int),  # minpts, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_kriging_factors_2d.restype = c_int
+
+    # calc_kriging_factors_auto_2d(
+    #   npts,ecs,ncs,zns,mpts,ect,nct,znt,krigtype,anis,bearing,
+    #   factorfile,factorfiletype,icount_interp)
+    lib.calc_kriging_factors_auto_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        POINTER(c_int),  # krigtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_kriging_factors_auto_2d.restype = c_int
+
+    # calc_kriging_factors_3d(
+    #   npts,ecs,ncs,zcs,zns,mpts,ect,nct,zct,znt,krigtype,nzone,zonenum,
+    #   vartype,ahmax,ahmin,avert,bearing,dip,rake,srhmax,srhmin,srvert,
+    #   maxpts,minpts,factorfile,factorfiletype,icount_interp)
+    lib.calc_kriging_factors_3d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zcs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        POINTER(c_int),  # krigtype, in
+        POINTER(c_int),  # nzone, in
+        ndpointer(c_int, ndim=1, flags="F"),  # zonenum(nzone), in
+        ndpointer(c_int, ndim=1, flags="F"),  # vartype(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(nzone), in
+        POINTER(c_double),  # srhmax, in
+        POINTER(c_double),  # srhmin, in
+        POINTER(c_double),  # srvert, in
+        POINTER(c_int),  # maxpts, in
+        POINTER(c_int),  # minpts, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_kriging_factors_3d.restype = c_int
+
+    # krige_using_file(
+    #   factorfile,factorfiletype,npts,mpts,krigtype,transtype,
+    #   sourceval,targval,icount_interp,meanval)
+    lib.krige_using_file.argtypes = (
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # npts, in
+        POINTER(c_int),  # mpts, in
+        POINTER(c_int),  # krigtype, in
+        POINTER(c_int),  # transtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), out
+        POINTER(c_int),  # icount_interp, out
+        ndpointer(c_double, ndim=1, flags="F"),  # meanval(mpts), in, optional
+    )
+    lib.krige_using_file.restype = c_int
+
+    # build_covar_matrix_2d(
+    #   npts,ec,nc,zn,vartype,nugget,aa,sill,anis,bearing,ldcovmat,covmat)
+    lib.build_covar_matrix_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zn(npts), in
+        POINTER(c_int),  # vartype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # nugget(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sill(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(npts), in
+        POINTER(c_int),  # ldcovmat, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # covmat(ldcovmat,npts), out
+    )
+    lib.build_covar_matrix_2d.restype = c_int
+
+    # build_covar_matrix_3d(
+    #   npts,ec,nc,zc,zn,vartype,
+    # nugget,sill,ahmax,ahmin,avert,bearing,dip,rake,ldcovmat,covmat)
+    lib.build_covar_matrix_3d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zc(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zn(npts), in
+        POINTER(c_int),  # vartype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # nugget(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sill(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(npts), in
+        POINTER(c_int),  # ldcovmat, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # covmat(ldcovmat,npts), out
+    )
+    lib.build_covar_matrix_3d.restype = c_int
+
+    # calc_structural_overlay_factors(
+    #   npts,ecs,ncs,ids,conwidth,aa,structype,inverse_power,
+    #   mpts,ect,nct,active,factorfile,factorfiletype,icount_interp)
+    lib.calc_structural_overlay_factors.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # ids(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # conwidth(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(npts), in
+        POINTER(c_int),  # structype, in
+        POINTER(c_double),  # inverse_power, in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # active(mpts), in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_structural_overlay_factors.restype = c_int
+
+    # interpolate_blend_using_file(
+    #   factorfile,factorfiletype,npts,mpts,transtype,
+    #   lt_target,gt_target,sourceval,targval,icount_interp)
+    lib.interpolate_blend_using_file.argtypes = (
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # npts, in
+        POINTER(c_int),  # mpts, in
+        POINTER(c_int),  # transtype, in
+        POINTER(c_char),  # lt_target, in
+        POINTER(c_char),  # gt_target, in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), inout
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.interpolate_blend_using_file.restype = c_int
+
+    # ipd_interpolate_2d(npts,ecs,ncs,zns,sourceval,
+    #   mpts,ect,nct,znt,targval,transtype,anis,bearing,invpow)
+    lib.ipd_interpolate_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), out
+        POINTER(c_int),  # transtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # invpow(mpts), in
+    )
+    lib.ipd_interpolate_2d.restype = c_int
+
+    # ipd_interpolate_3d(npts,ecs,ncs,zcs,zns,sourceval,mpts,ect,nct,zct,znt,
+    #   targval,transtype,ahmax,ahmin,avert,bearing,dip,rake,invpow)
+    lib.ipd_interpolate_3d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zcs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), out
+        POINTER(c_int),  # transtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # invpow(mpts), in
+    )
+    lib.ipd_interpolate_3d.restype = c_int
+
+    # initialize_randgen(iseed)
+    lib.initialize_randgen.argtypes = (POINTER(c_int),)  # iseed, in
+    lib.initialize_randgen.restype = c_int
+
+    # fieldgen2d_sva(
+    #   nnode,ec,nc,area,active,mean,var,aa,anis,bearing,
+    #   transtype,avetype,power,ldrand,nreal,randfield)
+    lib.fieldgen2d_sva.argtypes = (
+        POINTER(c_int),  # nnode, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # area(nnode), in
+        ndpointer(c_int, ndim=1, flags="F"),  # active(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # mean(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # var(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(nnode), in
+        POINTER(c_int),  # transtype, in
+        POINTER(c_int),  # avetype, in
+        POINTER(c_double),  # power, in
+        POINTER(c_int),  # ldrand, in
+        POINTER(c_int),  # nreal, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # randfield(ldrand,nreal), out
+    )
+    lib.fieldgen2d_sva.restype = c_int
+
+    # fieldgen3d_sva(
+    #   nnode,ec,nc,zc,area,height,active,mean,var,ahmax,ahmin,avert,
+    #   bearing,dip,rake,transtype,avetype,power,ldrand,nreal,randfield)
+    lib.fieldgen3d_sva.argtypes = (
+        POINTER(c_int),  # nnode, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zc(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # area(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # height(nnode), in
+        ndpointer(c_int, ndim=1, flags="F"),  # active(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # mean(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # var(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(nnode), in
+        POINTER(c_int),  # transtype, in
+        POINTER(c_int),  # avetype, in
+        POINTER(c_double),  # power, in
+        POINTER(c_int),  # ldrand, in
+        POINTER(c_int),  # nreal, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # randfield(ldrand,nreal), out
+    )
+    lib.fieldgen3d_sva.restype = c_int
+
+
+
+
+
+
+
+

Functions

+
+
+def get_char_array(lib: CDLL, name: str) +
+
+

Get c_char Array with a fixed size from dimvar.

+

Parameters

+
+
lib : CDLL
+
Ctypes library instance.
+
name : str
+
Uppercase name of variable in dimvar or other custom name.
+
+
+ +Expand source code + +
def get_char_array(lib: CDLL, name: str):
+    """Get c_char Array with a fixed size from dimvar.
+
+    Parameters
+    ----------
+    lib : CDLL
+        Ctypes library instance.
+    name : str
+        Uppercase name of variable in dimvar or other custom name.
+    """
+    if name in _char_array_cache:
+        return _char_array_cache[name]
+    size = get_dimvar_int(lib, name)
+    array_type = ARRAY(c_char, size)
+    _char_array_cache[name] = array_type
+    return array_type
+
+
+
+def get_dimvar_int(lib: CDLL, name: str) ‑> int +
+
+

Get dimvar constant integer from library instance.

+

Parameters

+
+
lib : CDLL
+
Ctypes library instance.
+
name : str
+
Uppercase name of variable in dimvar or other custom name.
+
+

Returns

+
+
int
+
 
+
+

Raises

+
+
ValueError
+
If name is not defined in lib object.
+
+
+ +Expand source code + +
def get_dimvar_int(lib: CDLL, name: str) -> int:
+    """Get dimvar constant integer from library instance.
+
+    Parameters
+    ----------
+    lib : CDLL
+        Ctypes library instance.
+    name : str
+        Uppercase name of variable in dimvar or other custom name.
+
+    Returns
+    -------
+    int
+
+    Raises
+    ------
+    ValueError
+        If name is not defined in lib object.
+    """
+    if name in _dimvar_cache:
+        return _dimvar_cache[name]
+    elif name in _misc_lengths:
+        # Special consideration for constants not specified by dimvar
+        return _misc_lengths[name]
+    c_var = c_int.in_dll(lib, name)
+    _dimvar_cache[name] = c_var.value
+    return c_var.value
+
+
+
+def prototype(lib) ‑> None +
+
+

Add ctypes prototypes for each function in pestutils.

+

Parameters

+
+
lib : CDLL
+
Ctypes library instance, which is modified in-place
+
+
+ +Expand source code + +
def prototype(lib) -> None:
+    """Add ctypes prototypes for each function in pestutils.
+
+    Parameters
+    ----------
+    lib : CDLL
+        Ctypes library instance, which is modified in-place
+    """
+    # Generate c_char Array types based on dimvar sizes
+    filename_t = get_char_array(lib, "LENFILENAME")
+    message_t = get_char_array(lib, "LENMESSAGE")
+    gridname_t = get_char_array(lib, "LENGRIDNAME")
+    vartype_t = get_char_array(lib, "LENVARTYPE")
+    flowtype_t = get_char_array(lib, "LENFLOWTYPE")
+
+    # inquire_modflow_binary_file_specs(
+    #   filein,fileout,isim,itype,iprec,narray,ntime)
+    lib.inquire_modflow_binary_file_specs.argtypes = (
+        POINTER(filename_t),  # filein, in
+        POINTER(filename_t),  # fileout, in
+        POINTER(c_int),  # isim, in
+        POINTER(c_int),  # itype, in
+        POINTER(c_int),  # iprec, out
+        POINTER(c_int),  # narray, out
+        POINTER(c_int),  # ntime, out
+    )
+    lib.inquire_modflow_binary_file_specs.restype = c_int
+
+    # retrieve_error_message(errormessage)
+    lib.retrieve_error_message.argtypes = (POINTER(message_t),)  # errormessage, out
+    lib.retrieve_error_message.restype = c_int
+
+    # install_structured_grid(
+    #   gridname,ncol,nrow,nlay,icorner,e0,n0,rotation,delr,delc)
+    lib.install_structured_grid.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # ncol, in
+        POINTER(c_int),  # nrow, in
+        POINTER(c_int),  # nlay, in
+        POINTER(c_int),  # icorner, in
+        POINTER(c_double),  # e0, in
+        POINTER(c_double),  # n0, in
+        POINTER(c_double),  # rotation, in
+        ndpointer(c_double, ndim=1, flags="F"),  # delr(ncol), in
+        ndpointer(c_double, ndim=1, flags="F"),  # delc(nrow), in
+    )
+    lib.install_structured_grid.restype = c_int
+
+    # get_cell_centres_structured(gridname,ncpl,cellx,celly)
+    lib.get_cell_centres_structured.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # ncpl, in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # cellx(ncells), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # celly(ncells), out
+    )
+    lib.get_cell_centres_structured.restype = c_int
+
+    # uninstall_structured_grid(gridname)
+    lib.uninstall_structured_grid.argtypes = (POINTER(gridname_t),)  # gridname, in
+    lib.uninstall_structured_grid.restype = c_int
+
+    # free_all_memory()
+    lib.free_all_memory.argtypes = ()
+    lib.free_all_memory.restype = c_int
+
+    # interp_from_structured_grid(
+    #   gridname,depvarfile,isim,iprec,ntime,vartype,interpthresh,nointerpval,
+    #   npts,ecoord,ncoord,layer,nproctime,simtime,simstate)
+    lib.interp_from_structured_grid.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(filename_t),  # depvarfile, in
+        POINTER(c_int),  # isim, in
+        POINTER(c_int),  # iprec, in
+        POINTER(c_int),  # ntime, in
+        POINTER(vartype_t),  # vartype, in
+        POINTER(c_double),  # interpthresh, in
+        POINTER(c_double),  # nointerpval, in
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecoord, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncoord, in
+        ndpointer(c_int, ndim=1, flags="F"),  # layer, in
+        POINTER(c_int),  # nproctime, out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # simtime(ntime), out
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # simstate(ntime,npts), out
+    )
+    lib.interp_from_structured_grid.restype = c_int
+
+    # interp_to_obstime(
+    #   nsimtime,nproctime,npts,simtime,simval,interpthresh,how_extrap,
+    #   time_extrap,nointerpval,nobs,obspoint,obstime,obssimval)
+    lib.interp_to_obstime.argtypes = (
+        POINTER(c_int),  # nsimtime, in
+        POINTER(c_int),  # nproctime, in
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # simtime(nsimtime), in
+        ndpointer(c_double, ndim=2, flags="F"),  # simval(nsimtime,npts), in
+        POINTER(c_double),  # interpthresh, in
+        POINTER(c_char),  # how_extrap, in
+        POINTER(c_double),  # time_extrap, in
+        POINTER(c_double),  # nointerpval, in
+        POINTER(c_int),  # nobs, in
+        ndpointer(c_int, ndim=1, flags="F"),  # obspoint(nobs), in
+        ndpointer(c_double, ndim=1, flags="F"),  # obstime(nobs), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # obssimval(nobs), out
+    )
+    lib.interp_to_obstime.restype = c_int
+
+    # install_mf6_grid_from_file(
+    #   gridname,grbfile,idis,ncells,ndim1,ndim2,ndim3)
+    lib.install_mf6_grid_from_file.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(filename_t),  # grbfile, in
+        POINTER(c_int),  # idis, out
+        POINTER(c_int),  # ncells, out
+        POINTER(c_int),  # ndim1, out
+        POINTER(c_int),  # ndim2, out
+        POINTER(c_int),  # ndim3, out
+    )
+    lib.install_mf6_grid_from_file.restype = c_int
+
+    # get_cell_centres_mf6(gridname,ncells,cellx,celly,cellz)
+    lib.get_cell_centres_mf6.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # ncells, in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # cellx(ncells), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # celly(ncells), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # cellz(ncells), out
+    )
+    lib.get_cell_centres_mf6.restype = c_int
+
+    # uninstall_mf6_grid(gridname)
+    lib.uninstall_mf6_grid.argtypes = (POINTER(gridname_t),)  # gridname, in
+    lib.uninstall_mf6_grid.restype = c_int
+
+    # calc_mf6_interp_factors(
+    #   gridname,npts,ecoord,ncoord,layer,factorfile,
+    #   factorfiletype,blnfile,interp_success)
+    lib.calc_mf6_interp_factors.argtypes = (
+        POINTER(gridname_t),  # gridname, in
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecoord, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncoord, in
+        ndpointer(c_int, ndim=1, flags="F"),  # layer, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(filename_t),  # blnfile, in
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # interp_success, out
+    )
+    lib.calc_mf6_interp_factors.restype = c_int
+
+    # interp_from_mf6_depvar_file(
+    #   depvarfile,factorfile,factorfiletype,ntime,vartype,interpthresh,
+    #   reapportion,nointerpval,npts,nproctime,simtime,simstate)
+    lib.interp_from_mf6_depvar_file.argtypes = (
+        POINTER(filename_t),  # depvarfile, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # ntime, in
+        POINTER(vartype_t),  # vartype(17), in
+        POINTER(c_double),  # interpthresh, in
+        POINTER(c_int),  # reapportion, in
+        POINTER(c_double),  # nointerpval, in
+        POINTER(c_int),  # npts, in
+        POINTER(c_int),  # nproctime, out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # simtime(ntime), out
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # simstate(ntime,npts), out
+    )
+    lib.interp_from_mf6_depvar_file.restype = c_int
+
+    # extract_flows_from_cbc_file(
+    #   cbcfile,flowtype,isim,iprec,ncell,izone,nzone,numzone,zonenumber,
+    #   ntime,nproctime,timestep,stressperiod,simtime,simflow)
+    lib.extract_flows_from_cbc_file.argtypes = (
+        POINTER(filename_t),  # cbcfile, in
+        POINTER(flowtype_t),  # flowtype, in
+        POINTER(c_int),  # isim, in
+        POINTER(c_int),  # iprec, in
+        POINTER(c_int),  # ncell, in
+        ndpointer(c_int, ndim=1, flags="F"),  # izone(ncell), in
+        POINTER(c_int),  # nzone, in
+        POINTER(c_int),  # numzone, out
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # zonenumber(nzone), out
+        POINTER(c_int),  # ntime, in
+        POINTER(c_int),  # nproctime, out
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # timestep(ntime), out
+        ndpointer(c_int, ndim=1, flags=("F", "W")),  # stressperiod(ntime), out
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # simtime(ntime), out
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # simflow(ntime,nzone), out
+    )
+    lib.extract_flows_from_cbc_file.restype = c_int
+
+    # calc_kriging_factors_2d(
+    #   npts,ecs,ncs,zns,mpts,ect,nct,znt,vartype,krigtype,aa,anis,bearing,
+    #   searchrad,maxpts,minpts,factorfile,factorfiletype,icount_interp)
+    lib.calc_kriging_factors_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        POINTER(c_int),  # vartype, in
+        POINTER(c_int),  # krigtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        POINTER(c_double),  # searchrad, in
+        POINTER(c_int),  # maxpts, in
+        POINTER(c_int),  # minpts, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_kriging_factors_2d.restype = c_int
+
+    # calc_kriging_factors_auto_2d(
+    #   npts,ecs,ncs,zns,mpts,ect,nct,znt,krigtype,anis,bearing,
+    #   factorfile,factorfiletype,icount_interp)
+    lib.calc_kriging_factors_auto_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        POINTER(c_int),  # krigtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_kriging_factors_auto_2d.restype = c_int
+
+    # calc_kriging_factors_3d(
+    #   npts,ecs,ncs,zcs,zns,mpts,ect,nct,zct,znt,krigtype,nzone,zonenum,
+    #   vartype,ahmax,ahmin,avert,bearing,dip,rake,srhmax,srhmin,srvert,
+    #   maxpts,minpts,factorfile,factorfiletype,icount_interp)
+    lib.calc_kriging_factors_3d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zcs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        POINTER(c_int),  # krigtype, in
+        POINTER(c_int),  # nzone, in
+        ndpointer(c_int, ndim=1, flags="F"),  # zonenum(nzone), in
+        ndpointer(c_int, ndim=1, flags="F"),  # vartype(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(nzone), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(nzone), in
+        POINTER(c_double),  # srhmax, in
+        POINTER(c_double),  # srhmin, in
+        POINTER(c_double),  # srvert, in
+        POINTER(c_int),  # maxpts, in
+        POINTER(c_int),  # minpts, in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_kriging_factors_3d.restype = c_int
+
+    # krige_using_file(
+    #   factorfile,factorfiletype,npts,mpts,krigtype,transtype,
+    #   sourceval,targval,icount_interp,meanval)
+    lib.krige_using_file.argtypes = (
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # npts, in
+        POINTER(c_int),  # mpts, in
+        POINTER(c_int),  # krigtype, in
+        POINTER(c_int),  # transtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), out
+        POINTER(c_int),  # icount_interp, out
+        ndpointer(c_double, ndim=1, flags="F"),  # meanval(mpts), in, optional
+    )
+    lib.krige_using_file.restype = c_int
+
+    # build_covar_matrix_2d(
+    #   npts,ec,nc,zn,vartype,nugget,aa,sill,anis,bearing,ldcovmat,covmat)
+    lib.build_covar_matrix_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zn(npts), in
+        POINTER(c_int),  # vartype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # nugget(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sill(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(npts), in
+        POINTER(c_int),  # ldcovmat, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # covmat(ldcovmat,npts), out
+    )
+    lib.build_covar_matrix_2d.restype = c_int
+
+    # build_covar_matrix_3d(
+    #   npts,ec,nc,zc,zn,vartype,
+    # nugget,sill,ahmax,ahmin,avert,bearing,dip,rake,ldcovmat,covmat)
+    lib.build_covar_matrix_3d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zc(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zn(npts), in
+        POINTER(c_int),  # vartype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # nugget(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sill(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(npts), in
+        POINTER(c_int),  # ldcovmat, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # covmat(ldcovmat,npts), out
+    )
+    lib.build_covar_matrix_3d.restype = c_int
+
+    # calc_structural_overlay_factors(
+    #   npts,ecs,ncs,ids,conwidth,aa,structype,inverse_power,
+    #   mpts,ect,nct,active,factorfile,factorfiletype,icount_interp)
+    lib.calc_structural_overlay_factors.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # ids(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # conwidth(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(npts), in
+        POINTER(c_int),  # structype, in
+        POINTER(c_double),  # inverse_power, in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # active(mpts), in
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.calc_structural_overlay_factors.restype = c_int
+
+    # interpolate_blend_using_file(
+    #   factorfile,factorfiletype,npts,mpts,transtype,
+    #   lt_target,gt_target,sourceval,targval,icount_interp)
+    lib.interpolate_blend_using_file.argtypes = (
+        POINTER(filename_t),  # factorfile, in
+        POINTER(c_int),  # factorfiletype, in
+        POINTER(c_int),  # npts, in
+        POINTER(c_int),  # mpts, in
+        POINTER(c_int),  # transtype, in
+        POINTER(c_char),  # lt_target, in
+        POINTER(c_char),  # gt_target, in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), inout
+        POINTER(c_int),  # icount_interp, out
+    )
+    lib.interpolate_blend_using_file.restype = c_int
+
+    # ipd_interpolate_2d(npts,ecs,ncs,zns,sourceval,
+    #   mpts,ect,nct,znt,targval,transtype,anis,bearing,invpow)
+    lib.ipd_interpolate_2d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), out
+        POINTER(c_int),  # transtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # invpow(mpts), in
+    )
+    lib.ipd_interpolate_2d.restype = c_int
+
+    # ipd_interpolate_3d(npts,ecs,ncs,zcs,zns,sourceval,mpts,ect,nct,zct,znt,
+    #   targval,transtype,ahmax,ahmin,avert,bearing,dip,rake,invpow)
+    lib.ipd_interpolate_3d.argtypes = (
+        POINTER(c_int),  # npts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ecs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ncs(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zcs(npts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # zns(npts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # sourceval(npts), in
+        POINTER(c_int),  # mpts, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ect(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nct(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zct(mpts), in
+        ndpointer(c_int, ndim=1, flags="F"),  # znt(mpts), in
+        ndpointer(c_double, ndim=1, flags=("F", "W")),  # targval(mpts), out
+        POINTER(c_int),  # transtype, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(mpts), in
+        ndpointer(c_double, ndim=1, flags="F"),  # invpow(mpts), in
+    )
+    lib.ipd_interpolate_3d.restype = c_int
+
+    # initialize_randgen(iseed)
+    lib.initialize_randgen.argtypes = (POINTER(c_int),)  # iseed, in
+    lib.initialize_randgen.restype = c_int
+
+    # fieldgen2d_sva(
+    #   nnode,ec,nc,area,active,mean,var,aa,anis,bearing,
+    #   transtype,avetype,power,ldrand,nreal,randfield)
+    lib.fieldgen2d_sva.argtypes = (
+        POINTER(c_int),  # nnode, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # area(nnode), in
+        ndpointer(c_int, ndim=1, flags="F"),  # active(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # mean(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # var(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # aa(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # anis(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(nnode), in
+        POINTER(c_int),  # transtype, in
+        POINTER(c_int),  # avetype, in
+        POINTER(c_double),  # power, in
+        POINTER(c_int),  # ldrand, in
+        POINTER(c_int),  # nreal, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # randfield(ldrand,nreal), out
+    )
+    lib.fieldgen2d_sva.restype = c_int
+
+    # fieldgen3d_sva(
+    #   nnode,ec,nc,zc,area,height,active,mean,var,ahmax,ahmin,avert,
+    #   bearing,dip,rake,transtype,avetype,power,ldrand,nreal,randfield)
+    lib.fieldgen3d_sva.argtypes = (
+        POINTER(c_int),  # nnode, in
+        ndpointer(c_double, ndim=1, flags="F"),  # ec(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # nc(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # zc(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # area(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # height(nnode), in
+        ndpointer(c_int, ndim=1, flags="F"),  # active(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # mean(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # var(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmax(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # ahmin(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # avert(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # bearing(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # dip(nnode), in
+        ndpointer(c_double, ndim=1, flags="F"),  # rake(nnode), in
+        POINTER(c_int),  # transtype, in
+        POINTER(c_int),  # avetype, in
+        POINTER(c_double),  # power, in
+        POINTER(c_int),  # ldrand, in
+        POINTER(c_int),  # nreal, in
+        ndpointer(c_double, ndim=2, flags=("F", "W")),  # randfield(ldrand,nreal), out
+    )
+    lib.fieldgen3d_sva.restype = c_int
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/data.html b/docs/data.html new file mode 100644 index 0000000..0c4af83 --- /dev/null +++ b/docs/data.html @@ -0,0 +1,758 @@ + + + + + + +pypestutils.data API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.data

+
+
+

Data module.

+
+ +Expand source code + +
"""Data module."""
+from __future__ import annotations
+
+from enum import Enum
+from inspect import isclass
+from typing import Any
+
+import numpy as np
+import numpy.typing as npt
+
+from pypestutils.enum import ParamEnum
+
+__all__ = ["ManyArrays", "validate_scalar"]
+
+
+def validate_scalar(name: str, value: Any, **kwargs) -> None:
+    """Validate scalar value according to supported kwargs.
+
+    Parameters
+    ----------
+    name : str
+        Name of parameter, used for error message.
+    value : any
+        Value of parameter.
+    kwargs : dict
+        Supported validation keywords are: isfinite, gt, ge, lt, le, isin,
+        enum, minlen, maxlen and leneq.
+
+    Raises
+    ------
+    ValueError
+        When value fails validation criteria.
+    TypeError
+        When parameter use is not expected.
+    NotImplementedError
+        When keyword is not recognized.
+    """
+    if not np.isscalar(value):
+        raise TypeError(f"'{name}' is not a scalar value")
+    if "isfinite" in kwargs:
+        if kwargs.pop("isfinite") is not True:
+            raise TypeError("isfinite must be True")
+        if not np.isfinite(value):
+            raise ValueError(f"'{name}' must be finite (was {value!r})")
+    if "gt" in kwargs:
+        gt = kwargs.pop("gt")
+        if not (value > gt):
+            raise ValueError(f"'{name}' must be greater than {gt} (was {value!r})")
+    if "ge" in kwargs:
+        ge = kwargs.pop("ge")
+        if not (value >= ge):
+            raise ValueError(
+                f"'{name}' must be greater than or equal to {ge} (was {value!r})"
+            )
+    if "lt" in kwargs:
+        lt = kwargs.pop("lt")
+        if not (value < lt):
+            raise ValueError(f"'{name}' must be less than {lt} (was {value!r})")
+    if "le" in kwargs:
+        le = kwargs.pop("le")
+        if not (value <= le):
+            raise ValueError(
+                f"'{name}' must be less than or equal to {le} (was {value!r})"
+            )
+    if "isin" in kwargs:
+        isin = kwargs.pop("isin")
+        if not np.isin(value, isin):
+            raise ValueError(f"'{name}' must be in {isin} (was {value!r})")
+    if "enum" in kwargs:
+        enum_t = kwargs.pop("enum")
+        if not (isclass(enum_t) and issubclass(enum_t, ParamEnum)):
+            raise TypeError("enum must be a subclass of ParamEnum")
+        elif isinstance(value, Enum) and not isinstance(value, enum_t):
+            raise TypeError(f"'{value!s}' is not an enum {enum_t.__name__}")
+        elif not isinstance(value, int):
+            raise TypeError(f"enum value must be either {enum_t.__name__} or int")
+        valid_options = enum_t.get_valid_options()
+        if not np.isin(value, list(valid_options.keys())):
+            enum_str = ", ".join([f"{v} ({n})" for (v, n) in valid_options.items()])
+            raise ValueError(
+                f"'{name}' must be in enum {enum_t.__name__} {enum_str} (was {value!r})"
+            )
+    if "minlen" in kwargs:
+        valuelen = len(value)
+        minlen = kwargs.pop("minlen")
+        if minlen < 1:
+            raise TypeError("minlen must be 1 or more")
+        elif minlen == 1 and valuelen < 1:  # special case `minlen=1`
+            raise ValueError(f"'{name}' cannot have zero len")
+        elif valuelen < minlen:
+            raise ValueError(f"'{name}' has a min len {minlen} (was {len(value)})")
+    if "maxlen" in kwargs:
+        valuelen = len(value)
+        maxlen = kwargs.pop("maxlen")
+        if valuelen > maxlen:
+            raise ValueError(f"'{name}' has a max len {maxlen} (was {valuelen})")
+    if "leneq" in kwargs:
+        valuelen = len(value)
+        leneq = kwargs.pop("leneq")
+        if valuelen != leneq:
+            raise ValueError(f"'{name}' must have len {leneq} (was {valuelen})")
+    if kwargs:
+        raise NotImplementedError(f"unhandled kwargs {kwargs}")
+
+
+class ManyArrays:
+    """Gather and check arrays and, if needed, fill-out scalars.
+
+    All arrays are 1D with the same shape (or length). Float arrays are always
+    float64, and integer arrays are always int32. All arrays are contiguous.
+    This class is used as a pre-processor input for ctypes.
+
+    Parameters
+    ----------
+    float_arrays : dict of array_like, optional
+        Dict of 1D arrays, assume to have same shape.
+    float_any : dict of array_like or float, optional
+        Dict of float or 1D arrays.
+    int_any : dict of array_like or int, optional
+        Dict of int or 1D arrays.
+    ar_len : int, optional
+        If specified, this is used for the expected array size and shape.
+    """
+
+    shape = ()  # type: tuple | tuple[int]
+    _names = []  # type: list[str]
+
+    def __init__(
+        self,
+        float_arrays: dict[str, npt.ArrayLike] = {},
+        float_any: dict[str, float | npt.ArrayLike] = {},
+        int_any: dict[str, int | npt.ArrayLike] = {},
+        ar_len: int | None = None,
+    ) -> None:
+        self._names = []
+        # find common array size
+        if ar_len is not None:
+            if not isinstance(ar_len, int):
+                raise TypeError("'ar_len' must be int")
+            self.shape = (ar_len,)
+        for name in float_arrays.keys():
+            if name in self._names:
+                raise KeyError(f"'{name}' defined more than once")
+            self._names.append(name)
+            # Each must be 1D and the same shape
+            ar = np.array(float_arrays[name], np.float64, order="F", copy=False)
+            if ar.ndim != 1:
+                raise ValueError(f"expected '{name}' ndim to be 1; found {ar.ndim}")
+            if not self.shape:
+                self.shape = ar.shape
+            elif ar.shape != self.shape:
+                raise ValueError(
+                    f"expected '{name}' shape to be {self.shape}; found {ar.shape}"
+                )
+            setattr(self, name, ar)
+        for name in float_any.keys():
+            if name in self._names:
+                raise KeyError(f"'{name}' defined more than once")
+            self._names.append(name)
+            float_any[name] = ar = np.array(
+                float_any[name], np.float64, order="F", copy=False
+            )
+            if not self.shape and ar.ndim == 1:
+                self.shape = ar.shape
+        for name in int_any.keys():
+            if name in self._names:
+                raise KeyError(f"'{name}' defined more than once")
+            self._names.append(name)
+            int_any[name] = ar = np.array(int_any[name], order="F", copy=False)
+            if not self.shape and ar.ndim == 1:
+                self.shape = ar.shape
+        if not self.shape:
+            self.shape = (1,)  # if all scalars, assume this size
+        for name in float_any.keys():
+            ar = float_any[name]
+            if ar.ndim == 0:
+                ar = np.full(self.shape, ar)
+            elif ar.ndim != 1:
+                raise ValueError(f"expected '{name}' ndim to be 1; found {ar.ndim}")
+            elif ar.shape != self.shape:
+                raise ValueError(
+                    f"expected '{name}' shape to be {self.shape}; found {ar.shape}"
+                )
+            setattr(self, name, ar)
+        for name in int_any.keys():
+            ar = int_any[name]
+            if ar.ndim == 0:
+                ar = np.full(self.shape, ar)
+            elif ar.ndim != 1:
+                raise ValueError(f"expected '{name}' ndim to be 1; found {ar.ndim}")
+            elif ar.shape != self.shape:
+                raise ValueError(
+                    f"expected '{name}' shape to be {self.shape}; found {ar.shape}"
+                )
+            if not np.issubdtype(ar.dtype, np.integer):
+                raise ValueError(
+                    f"expected '{name}' to be integer type; found {ar.dtype}"
+                )
+            setattr(self, name, ar.astype(np.int32, copy=False))
+
+    def __len__(self) -> int:
+        """Return length of dimension from shape[0]."""
+        return self.shape[0]
+
+    def validate(self, name, **kwargs) -> None:
+        """Validate array values.
+
+        Parameters
+        ----------
+        name : str
+            Name of parameter.
+        kwargs : dict
+            Supported validation keywords are: isfinite, gt, ge, lt, le, isin
+            and enum.
+
+        Raises
+        ------
+        ValueError
+            When 1 or more array elements fail validation criteria.
+        TypeError
+            When parameter use is not expected.
+        NotImplementedError
+            When keyword is not recognized.
+        """
+        if name not in self._names:
+            raise KeyError(f"'{name}' not found")
+        ar = getattr(self, name)
+        dtp = ar.dtype
+        typ = dtp.type
+        if "isfinite" in kwargs:
+            if kwargs.pop("isfinite") is not True:
+                raise TypeError("isfinite must be True")
+            if not np.isfinite(ar).all():
+                raise ValueError(f"'{name}' must be finite")
+        if "gt" in kwargs:
+            gt = typ(kwargs.pop("gt"))
+            if not (ar > gt).all():
+                raise ValueError(f"'{name}' must be greater than {gt}")
+        if "ge" in kwargs:
+            ge = typ(kwargs.pop("ge"))
+            if not (ar >= ge).all():
+                raise ValueError(f"'{name}' must be greater than or equal to {ge}")
+        if "lt" in kwargs:
+            lt = typ(kwargs.pop("lt"))
+            if not (ar < lt).all():
+                raise ValueError(f"'{name}' must be less than {lt}")
+        if "le" in kwargs:
+            le = typ(kwargs.pop("le"))
+            if not (ar <= le).all():
+                raise ValueError(f"'{name}' must be less than or equal to {le}")
+        if "isin" in kwargs:
+            isin = kwargs.pop("isin")
+            if not np.isin(ar, isin).all():
+                raise ValueError(f"'{name}' must be in {isin}")
+        if "enum" in kwargs:
+            enum_t = kwargs.pop("enum")
+            if not (isclass(enum_t) and issubclass(enum_t, ParamEnum)):
+                raise TypeError("enum must be a subclass of ParamEnum")
+            elif not np.issubdtype(dtp, np.integer):
+                raise TypeError(f"'{name}' values must be integer type")
+            valid_options = enum_t.get_valid_options()
+            if not np.isin(ar, list(valid_options.keys())).all():
+                enum_str = ", ".join([f"{v} ({n})" for (v, n) in valid_options.items()])
+                raise ValueError(
+                    f"'{name}' must be in enum {enum_t.__name__} {enum_str}"
+                )
+        if kwargs:
+            raise NotImplementedError(f"unhandled kwargs {kwargs}")
+
+
+
+
+
+
+
+

Functions

+
+
+def validate_scalar(name: str, value: Any, **kwargs) ‑> None +
+
+

Validate scalar value according to supported kwargs.

+

Parameters

+
+
name : str
+
Name of parameter, used for error message.
+
value : any
+
Value of parameter.
+
kwargs : dict
+
Supported validation keywords are: isfinite, gt, ge, lt, le, isin, +enum, minlen, maxlen and leneq.
+
+

Raises

+
+
ValueError
+
When value fails validation criteria.
+
TypeError
+
When parameter use is not expected.
+
NotImplementedError
+
When keyword is not recognized.
+
+
+ +Expand source code + +
def validate_scalar(name: str, value: Any, **kwargs) -> None:
+    """Validate scalar value according to supported kwargs.
+
+    Parameters
+    ----------
+    name : str
+        Name of parameter, used for error message.
+    value : any
+        Value of parameter.
+    kwargs : dict
+        Supported validation keywords are: isfinite, gt, ge, lt, le, isin,
+        enum, minlen, maxlen and leneq.
+
+    Raises
+    ------
+    ValueError
+        When value fails validation criteria.
+    TypeError
+        When parameter use is not expected.
+    NotImplementedError
+        When keyword is not recognized.
+    """
+    if not np.isscalar(value):
+        raise TypeError(f"'{name}' is not a scalar value")
+    if "isfinite" in kwargs:
+        if kwargs.pop("isfinite") is not True:
+            raise TypeError("isfinite must be True")
+        if not np.isfinite(value):
+            raise ValueError(f"'{name}' must be finite (was {value!r})")
+    if "gt" in kwargs:
+        gt = kwargs.pop("gt")
+        if not (value > gt):
+            raise ValueError(f"'{name}' must be greater than {gt} (was {value!r})")
+    if "ge" in kwargs:
+        ge = kwargs.pop("ge")
+        if not (value >= ge):
+            raise ValueError(
+                f"'{name}' must be greater than or equal to {ge} (was {value!r})"
+            )
+    if "lt" in kwargs:
+        lt = kwargs.pop("lt")
+        if not (value < lt):
+            raise ValueError(f"'{name}' must be less than {lt} (was {value!r})")
+    if "le" in kwargs:
+        le = kwargs.pop("le")
+        if not (value <= le):
+            raise ValueError(
+                f"'{name}' must be less than or equal to {le} (was {value!r})"
+            )
+    if "isin" in kwargs:
+        isin = kwargs.pop("isin")
+        if not np.isin(value, isin):
+            raise ValueError(f"'{name}' must be in {isin} (was {value!r})")
+    if "enum" in kwargs:
+        enum_t = kwargs.pop("enum")
+        if not (isclass(enum_t) and issubclass(enum_t, ParamEnum)):
+            raise TypeError("enum must be a subclass of ParamEnum")
+        elif isinstance(value, Enum) and not isinstance(value, enum_t):
+            raise TypeError(f"'{value!s}' is not an enum {enum_t.__name__}")
+        elif not isinstance(value, int):
+            raise TypeError(f"enum value must be either {enum_t.__name__} or int")
+        valid_options = enum_t.get_valid_options()
+        if not np.isin(value, list(valid_options.keys())):
+            enum_str = ", ".join([f"{v} ({n})" for (v, n) in valid_options.items()])
+            raise ValueError(
+                f"'{name}' must be in enum {enum_t.__name__} {enum_str} (was {value!r})"
+            )
+    if "minlen" in kwargs:
+        valuelen = len(value)
+        minlen = kwargs.pop("minlen")
+        if minlen < 1:
+            raise TypeError("minlen must be 1 or more")
+        elif minlen == 1 and valuelen < 1:  # special case `minlen=1`
+            raise ValueError(f"'{name}' cannot have zero len")
+        elif valuelen < minlen:
+            raise ValueError(f"'{name}' has a min len {minlen} (was {len(value)})")
+    if "maxlen" in kwargs:
+        valuelen = len(value)
+        maxlen = kwargs.pop("maxlen")
+        if valuelen > maxlen:
+            raise ValueError(f"'{name}' has a max len {maxlen} (was {valuelen})")
+    if "leneq" in kwargs:
+        valuelen = len(value)
+        leneq = kwargs.pop("leneq")
+        if valuelen != leneq:
+            raise ValueError(f"'{name}' must have len {leneq} (was {valuelen})")
+    if kwargs:
+        raise NotImplementedError(f"unhandled kwargs {kwargs}")
+
+
+
+
+
+

Classes

+
+
+class ManyArrays +(float_arrays: dict[str, npt.ArrayLike] = {}, float_any: dict[str, float | npt.ArrayLike] = {}, int_any: dict[str, int | npt.ArrayLike] = {}, ar_len: int | None = None) +
+
+

Gather and check arrays and, if needed, fill-out scalars.

+

All arrays are 1D with the same shape (or length). Float arrays are always +float64, and integer arrays are always int32. All arrays are contiguous. +This class is used as a pre-processor input for ctypes.

+

Parameters

+
+
float_arrays : dict of array_like, optional
+
Dict of 1D arrays, assume to have same shape.
+
float_any : dict of array_like or float, optional
+
Dict of float or 1D arrays.
+
int_any : dict of array_like or int, optional
+
Dict of int or 1D arrays.
+
ar_len : int, optional
+
If specified, this is used for the expected array size and shape.
+
+
+ +Expand source code + +
class ManyArrays:
+    """Gather and check arrays and, if needed, fill-out scalars.
+
+    All arrays are 1D with the same shape (or length). Float arrays are always
+    float64, and integer arrays are always int32. All arrays are contiguous.
+    This class is used as a pre-processor input for ctypes.
+
+    Parameters
+    ----------
+    float_arrays : dict of array_like, optional
+        Dict of 1D arrays, assume to have same shape.
+    float_any : dict of array_like or float, optional
+        Dict of float or 1D arrays.
+    int_any : dict of array_like or int, optional
+        Dict of int or 1D arrays.
+    ar_len : int, optional
+        If specified, this is used for the expected array size and shape.
+    """
+
+    shape = ()  # type: tuple | tuple[int]
+    _names = []  # type: list[str]
+
+    def __init__(
+        self,
+        float_arrays: dict[str, npt.ArrayLike] = {},
+        float_any: dict[str, float | npt.ArrayLike] = {},
+        int_any: dict[str, int | npt.ArrayLike] = {},
+        ar_len: int | None = None,
+    ) -> None:
+        self._names = []
+        # find common array size
+        if ar_len is not None:
+            if not isinstance(ar_len, int):
+                raise TypeError("'ar_len' must be int")
+            self.shape = (ar_len,)
+        for name in float_arrays.keys():
+            if name in self._names:
+                raise KeyError(f"'{name}' defined more than once")
+            self._names.append(name)
+            # Each must be 1D and the same shape
+            ar = np.array(float_arrays[name], np.float64, order="F", copy=False)
+            if ar.ndim != 1:
+                raise ValueError(f"expected '{name}' ndim to be 1; found {ar.ndim}")
+            if not self.shape:
+                self.shape = ar.shape
+            elif ar.shape != self.shape:
+                raise ValueError(
+                    f"expected '{name}' shape to be {self.shape}; found {ar.shape}"
+                )
+            setattr(self, name, ar)
+        for name in float_any.keys():
+            if name in self._names:
+                raise KeyError(f"'{name}' defined more than once")
+            self._names.append(name)
+            float_any[name] = ar = np.array(
+                float_any[name], np.float64, order="F", copy=False
+            )
+            if not self.shape and ar.ndim == 1:
+                self.shape = ar.shape
+        for name in int_any.keys():
+            if name in self._names:
+                raise KeyError(f"'{name}' defined more than once")
+            self._names.append(name)
+            int_any[name] = ar = np.array(int_any[name], order="F", copy=False)
+            if not self.shape and ar.ndim == 1:
+                self.shape = ar.shape
+        if not self.shape:
+            self.shape = (1,)  # if all scalars, assume this size
+        for name in float_any.keys():
+            ar = float_any[name]
+            if ar.ndim == 0:
+                ar = np.full(self.shape, ar)
+            elif ar.ndim != 1:
+                raise ValueError(f"expected '{name}' ndim to be 1; found {ar.ndim}")
+            elif ar.shape != self.shape:
+                raise ValueError(
+                    f"expected '{name}' shape to be {self.shape}; found {ar.shape}"
+                )
+            setattr(self, name, ar)
+        for name in int_any.keys():
+            ar = int_any[name]
+            if ar.ndim == 0:
+                ar = np.full(self.shape, ar)
+            elif ar.ndim != 1:
+                raise ValueError(f"expected '{name}' ndim to be 1; found {ar.ndim}")
+            elif ar.shape != self.shape:
+                raise ValueError(
+                    f"expected '{name}' shape to be {self.shape}; found {ar.shape}"
+                )
+            if not np.issubdtype(ar.dtype, np.integer):
+                raise ValueError(
+                    f"expected '{name}' to be integer type; found {ar.dtype}"
+                )
+            setattr(self, name, ar.astype(np.int32, copy=False))
+
+    def __len__(self) -> int:
+        """Return length of dimension from shape[0]."""
+        return self.shape[0]
+
+    def validate(self, name, **kwargs) -> None:
+        """Validate array values.
+
+        Parameters
+        ----------
+        name : str
+            Name of parameter.
+        kwargs : dict
+            Supported validation keywords are: isfinite, gt, ge, lt, le, isin
+            and enum.
+
+        Raises
+        ------
+        ValueError
+            When 1 or more array elements fail validation criteria.
+        TypeError
+            When parameter use is not expected.
+        NotImplementedError
+            When keyword is not recognized.
+        """
+        if name not in self._names:
+            raise KeyError(f"'{name}' not found")
+        ar = getattr(self, name)
+        dtp = ar.dtype
+        typ = dtp.type
+        if "isfinite" in kwargs:
+            if kwargs.pop("isfinite") is not True:
+                raise TypeError("isfinite must be True")
+            if not np.isfinite(ar).all():
+                raise ValueError(f"'{name}' must be finite")
+        if "gt" in kwargs:
+            gt = typ(kwargs.pop("gt"))
+            if not (ar > gt).all():
+                raise ValueError(f"'{name}' must be greater than {gt}")
+        if "ge" in kwargs:
+            ge = typ(kwargs.pop("ge"))
+            if not (ar >= ge).all():
+                raise ValueError(f"'{name}' must be greater than or equal to {ge}")
+        if "lt" in kwargs:
+            lt = typ(kwargs.pop("lt"))
+            if not (ar < lt).all():
+                raise ValueError(f"'{name}' must be less than {lt}")
+        if "le" in kwargs:
+            le = typ(kwargs.pop("le"))
+            if not (ar <= le).all():
+                raise ValueError(f"'{name}' must be less than or equal to {le}")
+        if "isin" in kwargs:
+            isin = kwargs.pop("isin")
+            if not np.isin(ar, isin).all():
+                raise ValueError(f"'{name}' must be in {isin}")
+        if "enum" in kwargs:
+            enum_t = kwargs.pop("enum")
+            if not (isclass(enum_t) and issubclass(enum_t, ParamEnum)):
+                raise TypeError("enum must be a subclass of ParamEnum")
+            elif not np.issubdtype(dtp, np.integer):
+                raise TypeError(f"'{name}' values must be integer type")
+            valid_options = enum_t.get_valid_options()
+            if not np.isin(ar, list(valid_options.keys())).all():
+                enum_str = ", ".join([f"{v} ({n})" for (v, n) in valid_options.items()])
+                raise ValueError(
+                    f"'{name}' must be in enum {enum_t.__name__} {enum_str}"
+                )
+        if kwargs:
+            raise NotImplementedError(f"unhandled kwargs {kwargs}")
+
+

Class variables

+
+
var shape
+
+
+
+
+

Methods

+
+
+def validate(self, name, **kwargs) ‑> None +
+
+

Validate array values.

+

Parameters

+
+
name : str
+
Name of parameter.
+
kwargs : dict
+
Supported validation keywords are: isfinite, gt, ge, lt, le, isin +and enum.
+
+

Raises

+
+
ValueError
+
When 1 or more array elements fail validation criteria.
+
TypeError
+
When parameter use is not expected.
+
NotImplementedError
+
When keyword is not recognized.
+
+
+ +Expand source code + +
def validate(self, name, **kwargs) -> None:
+    """Validate array values.
+
+    Parameters
+    ----------
+    name : str
+        Name of parameter.
+    kwargs : dict
+        Supported validation keywords are: isfinite, gt, ge, lt, le, isin
+        and enum.
+
+    Raises
+    ------
+    ValueError
+        When 1 or more array elements fail validation criteria.
+    TypeError
+        When parameter use is not expected.
+    NotImplementedError
+        When keyword is not recognized.
+    """
+    if name not in self._names:
+        raise KeyError(f"'{name}' not found")
+    ar = getattr(self, name)
+    dtp = ar.dtype
+    typ = dtp.type
+    if "isfinite" in kwargs:
+        if kwargs.pop("isfinite") is not True:
+            raise TypeError("isfinite must be True")
+        if not np.isfinite(ar).all():
+            raise ValueError(f"'{name}' must be finite")
+    if "gt" in kwargs:
+        gt = typ(kwargs.pop("gt"))
+        if not (ar > gt).all():
+            raise ValueError(f"'{name}' must be greater than {gt}")
+    if "ge" in kwargs:
+        ge = typ(kwargs.pop("ge"))
+        if not (ar >= ge).all():
+            raise ValueError(f"'{name}' must be greater than or equal to {ge}")
+    if "lt" in kwargs:
+        lt = typ(kwargs.pop("lt"))
+        if not (ar < lt).all():
+            raise ValueError(f"'{name}' must be less than {lt}")
+    if "le" in kwargs:
+        le = typ(kwargs.pop("le"))
+        if not (ar <= le).all():
+            raise ValueError(f"'{name}' must be less than or equal to {le}")
+    if "isin" in kwargs:
+        isin = kwargs.pop("isin")
+        if not np.isin(ar, isin).all():
+            raise ValueError(f"'{name}' must be in {isin}")
+    if "enum" in kwargs:
+        enum_t = kwargs.pop("enum")
+        if not (isclass(enum_t) and issubclass(enum_t, ParamEnum)):
+            raise TypeError("enum must be a subclass of ParamEnum")
+        elif not np.issubdtype(dtp, np.integer):
+            raise TypeError(f"'{name}' values must be integer type")
+        valid_options = enum_t.get_valid_options()
+        if not np.isin(ar, list(valid_options.keys())).all():
+            enum_str = ", ".join([f"{v} ({n})" for (v, n) in valid_options.items()])
+            raise ValueError(
+                f"'{name}' must be in enum {enum_t.__name__} {enum_str}"
+            )
+    if kwargs:
+        raise NotImplementedError(f"unhandled kwargs {kwargs}")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/enum.html b/docs/enum.html new file mode 100644 index 0000000..d5ef904 --- /dev/null +++ b/docs/enum.html @@ -0,0 +1,584 @@ + + + + + + +pypestutils.enum API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.enum

+
+
+

Enumeration module.

+
+ +Expand source code + +
"""Enumeration module."""
+from __future__ import annotations
+
+from enum import IntEnum
+
+
+class ParamEnum(IntEnum):
+    """Wraps IntEnum to provide validation of a requested item.
+
+    Intended for enums used for function parameters.
+
+    Use enum.get_value(item) for this behavior instead of builtin enum[item].
+    """
+
+    @classmethod
+    def get_valid_options(cls) -> dict[int, str]:
+        """Get valid options as a dict."""
+        return dict((e.value, e.name) for e in cls)
+
+    @classmethod
+    def get_value(cls, item: str) -> int:
+        """Get integer value from str key.
+
+        Validate item and raise a ValueError with valid options.
+        """
+        try:
+            return cls[item].value
+        except KeyError:
+            valid_options_list = [
+                f"'{n}' ({v})" for (v, n) in cls.get_valid_options().items()
+            ]
+            raise ValueError(
+                "{}: '{}' is not a valid option, must be one of {}".format(
+                    cls.__name__, item, ", ".join(valid_options_list)
+                )
+            )
+
+
+class Prec(ParamEnum):
+    """Floating precision type, where 1 is single and 2 is double precision."""
+
+    single = 1
+    double = 2
+
+
+class KrigType(ParamEnum):
+    """Kriging type, where 0 is simple and 1 is ordinary."""
+
+    simple = 0
+    ordinary = 1
+
+
+class VarioType(ParamEnum):
+    """Variogram type, where 1:spher, 2:exp, 3:gauss and 4:pow."""
+
+    spher = 1
+    exp = 2
+    gauss = 3
+    pow = 4
+
+
+class FactorFileType(ParamEnum):
+    """Factor file type, where 0 is binary and 1 is text."""
+
+    binary = 0
+    text = 1
+
+
+class StrucType(ParamEnum):
+    """Structure type, where 0 is for polylinear and 1 for polygonal."""
+
+    polylinear = 0
+    polygonal = 1
+
+
+class TransType(ParamEnum):
+    """Enable log-transformation of values."""
+
+    none = 0
+    log = 1
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class FactorFileType +(*args, **kwds) +
+
+

Factor file type, where 0 is binary and 1 is text.

+
+ +Expand source code + +
class FactorFileType(ParamEnum):
+    """Factor file type, where 0 is binary and 1 is text."""
+
+    binary = 0
+    text = 1
+
+

Ancestors

+
    +
  • ParamEnum
  • +
  • enum.IntEnum
  • +
  • builtins.int
  • +
  • enum.ReprEnum
  • +
  • enum.Enum
  • +
+

Class variables

+
+
var binary
+
+
+
+
var text
+
+
+
+
+

Inherited members

+ +
+
+class KrigType +(*args, **kwds) +
+
+

Kriging type, where 0 is simple and 1 is ordinary.

+
+ +Expand source code + +
class KrigType(ParamEnum):
+    """Kriging type, where 0 is simple and 1 is ordinary."""
+
+    simple = 0
+    ordinary = 1
+
+

Ancestors

+
    +
  • ParamEnum
  • +
  • enum.IntEnum
  • +
  • builtins.int
  • +
  • enum.ReprEnum
  • +
  • enum.Enum
  • +
+

Class variables

+
+
var ordinary
+
+
+
+
var simple
+
+
+
+
+

Inherited members

+ +
+
+class ParamEnum +(*args, **kwds) +
+
+

Wraps IntEnum to provide validation of a requested item.

+

Intended for enums used for function parameters.

+

Use enum.get_value(item) for this behavior instead of builtin enum[item].

+
+ +Expand source code + +
class ParamEnum(IntEnum):
+    """Wraps IntEnum to provide validation of a requested item.
+
+    Intended for enums used for function parameters.
+
+    Use enum.get_value(item) for this behavior instead of builtin enum[item].
+    """
+
+    @classmethod
+    def get_valid_options(cls) -> dict[int, str]:
+        """Get valid options as a dict."""
+        return dict((e.value, e.name) for e in cls)
+
+    @classmethod
+    def get_value(cls, item: str) -> int:
+        """Get integer value from str key.
+
+        Validate item and raise a ValueError with valid options.
+        """
+        try:
+            return cls[item].value
+        except KeyError:
+            valid_options_list = [
+                f"'{n}' ({v})" for (v, n) in cls.get_valid_options().items()
+            ]
+            raise ValueError(
+                "{}: '{}' is not a valid option, must be one of {}".format(
+                    cls.__name__, item, ", ".join(valid_options_list)
+                )
+            )
+
+

Ancestors

+
    +
  • enum.IntEnum
  • +
  • builtins.int
  • +
  • enum.ReprEnum
  • +
  • enum.Enum
  • +
+

Subclasses

+ +

Static methods

+
+
+def get_valid_options() ‑> dict[int, str] +
+
+

Get valid options as a dict.

+
+ +Expand source code + +
@classmethod
+def get_valid_options(cls) -> dict[int, str]:
+    """Get valid options as a dict."""
+    return dict((e.value, e.name) for e in cls)
+
+
+
+def get_value(item: str) ‑> int +
+
+

Get integer value from str key.

+

Validate item and raise a ValueError with valid options.

+
+ +Expand source code + +
@classmethod
+def get_value(cls, item: str) -> int:
+    """Get integer value from str key.
+
+    Validate item and raise a ValueError with valid options.
+    """
+    try:
+        return cls[item].value
+    except KeyError:
+        valid_options_list = [
+            f"'{n}' ({v})" for (v, n) in cls.get_valid_options().items()
+        ]
+        raise ValueError(
+            "{}: '{}' is not a valid option, must be one of {}".format(
+                cls.__name__, item, ", ".join(valid_options_list)
+            )
+        )
+
+
+
+
+
+class Prec +(*args, **kwds) +
+
+

Floating precision type, where 1 is single and 2 is double precision.

+
+ +Expand source code + +
class Prec(ParamEnum):
+    """Floating precision type, where 1 is single and 2 is double precision."""
+
+    single = 1
+    double = 2
+
+

Ancestors

+
    +
  • ParamEnum
  • +
  • enum.IntEnum
  • +
  • builtins.int
  • +
  • enum.ReprEnum
  • +
  • enum.Enum
  • +
+

Class variables

+
+
var double
+
+
+
+
var single
+
+
+
+
+

Inherited members

+ +
+
+class StrucType +(*args, **kwds) +
+
+

Structure type, where 0 is for polylinear and 1 for polygonal.

+
+ +Expand source code + +
class StrucType(ParamEnum):
+    """Structure type, where 0 is for polylinear and 1 for polygonal."""
+
+    polylinear = 0
+    polygonal = 1
+
+

Ancestors

+
    +
  • ParamEnum
  • +
  • enum.IntEnum
  • +
  • builtins.int
  • +
  • enum.ReprEnum
  • +
  • enum.Enum
  • +
+

Class variables

+
+
var polygonal
+
+
+
+
var polylinear
+
+
+
+
+

Inherited members

+ +
+
+class TransType +(*args, **kwds) +
+
+

Enable log-transformation of values.

+
+ +Expand source code + +
class TransType(ParamEnum):
+    """Enable log-transformation of values."""
+
+    none = 0
+    log = 1
+
+

Ancestors

+
    +
  • ParamEnum
  • +
  • enum.IntEnum
  • +
  • builtins.int
  • +
  • enum.ReprEnum
  • +
  • enum.Enum
  • +
+

Class variables

+
+
var log
+
+
+
+
var none
+
+
+
+
+

Inherited members

+ +
+
+class VarioType +(*args, **kwds) +
+
+

Variogram type, where 1:spher, 2:exp, 3:gauss and 4:pow.

+
+ +Expand source code + +
class VarioType(ParamEnum):
+    """Variogram type, where 1:spher, 2:exp, 3:gauss and 4:pow."""
+
+    spher = 1
+    exp = 2
+    gauss = 3
+    pow = 4
+
+

Ancestors

+
    +
  • ParamEnum
  • +
  • enum.IntEnum
  • +
  • builtins.int
  • +
  • enum.ReprEnum
  • +
  • enum.Enum
  • +
+

Class variables

+
+
var exp
+
+
+
+
var gauss
+
+
+
+
var pow
+
+
+
+
var spher
+
+
+
+
+

Inherited members

+ +
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/finder.html b/docs/finder.html new file mode 100644 index 0000000..9a2fb1b --- /dev/null +++ b/docs/finder.html @@ -0,0 +1,275 @@ + + + + + + +pypestutils.finder API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.finder

+
+
+

Locate pestutils shared library by any means necessary.

+
+ +Expand source code + +
"""Locate pestutils shared library by any means necessary."""
+import ctypes
+import os
+import platform
+from ctypes.util import find_library
+from pathlib import Path
+
+# the current working directory of this file
+pkg_dir = Path(__file__).parent
+
+# generate a bunch of candidate locations where the
+# shared library *might* be hanging out
+_candidates = [
+    os.environ.get("PESTUTILS_LIBRARY", None),
+    str(pkg_dir / "lib"),  # see scripts/build_lib.sh
+    str(pkg_dir.parent / "inst" / ("bin" if os.name == "nt" else "lib")),
+    ".",
+]
+
+
+def load() -> ctypes.CDLL:
+    """Load the pestutils shared library.
+
+    Returns
+    -------
+    ctypes.CDLL
+        Loaded shared library
+    """
+    if os.name == "nt":
+        lib_name = "pestutils.dll"
+
+        # get the current PATH
+        oldenv = os.environ.get("PATH", "").strip().rstrip(";")
+        # run through our list of candidate locations
+        for path in _candidates:
+            if not path or not os.path.exists(path):
+                continue
+            # temporarily add the path to the PATH environment variable
+            # so Windows can find additional DLL dependencies.
+            os.environ["PATH"] = ";".join([path, oldenv])
+            try:
+                rt = ctypes.cdll.LoadLibrary(os.path.join(path, lib_name))
+                if rt is not None:
+                    print("lib found at",path)
+                    return rt
+            except OSError:
+                pass
+            except BaseException as err:
+                print(f"pypestutils.finder unexpected error: {err!s}")
+            finally:
+                os.environ["PATH"] = oldenv
+        raise OSError(f"could not find or load {lib_name}")
+
+    elif os.name == "posix":
+        # posix includes both mac and linux
+        # use the extension for the specific platform
+        if platform.system() == "Darwin":
+            # macos shared libraries are `.dylib`
+            lib_name = "libpestutils.dylib"
+        else:
+            # linux shared libraries are `.so`
+            lib_name = "libpestutils.so"
+
+        # get the starting working directory
+        cwd = os.getcwd()
+        for cand in _candidates:
+            if cand is None:
+                continue
+            elif os.path.isdir(cand):
+                # if our candidate is a directory use best guess
+                path = cand
+                target = os.path.join(cand, lib_name)
+            elif os.path.isfile(cand):
+                # if candidate is just a file use that
+                path = os.path.split(cand)[0]
+                target = cand
+            else:
+                continue
+
+            if not os.path.exists(target):
+                continue
+
+            try:
+                # move to the location we're checking
+                os.chdir(path)
+                # try loading the target file candidate
+                rt = ctypes.cdll.LoadLibrary(target)
+                if rt is not None:
+                    print("lib found at",path)
+                    return rt
+            except BaseException as err:
+                print(f"pypestutils.finder ({target}) unexpected error: {err!s}")
+            finally:
+                os.chdir(cwd)
+
+    try:
+        # try loading library using LD path search
+        path = find_library("libpestutils")
+        if path is not None:
+            print("lib found at",path)
+            return ctypes.cdll.LoadLibrary(path)
+
+    except BaseException:
+        pass
+
+    raise OSError("Could not load pestutils library")
+
+
+
+
+
+
+
+

Functions

+
+
+def load() ‑> ctypes.CDLL +
+
+

Load the pestutils shared library.

+

Returns

+
+
ctypes.CDLL
+
Loaded shared library
+
+
+ +Expand source code + +
def load() -> ctypes.CDLL:
+    """Load the pestutils shared library.
+
+    Returns
+    -------
+    ctypes.CDLL
+        Loaded shared library
+    """
+    if os.name == "nt":
+        lib_name = "pestutils.dll"
+
+        # get the current PATH
+        oldenv = os.environ.get("PATH", "").strip().rstrip(";")
+        # run through our list of candidate locations
+        for path in _candidates:
+            if not path or not os.path.exists(path):
+                continue
+            # temporarily add the path to the PATH environment variable
+            # so Windows can find additional DLL dependencies.
+            os.environ["PATH"] = ";".join([path, oldenv])
+            try:
+                rt = ctypes.cdll.LoadLibrary(os.path.join(path, lib_name))
+                if rt is not None:
+                    print("lib found at",path)
+                    return rt
+            except OSError:
+                pass
+            except BaseException as err:
+                print(f"pypestutils.finder unexpected error: {err!s}")
+            finally:
+                os.environ["PATH"] = oldenv
+        raise OSError(f"could not find or load {lib_name}")
+
+    elif os.name == "posix":
+        # posix includes both mac and linux
+        # use the extension for the specific platform
+        if platform.system() == "Darwin":
+            # macos shared libraries are `.dylib`
+            lib_name = "libpestutils.dylib"
+        else:
+            # linux shared libraries are `.so`
+            lib_name = "libpestutils.so"
+
+        # get the starting working directory
+        cwd = os.getcwd()
+        for cand in _candidates:
+            if cand is None:
+                continue
+            elif os.path.isdir(cand):
+                # if our candidate is a directory use best guess
+                path = cand
+                target = os.path.join(cand, lib_name)
+            elif os.path.isfile(cand):
+                # if candidate is just a file use that
+                path = os.path.split(cand)[0]
+                target = cand
+            else:
+                continue
+
+            if not os.path.exists(target):
+                continue
+
+            try:
+                # move to the location we're checking
+                os.chdir(path)
+                # try loading the target file candidate
+                rt = ctypes.cdll.LoadLibrary(target)
+                if rt is not None:
+                    print("lib found at",path)
+                    return rt
+            except BaseException as err:
+                print(f"pypestutils.finder ({target}) unexpected error: {err!s}")
+            finally:
+                os.chdir(cwd)
+
+    try:
+        # try loading library using LD path search
+        path = find_library("libpestutils")
+        if path is not None:
+            print("lib found at",path)
+            return ctypes.cdll.LoadLibrary(path)
+
+    except BaseException:
+        pass
+
+    raise OSError("Could not load pestutils library")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/helpers.html b/docs/helpers.html new file mode 100644 index 0000000..b158016 --- /dev/null +++ b/docs/helpers.html @@ -0,0 +1,3303 @@ + + + + + + +pypestutils.helpers API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.helpers

+
+
+
+ +Expand source code + +
from __future__ import annotations
+
+import os
+import numpy as np
+import pandas as pd
+
+from .pestutilslib import PestUtilsLib
+
+
+def mod2obs_mf6(gridinfo_fname: str,depvar_fname: str,obscsv_fname: str ,model_type: int,start_datetime: str | pd.TimeStamp,depvar_ftype=1,
+                depvar_name="head",interp_thresh=1.0e+30,no_interp_val=1.0e+30,model_timeunit="d",
+                time_extrap=1.0)->dict:
+
+    """python implementation of mod2smp and mod2obs using modflow6 binary grid files
+    Parameters
+    ----------
+    gridinfo_fname: str
+        grid information file
+    depvar_fname: str
+        MODFLOW-6 output binary file
+    obscsv_fname: str | pd.DataFrame
+        observation information.  Must contain columns "site","x","y","datetime",and "layer"
+    model_type: int
+        type of model.  Must be either 31 (dis mf6) or 32 (disv mf6)
+    start_datetime: str | datetime
+        the simulation start datetime
+    depvar_ftype : int
+        the modflow-6 output file type.  1 for states, 2 or cell-by-cell budgets
+    depvar_name: str
+        the name of the dependent variable in `depvar_fname` to extract (for example "head")
+    interp_thresh: float
+        the upper limit above which extracted values are treated as invalid.  Default is 1.0+30
+    no_interp_val: float
+        value used to fill invalid/null extracted/interpolated values
+    model_time_unit: str
+        pandas style time unit.  Default is "d"ay
+    time_extrap: float
+        length of time units to extrapolate.  Default is 1.0 time unit
+
+    Returns
+    -------
+    all_results: pd.DataFrame
+        all simulated times at observation locations (ie mod2smp)
+    interpolated_results: pd.DataFrame
+        temporally interpolated simulated results at observation locations (ie mod2obs)
+    """
+
+    for fname in [gridinfo_fname,depvar_fname]:
+        assert os.path.exists(fname),"file {0} not found".format(fname)
+    lib = PestUtilsLib()
+    is_mf6 = False
+    is_structured = True
+    model_type = int(model_type)
+    if model_type == 1:
+        is_mf6 = False
+    elif model_type == 21:
+        pass
+    elif model_type == 22:
+        is_structured = False
+    elif model_type == 31:
+        is_mf6 = True
+    elif model_type == 32:
+        is_mf6 = True
+        is_structured = False
+    elif model_type == 33:
+        is_mf6 = True
+        is_structured = False
+    else:
+        raise Exception("unrecognized 'model_type':{0}".format(model_type))
+
+    depvar_ftype = int(depvar_ftype)
+    if depvar_ftype not in [1,2]:
+        raise Exception("unrecognized 'depvar_ftype':{0}".format(depvar_ftype))
+
+    if is_mf6:
+        grid_info = lib.install_mf6_grid_from_file("grid",gridinfo_fname)
+    else:
+        raise NotImplementedError()
+    
+    if isinstance(start_datetime,str):
+        start_datetime = pd.to_datetime(start_datetime)
+
+    depvar_info = lib.inquire_modflow_binary_file_specs(depvar_fname,depvar_fname+".out.csv",model_type,depvar_ftype)    
+    depvar_df = pd.read_csv(depvar_fname+".out.csv")
+    depvar_df.columns = [c.lower() for c in depvar_df.columns]
+    #print(depvar_df)
+
+    if isinstance(obscsv_fname,str):
+        if not os.path.exists(obscsv_fname):
+            raise Exception("obscsv_fname '{0}' not found".format(obscsv_fname))
+        # todo: think about supporting a site sample file maybe?
+        obsdf = pd.read_csv(os.path.join(obscsv_fname),parse_dates=["datetime"])
+    elif isinstance(obscsv_fname,pd.DataFrame):
+        obsdf = obscsv_fname.copy()
+    else:
+        raise Exception("obscsv arg type not recognized (looking for str or pd.DataFrame):'{0}'".format(type(obscsv_fname)))
+    #check obsdf
+    obsdf.columns = [c.lower() for c in obsdf.columns]
+    for req_col in ["site","x","y","datetime","layer"]:
+        if req_col not in obsdf.columns:
+            raise Exception("observation dataframe missing column '{0}'".format(req_col))
+    usitedf = obsdf.groupby("site").first()
+    pth = os.path.split(depvar_fname)[0]
+    fac_file = os.path.join(pth,"obs_interp_fac.bin")
+    bln_file = fac_file.replace(".bin",".bln")
+    interp_fac_results = lib.calc_mf6_interp_factors("grid",usitedf.x.values,usitedf.y.values,usitedf.layer.values,fac_file,"binary",bln_file)
+    if 0 in interp_fac_results:
+        print("warning: the following site(s) failed to have interpolation factors calculated:")
+        fsites = usitedf.site.iloc[interp_fac_results==0].to_list()
+        print(fsites)
+    all_results = lib.interp_from_mf6_depvar_file(depvar_fname,fac_file,"binary",depvar_info["ntime"],"head",interp_thresh,True,
+        no_interp_val,usitedf.shape[0])
+    datetimes = start_datetime+pd.to_timedelta(all_results["simtime"],unit=model_timeunit)
+    allresults_df = pd.DataFrame(all_results["simstate"],index=datetimes,columns=usitedf.index)
+    allresults_df.to_csv(depvar_fname+".all.csv")
+
+    if "totim" in obsdf:
+        print("WARNING: replacing existing 'totim' column in observation dataframe")
+    obsdf.loc[:,"totim"] = obsdf.datetime.apply(lambda x: x  - start_datetime).dt.days 
+
+    usite = obsdf.site.unique()
+    usite.sort()
+    usite_dict = {s:c for s,c in zip(usite,np.arange(usite.shape[0],dtype=int))}
+    obsdf.loc[:,"isite"] = obsdf.site.apply(lambda x: usite_dict[x])
+    obsdf.sort_values(by=["isite","totim"],inplace=True)
+    
+    interp_results = lib.interp_to_obstime(all_results["nproctime"],all_results["simtime"],all_results["simstate"],interp_thresh,"L",
+        time_extrap,no_interp_val,obsdf.isite.values,obsdf.totim.values)
+
+    obsdf.loc[:,"simulated"] = interp_results
+    lib.uninstall_mf6_grid('grid')
+    lib.free_all_memory()
+    return {"all_results":allresults_df,"interpolated_results":obsdf}
+
+def get_grid_info_from_gridspec(gridspec_fname: str) -> dict:
+    """Read structured grid info from a PEST-style grid specificatin file
+    Parameters
+    ----------
+    gridspec_fname : str
+        PEST-style grid specification file
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """
+
+    if not os.path.exists(gridspec_fname):
+        raise FileNotFoundError(gridspec_fname)
+    sr = SpatialReference.from_gridspec(gridspec_fname)
+    return {
+        "x": sr.xcentergrid.flatten(),
+        "y": sr.ycentergrid.flatten(),
+        "area": sr.areagrid.flatten(),
+        "nrow": sr.nrow,
+        "ncol": sr.ncol,
+        "delr": sr.delr,
+        "delc": sr.delc
+    }
+
+
+def get_grid_info_from_mf6_grb(grb_fname: str) -> dict:
+    """Read grid info from a MODFLOW-6 binary grid file
+    Parameters
+    ----------
+    grb_fname: str
+        MODFLOW-6 binary grid file
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """
+    if not os.path.exists(grb_fname):
+        raise FileNotFoundError(grb_fname)
+    lib = PestUtilsLib()
+    data = lib.install_mf6_grid_from_file("grid",grb_fname)
+    data["x"],data["y"],data["z"] = lib.get_cell_centres_mf6("grid",data["ncells"])
+    lib.uninstall_mf6_grid("grid")
+    lib.free_all_memory()
+    return data
+
+def get_2d_grid_info_from_file(fname: str,layer=None) -> dict:
+    """Try to read 2-D grid info from a variety of filename sources
+    Parameters
+    ----------
+    fname: str
+        filename that stores 2-D grid info.  Optionally, a pandas DataFrame
+        at least columns 'x','y' and possibly 'layer'.
+    layer: int (optional)
+        the layer number to use for 2-D.  If None and 
+        grid info is 3-D, a value of 1 is used
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """ 
+
+    grid_info = None
+    if isinstance(fname,str):
+        if not os.path.exists(fname):
+            raise FileNotFoundError(fname)
+        if fname.lower().endswith(".csv"):
+            grid_info = pd.read_csv(fname)
+            grid_info.columns = [c.lower() for c in grid_info.columns]
+            fname = grid_info # for  checks and processing below
+            
+        else:
+            try:
+                grid_info = get_grid_info_from_gridspec(fname)
+            except Exception as e1:
+                try:
+                    grid_info = get_2d_grid_info_from_mf6_grb(fname,layer=layer)
+                except Exception as e2:
+                    
+                    raise Exception("error getting grid info from file '{0}'".format(fname))
+        
+    if isinstance(fname,pd.DataFrame):
+        if 'x' not in fname.columns:
+            raise Exception("required 'x' column not found in grid info dataframe")
+        if 'y' not in fname.columns:
+            raise Exception("required 'y' column not found in grid info dataframe")
+        if layer is not None and 'layer' not in fname.columns:
+            print("WARNING: 'layer' arg is not None but 'layer' not found in grid info dataframe...")
+        # I think these should just be references to column values (not copies)
+        grid_info = {c:fname[c].values for c in fname.columns}
+    
+    return grid_info
+
+
+def get_2d_grid_info_from_mf6_grb(grb_fname: str,layer=None) -> dict:
+    """Read grid info from a MODFLOW-6 binary grid file
+    Parameters
+    ----------
+    grb_fname: str
+        MODFLOW-6 binary grid file
+    layer: int (optional)
+        the layer number to use for 2-D.  If None,
+        a value of 1 is used
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """
+    grid_info = get_grid_info_from_mf6_grb(grb_fname)
+    nnodes = grid_info["ncells"]
+    x = grid_info["x"].copy()
+    y = grid_info["y"].copy()
+    nrow,ncol = None,None
+    if grid_info["idis"] == 1:
+        nlay = grid_info["ndim3"]
+        if layer is not None:
+            if layer > nlay:
+                raise Exception("user-supplied 'layer' {0} greater than nlay {1}".format(layer,nlay))
+        else:
+            layer = 1
+        nrow = grid_info["ndim2"]
+        ncol = grid_info["ndim1"]
+        x = x.reshape((nlay,nrow,ncol))[layer-1]
+        y = y.reshape((nlay,nrow,ncol))[layer-1]
+        grid_info["nnodes"] = nrow * ncol
+        grid_info["x"] = x
+        grid_info["y"] = y
+        grid_info["nrow"] = nrow
+        grid_info["ncol"] = ncol
+
+    elif grid_info["idis"] == 2:
+        nlay = grid_info["ndim3"]
+        if layer is not None:
+            if layer > nlay:
+                raise Exception("user-supplied 'layer' {0} greater than nlay {1}".format(layer,nlay))
+        else:
+            layer = 1
+        ncpl = grid_info["ndim1"]
+        x = x.reshape((nlay,ncpl))[layer-1]
+        y = y.reshape((nlay,ncpl))[layer-1]
+        grid_info["nnodes"] = ncpl
+        grid_info["x"] = x
+        grid_info["y"] = y
+    return grid_info
+
+
+def get_2d_pp_info_structured_grid(
+    pp_space: int,
+    gridinfo_fname: str,
+    array_dict = {},
+    name_prefix="pp"
+) -> pandas.DataFrame:
+    """Create a grid of pilot point locations for a 
+    2-D structured grid
+    Parameters
+    ----------
+    pp_space: int
+        row and column spacing for pilot point locations
+    gridinfo_fname: str
+        file contain grid information
+    array_dict: dict (optional)
+        a dict of 2-D grid-shape arrays used to populate 
+        pilot point attributes.  Special values include:
+        "value","zone","bearing","aniso" and "corrlen", 
+        although any number of arrays can be passed and will
+        sampled at pilot point locations
+    name_prefix: str
+        pilot point name prefix. Default is "pp"
+    
+    Returns
+    -------
+    ppdf: pd.DataaFrame
+        dataframe of pilot point information
+
+    """
+
+    grid_info = get_2d_grid_info_from_file(gridinfo_fname)
+    pname, px, py, pval = [], [], [], []
+    pi, pj = [], []
+    parr_dict = {k:[] for k in array_dict.keys()}
+    count = 0
+    nrow = grid_info["nrow"]
+    ncol = grid_info["ncol"]
+    nlay = grid_info.get("nlay",1)
+
+    zone_array = array_dict.get("zone",None)
+
+    x = grid_info['x']
+    y = grid_info['y']
+    x = x.reshape((nlay,nrow,ncol))[0,:,:]
+    y = y.reshape((nlay,nrow,ncol))[0,:,:]
+    if nrow is None:
+        raise Exception("unstructured grid loaded from gridinfo_fname '{0}'".format(gridspec_fname))
+    for i in range(int(pp_space / 2), nrow, pp_space):
+        for j in range(int(pp_space / 2), ncol, pp_space):
+            if zone_array is not None and zone_array[i, j] <= 0:
+                continue
+            px.append(x[i, j])
+            py.append(y[i, j])
+            #if zone_array is not None:
+            #    pzone.append(zone_array[i, j])
+            #else:
+            #    pzone.append(1)
+
+            pname.append(name_prefix + "{0}".format(count))
+            pi.append(i)
+            pj.append(j)
+            count += 1
+    df = pd.DataFrame(
+        {
+            "ppname": pname,
+            "x": px,
+            "y": py,
+            "i": pi,
+            "j": pj,
+        },
+        index=pname,
+    )
+    df.loc[:,"value"] = 1.0
+    df.loc[:, "bearing"] = 0.0
+    df.loc[:, "aniso"] = 1.0
+    delx = pp_space * 5 * int((x.max() - x.min()) / float(ncol))
+    dely = pp_space * 5 * int((y.max() - y.min()) / float(nrow))
+    df.loc[:, "corrlen"] = max(delx,dely)  # ?
+    df.loc[:,"zone"] = 1
+    for k,arr in array_dict.items():
+        df.loc[:,k] = arr[df.i,df.j]
+    df["zone"] = df.zone.astype(int)
+
+    return df
+
+
+def interpolate_with_sva_pilotpoints_2d(
+    pp_info: pandas.DataFrame,
+    gridinfo_fname: str,
+    vartype="exp",
+    krigtype="ordinary",
+    vartransform="none",
+    max_pts=50,
+    min_pts=1,
+    search_dist=1e30,
+    zone_array=1,
+    verbose=True,
+    layer=None
+) -> dict:
+    """Perform 2-D pilot point interpolation using
+    spatially varying geostatistical hyper-parameters
+    Parameters
+    ----------
+    pp_info: pandas.DataFrame
+        dataframe with pilot point info.  Required columns 
+        include: "x","y",and "value".  optional columns include:
+        "zone","bearing","aniso",and "corrlen"    
+    gridinfo_fname: str
+        file name storing grid information
+    vartype: str
+        variogram type.  Default is "exp"onential
+    krigtype: str
+        kriging type.  Default is "ordinary"
+    vartransform: str
+        variogram transformation.  Default is "none"
+    max_pts: int
+        maximum number of pilot points to use in interpolation.
+        Default is 50
+    min_pts: int
+        minimum number of pilot points to use in interplation.
+        Default is 1
+    search_dict: float
+        search distance to use when looking for nearby pilot points.
+        Default is 1.0e+30
+    zone_array: int | numpy.ndarray
+        the zone array to match up with "zone" value in `pp_info`.  If 
+        integer type, a constant zone array of value "zone_array" is used.
+        Default is 1
+    verbose: bool
+        flag to output.  Default is True
+    layer: int
+        layer number to use if gridinfo_fname points to 3-D grid info.
+        Default is None, which results in layer 1 being used
+
+    Returns
+    -------
+    results: dict
+        resulting arrays of the various interpolation from pilot 
+        points to grid-shaped arrays
+    """
+    # some checks on pp_info
+    req_cols = ["ppname", "x", "y", "value"]
+    missing = []
+    for req_col in req_cols:
+        if req_col not in pp_info.columns:
+            missing.append(req_col)
+    if len(missing) > 0:
+        raise Exception(
+            "the following required columns are not in pp_info:{0}".format(
+                ",".join(missing)
+            )
+        )
+
+    if "zone" not in pp_info:
+        pp_info.loc[:, "zone"] = 1
+
+    nnodes, nrow, ncol = None, None, None
+    x, y, area = None, None, None
+
+    nrow, ncol = None, None
+    x, y, area = None, None, None
+    grid_info = get_2d_grid_info_from_file(gridinfo_fname)
+    nrow = grid_info.get("nrow",None)
+    ncol = grid_info.get("ncol",None)
+    x = grid_info['x']
+    y = grid_info['y']
+    area = grid_info.get("area",None)
+    idis = grid_info.get("idis",None)
+    nnodes = grid_info.get("nnodes",None)
+    if area is None:
+        area = np.ones_like(x)
+    if nnodes is None:
+        nnodes = x.shape[0]
+
+    lib = PestUtilsLib()
+
+    if not isinstance(zone_array, np.ndarray):
+        zone_array = np.ones((nnodes), dtype=int)
+        lib.logger.info("using 1s as zone array for interpolation")
+    elif zone_array.dtype != int:
+        # TODO warn here
+        lib.logger.info("casting zone_array from %r to int", zone_array.dtype)
+        zone_array = zone_array.astype(int)
+    
+    
+    hyperfac_ftype = "binary"
+    if verbose:
+        hyperfac_ftype = "text"
+    hyperbearing = 0.0
+    hyperaniso = 1.0
+    hypervartype = "exp"
+    hyperkrigtype = "ordinary"
+    hypertrans = "none"
+
+    fac_files = []
+    lib.logger.info("using bearing of %r and aniso of %r for hyperpar interpolation", hyperbearing, hyperaniso)
+    lib.logger.info("using %r variogram with %r transform for hyperpar interpolation",hypervartype,hypertrans)
+       
+    results = {}    
+
+    bearing = np.zeros_like(x)
+    if "bearing" in pp_info.columns:
+        hypernoint = pp_info.bearing.mean()
+        lib.logger.info("using no-interpolation value of %r for 'bearing' hyperpar interpolation", hypernoint)
+        hyperfac_fname = "tempbearing.fac"
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values.astype(int),
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            hyperkrigtype,
+            hyperaniso,
+            hyperbearing,
+            hyperfac_fname,
+            hyperfac_ftype,
+        )
+        result = lib.krige_using_file(
+            hyperfac_fname,
+            hyperfac_ftype,
+            nnodes,
+            hyperkrigtype,
+            hypertrans,
+            pp_info.bearing.values,
+            hypernoint,
+            hypernoint,
+        )
+        bearing = result["targval"]
+        fac_files.append(hyperfac_fname)
+        if verbose:
+            if nrow is not None:
+                np.savetxt("bearing.txt",bearing.reshape(nrow,ncol),fmt="%15.6E")
+            else:
+                np.savetxt("bearing.txt",bearing,fmt="%15.6E")
+    results["bearing"] = bearing
+    
+
+    aniso = np.zeros_like(x)
+    if "aniso" in pp_info.columns:
+        hypernoint = pp_info.aniso.mean()
+        lib.logger.info("using no-interpolation value of %r for 'aniso' hyperpar interpolation", hypernoint)
+        
+        hyperfac_fname = "tempaniso.fac"
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            hyperkrigtype,
+            hyperaniso,
+            hyperbearing,
+            hyperfac_fname,
+            hyperfac_ftype,
+        )
+        result = lib.krige_using_file(
+            hyperfac_fname,
+            hyperfac_ftype,
+            nnodes,
+            hyperkrigtype,
+            hypertrans,
+            pp_info.aniso.values,
+            hypernoint,
+            hypernoint,
+        )
+        aniso = result["targval"]
+        if verbose:
+            if nrow is not None:
+                np.savetxt("aniso.txt",aniso.reshape(nrow,ncol),fmt="%15.6E")
+            else:
+                np.savetxt("aniso.txt",aniso,fmt="%15.6E")
+
+    results["aniso"] = aniso
+
+    use_auto = False
+    corrlen = None
+    if "corrlen" in pp_info.columns:
+        hypernoint = pp_info.corrlen.mean()
+        lib.logger.info("using no-interpolation value of %r for 'corrlen' hyperpar interpolation", hypernoint)
+        hyperfac_fname = "tempcorrlen.fac"
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            hyperkrigtype,
+            hyperaniso,
+            hyperbearing,
+            hyperfac_fname,
+            hyperfac_ftype,
+        )
+        result = lib.krige_using_file(
+            hyperfac_fname,
+            hyperfac_ftype,
+            nnodes,
+            hyperkrigtype,
+            hypertrans,
+            pp_info.corrlen.values,
+            hypernoint,
+            hypernoint,
+        )
+        corrlen = result["targval"]
+        fac_files.append(hyperfac_fname)
+        use_auto = False
+        if verbose:
+            if nrow is not None:
+                np.savetxt("corrlen.txt",corrlen.reshape(nrow,ncol),fmt="%15.6E")
+            else:
+                np.savetxt("corrlen.txt",corrlen,fmt="%15.6E")
+        results["corrlen"] = corrlen
+
+    if not verbose:
+        for fac_file in fac_files:
+            try:
+                os.remove(fac_file)
+            except Exception as e:
+                pass
+
+    # todo: maybe make these args?
+    fac_fname = "var.fac"
+    fac_ftype = "binary"
+    if verbose:
+        fac_ftype = "text"
+    noint = pp_info.loc[:, "value"].mean()
+    if use_auto:
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            krigtype,
+            aniso.flatten(),
+            bearing.flatten(),
+            fac_fname,
+            fac_ftype,
+        )
+    else:
+        npts = lib.calc_kriging_factors_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            vartype,
+            krigtype,
+            corrlen.flatten(),
+            aniso.flatten(),
+            bearing.flatten(),
+            search_dist,
+            max_pts,
+            min_pts,
+            fac_fname,
+            fac_ftype,
+        )
+
+    result = lib.krige_using_file(
+        fac_fname,
+        fac_ftype,
+        nnodes,
+        krigtype,
+        vartransform,
+        pp_info.loc[:, "value"].values,
+        noint,
+        noint,
+    )
+    results["result"] = result["targval"]
+
+    if nrow is not None:
+        for k,v in results.items():
+            results[k] = v.reshape(nrow,ncol)
+
+    return results
+
+
+def generate_2d_grid_realizations(
+    gridinfo_fname: str,
+    num_reals=100,
+    variotype="exp",
+    mean=1.0,
+    variance=1.0,
+    variorange=None,
+    variotransform="none",
+    zone_array=None,
+    varioaniso=1.0,
+    variobearing=0.0,
+    random_seed=12345,
+    layer=None,
+) ->np.NDArray[float]:
+    """draw 2-D realizations using sequential gaussian 
+    simulations and optionally using spatially varying 
+    geostatistical hyper parameters.
+    Parameters
+    ----------
+    gridinfo_fname: str
+        file containing grid information
+    num_real : int
+        number of realizations to generate
+    variotype: str
+        variogram type.  Default is "exp"onential
+    mean: float or numpy.ndarray
+        field mean.  Either a scalar or array of shape nnodes.  Default is 1.0
+    variance: float
+        field variance. Either a scalar or array of shape nnodes.  Default is 1.0
+    variorange: float
+        range of the variogram. Either a scalar or array of shape nnodes.
+    variotransform: str
+        variogram transform.  Default is "none".
+    zone_array: int or numpy.ndarray
+        the zone array
+    varioaniso: float or numpy.ndarray
+        the variogram anisotropy ratio.  Either a scalar or array of shape nnodes.
+    variobearing: flaot or numpy.ndarray
+        the variogram anisotropy bearing.  Either a scalar or array of shape nnodes
+    random_seed: int
+        the random seed.  Default is 12345
+    layer : int or None
+        the layer to use of gridinfo_fname contains 3-D info.  Default
+        is None, which results in layer 1 being used
+
+    Returns
+    -------
+    results: numpy.ndarray(float)
+        realizations (if `grid_info` indicates a structured grid, realizations
+        will be reshaped to NROW X NCOL)
+    """
+
+    
+
+    nrow, ncol = None, None
+    x, y, area = None, None, None
+    grid_info = get_2d_grid_info_from_file(gridinfo_fname)
+    nrow = grid_info.get("nrow",None)
+    ncol = grid_info.get("ncol",None)
+    x = grid_info['x']
+    y = grid_info['y']
+    area = grid_info.get("area",None)
+    idis = grid_info.get("idis",None)
+    nnodes = grid_info.get("nnodes",None)
+    if area is None:
+        area = np.ones_like(x)
+    if nnodes is None:
+        nnodes = x.shape[0]
+
+
+    if not isinstance(mean, np.ndarray):
+        mean = np.zeros((nnodes)) + mean
+    if not isinstance(variance, np.ndarray):
+        variance = np.zeros((nnodes)) + variance
+    if variorange is None:
+        delx = x.max() - x.min()
+        dely = y.max() - y.min()
+
+        variorange = np.zeros((nnodes)) + max(delx,dely) / 10 # ?
+    elif not isinstance(variorange, np.ndarray):
+        variorange = np.zeros((nnodes)) + variorange
+
+    if not isinstance(variobearing, np.ndarray):
+        variobearing = np.zeros((nnodes)) + variobearing
+
+    if not isinstance(varioaniso, np.ndarray):
+        varioaniso = np.zeros((nnodes)) + varioaniso
+
+    if not isinstance(zone_array, np.ndarray):
+        zone_array = np.ones((nnodes), dtype=int)
+    elif zone_array.dtype != int:
+        # TODO warn here
+        zone_array = zone_array.astype(int)
+
+    
+
+    power = 1.0
+
+    lib = PestUtilsLib()
+    lib.initialize_randgen(random_seed)
+
+    reals = lib.fieldgen2d_sva(
+        x.flatten(),
+        y.flatten(),
+        area.flatten(),
+        zone_array.flatten(),
+        mean.flatten(),
+        variance.flatten(),
+        variorange.flatten(),
+        varioaniso.flatten(),
+        variobearing.flatten(),
+        variotransform,
+        variotype,
+        power,
+        num_reals,
+    )
+    lib.free_all_memory()
+    if nrow is not None:
+        return reals.transpose().reshape((num_reals, nrow, ncol))
+    else:
+        return reals.transpose()
+
+
+class SpatialReference(object):
+    """
+    a class to locate a structured model grid in x-y space.
+
+    Parameters
+    ----------
+    delr:  numpy.ndarray
+        the model discretization delr vector (An array of spacings along a row)
+    delc: numpy ndarray
+        the model discretization delc vector (An array of spacings along a column)
+    xul: float
+        The x coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll.
+    yul: float
+        The y coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll.
+    rotation: float
+        The counter-clockwise rotation (in degrees) of the grid
+    """
+
+    def __init__(self, delr, delc, xul, yul, rotation=0.0):
+        self.xul = float(xul)
+        self.yul = float(yul)
+        self.rotation = float(rotation)
+        for delrc in [delr, delc]:
+            if isinstance(delrc, float) or isinstance(delrc, int):
+                msg = (
+                    "delr and delcs must be an array or sequences equal in "
+                    "length to the number of rows/columns."
+                )
+                raise TypeError(msg)
+
+        self.delc = np.atleast_1d(np.array(delc)).astype(np.float64)
+        self.delr = np.atleast_1d(np.array(delr)).astype(np.float64)
+
+        self._xgrid = None
+        self._ygrid = None
+        self._xcentergrid = None
+        self._ycentergrid = None
+
+    @property
+    def xll(self)->float:
+        """lower left x coord
+        """
+        xll = self.xul - (np.sin(self.theta) * self.yedge[0])
+        return xll
+
+    @property
+    def yll(self)->float:
+        """lower left y coord
+        """
+        yll = self.yul - (np.cos(self.theta) * self.yedge[0])
+        return yll
+
+    @property
+    def nrow(self)->int:
+        """number of rows
+        """
+        return self.delc.shape[0]
+
+    @property
+    def ncol(self)->int:
+        """number of cols
+        """
+        return self.delr.shape[0]
+
+    @classmethod
+    def from_gridspec(cls, gridspec_file)->SpatialReference:
+        """instantiate from a pest-style grid specification file
+        Parameters
+        ----------
+        gridspec_file: str
+            grid specification file name
+
+        Returns
+        -------
+        sr: SpatialReference
+            sr instance
+        """
+        f = open(gridspec_file, "r")
+        raw = f.readline().strip().split()
+        nrow = int(raw[0])
+        ncol = int(raw[1])
+        raw = f.readline().strip().split()
+        xul, yul, rot = float(raw[0]), float(raw[1]), float(raw[2])
+        delr = []
+        j = 0
+        while j < ncol:
+            raw = f.readline().strip().split()
+            for r in raw:
+                if "*" in r:
+                    rraw = r.split("*")
+                    for n in range(int(rraw[0])):
+                        delr.append(float(rraw[1]))
+                        j += 1
+                else:
+                    delr.append(float(r))
+                    j += 1
+        delc = []
+        i = 0
+        while i < nrow:
+            raw = f.readline().strip().split()
+            for r in raw:
+                if "*" in r:
+                    rraw = r.split("*")
+                    for n in range(int(rraw[0])):
+                        delc.append(float(rraw[1]))
+                        i += 1
+                else:
+                    delc.append(float(r))
+                    i += 1
+        f.close()
+        return cls(np.array(delr), np.array(delc), xul=xul, yul=yul, rotation=rot)
+
+    @property
+    def theta(self)->float:
+        """rotation in radians
+        """
+        return -self.rotation * np.pi / 180.0
+
+    @property
+    def xedge(self)->np.NDArray[float]:
+        """the xedge array of the grid
+        """
+        return self.get_xedge_array()
+
+    @property
+    def yedge(self)->np.NDArray[float]:
+        """the yedge array of the grid
+        """
+        return self.get_yedge_array()
+
+    @property
+    def xgrid(self)->np.NDArray[float]:
+        """xgrid array
+        """
+        if self._xgrid is None:
+            self._set_xygrid()
+        return self._xgrid
+
+    @property
+    def ygrid(self)->np.NDArray[float]:
+        """ygrid array
+        """
+        if self._ygrid is None:
+            self._set_xygrid()
+        return self._ygrid
+
+    @property
+    def xcenter(self)->np.NDArray[float]:
+        """grid x center array
+        """
+        return self.get_xcenter_array()
+
+    @property
+    def ycenter(self)->np.NDArray[float]:
+        """grid y center array
+        """
+        return self.get_ycenter_array()
+
+    @property
+    def ycentergrid(self)->np.NDArray[float]:
+        """grid y center array
+        """
+        if self._ycentergrid is None:
+            self._set_xycentergrid()
+        return self._ycentergrid
+
+    @property
+    def xcentergrid(self)->np.NDArray[float]:
+        """grid x center array
+        """
+        if self._xcentergrid is None:
+            self._set_xycentergrid()
+        return self._xcentergrid
+
+    @property
+    def areagrid(self)->np.NDArray[float]:
+        """area of grid nodes
+        """
+        dr, dc = np.meshgrid(self.delr, self.delc)
+        return dr * dc
+
+    def _set_xycentergrid(self):
+        self._xcentergrid, self._ycentergrid = np.meshgrid(self.xcenter, self.ycenter)
+        self._xcentergrid, self._ycentergrid = self.transform(
+            self._xcentergrid, self._ycentergrid
+        )
+
+    def _set_xygrid(self):
+        self._xgrid, self._ygrid = np.meshgrid(self.xedge, self.yedge)
+        self._xgrid, self._ygrid = self.transform(self._xgrid, self._ygrid)
+
+    def get_xedge_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell edge x
+        coordinates for every column in the grid in model space - not offset
+        or rotated.  Array is of size (ncol + 1)
+
+        """
+        assert self.delr is not None and len(self.delr) > 0, (
+            "delr not passed to " "spatial reference object"
+        )
+        xedge = np.concatenate(([0.0], np.add.accumulate(self.delr)))
+        return xedge
+
+    def get_yedge_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell edge y
+        coordinates for every row in the grid in model space - not offset or
+        rotated. Array is of size (nrow + 1)
+
+        """
+        assert self.delc is not None and len(self.delc) > 0, (
+            "delc not passed to " "spatial reference object"
+        )
+        length_y = np.add.reduce(self.delc)
+        yedge = np.concatenate(([length_y], length_y - np.add.accumulate(self.delc)))
+        return yedge
+
+    def get_xcenter_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell center x
+        coordinate for every column in the grid in model space - not offset or rotated.
+
+        """
+        assert self.delr is not None and len(self.delr) > 0, (
+            "delr not passed to " "spatial reference object"
+        )
+        x = np.add.accumulate(self.delr) - 0.5 * self.delr
+        return x
+
+    def get_ycenter_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell center x
+        coordinate for every row in the grid in model space - not offset of rotated.
+
+        """
+        assert self.delc is not None and len(self.delc) > 0, (
+            "delc not passed to " "spatial reference object"
+        )
+        Ly = np.add.reduce(self.delc)
+        y = Ly - (np.add.accumulate(self.delc) - 0.5 * self.delc)
+        return y
+
+    @staticmethod
+    def rotate(x, y, theta, xorigin=0.0, yorigin=0.0):
+        """
+        Given x and y array-like values calculate the rotation about an
+        arbitrary origin and then return the rotated coordinates.  theta is in
+        degrees.
+
+        """
+        # jwhite changed on Oct 11 2016 - rotation is now positive CCW
+        # theta = -theta * np.pi / 180.
+        theta = theta * np.pi / 180.0
+
+        xrot = xorigin + np.cos(theta) * (x - xorigin) - np.sin(theta) * (y - yorigin)
+        yrot = yorigin + np.sin(theta) * (x - xorigin) + np.cos(theta) * (y - yorigin)
+        return xrot, yrot
+
+    def transform(self, x, y, inverse=False):
+        """
+        Given x and y array-like values, apply rotation, scale and offset,
+        to convert them from model coordinates to real-world coordinates.
+        """
+        if isinstance(x, list):
+            x = np.array(x)
+            y = np.array(y)
+        if not np.isscalar(x):
+            x, y = x.copy(), y.copy()
+
+        if not inverse:
+            x += self.xll
+            y += self.yll
+            x, y = SpatialReference.rotate(
+                x, y, theta=self.rotation, xorigin=self.xll, yorigin=self.yll
+            )
+        else:
+            x, y = SpatialReference.rotate(x, y, -self.rotation, self.xll, self.yll)
+            x -= self.xll
+            y -= self.yll
+        return x, y
+
+    def get_extent(self)->tuple[float]:
+        """
+        Get the extent of the rotated and offset grid
+
+        """
+        x0 = self.xedge[0]
+        x1 = self.xedge[-1]
+        y0 = self.yedge[0]
+        y1 = self.yedge[-1]
+
+        # upper left point
+        x0r, y0r = self.transform(x0, y0)
+
+        # upper right point
+        x1r, y1r = self.transform(x1, y0)
+
+        # lower right point
+        x2r, y2r = self.transform(x1, y1)
+
+        # lower left point
+        x3r, y3r = self.transform(x0, y1)
+
+        xmin = min(x0r, x1r, x2r, x3r)
+        xmax = max(x0r, x1r, x2r, x3r)
+        ymin = min(y0r, y1r, y2r, y3r)
+        ymax = max(y0r, y1r, y2r, y3r)
+
+        return (xmin, xmax, ymin, ymax)
+
+    def get_vertices(self, i, j)->list[list[float]]:
+        """Get vertices for a single cell or sequence if i, j locations."""
+        pts = []
+        xgrid, ygrid = self.xgrid, self.ygrid
+        pts.append([xgrid[i, j], ygrid[i, j]])
+        pts.append([xgrid[i + 1, j], ygrid[i + 1, j]])
+        pts.append([xgrid[i + 1, j + 1], ygrid[i + 1, j + 1]])
+        pts.append([xgrid[i, j + 1], ygrid[i, j + 1]])
+        pts.append([xgrid[i, j], ygrid[i, j]])
+        if np.isscalar(i):
+            return pts
+        else:
+            vrts = np.array(pts).transpose([2, 0, 1])
+            return [v.tolist() for v in vrts]
+
+    def get_ij(self, x, y)->tuple(int):
+        """Return the row and column of a point or sequence of points
+        in real-world coordinates.
+
+        """
+        if np.isscalar(x):
+            c = (np.abs(self.xcentergrid[0] - x)).argmin()
+            r = (np.abs(self.ycentergrid[:, 0] - y)).argmin()
+        else:
+            xcp = np.array([self.xcentergrid[0]] * (len(x)))
+            ycp = np.array([self.ycentergrid[:, 0]] * (len(x)))
+            c = (np.abs(xcp.transpose() - x)).argmin(axis=0)
+            r = (np.abs(ycp.transpose() - y)).argmin(axis=0)
+        return r, c
+
+    def write_gridspec(self, filename):
+
+        """write a PEST-style grid specification file
+        Parameters
+        ----------
+        filename: str
+            file to write
+
+
+        """
+        f = open(filename, "w")
+        f.write("{0:10d} {1:10d}\n".format(self.delc.shape[0], self.delr.shape[0]))
+        f.write(
+            "{0:15.6E} {1:15.6E} {2:15.6E}\n".format(
+                self.xul,
+                self.yul,
+                self.rotation,
+            )
+        )
+
+        for r in self.delr:
+            f.write("{0:15.6E} ".format(r))
+        f.write("\n")
+        for c in self.delc:
+            f.write("{0:15.6E} ".format(c))
+        f.write("\n")
+        return
+
+
+
+
+
+
+
+

Functions

+
+
+def generate_2d_grid_realizations(gridinfo_fname: str, num_reals=100, variotype='exp', mean=1.0, variance=1.0, variorange=None, variotransform='none', zone_array=None, varioaniso=1.0, variobearing=0.0, random_seed=12345, layer=None) ‑> np.NDArray[float] +
+
+

draw 2-D realizations using sequential gaussian +simulations and optionally using spatially varying +geostatistical hyper parameters. +Parameters

+
+
+
gridinfo_fname : str
+
file containing grid information
+
num_real : int
+
number of realizations to generate
+
variotype : str
+
variogram type. +Default is "exp"onential
+
mean : float or numpy.ndarray
+
field mean. +Either a scalar or array of shape nnodes. +Default is 1.0
+
variance : float
+
field variance. Either a scalar or array of shape nnodes. +Default is 1.0
+
variorange : float
+
range of the variogram. Either a scalar or array of shape nnodes.
+
variotransform : str
+
variogram transform. +Default is "none".
+
zone_array : int or numpy.ndarray
+
the zone array
+
varioaniso : float or numpy.ndarray
+
the variogram anisotropy ratio. +Either a scalar or array of shape nnodes.
+
variobearing : flaot or numpy.ndarray
+
the variogram anisotropy bearing. +Either a scalar or array of shape nnodes
+
random_seed : int
+
the random seed. +Default is 12345
+
layer : int or None
+
the layer to use of gridinfo_fname contains 3-D info. +Default +is None, which results in layer 1 being used
+
+

Returns

+
+
results : numpy.ndarray(float)
+
realizations (if grid_info indicates a structured grid, realizations +will be reshaped to NROW X NCOL)
+
+
+ +Expand source code + +
def generate_2d_grid_realizations(
+    gridinfo_fname: str,
+    num_reals=100,
+    variotype="exp",
+    mean=1.0,
+    variance=1.0,
+    variorange=None,
+    variotransform="none",
+    zone_array=None,
+    varioaniso=1.0,
+    variobearing=0.0,
+    random_seed=12345,
+    layer=None,
+) ->np.NDArray[float]:
+    """draw 2-D realizations using sequential gaussian 
+    simulations and optionally using spatially varying 
+    geostatistical hyper parameters.
+    Parameters
+    ----------
+    gridinfo_fname: str
+        file containing grid information
+    num_real : int
+        number of realizations to generate
+    variotype: str
+        variogram type.  Default is "exp"onential
+    mean: float or numpy.ndarray
+        field mean.  Either a scalar or array of shape nnodes.  Default is 1.0
+    variance: float
+        field variance. Either a scalar or array of shape nnodes.  Default is 1.0
+    variorange: float
+        range of the variogram. Either a scalar or array of shape nnodes.
+    variotransform: str
+        variogram transform.  Default is "none".
+    zone_array: int or numpy.ndarray
+        the zone array
+    varioaniso: float or numpy.ndarray
+        the variogram anisotropy ratio.  Either a scalar or array of shape nnodes.
+    variobearing: flaot or numpy.ndarray
+        the variogram anisotropy bearing.  Either a scalar or array of shape nnodes
+    random_seed: int
+        the random seed.  Default is 12345
+    layer : int or None
+        the layer to use of gridinfo_fname contains 3-D info.  Default
+        is None, which results in layer 1 being used
+
+    Returns
+    -------
+    results: numpy.ndarray(float)
+        realizations (if `grid_info` indicates a structured grid, realizations
+        will be reshaped to NROW X NCOL)
+    """
+
+    
+
+    nrow, ncol = None, None
+    x, y, area = None, None, None
+    grid_info = get_2d_grid_info_from_file(gridinfo_fname)
+    nrow = grid_info.get("nrow",None)
+    ncol = grid_info.get("ncol",None)
+    x = grid_info['x']
+    y = grid_info['y']
+    area = grid_info.get("area",None)
+    idis = grid_info.get("idis",None)
+    nnodes = grid_info.get("nnodes",None)
+    if area is None:
+        area = np.ones_like(x)
+    if nnodes is None:
+        nnodes = x.shape[0]
+
+
+    if not isinstance(mean, np.ndarray):
+        mean = np.zeros((nnodes)) + mean
+    if not isinstance(variance, np.ndarray):
+        variance = np.zeros((nnodes)) + variance
+    if variorange is None:
+        delx = x.max() - x.min()
+        dely = y.max() - y.min()
+
+        variorange = np.zeros((nnodes)) + max(delx,dely) / 10 # ?
+    elif not isinstance(variorange, np.ndarray):
+        variorange = np.zeros((nnodes)) + variorange
+
+    if not isinstance(variobearing, np.ndarray):
+        variobearing = np.zeros((nnodes)) + variobearing
+
+    if not isinstance(varioaniso, np.ndarray):
+        varioaniso = np.zeros((nnodes)) + varioaniso
+
+    if not isinstance(zone_array, np.ndarray):
+        zone_array = np.ones((nnodes), dtype=int)
+    elif zone_array.dtype != int:
+        # TODO warn here
+        zone_array = zone_array.astype(int)
+
+    
+
+    power = 1.0
+
+    lib = PestUtilsLib()
+    lib.initialize_randgen(random_seed)
+
+    reals = lib.fieldgen2d_sva(
+        x.flatten(),
+        y.flatten(),
+        area.flatten(),
+        zone_array.flatten(),
+        mean.flatten(),
+        variance.flatten(),
+        variorange.flatten(),
+        varioaniso.flatten(),
+        variobearing.flatten(),
+        variotransform,
+        variotype,
+        power,
+        num_reals,
+    )
+    lib.free_all_memory()
+    if nrow is not None:
+        return reals.transpose().reshape((num_reals, nrow, ncol))
+    else:
+        return reals.transpose()
+
+
+
+def get_2d_grid_info_from_file(fname: str, layer=None) ‑> dict +
+
+

Try to read 2-D grid info from a variety of filename sources +Parameters

+
+
+
fname : str
+
filename that stores 2-D grid info. +Optionally, a pandas DataFrame +at least columns 'x','y' and possibly 'layer'.
+
layer : int (optional)
+
the layer number to use for 2-D. +If None and +grid info is 3-D, a value of 1 is used
+
+

Returns

+
+
grid_info : dict
+
grid information
+
+
+ +Expand source code + +
def get_2d_grid_info_from_file(fname: str,layer=None) -> dict:
+    """Try to read 2-D grid info from a variety of filename sources
+    Parameters
+    ----------
+    fname: str
+        filename that stores 2-D grid info.  Optionally, a pandas DataFrame
+        at least columns 'x','y' and possibly 'layer'.
+    layer: int (optional)
+        the layer number to use for 2-D.  If None and 
+        grid info is 3-D, a value of 1 is used
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """ 
+
+    grid_info = None
+    if isinstance(fname,str):
+        if not os.path.exists(fname):
+            raise FileNotFoundError(fname)
+        if fname.lower().endswith(".csv"):
+            grid_info = pd.read_csv(fname)
+            grid_info.columns = [c.lower() for c in grid_info.columns]
+            fname = grid_info # for  checks and processing below
+            
+        else:
+            try:
+                grid_info = get_grid_info_from_gridspec(fname)
+            except Exception as e1:
+                try:
+                    grid_info = get_2d_grid_info_from_mf6_grb(fname,layer=layer)
+                except Exception as e2:
+                    
+                    raise Exception("error getting grid info from file '{0}'".format(fname))
+        
+    if isinstance(fname,pd.DataFrame):
+        if 'x' not in fname.columns:
+            raise Exception("required 'x' column not found in grid info dataframe")
+        if 'y' not in fname.columns:
+            raise Exception("required 'y' column not found in grid info dataframe")
+        if layer is not None and 'layer' not in fname.columns:
+            print("WARNING: 'layer' arg is not None but 'layer' not found in grid info dataframe...")
+        # I think these should just be references to column values (not copies)
+        grid_info = {c:fname[c].values for c in fname.columns}
+    
+    return grid_info
+
+
+
+def get_2d_grid_info_from_mf6_grb(grb_fname: str, layer=None) ‑> dict +
+
+

Read grid info from a MODFLOW-6 binary grid file +Parameters

+
+
+
grb_fname : str
+
MODFLOW-6 binary grid file
+
layer : int (optional)
+
the layer number to use for 2-D. +If None, +a value of 1 is used
+
+

Returns

+
+
grid_info : dict
+
grid information
+
+
+ +Expand source code + +
def get_2d_grid_info_from_mf6_grb(grb_fname: str,layer=None) -> dict:
+    """Read grid info from a MODFLOW-6 binary grid file
+    Parameters
+    ----------
+    grb_fname: str
+        MODFLOW-6 binary grid file
+    layer: int (optional)
+        the layer number to use for 2-D.  If None,
+        a value of 1 is used
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """
+    grid_info = get_grid_info_from_mf6_grb(grb_fname)
+    nnodes = grid_info["ncells"]
+    x = grid_info["x"].copy()
+    y = grid_info["y"].copy()
+    nrow,ncol = None,None
+    if grid_info["idis"] == 1:
+        nlay = grid_info["ndim3"]
+        if layer is not None:
+            if layer > nlay:
+                raise Exception("user-supplied 'layer' {0} greater than nlay {1}".format(layer,nlay))
+        else:
+            layer = 1
+        nrow = grid_info["ndim2"]
+        ncol = grid_info["ndim1"]
+        x = x.reshape((nlay,nrow,ncol))[layer-1]
+        y = y.reshape((nlay,nrow,ncol))[layer-1]
+        grid_info["nnodes"] = nrow * ncol
+        grid_info["x"] = x
+        grid_info["y"] = y
+        grid_info["nrow"] = nrow
+        grid_info["ncol"] = ncol
+
+    elif grid_info["idis"] == 2:
+        nlay = grid_info["ndim3"]
+        if layer is not None:
+            if layer > nlay:
+                raise Exception("user-supplied 'layer' {0} greater than nlay {1}".format(layer,nlay))
+        else:
+            layer = 1
+        ncpl = grid_info["ndim1"]
+        x = x.reshape((nlay,ncpl))[layer-1]
+        y = y.reshape((nlay,ncpl))[layer-1]
+        grid_info["nnodes"] = ncpl
+        grid_info["x"] = x
+        grid_info["y"] = y
+    return grid_info
+
+
+
+def get_2d_pp_info_structured_grid(pp_space: int, gridinfo_fname: str, array_dict={}, name_prefix='pp') ‑> pandas.DataFrame +
+
+

Create a grid of pilot point locations for a +2-D structured grid +Parameters

+
+
+
pp_space : int
+
row and column spacing for pilot point locations
+
gridinfo_fname : str
+
file contain grid information
+
array_dict : dict (optional)
+
a dict of 2-D grid-shape arrays used to populate +pilot point attributes. +Special values include: +"value","zone","bearing","aniso" and "corrlen", +although any number of arrays can be passed and will +sampled at pilot point locations
+
name_prefix : str
+
pilot point name prefix. Default is "pp"
+
+

Returns

+
+
ppdf : pd.DataaFrame
+
dataframe of pilot point information
+
+
+ +Expand source code + +
def get_2d_pp_info_structured_grid(
+    pp_space: int,
+    gridinfo_fname: str,
+    array_dict = {},
+    name_prefix="pp"
+) -> pandas.DataFrame:
+    """Create a grid of pilot point locations for a 
+    2-D structured grid
+    Parameters
+    ----------
+    pp_space: int
+        row and column spacing for pilot point locations
+    gridinfo_fname: str
+        file contain grid information
+    array_dict: dict (optional)
+        a dict of 2-D grid-shape arrays used to populate 
+        pilot point attributes.  Special values include:
+        "value","zone","bearing","aniso" and "corrlen", 
+        although any number of arrays can be passed and will
+        sampled at pilot point locations
+    name_prefix: str
+        pilot point name prefix. Default is "pp"
+    
+    Returns
+    -------
+    ppdf: pd.DataaFrame
+        dataframe of pilot point information
+
+    """
+
+    grid_info = get_2d_grid_info_from_file(gridinfo_fname)
+    pname, px, py, pval = [], [], [], []
+    pi, pj = [], []
+    parr_dict = {k:[] for k in array_dict.keys()}
+    count = 0
+    nrow = grid_info["nrow"]
+    ncol = grid_info["ncol"]
+    nlay = grid_info.get("nlay",1)
+
+    zone_array = array_dict.get("zone",None)
+
+    x = grid_info['x']
+    y = grid_info['y']
+    x = x.reshape((nlay,nrow,ncol))[0,:,:]
+    y = y.reshape((nlay,nrow,ncol))[0,:,:]
+    if nrow is None:
+        raise Exception("unstructured grid loaded from gridinfo_fname '{0}'".format(gridspec_fname))
+    for i in range(int(pp_space / 2), nrow, pp_space):
+        for j in range(int(pp_space / 2), ncol, pp_space):
+            if zone_array is not None and zone_array[i, j] <= 0:
+                continue
+            px.append(x[i, j])
+            py.append(y[i, j])
+            #if zone_array is not None:
+            #    pzone.append(zone_array[i, j])
+            #else:
+            #    pzone.append(1)
+
+            pname.append(name_prefix + "{0}".format(count))
+            pi.append(i)
+            pj.append(j)
+            count += 1
+    df = pd.DataFrame(
+        {
+            "ppname": pname,
+            "x": px,
+            "y": py,
+            "i": pi,
+            "j": pj,
+        },
+        index=pname,
+    )
+    df.loc[:,"value"] = 1.0
+    df.loc[:, "bearing"] = 0.0
+    df.loc[:, "aniso"] = 1.0
+    delx = pp_space * 5 * int((x.max() - x.min()) / float(ncol))
+    dely = pp_space * 5 * int((y.max() - y.min()) / float(nrow))
+    df.loc[:, "corrlen"] = max(delx,dely)  # ?
+    df.loc[:,"zone"] = 1
+    for k,arr in array_dict.items():
+        df.loc[:,k] = arr[df.i,df.j]
+    df["zone"] = df.zone.astype(int)
+
+    return df
+
+
+
+def get_grid_info_from_gridspec(gridspec_fname: str) ‑> dict +
+
+

Read structured grid info from a PEST-style grid specificatin file +Parameters

+
+
+
gridspec_fname : str
+
PEST-style grid specification file
+
+

Returns

+
+
grid_info : dict
+
grid information
+
+
+ +Expand source code + +
def get_grid_info_from_gridspec(gridspec_fname: str) -> dict:
+    """Read structured grid info from a PEST-style grid specificatin file
+    Parameters
+    ----------
+    gridspec_fname : str
+        PEST-style grid specification file
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """
+
+    if not os.path.exists(gridspec_fname):
+        raise FileNotFoundError(gridspec_fname)
+    sr = SpatialReference.from_gridspec(gridspec_fname)
+    return {
+        "x": sr.xcentergrid.flatten(),
+        "y": sr.ycentergrid.flatten(),
+        "area": sr.areagrid.flatten(),
+        "nrow": sr.nrow,
+        "ncol": sr.ncol,
+        "delr": sr.delr,
+        "delc": sr.delc
+    }
+
+
+
+def get_grid_info_from_mf6_grb(grb_fname: str) ‑> dict +
+
+

Read grid info from a MODFLOW-6 binary grid file +Parameters

+
+
+
grb_fname : str
+
MODFLOW-6 binary grid file
+
+

Returns

+
+
grid_info : dict
+
grid information
+
+
+ +Expand source code + +
def get_grid_info_from_mf6_grb(grb_fname: str) -> dict:
+    """Read grid info from a MODFLOW-6 binary grid file
+    Parameters
+    ----------
+    grb_fname: str
+        MODFLOW-6 binary grid file
+    
+    Returns
+    -------
+    grid_info: dict
+        grid information
+    """
+    if not os.path.exists(grb_fname):
+        raise FileNotFoundError(grb_fname)
+    lib = PestUtilsLib()
+    data = lib.install_mf6_grid_from_file("grid",grb_fname)
+    data["x"],data["y"],data["z"] = lib.get_cell_centres_mf6("grid",data["ncells"])
+    lib.uninstall_mf6_grid("grid")
+    lib.free_all_memory()
+    return data
+
+
+
+def interpolate_with_sva_pilotpoints_2d(pp_info: pandas.DataFrame, gridinfo_fname: str, vartype='exp', krigtype='ordinary', vartransform='none', max_pts=50, min_pts=1, search_dist=1e+30, zone_array=1, verbose=True, layer=None) ‑> dict +
+
+

Perform 2-D pilot point interpolation using +spatially varying geostatistical hyper-parameters +Parameters

+
+
+
pp_info : pandas.DataFrame
+
dataframe with pilot point info. +Required columns +include: "x","y",and "value". +optional columns include: +"zone","bearing","aniso",and "corrlen"
+
gridinfo_fname : str
+
file name storing grid information
+
vartype : str
+
variogram type. +Default is "exp"onential
+
krigtype : str
+
kriging type. +Default is "ordinary"
+
vartransform : str
+
variogram transformation. +Default is "none"
+
max_pts : int
+
maximum number of pilot points to use in interpolation. +Default is 50
+
min_pts : int
+
minimum number of pilot points to use in interplation. +Default is 1
+
search_dict : float
+
search distance to use when looking for nearby pilot points. +Default is 1.0e+30
+
zone_array : int | numpy.ndarray
+
the zone array to match up with "zone" value in pp_info. +If +integer type, a constant zone array of value "zone_array" is used. +Default is 1
+
verbose : bool
+
flag to output. +Default is True
+
layer : int
+
layer number to use if gridinfo_fname points to 3-D grid info. +Default is None, which results in layer 1 being used
+
+

Returns

+
+
results : dict
+
resulting arrays of the various interpolation from pilot +points to grid-shaped arrays
+
+
+ +Expand source code + +
def interpolate_with_sva_pilotpoints_2d(
+    pp_info: pandas.DataFrame,
+    gridinfo_fname: str,
+    vartype="exp",
+    krigtype="ordinary",
+    vartransform="none",
+    max_pts=50,
+    min_pts=1,
+    search_dist=1e30,
+    zone_array=1,
+    verbose=True,
+    layer=None
+) -> dict:
+    """Perform 2-D pilot point interpolation using
+    spatially varying geostatistical hyper-parameters
+    Parameters
+    ----------
+    pp_info: pandas.DataFrame
+        dataframe with pilot point info.  Required columns 
+        include: "x","y",and "value".  optional columns include:
+        "zone","bearing","aniso",and "corrlen"    
+    gridinfo_fname: str
+        file name storing grid information
+    vartype: str
+        variogram type.  Default is "exp"onential
+    krigtype: str
+        kriging type.  Default is "ordinary"
+    vartransform: str
+        variogram transformation.  Default is "none"
+    max_pts: int
+        maximum number of pilot points to use in interpolation.
+        Default is 50
+    min_pts: int
+        minimum number of pilot points to use in interplation.
+        Default is 1
+    search_dict: float
+        search distance to use when looking for nearby pilot points.
+        Default is 1.0e+30
+    zone_array: int | numpy.ndarray
+        the zone array to match up with "zone" value in `pp_info`.  If 
+        integer type, a constant zone array of value "zone_array" is used.
+        Default is 1
+    verbose: bool
+        flag to output.  Default is True
+    layer: int
+        layer number to use if gridinfo_fname points to 3-D grid info.
+        Default is None, which results in layer 1 being used
+
+    Returns
+    -------
+    results: dict
+        resulting arrays of the various interpolation from pilot 
+        points to grid-shaped arrays
+    """
+    # some checks on pp_info
+    req_cols = ["ppname", "x", "y", "value"]
+    missing = []
+    for req_col in req_cols:
+        if req_col not in pp_info.columns:
+            missing.append(req_col)
+    if len(missing) > 0:
+        raise Exception(
+            "the following required columns are not in pp_info:{0}".format(
+                ",".join(missing)
+            )
+        )
+
+    if "zone" not in pp_info:
+        pp_info.loc[:, "zone"] = 1
+
+    nnodes, nrow, ncol = None, None, None
+    x, y, area = None, None, None
+
+    nrow, ncol = None, None
+    x, y, area = None, None, None
+    grid_info = get_2d_grid_info_from_file(gridinfo_fname)
+    nrow = grid_info.get("nrow",None)
+    ncol = grid_info.get("ncol",None)
+    x = grid_info['x']
+    y = grid_info['y']
+    area = grid_info.get("area",None)
+    idis = grid_info.get("idis",None)
+    nnodes = grid_info.get("nnodes",None)
+    if area is None:
+        area = np.ones_like(x)
+    if nnodes is None:
+        nnodes = x.shape[0]
+
+    lib = PestUtilsLib()
+
+    if not isinstance(zone_array, np.ndarray):
+        zone_array = np.ones((nnodes), dtype=int)
+        lib.logger.info("using 1s as zone array for interpolation")
+    elif zone_array.dtype != int:
+        # TODO warn here
+        lib.logger.info("casting zone_array from %r to int", zone_array.dtype)
+        zone_array = zone_array.astype(int)
+    
+    
+    hyperfac_ftype = "binary"
+    if verbose:
+        hyperfac_ftype = "text"
+    hyperbearing = 0.0
+    hyperaniso = 1.0
+    hypervartype = "exp"
+    hyperkrigtype = "ordinary"
+    hypertrans = "none"
+
+    fac_files = []
+    lib.logger.info("using bearing of %r and aniso of %r for hyperpar interpolation", hyperbearing, hyperaniso)
+    lib.logger.info("using %r variogram with %r transform for hyperpar interpolation",hypervartype,hypertrans)
+       
+    results = {}    
+
+    bearing = np.zeros_like(x)
+    if "bearing" in pp_info.columns:
+        hypernoint = pp_info.bearing.mean()
+        lib.logger.info("using no-interpolation value of %r for 'bearing' hyperpar interpolation", hypernoint)
+        hyperfac_fname = "tempbearing.fac"
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values.astype(int),
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            hyperkrigtype,
+            hyperaniso,
+            hyperbearing,
+            hyperfac_fname,
+            hyperfac_ftype,
+        )
+        result = lib.krige_using_file(
+            hyperfac_fname,
+            hyperfac_ftype,
+            nnodes,
+            hyperkrigtype,
+            hypertrans,
+            pp_info.bearing.values,
+            hypernoint,
+            hypernoint,
+        )
+        bearing = result["targval"]
+        fac_files.append(hyperfac_fname)
+        if verbose:
+            if nrow is not None:
+                np.savetxt("bearing.txt",bearing.reshape(nrow,ncol),fmt="%15.6E")
+            else:
+                np.savetxt("bearing.txt",bearing,fmt="%15.6E")
+    results["bearing"] = bearing
+    
+
+    aniso = np.zeros_like(x)
+    if "aniso" in pp_info.columns:
+        hypernoint = pp_info.aniso.mean()
+        lib.logger.info("using no-interpolation value of %r for 'aniso' hyperpar interpolation", hypernoint)
+        
+        hyperfac_fname = "tempaniso.fac"
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            hyperkrigtype,
+            hyperaniso,
+            hyperbearing,
+            hyperfac_fname,
+            hyperfac_ftype,
+        )
+        result = lib.krige_using_file(
+            hyperfac_fname,
+            hyperfac_ftype,
+            nnodes,
+            hyperkrigtype,
+            hypertrans,
+            pp_info.aniso.values,
+            hypernoint,
+            hypernoint,
+        )
+        aniso = result["targval"]
+        if verbose:
+            if nrow is not None:
+                np.savetxt("aniso.txt",aniso.reshape(nrow,ncol),fmt="%15.6E")
+            else:
+                np.savetxt("aniso.txt",aniso,fmt="%15.6E")
+
+    results["aniso"] = aniso
+
+    use_auto = False
+    corrlen = None
+    if "corrlen" in pp_info.columns:
+        hypernoint = pp_info.corrlen.mean()
+        lib.logger.info("using no-interpolation value of %r for 'corrlen' hyperpar interpolation", hypernoint)
+        hyperfac_fname = "tempcorrlen.fac"
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            hyperkrigtype,
+            hyperaniso,
+            hyperbearing,
+            hyperfac_fname,
+            hyperfac_ftype,
+        )
+        result = lib.krige_using_file(
+            hyperfac_fname,
+            hyperfac_ftype,
+            nnodes,
+            hyperkrigtype,
+            hypertrans,
+            pp_info.corrlen.values,
+            hypernoint,
+            hypernoint,
+        )
+        corrlen = result["targval"]
+        fac_files.append(hyperfac_fname)
+        use_auto = False
+        if verbose:
+            if nrow is not None:
+                np.savetxt("corrlen.txt",corrlen.reshape(nrow,ncol),fmt="%15.6E")
+            else:
+                np.savetxt("corrlen.txt",corrlen,fmt="%15.6E")
+        results["corrlen"] = corrlen
+
+    if not verbose:
+        for fac_file in fac_files:
+            try:
+                os.remove(fac_file)
+            except Exception as e:
+                pass
+
+    # todo: maybe make these args?
+    fac_fname = "var.fac"
+    fac_ftype = "binary"
+    if verbose:
+        fac_ftype = "text"
+    noint = pp_info.loc[:, "value"].mean()
+    if use_auto:
+        npts = lib.calc_kriging_factors_auto_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            krigtype,
+            aniso.flatten(),
+            bearing.flatten(),
+            fac_fname,
+            fac_ftype,
+        )
+    else:
+        npts = lib.calc_kriging_factors_2d(
+            pp_info.x.values,
+            pp_info.y.values,
+            pp_info.zone.values,
+            x.flatten(),
+            y.flatten(),
+            zone_array.flatten().astype(int),
+            vartype,
+            krigtype,
+            corrlen.flatten(),
+            aniso.flatten(),
+            bearing.flatten(),
+            search_dist,
+            max_pts,
+            min_pts,
+            fac_fname,
+            fac_ftype,
+        )
+
+    result = lib.krige_using_file(
+        fac_fname,
+        fac_ftype,
+        nnodes,
+        krigtype,
+        vartransform,
+        pp_info.loc[:, "value"].values,
+        noint,
+        noint,
+    )
+    results["result"] = result["targval"]
+
+    if nrow is not None:
+        for k,v in results.items():
+            results[k] = v.reshape(nrow,ncol)
+
+    return results
+
+
+
+def mod2obs_mf6(gridinfo_fname: str, depvar_fname: str, obscsv_fname: str, model_type: int, start_datetime: str | pd.TimeStamp, depvar_ftype=1, depvar_name='head', interp_thresh=1e+30, no_interp_val=1e+30, model_timeunit='d', time_extrap=1.0) ‑> dict +
+
+

python implementation of mod2smp and mod2obs using modflow6 binary grid files +Parameters

+
+
+
gridinfo_fname : str
+
grid information file
+
depvar_fname : str
+
MODFLOW-6 output binary file
+
obscsv_fname : str | pd.DataFrame
+
observation information. +Must contain columns "site","x","y","datetime",and "layer"
+
model_type : int
+
type of model. +Must be either 31 (dis mf6) or 32 (disv mf6)
+
start_datetime : str | datetime
+
the simulation start datetime
+
depvar_ftype : int
+
the modflow-6 output file type. +1 for states, 2 or cell-by-cell budgets
+
depvar_name : str
+
the name of the dependent variable in depvar_fname to extract (for example "head")
+
interp_thresh : float
+
the upper limit above which extracted values are treated as invalid. +Default is 1.0+30
+
no_interp_val : float
+
value used to fill invalid/null extracted/interpolated values
+
model_time_unit : str
+
pandas style time unit. +Default is "d"ay
+
time_extrap : float
+
length of time units to extrapolate. +Default is 1.0 time unit
+
+

Returns

+
+
all_results : pd.DataFrame
+
all simulated times at observation locations (ie mod2smp)
+
interpolated_results : pd.DataFrame
+
temporally interpolated simulated results at observation locations (ie mod2obs)
+
+
+ +Expand source code + +
def mod2obs_mf6(gridinfo_fname: str,depvar_fname: str,obscsv_fname: str ,model_type: int,start_datetime: str | pd.TimeStamp,depvar_ftype=1,
+                depvar_name="head",interp_thresh=1.0e+30,no_interp_val=1.0e+30,model_timeunit="d",
+                time_extrap=1.0)->dict:
+
+    """python implementation of mod2smp and mod2obs using modflow6 binary grid files
+    Parameters
+    ----------
+    gridinfo_fname: str
+        grid information file
+    depvar_fname: str
+        MODFLOW-6 output binary file
+    obscsv_fname: str | pd.DataFrame
+        observation information.  Must contain columns "site","x","y","datetime",and "layer"
+    model_type: int
+        type of model.  Must be either 31 (dis mf6) or 32 (disv mf6)
+    start_datetime: str | datetime
+        the simulation start datetime
+    depvar_ftype : int
+        the modflow-6 output file type.  1 for states, 2 or cell-by-cell budgets
+    depvar_name: str
+        the name of the dependent variable in `depvar_fname` to extract (for example "head")
+    interp_thresh: float
+        the upper limit above which extracted values are treated as invalid.  Default is 1.0+30
+    no_interp_val: float
+        value used to fill invalid/null extracted/interpolated values
+    model_time_unit: str
+        pandas style time unit.  Default is "d"ay
+    time_extrap: float
+        length of time units to extrapolate.  Default is 1.0 time unit
+
+    Returns
+    -------
+    all_results: pd.DataFrame
+        all simulated times at observation locations (ie mod2smp)
+    interpolated_results: pd.DataFrame
+        temporally interpolated simulated results at observation locations (ie mod2obs)
+    """
+
+    for fname in [gridinfo_fname,depvar_fname]:
+        assert os.path.exists(fname),"file {0} not found".format(fname)
+    lib = PestUtilsLib()
+    is_mf6 = False
+    is_structured = True
+    model_type = int(model_type)
+    if model_type == 1:
+        is_mf6 = False
+    elif model_type == 21:
+        pass
+    elif model_type == 22:
+        is_structured = False
+    elif model_type == 31:
+        is_mf6 = True
+    elif model_type == 32:
+        is_mf6 = True
+        is_structured = False
+    elif model_type == 33:
+        is_mf6 = True
+        is_structured = False
+    else:
+        raise Exception("unrecognized 'model_type':{0}".format(model_type))
+
+    depvar_ftype = int(depvar_ftype)
+    if depvar_ftype not in [1,2]:
+        raise Exception("unrecognized 'depvar_ftype':{0}".format(depvar_ftype))
+
+    if is_mf6:
+        grid_info = lib.install_mf6_grid_from_file("grid",gridinfo_fname)
+    else:
+        raise NotImplementedError()
+    
+    if isinstance(start_datetime,str):
+        start_datetime = pd.to_datetime(start_datetime)
+
+    depvar_info = lib.inquire_modflow_binary_file_specs(depvar_fname,depvar_fname+".out.csv",model_type,depvar_ftype)    
+    depvar_df = pd.read_csv(depvar_fname+".out.csv")
+    depvar_df.columns = [c.lower() for c in depvar_df.columns]
+    #print(depvar_df)
+
+    if isinstance(obscsv_fname,str):
+        if not os.path.exists(obscsv_fname):
+            raise Exception("obscsv_fname '{0}' not found".format(obscsv_fname))
+        # todo: think about supporting a site sample file maybe?
+        obsdf = pd.read_csv(os.path.join(obscsv_fname),parse_dates=["datetime"])
+    elif isinstance(obscsv_fname,pd.DataFrame):
+        obsdf = obscsv_fname.copy()
+    else:
+        raise Exception("obscsv arg type not recognized (looking for str or pd.DataFrame):'{0}'".format(type(obscsv_fname)))
+    #check obsdf
+    obsdf.columns = [c.lower() for c in obsdf.columns]
+    for req_col in ["site","x","y","datetime","layer"]:
+        if req_col not in obsdf.columns:
+            raise Exception("observation dataframe missing column '{0}'".format(req_col))
+    usitedf = obsdf.groupby("site").first()
+    pth = os.path.split(depvar_fname)[0]
+    fac_file = os.path.join(pth,"obs_interp_fac.bin")
+    bln_file = fac_file.replace(".bin",".bln")
+    interp_fac_results = lib.calc_mf6_interp_factors("grid",usitedf.x.values,usitedf.y.values,usitedf.layer.values,fac_file,"binary",bln_file)
+    if 0 in interp_fac_results:
+        print("warning: the following site(s) failed to have interpolation factors calculated:")
+        fsites = usitedf.site.iloc[interp_fac_results==0].to_list()
+        print(fsites)
+    all_results = lib.interp_from_mf6_depvar_file(depvar_fname,fac_file,"binary",depvar_info["ntime"],"head",interp_thresh,True,
+        no_interp_val,usitedf.shape[0])
+    datetimes = start_datetime+pd.to_timedelta(all_results["simtime"],unit=model_timeunit)
+    allresults_df = pd.DataFrame(all_results["simstate"],index=datetimes,columns=usitedf.index)
+    allresults_df.to_csv(depvar_fname+".all.csv")
+
+    if "totim" in obsdf:
+        print("WARNING: replacing existing 'totim' column in observation dataframe")
+    obsdf.loc[:,"totim"] = obsdf.datetime.apply(lambda x: x  - start_datetime).dt.days 
+
+    usite = obsdf.site.unique()
+    usite.sort()
+    usite_dict = {s:c for s,c in zip(usite,np.arange(usite.shape[0],dtype=int))}
+    obsdf.loc[:,"isite"] = obsdf.site.apply(lambda x: usite_dict[x])
+    obsdf.sort_values(by=["isite","totim"],inplace=True)
+    
+    interp_results = lib.interp_to_obstime(all_results["nproctime"],all_results["simtime"],all_results["simstate"],interp_thresh,"L",
+        time_extrap,no_interp_val,obsdf.isite.values,obsdf.totim.values)
+
+    obsdf.loc[:,"simulated"] = interp_results
+    lib.uninstall_mf6_grid('grid')
+    lib.free_all_memory()
+    return {"all_results":allresults_df,"interpolated_results":obsdf}
+
+
+
+
+
+

Classes

+
+
+class SpatialReference +(delr, delc, xul, yul, rotation=0.0) +
+
+

a class to locate a structured model grid in x-y space.

+

Parameters

+
+
delr :  numpy.ndarray
+
the model discretization delr vector (An array of spacings along a row)
+
delc : numpy ndarray
+
the model discretization delc vector (An array of spacings along a column)
+
xul : float
+
The x coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll.
+
yul : float
+
The y coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll.
+
rotation : float
+
The counter-clockwise rotation (in degrees) of the grid
+
+
+ +Expand source code + +
class SpatialReference(object):
+    """
+    a class to locate a structured model grid in x-y space.
+
+    Parameters
+    ----------
+    delr:  numpy.ndarray
+        the model discretization delr vector (An array of spacings along a row)
+    delc: numpy ndarray
+        the model discretization delc vector (An array of spacings along a column)
+    xul: float
+        The x coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll.
+    yul: float
+        The y coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll.
+    rotation: float
+        The counter-clockwise rotation (in degrees) of the grid
+    """
+
+    def __init__(self, delr, delc, xul, yul, rotation=0.0):
+        self.xul = float(xul)
+        self.yul = float(yul)
+        self.rotation = float(rotation)
+        for delrc in [delr, delc]:
+            if isinstance(delrc, float) or isinstance(delrc, int):
+                msg = (
+                    "delr and delcs must be an array or sequences equal in "
+                    "length to the number of rows/columns."
+                )
+                raise TypeError(msg)
+
+        self.delc = np.atleast_1d(np.array(delc)).astype(np.float64)
+        self.delr = np.atleast_1d(np.array(delr)).astype(np.float64)
+
+        self._xgrid = None
+        self._ygrid = None
+        self._xcentergrid = None
+        self._ycentergrid = None
+
+    @property
+    def xll(self)->float:
+        """lower left x coord
+        """
+        xll = self.xul - (np.sin(self.theta) * self.yedge[0])
+        return xll
+
+    @property
+    def yll(self)->float:
+        """lower left y coord
+        """
+        yll = self.yul - (np.cos(self.theta) * self.yedge[0])
+        return yll
+
+    @property
+    def nrow(self)->int:
+        """number of rows
+        """
+        return self.delc.shape[0]
+
+    @property
+    def ncol(self)->int:
+        """number of cols
+        """
+        return self.delr.shape[0]
+
+    @classmethod
+    def from_gridspec(cls, gridspec_file)->SpatialReference:
+        """instantiate from a pest-style grid specification file
+        Parameters
+        ----------
+        gridspec_file: str
+            grid specification file name
+
+        Returns
+        -------
+        sr: SpatialReference
+            sr instance
+        """
+        f = open(gridspec_file, "r")
+        raw = f.readline().strip().split()
+        nrow = int(raw[0])
+        ncol = int(raw[1])
+        raw = f.readline().strip().split()
+        xul, yul, rot = float(raw[0]), float(raw[1]), float(raw[2])
+        delr = []
+        j = 0
+        while j < ncol:
+            raw = f.readline().strip().split()
+            for r in raw:
+                if "*" in r:
+                    rraw = r.split("*")
+                    for n in range(int(rraw[0])):
+                        delr.append(float(rraw[1]))
+                        j += 1
+                else:
+                    delr.append(float(r))
+                    j += 1
+        delc = []
+        i = 0
+        while i < nrow:
+            raw = f.readline().strip().split()
+            for r in raw:
+                if "*" in r:
+                    rraw = r.split("*")
+                    for n in range(int(rraw[0])):
+                        delc.append(float(rraw[1]))
+                        i += 1
+                else:
+                    delc.append(float(r))
+                    i += 1
+        f.close()
+        return cls(np.array(delr), np.array(delc), xul=xul, yul=yul, rotation=rot)
+
+    @property
+    def theta(self)->float:
+        """rotation in radians
+        """
+        return -self.rotation * np.pi / 180.0
+
+    @property
+    def xedge(self)->np.NDArray[float]:
+        """the xedge array of the grid
+        """
+        return self.get_xedge_array()
+
+    @property
+    def yedge(self)->np.NDArray[float]:
+        """the yedge array of the grid
+        """
+        return self.get_yedge_array()
+
+    @property
+    def xgrid(self)->np.NDArray[float]:
+        """xgrid array
+        """
+        if self._xgrid is None:
+            self._set_xygrid()
+        return self._xgrid
+
+    @property
+    def ygrid(self)->np.NDArray[float]:
+        """ygrid array
+        """
+        if self._ygrid is None:
+            self._set_xygrid()
+        return self._ygrid
+
+    @property
+    def xcenter(self)->np.NDArray[float]:
+        """grid x center array
+        """
+        return self.get_xcenter_array()
+
+    @property
+    def ycenter(self)->np.NDArray[float]:
+        """grid y center array
+        """
+        return self.get_ycenter_array()
+
+    @property
+    def ycentergrid(self)->np.NDArray[float]:
+        """grid y center array
+        """
+        if self._ycentergrid is None:
+            self._set_xycentergrid()
+        return self._ycentergrid
+
+    @property
+    def xcentergrid(self)->np.NDArray[float]:
+        """grid x center array
+        """
+        if self._xcentergrid is None:
+            self._set_xycentergrid()
+        return self._xcentergrid
+
+    @property
+    def areagrid(self)->np.NDArray[float]:
+        """area of grid nodes
+        """
+        dr, dc = np.meshgrid(self.delr, self.delc)
+        return dr * dc
+
+    def _set_xycentergrid(self):
+        self._xcentergrid, self._ycentergrid = np.meshgrid(self.xcenter, self.ycenter)
+        self._xcentergrid, self._ycentergrid = self.transform(
+            self._xcentergrid, self._ycentergrid
+        )
+
+    def _set_xygrid(self):
+        self._xgrid, self._ygrid = np.meshgrid(self.xedge, self.yedge)
+        self._xgrid, self._ygrid = self.transform(self._xgrid, self._ygrid)
+
+    def get_xedge_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell edge x
+        coordinates for every column in the grid in model space - not offset
+        or rotated.  Array is of size (ncol + 1)
+
+        """
+        assert self.delr is not None and len(self.delr) > 0, (
+            "delr not passed to " "spatial reference object"
+        )
+        xedge = np.concatenate(([0.0], np.add.accumulate(self.delr)))
+        return xedge
+
+    def get_yedge_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell edge y
+        coordinates for every row in the grid in model space - not offset or
+        rotated. Array is of size (nrow + 1)
+
+        """
+        assert self.delc is not None and len(self.delc) > 0, (
+            "delc not passed to " "spatial reference object"
+        )
+        length_y = np.add.reduce(self.delc)
+        yedge = np.concatenate(([length_y], length_y - np.add.accumulate(self.delc)))
+        return yedge
+
+    def get_xcenter_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell center x
+        coordinate for every column in the grid in model space - not offset or rotated.
+
+        """
+        assert self.delr is not None and len(self.delr) > 0, (
+            "delr not passed to " "spatial reference object"
+        )
+        x = np.add.accumulate(self.delr) - 0.5 * self.delr
+        return x
+
+    def get_ycenter_array(self)->np.NDArray[float]:
+        """
+        a numpy one-dimensional float array that has the cell center x
+        coordinate for every row in the grid in model space - not offset of rotated.
+
+        """
+        assert self.delc is not None and len(self.delc) > 0, (
+            "delc not passed to " "spatial reference object"
+        )
+        Ly = np.add.reduce(self.delc)
+        y = Ly - (np.add.accumulate(self.delc) - 0.5 * self.delc)
+        return y
+
+    @staticmethod
+    def rotate(x, y, theta, xorigin=0.0, yorigin=0.0):
+        """
+        Given x and y array-like values calculate the rotation about an
+        arbitrary origin and then return the rotated coordinates.  theta is in
+        degrees.
+
+        """
+        # jwhite changed on Oct 11 2016 - rotation is now positive CCW
+        # theta = -theta * np.pi / 180.
+        theta = theta * np.pi / 180.0
+
+        xrot = xorigin + np.cos(theta) * (x - xorigin) - np.sin(theta) * (y - yorigin)
+        yrot = yorigin + np.sin(theta) * (x - xorigin) + np.cos(theta) * (y - yorigin)
+        return xrot, yrot
+
+    def transform(self, x, y, inverse=False):
+        """
+        Given x and y array-like values, apply rotation, scale and offset,
+        to convert them from model coordinates to real-world coordinates.
+        """
+        if isinstance(x, list):
+            x = np.array(x)
+            y = np.array(y)
+        if not np.isscalar(x):
+            x, y = x.copy(), y.copy()
+
+        if not inverse:
+            x += self.xll
+            y += self.yll
+            x, y = SpatialReference.rotate(
+                x, y, theta=self.rotation, xorigin=self.xll, yorigin=self.yll
+            )
+        else:
+            x, y = SpatialReference.rotate(x, y, -self.rotation, self.xll, self.yll)
+            x -= self.xll
+            y -= self.yll
+        return x, y
+
+    def get_extent(self)->tuple[float]:
+        """
+        Get the extent of the rotated and offset grid
+
+        """
+        x0 = self.xedge[0]
+        x1 = self.xedge[-1]
+        y0 = self.yedge[0]
+        y1 = self.yedge[-1]
+
+        # upper left point
+        x0r, y0r = self.transform(x0, y0)
+
+        # upper right point
+        x1r, y1r = self.transform(x1, y0)
+
+        # lower right point
+        x2r, y2r = self.transform(x1, y1)
+
+        # lower left point
+        x3r, y3r = self.transform(x0, y1)
+
+        xmin = min(x0r, x1r, x2r, x3r)
+        xmax = max(x0r, x1r, x2r, x3r)
+        ymin = min(y0r, y1r, y2r, y3r)
+        ymax = max(y0r, y1r, y2r, y3r)
+
+        return (xmin, xmax, ymin, ymax)
+
+    def get_vertices(self, i, j)->list[list[float]]:
+        """Get vertices for a single cell or sequence if i, j locations."""
+        pts = []
+        xgrid, ygrid = self.xgrid, self.ygrid
+        pts.append([xgrid[i, j], ygrid[i, j]])
+        pts.append([xgrid[i + 1, j], ygrid[i + 1, j]])
+        pts.append([xgrid[i + 1, j + 1], ygrid[i + 1, j + 1]])
+        pts.append([xgrid[i, j + 1], ygrid[i, j + 1]])
+        pts.append([xgrid[i, j], ygrid[i, j]])
+        if np.isscalar(i):
+            return pts
+        else:
+            vrts = np.array(pts).transpose([2, 0, 1])
+            return [v.tolist() for v in vrts]
+
+    def get_ij(self, x, y)->tuple(int):
+        """Return the row and column of a point or sequence of points
+        in real-world coordinates.
+
+        """
+        if np.isscalar(x):
+            c = (np.abs(self.xcentergrid[0] - x)).argmin()
+            r = (np.abs(self.ycentergrid[:, 0] - y)).argmin()
+        else:
+            xcp = np.array([self.xcentergrid[0]] * (len(x)))
+            ycp = np.array([self.ycentergrid[:, 0]] * (len(x)))
+            c = (np.abs(xcp.transpose() - x)).argmin(axis=0)
+            r = (np.abs(ycp.transpose() - y)).argmin(axis=0)
+        return r, c
+
+    def write_gridspec(self, filename):
+
+        """write a PEST-style grid specification file
+        Parameters
+        ----------
+        filename: str
+            file to write
+
+
+        """
+        f = open(filename, "w")
+        f.write("{0:10d} {1:10d}\n".format(self.delc.shape[0], self.delr.shape[0]))
+        f.write(
+            "{0:15.6E} {1:15.6E} {2:15.6E}\n".format(
+                self.xul,
+                self.yul,
+                self.rotation,
+            )
+        )
+
+        for r in self.delr:
+            f.write("{0:15.6E} ".format(r))
+        f.write("\n")
+        for c in self.delc:
+            f.write("{0:15.6E} ".format(c))
+        f.write("\n")
+        return
+
+

Static methods

+
+
+def from_gridspec(gridspec_file) ‑> SpatialReference +
+
+

instantiate from a pest-style grid specification file +Parameters

+
+
+
gridspec_file : str
+
grid specification file name
+
+

Returns

+
+
sr : SpatialReference
+
sr instance
+
+
+ +Expand source code + +
@classmethod
+def from_gridspec(cls, gridspec_file)->SpatialReference:
+    """instantiate from a pest-style grid specification file
+    Parameters
+    ----------
+    gridspec_file: str
+        grid specification file name
+
+    Returns
+    -------
+    sr: SpatialReference
+        sr instance
+    """
+    f = open(gridspec_file, "r")
+    raw = f.readline().strip().split()
+    nrow = int(raw[0])
+    ncol = int(raw[1])
+    raw = f.readline().strip().split()
+    xul, yul, rot = float(raw[0]), float(raw[1]), float(raw[2])
+    delr = []
+    j = 0
+    while j < ncol:
+        raw = f.readline().strip().split()
+        for r in raw:
+            if "*" in r:
+                rraw = r.split("*")
+                for n in range(int(rraw[0])):
+                    delr.append(float(rraw[1]))
+                    j += 1
+            else:
+                delr.append(float(r))
+                j += 1
+    delc = []
+    i = 0
+    while i < nrow:
+        raw = f.readline().strip().split()
+        for r in raw:
+            if "*" in r:
+                rraw = r.split("*")
+                for n in range(int(rraw[0])):
+                    delc.append(float(rraw[1]))
+                    i += 1
+            else:
+                delc.append(float(r))
+                i += 1
+    f.close()
+    return cls(np.array(delr), np.array(delc), xul=xul, yul=yul, rotation=rot)
+
+
+
+def rotate(x, y, theta, xorigin=0.0, yorigin=0.0) +
+
+

Given x and y array-like values calculate the rotation about an +arbitrary origin and then return the rotated coordinates. +theta is in +degrees.

+
+ +Expand source code + +
@staticmethod
+def rotate(x, y, theta, xorigin=0.0, yorigin=0.0):
+    """
+    Given x and y array-like values calculate the rotation about an
+    arbitrary origin and then return the rotated coordinates.  theta is in
+    degrees.
+
+    """
+    # jwhite changed on Oct 11 2016 - rotation is now positive CCW
+    # theta = -theta * np.pi / 180.
+    theta = theta * np.pi / 180.0
+
+    xrot = xorigin + np.cos(theta) * (x - xorigin) - np.sin(theta) * (y - yorigin)
+    yrot = yorigin + np.sin(theta) * (x - xorigin) + np.cos(theta) * (y - yorigin)
+    return xrot, yrot
+
+
+
+

Instance variables

+
+
var areagrid : np.NDArray[float]
+
+

area of grid nodes

+
+ +Expand source code + +
@property
+def areagrid(self)->np.NDArray[float]:
+    """area of grid nodes
+    """
+    dr, dc = np.meshgrid(self.delr, self.delc)
+    return dr * dc
+
+
+
var ncol : int
+
+

number of cols

+
+ +Expand source code + +
@property
+def ncol(self)->int:
+    """number of cols
+    """
+    return self.delr.shape[0]
+
+
+
var nrow : int
+
+

number of rows

+
+ +Expand source code + +
@property
+def nrow(self)->int:
+    """number of rows
+    """
+    return self.delc.shape[0]
+
+
+
var theta : float
+
+

rotation in radians

+
+ +Expand source code + +
@property
+def theta(self)->float:
+    """rotation in radians
+    """
+    return -self.rotation * np.pi / 180.0
+
+
+
var xcenter : np.NDArray[float]
+
+

grid x center array

+
+ +Expand source code + +
@property
+def xcenter(self)->np.NDArray[float]:
+    """grid x center array
+    """
+    return self.get_xcenter_array()
+
+
+
var xcentergrid : np.NDArray[float]
+
+

grid x center array

+
+ +Expand source code + +
@property
+def xcentergrid(self)->np.NDArray[float]:
+    """grid x center array
+    """
+    if self._xcentergrid is None:
+        self._set_xycentergrid()
+    return self._xcentergrid
+
+
+
var xedge : np.NDArray[float]
+
+

the xedge array of the grid

+
+ +Expand source code + +
@property
+def xedge(self)->np.NDArray[float]:
+    """the xedge array of the grid
+    """
+    return self.get_xedge_array()
+
+
+
var xgrid : np.NDArray[float]
+
+

xgrid array

+
+ +Expand source code + +
@property
+def xgrid(self)->np.NDArray[float]:
+    """xgrid array
+    """
+    if self._xgrid is None:
+        self._set_xygrid()
+    return self._xgrid
+
+
+
var xll : float
+
+

lower left x coord

+
+ +Expand source code + +
@property
+def xll(self)->float:
+    """lower left x coord
+    """
+    xll = self.xul - (np.sin(self.theta) * self.yedge[0])
+    return xll
+
+
+
var ycenter : np.NDArray[float]
+
+

grid y center array

+
+ +Expand source code + +
@property
+def ycenter(self)->np.NDArray[float]:
+    """grid y center array
+    """
+    return self.get_ycenter_array()
+
+
+
var ycentergrid : np.NDArray[float]
+
+

grid y center array

+
+ +Expand source code + +
@property
+def ycentergrid(self)->np.NDArray[float]:
+    """grid y center array
+    """
+    if self._ycentergrid is None:
+        self._set_xycentergrid()
+    return self._ycentergrid
+
+
+
var yedge : np.NDArray[float]
+
+

the yedge array of the grid

+
+ +Expand source code + +
@property
+def yedge(self)->np.NDArray[float]:
+    """the yedge array of the grid
+    """
+    return self.get_yedge_array()
+
+
+
var ygrid : np.NDArray[float]
+
+

ygrid array

+
+ +Expand source code + +
@property
+def ygrid(self)->np.NDArray[float]:
+    """ygrid array
+    """
+    if self._ygrid is None:
+        self._set_xygrid()
+    return self._ygrid
+
+
+
var yll : float
+
+

lower left y coord

+
+ +Expand source code + +
@property
+def yll(self)->float:
+    """lower left y coord
+    """
+    yll = self.yul - (np.cos(self.theta) * self.yedge[0])
+    return yll
+
+
+
+

Methods

+
+
+def get_extent(self) ‑> tuple[float] +
+
+

Get the extent of the rotated and offset grid

+
+ +Expand source code + +
def get_extent(self)->tuple[float]:
+    """
+    Get the extent of the rotated and offset grid
+
+    """
+    x0 = self.xedge[0]
+    x1 = self.xedge[-1]
+    y0 = self.yedge[0]
+    y1 = self.yedge[-1]
+
+    # upper left point
+    x0r, y0r = self.transform(x0, y0)
+
+    # upper right point
+    x1r, y1r = self.transform(x1, y0)
+
+    # lower right point
+    x2r, y2r = self.transform(x1, y1)
+
+    # lower left point
+    x3r, y3r = self.transform(x0, y1)
+
+    xmin = min(x0r, x1r, x2r, x3r)
+    xmax = max(x0r, x1r, x2r, x3r)
+    ymin = min(y0r, y1r, y2r, y3r)
+    ymax = max(y0r, y1r, y2r, y3r)
+
+    return (xmin, xmax, ymin, ymax)
+
+
+
+def get_ij(self, x, y) ‑> tuple(int) +
+
+

Return the row and column of a point or sequence of points +in real-world coordinates.

+
+ +Expand source code + +
def get_ij(self, x, y)->tuple(int):
+    """Return the row and column of a point or sequence of points
+    in real-world coordinates.
+
+    """
+    if np.isscalar(x):
+        c = (np.abs(self.xcentergrid[0] - x)).argmin()
+        r = (np.abs(self.ycentergrid[:, 0] - y)).argmin()
+    else:
+        xcp = np.array([self.xcentergrid[0]] * (len(x)))
+        ycp = np.array([self.ycentergrid[:, 0]] * (len(x)))
+        c = (np.abs(xcp.transpose() - x)).argmin(axis=0)
+        r = (np.abs(ycp.transpose() - y)).argmin(axis=0)
+    return r, c
+
+
+
+def get_vertices(self, i, j) ‑> list[list[float]] +
+
+

Get vertices for a single cell or sequence if i, j locations.

+
+ +Expand source code + +
def get_vertices(self, i, j)->list[list[float]]:
+    """Get vertices for a single cell or sequence if i, j locations."""
+    pts = []
+    xgrid, ygrid = self.xgrid, self.ygrid
+    pts.append([xgrid[i, j], ygrid[i, j]])
+    pts.append([xgrid[i + 1, j], ygrid[i + 1, j]])
+    pts.append([xgrid[i + 1, j + 1], ygrid[i + 1, j + 1]])
+    pts.append([xgrid[i, j + 1], ygrid[i, j + 1]])
+    pts.append([xgrid[i, j], ygrid[i, j]])
+    if np.isscalar(i):
+        return pts
+    else:
+        vrts = np.array(pts).transpose([2, 0, 1])
+        return [v.tolist() for v in vrts]
+
+
+
+def get_xcenter_array(self) ‑> np.NDArray[float] +
+
+

a numpy one-dimensional float array that has the cell center x +coordinate for every column in the grid in model space - not offset or rotated.

+
+ +Expand source code + +
def get_xcenter_array(self)->np.NDArray[float]:
+    """
+    a numpy one-dimensional float array that has the cell center x
+    coordinate for every column in the grid in model space - not offset or rotated.
+
+    """
+    assert self.delr is not None and len(self.delr) > 0, (
+        "delr not passed to " "spatial reference object"
+    )
+    x = np.add.accumulate(self.delr) - 0.5 * self.delr
+    return x
+
+
+
+def get_xedge_array(self) ‑> np.NDArray[float] +
+
+

a numpy one-dimensional float array that has the cell edge x +coordinates for every column in the grid in model space - not offset +or rotated. +Array is of size (ncol + 1)

+
+ +Expand source code + +
def get_xedge_array(self)->np.NDArray[float]:
+    """
+    a numpy one-dimensional float array that has the cell edge x
+    coordinates for every column in the grid in model space - not offset
+    or rotated.  Array is of size (ncol + 1)
+
+    """
+    assert self.delr is not None and len(self.delr) > 0, (
+        "delr not passed to " "spatial reference object"
+    )
+    xedge = np.concatenate(([0.0], np.add.accumulate(self.delr)))
+    return xedge
+
+
+
+def get_ycenter_array(self) ‑> np.NDArray[float] +
+
+

a numpy one-dimensional float array that has the cell center x +coordinate for every row in the grid in model space - not offset of rotated.

+
+ +Expand source code + +
def get_ycenter_array(self)->np.NDArray[float]:
+    """
+    a numpy one-dimensional float array that has the cell center x
+    coordinate for every row in the grid in model space - not offset of rotated.
+
+    """
+    assert self.delc is not None and len(self.delc) > 0, (
+        "delc not passed to " "spatial reference object"
+    )
+    Ly = np.add.reduce(self.delc)
+    y = Ly - (np.add.accumulate(self.delc) - 0.5 * self.delc)
+    return y
+
+
+
+def get_yedge_array(self) ‑> np.NDArray[float] +
+
+

a numpy one-dimensional float array that has the cell edge y +coordinates for every row in the grid in model space - not offset or +rotated. Array is of size (nrow + 1)

+
+ +Expand source code + +
def get_yedge_array(self)->np.NDArray[float]:
+    """
+    a numpy one-dimensional float array that has the cell edge y
+    coordinates for every row in the grid in model space - not offset or
+    rotated. Array is of size (nrow + 1)
+
+    """
+    assert self.delc is not None and len(self.delc) > 0, (
+        "delc not passed to " "spatial reference object"
+    )
+    length_y = np.add.reduce(self.delc)
+    yedge = np.concatenate(([length_y], length_y - np.add.accumulate(self.delc)))
+    return yedge
+
+
+
+def transform(self, x, y, inverse=False) +
+
+

Given x and y array-like values, apply rotation, scale and offset, +to convert them from model coordinates to real-world coordinates.

+
+ +Expand source code + +
def transform(self, x, y, inverse=False):
+    """
+    Given x and y array-like values, apply rotation, scale and offset,
+    to convert them from model coordinates to real-world coordinates.
+    """
+    if isinstance(x, list):
+        x = np.array(x)
+        y = np.array(y)
+    if not np.isscalar(x):
+        x, y = x.copy(), y.copy()
+
+    if not inverse:
+        x += self.xll
+        y += self.yll
+        x, y = SpatialReference.rotate(
+            x, y, theta=self.rotation, xorigin=self.xll, yorigin=self.yll
+        )
+    else:
+        x, y = SpatialReference.rotate(x, y, -self.rotation, self.xll, self.yll)
+        x -= self.xll
+        y -= self.yll
+    return x, y
+
+
+
+def write_gridspec(self, filename) +
+
+

write a PEST-style grid specification file +Parameters

+
+
+
filename : str
+
file to write
+
+
+ +Expand source code + +
def write_gridspec(self, filename):
+
+    """write a PEST-style grid specification file
+    Parameters
+    ----------
+    filename: str
+        file to write
+
+
+    """
+    f = open(filename, "w")
+    f.write("{0:10d} {1:10d}\n".format(self.delc.shape[0], self.delr.shape[0]))
+    f.write(
+        "{0:15.6E} {1:15.6E} {2:15.6E}\n".format(
+            self.xul,
+            self.yul,
+            self.rotation,
+        )
+    )
+
+    for r in self.delr:
+        f.write("{0:15.6E} ".format(r))
+    f.write("\n")
+    for c in self.delc:
+        f.write("{0:15.6E} ".format(c))
+    f.write("\n")
+    return
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..9c330da --- /dev/null +++ b/docs/index.html @@ -0,0 +1,101 @@ + + + + + + +pypestutils API documentation + + + + + + + + + + + +
+
+
+

Package pypestutils

+
+
+
+ +Expand source code + +
from .version import __version__
+
+
+
+

Sub-modules

+
+
pypestutils.ctypes_declarations
+
+

Low-level Fortran-Python ctypes functions.

+
+
pypestutils.data
+
+

Data module.

+
+
pypestutils.enum
+
+

Enumeration module.

+
+
pypestutils.finder
+
+

Locate pestutils shared library by any means necessary.

+
+
pypestutils.helpers
+
+
+
+
pypestutils.logger
+
+

Logger module.

+
+
pypestutils.pestutilslib
+
+

Mid-level pestutilslib module to implement ctypes functions.

+
+
pypestutils.version
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/logger.html b/docs/logger.html new file mode 100644 index 0000000..72c70d9 --- /dev/null +++ b/docs/logger.html @@ -0,0 +1,154 @@ + + + + + + +pypestutils.logger API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.logger

+
+
+

Logger module.

+
+ +Expand source code + +
"""Logger module."""
+from __future__ import annotations
+
+__all__ = ["get_logger"]
+
+import logging
+import sys
+from logging import Logger
+
+
+def get_logger(name: str, level: str | int = 0) -> Logger:
+    """Return a named logger.
+
+    Parameters
+    ----------
+    name : str
+        Logger name.
+    logger_level : str, int, optional
+        Logger level, default 0 ("NOTSET"). Accepted values are
+        "DEBUG" (10), "INFO" (20), "WARNING" (30), "ERROR" (40) or
+        "CRITICAL" (50).
+
+    Returns
+    -------
+    Logger
+    """
+    logger = logging.getLogger(name)
+    logger.setLevel(level)
+
+    if not logger.hasHandlers():
+        formatter = logging.Formatter("%(levelname)s:%(name)s: %(message)s")
+        handler = logging.StreamHandler(sys.stdout)
+        handler.setFormatter(formatter)
+        logger.addHandler(handler)
+
+    return logger
+
+
+
+
+
+
+
+

Functions

+
+
+def get_logger(name: str, level: str | int = 0) ‑> logging.Logger +
+
+

Return a named logger.

+

Parameters

+
+
name : str
+
Logger name.
+
logger_level : str, int, optional
+
Logger level, default 0 ("NOTSET"). Accepted values are +"DEBUG" (10), "INFO" (20), "WARNING" (30), "ERROR" (40) or +"CRITICAL" (50).
+
+

Returns

+
+
Logger
+
 
+
+
+ +Expand source code + +
def get_logger(name: str, level: str | int = 0) -> Logger:
+    """Return a named logger.
+
+    Parameters
+    ----------
+    name : str
+        Logger name.
+    logger_level : str, int, optional
+        Logger level, default 0 ("NOTSET"). Accepted values are
+        "DEBUG" (10), "INFO" (20), "WARNING" (30), "ERROR" (40) or
+        "CRITICAL" (50).
+
+    Returns
+    -------
+    Logger
+    """
+    logger = logging.getLogger(name)
+    logger.setLevel(level)
+
+    if not logger.hasHandlers():
+        formatter = logging.Formatter("%(levelname)s:%(name)s: %(message)s")
+        handler = logging.StreamHandler(sys.stdout)
+        handler.setFormatter(formatter)
+        logger.addHandler(handler)
+
+    return logger
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/pestutilslib.html b/docs/pestutilslib.html new file mode 100644 index 0000000..67763ad --- /dev/null +++ b/docs/pestutilslib.html @@ -0,0 +1,6500 @@ + + + + + + +pypestutils.pestutilslib API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.pestutilslib

+
+
+

Mid-level pestutilslib module to implement ctypes functions.

+
+ +Expand source code + +
"""Mid-level pestutilslib module to implement ctypes functions."""
+from __future__ import annotations
+
+import logging
+from ctypes import byref, c_char, c_double, c_int, create_string_buffer
+from os import PathLike
+from pathlib import Path
+
+import numpy as np
+import numpy.typing as npt
+
+from . import enum
+from .data import ManyArrays, validate_scalar
+
+
+class PestUtilsLibError(BaseException):
+    """Exception from PestUtilsLib."""
+
+    pass
+
+
+class PestUtilsLib:
+    """Mid-level Fortran-Python handler for pestutils library via ctypes.
+
+    Parameters
+    ----------
+    logger_level : int, str, default 20 (INFO)
+    """
+
+    def __init__(self, *, logger_level=logging.INFO) -> None:
+        from .ctypes_declarations import prototype
+        from .finder import load
+        from .logger import get_logger
+
+        self.logger = get_logger(self.__class__.__name__, logger_level)
+        self.pestutils = load()
+        self.logger.debug("loaded %s", self.pestutils)
+        prototype(self.pestutils)
+        self.logger.debug("added prototypes")
+
+    # def __del__(self):
+    #    """Clean-up library instance."""
+    #    try:
+    #        self.free_all_memory()
+    #    except (AttributeError, PestUtilsLibError) as err:
+    #        if hasattr(self, "logger"):
+    #            self.logger.warning("cannot call __del__: %s", err)
+
+    def create_char_array(self, init: str | bytes, name: str):
+        """Create c_char Array with a fixed size from dimvar and intial value.
+
+        Parameters
+        ----------
+        init : str or bytes
+            Initial value.
+        name : str
+            Uppercase variable length name, e.g. LENFILENAME or LENVARTYPE.
+        """
+        from .ctypes_declarations import get_dimvar_int
+
+        if isinstance(init, str):
+            init = init.encode()
+        elif isinstance(init, bytes):
+            pass
+        else:
+            raise TypeError(f"expecting either str or bytes; found {type(init)}")
+        size = get_dimvar_int(self.pestutils, name)
+        if len(init) > size:
+            raise ValueError(f"init size is {len(init)} but {name} is {size}")
+        return create_string_buffer(init, size)
+
+    def inquire_modflow_binary_file_specs(
+        self,
+        filein: str | PathLike,
+        fileout: str | PathLike | None,
+        isim: int,
+        itype: int,
+    ) -> dict:
+        """Report some of the details of a MODFLOW-written binary file.
+
+        Parameters
+        ----------
+        filein : str or PathLike
+            MODFLOW-generated binary file to be read.
+        fileout : str, PathLike, None
+            Output file with with table of array headers. Use None or "" for
+            no output file.
+        isim : int
+            Inform the function the simulator that generated the binary file:
+
+             * 1 = traditional MODFLOW
+             * 21 = MODFLOW-USG with structured grid
+             * 22 = MODFLOW-USG with unstructured grid
+             * 31 = MODFLOW 6 with DIS grid
+             * 32 = MODFLOW 6 with DISV grid
+             * 33 = MODFLOW 6 with DISU grid
+
+        itype : int
+            Where 1 = system state or dependent variable;
+            2 = cell-by-cell flows.
+
+        Returns
+        -------
+        iprec : int
+            Where 1 = single; 2 = double.
+        narray : int
+            Number of arrays.
+        ntime : int
+            Number of times.
+        """
+        filein = Path(filein)
+        if not filein.is_file():
+            raise FileNotFoundError(f"could not find filein {filein}")
+        if fileout:
+            fileout = Path(fileout)
+        else:
+            fileout = b""
+        validate_scalar("isim", isim, isin=[1, 21, 22, 31, 32, 33])
+        validate_scalar("itype", itype, isin=[1, 2])
+        iprec = c_int()
+        narray = c_int()
+        ntime = c_int()
+        res = self.pestutils.inquire_modflow_binary_file_specs(
+            byref(self.create_char_array(bytes(filein), "LENFILENAME")),
+            byref(self.create_char_array(bytes(fileout), "LENFILENAME")),
+            byref(c_int(isim)),
+            byref(c_int(itype)),
+            byref(iprec),
+            byref(narray),
+            byref(ntime),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("inquired modflow binary file specs from %r", filein.name)
+        return {
+            "iprec": iprec.value,
+            "narray": narray.value,
+            "ntime": ntime.value,
+        }
+
+    def retrieve_error_message(self) -> str:
+        """Retrieve error message from library.
+
+        Returns
+        -------
+        str
+        """
+        from .ctypes_declarations import get_char_array
+
+        charray = get_char_array(self.pestutils, "LENMESSAGE")()
+        res = self.pestutils.retrieve_error_message(byref(charray))
+        return charray[:res].rstrip(b"\x00").decode()
+
+    def install_structured_grid(
+        self,
+        gridname: str,
+        ncol: int,
+        nrow: int,
+        nlay: int,
+        icorner: int,
+        e0: float,
+        n0: float,
+        rotation: float,
+        delr: float | npt.ArrayLike,
+        delc: float | npt.ArrayLike,
+    ) -> None:
+        """Install specifications for a structured grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        ncol, nrow, nlay : int
+            Grid dimensions.
+        icorner : int
+            Reference corner, use 1 for top left and 2 for bottom left.
+        e0, n0 : float
+            Reference offsets.
+        rotation : float
+            Grid rotation, counter-clockwise degrees.
+        """
+        validate_scalar("ncol", ncol, gt=0)
+        validate_scalar("nrow", nrow, gt=0)
+        validate_scalar("nlay", nlay, gt=0)
+        col = ManyArrays(float_any={"delr": delr}, ar_len=ncol)
+        row = ManyArrays(float_any={"delc": delc}, ar_len=nrow)
+        col.validate("delr", gt=0.0)
+        row.validate("delc", gt=0.0)
+        validate_scalar("icorner", icorner, isin=[1, 2])
+        res = self.pestutils.install_structured_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(ncol)),
+            byref(c_int(nrow)),
+            byref(c_int(nlay)),
+            byref(c_int(icorner)),
+            byref(c_double(e0)),
+            byref(c_double(n0)),
+            byref(c_double(rotation)),
+            col.delr,
+            row.delc,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("installed structured grid %r from specs", gridname)
+
+    def get_cell_centres_structured(self, gridname: str, ncpl: int) -> tuple:
+        """Get cell centres of a single layer of an installed structured grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Name of installed structured grid.
+        ncpl : int
+            Dimensions of grid (nrow x ncol).
+
+        Returns
+        -------
+        cellx, cellx : npt.NDArray[np.float64]
+            Coordinates of cell centres with dimensions (ncpl,).
+        """
+        cellx = np.zeros(ncpl, np.float64, order="F")
+        celly = np.zeros(ncpl, np.float64, order="F")
+        res = self.pestutils.get_cell_centres_structured(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(ncpl)),
+            cellx,
+            celly,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "evaluated %d cell centres from structured grid %r", ncpl, gridname
+        )
+        return cellx.copy("A"), celly.copy("A")
+
+    def uninstall_structured_grid(self, gridname: str) -> None:
+        """Uninstall structured grid set by :meth:`install_structured_grid`.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        """
+        res = self.pestutils.uninstall_structured_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME"))
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("uninstalled structured grid %r", gridname)
+
+    def free_all_memory(self) -> None:
+        """Deallocate all memory that is being used."""
+        ret = self.pestutils.free_all_memory()
+        if ret != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("all memory was freed up")
+
+    def interp_from_structured_grid(
+        self,
+        gridname: str,
+        depvarfile: str | PathLike,
+        isim: int,
+        iprec: int | str | enum.Prec,
+        ntime: int,
+        vartype: str,
+        interpthresh: float,
+        nointerpval: float,
+        # npts: int,  # determined from layer.shape[0]
+        ecoord: npt.ArrayLike,
+        ncoord: npt.ArrayLike,
+        layer: int | npt.ArrayLike,
+    ) -> dict:
+        """Spatial interpolate points from a structured grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Name of installed structured grid.
+        depvarfile : str or PathLike
+            Name of binary file to read.
+        isim : int
+            Specify -1 for MT3D; 1 for MODFLOW.
+        iprec : int, str or enum.Prec
+            Specify 1 or "single", 2 or "double", or use enum.Prec.
+        ntime : int
+            Number of output times.
+        vartype : str
+            Only read arrays of this type.
+        interpthresh : float
+            Absolute threshold for dry or inactive.
+        nointerpval : float
+            Value to use where interpolation is not possible.
+        ecoord, ncoord : array_like
+            X/Y or Easting/Northing coordinates for points with shape (npts,).
+        layer : int or array_like
+            Layers of points with shape (npts,).
+
+        Returns
+        -------
+        nproctime : int
+            Number of processed simulation times.
+        simtime : npt.NDArray[np.float64]
+            Simulation times, with shape (ntime,).
+        simstate : npt.NDArray[np.float64]
+            Interpolated system states, with shape (ntime, npts).
+        """
+        depvarfile = Path(depvarfile)
+        if not depvarfile.is_file():
+            raise FileNotFoundError(f"could not find depvarfile {depvarfile}")
+        if isinstance(iprec, str):
+            iprec = enum.Prec.get_value(iprec)
+        pta = ManyArrays({"ecoord": ecoord, "ncoord": ncoord}, int_any={"layer": layer})
+        npts = len(pta)
+        simtime = np.zeros(ntime, np.float64, order="F")
+        simstate = np.zeros((ntime, npts), np.float64, order="F")
+        nproctime = c_int()
+        res = self.pestutils.interp_from_structured_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(self.create_char_array(bytes(depvarfile), "LENFILENAME")),
+            byref(c_int(isim)),
+            byref(c_int(iprec)),
+            byref(c_int(ntime)),
+            byref(self.create_char_array(vartype, "LENVARTYPE")),
+            byref(c_double(interpthresh)),
+            byref(c_double(nointerpval)),
+            byref(c_int(npts)),
+            pta.ecoord,
+            pta.ncoord,
+            pta.layer,
+            byref(nproctime),
+            simtime,
+            simstate,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "interpolated %d points from structured grid %r", npts, gridname
+        )
+        return {
+            "nproctime": nproctime.value,
+            "simtime": simtime.copy("A"),
+            "simstate": simstate.copy("A"),
+        }
+
+    def interp_to_obstime(
+        self,
+        # nsimtime: int,  # determined from simval.shape[0]
+        nproctime: int,
+        # npts: int,  # determined from simval.shape[1]
+        simtime: npt.ArrayLike,
+        simval: npt.ArrayLike,
+        interpthresh: float,
+        how_extrap: str,
+        time_extrap: float,
+        nointerpval: float,
+        # nobs: int,  # determined from obspoint.shape[0]
+        obspoint: npt.ArrayLike,
+        obstime: npt.ArrayLike,
+    ) -> npt.NDArray[np.float64]:
+        """Temporal interpolation for simulation times to observed times.
+
+        Parameters
+        ----------
+        nproctime : int
+            Number of times featured in simtime and simval.
+        simtime : array_like
+            1D array of simulation times with shape (nsimtime,).
+        simval : array_like
+            2D array of simulated values with shape (nsimtime, npts).
+        interpthresh : float
+            Values equal or above this in simval have no meaning.
+        how_extrap : str
+            Method, where 'L'=linear; 'C'=constant.
+        time_extrap : float
+            Permitted extrapolation time.
+        nointerpval : float
+            Value to use where interpolation is not possible.
+        obspoint : array_like
+            1D integer array of indices of observation points,
+            which start at 0 and -1 means no index. Shape is (nobs,).
+        obstime : array_like
+            1D array of observation times with shape (nobs,).
+
+        Returns
+        -------
+        np.ndarray
+            Time-interpolated simulation values with shape (nobs,).
+        """
+        simtime = np.array(simtime, dtype=np.float64, order="F", copy=False)
+        simval = np.array(simval, dtype=np.float64, order="F", copy=False)
+        obspoint = np.array(obspoint, order="F", copy=False)
+        obstime = np.array(obstime, dtype=np.float64, order="F", copy=False)
+        if simtime.ndim != 1:
+            raise ValueError("expected 'simtime' to have ndim=1")
+        elif simval.ndim != 2:
+            raise ValueError("expected 'simval' to have ndim=2")
+        elif obspoint.ndim != 1:
+            raise ValueError("expected 'obspoint' to have ndim=1")
+        elif obstime.ndim != 1:
+            raise ValueError("expected 'obstime' to have ndim=1")
+        elif not np.issubdtype(obspoint.dtype, np.integer):
+            raise ValueError(
+                f"expected 'obspoint' to be integer type; found {obspoint.dtype}"
+            )
+        nsimtime, npts = simval.shape
+        nobs = len(obspoint)
+        obssimval = np.zeros(nobs, np.float64, order="F")
+        res = self.pestutils.interp_to_obstime(
+            byref(c_int(nsimtime)),
+            byref(c_int(nproctime)),
+            byref(c_int(npts)),
+            simtime,
+            simval,
+            byref(c_double(interpthresh)),
+            byref(c_char(how_extrap.encode())),
+            byref(c_double(time_extrap)),
+            byref(c_double(nointerpval)),
+            byref(c_int(nobs)),
+            obspoint.astype(np.int32, copy=False),
+            obstime,
+            obssimval,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("interpolated %d time points to %d observations", npts, nobs)
+        return obssimval.copy("A")
+
+    def install_mf6_grid_from_file(
+        self, gridname: str, grbfile: str | PathLike
+    ) -> dict:
+        """Install specifications for a MF6 grid from a GRB file.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        grbfile : str or PathLike
+            Path to a GRB binary grid file.
+
+        Returns
+        -------
+        idis : int
+            Where 1 is for DIS and 2 is for DISV.
+        ncells : int
+            Number of cells in the grid.
+        ndim1, ndim2, ndim3 : int
+            Grid dimensions.
+        """
+        grbfile = Path(grbfile)
+        if not grbfile.is_file():
+            raise FileNotFoundError(f"could not find grbfile {grbfile}")
+        idis = c_int()
+        ncells = c_int()
+        ndim1 = c_int()
+        ndim2 = c_int()
+        ndim3 = c_int()
+        res = self.pestutils.install_mf6_grid_from_file(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(self.create_char_array(bytes(grbfile), "LENFILENAME")),
+            byref(idis),
+            byref(ncells),
+            byref(ndim1),
+            byref(ndim2),
+            byref(ndim3),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "installed mf6 grid %r from grbfile=%r", gridname, grbfile.name
+        )
+        return {
+            "idis": idis.value,
+            "ncells": ncells.value,
+            "ndim1": ndim1.value,
+            "ndim2": ndim2.value,
+            "ndim3": ndim3.value,
+        }
+
+    def get_cell_centres_mf6(self, gridname: str, ncells: int) -> tuple:
+        """Get cell centres from an installed MF6 grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Name of installed MF6 grid.
+        ncells : int
+            Dimensions of grid.
+
+        Returns
+        -------
+        cellx, cellx, cellz : npt.NDArray[np.float64]
+            Coordinates of cell centres with dimensions (ncells,).
+        """
+        cellx = np.zeros(ncells, np.float64, order="F")
+        celly = np.zeros(ncells, np.float64, order="F")
+        cellz = np.zeros(ncells, np.float64, order="F")
+        res = self.pestutils.get_cell_centres_mf6(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(ncells)),
+            cellx,
+            celly,
+            cellz,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("evaluated %d cell centres from MF6 grid %r", ncells, gridname)
+        return cellx.copy("A"), celly.copy("A"), cellz.copy("A")
+
+    def uninstall_mf6_grid(self, gridname: str) -> None:
+        """Uninstall MF6 grid set by :meth:`install_mf6_grid_from_file`.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        """
+        res = self.pestutils.uninstall_mf6_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME"))
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("uninstalled mf6 grid %r", gridname)
+
+    def calc_mf6_interp_factors(
+        self,
+        gridname: str,
+        # npts: int,  # determined from ecoord.shape[0]
+        ecoord: npt.ArrayLike,
+        ncoord: npt.ArrayLike,
+        layer: int | npt.ArrayLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        blnfile: str | PathLike,
+    ) -> npt.NDArray[np.int32]:
+        """Calculate interpolation factors from a MODFLOW 6 DIS or DISV.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        ecoord, ncoord : array_like
+            X/Y or Easting/Northing coordinates for points with shape (npts,).
+        layer : int or array_like
+            Layers of points with shape (npts,).
+        factorfile : str or PathLike
+            File for kriging factors to write.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+        blnfile : str or PathLike
+            Name of bln file to write.
+
+        Returns
+        -------
+        npt.NDArray[np.int32]
+            Array interp_success(npts), where 1 is success and 0 is failure.
+        """
+        pta = ManyArrays({"ecoord": ecoord, "ncoord": ncoord}, int_any={"layer": layer})
+        npts = len(pta)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        blnfile = Path(blnfile)
+        interp_success = np.zeros(npts, np.int32, order="F")
+        res = self.pestutils.calc_mf6_interp_factors(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(npts)),
+            pta.ecoord,
+            pta.ncoord,
+            pta.layer,
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(self.create_char_array(bytes(blnfile), "LENFILENAME")),
+            interp_success,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated mf6 interp factors for %r", gridname)
+        return interp_success.copy("A")
+
+    def interp_from_mf6_depvar_file(
+        self,
+        depvarfile: str | PathLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        ntime: int,
+        vartype: str,
+        interpthresh: float,
+        reapportion: int | bool,
+        nointerpval: float,
+        npts: int,
+    ) -> dict:
+        """
+        Interpolate points using previously-calculated interpolation factors.
+
+        Parameters
+        ----------
+        depvarfile : str or PathLike
+            Name of binary file to read.
+        factorfile : str or PathLike
+            File containing spatial interpolation factors, written by
+            :meth:`calc_mf6_interp_factors`.
+        factorfiletype : int, str or enum.FactorFileType
+            Use 0 for binary; 1 for text.
+        ntime : int
+            Number of output times.
+        vartype : str
+            Only read arrays of this type.
+        interpthresh : float
+            Absolute threshold for dry or inactive.
+        reapportion : int or bool
+            Use 0 for no (False); 1 for yes (True).
+        nointerpval : float
+            Value to use where interpolation is not possible.
+        npts : int
+            Number of points for interpolation.
+
+        Returns
+        -------
+        nproctime : int
+            Number of processed simulation times.
+        simtime : npt.NDArray[np.float64]
+            Simulation times, with shape (ntime,).
+        simstate : npt.NDArray[np.float64]
+            Interpolated system states, with shape (ntime, npts).
+        """
+        depvarfile = Path(depvarfile)
+        if not depvarfile.is_file():
+            raise FileNotFoundError(f"could not find depvarfile {depvarfile}")
+        factorfile = Path(factorfile)
+        if not factorfile.is_file():
+            raise FileNotFoundError(f"could not find factorfile {factorfile}")
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        simtime = np.zeros(ntime, np.float64, order="F")
+        simstate = np.zeros((ntime, npts), np.float64, order="F")
+        nproctime = c_int()
+        res = self.pestutils.interp_from_mf6_depvar_file(
+            byref(self.create_char_array(bytes(depvarfile), "LENFILENAME")),
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(c_int(ntime)),
+            byref(self.create_char_array(vartype, "LENVARTYPE")),
+            byref(c_double(interpthresh)),
+            byref(c_int(reapportion)),
+            byref(c_double(nointerpval)),
+            byref(c_int(npts)),
+            byref(nproctime),
+            simtime,
+            simstate,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "interpolated %d points from mf6 depvar file %r", npts, depvarfile.name
+        )
+        return {
+            "nproctime": nproctime.value,
+            "simtime": simtime.copy("A"),
+            "simstate": simstate.copy("A"),
+        }
+
+    def extract_flows_from_cbc_file(
+        self,
+        cbcfile: str | PathLike,
+        flowtype: str,
+        isim: int,
+        iprec: int | str | enum.Prec,
+        # ncell: int,  # from izone.shape[0]
+        izone: npt.ArrayLike,
+        nzone: int,
+        ntime: int,
+    ) -> dict:
+        """
+        Read and accumulates flows from a CBC flow file to a user-specified BC.
+
+        Parameters
+        ----------
+        cbcfile : str | PathLike
+            Cell-by-cell flow term file written by any MF version.
+        flowtype : str
+            Type of flow to read.
+        isim : int
+            Simulator type.
+        iprec : int, str or enum.Prec
+            Precision used to record real variables in cbc file.
+        izone : array_like
+            Zonation of model domain, with shape (ncell,).
+        nzone : int
+            Equals or exceeds number of zones; zone 0 doesn't count.
+        ntime : int
+            Equals or exceed number of model output times for flow type.
+
+        Returns
+        -------
+        numzone : int
+            Number of non-zero-valued zones.
+        zonenumber : npt.NDArray[np.int32]
+            Zone numbers, with shape (nzone,).
+        nproctime : int
+            Number of processed simulation times.
+        timestep : npt.NDArray[np.int32]
+            Simulation time step, with shape (ntime,).
+        stressperiod : npt.NDArray[np.int32]
+            Simulation stress period, with shape (ntime,).
+        simtime : npt.NDArray[np.int32]
+            Simulation time, with shape (ntime,).
+            A time of -1.0 indicates unknown.
+        simflow : npt.NDArray[np.int32]
+            Interpolated flows, with shape (ntime, nzone).
+        """
+        cbcfile = Path(cbcfile)
+        if not cbcfile.is_file():
+            raise FileNotFoundError(f"could not find cbcfile {cbcfile}")
+        validate_scalar("flowtype", flowtype, minlen=1)
+        validate_scalar("iprec", iprec, enum=enum.Prec)
+        if isinstance(iprec, str):
+            iprec = enum.Prec.get_value(iprec)
+        cell = ManyArrays(int_any={"izone": izone})
+        ncell = len(cell)
+        numzone = c_int()
+        zonenumber = np.zeros(nzone, np.int32, order="F")
+        nproctime = c_int()
+        timestep = np.zeros(ntime, np.int32, order="F")
+        stressperiod = np.zeros(ntime, np.int32, order="F")
+        simtime = np.zeros(ntime, np.float64, order="F")
+        simflow = np.zeros((ntime, nzone), np.float64, order="F")
+        res = self.pestutils.extract_flows_from_cbc_file(
+            byref(self.create_char_array(bytes(cbcfile), "LENFILENAME")),
+            byref(self.create_char_array(flowtype, "LENFLOWTYPE")),
+            byref(c_int(isim)),
+            byref(c_int(iprec)),
+            byref(c_int(ncell)),
+            cell.izone,
+            byref(c_int(nzone)),
+            byref(numzone),
+            zonenumber,
+            byref(c_int(ntime)),
+            byref(nproctime),
+            timestep,
+            stressperiod,
+            simtime,
+            simflow,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("extracted flows from %r", cbcfile.name)
+        return {
+            "numzone": numzone.value,
+            "zonenumber": zonenumber.copy("A"),
+            "nproctime": nproctime.value,
+            "timestep": timestep.copy("A"),
+            "stressperiod": stressperiod.copy("A"),
+            "simtime": simtime.copy("A"),
+            "simflow": simflow.copy("A"),
+        }
+
+    def calc_kriging_factors_2d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        vartype: int | str | enum.VarioType,
+        krigtype: int | str | enum.KrigType,
+        aa: float | npt.ArrayLike,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        searchrad: float,
+        maxpts: int,
+        minpts: int,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate 2D kriging factors.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        vartype : int, str or enum.VarioType
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        krigtype : int, str, or enum.KrigType,
+            Kriging type, where 0:simple, 1:ordinary.
+        aa : float or array_like
+            Variogram "a" value, float or 1D array with shape (mpts,).
+        anis : float or array_like
+            Variogram anisotropies, float or 1D array with shape (mpts,).
+        bearing : float or array_like
+            Variogram bearings, float or 1D array with shape (mpts,).
+        searchrad : float
+            Search radius.
+        maxpts, minpts : int
+            Search specifications.
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays({"ecs": ecs, "ncs": ncs}, int_any={"zns": zns})
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct},
+            {"aa": aa, "anis": anis, "bearing": bearing},
+            {"znt": znt},
+        )
+        mpts = len(mpta)
+        if isinstance(vartype, str):
+            vartype = enum.VarioType.get_value(vartype)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_kriging_factors_2d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zns,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.znt,
+            byref(c_int(vartype)),
+            byref(c_int(krigtype)),
+            mpta.aa,
+            mpta.anis,
+            mpta.bearing,
+            byref(c_double(searchrad)),
+            byref(c_int(maxpts)),
+            byref(c_int(minpts)),
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated 2D kriging factors to %r", factorfile.name)
+        return icount_interp.value
+
+    def calc_kriging_factors_auto_2d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        krigtype: int | str | enum.KrigType,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate 2D kriging factors, with automatic variogram properties.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        krigtype : int, str, enum.KrigType
+            Kriging type, where 0:simple, 1:ordinary.
+        anis : float or array_like
+            Variogram anisotropies, float or 1D array with shape (mpts,).
+        bearing : float or array_like
+            Variogram bearings, float or 1D array with shape (mpts,).
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays({"ecs": ecs, "ncs": ncs}, int_any={"zns": zns})
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct}, {"anis": anis, "bearing": bearing}, {"znt": znt}
+        )
+        mpts = len(mpta)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_kriging_factors_auto_2d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zns,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.znt,
+            byref(c_int(krigtype)),
+            mpta.anis,
+            mpta.bearing,
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated 2D auto kriging factors to %r", factorfile.name)
+        return icount_interp.value
+
+    def calc_kriging_factors_3d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zcs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        zct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        zonenum: int | npt.ArrayLike,
+        krigtype: int | str | enum.KrigType,
+        # nzone: int,  # determined from shape[0] from any zonenum..rake else 1
+        vartype: int | str | enum.VarioType | npt.ArrayLike,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        srhmax: float,
+        srhmin: float,
+        srvert: float,
+        maxpts: int,
+        minpts: int,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate 3D kriging factors.
+
+        Parameters
+        ----------
+        ecs, ncs, zcs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        ect, nct, zct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        krigtype : int, str, or enum.KrigType,
+            Kriging type, where 0:simple, 1:ordinary.
+        zonenum : int, or array_like
+            Zone numbers, inteter or 1D array with shape (nzone,).
+        vartype : int, str, enum.VarioType or array_like
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow. If array,
+            then it should have shape (nzone,).
+        ahmax, ahmin, avert : float or array_like
+            Variogram "a" values in 3 orthogonal directions (hmax, hmin, vert).
+            Each can be a float or 1D array with shape (nzone,).
+        bearing : float or array_like
+            Bearing of hmax, float or 1D array with shape (nzone,).
+        dip : float or array_like
+            Dip of hmax, float or 1D array with shape (nzone,).
+        rake : float or array_like
+            Twist about hmax axis, float or 1D array with shape (nzone,).
+        srhmax, srhmin, srvert : float
+            Search radius in hmax, hmin, and vert directions.
+        maxpts, minpts : int
+            Search specifications.
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays({"ecs": ecs, "ncs": ncs, "zcs": zcs}, int_any={"zns": zns})
+        npts = len(npta)
+        mpta = ManyArrays({"ect": ect, "nct": nct, "zct": zct}, int_any={"znt": znt})
+        mpts = len(mpta)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        vartype = np.array(vartype)
+        if np.issubdtype(vartype.dtype, str):
+            vartype = np.vectorize(enum.VarioType.get_value)(vartype)
+        if not np.issubdtype(vartype.dtype, np.integer):
+            raise ValueError("expected 'vartype' to be integer, str or enum.VarioType")
+        nzone = ManyArrays(
+            float_any={
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+            },
+            int_any={"zonenum": zonenum, "vartype": vartype},
+        )
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_kriging_factors_3d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zcs,
+            npta.zns,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.zct,
+            mpta.znt,
+            byref(c_int(krigtype)),
+            byref(c_int(len(nzone))),
+            nzone.zonenum,
+            nzone.vartype,
+            nzone.ahmax,
+            nzone.ahmin,
+            nzone.avert,
+            nzone.bearing,
+            nzone.dip,
+            nzone.rake,
+            byref(c_double(srhmax)),
+            byref(c_double(srhmin)),
+            byref(c_double(srvert)),
+            byref(c_int(maxpts)),
+            byref(c_int(minpts)),
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated 3D kriging factors to %r", factorfile.name)
+        return icount_interp.value
+
+    def krige_using_file(
+        self,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        # npts: int,  # determined from sourceval.shape[0]
+        mpts: int,
+        krigtype: int | str | enum.KrigType,
+        transtype: int | str | enum.TransType,
+        sourceval: npt.ArrayLike,
+        meanval: float | npt.ArrayLike | None,
+        nointerpval: float,
+    ) -> dict:
+        """
+        Apply interpolation factors calculated by other functions.
+
+        Parameters
+        ----------
+        factorfile : str or PathLike
+            Input file with kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+        mpts : int
+            Number of target points, used to compare with value in factor file.
+        krigtype : int, str, or enum.KrigType,
+            Kriging type, where 0:simple, 1:ordinary.
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        sourceval : array_like
+            Values at sources, 1D array with shape (npts,).
+        meanval : float, array_like, optional
+            Mean values are required if simple kriging, described as a float
+            or 1D array with shape (mpts,).
+        nointerpval : float
+            Value to use where interpolation is not possible.
+
+        Returns
+        -------
+        targval : npt.NDArray[np.float64]
+            Values calculated for targets.
+        icount_interp : int
+            Number of interpolation pts.
+        """
+        factorfile = Path(factorfile)
+        if not factorfile.is_file():
+            raise FileNotFoundError(f"could not find factorfile {factorfile}")
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        npta = ManyArrays({"sourceval": sourceval})
+        npts = len(npta)
+        if meanval is None:
+            if krigtype == enum.KrigType.simple:
+                self.logger.error(
+                    "simple kriging requires 'meanval'; assuming zero for now"
+                )
+            meanval = 0.0
+        mpta = ManyArrays(float_any={"meanval": meanval}, ar_len=mpts)
+        targval = np.full(mpts, nointerpval, dtype=np.float64, order="F")
+        icount_interp = c_int()
+        res = self.pestutils.krige_using_file(
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(c_int(npts)),
+            byref(c_int(mpts)),
+            byref(c_int(krigtype)),
+            byref(c_int(transtype)),
+            npta.sourceval,
+            targval,
+            byref(icount_interp),
+            mpta.meanval,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("kriged using factor file %r", factorfile.name)
+        return {
+            "targval": targval.copy("A"),
+            "icount_interp": icount_interp.value,
+        }
+
+    def build_covar_matrix_2d(
+        self,
+        # npts: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        zn: int | npt.ArrayLike,
+        vartype: int | str | enum.VarioType,
+        nugget: float | npt.ArrayLike,
+        aa: float | npt.ArrayLike,
+        sill: float | npt.ArrayLike,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        ldcovmat: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Calculate a covariance matrix for a set of 2D pilot points.
+
+        Parameters
+        ----------
+        ec, nc : array_like
+            Pilot point coordinates, each 1D array with shape (npts,).
+        zn : int or array_like
+            Pilot point zones, integer or 1D array with shape (npts,).
+        vartype : int, str or enum.VarioType
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        nugget, aa, sill, anis, bearing : float or array_like
+            Variogram parameters, each float or 1D array with shape (npts,).
+        ldcovmat : int
+            Leading dimension of covmat.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            2D matrix covmat(ldcovmat, npts).
+        """
+        pta = ManyArrays(
+            {"ec": ec, "nc": nc},
+            {
+                "nugget": nugget,
+                "aa": aa,
+                "sill": sill,
+                "anis": anis,
+                "bearing": bearing,
+            },
+            {"zn": zn},
+        )
+        npts = len(pta)
+        if isinstance(vartype, str):
+            vartype = enum.VarioType.get_value(vartype)
+        covmat = np.zeros((ldcovmat, npts), np.float64, order="F")
+        res = self.pestutils.build_covar_matrix_2d(
+            byref(c_int(npts)),
+            pta.ec,
+            pta.nc,
+            pta.zn,
+            byref(c_int(vartype)),
+            pta.nugget,
+            pta.aa,
+            pta.sill,
+            pta.anis,
+            pta.bearing,
+            byref(c_int(ldcovmat)),
+            covmat,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated covariance matrix for %d 2D pilot points", npts)
+        return covmat.copy("A")
+
+    def build_covar_matrix_3d(
+        self,
+        # npts: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        zc: npt.ArrayLike,
+        zn: int | npt.ArrayLike,
+        vartype: int | str | enum.VarioType,
+        nugget: float | npt.ArrayLike,
+        sill: float | npt.ArrayLike,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        ldcovmat: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Calculate a covariance matrix for a set of 3D pilot points.
+
+        Parameters
+        ----------
+        ec, nc, zc: array_like
+            Pilot point coordinates, each 1D array with shape (npts,).
+        zn : int or array_like
+            Pilot point zones, integer or 1D array with shape (npts,).
+        vartype : int, str or enum.VarioType
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        nugget, sill : float or array_like
+            Variogram parameters, each float or 1D array with shape (npts,).
+        ahmax, ahmin, avert : float or array_like
+            Variogram a parameters, each float or 1D array with shape (npts,).
+        bearing, dip, rake : float or array_like
+            Variogram angles, each float or 1D array with shape (npts,).
+        ldcovmat : int
+            Leading dimension of covmat.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            2D matrix covmat(ldcovmat, npts).
+        """
+        pta = ManyArrays(
+            {"ec": ec, "nc": nc, "zc": zc},
+            {
+                "nugget": nugget,
+                "sill": sill,
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+            },
+            {"zn": zn},
+        )
+        npts = len(pta)
+        if isinstance(vartype, str):
+            vartype = enum.VarioType.get_value(vartype)
+        covmat = np.zeros((ldcovmat, npts), np.float64, order="F")
+        res = self.pestutils.build_covar_matrix_3d(
+            byref(c_int(npts)),
+            pta.ec,
+            pta.nc,
+            pta.zc,
+            pta.zn,
+            byref(c_int(vartype)),
+            pta.nugget,
+            pta.sill,
+            pta.ahmax,
+            pta.ahmin,
+            pta.avert,
+            pta.bearing,
+            pta.dip,
+            pta.rake,
+            byref(c_int(ldcovmat)),
+            covmat,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated covariance matrix for %d 3D pilot points", npts)
+        return covmat.copy("A")
+
+    def calc_structural_overlay_factors(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        ids: int | npt.ArrayLike,
+        conwidth: npt.ArrayLike,
+        aa: npt.ArrayLike,
+        structype: int | str | enum.StrucType,
+        inverse_power: float,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        active: int | npt.ArrayLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate interpolation/blending factors for structural overlay parameters.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        ids : int or array_like
+            Source point structure number, integer or 1D array with shape (npts,).
+        conwidth, aa : float or array_like
+            Blending parameters, float or 1D array with shape (npts,).
+        structype : int, str or enum.StrucType
+            Structure type, where 0 is polylinear and 1 is polygonal.
+        inverse_power : float
+            Inverse power of distance.
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        active : int or array_like
+            Target point activity, integer or 1D array with shape (mpts,).
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays(
+            {"ecs": ecs, "ncs": ncs}, {"conwidth": conwidth, "aa": aa}, {"ids": ids}
+        )
+        npts = len(npta)
+        mpta = ManyArrays({"ect": ect, "nct": nct}, int_any={"active": active})
+        mpts = len(mpta)
+        if isinstance(structype, str):
+            structype = enum.StrucType.get_value(structype)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_structural_overlay_factors(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.ids,
+            npta.conwidth,
+            npta.aa,
+            byref(c_int(structype)),
+            byref(c_double(inverse_power)),
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.active,
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "calculated interpolation/blending factors to %r", factorfile.name
+        )
+        return icount_interp.value
+
+    def interpolate_blend_using_file(
+        self,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        # npts: int,  # determined from sourceval.shape[0]
+        # mpts: int,  # determined from targval.shape[0]
+        transtype: int | str | enum.TransType,
+        lt_target: str | bool,
+        gt_target: str | bool,
+        sourceval: npt.ArrayLike,
+        targval: npt.ArrayLike,
+    ) -> dict:
+        """
+        Apply interpolation factors calculated by :meth:`calc_structural_overlay_factors`.
+
+        Parameters
+        ----------
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        lt_target, gt_target : str or bool
+            Whether to undercut or exceed target, use "Y"/"N" or bool.
+        sourceval : array_like
+            Values at sources, 1D array with shape (npts,).
+        targval : array_like
+            Values at targets, 1D array with shape (mpts,).
+
+        Returns
+        -------
+        targval : npt.NDArray[np.float64]
+            Values calculated for targets.
+        icount_interp : int
+            Number of interpolation pts.
+        """
+        factorfile = Path(factorfile)
+        if not factorfile.is_file():
+            raise FileNotFoundError(f"could not find factorfile {factorfile}")
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        if isinstance(lt_target, bool):
+            lt_target = "y" if lt_target else "n"
+        if isinstance(gt_target, bool):
+            gt_target = "y" if gt_target else "n"
+        npta = ManyArrays({"sourceval": sourceval})
+        npts = len(npta)
+        mpta = ManyArrays({"targval": targval})
+        mpts = len(mpta)
+        icount_interp = c_int()
+        res = self.pestutils.interpolate_blend_using_file(
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(c_int(npts)),
+            byref(c_int(mpts)),
+            byref(c_int(transtype)),
+            byref(c_char(lt_target.encode())),
+            byref(c_char(gt_target.encode())),
+            npta.sourceval,
+            mpta.targval,
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("applied interpolation factors from %r", factorfile.name)
+        return {
+            "targval": mpts.targval.copy("A"),
+            "icount_interp": icount_interp.value,
+        }
+
+    def ipd_interpolate_2d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        sourceval: npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        invpow: float | npt.ArrayLike,
+    ) -> npt.NDArray[np.float64]:
+        """Undertake 2D inverse-power-of-distance spatial interpolation.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        sourceval : array_like
+            Source values, 1D array with shape (npts,).
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        anis : float or array_like
+            Local anisotropy, float or 1D array with shape (mpts,).
+        bearing : float or array_like
+            Local anisotropy bearing, float or 1D array with shape (mpts,).
+        invpow : float or array_like
+            Local inverse power of distance, float or 1D array with shape (mpts,).
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Values calculated for targets.
+        """
+        npta = ManyArrays(
+            {"ecs": ecs, "ncs": ncs, "sourceval": sourceval}, int_any={"zns": zns}
+        )
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct},
+            {"anis": anis, "bearing": bearing, "invpow": invpow},
+            {"znt": znt},
+        )
+        mpts = len(mpta)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        targval = np.zeros(mpts, np.float64, order="F")
+        res = self.pestutils.ipd_interpolate_2d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zns,
+            npta.sourceval,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.znt,
+            targval,
+            byref(c_int(transtype)),
+            mpta.anis,
+            mpta.bearing,
+            mpta.invpow,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("undertook 2D inverse-power-of-distance spatial interpolation")
+        return targval.copy("A")
+
+    def ipd_interpolate_3d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zcs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        sourceval: npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        zct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        invpow: float | npt.ArrayLike,
+    ) -> npt.NDArray[np.float64]:
+        """Undertake 3D inverse-power-of-distance spatial interpolation.
+
+        Parameters
+        ----------
+        ecs, ncs, zcs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        sourceval : array_like
+            Source values, 1D array with shape (npts,).
+        ect, nct, zct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        ahmax, ahmin, avert : float or array_like
+            Relative correlation lengths, float or 1D array with shape (mpts,).
+        bearing, dip, rake : float or array_like
+            Correlation directions, float or 1D array with shape (mpts,).
+        invpow : float or array_like
+            Local inverse power of distance, float or 1D array with shape (mpts,).
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Values calculated for targets.
+        """
+        npta = ManyArrays(
+            {"ecs": ecs, "ncs": ncs, "zcs": zcs, "sourceval": sourceval},
+            int_any={"zns": zns},
+        )
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct, "zct": zct},
+            {
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+                "invpow": invpow,
+            },
+            {"znt": znt},
+        )
+        mpts = len(mpta)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        targval = np.zeros(mpts, np.float64, order="F")
+        res = self.pestutils.ipd_interpolate_3d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zcs,
+            npta.zns,
+            npta.sourceval,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.zct,
+            mpta.znt,
+            targval,
+            byref(c_int(transtype)),
+            mpta.ahmax,
+            mpta.ahmin,
+            mpta.avert,
+            mpta.bearing,
+            mpta.dip,
+            mpta.rake,
+            mpta.invpow,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("undertook 3D inverse-power-of-distance spatial interpolation")
+        return targval.copy("A")
+
+    def initialize_randgen(self, iseed: int) -> None:
+        """
+        Initialize the random number generator.
+
+        Parameters
+        ----------
+        iseed : int
+            Seed value.
+        """
+        res = self.pestutils.initialize_randgen(byref(c_int(iseed)))
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("initialized the random number generator")
+
+    def fieldgen2d_sva(
+        self,
+        # nnode: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        area: float | npt.ArrayLike,
+        active: int | npt.ArrayLike,
+        mean: float | npt.ArrayLike,
+        var: float | npt.ArrayLike,
+        aa: float | npt.ArrayLike,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        avetype: int | str | enum.VarioType,
+        power: float,
+        # ldrand: int,  # same as nnode
+        nreal: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Generate 2D stochastic fields based on a spatially varying variogram.
+
+        Parameters
+        ----------
+        ec, nc : array_like
+            Model grid coordinates, each 1D array with shape (nnode,).
+        area : float or array_like
+            Areas of grid cells.
+        active : int or array_like
+            Inactive grid cells are equal to zero.
+        mean : float or array_like
+            Mean value of stochastic field.
+        var : float or array_like
+            Variance of stochastic field.
+        aa : float or array_like
+            Averaging function spatial dimension.
+        anis : float or array_like
+            Anisotropy ratio.
+        bearing : float or array_like
+            Bearing of principal anisotropy axis.
+        transtype : int, str or enum.TransType
+            Stochastic field pertains to natural(0) or log(1) properties.
+        avetype : int, str or enum.VarioType
+            Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        power : float
+            Power used if avetype is 4 (pow).
+        nreal : int
+            Number of realisations to generate.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Realisations with shape (nnode, nreal).
+        """
+        node = ManyArrays(
+            {"ec": ec, "nc": nc},
+            {
+                "area": area,
+                "mean": mean,
+                "var": var,
+                "aa": aa,
+                "anis": anis,
+                "bearing": bearing,
+            },
+            {"active": active},
+        )
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        if isinstance(avetype, str):
+            avetype = enum.VarioType.get_value(avetype)
+        ldrand = nnode = len(node)
+        randfield = np.zeros((ldrand, nreal), np.float64, order="F")
+        res = self.pestutils.fieldgen2d_sva(
+            byref(c_int(nnode)),
+            node.ec,
+            node.nc,
+            node.area,
+            node.active,
+            node.mean,
+            node.var,
+            node.aa,
+            node.anis,
+            node.bearing,
+            byref(c_int(transtype)),
+            byref(c_int(avetype)),
+            byref(c_double(power)),
+            byref(c_int(ldrand)),
+            byref(c_int(nreal)),
+            randfield,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("generated 2D stochastic fields for %d realisations", nreal)
+        return randfield.copy("A")
+
+    def fieldgen3d_sva(
+        self,
+        # nnode: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        zc: npt.ArrayLike,
+        area: float | npt.ArrayLike,
+        height: float | npt.ArrayLike,
+        active: int | npt.ArrayLike,
+        mean: float | npt.ArrayLike,
+        var: float | npt.ArrayLike,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        avetype: int | str | enum.VarioType,
+        power: float,
+        # ldrand: int,  # same as nnode
+        nreal: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Generate 3D stochastic fields based on a spatially varying variogram.
+
+        Parameters
+        ----------
+        ec, nc, nz : array_like
+            Model grid coordinates, each 1D array with shape (nnode,).
+        area, height : float or array_like
+            Areas and height of grid cells.
+        active : int or array_like
+            Inactive grid cells are equal to zero.
+        mean : float or array_like
+            Mean value of stochastic field.
+        var : float or array_like
+            Variance of stochastic field.
+        ahmax, ahmin, avert : float or array_like
+            Averaging function correlation lengths.
+        bearing : float or array_like
+            Bearing of ahmax direction.
+        dip : float or array_like
+            Dip of ahmax direction.
+        rake : float or array_like
+            Rotation of ahmin direction.
+        transtype : int, str or enum.TransType
+            Stochastic field pertains to natural(0) or log(1) properties.
+        avetype : int, str or enum.VarioType
+            Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        power : float
+            Power used if avetype is 4 (pow).
+        nreal : int
+            Number of realisations to generate.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Realisations with shape (nnode, nreal).
+        """
+        node = ManyArrays(
+            {"ec": ec, "nc": nc, "zc": zc},
+            {
+                "area": area,
+                "height": height,
+                "mean": mean,
+                "var": var,
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+            },
+            {"active": active},
+        )
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        if isinstance(avetype, str):
+            avetype = enum.VarioType.get_value(avetype)
+        ldrand = nnode = len(node)
+        randfield = np.zeros((ldrand, nreal), np.float64, order="F")
+        res = self.pestutils.fieldgen3d_sva(
+            byref(c_int(nnode)),
+            node.ec,
+            node.nc,
+            node.zc,
+            node.area,
+            node.height,
+            node.active,
+            node.mean,
+            node.var,
+            node.ahmax,
+            node.ahmin,
+            node.avert,
+            node.bearing,
+            node.dip,
+            node.rake,
+            byref(c_int(transtype)),
+            byref(c_int(avetype)),
+            byref(c_double(power)),
+            byref(c_int(ldrand)),
+            byref(c_int(nreal)),
+            randfield,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("generated 3D stochastic fields for %d realisations", nreal)
+        return randfield.copy("A")
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class PestUtilsLib +(*, logger_level=20) +
+
+

Mid-level Fortran-Python handler for pestutils library via ctypes.

+

Parameters

+
+
logger_level : int, str, default 20 (INFO)
+
 
+
+
+ +Expand source code + +
class PestUtilsLib:
+    """Mid-level Fortran-Python handler for pestutils library via ctypes.
+
+    Parameters
+    ----------
+    logger_level : int, str, default 20 (INFO)
+    """
+
+    def __init__(self, *, logger_level=logging.INFO) -> None:
+        from .ctypes_declarations import prototype
+        from .finder import load
+        from .logger import get_logger
+
+        self.logger = get_logger(self.__class__.__name__, logger_level)
+        self.pestutils = load()
+        self.logger.debug("loaded %s", self.pestutils)
+        prototype(self.pestutils)
+        self.logger.debug("added prototypes")
+
+    # def __del__(self):
+    #    """Clean-up library instance."""
+    #    try:
+    #        self.free_all_memory()
+    #    except (AttributeError, PestUtilsLibError) as err:
+    #        if hasattr(self, "logger"):
+    #            self.logger.warning("cannot call __del__: %s", err)
+
+    def create_char_array(self, init: str | bytes, name: str):
+        """Create c_char Array with a fixed size from dimvar and intial value.
+
+        Parameters
+        ----------
+        init : str or bytes
+            Initial value.
+        name : str
+            Uppercase variable length name, e.g. LENFILENAME or LENVARTYPE.
+        """
+        from .ctypes_declarations import get_dimvar_int
+
+        if isinstance(init, str):
+            init = init.encode()
+        elif isinstance(init, bytes):
+            pass
+        else:
+            raise TypeError(f"expecting either str or bytes; found {type(init)}")
+        size = get_dimvar_int(self.pestutils, name)
+        if len(init) > size:
+            raise ValueError(f"init size is {len(init)} but {name} is {size}")
+        return create_string_buffer(init, size)
+
+    def inquire_modflow_binary_file_specs(
+        self,
+        filein: str | PathLike,
+        fileout: str | PathLike | None,
+        isim: int,
+        itype: int,
+    ) -> dict:
+        """Report some of the details of a MODFLOW-written binary file.
+
+        Parameters
+        ----------
+        filein : str or PathLike
+            MODFLOW-generated binary file to be read.
+        fileout : str, PathLike, None
+            Output file with with table of array headers. Use None or "" for
+            no output file.
+        isim : int
+            Inform the function the simulator that generated the binary file:
+
+             * 1 = traditional MODFLOW
+             * 21 = MODFLOW-USG with structured grid
+             * 22 = MODFLOW-USG with unstructured grid
+             * 31 = MODFLOW 6 with DIS grid
+             * 32 = MODFLOW 6 with DISV grid
+             * 33 = MODFLOW 6 with DISU grid
+
+        itype : int
+            Where 1 = system state or dependent variable;
+            2 = cell-by-cell flows.
+
+        Returns
+        -------
+        iprec : int
+            Where 1 = single; 2 = double.
+        narray : int
+            Number of arrays.
+        ntime : int
+            Number of times.
+        """
+        filein = Path(filein)
+        if not filein.is_file():
+            raise FileNotFoundError(f"could not find filein {filein}")
+        if fileout:
+            fileout = Path(fileout)
+        else:
+            fileout = b""
+        validate_scalar("isim", isim, isin=[1, 21, 22, 31, 32, 33])
+        validate_scalar("itype", itype, isin=[1, 2])
+        iprec = c_int()
+        narray = c_int()
+        ntime = c_int()
+        res = self.pestutils.inquire_modflow_binary_file_specs(
+            byref(self.create_char_array(bytes(filein), "LENFILENAME")),
+            byref(self.create_char_array(bytes(fileout), "LENFILENAME")),
+            byref(c_int(isim)),
+            byref(c_int(itype)),
+            byref(iprec),
+            byref(narray),
+            byref(ntime),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("inquired modflow binary file specs from %r", filein.name)
+        return {
+            "iprec": iprec.value,
+            "narray": narray.value,
+            "ntime": ntime.value,
+        }
+
+    def retrieve_error_message(self) -> str:
+        """Retrieve error message from library.
+
+        Returns
+        -------
+        str
+        """
+        from .ctypes_declarations import get_char_array
+
+        charray = get_char_array(self.pestutils, "LENMESSAGE")()
+        res = self.pestutils.retrieve_error_message(byref(charray))
+        return charray[:res].rstrip(b"\x00").decode()
+
+    def install_structured_grid(
+        self,
+        gridname: str,
+        ncol: int,
+        nrow: int,
+        nlay: int,
+        icorner: int,
+        e0: float,
+        n0: float,
+        rotation: float,
+        delr: float | npt.ArrayLike,
+        delc: float | npt.ArrayLike,
+    ) -> None:
+        """Install specifications for a structured grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        ncol, nrow, nlay : int
+            Grid dimensions.
+        icorner : int
+            Reference corner, use 1 for top left and 2 for bottom left.
+        e0, n0 : float
+            Reference offsets.
+        rotation : float
+            Grid rotation, counter-clockwise degrees.
+        """
+        validate_scalar("ncol", ncol, gt=0)
+        validate_scalar("nrow", nrow, gt=0)
+        validate_scalar("nlay", nlay, gt=0)
+        col = ManyArrays(float_any={"delr": delr}, ar_len=ncol)
+        row = ManyArrays(float_any={"delc": delc}, ar_len=nrow)
+        col.validate("delr", gt=0.0)
+        row.validate("delc", gt=0.0)
+        validate_scalar("icorner", icorner, isin=[1, 2])
+        res = self.pestutils.install_structured_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(ncol)),
+            byref(c_int(nrow)),
+            byref(c_int(nlay)),
+            byref(c_int(icorner)),
+            byref(c_double(e0)),
+            byref(c_double(n0)),
+            byref(c_double(rotation)),
+            col.delr,
+            row.delc,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("installed structured grid %r from specs", gridname)
+
+    def get_cell_centres_structured(self, gridname: str, ncpl: int) -> tuple:
+        """Get cell centres of a single layer of an installed structured grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Name of installed structured grid.
+        ncpl : int
+            Dimensions of grid (nrow x ncol).
+
+        Returns
+        -------
+        cellx, cellx : npt.NDArray[np.float64]
+            Coordinates of cell centres with dimensions (ncpl,).
+        """
+        cellx = np.zeros(ncpl, np.float64, order="F")
+        celly = np.zeros(ncpl, np.float64, order="F")
+        res = self.pestutils.get_cell_centres_structured(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(ncpl)),
+            cellx,
+            celly,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "evaluated %d cell centres from structured grid %r", ncpl, gridname
+        )
+        return cellx.copy("A"), celly.copy("A")
+
+    def uninstall_structured_grid(self, gridname: str) -> None:
+        """Uninstall structured grid set by :meth:`install_structured_grid`.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        """
+        res = self.pestutils.uninstall_structured_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME"))
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("uninstalled structured grid %r", gridname)
+
+    def free_all_memory(self) -> None:
+        """Deallocate all memory that is being used."""
+        ret = self.pestutils.free_all_memory()
+        if ret != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("all memory was freed up")
+
+    def interp_from_structured_grid(
+        self,
+        gridname: str,
+        depvarfile: str | PathLike,
+        isim: int,
+        iprec: int | str | enum.Prec,
+        ntime: int,
+        vartype: str,
+        interpthresh: float,
+        nointerpval: float,
+        # npts: int,  # determined from layer.shape[0]
+        ecoord: npt.ArrayLike,
+        ncoord: npt.ArrayLike,
+        layer: int | npt.ArrayLike,
+    ) -> dict:
+        """Spatial interpolate points from a structured grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Name of installed structured grid.
+        depvarfile : str or PathLike
+            Name of binary file to read.
+        isim : int
+            Specify -1 for MT3D; 1 for MODFLOW.
+        iprec : int, str or enum.Prec
+            Specify 1 or "single", 2 or "double", or use enum.Prec.
+        ntime : int
+            Number of output times.
+        vartype : str
+            Only read arrays of this type.
+        interpthresh : float
+            Absolute threshold for dry or inactive.
+        nointerpval : float
+            Value to use where interpolation is not possible.
+        ecoord, ncoord : array_like
+            X/Y or Easting/Northing coordinates for points with shape (npts,).
+        layer : int or array_like
+            Layers of points with shape (npts,).
+
+        Returns
+        -------
+        nproctime : int
+            Number of processed simulation times.
+        simtime : npt.NDArray[np.float64]
+            Simulation times, with shape (ntime,).
+        simstate : npt.NDArray[np.float64]
+            Interpolated system states, with shape (ntime, npts).
+        """
+        depvarfile = Path(depvarfile)
+        if not depvarfile.is_file():
+            raise FileNotFoundError(f"could not find depvarfile {depvarfile}")
+        if isinstance(iprec, str):
+            iprec = enum.Prec.get_value(iprec)
+        pta = ManyArrays({"ecoord": ecoord, "ncoord": ncoord}, int_any={"layer": layer})
+        npts = len(pta)
+        simtime = np.zeros(ntime, np.float64, order="F")
+        simstate = np.zeros((ntime, npts), np.float64, order="F")
+        nproctime = c_int()
+        res = self.pestutils.interp_from_structured_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(self.create_char_array(bytes(depvarfile), "LENFILENAME")),
+            byref(c_int(isim)),
+            byref(c_int(iprec)),
+            byref(c_int(ntime)),
+            byref(self.create_char_array(vartype, "LENVARTYPE")),
+            byref(c_double(interpthresh)),
+            byref(c_double(nointerpval)),
+            byref(c_int(npts)),
+            pta.ecoord,
+            pta.ncoord,
+            pta.layer,
+            byref(nproctime),
+            simtime,
+            simstate,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "interpolated %d points from structured grid %r", npts, gridname
+        )
+        return {
+            "nproctime": nproctime.value,
+            "simtime": simtime.copy("A"),
+            "simstate": simstate.copy("A"),
+        }
+
+    def interp_to_obstime(
+        self,
+        # nsimtime: int,  # determined from simval.shape[0]
+        nproctime: int,
+        # npts: int,  # determined from simval.shape[1]
+        simtime: npt.ArrayLike,
+        simval: npt.ArrayLike,
+        interpthresh: float,
+        how_extrap: str,
+        time_extrap: float,
+        nointerpval: float,
+        # nobs: int,  # determined from obspoint.shape[0]
+        obspoint: npt.ArrayLike,
+        obstime: npt.ArrayLike,
+    ) -> npt.NDArray[np.float64]:
+        """Temporal interpolation for simulation times to observed times.
+
+        Parameters
+        ----------
+        nproctime : int
+            Number of times featured in simtime and simval.
+        simtime : array_like
+            1D array of simulation times with shape (nsimtime,).
+        simval : array_like
+            2D array of simulated values with shape (nsimtime, npts).
+        interpthresh : float
+            Values equal or above this in simval have no meaning.
+        how_extrap : str
+            Method, where 'L'=linear; 'C'=constant.
+        time_extrap : float
+            Permitted extrapolation time.
+        nointerpval : float
+            Value to use where interpolation is not possible.
+        obspoint : array_like
+            1D integer array of indices of observation points,
+            which start at 0 and -1 means no index. Shape is (nobs,).
+        obstime : array_like
+            1D array of observation times with shape (nobs,).
+
+        Returns
+        -------
+        np.ndarray
+            Time-interpolated simulation values with shape (nobs,).
+        """
+        simtime = np.array(simtime, dtype=np.float64, order="F", copy=False)
+        simval = np.array(simval, dtype=np.float64, order="F", copy=False)
+        obspoint = np.array(obspoint, order="F", copy=False)
+        obstime = np.array(obstime, dtype=np.float64, order="F", copy=False)
+        if simtime.ndim != 1:
+            raise ValueError("expected 'simtime' to have ndim=1")
+        elif simval.ndim != 2:
+            raise ValueError("expected 'simval' to have ndim=2")
+        elif obspoint.ndim != 1:
+            raise ValueError("expected 'obspoint' to have ndim=1")
+        elif obstime.ndim != 1:
+            raise ValueError("expected 'obstime' to have ndim=1")
+        elif not np.issubdtype(obspoint.dtype, np.integer):
+            raise ValueError(
+                f"expected 'obspoint' to be integer type; found {obspoint.dtype}"
+            )
+        nsimtime, npts = simval.shape
+        nobs = len(obspoint)
+        obssimval = np.zeros(nobs, np.float64, order="F")
+        res = self.pestutils.interp_to_obstime(
+            byref(c_int(nsimtime)),
+            byref(c_int(nproctime)),
+            byref(c_int(npts)),
+            simtime,
+            simval,
+            byref(c_double(interpthresh)),
+            byref(c_char(how_extrap.encode())),
+            byref(c_double(time_extrap)),
+            byref(c_double(nointerpval)),
+            byref(c_int(nobs)),
+            obspoint.astype(np.int32, copy=False),
+            obstime,
+            obssimval,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("interpolated %d time points to %d observations", npts, nobs)
+        return obssimval.copy("A")
+
+    def install_mf6_grid_from_file(
+        self, gridname: str, grbfile: str | PathLike
+    ) -> dict:
+        """Install specifications for a MF6 grid from a GRB file.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        grbfile : str or PathLike
+            Path to a GRB binary grid file.
+
+        Returns
+        -------
+        idis : int
+            Where 1 is for DIS and 2 is for DISV.
+        ncells : int
+            Number of cells in the grid.
+        ndim1, ndim2, ndim3 : int
+            Grid dimensions.
+        """
+        grbfile = Path(grbfile)
+        if not grbfile.is_file():
+            raise FileNotFoundError(f"could not find grbfile {grbfile}")
+        idis = c_int()
+        ncells = c_int()
+        ndim1 = c_int()
+        ndim2 = c_int()
+        ndim3 = c_int()
+        res = self.pestutils.install_mf6_grid_from_file(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(self.create_char_array(bytes(grbfile), "LENFILENAME")),
+            byref(idis),
+            byref(ncells),
+            byref(ndim1),
+            byref(ndim2),
+            byref(ndim3),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "installed mf6 grid %r from grbfile=%r", gridname, grbfile.name
+        )
+        return {
+            "idis": idis.value,
+            "ncells": ncells.value,
+            "ndim1": ndim1.value,
+            "ndim2": ndim2.value,
+            "ndim3": ndim3.value,
+        }
+
+    def get_cell_centres_mf6(self, gridname: str, ncells: int) -> tuple:
+        """Get cell centres from an installed MF6 grid.
+
+        Parameters
+        ----------
+        gridname : str
+            Name of installed MF6 grid.
+        ncells : int
+            Dimensions of grid.
+
+        Returns
+        -------
+        cellx, cellx, cellz : npt.NDArray[np.float64]
+            Coordinates of cell centres with dimensions (ncells,).
+        """
+        cellx = np.zeros(ncells, np.float64, order="F")
+        celly = np.zeros(ncells, np.float64, order="F")
+        cellz = np.zeros(ncells, np.float64, order="F")
+        res = self.pestutils.get_cell_centres_mf6(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(ncells)),
+            cellx,
+            celly,
+            cellz,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("evaluated %d cell centres from MF6 grid %r", ncells, gridname)
+        return cellx.copy("A"), celly.copy("A"), cellz.copy("A")
+
+    def uninstall_mf6_grid(self, gridname: str) -> None:
+        """Uninstall MF6 grid set by :meth:`install_mf6_grid_from_file`.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        """
+        res = self.pestutils.uninstall_mf6_grid(
+            byref(self.create_char_array(gridname, "LENGRIDNAME"))
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("uninstalled mf6 grid %r", gridname)
+
+    def calc_mf6_interp_factors(
+        self,
+        gridname: str,
+        # npts: int,  # determined from ecoord.shape[0]
+        ecoord: npt.ArrayLike,
+        ncoord: npt.ArrayLike,
+        layer: int | npt.ArrayLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        blnfile: str | PathLike,
+    ) -> npt.NDArray[np.int32]:
+        """Calculate interpolation factors from a MODFLOW 6 DIS or DISV.
+
+        Parameters
+        ----------
+        gridname : str
+            Unique non-blank grid name.
+        ecoord, ncoord : array_like
+            X/Y or Easting/Northing coordinates for points with shape (npts,).
+        layer : int or array_like
+            Layers of points with shape (npts,).
+        factorfile : str or PathLike
+            File for kriging factors to write.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+        blnfile : str or PathLike
+            Name of bln file to write.
+
+        Returns
+        -------
+        npt.NDArray[np.int32]
+            Array interp_success(npts), where 1 is success and 0 is failure.
+        """
+        pta = ManyArrays({"ecoord": ecoord, "ncoord": ncoord}, int_any={"layer": layer})
+        npts = len(pta)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        blnfile = Path(blnfile)
+        interp_success = np.zeros(npts, np.int32, order="F")
+        res = self.pestutils.calc_mf6_interp_factors(
+            byref(self.create_char_array(gridname, "LENGRIDNAME")),
+            byref(c_int(npts)),
+            pta.ecoord,
+            pta.ncoord,
+            pta.layer,
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(self.create_char_array(bytes(blnfile), "LENFILENAME")),
+            interp_success,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated mf6 interp factors for %r", gridname)
+        return interp_success.copy("A")
+
+    def interp_from_mf6_depvar_file(
+        self,
+        depvarfile: str | PathLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        ntime: int,
+        vartype: str,
+        interpthresh: float,
+        reapportion: int | bool,
+        nointerpval: float,
+        npts: int,
+    ) -> dict:
+        """
+        Interpolate points using previously-calculated interpolation factors.
+
+        Parameters
+        ----------
+        depvarfile : str or PathLike
+            Name of binary file to read.
+        factorfile : str or PathLike
+            File containing spatial interpolation factors, written by
+            :meth:`calc_mf6_interp_factors`.
+        factorfiletype : int, str or enum.FactorFileType
+            Use 0 for binary; 1 for text.
+        ntime : int
+            Number of output times.
+        vartype : str
+            Only read arrays of this type.
+        interpthresh : float
+            Absolute threshold for dry or inactive.
+        reapportion : int or bool
+            Use 0 for no (False); 1 for yes (True).
+        nointerpval : float
+            Value to use where interpolation is not possible.
+        npts : int
+            Number of points for interpolation.
+
+        Returns
+        -------
+        nproctime : int
+            Number of processed simulation times.
+        simtime : npt.NDArray[np.float64]
+            Simulation times, with shape (ntime,).
+        simstate : npt.NDArray[np.float64]
+            Interpolated system states, with shape (ntime, npts).
+        """
+        depvarfile = Path(depvarfile)
+        if not depvarfile.is_file():
+            raise FileNotFoundError(f"could not find depvarfile {depvarfile}")
+        factorfile = Path(factorfile)
+        if not factorfile.is_file():
+            raise FileNotFoundError(f"could not find factorfile {factorfile}")
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        simtime = np.zeros(ntime, np.float64, order="F")
+        simstate = np.zeros((ntime, npts), np.float64, order="F")
+        nproctime = c_int()
+        res = self.pestutils.interp_from_mf6_depvar_file(
+            byref(self.create_char_array(bytes(depvarfile), "LENFILENAME")),
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(c_int(ntime)),
+            byref(self.create_char_array(vartype, "LENVARTYPE")),
+            byref(c_double(interpthresh)),
+            byref(c_int(reapportion)),
+            byref(c_double(nointerpval)),
+            byref(c_int(npts)),
+            byref(nproctime),
+            simtime,
+            simstate,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "interpolated %d points from mf6 depvar file %r", npts, depvarfile.name
+        )
+        return {
+            "nproctime": nproctime.value,
+            "simtime": simtime.copy("A"),
+            "simstate": simstate.copy("A"),
+        }
+
+    def extract_flows_from_cbc_file(
+        self,
+        cbcfile: str | PathLike,
+        flowtype: str,
+        isim: int,
+        iprec: int | str | enum.Prec,
+        # ncell: int,  # from izone.shape[0]
+        izone: npt.ArrayLike,
+        nzone: int,
+        ntime: int,
+    ) -> dict:
+        """
+        Read and accumulates flows from a CBC flow file to a user-specified BC.
+
+        Parameters
+        ----------
+        cbcfile : str | PathLike
+            Cell-by-cell flow term file written by any MF version.
+        flowtype : str
+            Type of flow to read.
+        isim : int
+            Simulator type.
+        iprec : int, str or enum.Prec
+            Precision used to record real variables in cbc file.
+        izone : array_like
+            Zonation of model domain, with shape (ncell,).
+        nzone : int
+            Equals or exceeds number of zones; zone 0 doesn't count.
+        ntime : int
+            Equals or exceed number of model output times for flow type.
+
+        Returns
+        -------
+        numzone : int
+            Number of non-zero-valued zones.
+        zonenumber : npt.NDArray[np.int32]
+            Zone numbers, with shape (nzone,).
+        nproctime : int
+            Number of processed simulation times.
+        timestep : npt.NDArray[np.int32]
+            Simulation time step, with shape (ntime,).
+        stressperiod : npt.NDArray[np.int32]
+            Simulation stress period, with shape (ntime,).
+        simtime : npt.NDArray[np.int32]
+            Simulation time, with shape (ntime,).
+            A time of -1.0 indicates unknown.
+        simflow : npt.NDArray[np.int32]
+            Interpolated flows, with shape (ntime, nzone).
+        """
+        cbcfile = Path(cbcfile)
+        if not cbcfile.is_file():
+            raise FileNotFoundError(f"could not find cbcfile {cbcfile}")
+        validate_scalar("flowtype", flowtype, minlen=1)
+        validate_scalar("iprec", iprec, enum=enum.Prec)
+        if isinstance(iprec, str):
+            iprec = enum.Prec.get_value(iprec)
+        cell = ManyArrays(int_any={"izone": izone})
+        ncell = len(cell)
+        numzone = c_int()
+        zonenumber = np.zeros(nzone, np.int32, order="F")
+        nproctime = c_int()
+        timestep = np.zeros(ntime, np.int32, order="F")
+        stressperiod = np.zeros(ntime, np.int32, order="F")
+        simtime = np.zeros(ntime, np.float64, order="F")
+        simflow = np.zeros((ntime, nzone), np.float64, order="F")
+        res = self.pestutils.extract_flows_from_cbc_file(
+            byref(self.create_char_array(bytes(cbcfile), "LENFILENAME")),
+            byref(self.create_char_array(flowtype, "LENFLOWTYPE")),
+            byref(c_int(isim)),
+            byref(c_int(iprec)),
+            byref(c_int(ncell)),
+            cell.izone,
+            byref(c_int(nzone)),
+            byref(numzone),
+            zonenumber,
+            byref(c_int(ntime)),
+            byref(nproctime),
+            timestep,
+            stressperiod,
+            simtime,
+            simflow,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("extracted flows from %r", cbcfile.name)
+        return {
+            "numzone": numzone.value,
+            "zonenumber": zonenumber.copy("A"),
+            "nproctime": nproctime.value,
+            "timestep": timestep.copy("A"),
+            "stressperiod": stressperiod.copy("A"),
+            "simtime": simtime.copy("A"),
+            "simflow": simflow.copy("A"),
+        }
+
+    def calc_kriging_factors_2d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        vartype: int | str | enum.VarioType,
+        krigtype: int | str | enum.KrigType,
+        aa: float | npt.ArrayLike,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        searchrad: float,
+        maxpts: int,
+        minpts: int,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate 2D kriging factors.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        vartype : int, str or enum.VarioType
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        krigtype : int, str, or enum.KrigType,
+            Kriging type, where 0:simple, 1:ordinary.
+        aa : float or array_like
+            Variogram "a" value, float or 1D array with shape (mpts,).
+        anis : float or array_like
+            Variogram anisotropies, float or 1D array with shape (mpts,).
+        bearing : float or array_like
+            Variogram bearings, float or 1D array with shape (mpts,).
+        searchrad : float
+            Search radius.
+        maxpts, minpts : int
+            Search specifications.
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays({"ecs": ecs, "ncs": ncs}, int_any={"zns": zns})
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct},
+            {"aa": aa, "anis": anis, "bearing": bearing},
+            {"znt": znt},
+        )
+        mpts = len(mpta)
+        if isinstance(vartype, str):
+            vartype = enum.VarioType.get_value(vartype)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_kriging_factors_2d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zns,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.znt,
+            byref(c_int(vartype)),
+            byref(c_int(krigtype)),
+            mpta.aa,
+            mpta.anis,
+            mpta.bearing,
+            byref(c_double(searchrad)),
+            byref(c_int(maxpts)),
+            byref(c_int(minpts)),
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated 2D kriging factors to %r", factorfile.name)
+        return icount_interp.value
+
+    def calc_kriging_factors_auto_2d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        krigtype: int | str | enum.KrigType,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate 2D kriging factors, with automatic variogram properties.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        krigtype : int, str, enum.KrigType
+            Kriging type, where 0:simple, 1:ordinary.
+        anis : float or array_like
+            Variogram anisotropies, float or 1D array with shape (mpts,).
+        bearing : float or array_like
+            Variogram bearings, float or 1D array with shape (mpts,).
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays({"ecs": ecs, "ncs": ncs}, int_any={"zns": zns})
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct}, {"anis": anis, "bearing": bearing}, {"znt": znt}
+        )
+        mpts = len(mpta)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_kriging_factors_auto_2d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zns,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.znt,
+            byref(c_int(krigtype)),
+            mpta.anis,
+            mpta.bearing,
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated 2D auto kriging factors to %r", factorfile.name)
+        return icount_interp.value
+
+    def calc_kriging_factors_3d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zcs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        zct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        zonenum: int | npt.ArrayLike,
+        krigtype: int | str | enum.KrigType,
+        # nzone: int,  # determined from shape[0] from any zonenum..rake else 1
+        vartype: int | str | enum.VarioType | npt.ArrayLike,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        srhmax: float,
+        srhmin: float,
+        srvert: float,
+        maxpts: int,
+        minpts: int,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate 3D kriging factors.
+
+        Parameters
+        ----------
+        ecs, ncs, zcs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        ect, nct, zct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        krigtype : int, str, or enum.KrigType,
+            Kriging type, where 0:simple, 1:ordinary.
+        zonenum : int, or array_like
+            Zone numbers, inteter or 1D array with shape (nzone,).
+        vartype : int, str, enum.VarioType or array_like
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow. If array,
+            then it should have shape (nzone,).
+        ahmax, ahmin, avert : float or array_like
+            Variogram "a" values in 3 orthogonal directions (hmax, hmin, vert).
+            Each can be a float or 1D array with shape (nzone,).
+        bearing : float or array_like
+            Bearing of hmax, float or 1D array with shape (nzone,).
+        dip : float or array_like
+            Dip of hmax, float or 1D array with shape (nzone,).
+        rake : float or array_like
+            Twist about hmax axis, float or 1D array with shape (nzone,).
+        srhmax, srhmin, srvert : float
+            Search radius in hmax, hmin, and vert directions.
+        maxpts, minpts : int
+            Search specifications.
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays({"ecs": ecs, "ncs": ncs, "zcs": zcs}, int_any={"zns": zns})
+        npts = len(npta)
+        mpta = ManyArrays({"ect": ect, "nct": nct, "zct": zct}, int_any={"znt": znt})
+        mpts = len(mpta)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        vartype = np.array(vartype)
+        if np.issubdtype(vartype.dtype, str):
+            vartype = np.vectorize(enum.VarioType.get_value)(vartype)
+        if not np.issubdtype(vartype.dtype, np.integer):
+            raise ValueError("expected 'vartype' to be integer, str or enum.VarioType")
+        nzone = ManyArrays(
+            float_any={
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+            },
+            int_any={"zonenum": zonenum, "vartype": vartype},
+        )
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_kriging_factors_3d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zcs,
+            npta.zns,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.zct,
+            mpta.znt,
+            byref(c_int(krigtype)),
+            byref(c_int(len(nzone))),
+            nzone.zonenum,
+            nzone.vartype,
+            nzone.ahmax,
+            nzone.ahmin,
+            nzone.avert,
+            nzone.bearing,
+            nzone.dip,
+            nzone.rake,
+            byref(c_double(srhmax)),
+            byref(c_double(srhmin)),
+            byref(c_double(srvert)),
+            byref(c_int(maxpts)),
+            byref(c_int(minpts)),
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated 3D kriging factors to %r", factorfile.name)
+        return icount_interp.value
+
+    def krige_using_file(
+        self,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        # npts: int,  # determined from sourceval.shape[0]
+        mpts: int,
+        krigtype: int | str | enum.KrigType,
+        transtype: int | str | enum.TransType,
+        sourceval: npt.ArrayLike,
+        meanval: float | npt.ArrayLike | None,
+        nointerpval: float,
+    ) -> dict:
+        """
+        Apply interpolation factors calculated by other functions.
+
+        Parameters
+        ----------
+        factorfile : str or PathLike
+            Input file with kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+        mpts : int
+            Number of target points, used to compare with value in factor file.
+        krigtype : int, str, or enum.KrigType,
+            Kriging type, where 0:simple, 1:ordinary.
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        sourceval : array_like
+            Values at sources, 1D array with shape (npts,).
+        meanval : float, array_like, optional
+            Mean values are required if simple kriging, described as a float
+            or 1D array with shape (mpts,).
+        nointerpval : float
+            Value to use where interpolation is not possible.
+
+        Returns
+        -------
+        targval : npt.NDArray[np.float64]
+            Values calculated for targets.
+        icount_interp : int
+            Number of interpolation pts.
+        """
+        factorfile = Path(factorfile)
+        if not factorfile.is_file():
+            raise FileNotFoundError(f"could not find factorfile {factorfile}")
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        if isinstance(krigtype, str):
+            krigtype = enum.KrigType.get_value(krigtype)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        npta = ManyArrays({"sourceval": sourceval})
+        npts = len(npta)
+        if meanval is None:
+            if krigtype == enum.KrigType.simple:
+                self.logger.error(
+                    "simple kriging requires 'meanval'; assuming zero for now"
+                )
+            meanval = 0.0
+        mpta = ManyArrays(float_any={"meanval": meanval}, ar_len=mpts)
+        targval = np.full(mpts, nointerpval, dtype=np.float64, order="F")
+        icount_interp = c_int()
+        res = self.pestutils.krige_using_file(
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(c_int(npts)),
+            byref(c_int(mpts)),
+            byref(c_int(krigtype)),
+            byref(c_int(transtype)),
+            npta.sourceval,
+            targval,
+            byref(icount_interp),
+            mpta.meanval,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("kriged using factor file %r", factorfile.name)
+        return {
+            "targval": targval.copy("A"),
+            "icount_interp": icount_interp.value,
+        }
+
+    def build_covar_matrix_2d(
+        self,
+        # npts: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        zn: int | npt.ArrayLike,
+        vartype: int | str | enum.VarioType,
+        nugget: float | npt.ArrayLike,
+        aa: float | npt.ArrayLike,
+        sill: float | npt.ArrayLike,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        ldcovmat: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Calculate a covariance matrix for a set of 2D pilot points.
+
+        Parameters
+        ----------
+        ec, nc : array_like
+            Pilot point coordinates, each 1D array with shape (npts,).
+        zn : int or array_like
+            Pilot point zones, integer or 1D array with shape (npts,).
+        vartype : int, str or enum.VarioType
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        nugget, aa, sill, anis, bearing : float or array_like
+            Variogram parameters, each float or 1D array with shape (npts,).
+        ldcovmat : int
+            Leading dimension of covmat.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            2D matrix covmat(ldcovmat, npts).
+        """
+        pta = ManyArrays(
+            {"ec": ec, "nc": nc},
+            {
+                "nugget": nugget,
+                "aa": aa,
+                "sill": sill,
+                "anis": anis,
+                "bearing": bearing,
+            },
+            {"zn": zn},
+        )
+        npts = len(pta)
+        if isinstance(vartype, str):
+            vartype = enum.VarioType.get_value(vartype)
+        covmat = np.zeros((ldcovmat, npts), np.float64, order="F")
+        res = self.pestutils.build_covar_matrix_2d(
+            byref(c_int(npts)),
+            pta.ec,
+            pta.nc,
+            pta.zn,
+            byref(c_int(vartype)),
+            pta.nugget,
+            pta.aa,
+            pta.sill,
+            pta.anis,
+            pta.bearing,
+            byref(c_int(ldcovmat)),
+            covmat,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated covariance matrix for %d 2D pilot points", npts)
+        return covmat.copy("A")
+
+    def build_covar_matrix_3d(
+        self,
+        # npts: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        zc: npt.ArrayLike,
+        zn: int | npt.ArrayLike,
+        vartype: int | str | enum.VarioType,
+        nugget: float | npt.ArrayLike,
+        sill: float | npt.ArrayLike,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        ldcovmat: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Calculate a covariance matrix for a set of 3D pilot points.
+
+        Parameters
+        ----------
+        ec, nc, zc: array_like
+            Pilot point coordinates, each 1D array with shape (npts,).
+        zn : int or array_like
+            Pilot point zones, integer or 1D array with shape (npts,).
+        vartype : int, str or enum.VarioType
+            Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        nugget, sill : float or array_like
+            Variogram parameters, each float or 1D array with shape (npts,).
+        ahmax, ahmin, avert : float or array_like
+            Variogram a parameters, each float or 1D array with shape (npts,).
+        bearing, dip, rake : float or array_like
+            Variogram angles, each float or 1D array with shape (npts,).
+        ldcovmat : int
+            Leading dimension of covmat.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            2D matrix covmat(ldcovmat, npts).
+        """
+        pta = ManyArrays(
+            {"ec": ec, "nc": nc, "zc": zc},
+            {
+                "nugget": nugget,
+                "sill": sill,
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+            },
+            {"zn": zn},
+        )
+        npts = len(pta)
+        if isinstance(vartype, str):
+            vartype = enum.VarioType.get_value(vartype)
+        covmat = np.zeros((ldcovmat, npts), np.float64, order="F")
+        res = self.pestutils.build_covar_matrix_3d(
+            byref(c_int(npts)),
+            pta.ec,
+            pta.nc,
+            pta.zc,
+            pta.zn,
+            byref(c_int(vartype)),
+            pta.nugget,
+            pta.sill,
+            pta.ahmax,
+            pta.ahmin,
+            pta.avert,
+            pta.bearing,
+            pta.dip,
+            pta.rake,
+            byref(c_int(ldcovmat)),
+            covmat,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("calculated covariance matrix for %d 3D pilot points", npts)
+        return covmat.copy("A")
+
+    def calc_structural_overlay_factors(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        ids: int | npt.ArrayLike,
+        conwidth: npt.ArrayLike,
+        aa: npt.ArrayLike,
+        structype: int | str | enum.StrucType,
+        inverse_power: float,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        active: int | npt.ArrayLike,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+    ) -> int:
+        """
+        Calculate interpolation/blending factors for structural overlay parameters.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        ids : int or array_like
+            Source point structure number, integer or 1D array with shape (npts,).
+        conwidth, aa : float or array_like
+            Blending parameters, float or 1D array with shape (npts,).
+        structype : int, str or enum.StrucType
+            Structure type, where 0 is polylinear and 1 is polygonal.
+        inverse_power : float
+            Inverse power of distance.
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        active : int or array_like
+            Target point activity, integer or 1D array with shape (mpts,).
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+
+        Returns
+        -------
+        int
+            Number of interp points.
+        """
+        npta = ManyArrays(
+            {"ecs": ecs, "ncs": ncs}, {"conwidth": conwidth, "aa": aa}, {"ids": ids}
+        )
+        npts = len(npta)
+        mpta = ManyArrays({"ect": ect, "nct": nct}, int_any={"active": active})
+        mpts = len(mpta)
+        if isinstance(structype, str):
+            structype = enum.StrucType.get_value(structype)
+        factorfile = Path(factorfile)
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        icount_interp = c_int()
+        res = self.pestutils.calc_structural_overlay_factors(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.ids,
+            npta.conwidth,
+            npta.aa,
+            byref(c_int(structype)),
+            byref(c_double(inverse_power)),
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.active,
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info(
+            "calculated interpolation/blending factors to %r", factorfile.name
+        )
+        return icount_interp.value
+
+    def interpolate_blend_using_file(
+        self,
+        factorfile: str | PathLike,
+        factorfiletype: int | str | enum.FactorFileType,
+        # npts: int,  # determined from sourceval.shape[0]
+        # mpts: int,  # determined from targval.shape[0]
+        transtype: int | str | enum.TransType,
+        lt_target: str | bool,
+        gt_target: str | bool,
+        sourceval: npt.ArrayLike,
+        targval: npt.ArrayLike,
+    ) -> dict:
+        """
+        Apply interpolation factors calculated by :meth:`calc_structural_overlay_factors`.
+
+        Parameters
+        ----------
+        factorfile : str or PathLike
+            File for kriging factors.
+        factorfiletype : int, str or enum.FactorFileType
+            Factor file type, where 0:binary, 1:text.
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        lt_target, gt_target : str or bool
+            Whether to undercut or exceed target, use "Y"/"N" or bool.
+        sourceval : array_like
+            Values at sources, 1D array with shape (npts,).
+        targval : array_like
+            Values at targets, 1D array with shape (mpts,).
+
+        Returns
+        -------
+        targval : npt.NDArray[np.float64]
+            Values calculated for targets.
+        icount_interp : int
+            Number of interpolation pts.
+        """
+        factorfile = Path(factorfile)
+        if not factorfile.is_file():
+            raise FileNotFoundError(f"could not find factorfile {factorfile}")
+        if isinstance(factorfiletype, str):
+            factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        if isinstance(lt_target, bool):
+            lt_target = "y" if lt_target else "n"
+        if isinstance(gt_target, bool):
+            gt_target = "y" if gt_target else "n"
+        npta = ManyArrays({"sourceval": sourceval})
+        npts = len(npta)
+        mpta = ManyArrays({"targval": targval})
+        mpts = len(mpta)
+        icount_interp = c_int()
+        res = self.pestutils.interpolate_blend_using_file(
+            byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+            byref(c_int(factorfiletype)),
+            byref(c_int(npts)),
+            byref(c_int(mpts)),
+            byref(c_int(transtype)),
+            byref(c_char(lt_target.encode())),
+            byref(c_char(gt_target.encode())),
+            npta.sourceval,
+            mpta.targval,
+            byref(icount_interp),
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("applied interpolation factors from %r", factorfile.name)
+        return {
+            "targval": mpts.targval.copy("A"),
+            "icount_interp": icount_interp.value,
+        }
+
+    def ipd_interpolate_2d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        sourceval: npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        invpow: float | npt.ArrayLike,
+    ) -> npt.NDArray[np.float64]:
+        """Undertake 2D inverse-power-of-distance spatial interpolation.
+
+        Parameters
+        ----------
+        ecs, ncs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        sourceval : array_like
+            Source values, 1D array with shape (npts,).
+        ect, nct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        anis : float or array_like
+            Local anisotropy, float or 1D array with shape (mpts,).
+        bearing : float or array_like
+            Local anisotropy bearing, float or 1D array with shape (mpts,).
+        invpow : float or array_like
+            Local inverse power of distance, float or 1D array with shape (mpts,).
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Values calculated for targets.
+        """
+        npta = ManyArrays(
+            {"ecs": ecs, "ncs": ncs, "sourceval": sourceval}, int_any={"zns": zns}
+        )
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct},
+            {"anis": anis, "bearing": bearing, "invpow": invpow},
+            {"znt": znt},
+        )
+        mpts = len(mpta)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        targval = np.zeros(mpts, np.float64, order="F")
+        res = self.pestutils.ipd_interpolate_2d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zns,
+            npta.sourceval,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.znt,
+            targval,
+            byref(c_int(transtype)),
+            mpta.anis,
+            mpta.bearing,
+            mpta.invpow,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("undertook 2D inverse-power-of-distance spatial interpolation")
+        return targval.copy("A")
+
+    def ipd_interpolate_3d(
+        self,
+        # npts: int,  # determined from ecs.shape[0]
+        ecs: npt.ArrayLike,
+        ncs: npt.ArrayLike,
+        zcs: npt.ArrayLike,
+        zns: int | npt.ArrayLike,
+        sourceval: npt.ArrayLike,
+        # mpts: int,  # determined from ect.shape[0]
+        ect: npt.ArrayLike,
+        nct: npt.ArrayLike,
+        zct: npt.ArrayLike,
+        znt: int | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        invpow: float | npt.ArrayLike,
+    ) -> npt.NDArray[np.float64]:
+        """Undertake 3D inverse-power-of-distance spatial interpolation.
+
+        Parameters
+        ----------
+        ecs, ncs, zcs : array_like
+            Source point coordinates, each 1D array with shape (npts,).
+        zns : int or array_like
+            Source point zones, integer or 1D array with shape (npts,).
+        sourceval : array_like
+            Source values, 1D array with shape (npts,).
+        ect, nct, zct : array_like
+            Target point coordinates, each 1D array with shape (mpts,).
+        znt : int or array_like
+            Target point zones, integer or 1D array with shape (mpts,).
+        transtype : int, str, enum.TransType
+            Tranformation type, where 0 is none and 1 is log.
+        ahmax, ahmin, avert : float or array_like
+            Relative correlation lengths, float or 1D array with shape (mpts,).
+        bearing, dip, rake : float or array_like
+            Correlation directions, float or 1D array with shape (mpts,).
+        invpow : float or array_like
+            Local inverse power of distance, float or 1D array with shape (mpts,).
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Values calculated for targets.
+        """
+        npta = ManyArrays(
+            {"ecs": ecs, "ncs": ncs, "zcs": zcs, "sourceval": sourceval},
+            int_any={"zns": zns},
+        )
+        npts = len(npta)
+        mpta = ManyArrays(
+            {"ect": ect, "nct": nct, "zct": zct},
+            {
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+                "invpow": invpow,
+            },
+            {"znt": znt},
+        )
+        mpts = len(mpta)
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        targval = np.zeros(mpts, np.float64, order="F")
+        res = self.pestutils.ipd_interpolate_3d(
+            byref(c_int(npts)),
+            npta.ecs,
+            npta.ncs,
+            npta.zcs,
+            npta.zns,
+            npta.sourceval,
+            byref(c_int(mpts)),
+            mpta.ect,
+            mpta.nct,
+            mpta.zct,
+            mpta.znt,
+            targval,
+            byref(c_int(transtype)),
+            mpta.ahmax,
+            mpta.ahmin,
+            mpta.avert,
+            mpta.bearing,
+            mpta.dip,
+            mpta.rake,
+            mpta.invpow,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("undertook 3D inverse-power-of-distance spatial interpolation")
+        return targval.copy("A")
+
+    def initialize_randgen(self, iseed: int) -> None:
+        """
+        Initialize the random number generator.
+
+        Parameters
+        ----------
+        iseed : int
+            Seed value.
+        """
+        res = self.pestutils.initialize_randgen(byref(c_int(iseed)))
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("initialized the random number generator")
+
+    def fieldgen2d_sva(
+        self,
+        # nnode: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        area: float | npt.ArrayLike,
+        active: int | npt.ArrayLike,
+        mean: float | npt.ArrayLike,
+        var: float | npt.ArrayLike,
+        aa: float | npt.ArrayLike,
+        anis: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        avetype: int | str | enum.VarioType,
+        power: float,
+        # ldrand: int,  # same as nnode
+        nreal: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Generate 2D stochastic fields based on a spatially varying variogram.
+
+        Parameters
+        ----------
+        ec, nc : array_like
+            Model grid coordinates, each 1D array with shape (nnode,).
+        area : float or array_like
+            Areas of grid cells.
+        active : int or array_like
+            Inactive grid cells are equal to zero.
+        mean : float or array_like
+            Mean value of stochastic field.
+        var : float or array_like
+            Variance of stochastic field.
+        aa : float or array_like
+            Averaging function spatial dimension.
+        anis : float or array_like
+            Anisotropy ratio.
+        bearing : float or array_like
+            Bearing of principal anisotropy axis.
+        transtype : int, str or enum.TransType
+            Stochastic field pertains to natural(0) or log(1) properties.
+        avetype : int, str or enum.VarioType
+            Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        power : float
+            Power used if avetype is 4 (pow).
+        nreal : int
+            Number of realisations to generate.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Realisations with shape (nnode, nreal).
+        """
+        node = ManyArrays(
+            {"ec": ec, "nc": nc},
+            {
+                "area": area,
+                "mean": mean,
+                "var": var,
+                "aa": aa,
+                "anis": anis,
+                "bearing": bearing,
+            },
+            {"active": active},
+        )
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        if isinstance(avetype, str):
+            avetype = enum.VarioType.get_value(avetype)
+        ldrand = nnode = len(node)
+        randfield = np.zeros((ldrand, nreal), np.float64, order="F")
+        res = self.pestutils.fieldgen2d_sva(
+            byref(c_int(nnode)),
+            node.ec,
+            node.nc,
+            node.area,
+            node.active,
+            node.mean,
+            node.var,
+            node.aa,
+            node.anis,
+            node.bearing,
+            byref(c_int(transtype)),
+            byref(c_int(avetype)),
+            byref(c_double(power)),
+            byref(c_int(ldrand)),
+            byref(c_int(nreal)),
+            randfield,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("generated 2D stochastic fields for %d realisations", nreal)
+        return randfield.copy("A")
+
+    def fieldgen3d_sva(
+        self,
+        # nnode: int,  # determined from ec.shape[0]
+        ec: npt.ArrayLike,
+        nc: npt.ArrayLike,
+        zc: npt.ArrayLike,
+        area: float | npt.ArrayLike,
+        height: float | npt.ArrayLike,
+        active: int | npt.ArrayLike,
+        mean: float | npt.ArrayLike,
+        var: float | npt.ArrayLike,
+        ahmax: float | npt.ArrayLike,
+        ahmin: float | npt.ArrayLike,
+        avert: float | npt.ArrayLike,
+        bearing: float | npt.ArrayLike,
+        dip: float | npt.ArrayLike,
+        rake: float | npt.ArrayLike,
+        transtype: int | str | enum.TransType,
+        avetype: int | str | enum.VarioType,
+        power: float,
+        # ldrand: int,  # same as nnode
+        nreal: int,
+    ) -> npt.NDArray[np.float64]:
+        """
+        Generate 3D stochastic fields based on a spatially varying variogram.
+
+        Parameters
+        ----------
+        ec, nc, nz : array_like
+            Model grid coordinates, each 1D array with shape (nnode,).
+        area, height : float or array_like
+            Areas and height of grid cells.
+        active : int or array_like
+            Inactive grid cells are equal to zero.
+        mean : float or array_like
+            Mean value of stochastic field.
+        var : float or array_like
+            Variance of stochastic field.
+        ahmax, ahmin, avert : float or array_like
+            Averaging function correlation lengths.
+        bearing : float or array_like
+            Bearing of ahmax direction.
+        dip : float or array_like
+            Dip of ahmax direction.
+        rake : float or array_like
+            Rotation of ahmin direction.
+        transtype : int, str or enum.TransType
+            Stochastic field pertains to natural(0) or log(1) properties.
+        avetype : int, str or enum.VarioType
+            Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+        power : float
+            Power used if avetype is 4 (pow).
+        nreal : int
+            Number of realisations to generate.
+
+        Returns
+        -------
+        npt.NDArray[np.float64]
+            Realisations with shape (nnode, nreal).
+        """
+        node = ManyArrays(
+            {"ec": ec, "nc": nc, "zc": zc},
+            {
+                "area": area,
+                "height": height,
+                "mean": mean,
+                "var": var,
+                "ahmax": ahmax,
+                "ahmin": ahmin,
+                "avert": avert,
+                "bearing": bearing,
+                "dip": dip,
+                "rake": rake,
+            },
+            {"active": active},
+        )
+        if isinstance(transtype, str):
+            transtype = enum.TransType.get_value(transtype)
+        if isinstance(avetype, str):
+            avetype = enum.VarioType.get_value(avetype)
+        ldrand = nnode = len(node)
+        randfield = np.zeros((ldrand, nreal), np.float64, order="F")
+        res = self.pestutils.fieldgen3d_sva(
+            byref(c_int(nnode)),
+            node.ec,
+            node.nc,
+            node.zc,
+            node.area,
+            node.height,
+            node.active,
+            node.mean,
+            node.var,
+            node.ahmax,
+            node.ahmin,
+            node.avert,
+            node.bearing,
+            node.dip,
+            node.rake,
+            byref(c_int(transtype)),
+            byref(c_int(avetype)),
+            byref(c_double(power)),
+            byref(c_int(ldrand)),
+            byref(c_int(nreal)),
+            randfield,
+        )
+        if res != 0:
+            raise PestUtilsLibError(self.retrieve_error_message())
+        self.logger.info("generated 3D stochastic fields for %d realisations", nreal)
+        return randfield.copy("A")
+
+

Methods

+
+
+def build_covar_matrix_2d(self, ec: npt.ArrayLike, nc: npt.ArrayLike, zn: int | npt.ArrayLike, vartype: int | str | enum.VarioType, nugget: float | npt.ArrayLike, aa: float | npt.ArrayLike, sill: float | npt.ArrayLike, anis: float | npt.ArrayLike, bearing: float | npt.ArrayLike, ldcovmat: int) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +
+
+

Calculate a covariance matrix for a set of 2D pilot points.

+

Parameters

+
+
ec, nc : array_like
+
Pilot point coordinates, each 1D array with shape (npts,).
+
zn : int or array_like
+
Pilot point zones, integer or 1D array with shape (npts,).
+
vartype : int, str or enum.VarioType
+
Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+
nugget, aa, sill, anis, bearing : float or array_like
+
Variogram parameters, each float or 1D array with shape (npts,).
+
ldcovmat : int
+
Leading dimension of covmat.
+
+

Returns

+
+
npt.NDArray[np.float64]
+
2D matrix covmat(ldcovmat, npts).
+
+
+ +Expand source code + +
def build_covar_matrix_2d(
+    self,
+    # npts: int,  # determined from ec.shape[0]
+    ec: npt.ArrayLike,
+    nc: npt.ArrayLike,
+    zn: int | npt.ArrayLike,
+    vartype: int | str | enum.VarioType,
+    nugget: float | npt.ArrayLike,
+    aa: float | npt.ArrayLike,
+    sill: float | npt.ArrayLike,
+    anis: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    ldcovmat: int,
+) -> npt.NDArray[np.float64]:
+    """
+    Calculate a covariance matrix for a set of 2D pilot points.
+
+    Parameters
+    ----------
+    ec, nc : array_like
+        Pilot point coordinates, each 1D array with shape (npts,).
+    zn : int or array_like
+        Pilot point zones, integer or 1D array with shape (npts,).
+    vartype : int, str or enum.VarioType
+        Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+    nugget, aa, sill, anis, bearing : float or array_like
+        Variogram parameters, each float or 1D array with shape (npts,).
+    ldcovmat : int
+        Leading dimension of covmat.
+
+    Returns
+    -------
+    npt.NDArray[np.float64]
+        2D matrix covmat(ldcovmat, npts).
+    """
+    pta = ManyArrays(
+        {"ec": ec, "nc": nc},
+        {
+            "nugget": nugget,
+            "aa": aa,
+            "sill": sill,
+            "anis": anis,
+            "bearing": bearing,
+        },
+        {"zn": zn},
+    )
+    npts = len(pta)
+    if isinstance(vartype, str):
+        vartype = enum.VarioType.get_value(vartype)
+    covmat = np.zeros((ldcovmat, npts), np.float64, order="F")
+    res = self.pestutils.build_covar_matrix_2d(
+        byref(c_int(npts)),
+        pta.ec,
+        pta.nc,
+        pta.zn,
+        byref(c_int(vartype)),
+        pta.nugget,
+        pta.aa,
+        pta.sill,
+        pta.anis,
+        pta.bearing,
+        byref(c_int(ldcovmat)),
+        covmat,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("calculated covariance matrix for %d 2D pilot points", npts)
+    return covmat.copy("A")
+
+
+
+def build_covar_matrix_3d(self, ec: npt.ArrayLike, nc: npt.ArrayLike, zc: npt.ArrayLike, zn: int | npt.ArrayLike, vartype: int | str | enum.VarioType, nugget: float | npt.ArrayLike, sill: float | npt.ArrayLike, ahmax: float | npt.ArrayLike, ahmin: float | npt.ArrayLike, avert: float | npt.ArrayLike, bearing: float | npt.ArrayLike, dip: float | npt.ArrayLike, rake: float | npt.ArrayLike, ldcovmat: int) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +
+
+

Calculate a covariance matrix for a set of 3D pilot points.

+

Parameters

+
+
ec, nc, zc : array_like
+
Pilot point coordinates, each 1D array with shape (npts,).
+
zn : int or array_like
+
Pilot point zones, integer or 1D array with shape (npts,).
+
vartype : int, str or enum.VarioType
+
Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+
nugget, sill : float or array_like
+
Variogram parameters, each float or 1D array with shape (npts,).
+
ahmax, ahmin, avert : float or array_like
+
Variogram a parameters, each float or 1D array with shape (npts,).
+
bearing, dip, rake : float or array_like
+
Variogram angles, each float or 1D array with shape (npts,).
+
ldcovmat : int
+
Leading dimension of covmat.
+
+

Returns

+
+
npt.NDArray[np.float64]
+
2D matrix covmat(ldcovmat, npts).
+
+
+ +Expand source code + +
def build_covar_matrix_3d(
+    self,
+    # npts: int,  # determined from ec.shape[0]
+    ec: npt.ArrayLike,
+    nc: npt.ArrayLike,
+    zc: npt.ArrayLike,
+    zn: int | npt.ArrayLike,
+    vartype: int | str | enum.VarioType,
+    nugget: float | npt.ArrayLike,
+    sill: float | npt.ArrayLike,
+    ahmax: float | npt.ArrayLike,
+    ahmin: float | npt.ArrayLike,
+    avert: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    dip: float | npt.ArrayLike,
+    rake: float | npt.ArrayLike,
+    ldcovmat: int,
+) -> npt.NDArray[np.float64]:
+    """
+    Calculate a covariance matrix for a set of 3D pilot points.
+
+    Parameters
+    ----------
+    ec, nc, zc: array_like
+        Pilot point coordinates, each 1D array with shape (npts,).
+    zn : int or array_like
+        Pilot point zones, integer or 1D array with shape (npts,).
+    vartype : int, str or enum.VarioType
+        Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+    nugget, sill : float or array_like
+        Variogram parameters, each float or 1D array with shape (npts,).
+    ahmax, ahmin, avert : float or array_like
+        Variogram a parameters, each float or 1D array with shape (npts,).
+    bearing, dip, rake : float or array_like
+        Variogram angles, each float or 1D array with shape (npts,).
+    ldcovmat : int
+        Leading dimension of covmat.
+
+    Returns
+    -------
+    npt.NDArray[np.float64]
+        2D matrix covmat(ldcovmat, npts).
+    """
+    pta = ManyArrays(
+        {"ec": ec, "nc": nc, "zc": zc},
+        {
+            "nugget": nugget,
+            "sill": sill,
+            "ahmax": ahmax,
+            "ahmin": ahmin,
+            "avert": avert,
+            "bearing": bearing,
+            "dip": dip,
+            "rake": rake,
+        },
+        {"zn": zn},
+    )
+    npts = len(pta)
+    if isinstance(vartype, str):
+        vartype = enum.VarioType.get_value(vartype)
+    covmat = np.zeros((ldcovmat, npts), np.float64, order="F")
+    res = self.pestutils.build_covar_matrix_3d(
+        byref(c_int(npts)),
+        pta.ec,
+        pta.nc,
+        pta.zc,
+        pta.zn,
+        byref(c_int(vartype)),
+        pta.nugget,
+        pta.sill,
+        pta.ahmax,
+        pta.ahmin,
+        pta.avert,
+        pta.bearing,
+        pta.dip,
+        pta.rake,
+        byref(c_int(ldcovmat)),
+        covmat,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("calculated covariance matrix for %d 3D pilot points", npts)
+    return covmat.copy("A")
+
+
+
+def calc_kriging_factors_2d(self, ecs: npt.ArrayLike, ncs: npt.ArrayLike, zns: int | npt.ArrayLike, ect: npt.ArrayLike, nct: npt.ArrayLike, znt: int | npt.ArrayLike, vartype: int | str | enum.VarioType, krigtype: int | str | enum.KrigType, aa: float | npt.ArrayLike, anis: float | npt.ArrayLike, bearing: float | npt.ArrayLike, searchrad: float, maxpts: int, minpts: int, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType) ‑> int +
+
+

Calculate 2D kriging factors.

+

Parameters

+
+
ecs, ncs : array_like
+
Source point coordinates, each 1D array with shape (npts,).
+
zns : int or array_like
+
Source point zones, integer or 1D array with shape (npts,).
+
ect, nct : array_like
+
Target point coordinates, each 1D array with shape (mpts,).
+
znt : int or array_like
+
Target point zones, integer or 1D array with shape (mpts,).
+
vartype : int, str or enum.VarioType
+
Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+
krigtype : int, str, or enum.KrigType,
+
Kriging type, where 0:simple, 1:ordinary.
+
aa : float or array_like
+
Variogram "a" value, float or 1D array with shape (mpts,).
+
anis : float or array_like
+
Variogram anisotropies, float or 1D array with shape (mpts,).
+
bearing : float or array_like
+
Variogram bearings, float or 1D array with shape (mpts,).
+
searchrad : float
+
Search radius.
+
maxpts, minpts : int
+
Search specifications.
+
factorfile : str or PathLike
+
File for kriging factors.
+
factorfiletype : int, str or enum.FactorFileType
+
Factor file type, where 0:binary, 1:text.
+
+

Returns

+
+
int
+
Number of interp points.
+
+
+ +Expand source code + +
def calc_kriging_factors_2d(
+    self,
+    # npts: int,  # determined from ecs.shape[0]
+    ecs: npt.ArrayLike,
+    ncs: npt.ArrayLike,
+    zns: int | npt.ArrayLike,
+    # mpts: int,  # determined from ect.shape[0]
+    ect: npt.ArrayLike,
+    nct: npt.ArrayLike,
+    znt: int | npt.ArrayLike,
+    vartype: int | str | enum.VarioType,
+    krigtype: int | str | enum.KrigType,
+    aa: float | npt.ArrayLike,
+    anis: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    searchrad: float,
+    maxpts: int,
+    minpts: int,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+) -> int:
+    """
+    Calculate 2D kriging factors.
+
+    Parameters
+    ----------
+    ecs, ncs : array_like
+        Source point coordinates, each 1D array with shape (npts,).
+    zns : int or array_like
+        Source point zones, integer or 1D array with shape (npts,).
+    ect, nct : array_like
+        Target point coordinates, each 1D array with shape (mpts,).
+    znt : int or array_like
+        Target point zones, integer or 1D array with shape (mpts,).
+    vartype : int, str or enum.VarioType
+        Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+    krigtype : int, str, or enum.KrigType,
+        Kriging type, where 0:simple, 1:ordinary.
+    aa : float or array_like
+        Variogram "a" value, float or 1D array with shape (mpts,).
+    anis : float or array_like
+        Variogram anisotropies, float or 1D array with shape (mpts,).
+    bearing : float or array_like
+        Variogram bearings, float or 1D array with shape (mpts,).
+    searchrad : float
+        Search radius.
+    maxpts, minpts : int
+        Search specifications.
+    factorfile : str or PathLike
+        File for kriging factors.
+    factorfiletype : int, str or enum.FactorFileType
+        Factor file type, where 0:binary, 1:text.
+
+    Returns
+    -------
+    int
+        Number of interp points.
+    """
+    npta = ManyArrays({"ecs": ecs, "ncs": ncs}, int_any={"zns": zns})
+    npts = len(npta)
+    mpta = ManyArrays(
+        {"ect": ect, "nct": nct},
+        {"aa": aa, "anis": anis, "bearing": bearing},
+        {"znt": znt},
+    )
+    mpts = len(mpta)
+    if isinstance(vartype, str):
+        vartype = enum.VarioType.get_value(vartype)
+    if isinstance(krigtype, str):
+        krigtype = enum.KrigType.get_value(krigtype)
+    factorfile = Path(factorfile)
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    icount_interp = c_int()
+    res = self.pestutils.calc_kriging_factors_2d(
+        byref(c_int(npts)),
+        npta.ecs,
+        npta.ncs,
+        npta.zns,
+        byref(c_int(mpts)),
+        mpta.ect,
+        mpta.nct,
+        mpta.znt,
+        byref(c_int(vartype)),
+        byref(c_int(krigtype)),
+        mpta.aa,
+        mpta.anis,
+        mpta.bearing,
+        byref(c_double(searchrad)),
+        byref(c_int(maxpts)),
+        byref(c_int(minpts)),
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(icount_interp),
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("calculated 2D kriging factors to %r", factorfile.name)
+    return icount_interp.value
+
+
+
+def calc_kriging_factors_3d(self, ecs: npt.ArrayLike, ncs: npt.ArrayLike, zcs: npt.ArrayLike, zns: int | npt.ArrayLike, ect: npt.ArrayLike, nct: npt.ArrayLike, zct: npt.ArrayLike, znt: int | npt.ArrayLike, zonenum: int | npt.ArrayLike, krigtype: int | str | enum.KrigType, vartype: int | str | enum.VarioType | npt.ArrayLike, ahmax: float | npt.ArrayLike, ahmin: float | npt.ArrayLike, avert: float | npt.ArrayLike, bearing: float | npt.ArrayLike, dip: float | npt.ArrayLike, rake: float | npt.ArrayLike, srhmax: float, srhmin: float, srvert: float, maxpts: int, minpts: int, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType) ‑> int +
+
+

Calculate 3D kriging factors.

+

Parameters

+
+
ecs, ncs, zcs : array_like
+
Source point coordinates, each 1D array with shape (npts,).
+
zns : int or array_like
+
Source point zones, integer or 1D array with shape (npts,).
+
ect, nct, zct : array_like
+
Target point coordinates, each 1D array with shape (mpts,).
+
znt : int or array_like
+
Target point zones, integer or 1D array with shape (mpts,).
+
krigtype : int, str, or enum.KrigType,
+
Kriging type, where 0:simple, 1:ordinary.
+
zonenum : int, or array_like
+
Zone numbers, inteter or 1D array with shape (nzone,).
+
vartype : int, str, enum.VarioType or array_like
+
Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow. If array, +then it should have shape (nzone,).
+
ahmax, ahmin, avert : float or array_like
+
Variogram "a" values in 3 orthogonal directions (hmax, hmin, vert). +Each can be a float or 1D array with shape (nzone,).
+
bearing : float or array_like
+
Bearing of hmax, float or 1D array with shape (nzone,).
+
dip : float or array_like
+
Dip of hmax, float or 1D array with shape (nzone,).
+
rake : float or array_like
+
Twist about hmax axis, float or 1D array with shape (nzone,).
+
srhmax, srhmin, srvert : float
+
Search radius in hmax, hmin, and vert directions.
+
maxpts, minpts : int
+
Search specifications.
+
factorfile : str or PathLike
+
File for kriging factors.
+
factorfiletype : int, str or enum.FactorFileType
+
Factor file type, where 0:binary, 1:text.
+
+

Returns

+
+
int
+
Number of interp points.
+
+
+ +Expand source code + +
def calc_kriging_factors_3d(
+    self,
+    # npts: int,  # determined from ecs.shape[0]
+    ecs: npt.ArrayLike,
+    ncs: npt.ArrayLike,
+    zcs: npt.ArrayLike,
+    zns: int | npt.ArrayLike,
+    # mpts: int,  # determined from ect.shape[0]
+    ect: npt.ArrayLike,
+    nct: npt.ArrayLike,
+    zct: npt.ArrayLike,
+    znt: int | npt.ArrayLike,
+    zonenum: int | npt.ArrayLike,
+    krigtype: int | str | enum.KrigType,
+    # nzone: int,  # determined from shape[0] from any zonenum..rake else 1
+    vartype: int | str | enum.VarioType | npt.ArrayLike,
+    ahmax: float | npt.ArrayLike,
+    ahmin: float | npt.ArrayLike,
+    avert: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    dip: float | npt.ArrayLike,
+    rake: float | npt.ArrayLike,
+    srhmax: float,
+    srhmin: float,
+    srvert: float,
+    maxpts: int,
+    minpts: int,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+) -> int:
+    """
+    Calculate 3D kriging factors.
+
+    Parameters
+    ----------
+    ecs, ncs, zcs : array_like
+        Source point coordinates, each 1D array with shape (npts,).
+    zns : int or array_like
+        Source point zones, integer or 1D array with shape (npts,).
+    ect, nct, zct : array_like
+        Target point coordinates, each 1D array with shape (mpts,).
+    znt : int or array_like
+        Target point zones, integer or 1D array with shape (mpts,).
+    krigtype : int, str, or enum.KrigType,
+        Kriging type, where 0:simple, 1:ordinary.
+    zonenum : int, or array_like
+        Zone numbers, inteter or 1D array with shape (nzone,).
+    vartype : int, str, enum.VarioType or array_like
+        Variogram type, where 1:spher, 2:exp, 3:gauss, 4:pow. If array,
+        then it should have shape (nzone,).
+    ahmax, ahmin, avert : float or array_like
+        Variogram "a" values in 3 orthogonal directions (hmax, hmin, vert).
+        Each can be a float or 1D array with shape (nzone,).
+    bearing : float or array_like
+        Bearing of hmax, float or 1D array with shape (nzone,).
+    dip : float or array_like
+        Dip of hmax, float or 1D array with shape (nzone,).
+    rake : float or array_like
+        Twist about hmax axis, float or 1D array with shape (nzone,).
+    srhmax, srhmin, srvert : float
+        Search radius in hmax, hmin, and vert directions.
+    maxpts, minpts : int
+        Search specifications.
+    factorfile : str or PathLike
+        File for kriging factors.
+    factorfiletype : int, str or enum.FactorFileType
+        Factor file type, where 0:binary, 1:text.
+
+    Returns
+    -------
+    int
+        Number of interp points.
+    """
+    npta = ManyArrays({"ecs": ecs, "ncs": ncs, "zcs": zcs}, int_any={"zns": zns})
+    npts = len(npta)
+    mpta = ManyArrays({"ect": ect, "nct": nct, "zct": zct}, int_any={"znt": znt})
+    mpts = len(mpta)
+    if isinstance(krigtype, str):
+        krigtype = enum.KrigType.get_value(krigtype)
+    vartype = np.array(vartype)
+    if np.issubdtype(vartype.dtype, str):
+        vartype = np.vectorize(enum.VarioType.get_value)(vartype)
+    if not np.issubdtype(vartype.dtype, np.integer):
+        raise ValueError("expected 'vartype' to be integer, str or enum.VarioType")
+    nzone = ManyArrays(
+        float_any={
+            "ahmax": ahmax,
+            "ahmin": ahmin,
+            "avert": avert,
+            "bearing": bearing,
+            "dip": dip,
+            "rake": rake,
+        },
+        int_any={"zonenum": zonenum, "vartype": vartype},
+    )
+    factorfile = Path(factorfile)
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    icount_interp = c_int()
+    res = self.pestutils.calc_kriging_factors_3d(
+        byref(c_int(npts)),
+        npta.ecs,
+        npta.ncs,
+        npta.zcs,
+        npta.zns,
+        byref(c_int(mpts)),
+        mpta.ect,
+        mpta.nct,
+        mpta.zct,
+        mpta.znt,
+        byref(c_int(krigtype)),
+        byref(c_int(len(nzone))),
+        nzone.zonenum,
+        nzone.vartype,
+        nzone.ahmax,
+        nzone.ahmin,
+        nzone.avert,
+        nzone.bearing,
+        nzone.dip,
+        nzone.rake,
+        byref(c_double(srhmax)),
+        byref(c_double(srhmin)),
+        byref(c_double(srvert)),
+        byref(c_int(maxpts)),
+        byref(c_int(minpts)),
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(icount_interp),
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("calculated 3D kriging factors to %r", factorfile.name)
+    return icount_interp.value
+
+
+
+def calc_kriging_factors_auto_2d(self, ecs: npt.ArrayLike, ncs: npt.ArrayLike, zns: int | npt.ArrayLike, ect: npt.ArrayLike, nct: npt.ArrayLike, znt: int | npt.ArrayLike, krigtype: int | str | enum.KrigType, anis: float | npt.ArrayLike, bearing: float | npt.ArrayLike, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType) ‑> int +
+
+

Calculate 2D kriging factors, with automatic variogram properties.

+

Parameters

+
+
ecs, ncs : array_like
+
Source point coordinates, each 1D array with shape (npts,).
+
zns : int or array_like
+
Source point zones, integer or 1D array with shape (npts,).
+
ect, nct : array_like
+
Target point coordinates, each 1D array with shape (mpts,).
+
znt : int or array_like
+
Target point zones, integer or 1D array with shape (mpts,).
+
krigtype : int, str, enum.KrigType
+
Kriging type, where 0:simple, 1:ordinary.
+
anis : float or array_like
+
Variogram anisotropies, float or 1D array with shape (mpts,).
+
bearing : float or array_like
+
Variogram bearings, float or 1D array with shape (mpts,).
+
factorfile : str or PathLike
+
File for kriging factors.
+
factorfiletype : int, str or enum.FactorFileType
+
Factor file type, where 0:binary, 1:text.
+
+

Returns

+
+
int
+
Number of interp points.
+
+
+ +Expand source code + +
def calc_kriging_factors_auto_2d(
+    self,
+    # npts: int,  # determined from ecs.shape[0]
+    ecs: npt.ArrayLike,
+    ncs: npt.ArrayLike,
+    zns: int | npt.ArrayLike,
+    # mpts: int,  # determined from ect.shape[0]
+    ect: npt.ArrayLike,
+    nct: npt.ArrayLike,
+    znt: int | npt.ArrayLike,
+    krigtype: int | str | enum.KrigType,
+    anis: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+) -> int:
+    """
+    Calculate 2D kriging factors, with automatic variogram properties.
+
+    Parameters
+    ----------
+    ecs, ncs : array_like
+        Source point coordinates, each 1D array with shape (npts,).
+    zns : int or array_like
+        Source point zones, integer or 1D array with shape (npts,).
+    ect, nct : array_like
+        Target point coordinates, each 1D array with shape (mpts,).
+    znt : int or array_like
+        Target point zones, integer or 1D array with shape (mpts,).
+    krigtype : int, str, enum.KrigType
+        Kriging type, where 0:simple, 1:ordinary.
+    anis : float or array_like
+        Variogram anisotropies, float or 1D array with shape (mpts,).
+    bearing : float or array_like
+        Variogram bearings, float or 1D array with shape (mpts,).
+    factorfile : str or PathLike
+        File for kriging factors.
+    factorfiletype : int, str or enum.FactorFileType
+        Factor file type, where 0:binary, 1:text.
+
+    Returns
+    -------
+    int
+        Number of interp points.
+    """
+    npta = ManyArrays({"ecs": ecs, "ncs": ncs}, int_any={"zns": zns})
+    npts = len(npta)
+    mpta = ManyArrays(
+        {"ect": ect, "nct": nct}, {"anis": anis, "bearing": bearing}, {"znt": znt}
+    )
+    mpts = len(mpta)
+    if isinstance(krigtype, str):
+        krigtype = enum.KrigType.get_value(krigtype)
+    factorfile = Path(factorfile)
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    icount_interp = c_int()
+    res = self.pestutils.calc_kriging_factors_auto_2d(
+        byref(c_int(npts)),
+        npta.ecs,
+        npta.ncs,
+        npta.zns,
+        byref(c_int(mpts)),
+        mpta.ect,
+        mpta.nct,
+        mpta.znt,
+        byref(c_int(krigtype)),
+        mpta.anis,
+        mpta.bearing,
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(icount_interp),
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("calculated 2D auto kriging factors to %r", factorfile.name)
+    return icount_interp.value
+
+
+
+def calc_mf6_interp_factors(self, gridname: str, ecoord: npt.ArrayLike, ncoord: npt.ArrayLike, layer: int | npt.ArrayLike, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType, blnfile: str | PathLike) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]] +
+
+

Calculate interpolation factors from a MODFLOW 6 DIS or DISV.

+

Parameters

+
+
gridname : str
+
Unique non-blank grid name.
+
ecoord, ncoord : array_like
+
X/Y or Easting/Northing coordinates for points with shape (npts,).
+
layer : int or array_like
+
Layers of points with shape (npts,).
+
factorfile : str or PathLike
+
File for kriging factors to write.
+
factorfiletype : int, str or enum.FactorFileType
+
Factor file type, where 0:binary, 1:text.
+
blnfile : str or PathLike
+
Name of bln file to write.
+
+

Returns

+
+
npt.NDArray[np.int32]
+
Array interp_success(npts), where 1 is success and 0 is failure.
+
+
+ +Expand source code + +
def calc_mf6_interp_factors(
+    self,
+    gridname: str,
+    # npts: int,  # determined from ecoord.shape[0]
+    ecoord: npt.ArrayLike,
+    ncoord: npt.ArrayLike,
+    layer: int | npt.ArrayLike,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+    blnfile: str | PathLike,
+) -> npt.NDArray[np.int32]:
+    """Calculate interpolation factors from a MODFLOW 6 DIS or DISV.
+
+    Parameters
+    ----------
+    gridname : str
+        Unique non-blank grid name.
+    ecoord, ncoord : array_like
+        X/Y or Easting/Northing coordinates for points with shape (npts,).
+    layer : int or array_like
+        Layers of points with shape (npts,).
+    factorfile : str or PathLike
+        File for kriging factors to write.
+    factorfiletype : int, str or enum.FactorFileType
+        Factor file type, where 0:binary, 1:text.
+    blnfile : str or PathLike
+        Name of bln file to write.
+
+    Returns
+    -------
+    npt.NDArray[np.int32]
+        Array interp_success(npts), where 1 is success and 0 is failure.
+    """
+    pta = ManyArrays({"ecoord": ecoord, "ncoord": ncoord}, int_any={"layer": layer})
+    npts = len(pta)
+    factorfile = Path(factorfile)
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    blnfile = Path(blnfile)
+    interp_success = np.zeros(npts, np.int32, order="F")
+    res = self.pestutils.calc_mf6_interp_factors(
+        byref(self.create_char_array(gridname, "LENGRIDNAME")),
+        byref(c_int(npts)),
+        pta.ecoord,
+        pta.ncoord,
+        pta.layer,
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(self.create_char_array(bytes(blnfile), "LENFILENAME")),
+        interp_success,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("calculated mf6 interp factors for %r", gridname)
+    return interp_success.copy("A")
+
+
+
+def calc_structural_overlay_factors(self, ecs: npt.ArrayLike, ncs: npt.ArrayLike, ids: int | npt.ArrayLike, conwidth: npt.ArrayLike, aa: npt.ArrayLike, structype: int | str | enum.StrucType, inverse_power: float, ect: npt.ArrayLike, nct: npt.ArrayLike, active: int | npt.ArrayLike, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType) ‑> int +
+
+

Calculate interpolation/blending factors for structural overlay parameters.

+

Parameters

+
+
ecs, ncs : array_like
+
Source point coordinates, each 1D array with shape (npts,).
+
ids : int or array_like
+
Source point structure number, integer or 1D array with shape (npts,).
+
conwidth, aa : float or array_like
+
Blending parameters, float or 1D array with shape (npts,).
+
structype : int, str or enum.StrucType
+
Structure type, where 0 is polylinear and 1 is polygonal.
+
inverse_power : float
+
Inverse power of distance.
+
ect, nct : array_like
+
Target point coordinates, each 1D array with shape (mpts,).
+
active : int or array_like
+
Target point activity, integer or 1D array with shape (mpts,).
+
factorfile : str or PathLike
+
File for kriging factors.
+
factorfiletype : int, str or enum.FactorFileType
+
Factor file type, where 0:binary, 1:text.
+
+

Returns

+
+
int
+
Number of interp points.
+
+
+ +Expand source code + +
def calc_structural_overlay_factors(
+    self,
+    # npts: int,  # determined from ecs.shape[0]
+    ecs: npt.ArrayLike,
+    ncs: npt.ArrayLike,
+    ids: int | npt.ArrayLike,
+    conwidth: npt.ArrayLike,
+    aa: npt.ArrayLike,
+    structype: int | str | enum.StrucType,
+    inverse_power: float,
+    # mpts: int,  # determined from ect.shape[0]
+    ect: npt.ArrayLike,
+    nct: npt.ArrayLike,
+    active: int | npt.ArrayLike,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+) -> int:
+    """
+    Calculate interpolation/blending factors for structural overlay parameters.
+
+    Parameters
+    ----------
+    ecs, ncs : array_like
+        Source point coordinates, each 1D array with shape (npts,).
+    ids : int or array_like
+        Source point structure number, integer or 1D array with shape (npts,).
+    conwidth, aa : float or array_like
+        Blending parameters, float or 1D array with shape (npts,).
+    structype : int, str or enum.StrucType
+        Structure type, where 0 is polylinear and 1 is polygonal.
+    inverse_power : float
+        Inverse power of distance.
+    ect, nct : array_like
+        Target point coordinates, each 1D array with shape (mpts,).
+    active : int or array_like
+        Target point activity, integer or 1D array with shape (mpts,).
+    factorfile : str or PathLike
+        File for kriging factors.
+    factorfiletype : int, str or enum.FactorFileType
+        Factor file type, where 0:binary, 1:text.
+
+    Returns
+    -------
+    int
+        Number of interp points.
+    """
+    npta = ManyArrays(
+        {"ecs": ecs, "ncs": ncs}, {"conwidth": conwidth, "aa": aa}, {"ids": ids}
+    )
+    npts = len(npta)
+    mpta = ManyArrays({"ect": ect, "nct": nct}, int_any={"active": active})
+    mpts = len(mpta)
+    if isinstance(structype, str):
+        structype = enum.StrucType.get_value(structype)
+    factorfile = Path(factorfile)
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    icount_interp = c_int()
+    res = self.pestutils.calc_structural_overlay_factors(
+        byref(c_int(npts)),
+        npta.ecs,
+        npta.ncs,
+        npta.ids,
+        npta.conwidth,
+        npta.aa,
+        byref(c_int(structype)),
+        byref(c_double(inverse_power)),
+        byref(c_int(mpts)),
+        mpta.ect,
+        mpta.nct,
+        mpta.active,
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(icount_interp),
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info(
+        "calculated interpolation/blending factors to %r", factorfile.name
+    )
+    return icount_interp.value
+
+
+
+def create_char_array(self, init: str | bytes, name: str) +
+
+

Create c_char Array with a fixed size from dimvar and intial value.

+

Parameters

+
+
init : str or bytes
+
Initial value.
+
name : str
+
Uppercase variable length name, e.g. LENFILENAME or LENVARTYPE.
+
+
+ +Expand source code + +
def create_char_array(self, init: str | bytes, name: str):
+    """Create c_char Array with a fixed size from dimvar and intial value.
+
+    Parameters
+    ----------
+    init : str or bytes
+        Initial value.
+    name : str
+        Uppercase variable length name, e.g. LENFILENAME or LENVARTYPE.
+    """
+    from .ctypes_declarations import get_dimvar_int
+
+    if isinstance(init, str):
+        init = init.encode()
+    elif isinstance(init, bytes):
+        pass
+    else:
+        raise TypeError(f"expecting either str or bytes; found {type(init)}")
+    size = get_dimvar_int(self.pestutils, name)
+    if len(init) > size:
+        raise ValueError(f"init size is {len(init)} but {name} is {size}")
+    return create_string_buffer(init, size)
+
+
+
+def extract_flows_from_cbc_file(self, cbcfile: str | PathLike, flowtype: str, isim: int, iprec: int | str | enum.Prec, izone: npt.ArrayLike, nzone: int, ntime: int) ‑> dict +
+
+

Read and accumulates flows from a CBC flow file to a user-specified BC.

+

Parameters

+
+
cbcfile : str | PathLike
+
Cell-by-cell flow term file written by any MF version.
+
flowtype : str
+
Type of flow to read.
+
isim : int
+
Simulator type.
+
iprec : int, str or enum.Prec
+
Precision used to record real variables in cbc file.
+
izone : array_like
+
Zonation of model domain, with shape (ncell,).
+
nzone : int
+
Equals or exceeds number of zones; zone 0 doesn't count.
+
ntime : int
+
Equals or exceed number of model output times for flow type.
+
+

Returns

+
+
numzone : int
+
Number of non-zero-valued zones.
+
zonenumber : npt.NDArray[np.int32]
+
Zone numbers, with shape (nzone,).
+
nproctime : int
+
Number of processed simulation times.
+
timestep : npt.NDArray[np.int32]
+
Simulation time step, with shape (ntime,).
+
stressperiod : npt.NDArray[np.int32]
+
Simulation stress period, with shape (ntime,).
+
simtime : npt.NDArray[np.int32]
+
Simulation time, with shape (ntime,). +A time of -1.0 indicates unknown.
+
simflow : npt.NDArray[np.int32]
+
Interpolated flows, with shape (ntime, nzone).
+
+
+ +Expand source code + +
def extract_flows_from_cbc_file(
+    self,
+    cbcfile: str | PathLike,
+    flowtype: str,
+    isim: int,
+    iprec: int | str | enum.Prec,
+    # ncell: int,  # from izone.shape[0]
+    izone: npt.ArrayLike,
+    nzone: int,
+    ntime: int,
+) -> dict:
+    """
+    Read and accumulates flows from a CBC flow file to a user-specified BC.
+
+    Parameters
+    ----------
+    cbcfile : str | PathLike
+        Cell-by-cell flow term file written by any MF version.
+    flowtype : str
+        Type of flow to read.
+    isim : int
+        Simulator type.
+    iprec : int, str or enum.Prec
+        Precision used to record real variables in cbc file.
+    izone : array_like
+        Zonation of model domain, with shape (ncell,).
+    nzone : int
+        Equals or exceeds number of zones; zone 0 doesn't count.
+    ntime : int
+        Equals or exceed number of model output times for flow type.
+
+    Returns
+    -------
+    numzone : int
+        Number of non-zero-valued zones.
+    zonenumber : npt.NDArray[np.int32]
+        Zone numbers, with shape (nzone,).
+    nproctime : int
+        Number of processed simulation times.
+    timestep : npt.NDArray[np.int32]
+        Simulation time step, with shape (ntime,).
+    stressperiod : npt.NDArray[np.int32]
+        Simulation stress period, with shape (ntime,).
+    simtime : npt.NDArray[np.int32]
+        Simulation time, with shape (ntime,).
+        A time of -1.0 indicates unknown.
+    simflow : npt.NDArray[np.int32]
+        Interpolated flows, with shape (ntime, nzone).
+    """
+    cbcfile = Path(cbcfile)
+    if not cbcfile.is_file():
+        raise FileNotFoundError(f"could not find cbcfile {cbcfile}")
+    validate_scalar("flowtype", flowtype, minlen=1)
+    validate_scalar("iprec", iprec, enum=enum.Prec)
+    if isinstance(iprec, str):
+        iprec = enum.Prec.get_value(iprec)
+    cell = ManyArrays(int_any={"izone": izone})
+    ncell = len(cell)
+    numzone = c_int()
+    zonenumber = np.zeros(nzone, np.int32, order="F")
+    nproctime = c_int()
+    timestep = np.zeros(ntime, np.int32, order="F")
+    stressperiod = np.zeros(ntime, np.int32, order="F")
+    simtime = np.zeros(ntime, np.float64, order="F")
+    simflow = np.zeros((ntime, nzone), np.float64, order="F")
+    res = self.pestutils.extract_flows_from_cbc_file(
+        byref(self.create_char_array(bytes(cbcfile), "LENFILENAME")),
+        byref(self.create_char_array(flowtype, "LENFLOWTYPE")),
+        byref(c_int(isim)),
+        byref(c_int(iprec)),
+        byref(c_int(ncell)),
+        cell.izone,
+        byref(c_int(nzone)),
+        byref(numzone),
+        zonenumber,
+        byref(c_int(ntime)),
+        byref(nproctime),
+        timestep,
+        stressperiod,
+        simtime,
+        simflow,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("extracted flows from %r", cbcfile.name)
+    return {
+        "numzone": numzone.value,
+        "zonenumber": zonenumber.copy("A"),
+        "nproctime": nproctime.value,
+        "timestep": timestep.copy("A"),
+        "stressperiod": stressperiod.copy("A"),
+        "simtime": simtime.copy("A"),
+        "simflow": simflow.copy("A"),
+    }
+
+
+
+def fieldgen2d_sva(self, ec: npt.ArrayLike, nc: npt.ArrayLike, area: float | npt.ArrayLike, active: int | npt.ArrayLike, mean: float | npt.ArrayLike, var: float | npt.ArrayLike, aa: float | npt.ArrayLike, anis: float | npt.ArrayLike, bearing: float | npt.ArrayLike, transtype: int | str | enum.TransType, avetype: int | str | enum.VarioType, power: float, nreal: int) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +
+
+

Generate 2D stochastic fields based on a spatially varying variogram.

+

Parameters

+
+
ec, nc : array_like
+
Model grid coordinates, each 1D array with shape (nnode,).
+
area : float or array_like
+
Areas of grid cells.
+
active : int or array_like
+
Inactive grid cells are equal to zero.
+
mean : float or array_like
+
Mean value of stochastic field.
+
var : float or array_like
+
Variance of stochastic field.
+
aa : float or array_like
+
Averaging function spatial dimension.
+
anis : float or array_like
+
Anisotropy ratio.
+
bearing : float or array_like
+
Bearing of principal anisotropy axis.
+
transtype : int, str or enum.TransType
+
Stochastic field pertains to natural(0) or log(1) properties.
+
avetype : int, str or enum.VarioType
+
Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+
power : float
+
Power used if avetype is 4 (pow).
+
nreal : int
+
Number of realisations to generate.
+
+

Returns

+
+
npt.NDArray[np.float64]
+
Realisations with shape (nnode, nreal).
+
+
+ +Expand source code + +
def fieldgen2d_sva(
+    self,
+    # nnode: int,  # determined from ec.shape[0]
+    ec: npt.ArrayLike,
+    nc: npt.ArrayLike,
+    area: float | npt.ArrayLike,
+    active: int | npt.ArrayLike,
+    mean: float | npt.ArrayLike,
+    var: float | npt.ArrayLike,
+    aa: float | npt.ArrayLike,
+    anis: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    transtype: int | str | enum.TransType,
+    avetype: int | str | enum.VarioType,
+    power: float,
+    # ldrand: int,  # same as nnode
+    nreal: int,
+) -> npt.NDArray[np.float64]:
+    """
+    Generate 2D stochastic fields based on a spatially varying variogram.
+
+    Parameters
+    ----------
+    ec, nc : array_like
+        Model grid coordinates, each 1D array with shape (nnode,).
+    area : float or array_like
+        Areas of grid cells.
+    active : int or array_like
+        Inactive grid cells are equal to zero.
+    mean : float or array_like
+        Mean value of stochastic field.
+    var : float or array_like
+        Variance of stochastic field.
+    aa : float or array_like
+        Averaging function spatial dimension.
+    anis : float or array_like
+        Anisotropy ratio.
+    bearing : float or array_like
+        Bearing of principal anisotropy axis.
+    transtype : int, str or enum.TransType
+        Stochastic field pertains to natural(0) or log(1) properties.
+    avetype : int, str or enum.VarioType
+        Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+    power : float
+        Power used if avetype is 4 (pow).
+    nreal : int
+        Number of realisations to generate.
+
+    Returns
+    -------
+    npt.NDArray[np.float64]
+        Realisations with shape (nnode, nreal).
+    """
+    node = ManyArrays(
+        {"ec": ec, "nc": nc},
+        {
+            "area": area,
+            "mean": mean,
+            "var": var,
+            "aa": aa,
+            "anis": anis,
+            "bearing": bearing,
+        },
+        {"active": active},
+    )
+    if isinstance(transtype, str):
+        transtype = enum.TransType.get_value(transtype)
+    if isinstance(avetype, str):
+        avetype = enum.VarioType.get_value(avetype)
+    ldrand = nnode = len(node)
+    randfield = np.zeros((ldrand, nreal), np.float64, order="F")
+    res = self.pestutils.fieldgen2d_sva(
+        byref(c_int(nnode)),
+        node.ec,
+        node.nc,
+        node.area,
+        node.active,
+        node.mean,
+        node.var,
+        node.aa,
+        node.anis,
+        node.bearing,
+        byref(c_int(transtype)),
+        byref(c_int(avetype)),
+        byref(c_double(power)),
+        byref(c_int(ldrand)),
+        byref(c_int(nreal)),
+        randfield,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("generated 2D stochastic fields for %d realisations", nreal)
+    return randfield.copy("A")
+
+
+
+def fieldgen3d_sva(self, ec: npt.ArrayLike, nc: npt.ArrayLike, zc: npt.ArrayLike, area: float | npt.ArrayLike, height: float | npt.ArrayLike, active: int | npt.ArrayLike, mean: float | npt.ArrayLike, var: float | npt.ArrayLike, ahmax: float | npt.ArrayLike, ahmin: float | npt.ArrayLike, avert: float | npt.ArrayLike, bearing: float | npt.ArrayLike, dip: float | npt.ArrayLike, rake: float | npt.ArrayLike, transtype: int | str | enum.TransType, avetype: int | str | enum.VarioType, power: float, nreal: int) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +
+
+

Generate 3D stochastic fields based on a spatially varying variogram.

+

Parameters

+
+
ec, nc, nz : array_like
+
Model grid coordinates, each 1D array with shape (nnode,).
+
area, height : float or array_like
+
Areas and height of grid cells.
+
active : int or array_like
+
Inactive grid cells are equal to zero.
+
mean : float or array_like
+
Mean value of stochastic field.
+
var : float or array_like
+
Variance of stochastic field.
+
ahmax, ahmin, avert : float or array_like
+
Averaging function correlation lengths.
+
bearing : float or array_like
+
Bearing of ahmax direction.
+
dip : float or array_like
+
Dip of ahmax direction.
+
rake : float or array_like
+
Rotation of ahmin direction.
+
transtype : int, str or enum.TransType
+
Stochastic field pertains to natural(0) or log(1) properties.
+
avetype : int, str or enum.VarioType
+
Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+
power : float
+
Power used if avetype is 4 (pow).
+
nreal : int
+
Number of realisations to generate.
+
+

Returns

+
+
npt.NDArray[np.float64]
+
Realisations with shape (nnode, nreal).
+
+
+ +Expand source code + +
def fieldgen3d_sva(
+    self,
+    # nnode: int,  # determined from ec.shape[0]
+    ec: npt.ArrayLike,
+    nc: npt.ArrayLike,
+    zc: npt.ArrayLike,
+    area: float | npt.ArrayLike,
+    height: float | npt.ArrayLike,
+    active: int | npt.ArrayLike,
+    mean: float | npt.ArrayLike,
+    var: float | npt.ArrayLike,
+    ahmax: float | npt.ArrayLike,
+    ahmin: float | npt.ArrayLike,
+    avert: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    dip: float | npt.ArrayLike,
+    rake: float | npt.ArrayLike,
+    transtype: int | str | enum.TransType,
+    avetype: int | str | enum.VarioType,
+    power: float,
+    # ldrand: int,  # same as nnode
+    nreal: int,
+) -> npt.NDArray[np.float64]:
+    """
+    Generate 3D stochastic fields based on a spatially varying variogram.
+
+    Parameters
+    ----------
+    ec, nc, nz : array_like
+        Model grid coordinates, each 1D array with shape (nnode,).
+    area, height : float or array_like
+        Areas and height of grid cells.
+    active : int or array_like
+        Inactive grid cells are equal to zero.
+    mean : float or array_like
+        Mean value of stochastic field.
+    var : float or array_like
+        Variance of stochastic field.
+    ahmax, ahmin, avert : float or array_like
+        Averaging function correlation lengths.
+    bearing : float or array_like
+        Bearing of ahmax direction.
+    dip : float or array_like
+        Dip of ahmax direction.
+    rake : float or array_like
+        Rotation of ahmin direction.
+    transtype : int, str or enum.TransType
+        Stochastic field pertains to natural(0) or log(1) properties.
+    avetype : int, str or enum.VarioType
+        Averaging function type, where 1:spher, 2:exp, 3:gauss, 4:pow.
+    power : float
+        Power used if avetype is 4 (pow).
+    nreal : int
+        Number of realisations to generate.
+
+    Returns
+    -------
+    npt.NDArray[np.float64]
+        Realisations with shape (nnode, nreal).
+    """
+    node = ManyArrays(
+        {"ec": ec, "nc": nc, "zc": zc},
+        {
+            "area": area,
+            "height": height,
+            "mean": mean,
+            "var": var,
+            "ahmax": ahmax,
+            "ahmin": ahmin,
+            "avert": avert,
+            "bearing": bearing,
+            "dip": dip,
+            "rake": rake,
+        },
+        {"active": active},
+    )
+    if isinstance(transtype, str):
+        transtype = enum.TransType.get_value(transtype)
+    if isinstance(avetype, str):
+        avetype = enum.VarioType.get_value(avetype)
+    ldrand = nnode = len(node)
+    randfield = np.zeros((ldrand, nreal), np.float64, order="F")
+    res = self.pestutils.fieldgen3d_sva(
+        byref(c_int(nnode)),
+        node.ec,
+        node.nc,
+        node.zc,
+        node.area,
+        node.height,
+        node.active,
+        node.mean,
+        node.var,
+        node.ahmax,
+        node.ahmin,
+        node.avert,
+        node.bearing,
+        node.dip,
+        node.rake,
+        byref(c_int(transtype)),
+        byref(c_int(avetype)),
+        byref(c_double(power)),
+        byref(c_int(ldrand)),
+        byref(c_int(nreal)),
+        randfield,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("generated 3D stochastic fields for %d realisations", nreal)
+    return randfield.copy("A")
+
+
+
+def free_all_memory(self) ‑> None +
+
+

Deallocate all memory that is being used.

+
+ +Expand source code + +
def free_all_memory(self) -> None:
+    """Deallocate all memory that is being used."""
+    ret = self.pestutils.free_all_memory()
+    if ret != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("all memory was freed up")
+
+
+
+def get_cell_centres_mf6(self, gridname: str, ncells: int) ‑> tuple +
+
+

Get cell centres from an installed MF6 grid.

+

Parameters

+
+
gridname : str
+
Name of installed MF6 grid.
+
ncells : int
+
Dimensions of grid.
+
+

Returns

+
+
cellx, cellx, cellz : npt.NDArray[np.float64]
+
Coordinates of cell centres with dimensions (ncells,).
+
+
+ +Expand source code + +
def get_cell_centres_mf6(self, gridname: str, ncells: int) -> tuple:
+    """Get cell centres from an installed MF6 grid.
+
+    Parameters
+    ----------
+    gridname : str
+        Name of installed MF6 grid.
+    ncells : int
+        Dimensions of grid.
+
+    Returns
+    -------
+    cellx, cellx, cellz : npt.NDArray[np.float64]
+        Coordinates of cell centres with dimensions (ncells,).
+    """
+    cellx = np.zeros(ncells, np.float64, order="F")
+    celly = np.zeros(ncells, np.float64, order="F")
+    cellz = np.zeros(ncells, np.float64, order="F")
+    res = self.pestutils.get_cell_centres_mf6(
+        byref(self.create_char_array(gridname, "LENGRIDNAME")),
+        byref(c_int(ncells)),
+        cellx,
+        celly,
+        cellz,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("evaluated %d cell centres from MF6 grid %r", ncells, gridname)
+    return cellx.copy("A"), celly.copy("A"), cellz.copy("A")
+
+
+
+def get_cell_centres_structured(self, gridname: str, ncpl: int) ‑> tuple +
+
+

Get cell centres of a single layer of an installed structured grid.

+

Parameters

+
+
gridname : str
+
Name of installed structured grid.
+
ncpl : int
+
Dimensions of grid (nrow x ncol).
+
+

Returns

+
+
cellx, cellx : npt.NDArray[np.float64]
+
Coordinates of cell centres with dimensions (ncpl,).
+
+
+ +Expand source code + +
def get_cell_centres_structured(self, gridname: str, ncpl: int) -> tuple:
+    """Get cell centres of a single layer of an installed structured grid.
+
+    Parameters
+    ----------
+    gridname : str
+        Name of installed structured grid.
+    ncpl : int
+        Dimensions of grid (nrow x ncol).
+
+    Returns
+    -------
+    cellx, cellx : npt.NDArray[np.float64]
+        Coordinates of cell centres with dimensions (ncpl,).
+    """
+    cellx = np.zeros(ncpl, np.float64, order="F")
+    celly = np.zeros(ncpl, np.float64, order="F")
+    res = self.pestutils.get_cell_centres_structured(
+        byref(self.create_char_array(gridname, "LENGRIDNAME")),
+        byref(c_int(ncpl)),
+        cellx,
+        celly,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info(
+        "evaluated %d cell centres from structured grid %r", ncpl, gridname
+    )
+    return cellx.copy("A"), celly.copy("A")
+
+
+
+def initialize_randgen(self, iseed: int) ‑> None +
+
+

Initialize the random number generator.

+

Parameters

+
+
iseed : int
+
Seed value.
+
+
+ +Expand source code + +
def initialize_randgen(self, iseed: int) -> None:
+    """
+    Initialize the random number generator.
+
+    Parameters
+    ----------
+    iseed : int
+        Seed value.
+    """
+    res = self.pestutils.initialize_randgen(byref(c_int(iseed)))
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("initialized the random number generator")
+
+
+
+def inquire_modflow_binary_file_specs(self, filein: str | PathLike, fileout: str | PathLike | None, isim: int, itype: int) ‑> dict +
+
+

Report some of the details of a MODFLOW-written binary file.

+

Parameters

+
+
filein : str or PathLike
+
MODFLOW-generated binary file to be read.
+
fileout : str, PathLike, None
+
Output file with with table of array headers. Use None or "" for +no output file.
+
isim : int
+
+

Inform the function the simulator that generated the binary file:

+
    +
  • 1 = traditional MODFLOW
  • +
  • 21 = MODFLOW-USG with structured grid
  • +
  • 22 = MODFLOW-USG with unstructured grid
  • +
  • 31 = MODFLOW 6 with DIS grid
  • +
  • 32 = MODFLOW 6 with DISV grid
  • +
  • 33 = MODFLOW 6 with DISU grid
  • +
+
+
itype : int
+
Where 1 = system state or dependent variable; +2 = cell-by-cell flows.
+
+

Returns

+
+
iprec : int
+
Where 1 = single; 2 = double.
+
narray : int
+
Number of arrays.
+
ntime : int
+
Number of times.
+
+
+ +Expand source code + +
def inquire_modflow_binary_file_specs(
+    self,
+    filein: str | PathLike,
+    fileout: str | PathLike | None,
+    isim: int,
+    itype: int,
+) -> dict:
+    """Report some of the details of a MODFLOW-written binary file.
+
+    Parameters
+    ----------
+    filein : str or PathLike
+        MODFLOW-generated binary file to be read.
+    fileout : str, PathLike, None
+        Output file with with table of array headers. Use None or "" for
+        no output file.
+    isim : int
+        Inform the function the simulator that generated the binary file:
+
+         * 1 = traditional MODFLOW
+         * 21 = MODFLOW-USG with structured grid
+         * 22 = MODFLOW-USG with unstructured grid
+         * 31 = MODFLOW 6 with DIS grid
+         * 32 = MODFLOW 6 with DISV grid
+         * 33 = MODFLOW 6 with DISU grid
+
+    itype : int
+        Where 1 = system state or dependent variable;
+        2 = cell-by-cell flows.
+
+    Returns
+    -------
+    iprec : int
+        Where 1 = single; 2 = double.
+    narray : int
+        Number of arrays.
+    ntime : int
+        Number of times.
+    """
+    filein = Path(filein)
+    if not filein.is_file():
+        raise FileNotFoundError(f"could not find filein {filein}")
+    if fileout:
+        fileout = Path(fileout)
+    else:
+        fileout = b""
+    validate_scalar("isim", isim, isin=[1, 21, 22, 31, 32, 33])
+    validate_scalar("itype", itype, isin=[1, 2])
+    iprec = c_int()
+    narray = c_int()
+    ntime = c_int()
+    res = self.pestutils.inquire_modflow_binary_file_specs(
+        byref(self.create_char_array(bytes(filein), "LENFILENAME")),
+        byref(self.create_char_array(bytes(fileout), "LENFILENAME")),
+        byref(c_int(isim)),
+        byref(c_int(itype)),
+        byref(iprec),
+        byref(narray),
+        byref(ntime),
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("inquired modflow binary file specs from %r", filein.name)
+    return {
+        "iprec": iprec.value,
+        "narray": narray.value,
+        "ntime": ntime.value,
+    }
+
+
+
+def install_mf6_grid_from_file(self, gridname: str, grbfile: str | PathLike) ‑> dict +
+
+

Install specifications for a MF6 grid from a GRB file.

+

Parameters

+
+
gridname : str
+
Unique non-blank grid name.
+
grbfile : str or PathLike
+
Path to a GRB binary grid file.
+
+

Returns

+
+
idis : int
+
Where 1 is for DIS and 2 is for DISV.
+
ncells : int
+
Number of cells in the grid.
+
ndim1, ndim2, ndim3 : int
+
Grid dimensions.
+
+
+ +Expand source code + +
def install_mf6_grid_from_file(
+    self, gridname: str, grbfile: str | PathLike
+) -> dict:
+    """Install specifications for a MF6 grid from a GRB file.
+
+    Parameters
+    ----------
+    gridname : str
+        Unique non-blank grid name.
+    grbfile : str or PathLike
+        Path to a GRB binary grid file.
+
+    Returns
+    -------
+    idis : int
+        Where 1 is for DIS and 2 is for DISV.
+    ncells : int
+        Number of cells in the grid.
+    ndim1, ndim2, ndim3 : int
+        Grid dimensions.
+    """
+    grbfile = Path(grbfile)
+    if not grbfile.is_file():
+        raise FileNotFoundError(f"could not find grbfile {grbfile}")
+    idis = c_int()
+    ncells = c_int()
+    ndim1 = c_int()
+    ndim2 = c_int()
+    ndim3 = c_int()
+    res = self.pestutils.install_mf6_grid_from_file(
+        byref(self.create_char_array(gridname, "LENGRIDNAME")),
+        byref(self.create_char_array(bytes(grbfile), "LENFILENAME")),
+        byref(idis),
+        byref(ncells),
+        byref(ndim1),
+        byref(ndim2),
+        byref(ndim3),
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info(
+        "installed mf6 grid %r from grbfile=%r", gridname, grbfile.name
+    )
+    return {
+        "idis": idis.value,
+        "ncells": ncells.value,
+        "ndim1": ndim1.value,
+        "ndim2": ndim2.value,
+        "ndim3": ndim3.value,
+    }
+
+
+
+def install_structured_grid(self, gridname: str, ncol: int, nrow: int, nlay: int, icorner: int, e0: float, n0: float, rotation: float, delr: float | npt.ArrayLike, delc: float | npt.ArrayLike) ‑> None +
+
+

Install specifications for a structured grid.

+

Parameters

+
+
gridname : str
+
Unique non-blank grid name.
+
ncol, nrow, nlay : int
+
Grid dimensions.
+
icorner : int
+
Reference corner, use 1 for top left and 2 for bottom left.
+
e0, n0 : float
+
Reference offsets.
+
rotation : float
+
Grid rotation, counter-clockwise degrees.
+
+
+ +Expand source code + +
def install_structured_grid(
+    self,
+    gridname: str,
+    ncol: int,
+    nrow: int,
+    nlay: int,
+    icorner: int,
+    e0: float,
+    n0: float,
+    rotation: float,
+    delr: float | npt.ArrayLike,
+    delc: float | npt.ArrayLike,
+) -> None:
+    """Install specifications for a structured grid.
+
+    Parameters
+    ----------
+    gridname : str
+        Unique non-blank grid name.
+    ncol, nrow, nlay : int
+        Grid dimensions.
+    icorner : int
+        Reference corner, use 1 for top left and 2 for bottom left.
+    e0, n0 : float
+        Reference offsets.
+    rotation : float
+        Grid rotation, counter-clockwise degrees.
+    """
+    validate_scalar("ncol", ncol, gt=0)
+    validate_scalar("nrow", nrow, gt=0)
+    validate_scalar("nlay", nlay, gt=0)
+    col = ManyArrays(float_any={"delr": delr}, ar_len=ncol)
+    row = ManyArrays(float_any={"delc": delc}, ar_len=nrow)
+    col.validate("delr", gt=0.0)
+    row.validate("delc", gt=0.0)
+    validate_scalar("icorner", icorner, isin=[1, 2])
+    res = self.pestutils.install_structured_grid(
+        byref(self.create_char_array(gridname, "LENGRIDNAME")),
+        byref(c_int(ncol)),
+        byref(c_int(nrow)),
+        byref(c_int(nlay)),
+        byref(c_int(icorner)),
+        byref(c_double(e0)),
+        byref(c_double(n0)),
+        byref(c_double(rotation)),
+        col.delr,
+        row.delc,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("installed structured grid %r from specs", gridname)
+
+
+
+def interp_from_mf6_depvar_file(self, depvarfile: str | PathLike, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType, ntime: int, vartype: str, interpthresh: float, reapportion: int | bool, nointerpval: float, npts: int) ‑> dict +
+
+

Interpolate points using previously-calculated interpolation factors.

+

Parameters

+
+
depvarfile : str or PathLike
+
Name of binary file to read.
+
factorfile : str or PathLike
+
File containing spatial interpolation factors, written by +:meth:calc_mf6_interp_factors.
+
factorfiletype : int, str or enum.FactorFileType
+
Use 0 for binary; 1 for text.
+
ntime : int
+
Number of output times.
+
vartype : str
+
Only read arrays of this type.
+
interpthresh : float
+
Absolute threshold for dry or inactive.
+
reapportion : int or bool
+
Use 0 for no (False); 1 for yes (True).
+
nointerpval : float
+
Value to use where interpolation is not possible.
+
npts : int
+
Number of points for interpolation.
+
+

Returns

+
+
nproctime : int
+
Number of processed simulation times.
+
simtime : npt.NDArray[np.float64]
+
Simulation times, with shape (ntime,).
+
simstate : npt.NDArray[np.float64]
+
Interpolated system states, with shape (ntime, npts).
+
+
+ +Expand source code + +
def interp_from_mf6_depvar_file(
+    self,
+    depvarfile: str | PathLike,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+    ntime: int,
+    vartype: str,
+    interpthresh: float,
+    reapportion: int | bool,
+    nointerpval: float,
+    npts: int,
+) -> dict:
+    """
+    Interpolate points using previously-calculated interpolation factors.
+
+    Parameters
+    ----------
+    depvarfile : str or PathLike
+        Name of binary file to read.
+    factorfile : str or PathLike
+        File containing spatial interpolation factors, written by
+        :meth:`calc_mf6_interp_factors`.
+    factorfiletype : int, str or enum.FactorFileType
+        Use 0 for binary; 1 for text.
+    ntime : int
+        Number of output times.
+    vartype : str
+        Only read arrays of this type.
+    interpthresh : float
+        Absolute threshold for dry or inactive.
+    reapportion : int or bool
+        Use 0 for no (False); 1 for yes (True).
+    nointerpval : float
+        Value to use where interpolation is not possible.
+    npts : int
+        Number of points for interpolation.
+
+    Returns
+    -------
+    nproctime : int
+        Number of processed simulation times.
+    simtime : npt.NDArray[np.float64]
+        Simulation times, with shape (ntime,).
+    simstate : npt.NDArray[np.float64]
+        Interpolated system states, with shape (ntime, npts).
+    """
+    depvarfile = Path(depvarfile)
+    if not depvarfile.is_file():
+        raise FileNotFoundError(f"could not find depvarfile {depvarfile}")
+    factorfile = Path(factorfile)
+    if not factorfile.is_file():
+        raise FileNotFoundError(f"could not find factorfile {factorfile}")
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    simtime = np.zeros(ntime, np.float64, order="F")
+    simstate = np.zeros((ntime, npts), np.float64, order="F")
+    nproctime = c_int()
+    res = self.pestutils.interp_from_mf6_depvar_file(
+        byref(self.create_char_array(bytes(depvarfile), "LENFILENAME")),
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(c_int(ntime)),
+        byref(self.create_char_array(vartype, "LENVARTYPE")),
+        byref(c_double(interpthresh)),
+        byref(c_int(reapportion)),
+        byref(c_double(nointerpval)),
+        byref(c_int(npts)),
+        byref(nproctime),
+        simtime,
+        simstate,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info(
+        "interpolated %d points from mf6 depvar file %r", npts, depvarfile.name
+    )
+    return {
+        "nproctime": nproctime.value,
+        "simtime": simtime.copy("A"),
+        "simstate": simstate.copy("A"),
+    }
+
+
+
+def interp_from_structured_grid(self, gridname: str, depvarfile: str | PathLike, isim: int, iprec: int | str | enum.Prec, ntime: int, vartype: str, interpthresh: float, nointerpval: float, ecoord: npt.ArrayLike, ncoord: npt.ArrayLike, layer: int | npt.ArrayLike) ‑> dict +
+
+

Spatial interpolate points from a structured grid.

+

Parameters

+
+
gridname : str
+
Name of installed structured grid.
+
depvarfile : str or PathLike
+
Name of binary file to read.
+
isim : int
+
Specify -1 for MT3D; 1 for MODFLOW.
+
iprec : int, str or enum.Prec
+
Specify 1 or "single", 2 or "double", or use enum.Prec.
+
ntime : int
+
Number of output times.
+
vartype : str
+
Only read arrays of this type.
+
interpthresh : float
+
Absolute threshold for dry or inactive.
+
nointerpval : float
+
Value to use where interpolation is not possible.
+
ecoord, ncoord : array_like
+
X/Y or Easting/Northing coordinates for points with shape (npts,).
+
layer : int or array_like
+
Layers of points with shape (npts,).
+
+

Returns

+
+
nproctime : int
+
Number of processed simulation times.
+
simtime : npt.NDArray[np.float64]
+
Simulation times, with shape (ntime,).
+
simstate : npt.NDArray[np.float64]
+
Interpolated system states, with shape (ntime, npts).
+
+
+ +Expand source code + +
def interp_from_structured_grid(
+    self,
+    gridname: str,
+    depvarfile: str | PathLike,
+    isim: int,
+    iprec: int | str | enum.Prec,
+    ntime: int,
+    vartype: str,
+    interpthresh: float,
+    nointerpval: float,
+    # npts: int,  # determined from layer.shape[0]
+    ecoord: npt.ArrayLike,
+    ncoord: npt.ArrayLike,
+    layer: int | npt.ArrayLike,
+) -> dict:
+    """Spatial interpolate points from a structured grid.
+
+    Parameters
+    ----------
+    gridname : str
+        Name of installed structured grid.
+    depvarfile : str or PathLike
+        Name of binary file to read.
+    isim : int
+        Specify -1 for MT3D; 1 for MODFLOW.
+    iprec : int, str or enum.Prec
+        Specify 1 or "single", 2 or "double", or use enum.Prec.
+    ntime : int
+        Number of output times.
+    vartype : str
+        Only read arrays of this type.
+    interpthresh : float
+        Absolute threshold for dry or inactive.
+    nointerpval : float
+        Value to use where interpolation is not possible.
+    ecoord, ncoord : array_like
+        X/Y or Easting/Northing coordinates for points with shape (npts,).
+    layer : int or array_like
+        Layers of points with shape (npts,).
+
+    Returns
+    -------
+    nproctime : int
+        Number of processed simulation times.
+    simtime : npt.NDArray[np.float64]
+        Simulation times, with shape (ntime,).
+    simstate : npt.NDArray[np.float64]
+        Interpolated system states, with shape (ntime, npts).
+    """
+    depvarfile = Path(depvarfile)
+    if not depvarfile.is_file():
+        raise FileNotFoundError(f"could not find depvarfile {depvarfile}")
+    if isinstance(iprec, str):
+        iprec = enum.Prec.get_value(iprec)
+    pta = ManyArrays({"ecoord": ecoord, "ncoord": ncoord}, int_any={"layer": layer})
+    npts = len(pta)
+    simtime = np.zeros(ntime, np.float64, order="F")
+    simstate = np.zeros((ntime, npts), np.float64, order="F")
+    nproctime = c_int()
+    res = self.pestutils.interp_from_structured_grid(
+        byref(self.create_char_array(gridname, "LENGRIDNAME")),
+        byref(self.create_char_array(bytes(depvarfile), "LENFILENAME")),
+        byref(c_int(isim)),
+        byref(c_int(iprec)),
+        byref(c_int(ntime)),
+        byref(self.create_char_array(vartype, "LENVARTYPE")),
+        byref(c_double(interpthresh)),
+        byref(c_double(nointerpval)),
+        byref(c_int(npts)),
+        pta.ecoord,
+        pta.ncoord,
+        pta.layer,
+        byref(nproctime),
+        simtime,
+        simstate,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info(
+        "interpolated %d points from structured grid %r", npts, gridname
+    )
+    return {
+        "nproctime": nproctime.value,
+        "simtime": simtime.copy("A"),
+        "simstate": simstate.copy("A"),
+    }
+
+
+
+def interp_to_obstime(self, nproctime: int, simtime: npt.ArrayLike, simval: npt.ArrayLike, interpthresh: float, how_extrap: str, time_extrap: float, nointerpval: float, obspoint: npt.ArrayLike, obstime: npt.ArrayLike) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +
+
+

Temporal interpolation for simulation times to observed times.

+

Parameters

+
+
nproctime : int
+
Number of times featured in simtime and simval.
+
simtime : array_like
+
1D array of simulation times with shape (nsimtime,).
+
simval : array_like
+
2D array of simulated values with shape (nsimtime, npts).
+
interpthresh : float
+
Values equal or above this in simval have no meaning.
+
how_extrap : str
+
Method, where 'L'=linear; 'C'=constant.
+
time_extrap : float
+
Permitted extrapolation time.
+
nointerpval : float
+
Value to use where interpolation is not possible.
+
obspoint : array_like
+
1D integer array of indices of observation points, +which start at 0 and -1 means no index. Shape is (nobs,).
+
obstime : array_like
+
1D array of observation times with shape (nobs,).
+
+

Returns

+
+
np.ndarray
+
Time-interpolated simulation values with shape (nobs,).
+
+
+ +Expand source code + +
def interp_to_obstime(
+    self,
+    # nsimtime: int,  # determined from simval.shape[0]
+    nproctime: int,
+    # npts: int,  # determined from simval.shape[1]
+    simtime: npt.ArrayLike,
+    simval: npt.ArrayLike,
+    interpthresh: float,
+    how_extrap: str,
+    time_extrap: float,
+    nointerpval: float,
+    # nobs: int,  # determined from obspoint.shape[0]
+    obspoint: npt.ArrayLike,
+    obstime: npt.ArrayLike,
+) -> npt.NDArray[np.float64]:
+    """Temporal interpolation for simulation times to observed times.
+
+    Parameters
+    ----------
+    nproctime : int
+        Number of times featured in simtime and simval.
+    simtime : array_like
+        1D array of simulation times with shape (nsimtime,).
+    simval : array_like
+        2D array of simulated values with shape (nsimtime, npts).
+    interpthresh : float
+        Values equal or above this in simval have no meaning.
+    how_extrap : str
+        Method, where 'L'=linear; 'C'=constant.
+    time_extrap : float
+        Permitted extrapolation time.
+    nointerpval : float
+        Value to use where interpolation is not possible.
+    obspoint : array_like
+        1D integer array of indices of observation points,
+        which start at 0 and -1 means no index. Shape is (nobs,).
+    obstime : array_like
+        1D array of observation times with shape (nobs,).
+
+    Returns
+    -------
+    np.ndarray
+        Time-interpolated simulation values with shape (nobs,).
+    """
+    simtime = np.array(simtime, dtype=np.float64, order="F", copy=False)
+    simval = np.array(simval, dtype=np.float64, order="F", copy=False)
+    obspoint = np.array(obspoint, order="F", copy=False)
+    obstime = np.array(obstime, dtype=np.float64, order="F", copy=False)
+    if simtime.ndim != 1:
+        raise ValueError("expected 'simtime' to have ndim=1")
+    elif simval.ndim != 2:
+        raise ValueError("expected 'simval' to have ndim=2")
+    elif obspoint.ndim != 1:
+        raise ValueError("expected 'obspoint' to have ndim=1")
+    elif obstime.ndim != 1:
+        raise ValueError("expected 'obstime' to have ndim=1")
+    elif not np.issubdtype(obspoint.dtype, np.integer):
+        raise ValueError(
+            f"expected 'obspoint' to be integer type; found {obspoint.dtype}"
+        )
+    nsimtime, npts = simval.shape
+    nobs = len(obspoint)
+    obssimval = np.zeros(nobs, np.float64, order="F")
+    res = self.pestutils.interp_to_obstime(
+        byref(c_int(nsimtime)),
+        byref(c_int(nproctime)),
+        byref(c_int(npts)),
+        simtime,
+        simval,
+        byref(c_double(interpthresh)),
+        byref(c_char(how_extrap.encode())),
+        byref(c_double(time_extrap)),
+        byref(c_double(nointerpval)),
+        byref(c_int(nobs)),
+        obspoint.astype(np.int32, copy=False),
+        obstime,
+        obssimval,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("interpolated %d time points to %d observations", npts, nobs)
+    return obssimval.copy("A")
+
+
+
+def interpolate_blend_using_file(self, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType, transtype: int | str | enum.TransType, lt_target: str | bool, gt_target: str | bool, sourceval: npt.ArrayLike, targval: npt.ArrayLike) ‑> dict +
+
+

Apply interpolation factors calculated by :meth:calc_structural_overlay_factors.

+

Parameters

+
+
factorfile : str or PathLike
+
File for kriging factors.
+
factorfiletype : int, str or enum.FactorFileType
+
Factor file type, where 0:binary, 1:text.
+
transtype : int, str, enum.TransType
+
Tranformation type, where 0 is none and 1 is log.
+
lt_target, gt_target : str or bool
+
Whether to undercut or exceed target, use "Y"/"N" or bool.
+
sourceval : array_like
+
Values at sources, 1D array with shape (npts,).
+
targval : array_like
+
Values at targets, 1D array with shape (mpts,).
+
+

Returns

+
+
targval : npt.NDArray[np.float64]
+
Values calculated for targets.
+
icount_interp : int
+
Number of interpolation pts.
+
+
+ +Expand source code + +
def interpolate_blend_using_file(
+    self,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+    # npts: int,  # determined from sourceval.shape[0]
+    # mpts: int,  # determined from targval.shape[0]
+    transtype: int | str | enum.TransType,
+    lt_target: str | bool,
+    gt_target: str | bool,
+    sourceval: npt.ArrayLike,
+    targval: npt.ArrayLike,
+) -> dict:
+    """
+    Apply interpolation factors calculated by :meth:`calc_structural_overlay_factors`.
+
+    Parameters
+    ----------
+    factorfile : str or PathLike
+        File for kriging factors.
+    factorfiletype : int, str or enum.FactorFileType
+        Factor file type, where 0:binary, 1:text.
+    transtype : int, str, enum.TransType
+        Tranformation type, where 0 is none and 1 is log.
+    lt_target, gt_target : str or bool
+        Whether to undercut or exceed target, use "Y"/"N" or bool.
+    sourceval : array_like
+        Values at sources, 1D array with shape (npts,).
+    targval : array_like
+        Values at targets, 1D array with shape (mpts,).
+
+    Returns
+    -------
+    targval : npt.NDArray[np.float64]
+        Values calculated for targets.
+    icount_interp : int
+        Number of interpolation pts.
+    """
+    factorfile = Path(factorfile)
+    if not factorfile.is_file():
+        raise FileNotFoundError(f"could not find factorfile {factorfile}")
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    if isinstance(transtype, str):
+        transtype = enum.TransType.get_value(transtype)
+    if isinstance(lt_target, bool):
+        lt_target = "y" if lt_target else "n"
+    if isinstance(gt_target, bool):
+        gt_target = "y" if gt_target else "n"
+    npta = ManyArrays({"sourceval": sourceval})
+    npts = len(npta)
+    mpta = ManyArrays({"targval": targval})
+    mpts = len(mpta)
+    icount_interp = c_int()
+    res = self.pestutils.interpolate_blend_using_file(
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(c_int(npts)),
+        byref(c_int(mpts)),
+        byref(c_int(transtype)),
+        byref(c_char(lt_target.encode())),
+        byref(c_char(gt_target.encode())),
+        npta.sourceval,
+        mpta.targval,
+        byref(icount_interp),
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("applied interpolation factors from %r", factorfile.name)
+    return {
+        "targval": mpts.targval.copy("A"),
+        "icount_interp": icount_interp.value,
+    }
+
+
+
+def ipd_interpolate_2d(self, ecs: npt.ArrayLike, ncs: npt.ArrayLike, zns: int | npt.ArrayLike, sourceval: npt.ArrayLike, ect: npt.ArrayLike, nct: npt.ArrayLike, znt: int | npt.ArrayLike, transtype: int | str | enum.TransType, anis: float | npt.ArrayLike, bearing: float | npt.ArrayLike, invpow: float | npt.ArrayLike) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +
+
+

Undertake 2D inverse-power-of-distance spatial interpolation.

+

Parameters

+
+
ecs, ncs : array_like
+
Source point coordinates, each 1D array with shape (npts,).
+
zns : int or array_like
+
Source point zones, integer or 1D array with shape (npts,).
+
sourceval : array_like
+
Source values, 1D array with shape (npts,).
+
ect, nct : array_like
+
Target point coordinates, each 1D array with shape (mpts,).
+
znt : int or array_like
+
Target point zones, integer or 1D array with shape (mpts,).
+
transtype : int, str, enum.TransType
+
Tranformation type, where 0 is none and 1 is log.
+
anis : float or array_like
+
Local anisotropy, float or 1D array with shape (mpts,).
+
bearing : float or array_like
+
Local anisotropy bearing, float or 1D array with shape (mpts,).
+
invpow : float or array_like
+
Local inverse power of distance, float or 1D array with shape (mpts,).
+
+

Returns

+
+
npt.NDArray[np.float64]
+
Values calculated for targets.
+
+
+ +Expand source code + +
def ipd_interpolate_2d(
+    self,
+    # npts: int,  # determined from ecs.shape[0]
+    ecs: npt.ArrayLike,
+    ncs: npt.ArrayLike,
+    zns: int | npt.ArrayLike,
+    sourceval: npt.ArrayLike,
+    # mpts: int,  # determined from ect.shape[0]
+    ect: npt.ArrayLike,
+    nct: npt.ArrayLike,
+    znt: int | npt.ArrayLike,
+    transtype: int | str | enum.TransType,
+    anis: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    invpow: float | npt.ArrayLike,
+) -> npt.NDArray[np.float64]:
+    """Undertake 2D inverse-power-of-distance spatial interpolation.
+
+    Parameters
+    ----------
+    ecs, ncs : array_like
+        Source point coordinates, each 1D array with shape (npts,).
+    zns : int or array_like
+        Source point zones, integer or 1D array with shape (npts,).
+    sourceval : array_like
+        Source values, 1D array with shape (npts,).
+    ect, nct : array_like
+        Target point coordinates, each 1D array with shape (mpts,).
+    znt : int or array_like
+        Target point zones, integer or 1D array with shape (mpts,).
+    transtype : int, str, enum.TransType
+        Tranformation type, where 0 is none and 1 is log.
+    anis : float or array_like
+        Local anisotropy, float or 1D array with shape (mpts,).
+    bearing : float or array_like
+        Local anisotropy bearing, float or 1D array with shape (mpts,).
+    invpow : float or array_like
+        Local inverse power of distance, float or 1D array with shape (mpts,).
+
+    Returns
+    -------
+    npt.NDArray[np.float64]
+        Values calculated for targets.
+    """
+    npta = ManyArrays(
+        {"ecs": ecs, "ncs": ncs, "sourceval": sourceval}, int_any={"zns": zns}
+    )
+    npts = len(npta)
+    mpta = ManyArrays(
+        {"ect": ect, "nct": nct},
+        {"anis": anis, "bearing": bearing, "invpow": invpow},
+        {"znt": znt},
+    )
+    mpts = len(mpta)
+    if isinstance(transtype, str):
+        transtype = enum.TransType.get_value(transtype)
+    targval = np.zeros(mpts, np.float64, order="F")
+    res = self.pestutils.ipd_interpolate_2d(
+        byref(c_int(npts)),
+        npta.ecs,
+        npta.ncs,
+        npta.zns,
+        npta.sourceval,
+        byref(c_int(mpts)),
+        mpta.ect,
+        mpta.nct,
+        mpta.znt,
+        targval,
+        byref(c_int(transtype)),
+        mpta.anis,
+        mpta.bearing,
+        mpta.invpow,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("undertook 2D inverse-power-of-distance spatial interpolation")
+    return targval.copy("A")
+
+
+
+def ipd_interpolate_3d(self, ecs: npt.ArrayLike, ncs: npt.ArrayLike, zcs: npt.ArrayLike, zns: int | npt.ArrayLike, sourceval: npt.ArrayLike, ect: npt.ArrayLike, nct: npt.ArrayLike, zct: npt.ArrayLike, znt: int | npt.ArrayLike, transtype: int | str | enum.TransType, ahmax: float | npt.ArrayLike, ahmin: float | npt.ArrayLike, avert: float | npt.ArrayLike, bearing: float | npt.ArrayLike, dip: float | npt.ArrayLike, rake: float | npt.ArrayLike, invpow: float | npt.ArrayLike) ‑> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +
+
+

Undertake 3D inverse-power-of-distance spatial interpolation.

+

Parameters

+
+
ecs, ncs, zcs : array_like
+
Source point coordinates, each 1D array with shape (npts,).
+
zns : int or array_like
+
Source point zones, integer or 1D array with shape (npts,).
+
sourceval : array_like
+
Source values, 1D array with shape (npts,).
+
ect, nct, zct : array_like
+
Target point coordinates, each 1D array with shape (mpts,).
+
znt : int or array_like
+
Target point zones, integer or 1D array with shape (mpts,).
+
transtype : int, str, enum.TransType
+
Tranformation type, where 0 is none and 1 is log.
+
ahmax, ahmin, avert : float or array_like
+
Relative correlation lengths, float or 1D array with shape (mpts,).
+
bearing, dip, rake : float or array_like
+
Correlation directions, float or 1D array with shape (mpts,).
+
invpow : float or array_like
+
Local inverse power of distance, float or 1D array with shape (mpts,).
+
+

Returns

+
+
npt.NDArray[np.float64]
+
Values calculated for targets.
+
+
+ +Expand source code + +
def ipd_interpolate_3d(
+    self,
+    # npts: int,  # determined from ecs.shape[0]
+    ecs: npt.ArrayLike,
+    ncs: npt.ArrayLike,
+    zcs: npt.ArrayLike,
+    zns: int | npt.ArrayLike,
+    sourceval: npt.ArrayLike,
+    # mpts: int,  # determined from ect.shape[0]
+    ect: npt.ArrayLike,
+    nct: npt.ArrayLike,
+    zct: npt.ArrayLike,
+    znt: int | npt.ArrayLike,
+    transtype: int | str | enum.TransType,
+    ahmax: float | npt.ArrayLike,
+    ahmin: float | npt.ArrayLike,
+    avert: float | npt.ArrayLike,
+    bearing: float | npt.ArrayLike,
+    dip: float | npt.ArrayLike,
+    rake: float | npt.ArrayLike,
+    invpow: float | npt.ArrayLike,
+) -> npt.NDArray[np.float64]:
+    """Undertake 3D inverse-power-of-distance spatial interpolation.
+
+    Parameters
+    ----------
+    ecs, ncs, zcs : array_like
+        Source point coordinates, each 1D array with shape (npts,).
+    zns : int or array_like
+        Source point zones, integer or 1D array with shape (npts,).
+    sourceval : array_like
+        Source values, 1D array with shape (npts,).
+    ect, nct, zct : array_like
+        Target point coordinates, each 1D array with shape (mpts,).
+    znt : int or array_like
+        Target point zones, integer or 1D array with shape (mpts,).
+    transtype : int, str, enum.TransType
+        Tranformation type, where 0 is none and 1 is log.
+    ahmax, ahmin, avert : float or array_like
+        Relative correlation lengths, float or 1D array with shape (mpts,).
+    bearing, dip, rake : float or array_like
+        Correlation directions, float or 1D array with shape (mpts,).
+    invpow : float or array_like
+        Local inverse power of distance, float or 1D array with shape (mpts,).
+
+    Returns
+    -------
+    npt.NDArray[np.float64]
+        Values calculated for targets.
+    """
+    npta = ManyArrays(
+        {"ecs": ecs, "ncs": ncs, "zcs": zcs, "sourceval": sourceval},
+        int_any={"zns": zns},
+    )
+    npts = len(npta)
+    mpta = ManyArrays(
+        {"ect": ect, "nct": nct, "zct": zct},
+        {
+            "ahmax": ahmax,
+            "ahmin": ahmin,
+            "avert": avert,
+            "bearing": bearing,
+            "dip": dip,
+            "rake": rake,
+            "invpow": invpow,
+        },
+        {"znt": znt},
+    )
+    mpts = len(mpta)
+    if isinstance(transtype, str):
+        transtype = enum.TransType.get_value(transtype)
+    targval = np.zeros(mpts, np.float64, order="F")
+    res = self.pestutils.ipd_interpolate_3d(
+        byref(c_int(npts)),
+        npta.ecs,
+        npta.ncs,
+        npta.zcs,
+        npta.zns,
+        npta.sourceval,
+        byref(c_int(mpts)),
+        mpta.ect,
+        mpta.nct,
+        mpta.zct,
+        mpta.znt,
+        targval,
+        byref(c_int(transtype)),
+        mpta.ahmax,
+        mpta.ahmin,
+        mpta.avert,
+        mpta.bearing,
+        mpta.dip,
+        mpta.rake,
+        mpta.invpow,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("undertook 3D inverse-power-of-distance spatial interpolation")
+    return targval.copy("A")
+
+
+
+def krige_using_file(self, factorfile: str | PathLike, factorfiletype: int | str | enum.FactorFileType, mpts: int, krigtype: int | str | enum.KrigType, transtype: int | str | enum.TransType, sourceval: npt.ArrayLike, meanval: float | npt.ArrayLike | None, nointerpval: float) ‑> dict +
+
+

Apply interpolation factors calculated by other functions.

+

Parameters

+
+
factorfile : str or PathLike
+
Input file with kriging factors.
+
factorfiletype : int, str or enum.FactorFileType
+
Factor file type, where 0:binary, 1:text.
+
mpts : int
+
Number of target points, used to compare with value in factor file.
+
krigtype : int, str, or enum.KrigType,
+
Kriging type, where 0:simple, 1:ordinary.
+
transtype : int, str, enum.TransType
+
Tranformation type, where 0 is none and 1 is log.
+
sourceval : array_like
+
Values at sources, 1D array with shape (npts,).
+
meanval : float, array_like, optional
+
Mean values are required if simple kriging, described as a float +or 1D array with shape (mpts,).
+
nointerpval : float
+
Value to use where interpolation is not possible.
+
+

Returns

+
+
targval : npt.NDArray[np.float64]
+
Values calculated for targets.
+
icount_interp : int
+
Number of interpolation pts.
+
+
+ +Expand source code + +
def krige_using_file(
+    self,
+    factorfile: str | PathLike,
+    factorfiletype: int | str | enum.FactorFileType,
+    # npts: int,  # determined from sourceval.shape[0]
+    mpts: int,
+    krigtype: int | str | enum.KrigType,
+    transtype: int | str | enum.TransType,
+    sourceval: npt.ArrayLike,
+    meanval: float | npt.ArrayLike | None,
+    nointerpval: float,
+) -> dict:
+    """
+    Apply interpolation factors calculated by other functions.
+
+    Parameters
+    ----------
+    factorfile : str or PathLike
+        Input file with kriging factors.
+    factorfiletype : int, str or enum.FactorFileType
+        Factor file type, where 0:binary, 1:text.
+    mpts : int
+        Number of target points, used to compare with value in factor file.
+    krigtype : int, str, or enum.KrigType,
+        Kriging type, where 0:simple, 1:ordinary.
+    transtype : int, str, enum.TransType
+        Tranformation type, where 0 is none and 1 is log.
+    sourceval : array_like
+        Values at sources, 1D array with shape (npts,).
+    meanval : float, array_like, optional
+        Mean values are required if simple kriging, described as a float
+        or 1D array with shape (mpts,).
+    nointerpval : float
+        Value to use where interpolation is not possible.
+
+    Returns
+    -------
+    targval : npt.NDArray[np.float64]
+        Values calculated for targets.
+    icount_interp : int
+        Number of interpolation pts.
+    """
+    factorfile = Path(factorfile)
+    if not factorfile.is_file():
+        raise FileNotFoundError(f"could not find factorfile {factorfile}")
+    if isinstance(factorfiletype, str):
+        factorfiletype = enum.FactorFileType.get_value(factorfiletype)
+    if isinstance(krigtype, str):
+        krigtype = enum.KrigType.get_value(krigtype)
+    if isinstance(transtype, str):
+        transtype = enum.TransType.get_value(transtype)
+    npta = ManyArrays({"sourceval": sourceval})
+    npts = len(npta)
+    if meanval is None:
+        if krigtype == enum.KrigType.simple:
+            self.logger.error(
+                "simple kriging requires 'meanval'; assuming zero for now"
+            )
+        meanval = 0.0
+    mpta = ManyArrays(float_any={"meanval": meanval}, ar_len=mpts)
+    targval = np.full(mpts, nointerpval, dtype=np.float64, order="F")
+    icount_interp = c_int()
+    res = self.pestutils.krige_using_file(
+        byref(self.create_char_array(bytes(factorfile), "LENFILENAME")),
+        byref(c_int(factorfiletype)),
+        byref(c_int(npts)),
+        byref(c_int(mpts)),
+        byref(c_int(krigtype)),
+        byref(c_int(transtype)),
+        npta.sourceval,
+        targval,
+        byref(icount_interp),
+        mpta.meanval,
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("kriged using factor file %r", factorfile.name)
+    return {
+        "targval": targval.copy("A"),
+        "icount_interp": icount_interp.value,
+    }
+
+
+
+def retrieve_error_message(self) ‑> str +
+
+

Retrieve error message from library.

+

Returns

+
+
str
+
 
+
+
+ +Expand source code + +
def retrieve_error_message(self) -> str:
+    """Retrieve error message from library.
+
+    Returns
+    -------
+    str
+    """
+    from .ctypes_declarations import get_char_array
+
+    charray = get_char_array(self.pestutils, "LENMESSAGE")()
+    res = self.pestutils.retrieve_error_message(byref(charray))
+    return charray[:res].rstrip(b"\x00").decode()
+
+
+
+def uninstall_mf6_grid(self, gridname: str) ‑> None +
+
+

Uninstall MF6 grid set by :meth:install_mf6_grid_from_file.

+

Parameters

+
+
gridname : str
+
Unique non-blank grid name.
+
+
+ +Expand source code + +
def uninstall_mf6_grid(self, gridname: str) -> None:
+    """Uninstall MF6 grid set by :meth:`install_mf6_grid_from_file`.
+
+    Parameters
+    ----------
+    gridname : str
+        Unique non-blank grid name.
+    """
+    res = self.pestutils.uninstall_mf6_grid(
+        byref(self.create_char_array(gridname, "LENGRIDNAME"))
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("uninstalled mf6 grid %r", gridname)
+
+
+
+def uninstall_structured_grid(self, gridname: str) ‑> None +
+
+

Uninstall structured grid set by :meth:install_structured_grid.

+

Parameters

+
+
gridname : str
+
Unique non-blank grid name.
+
+
+ +Expand source code + +
def uninstall_structured_grid(self, gridname: str) -> None:
+    """Uninstall structured grid set by :meth:`install_structured_grid`.
+
+    Parameters
+    ----------
+    gridname : str
+        Unique non-blank grid name.
+    """
+    res = self.pestutils.uninstall_structured_grid(
+        byref(self.create_char_array(gridname, "LENGRIDNAME"))
+    )
+    if res != 0:
+        raise PestUtilsLibError(self.retrieve_error_message())
+    self.logger.info("uninstalled structured grid %r", gridname)
+
+
+
+
+
+class PestUtilsLibError +(*args, **kwargs) +
+
+

Exception from PestUtilsLib.

+
+ +Expand source code + +
class PestUtilsLibError(BaseException):
+    """Exception from PestUtilsLib."""
+
+    pass
+
+

Ancestors

+
    +
  • builtins.BaseException
  • +
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/pestutilslib/27f8c02c8bf9e8d8da35c15fdeb49ccd801b6fce.wmf b/docs/pestutilslib/27f8c02c8bf9e8d8da35c15fdeb49ccd801b6fce.wmf new file mode 100644 index 0000000..4e0a7db Binary files /dev/null and b/docs/pestutilslib/27f8c02c8bf9e8d8da35c15fdeb49ccd801b6fce.wmf differ diff --git a/docs/pestutilslib/3b0e642d96cf8c2fa6918ab9ab7cdcd51d2d0a9e.wmf b/docs/pestutilslib/3b0e642d96cf8c2fa6918ab9ab7cdcd51d2d0a9e.wmf new file mode 100644 index 0000000..8a2f0d1 Binary files /dev/null and b/docs/pestutilslib/3b0e642d96cf8c2fa6918ab9ab7cdcd51d2d0a9e.wmf differ diff --git a/docs/pestutilslib/42fe24d599d12d8cdd7204bddb0127a2137d4368.emf b/docs/pestutilslib/42fe24d599d12d8cdd7204bddb0127a2137d4368.emf new file mode 100644 index 0000000..eead8c8 Binary files /dev/null and b/docs/pestutilslib/42fe24d599d12d8cdd7204bddb0127a2137d4368.emf differ diff --git a/docs/pestutilslib/664dcbb389df25a182c4f4f4f687db5856f1ddbe.png b/docs/pestutilslib/664dcbb389df25a182c4f4f4f687db5856f1ddbe.png new file mode 100644 index 0000000..2479224 Binary files /dev/null and b/docs/pestutilslib/664dcbb389df25a182c4f4f4f687db5856f1ddbe.png differ diff --git a/docs/pestutilslib/675c52ab525262814f5fd2e38140492ded091061.wmf b/docs/pestutilslib/675c52ab525262814f5fd2e38140492ded091061.wmf new file mode 100644 index 0000000..588de9a Binary files /dev/null and b/docs/pestutilslib/675c52ab525262814f5fd2e38140492ded091061.wmf differ diff --git a/docs/pestutilslib/7b07d09996a153ee96b89ddade8d58be984eaa9e.emf b/docs/pestutilslib/7b07d09996a153ee96b89ddade8d58be984eaa9e.emf new file mode 100644 index 0000000..11134dc Binary files /dev/null and b/docs/pestutilslib/7b07d09996a153ee96b89ddade8d58be984eaa9e.emf differ diff --git a/docs/pestutilslib/809a31f7aa3068c2aa737448a4c8bc0c4f49a4fa.wmf b/docs/pestutilslib/809a31f7aa3068c2aa737448a4c8bc0c4f49a4fa.wmf new file mode 100644 index 0000000..221305a Binary files /dev/null and b/docs/pestutilslib/809a31f7aa3068c2aa737448a4c8bc0c4f49a4fa.wmf differ diff --git a/docs/pestutilslib/86800f31d83e2dea88818e92a85332282d9f0ac6.png b/docs/pestutilslib/86800f31d83e2dea88818e92a85332282d9f0ac6.png new file mode 100644 index 0000000..8abdcbb Binary files /dev/null and b/docs/pestutilslib/86800f31d83e2dea88818e92a85332282d9f0ac6.png differ diff --git a/docs/pestutilslib/a5a89d68f7acdbdd57cea0e642175adef1466bf2.wmf b/docs/pestutilslib/a5a89d68f7acdbdd57cea0e642175adef1466bf2.wmf new file mode 100644 index 0000000..d822a31 Binary files /dev/null and b/docs/pestutilslib/a5a89d68f7acdbdd57cea0e642175adef1466bf2.wmf differ diff --git a/docs/pestutilslib/b7bb599e95261a75aa25fe7fd353c0a8b9a011a1.png b/docs/pestutilslib/b7bb599e95261a75aa25fe7fd353c0a8b9a011a1.png new file mode 100644 index 0000000..a152906 Binary files /dev/null and b/docs/pestutilslib/b7bb599e95261a75aa25fe7fd353c0a8b9a011a1.png differ diff --git a/docs/pestutilslib/bce5756a8fb3e4a28413846e3c52f19d28f36d12.png b/docs/pestutilslib/bce5756a8fb3e4a28413846e3c52f19d28f36d12.png new file mode 100644 index 0000000..1128f22 Binary files /dev/null and b/docs/pestutilslib/bce5756a8fb3e4a28413846e3c52f19d28f36d12.png differ diff --git a/docs/pestutilslib/docx_to_md.py b/docs/pestutilslib/docx_to_md.py new file mode 100644 index 0000000..5944137 --- /dev/null +++ b/docs/pestutilslib/docx_to_md.py @@ -0,0 +1,165 @@ +import sys +import os + +def processFile(inFile, outFile): + mdFile = open(inFile, 'r') + toc = [] + levels = [0,0,0,0,0 ] + newFile = open(outFile, 'w') + tempFile = [] + tocLoc = 0 + partOfToc = False + + for line in mdFile: + if partOfToc and line != '\n': + continue + else: + partOfToc = False + if 'Table of Contents' in line: + tocLoc = len(tempFile) + 1 + partOfToc = True + line += "\n" + elif line[0] == '#': + secId = buildToc(line, toc, levels) + line = addSectionTag(cleanLine(line), secId) + '\n' + tempFile.append(line) + + + for line in toc: + tempFile.insert(tocLoc, line) + tocLoc += 1 + + newFile.write("\n") + for line in tempFile: + newFile.write(line) + + mdFile.close() + newFile.close() + +def addSectionTag(line, secId): + startIndex = line.find(' ') + line = line[:startIndex + 1] + '' + line[startIndex + 1:] + return line + +def buildToc(line, toc, levels): + line = cleanLine(line) + secId = 's' + if line[:4] == '####': + + #raise UserWarning('Header levels greater than 3 not supported') + levels[4] += 1 + secId += str(levels[1]) + '-' + str(levels[2]) + '-' + str(levels[3]) + '-' + str(levels[4]) + toc.append(' - [' + line[5:] + '](#' + secId + ')\n') + elif line[:3] == '###': + levels[3] += 1 + secId += str(levels[1]) + '-' + str(levels[2]) + '-' + str(levels[3]) + toc.append(' - [' + line[4:] + '](#' + secId + ')\n') + elif line[:2] == '##': + levels[2] += 1 + levels[3] = 0 + secId += str(levels[1]) + '-' + str(levels[2]) + toc.append(' - [' + line[3:] + '](#' + secId + ')\n') + elif line[:1] == '#': + levels[1] += 1 + levels[3] = levels[2] = 0 + secId += str(levels[1]) + toc.append('- [' + line[2:] + '](#' + secId + ')\n') + return secId + +def cleanLine(text): + text = stripNewline(text) + text = removeAnchors(text) + return text + +def stripNewline(text): + return text.replace('\n', '') + +def removeAnchors(text): + while ('<' in text and '>' in text): + leftTag = text.index('<') + rightTag = text.index('>') + text = text[0:leftTag] + text[rightTag + 1:] + return text + +def clean(docx_file,inFile,outFile,run_pandoc=True): + if run_pandoc: + os.system("pandoc -t gfm --wrap=none --extract-media . -o file.md {0} --mathjax".format(docx_file)) + num_str = [str(i) for i in range(1,11)] + lines = open(inFile,'r').readlines() + + #notoc_lines = [] + i = 0 + # while i < len(lines): + # line = lines[i] + # if "table of contents" in line.lower(): + # while True: + # i += 1 + # line = lines[i] + # if line.lower().strip().startswith("introduction") or line.lower().strip().startswith("# introduction") : + # break + # #print(line) + # notoc_lines.append(line) + # i += 1 + # lines = notoc_lines + # notoc_lines = [] + i = 0 + while i < len(lines): + #if lines[i].strip().startswith("----"): + # lines[i-1] = "## " + lines[i-1] + # lines[i] = "\n" + if "" in lines[i]: + lines[i] = lines[i].replace("

","").replace("

","
").replace("#","~") + if "blockquote" in lines[i]: + lines[i] = lines[i].replace("
","").replace("
","") + if lines[i].strip().startswith("$") and not "bmatrix" in lines[i].lower(): + label = lines[i].split()[-1] + eq_str = lines[i].replace("$$","$").split('$')[1] + eq_str = r"{0}".format(eq_str).replace("\\\\","\\").replace(" \\ "," ").replace("\\_","_") + math_str_pre = " {1}
".format(eq_str,label) + lines[i] = math_str_pre + " " + math_str_post + if lines[i].strip().startswith(""): # and "pcf" in lines[i].lower(): + lines[i] = "
" + lines[i] + "
" + if "comment" in lines[i].lower(): + lines[i] = lines[i].replace("~","#") + elif lines[i].strip().startswith("[") and "]" in lines[i]: + raw = lines[i].strip().split(']') + rraw = " ".join(raw[0].split()[:-1])+"]" + new_line = rraw + "".join(raw[1:]) + "\n" + print(lines[i],new_line) + lines[i] = new_line + # elif "bmatrix" in lines[i].lower(): + # eq_str = lines[i] + # lines.pop(i) + # while True: + # i += 1 + # eq_str += lines[i] + # lines.pop(i) + # if "bmatrix" in lines[i].lower(): + # break + # eq_str = r"{0}".format(eq_str).replace("\\\\", "\\").replace(" \\ ", " ").replace("\\_", "_") + # math_str_pre = "".format(eq_str) + # lines[i] = math_str_pre + " " + math_str_post + + i += 1 + + #lines[0] = "# Table of Contents\n" + with open(outFile,'w') as f: + + for line in lines: + #if line.lower().strip().startswith("[introduction"): + # f.write("\n# Table of Contents\n\n") + f.write(line) + + +if __name__ == "__main__": + + clean("new_function_documentation.docx","file.md","fortran_library_documentation.md",True) + + + + + diff --git a/docs/pestutilslib/fortran_library_documentation.md b/docs/pestutilslib/fortran_library_documentation.md new file mode 100644 index 0000000..59550b0 --- /dev/null +++ b/docs/pestutilslib/fortran_library_documentation.md @@ -0,0 +1,3013 @@ +# PyPestUtils FORTRAN Shared Library Documentation + +John Doherty, 2023 + +# Table of Contents + +[Introduction](#introduction) + +[Function Library](#function-library) + +[Function Philosophy](#function-philosophy) + +[Passing Text](#passing-text) + +[Avoidance of Passing Text](#avoidance-of-passing-text) + +[Function Return Value](#function-return-value) + +[Model Pre-Processing Functions](#model-pre-processing-functions) + +[calc_mf6_interp_factors](#calc_mf6_interp_factors) + +[Description](#description) + +[Function Call](#function-call) + +[Return Value](#return-value) + +[Function Arguments](#function-arguments) + +[Notes](#notes) + +[extract_flows_from_cbc_file](#extract_flows_from_cbc_file) + +[Description](#description-1) + +[Function Call](#function-call-1) + +[Return Value](#return-value-1) + +[Function Arguments](#function-arguments-1) + +[Notes](#notes-1) + +[free_all_memory](#free_all_memory) + +[Description](#description-2) + +[Function Call](#function-call-2) + +[Return Value](#return-value-2) + +[Function Arguments](#function-arguments-2) + +[get_cell_centres_mf6](#get_cell_centres_mf6) + +[Description](#description-3) + +[Function Call](#function-call-3) + +[Return Value](#return-value-3) + +[Function Arguments](#function-arguments-3) + +[Notes](#notes-2) + +[get_cell_centres_structured](#get_cell_centres_structured) + +[Description](#description-4) + +[Function Call](#function-call-4) + +[Return Value](#return-value-4) + +[Function Arguments](#function-arguments-4) + +[Notes](#notes-3) + +[inquire_modflow_binary_file_specs](#inquire_modflow_binary_file_specs) + +[Description](#description-5) + +[Function Call](#function-call-5) + +[Return Value](#return-value-5) + +[Function Arguments](#function-arguments-5) + +[Notes](#notes-4) + +[install_mf6_grid_from_file](#install_mf6_grid_from_file) + +[Description](#description-6) + +[Function Call](#function-call-6) + +[Return Value](#return-value-6) + +[Function Arguments](#function-arguments-6) + +[Notes](#notes-5) + +[install_structured_grid](#install_structured_grid) + +[Description](#description-7) + +[Function Call](#function-call-7) + +[Return Value](#return-value-7) + +[Function Arguments](#function-arguments-7) + +[Notes](#notes-6) + +[interp_from_mf6_depvar_file](#interp_from_mf6_depvar_file) + +[Description](#description-8) + +[Function Call](#function-call-8) + +[Return Value](#return-value-8) + +[Function Arguments](#function-arguments-8) + +[Notes](#notes-7) + +[interp_from_structured_grid](#interp_from_structured_grid) + +[Description](#description-9) + +[Function Call](#function-call-9) + +[Return Value](#return-value-9) + +[Function Arguments](#function-arguments-9) + +[Notes](#notes-8) + +[interp_to_obstime](#interp_to_obstime) + +[Description](#description-10) + +[Function Call](#function-call-10) + +[Return Value](#return-value-10) + +[Function Arguments](#function-arguments-10) + +[Notes](#notes-9) + +[retrieve_error_message](#retrieve_error_message) + +[Description](#description-11) + +[Function Call](#function-call-11) + +[Return Value](#return-value-11) + +[Function Arguments](#function-arguments-11) + +[Notes](#notes-10) + +[uninstall_mf6_grid](#uninstall_mf6_grid) + +[Description](#description-12) + +[Function Call](#function-call-12) + +[Return Value](#return-value-12) + +[Function Arguments](#function-arguments-12) + +[uninstall_structured_grid](#uninstall_structured_grid) + +[Description](#description-13) + +[Function Call](#function-call-13) + +[Return Value](#return-value-13) + +[Function Arguments](#function-arguments-13) + +[Model Post-Processing Programs](#model-post-processing-programs) + +[calc_kriging_factors_2d](#calc_kriging_factors_2d) + +[Description](#description-14) + +[Function Call](#function-call-14) + +[Return Value](#return-value-14) + +[Function Arguments](#function-arguments-14) + +[Notes](#notes-11) + +[calc_kriging_factors_auto_2d](#calc_kriging_factors_auto_2d) + +[Description](#description-15) + +[Function Call](#function-call-15) + +[Return Value](#return-value-15) + +[Function Arguments](#function-arguments-15) + +[Notes](#notes-12) + +[calc_kriging_factors_3d](#calc_kriging_factors_3d) + +[Description](#description-16) + +[Function Call](#function-call-16) + +[Return Value](#return-value-16) + +[Function Arguments](#function-arguments-16) + +[Notes](#notes-13) + +[krige_using_file](#krige_using_file) + +[Description](#description-17) + +[Function Call](#function-call-17) + +[Function Arguments](#function-arguments-17) + +[Notes](#notes-14) + +[build_covar_matrix_2d](#build_covar_matrix_2d) + +[Description](#description-18) + +[Function Call](#function-call-18) + +[Return Value](#return-value-17) + +[Function Arguments](#function-arguments-18) + +[Notes](#notes-15) + +[build_covar_matrix_3d](#build_covar_matrix_3d) + +[Description](#description-19) + +[Function Call](#function-call-19) + +[Return Value](#return-value-18) + +[Function Arguments](#function-arguments-19) + +[Notes](#notes-16) + +[calc_structural_overlay_factors](#calc_structural_overlay_factors) + +[Description](#description-20) + +[Structural Overlay Parameters](#structural-overlay-parameters) + +[Function Call](#function-call-20) + +[Return Value](#return-value-19) + +[Function Arguments](#function-arguments-20) + +[Notes](#notes-17) + +[interpolate_blend_using_file](#interpolate_blend_using_file) + +[Description](#description-21) + +[Function Call](#function-call-21) + +[Return Value](#return-value-20) + +[Function Arguments](#function-arguments-21) + +[Notes](#notes-18) + +[ipd_interpolate_2d](#ipd_interpolate_2d) + +[Description](#description-22) + +[Function Call](#function-call-22) + +[Return Value](#return-value-21) + +[Function Arguments](#function-arguments-22) + +[Notes](#notes-19) + +[ipd_interpolate \_3d](#ipd_interpolate-_3d) + +[Description](#description-23) + +[Function Call](#function-call-23) + +[Return Value](#return-value-22) + +[Function Arguments](#function-arguments-23) + +[Notes](#notes-20) + +[initialize_randgen](#initialize_randgen) + +[Description](#description-24) + +[Function Call](#function-call-24) + +[Return Value](#return-value-23) + +[Function Arguments](#function-arguments-24) + +[Notes](#notes-21) + +[fieldgen2d_sva](#fieldgen2d_sva) + +[Description](#description-25) + +[Some Theory](#some-theory) + +[Function Call](#function-call-25) + +[Function Arguments](#function-arguments-25) + +[Notes](#notes-22) + +[fieldgen3d_sva](#fieldgen3d_sva) + +[Description](#description-26) + +[Function Call](#function-call-26) + +[Return Value](#return-value-24) + +[Function Arguments](#function-arguments-26) + +[Notes](#notes-23) + +[Driver Programs](#driver-programs) + +[Introduction](#introduction-1) + +[DRIVER1](#driver1) + +[DRIVER2](#driver2) + +[DRIVER3](#driver3) + +[DRIVER4](#driver4) + +[Option 1](#option-1) + +[Option 2](#option-2) + +[Option 3](#option-3) + +[Option 4](#option-4) + +[Option 5](#option-5) + +[Option 6](#option-6) + +[DRIVER5](#driver5) + +[DRIVER6](#driver6) + +[DRIVER7](#driver7) + +[DRIVER8](#driver8) + +[DRIVER9](#driver9) + +[DRIVER10](#driver10) + +[DRIVER11](#driver11) + +[DRIVER12](#driver12) + +[DRIVER13](#driver13) + +[DRIVER14](#driver14) + +[General](#general-1) + +[File Types](#file-types) + +[Using DRIVER14](#using-driver14) + +[DRIVER15](#driver15) + +[General](#general-2) + +[File Types](#file-types-1) + +[Using DRIVER15](#using-driver15) + +[DRIVER16](#driver16) + +[DRIVER17](#driver17) + +[References](#references) + +# + +# + +# Introduction + +## Function Library + +Tasks that are provided in the *model_interface* library can be subdivided into two broad categories. The first is the performance of model preprocessing tasks. These tasks normally pertain to parameters. They include (but are not limited to) spatial interpolation from pilot points to a general model grid. The second set of tasks are model postprocessing tasks. These include the reading of model-generated files (normally binary files), and undertaking spatial and temporal interpolation of the contents of these files to the sites and times at which field measurements were made. + +All library functions are accessible through the library file *model_interface.lib*. + +## Function Philosophy + +### Passing Text + +Text is exchanged with the functions that comprise the *model_interface* library using one-dimensional character arrays. These are converted to FORTRAN CHARACTER variables internally. Presumably these character arrays are passed by address. Hence the receiving program does not know the length of the array. So it assumes a conservative length. The length does not matter unless a function writes to the array. Instances of this have been kept to a minimum. In fact they are restricted to the *retrieve_error_message()* function. Documentation of this function specifies the maximum number of elements that will be written, and hence the recommended size of the user-supplied receiving character array. + +It is important that a user terminate any character array that it supplies to functions of *model_interface* library using an easily recognized character. In accordance with C convention, this character is “\\0” (i.e. ACHAR(0)). It is easy to employ another termination code if this is preferable. + +### Avoidance of Passing Text + +Because passing text is painful, integer flags are used instead of text flags where this is possible, for example to indicate file type, precision type, etc. (Note that the FORTRAN standard allows for C-interoperability of FORTRAN CHARACTER variables – up to a point. However this functionality is not employed in the functions that are documented herein in order to avoid any possibility of error in argument passing.) + +## Function Return Value + +All functions return an integer value. This value is zero unless an error condition arises. An error message can then be retrieved using function *retrieve_error_message()*. A call to this function must be made immediately after the function call in which the error condition was encountered. + +# Model Pre-Processing Functions + +## calc_mf6_interp_factors + +### Description + +Function *calc_mf6_interp_factors()* calculates interpolation factors from a MODFLOW 6 DIS or DISV grid to a set of user-supplied points. It records these factors in a text or binary interpolation factor file. This file can be used by other *model_interface* module functions such as *interp_from_mf6_depvar_file()*. + +### Function Call + +
integer (kind=c_int) function calc_mf6_interp_factors( &
gridname, &
npts,ecoord,ncoord,layer, &
factorfile, factorfiletype, &
blnfile,interp_success) &
bind(c,name="calc_mf6_interp_factors_")
use iso_c_binding, only: c_int,c_double,c_char
character (kind=c_char,len=1), intent(in) :: gridname(*)
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecoord(npts),ncoord(npts)
integer(kind=c_int), intent(in) :: layer(npts)
character(kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
character(kind=c_char,len=1), intent(in) :: blnfile(*)
integer(kind=c_int), intent(out) :: interp_success(npts)
end function calc_mf6_interp_factors
+ +### Return Value + +The return value is zero unless an error condition is encountered, in which case the return value is 1. An error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| gridname | The user-supplied name of an installed MODFLOW 6 grid specification. | +| npts | The number of points for which interpolation factors must be calculated. | +| ecoord, ncoord | The eastings and northings of points to which interpolation must take place. | +| layer | The model layer numbers to which interpolation target points belong. | +| factorfile | The file in which interpolation factors must be stored. | +| factorfiletype | Provide as 0 for a binary interpolation factor file, and 1 for an ASCII (i.e. text) interpolation factor file. | +| blnfile | If this is provided with a nonblank value, then *calc_mf6_interp_factors()* writes a SURFER BLN file denoting polygons that contain interpolation target points. This polygon is a triangle for DISV grids, or a rectangle (for DIS grids). | +| interp_success | This is returned as 1 for points to which interpolation can take place, and as 0 for points to which interpolation cannot take place. | + +### Notes + +#### Interpolation Methodology + +For a DIS grid, function *calc_mf6_interp_factors()* implements bilinear interpolation from the four cell centres that surround each interpolation point (normally points at which system state observations have been made). If an observation point is not surrounded by four cell centres (this occurs if it lies outside of the model domain, or is too close to the edge of the model domain), then *calc_mf6_interp_factors()* reports its inability to interpolate to that point through the pertinent element of the *interp_success* array. + +For a DISV grid, *calc_mf6_interp_factors()* attempts to construct a triangle of connected cell centres to enclose each point to which spatial interpolation is required (normally points at which system state observations have been made). If it cannot build a triangle, it tries to construct a quadrilateral of connected cell centres. If it cannot build a quadrilateral of enclosing, connected cell centres around a particular point, then *calc_mf6_interp_factors()* declares that interpolation cannot take place to that point. If a triangle can be found, interpolation is barycentric. If a quadrilateral must be used, then *calc_mf6_interp_factors()* performs barycentric interpolation from the two quadrilateral-forming triangles that enclose the observation point and averages the result. + +It is important to note that all interpolation is two-dimensional. Interpolation to a particular point takes place only from cell centres which belong to the same model layer as that to which the point is assigned. + +#### Coordinates + +The geographical coordinates that are assigned to points to which spatial interpolation must take place should be consistent with those of the model. These can be real world coordinates, as MODFLOW 6 grid specifications include numbers which link a model grid to the real world. (XORIGIN, YORIGIN and ANGROT). These are applied when calculating interpolation factors. + +#### The Interpolation Factor File + +No point names are provided to subroutine *calc_mf6_interp_factors()*. Hence no point names are stored in the interpolation factor file that is written by this function. It is assumed that other functions of the *model_interface* module that use this file are aware of the ordering of points. + +As a quality-assurance measure, point details (i.e. the eastings, northings and layers of points) are recorded before interpolation factors in the interpolation factor file that is written by function *calc_mf6_interp_factors()*. All user-supplied points are cited in this table, regardless of whether interpolation can take place to any particular point or not. The following table of interpolation factors also features all user-supplied points. However the integer which indicates the number of cell centres from which interpolation takes place to any point is assigned a value of zero for points to which spatial interpolation is impossible; no interpolation factors are associated with such points. + +Entries on each line in the interpolation factor table are as follows: + +1. The number of cell centres from which interpolation takes place to the point; + +2. (Integer, real number) pairs comprised of the index of a cell centre and the interpolation factor associated with that cell centre. + +## extract_flows_from_cbc_file + +### Description + +Function *extract_flows_from_cbc_file()* reads a cell-by-cell flow term file written by any version of MODFLOW (including MODFLOW-USG). It accumulates flows (normally pertaining to boundary conditions) over user-specified zones. Flows are stored in arrays that can be used by function *interp_to_obstime()* to conduct time-interpolation to observation times. + +### Function Call + +
integer (kind=c_int) function extract_flows_from_cbc_file( &
cbcfile,flowtype,isim,iprec, &
ncell,izone,nzone, &
numzone,zonenumber, &
ntime,nproctime, &
timestep,stressperiod,simtime,simflow) &
bind(c,name="extract_flows_from_cbc_file_")
use iso_c_binding, only: c_int,c_char,c_double
character (kind=c_char,len=1), intent(in) :: cbcfile(*)
character (kind=c_char,len=1), intent(in) :: flowtype(*)
integer(kind=c_int), intent(in) :: isim
integer(kind=c_int), intent(in) :: iprec
integer(kind=c_int), intent(in) :: ncell
integer(kind=c_int), intent(in) :: izone(ncell)
integer(kind=c_int), intent(in) :: nzone
integer(kind=c_int), intent(out) :: numzone
integer(kind=c_int), intent(out) :: zonenumber(nzone)
integer(kind=c_int), intent(in) :: ntime
integer(kind=c_int), intent(out) :: nproctime
integer(kind=c_int), intent(out) :: timestep(ntime)
integer(kind=c_int), intent(out) :: stressperiod(ntime)
real(kind=c_double), intent(out) :: simtime(ntime)
real(kind=c_double), intent(out) :: simflow(ntime,nzone)
end function extract_flows_from_cbc_file
+
+### Return Value + +Function *extract_flows_from_cbc_file()* returns a value of zero unless an error condition is encountered, in which case it returns a value is 1. An error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +
ArgumentRole
cbcfileThe name of a cell-by-cell flow term file produced by any version of MODFLOW.
flowtypeText that identifies a flow type of interest. This variable is used in a case-insensitive manner. It should uniquely identify a 16-character text string that precedes flow-type-pertinent tables or arrays in cbcfile.
isimAn integer code which informs extract_flows_from_cbc_file() of the simulator that generated the binary file:
1 traditional MODFLOW;
21 MODFLOW-USG with structured grid;
22 MODFLOW-USG with unstructured grid;
31 MODFLOW 6 with DIS grid;
32 MODFLOW 6 with DISV grid;
33 MODFLOW 6 with DISU grid.
iprecIndicates the precision with which real variables are stored in cbcfile. A value of 1 indicates single precision while a value of 2 indicates double precision.
ncellThe number of cells in the model grid. For a structured grid model this is ncol ᵡ nrow ᵡ nlay. For a MODFLOW 6 DISV model this is NCPL ᵡ NLAY.
izoneAn integer zonation array. It associates an integer with each cell of the model grid. Cells with the same integer belong to the same zone. Cells that are assigned an integer value of zero do not belong to any zone.
nzoneA user-supplied positive integer which must equal or exceed the number of different zones which are featured in the izone array, that is the number of different non-zero-valued integers that are featured in this array.
numzoneThe number of different zones in the izone array as determined by function extract_flows_from_cbc_file(). If numzone is found to exceed nzone, then extract_flows_from_cbc_file() will report an error condition.
zonenumberAn array which lists izone zone numbers in order of increasing value. Note that zero is not a zone number.
ntimeA user-supplied number which should equal or exceed the number of simulation times for which cell-by-cell flow terms are recorded in cbcfile. This is also the size of the timestep, stressperiod, and simtime arrays provided to function extract_flows_from_cbc_file(). It is also the leading dimension of the simflow array. All of these arrays are filled by function extract_flows_from_cbc_file().
nproctimeThe number of processed times. This is less than ntime if fewer than ntime simulation times are featured in cbcfile. If there are more than ntime simulation times featured in cbcfile, then nproctime is limited to ntime.
timestepThe simulation time steps for which flowtype flows are recorded in cbcfile, for up to nproctime output times.
stressperiodThe simulation stress periods for which flowtype flows are recorded in cbcfile, for up to nproctime output times.
simtimeSimulation times for which flowtype flows are recorded in cbcfile, for up to nproctime output times.
simflowThe flow in a particular zone at a particular output time.
+
+### Notes + +#### Zones + +Function *extract_flows_from_cbc_file()* accumulates flows over zones. Zones are defined in the *izone* integer array. *extract_flows_from_cbc_file()* identifies all of the different non-zero integers that occur in this array and counts them. The total count is equal to *numzone*. It then sorts these integers in order of increasing value and returns them in the *zonenumber* array. Zones can then be referenced by array index in functions such as *interp_to_obstime()*. The *zonenumber* (and some other) arrays are given a dimension of *nzone* by the user. If *extract_flows_from_cbc_file()* establishes that *nzone* does not equal or exceed *numzone* it reports this as an error condition. + +#### Contents of a Cell-by-Cell Flow Term File + +The headers to all arrays and tables that appear in a cell-by-cell flow term file can be ascertained using function *inquire_modflow_binary_file_specs()*. Each array header includes a 16 character text identifier for each flow type at each output time. A user should provide one of these headers as the *flowtype* input variable (or enough of this header to allow its unique identification). Note, however, that *extract_flows_from_cbc_file()* will not extract flows between one cell and another internally to a model grid; it reports an error message if asked to do so. It is used to accumulate flows over part or all of a boundary condition type; that is, it accumulates flows of a certain type in or out of a model domain. + +#### Simulation Times + +The headers to flow arrays in cell-by-cell flow term files produced by newer versions of MODFLOW include the simulation time to which each array pertains. This is not the case for earlier versions of MODFLOW. Hence *extract_flows_from_cbc_file()* reports time steps and stress periods pertaining to accumulated flows in addition to the model simulation time that is associated (or not) with these accumulated flows. Where model simulation times are not provided in a cell-by-cell flow term file, a dummy simulation time of ‑1.0 is recorded by *extract_flows_from_cbc_file()*. It is the user’s responsibility to fill this array with correct simulation times prior to providing it to a function such as *interp_to_obstime()* that undertake time-interpolation of model outputs to user-supplied times of interest. + +#### The simflow Output Array + +The *simflow* array is dimensioned by the user. It is filled by function *extract_flows_from_cbc_file()*. If *ntime* exceeds the number of simulation times for which flow terms are reported in *cbcfile*, and/or *nzone* exceeds the number of zones that are present in the *izone* array, then pertinent elements of *simflow* cannot be filled by *extract_flows_from_cbc_file()*. These elements are provided with values of zero. + +The second dimension of the *simflow* array links flows to zones. Linkage is by order. Thus the contents of *simflow(i,j)* are the accumulated flows in zone *j* at time *i*. The zone pertaining to index *j* is the *j*’th element of the *zonenumber* array. The time associated with index *i* is the *i’*th element of the *simtime* array. Note that a user of function *extract_flows_from_cbc_file()* does not need to be aware of its internal array indexing conventions. However, this does not apply to function *interp_to_obstime()* which uses this array. When calling the latter function, a user can assume that zonal indexing begins at zero. + +## free_all_memory + +### Description + +This function deallocates all arrays that are used by the *model_interface* module for persistent storage. + +### Function Call + +
integer (kind=c_int) function free_all_memory()
bind(c,name="free_all_memory_")
use iso_c_binding, only: c_int
+
+### Return Value + +Function *free_all_memory()* returns a value of zero. In the unlikely event that arrays cannot be deallocated, it returns a value of 1. An error message to this effect can then be accessed using the *retrieve_error_message()* function. + +### Function Arguments + +Function *free_all_memory()* has no arguments. + +## get_cell_centres_mf6 + +### Description + +Function *get_cell_centres_mf6()* calculates the *x*, *y* and *z* coordinates of every cell of an installed MODFLOW 6 grid. Normally this grid will have been installed using function *install_mf6_grid_from_file()*. + +### Function Call + +
integer (kind=c_int) function get_cell_centres_mf6( &
gridname, &
ncells, &
cellx,celly,cellz) &
bind(c,name="get_cell_centres_mf6_")
use iso_c_binding, only: c_int,c_double,c_char
character (kind=c_char,len=1), intent(in) :: gridname(*)
integer(kind=c_int), intent(in) :: ncells
real(kind=c_double), intent(out) :: cellx(ncells)
real(kind=c_double), intent(out) :: celly(ncells)
real(kind=c_double), intent(out) :: cellz(ncells)
end function get_cell_centres_mf6
+
+### Return Value + +The function returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| gridname | The user-supplied name for an installed set of MODFLOW 6 grid specification. As presently coded, this name must be 200 characters or less in length. | +| ncells | The number of cells in the MODFLOW 6 grid. | +| cellx, celly, cellz | The *x*, *y* and *z* coordinates of grid cell centres. | + +### Notes + +Every instance of a MODFLOW 6 grid specification must be provided with a unique name. As presently programmed, this name must be 200 characters or less in length. It is case-insensitive. + +As is apparent from specifications of the above function call, a user must provide a value for *ncells*, the number of cells in the installed MODFLOW 6 grid. Dimensions of the user-supplied *cellx*, *celly* and *cellz* arrays must match this. Before calculating cell centre coordinates, function *get_cell_centres_mf6()* checks that *ncells* agrees with that specified for the installed model grid. If this is not the case, an error condition is reported. + +For a structured grid, the ordering of cells in the *cellx*, *celly* and *cellz* arrays returned by function *get_cell_centres_mf6()* adheres to the MODFLOW convention. That is, element numbers increase fastest by column number, then row number, then layer number. Note also that for DIS and DISV grids, *cellx* and *celly* coordinates are repeated every *ncpl* cells where *ncpl* is the number of cells per layer. + +The geographical location of a MODFLOW 6 grid is read from its binary grid file; see function *install_mf6_grid_from_file()* These are provided by the modeller as the XORIGIN, YORIGIN and ANGROT variables in the model’s discretization input file. Cell centre coordinates provided by function *get_cell_centres_mf6()* will, of course, be affected by the values of these grid specifications. + +## get_cell_centres_structured + +### Description + +Function *get_cell_centres_structured()* calculates the *x* and *y* coordinates of all cells in a single layer of an installed structured model grid. Normally this grid will have been installed using function *install_structured_grid()*. + +### Function Call + +
integer (kind=c_int) function get_cell_centres_structured( &
gridname, &
ncpl, &
cellx,celly) &
bind(c,name="get_cell_centres_structured_")
use iso_c_binding, only: c_int,c_double,c_char
character (kind=c_char,len=1), intent(in) :: gridname(*)
integer(kind=c_int), intent(in) :: ncpl
real(kind=c_double), intent(out) :: cellx(ncpl)
real(kind=c_double), intent(out) :: celly(ncpl)
end function get_cell_centres_structured
+
+### Return Value + +The function returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|----------------------------------------------------------------------------------------------------------------------------------| +| gridname | The user-supplied name for an installed structured grid. As presently coded, this name must be 200 characters or less in length. | +| ncpl | The number of cells in a single layer of the structured grid. | +| cellx, celly | The *x* and *y* coordinates of grid cell centres. | + +### Notes + +Every instance of an installed structured grid must be provided with a unique name. As presently programmed, this name must be 200 characters or less in length. It is case-insensitive. + +As is apparent from specifications of the above function call, a user must provide a value for *ncpl*, the number of cells in a single layer of the structured grid. This must be equal to the number of rows in the grid times the number of columns. Dimensions of the user-supplied *cellx* and *celly* arrays must match this. Before calculating cell centre coordinates, function *get_cell_centres_structured()* checks that *ncpl* agrees with that specified for the installed structured grid. If this is not the case, an error condition is reported. + +For a structured grid, the ordering of cells in the *cellx* and *celly* arrays returned by function *get_cell_centres_structured()* adheres to the MODFLOW convention. That is, element numbers increase fastest by column number and then row number. + +## inquire_modflow_binary_file_specs + +### Description + +Function *inquire_modflow_binary_file_specs()* reads a binary, dependent-variable or budget file that is written by MODFLOW, MODFLOW-USG or MODFLOW 6. It informs the user of the following file specifications: + +- whether the file is recorded in single or double precision; + +- the number of separate arrays that are recorded in the file; + +- the number of output times that are represented in the file. (This may be required for dimensioning arrays prior to making calls to functions such as *interp_from_structured_grid()*.) + +Optionally, *inquire_modflow_binary_file_specs()* will write a tabular data file that lists all elements of the header to each array that it finds in the simulator output file. + +### Function Call + +
integer (kind=c_int) function inquire_modflow_binary_file_specs( &
FileIn,FileOut,isim,itype,iprec,narray,ntime) &
bind(C,name="inquire_modflow_binary_file_specs_")
use iso_c_binding, only: c_int,c_char
character (kind=c_char), intent(in) :: FileIn(*)
character (kind=c_char), intent(in) :: FileOut(*)
integer(kind=c_int), intent(in) :: isim
integer(kind=c_int), intent(in) :: itype
integer(kind=c_int), intent(out) :: iprec
integer(kind=c_int), intent(out) :: narray
integer(kind=c_int), intent(out) :: ntime
end function inquire_modflow_binary_file_specs
+
+### Return Value + +The return value is zero unless an error condition is encountered, in which case it is 1. An error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +
ArgumentRole
fileinThe name of the MODFLOW-generated binary file that must be read. The length of the filename should be 256 characters or less.
fileoutThe name of a file to which function inquire_modflow_binary_file_specs() will write a table of array headers. (Supply this as blank for no output file.) The length of the filename should be 256 characters or less.
isimAn integer code which informs the function of the simulator that generated the binary file:
1 traditional MODFLOW;
21 MODFLOW-USG with structured grid;
22 MODFLOW-USG with unstructured grid;
31 MODFLOW 6 with DIS grid;
32 MODFLOW 6 with DISV grid;
33 MODFLOW 6 with DISU grid.
itypeType of file to read:
1 dependent variable;
2 cell-by-cell flow term.
iprecPrecision used for real numbers:
1 single precision;
2 double precision.
narrayNumber of arrays/tables recorded in the binary MODFLOW output file. Each array or table pertains to a flow or boundary condition type at a particular time. Each array or table is preceded by a header.
ntimeThe number of different times for which dependent variables or flow terms are recorded.
+
+### Notes + +As stated above, a termination character (“\\0”, i.e. ACHAR(0)) must be provided with string input arguments. For function *inquire_modflow_binary_file_specs()* these are filenames. The character arrays through which input and output filenames are provided should contain no greater than 256 elements. However they can have fewer elements than this if desired. + +## install_mf6_grid_from_file + +### Description + +Function *install_mf6_grid_from_file()* reads a binary grid file (i.e. a GRB file) written by MODFLOW 6. This file contains all specifications of the grid of a MODFLOW 6 model, including its geographical reference point and rotation. *install_mf6_grid_from_file()* stores specifications for this grid for the future use of other *model_interface* functions such as *calc_mf6_interp_factors()* and *get_cell_centres_mf6()*. + +Multiple instances of MODFLOW 6 grid specifications can be installed; as presently programmed, an upper limit of five is imposed. + +### Function Call + +
integer (kind=c_int) function install_mf6_grid_from_file( &
gridname,grbfile, &
idis,ncells,ndim1,ndim2,ndim3) &
bind(c,name="install_mf6_grid_from_file_")
use iso_c_binding, only: c_int,c_char
character (kind=c_char,len=1), intent(in) :: gridname(*)
character (kind=c_char,len=1), intent(in) :: grbfile(*)
integer(kind=c_int), intent(out) :: idis
integer(kind=c_int), intent(out) :: ncells
integer(kind=c_int), intent(out) :: ndim1,ndim2,ndim3
end function install_mf6_grid_from_file
+
+### Return Value + +The function returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| gridname | The user-supplied name for an installed set of MODFLOW 6 grid specification. As presently coded, this name must be 200 characters or less in length. | +| grbfile | The binary grid file, recorded by MODFLOW 6, from which grid specifications are read. | +| idis | This argument reports whether the MODFLOW 6 grid is of the DIS type (returned value of 1) or DISV type (returned value of 2). As presently coded, *install_mf6_grid_from file()* will object with an error message if the MODFLOW 6 grid is of the DISU type. | +| ncells | The number of cells in the MODFLOW 6 grid. | +| ndim1 | Returned as the number of model columns (*ncol*) for a DIS grid or the number of model cells per layer (*ncpl*) for a DISV grid. | +| ndim1 | Returned as the number of model rows (*nrow*) for a DIS grid, or as 1 for a DISV grid. | +| ndim3 | Returned as the number of model layers (*nlay*) for both DIS and DISV grids. | + +### Notes + +Every instance of a MODFLOW 6 grid specification must be provided with a unique name. As presently programmed, this name must be 200 characters or less in length. It is case-insensitive. + +As presently programmed, functions of the *model_interface* module cannot accommodate a DISU grid. If the binary grid file which *install_mf6_grid_from_file()* is asked to read is written by a DISU-gridded model, an error condition arises. An error message can then be retrieved using the *retrieve_error_message()* function. + +The geographical coordinates of a MODFLOW 6 grid are read from the binary grid file. These are provided by the modeller as the XORIGIN, YORIGIN and ANGROT variables in the model’s discretization input file. It is the user’s responsibility to ensure that the values of these variables are correct, and that they are therefore compatible with the coordinates of points for which interpolation factors will be calculated by other functions of the *model_interface* module. + +For large models, storage of MODFLOW 6 grid specifications requires a lot of memory. It is recommended that a set of MODFLOW 6 specifications be uninstalled using function *uninstall_mf6_grid()* when no longer required. + +## install_structured_grid + +### Description + +Function *install_structured_grid()* installs the specifications of a structured grid into the persistent memory of the *model_interface* module. These specifications are comprised of the following: + +- the number of rows, columns and layers in the model grid; + +- real world coordinates of either its top left or bottom left corner; + +- the anticlockwise rotation of the grid row direction from east; + +- the model grid DELR and DELC arrays. + +Once grid specifications are installed, they can be used by functions such as *interp_from_structured_grid()*. The latter function can read binary dependent-variable files produced by structured MODFLOW, MT3D, MODFLOW-USG and MODFLOW 6 models. + +Multiple instances of structured grid specifications can be installed; as presently programmed, an upper limit of five is imposed. + +### Function Call + +
integer (kind=c_int) function install_structured_grid( &
gridname,ncol,nrow,nlay,icorner, &
e0,n0,rotation,delr,delc) &
bind(c,name="install_structured_grid_")
use iso_c_binding, only: c_int,c_char,c_double
character (kind=c_char), intent(in) :: gridname(*)
integer (kind=c_int), intent(in) :: ncol,nrow,nlay
integer (kind=c_int), intent(in) :: icorner
real (kind=c_double), intent(in) :: e0,n0,rotation
real (kind=c_double), intent(in) :: delr(ncol),delc(nrow)
end function install_structured_grid
+
+### Return Value + +The function returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| gridname | The user-supplied name of an installed structured grid specification. As presently coded, this name must be 200 characters or less in length. | +| ncol | The number of columns in the structured model grid. | +| nrow | The number of rows in the structured model grid. | +| nlay | The number of layers in the structured model grid. | +| icorner | Supply as 1 or 2. If supplied as 1, *e0* and *n0* pertain to the top left corner of the structured model grid. If supplied as 2, *e0* and *n0* pertain to the bottom left corner of the structured model grid. | +| e0 | Real-world eastern coordinate of model grid corner. | +| n0 | Real-world northern coordinate of model grid corner. | +| rotation | Counter-clockwise rotation of row direction of model grid from east. | +| delr | Row widths of model cells | +| delc | Column widths of model cells. | + +### Notes + +Every instance of an installed structured grid specification must be provided with a unique name. As presently programmed, this name must be 200 characters or less in length. It is case-insensitive. + +Specifications of a structured model grid are also provided in a “grid specification file” that is supported by the PEST Groundwater Utilities. In this file, *e0* and *n0* pertain to the top left corner of the grid (i.e. *icorner* option 1). Also, a grid specification file does not record the number of layers in a model grid. + +## interp_from_mf6_depvar_file + +### Description + +Function *interp_from_mf6_depvar_file()* reads a dependent-variable file written by MODFLOW 6. For every output time that is recorded in this file, it undertakes spatial interpolation of model-calculated dependent variables to a set of points. Spatial interpolation is implemented using factors that were previously calculated by function *calc_mf6_interp_factors()*. + +Arrays used by *interp_from_mf6_depvar_file()* to store spatial interpolation outcomes are useable by function *interp_to_obstime()*. Hence temporal interpolation to observation times can follow spatial interpolation to observation sites. + +### Function Call + +
integer (kind=c_int) function interp_from_mf6_depvar_file( &
depvarfile,factorfile,factorfiletype, &
ntime,vartype,interpthresh,reapportion,nointerpval, &
npts,nproctime,simtime,simstate) &
bind(c,name="interp_from_mf6_depvar_file_")
use iso_c_binding, only: c_int,c_char,c_double
character(kind=c_char,len=1), intent(in) :: depvarfile(*)
character(kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
integer(kind=c_int), intent(in) :: ntime
character (kind=c_char,len=1), intent(in) :: vartype(*)
real(kind=c_double), intent(in) :: interpthresh
integer(kind=c_int), intent(in) :: reapportion
real(kind=c_double), intent(in) :: nointerpval
integer(kind=c_int), intent(in) :: npts
integer(kind=c_int), intent(out) :: nproctime
real(kind=c_double), intent(out) :: simtime(ntime)
real(kind=c_double), intent(out) :: simstate(ntime,npts)
end function interp_from_mf6_depvar_file
+
+### Return Value + +Function *interp_from_mf6_depvar_file()* returns a value of zero unless an error condition is encountered. In that case it returns a value of 1. An error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| depvarfile | The name of a binary dependent-variable file written by MODFLOW 6. | +| factorfile | The name of an interpolation factor file written by function *calc_mf6_interp_factors()*. The factors that are recorded in this file must pertain to the same MODFLOW 6 grid as that to which the dependent variable file pertains. | +| factorfiletype | 0 for binary, or 1 for ASCII. | +| ntime | The first dimension of the *simtime* and *simstate* arrays. Generally, this should equal or exceed the number of simulation times for which model outputs are available in *depvarfile*. However if *ntime* is less than this, only dependent variables corresponding to the first *ntime* times that are recorded in the dependent variable file are processed. | +| vartype | Each array in a dependent variable file contains a 16 character text header. *vartype* provides (case-insensitive) text that allows unique identification of a header of interest. Only variables of this type will be processed by function *interp_from_mf6_depvar_file()*. | +| interpthresh | If the absolute value of a model-calculated dependent variable is equal to, or greater than *interpthresh*, its value is treated as invalid. This may, for example, indicate a dry or inactive model cell. | +| reapportion | If a cell from which spatial interpolation takes place is endowed with a dependent variable value which exceeds *interpthresh* then this can be handled in one of two ways. If *reapportion* is provided as 0, then the point-interpolated value is also declared to be invalid; hence it is assigned a value of *nointerpval*. However if reapportion is supplied as 1, then interpolation to the point may still take place. Other factors which feature in the interpolation process to that point are then adjusted so that they add to 1.0. However if all cells from which interpolation takes place are endowed with dependent variable values which are greater than *interpthresh*, then spatial interpolation to that point becomes impossible; the point is then awarded an interpolated value of *nointerpval*. | +| nointerpval | Function *interp_from_mf6_depvar_file()* assigns a value of *nointerpval* to any point for which spatial interpolation is impossible. This can occur if the point lies outside the MODFLOW 6 grid. It can also occur if surrounding model cells are dry or inactive. | +| npts | The number of points to which interpolation takes place. This is the second dimension of the *simstate* array. This same value should have been supplied to function *calc_mf6_interp_factors()* when it calculated point interpolation factors. Its value is recorded at the top of the interpolation factor file which the latter function writes. If the value of *npts* supplied to function *interp_from_mf6_depvar_file()* does not agree with this value, an appropriate error message is generated. | +| nproctime | Function *interp_from_mf6_depvar_file()* informs a calling program of the number of separate simulation times for which it undertakes spatial interpolation through this variable. Note that the value assigned to *nproctime* will not exceed the user-supplied value of *ntime,* as *interp_from_mf6_depvar_file()* does not process simulator-generated data pertaining to more than *ntime* simulation times. | +| simtime | Simulation times for which values of dependent variables have been calculated are recorded in this array. Time values beyond *nproctime* are returned as *nointerpval*. | +| simstate | This two-dimensional array contains spatially-interpolated values of dependent variables. The ordering of points is the same as in the *ecoord* and *ncoord* arrays that were provided to function *calc_mf6_interp_factors()* when it calculated interpolation factors. The ordering of times is the same as that encountered in the dependent variable output file that was written by MODFLOW 6. | + +### Notes + +#### Complementary Functions + +Use of function *interp_from_mf6_depvar_file()* complements that of function *calc_mf6_interp_factors()*. Spatial interpolation factors that were calculated by the latter function can be used to interpolate many sets of dependent variables that reside in one or a number of binary, MODFLOW 6 dependent variable files using function *interp_from_mf6_depvar_file()*. However this implies that spatial interpolation of all of these dependent variables takes place to the same set of points. + +Use of function *interp_from_mf6_depvar_file()* also complements that of function *interp_to_obstime()*. The latter function undertakes temporal interpolation, presumably to the times at which observations of system state were made. This function requires use of *nproctime*, as well as the *simtime* and *simstate* arrays that are filled by function *interp_from_mf6_depvar_file()*. + +#### Ordering of Points + +The eastings and northings of points to which spatial interpolation must take place were supplied to function *calc_mf6_interp_factors()* through the *ecoord* and *ncoord* arrays that are required by that function. These arrays host coordinates for *npts* points, where *npts* is another *calc_mf6_interp_factors()* input variable. + +Function *interp_from_mf6_depvar_file()* also requires a value for *npts*. This must be the same as that which was previously supplied to *calc_mf6_interp_factors()*. The ordering of points in the *simstate* array (these pertain to the second dimension of that array) is the same as that in the *ecoord* and *ncoord* arrays that were previously supplied to *calc_mf6_interp_factors()*. Meanwhile, the ordering of times in the *simtime* and *simstate* arrays is the same as that for which system states are recorded in the MODFLOW 6 dependent variable file. + +#### Inability to Interpolate + +As is described in documentation to function *calc_mf6_interp_factors()*, spatial interpolation to user-specified points is barycentric for a DISV grid, and bilinear for a DIS grid. However, as is also explained in its documentation, function *calc_mf6_interp_factors()* does not actually undertake spatial interpolation; it simply calculates the factors which enable spatial interpolation to take place. + +Spatial interpolation of model-calculated dependent variables to a particular point in space requires that the values of dependent variables that are calculated for model cells be multiplied by these factors and summed. For any interpolation point, these factors sum to 1.0. Hence the spatially interpolated value of a dependent variable is the weighted average of dependent variable values ascribed to the centres of model cells that surround it. + +If the dependent variable value ascribed to a particular model cell equals or exceeds *interpthresh,* then this value cannot be used in the spatial interpolation process. Function *interp_from_mf6_depvar_file()* provides two options for handling this situation. The appropriate option can be selected through its *reapportion* input argument. A *reapportion* value of 0 instructs *interp_from_mf6_depvar_file()* to endow any point for which spatial interpolation requires use of an above-*interpthresh* dependent variable value with an interpolated value of *no_interpval*. Alternatively, if *reapportion* is supplied as 1, then the above-*interpthresh* value is omitted from the interpolation process; other spatial interpolation factors pertaining to the point of interest are then adjusted so that they sum to 1.0. + +#### Dependent Variable Types + +On most occasions of MODFLOW 6 usage, a single dependent variable type is stored in each dependent variable file. The type of variable is recognized by a 16 character descriptor that is provided in the header to each array that is stored in the file. + +There may be circumstances, however, where more than one dependent variable type is stored in a single dependent variable file. Function *interp_from_mf6_depvar_file()* allows processing of only one dependent variable at a time. This variable must be denoted by the user using the *vartype* function argument. Supply a set of characters that allows unique identification of the variable type of interest from its header. For example “he” or even “h” can be used to identify heads arrays, if heads are the only type of dependent variable that is stored in a dependent variable file. Array identifiers for different dependent variable types are listed in the MODFLOW 6 manual. Those that are featured in a particular binary dependent variable file can be ascertained using function *inquire_modflow_binary_file_specs()*. + +#### Array Precision + +Function *interp_from_mf6_depvar_file()* assumes that all real numbers are stored in double precision in a dependent variable file. At the time of writing, this is a MODFLOW 6 specification. + +## interp_from_structured_grid + +### Description + +Function *interp_from_structured_grid()* undertakes layer-specific (i.e. two-dimensional) spatial interpolation from dependent-variable arrays that are recorded in binary simulator output files. Interpolation takes place to user-specified points. The simulator can be MODFLOW, MT3D, MODFLOW-USG or MODFLOW 6. An important requirement for the latter two simulators is that the model employs a structured grid. + +### Function Call + +
integer (kind=c_int) function interp_from_structured_grid( &
GridName,DepVarFile,isim,iprec,ntime, &
VarType,InterpThresh,NoInterpVal, &
npts,ecoord,ncoord,layer, &
nproctime,simtime,simstate) &
bind(c,name="interp_from_structured_grid_")
use iso_c_binding, only: c_int,c_double,c_char
character (kind=c_char), intent(in) :: gridname(*)
character (kind=c_char), intent(in) :: depvarfile(*)
integer(kind=c_int), intent(in) :: isim
integer(kind=c_int), intent(in) :: iprec
integer(kind=c_int), intent(in) :: ntime
character (kind=c_char), intent(in) :: vartype(*)
real(kind=c_double), intent(in) :: interpthresh
real(kind=c_double), intent(in) :: nointerpval
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecoord(npts),ncoord(npts)
integer(kind=c_int), intent(in) :: layer(npts)
integer(kind=c_int), intent(out) :: nproctime
real(kind=c_double), intent(out) :: simtime(ntime)
real(kind=c_double), intent(out) :: simstate(ntime,npts)
end function interp_from_structured_grid
+
+### Return Value + +Function *interp_from_structured_grid()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| gridname | The name of an installed structured grid. | +| depvarfile | The name of a binary dependent-variable file. This file may have been written by MODFLOW, MT3D, MODFLOW-USG or MODFLOW 6. It must pertain to a structured grid. | +| isim | This argument must be supplied as 1 or -1. Supply a value of 1 for a file produced by any version of MODFLOW. Supply a value of -1 for a file written by MT3D. | +| iprec | Provide a value of 1 or 2. 1 indicates single precision while 2 indicated double precision. | +| ntime | This is used for dimensioning arrays that *interp_from_structured_grid()* will fill with interpolated values of model-calculated dependent variables. If *ntime* is greater than the number of times for which model outputs are recorded in *depvarfile*, then elements pertaining to non-represented simulation times are assigned a value of *nointerpval*. If *ntime* is less than the number of model simulation times that are featured in *depvarfile*, spatially-interpolated model outputs pertaining only to the first *ntime* simulation times are represented in *interp_from_structured_grid()* output arrays. | +| vartype | It is possible that a dependent-variable output file contains calculated values for more than a single dependent variable. Dependent variable types are identified by array headers. Provide the dependent variable type for which interpolation to user-supplied points is required using the *vartype* argument. | +| interpthresh | If the absolute value of a dependent variable assigned to a model cell is above this threshold, then interpolation does not take place from that cell. The cell is assumed to be inactive or dry. | +| nointerpval | This value is provided to elements of *interp_from_structured_grid()* output arrays where spatial interpolation is impossible. | +| npts | The number of points to which spatial interpolation is required. | +| ecoord | Point east coordinates. | +| ncoord | Point north coordinates. | +| layer | The model layers to which points belong. | +| nproctime | Function *interp_from_structured_grid()* informs a calling program of the number of separate simulation times for which it undertakes spatial interpolation through this variable. Note that the value assigned to *nproctime* will not exceed the user-supplied value of *ntime* as *interp_from_structured_grid()* does not process simulator-generated data pertaining to more than *ntime* simulation times. | +| simtime | Simulation times are recorded in this array. Time values beyond *nproctime* are returned as *nointerpval*. | +| simstate | This two-dimensional array contains spatially-interpolated values of dependent variables. The ordering of points is the same as in the user-supplied *ecoord* and *ncoord* arrays. The ordering of times is the same as that encountered in the dependent variable output file. | + +### Notes + +#### Interpolation Scheme + +Function *interp_from_structured_grid()* performs within-layer, bilinear interpolation from the centres of model cells to points of user interest. This interpolation scheme accommodates dry and inactive cells. By setting pertinent simulator input variables (for example HNOFLO and HDRY) to appropriate values before running a model, a user must ensure that such cells are easily recognizable. + +A number with a high absolute value must be used to denote inactive and dry cells. This absolute value value must be higher than that of *interpthresh*. That is, a “calculated” system state whose absolute value is greater than *interpthresh* is assumed to be dry or inactive. + +Interpolation will not take place to a user-supplied point under the following circumstances: + +- The point lies outside the model grid; + +- The point is surrounded by the centres of four dry or inactive model cells. + +Interpolation can take place to a point which lies inside the model grid, but is beyond the first/last row/column of (active) model cells. Obviously, interpolation to such a point can only be approximate. To prevent interpolation to such points, ensure they are not included in the set of points that is supplied to function *interp_from_structured_grid()* through its *ecoord* and *ncoord* arguments. + +Where interpolation cannot take place to a point, its interpolated value is set to the user-supplied value of *nointerpval.* + +#### Knowledge of File Contents + +Some of the argument values required by function *interp_from_structured_grid()* require knowledge of the contents of the binary file which is read by this function. In particular, a user must know: + +- array text headers; + +- the precision with which real numbers are recorded; + +- the number of times for which simulator outputs are recorded. + +For all but MT3D output files, this information can be obtained using function *inquire_modflow_binary_file_specs()*. + +#### Model Output Times + +Ideally the value of the *ntime* argument should be the same as the number of simulation times for which calculated system states are recorded in the binary dependent-variable file *depvarfile*. If *ntime* is greater than this, then elements of the *simstate* array that pertain to unpresented model output times are filled with dummy spatially-interpolated system state values of *nointerpval*. If *ntime* is provided with a value that is less than the number of times for which simulation outputs are provided in file *depvarfile*, then spatial interpolation takes place for only the first *ntime* simulation times. + +#### Dependent Variable Types + +A MODFLOW-compatible dependent-variable file often provides values for only a single type of dependent variable (i.e. for a single system state). This state type is listed as a 16 character text string in the header to each array that is recorded in *depvarfile*. Examples are “HEAD”, “DRAWDOWN” and “SUBSIDENCE”. On most occasions of model usage, different types of model-computed state arrays are recorded in different binary output files. + +However, there may be some occasions on which this protocol is not followed. A common example is where concentrations of different chemical species are recorded in a single MODFLOW-USG binary output file. + +A user specifies what type of system state data are to be processed using the *vartype* argument of function *interp_from_structured_grid()*. Enough characters must be provided in this variable to uniquely identify the requested dependent-variable type. For example “d” can identify HEAD arrays in a file that contains only head arrays. However it cannot uniquely identify HEAD arrays in a binary output file that contains both HEAD and DRAWDOWN arrays. Where a dependent variable type is not uniquely identifiable, interpolated values pertaining to one dependent variable type will be overwritten by those pertaining to another dependent variable type whenever both of them are recorded at the same simulation type. Obviously, this is an undesirable state of affairs. It can be avoided by supplying the full name of a dependent variable through the *vartype* argument. Function *inquire_modflow_binary_file_specs()* can be used to ascertain these full names from array text headers. + +#### Grid Real World Coordinates + +The real world location of a structured model grid is recorded in its specifications. These are stored using function *install_structured_grid()*. + +A binary grid file which is written by MODFLOW 6 also contains information that links a model grid to real world coordinates. However function *interp_from_structured_grid()* does not read this file. Hence if the real-world location of a MODFLOW 6 grid as recorded in its binary grid file differs from that which is installed in its structured grid specifications, then *interp_from_structured_grid()* has no way of knowing about this conflict. + +Similarly, the real-world coordinates of a structured-grid MODFLOW-USG model may be recorded in its (rather extensive) grid specification file. *Interp_from_structured_grid()* makes no reference to this file. It relies purely on geographical information that is installed with pertinent structured grid specifications using function *install_structured_grid()*. + +## interp_to_obstime + +### Description + +Function *interp_to_obstime()* undertakes time-interpolation of quantities that were previously read from simulator output files, and possibly spatially interpolated to a set of user-supplied points. The times to which time-interpolation takes place are normally the times at which field observations were made. + +Arrays that store the outcomes of spatial interpolation or boundary flow extraction are useable by function *interp_to_obstime()*. + +### Function Call + +
integer (kind=c_int) function interp_to_obstime( &
nsimtime,nproctime,npts,simtime,simval, &
interpthresh,how_extrap,time_extrap,nointerpval, &
nobs,obspoint,obstime,obssimval) &
bind(c,name="interp_to_obstime_")
use iso_c_binding, only: c_int,c_double,c_char
integer(kind=c_int), intent(in) :: nsimtime
integer(kind=c_int), intent(in) :: nproctime
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: simtime(nsimtime)
real(kind=c_double), intent(in) :: simval(nsimtime,npts)
real(kind=c_double), intent(in) :: interpthresh
character(kind=c_char,len=1),intent(in) :: how_extrap
real(kind=c_double), intent(in) :: time_extrap
real(kind=c_double), intent(in) :: nointerpval
integer(kind=c_int), intent(in) :: nobs
integer(kind=c_int), intent(in) :: obspoint(nobs)
real(kind=c_double), intent(in) :: obstime(nobs)
real(kind=c_double), intent(out) :: obssimval(nobs)
end function interp_to_obstime
+
+### Return Value + +Function *interp_to_obstime()* returns a value of 0. However if an error condition is encountered, it returns a value of 1. An error message can then be retrieved using the *retrieve_error_message()* function. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| nsimtime | First dimension of arrays *simtime* and *simval*. | +| nproctime | Number of times featured in arrays *simtime* and *simval*. *nproctime* must not exceed *nsimtime*. | +| npts | Second dimension of array *simval*. This is the number of points or zones for which temporal interpolation must take place. | +| simtime | This array contains simulation times for which model outputs are available. The *simtime* array will normally have been filled by functions such as *interp_from_structured_grid()*, *interp_from_mf6_depvar_file()* or *extract_flows_from_cbc_file()*. | +| simval | Like the *simtime* array, this array will normally have been filled by functions such as *interp_from_structured_grid()*, *interp_from_mf6_depvar_file()* and *extract_flows_from_cbc_file()*. In the first two cases, it contains spatially-interpolated, model-simulated values at times for which these have been read from a binary model dependent-variable output file In the second case it contains zone-accumulated flows extracted from a model-generated cell-by-cell flow term file. In both cases, these values are available at *nproctime* times. Functions *interp_from_structrured_grid()*, *interp_from_mf6_depvar_file()* and *extract_flows_from_cbc_file()* assign a value to *nproctime*. | +| *interpthresh* | If the absolute value of a quantity in the *simval* array exceeds *interpthresh*, then its value is treated with suspicion by *interp_to_obstime()*. See following notes. | +| *how_extrap* | Supply this as “L” or “C”. “L” signifies linear while “C” signifies constant. | +| *time_extrap* | The time over which time-extrapolation (in contrast to interpolation) is allowed. This must be supplied as 0.0 or greater. | +| *noninterpval* | If time-interpolation cannot take place to a certain point or zone at a certain time, then a dummy time-interpolated value of *noninterpval* is provided. | +| *nobs* | The number of times (normally observation times) to which time-interpolation must be undertaken. | +| *obspoint* | The indices of points or zones to which time-interpolation is undertaken. These pertain to the *simval* *npts* array dimension, starting at 0. If a point or zone is not referenced in the *simval* array, provide an index of -1. No temporal interpolation is therefore possible to this point or zone. | +| *obstime* | Times to which time-interpolation must take place, presumably times at which observations were made. The time units and reference time must be the same as for elements of the *simtime* array; presumably these are model simulation times. | +| *obssimval* | Time interpolated values. Times correspond to times provided in *obstime*. Points or zones correspond to indices provided in *obspoint*. | + +### Notes + +#### General Usage + +As stated above, normally function *interp_to_obstime()* is called following a call to a function such as *interp_from_structured_grid()*, *interp_from_mf6_depvar_file()* and *extract_flows_from_cbc_file()*. Function *interp_from_structured_grid()* and *interp_from_mf6_depvar_file()* undertake spatial interpolation of system states that are extracted from a binary MODFLOW-generated dependent variable file. Function *extract_flows_from_cbc_file()* does not undertake spatial interpolation; however it accumulates flow rates over use-specified zones. + +Where spatial interpolation has preceded a call to *interp_to_obstime()*, the geographical coordinates of points to which spatial interpolation took place were supplied to respective spatial interpolation functions. *Interp_to_obstime()* undertakes temporal interpolation for these same points. These points are referenced by their *simval* array *npts* index. These indices are presumed to start at 0. The ordering of points is the same as that supplied to the spatial interpolation function. + +Following a call to *extract_flows_from_cbc_file()* the *simval* array may contain zone-accumulated flows at model output times. Zone numbers for which flow was accumulated are referenced by index in the second dimension of the *simval* array. Hence in this case, *npts* actually refers to the number of zones for which flow was accumulated rather than to the number of points to which system states had previously been spatially interpolated. Zone indices start at zero. The zone with an index of zero is that with the lowest zone number, that with an index of 1 is that with the next highest zone number etc. Zone numbers are extracted from a model-specific inter zonation array by function *extract_flows_from_cbc_files()*. They are listed (after sorting) in the *zonenumber()* array that is produced by this function. The zone number index refers to this array. + +Times from which interpolation takes place are recorded in the *simtime* array. These are generally model simulation times. *interp_to_obstime()* checks that these times are provided in increasing order. Efficiency of its time-interpolation algorithm depends on this. + +In contrast, times that are provided in the *obstime* array do not need to be in increasing order. In fact, this is unlikely, as the times at which field measurements of system state or flux were made are normally different for different points or zones. + +As stated above, temporal interpolation of model-originating values that are recorded in the *simval* array to observation times that are recorded in the *obstime* array requires that points to which spatial interpolation has already taken place or zones over which flow accumulation has already taken place be identified. These are identified by their *simval* *npts* index, starting at 0. If a user-supplied point or zone does not correspond to a *simval*-indexed point or zone, then the *obspoint* index for this point should be supplied as -1. “Interpolated” values for this point or zone are then assigned values of *nointerpval* at all observation times that are associated with it. + +#### Interpolation Scheme + +Time-interpolation is linear if an observation time lies between two simulation times. + +#### Extrapolation + +If an observation time lies before the first simulation time or after the last simulation time, then temporal extrapolation (rather than interpolation) is required. Three extrapolation options are provided. + +The first option is to assign a value at the extrapolated time that is equal to that of the first or last simulation time. The second is to undertake linear extrapolation from the first two or last two simulation times. The third is to forbid extrapolation. The last option is implemented by setting *time_extrap* to 0.0. *time_extrap* is the time over which extrapolation is allowed. If an observation time precedes the first simulation time by more than this amount, or postdates the last simulation time by more than this amount, then its “extrapolated” value is *nointerpval*. + +#### Dummy Simulated Values + +In functions such as *interp_from_structured_grid()* and *interp_from_mf6_depvar_file()*, a dummy model-generated value is assigned at points and times at which spatial interpolation is impossible. This may occur if, for example, a point is outside the model grid, or because a point is temporarily or permanently surrounded by dry or inactive cells. These dummy values must be recognizable as being larger in absolute value than *interpthresh*. + +If temporal interpolation of model-generated values to observation times requires use of a *simval* element whose absolute value is greater than *interpthresh*, then the time-interpolated value ascribed to the pertinent *obssimval* element is assigned a value of *nointerpval* as interpolation/extrapolation is considered to be unreliable under these circumstances. + +## retrieve_error_message + +### Description + +Function *retrieve_error_message()* returns an error message. This is a non-blank string if the previous *model_inferface* module function call returned an error condition. + +### Function Call + +
integer(kind=c_int) function retrieve_error_message(errormessage) &
bind(c, name="retrieve_error_message_")
use iso_c_binding, only: c_int,c_char
character (kind=c_char), intent(out) :: errormessage(*)
end function retrieve_error_message
+
+### Return Value + +Function *retrieve_error_message()* returns a value of 0. However if the error message string is not empty, it returns a value of 1. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| errorstring | A character array of length 1500 containing an error message. This message is terminated by the string termination character “\\0” (i.e. ACHAR(0)). | + +### Notes + +If a *model_interface* module function experiences an error condition, then an error message string is available until the next function call. During this subsequent call, it is assigned a blank value, or is overwritten by another error message. + +The user-supplied character array to which this error message is written should be at least 1500 characters in length. If this is not the case, there is a risk of inadvertent memory over-write. + +## uninstall_mf6_grid + +### Description + +This function acts as a complement to *install_mf6_grid_from_file()*. As the name implies, it uninstalls previously-installed specifications of a MODFLOW 6 grid. In doing this, it deallocates the considerable amount of memory that may be required to store the complete specifications of this grid. + +### Function Call + +
integer (kind=c_int) function uninstall_mf6_grid(gridname) &
bind(c,name="uninstall_mf6_grid_")
use iso_c_binding, only: c_int,c_char
character (kind=c_char,len=1), intent(in) :: gridname(*)
end function uninstall_mf6_grid
+
+### Return Value + +Function *uninstall_mf6_grid()* returns a value of zero unless an error condition arises. This will occur if the MODFLOW 6 grid nominated through the *gridname* argument is not actually installed, or if some problem is encountered in deallocating memory. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|---------------------------------------------------------------------------------------------------------------------------------------| +| gridname | The name of a MODFLOW 6 grid which requires removal. As presently programmed, this name can be a maximum of 200 characters in length. | + +## uninstall_structured_grid + +### Description + +This function acts as a complement to *install_structured_grid()*. As the name implies, it uninstalls previously-installed specifications of a structured grid. In doing this, it deallocates memory that is used to house the DELR and DELC arrays pertaining to this grid. + +### Function Call + +
integer (kind=c_int) function uninstall_structured_grid(gridname) &
bind(c,name="uninstall_structured_grid_")
use iso_c_binding, only: c_int,c_char
character (kind=c_char), intent(in) :: gridname(*)
end function uninstall_structured_grid
+
+### Return Value + +Function *uninstall_structured_grid()* returns a value of zero unless an error condition arises. This will occur if the structured grid nominated through the *gridname* argument is not actually installed, or if some problem is encountered in deallocating memory. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|----------------------------------------------------------------------------------------------------------------------------| +| gridname | The name of an installed structured grid. As presently programmed, this name can be a maximum of 200 characters in length. | + +# Model Post-Processing Programs + +## calc_kriging_factors_2d + +### Description + +The task performed by function *calc_kriging_factors_2d()* resembles that performed by a PLPROC function of the same name. As the name suggests, this function calculates kriging factors. More accurately, it calls modified functions of the SGLIB library (Deutsch and Journel, 1998) to calcite kriging factors. These factors will often be used to implement spatial interpolation from pilot points to a model grid. However function *calc_kriging_factors_2d()* makes no mention of either pilot points nor a model grid. The set of points to which spatial interpolation takes place are specified only by their coordinates. These can be the centres of cells of a structured or unstructured model grid. Alternatively, they may have no relationship to a model grid whatsoever. + +Other features of *calc_kriging_factors_2d* include the following: + +- Kriging can be simple or ordinary; + +- Kriging can be based on a Gaussian, exponential, spherical or power variogram; + +- The specifications of this variogram (i.e. its range, anisotropy and anisotropy direction) can be spatially variable. A value for each of these specifications is assigned to every point to which spatial interpolation is required (often every cell within a model grid). Values for these specifications may have been interpolated to these cells using a previous call to this, or another, interpolation function; alternatively, they may be spatially invariant. + +*calc_kriging_factors_2d()* records the kriging factors that it calculates in a so-called “factor file”. This file can be binary or text. Interpolation factors can be used by function *krige_using_file()*. It is through this latter function that other variables required by the interpolation process are provided. These include source values (e.g. hydraulic property values associated with pilot points), mean values at target point locations (if simple kriging is undertaken), and whether interpolation must be based on native source point values or the logs of source point values. + +### Function Call + +
integer (kind=c_int) function calc_kriging_factors_2d( &
npts,ecs,ncs,zns, &
mpts,ect,nct,znt, &
vartype,krigtype, &
aa,anis,bearing, &
searchrad,maxpts,minpts, &
factorfile,factorfiletype, &
icount_interp)
use iso_c_binding, only: c_int,c_char,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecs(npts),ncs(npts)
integer(kind=c_int), intent(in) :: zns(npts)
integer(kind=c_int), intent(in) :: mpts
real(kind=c_double), intent(in) :: ect(mpts),nct(mpts)
integer(kind=c_int), intent(in) :: znt(mpts)
integer(kind=c_int), intent(in) :: vartype
integer(kind=c_int), intent(in) :: krigtype
real(kind=c_double), intent(in) :: aa(mpts)
real(kind=c_double), intent(in) :: anis(mpts)
real(kind=c_double), intent(in) :: bearing(mpts)
real(kind=c_double), intent(in) :: searchrad
integer(kind=c_int), intent(in) :: maxpts, minpts
character (kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
integer(kind=c_int), intent(out) :: icount_interp
end function calc_kriging_factors_2d
+
+### Return Value + +Function *calc_kriging_factors_2d()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| npts | Number of source points (often pilot points). | +| ecs, ncs | East and north coordinates of source points. | +| zns | Zone numbers of source points. | +| mpts | Number of target points (often cells of a model grid). | +| ect, nct | East and north coordinates of target points. | +| znt | Zone numbers of target points. | +| vartype | Variogram type; 1 = spherical; 2= exponential; 3 = Gaussian; 4 = power. | +| krigtype | Type of kriging: 0 = simple; 1 = ordinary. | +| aa | Variogram “a” value. | +| anis | Ratio of variogram “a” value in principle direction of anisotropy to that in the orthogonal direction. The principle direction of anisotropy is that given by the *bearing* argument. | +| bearing | Principle direction of anisotropy measured in degrees clockwise from north. | +| searchrad | The search radius; only points within this distance of a target point are used in kriging to that point. | +| maxpts | Only the closest *maxpts* points to a target point are used for interpolation to that point. | +| minpts | If there are less than *minpts* points within a distance of *searchrad* of a target point, an error condition arises, and is reported to the calling program. | +| factorfile | The name of the file in which interpolation factors are recorded. | +| factorfiletype | Whether the factor file is text or binary: 0 = binary; 1 = text. | +| icount_interp | Returned as the number of target points for which interpolation factors were calculated. | + +### Notes + +#### Zones + +Each source point and each target point must be assigned an integer zone number. This is done using the *zns* and *znt* arrays respectively. Interpolation to a target point is only undertaken from source points which lie within the same zone as that of the target point. It is important to note, however, that no interpolation takes place to target points whose zone number is zero. + +Suppose that a target point belongs to zone *N*. If no source point belongs to this same zone, then *calc_kriging_factors_2d()* reports an error message. + +#### Variograms + +Formulas for variograms are as follows. “a” values supplied through the *aa* function argument pertain to *a* featured in each of these equations. *a* must be supplied as greater than zero. + +*Spherical* + +![](809a31f7aa3068c2aa737448a4c8bc0c4f49a4fa.wmf) if *h* \< *a* + +![](675c52ab525262814f5fd2e38140492ded091061.wmf) if *h* ≥ *a* + +*Exponential* + +![](a5a89d68f7acdbdd57cea0e642175adef1466bf2.wmf) + +*Gaussian* + +![](27f8c02c8bf9e8d8da35c15fdeb49ccd801b6fce.wmf) + +*Power* + +![](3b0e642d96cf8c2fa6918ab9ab7cdcd51d2d0a9e.wmf) + +Note that a variogram sill is not required by function *calc_kriging_factors_2d()*. This is only required when a variogram is accompanied by a nugget. *calc_kriging_factors_2d()* does not require a nugget. + +#### Spatially-Varying Variograms + +As discussed above, variogram properties must be supplied in arrays whose dimensions are equal to the number of target points. If variogram properties are not spatially variable, then it is still necessary to provide these properties as arrays. In this case, of course, all elements of each array have the same value. + +#### Interpolation + +Once kriging factors have been calculated and stored, interpolation can be carried out using function *krige-using_file()*. + +## calc_kriging_factors_auto_2d + +### Description + +Function *calc_kriging_factors_auto_2d()* has a similar role to the PLPROC function of the same name. As the name implies, it calculates kriging factors for two-dimensional interpolation. As for function *calc_kriging_factors_2d()*, specifications of the variogram on which kriging is based can be spatially variable. However *calc_kriging_factors_auto_2d()* requires fewer arguments than the latter function, as it determines the variogram range itself based on local pilot point spatial density. It also works out for itself the local source point search radius; hence a user does not need to supply this as a function argument. Nor does he/she need to supply the maximum/minimum number of source points to employ in interpolation to a target point. + +### Function Call + +
integer (kind=c_int) function calc_kriging_factors_auto_2d( &
npts,ecs,ncs,zns, &
mpts,ect,nct,znt, &
krigtype, &
anis,bearing, &
factorfile,factorfiletype, &
icount_interp)
use iso_c_binding, only: c_int,c_char,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecs(npts),ncs(npts)
integer(kind=c_int), intent(in) :: zns(npts)
integer(kind=c_int), intent(in) :: mpts
real(kind=c_double), intent(in) :: ect(mpts),nct(mpts)
integer(kind=c_int), intent(in) :: znt(mpts)
integer(kind=c_int), intent(in) :: krigtype
real(kind=c_double), intent(in) :: anis(mpts)
real(kind=c_double), intent(in) :: bearing(mpts)
character (kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
integer(kind=c_int), intent(out) :: icount_interp
end function calc_kriging_factors_auto_2d
+
+### Return Value + +Function *calc_kriging_factors_auto_2d()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| npts | Number of source points (often pilot points). | +| ecs, ncs | East and north coordinates of source points. | +| zns | Zone numbers of source points. | +| mpts | Number of target points (often cells of a model grid). | +| ect, nct | East and north coordinates of target points. | +| znt | Zone numbers of target points. | +| krigtype | Type of kriging: 0 = simple; 1 = ordinary. | +| anis | Ratio of variogram “a” value in principle direction of anisotropy to that in the orthogonal direction. The principle direction of anisotropy is that given by the *bearing* argument. | +| bearing | Principle direction of anisotropy measured in degrees clockwise from north. | +| factorfile | The name of the file in which interpolation factors are recorded. | +| factorfiletype | Whether the factor file is text or binary: 0 = binary; 1 = text. | +| icount_interp | Returned as the number of target points for which interpolation factors were calculated. | + +### Notes + +Function *calc_kriging_factors_auto_2d()* employs an exponential variogram. Hence no variogram type argument is required. Because it calculates the variogram range itself based on local pilot point spatial density, problems that can sometimes occur where source point separations are very different in different parts of an interpolation domain are avoided. + +Other arguments that are absent from function *calc_kriging_factors_auto_2d()* but that are present in function *calc_kriging_factors_2d()* are the *searchrad, maxpts* and *minpts* arguments. *calc_kriging_factors_auto_2d()* works these out for itself. In doing this, it attempts to maximize the efficiency of the interpolation factor calculation process, while minimizing the size of the interpolation factor file that it writes. Its algorithm seems to work satisfactorily on most occasions. However a user should check that interpolated hydraulic property fields do not exhibit a “paintbrush effect”. These are local discontinuities that arise as source points move in and out of the interpolation search radius of neighbouring target points. + +Argument of the same name in functions *calc_kriging_factors_2d()* and *calc_kriging_factors_auto_2d()* perform similar roles. Refer to documentation of the former program for a discussion of the use of zones and spatially varying anisotropy. + +## calc_kriging_factors_3d + +### Description + +Function *calc_kriging_factors_3d()* implements three-dimensional kriging. As for other kriging functions provided by the *model_interface* library, kriging factors are actually calculated by subroutines that were modified from the GSLIB geostatistical software library (Deutsch and Journel, 1998). The tasks that *calc_kriging_factors_3d()* performs are similar to those performed by the PLPROC function of the same name. + +Kriging factors calculated by *calc_kriging_factors_3d()*can be used to implement spatial interpolation from pilot points to a model grid. However function *calc_kriging_factors_3d()* makes mention of neither pilot points nor a model grid. The set of points to which spatial interpolation takes place are specified only by their coordinates. These can be the centres of cells of a structured or unstructured grid. Alternatively, they may have no relationship to a model grid whatsoever. + +Other features of *calc_kriging_factors_3d* include the following: + +- Kriging can be simple or ordinary; + +- Kriging can be based on a Gaussian, exponential, spherical or power variogram; + +- The specifications of this variogram can be zone-based. + +*calc_kriging_factors_3d()* records the kriging factors that it calculates in a so-called “factor file”. This file can be binary or text. Interpolation factors are used by function *krige_using_file()*. As is recorded in documentation of this function, *krige_using_file()* requires further inputs that are required by the spatial interpolation process. These include source values (e.g. hydraulic property values at pilot point locations), mean hydraulic property values at target point locations (if simple kriging is undertaken), and whether interpolation is based on native source point values or the logs of source point values. + +### Function Call + +
integer (kind=c_int) function calc_kriging_factors_3d( &
npts,ecs,ncs,zcs,zns, &
mpts,ect,nct,zct,znt, &
krigtype, &
nzone,zonenum, &
vartype, &
ahmax,ahmin,avert, &
bearing,dip,rake, &
srhmax,srhmin,srvert, &
maxpts,minpts, &
factorfile,factorfiletype, &
icount_interp)
use iso_c_binding, only: c_int,c_char,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecs(npts),ncs(npts),zcs(npts)
integer(kind=c_int), intent(in) :: zns(npts)
integer(kind=c_int), intent(in) :: mpts
real(kind=c_double), intent(in) :: ect(mpts),nct(mpts),zct(mpts)
integer(kind=c_int), intent(in) :: znt(mpts)
integer(kind=c_int), intent(in) :: krigtype
integer(kind=c_int), intent(in) :: nzone
integer(kind=c_int), intent(in) :: zonenum(nzone)
integer(kind=c_int), intent(in) :: vartype(nzone)
real(kind=c_double), intent(in) :: ahmax(nzone),ahmin(nzone),avert(nzone)
real(kind=c_double), intent(in) :: bearing(nzone)
real(kind=c_double), intent(in) :: dip(nzone)
real(kind=c_double), intent(in) :: rake(nzone)
real(kind=c_double), intent(in) :: srhmax
real(kind=c_double), intent(in) :: srhmin
real(kind=c_double), intent(in) :: srvert
integer(kind=c_int), intent(in) :: maxpts, minpts
character (kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
integer(kind=c_int), intent(out) :: icount_interp
end function calc_kriging_factors_3d
+
+### Return Value + +Function *calc_kriging_factors_3d()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| npts | Number of source points (often pilot points). | +| ecs, ncs, zcs | East, north and vertical coordinates of source points. | +| zns | Zone numbers of source points. | +| mpts | Number of target points (often cells of a model grid). | +| ect, nct, zct | East, north and vertical coordinates of target points. | +| znt | Zone numbers of target points. | +| krigtype | Type of kriging: 0 = simple; 1 = ordinary. | +| nzone | Number of zones to which source and target points belong. | +| zonenum | Numbers assigned to zones. | +| vartype | Variogram type; 1 = spherical; 2= exponential; 3 = Gaussian; 4 = power. | +| ahmax | Variogram “*a*” value in principle direction of anisotropy (normally horizontal, or nearly so). | +| ahmin | Variogram “*a*” value in direction of medium anisotropy (normally horizontal, or nearly so). | +| avert | Variogram “*a*” value in a direction that is perpendicular to that of *ahmax* and *ahmin* (normally vertical, or nearly so). | +| bearing | Direction (in degrees) of the horizontal projection of the *ahmax* direction measured in degrees clockwise from north. *bearing* must exceed -360 degrees and be less than 360 degrees. | +| dip | Dip, in degrees, of the *ahmax* direction. In accordance with GSLIB conventions, a downwards dip of the positive *ahmax* axis is negative. *dip* must lie between -180 degrees and 180 degrees. | +| rake | Rotation, in degrees, of the *ahmin* and *avert* directions about the *ahmax* axis. See below. | +| srhmax | Search radius in the *ahmax* direction. | +| srhmin | Search radius in the *ahmin* direction. | +| srvert | Search radius in the *avert* direction. | +| maxpts | Only the closest *maxpts* source points that lie with the search envelope will be used for spatial interpolation to a target point. | +| minpts | If fewer than *minpts* source points lie within the search envelope of a target point, an error condition results. | +| factorfile | The name of the file in which interpolation factors are recorded. | +| factorfiletype | Whether the factor file is text or binary: 0 = binary; 1 = text. | +| icount_interp | Returned as the number of target points for which interpolation factors were calculated. | + +### Notes + +#### Zones + +Prior to calling function *calc_kriging_factors_3d()*, the calling program should fill the *zonenum* array with integers that denote zones that are allocated to both source and target points. Interpolation only takes place from and to points that lie within the same zone. These zones are designated in the *zns* and *znt* arrays for source and target points respectively. However, no interpolation takes place to or from points which lie in zone 0. Hence source and target points which belong to zone 0 are ignored. It is optional whether a zone number of 0 is supplied in the *zonenum* array; if it is supplied, then it too is ignored. + +It is important to note, however, that any zone number (except zero) that features in any one of the *zns*, *znt* and *zonenum* arrays must be featured in all of them. If this is not the case, function *calc_kriging_factors_3d()* will report an error condition. + +#### The Ellipse of Anisotropy + +See Deutsch and Journel (1998) and Remy et al (2011) for a full description. + +*ahmax*, *ahmin* and *avert* provide variogram *a* values in three orthogonal directions. The first two of these directions are presumed to approximate horizontal (though this is not essential); the third is therefore approximately vertical. *ahmax* is normally larger than *ahmin* (though this is not essential). + +*Bearing* must lie between -360 degrees and 360 degrees. This is the direction in which the horizontal projection of the positive direction of the *ahmax* axis points. It is measured clockwise from north. *Dip* is the angle (positive above ground level) in which the positive direction of the *ahmax* axis points. + +Values for *dip* can vary between -180 degrees and 180 degrees. + +To understand the meaning of *rake*, picture yourself looking down the *a_hmax* axis from its positive end towards its negative end. Now picture the ellipsoid of anisotropy being rotated anticlockwise about this *a_hmax* axis so that the positive direction of the *a_hmin* axis rises. The angle by which it rises is the *rake*. The value supplied for *rake* must lie between -90 degrees and 90 degrees. + +Note that the positive directions of each of the *a_hmax*, *a_hmin* and *a_vert* axes form a right hand coordinate system. A screw that is turned from positive *a_hmax* to positive *a_hmin* therefore advances in the *a_vert* direction. + +#### Search Radii + +The higher are the values that are supplied for *srhmax*, *srhmin* and *srvert*, the longer does *calc_kriging_factors_3d()* require to do its work, and the larger is the interpolation factor file that it writes. However the interpolated hydraulic property field is likely to be smooth, as no interpolation discontinuities are introduces as source points move in and out of the search radii of neighbouring target points. + +#### Interpolation + +The factor file which is written by *calc_kriging_factors_3d()* is read by function *krige_using_file()*. This is the function that interpolates values associated with source points to the locations of target points. + +### + +## krige_using_file + +### Description + +Function *krige_using_file()* reads an interpolation factor file written by any of the following functions: + +- *calc_kriging_factors_2d()* + +- *calc_kriging_factors_auto_2d()* + +- *calc_kriging_factors_3d()* + +It then implements spatial interpolation based on these functions. + +### Function Call + +
integer (kind=c_int) function krige_using_file( &
factorfile,factorfiletype, &
npts,mpts, &
krigtype,transtype, &
sourceval,targval, &
icount_interp, &
meanval)
use iso_c_binding, only: c_int,c_char,c_double
character (kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
integer(kind=c_int), intent(in) :: npts
integer(kind=c_int), intent(in) :: mpts
integer(kind=c_int), intent(in) :: krigtype
integer(kind=c_int), intent(in) :: transtype
real(kind=c_double), intent(in) :: sourceval(npts)
real(kind=c_double), intent(out) :: targval(mpts)
integer(kind=c_int), intent(out) :: icount_interp
real(kind=c_double), intent(in), optional :: meanval(mpts)
end function krige_using_file
+
+### Function Arguments + +| **Argument** | **Role** | +|----------------|-------------------------------------------------------------------------------------------------------| +| factorfile | Name of the file in which interpolation factors are stored. | +| factorfiletype | Whether the factor file is text or binary: 0 = binary; 1 = text. | +| npts | Number of source points. | +| mpts | Number of target points. | +| krigtype | Type of kriging: 0 = simple; 1 = ordinary. | +| transtype | Whether interpolation is applied to native or log values: 0 = native; 1=log. | +| sourceval | Values at source points that require spatial interpolation. | +| targval | Interpolated values at target points. | +| icount_interp | The number of target points to which interpolation takes place. | +| meanval | Mean values at target points. This is an optional argument. It must be supplied if kriging is simple. | + +### Notes + +#### Quality Assurance + +As stated above, function *krige_using_file()* uses kriging factors that are calculated by other functions. These functions write the interpolation factor file which *krige_using_file()* reads. The *sourceval* argument of *krige_using_file()* must contain values that are associated with source locations that were provided to these factor-file-generating functions. The number of elements in this array must therefore be the same as the number of elements in complementary arrays provided to the preceding functions. *krige_using_file()* checks that this is the case. It can do this because the value of *npts* is recorded in the factor file. + +Similar considerations apply to the *targval* argument. The value of *mpts* that is provided in a call to function *krige_using_file()* is compared with that recorded in the factor file. If they are not the same, an error condition is reported. An error will also be reported if factors were calculated for simple kriging by the function which wrote the factor file, while ordinary kriging is requested in a call to *krige_using_file()*. + +A further check on quality assurance is enabled by the *icount_interp* function argument. Functions which write the factor file which *krige_using_file()* reads return a value for this variable. The same value should be returned by *krige_using_file()*. + +Note that these quality assurance measures are not full proof. Values of *npts* and *mpts* may be the same in sequential calls to a factor-generating function and *krige_using_file()*. However, if a user is not careful, the ordering of points within respective source and target arrays may be different. This error cannot be detected. + +#### Simple Kriging + +Simple kriging requires that a mean value be associated with all target points. Under conditions of geostatistical stationarity, this mean value is the same for all points. However, as has been discussed, neither *krige_using_file()* nor its complementary factor-file-generating programs rely on an assumption of stationarity. Hence the mean value can be different at every target point. Because of this, *krige_using_file()* requires that an array of mean values be supplied for target points. If stationarity prevails, then all values supplied in this array should be the same. + +#### Transformation + +Interpolation can take place either in the log domain or in the domain of natural numbers. In the former case, source values are log-transformed by *krige_using_file()* before being interpolated to target points. Interpolated values at target points are then back-transformed to the domain of natural numbers before being assigned to them. If any source point value is zero or negative, this precipitates an error condition (because only positive numbers can be log-transformed); *krige_using_file()* then provides an appropriate error message. + +If kriging is simple, user-supplied target point mean values must also be greater than zero. If any of them are zero or negative, an error condition is reported. Note, however, that they must be supplied to *krige_using_file()* as natural numbers. *krige_using_file()* log-transforms them before using them. + +## build_covar_matrix_2d + +### Description + +Function *build_covar_matrix_2d()* builds a covariance matrix for a set of two-dimensional pilot points whose eastings, northings and zone numbers are provided. It calls functions from the GSLIB library to perform covariance calculation. The covariance between any two points is calculated using a variogram. However variogram properties can vary from point to point. Where variogram properties are different for two points for which the covariance must calculated, the smaller of the two covariances is adopted. + +### Function Call + +
integer (kind=c_int) function build_covar_matrix_2d( &
npts,ec,nc,zn, &
vartype, &
nugget,aa,sill,anis,bearing, &
ldcovmat,covmat)
use iso_c_binding, only: c_int,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ec(npts),nc(npts)
integer(kind=c_int), intent(in) :: zn(npts)
integer(kind=c_int), intent(in) :: vartype
real(kind=c_double), intent(in) :: nugget(npts)
real(kind=c_double), intent(in) :: aa(npts)
real(kind=c_double), intent(in) :: sill(npts)
real(kind=c_double), intent(in) :: anis(npts)
real(kind=c_double), intent(in) :: bearing(npts)
integer(kind=c_int), intent(in) :: ldcovmat
real(kind=c_double), intent(out) :: covmat(ldcovmat,npts)
end function build_covar_matrix_2d
+
+### Return Value + +Function *build_covar_matrix_2d()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|-------------------------------------------------------------| +| npts | Number of pilot points. | +| ecs, ncs | East and north coordinates of pilot points. | +| zns | Zone numbers of pilot points. | +| vartype | The variogram type used for covariance calculation. | +| nugget | Variogram nugget. | +| aa | Value of “a” appearing in variogram equation. | +| sill | Variogram sill. | +| anis | Variogram anisotropy. | +| bearing | Bearing of principle anisotropy axis. | +| ldcovmat | The leading dimension of the user-supplied *covmat* matrix. | +| covmat | The covariance matrix which is filled. | + +### Notes + +#### Variogram Parameters + +As is apparent from the above specifications, variogram properties can vary from pilot point to pilot point. Hence they must be supplied as arrays. The number of elements in each of these arrays must be the same as the number of pilot points. + +A covariance matrix must be positive definite. Function *build_covar_matrix_2d()* ensures this by first calculating a trial covariance matrix using spatially varying variogram properties, and then subjecting this matrix to singular value decomposition. Positive definiteness can then be acquired by flipping one or other of the corresponding left or right eigenvectors of the trial covariance matrix if one is the negative of the other. There the number of pilot points is large, this operation may take a while. Hence *build_covar_matrix_2d()* does not undertake this correction operation unless it detects heterogeneity of variogram properties. + +Variogram equations are provided in documentation of function *calc_kriging_factors_2d()*. Recall that: + +- *bearing* is measured clockwise from north. This is the direction of the principle axis of anisotropy. + +- *anis* is the anisotropy ratio. This is the ratio of the variogram “a” value in the direction of the principle anisotropy axis to that in the perpendicular direction. *anis* can be greater or less than unity. + +Note that use of the power variogram is not recommended. + +#### Zones + +Each pilot point must be assigned to a zone. Pilot points in different zones are assumed to have no geostatistical relationship with each other, and hence exhibit zero spatial correlation. + +*build_covar_matrix_2d()* does not permit a zone number of zero. This is to avoid confusion. Ideally, a zone number of zero could be used to denote elements of a user-supplied covariance matrix that must not be altered by *build_covar_matrix_2d()*. However this is not permitted, as SVD-correction of a covariance matrix that was partly filled on a previous call to *build_covar_matrix_2d()* cannot be guaranteed to leave previously-filled elements unaltered. + +#### Colocation + +*build_covar_matrix_2d()* will object (with an error message) if two pilot points are situated at the same location and are assigned to the same zone. + +## build_covar_matrix_3d + +### Description + +Function *build_covar_matrix_3d()* builds a covariance matrix for a set of three-dimensional pilot points. Covariance calculation is undertaken using functions from the GSLIB library. + +Pilot points can be assigned to different zones; covariances between pilot points in different zones are assumed to be zero. Within each zone, the properties of the variogram on which calculation of covariance is based can vary on a pilot point by pilot point basis. + +### Function Call + +
integer (kind=c_int) function build_covar_matrix_3d( &
npts,ec,nc,zc,zn, &
vartype, &
nugget,sill, &
ahmax,ahmin,avert, &
bearing,dip,rake, &
ldcovmat,covmat)
use iso_c_binding, only: c_int,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ec(npts),nc(npts),zc(npts)
integer(kind=c_int), intent(in) :: zn(npts)
integer(kind=c_int), intent(in) :: vartype
real(kind=c_double), intent(in) :: nugget(npts)
real(kind=c_double), intent(in) :: sill(npts)
real(kind=c_double), intent(in) :: ahmax(npts),ahmin(npts),avert(npts)
real(kind=c_double), intent(in) :: bearing(npts),dip(npts),rake(npts)
integer(kind=c_int), intent(in) :: ldcovmat
real(kind=c_double), intent(out) :: covmat(ldcovmat,npts)
end function build_covar_matrix_3d
+
+### Return Value + +Function *build_covar_matrix_3d()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| npts | Number of pilot points. | +| ec, nc, zc | East, north and vertical coordinates of pilot points. | +| zn | Zone numbers of pilot points. | +| vartype | Variogram type; 1 = spherical; 2= exponential; 3 = Gaussian; 4 = power. | +| ahmax | Variogram “*a*” value in principle direction of anisotropy (normally horizontal, or nearly so). | +| ahmin | Variogram “*a*” value in direction of medium anisotropy (normally horizontal, or nearly so). | +| avert | Variogram “*a*” value in a direction that is perpendicular to that of *ahmax* and *ahmin* (normally vertical, or nearly so). | +| bearing | Direction (in degrees) of the horizontal projection of the *ahmax* direction measured in degrees clockwise from north. *bearing* must exceed -360 degrees and be less than 360 degrees. | +| dip | Dip, in degrees, of the *ahmax* direction. In accordance with GSLIB protocol, a downwards dip of the positive *ahmax* axis is negative. *dip* must lie between -180 degrees and 180 degrees. | +| rake | Rotation, in degrees, of the *ahmin* and *avert* directions about the *ahmax* axis. | +| ldcovmat | The leading dimension of the user-supplied *covmat* matrix. | +| covmat | The covariance matrix that is filled. | + +### Notes + +#### Variogram Properties + +See documentation for *function calc_kriging_factors_3d()* for a description of the properties of a three-dimensional variogram. (Note that use of the power variogram is not recommended.) + +#### Spatially-Varying Variogram Properties + +As is stated above, different variogram properties can be ascribed to different pilot points. Hence the variogram that is used as a basis for covariance calculation can itself be spatially variable. In order to guarantee that the resulting covariance matrix is positive definite, *build_covar_matrix_3d()* first builds a trial covariance matrix. In building this matrix, it calculates the covariance between two pilot points using the variogram properties that are assigned to each of them. It then accepts the lower of the two covariances. However this process does not guarantee positive-definiteness of the resulting matrix. (Positive definiteness is a requirement of any covariance matrix.) *build_covar_matrix_3d()* provides positive definiteness by subjecting the trial covariance matrix to singular value decomposition (SVD), and then ensuring that all corresponding left and right eigencomponents are equal. It then re-builds the decomposed covariance matrix. Where there are many pilot points, this process can take a while. Note, however, that *build_covar_matrix_3d()* will not subject a trial covariance matrix to singular value decomposition unless intra-zonal spatial variation of variogram properties requires this. + +#### Zones + +Each pilot point must be assigned to a zone. Pilot points in different zones are assumed to have no geostatistical relationship with each other, and hence exhibit zero covariance. + +*build_covar_matrix_3d()* does not permit a zone number of zero. This is to avoid confusion. Ideally, a zone number of zero could be used to denote elements of a user-supplied covariance matrix that must not be altered by *build_covar_matrix_3d()*. However this is not permitted, as SVD-correction of a covariance matrix that was partly filled on a previous call to *build_covar_matrix_3d()* cannot be guaranteed to leave previously-filled elements unaltered. + +#### Colocation + +*build_covar_matrix_3d()* will object (with an error message) if two pilot points are situated at the same location and are assigned to the same zone. + +## calc_structural_overlay_factors + +### Description + +Tasks performed by function *calc_structural_overlay_factors()* are nearly identical to those performed by the PLPROC function of the same name. It allows the hydraulic properties of polylinear or polygonal structural features to be superimposed on one or many layers of a model grid. Furthermore, the properties of these features can fade into those of the background grid as distance from them increases. Smoothing, or “blurring”, of hydraulic property discontinuities at structural feature boundaries allows a model history-matching process to adjust their vertices, for the relationships between model-calculated quantities and the positions of these features are therefore discontinuous. The degree of “blurring” of these features is specified by the user. + +Interpolation/extrapolation/blending factors that are calculated by function *calc_structural_overlay_factors()* are used by function *interpolate_blend_using_file()*. Separation of deployment of these factors from calculation of these factors allow their use by more than one hydraulic property. + +Note that, at the time of writing, functionality provided by the *model_interface* library does not support movement of structural feature vertices. If required, this must be performed by programs which call these functions. Interested readers may note, however, that PLPROC provides functionality through which structure vertices can move along so-called “sliders”. The positions of these vertices are therefore history-match-adjustable. + +### Structural Overlay Parameters + +Before describing use of function *calc_structural_overlay_factors(),* the nature and practicalities of structural overlay parameterisation are described. Much of the description that follows is extracted from PLPROC documentation. + +#### Defining Structures + +A structure is comprised of a set of linear segments. As presently programmed, these are considered to be two-dimensional. Hence they are defined by the *x* and *y* coordinates of their vertices. + +Structures are of two types; see the following figure. The first structure type is polylinear (referred to as “piecewise-linear” in PLPROC documentation). The second is polygonal. The latter is distinguished from the former by closure of the linear segments which comprise the structure. However the user must not close a structure him/herself. Rather he/she must inform *calc_structural_overlay_factors()* that the last vertex of the structure is joined to the first vertex of the structure through designation of the structure type. Interpolation/extrapolation/blending factors for a number of structures can be calculated on a single call to *calc_structural_overlay_factors()*. However, on a single call to this function, all structures for which these factors are calculated must be either polylinear or polygonal. + + + +A polylinear structure is depicted on the left while a polygonal structure is depicted on the right. + +#### Identification of Individual Structures + +In a call to function *calc_structural_overlay_factors()*, a user must supply a list of *x* and *y* coordinates. These are the coordinates of structure vertices. An integer array must accompany these coordinate arrays. This array ascribes different integers to different structures, thereby allowing their differentiation. Certain rules apply to these structure-defining arrays. They are as follows. + +- All vertices of one structure must be provided before any vertices of another structure are provided. + +- The order of vertices in these arrays signifies the way that vertices are connected to form a structure. + +- The first and last vertices of a structure cannot be the same. As stated above, *calc_structural_overlay_factors()* closes a polygonal feature itself. + +Vertices in separate structures are not joined; each structure is independent of all other structures. + +#### Structure Parameters + +A hydraulic property value can be ascribed to each structure vertex. Hence vertices serve two roles. Firstly, they define the geometry of a structure; at the same time, they define the loci at which hydraulic properties are assigned to different parts of a structure. (Note that function *calc_structural_overlay_factors()* itself does not require that a user ascribe hydraulic properties to structure vertices; these are provided to the complementary *interpolate_blend_using_file()* function.) + +#### The Background Model Grid + +As well as supplying a list of structure vertices, a user must provide function *calc_structural_overlay_factors()* with a list of coordinates that define the centres of a model’s cells. In keeping with nomenclature that is employed by other *model_interface* functions, structure vertex coordinates are referred to as “source coordinates” whereas model cell centre coordinates are referred to as “target coordinates”. Only *x* and *y* target coordinates are required. However coordinates can be repeated. Hence target coordinates can represent a multi-layered (structured or unstructured) model grid. Where a grid is multilayered, a structure can thus penetrate multiple model layers. However a user-supplied integer array can be used to identify target coordinates that are unaffected by structures. + +#### Interpolation/Extrapolation/Blending of Hydraulic Properties: Polylinear Structures + +Consider a model grid cell centre that is situated at a perpendicular distance *d* from a polylinear structure. Suppose that the hydraulic property value that is already assigned to this cell is *pgi* (*i* stands for “initial”). This may have been assigned, for example, through interpolation from a set of pilot points. Let *ps* designate the hydraulic property value at the point along the structure that is closest to the grid cell centre. This hydraulic property value is evaluated by linear interpolation between the structure vertices on either side of this closest-approach point. + +We define *w* as half the value of the *conwidth* argument of function *calc_structural_overlay_factors()*; “conwidth” stands for “width of value constancy”. In the following equation, *a* is the value of the *aa* argument of function *calc_structural_overlay_factors().* The structure-influenced hydraulic property value calculated for the grid cell (which we designate as *pgf*, where *f* stands for “final”) is calculated using the following equations. + +*p*gf = *p**s* if *d* \< *w* (1a) + + (1b)
+Equation (1) states that if a model grid cell is within a distance *w* of a structure line (i.e. within a distance *conwidth/2* of this line), then it inherits its hydraulic property value from that point of the structure line to which it is closest. Conversely, if the model grid cell is a considerable distance from the structure line, then it retains its original hydraulic property value. Beyond a distance *w* from the structure line, the hydraulic property value assigned to the model cell (i.e. the target point) is a combination of that pertaining to the closest point on the structure and the original hydraulic property value that is already assigned to the cell. The influence of the structure wanes with distance from the structure in accordance with a squared exponential spatial decay function. Thus it wanes rapidly with distance. Nevertheless, the influence of the structure is continuous with distance. Hence the structure induces no abrupt changes in model hydraulic properties. This promotes continuity of model outputs with respect to parameter values if the latter include structure vertex coordinates. As stated above, the latter can therefore be estimated through history-matching. + +Unfortunately, implementation of the above scheme can result in hydraulic property valleys which bisect the inside angles of bends in a polylinear structure. These valleys can be smoothed through superimposition of inverse-power-of-distance interpolation on the above scheme. One of the points from which this superimposed interpolation takes place is the closest point on the structure to the target point; squared exponential decay with distance from the structure is also enforced. A user must supply an appropriate power-of-distance to function *calc_structural_overlay_factors()*. A value of between 2.0 and 3.0 generally suffices; it is not critical. + +#### Interpolation/Extrapolation/Blending of Hydraulic Properties: Polygonal Structure + +Interpolation/extrapolation/blending of a polygonal structure with that of a background grid differs from that of a polylinear structure. If an active model grid cell lies within a structural polygon, then the hydraulic property value that is assigned to that model cell is replaced by a value that is inherited entirely from the structure. This value is calculated through inverse power of distance interpolation of hydraulic properties ascribed to structure vertices. The power is supplied through the *inverse_power* argument of function *calc_structural_overlay_factors()*. A polygon can therefore represent a feature such as a hole in an aquitard. + +Where a grid point lies outside a structural polygon, the final hydraulic property value assigned to that point is a combination of structure hydraulic properties and hydraulic properties that are already assigned to model grid cells. Blending is performed using equation (1), where *d* is the distance from a model grid cell centre to the closest point on the structure’s polygonal boundary. + +#### Spatial Variability of Line-to-Grid Interpolation Variables + +Both *conwidth* and *aa*, the variables that appear in equation (1), are supplied to function *calc_structural_overlay_factors()* as arrays. Each element of each of these arrays pertains to a structure vertex. Their values are linearly interpolated between these vertices before being used in the above equation. + +#### Overlay Order + +In some parameterisation contexts, a model grid may be subjected to multiple hydraulic property overlay events (just as a host rock may have been subjected to multiple structural events). This may be effected by repeated use of function *calc_structural_overlay_factors()*. It may also be effected through use of multiple structures in a single call to function *calc_structural_overlay_factors()*. In this case, the order of parameter overlay is the same as the ordering of structures in source arrays that are supplied to this function. + +Similarly, if a polylinear structure is defined in such a way that it crosses itself (not a good idea), those elements of the structure that are featured later in its definition supersede those that are featured earlier in its definition when overlaying and blending its hydraulic properties with pre-existing properties of the underlying model grid. + +### Function Call + +
integer (kind=c_int) function calc_structural_overlay_factors( &
npts, &
ecs,ncs,ids, &
conwidth,aa, &
structype,inverse_power, &
mpts, &
ect,nct,active, &
factorfile,factorfiletype, &
icount_interp)
use iso_c_binding, only: c_int,c_char,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecs(npts),ncs(npts)
integer(kind=c_int), intent(in) :: ids(npts)
real(kind=c_double), intent(in) :: conwidth(npts),aa(npts)
integer(kind=c_int), intent(in) :: structype
real(kind=c_double), intent(in) :: inverse_power
integer(kind=c_int), intent(in) :: mpts
real(kind=c_double), intent(in) :: ect(mpts),nct(mpts)
integer(kind=c_int), intent(in) :: active(mpts)
character (kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
integer(kind=c_int), intent(out) :: icount_interp
end function calc_structural_overlay_factors
+
+### Return Value + +Function *calc_structural_overlay_factors()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can then be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| npts | Number of source points. These define the vertices of one or more structures. | +| ec, nc | East and north coordinates of source points. | +| ids | Elements of this array identify different structures using different integers. | +| conwidth | The value of the *cw* variable (see equation 1) at every structure vertex. | +| aa | The value of the *a* variable (see equation 1) at every structure vertex. | +| structype | Provide a value of 0 for polylinear structures and 1 for polygonal structures. | +| inverse_power | Inverse power of distance. See above description for use of this variable in interpolation/extrapolation/blending from polylinear and polygonal features to a background model grid. | +| mpts | Number of target points, normally the centres of model grid cells. | +| ect, nct | East and north coordinates of target points. | +| active | If the *active* value assigned to a target point is zero, then it is unaffected by the presence of structures. | +| factorfile | The name of the file in which interpolation factors are recorded. | +| factorfiletype | Provide a value of 0 if *factorfile* is binary and 1 if it is a text file. | +| icount_interp | The number of interpolation factors that are calculated. | + +### Notes + +#### icount_interp + +Function *calc_structural_overlay_factors()* calculates a value for *icount_interp* as a quality assurance measure. This helps a user to verify that the relationship between structures and an underlying model grid is correct. It is calculated as follows: + +*ic* = *nstruc*×(*mpts*-*ina*) (2) + +In equation 2, *ic* is the value of *icount_interp*. Meanwhile *mpts* is the number of elements that are featured in target arrays, while *ina* is the number of these elements that are inactive. *nstruc* is the number of structures; interpolation/extrapolation/blending is undertaken once for each structure. + +It is important to note that *icount_interp* is simply a book-keeping device. A structure has little or no effect on model cells that are a long way from it. *icount_interp* is not reduced to accommodate zero, or near-zero, interpolation factors from structures to distant model cells. + +#### Hydraulic Property Values + +As has already been discussed, factors that are calculated by *calc_structural_overlay_factors()* are used by function *interpolate_blend_using_file()*. The latter function also provides a value for *icount_interp*. This value should be in agreement with that calculated by *calc_structural_overlay_factors()*. + +## interpolate_blend_using_file + +### Description + +Function *interpolate_blend_using_file()* uses factors calculated by *calc_structural_overlay_factors()* to alter values that have already been assigned to cells of a model grid. As is described in documentation of the latter function, these alterations mimic the action of structural features that have been superimposed on a model domain. + +### Function Call + +
integer (kind=c_int) function interpolate_blend_using_file( &
factorfile,factorfiletype, &
npts,mpts, &
transtype, &
lt_target,gt_target, &
sourceval,targval, &
icount_interp)
use iso_c_binding, only: c_int,c_char,c_double
character (kind=c_char,len=1), intent(in) :: factorfile(*)
integer(kind=c_int), intent(in) :: factorfiletype
integer(kind=c_int), intent(in) :: npts
integer(kind=c_int), intent(in) :: mpts
integer(kind=c_int), intent(in) :: transtype
character (kind=c_char,len=1), intent(in) :: lt_target,gt_target
real(kind=c_double), intent(in) :: sourceval(npts)
real(kind=c_double), intent(inout) :: targval(mpts)
integer(kind=c_int), intent(out) :: icount_interp
end function interpolate_blend_using_file
+
+### Return Value + +Function *interpolate_blend_using_file()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| factorfile | Name of the file that contains interpolation factors computed by function *calc_structural_overlay_factors()*. | +| factorfiletype | Provide a value of 0 if *factorfile* is binary and 1 it is a text file. | +| npts | Number of source points (vertices of one or more structures). | +| mpts | Number of target points (normally model cell centres). | +| transtype | Set to 0 if the interpolation/extrapolation/blending process is applied to native hydraulic property values, and 1 if it is applied to their logs. | +| lt_target | Supply as “y” if an interpolated/extrapolated/blended model grid value can be smaller than the value that has already been ascribed to the same cell. Supply a value of “n” otherwise. | +| gt_target | Supply as “y” if an interpolated/extrapolated/blended model grid value can be larger than the value that has already been ascribed to the same cell. Supply a value of “n” otherwise. | +| sourceval | Values assigned to structure vertices. These are the values that will be extrapolated along or within a structure, and then blended with existing values ascribed to model grid cells. | +| targval | Values ascribed to model cells. Values at near-structure cells are modified by function *interpolate_blend_using_file()*. | +| icount_interp | The number of occasions on which interpolation/extrapolation/blending takes place from a structure to a model cell. | + +### Notes + +#### Array Dimensions + +The values supplied for *npts* and *mpts* on a call to function *interp_blend_using_file()* must be in agreement with those supplied to function *calc_structural_overlay_factors()* when it wrote the file that function *interpolate_blend_using_file()* reads. If this is not the case, an error condition will be reported. (Values for *npts* and *mpts* are stored in the factor file.) + +It is incumbent on a user to ensure that the ordering of elements in the *sourceval* and *targval* arrays is the same as those in the *ecs*/*ncs* and *ect*/*nct* arrays respectively that were provided to function *calc_structural_overlay_factors()* when it wrote the factor file that *interp_blend_using_file()* reads. + +#### Target Values + +Target values are modified by application of factors supplied in the factor file. The way in which modified values are calculated is described in documentation of function *calc_structural_overlay_factors()*. Implementation of these equations requires that the *targval* array be already filled with background values. + +If, in the previous call to function *calc_structural_overlay_factors()*, a model grid cell was designated as inactive, then its value is not altered by *interpolate_blend_using_file()*. + +#### Log-Transformation + +The equations through which interpolation/extrapolation/blending is implemented can be applied to the logs of parameter values that are ascribed to structural elements and those that are ascribed to the model grid. This may be appropriate for hydraulic properties such as hydraulic conductivity whose values can vary over orders of magnitude. If requested, this takes place behind the scenes; no user input other than a request (through the *transtype* function argument) for this mode of interpolation is required. + +#### Limits + +*interpolate_blend_using_file()* allows a user to impose cell-specific limits on the outcomes of the interpolation/extrapolation/blending operation that it performs. If a structure has high hydraulic property values and the background grid has low hydraulic property values, then a user may wish to prevent lowering of background grid hydraulic property values by function *interpolate_blend_using_file()*. (Note that equation 1 of the previous section prevents this; however it may occur inadvertently if low parameter values are assigned to structural source points during history-matching). This can be prevented by setting the value of the *lt_targval* input argument to “*n*” (for “no”). Converse functionality is activated by setting *gt_targval* to “n”; in this case the interpolation/extrapolation/blending procedure is not allowed to raise the value that is already ascribed to a target cell. + +If a user wishes to impose upper and lower limits on target cell values that are independent of their present values, then he/she must do this in code that calls function *interpolate_blend_using_file()*. + +## ipd_interpolate_2d + +### Description + +Function *ipd_interpolate_2d()* undertakes inverse power of distance interpolation from one set of points to another; these are referred to as “source points” and “target points” respectively. The source points may be pilot points, while the points to which interpolation takes place may be the cell centres of a structured or unstructured model grid. + +The variables which govern interpolation details can vary from point to point within the model grid. These are: + +- the magnitude and direction of anisotropy; + +- the inverse power of distance; + +- the target point zone number. + +### Function Call + +
integer (kind=c_int) function ipd_interpolate_2d( &
npts, &
ecs,ncs,zns,sourceval, &
mpts, &
ect,nct,znt,targval, &
transtype, &
anis,bearing,invpow)
use iso_c_binding, only: c_int,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecs(npts),ncs(npts)
integer(kind=c_int), intent(in) :: zns(npts)
real(kind=c_double), intent(in) :: sourceval(npts)
integer(kind=c_int), intent(in) :: mpts
real(kind=c_double), intent(in) :: ect(mpts),nct(mpts)
integer(kind=c_int), intent(in) :: znt(mpts)
real(kind=c_double), intent(out) :: targval(mpts)
integer(kind=c_int), intent(in) :: transtype
real(kind=c_double), intent(in) :: anis(mpts)
real(kind=c_double), intent(in) :: bearing(mpts)
real(kind=c_double), intent(in) :: invpow(mpts)
end function ipd_interpolate_2d
+
+### Return Value + +Function *ipd_interpolate_2d()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| npts | Number of source points (often pilot points). | +| ecs, ncs | East and north coordinates of source points. | +| zns | Zones to which source points belong. | +| sourceval | Values at source points; these are interpolated to target points. | +| mpts | Number of target points. | +| ect, nct | East and north coordinates of target points. | +| znt | Zones to which target points belong. | +| targval | Values that are interpolated to target points. | +| transtype | Provide as 1 if interpolation takes place in the log domain; provide as 0 otherwise. | +| anis | The anisotropy ratio at each target point. This is the ratio of continuity length in the direction of *bearing* (see below) to continuity length in directions that are perpendicular to *bearing*. | +| bearing | The bearing of the principal axis of anisotropy at each target point. | +| invpow | The inverse power of distance to employ at each target point. | + +### Notes + +#### Zones + +Each source point and each target point must be assigned an integer zone number. This is done using the *zns* and *znt* arrays respectively. Interpolation to a target point is only undertaken from source points which lie within the same zone as that of the target point. It is important to note, however, that no interpolation takes place to target points whose zone number is zero. Similarly, no interpolation takes place from source points whose zone number is zero. + +Suppose that a target point belongs to zone *N*. If no source point belongs to this same zone, then *ipd_interpolate_2d()* reports an error message. + +#### Anisotropy + +As is apparent from its arguments, function *ipd_interpolate_2d()* can handle (spatially varying) anisotropy. When calculating distances between a target point and the source points whose values contribute to the interpolated value at that point, distances are effectively lengthened in the direction that is perpendicular to the principal axis of anisotropy. The direction of the latter is given by *bearing*. This is measured clockwise from north. A greater effective distance between a target and source point means that the source point has less effect on the interpolated value at the target point. + +Note that the anisotropy ratio is normally greater than 1.0. Hence the principle axis of anisotropy points in the direction of maximum continuity of heterogeneity. However *ipd_interpolate_2d()* does not insist on this. + +#### Inverse Power of Distance + +The inverse power of distance can vary from target point to target point. Beware, however; it should not be allowed to vary abruptly, as this can cause discontinuities in the interpolated field. + +## ipd_interpolate \_3d + +### Description + +Function *ipd_interpolate_3d()* undertakes inverse power of distance interpolation from one set of points to another; these are referred to as “source points” and “target points” respectively. The source points may be pilot points, while the points to which interpolation takes place may be the centres of cells comprising a structured or unstructured model grid. + +The variables which govern details can vary from point to point throughout the model grid. These are: + +- the orientation and shape of the ellipse of anisotropy; + +- the inverse power of distance; + +- the target point zone number. + +### Function Call + +
integer (kind=c_int) function ipd_interpolate_3d( &
npts, &
ecs,ncs,zcs,zns,sourceval, &
mpts, &
ect,nct,zct,znt,targval, &
transtype, &
ahmax,ahmin,avert, &
bearing,dip,rake, &
invpow)
use iso_c_binding, only: c_int,c_double
integer(kind=c_int), intent(in) :: npts
real(kind=c_double), intent(in) :: ecs(npts),ncs(npts),zcs(npts)
integer(kind=c_int), intent(in) :: zns(npts)
real(kind=c_double), intent(in) :: sourceval(npts)
integer(kind=c_int), intent(in) :: mpts
real(kind=c_double), intent(in) :: ect(mpts),nct(mpts),zct(mpts)
integer(kind=c_int), intent(in) :: znt(mpts)
real(kind=c_double), intent(out) :: targval(mpts)
integer(kind=c_int), intent(in) :: transtype
real(kind=c_double), intent(in) :: ahmax(mpts),ahmin(mpts),avert(mpts)
real(kind=c_double), intent(in) :: bearing(mpts),dip(mpts),rake(mpts)
real(kind=c_double), intent(in) :: invpow(mpts)
end function ipd_interpolate_3d
+
+### Return Value + +Function *ipd_interpolate_3d()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|---------------------|-----------------------------------------------------------------------------------------------------------| +| npts | Number of source points (often pilot points). | +| ecs, ncs, zcs | East, north and z coordinates of source points. | +| zns | Zones to which source points belong. | +| sourceval | Values at source points; these are interpolated to target points. | +| mpts | Number of target points. | +| ect, nct, zct | East, north and z coordinates of target points. | +| znt | Zones to which target points belong. | +| targval | Values that are interpolated to target points. | +| transtype | Provide as 1 if interpolation takes place in the log domain; provide as 0 otherwise. | +| ahmax, ahmin, avert | Relative magnitudes of horizontal major and minor axes of anisotropy, and of vertical axis of anisotropy. | +| bearing, dip, rake | Directions in which axes of anisotropy point. | +| invpow | The inverse power of distance to employ at each target point. | + +### Notes + +#### Anisotropy + +As is apparent from *ipd_interpolate_3d()* arguments, different specifications of the ellipse of anisotropy can be supplied at every target point. + +In other functions of the *model_interface* library, *ahmax,* *ahmin* and *avert* refer to variogram “a” values in three orthogonal directions, two of which are generally horizontal. The same nomenclature is used by function *ipd_interpolate_3d()* for the sake of consistency. However only their ratios are relevant. + +As usual, *bearing* is the angle between north (measured clockwise) and the direction of the horizontal projection of the principle axis of anisotropy (i.e. the direction pertaining to *ahmax*). *dip* is the dip of the *ahmax* axis, with positive being in the upwards direction. *rake* is the rotation of the *ahmin* axis about the *ahmax* axis. If looking from the positive direction of the *ahmax* axis, a positive rake denotes a lifting of the right side of the *ahmin* axis. + +#### Inverse Power + +The inverse power of distance used in the interpolation process can also vary from target point to target point. If this is done, then variations should be smooth so that discontinuities are not introduced to the interpolated field. + +#### Zones + +Each target and source point is assigned a zone number. Spatial interpolation to a target point within a certain zone takes place only from source points which belong to the same zone. No interpolation takes place from/to source/target points that belong to zone 0. + +## initialize_randgen + +### Description + +Function *initialize_randgen()* is used to initialize and seed the random number generator used by *model_interface* functions. *initializer_randgen()* must be called before calling functions such as *fieldgen2d_sva()* and *fieldgen3d_sva()* which use random numbers to populate models with stochastic hydraulic property fields. + +### Function Call + +
integer (kind=c_int) function initialize_randgen(iseed)
use iso_c_binding, only: c_int
integer(kind=c_int), intent(in) :: iseed
end function initialize_randgen
+
+### Return Value + +Function *initialize_randgen()* returns a value of zero. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|---------------------------------------------------------------| +| iseed | An integer. This is the seed for the random number generator. | + +### Notes + +As stated above, function *initialize_randgen()* must be called before certain other *model_interface* functions are called. However it only needs to be called once, regardless of the number of times that random number generation functions are called. Nevertheless, there is no reason why it cannot be called multiple times. This allows a user to enter a specific random number seed before each call to a certain function, if this is his/her desire. + +## fieldgen2d_sva + +### Description + +Function *fieldgen2d_sva()* populates the cells of a model grid with random hydraulic property fields. The statistical properties of these fields (including variance, correlation length and anisotropy) can vary from model cell to model cell. The grid which is populated by *fieldgen2d_sva()* can be structured or unstructured. + +The algorithm used by *fieldgen2d_sva()* closely resembles that used by the PEST Groundwater Utility program after which it is named. Random field generation is undertaken through spatial averaging of a field of independent, uncorrelated random numbers; each of these numbers is associated with a model cell. + +### Some Theory + +Much of the following text is extracted from the manual for the PEST Groundwater Utilities. + +#### General + +*fieldgen2d_sva()* employs the so-called “non-centred” method of stochastic field generation. A benefit of this method is that it can be used to generate stochastic fields based on both stationary and non-stationary variograms. A disadvantage of the method is that the actual variogram that is associated with the generated stochastic field is unknown, except in special circumstances. This matters if a variogram that is derived from field measurements of hydraulic properties must be respected. However it generally does not matter for stochastic fields that are used by a groundwater model, for the spatial correlation length of these fields is usually guessed. If a modeller does not like the patterns of spatial variability that *fieldgen2d_sva()* generates, then he/she is at liberty to regenerate these fields using different *fieldgen2d_sva()* settings until the patterns reflect those which he/she expects to prevail in the real world. Nevertheless, as we shall see, approximate relationships between *fieldgen2d_sva()* algorithm control variables and correlation lengths exhibited by the stochastic patterns that it generates are reasonably easy to establish. + +#### Spatial Averaging + +Suppose that *fieldgen2d_sva()* is asked to build a stochastic field for a two-dimensional grid comprised of *nnodes* cells. *fieldgen2d_sva()* begins by sampling a standard, univariate normal probability distribution *nnodes* times. This distribution has a mean of zero and a standard deviation of 1.0. *fieldgen2d_sva()* assigns one such random sample to each model cell. Next, for every grid cell to which it must assign a random hydraulic property value, it passes a moving average over the independent standard normal deviates that occupy the grid; this is equivalent to a two-dimensional convolution integral. + +It can be shown that if this moving average filter is proportional to a Gaussian function - i.e. if it is proportional to *exp\[-(h/a)2\]*, then the resulting stochastic hydraulic property field is equivalent to that which would be generated using a Gaussian variogram with an “*a*” value of *√2* times that which is used for spatial averaging. An appropriate constant is applied in order to achieve the desired variogram sill for that point in the model grid. + +Convolution using other spatial averaging functions yields stochastic hydraulic property fields that pertain to other variograms. However, in these cases, the variogram may not have an analytical expression. Spatial correlation that is exhibited by a stochastic field which is generated in this way extends to a length which is commensurate with the length over which the convolution function (i.e. the moving average function) is nonzero. In actual fact, the range of the implied variogram is somewhat greater than the range of the moving average function. This is because two points in space exhibit spatial correlation if moving average functions which are centred on each of them include one or a number of the same grid points. Hence the same sampled random normal variate is included in the respective spatial averages that generate hydraulic property values at both of these points. + +#### Spatially Varying Stochasticity + +The moving average method is easily extended to nonstationary contexts. The moving average function can therefore vary in space. Hence the function which is used to attribute a hydraulic property to one point in a grid can be different from that which is used to assign a hydraulic property to other points in the grid. The variables which govern the grid-point-specific moving average function can be spatially interpolated to all model grid cells prior to implementing the spatial averaging process. + +#### The Functions + +*fieldgen2d_sva()* employs the following spatial averaging functions. Note that while some of these functions have the same names as variograms, these names do not describe the variograms that are associated with the fields that they generate (except for the Gaussian function) for reasons that are described above. Note also that each of these functions is equal to 1.0 when the abscissa *h* is zero. *h* is the distance between the target point for which the random hydraulic property value is calculated and a field point; averaging takes place over field points. + +*fieldgen2d_sva()* allows a user to assign a variance to each grid point. Coefficients assigned to the spatial averaging function ensure that this variance is respected in the stochastic hydraulic property fields that it generates. Note also, that, as is discussed below, *fieldgen2d_sva()* supports variogram anisotropy. Where anisotropy prevails, the value of *h* in the following equations is direction-dependent. + +*Spherical:* $f(h) = 1 - \\frac{3}{2}\\frac{h}{a} - \\frac{1}{2}\\frac{h^{3}}{a^{3}}$ + +*Exponential:* $f(h) = \\ exp\\left( - \\frac{h}{a} \\right)$ + +*Gaussian:* $f(h) = exp\\left( - \\frac{h^{2}}{a^{2}} \\right)$ + +*Power:* $f(h) = 1 - \\left( \\frac{h}{a} \\right)^{p}$ + +The following picture depicts the power function for different values of the power *p*. + + + +The power averaging function for different values of power. + +Which is the best averaging function to use? You must decide for yourself based on the patterns that *fieldgen2d_sva()* generates. + +#### Disadvantages + +Calculation of convolution integrals for the exponential averaging function (and to some extent the Gaussian averaging function) may be slower than for other functions. For the spherical and power functions, averaging ceases at a distance of *h/a* from each target point. In contrast, for the exponential and Gaussian averaging functions it extends further than this. For large, dense model grids, this can increase the time required for populating the grid with a random hydraulic property field. + +The algorithm that *fieldgen2d_sva()* employs does not readily admit the presence of conditioning points (i.e. points at which the hydraulic property field is known) into its algorithm. Nevertheless, conditioning can be effected in a more roundabout manner. Suppose that there are *N* points within the model grid at which hydraulic property values have been measured. Then the mean hydraulic property field value that is supplied by the user should be equal to these observed values at the *N* measurement sites. At the same time, the user-supplied variance (see below) should be set to a small value at these and surrounding points in order to express the conditioning effect of these measurements. + +#### More Reading + +See, for example, Higdon et al (1999), Fuentes (2002), Oliver (2022) and references cited therein. + +### Function Call + +
integer (kind=c_int) function fieldgen2d_sva( &
nnode, &
ec,nc,area,active, &
mean,var,aa,anis,bearing, &
transtype,avetype,power, &
ldrand,nreal,randfield)
use iso_c_binding, only: c_int,c_double
integer(kind=c_int), intent(in) :: nnode
real(kind=c_double), intent(in) :: ec(nnode),nc(nnode)
real(kind=c_double), intent(in) :: area(nnode)
integer(kind=c_int), intent(in) :: active(nnode)
real(kind=c_double), intent(in) :: mean(nnode)
real(kind=c_double), intent(in) :: var(nnode)
real(kind=c_double), intent(in) :: aa(nnode)
real(kind=c_double), intent(in) :: anis(nnode)
real(kind=c_double), intent(in) :: bearing(nnode)
integer(kind=c_int), intent(in) :: transtype
integer(kind=c_int), intent(in) :: avetype
real(kind=c_double), intent(in) :: power
integer(kind=c_int), intent(in) :: ldrand
integer(kind=c_int), intent(in) :: nreal
real(kind=c_double), intent(out) :: randfield(ldrand,nreal)
end function fieldgen2d_sva
+
+### Function Arguments + +| **Argument** | **Role** | +|--------------|----------------------------------------------------------------------------------------------------------------------------------------| +| nnode | The number of nodes (i.e. cells) in the pertinent layer of the model grid. | +| ec, nc | East and north coordinates of model grid nodes (i.e. model grid cell centres). | +| area | The area of a model cell. (This is used in spatial averaging.) | +| active | Set to 0 for an inactive cell. See below. | +| mean | Cell-by-cell values of the mean hydraulic property. Stochastic fields are centred on these means. | +| var | The variance of hydraulic property variation about the mean at each model cell. This is respected in stochastic field generation | +| aa | The “a” value appearing in the above equations for averaging functions. | +| anis | The ratio of hydraulic property correlation length in the principal direction of anisotropy to that in directions perpendicular to it. | +| bearing | The angle between north and the principal direction of anisotropy. This angle is measured clockwise. | +| transtype | Set to 0 if stochastic properties pertain to natural numbers, and to 1 if they pertain to the logs of natural numbers. See below. | +| avetype | Averaging function; 1 = spherical; 2= exponential; 3 = Gaussian; 4 = power. | +| power | The power used in the averaging function if *avetype* is set to 4. | +| ldrand | The leading dimension of the *randfield* array. (Note that the FORTRAN array indicial convention is used.) | +| nreal | The number of realisations to be generated. | +| randfield | On exit, this array contains stochastic hydraulic property fields. | + +### Notes + +#### Speed of Execution + +Use of the spherical and power averaging functions is much faster than that of other functions. This is because the integration area is smaller for these functions. + +Experience shows that execution of the *fieldgen2d_sva()* spatial averaging algorithm is much faster if the model-interface library is compiled with the compiler’s optimiser turned on than if it is compiled in “debug” mode. + +#### Memory + +The *randfield* array must hold all stochastic fields at once. Another array of the same dimensions must be allocated internally by *fieldgen2d_sva()* to hold uncorrelated random numbers that are integrated to calculate these fields. The memory requirements of *fieldgen2d_sva()* may thus become heavy. In some circumstances it may be wise for a user to call *fieldgen2d_sva()* to generate a few random fields at a time, and then store these fields in a file after each call. If this is done, the random number generation process is automatically advanced so that the same fields are not re-generated on subsequent calls to *fieldgen2d_sva()* (unless a user calls function *initialize_randgen()* to re-initialize the random number seed). + +#### Log-Transformation + +If *transtype* is set to 1, then hydraulic property fields, and the user-supplied variables that govern them, pertain to the log (to base10) of hydraulic property values. These are used to multiply the mean hydraulic property field supplied by the calling program. The resulting fields are then anti-logged before being returned through the *randfield* array. + +#### Active and Inactive Cells + +Random hydraulic properties are assigned only to cells that are designated as active. Additionally, random number averaging is performed only over active cells. *randfield* values that are ascribed to inactive cells on entry to function *fieldgen2d_sva()* are left unaltered by this function. + +## fieldgen3d_sva + +### Description + +Function *fieldgen3d_sva()* populates the cells of a model grid with random hydraulic property fields. The statistical properties of these fields (including variance, correlation length and anisotropy) can vary from model cell to model cell. The grid which is populated by *fieldgen3d_sva()* can be structured or unstructured. + +The algorithm used by *fieldgen3d_sva()* closely resembles that used by the PEST Groundwater Utility program after which it is named. Random field generation is undertaken through spatial averaging of a field of independent, uncorrelated random numbers; each of these numbers is associated with a model cell. See documentation of function *fieldgen2d_sva()* for theory. + +### Function Call + +
integer (kind=c_int) function fieldgen3d_sva( &
nnode, &
ec,nc,zc, &
area,height,active, &
mean,var, &
ahmax,ahmin,avert, &
bearing,dip,rake, &
transtype,avetype,power, &
ldrand,nreal,randfield)
use iso_c_binding, only: c_int,c_double
integer(kind=c_int), intent(in) :: nnode
real(kind=c_double), intent(in) :: ec(nnode),nc(nnode),zc(nnode)
real(kind=c_double), intent(in) :: area(nnode)
real(kind=c_double), intent(in) :: height(nnode)
integer(kind=c_int), intent(in) :: active(nnode)
real(kind=c_double), intent(in) :: mean(nnode)
real(kind=c_double), intent(in) :: var(nnode)
real(kind=c_double), intent(in) :: ahmax(nnode),ahmin(nnode),avert(nnode)
real(kind=c_double), intent(in) :: bearing(nnode)
real(kind=c_double), intent(in) :: dip(nnode)
real(kind=c_double), intent(in) :: rake(nnode)
integer(kind=c_int), intent(in) :: transtype
integer(kind=c_int), intent(in) :: avetype
real(kind=c_double), intent(in) :: power
integer(kind=c_int), intent(in) :: ldrand
integer(kind=c_int), intent(in) :: nreal
real(kind=c_double), intent(out) :: randfield(ldrand,nreal)
end function fieldgen3d_sva
+
+### Return Value + +Function *fieldgen3d_sva()* returns a value of zero unless an error condition is encountered, in which case it returns a value of 1. In the latter case, an error message can be retrieved using function *retrieve_error_message()*. + +### Function Arguments + +| **Argument** | **Role** | +|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| nnode | The number of nodes (i.e. cells) in the pertinent layer of the model grid. | +| ec, nc, zc | East, north and elevation coordinates of model grid nodes (i.e. model grid cell centres). | +| area | The area of a model cell. (This is used in spatial averaging.) | +| height | The height of a model cell. (This is used in spatial averaging.) | +| active | Set to 0 for an inactive cell. See below. | +| mean | Cell-by-cell values of the mean hydraulic property. Stochastic fields are centred on these means. | +| var | The variance of hydraulic property variation about the mean at each model cell. This is respected in stochastic field generation | +| ahmax | The “a” value appearing in the equations for averaging functions in the principal direction of anisotropy. | +| ahmin | The “a” value appearing in the equations for averaging functions in a direction that is perpendicular to that of *ahmax* in a horizontal/subhorizontal direction. | +| ahvert | The “a” value appearing in the equations for averaging functions in a direction that is perpendicular to that of *ahmax* and *ahmin*. | +| transtype | Set to 0 if stochastic properties pertain to natural numbers, and to 1 if they pertain to the logs of natural numbers. See below. | +| avetype | Averaging function; 1 = spherical; 2= exponential; 3 = Gaussian; 4 = power. | +| power | The power used in the averaging function if *avetype* is set to 4. | +| ldrand | The leading dimension of the *randfield* array. (Note that the FORTRAN array indicial convention is used.) | +| nreal | The number of realisations to be generated. | +| randfield | On exit, this array contains stochastic hydraulic property fields. | + +### Notes + +#### Speed of Execution + +Use of the spherical and power averaging functions is much faster than that of other functions. This is because the integration area is smaller for these functions. Nevertheless, repeated spatial averaging over a 3D grid is a slow and repetitive process; expect large execution times. + +Experience shows that execution of the *fieldgen3d_sva()* spatial averaging algorithm is much faster if the model-interface library is compiled with the compiler’s optimiser turned on than if it is compiled in “debug” mode. + +#### Memory + +The *randfield* array must hold all stochastic fields at once. Another array of the same dimensions must be allocated internally by *fieldgen3d_sva()* to hold uncorrelated random numbers that are integrated to calculate these fields. The memory requirements of *fieldgen3d_sva()* may thus become heavy. In some circumstances it may be wise for a user to call *fieldgen3d_sva()* to generate a few stochastic fields at a time, and then store these fields in a file after each call. If this is done, the random number generation process is automatically advanced so that the same fields are not re-generated on subsequent calls to *fieldgen3d_sva()* (unless a user calls function *initialize_randgen()* to re-initialize the random number seed). + +#### Log-Transformation + +If *transtype* is set to 1, then hydraulic property fields, and the user-supplied variables that govern them, pertain to the log (to base10) of hydraulic property values. These are used to multiply the mean hydraulic property field supplied by the calling program. The resulting fields are then anti-logged before being returned through the *randfield* array. + +#### Active and Inactive Cells + +Random hydraulic properties are assigned only to cells that are designated as active. Additionally, random number averaging is performed only over active cells. *randfield* values that are ascribed to inactive cells on entry to function *fieldgen3d_sva()* are left unaltered by this function. + +# Driver Programs + +## Introduction + +A number of “driver programs” have been written to test the integrity of *model_interface* module functions. The functionality of these driver programs is primitive; they are written in an ad-hoc manner, and accomplish very little. Nevertheless, their workings are explained herein in case they prove useful to someone other than the author. + +Note the following. + +1. Because these driver programs are written in FORTRAN, they do not test C or Python interoperability of *model_interface* functions. However they allow testing of *model_interface* function algorithms, as well as their responses to error conditions. With small modifications to these driver programs, the range and nature of this testing can be easily extended. + +2. It is expected that small modifications will be made to these driver programs on a regular basis. Furthermore, more of them will be written as more *model_interface* module functions are developed. Hence documentation provided in this chapter is likely to become quickly outdated. + +## DRIVER1 + +DRIVER1 calls the following *model_interface* module functions: + +1. *inquire_modflow_binary_file_specs()* + +2. *retrieve_error_message()* + +Typical prompts and responses are as follows: + +A simulation code is required. + +(1=mf; 21=mfusg_s; 22=mfusg_us; 31=mf6_dis; 32=mfusg_disv; 33=mfusg_disu) + +Enter simulation code: ***22*** + +Enter name of MODFLOW binary output file (\ if no more): ***GWHv4TR011.hds*** + +Enter file type code (1=state; 2=flow): ***1*** + +Enter name for file details file (\ if none): ***temp1.dat*** + +Calling function inquire_modflow_binary_file_specs... + +IFAIL = 0 + +FILE TYPE = dependent-variable + +PRECISION = single + +Number of arrays = 1820 + +Number of output times = 91 + +Enter name of MODFLOW binary output file (\ if no more): ***GWHv4TR011.cbb*** + +Enter file type code (1=state; 2=flow): ***2*** + +Enter name for file details file (\ if none): ***temp2.dat*** + +Calling function inquire_modflow_binary_file_specs... + +IFAIL = 0 + +FILE TYPE = cell-by-cell flow term + +PRECISION = single + +Number of arrays = 727 + +Number of output times = 91 + +Enter name of MODFLOW binary output file (\ if no more): ***\*** + +If an error condition is encountered, DRIVER1 reports the error message to the screen. It then prompts for another MODFLOW binary output file to read. If no error condition is encountered, it records some details of the file that it has just read to the screen. See above. + +## DRIVER2 + +DRIVER2 calls the following *model_interface* module functions: + +- *install_structured_grid()* + +- *uninstall_structured_grid()* + +- *retrieve_error_message()* + +- *free_all_memory()* + +- *interp_from_structured_grid()* + +DRIVER2 commences execution by testing installation of structured grid specifications. It asks: + +Enter name of a GW Utils grid spec file (\ if no more): + +Provide the name of a grid specification file in response to this prompt. See Part A of the manual for the PEST groundwater utilities for specifications of this file type. + +Once DRIVER2 has read the file it asks: + +How many layers in this grid? + +Supply a name for this grid: + +After having obtained this information, DRIVER2 installs the grid specifications. Any error messages are reported to the screen. + +A response of \ to the first of the above prompts causes DRIVER2 to move into the second phase of its operations. During this phase it uninstalls user-specified structured grid specifications. It asks: + +Enter name of grid to uninstall (\ if no more): + +Provide the name of previously installed grid specifications. DRIVER2 then calls function *uninstall_structured_grid()* in order to uninstall the pertinent structured grid specifications. It then informs the user of its success or failure in doing this. An error message accompanies failure. + +If the response to the above prompt is \, DRIVER2 provides the opportunity to return to phase 1 of its operations, that is the installation of more grids. It asks: + +Install more grids? \[y/n\]: + +A response of “n” takes the user into phase 3 of DRIVER2’s operations. This is where it tests function *interp_from_structured_grid()*. It asks the following series of questions. + +Enter name of point coordinates file (\ if no more): + +Enter name of MODFLOW binary dep. var. output file: + +Enter grid name to which it pertains: + +Enter number of output times in this file: + +Enter precision (1=single; 2=double): + +Enter HDRY/HNOFLO threshold: + +Enter header text of interest: + +Enter name for output file: + +A “points coordinates file” contains four columns of data, with no header. The first column contains point identifiers. These must be 20 spaces or less in length. (Note that this implies no name length restrictions for other programs which call *model_interface* model functions.) The next two columns contain real-world eastings and northings, while the last column contains point layer numbers. + +Once it has enough information to call *interp_from_structured_grid()*, DRIVER2 calls this function. If the function call was unsuccessful, this is reported to the screen, together with an error message. Otherwise, DRIVER2 records the values of spatially-interpolated dependent variables in the user-nominated output file and reports to the screen the number of separate simulation times for which interpolation took place. + +The spatial interpolation cycle is then re-commenced. DRIVER2 repeats the first of the above prompts. If the response is \ (implying that no more testing of spatial interpolation functionality is required), DRIVER2 tidies up. It does this by calling function *free_all_memory()*. It then ceases execution. + +## DRIVER3 + +DRIVER3 calls the following *model_interface* module functions: + +- *install_structured_grid()* + +- *retrieve_error_message()* + +- *free_all_memory()* + +- *interp_from_structured_grid()* + +- *interp_to_obstime()* + +Upon commencement of execution, DRIVER3 installs the specifications of a structured MODFLOW grid. Its prompts are: + +Enter name of a GW Utils grid spec file: + +How many layers in this grid? + +Supply a name for this grid: + +Next DRIVER3 reads point data from a “point data file”. As explained in documentation of DRIVER2, this file contains four columns of data, with no header. The first column must contain point identifiers. These must be 20 characters or less in length. (Note that this implies no name length restrictions for programs which call *model_interface* model functions.) The next two columns must contain real-world eastings and northings, while the last column must contain model layer numbers with which points are associated. The prompt is: + +Enter name of point data file: + +DRIVER3’s next task is to read a MODFLOW-generated binary dependent-variable file. So it prompts for the name of this file, as well as for a few details pertaining to this file (see documentation of DRIVER2 for further details). The prompts are: + +Enter name of MODFLOW binary dep. var. output file: + +Enter grid name to which it pertains: + +Enter number of output times in this file: + +Enter precision (1=single; 2=double): + +Enter HDRY/HNOFLO threshold: + +Enter header text of interest: + +DRIVER3 next calls function *interp_from_structured_grid()* to undertake spatial interpolation of the contents of this file to the points whose coordinates were provided in the point data file. + +DRIVER3 then prompts for the name of a “post-spatial-interpolation” output file. This is the same file that is written by DRIVER2. It contains model-generated values of dependent variables that have undergone spatial interpolation to the locations of user-supplied points. The prompt is: + +Enter name for post-spatial interpolation output file: + +Next DRIVER3 prompts for the name of an “observation time file”. This file should contain two data columns with no headers. The first column should contain point names (20 characters or less in length). These should correspond to point names in the previously-supplied point data file. However these points do not need to be supplied in the same order. Points can be omitted if desired. Furthermore, points that are not featured in the point data file can be supplied in the observation time file if desired; this tests *interp_to_obstime()*’s ability to ignore these points. The second column should associate an arbitrary time with each point. These times can be in arbitrary order. + +DRIVER3 next asks for time-extrapolation options: + +Enter time-extrapolation option (L/C): + +Enter extrapolation time limit: + +Finally DRIVER3 asks for the name of another file that it will write: + +Enter name for post-time-interpolation output file: + +This file contains time-interpolated values to point observation times. + +## DRIVER4 + +DRIVER4 tests the following *model_interface* module functions: + +- *install_mf6_grid_from_file()* + +- *uninstall_mf6_grid()* + +- *calc_mf6_interp_factors()* + +- *interp_from_mf6_depvar_file()* + +- *retrieve_error_message()* + +- *interp_to_obstime()* + +- *free_all_memory()* + +Upon commencement of execution, DRIVER4 provides the user with a suite of choices. Its screen display is as follows: + +What do you want to do? + +To end this program - enter 0 + +To install a set of MF6 grid specs - enter 1 + +To uninstall a set of MF6 grid specs - enter 2 + +To read a set of point coordinates - enter 3 + +To write an interpolation factor file - enter 4 + +To interpolate from an interp factor file - enter 5 + +To time-interpolate after spatial interpolation - enter 6 + +Enter your choice: + +Some of the tasks that are performed by DRIVER4 are the same as those performed by DRIVER3. However, in contrast to DRIVER3, DRIVER4 uses *model_interface* module functionality to perform spatial interpolation from MODFLOW 6 grids to a set of points. Spatial interpolation is achieved through a two-step process. First, interpolation factors that are specific to a particular MODFLOW 6 DIS or DISV grid are calculated by function *calc_mf6_interp_factors()*. These factors are then applied to the contents of a binary dependent-variable file that is written by MODFLOW 6 using function *interp_from_mf6_depvar_file()*. If desired, temporal interpolation to user-specified times that are unique to each observation can also be carried out. (This mimics interpolation to the times at which measurements are made.) + +Some of the details of DRIVER4 functionality are now described. + +When implementing any of the following options, DRIVER4 reports its activities to the screen. If an error condition is encountered during any call to a *model_interface* function, DRIVER4 calls function *retrieve_error_message()* and then reports the error message to the screen. Hence DRIVER4 can be used to test *model_interface* function error recognition and reporting at the same time as it tests its functionality. + +### Option 1 + +If the first of the above options is selected, DRIVER4 asks: + +Enter name of MF6 GRB file: + +Enter name for grid: + +Once it has received answers to these questions, DRIVER4 calls function *install_mf6_grid_from_file()* to install a set of MODFLOW 6 grid specifications. It associates this set of specifications with a user-supplied name. + +### Option 2 + +DRIVER4 prompts for the name of the set of MODFLOW 6 grid specifications that it must uninstall. + +Enter name of MF6 grid to uninstall: + +Once the name of a previously installed set of grid specifications has been provided, DRIVER4 uninstalls this set of specifications by calling function *uninstall_mf6_grid()*. + +### Option 3 + +Invocation of this option causes MODFLOW 6 to read a set of point coordinates. These are points to which spatial interpolation will take place through invocation of other DRIVER4 options. + +DRIVER4 asks: + +Enter name of points coordinate file: + +The format of a points coordinates file is straightforward. The following figure shows the first part of one such file. The file must have four columns, these being space- or comma-delimited. The first column must contain point names (20 characters or less in length). Other columns contain point easting, northing and layer numbers respectively. (Note that functions of the *model_interface* module do not store point names. Hence the 20 character limit does not apply to them.) + +
BH13,711867,7459940,1
BH14a,719593,7461005,1
BH14b,719586,7461003,1
BH15,722145,7461300,1
BH16d,724055,7462377,1
BH16s,724058,7462378,1
BH17d,725698.126,7463790.821,1
BH17s,725698.822,7463790.494,1
BH18d,725308,7463169,1
BH18s,725309,7463167,1
etc
+
+If option 3 is invoked again, then any previously-read set of points is emptied from DRIVER4’s memory and over-written by a new set of points. + +### Option 4 + +This is where DRIVER4 calls *function calc_mf6_interp_factors()* to calculate spatial interpolation factors. It asks: + +Enter name of MF6 grid to interpolate from: + +Enter name of factor file to write: + +Is this a binary or text file? \[0/1\]: + +Enter name of bln file to write: + +DRIVER4 will accept a blank response to the last of the above prompts. In this case it does not request that function *calc_mf6_interp_factors()* writes a SURFER BLN file whose vertices are the centres of model cells that surround interpolation points. + +After it has called function *calc_mf6_interp_factors()*, DRIVER 4 reports the “interpolation success rate” to the screen. Interpolation is unsuccessful if a point is not enclosed by a triangle or quadrilateral whose vertices are model cell centres. The latter are the points from which spatial interpolation takes place. + +### Option 5 + +If this option is invoked, DRIVER4 conducts spatial interpolation from a MODFLOW 6 dependent-variable file to the latest set of points that it has read. It asks for the name of the interpolation factor file that it must write. Note that this file may have been written on a previous DRIVER4 run. + +DRIVER4’s prompts are: + +Enter name of MODFLOW 6 dependent variable file: + +How many times are represented in this file? + +Enter text array identifier: + +Enter inactive threshold: + +Reapportion interp factors for dry/inact cells? \[n/y = 0/1\]: + +Enter value indicating no interpolation: + +Enter name of interpolation factor file: + +Is this a binary or text file? \[0/1\]: + +These prompts are all easily related to *interp_from_mf6_depvar_file()* arguments. + +If spatial interpolation is successful, DRIVER4 prompts for the name of a file in which it can record the outcomes of spatial dependent-variable interpolation. The prompt is: + +Enter file to record interpolated values: + +This file has three columns. The first lists point identifiers, the second lists model output times, and the third lists spatially interpolated dependent variable values. All of the model output times and interpolated values are listed for the first point, then for the second point, and so on. + +### Option 6 + +If this option is invoked, DRIVER 4 undertakes temporal interpolation of values which have previously been spatially interpolated to the locations of user-supplied points. It calls model interface function function *interp_to_obstime()* to perform this task. + +Its prompts are: + +Enter name of observation time file: + +Enter inactive threshold in spatially-interpolated data: + +Enter time-extrapolation option (L/C): + +Enter extrapolation time limit: + +Enter value indicating no time interpolation: + +The figure below shows an example of an “observation time file” that is requested through the first of the above prompts. + +
BH3 42594.5000
WB15HD1S002 42601.5000
WB15HD1S002 42608.5000
WB15HD1S002 42615.5000
WB15HD1S002 42622.5000
WB15HD1S002 42629.5000
WB15HD1S002 42636.5000
WB15HD1S002 42643.5000
BH5 34521.9
WB18HD1N0002 43315.5000
WB19HD1N0007 44428.5000
WB20H1SW0001 44358.5000
Etc
+
+An observation time file must possess two columns of data. The first column contains point names while the second column contains times. “Times” are rea numbers that are, in fact, elapsed times since a certain reference time. These elapsed times must have the same reference time, and employ the same units, as the simulator does. Points and times can be supplied in any order. If a point is not among those to which spatial interpolation previously took place, then the interpolated value of the dependent variable to that point will be the user-supplied value for “no time interpolation” (see the last of the above DRIVER4 prompts). + +Once time-interpolation has taken place, and if an error condition is not encountered, DRIVER4 asks: + +Enter name for post-time-interpolation output file: + +DRIVER4 records a file in which points are listed in the order that they were supplied to it through option 3. The times to which temporal interpolation was requested for each point are recorded in the second column of this file. These are recorded for the first point, then for the second point, and so on. The final column contains time-interpolated dependent-variable values to points and times referenced in the first two columns. + +## DRIVER5 + +DRIVER5 tests the following *model_interface* module functions: + +- *inquire_modflow_binary_file_specs()* + +- *extract_flows_from_cbc_file()* + +- *interp_to_obstime()* + +- *free_all_memory()* + +- *retrieve_error_message()* + +DRIVER5 commences execution by prompting for the number of cells in the model grid pertaining to the model whose cell-by-cell flow term file it will shortly read. It asks: + +Enter number of cells in model: + +It then asks for the type of model that it is dealing with: + +A simulator code is required. + +(1=mf; 21=mfusg_s; 22=mfusg_us; 31=mf6_dis; 32=mfusg_disv; 33=mfusg_disu) + +Enter simulation code: + +As is apparent from the above prompt, this is the value that DRIVER5 assigns to the *isim* argument of function *extract_flows_from_cbc_file()*. + +DRIVER5 then asks for the name of a file that contains an integer zonation array. This is a simple file that must contain two columns of integers (with no header provided for each column). The first column must contain model node numbers, while the second column must contain the zone number that is associated with respective nodes. The order in which these pairs of values are supplied does not matter. Omitted cells are awarded zone numbers of zero. + +DRIVER5 now asks for the *nzone* value that it must provide to function *extract_flows_from_cbc_file()*. The prompt is: + +Enter a number that equals or exceeds number of separate zones: + +Next it asks for the MODFLOW-generated cell-by-cell flow term file that it must read: + +Enter name of model-generated cbc flow term file to read: + +Before calling *extract_flows_from_cbc_file()* DRIVER5 calls function *inquire_modflow_binary_file_specs()*. It prompts for the name of the file in which the latter function should record the headers to arrays and tables that if finds in the cell-by-cell flow term file: + +Enter name of file to record contents of this file: + +DRIVER5 then instructs function *inquire_modflow_binary_file_specs()* to reads the cell-by-cell flow term file and report its findings to the user-nominated file. It also writes a short report to the screen. For example: + +\- calling function inquire_modflow_binary_file_specs()... + +\- function call successful + +FILE TYPE = cell-by-cell flow term + +PRECISION = single + +Number of arrays in file = 10 + +Number of output times in file = 1 + +DRIVER5 now prepares to call function *extract_flows_from_cbc_file()*. It asks for the text identifier of the flow type that it must read. This is the *flowtype* argument of function *extract_flows_from_cbc_file()*. The prompt is: + +Enter text that denotes flow type of interest: + +It then calls function *extract_flows_from_cbc_file()* to accumulate flow terms of this type. It records its success (or otherwise) in doing this to the screen. If *extract_flows_from_cbc_file()* was successful, DRIVER5 also lists (in order) the zone numbers that *extract_flows_from_cbc_file()* encountered in the integer zonation array. For example: + +\- calling function extract_flows_from_cbc_file()... + +\- function call successful + +NPROCTIME = 1 + +NUMZONE = 5 + +Zone numbers:- + +2 + +4 + +6 + +8 + +10 + +DRIVER5 also prompts for the name of a file to which it can report zone-accumulated flows for each simulation time that *extract_flows_from_cbc_file()* encountered in the cell-by-cell flow term file that it read. The prompt is: + +Enter name of flow-in-zone file to write: + +DRIVER5 now asks whether the user would like these accumulated flows to undergo interpolation to times of his/her choosing. It asks: + +Undertake time interpolation? (y/n): + +Then, if the answer is “y”: + +Enter name of observation time file: + +The figure below shows an example of such a file: + +
zone1 100.0
zone1 250.0
zone1 429.0
zone2 50.0
zone3 90.0
etc
+
+The file should have two columns, neither of which should possess a header. The first column should list text identifiers for a number of zones. The first four letters of each such zone identifier should be “zone”; the zone number must follow. (Note that if this protocol is not followed for a particular zone, then DRIVER5 will define an unidentified zone. The same will occur if a zone number supplied in this file is not featured in the previously supplied integer zonation array.) + +Simulation times should appear in the second column of the above file. Note the following: + +- zones and times can be provided in any order; + +- zones that feature in the previously-supplied integer zonation array can be omitted if desired; + +DRIVER5 next asks a few questions pertaining to temporal extrapolation: + +Enter time-extrapolation option (L/C): + +In the above prompt “L” pertains to “linear” and “C” pertains to “constant”. + +Enter extrapolation time limit: + +and then: + +Enter value indicating no time interpolation: + +DRIVER5 then calls function *interp_to_obstime()* to undertake the necessary time-interpolation. If there are no problems, it prompts for the name of a file to which it can report the outcomes of the time-interpolation process: + +Enter name for post-time-interpolation output file: + +This file records time-interpolated values of zonal flows at user-nominated, zone-specific, interpolation times. + +## DRIVER6 + +DRIVER6 tests the following *model_interface* module functions: + +- *calc_kriging_factors_2d()* + +- *krige_using_file()* + +- *retrieve_error_message()* + +DRIVER6 commences execution by asking for the name of a MODFLOW grid specification file. This type of file is described in Part A of the manual for the PEST Groundwater Utilities. The prompt is: + +Enter name of MF grid spec file: + +DRIVER6 instructs function *calc_kriging_factors_2d()* to interpolate to all cells of this model grid except for those which are assigned a zone value of zero (see below). + +DRIVER6 next prompts for a series of files. These files must contain *ncol* × *nrow* numbers, where *ncol* and *nrow* are the number of columns and rows in the model grid. These numbers are best recorded one to a line in files supplied to DRIVER6. Ordering of numbers must be such that columns vary fastest, and then rows. + +The prompts are: + +Enter name of variogram a value array file: + +Enter name of mean array file: + +Enter name of anisotropy array file: + +Enter name of anis. bearing array file: + +Enter name of zone array file: + +Note the following: + +- Variogram “a” values must all be greater than zero. + +- Mean values must be greater than zero if log-interpolation is required (see below). + +- Anisotropies must be greater than zero. Anisotropy specifies the ratio of the variogram range in the bearing direction (see below) to the variogram range in the orthogonal direction. + +- The bearing of anisotropy must lie between -360 degrees and 360 degrees. It is measured in a clockwise direction from north. + +- Zone numbers are integers. No spatial interpolation takes place to target elements whose zone values are zero. + +DRIVER6’s next prompt is: + +Enter name of pilot points file: + +A pilot points file is shown below. Its first column contains pilot point names (ignored by DRIVER6) while its second and third columns contain pilot point eastings and northings. The fourth column specifies zone numbers, while variable values (for example hydraulic property values) are reported in the fifth column. + +
point1 10573.878756612 20008.455612214 1 33.09472898
point2 11670.518421482 20391.618868614 1 39.00021237
point3 12456.663723407 21052.245172753 2 52.13744511
point4 13328.69044487 21309.889431367 2 61.64696319
point5 10917.404434764 19294.979203744 1 37.07478991
point6 11518.57437153 19915.967929634 1 57.52001219
point7 12403.813619076 20391.618868614 2 42.48673892
point8 13222.990236208 20682.294442435 2 41.38308306
point9 11564.81821282 18971.272314716 1 46.13677594
point10 12370.782303869 19519.592147151 1 55.91406942
point11 13090.86497538 20127.368346959 2 55.67195376
point12 14055.379379423 20266.099870828 2 51.48889079
+
+DRIVER6 next requests the following information: + +Enter variogram type (1=spher;2=exp;3=gaus;4=pow): + +Enter search radius: + +Enter minimum number of points to use in kriging: + +Enter maximum number of points to use in kriging: + +Enter kriging type (0=simple; 1=ordinary): + +Responses to these questions are obvious. + +DRIVER6 then asks for the name and type of the factor file that it must write: + +Enter name for factor file: + +Is this a text or binary file? (t/b): + +DRIVER6 then calls function *calc_kriging_factors_2d()* to fill the factor file. If an error condition is encountered, it reports the error message to the screen and then ceases execution. Otherwise, it reports to the screen the number of interpolation factors that it has calculated. This is equal to the number of cells within the model domain whose zone value is non-zero. + +Next, DRIVER6 prepares to undertake spatial interpolation using the kriging factors that it has just calculated. It interpolates the values that it read from the final column of the pilot points file to the cells of the model grid. It asks for a few specifications of the interpolation process. + +Interpolate in natural or log domain? (n/l): + +Enter number indicating no interpolation to target array: + +DRIVER6 then fills a target array (with *ncol* × *nrow* elements) with a value that is equal to the number supplied in response to the second of the above prompts. These values will shortly be over-written, except for target cells whose zone value is zero. + +DRIVER6 then calls function *krige_using_file()* to undertake spatial interpolation. Unless an error condition is encountered, DRIVER6 writes the number of points to which spatial interpolation has taken place to the screen. + +DRIVER6 stores interpolated values as a long list of numbers. Because they are in the correct order, they can be read as a MODFLOW-compatible real array. It asks: + +Enter filename for real array storage: + +It then writes this file and ceases execution. + +## DRIVER7 + +DRIVER7 tests the following *model_interface* module functions: + +- *calc_kriging_factors_auto_2d()* + +- *krige_using_file()* + +- *retrieve_error_message()* + +Tasks that are performed by DRIVER7 are almost the same as those performed by DRIVER6. However DRIVER6 asks a few less questions than DRIVER7 because it calls function *calc_kriging_factors_auto_2d()* instead of function *calc_kriging_factors_2d()*. As is described in documentation of these functions, the latter *model_interface* function has fewer arguments than the former function, because it requires less information. Hence fewer user inputs to the driving program are required. + +Typical prompts and responses are as follows. + +Enter name of MF grid spec file: ***rectmodel.spc*** + +\- file rectmodel.spc read ok. + +Enter name of mean array file: ***mean.ref*** + +\- file mean.ref read ok. + +Enter name of anisotropy array file: ***anis.ref*** + +\- file anis.ref read ok. + +Enter name of anis. bearing array file: ***bearing.ref*** + +\- file bearing.ref read ok. + +Enter name of zone array file: ***zones.inf*** + +\- file zones.inf read ok. + +Enter name of pilot points file: ***pp.dat*** + +\- file pp.dat read ok. + +Enter kriging type (0=simple; 1=ordinary): ***0*** + +Enter name for factor file: factors1.dat + +Is this a text or binary file? (t/b): ***t*** + +Calling calc_kriging_factors_2d().... + +Interpolation factors calculated for 19895 points. + +Now undertaking spatial interpolation. + +Interpolate in natural or log domain? (n/l): ***n*** + +Enter number indicating no interpolation to target array: ***1.1e30*** + +Calling krige_using_file()... + +Interpolation undertaken to 19895 points. + +Enter filename for real array storage: ***interpolated1.ref*** + +\- file interpolated1.ref written ok. + +## DRIVER8 + +DRIVER8 calls the following *model_interface* library functions: + +- *calc_kriging_factors_auto_3d()* + +- *krige_using_file()* + +- *retrieve_error_message()* + +Unlike most of the other drivers that are documented herein, some *model_interface* library testing data are hardwired into DRIVER8’s code. This lessens the load of preparing input files for this driver – a task that can become tedious in three dimensions, and that would require programming. Hence the necessary programming is built into DRIVER8 itself. (The code is easily modified.) + +DRIVER8 commences execution by prompting for the name of a structured-grid MODFLOW model specification file. It prompts: + +Enter name of MF grid spec file: + +It then asks how many model layers the model possesses. + +Enter number of model layers: + +As is recorded in its documentation, function *calc_kriging_factors_auto_3d()* allows zone-based variogram parameterization. DRIVER8 allows a user to choose between two pre-specified zonation. They are as follows. + +- *Option 1.* Cells in model rows 15 and less are assigned to zone 1, while remaining cells are assigned to zone 2. However in layers 1 to 5, a zone number of 0 is assigned to cells whose row number exceeds 20 and whose column number exceeds 21. + +- *Option 2.* All cells in the model are ascribed a zone number of 1. + +Next DRIVER8 prompts for the name of a 3D pilot points file. The first part of such a file is shown below. Each line contains data pertaining to a specific source point. Entries are as follows: + +- name (this is ignored by DRIVER8); + +- easting; + +- northing; + +- elevation; + +- zone number; + +- hydraulic property values which will be interpolated to target point locations. + +
point1 10087.250120029 19989.131931017 -10.0 1 33.09472898
point2 10331.461539042 20025.101054748 -10.0 1 39.00021237
point3 10477.231145741 20155.725767244 -10.0 1 52.13744511
point4 10658.969876169 20303.388485717 -30.0 1 61.64696319
point5 10174.333261692 19860.400330297 -50.0 1 37.07478991
point6 10380.6824452 19953.162807287 -10.0 1 57.52001219
point7 10587.031628708 19955.055919062 -40.0 2 42.48673892
point8 10812.311929969 20017.528607647 -10.0 2 41.38308306
point9 10318.209756615 19582.112899328 -110.0 2 46.13677594
point10 10435.582686684 19756.279182656 -120.0 2 55.91406942
point11 10604.069634686 19807.393200589 -130.0 2 55.67195376
point12 10869.105283228 19913.407460005 -140.0 2 51.48889079
point13 10087.250120029 19989.131931017 -200.0 1 82.48127804
point14 10331.461539042 20025.101054748 -200.0 1 67.7816446
point15 10477.231145741 20155.725767244 -200.0 1 76.04121403
point16 10658.969876169 20303.388485717 -200.0 1 80.07426233
+
+Note that while DRIVER8 reads the locations of source points, it does not read the locations of target points, for these are the cell centres of the model grid whose specifications it previously read. + +DRIVER8’s next prompt is for the kriging type: + +Enter kriging type (0=simple; 1=ordinary): + +Next DRIVER8 issues the following series or prompts for each zone into which the model domain is subdivided (except for zone 0). Example responses are also shown. Note that the mean value is not used unless simple kriging is requested. + +Enter variogram type (1=spher;2=exp;3=gaus;4=pow): ***2*** + +Enter ahmax, ahmin, avert: ***500,200,50*** + +Enter bearing, dip, rake: ***60,30,0*** + +Enter mean value: ***2.0*** + +DRIVER8 next asks for search radii in each of the orthogonal directions that define the ellipse of anisotropy. The prompt is: + +Enter srhmax, srhmin, srvert: + +Then it prompts for the maximum allowed, and minimum acceptable, source points to use in interpolation to any target point: + +Enter maxpts, minpts: + +Next DRIVER8 asks for the name of the interpolation factor file that it will ask *calc_kriging_factors_3d()* to write. It also inquires whether this should be a text or binary file. + +Enter name for factor file: + +Is this a text or binary file? (t/b): + +Once it has received all of this information, DRIVER8 calls *calc_kriging_factors_3d()*. If there are any problems, DRIVER8 writes to the screen the error message that is provided to it by *calc_kriging_factors_3d()*. Otherwise it writes to the screen the number of target points for which kriging factors have been calculated. + +Next DRIVER8 gets ready to call the *krige_using_file()* function. It asks: + +Interpolate in natural or log domain? (n/l): + +and then for the value that it should assign to target points to which no interpolation takes place (because their zone values are zero). + +Enter number indicating no interpolation to target array: + +Having received this information, DRIVER8 calls function *krige_using_file()*. If all goes well, it reports to the screen the number of points to which interpolation has taken place. (This should be the same as the number of points for which kriging factors were calculated.) Finally, it asks for the name of a file in which to store interpolated values at target points: + +Enter filename for target array storage: + +The file which DRIVER8 writes is readable by the MF2VTK1 program that is supplied with the PEST Groundwater Utilities. Kriged hydraulic property fields are therefore easily displayed in PARAVIEW. + +## DRIVER9 + +DRIVER9 calls the following *model_interface* library functions: + +- *build_covar_matrix_2d()* + +- *retrieve_error_message()* + +DRIVER9 commences execution by prompting for the name of a “pilot points statistical specification file”. It also asks whether this file has a header. Prompts are: + +Enter name of pilot points statistical specification file: + +Skip header line? (y/n): + +The format of a pilot points statistical specification file is described in documentation of program PPCOV_SVA supplied with the PEST Groundwater Utility Suite. (Function *build_covar_matrix_2d()* performs a similar role to this program.) The first part of such a file is depicted in the following figure. The (optional) column headers that appear in this figure show the contents and ordering of data columns that comprise this type of file. + +
point x y zone nugget sill a hanis bearing
ppt1 35.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt2 95.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt3 155.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt4 215.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt5 275.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt6 335.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt7 395.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt8 455.0 765.0 1 0.2 1.0 40.0 2.0 90.0
ppt9 35.0 705.0 1 0.2 1.0 40.0 2.0 90.0
ppt10 95.0 705.0 1 0.2 1.0 40.0 2.0 45.0
ppt11 155.0 705.0 1 0.2 1.0 40.0 2.0 45.0
ppt12 215.0 705.0 1 0.2 0.5 50.0 2.0 45.0
ppt13 275.0 705.0 1 0.2 0.5 50.0 2.0 45.0
ppt14 335.0 705.0 1 0.2 0.5 50.0 2.0 45.0
ppt15 395.0 705.0 1 0.2 0.5 50.0 2.0 45.0
ppt16 455.0 705.0 1 0.2 0.5 50.0 1.5 45.0
ppt17 35.0 645.0 1 0.2 0.5 50.0 1.5 45.0
etc
+
+DRIVER9 next asks for the variogram type on which it must base it covariance calculations. The prompt is: + +Enter variogram type (1:spher,2:exp,3:gauss,4:pow): + +Its final prompt is for the name of the file in which it should store the covariance matrix that it calculates: + +Enter name of file to store covariance matrix: + +The file is recorded in PEST matrix file format; see Part 2 of the PEST manual for details. + +## DRIVER10 + +DRIVER10 calls the following *model_interface* library functions: + +- *build_covar_matrix_3d()* + +- *retrieve_error_message()* + +DRIVER10 commences execution by prompting for the name of a “three-dimensional pilot points statistical specification file”. It also asks whether this file has a header. Prompts are: + +Enter name of 3D pilot points statistical specs file: + +Skip header line? (y/n): + +The format of a three-dimensional pilot points statistical specification file is described in documentation of program PPCOV3D_SVA supplied with the PEST Groundwater Utility Suite. (Function *build_covar_matrix_3d()* performs a similar role to this program.) The first part of such a file is depicted in the following figure. The (optional) column headers that appear in this figure show the contents and ordering of data columns that comprise this type of file. + +
point x y z zone nugget sill ahmax ahmin avert bearing dip rake
ppt1 35.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt2 95.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt3 155.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt4 215.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt5 275.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt6 335.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt7 395.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt8 455.0 765.0 0.0 2 0.05 0.50 50.0 40.0 10.0 90.0 10.0 0.0
ppt9 35.0 705.0 0.0 2 0.05 0.50 50.0 40.0 12.0 120.0 10.0 0.0
ppt10 95.0 705.0 0.0 1 0.05 0.50 50.0 40.0 13.0 120.0 10.0 0.0
ppt11 155.0 705.0 0.0 2 0.05 0.50 50.0 40.0 15.0 120.0 10.0 0.0
ppt12 215.0 705.0 0.0 2 0.05 0.50 50.0 40.0 20.0 120.0 10.0 0.0
ppt13 275.0 705.0 0.0 2 0.05 0.50 50.0 40.0 20.0 120.0 10.0 0.0
etc
+
+DRIVER10 next asks for the variogram type on which it must base it covariance calculations. The prompt is: + +Enter variogram type (1:spher,2:exp,3:gauss,4:pow): + +Its final prompt is for the name of the file in which it should store the covariance matrix that it calculates: + +Enter name of file to store covariance matrix: + +The file is recorded in PEST matrix file format; see Part 2 of the PEST manual for details. + +## DRIVER11 + +DRIVER11 calls the following *model_interface* library functions: + +- *calc_structural_overlay_factors()* + +- *interpolate_blend_using_file()* + +- *retrieve_error_message()* + +DRIVER11 commences execution by prompting for the name of a file which lists the coordinates of the cell centres of a model grid. This file should contain grid cell *x*, *y* and *z* coordinates. It must also contain an extra column comprised of integers that designate the activity status of each model cell. Target cells whose activity status is zero are not affected by the actions of functions *calc_structural_overlay_factors ()* and *interpolate_blend_using_file()*. Note that DRIVER11 does not use the cell *z* coordinates that it reads, for neither of the above functions employ vertical target coordinates in their calculations. + +DRIVER11’s first prompts are: + +Enter name of file containing grid cell centre coordinates: + +Skip header line? (y/n): + +The first part of a DRIVER11-readable file is depicted in the following figure. + +
X-coord Y-coord Z-coord active
25.000 9975.000 230.000 1
75.000 9975.000 230.000 1
125.000 9975.000 230.000 1
175.000 9975.000 230.000 1
225.000 9975.000 230.000 1
275.000 9975.000 230.000 1
325.000 9975.000 230.000 1
375.000 9975.000 230.000 1
425.000 9975.000 230.000 1
475.000 9975.000 230.000 1
525.000 9975.000 230.000 1
575.000 9975.000 230.000 1
etc
+
+Next DRIVER11 asks for a value with which to pre-populate a target model property array. The prompt is: + +Enter background value for grid cell properties: + +DRIVER11 then asks for the name of a file that contains details of the structural features that are overlain on the model grid. The prompt is: + +Enter name of file containing structural overlay details: + +Skip header line? (y/n): + +Such a file is shown in the figure below. + +
point easting northing struc_id cw aa kh
1 3223.4994 9153.5041 1 70.0 100.0 100.0
2 2891.9991 7145.0021 1 80.0 110.0 200.0
3 2599.4988 4804.9998 1 90.0 120.0 300.0
4 2170.4984 2874.4978 1 100.0 130.0 400.0
5 1390.4976 1450.9964 1 110.0 140.0 500.0
6 1000.4972 6365.0013 2 150.0 150.0 600.0
7 1760.9980 4492.9994 2 160.0 160.0 700.0
8 3437.9996 2893.9978 2 170.0 170.0 800.0
9 5154.0014 1606.9966 2 180.0 180.0 900.0
+
+Columns in the structural overlay details file are as follows: + +- point name (ignored by DRIVER11); + +- easting; + +- northing; + +- structure identifier (an integer); + +- *conwidth*; + +- *aa*; + +- structural hydraulic property values that will undergo interpolation/extrapolation/blending to the model grid. + +Column headers are optional, as are names that appear in them. + +DRIVER 11 next asks for the structure type: + +Are structures polylinear or polygonal? (L/p): + +and for the inverse power of distance that it should apply when interpolating: + +Enter inv power of distance: + +Next DRIVER11 asks for the name of the factor file that it should ask function *calc_structural_overlay_factors()* to write, and whether this file is formatted or binary. The prompts are: + +Enter name for factor file: + +Is this a text or binary file? (t/b): + +Once the factor file has been written, DRIVER11 turns its attention to using the file in order to perform interpolation/extrapolation/blending. It asks: + +Interpolate in natural or log domain? (n/l): + +Then it calls function *interpolate_blend_using_file()* to perform the blending task. Finally, DRIVER11 prompts for the name of the file that it must write: + +Enter filename for target array storage: + +The file that DRIVER11 writes contains as many entries as there are cells in the model grid. The values ascribed to most of these cells are the same; these are the user-prescribed background value for model grid cells. However values deviate from this background value for cells that are close to structures. + +## DRIVER12 + +DRIVER12 calls the following *model_interface* library functions: + +- *ipd_interpolate_2d()* + +- *retrieve_error_message()* + +Its prompts are very similar to those of DRIVER6. + +DRIVER12 commences execution by prompting for the name of a structured grid specification file. See documentation of the PEST Groundwater Utilities for the structure of this file. The coordinates of a set of target points can be calculated from the contents of this file. These occupy the centres of cells that are defined by the grid whose specifications are provided in the file. The prompt is: + +Enter name of MF grid spec file: + +Next, DRIVER12 prompts for the names of a series of files, each containing as many numbers as there are cells in the model grid. These can be supplied as MODFLOW-compatible real arrays. Alternatively they can be supplied as list of numbers. In the latter case, column indices vary faster than row indices (in accordance with the MODFLOW cell numbering protocol). The prompts are: + +Enter name of anisotropy ratio array file: + +Enter name of anisotropy bearing array file: + +Enter name of inverse power array file: + +Enter name of zone array file: + +Note that anisotropy ratio and bearing, as well as inverse power of distance are real numbers, while zone numbers are integers. + +Next DRIVER12 prompts for the name of a pilot points file: + +Enter name of pilot points file: + +See documentation of DRIVER6 for the format of this file. + +DRIVER12’s next prompts are as follows: + +Interpolate in natural or log domain? \[n/l\]: + +Enter number indicating no interpolation to target array: + +No interpolation takes place to a target point if its zone number is zero. + +Finally, DRIVER12 prompts for the name of the file in which it should record interpolated values. The prompt is: + +Enter filename for real array storage: + +Interpolated values are stored one to a line in MODFLOW cell order. + +## DRIVER13 + +DRIVER13 calls the following *model_interface* library functions: + +- *ipd_interpolate_3d()* + +- *retrieve_error_message()* + +DRIVER13 is a modification of DRIVER8. Because it is so similar, the results of kriging-based spatial interpolation can readily be compared with those of inverse power of distance interpolation. As is described in documentation of DRIVER8, some of the information that DRIVER13 requires is hard-coded in order to mitigate over-complexity of its input dataset. These specifications are easily altered. + +DRIVER13 commences execution by prompting for the name of a structured MODFLOW grid specification file. The prompt is: + +Enter name of MF grid spec file: + +It then builds a model in which all layers are flat, and 20 metres thick. It asks for the number of layers with which this model should be endowed: + +Enter number of model layers: + +DRIVER13 then subdivides the model into zones. Note that function *ipd_interpolate_3d()* does not use zones. DRIVER13 populates arrays that it supplies to this function using specifications which are assigned to these zones. It asks: + +Use option 1 or 2 for zones? + +Option 1 is the use of 3 zones. One of these zones has a value of 0, while the others have values of 1 and 2. Zone 1 occupies all cells in all layers in rows 1 to 15 of the model. Zone 2 occupies the remainder of the model except for a small rectangular prism at its top corner where row numbers are greater than 20, column numbers are greater than 15 and layer numbers range from 1 to 5. + +The second zonation option is assignment of a zone number of 1 to the entirety of the model domain. + +Next DRIVER13 prompts for the name of a 3D pilot points file. This is described in documentation of DRIVER8. Respective columns of this file contain pilot point: + +- names; + +- eastings, northings and elevations; + +- zones numbers; + +- values (which are interpolated to grid cell centres). + +For each zone (except for zone 0) DRIVER13 asks: + +Enter ahmax, ahmin, avert: + +Enter bearing, dip, rake: + +Enter inverse power of distance: + +When providing these numbers, separate them by a comma, space, tab or \. + +Next DRIVER13 asks whether interpolation should take place in the natural or log domain: + +Interpolate in natural or log domain? \[n/l\]: + +and what number it should assign to target points to which no interpolation takes place (these have zone numbers of 0): + +Enter number indicating no interpolation to target array: + +Finally DRIVER13 asks for the name of the file in which it should record interpolated values. These are stored in a format that is readable by the MF2VTK1 program that is supplied with the PEST Groundwater Utility Suite. The prompt is: + +Enter filename for target array storage: + +## DRIVER14 + +### General + +DRIVER14 calls the following *model_interface* library functions: + +- *initialize_randgen()* + +- *fieldgen2d_sva()* + +- *retrieve_error_message()* + +DRIVER14 requires very similar inputs to program FIELDGEN2D_SVA from the PEST Groundwater Utility Suite. Hence running one of these programs is very much like running the other. Much of the following documentation of DRIVER14 is therefore copied from documentation of FIELDGEN2D_SVA. + +### File Types + +DRIVER14 requires two tabular data files. These can be CSV files if you like; the contents of these files can be space or comma-delimited. + +#### 2D Node Specification File + +The first of these file types is referred to as a “2D node specification file”. Its contents pertain to nodes of a model grid. DRIVER14 does not care whether the model grid is structured or unstructured. + +The figure below shows the first few lines of such a node specification file displayed in Microsoft EXCEL. + + + +First few lines of a 2D node specification file. + +DRIVER14 insists that certain protocols be followed by this file. The first line of a 2D node specification file must contain the same headers as those that appear in the above picture. These define the contents of respective columns. The first column must contain model node numbers. DRIVER14 insists that these be numbered sequentially starting from 1. The *x* and *y* coordinates of nodes (normally cell centres) follow in ensuing columns. The next column contains cell areas. The final column specifies whether a cell is active or not. *fieldgen2d_sva()* does not calculate stochastic property field values for inactive cells. + +Note that cell areas are used in the integration procedure through which the spatial averaging function is applied. + +#### 2D Averaging Function Specification File + +The first part of a 2D averaging function specification file is pictured below. In many instances of its use, the contents of this file will have been spatially interpolated to the locations of model grid cell centres. + + + +The first part of a 2D averaging function specification file. + +A 2D averaging function specification file must possess nine columns. Their labels should be exactly as shown in the above figure. (However you can use “variance” instead of “sill” and “a” instead of “aa”.) The contents of these columns are obvious from their labels. As for the 2D node specification file, the first column must feature grid nodes. These must be sequential and start at 1. All grid nodes must be cited in this column whether or not they are active. + +### Using DRIVER14 + +DRIVER14 begins execution by prompting for the names of the above two files. It then reads these files, reporting any errors that it encounters to the screen. + +Enter name of 2D node specifications file: + +Enter name of 2D averaging function specification file: + +Its next prompt is: + +Express stochasticity in natural or log domain? \[n/l\]: + +If your answer to this prompt is “n”, then stochastic fields which are generated by DRIVER14 are added to the mean value field that is supplied in the averaging function specification file. Alternatively, if the response is “L”, then mean cell values are multiplied by the log (to base 10) of DRIVER14-generated stochastic fields. Variances supplied in the averaging function specification file must therefore pertain to the log of the hydraulic property for which stochastic fields are being generated. (Note, however, that this does not apply to the mean.) + +The nature of the moving average function is next requested: + +Is moving ave spherical, exponential, gaussian or power? \[s/x/g/p(..)\]: + +If a power function is desired, then the response to this prompt should be something like: “p(1.0)”. This indicates a power of 1.0. A response of “p(0.5)” indicates a power of 0.5. The power must be greater than zero. A power of 1.0 defines a linear function. Alternatively, respond to the above prompt with “s”, “x” or “g”. + +DRIVER14 next asks for the number of realisations that it must generate. The prompt is: + +How many realizations do you wish to generate? + +The name of a CSV output file is next requested. + +Enter name for csv output file: + +DRIVER14’s final prompt is: + +Enter integer seed for random number generator \[324853\]: + +Once a response to this prompt has been provided (respond with \ to accept the default), DRIVER14 calls function *initialize_random()* to initiate the random number generator, then function *fieldgen2d_sva()* to generate random fields. When *fieldgen2d_sva()* has finished its work, DRIVER14 records the random fields in the user-nominated CSV file, and then ceases execution. + +## DRIVER15 + +### General + +DRIVER15 calls the following *model_interface* library functions: + +- *initialize_randgen()* + +- *fieldgen3d_sva()* + +- *retrieve_error_message()* + +DRIVER15 requires very similar inputs to program FIELDGEN3D_SVA from the PEST Groundwater Utility Suite. Hence running one of these programs is very much like running the other. Much of the following documentation of DRIVER15 is therefore copied from documentation of FIELDGEN3D_SVA. + +### File Types + +DRIVER15 requires two tabular data files. These can be CSV files if you like; the contents of these files can be space or comma-delimited. + +#### 3D Node Specification File + +The first of these file types is referred to as a “3D node specification file”. Its contents pertain to nodes of a 3D model grid. DRIVER15 does not care whether the model grid is structured or unstructured. + +The figure below shows the first few lines of such a node specification file displayed in Microsoft EXCEL. + + + +First few lines of a 3D node specification file. + +DRIVER15 insists that certain protocols be followed by this file. The first line of a 3D node specification file must contain the same headers as those that appear in the above picture. These define the contents of respective columns. The first column must contain model node numbers. DRIVER15 insists that these be numbered sequentially starting from 1. The *x*, *y* and *z* coordinates of nodes (normally cell centres) follow in ensuing columns. The next column contains cell layer numbers; these are ignored by DRIVER15 as they are not required by function *fieldgen3d_sva()*. The next two columns contain cell areas and heights. The final column specifies whether a cell is active or not. *fieldgen3d_sva()* does not calculate stochastic property field values for inactive cells. + +Note that cell areas and heights are used in the integration procedure through which the spatial averaging function is applied. + +#### 3D Averaging Function Specification File + +The first part of a 3D averaging function specification file is pictured below. In many instances of its use, the contents of this file will have been spatially interpolated to the locations of model grid cell centres. + + + +The first part of a 3D averaging function specification file. + +A 3D averaging function specification file must possess nine columns. Their labels should be exactly as shown in the above figure. (However you can use “variance” instead of “sill”.) The contents of these columns are obvious from their labels. As for the 3D node specification file, the first column must feature grid nodes. These must be sequential and start at 1. All grid nodes must be cited in this column whether or not they are active. + +Note that bearing is measured clockwise from north. Dip is positive upwards. A positive rake implies an upward movement of the positive *ahmin* axis when looking from the positive direction of the *ahmax* axis. + +### Using DRIVER15 + +DRIVER15 begins execution by prompting for the names of the above two files. It then reads these files, reporting any errors that it encounters to the screen. + +Enter name of 3D node specifications file: + +Enter name of 3D averaging function specification file: + +Its next prompt is: + +Express stochasticity in natural or log domain? \[n/l\]: + +If your answer to this prompt is “n”, then stochastic fields which are generated by DRIVER15 are added to the mean value field that is supplied in the averaging function specification file. Alternatively, if the response is “L”, then mean cell values are multiplied by the log (to base 10) of DRIVER15-generated stochastic fields. Variances supplied in the averaging function specification file must therefore pertain to the log of the hydraulic property for which stochastic fields are being generated. (Note, however, that this does not apply to the mean.) + +The nature of the moving average function is next requested: + +Is moving ave spherical, exponential, gaussian or power? \[s/x/g/p(..)\]: + +If a power function is desired, then the response to this prompt should be something like: “p(1.0)”. This indicates a power of 1.0. A response of “p(0.5)” indicates a power of 0.5. The power must be greater than zero. A power of 1.0 defines a linear function. Alternatively, respond to the above prompt with “s”, “x” or “g”. + +DRIVER15 next asks for the number of realisations that it must generate. The prompt is: + +How many realizations do you wish to generate? + +The name of a CSV output file is next requested. + +Enter name for csv output file: + +DRIVER15’s final prompt is: + +Enter integer seed for random number generator \[324853\]: + +Once a response to this prompt has been provided (respond with \ to accept the default), DRIVER15 calls function *initialize_random()* to initiate the random number generator, then function *fieldgen3d_sva()* to generate random fields. When *fieldgen3d_sva()* has finished its work, DRIVER15 records the random fields in the user-nominated CSV file, and then ceases execution. + +## DRIVER16 + +DRIVER16 is a slightly-modified version of DRIVER4. It provides all of the functionality that DRIVER4 provides, but adds one extra option. This is the option to write a file that contains the coordinates of the centres of all cells belonging to an installed MODFLOW 6 grid. + +This extra functionality appears as option number 7 of DRIVER16’s menu: + +What do you want to do? + +To end this program - enter 0 + +To install a set of MF6 grid specs - enter 1 + +To uninstall a set of MF6 grid specs - enter 2 + +To read a set of point coordinates - enter 3 + +To write an interpolation factor file - enter 4 + +To interpolate from an interp factor file - enter 5 + +To time-interpolate after spatial interpolation - enter 6 + +To record grid cell centre coordinates - enter 7 + +If option 7 is selected, DRIVER16 asks: + +Enter name of installed MF6 grid: + +Enter number of cells in grid: + +DRIVER16 needs to know the number of cells in the model grid so that it can allocate space for the arrays that store model cell *x*, *y* and *z* coordinates. As a user, you can obtain this number from information that DRIVER16 writes to the screen when it stores a MODFLOW 6 grid. + +Next DRIVER16 asks for the name of the file in which cell centre coordinates should be recorded. The prompt is: + +Enter file to record grid cell centre coordinates: + +DRIVER16 then calls function *get_cell_centres_mf6()* in order to obtain cell centre coordinates. It then stores them in the requested file and returns the user to the main menu. + +## DRIVER17 + +DRIVER17 calls the following *model_interface* module functions: + +- *install_structured_grid()* + +- *retrieve_error_message()* + +- *free_all_memory()* + +- *get_cell_centres_stuctured()* + +Upon commencement of execution, DRIVER17 installs the specifications of a structured MODFLOW grid. It prompts: + +Enter name of a GW Utils grid spec file: + +After installing the grid, it asks for the name of a file in which to store the *x* and *y* coordinates of grid cell centres. The prompt is: + +Enter name of file to store cell centre coordinates: + +It then writes the file and ceases execution. + +Note that the number of model layers and the name of the structured grid are immaterial to testing of function *get_cell_centres_structured()*. DRIVER17 assigns these internally itself. + +## References + +Deutsch, C.V. and Journel, A.G., 1998. GSLIB. Geostatistical Software Library and User’s Guide. Oxford University Press. + +Higdon, D, Swall, J. and Kern J., 1999. Non-stationary spatial modeling, in: *J.M. Bernardo et al. (Eds.), Bayesian Statistics 6, Oxford University Press, Oxford*, pp. 761–768. + +Fuentes, M., 2002. Spectral methods for nonstationary spatial processes. *Biometrika,* 89: 197-210. + +Oliver, D., 2022. Hybrid iterative ensemble smoother for history-matching of hierarchical models. *Math. Geosci*. 54: 1289-1313. + +Remy, N., Boucher, A. and Wu J., 2011. Applied Geostatistics with SGEMS. Cambridge University Press. diff --git a/docs/pestutilslib/new_function_documentation.docx b/docs/pestutilslib/new_function_documentation.docx new file mode 100644 index 0000000..06f05e1 Binary files /dev/null and b/docs/pestutilslib/new_function_documentation.docx differ diff --git a/docs/pestutilslib/~$w_function_documentation.docx b/docs/pestutilslib/~$w_function_documentation.docx new file mode 100644 index 0000000..1215360 Binary files /dev/null and b/docs/pestutilslib/~$w_function_documentation.docx differ diff --git a/docs/version.html b/docs/version.html new file mode 100644 index 0000000..8908f15 --- /dev/null +++ b/docs/version.html @@ -0,0 +1,60 @@ + + + + + + +pypestutils.version API documentation + + + + + + + + + + + +
+
+
+

Module pypestutils.version

+
+
+
+ +Expand source code + +
# This is the only place to update version
+__version__ = "0.1.1.dev0"
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/etc/environment.yml b/etc/environment.yml index 736703e..f3acdfa 100644 --- a/etc/environment.yml +++ b/etc/environment.yml @@ -29,6 +29,7 @@ dependencies: - pip: - git+https://github.com/pypest/pyemu.git - git+https://github.com/modflowpy/flopy.git + - pdoc3 # Win64 may also want diff --git a/pypestutils/helpers.py b/pypestutils/helpers.py index 5fa0194..1adc4ed 100644 --- a/pypestutils/helpers.py +++ b/pypestutils/helpers.py @@ -7,9 +7,9 @@ from .pestutilslib import PestUtilsLib -def mod2obs_mf6(gridinfo_fname,depvar_fname,obscsv_fname,model_type,start_datetime,depvar_ftype=1, +def mod2obs_mf6(gridinfo_fname: str,depvar_fname: str,obscsv_fname: str ,model_type: int,start_datetime: str | pd.TimeStamp,depvar_ftype=1, depvar_name="head",interp_thresh=1.0e+30,no_interp_val=1.0e+30,model_timeunit="d", - time_extrap=1.0): + time_extrap=1.0)->dict: """python implementation of mod2smp and mod2obs using modflow6 binary grid files Parameters @@ -36,6 +36,13 @@ def mod2obs_mf6(gridinfo_fname,depvar_fname,obscsv_fname,model_type,start_dateti pandas style time unit. Default is "d"ay time_extrap: float length of time units to extrapolate. Default is 1.0 time unit + + Returns + ------- + all_results: pd.DataFrame + all simulated times at observation locations (ie mod2smp) + interpolated_results: pd.DataFrame + temporally interpolated simulated results at observation locations (ie mod2obs) """ for fname in [gridinfo_fname,depvar_fname]: @@ -131,6 +138,11 @@ def get_grid_info_from_gridspec(gridspec_fname: str) -> dict: ---------- gridspec_fname : str PEST-style grid specification file + + Returns + ------- + grid_info: dict + grid information """ if not os.path.exists(gridspec_fname): @@ -153,6 +165,11 @@ def get_grid_info_from_mf6_grb(grb_fname: str) -> dict: ---------- grb_fname: str MODFLOW-6 binary grid file + + Returns + ------- + grid_info: dict + grid information """ if not os.path.exists(grb_fname): raise FileNotFoundError(grb_fname) @@ -173,6 +190,11 @@ def get_2d_grid_info_from_file(fname: str,layer=None) -> dict: layer: int (optional) the layer number to use for 2-D. If None and grid info is 3-D, a value of 1 is used + + Returns + ------- + grid_info: dict + grid information """ grid_info = None @@ -216,6 +238,11 @@ def get_2d_grid_info_from_mf6_grb(grb_fname: str,layer=None) -> dict: layer: int (optional) the layer number to use for 2-D. If None, a value of 1 is used + + Returns + ------- + grid_info: dict + grid information """ grid_info = get_grid_info_from_mf6_grb(grb_fname) nnodes = grid_info["ncells"] @@ -277,6 +304,12 @@ def get_2d_pp_info_structured_grid( sampled at pilot point locations name_prefix: str pilot point name prefix. Default is "pp" + + Returns + ------- + ppdf: pd.DataaFrame + dataframe of pilot point information + """ grid_info = get_2d_grid_info_from_file(gridinfo_fname) @@ -382,6 +415,12 @@ def interpolate_with_sva_pilotpoints_2d( layer: int layer number to use if gridinfo_fname points to 3-D grid info. Default is None, which results in layer 1 being used + + Returns + ------- + results: dict + resulting arrays of the various interpolation from pilot + points to grid-shaped arrays """ # some checks on pp_info req_cols = ["ppname", "x", "y", "value"] @@ -416,38 +455,6 @@ def interpolate_with_sva_pilotpoints_2d( area = np.ones_like(x) if nnodes is None: nnodes = x.shape[0] - - # try: - # sr = SpatialReference.from_gridspec(gridinfo_fname) - # nnodes = sr.nrow * sr.ncol - # nrow = sr.nrow - # ncol = sr.ncol - # x = sr.xcentergrid - # y = sr.ycentergrid - # area = sr.areagrid - # if layer is None: - # layer = 1 - - # except: - # #try: - # grid_info = get_2d_grid_info_from_mf6_grb(gridinfo_fname,layer) - # x = grid_info["x"] - # y = grid_info["y"] - # area = grid_info.get("area",np.ones_like(x)) - - # if grid_info["idis"] == 1: - # nrow = grid_info["nrow"] - # ncol = grid_info["ncol"] - # nnodes = grid_info["nnodes"] - # #lib.logger.info("structured grid loaded from mf6 grb file %r",gridinfo_fname) - # elif grid_info["idis"] == 2: - # nnodes = grid_info["nnodes"] - - - #except: - # raise Exception( - # "failed to load grid spec file {0}: {1}".format(gridspec_fname, str(e)) - # ) lib = PestUtilsLib() @@ -669,7 +676,7 @@ def generate_2d_grid_realizations( variobearing=0.0, random_seed=12345, layer=None, -) -> numpy.ndarray: +) ->np.NDArray[float]: """draw 2-D realizations using sequential gaussian simulations and optionally using spatially varying geostatistical hyper parameters. @@ -700,6 +707,12 @@ def generate_2d_grid_realizations( layer : int or None the layer to use of gridinfo_fname contains 3-D info. Default is None, which results in layer 1 being used + + Returns + ------- + results: numpy.ndarray(float) + realizations (if `grid_info` indicates a structured grid, realizations + will be reshaped to NROW X NCOL) """ @@ -777,13 +790,18 @@ class SpatialReference(object): """ a class to locate a structured model grid in x-y space. - Args: - - delr (`numpy ndarray`): the model discretization delr vector (An array of spacings along a row) - delc (`numpy ndarray`): the model discretization delc vector (An array of spacings along a column) - xul (`float`): The x coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll. - yul (`float`): The y coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll. - rotation (`float`): The counter-clockwise rotation (in degrees) of the grid + Parameters + ---------- + delr: numpy.ndarray + the model discretization delr vector (An array of spacings along a row) + delc: numpy ndarray + the model discretization delc vector (An array of spacings along a column) + xul: float + The x coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll. + yul: float + The y coordinate of the upper left corner of the grid. Enter either xul and yul or xll and yll. + rotation: float + The counter-clockwise rotation (in degrees) of the grid """ def __init__(self, delr, delc, xul, yul, rotation=0.0): @@ -807,26 +825,44 @@ def __init__(self, delr, delc, xul, yul, rotation=0.0): self._ycentergrid = None @property - def xll(self): - # calculate coords for lower left corner + def xll(self)->float: + """lower left x coord + """ xll = self.xul - (np.sin(self.theta) * self.yedge[0]) return xll @property - def yll(self): + def yll(self)->float: + """lower left y coord + """ yll = self.yul - (np.cos(self.theta) * self.yedge[0]) return yll @property - def nrow(self): + def nrow(self)->int: + """number of rows + """ return self.delc.shape[0] @property - def ncol(self): + def ncol(self)->int: + """number of cols + """ return self.delr.shape[0] @classmethod - def from_gridspec(cls, gridspec_file): + def from_gridspec(cls, gridspec_file)->SpatialReference: + """instantiate from a pest-style grid specification file + Parameters + ---------- + gridspec_file: str + grid specification file name + + Returns + ------- + sr: SpatialReference + sr instance + """ f = open(gridspec_file, "r") raw = f.readline().strip().split() nrow = int(raw[0]) @@ -863,51 +899,71 @@ def from_gridspec(cls, gridspec_file): return cls(np.array(delr), np.array(delc), xul=xul, yul=yul, rotation=rot) @property - def theta(self): + def theta(self)->float: + """rotation in radians + """ return -self.rotation * np.pi / 180.0 @property - def xedge(self): + def xedge(self)->np.NDArray[float]: + """the xedge array of the grid + """ return self.get_xedge_array() @property - def yedge(self): + def yedge(self)->np.NDArray[float]: + """the yedge array of the grid + """ return self.get_yedge_array() @property - def xgrid(self): + def xgrid(self)->np.NDArray[float]: + """xgrid array + """ if self._xgrid is None: self._set_xygrid() return self._xgrid @property - def ygrid(self): + def ygrid(self)->np.NDArray[float]: + """ygrid array + """ if self._ygrid is None: self._set_xygrid() return self._ygrid @property - def xcenter(self): + def xcenter(self)->np.NDArray[float]: + """grid x center array + """ return self.get_xcenter_array() @property - def ycenter(self): + def ycenter(self)->np.NDArray[float]: + """grid y center array + """ return self.get_ycenter_array() @property - def ycentergrid(self): + def ycentergrid(self)->np.NDArray[float]: + """grid y center array + """ if self._ycentergrid is None: self._set_xycentergrid() return self._ycentergrid @property - def xcentergrid(self): + def xcentergrid(self)->np.NDArray[float]: + """grid x center array + """ if self._xcentergrid is None: self._set_xycentergrid() return self._xcentergrid @property - def areagrid(self): + def areagrid(self)->np.NDArray[float]: + """area of grid nodes + """ dr, dc = np.meshgrid(self.delr, self.delc) return dr * dc @@ -921,9 +977,9 @@ def _set_xygrid(self): self._xgrid, self._ygrid = np.meshgrid(self.xedge, self.yedge) self._xgrid, self._ygrid = self.transform(self._xgrid, self._ygrid) - def get_xedge_array(self): + def get_xedge_array(self)->np.NDArray[float]: """ - Return a numpy one-dimensional float array that has the cell edge x + a numpy one-dimensional float array that has the cell edge x coordinates for every column in the grid in model space - not offset or rotated. Array is of size (ncol + 1) @@ -934,9 +990,9 @@ def get_xedge_array(self): xedge = np.concatenate(([0.0], np.add.accumulate(self.delr))) return xedge - def get_yedge_array(self): + def get_yedge_array(self)->np.NDArray[float]: """ - Return a numpy one-dimensional float array that has the cell edge y + a numpy one-dimensional float array that has the cell edge y coordinates for every row in the grid in model space - not offset or rotated. Array is of size (nrow + 1) @@ -948,9 +1004,9 @@ def get_yedge_array(self): yedge = np.concatenate(([length_y], length_y - np.add.accumulate(self.delc))) return yedge - def get_xcenter_array(self): + def get_xcenter_array(self)->np.NDArray[float]: """ - Return a numpy one-dimensional float array that has the cell center x + a numpy one-dimensional float array that has the cell center x coordinate for every column in the grid in model space - not offset or rotated. """ @@ -960,9 +1016,9 @@ def get_xcenter_array(self): x = np.add.accumulate(self.delr) - 0.5 * self.delr return x - def get_ycenter_array(self): + def get_ycenter_array(self)->np.NDArray[float]: """ - Return a numpy one-dimensional float array that has the cell center x + a numpy one-dimensional float array that has the cell center x coordinate for every row in the grid in model space - not offset of rotated. """ @@ -1012,7 +1068,7 @@ def transform(self, x, y, inverse=False): y -= self.yll return x, y - def get_extent(self): + def get_extent(self)->tuple[float]: """ Get the extent of the rotated and offset grid @@ -1041,7 +1097,7 @@ def get_extent(self): return (xmin, xmax, ymin, ymax) - def get_vertices(self, i, j): + def get_vertices(self, i, j)->list[list[float]]: """Get vertices for a single cell or sequence if i, j locations.""" pts = [] xgrid, ygrid = self.xgrid, self.ygrid @@ -1056,19 +1112,10 @@ def get_vertices(self, i, j): vrts = np.array(pts).transpose([2, 0, 1]) return [v.tolist() for v in vrts] - def get_ij(self, x, y): + def get_ij(self, x, y)->tuple(int): """Return the row and column of a point or sequence of points in real-world coordinates. - Args: - x (`float`): scalar or sequence of x coordinates - y (`float`): scalar or sequence of y coordinates - - Returns: - tuple of - - - **int** : row or sequence of rows (zero-based) - - **int** : column or sequence of columns (zero-based) """ if np.isscalar(x): c = (np.abs(self.xcentergrid[0] - x)).argmin() @@ -1081,7 +1128,15 @@ def get_ij(self, x, y): return r, c def write_gridspec(self, filename): - """write a PEST-style grid specification file""" + + """write a PEST-style grid specification file + Parameters + ---------- + filename: str + file to write + + + """ f = open(filename, "w") f.write("{0:10d} {1:10d}\n".format(self.delc.shape[0], self.delr.shape[0])) f.write( @@ -1100,6 +1155,3 @@ def write_gridspec(self, filename): f.write("\n") return - -if __name__ == "__main__": - pass