|
5 | 5 | from pathlib import Path |
6 | 6 | from subprocess import PIPE, STDOUT, Popen |
7 | 7 | from traceback import format_exc |
8 | | -from typing import Callable, Optional, Union |
| 8 | +from typing import Callable, Union |
9 | 9 | from warnings import warn |
10 | 10 |
|
11 | 11 | import flopy |
12 | | -import numpy as np |
13 | 12 | from compare import ( |
14 | 13 | Comparison, |
15 | 14 | adjust_htol, |
|
21 | 20 | ) |
22 | 21 | from flopy.mbase import BaseModel |
23 | 22 | from flopy.mf6 import MFSimulation |
24 | | -from flopy.utils.compare import compare_heads |
| 23 | +from flopy.utils.compare import compare_cell_budget, compare_heads |
25 | 24 | from modflow_devtools.misc import get_ostag, is_in_ci |
26 | 25 |
|
27 | 26 | DNODATA = 3.0e30 |
@@ -232,20 +231,20 @@ class TestFramework: |
232 | 231 | def __init__( |
233 | 232 | self, |
234 | 233 | name: str, |
235 | | - workspace: Union[str, os.PathLike], |
| 234 | + workspace: str | os.PathLike, |
236 | 235 | targets: dict[str, Path], |
237 | | - api_func: Optional[Callable] = None, |
238 | | - build: Optional[Callable] = None, |
239 | | - check: Optional[Callable] = None, |
240 | | - plot: Optional[Callable] = None, |
241 | | - compare: Optional[str] = "auto", |
242 | | - parallel=False, |
243 | | - ncpus=1, |
244 | | - htol=None, |
245 | | - rclose=None, |
246 | | - overwrite=True, |
247 | | - verbose=False, |
248 | | - xfail=False, |
| 236 | + api_func: Callable | None = None, |
| 237 | + build: Callable | None = None, |
| 238 | + check: Callable | None = None, |
| 239 | + plot: Callable | None = None, |
| 240 | + compare: str | Comparison | None = "auto", |
| 241 | + parallel: bool = False, |
| 242 | + ncpus: int = 1, |
| 243 | + htol: float | None = None, |
| 244 | + rclose: float | None = None, |
| 245 | + overwrite: bool = True, |
| 246 | + verbose: bool = False, |
| 247 | + xfail: bool | list[bool] = False, |
249 | 248 | ): |
250 | 249 | # make sure workspace exists |
251 | 250 | workspace = Path(workspace).expanduser().absolute() |
@@ -422,96 +421,13 @@ def _compare_budgets(self, extensions="cbc", rclose=0.001) -> bool: |
422 | 421 | f"{EXTTEXT[extension]} comparison {i + 1}", |
423 | 422 | f"{self.name} ({os.path.basename(fpth0)})", |
424 | 423 | ) |
425 | | - success = self._compare_budget_files(extension, fpth0, fpth1, rclose) |
| 424 | + outname = os.path.splitext(os.path.basename(fpth0))[0] |
| 425 | + outfile = os.path.join(self.workspace, f"{outname}.{extension}.cmp.out") |
| 426 | + success = compare_cell_budget(fpth0, fpth1, outfile=outfile, rclose=rclose) |
426 | 427 | if not success: |
427 | 428 | return False |
428 | 429 | return True |
429 | 430 |
|
430 | | - def _compare_budget_files(self, extension, fpth0, fpth1, rclose=0.001) -> bool: |
431 | | - success = True |
432 | | - if os.stat(fpth0).st_size * os.stat(fpth0).st_size == 0: |
433 | | - return success, "" |
434 | | - outfile = os.path.splitext(os.path.basename(fpth0))[0] |
435 | | - outfile = os.path.join(self.workspace, outfile + f".{extension}.cmp.out") |
436 | | - fcmp = open(outfile, "w") |
437 | | - fcmp.write("Performing CELL-BY-CELL to CELL-BY-CELL comparison\n") |
438 | | - fcmp.write(f"{fpth0}\n") |
439 | | - fcmp.write(f"{fpth1}\n\n") |
440 | | - |
441 | | - # open the files |
442 | | - cbc0 = flopy.utils.CellBudgetFile( |
443 | | - fpth0, precision="double", verbose=self.verbose |
444 | | - ) |
445 | | - cbc1 = flopy.utils.CellBudgetFile( |
446 | | - fpth1, precision="double", verbose=self.verbose |
447 | | - ) |
448 | | - |
449 | | - # build list of cbc data to retrieve |
450 | | - avail0 = cbc0.get_unique_record_names() |
451 | | - avail1 = cbc1.get_unique_record_names() |
452 | | - avail0 = [t.decode().strip() for t in avail0] |
453 | | - avail1 = [t.decode().strip() for t in avail1] |
454 | | - |
455 | | - # initialize list for storing totals for each budget term terms |
456 | | - cbc_keys0 = [] |
457 | | - cbc_keys1 = [] |
458 | | - for t in avail0: |
459 | | - t1 = t |
460 | | - if t not in avail1: |
461 | | - # check if RCHA or EVTA is available and use that instead |
462 | | - # should be able to remove this once v6.3.0 is released |
463 | | - if t[:-1] in avail1: |
464 | | - t1 = t[:-1] |
465 | | - else: |
466 | | - raise Exception(f"Could not find {t} in {fpth1}") |
467 | | - cbc_keys0.append(t) |
468 | | - cbc_keys1.append(t1) |
469 | | - |
470 | | - # get list of times and kstpkper |
471 | | - kk = cbc0.get_kstpkper() |
472 | | - times = cbc0.get_times() |
473 | | - |
474 | | - # process data |
475 | | - for key, key1 in zip(cbc_keys0, cbc_keys1): |
476 | | - for idx, (k, t) in enumerate(zip(kk, times)): |
477 | | - v0 = cbc0.get_data(kstpkper=k, text=key)[0] |
478 | | - v1 = cbc1.get_data(kstpkper=k, text=key1)[0] |
479 | | - if v0.dtype.names is not None: |
480 | | - v0 = v0["q"] |
481 | | - v1 = v1["q"] |
482 | | - # skip empty vectors |
483 | | - if v0.size < 1: |
484 | | - continue |
485 | | - vmin = rclose |
486 | | - if vmin < 1e-6: |
487 | | - vmin = 1e-6 |
488 | | - vmin_tol = 5.0 * vmin |
489 | | - if v0.shape != v1.shape: |
490 | | - v0 = v0.flatten() |
491 | | - v1 = v1.flatten() |
492 | | - idx = (abs(v0) > vmin) & (abs(v1) > vmin) |
493 | | - diff = np.zeros(v0.shape, dtype=v0.dtype) |
494 | | - diff[idx] = abs(v0[idx] - v1[idx]) |
495 | | - diffmax = diff.max() |
496 | | - indices = np.where(diff == diffmax)[0] |
497 | | - if diffmax > vmin_tol: |
498 | | - success = False |
499 | | - msg = ( |
500 | | - f"{os.path.basename(fpth0)} - " |
501 | | - + f"{key:16s} " |
502 | | - + f"difference ({diffmax:10.4g}) " |
503 | | - + f"> {vmin_tol:10.4g} " |
504 | | - + f"at {indices.size} nodes " |
505 | | - + f" [first location ({indices[0] + 1})] " |
506 | | - + f"at time {t} " |
507 | | - ) |
508 | | - fcmp.write(f"{msg}\n") |
509 | | - if self.verbose: |
510 | | - print(msg) |
511 | | - |
512 | | - fcmp.close() |
513 | | - return success |
514 | | - |
515 | 431 | def _compare(self, comparison: Comparison): |
516 | 432 | """ |
517 | 433 | Compare the main simulation's output with that of another simulation or model. |
|
0 commit comments