Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
wpbonelli committed Oct 31, 2024
1 parent cde46ed commit 07985b5
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 318 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/mf6.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
pip install https://github.com/modflowpy/pymake/zipball/master
pip install https://github.com/Deltares/xmipy/zipball/develop
pip install https://github.com/MODFLOW-USGS/modflowapi/zipball/develop
pip install .[codegen,test,optional]
pip install .[dev]
pip install meson ninja
- name: Setup GNU Fortran
Expand Down Expand Up @@ -120,7 +120,7 @@ jobs:
pip install https://github.com/modflowpy/pymake/zipball/master
pip install https://github.com/Deltares/xmipy/zipball/develop
pip install https://github.com/MODFLOW-USGS/modflowapi/zipball/develop
pip install .[codegen,test,optional]
pip install .[dev]
pip install meson ninja
pip install -r modflow6-examples/etc/requirements.pip.txt
Expand Down
61 changes: 15 additions & 46 deletions flopy/mf6/utils/codegen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,43 @@
from pathlib import Path
from warnings import warn

from flopy.utils import import_optional_dependency

__all__ = ["make_targets", "make_all"]

jinja = import_optional_dependency("jinja2", errors="ignore")
if jinja:
_TEMPLATES_PATH = "mf6/utils/codegen/templates/"
_TEMPLATE_LOADER = jinja.PackageLoader("flopy", _TEMPLATES_PATH)
_TEMPLATE_ENV = jinja.Environment(loader=_TEMPLATE_LOADER)
__jinja = import_optional_dependency("jinja2", errors="ignore")


def make_targets(dfn, outdir: Path, verbose: bool = False):
"""Generate Python source file(s) from the given input definition."""
"""Generate Python source file(s) from an input definition."""

if not __jinja:
raise RuntimeError("Need Jinja2 for code generation")

from flopy.mf6.utils.codegen.context import Context

if not jinja:
raise RuntimeError("Jinja2 not installed, can't make targets")
loader = __jinja.PackageLoader("flopy", "mf6/utils/codegen/templates/")
env = __jinja.Environment(loader=loader)

for context in Context.from_dfn(dfn):
name = context.name
target = outdir / name.target
template = _TEMPLATE_ENV.get_template(name.template)
template = env.get_template(name.template)
with open(target, "w") as f:
f.write(template.render(**context.render()))
if verbose:
print(f"Wrote {target}")


def make_all(dfndir: Path, outdir: Path, verbose: bool = False):
"""Generate Python source files from the DFN files in the given location."""

from flopy.mf6.utils.codegen.context import Context
from flopy.mf6.utils.codegen.dfn import Dfn, Dfns, Ref, Refs
"""Generate Python source files from DFN files."""

if not jinja:
raise RuntimeError("Jinja2 not installed, can't make targets")
if not __jinja:
raise RuntimeError("Need Jinja2 for code generation")

# find definition files
paths = [
p for p in dfndir.glob("*.dfn") if p.stem not in ["common", "flopy"]
]

# try to load common variables
common_path = dfndir / "common.dfn"
if not common_path.is_file:
common = None
else:
with open(common_path, "r") as f:
common, _ = Dfn._load(f)

# load subpackage references first
refs: Refs = {}
for path in paths:
name = Dfn.Name(*path.stem.split("-"))
with open(path) as f:
dfn = Dfn.load(f, name=name, common=common)
ref = Ref.from_dfn(dfn)
if ref:
refs[ref.key] = ref
from flopy.mf6.utils.codegen.context import Context
from flopy.mf6.utils.codegen.dfn import Dfn

# load all the input definitions
dfns: Dfns = {}
for path in paths:
name = Dfn.Name(*path.stem.split("-"))
with open(path) as f:
dfn = Dfn.load(f, name=name, refs=refs, common=common)
dfns[name] = dfn
# load definition files
dfns = Dfn.load_all(dfndir)

# make target files
for dfn in dfns.values():
Expand Down
85 changes: 37 additions & 48 deletions flopy/mf6/utils/codegen/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Optional,
)

from flopy.mf6.utils.codegen.dfn import Dfn, Ref, Vars
from flopy.mf6.utils.codegen.dfn import Dfn, Ref
from flopy.mf6.utils.codegen.renderable import renderable
from flopy.mf6.utils.codegen.shim import SHIM

