From 80c10a234cc420623155e25d8df2ecd64bbefcc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Angermeyer?= Date: Wed, 26 Nov 2025 15:10:44 -0300 Subject: [PATCH 1/2] fix(mfusg): fix precision loss in CLN/GNC packages writing coordinates MFUSG CLN/GNC packages were losing significant precision when writing node coordinates, causing coordinate errors up to 3.5 km and affecting model results. ## Root Cause The fmt_string() method in mfusgcln.py and mfusggnc.py used %10.2e format (only 3 significant figures) instead of the standard Util2d format %15.6G (9 significant figures). ## Impact on Real Data Complete line from CLN package showing precision loss: Before (bug - %10.2e): 1 1 0 5.75e+01 1.15e+03 0.00e+00 0 0 4.01e+05 6.90e+06 1.21e+03 4.01e+05 6.90e+06 1.15e+03 After (fixed - %16.9G): 1 1 0 57.4650002 1150.19897 0 0 0 400569 6903567 1207.66394 400569 6903567 1150.19897 The precision loss caused measurable differences in model results. ## Solution Changed fmt_string() to return "%16.9G" format, which: - Provides 9 significant figures (exceeds float32's 7-digit precision) - Uses adaptive format (decimal when readable, scientific when needed) - Maintains proper spacing with 16-character width - Consistent with other MFUSG packages (WEL/DRN/RIV use %G) ## Testing Verified with Valle Copiapo model (142 CLN nodes, 181 stress periods). Model results now match original with negligible differences. --- flopy/mfusg/mfusg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flopy/mfusg/mfusg.py b/flopy/mfusg/mfusg.py index b7817240e..f58b9e802 100644 --- a/flopy/mfusg/mfusg.py +++ b/flopy/mfusg/mfusg.py @@ -550,7 +550,7 @@ def fmt_string(array): if vtype in {"i", "b"}: fmts.append("%10d") elif vtype == "f": - fmts.append("%10.2e") + fmts.append("%16.9G") elif vtype == "o": fmts.append("%10s") elif vtype == "s": From 2c85e71441e12c10f1da9731d681fa95eeba9f03 Mon Sep 17 00:00:00 2001 From: reneangermeyer Date: Tue, 2 Dec 2025 22:47:14 -0300 Subject: [PATCH 2/2] fix(mfusg): use high-precision format only when FREE format is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix used %16.9G unconditionally, which violates MODFLOW-USG's 10-character fixed-width field requirement for models without FREE format in the BAS file. This fix checks if FREE format is enabled: - With FREE: use %16.9G for full precision - Without FREE: use original %10.2e for compatibility Users who need to preserve coordinate precision (e.g., 57.465 vs 5.75e+01) should enable FREE format in their model. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- flopy/mfusg/mfusg.py | 11 +++++++++-- flopy/mfusg/mfusgcln.py | 13 +++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/flopy/mfusg/mfusg.py b/flopy/mfusg/mfusg.py index f58b9e802..db79228e0 100644 --- a/flopy/mfusg/mfusg.py +++ b/flopy/mfusg/mfusg.py @@ -535,7 +535,7 @@ def _send_load_messages(model, files_successfully_loaded, files_not_loaded): print(f" {os.path.basename(fname)}") -def fmt_string(array): +def fmt_string(array, free=False): """ Returns a C-style fmt string for numpy savetxt that corresponds to the dtype. @@ -543,14 +543,21 @@ def fmt_string(array): Parameters ---------- array : numpy array + free : bool, optional + If True, use high-precision format (%16.9G) for floats which requires + FREE format in the BAS file. If False (default), use fixed 10-character + format (%10.2e) compatible with MODFLOW-USG's fixed-width input format. """ fmts = [] + # When FREE format is enabled in BAS, we can use high-precision output. + # Without FREE, MODFLOW-USG expects 10-character fixed-width fields. + float_fmt = "%16.9G" if free else "%10.2e" for field in array.dtype.descr: vtype = field[1][1].lower() if vtype in {"i", "b"}: fmts.append("%10d") elif vtype == "f": - fmts.append("%16.9G") + fmts.append(float_fmt) elif vtype == "o": fmts.append("%10s") elif vtype == "s": diff --git a/flopy/mfusg/mfusgcln.py b/flopy/mfusg/mfusgcln.py index 15741bb6f..d2e87798c 100644 --- a/flopy/mfusg/mfusgcln.py +++ b/flopy/mfusg/mfusgcln.py @@ -543,18 +543,23 @@ def write_file(self, f=None, check=False): f_cln.write(self.iac_cln.get_file_entry()) f_cln.write(self.ja_cln.get_file_entry()) - np.savetxt(f_cln, self.node_prop, fmt=fmt_string(self.node_prop), delimiter="") + free = self.parent.free_format_input + np.savetxt( + f_cln, self.node_prop, fmt=fmt_string(self.node_prop, free), delimiter="" + ) - np.savetxt(f_cln, self.cln_gwc, fmt=fmt_string(self.cln_gwc), delimiter="") + np.savetxt( + f_cln, self.cln_gwc, fmt=fmt_string(self.cln_gwc, free), delimiter="" + ) if self.nconduityp > 0: np.savetxt( - f_cln, self.cln_circ, fmt=fmt_string(self.cln_circ), delimiter="" + f_cln, self.cln_circ, fmt=fmt_string(self.cln_circ, free), delimiter="" ) if self.nrectyp > 0: np.savetxt( - f_cln, self.cln_rect, fmt=fmt_string(self.cln_rect), delimiter="" + f_cln, self.cln_rect, fmt=fmt_string(self.cln_rect, free), delimiter="" ) f_cln.write(self.ibound.get_file_entry())