import os
import numpy as np
from ..mbase import BaseModel
from ..pakbase import Package
from .mp6sim import Modpath6Sim
[docs]class Modpath6List(Package):
"""
List package class
"""
def __init__(self, model, extension="list", listunit=7):
# call base package constructor
super().__init__(model, extension, "LIST", listunit)
# self.parent.add_package(self) This package is not added to the base
# model so that it is not included in get_name_file_entries()
return
[docs] def write_file(self):
# Not implemented for list class
return
[docs]class Modpath6(BaseModel):
"""
Modpath6 class.
Parameters
----------
modelname : str, default "modpathtest"
Basename for MODPATH 6 input and output files.
simfile_ext : str, default "mpsim"
Filename extension of the MODPATH 6 simulation file.
namefile_ext : str, default mpnam"
Filename extension of the MODPATH 6 namefile.
version : str, default "modpath"
String that defines the MODPATH version. Valid versions are
"modpath" (default).
exe_name : str, default "mp6.exe"
The name of the executable to use.
modflowmodel : flopy.modflow.Modflow
MODFLOW model object with one of LPF, BCF6, or UPW packages.
dis_file : str
Required dis file name.
dis_unit : int, default 87
Optional dis file unit number.
head_file : str
Required filename of the MODFLOW output head file.
budget_file : str
Required filename of the MODFLOW output cell-by-cell budget file.
model_ws : str, optional
Model workspace. Directory name to create model data sets.
Default is the current working directory.
external_path : str, optional
Location for external files.
verbose : bool, default False
Print additional information to the screen.
load : bool, default True
Load model.
listunit : int, default 7
LIST file unit number.
"""
def __init__(
self,
modelname="modpathtest",
simfile_ext="mpsim",
namefile_ext="mpnam",
version="modpath",
exe_name="mp6.exe",
modflowmodel=None,
dis_file=None,
dis_unit=87,
head_file=None,
budget_file=None,
model_ws=None,
external_path=None,
verbose=False,
load=True,
listunit=7,
):
super().__init__(
modelname,
simfile_ext,
exe_name,
model_ws=model_ws,
verbose=verbose,
)
self.version_types = {"modpath": "MODPATH"}
self.set_version(version)
self.__mf = modflowmodel
self.lst = Modpath6List(self, listunit=listunit)
self.mpnamefile = f"{self.name}.{namefile_ext}"
self.mpbas_file = f"{modelname}.mpbas"
if self.__mf is not None:
# ensure that user-specified files are used
iu = self.__mf.oc.iuhead
head_file = self.__mf.get_output(unit=iu)
p = self.__mf.get_package("LPF")
if p is None:
p = self.__mf.get_package("BCF6")
if p is None:
p = self.__mf.get_package("UPW")
if p is None:
raise Exception(
"LPF, BCF6, or UPW packages must be included in the "
"passed MODFLOW model"
)
iu = p.ipakcb
budget_file = self.__mf.get_output(unit=iu)
dis_file = (
self.__mf.dis.file_name[0] if dis_file is None else dis_file
)
dis_unit = self.__mf.dis.unit_number[0]
self.head_file = head_file
self.budget_file = budget_file
self.dis_file = dis_file
self.dis_unit = dis_unit
# make sure the valid files are available
if self.head_file is None:
raise ValueError(
"the head file in the MODFLOW model or passed "
"to __init__ cannot be None"
)
if self.budget_file is None:
raise ValueError(
"the budget file in the MODFLOW model or passed "
"to __init__ cannot be None"
)
if self.dis_file is None:
raise ValueError(
"the dis file in the MODFLOW model or passed "
"to __init__ cannot be None"
)
# set the rest of the attributes
self.__sim = None
self.array_free_format = False
self.array_format = "modflow"
self.external_path = external_path
self.external = False
self.external_fnames = []
self.external_units = []
self.external_binflag = []
self.load = load
self.__next_ext_unit = 500
if external_path is not None:
assert os.path.exists(
external_path
), "external_path does not exist"
self.external = True
return
def __repr__(self):
return "Modpath model"
# function to encapsulate next_ext_unit attribute
[docs] def next_ext_unit(self):
self.__next_ext_unit += 1
return self.__next_ext_unit
[docs] def getsim(self):
if self.__sim == None:
for p in self.packagelist:
if isinstance(p, Modpath6Sim):
self.__sim = p
return self.__sim
[docs] def getmf(self):
return self.__mf
[docs] def write_name_file(self):
"""
Write the name file
Returns
-------
None
"""
fn_path = os.path.join(self.model_ws, self.mpnamefile)
f_nam = open(fn_path, "w")
f_nam.write(f"{self.heading}\n")
if self.mpbas_file is not None:
f_nam.write(f"MPBAS 86 {self.mpbas_file}\n")
if self.dis_file is not None:
f_nam.write(f"DIS {self.dis_unit:3} {self.dis_file}\n")
if self.head_file is not None:
f_nam.write(f"HEAD 88 {self.head_file}\n")
if self.budget_file is not None:
f_nam.write(f"BUDGET 89 {self.budget_file}\n")
for u, f in zip(self.external_units, self.external_fnames):
f_nam.write(f"DATA {u:3d} {f}\n")
f_nam.close()
sim = property(getsim) # Property has no setter, so read-only
mf = property(getmf) # Property has no setter, so read-only
[docs] def create_mpsim(
self,
simtype="pathline",
trackdir="forward",
packages="WEL",
start_time=0,
default_ifaces=None,
ParticleColumnCount=4,
ParticleRowCount=4,
MinRow=0,
MinColumn=0,
MaxRow=None,
MaxColumn=None,
):
"""
Create a MODPATH simulation file using available MODFLOW boundary
package data.
Parameters
----------
simtype : str
Keyword defining the MODPATH simulation type. Available simtype's
are 'endpoint', 'pathline', and 'timeseries'.
(default is 'PATHLINE')
trackdir : str
Keyword that defines the MODPATH particle tracking direction.
Available trackdir's are 'backward' and 'forward'.
(default is 'forward')
packages : str or list of strings
Keyword defining the modflow packages used to create initial
particle locations. Supported packages are 'WEL', 'MNW2' and 'RCH'.
(default is 'WEL').
start_time : float or tuple
Sets the value of MODPATH reference time relative to MODFLOW time.
float : value of MODFLOW simulation time at which to start the particle tracking simulation.
Sets the value of MODPATH ReferenceTimeOption to 1.
tuple : (period, step, time fraction) MODFLOW stress period, time step and fraction
between 0 and 1 at which to start the particle tracking simulation.
Sets the value of MODPATH ReferenceTimeOption to 2.
default_ifaces : list
List of cell faces (1-6; see MODPATH6 manual, fig. 7) on which to start particles.
(default is None, meaning ifaces will vary depending on packages argument above)
ParticleRowCount : int
Rows of particles to start on each cell index face (iface).
ParticleColumnCount : int
Columns of particles to start on each cell index face (iface).
Returns
-------
mpsim : ModpathSim object
"""
if isinstance(packages, str):
packages = [packages]
pak_list = self.__mf.get_package_list()
# not sure if this is the best way to handle this
ReferenceTimeOption = 1
ref_time = 0
ref_time_per_stp = (0, 0, 1.0)
if isinstance(start_time, tuple):
ReferenceTimeOption = 2 # 1: specify value for ref. time, 2: specify kper, kstp, rel. time pos
ref_time_per_stp = start_time
else:
ref_time = start_time
# set iface particle grids
ptrow = ParticleRowCount
ptcol = ParticleColumnCount
side_faces = [
[1, ptrow, ptcol],
[2, ptrow, ptcol],
[3, ptrow, ptcol],
[4, ptrow, ptcol],
]
top_face = [5, ptrow, ptcol]
botm_face = [6, ptrow, ptcol]
if default_ifaces is not None:
default_ifaces = [[ifc, ptrow, ptcol] for ifc in default_ifaces]
Grid = 1
GridCellRegionOption = 1
PlacementOption = 1
ReleaseStartTime = 0.0
ReleaseOption = 1
CHeadOption = 1
nper = self.__mf.dis.nper
nlay, nrow, ncol = (
self.__mf.dis.nlay,
self.__mf.dis.nrow,
self.__mf.dis.ncol,
)
arr = np.zeros((nlay, nrow, ncol), dtype=int)
group_name = []
group_region = []
group_placement = []
ifaces = []
face_ct = []
strt_file = None
for package in packages:
if package.upper() == "WEL":
ParticleGenerationOption = 1
if "WEL" not in pak_list:
raise Exception(
"Error: no well package in the passed model"
)
for kper in range(nper):
mflist = self.__mf.wel.stress_period_data[kper]
idx = (mflist["k"], mflist["i"], mflist["j"])
arr[idx] = 1
ngrp = arr.sum()
icnt = 0
for k in range(nlay):
for i in range(nrow):
for j in range(ncol):
if arr[k, i, j] < 1:
continue
group_name.append(f"wc{icnt}")
group_placement.append(
[
Grid,
GridCellRegionOption,
PlacementOption,
ReleaseStartTime,
ReleaseOption,
CHeadOption,
]
)
group_region.append([k, i, j, k, i, j])
if default_ifaces is None:
ifaces.append(
side_faces + [top_face, botm_face]
)
face_ct.append(6)
else:
ifaces.append(default_ifaces)
face_ct.append(len(default_ifaces))
icnt += 1
# this is kind of a band aid pending refactoring of mpsim class
elif "MNW" in package.upper():
ParticleGenerationOption = 1
if "MNW2" not in pak_list:
raise Exception(
"Error: no MNW2 package in the passed model"
)
node_data = self.__mf.mnw2.get_allnode_data()
node_data.sort(order=["wellid", "k"])
wellids = np.unique(node_data.wellid)
def append_node(ifaces_well, wellid, node_number, k, i, j):
"""add a single MNW node"""
group_region.append([k, i, j, k, i, j])
if default_ifaces is None:
ifaces.append(ifaces_well)
face_ct.append(len(ifaces_well))
else:
ifaces.append(default_ifaces)
face_ct.append(len(default_ifaces))
group_name.append(f"{wellid}{node_number}")
group_placement.append(
[
Grid,
GridCellRegionOption,
PlacementOption,
ReleaseStartTime,
ReleaseOption,
CHeadOption,
]
)
for wellid in wellids:
nd = node_data[node_data.wellid == wellid]
k, i, j = nd.k[0], nd.i[0], nd.j[0]
if len(nd) == 1:
append_node(
side_faces + [top_face, botm_face],
wellid,
0,
k,
i,
j,
)
else:
append_node(
side_faces + [top_face], wellid, 0, k, i, j
)
for n in range(len(nd))[1:]:
k, i, j = nd.k[n], nd.i[n], nd.j[n]
if n == len(nd) - 1:
append_node(
side_faces + [botm_face],
wellid,
n,
k,
i,
j,
)
else:
append_node(side_faces, wellid, n, k, i, j)
elif package.upper() == "RCH":
ParticleGenerationOption = 1
# for j in range(nrow):
# for i in range(ncol):
# group_name.append('rch')
group_name.append("rch")
group_placement.append(
[
Grid,
GridCellRegionOption,
PlacementOption,
ReleaseStartTime,
ReleaseOption,
CHeadOption,
]
)
group_region.append([0, 0, 0, 0, nrow - 1, ncol - 1])
if default_ifaces is None:
face_ct.append(1)
ifaces.append([[6, 1, 1]])
else:
ifaces.append(default_ifaces)
face_ct.append(len(default_ifaces))
else:
model_ws = ""
if self.__mf is not None:
model_ws = self.__mf.model_ws
if os.path.exists(os.path.join(model_ws, package)):
print(
"detected a particle starting locations file in packages"
)
assert len(packages) == 1, (
"if a particle starting locations file is passed, "
"other packages cannot be specified"
)
ParticleGenerationOption = 2
strt_file = package
else:
raise Exception(f"package '{package}' not supported")
SimulationType = 1
if simtype.lower() == "endpoint":
SimulationType = 1
elif simtype.lower() == "pathline":
SimulationType = 2
elif simtype.lower() == "timeseries":
SimulationType = 3
if trackdir.lower() == "forward":
TrackingDirection = 1
elif trackdir.lower() == "backward":
TrackingDirection = 2
WeakSinkOption = 2
WeakSourceOption = 1
StopOption = 2
if SimulationType == 1:
TimePointOption = 1
else:
TimePointOption = 3
BudgetOutputOption = 1
ZoneArrayOption = 1
RetardationOption = 1
AdvectiveObservationsOption = 1
mpoptions = [
SimulationType,
TrackingDirection,
WeakSinkOption,
WeakSourceOption,
ReferenceTimeOption,
StopOption,
ParticleGenerationOption,
TimePointOption,
BudgetOutputOption,
ZoneArrayOption,
RetardationOption,
AdvectiveObservationsOption,
]
return Modpath6Sim(
self,
ref_time=ref_time,
ref_time_per_stp=ref_time_per_stp,
option_flags=mpoptions,
group_placement=group_placement,
group_name=group_name,
group_region=group_region,
face_ct=face_ct,
ifaces=ifaces,
strt_file=strt_file,
)