forked from modflowpy/flopy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(createpackages): use jinja for mf6 module code generation
- Loading branch information
Showing
39 changed files
with
2,321 additions
and
1,102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import traceback | ||
from ast import AST, Attribute, ClassDef, Name, arg, expr | ||
from ast import parse as parse_ast | ||
from itertools import zip_longest | ||
from pprint import pformat | ||
from shutil import copytree | ||
from typing import Union | ||
|
||
import pytest | ||
from modflow_devtools.misc import run_cmd | ||
|
||
from autotest.conftest import get_project_root_path | ||
from flopy.mf6.utils.codegen.context import get_context_names | ||
from flopy.mf6.utils.codegen.dfn import Dfn | ||
from flopy.mf6.utils.codegen.make import ( | ||
DfnName, | ||
make_all, | ||
make_context, | ||
make_contexts, | ||
make_targets, | ||
) | ||
|
||
PROJ_ROOT = get_project_root_path() | ||
MF6_PATH = PROJ_ROOT / "flopy" / "mf6" | ||
TGT_PATH = MF6_PATH / "modflow" | ||
DFN_PATH = MF6_PATH / "data" / "dfn" | ||
DFN_NAMES = [ | ||
dfn.stem | ||
for dfn in DFN_PATH.glob("*.dfn") | ||
if dfn.stem not in ["common", "flopy"] | ||
] | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"dfn, n_vars, n_flat, n_meta", | ||
[("gwf-ic", 2, 2, 0), ("prt-prp", 18, 40, 1)], | ||
) | ||
def test_make_context(dfn, n_vars, n_flat, n_meta): | ||
with open(DFN_PATH / "common.dfn") as f: | ||
commonvars = Dfn.load(f) | ||
|
||
with open(DFN_PATH / f"{dfn}.dfn") as f: | ||
dfn = DfnName(*dfn.split("-")) | ||
definition = Dfn.load(f, name=dfn) | ||
|
||
context_names = get_context_names(dfn) | ||
context_name = context_names[0] | ||
context = make_context(context_name, definition, commonvars) | ||
assert len(context_names) == 1 | ||
assert len(context.variables) == n_vars | ||
assert len(context.definition) == n_flat | ||
assert len(context.definition.metadata) == n_meta | ||
|
||
|
||
@pytest.mark.skip(reason="TODO") | ||
@pytest.mark.parametrize("dfn_name", ["gwf-ic", "prt-prp", "gwf-nam"]) | ||
def test_make_contexts(dfn_name): | ||
with open(DFN_PATH / "common.dfn") as f: | ||
common = Dfn.load(f) | ||
|
||
# TODO | ||
|
||
|
||
@pytest.mark.parametrize("dfn_name", DFN_NAMES) | ||
def test_make_targets(dfn_name, function_tmpdir): | ||
with open(DFN_PATH / "common.dfn") as f: | ||
common = Dfn.load(f) | ||
|
||
with open(DFN_PATH / f"{dfn_name}.dfn", "r") as f: | ||
dfn_name = DfnName(*dfn_name.split("-")) | ||
dfn = Dfn.load(f, name=dfn_name) | ||
|
||
make_targets(dfn, function_tmpdir, common=common) | ||
for ctx_name in get_context_names(dfn_name): | ||
source_path = function_tmpdir / ctx_name.target | ||
assert source_path.is_file() | ||
|
||
|
||
def test_make_all(function_tmpdir): | ||
make_all(DFN_PATH, function_tmpdir, verbose=True) | ||
|
||
|
||
def compare_ast( | ||
node1: Union[expr, list[expr]], node2: Union[expr, list[expr]] | ||
) -> bool: | ||
if type(node1) is not type(node2): | ||
return False | ||
|
||
if isinstance(node1, AST): | ||
for k, v in vars(node1).items(): | ||
if k in { | ||
"lineno", | ||
"end_lineno", | ||
"col_offset", | ||
"end_col_offset", | ||
"ctx", | ||
}: | ||
continue | ||
if not ( | ||
compare_ast(v, getattr(node2, k)) or v == getattr(node2, k) | ||
): | ||
return False | ||
return True | ||
elif isinstance(node1, list) and isinstance(node2, list): | ||
if ( | ||
len(node1) == 1 | ||
and len(node2) == 1 | ||
and ( | ||
isinstance( | ||
node1[0], | ||
(Name, Attribute) | ||
or isinstance(node2[0], (Name, Attribute)), | ||
) | ||
) | ||
): | ||
# difference due to direct class usages vs fully qualified names, ignore it | ||
return True | ||
if not all( | ||
compare_ast(n1, n2) for n1, n2 in zip_longest(node1, node2) | ||
): | ||
# some components currently include an extraneous "filein" param | ||
if ( | ||
all(isinstance(n, arg) for n in node1) | ||
and all(isinstance(n, arg) for n in node2) | ||
and abs(len(node1) - len(node2)) <= 1 | ||
and (set([n.arg for n in node1]) ^ set([n.arg for n in node2])) | ||
== {"filein"} | ||
): | ||
return True | ||
if abs(len(node1) - len(node2)) <= 1: | ||
return True | ||
return False | ||
return True | ||
else: | ||
if node1 != node2: | ||
if isinstance(node1, str) and isinstance(node2, str): | ||
# if both are strings its likely a docstring, tolerate differences | ||
return True | ||
return False | ||
return True | ||
|
||
|
||
def test_equivalence(function_tmpdir): | ||
prev_dir = function_tmpdir / "prev" | ||
test_dir = function_tmpdir / "test" | ||
test_dir.mkdir() | ||
copytree(TGT_PATH, prev_dir) | ||
make_all(DFN_PATH, test_dir, verbose=True) | ||
prev_files = list(prev_dir.glob("*.py")) | ||
test_files = list(test_dir.glob("*.py")) | ||
prev_names = set([p.name for p in prev_files]) | ||
test_names = set([p.name for p in test_files]) | ||
diff = prev_names ^ test_names | ||
assert not any(diff), ( | ||
f"previous files don't match test files\n" | ||
f"=> symmetric difference:\n{pformat(diff)}\n" | ||
f"=> prev - test:\n{pformat(prev_names - test_names)}\n" | ||
f"=> test - prev:\n{pformat(test_names - prev_names)}\n" | ||
) | ||
for prev_file, test_file in zip(prev_files, test_files): | ||
prev = parse_ast(open(prev_file).read()) | ||
try: | ||
test = parse_ast(open(test_file).read()) | ||
except: | ||
raise ValueError( | ||
f"Failed to parse {test_file}: {traceback.format_exc()}" | ||
) | ||
prev_classes = [n for n in prev.body if isinstance(n, ClassDef)] | ||
test_classes = [n for n in test.body if isinstance(n, ClassDef)] | ||
prev_clsnames = set([c.name for c in prev_classes]) | ||
test_clsnames = set([c.name for c in test_classes]) | ||
diff = prev_clsnames ^ test_clsnames | ||
assert not any(diff), ( | ||
f"previous classes don't match test classes in {test_file.name}\n" | ||
f"=> symmetric difference:\n{pformat(diff)}\n" | ||
f"=> prev - test:\n{pformat(prev_clsnames - test_clsnames)}\n" | ||
f"=> test - prev:\n{pformat(test_clsnames - prev_clsnames)}\n" | ||
) | ||
for prev_cls, test_cls in zip(prev_classes, test_classes): | ||
assert compare_ast(prev_cls, test_cls), prev_file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import pytest | ||
|
||
from autotest.conftest import get_project_root_path | ||
from flopy.mf6.utils.codegen.dfn import Dfn | ||
from flopy.mf6.utils.codegen.make import DfnName | ||
|
||
PROJ_ROOT = get_project_root_path() | ||
MF6_PATH = PROJ_ROOT / "flopy" / "mf6" | ||
TGT_PATH = MF6_PATH / "modflow" | ||
DFN_PATH = MF6_PATH / "data" / "dfn" | ||
DFN_NAMES = [ | ||
dfn.stem | ||
for dfn in DFN_PATH.glob("*.dfn") | ||
if dfn.stem not in ["common", "flopy"] | ||
] | ||
|
||
|
||
@pytest.mark.parametrize("dfn_name", DFN_NAMES) | ||
def test_load_dfn(dfn_name): | ||
dfn_path = DFN_PATH / f"{dfn_name}.dfn" | ||
with open(dfn_path, "r") as f: | ||
dfn = Dfn.load(f, name=DfnName(*dfn_name.split("-"))) | ||
print(dfn) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.