Expand All @@ -31,8 +31,7 @@ class Context:
becomes the first `__init__` method parameter).
The context class may reference other contexts via foreign key
relations held by its variables, and may itself be referenced
by other contexts if desired.
relations held by its variables, and may itself be referenced.
"""

Expand All @@ -59,37 +58,35 @@ class Name(NamedTuple):
"""

l: str
r: Optional[str]
component: str
subcomponent: Optional[str]

@property
def title(self) -> str:
"""
The input context's unique title. This is not
identical to `f"{l}{r}` in some cases, but it
remains unique. The title is substituted into
The input context's title. Substituted into
the file name and class name.
"""
l, r = self
comp, sub = self
if self == ("sim", "nam"):
return "simulation"
if l is None:
return r
if r is None:
return l
if l == "sim":
return r
if l in ["sln", "exg"]:
return r
return l + r
if comp is None:
return sub
if sub is None:
return comp
if comp == "sim":
return sub
if comp in ["sln", "exg"]:
return sub
return comp + sub

@property
def base(self) -> str:
"""Base class from which the input context should inherit."""
_, r = self
_, sub = self
if self == ("sim", "nam"):
return "MFSimulationBase"
if r is None:
if sub is None:
return "MFModel"
return "MFPackage"

Expand All @@ -106,19 +103,19 @@ def template(self) -> str:
elif self.base == "MFModel":
return "model.py.jinja"
elif self.base == "MFPackage":
if self.l == "exg":
if self.component == "exg":
return "exchange.py.jinja"
return "package.py.jinja"

@property
def description(self) -> str:
"""A description of the input context."""
l, r = self
comp, sub = self
title = self.title.title()
if self.base == "MFPackage":
return f"Modflow{title} defines a {r.upper()} package."
return f"Modflow{title} defines a {sub.upper()} package."
elif self.base == "MFModel":
return f"Modflow{title} defines a {l.upper()} model."
return f"Modflow{title} defines a {comp.upper()} model."
elif self.base == "MFSimulationBase":
return (
"MFSimulation is used to load, build, and/or save a MODFLOW 6 simulation."
Expand All @@ -129,7 +126,7 @@ def description(self) -> str:
@staticmethod
def from_dfn(dfn: Dfn) -> List["Context.Name"]:
"""
Returns a list of context names this definition produces.
Get a list of context names this definition produces.
Notes
-----
Expand All @@ -140,50 +137,42 @@ def from_dfn(dfn: Dfn) -> List["Context.Name"]:
definition files. All other definition files produce a single
context.
"""
if dfn.name.r == "nam":
if dfn.name.l == "sim":
if dfn.name.subcomponent == "nam":
if dfn.name.component == "sim":
return [
Context.Name(None, dfn.name.r), # nam pkg
Context.Name(None, dfn.name.subcomponent), # nam pkg
Context.Name(*dfn.name), # simulation
]
else:
return [
Context.Name(*dfn.name), # nam pkg
Context.Name(dfn.name.l, None), # model
Context.Name(dfn.name.component, None), # model
]
elif dfn.name in [
elif (dfn.name.component, dfn.name.subcomponent) in [
("gwf", "mvr"),
("gwf", "gnc"),
("gwt", "mvt"),
]:
# TODO: remove these special cases and deduplicate
# mfmvr.py/mfgwfmvr.py etc
return [
Context.Name(*dfn.name),
Context.Name(None, dfn.name.r),
Context.Name(None, dfn.name.subcomponent),
]
return [Context.Name(*dfn.name)]

name: Name
vars: Vars
base: Optional[type] = None
description: Optional[str] = None
dfn: Dfn
meta: Optional[Dict[str, Any]] = None

@classmethod
def from_dfn(cls, dfn: Dfn) -> Iterator["Context"]:
"""
Extract context class descriptor(s) from an input definition.
These are structured representations of input context classes.
Each input definition yields one or more input contexts.
Create context class descriptor(s) from an input definition.
These are structured representations of input context class
definitions. Each input definition yields one or more class
definitions.
"""
meta = dfn.meta.copy()
ref = Ref.from_dfn(dfn)
if ref:
meta["ref"] = ref

for name in Context.Name.from_dfn(dfn):
yield Context(
name=name,
vars=dfn.data,
base=name.base,
description=name.description,
meta=meta,
)
yield Context(name=name, dfn=dfn, meta=dfn.meta)
Loading

0 comments on commit 07985b5

Please sign in to comment.