From 4ac5462b79a3eb209a0b04b942c218384b47e550 Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Fri, 2 Oct 2026 18:37:25 -0700 Subject: [PATCH] fix(dfns): mark blocks MF6 reads as optional MF6 reads SFR CROSSSECTIONS/DIVERSIONS/INITIALSTAGES, LAK OUTLETS and IMS NONLINEAR/LINEAR with blockRequired=.false., but their v1 fields aren't all marked optional, so the derived Block.optional came out false. A writer that emits required blocks even when empty then writes e.g. an empty SFR DIVERSIONS block, which MF6 rejects. Mark these blocks' fields optional during migration until the upstream DFNs are fixed. Co-Authored-By: Claude Opus 5.5 --- .../__snapshots__/v2.0.0.dev2/gwf-lak.json | 1 + .../__snapshots__/v2.0.0.dev2/gwf-lak.toml | 1 + .../__snapshots__/v2.0.0.dev2/gwf-lak.yaml | 1 + .../__snapshots__/v2.0.0.dev2/gwf-sfr.json | 3 ++ .../__snapshots__/v2.0.0.dev2/gwf-sfr.toml | 3 ++ .../__snapshots__/v2.0.0.dev2/gwf-sfr.yaml | 3 ++ .../__snapshots__/v2.0.0.dev2/sln-ims.json | 16 +++++--- .../__snapshots__/v2.0.0.dev2/sln-ims.toml | 6 +++ .../__snapshots__/v2.0.0.dev2/sln-ims.yaml | 6 +++ .../__snapshots__/v2.0.0.dev3/gwf-lak.json | 1 + .../__snapshots__/v2.0.0.dev3/gwf-lak.toml | 1 + .../__snapshots__/v2.0.0.dev3/gwf-lak.yaml | 1 + .../__snapshots__/v2.0.0.dev3/gwf-sfr.json | 3 ++ .../__snapshots__/v2.0.0.dev3/gwf-sfr.toml | 3 ++ .../__snapshots__/v2.0.0.dev3/gwf-sfr.yaml | 3 ++ .../__snapshots__/v2.0.0.dev3/sln-ims.json | 16 +++++--- .../__snapshots__/v2.0.0.dev3/sln-ims.toml | 6 +++ .../__snapshots__/v2.0.0.dev3/sln-ims.yaml | 6 +++ autotest/dfns/test_migrate.py | 19 +++++++++ .../dfns/migrate_to_v2_0_0_dev2.py | 40 +++++++++++++++++++ 20 files changed, 129 insertions(+), 10 deletions(-) diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.json index f29e7d88..98d9b61a 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.json @@ -401,6 +401,7 @@ "fields": { "outlets": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.toml index 455683c6..8dde7fe9 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.toml @@ -358,6 +358,7 @@ direction = "in" [blocks.outlets.fields.outlets] type = "list" +optional = true shape = [ "noutlets", ] diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.yaml index a025bdd7..9b11ae52 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-lak.yaml @@ -419,6 +419,7 @@ blocks: fields: outlets: type: list + optional: true item: type: record fields: diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.json index dfd9d903..844b47d5 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.json @@ -349,6 +349,7 @@ "fields": { "crosssections": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { @@ -413,6 +414,7 @@ "fields": { "diversions": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { @@ -454,6 +456,7 @@ "fields": { "initialstages": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.toml index 500123e1..ea3dc2e9 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.toml @@ -318,6 +318,7 @@ tagged = false [blocks.crosssections.fields.crosssections] type = "list" +optional = true [blocks.crosssections.fields.crosssections.item] type = "record" @@ -370,6 +371,7 @@ shape = [ [blocks.diversions.fields.diversions] type = "list" +optional = true [blocks.diversions.fields.diversions.item] type = "record" @@ -405,6 +407,7 @@ tagged = false [blocks.initialstages.fields.initialstages] type = "list" +optional = true [blocks.initialstages.fields.initialstages.item] type = "record" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.yaml index 3f0be5e3..fb5fc994 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-sfr.yaml @@ -364,6 +364,7 @@ blocks: fields: crosssections: type: list + optional: true item: type: record fields: @@ -429,6 +430,7 @@ blocks: fields: diversions: type: list + optional: true item: type: record fields: @@ -488,6 +490,7 @@ blocks: fields: initialstages: type: list + optional: true item: type: record fields: diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.json index 43c13fb3..816755a9 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.json @@ -115,7 +115,8 @@ "outer_dvclose": { "type": "double", "longname": "dependent-variable change criterion", - "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the outer (nonlinear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to OUTER_DVCLOSE, iteration stops. Commonly, OUTER_DVCLOSE equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error." + "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the outer (nonlinear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to OUTER_DVCLOSE, iteration stops. Commonly, OUTER_DVCLOSE equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error.", + "optional": true }, "outer_rclosebnd": { "type": "double", @@ -127,7 +128,8 @@ "outer_maximum": { "type": "integer", "longname": "outer maximum iterations", - "description": "integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1." + "description": "integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1.", + "optional": true }, "under_relaxation": { "type": "string", @@ -190,7 +192,8 @@ "inner_maximum": { "type": "integer", "longname": "maximum number of inner iterations", - "description": "integer value defining the maximum number of inner (linear) iterations. The number typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems." + "description": "integer value defining the maximum number of inner (linear) iterations. The number typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems.", + "optional": true }, "inner_hclose": { "type": "double", @@ -202,11 +205,13 @@ "inner_dvclose": { "type": "double", "longname": "dependent-variable change tolerance", - "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the inner (linear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to INNER_DVCLOSE, the matrix solver assumes convergence. Commonly, INNER_DVCLOSE is set equal to or an order of magnitude less than the OUTER_DVCLOSE value specified for the NONLINEAR block. The keyword, INNER_HCLOSE can be still be specified instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error." + "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the inner (linear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to INNER_DVCLOSE, the matrix solver assumes convergence. Commonly, INNER_DVCLOSE is set equal to or an order of magnitude less than the OUTER_DVCLOSE value specified for the NONLINEAR block. The keyword, INNER_HCLOSE can be still be specified instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error.", + "optional": true }, "rcloserecord": { "type": "record", "longname": "rclose record", + "optional": true, "fields": { "inner_rclose": { "type": "double", @@ -225,7 +230,8 @@ "linear_acceleration": { "type": "string", "longname": "linear acceleration method", - "description": "a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method." + "description": "a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method.", + "optional": true }, "relaxation_factor": { "type": "double", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.toml index 7efec774..bed1bab5 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.toml @@ -101,6 +101,7 @@ deprecated = "6.1.1" type = "double" longname = "dependent-variable change criterion" description = "real value defining the dependent-variable (for example, head) change criterion for convergence of the outer (nonlinear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to OUTER_DVCLOSE, iteration stops. Commonly, OUTER_DVCLOSE equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error." +optional = true [blocks.nonlinear.fields.outer_rclosebnd] type = "double" @@ -113,6 +114,7 @@ deprecated = "6.1.1" type = "integer" longname = "outer maximum iterations" description = "integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1." +optional = true [blocks.nonlinear.fields.under_relaxation] type = "string" @@ -172,6 +174,7 @@ optional = true type = "integer" longname = "maximum number of inner iterations" description = "integer value defining the maximum number of inner (linear) iterations. The number typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems." +optional = true [blocks.linear.fields.inner_hclose] type = "double" @@ -184,10 +187,12 @@ deprecated = "6.1.1" type = "double" longname = "dependent-variable change tolerance" description = "real value defining the dependent-variable (for example, head) change criterion for convergence of the inner (linear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to INNER_DVCLOSE, the matrix solver assumes convergence. Commonly, INNER_DVCLOSE is set equal to or an order of magnitude less than the OUTER_DVCLOSE value specified for the NONLINEAR block. The keyword, INNER_HCLOSE can be still be specified instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error." +optional = true [blocks.linear.fields.rcloserecord] type = "record" longname = "rclose record" +optional = true [blocks.linear.fields.rcloserecord.fields.inner_rclose] type = "double" @@ -205,6 +210,7 @@ tagged = false type = "string" longname = "linear acceleration method" description = "a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method." +optional = true [blocks.linear.fields.relaxation_factor] type = "double" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.yaml index 8448c33e..1674de91 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/sln-ims.yaml @@ -161,6 +161,7 @@ blocks: equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error. + optional: true outer_rclosebnd: type: double longname: boundary package flow residual tolerance @@ -179,6 +180,7 @@ blocks: longname: outer maximum iterations description: integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1. + optional: true under_relaxation: type: string longname: under relaxation scheme @@ -288,6 +290,7 @@ blocks: typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems. + optional: true inner_hclose: type: double longname: head change tolerance @@ -312,9 +315,11 @@ blocks: instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error. + optional: true rcloserecord: type: record longname: rclose record + optional: true fields: inner_rclose: type: double @@ -352,6 +357,7 @@ blocks: description: a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method. + optional: true relaxation_factor: type: double longname: relaxation factor used by ILU factorization diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.json index 31cb8d79..5f0ba5f9 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.json @@ -401,6 +401,7 @@ "fields": { "outlets": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.toml index 6bc1217e..f61e405d 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.toml @@ -358,6 +358,7 @@ direction = "in" [blocks.outlets.fields.outlets] type = "list" +optional = true shape = [ "noutlets", ] diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.yaml index d7747b39..eee169b8 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-lak.yaml @@ -419,6 +419,7 @@ blocks: fields: outlets: type: list + optional: true item: type: record fields: diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.json index 0c185da8..60a98990 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.json @@ -354,6 +354,7 @@ "fields": { "crosssections": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { @@ -418,6 +419,7 @@ "fields": { "diversions": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { @@ -459,6 +461,7 @@ "fields": { "initialstages": { "type": "list", + "optional": true, "item": { "type": "record", "fields": { diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.toml index 1d2e99d7..43e62eb8 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.toml @@ -321,6 +321,7 @@ tagged = false [blocks.crosssections.fields.crosssections] type = "list" +optional = true [blocks.crosssections.fields.crosssections.item] type = "record" @@ -373,6 +374,7 @@ shape = [ [blocks.diversions.fields.diversions] type = "list" +optional = true [blocks.diversions.fields.diversions.item] type = "record" @@ -408,6 +410,7 @@ tagged = false [blocks.initialstages.fields.initialstages] type = "list" +optional = true [blocks.initialstages.fields.initialstages.item] type = "record" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.yaml index 596e271b..35ff8cb2 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-sfr.yaml @@ -367,6 +367,7 @@ blocks: fields: crosssections: type: list + optional: true item: type: record fields: @@ -432,6 +433,7 @@ blocks: fields: diversions: type: list + optional: true item: type: record fields: @@ -491,6 +493,7 @@ blocks: fields: initialstages: type: list + optional: true item: type: record fields: diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.json index d94b29d3..d04e2c78 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.json @@ -115,7 +115,8 @@ "outer_dvclose": { "type": "double", "longname": "dependent-variable change criterion", - "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the outer (nonlinear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to OUTER_DVCLOSE, iteration stops. Commonly, OUTER_DVCLOSE equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error." + "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the outer (nonlinear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to OUTER_DVCLOSE, iteration stops. Commonly, OUTER_DVCLOSE equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error.", + "optional": true }, "outer_rclosebnd": { "type": "double", @@ -127,7 +128,8 @@ "outer_maximum": { "type": "integer", "longname": "outer maximum iterations", - "description": "integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1." + "description": "integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1.", + "optional": true }, "under_relaxation": { "type": "string", @@ -190,7 +192,8 @@ "inner_maximum": { "type": "integer", "longname": "maximum number of inner iterations", - "description": "integer value defining the maximum number of inner (linear) iterations. The number typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems." + "description": "integer value defining the maximum number of inner (linear) iterations. The number typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems.", + "optional": true }, "inner_hclose": { "type": "double", @@ -202,11 +205,13 @@ "inner_dvclose": { "type": "double", "longname": "dependent-variable change tolerance", - "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the inner (linear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to INNER_DVCLOSE, the matrix solver assumes convergence. Commonly, INNER_DVCLOSE is set equal to or an order of magnitude less than the OUTER_DVCLOSE value specified for the NONLINEAR block. The keyword, INNER_HCLOSE can be still be specified instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error." + "description": "real value defining the dependent-variable (for example, head) change criterion for convergence of the inner (linear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to INNER_DVCLOSE, the matrix solver assumes convergence. Commonly, INNER_DVCLOSE is set equal to or an order of magnitude less than the OUTER_DVCLOSE value specified for the NONLINEAR block. The keyword, INNER_HCLOSE can be still be specified instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error.", + "optional": true }, "rcloserecord": { "type": "record", "longname": "rclose record", + "optional": true, "fields": { "inner_rclose": { "type": "double", @@ -225,7 +230,8 @@ "linear_acceleration": { "type": "string", "longname": "linear acceleration method", - "description": "a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method." + "description": "a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method.", + "optional": true }, "relaxation_factor": { "type": "double", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.toml index 56c3d630..e7d64940 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.toml @@ -101,6 +101,7 @@ deprecated = "6.1.1" type = "double" longname = "dependent-variable change criterion" description = "real value defining the dependent-variable (for example, head) change criterion for convergence of the outer (nonlinear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to OUTER_DVCLOSE, iteration stops. Commonly, OUTER_DVCLOSE equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error." +optional = true [blocks.nonlinear.fields.outer_rclosebnd] type = "double" @@ -113,6 +114,7 @@ deprecated = "6.1.1" type = "integer" longname = "outer maximum iterations" description = "integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1." +optional = true [blocks.nonlinear.fields.under_relaxation] type = "string" @@ -172,6 +174,7 @@ optional = true type = "integer" longname = "maximum number of inner iterations" description = "integer value defining the maximum number of inner (linear) iterations. The number typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems." +optional = true [blocks.linear.fields.inner_hclose] type = "double" @@ -184,10 +187,12 @@ deprecated = "6.1.1" type = "double" longname = "dependent-variable change tolerance" description = "real value defining the dependent-variable (for example, head) change criterion for convergence of the inner (linear) iterations, in units of the dependent-variable (for example, length for head). When the maximum absolute value of the dependent-variable change at all nodes during an iteration is less than or equal to INNER_DVCLOSE, the matrix solver assumes convergence. Commonly, INNER_DVCLOSE is set equal to or an order of magnitude less than the OUTER_DVCLOSE value specified for the NONLINEAR block. The keyword, INNER_HCLOSE can be still be specified instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error." +optional = true [blocks.linear.fields.rcloserecord] type = "record" longname = "rclose record" +optional = true [blocks.linear.fields.rcloserecord.fields.inner_rclose] type = "double" @@ -205,6 +210,7 @@ tagged = false type = "string" longname = "linear acceleration method" description = "a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method." +optional = true [blocks.linear.fields.relaxation_factor] type = "double" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.yaml index 9f2d5fcc..7c5e60dd 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/sln-ims.yaml @@ -161,6 +161,7 @@ blocks: equals 0.01. The keyword, OUTER_HCLOSE can be still be specified instead of OUTER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually OUTER_HCLOSE will be deprecated and specification of OUTER_HCLOSE will cause MODFLOW 6 to terminate with an error. + optional: true outer_rclosebnd: type: double longname: boundary package flow residual tolerance @@ -179,6 +180,7 @@ blocks: longname: outer maximum iterations description: integer value defining the maximum number of outer (nonlinear) iterations -- that is, calls to the solution routine. For a linear problem OUTER_MAXIMUM should be 1. + optional: true under_relaxation: type: string longname: under relaxation scheme @@ -288,6 +290,7 @@ blocks: typically depends on the characteristics of the matrix solution scheme being used. For nonlinear problems, INNER_MAXIMUM usually ranges from 60 to 600; a value of 100 will be sufficient for most linear problems. + optional: true inner_hclose: type: double longname: head change tolerance @@ -312,9 +315,11 @@ blocks: instead of INNER_DVCLOSE for backward compatibility with previous versions of MODFLOW 6 but eventually INNER_HCLOSE will be deprecated and specification of INNER_HCLOSE will cause MODFLOW 6 to terminate with an error. + optional: true rcloserecord: type: record longname: rclose record + optional: true fields: inner_rclose: type: double @@ -352,6 +357,7 @@ blocks: description: a keyword that defines the linear acceleration method used by the default IMS linear solvers. CG - preconditioned conjugate gradient method. BICGSTAB - preconditioned bi-conjugate gradient stabilized method. + optional: true relaxation_factor: type: double longname: relaxation factor used by ILU factorization diff --git a/autotest/dfns/test_migrate.py b/autotest/dfns/test_migrate.py index 9d8eae8c..657dda84 100644 --- a/autotest/dfns/test_migrate.py +++ b/autotest/dfns/test_migrate.py @@ -265,6 +265,25 @@ def test_migrate_list_shape_bounds(dfn_dir, name, block, shape): assert next(f for f in fields.values() if isinstance(f, v2.List)).shape == shape +@pytest.mark.parametrize( + "name,optional,required", + [ + ( + "gwf-sfr", + ["crosssections", "diversions", "initialstages"], + ["packagedata", "connectiondata"], + ), + ("gwf-lak", ["outlets"], ["packagedata", "connectiondata"]), + ("sln-ims", ["nonlinear", "linear"], []), + ], +) +def test_migrate_optional_blocks(dfn_dir, name, optional, required): + # MF6 reads these with blockRequired=.false. though v1 marks fields required + component = _migrate_dev3(dfn_dir, name) + assert all(component.blocks[b].optional for b in optional) + assert not any(component.blocks[b].optional for b in required) + + def test_migrate_ts_shapes(dfn_dir): component = _migrate_dev3(dfn_dir, "utl-ts") attrs = component.blocks["attributes"].fields diff --git a/modflow_devtools/dfns/migrate_to_v2_0_0_dev2.py b/modflow_devtools/dfns/migrate_to_v2_0_0_dev2.py index c6fd25b8..0d73b0ef 100644 --- a/modflow_devtools/dfns/migrate_to_v2_0_0_dev2.py +++ b/modflow_devtools/dfns/migrate_to_v2_0_0_dev2.py @@ -1209,6 +1209,45 @@ def _fix_ssm_sources_write_if_empty(name: str, blocks: dict[str, v2.Block]) -> d return {**blocks, "sources": sources.model_copy(update={"write_if_empty": True})} +# Blocks MF6 reads with `GetBlock(..., blockRequired=.false.)` though their v1 +# fields aren't all marked optional, so the derived `Block.optional` comes out +# false. SFR CROSSSECTIONS/DIVERSIONS/INITIALSTAGES (gwf-sfr.f90) and LAK +# OUTLETS (gwf-lak.f90) each hold one list; IMS NONLINEAR (NumericalSolution.f90) +# and LINEAR (ImsLinearSettings.f90) hold settings that all have defaults. +# SFR DIVERSIONS and LAK OUTLETS are really conditional: present iff +# `sum(packagedata.ndv) > 0` and `noutlets > 0` respectively. There's no way to +# say that yet; optional is enough for a writer that omits empty blocks. +_OPTIONAL_BLOCKS: dict[str, tuple[str, ...]] = { + "gwf-sfr": ("crosssections", "diversions", "initialstages"), + "gwf-lak": ("outlets",), + "sln-ims": ("nonlinear", "linear"), +} + + +def _fix_optional_blocks(name: str, blocks: dict[str, v2.Block]) -> dict[str, v2.Block]: + """ + Mark every field in each of `_OPTIONAL_BLOCKS` optional, making the block + optional. Without this, a writer that emits required blocks even when empty + writes e.g. an empty SFR DIVERSIONS block, which MF6 rejects. Once modflow6 + marks these fields `optional true`, this stopgap can be removed. + """ + block_names = _OPTIONAL_BLOCKS.get(name) + if not block_names: + return blocks + missing = [b for b in block_names if b not in blocks] + if missing: + raise ValueError(f"{name}: expected optional block(s) not found: {missing!r}") + new_blocks = dict(blocks) + for b in block_names: + block = blocks[b] + fields = { + k: f if f.optional else f.model_copy(update={"optional": True}) + for k, f in block.fields.items() + } + new_blocks[b] = block.model_copy(update={"fields": fields}) + return new_blocks + + # List dims that MF6 treats as an upper bound (it reads up to that many rows and # uses the count read), though v1 doesn't mark them `<`. Each is confirmed in the # MF6 source: IDM's period list loader sets `nbound` from the rows read (every @@ -1852,6 +1891,7 @@ def _lookup(rname: str) -> dict | None: blocks = _fix_lak_relations(name, blocks) blocks = _fix_mvr_relations(name, blocks) blocks = _fix_ssm_sources_write_if_empty(name, blocks) + blocks = _fix_optional_blocks(name, blocks) blocks = _wrap_grid_aux(blocks, fields) # Must run after `_fix_lak_relations`: LAK's period `number` field looks # exactly like a lonely pk (leading required Integer, no fk) before it's