diff --git a/dfn/prt-prp.dfn b/dfn/prt-prp.dfn new file mode 100644 index 0000000..6affe2c --- /dev/null +++ b/dfn/prt-prp.dfn @@ -0,0 +1,405 @@ +# --------------------- prt prp options --------------------- +# flopy multi-package + +block options +name boundnames +type keyword +shape +reader urword +optional true +longname +description keyword to indicate that boundary names may be provided with the list of particle release points. + +block options +name print_input +type keyword +reader urword +optional true +longname print input to listing file +description REPLACE print_input {'{#1}': 'all model stress package'} + +block options +name dev_exit_solve_method +type integer +reader urword +optional true +longname exit solve method +description the method for iterative solution of particle exit location and time in the generalized Pollock's method. 0 default, 1 Brent, 2 Chandrupatla. The default is Brent's method. + +block options +name exit_solve_tolerance +type double precision +reader urword +optional false +longname exit solve tolerance +description the convergence tolerance for iterative solution of particle exit location and time in the generalized Pollock's method. A value of 0.00001 works well for many problems, but the value that strikes the best balance between accuracy and runtime is problem-dependent. + +block options +name local_z +type keyword +reader urword +optional true +longname whether to use local z coordinates +description indicates that ``zrpt'' defines the local z coordinate of the release point within the cell, with value of 0 at the bottom and 1 at the top of the cell. If the cell is partially saturated at release time, the top of the cell is considered to be the water table elevation (the head in the cell) rather than the top defined by the user. + +block options +name extend_tracking +type keyword +reader urword +optional true +longname whether to extend tracking beyond the end of the simulation +description indicates that particles should be tracked beyond the end of the simulation's final time step (using that time step's flows) until particles terminate or reach a specified stop time. By default, particles are terminated at the end of the simulation's final time step. + +block options +name track_filerecord +type record track fileout trackfile +shape +reader urword +tagged true +optional true +longname +description + +block options +name track +type keyword +shape +in_record true +reader urword +tagged true +optional false +longname track keyword +description keyword to specify that record corresponds to a binary track output file. Each PRP Package may have a distinct binary track output file. + +block options +name fileout +type keyword +shape +in_record true +reader urword +tagged true +optional false +longname file keyword +description keyword to specify that an output filename is expected next. + +block options +name trackfile +type string +preserve_case true +shape +in_record true +reader urword +tagged false +optional false +longname file keyword +description name of the binary output file to write tracking information. + +block options +name trackcsv_filerecord +type record trackcsv fileout trackcsvfile +shape +reader urword +tagged true +optional true +longname +description + +block options +name trackcsv +type keyword +shape +in_record true +reader urword +tagged true +optional false +longname track keyword +description keyword to specify that record corresponds to a CSV track output file. Each PRP Package may have a distinct CSV track output file. + +block options +name trackcsvfile +type string +preserve_case true +shape +in_record true +reader urword +tagged false +optional false +longname file keyword +description name of the comma-separated value (CSV) file to write tracking information. + +block options +name stoptime +type double precision +reader urword +optional true +longname stop time +description real value defining the maximum simulation time to which particles in the package can be tracked. Particles that have not terminated earlier due to another termination condition will terminate when simulation time STOPTIME is reached. If the last stress period in the simulation consists of more than one time step, particles will not be tracked past the ending time of the last stress period, regardless of STOPTIME. If the last stress period in the simulation consists of a single time step, it is assumed to be a steady-state stress period, and its ending time will not limit the simulation time to which particles can be tracked. If STOPTIME and STOPTRAVELTIME are both provided, particles will be stopped if either is reached. + +block options +name stoptraveltime +type double precision +reader urword +optional true +longname stop travel time +description real value defining the maximum travel time over which particles in the model can be tracked. Particles that have not terminated earlier due to another termination condition will terminate when their travel time reaches STOPTRAVELTIME. If the last stress period in the simulation consists of more than one time step, particles will not be tracked past the ending time of the last stress period, regardless of STOPTRAVELTIME. If the last stress period in the simulation consists of a single time step, it is assumed to be a steady-state stress period, and its ending time will not limit the travel time over which particles can be tracked. If STOPTIME and STOPTRAVELTIME are both provided, particles will be stopped if either is reached. + +block options +name stop_at_weak_sink +type keyword +reader urword +optional true +longname stop at weak sink +description is a text keyword to indicate that a particle is to terminate when it enters a cell that is a weak sink. By default, particles are allowed to pass though cells that are weak sinks. + +block options +name istopzone +type integer +reader urword +optional true +longname stop zone number +description integer value defining the stop zone number. If cells have been assigned IZONE values in the GRIDDATA block, a particle terminates if it enters a cell whose IZONE value matches ISTOPZONE. An ISTOPZONE value of zero indicates that there is no stop zone. The default value is zero. + +block options +name drape +type keyword +reader urword +optional true +longname drape +description is a text keyword to indicate that if a particle's release point is in a cell that happens to be inactive at release time, the particle is to be moved to the topmost active cell below it, if any. By default, a particle is not released into the simulation if its release point's cell is inactive at release time. + +block options +name release_timesrecord +type record release_times times +shape +reader urword +tagged true +optional true +longname +description + +block options +name release_times +type keyword +reader urword +in_record true +tagged true +shape +longname +description keyword indicating release times will follow + +block options +name times +type double precision +shape (unknown) +reader urword +in_record true +tagged false +repeating true +longname release times +description times to release, relative to the beginning of the simulation. RELEASE\_TIMES and RELEASE\_TIMESFILE are mutually exclusive. + +block options +name release_timesfilerecord +type record release_timesfile timesfile +shape +reader urword +tagged true +optional true +longname +description + +block options +name release_timesfile +type keyword +reader urword +in_record true +tagged true +shape +longname +description keyword indicating release times file name will follow + +block options +name timesfile +type string +preserve_case true +shape +in_record true +reader urword +tagged false +optional false +longname file keyword +description name of the release times file. RELEASE\_TIMES and RELEASE\_TIMESFILE are mutually exclusive. + +block options +name dev_forceternary +type keyword +reader urword +optional false +longname force ternary tracking method +description force use of the ternary tracking method regardless of cell type in DISV grids. +mf6internal ifrctrn + + +# --------------------- prt prp dimensions --------------------- + +block dimensions +name nreleasepts +type integer +reader urword +optional false +longname number of particle release points +description is the number of particle release points. + +# --------------------- prt prp packagedata --------------------- + +block packagedata +name packagedata +type recarray irptno cellid xrpt yrpt zrpt boundname +shape (nreleasepts) +reader urword +longname +description + +block packagedata +name irptno +type integer +shape +tagged false +in_record true +reader urword +longname PRP id number for release point +description integer value that defines the PRP release point number associated with the specified PACKAGEDATA data on the line. IRPTNO must be greater than zero and less than or equal to NRELEASEPTS. The program will terminate with an error if information for a PRP release point number is specified more than once. +numeric_index true + +block packagedata +name cellid +type integer +shape (ncelldim) +tagged false +in_record true +reader urword +longname cell identifier +description REPLACE cellid {} + +block packagedata +name xrpt +type double precision +shape +tagged false +in_record true +reader urword +longname x coordinate of release point +description real value that defines the x coordinate of the release point in model coordinates. The (x, y, z) location specified for the release point must lie within the cell that is identified by the specified cellid. + +block packagedata +name yrpt +type double precision +shape +tagged false +in_record true +reader urword +longname y coordinate of release point +description real value that defines the y coordinate of the release point in model coordinates. The (x, y, z) location specified for the release point must lie within the cell that is identified by the specified cellid. + +block packagedata +name zrpt +type double precision +shape +tagged false +in_record true +reader urword +longname z coordinate of release point +description real value that defines the z coordinate of the release point in model coordinates or, if the LOCAL\_Z option is active, in local cell coordinates. The (x, y, z) location specified for the release point must lie within the cell that is identified by the specified cellid. + +block packagedata +name boundname +type string +shape +tagged false +in_record true +reader urword +optional true +longname release point name +description name of the particle release point. BOUNDNAME is an ASCII character variable that can contain as many as 40 characters. If BOUNDNAME contains spaces in it, then the entire name must be enclosed within single quotes. + +# --------------------- prt prp period --------------------- + +block period +name iper +type integer +block_variable True +in_record true +tagged false +shape +valid +reader urword +optional false +longname stress period number +description integer value specifying the stress period number for which the data specified in the PERIOD block apply. IPER must be less than or equal to NPER in the TDIS Package and greater than zero. The IPER value assigned to a stress period block must be greater than the IPER value assigned for the previous PERIOD block. The information specified in the PERIOD block applies only to that stress period. + +block period +name perioddata +type recarray releasesetting +shape +reader urword +longname +description + +block period +name releasesetting +type keystring all first frequency steps fraction +shape +tagged false +in_record true +reader urword +longname +description specifies when to release particles within the stress period. Overrides package-level RELEASETIME option, which applies to all stress periods. By default, RELEASESETTING configures particles for release at the beginning of the specified time steps. For time-offset releases, provide a FRACTION value. + +block period +name all +type keyword +shape +in_record true +reader urword +longname +description keyword to indicate release of particles at the start of all time steps in the period. + +block period +name first +type keyword +shape +in_record true +reader urword +longname +description keyword to indicate release of particles at the start of the first time step in the period. This keyword may be used in conjunction with other keywords to release particles at the start of multiple time steps. + +block period +name frequency +type integer +shape +tagged true +in_record true +reader urword +longname +description release particles at the specified time step frequency. This keyword may be used in conjunction with other keywords to release particles at the start of multiple time steps. + +block period +name steps +type integer +shape ( list: + return list(self._dfn["block"][blockname]) + + def block(self, blockname) -> dict: + return self._dfn["block"][blockname] + + def param(self, blockname, tagname) -> dict: + return self._dfn["block"][blockname][tagname] + + @classmethod + def load(cls, f, metadata=None): + p = Path(f) + + if not p.exists(): + raise ValueError("Invalid DFN path") + + component, subcomponent = p.stem.split("-") + data = toml.load(f) + + return cls(component, subcomponent, data, **metadata) + + +class DfnSet: + def __init__(self, *args, **kwargs): + self._dfns = dict() + + def __getitem__(self, key): + return self._dfns[key] + + def __setitem__(self, key, value): + self._dfns[key] = value + + def __delitem__(self, key): + del self._dfns[key] + + def __iter__(self): + return iter(self._dfns) + + def __len__(self): + return len(self._dfns) + + def add(self, key, dfn): + if key in self._dfns: + raise ValueError("DFN exists in container") + + self._dfns[key] = dfn + + def get(self, key): + if key not in self._dfns: + raise ValueError("DFN does not exist in container") + + return self._dfns[key] diff --git a/pyproject.toml b/pyproject.toml index dd5abf4..519f84a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ include = ["flopy4", "flopy4.*"] [tool.setuptools.package-data] "flopy4.dfns" = ["dfns/*.dfn"] +"flopy4.toml" = ["dfns/toml/*.toml"] [tool.ruff] line-length = 79 @@ -96,4 +97,4 @@ select = [ ignore = [ "F821", # undefined name TODO FIXME "E722" # do not use bare `except` -] \ No newline at end of file +] diff --git a/test/test_dfn.py b/test/test_dfn.py new file mode 100644 index 0000000..29967c3 --- /dev/null +++ b/test/test_dfn.py @@ -0,0 +1,95 @@ +from pathlib import Path + +from flopy4.dfn import Dfn, DfnSet + + +class TestDfn(Dfn): + __test__ = False # tell pytest not to collect + + +def test_dfn_load(tmp_path): + name = "prt-prp" + + f = Path(f"../dfn/toml/{name}.toml") + dfn = Dfn.load(f.absolute(), {}) + + assert dfn.component == "prt" + assert dfn.subcomponent == "prp" + assert type(dfn.dfn) is dict + assert len(dfn) == 4 + assert dfn.blocknames == ["options", "dimensions", "packagedata", "period"] + + for b in dfn.blocknames: + block_d = dfn[b] + assert type(block_d) is dict + + assert list(dfn.blocktags("options")) == [ + "boundnames", + "print_input", + "dev_exit_solve_method", + "exit_solve_tolerance", + "local_z", + "extend_tracking", + "track_filerecord", + "track", + "fileout", + "trackfile", + "trackcsv_filerecord", + "trackcsv", + "trackcsvfile", + "stoptime", + "stoptraveltime", + "stop_at_weak_sink", + "istopzone", + "drape", + "release_timesrecord", + "release_times", + "times", + "release_timesfilerecord", + "release_timesfile", + "timesfile", + "dev_forceternary", + ] + + assert dfn.param("options", "drape") == { + "block_variable": False, + "deprecated": "", + "description": "is a text keyword to indicate that if a \ +particle's release " + "point is in a cell that happens to be inactive at release " + "time, the particle is to be moved to the topmost active " + "cell below it, if any. By default, a particle is not " + "released into the simulation if its release point's cell " + "is inactive at release time.", + "in_record": False, + "layered": False, + "longname": "drape", + "mf6internal": "drape", + "numeric_index": False, + "optional": "true", + "preserve_case": False, + "reader": "urword", + "shape": "", + "tagged": True, + "time_series": False, + "type": "keyword", + "valid": [], + } + + +def test_dfn_container(tmp_path): + key = "prt-prp" + f = Path(f"../dfn/toml/{key}.toml") + dfn = Dfn.load(f.absolute(), {}) + + dfns = DfnSet() + assert len(dfns) == 0 + dfns.add(key, dfn) + assert len(dfns) == 1 + + d = dfns[key] + assert type(d) is Dfn + assert d is dfn + assert d.component == "prt" + assert d.subcomponent == "prp" + assert dfns.get(key) is d