diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.json index 26d8a078..a41333ea 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.json @@ -76,14 +76,14 @@ "longname": "cellid of cell in surface water model", "description": "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of cell in groundwater model", "description": "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "bedleak": { "type": "double", @@ -105,5 +105,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.toml index 2240fce2..a260236d 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.toml @@ -65,14 +65,14 @@ type = "cellid" longname = "cellid of cell in surface water model" description = "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of cell in groundwater model" description = "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.bedleak] type = "double" @@ -85,3 +85,9 @@ type = "double" longname = "factor used for conductance calculation" description = "is the factor used for the conductance calculation. The definition for this parameter depends the type of surface water model and whether or not the fixed_conductance option is specified. If the fixed_conductance option is specified, then the hydraulic conductance is calculated as the product of bedleak and cfact. In this case, the conductance is fixed and does not change as a function of the calculated surface water and groundwater head. If the fixed_conductance option is not specified, then the definition of cfact depends on whether the surface water model represents one-dimensional channel flow or two-dimensional overland flow. If the surface water model represents one-dimensional channel flow, then cfact is the length of the channel cell in the groundwater model cell. If the surface water model represents two-dimensional overland flow, then cfact is the intersection area of the overland flow cell and the underlying groundwater model cell." tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.yaml index 9981c4da..7eb9c004 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-chfgwf.yaml @@ -71,7 +71,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of cell in groundwater model @@ -81,7 +81,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 bedleak: type: double longname: bed leakance @@ -106,3 +106,8 @@ blocks: tagged: false shape: - nexg +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.json index 0d4dbf2c..e9257826 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.json @@ -150,14 +150,14 @@ "longname": "cellid of first cell", "description": "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of second cell", "description": "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "ihc": { "type": "integer", @@ -210,5 +210,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.toml index 76ecd1e6..9c7259a4 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.toml @@ -137,14 +137,14 @@ type = "cellid" longname = "cellid of first cell" description = "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of second cell" description = "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.ihc] type = "integer" @@ -188,3 +188,9 @@ description = "name of the GWE Exchange cell. BOUNDNAME is an ASCII character v optional = true when = "boundnames" tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.yaml index 343477c9..cd8b9c22 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwegwe.yaml @@ -151,7 +151,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of second cell @@ -161,7 +161,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 ihc: type: integer longname: integer flag for connection type @@ -211,3 +211,8 @@ blocks: tagged: false shape: - nexg +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.json index 4d4a3320..002e624a 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.json @@ -4,5 +4,13 @@ "name": "exg-gwfgwe", "parent": "sim-nam", "ftype": "GWF6-GWE6", - "subtype": "exchange" + "subtype": "exchange", + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } + } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.toml index 1888ec52..c6153d40 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.toml @@ -4,3 +4,9 @@ name = "exg-gwfgwe" parent = "sim-nam" ftype = "GWF6-GWE6" subtype = "exchange" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.yaml index cf70ac4b..9ef5d9f7 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwe.yaml @@ -4,3 +4,8 @@ name: exg-gwfgwe parent: sim-nam ftype: GWF6-GWE6 subtype: exchange +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.json index cf30b081..3816ef5e 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.json @@ -104,7 +104,11 @@ "description": "is the file name for ghost node correction input file. Information for the ghost nodes are provided in the file provided with these keywords. The format for specifying the ghost nodes is the same as described for the GNC Package of the GWF Model. This includes specifying OPTIONS, DIMENSIONS, and GNCDATA blocks. The order of the ghost nodes must follow the same order as the order of the cells in the EXCHANGEDATA block. For the GNCDATA, noden and all of the nodej values are assumed to be located in model 1, and nodem is assumed to be in model 2.", "tagged": false, "direction": "in", - "component": "gwf-gnc" + "component": "gwf-gnc", + "args": { + "model1": "model1", + "model2": "model2" + } } } }, @@ -123,7 +127,11 @@ "description": "is the file name of the water mover input file to apply to this exchange. Information for the water mover are provided in the file provided with these keywords. The format for specifying the water mover information is the same as described for the Water Mover (MVR) Package of the GWF Model, with two exceptions. First, in the PACKAGES block, the model name must be included as a separate string before each package. Second, the appropriate model name must be included before package name 1 and package name 2 in the BEGIN PERIOD block. This allows providers and receivers to be located in both models listed as part of this exchange.", "tagged": false, "direction": "in", - "component": "gwf-mvr" + "component": "gwf-mvr", + "args": { + "model1": "model1", + "model2": "model2" + } } } }, @@ -178,14 +186,14 @@ "longname": "cellid of first cell", "description": "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of second cell", "description": "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "ihc": { "type": "integer", @@ -238,5 +246,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.toml index 974ecfed..fdf6bf47 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.toml @@ -99,6 +99,10 @@ tagged = false direction = "in" component = "gwf-gnc" +[blocks.options.fields.gnc_filerecord.fields.gnc6_filename.args] +model1 = "model1" +model2 = "model2" + [blocks.options.fields.mvr_filerecord] type = "record" optional = true @@ -116,6 +120,10 @@ tagged = false direction = "in" component = "gwf-mvr" +[blocks.options.fields.mvr_filerecord.fields.mvr6_filename.args] +model1 = "model1" +model2 = "model2" + [blocks.options.fields.obs_filerecord] type = "record" optional = true @@ -161,14 +169,14 @@ type = "cellid" longname = "cellid of first cell" description = "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of second cell" description = "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.ihc] type = "integer" @@ -212,3 +220,9 @@ description = "name of the GWF Exchange cell. BOUNDNAME is an ASCII character v optional = true when = "boundnames" tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.yaml index 2cbb5a16..1968c079 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwf.yaml @@ -127,6 +127,9 @@ blocks: tagged: false direction: in component: gwf-gnc + args: + model1: model1 + model2: model2 mvr_filerecord: type: record optional: true @@ -149,6 +152,9 @@ blocks: tagged: false direction: in component: gwf-mvr + args: + model1: model1 + model2: model2 obs_filerecord: type: record optional: true @@ -197,7 +203,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of second cell @@ -207,7 +213,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 ihc: type: integer longname: integer flag for connection type @@ -257,3 +263,8 @@ blocks: tagged: false shape: - nexg +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.json index fd252853..aabddead 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.json @@ -4,5 +4,13 @@ "name": "exg-gwfgwt", "parent": "sim-nam", "ftype": "GWF6-GWT6", - "subtype": "exchange" + "subtype": "exchange", + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } + } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.toml index b5e385ed..6581e3cc 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.toml @@ -4,3 +4,9 @@ name = "exg-gwfgwt" parent = "sim-nam" ftype = "GWF6-GWT6" subtype = "exchange" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.yaml index a90667d7..8e5fc8da 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfgwt.yaml @@ -4,3 +4,8 @@ name: exg-gwfgwt parent: sim-nam ftype: GWF6-GWT6 subtype: exchange +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.json index 1d9da0b6..6c038299 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.json @@ -4,5 +4,13 @@ "name": "exg-gwfprt", "parent": "sim-nam", "ftype": "GWF6-PRT6", - "subtype": "exchange" + "subtype": "exchange", + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } + } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.toml index 505a1090..c54c6ce4 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.toml @@ -4,3 +4,9 @@ name = "exg-gwfprt" parent = "sim-nam" ftype = "GWF6-PRT6" subtype = "exchange" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.yaml index d02cf7c7..6ca624c3 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwfprt.yaml @@ -4,3 +4,8 @@ name: exg-gwfprt parent: sim-nam ftype: GWF6-PRT6 subtype: exchange +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.json index 950a8f28..73797ab4 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.json @@ -150,14 +150,14 @@ "longname": "cellid of first cell", "description": "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of second cell", "description": "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "ihc": { "type": "integer", @@ -210,5 +210,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.toml index cfa5d7d7..c90cdde7 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.toml @@ -137,14 +137,14 @@ type = "cellid" longname = "cellid of first cell" description = "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of second cell" description = "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.ihc] type = "integer" @@ -188,3 +188,9 @@ description = "name of the GWT Exchange cell. BOUNDNAME is an ASCII character v optional = true when = "boundnames" tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.yaml index 37d00716..8a52df27 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-gwtgwt.yaml @@ -149,7 +149,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of second cell @@ -159,7 +159,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 ihc: type: integer longname: integer flag for connection type @@ -209,3 +209,8 @@ blocks: tagged: false shape: - nexg +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.json index 08e186fa..e976d0cc 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.json @@ -76,14 +76,14 @@ "longname": "cellid of cell in surface water model", "description": "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of cell in groundwater model", "description": "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "bedleak": { "type": "double", @@ -105,5 +105,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.toml index f047cff7..14695f9c 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.toml @@ -65,14 +65,14 @@ type = "cellid" longname = "cellid of cell in surface water model" description = "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of cell in groundwater model" description = "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.bedleak] type = "double" @@ -85,3 +85,9 @@ type = "double" longname = "factor used for conductance calculation" description = "is the factor used for the conductance calculation. The definition for this parameter depends the type of surface water model and whether or not the fixed_conductance option is specified. If the fixed_conductance option is specified, then the hydraulic conductance is calculated as the product of bedleak and cfact. In this case, the conductance is fixed and does not change as a function of the calculated surface water and groundwater head. If the fixed_conductance option is not specified, then the definition of cfact depends on whether the surface water model represents one-dimensional channel flow or two-dimensional overland flow. If the surface water model represents one-dimensional channel flow, then cfact is the length of the channel cell in the groundwater model cell. If the surface water model represents two-dimensional overland flow, then cfact is the intersection area of the overland flow cell and the underlying groundwater model cell." tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.yaml index a7366ea7..fab5e9c4 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/exg-olfgwf.yaml @@ -71,7 +71,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of cell in groundwater model @@ -81,7 +81,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 bedleak: type: double longname: bed leakance @@ -106,3 +106,8 @@ blocks: tagged: false shape: - nexg +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.json index af22159a..4139f8ec 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.json @@ -93,7 +93,10 @@ "component": [ "utl-spc", "utl-spca" - ] + ], + "args": { + "target": "pname" + } }, "mixed": { "type": "keyword", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.toml index 97584f46..7bc1ef8b 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.toml @@ -85,6 +85,9 @@ component = [ "utl-spca", ] +[blocks.fileinput.fields.fileinput.item.fields.spc6_filename.args] +target = "pname" + [blocks.fileinput.fields.fileinput.item.fields.mixed] type = "keyword" longname = "mixed keyword" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.yaml index 3e551e69..e7ebd4de 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwe-ssm.yaml @@ -106,6 +106,8 @@ blocks: component: - utl-spc - utl-spca + args: + target: pname mixed: type: keyword longname: mixed keyword diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.json index 8bff6db7..82a22591 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.json @@ -65,14 +65,14 @@ "longname": "GNC cellid n", "description": "is the cellid of the cell, $n$, in which the ghost node is located. For a structured grid that uses the DIS input file, CELLIDN is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDN is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDN is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm": { "type": "cellid", "longname": "GNC cellid n", "description": "is the cellid of the connecting cell, $m$, to which flow occurs from the ghost node. For a structured grid that uses the DIS input file, CELLIDM is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "cellidsj": { "type": "array", @@ -80,7 +80,7 @@ "description": "is the array of CELLIDS for the contributing j cells, which contribute to the interpolated head value at the ghost node. This item contains one CELLID for each of the contributing cells of the ghost node. Note that if the number of actual contributing cells needed by the user is less than NUMALPHAJ for any ghost node, then a dummy CELLID of zero(s) should be inserted with an associated contributing factor of zero. For a structured grid that uses the DIS input file, CELLID is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLID is the layer number and cell2d number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLID is the node number for the cell.", "tagged": false, "dtype": "cellid", - "model": "1", + "model": "model1", "allow_zero": true, "shape": [ "numalphaj" @@ -104,5 +104,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.toml index 3d942779..dac6a150 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.toml @@ -56,14 +56,14 @@ type = "cellid" longname = "GNC cellid n" description = "is the cellid of the cell, $n$, in which the ghost node is located. For a structured grid that uses the DIS input file, CELLIDN is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDN is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDN is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.gncdata.fields.gncdata.item.fields.cellidm] type = "cellid" longname = "GNC cellid n" description = "is the cellid of the connecting cell, $m$, to which flow occurs from the ghost node. For a structured grid that uses the DIS input file, CELLIDM is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.gncdata.fields.gncdata.item.fields.cellidsj] type = "array" @@ -71,7 +71,7 @@ longname = "GNC contributing cells" description = "is the array of CELLIDS for the contributing j cells, which contribute to the interpolated head value at the ghost node. This item contains one CELLID for each of the contributing cells of the ghost node. Note that if the number of actual contributing cells needed by the user is less than NUMALPHAJ for any ghost node, then a dummy CELLID of zero(s) should be inserted with an associated contributing factor of zero. For a structured grid that uses the DIS input file, CELLID is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLID is the layer number and cell2d number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLID is the node number for the cell." tagged = false dtype = "cellid" -model = "1" +model = "model1" allow_zero = true shape = [ "numalphaj", @@ -86,3 +86,9 @@ dtype = "double" shape = [ "numalphaj", ] + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.yaml index ecf14697..45cd369d 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-gnc.yaml @@ -65,7 +65,7 @@ blocks: number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDN is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm: type: cellid longname: GNC cellid n @@ -75,7 +75,7 @@ blocks: layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM is the node number for the cell. tagged: false - model: '2' + model: model2 cellidsj: type: array longname: GNC contributing cells @@ -90,7 +90,7 @@ blocks: CELLID is the node number for the cell. tagged: false dtype: cellid - model: '1' + model: model1 allow_zero: true shape: - numalphaj @@ -109,3 +109,8 @@ blocks: - numalphaj shape: - numgnc +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.json index 9d865de7..bd68e062 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.json @@ -34,7 +34,7 @@ "type": "keyword", "longname": "precede all package names with model names", "description": "keyword to indicate that all package names will be preceded by the model name for the package. Model names are required when the Mover Package is used with a GWF-GWF Exchange. The MODELNAME keyword should not be used for a Mover Package that is for a single GWF Model.", - "optional": true + "when": "model2" }, "budget_filerecord": { "type": "record", @@ -102,8 +102,10 @@ "when": "modelnames", "tagged": false, "fk": { - "path": "sim-nam.models.mname", - "rule": "exchange_models" + "param": [ + "model1", + "model2" + ] } }, "pname": { @@ -141,8 +143,10 @@ "when": "modelnames", "tagged": false, "fk": { - "path": "sim-nam.models.mname", - "rule": "exchange_models" + "param": [ + "model1", + "model2" + ] } }, "pname1": { @@ -172,8 +176,10 @@ "when": "modelnames", "tagged": false, "fk": { - "path": "sim-nam.models.mname", - "rule": "exchange_models" + "param": [ + "model1", + "model2" + ] } }, "pname2": { @@ -226,5 +232,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.toml index 11ed5f52..1d2f44de 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.toml @@ -29,7 +29,7 @@ optional = true type = "keyword" longname = "precede all package names with model names" description = "keyword to indicate that all package names will be preceded by the model name for the package. Model names are required when the Mover Package is used with a GWF-GWF Exchange. The MODELNAME keyword should not be used for a Mover Package that is for a single GWF Model." -optional = true +when = "model2" [blocks.options.fields.budget_filerecord] type = "record" @@ -90,8 +90,10 @@ when = "modelnames" tagged = false [blocks.packages.fields.packages.item.fields.mname.fk] -path = "sim-nam.models.mname" -rule = "exchange_models" +param = [ + "model1", + "model2", +] [blocks.packages.fields.packages.item.fields.pname] type = "string" @@ -120,8 +122,10 @@ when = "modelnames" tagged = false [blocks.period.fields.stress_period_data.item.fields.mname1.fk] -path = "sim-nam.models.mname" -rule = "exchange_models" +param = [ + "model1", + "model2", +] [blocks.period.fields.stress_period_data.item.fields.pname1] type = "string" @@ -151,8 +155,10 @@ when = "modelnames" tagged = false [blocks.period.fields.stress_period_data.item.fields.mname2.fk] -path = "sim-nam.models.mname" -rule = "exchange_models" +param = [ + "model1", + "model2", +] [blocks.period.fields.stress_period_data.item.fields.pname2] type = "string" @@ -198,3 +204,9 @@ longname = "stress period number" description = "integer value specifying the starting stress period number for which the data specified in the PERIOD block apply. IPER must be less than or equal to NPER in the TDIS Package and greater than zero. The IPER value assigned to a stress period block must be greater than the IPER value assigned for the previous PERIOD block. The information specified in the PERIOD block will continue to apply for all subsequent stress periods, unless the program encounters another PERIOD block." tagged = false index = true + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.yaml index 62fe8f78..ee851d04 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwf-mvr.yaml @@ -33,7 +33,7 @@ blocks: description: keyword to indicate that all package names will be preceded by the model name for the package. Model names are required when the Mover Package is used with a GWF-GWF Exchange. The MODELNAME keyword should not be used for a Mover Package that is for a single GWF Model. - optional: true + when: model2 budget_filerecord: type: record optional: true @@ -90,8 +90,9 @@ blocks: when: modelnames tagged: false fk: - path: sim-nam.models.mname - rule: exchange_models + param: + - model1 + - model2 pname: type: string description: is the name of a package that may be included in a subsequent stress period @@ -124,8 +125,9 @@ blocks: when: modelnames tagged: false fk: - path: sim-nam.models.mname - rule: exchange_models + param: + - model1 + - model2 pname1: type: string longname: provider package name @@ -158,8 +160,9 @@ blocks: when: modelnames tagged: false fk: - path: sim-nam.models.mname - rule: exchange_models + param: + - model1 + - model2 pname2: type: string longname: receiver package name @@ -211,3 +214,8 @@ blocks: another PERIOD block. tagged: false index: true +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.json index 40c27d04..06203744 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.json @@ -93,7 +93,10 @@ "component": [ "utl-spc", "utl-spca" - ] + ], + "args": { + "target": "pname" + } }, "mixed": { "type": "keyword", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.toml index a3de2451..4ddc3b15 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.toml @@ -85,6 +85,9 @@ component = [ "utl-spca", ] +[blocks.fileinput.fields.fileinput.item.fields.spc6_filename.args] +target = "pname" + [blocks.fileinput.fields.fileinput.item.fields.mixed] type = "keyword" longname = "mixed keyword" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.yaml index 387c47ab..1363a057 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/gwt-ssm.yaml @@ -106,6 +106,8 @@ blocks: component: - utl-spc - utl-spca + args: + target: pname mixed: type: keyword longname: mixed keyword diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.json index 57e4ccb1..3cbb890b 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.json @@ -141,7 +141,11 @@ "direction": "in", "mode_keyword": false, "component": "exchange", - "component_ftype": "exgtype" + "component_ftype": "exgtype", + "args": { + "model1": "exgmnamea", + "model2": "exgmnameb" + } }, "exgmnamea": { "type": "string", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.toml index 06a6ce00..c1f8d447 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.toml @@ -125,6 +125,10 @@ mode_keyword = false component = "exchange" component_ftype = "exgtype" +[blocks.exchanges.fields.exchanges.item.fields.exgfile.args] +model1 = "exgmnamea" +model2 = "exgmnameb" + [blocks.exchanges.fields.exchanges.item.fields.exgmnamea] type = "string" longname = "name of model A" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.yaml index 1f877d06..f92a4536 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/sim-nam.yaml @@ -131,6 +131,9 @@ blocks: mode_keyword: false component: exchange component_ftype: exgtype + args: + model1: exgmnamea + model2: exgmnameb exgmnamea: type: string longname: name of model A diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.json index a888b61c..bc6735eb 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.json @@ -74,7 +74,7 @@ "pk": true, "fk": { "key": "bound", - "rule": "ssm_package" + "param": "target" } }, "spcsetting": { @@ -115,5 +115,11 @@ } } } + }, + "params": { + "target": { + "type": "package", + "rule": "no_advanced_transport" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.toml index 19694fc9..9fd4cc30 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.toml @@ -64,7 +64,7 @@ pk = true [blocks.period.fields.stress_period_data.item.fields.bndno.fk] key = "bound" -rule = "ssm_package" +param = "target" [blocks.period.fields.stress_period_data.item.fields.spcsetting] type = "union" @@ -93,3 +93,7 @@ longname = "stress period number" description = "integer value specifying the starting stress period number for which the data specified in the PERIOD block apply. IPER must be less than or equal to NPER in the TDIS Package and greater than zero. The IPER value assigned to a stress period block must be greater than the IPER value assigned for the previous PERIOD block. The information specified in the PERIOD block will continue to apply for all subsequent stress periods, unless the program encounters another PERIOD block." tagged = false index = true + +[params.target] +type = "package" +rule = "no_advanced_transport" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.yaml index 6401b1dc..84d1658d 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spc.yaml @@ -65,7 +65,7 @@ blocks: pk: true fk: key: bound - rule: ssm_package + param: target spcsetting: type: union description: 'line of information that is parsed into a keyword and values. Keyword values @@ -104,3 +104,7 @@ blocks: another PERIOD block. tagged: false index: true +params: + target: + type: package + rule: no_advanced_transport diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.json b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.json index fe85851a..9374ad11 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.json @@ -87,5 +87,14 @@ } } } + }, + "params": { + "target": { + "rule": "no_advanced_transport", + "type": [ + "gwf-rcha", + "gwf-evta" + ] + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.toml b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.toml index ede376ee..aa43cdd0 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.toml @@ -75,3 +75,10 @@ longname = "stress period number" description = "integer value specifying the starting stress period number for which the data specified in the PERIOD block apply. IPER must be less than or equal to NPER in the TDIS Package and greater than zero. The IPER value assigned to a stress period block must be greater than the IPER value assigned for the previous PERIOD block. The information specified in the PERIOD block will continue to apply for all subsequent stress periods, unless the program encounters another PERIOD block." tagged = false index = true + +[params.target] +rule = "no_advanced_transport" +type = [ + "gwf-rcha", + "gwf-evta", +] diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.yaml index fd07e6f1..a9a2c16f 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev2/utl-spca.yaml @@ -84,3 +84,9 @@ blocks: another PERIOD block. tagged: false index: true +params: + target: + rule: no_advanced_transport + type: + - gwf-rcha + - gwf-evta diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.json index 443459ad..9b44b914 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.json @@ -76,14 +76,14 @@ "longname": "cellid of cell in surface water model", "description": "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of cell in groundwater model", "description": "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "bedleak": { "type": "double", @@ -167,5 +167,13 @@ "nexg" ] } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.toml index de1fa892..4cefb01b 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.toml @@ -65,14 +65,14 @@ type = "cellid" longname = "cellid of cell in surface water model" description = "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of cell in groundwater model" description = "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.bedleak] type = "double" @@ -145,3 +145,9 @@ dtype = "double" shape = [ "nexg", ] + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.yaml index 7223260f..0467815c 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-chfgwf.yaml @@ -71,7 +71,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of cell in groundwater model @@ -81,7 +81,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 bedleak: type: double longname: bed leakance @@ -157,3 +157,8 @@ memory: dtype: double shape: - nexg +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.json index abe47cdf..d5989069 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.json @@ -153,14 +153,14 @@ "longname": "cellid of first cell", "description": "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of second cell", "description": "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "ihc": { "type": "integer", @@ -344,5 +344,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.toml index 70642d4b..0f5d0308 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.toml @@ -140,14 +140,14 @@ type = "cellid" longname = "cellid of first cell" description = "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of second cell" description = "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.ihc] type = "integer" @@ -316,3 +316,9 @@ index = true type = "string" when = "boundnames" tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.yaml index afa9e9c7..0688166e 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwegwe.yaml @@ -153,7 +153,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of second cell @@ -163,7 +163,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 ihc: type: integer longname: integer flag for connection type @@ -318,3 +318,8 @@ observations: type: string when: boundnames tagged: false +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.json index 84441358..533c1619 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.json @@ -4,5 +4,13 @@ "name": "exg-gwfgwe", "parent": "sim-nam", "ftype": "GWF6-GWE6", - "subtype": "exchange" + "subtype": "exchange", + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } + } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.toml index 937210bd..363556d6 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.toml @@ -4,3 +4,9 @@ name = "exg-gwfgwe" parent = "sim-nam" ftype = "GWF6-GWE6" subtype = "exchange" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.yaml index febe5521..4696d54f 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwe.yaml @@ -4,3 +4,8 @@ name: exg-gwfgwe parent: sim-nam ftype: GWF6-GWE6 subtype: exchange +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.json index ae1535f9..ffeff2fb 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.json @@ -107,7 +107,11 @@ "description": "is the file name for ghost node correction input file. Information for the ghost nodes are provided in the file provided with these keywords. The format for specifying the ghost nodes is the same as described for the GNC Package of the GWF Model. This includes specifying OPTIONS, DIMENSIONS, and GNCDATA blocks. The order of the ghost nodes must follow the same order as the order of the cells in the EXCHANGEDATA block. For the GNCDATA, noden and all of the nodej values are assumed to be located in model 1, and nodem is assumed to be in model 2.", "tagged": false, "direction": "in", - "component": "gwf-gnc" + "component": "gwf-gnc", + "args": { + "model1": "model1", + "model2": "model2" + } } } }, @@ -126,7 +130,11 @@ "description": "is the file name of the water mover input file to apply to this exchange. Information for the water mover are provided in the file provided with these keywords. The format for specifying the water mover information is the same as described for the Water Mover (MVR) Package of the GWF Model, with two exceptions. First, in the PACKAGES block, the model name must be included as a separate string before each package. Second, the appropriate model name must be included before package name 1 and package name 2 in the BEGIN PERIOD block. This allows providers and receivers to be located in both models listed as part of this exchange.", "tagged": false, "direction": "in", - "component": "gwf-mvr" + "component": "gwf-mvr", + "args": { + "model1": "model1", + "model2": "model2" + } } } }, @@ -181,14 +189,14 @@ "longname": "cellid of first cell", "description": "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of second cell", "description": "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "ihc": { "type": "integer", @@ -372,5 +380,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.toml index 51620465..732abd11 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.toml @@ -102,6 +102,10 @@ tagged = false direction = "in" component = "gwf-gnc" +[blocks.options.fields.gnc_filerecord.fields.gnc6_filename.args] +model1 = "model1" +model2 = "model2" + [blocks.options.fields.mvr_filerecord] type = "record" optional = true @@ -119,6 +123,10 @@ tagged = false direction = "in" component = "gwf-mvr" +[blocks.options.fields.mvr_filerecord.fields.mvr6_filename.args] +model1 = "model1" +model2 = "model2" + [blocks.options.fields.obs_filerecord] type = "record" optional = true @@ -164,14 +172,14 @@ type = "cellid" longname = "cellid of first cell" description = "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of second cell" description = "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.ihc] type = "integer" @@ -340,3 +348,9 @@ index = true type = "string" when = "boundnames" tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.yaml index 8f0a0dab..d0defd04 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwf.yaml @@ -129,6 +129,9 @@ blocks: tagged: false direction: in component: gwf-gnc + args: + model1: model1 + model2: model2 mvr_filerecord: type: record optional: true @@ -151,6 +154,9 @@ blocks: tagged: false direction: in component: gwf-mvr + args: + model1: model1 + model2: model2 obs_filerecord: type: record optional: true @@ -199,7 +205,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of second cell @@ -209,7 +215,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 ihc: type: integer longname: integer flag for connection type @@ -364,3 +370,8 @@ observations: type: string when: boundnames tagged: false +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.json index bc5a0a97..d71f67e1 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.json @@ -4,5 +4,13 @@ "name": "exg-gwfgwt", "parent": "sim-nam", "ftype": "GWF6-GWT6", - "subtype": "exchange" + "subtype": "exchange", + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } + } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.toml index 7edb54fa..268377fa 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.toml @@ -4,3 +4,9 @@ name = "exg-gwfgwt" parent = "sim-nam" ftype = "GWF6-GWT6" subtype = "exchange" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.yaml index 293aa02e..5f2b0020 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfgwt.yaml @@ -4,3 +4,8 @@ name: exg-gwfgwt parent: sim-nam ftype: GWF6-GWT6 subtype: exchange +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.json index 4a6daa1e..2475d51e 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.json @@ -4,5 +4,13 @@ "name": "exg-gwfprt", "parent": "sim-nam", "ftype": "GWF6-PRT6", - "subtype": "exchange" + "subtype": "exchange", + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } + } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.toml index 02148582..a44d5711 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.toml @@ -4,3 +4,9 @@ name = "exg-gwfprt" parent = "sim-nam" ftype = "GWF6-PRT6" subtype = "exchange" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.yaml index 63e7eefb..ddf21fda 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwfprt.yaml @@ -4,3 +4,8 @@ name: exg-gwfprt parent: sim-nam ftype: GWF6-PRT6 subtype: exchange +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.json index b9eab8a7..628ac50d 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.json @@ -153,14 +153,14 @@ "longname": "cellid of first cell", "description": "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of second cell", "description": "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "ihc": { "type": "integer", @@ -344,5 +344,13 @@ } } } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.toml index 4e5d019e..ddf1209f 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.toml @@ -140,14 +140,14 @@ type = "cellid" longname = "cellid of first cell" description = "is the cellid of the cell in model 1 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of second cell" description = "is the cellid of the cell in model 2 as specified in the simulation name file. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.ihc] type = "integer" @@ -316,3 +316,9 @@ index = true type = "string" when = "boundnames" tagged = false + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.yaml index 00421ac0..bea2b9e6 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-gwtgwt.yaml @@ -151,7 +151,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of second cell @@ -161,7 +161,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 ihc: type: integer longname: integer flag for connection type @@ -316,3 +316,8 @@ observations: type: string when: boundnames tagged: false +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.json index df904839..1625d7ac 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.json @@ -76,14 +76,14 @@ "longname": "cellid of cell in surface water model", "description": "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm2": { "type": "cellid", "longname": "cellid of cell in groundwater model", "description": "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "bedleak": { "type": "double", @@ -167,5 +167,13 @@ "nexg" ] } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.toml index c07fa562..d5c66877 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.toml @@ -65,14 +65,14 @@ type = "cellid" longname = "cellid of cell in surface water model" description = "is the cellid of the cell in model 1, which must be the surface water model. For a structured grid that uses the DIS input file, CELLIDM1 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM1 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.exchangedata.fields.exchangedata.item.fields.cellidm2] type = "cellid" longname = "cellid of cell in groundwater model" description = "is the cellid of the cell in model 2, which must be the groundwater model. For a structured grid that uses the DIS input file, CELLIDM2 is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM2 is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.exchangedata.fields.exchangedata.item.fields.bedleak] type = "double" @@ -145,3 +145,9 @@ dtype = "double" shape = [ "nexg", ] + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.yaml index 8f632722..1f120793 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/exg-olfgwf.yaml @@ -71,7 +71,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM1 is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm2: type: cellid longname: cellid of cell in groundwater model @@ -81,7 +81,7 @@ blocks: number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM2 is the node number for the cell. tagged: false - model: '2' + model: model2 bedleak: type: double longname: bed leakance @@ -157,3 +157,8 @@ memory: dtype: double shape: - nexg +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.json index f4c36e06..e065c37d 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.json @@ -93,7 +93,10 @@ "component": [ "utl-spc", "utl-spca" - ] + ], + "args": { + "target": "pname" + } }, "mixed": { "type": "keyword", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.toml index f64d0117..cb8fdb5e 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.toml @@ -85,6 +85,9 @@ component = [ "utl-spca", ] +[blocks.fileinput.fields.fileinput.item.fields.spc6_filename.args] +target = "pname" + [blocks.fileinput.fields.fileinput.item.fields.mixed] type = "keyword" longname = "mixed keyword" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.yaml index ee5dea5d..98a143b3 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwe-ssm.yaml @@ -106,6 +106,8 @@ blocks: component: - utl-spc - utl-spca + args: + target: pname mixed: type: keyword longname: mixed keyword diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.json index e611512e..950d48b0 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.json @@ -65,14 +65,14 @@ "longname": "GNC cellid n", "description": "is the cellid of the cell, $n$, in which the ghost node is located. For a structured grid that uses the DIS input file, CELLIDN is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDN is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDN is the node number for the cell.", "tagged": false, - "model": "1" + "model": "model1" }, "cellidm": { "type": "cellid", "longname": "GNC cellid n", "description": "is the cellid of the connecting cell, $m$, to which flow occurs from the ghost node. For a structured grid that uses the DIS input file, CELLIDM is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM is the node number for the cell.", "tagged": false, - "model": "2" + "model": "model2" }, "cellidsj": { "type": "array", @@ -80,7 +80,7 @@ "description": "is the array of CELLIDS for the contributing j cells, which contribute to the interpolated head value at the ghost node. This item contains one CELLID for each of the contributing cells of the ghost node. Note that if the number of actual contributing cells needed by the user is less than NUMALPHAJ for any ghost node, then a dummy CELLID of zero(s) should be inserted with an associated contributing factor of zero. For a structured grid that uses the DIS input file, CELLID is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLID is the layer number and cell2d number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLID is the node number for the cell.", "tagged": false, "dtype": "cellid", - "model": "1", + "model": "model1", "allow_zero": true, "shape": [ "numalphaj" @@ -116,5 +116,13 @@ "source": "numalphaj", "type": "integer" } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.toml index 09c72084..2b581204 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.toml @@ -56,14 +56,14 @@ type = "cellid" longname = "GNC cellid n" description = "is the cellid of the cell, $n$, in which the ghost node is located. For a structured grid that uses the DIS input file, CELLIDN is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDN is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDN is the node number for the cell." tagged = false -model = "1" +model = "model1" [blocks.gncdata.fields.gncdata.item.fields.cellidm] type = "cellid" longname = "GNC cellid n" description = "is the cellid of the connecting cell, $m$, to which flow occurs from the ghost node. For a structured grid that uses the DIS input file, CELLIDM is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLIDM is the layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM is the node number for the cell." tagged = false -model = "2" +model = "model2" [blocks.gncdata.fields.gncdata.item.fields.cellidsj] type = "array" @@ -71,7 +71,7 @@ longname = "GNC contributing cells" description = "is the array of CELLIDS for the contributing j cells, which contribute to the interpolated head value at the ghost node. This item contains one CELLID for each of the contributing cells of the ghost node. Note that if the number of actual contributing cells needed by the user is less than NUMALPHAJ for any ghost node, then a dummy CELLID of zero(s) should be inserted with an associated contributing factor of zero. For a structured grid that uses the DIS input file, CELLID is the layer, row, and column numbers of the cell. For a grid that uses the DISV input file, CELLID is the layer number and cell2d number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLID is the node number for the cell." tagged = false dtype = "cellid" -model = "1" +model = "model1" allow_zero = true shape = [ "numalphaj", @@ -96,3 +96,9 @@ type = "integer" set_in = "ar" source = "numalphaj" type = "integer" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.yaml index bf7c0878..053b3a5b 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-gnc.yaml @@ -65,7 +65,7 @@ blocks: number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDN is the node number for the cell. tagged: false - model: '1' + model: model1 cellidm: type: cellid longname: GNC cellid n @@ -75,7 +75,7 @@ blocks: layer number and CELL2D number for the two cells. If the model uses the unstructured discretization (DISU) input file, then CELLIDM is the node number for the cell. tagged: false - model: '2' + model: model2 cellidsj: type: array longname: GNC contributing cells @@ -90,7 +90,7 @@ blocks: CELLID is the node number for the cell. tagged: false dtype: cellid - model: '1' + model: model1 allow_zero: true shape: - numalphaj @@ -118,3 +118,8 @@ memory: set_in: ar source: numalphaj type: integer +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.json index 8c8080c9..8d66c6df 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.json @@ -34,7 +34,7 @@ "type": "keyword", "longname": "precede all package names with model names", "description": "keyword to indicate that all package names will be preceded by the model name for the package. Model names are required when the Mover Package is used with a GWF-GWF Exchange. The MODELNAME keyword should not be used for a Mover Package that is for a single GWF Model.", - "optional": true + "when": "model2" }, "budget_filerecord": { "type": "record", @@ -102,8 +102,10 @@ "when": "modelnames", "tagged": false, "fk": { - "path": "sim-nam.models.mname", - "rule": "exchange_models" + "param": [ + "model1", + "model2" + ] } }, "pname": { @@ -141,8 +143,10 @@ "when": "modelnames", "tagged": false, "fk": { - "path": "sim-nam.models.mname", - "rule": "exchange_models" + "param": [ + "model1", + "model2" + ] } }, "pname1": { @@ -172,8 +176,10 @@ "when": "modelnames", "tagged": false, "fk": { - "path": "sim-nam.models.mname", - "rule": "exchange_models" + "param": [ + "model1", + "model2" + ] } }, "pname2": { @@ -238,5 +244,13 @@ "source": "maxpackages", "type": "integer" } + }, + "params": { + "model1": { + "type": "model" + }, + "model2": { + "type": "model" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.toml index 3e4ae664..76c43904 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.toml @@ -29,7 +29,7 @@ optional = true type = "keyword" longname = "precede all package names with model names" description = "keyword to indicate that all package names will be preceded by the model name for the package. Model names are required when the Mover Package is used with a GWF-GWF Exchange. The MODELNAME keyword should not be used for a Mover Package that is for a single GWF Model." -optional = true +when = "model2" [blocks.options.fields.budget_filerecord] type = "record" @@ -90,8 +90,10 @@ when = "modelnames" tagged = false [blocks.packages.fields.packages.item.fields.mname.fk] -path = "sim-nam.models.mname" -rule = "exchange_models" +param = [ + "model1", + "model2", +] [blocks.packages.fields.packages.item.fields.pname] type = "string" @@ -120,8 +122,10 @@ when = "modelnames" tagged = false [blocks.period.fields.stress_period_data.item.fields.mname1.fk] -path = "sim-nam.models.mname" -rule = "exchange_models" +param = [ + "model1", + "model2", +] [blocks.period.fields.stress_period_data.item.fields.pname1] type = "string" @@ -151,8 +155,10 @@ when = "modelnames" tagged = false [blocks.period.fields.stress_period_data.item.fields.mname2.fk] -path = "sim-nam.models.mname" -rule = "exchange_models" +param = [ + "model1", + "model2", +] [blocks.period.fields.stress_period_data.item.fields.pname2] type = "string" @@ -208,3 +214,9 @@ type = "integer" set_in = "ar" source = "maxpackages" type = "integer" + +[params.model1] +type = "model" + +[params.model2] +type = "model" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.yaml index 5077a720..1a7ce446 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwf-mvr.yaml @@ -33,7 +33,7 @@ blocks: description: keyword to indicate that all package names will be preceded by the model name for the package. Model names are required when the Mover Package is used with a GWF-GWF Exchange. The MODELNAME keyword should not be used for a Mover Package that is for a single GWF Model. - optional: true + when: model2 budget_filerecord: type: record optional: true @@ -90,8 +90,9 @@ blocks: when: modelnames tagged: false fk: - path: sim-nam.models.mname - rule: exchange_models + param: + - model1 + - model2 pname: type: string description: is the name of a package that may be included in a subsequent stress period @@ -124,8 +125,9 @@ blocks: when: modelnames tagged: false fk: - path: sim-nam.models.mname - rule: exchange_models + param: + - model1 + - model2 pname1: type: string longname: provider package name @@ -158,8 +160,9 @@ blocks: when: modelnames tagged: false fk: - path: sim-nam.models.mname - rule: exchange_models + param: + - model1 + - model2 pname2: type: string longname: receiver package name @@ -220,3 +223,8 @@ memory: set_in: ar source: maxpackages type: integer +params: + model1: + type: model + model2: + type: model diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.json index 443592e6..4097cfb3 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.json @@ -93,7 +93,10 @@ "component": [ "utl-spc", "utl-spca" - ] + ], + "args": { + "target": "pname" + } }, "mixed": { "type": "keyword", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.toml index 4ceb2d61..2be9d1aa 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.toml @@ -85,6 +85,9 @@ component = [ "utl-spca", ] +[blocks.fileinput.fields.fileinput.item.fields.spc6_filename.args] +target = "pname" + [blocks.fileinput.fields.fileinput.item.fields.mixed] type = "keyword" longname = "mixed keyword" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.yaml index a801d3ba..cd3fa99f 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/gwt-ssm.yaml @@ -106,6 +106,8 @@ blocks: component: - utl-spc - utl-spca + args: + target: pname mixed: type: keyword longname: mixed keyword diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.json index 71011824..c13b429c 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.json @@ -141,7 +141,11 @@ "direction": "in", "mode_keyword": false, "component": "exchange", - "component_ftype": "exgtype" + "component_ftype": "exgtype", + "args": { + "model1": "exgmnamea", + "model2": "exgmnameb" + } }, "exgmnamea": { "type": "string", diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.toml index 80a52341..64108353 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.toml @@ -125,6 +125,10 @@ mode_keyword = false component = "exchange" component_ftype = "exgtype" +[blocks.exchanges.fields.exchanges.item.fields.exgfile.args] +model1 = "exgmnamea" +model2 = "exgmnameb" + [blocks.exchanges.fields.exchanges.item.fields.exgmnamea] type = "string" longname = "name of model A" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.yaml index 2f3c04fb..7563bdaf 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/sim-nam.yaml @@ -131,6 +131,9 @@ blocks: mode_keyword: false component: exchange component_ftype: exgtype + args: + model1: exgmnamea + model2: exgmnameb exgmnamea: type: string longname: name of model A diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.json index da8958ed..7ae01ce0 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.json @@ -74,7 +74,7 @@ "pk": true, "fk": { "key": "bound", - "rule": "ssm_package" + "param": "target" } }, "spcsetting": { @@ -122,5 +122,11 @@ "source": "maxbound", "type": "integer" } + }, + "params": { + "target": { + "type": "package", + "rule": "no_advanced_transport" + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.toml index 2c6689a3..1dd9689b 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.toml @@ -64,7 +64,7 @@ pk = true [blocks.period.fields.stress_period_data.item.fields.bndno.fk] key = "bound" -rule = "ssm_package" +param = "target" [blocks.period.fields.stress_period_data.item.fields.spcsetting] type = "union" @@ -98,3 +98,7 @@ index = true set_in = "ar" source = "maxbound" type = "integer" + +[params.target] +type = "package" +rule = "no_advanced_transport" diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.yaml index 03bda0ae..6112ce10 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spc.yaml @@ -65,7 +65,7 @@ blocks: pk: true fk: key: bound - rule: ssm_package + param: target spcsetting: type: union description: 'line of information that is parsed into a keyword and values. Keyword values @@ -109,3 +109,7 @@ memory: set_in: ar source: maxbound type: integer +params: + target: + type: package + rule: no_advanced_transport diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.json b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.json index 7a5611d8..4a62d54e 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.json +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.json @@ -87,5 +87,14 @@ } } } + }, + "params": { + "target": { + "rule": "no_advanced_transport", + "type": [ + "gwf-rcha", + "gwf-evta" + ] + } } } \ No newline at end of file diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.toml b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.toml index c54b5567..aea16f5e 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.toml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.toml @@ -75,3 +75,10 @@ longname = "stress period number" description = "integer value specifying the starting stress period number for which the data specified in the PERIOD block apply. IPER must be less than or equal to NPER in the TDIS Package and greater than zero. The IPER value assigned to a stress period block must be greater than the IPER value assigned for the previous PERIOD block. The information specified in the PERIOD block will continue to apply for all subsequent stress periods, unless the program encounters another PERIOD block." tagged = false index = true + +[params.target] +rule = "no_advanced_transport" +type = [ + "gwf-rcha", + "gwf-evta", +] diff --git a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.yaml b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.yaml index 94d46c0c..d860c0bb 100644 --- a/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.yaml +++ b/autotest/dfns/__snapshots__/v2.0.0.dev3/utl-spca.yaml @@ -84,3 +84,9 @@ blocks: another PERIOD block. tagged: false index: true +params: + target: + rule: no_advanced_transport + type: + - gwf-rcha + - gwf-evta diff --git a/autotest/dfns/test_migrate.py b/autotest/dfns/test_migrate.py index 195a1f61..8a62aba1 100644 --- a/autotest/dfns/test_migrate.py +++ b/autotest/dfns/test_migrate.py @@ -381,9 +381,14 @@ def test_migrate_mvr_keys(dfn_dir): blocks = component.blocks packages = blocks["packages"].fields["packages"].item.fields period = blocks["period"].fields["stress_period_data"].item.fields - # a model, and one of the exchange's two (MF6's xmvr_check_packages) - model = v2.ForeignKey(path="sim-nam.models.mname", rule="exchange_models") + # one of the exchange's two models (MF6's xmvr_check_packages), passed + # down from the exchange; unset under a model + model = v2.ForeignKey(param=("model1", "model2")) assert packages["mname"].fk == model + assert component.params == {f"model{i}": v2.Param(type="model") for i in (1, 2)} + # MODELNAMES is set exactly when an exchange binds the mover + modelnames = blocks["options"].fields["modelnames"] + assert (modelnames.when, modelnames.optional) == ("model2", False) assert packages["pname"].pk == v2.PrimaryKey(scope="mname") # a package of that model, with its MOVER option set (MF6's check_packages) assert packages["pname"].fk == v2.ForeignKey( @@ -511,10 +516,12 @@ def test_migrate_maw_without_angledata(dfn_dir, tmp_path): def test_migrate_spc_bndno(dfn_dir): """SPC's bndno is one row per bound, and names a bound of the flow package its SSM row names.""" - blocks = _migrate_dev3(dfn_dir, "utl-spc").blocks + component = _migrate_dev3(dfn_dir, "utl-spc") + blocks = component.blocks bndno = blocks["period"].fields["stress_period_data"].item.fields["bndno"] assert bndno.pk == v2.PrimaryKey() - assert bndno.fk == v2.ForeignKey(key="bound", rule="ssm_package") + assert bndno.fk == v2.ForeignKey(key="bound", param="target") + assert component.params == {"target": v2.Param(type="package", rule="no_advanced_transport")} assert _fix_spc_relations("utl-spc", blocks) == blocks @@ -589,7 +596,7 @@ def test_migrate_gnc_cellids(dfn_dir): item = component.blocks["gncdata"].fields["gncdata"].item # Under an exchange, `cellidn` and `cellidsj` are in model 1's grid and # `cellidm` in model 2's (MF6's GhostNode.f90). - for name, model in [("cellidn", "1"), ("cellidm", "2")]: + for name, model in [("cellidn", "model1"), ("cellidm", "model2")]: field = item.fields[name] assert isinstance(field, v2.CellID) assert (field.model, field.allow_zero) == (model, False) @@ -599,7 +606,7 @@ def test_migrate_gnc_cellids(dfn_dir): assert (cellidsj.dtype, cellidsj.shape, cellidsj.model, cellidsj.allow_zero) == ( "cellid", ["numalphaj"], - "1", + "model1", True, ) assert cellidsj.render(inline=True) == "" @@ -663,7 +670,7 @@ def test_migrate_exchange_cellids(dfn_dir, name): component = _migrate_dev3(dfn_dir, name) assert component.subtype == "exchange" item = component.blocks["exchangedata"].fields["exchangedata"].item - for col, model in (("cellidm1", "1"), ("cellidm2", "2")): + for col, model in (("cellidm1", "model1"), ("cellidm2", "model2")): field = item.fields[col] assert isinstance(field, v2.CellID) assert (field.model, field.allow_zero) == (model, False) @@ -827,14 +834,18 @@ def test_migrate_period_header_is_index(dev3): def test_migrate_cellid_models_round_trip(dev3): - # "1"/"2" stay strings on disk in every format, and load back as such. + # A cellid's model names a parameter, on disk in every format. out, fmt = dev3 raw = _load(out / f"exg-gwfgwf.{fmt}", fmt) cols = raw["blocks"]["exchangedata"]["fields"]["exchangedata"]["item"]["fields"] - assert (cols["cellidm1"]["model"], cols["cellidm2"]["model"]) == ("1", "2") + assert (cols["cellidm1"]["model"], cols["cellidm2"]["model"]) == ("model1", "model2") dfns = v2.Dfns.load(out) gnc = dfns.components["gwf-gnc"].blocks["gncdata"].fields["gncdata"].item.fields - assert [gnc[c].model for c in ("cellidn", "cellidm", "cellidsj")] == ["1", "2", "1"] + assert [gnc[c].model for c in ("cellidn", "cellidm", "cellidsj")] == [ + "model1", + "model2", + "model1", + ] assert [type(gnc[c]) for c in ("cellidn", "cellidsj")] == [v2.CellID, v2.Array] assert gnc["cellidsj"].allow_zero @@ -1035,7 +1046,9 @@ def test_dfns_validate_obs_when_names_no_keyword_option(): cellid = v2.CellID(name="cellid", tagged=False) observation = v2.Union(name="wel", arms={"cellid": cellid, "boundname": boundname}) package = v2.Package(name="gwf-wel", observations={"wel": observation}) - with pytest.raises(ValueError, match="when='boundnames' names no keyword option of gwf-wel"): + with pytest.raises( + ValueError, match="when='boundnames' names no keyword option or parameter of gwf-wel" + ): v2.Dfns(components={"gwf-wel": package}) diff --git a/autotest/dfns/test_schema_relations.py b/autotest/dfns/test_schema_relations.py index 42e096de..ef8e446e 100644 --- a/autotest/dfns/test_schema_relations.py +++ b/autotest/dfns/test_schema_relations.py @@ -2,6 +2,7 @@ import pytest +from modflow_devtools.dfns.migrate_to_v2_0_0_dev2 import _replace_named from modflow_devtools.dfns.schema import ( Array, Block, @@ -17,6 +18,7 @@ List, Model, Package, + Param, PrimaryKey, Record, Simulation, @@ -442,11 +444,15 @@ def test_array_fk_requires_integer_or_string_dtype(dtype): Array(name="slnmnames", dtype=dtype, fk="models.mname") +# A GNC's or an exchange's two models, as parameters (see `Param`). +_MODELS = {"model1": Param(type="model"), "model2": Param(type="model")} + + def _cellid_ctx(field, name="gwf-gnc", parent="gwf-nam", dims=None): item = Record(name="item", fields={field.name: field}) lst = List(name="data", item=item) block = Block(name="data", fields={"data": lst}) - pkg = Package(name=name, parent=parent, blocks={"data": block}, dims=dims) + pkg = Package(name=name, parent=parent, blocks={"data": block}, dims=dims, params=_MODELS) gwf = Model(name="gwf-nam", blocks=None) return {"gwf-nam": gwf, name: pkg} @@ -476,8 +482,8 @@ def test_dfns_validate_cellid_array_shape_resolves(): def _exchange_ctx(*fields): item = Record(name="item", fields={f.name: f for f in fields}) block = Block(name="data", fields={"data": List(name="data", item=item)}) - exg = Package(name="exg-gwfgwf", subtype="exchange", blocks={"data": block}) - return {"exg-gwfgwf": exg} + exg = Package(name="exg-gwfgwf", subtype="exchange", blocks={"data": block}, params=_MODELS) + return {"gwf-nam": Model(name="gwf-nam", blocks=None), "exg-gwfgwf": exg} def _cellid_col(name, model=None, shape=None): @@ -487,10 +493,11 @@ def _cellid_col(name, model=None, shape=None): def test_dfns_validate_cellid_in_exchange(): - fields = (_cellid_col("cellidm1", "1"), _cellid_col("cellidm2", "2")) + fields = (_cellid_col("cellidm1", "model1"), _cellid_col("cellidm2", "model2")) spec = Dfns(components=_exchange_ctx(*fields)) item = spec.components["exg-gwfgwf"].blocks["data"].fields["data"].item - assert {n: f.model for n, f in item.fields.items()} == {"cellidm1": "1", "cellidm2": "2"} + models = {n: f.model for n, f in item.fields.items()} + assert models == {"cellidm1": "model1", "cellidm2": "model2"} @pytest.mark.parametrize("shape", [None, ["n"]]) @@ -501,11 +508,11 @@ def test_dfns_validate_exchange_cellid_must_name_model(shape): def test_dfns_validate_cellid_models_under_a_model(): - # GNC under a model: both models are that model, so "1"/"2" are valid. + # GNC under a model: its model parameters are unset, meaning that model. fields = ( - _cellid_col("cellidn", "1"), - _cellid_col("cellidm", "2"), - _cellid_col("cellidsj", "1", shape=("n",)), + _cellid_col("cellidn", "model1"), + _cellid_col("cellidm", "model2"), + _cellid_col("cellidsj", "model1", shape=("n",)), ) item = Record(name="item", fields={f.name: f for f in fields}) block = Block(name="data", fields={"data": List(name="data", item=item)}) @@ -514,16 +521,17 @@ def test_dfns_validate_cellid_models_under_a_model(): parent="gwf-nam", blocks={"data": block}, dims={"n": InputDim(value="2", scope="model")}, + params=_MODELS, ) spec = Dfns(components={"gwf-nam": Model(name="gwf-nam", blocks=None), "gwf-gnc": pkg}) assert "gwf-gnc" in spec.components def test_dfns_validate_cellid_no_mixing(): - item = Record(name="item", fields={"a": _cellid_col("a", "1"), "b": _cellid_col("b")}) + item = Record(name="item", fields={"a": _cellid_col("a", "model1"), "b": _cellid_col("b")}) block = Block(name="data", fields={"data": List(name="data", item=item)}) - pkg = Package(name="gwf-gnc", parent="gwf-nam", blocks={"data": block}) - with pytest.raises(ValueError, match="mix model=None with '1'/'2'"): + pkg = Package(name="gwf-gnc", parent="gwf-nam", blocks={"data": block}, params=_MODELS) + with pytest.raises(ValueError, match="mix model=None with a named model"): Dfns(components={"gwf-nam": Model(name="gwf-nam", blocks=None), "gwf-gnc": pkg}) @@ -538,17 +546,17 @@ def _union_cellid_pkg(arm_model, sibling_model): ) item = Record(name="item", fields={"a": _cellid_col("a", sibling_model), "id": union}) block = Block(name="data", fields={"data": List(name="data", item=item)}) - pkg = Package(name="gwf-gnc", parent="gwf-nam", blocks={"data": block}) + pkg = Package(name="gwf-gnc", parent="gwf-nam", blocks={"data": block}, params=_MODELS) return {"gwf-nam": Model(name="gwf-nam", blocks=None), "gwf-gnc": pkg} def test_dfns_validate_cellid_no_mixing_across_union_arm(): # A union arm's cellid is read in the same row as its sibling columns. - with pytest.raises(ValueError, match="mix model=None with '1'/'2'"): - Dfns(components=_union_cellid_pkg(None, "1")) + with pytest.raises(ValueError, match="mix model=None with a named model"): + Dfns(components=_union_cellid_pkg(None, "model1")) -@pytest.mark.parametrize("model", [None, "1"]) +@pytest.mark.parametrize("model", [None, "model1"]) def test_dfns_validate_cellid_union_arm_consistent(model): assert "gwf-gnc" in Dfns(components=_union_cellid_pkg(model, model)).components @@ -558,22 +566,33 @@ def test_dfns_validate_cellid_union_arms_are_alternatives(): # is checked against the row's other cellid columns. union = Union( name="id", - arms={"x": _cellid_col("x", "1"), "y": _cellid_col("y", "2")}, + arms={"x": _cellid_col("x", "model1"), "y": _cellid_col("y", "model2")}, ) - item = Record(name="item", fields={"a": _cellid_col("a", "1"), "id": union}) + item = Record(name="item", fields={"a": _cellid_col("a", "model1"), "id": union}) block = Block(name="data", fields={"data": List(name="data", item=item)}) - pkg = Package(name="gwf-gnc", parent="gwf-nam", blocks={"data": block}) + pkg = Package(name="gwf-gnc", parent="gwf-nam", blocks={"data": block}, params=_MODELS) Dfns(components={"gwf-nam": Model(name="gwf-nam", blocks=None), "gwf-gnc": pkg}) -@pytest.mark.parametrize("value", [True, 1, 2, 0, "true", "3", "a"]) +@pytest.mark.parametrize("value", [True, 1, 2, 0, "1", "", "model 1"]) @pytest.mark.parametrize("shape", [None, ["n"]]) def test_cellid_rejects_non_model_values(value, shape): - # `True == 1`: an int or bool must never be read as (or coerced to) a model. + # A model is a parameter's name. `True == 1`: an int or bool must never be + # read as (or coerced to) one. with pytest.raises(ValueError, match="model"): _cellid_col("cellid", value, shape=shape) +@pytest.mark.parametrize("params", [None, {"model1": Param(type="package")}]) +def test_dfns_validate_cellid_model_names_a_model_param(params): + # The name must be a parameter of the component, and of a model. + item = Record(name="item", fields={"a": _cellid_col("a", "model1")}) + block = Block(name="data", fields={"data": List(name="data", item=item)}) + pkg = Package(name="gwf-gnc", parent="gwf-nam", blocks={"data": block}, params=params) + with pytest.raises(ValueError, match="model='model1' names no parameter of gwf-gnc"): + Dfns(components={"gwf-nam": Model(name="gwf-nam", blocks=None), "gwf-gnc": pkg}) + + def test_cellid_render(): assert CellID(name="cellid", tagged=False).render(inline=True) == "" assert CellID(name="cellid", optional=True).render(inline=True) == "[CELLID ]" @@ -583,7 +602,7 @@ def test_cellid_render(): @pytest.mark.parametrize("attr", ["model", "allow_zero"]) def test_array_cellid_attrs_require_cellid_dtype(attr): - value = "1" if attr == "model" else True + value = "model1" if attr == "model" else True with pytest.raises(ValueError, match=f"{attr} requires dtype='cellid'"): Array(name="alphasj", dtype="integer", shape=["n"], **{attr: value}) @@ -848,7 +867,9 @@ def test_dfns_validate_when_names_keyword_option(): "option", [None, Keyword(name="other", optional=True), Integer(name="surf", optional=True)] ) def test_dfns_validate_when_names_no_keyword_option(option): - with pytest.raises(ValueError, match="when='surf' names no keyword option of gwf-evt"): + with pytest.raises( + ValueError, match="when='surf' names no keyword option or parameter of gwf-evt" + ): Dfns(components=_when_ctx("surf", option)) @@ -880,7 +901,8 @@ def test_integer_allow_zero_round_trips(): {"scope": "pname1", "key": "mover_provider"}, ForeignKey(scope="pname1", key="mover_provider"), ), - ({"key": "bound", "rule": "ssm_package"}, ForeignKey(key="bound", rule="ssm_package")), + ({"key": "bound", "param": "target"}, ForeignKey(key="bound", param="target")), + ({"param": ["model1", "model2"]}, ForeignKey(param=("model1", "model2"))), ( {"path": "gwf-nam.packages.pname", "rule": "flow_model"}, ForeignKey(path="gwf-nam.packages.pname", rule="flow_model"), @@ -905,9 +927,18 @@ def test_key_fk_parts(): @pytest.mark.parametrize( "kwargs, match", [ - ({}, "exactly one of path and key"), - ({"path": "a.b", "key": "k", "scope": "s"}, "exactly one of path and key"), - ({"key": "mover_provider"}, "needs a scope or rule"), + ({}, "needs one of path, key and param"), + ({"path": "a.b", "key": "k", "scope": "s"}, "with a path can't have a key or param"), + ({"path": "a.b", "param": "target"}, "with a path can't have a key or param"), + ({"key": "mover_provider"}, "needs one of scope and param"), + ({"key": "bound", "rule": "flow_model"}, "needs one of scope and param"), + ({"key": "bound", "scope": "s", "param": "p"}, "needs one of scope and param"), + ({"key": "bound", "param": ["p", "q"]}, "picks its instance by one param"), + ({"param": "p", "scope": "s"}, "only a param can't have a scope or rule"), + ({"param": "p", "rule": "flow_model"}, "only a param can't have a scope or rule"), + ({"param": []}, "at least one parameter"), + ({"param": ["p", "p"]}, "repeat a parameter"), + ({"param": "a.b"}, "must be a parameter name"), ({"key": "a.b", "scope": "s"}, "must be a key set name"), ({"path": "a.b", "rule": "nope"}, "fk rule 'nope' is not one of"), ], @@ -1023,6 +1054,7 @@ def _ctx_sim(): mode_keyword=False, component="exchange", component_ftype="exgtype", + args={"model1": "exgmnamea", "model2": "exgmnameb"}, ), String(name="exgmnamea", tagged=False, fk="models.mname"), String(name="exgmnameb", tagged=False, fk="models.mname"), @@ -1053,7 +1085,11 @@ def _nam(name, ftype): "mvr6": Keyword(name="mvr6"), "filein": Keyword(name="filein"), "mvr6_filename": File( - name="mvr6_filename", tagged=False, direction="in", component="gwf-mvr" + name="mvr6_filename", + tagged=False, + direction="in", + component="gwf-mvr", + args={"model1": "model1", "model2": "model2"}, ), }, ) @@ -1062,6 +1098,7 @@ def _nam(name, ftype): parent="sim-nam", subtype="exchange", ftype="GWF6-GWF6", + params=_MODELS, blocks={"options": Block(name="options", fields={"mvr_filerecord": mvr_file})}, ) return {"sim-nam": sim, "gwf-nam": _nam("gwf-nam", "GWF6"), "exg-gwfgwf": exg}, _nam @@ -1107,7 +1144,10 @@ def _mover_pkgs(lak_keys=None, wel_keys=None, wel_options=("mover",)): return {"gwf-lak": lak, "gwf-wel": wel} -_MNAME_FK = {"path": "sim-nam.models.mname", "rule": "exchange_models"} +_MNAME_FK = {"param": ["model1", "model2"]} + +# A mover's models, passed down from the exchange that binds it. +_FORWARD = _MODELS _PNAME_FK = {"path": "gwf-nam.packages.pname", "scope": "mname", "rule": "mover_enabled"} @@ -1152,6 +1192,7 @@ def _ctx_mvr(mname_fk=_MNAME_FK, id1_fk=None, id2_fk=None, pname_fk=_PNAME_FK, * parent=["exg-gwfgwf", "gwf-nam"], ftype="MVR6", blocks={"options": _options("modelnames"), "packages": packages, "period": period}, + params=_FORWARD, ) return {**_ctx_sim()[0], "gwf-mvr": mvr, **(pkgs or _mover_pkgs())} @@ -1160,7 +1201,7 @@ def test_dfns_validate_mover_keys(): spec = Dfns(components=_ctx_mvr()) period = spec.components["gwf-mvr"].blocks["period"].fields["stress_period_data"] assert period.item.fields["id1"].fk == ForeignKey(scope="pname1", key="mover_provider") - assert period.item.fields["mname1"].fk.rule == "exchange_models" + assert period.item.fields["mname1"].fk.params == ("model1", "model2") assert spec.components["gwf-wel"].keys["mover_provider"].bound == "maxbound" @@ -1181,10 +1222,9 @@ def test_dfns_validate_rule_with_scope_checks_scope(): def test_dfns_validate_rule_fk_path_checked(): - with pytest.raises(ValueError, match="'nope' is not the pk of list 'models'"): - Dfns( - components=_ctx_mvr(mname_fk={"path": "sim-nam.models.nope", "rule": "exchange_models"}) - ) + bad = {**_PNAME_FK, "path": "gwf-nam.packages.nope"} + with pytest.raises(ValueError, match="'nope' is not the pk of list 'packages'"): + Dfns(components=_ctx_mvr(pname_fk=bad)) def test_dfns_validate_key_undeclared(): @@ -1275,7 +1315,13 @@ def _ctx_transport(spc_key="bound", ssm_keys=_SOURCE, srctype=None): "fileinput", String(name="pname", tagged=False, pk=True, fk=pname_fk), Keyword(name="spc6"), - File(name="spc6_filename", tagged=False, direction="in", component="utl-spc"), + File( + name="spc6_filename", + tagged=False, + direction="in", + component="utl-spc", + args={"target": "pname"}, + ), ), }, ) @@ -1284,6 +1330,7 @@ def _ctx_transport(spc_key="bound", ssm_keys=_SOURCE, srctype=None): parent="gwt-ssm", subtype="utility", ftype="SPC6", + params={"target": Param(type="package", rule="no_advanced_transport")}, blocks={ "period": _list_block( "period", @@ -1292,7 +1339,7 @@ def _ctx_transport(spc_key="bound", ssm_keys=_SOURCE, srctype=None): tagged=False, index=True, pk=True, - fk={"key": spc_key, "rule": "ssm_package"}, + fk={"key": spc_key, "param": "target"}, ), Double(name="concentration", tagged=False), list_name="stress_period_data", @@ -1312,7 +1359,140 @@ def test_dfns_validate_ssm_and_spc_rules(): item = spec.components["gwt-ssm"].blocks["fileinput"].fields["fileinput"].item assert item.fields["pname"].fk.rule == "flow_model" period = spec.components["utl-spc"].blocks["period"].fields["stress_period_data"] - assert period.item.fields["bndno"].fk == ForeignKey(key="bound", rule="ssm_package") + assert period.item.fields["bndno"].fk == ForeignKey(key="bound", param="target") + + +# Binding parameters (`params`, see `Param`): what a component needs from +# whatever binds it, named by an fk's `param`, a cellid's `model` or a `when`. + + +def _with_params(ctx, name, **params): + """`ctx` with component `name`'s parameters replaced or added.""" + component = ctx[name] + merged = {**(component.params or {}), **params} + return {**ctx, name: component.model_copy(update={"params": merged})} + + +def _with_args(ctx, name, block, field, args): + """`ctx` with the args of component `name`'s File `field` replaced.""" + blocks = _replace_named( + name, + ctx[name].blocks, + block, + lambda f, _: f.model_copy(update={"args": args}), + {field}, + ) + return {**ctx, name: ctx[name].model_copy(update={"blocks": blocks})} + + +def test_param_loads_and_dumps(): + toml = {"type": ["gwf-rcha", "gwf-evta"], "rule": "no_advanced_transport"} + pkg = Package.model_validate({"name": "utl-spca", "params": {"target": toml}}) + assert pkg.params["target"] == Param( + type=["gwf-rcha", "gwf-evta"], rule="no_advanced_transport" + ) + assert pkg.model_dump(exclude_none=True)["params"] == {"target": toml} + + +def test_param_rule_checked(): + with pytest.raises(ValueError, match="parameter rule 'nope' is not one of"): + Param(type="package", rule="nope") + + +def test_param_name_checked(): + with pytest.raises(ValueError, match="parameter name 'a-b' must be an identifier"): + Package(name="x", params={"a-b": Param(type="model")}) + + +@pytest.mark.parametrize("args", [{"a": "b.c"}, {"a.b": "c"}, {"a": ""}]) +def test_file_args_checked(args): + with pytest.raises(ValueError, match="must name a column or parameter"): + File(name="f", direction="in", component="x", args=args) + + +def test_dfns_validate_args(): + """An exchange's models are passed from its `sim-nam` row, a mover's from + the exchange's own, and SPC's target from SSM's row. A mover under a + model gets none.""" + spec = Dfns(components=_ctx_transport()) + assert spec.components["exg-gwfgwf"].params == _MODELS + item = spec.components["gwt-ssm"].blocks["fileinput"].fields["fileinput"].item + assert item.fields["spc6_filename"].args == {"target": "pname"} + + +_EXG = ("sim-nam", "exchanges", "exgfile") +_MVR = ("exg-gwfgwf", "options", "mvr6_filename") + + +@pytest.mark.parametrize( + "where, args, match", + [ + ( + _EXG, + {"model1": "nope"}, + "arg model1='nope' must name exactly one of a sibling column and a parameter " + "of sim-nam", + ), + (_EXG, {"model1": "exgtype"}, "arg model1='exgtype': the column has no fk to follow"), + (_EXG, {"model3": "exgmnamea"}, "passes 'model3', which exg-gwfgwf doesn't declare"), + (_MVR, {"model1": "model3"}, "must name exactly one of a sibling column"), + (_MVR, {"model3": "model1"}, "passes 'model3', which gwf-mvr doesn't declare"), + ], +) +def test_dfns_validate_args_bad(where, args, match): + with pytest.raises(ValueError, match=match): + Dfns(components=_with_args(_ctx_transport(), *where, args)) + + +@pytest.mark.parametrize( + "param, match", + [ + (Param(type="package"), "names nothing of type 'package'"), + (Param(type="nope-nam"), "admits no component"), + ], +) +def test_dfns_validate_params_bad(param, match): + with pytest.raises(ValueError, match=match): + Dfns(components=_with_params(_ctx_transport(), "exg-gwfgwf", model1=param)) + + +def test_dfns_validate_fk_param_undeclared(): + ctx = _with_args(_ctx_transport(), *_MVR, None) + ctx["gwf-mvr"] = ctx["gwf-mvr"].model_copy(update={"params": None}) + with pytest.raises(ValueError, match="fk param='model1': gwf-mvr declares no such parameter"): + Dfns(components=ctx) + + +def test_dfns_validate_fk_param_only_names_from_a_string(): + with pytest.raises(ValueError, match="only a string column may name a parameter's instance"): + Dfns(components=_ctx_mvr(id1_fk={"param": "model1"})) + + +def _mvr_with_modelnames(**attrs): + ctx = _ctx_mvr() + mvr = ctx["gwf-mvr"] + options = Block(name="options", fields={"modelnames": Keyword(name="modelnames", **attrs)}) + ctx["gwf-mvr"] = mvr.model_copy(update={"blocks": {**mvr.blocks, "options": options}}) + return ctx + + +def test_dfns_validate_when_names_param(): + """`MODELNAMES` is set exactly when the mover's `model2` is: when an + exchange binds it.""" + spec = Dfns(components=_mvr_with_modelnames(when="model2")) + assert spec.components["gwf-mvr"].blocks["options"].fields["modelnames"].when == "model2" + + +@pytest.mark.parametrize( + "when, match", + [ + ("model3", "when='model3' names no keyword option or parameter of gwf-mvr"), + ("modelnames", "when='modelnames' names the field itself"), + ], +) +def test_dfns_validate_when_names_param_bad(when, match): + with pytest.raises(ValueError, match=match): + Dfns(components=_mvr_with_modelnames(when=when)) def test_dfns_validate_multi_path_keyset(): @@ -1340,5 +1520,5 @@ def test_dfns_validate_multi_path_keyset_types_differ(): def test_dfns_validate_rule_key_undeclared(): - with pytest.raises(ValueError, match="no component in the spec declares key 'nope'"): + with pytest.raises(ValueError, match="no component the parameter 'target' can name declares"): Dfns(components=_ctx_transport(spc_key="nope")) diff --git a/docs/md/dfnspec.md b/docs/md/dfnspec.md index 9bc94909..775107a5 100644 --- a/docs/md/dfnspec.md +++ b/docs/md/dfnspec.md @@ -15,6 +15,7 @@ This document describes the MODFLOW 6 component definition (DFN) system. This sy - [`memory`](#memory) - [`observations`](#observations) - [`keys`](#keys) + - [`params`](#params) - [Component types](#component-types) - [Simulation](#simulation) - [Model](#model) @@ -93,10 +94,14 @@ This document describes the MODFLOW 6 component definition (DFN) system. This sy - [Dimension sources](#dimension-sources) - [Bounds](#bounds) - [Dimension scope](#dimension-scope) + - [Bindings](#bindings) + - [Terms](#terms) + - [What a binding carries](#what-a-binding-carries) - [Primary/foreign keys](#primary-and-foreign-keys) - [Scoped keys](#scoped-keys) - [Key sets](#key-sets) - [Rules](#rules) + - [Parameters](#parameters) - [Memory catalog](#memory-catalog) - [Attributes](#attributes-1) - [`type`](#type-2) @@ -156,7 +161,7 @@ The component's type. Required. One of: #### `parent` -Except for the simulation, which is the root of the runtime hierarchy, all MF6 components have a parent. Parent-child relations may range from fully constrained (e.g., a `gwf-chd` package must be a child of a `gwf-nam` model) to completely unconstrained. Components which may be attached to multiple possible parents are historically called **subpackages**. +Except for the simulation, which is the root of the hierarchy, all MF6 components have a parent. The hierarchy is built by [bindings](#bindings): a `file` field with a [`component`](#component) makes the file it names a child of the file it's in. Parent-child relations may range from fully constrained (e.g., a `gwf-chd` package must be a child of a `gwf-nam` model) to completely unconstrained. Components which may be attached to multiple possible parents are historically called **subpackages**. Parent relationships are defined bottom-up with attribute `parent`: @@ -171,7 +176,7 @@ A term **admits** a component if it equals the component's name, type, or subtyp **Note:** Terms may be mixed. A type or subtype subsumes any named component of the same type or subtype: e.g., `["gwf-sfr", "package"]` reduces to `["package"]` since `gwf-sfr` is a package. -A component that other components' input files link (see [File](#file)), such as a utility, has its `parent` derived from the components that link to it: the simplest selector that admits all of them. The links are the exact relation; such a `parent` covers them but may admit more (e.g. `utl-ts`'s `package` admits `gwf-npf`, which never links a time series file). +A component that other components' input files bind (see [Bindings](#bindings)), such as a utility, has its `parent` derived from the components that bind it: the simplest selector that admits all of them. The bindings are the exact relation; such a `parent` covers them but may admit more (e.g. `utl-ts`'s `package` admits `gwf-npf`, which never binds a time series file). #### `ftype` @@ -203,6 +208,10 @@ If a component does not provide a `memory` catalog, all memory-managed variables `{string: string | [string] | {path, when}} (default: {})`. Named key sets: what a foreign key elsewhere can name with `key`, when the instance it references can be one of several component types, or a key spanning several lists. See [Key sets](#key-sets). +#### `params` + +`{string: {type, rule}} (default: {})`. What the component's references need from its binding, beyond its parent, such as an exchange's two models. The binding passes them in [`args`](#args). See [Parameters](#parameters). + ### Component types Three component types can be distinguished: simulation, model, and package. @@ -393,6 +402,8 @@ A field with a `when` renders in brackets, like an optional one, e.g. `[] A [key set](#key-sets) may have a `when` too, which switches the component's part in a role on or off in the same way. +A `when` may instead name one of the component's [parameters](#parameters). The field is then switched on when the parameter is set where the file is bound. `gwf-mvr`'s `MODELNAMES` keyword has `when = "model2"` and isn't optional: MODFLOW 6 rejects a mover whose `MODELNAMES` disagrees with its binding, either way (`gwf-mvr.f90:785-798`), so it's set exactly when an exchange binds the mover. A writer can set it, and a user never needs to. + On an [observation](#observations) field, `when` names an option of the component declaring the observation, which is the observation's target, not of `utl-obs`. An observation can name a boundary only if its target sets `BOUNDNAMES`, so a `boundname` form has `when = "boundnames"`, and a component with no such option, like `gwf-chdg`, doesn't offer one. This is about whether a field is present at all, not how many values it holds. A column sized by another field, like `aux` (one value per auxiliary variable), uses `shape` instead, and a size of 0 means it's absent. @@ -525,7 +536,7 @@ A cellid is only valid as a column in a list item, where each row names its cell ###### `model` -`"1" | "2" | null (default: null)`. Which model's grid the cellid refers to: `null` for the grid of the component's own model; `"1"` or `"2"` for the grid of the first or second model of the enclosing context, i.e. `EXGMNAMEA` or `EXGMNAMEB` under an exchange. Under a single model, both are that model, so a component read in either context (e.g. `gwf-gnc`) gives the value unconditionally. A cellid in an exchange ([`subtype`](#subtype) `"exchange"`) must give `"1"` or `"2"`, since an exchange has no grid of its own, and a list item giving `"1"` or `"2"` on one cellid column must give one on every cellid column. For example, in an exchange `cellidm1` is `"1"` and `cellidm2` is `"2"`; in `gwf-gnc`, `cellidn` and `cellidsj` are `"1"` and `cellidm` is `"2"`. The position of a column says nothing about its model: GNC's columns, in order, refer to models 1, 2 and 1. +`string | null (default: null)`. Which model's grid the cellid refers to: `null` for the grid of the component's own model, or the name of one of the component's [parameters](#parameters) whose type is a model, for that model's grid. A parameter that's unset where the file is bound is null, so it means the component's own model too, so a component read in either context (e.g. `gwf-gnc`, under an exchange or a model) gives the value unconditionally. A cellid in an exchange ([`subtype`](#subtype) `"exchange"`) must name one, since an exchange has no grid of its own, and a list item naming a model on one cellid column must name one on every cellid column. For example, in an exchange `cellidm1` is `"model1"` and `cellidm2` is `"model2"`, set from `EXGMNAMEA` and `EXGMNAMEB`; in `gwf-gnc`, `cellidn` and `cellidsj` are `"model1"` and `cellidm` is `"model2"`. The position of a column says nothing about its model: GNC's columns, in order, refer to models 1, 2 and 1. **Note:** The values are strings, not integers, as `true == 1` in many languages (e.g. Python). A reader rejects an integer or boolean `model`. @@ -551,7 +562,7 @@ If a file option is introduced by one or more leading keywords (e.g. `CROSS_SECT ###### `component` -`string | [string] | null (default: null)`. The component the file is input for. Valid for components whose `parent` admits this one. Only valid when `direction` is `"in"`. +`string | [string] | null (default: null)`. The component the file is input for. A `file` field with a `component` is a [binding](#bindings): it makes the file it names a child of the file it's in. Valid for components whose `parent` admits this one. Only valid when `direction` is `"in"`. ###### `component_ftype` @@ -559,6 +570,10 @@ If a file option is introduced by one or more leading keywords (e.g. `CROSS_SECT Each component declares its token in [`ftype`](#ftype). +###### `args` + +`{string: string} (default: {})`. The arguments this [binding](#bindings) passes the target's [parameters](#parameters), by name. Each names a sibling field with an `fk`, i.e. another field of the binding's record, or one of this component's own parameters, passed on. For example, `sim-nam`'s `exchanges` rows have `exgfile` with `args = { model1 = "exgmnamea", model2 = "exgmnameb" }`. Only valid with `component`. + ### Composites Three kinds of composite type are relevant to MF6: [product](https://en.wikipedia.org/wiki/Product_type) (record), [sum](https://en.wikipedia.org/wiki/Tagged_union) (union), and collection (array, list). @@ -860,7 +875,7 @@ An unprefixed length is exact, so a DFN must mark every bound. A consumer may re #### Dimension scope -Dimensions may specify a `scope` attribute controlling which other components can inherit the dimension: +Dimensions may specify a `scope` attribute controlling which other components can inherit the dimension, i.e. whose [scope](#terms) it's in: - **`"component"`**: only visible within this component (or to subpackages that list this component as their explicit parent). The default scope. - **`"model"`**: visible to any component that can share the same model instance, as determined by component `parent` attributes. A dimension defined in component A with `scope: "model"` is visible to component B if: @@ -874,8 +889,167 @@ Examples: Only a dimension that every component in the model (or simulation) shares should be widened beyond `"component"`: in practice, the discretization's grid dims and TDIS's `nper`. Other package dimensions (`maxbound`, `nseg`, `numgnc`, `nexg`, ...) describe a single instance of their component, since two EVT packages in a model can have different `nseg`, so they keep the default `"component"` scope even when the component's parent is a model or the simulation. +### Bindings + +A MODFLOW 6 simulation is a tree of input files. `mfsim.nam` names the model name files, the exchanges and the solutions; a model name file names its packages; a package's options name its utility files, such as time series and observations. This section says how the DFNs describe that tree, and what a file can know about where it sits in it. It comes before [keys](#primary-and-foreign-keys) and [parameters](#parameters) because they resolve in the tree that bindings build. + +#### Terms + +- A **binding** is a field in a component's input that names a subcomponent's input file: a `file` field with a [`component`](#component), which says what the subcomponent is. `GWF6 model.nam gwf_1` in `mfsim.nam` binds `model.nam` as a `gwf-nam`. +- The subcomponent's **parent** is the component whose input holds the binding. Bindings build the simulation's tree, and the [`parent`](#parent) attribute says which component types may be the parent of which. +- A binding is one field of a record, and its **siblings** are the record's other fields: on the `GWF6 model.nam gwf_1` line, `GWF6` and `gwf_1`. Siblings may be keys, references or plain data, and may say something about the child, such as its name. +- A **reference** is a value that names something introduced elsewhere in the simulation, and resolves by looking it up in the tree: a row (an [`fk`](#primary-and-foreign-keys)), a key in a [key set](#key-sets), a grid cell (a [cellid](#cellid)) or a time series. +- A component's **parameters** declare what its references need from the component that binds it, and **arguments** are what the parent passes to them, at the binding. See [Parameters](#parameters). +- A reference resolves in its component's **scope**: what it can see by name. That's the component itself, its ancestors, `sim-tdis`, and the component's [parameters](#parameters), which its binding passes it. `sim-tdis` is a child of the simulation, not an ancestor, but there's only one, so every component can see it: its `nper` is a [dimension](#dimension-scope) of every component. The spec's other uses of scope are the same idea. A [dimension's scope](#dimension-scope) says which components' scopes include it, and a [scoped key](#scoped-keys) names the scope its key is unique in, or resolves in. + +A binding and a reference are different things. A binding introduces what it names: the subcomponent is in the simulation because its parent binds it. A reference names something that exists without it, and resolves by looking it up in the tree, once bindings have built it. One row can hold both. `sim-nam`'s `exchanges` row binds the exchange file with `exgfile`, and refers to the exchange's two models with `exgmnamea` and `exgmnameb`, which are `fk`s to `sim-nam`'s `models`. + +So a component's file says what it says, and the binding's siblings may say more about it. The question in each case is whether the file's references need any of that. + +#### What a binding carries + +Every binding in MODFLOW 6 is one of these cases. + +| | Case | Example | The child needs | Said with | +|---|---|---|---|---| +| 1 | A file, and nothing else | `OBS6 FILEIN chd.obs` | nothing | [`component`](#component) | +| 2 | A sibling names the child | `CHD6 model.chd CHD-1` | nothing | a `pk` | +| 3 | A sibling says more, which the child doesn't use | `1 TAB6 FILEIN lake1.tab` | nothing | nothing | +| 4 | A sibling says what the child's references need | `GWF6-GWF6 model.gwfgwf gwf_1 gwf_2` | the exchange's models | `params`, `args` | +| 5 | The parent passes on what it was passed | `GNC6 FILEIN model.gnc` in an exchange | the exchange's models | `args` naming a parameter | +| 6 | One component, bound under different parents | `GNC6 model.gnc` in a model name file | nothing | no `args`: unset | +| 7 | A sibling names it by reference | `WEL-1 SPC6 FILEIN wel.spc` in SSM | the flow package `WEL-1` | `args` naming a column with an `fk` | +| 8 | Another row in the parent says it | `WEL-1 AUX CONCENTRATION` in a transport SSM's `sources` | the flow model | a [rule](#rules) | + +**1. A file, and nothing else.** Most bindings name a file and its component, and that's all the child needs. Its references resolve against its parent: + +``` +# a CHD package's options +TS6 FILEIN chd.ts +OBS6 FILEIN chd.obs + +# chd.obs +BEGIN continuous FILEOUT chd.obs.csv + q-west chd west +END continuous +``` + +`west` names a CHD boundary, as CHD's [`[observations]`](#observations) declares, and an observation by cellid is in the CHD's model's grid. `utl-obs` is bound by 52 components and needs nothing from any of them: whatever binds it is what it observes. References can also point down the tree: a CHD head given as a time series name names a series in the TS file it binds. + +**2. A sibling names the child.** A name file's row gives the child its name: + +``` +# a GWF model name file's packages +CHD6 model.chd CHD-1 +``` + +`pname` is a key of the row (`pk`), and other files refer to the package by it, within its model: a mover's `packages`, a transport model's SSM. The name is the parent's data, though, and the CHD file never refers to itself, so the child needs nothing. `mfsim.nam`'s `models` rows name models the same way, and its `solutiongroup` rows name the models each solution solves, which the solution's file never mentions. + +**3. A sibling says more, which the child doesn't use.** + +``` +# a LAK package's tables +1 TAB6 FILEIN lake1.tab +``` + +`ifno` says which lake the table is for. The table holds stage, volume and area, and nothing in it depends on the lake, so nothing is passed. SFR's cross-section tables are the same. A binding passes only what the child's references need. + +**4. A sibling says what the child's references need.** + +``` +# mfsim.nam exchanges +GWF6-GWF6 model.gwfgwf gwf_1 gwf_2 + +# model.gwfgwf exchangedata: cellidm1, cellidm2, ihc, cl1, cl2, hwva +1 5 10 1 5 1 1 50.0 50.0 100.0 +``` + +The first cellid is in `gwf_1`'s grid and the second in `gwf_2`'s, but nothing in the exchange file says which models those are. The binding's siblings do: `exgmnamea` and `exgmnameb`, which are references to `sim-nam`'s `models`. So every exchange declares two [parameters](#parameters), and the binding passes them: + +```toml +# every exchange +[params.model1] +type = "model" + +[params.model2] +type = "model" + +# sim-nam exchanges row +[blocks.exchanges.fields.exchanges.item.fields.exgfile] +args = { model1 = "exgmnamea", model2 = "exgmnameb" } +``` + +The cellids then say which parameter they're in: `cellidm1` has [`model`](#model) `"model1"`, and `cellidm2` has `"model2"`. + +**5. The parent passes on what it was passed.** + +``` +# model.gwfgwf options +GNC6 FILEIN model.gnc +MVR6 FILEIN model.mvr + +# model.mvr period: mname1, pname1, id1, mname2, pname2, id2, mvrtype, value +gwf_1 WEL-1 1 gwf_2 LAK-1 1 FACTOR 1.0 +``` + +GNC's cellids and the mover's model names are in the exchange's models, which only the exchange's row in `mfsim.nam` says. The exchange passes its own parameters on: + +```toml +# exg-gwfgwf +[blocks.options.fields.mvr_filerecord.fields.mvr6_filename] +args = { model1 = "model1", model2 = "model2" } +``` + +MVT and MVE, bound the same way in transport exchanges, refer to no model, so they're passed nothing. + +**6. One component, bound under different parents.** GNC and MVR can also be a model's packages: + +``` +# a GWF model name file's packages +GNC6 model.gnc +MVR6 model.mvr +``` + +A model name file passes them nothing, so their parameters are unset, which means null. Whatever names a parameter says what null means: a GNC whose cellids' models are null reads them all in its own model, and a mover whose `model2` is null has no `MODELNAMES` and no model columns. + +**7. A sibling names it by reference.** + +``` +# a GWT model's SSM fileinput +WEL-1 SPC6 FILEIN wel.spc + +# wel.spc period: bndno, spcsetting +1 CONCENTRATION 10.0 +``` + +SPC's `bndno` numbers the bounds of `WEL-1`. The argument is the binding's sibling `pname`, and the parameter is what it references, the package, not the string: + +```toml +# utl-spc +[params.target] +type = "package" +rule = "no_advanced_transport" + +# gwt-ssm, gwe-ssm fileinput row +[blocks.fileinput.fields.fileinput.item.fields.spc6_filename] +args = { target = "pname" } +``` + +`WEL-1` is a package of the flow model, not of the transport model that SSM is in, so `pname`'s `fk` has the rule `flow_model`, which case 8 explains. + +**8. Another row in the parent says it.** + +``` +# a GWT model's SSM sources +WEL-1 AUX CONCENTRATION +``` + +`WEL-1` is a package of the flow model that a `GWF6-GWT6` exchange pairs with this transport model. `mfsim.nam` says so, and `mfsim.nam` binds the transport model, but not in that binding's siblings. The pairing is in an `exchanges` row, which refers back to the transport model by `exgmnameb`. An argument names only a sibling or one of the parent's parameters, so no argument can pass it. A [rule](#rules) does: `flow_model`. With flows read from a budget file there is no exchange, and no check. + ### Primary and foreign keys +A reference names something in the tree that [bindings](#bindings) build, and resolves in its component's [scope](#terms). This section covers references to rows and keys; [cellids](#cellid) and [time series](#time_series) are covered with their types. + Sometimes a column in one list identifies a row in another list. This can be conceptualized as a primary key (PK) / foreign key (FK) relation. Integers and strings may encode PK/FK semantics with attributes `pk` and `fk`. A column referencing a grid cell instead of another list's row is a distinct concern, handled by the [`cellid`](#cellid) type rather than `pk`/`fk` — grid cells are resolved against the grid (DIS/DISV/DISU) the column refers to at runtime, not looked up via a `pk` column. **Note**: `pk` is only valid on integer and string fields appearing as columns in a tabular (i.e. regular) list's record item type. `fk` is valid there too, and additionally on integer- and string-`dtype` `Array` fields (see [Array](#array)) — there each element references another list's row, per grid cell for a grid array, or several per row for an array column in a list item; a string array's entries are keys. @@ -888,11 +1062,11 @@ An `fk` names an existing row: a file in which one names no row is invalid. An i An `fk` is a hierarchical path of the form `"[component.]block.field"`, naming the `pk` field it references. The `component` segment is necessary for cross-component references; it may be omitted for within-component references. On a `string` field, the path may instead name a string array (e.g. `"options.auxiliary"`), whose entries then act as the key set: the array is in effect a one-column list whose entries are its keys. A dynamic keyword (see [`dynamic`](#dynamic)) needs this form when its keywords are a string array's entries. -A cross-component `fk` resolves in the current context: against the singleton component (e.g. `sim-nam`), or the instance that owns this file (e.g. a reference from a model's package to that model's name file). +A cross-component `fk` resolves in the file's [scope](#terms): against an ancestor of the instance the file is input for (e.g. `sim-nam`, or a model's name file for a reference from one of its packages), or through a [parameter](#parameters). #### Scoped keys -MODFLOW names things hierarchically: a package name is unique within a model, and a model name within the simulation. A key can carry a **scope**, the name of another column in the same record that qualifies it, or of a record enclosing it within the same list item (a union arm's column may be scoped by the list item's). The scope belongs to the key, so it's written inside `pk` or `fk`: +MODFLOW names things hierarchically: a package name is unique within a model, and a model name within the simulation. A scoped key is a qualified name, like `mname1::pname1` in C++, whose `::` is called the scope resolution operator: `pname1` is resolved in the [scope](#terms) of model `mname1`. A key can carry a **scope**, the name of another column in the same record that qualifies it, or of a record enclosing it within the same list item (a union arm's column may be scoped by the list item's). The scope belongs to the key, so it's written inside `pk` or `fk`: ```toml pk = { scope = "mname" } @@ -903,7 +1077,7 @@ fk = { path = "packages.pname", scope = "mname1" } - An `fk` with scope `s` takes the target key's qualifier from `s`. - Within a component (`"block.field"`), the target `pk` must be scoped too, and `s` matches the target row's scope column. If both scope columns have an `fk`, it must be the same. - Across components (`"component.block.field"`), `s` picks the target component's *instance*. `s` must name that instance: it must be an `fk` to the key of the row that binds that component in its parent, i.e. a list whose item has a `file` naming the component (see [`component`](#component)), either directly or through other `fk`s. For example, `gwf-mvr`'s `pname1` names a `packages` row, whose `pname` names a row of a model name file's `packages`. -- An absent scope value means the current one, as a relative path resolves against the current directory. For example, `gwf-mvr`'s `packages` rows name a package within a model, `mname`, which is given only in an exchange's mover; in a model's own mover it's absent and means that model. Like an optional `pk` given a default, the key is complete once the default is applied. +- An absent scope value means the current scope, as a relative path resolves against the current directory. For example, `gwf-mvr`'s `packages` rows name a package within a model, `mname`, which is given only in an exchange's mover; in a model's own mover it's absent and means that model. Like an optional `pk` given a default, the key is complete once the default is applied. - A scope column's own key may be scoped, forming a chain, but not a cycle. - If a column's `pk` and `fk` are both scoped, the scope is the same column. - A scope names the nearest column of that name: in the key's own record, then in each enclosing record of the same list item. @@ -960,7 +1134,7 @@ bound = "<=maxbound" In relational terms, a role is a supertype table: each declaring component adds its keys to it, keyed by (instance, key), and an `fk` with `key` is an ordinary foreign key to it. -An `fk` names a role with `key`, and picks the instance with a scope, as for a scoped key across components (or with a [rule](#rules)): +An `fk` names a role with `key`, and picks the instance with a scope, as for a scoped key across components, or with a [parameter](#parameters): ```toml # gwf-mvr period id1 / id2 @@ -987,7 +1161,7 @@ A key set may be switched on by an option, written `{ path = "", when = "< A component that doesn't declare a role can't be named by an `fk` with that `key`. A validator checks that: -- each `key` an `fk` uses is declared by at least one component its scope can name (with a rule, by any component); +- each `key` an `fk` uses is declared by at least one component its scope or parameter can name; - each declared `path` is valid in its component; - a `<=dim` path is referenced only by an integer column with `index: true`, and a `"block.field"` path by a column of the same type. @@ -1005,7 +1179,7 @@ These follow each package's `pakmvrobj%ar(nproviders, nreceivers)` call: `(maxbo ```toml # utl-spc period bndno: a bound of the flow package its SSM row names pk = true -fk = { key = "bound", rule = "ssm_package" } +fk = { key = "bound", param = "target" } ``` MODFLOW 6 checks `bndno` only against SPC's own `maxbound`, which must be at least the flow package's bound count (`TspSpc.f90:326-349`). @@ -1016,14 +1190,11 @@ An advanced package's bounds are its features or its connections. SFR and UZF ha #### Rules -Some references depend on where a component's file is bound in a simulation, not only on the data in it. An exchange's mover may name only the exchange's two models; a transport model's SSM names packages of whichever flow model an exchange pairs it with. No path or sibling scope can say this. Instead, the `fk` names a **rule**: the spec states what the rule means, below, and consumers implement it in code, like JSON Schema's `format`. +Some constraints can't be said with a path, a scope or a [parameter](#parameters). A transport model's SSM names packages of whichever flow model an exchange pairs it with. `mfsim.nam` says which, in an `exchanges` row and not among the siblings of the binding that names the transport model, so no argument can pass it (case 8 in [What a binding carries](#what-a-binding-carries)). With flows from a budget file, nothing says it. A mover's packages must have `MOVER` set. Instead, the `fk` or parameter names a **rule**: the spec states what the rule means, below, and consumers implement it in code, like JSON Schema's `format`. -A rule names a subset of the `fk`'s target, and the value must be in it. In relational terms, the `fk` references a named view rather than the target table. A rule may stand in for a scope, or narrow a scoped target further: +A rule names a subset of the target, and the value must be in it. In relational terms, the `fk` references a named view rather than the target table. A rule may stand in for a scope, or narrow a scoped target further: ```toml -# gwf-mvr packages.mname, period mname1/mname2: the exchange's two models -fk = { path = "sim-nam.models.mname", rule = "exchange_models" } - # gwt-ssm / gwe-ssm sources.pname and fileinput.pname pk = true fk = { path = "gwf-nam.packages.pname", rule = "flow_model" } @@ -1037,13 +1208,77 @@ The rest of the `fk` is checked as usual, by its scope or as if unscoped: its pa | rule | meaning | MODFLOW 6 | |---|---|---| -| `exchange_models` | one of the two models (`exgmnamea`, `exgmnameb`) of the exchange whose `MVR6` option binds this mover. In a model's own mover the model names are absent (`MODELNAMES` is rejected there). | `GwfExchangeMover.f90:87-93` | | `flow_model` | in the flow model that a GWF-GWT or GWF-GWE exchange pairs with this transport model (its `exgmnamea`, where the transport model is `exgmnameb`). With flows read from a budget file there is no such exchange, and the reference isn't checked. | `exg-gwfgwt.f90:124-129`, `exg-gwfgwe.f90:121-126`, `tsp-ssm.f90:800-811` | -| `ssm_package` | in the flow package named by the SSM `fileinput` row that binds this SPC file. If an advanced transport package is paired with that flow package, SSM doesn't apply the SPC file and the reference isn't checked. | `TspSpc.f90:326-349`, `tsp-ssm.f90:195` | +| `no_advanced_transport` | a flow package with no advanced transport package paired with it. SSM doesn't apply an SPC file bound to one that has, so the file isn't checked. On `utl-spc`'s and `utl-spca`'s `target` | `tsp-ssm.f90:195` | | `mover_enabled` | a package that plays `mover_provider` or `mover_receiver`: one whose mover key sets are switched on by `MOVER` (see [Key sets](#key-sets)) | `gwf-mvr.f90:942-971` | The names are fixed by the schema, so a misspelled rule is an error. A new rule is added to the schema and this table together. +### Parameters + +A component's **parameters** declare what its references need from the component that binds it: cases 4 to 7 under [What a binding carries](#what-a-binding-carries). Binding works like a function call: + +| Binding | Function call | +|---|---| +| the component | the function | +| `params` | its signature | +| the binding field | a call site | +| `args` | the arguments | +| the parent | `self` | +| an unset parameter | an argument left at its default, `None` | + +In Python terms, for GNC: + +```python +class Gnc: + def __init__(self, model1=None, model2=None): ... + +Gnc(model1=self.model1, model2=self.model2) # in an exchange: passes on its own +Gnc() # in a model name file +``` + +A component declares its parameters in [`params`](#params): + +- `type` is what the parameter names, a selector as in [`parent`](#parent). +- `rule` is optional, and means what it means on an `fk`: a named subset of the target (see [Rules](#rules)). + +A binding passes them in its `file` field's [`args`](#args). An argument names one of two things, in the parent's scope: + +- a sibling of the binding with an `fk`, and the argument is what the `fk` references (cases 4 and 7); +- one of the parent's own parameters (case 5). + +It must name exactly one, so nothing is searched. Scope is lexical, as in most programming languages: a parent can pass only what's in its own scope, so an exchange's models reach its mover by being passed twice, from `sim-nam` to the exchange and from the exchange to the mover, and no component looks up the tree for a name. + +Following an argument's `fk` applies all of it, [rule](#rules) included, and the parameter's own `rule` applies after that. SSM's `pname` has `rule = "flow_model"`, so the `target` it passes is a package of the paired flow model, and `utl-spc`'s `rule = "no_advanced_transport"` then narrows it to one with no advanced transport package. + +A binding that passes no argument leaves the parameter unset, which is null, as for a field. Nothing fills it in from elsewhere. What null means is said by whatever names the parameter, which is one of: + +- an `fk`'s `param`. Alone, the value names the instance a parameter is set to, one of several if it's a list: `gwf-mvr`'s `mname1` is `fk = { param = ["model1", "model2"] }`, one of the exchange's two models. With a `key`, the parameter picks the instance whose key set is meant: `utl-spc`'s `bndno` is `fk = { key = "bound", param = "target" }`. +- a cellid's [`model`](#model): `"model1"` or `"model2"`. Null means the component's own model. +- a field's [`when`](#when): `gwf-mvr`'s `MODELNAMES` has `when = "model2"`. Null means off. + +A parameter's `type` can say what a key can't. MODFLOW 6 applies list-based SPC only to a list-based package, and array-based SPC (`utl-spca`) only to `gwf-rcha` and `gwf-evta` (`TspSpc.f90:353-375`). `utl-spca` says so with `type = ["gwf-rcha", "gwf-evta"]`, and `utl-spc` says so through `key = "bound"`, which RCHA and EVTA don't declare. + +Two things aren't parameters. The parent is already in scope, as `self` is, so `utl-obs` declares none. And what's in another row of the parent, not a sibling of the binding (case 8), can't be passed, so it's a rule. + +In relational terms, an argument is a foreign key in the binding's record, and the parameter is that column carried onto the child's row: + +| DFN | SQL | +|---|---| +| `args = { target = "pname" }` on SSM's `fileinput`, `[params.target]` on `utl-spc` | columns `flow_mname`, `flow_pname` on `spc`, from the `fileinput` row | +| `fk = { param = ["model1", "model2"] }` | a composite key to `exchange_models (exchange, mname)` | +| `fk = { param = "target", key = "bound" }` | a foreign key to the `bound` supertype table | +| `when = "model2"`, `optional = false` | `CHECK (modelnames = (model2 IS NOT NULL))` | + +A validator checks that: + +- each parameter's `type` admits some component; +- each argument is a parameter of every component its `file` can bind; +- each argument names exactly one of a sibling, with an `fk`, and a parent's parameter, and can name something of the parameter's type; +- each `fk`, cellid `model` and `when` naming a parameter names one the component declares, and a cellid's is of a model. + +So an exchange's cellid always resolves, and so does a mover's model name. + ## Memory catalog Each component may declare a `memory` catalog describing runtime variables accessible via the MODFLOW API. Memory variables correspond to values in the MODFLOW 6 memory manager that can be read or written at runtime using hierarchical `/`-delimited memory addresses. 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 602438d7..172abdda 100644 --- a/modflow_devtools/dfns/migrate_to_v2_0_0_dev2.py +++ b/modflow_devtools/dfns/migrate_to_v2_0_0_dev2.py @@ -597,17 +597,19 @@ def _mark_lonely_pk(blocks: dict[str, v2.Block]) -> dict[str, v2.Block]: # only means "needs 1-based/0-based conversion"). In every entry here the list # field's own name matches its enclosing block's name. # -# Each column also refers to one of two models' grids (see `v2.CellModel`), which -# v1 only says in prose and MF6 hard-codes (IDM's `StructArray.f90` maps -# `CELLIDM1`/`CELLIDM2` to `EXGMNAMEA`/`EXGMNAMEB`; `GhostNode.f90` reads -# `cellidn` and `cellidsj` in model 1's grid, `cellidm` in model 2's). -_CELLID_FIELDS: dict[str, tuple[str, dict[str, Literal["1", "2"]]]] = { - "exg-chfgwf": ("exchangedata", {"cellidm1": "1", "cellidm2": "2"}), - "exg-gwegwe": ("exchangedata", {"cellidm1": "1", "cellidm2": "2"}), - "exg-gwfgwf": ("exchangedata", {"cellidm1": "1", "cellidm2": "2"}), - "exg-gwtgwt": ("exchangedata", {"cellidm1": "1", "cellidm2": "2"}), - "exg-olfgwf": ("exchangedata", {"cellidm1": "1", "cellidm2": "2"}), - "gwf-gnc": ("gncdata", {"cellidn": "1", "cellidm": "2", "cellidsj": "1"}), +# Each column also refers to one of two models' grids, named by a parameter +# (see `v2.CellModel`, `_PARAMS`), which v1 only says in prose and MF6 +# hard-codes (IDM's `StructArray.f90` maps `CELLIDM1`/`CELLIDM2` to +# `EXGMNAMEA`/`EXGMNAMEB`; `GhostNode.f90` reads `cellidn` and `cellidsj` in +# model 1's grid, `cellidm` in model 2's). +_EXG_CELLIDS = {"cellidm1": "model1", "cellidm2": "model2"} +_CELLID_FIELDS: dict[str, tuple[str, dict[str, str]]] = { + "exg-chfgwf": ("exchangedata", _EXG_CELLIDS), + "exg-gwegwe": ("exchangedata", _EXG_CELLIDS), + "exg-gwfgwf": ("exchangedata", _EXG_CELLIDS), + "exg-gwtgwt": ("exchangedata", _EXG_CELLIDS), + "exg-olfgwf": ("exchangedata", _EXG_CELLIDS), + "gwf-gnc": ("gncdata", {"cellidn": "model1", "cellidm": "model2", "cellidsj": "model1"}), } @@ -620,7 +622,7 @@ def _cellid_base(field: "v2.Integer | v2.Array") -> dict[str, Any]: return base -def _as_cellid(field: v2.InputField, model: Literal["1", "2"] | None = None) -> v2.InputField: +def _as_cellid(field: v2.InputField, model: str | None = None) -> v2.InputField: """Rewrite an integer column as a cellid in the given model's grid: a `CellID` if it's one cellid (a plain integer, or a v1 `shape (ncelldim)` array), else an array of them, shaped by what counts them (GNC's @@ -1699,10 +1701,14 @@ def _fix_mvr_relations(name: str, blocks: dict[str, v2.Block]) -> dict[str, v2.B "mname"}`, `fk = {path = "gwf-nam.packages.pname", scope = "mname", rule = "mover_enabled"}`. - `packages.mname`, `period.perioddata`'s `mname1`/`mname2`: - `fk = {path = "sim-nam.models.mname", rule = "exchange_models"}`: a - model, and one of the exchange's two, which MF6 requires - (`xmvr_check_packages`, `GwfExchangeMover.f90:87-93`). In a model's - mover they're absent, since MF6 rejects `MODELNAMES` there. + `fk = {param = ["model1", "model2"]}`: one of the exchange's two + models (see `_PARAMS`), which MF6 requires (`xmvr_check_packages`, + `GwfExchangeMover.f90:87-93`). In a model's mover they're absent, + since MF6 rejects `MODELNAMES` there. + - `options.modelnames`: `when = "model2"`, required: MF6 rejects a + mover whose `MODELNAMES` disagrees with its binding, either way + (`gwf-mvr.f90:785-798`), so it's set exactly when an exchange binds + the mover, and a writer can set it. - `pname1`/`pname2`: `fk = {path = "packages.pname", scope = "mname1"}` (`"mname2"`), since `mvr_rp` looks each provider/receiver up among the `packages` rows, fatal if unlisted. @@ -1738,7 +1744,18 @@ def _fix_mvr_relations(name: str, blocks: dict[str, v2.Block]) -> dict[str, v2.B ): return blocks - model = v2.ForeignKey(path="sim-nam.models.mname", rule="exchange_models") + model = v2.ForeignKey(param=("model1", "model2")) + options = blocks.get("options") + if options is not None and isinstance(options.fields.get("modelnames"), v2.Keyword): + modelnames = options.fields["modelnames"].model_copy( + update={"when": "model2", "optional": False} + ) + blocks = { + **blocks, + "options": options.model_copy( + update={"fields": {**options.fields, "modelnames": modelnames}} + ), + } blocks = _set_list_item_attrs( blocks, "packages", @@ -1804,6 +1821,59 @@ def _fix_mvr_relations(name: str, blocks: dict[str, v2.Block]) -> dict[str, v2.B } +# Binding parameters (see `v2.Param`): what a component's references need +# from its binding, beyond its parent, which v1 says only in prose. The +# component declares them, like a function's signature; the binding passes +# them (see `_ARGS`). +# +# - Every exchange (`exg-*`) has two models, named on `sim-nam`'s +# `exchanges` row, not in its own file. Its cellids name them. +# - An exchange's mover and GNC get them from the exchange. Under a model +# they're unset (null), which a cellid reads as that model. +# - SPC and SPCA apply to the flow package SSM's `fileinput` row names +# (`TspSpc.f90:326-349`), skipped if an advanced transport package is +# paired with it (`tsp-ssm.f90:195`). MF6 rejects array SPC on any but +# RCHA and EVTA, and list SPC on those (`TspSpc.f90:353-375`): SPCA says +# so by its type, SPC by its `bound` key, which RCHA and EVTA don't +# declare. +_MODELS = {"model1": v2.Param(type="model"), "model2": v2.Param(type="model")} +_SPC_TARGET = "no_advanced_transport" +_PARAMS: dict[str, dict[str, v2.Param]] = { + "gwf-mvr": _MODELS, + "gwf-gnc": _MODELS, + "utl-spc": {"target": v2.Param(type="package", rule=_SPC_TARGET)}, + "utl-spca": {"target": v2.Param(type=["gwf-rcha", "gwf-evta"], rule=_SPC_TARGET)}, +} + +# The arguments each binding passes (see `v2.File.args`), as (block, file +# field, args): a sibling of the file field with an fk, or the parent's own parameter, +# passed on. +_PASS_MODELS = {"model1": "model1", "model2": "model2"} +_ARGS: dict[str, list[tuple[str, str, dict[str, str]]]] = { + "sim-nam": [("exchanges", "exgfile", {"model1": "exgmnamea", "model2": "exgmnameb"})], + "exg-gwfgwf": [ + ("options", "gnc6_filename", _PASS_MODELS), + ("options", "mvr6_filename", _PASS_MODELS), + ], + "gwt-ssm": [("fileinput", "spc6_filename", {"target": "pname"})], + "gwe-ssm": [("fileinput", "spc6_filename", {"target": "pname"})], +} + + +def _fix_file_args(name: str, blocks: dict[str, v2.Block]) -> dict[str, v2.Block]: + """Set the arguments this component's binding files pass. See `_ARGS`. + Skips an absent block, as a trimmed DFN may have.""" + for block_name, field_name, args in _ARGS.get(name, []): + if block_name not in blocks: + continue + + def _pass(field: Any, _in_union: bool, args: dict[str, str] = args) -> Any: + return field.model_copy(update={"args": args}) + + blocks = _replace_named(name, blocks, block_name, _pass, {field_name}) + return blocks + + def _component_keys( name: str, blocks: dict[str, v2.Block], dims: Mapping[str, Any] ) -> dict[str, v2.KeySet] | None: @@ -1856,10 +1926,11 @@ def _ssm_pname_keyed(blocks: dict[str, v2.Block], block_name: str) -> bool: def _fix_spc_relations(name: str, blocks: dict[str, v2.Block]) -> dict[str, v2.Block]: """ SPC's `bndno` names a bound of the flow package its SSM `fileinput` row - (its binding row) names, so `fk = {key = "bound", rule = - "ssm_package"}`, keeping its `pk` (one row per bound per period). MF6 reads `bndno` - against SPC's own `maxbound` and requires that to be at least the flow - package's bound count (`TspSpc.f90:326-349`). + names, its `target` parameter (see `_PARAMS`), so + `fk = {key = "bound", param = "target"}`, keeping its `pk` (one row per + bound per period). MF6 reads `bndno` against SPC's own `maxbound` and + requires that to be at least the flow package's bound count + (`TspSpc.f90:326-349`). Must run after `_mark_lonely_pk`, which marks only a column without an fk. """ @@ -1875,7 +1946,7 @@ def _fix_spc_relations(name: str, blocks: dict[str, v2.Block]) -> dict[str, v2.B return _set_list_item_attrs( blocks, "period", - {"bndno": {"fk": v2.ForeignKey(key=BOUND, rule="ssm_package")}}, + {"bndno": {"fk": v2.ForeignKey(key=BOUND, param="target")}}, list_name=period_list, ) @@ -2906,6 +2977,7 @@ def _lookup(rname: str) -> dict | None: blocks = _link_namefile_fields(name, blocks) blocks = _fix_model_nam_package_keys(name, blocks) blocks = _fix_spc_relations(name, blocks) + blocks = _fix_file_args(name, blocks) blocks = _fix_scoped_index_keys(name, blocks) blocks = _name_period_lists(blocks) blocks = _set_header_update(blocks) @@ -2919,6 +2991,7 @@ def _lookup(rname: str) -> dict | None: "blocks": blocks or None, "dims": dims, "keys": _component_keys(name, blocks, dims or {}), + "params": _MODELS if name.startswith("exg-") else _PARAMS.get(name), } if name == "sim-nam": return v2.Simulation(**d) diff --git a/modflow_devtools/dfns/schema.json b/modflow_devtools/dfns/schema.json index 5fd26561..83f5b1c2 100644 --- a/modflow_devtools/dfns/schema.json +++ b/modflow_devtools/dfns/schema.json @@ -165,10 +165,6 @@ "model": { "anyOf": [ { - "enum": [ - "1", - "2" - ], "type": "string" }, { @@ -461,10 +457,6 @@ "model": { "anyOf": [ { - "enum": [ - "1", - "2" - ], "type": "string" }, { @@ -744,6 +736,21 @@ ], "default": null, "title": "Component Ftype" + }, + "args": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Args" } }, "required": [ @@ -755,7 +762,7 @@ }, "ForeignKey": { "additionalProperties": false, - "description": "A reference to a `pk` column (or a string array's entries), one of:\n\n- `path`, `[component.]block.field`. With a `scope`, the name of a\n sibling column, that column qualifies the reference: within a\n component, it matches the target pk's own scope; across components, it\n picks the target component's instance. Written `fk = \"\"` when\n unscoped.\n- `key`, with a `scope` or `rule`: a key set (see `KeySet`) that the\n component of the picked instance declares, by name, in its `keys`. For\n an instance whose type is known only at runtime (MVR's `id1`).\n\nA `rule` (see `RULES`) names a subset of the target the value must fall\nin. It may stand in for a `scope` (MVR's `mname1`: `exchange_models`) or\nrestrict a scoped target further (MVR's `packages.pname`:\n`mover_enabled`).", + "description": "A reference to a `pk` column (or a string array's entries), one of:\n\n- `path`, `[component.]block.field`. With a `scope`, the name of a\n sibling column, that column qualifies the reference: within a\n component, it matches the target pk's own scope; across components, it\n picks the target component's instance. Written `fk = \"\"` when\n unscoped.\n- `key`, with a `scope` or `param` that picks the instance: a key set\n (see `KeySet`) that the instance's component declares, by name, in\n its `keys`. For an instance whose type is known only at runtime\n (MVR's `id1`).\n- `param` alone, one or more of this component's parameters (see\n `Param`): the value names the instance one of them is set to (MVR's\n `mname1`: `model1` or `model2`).\n\nWith a `key`, `param` names the parameter whose instance declares the\nkey set (SPC's `bndno`: `target`).\n\nA `rule` (see `RULES`) names a subset of the target the value must fall\nin. It may stand in for a `scope` (SSM's `pname`: `flow_model`) or\nrestrict a scoped target further (MVR's `packages.pname`:\n`mover_enabled`).", "properties": { "path": { "anyOf": [ @@ -804,6 +811,24 @@ ], "default": null, "title": "Rule" + }, + "param": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Param" } }, "title": "ForeignKey", @@ -1751,6 +1776,21 @@ "default": null, "title": "Keys" }, + "params": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/$defs/Param" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Params" + }, "type": { "const": "model", "default": "model", @@ -1996,6 +2036,21 @@ "default": null, "title": "Keys" }, + "params": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/$defs/Param" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Params" + }, "type": { "const": "package", "default": "package", @@ -2033,6 +2088,43 @@ "title": "Package", "type": "object" }, + "Param": { + "additionalProperties": false, + "description": "A binding parameter: what a component's references need from its\nbinding, beyond its parent (see `docs/md/dfnspec.md`, \"Bindings\").\nDeclared in the component's `params`, like a function's signature, and\npassed by the binding's `args` (see `File`). Named by an fk's\n`param`, a cellid's `model`, or a `when`.\n\n- `type`: what the parameter names, a selector as in `parent`.\n- `rule`: as on an fk (see `RULES`), a named subset of the target.\n\nA binding that passes no argument leaves it unset, i.e. null. Relationally, a\nparameter is a column on the child's row, with a foreign key.", + "properties": { + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "title": "Type" + }, + "rule": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Rule" + } + }, + "required": [ + "type" + ], + "title": "Param", + "type": "object" + }, "PrimaryKey": { "additionalProperties": false, "description": "A list item column's key: rows are unique by it. With a `scope`, the\nname of a sibling column, rows are unique by (scope, key) instead, e.g.\nMVR's `pname` within `mname`. Written `pk = true` when unscoped.", @@ -2442,6 +2534,21 @@ "default": null, "title": "Keys" }, + "params": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/$defs/Param" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Params" + }, "type": { "const": "simulation", "default": "simulation", diff --git a/modflow_devtools/dfns/schema.py b/modflow_devtools/dfns/schema.py index f00106cc..84b52650 100644 --- a/modflow_devtools/dfns/schema.py +++ b/modflow_devtools/dfns/schema.py @@ -8,6 +8,7 @@ from boltons.dictutils import OMD from pydantic import ( + AfterValidator, BaseModel, BeforeValidator, ConfigDict, @@ -144,26 +145,22 @@ def _derive_tagged(self) -> bool: _IDENT_RE = re.compile(r"^[A-Za-z_]\w*$") -# Named rules an fk can carry (see `ForeignKey.rule`): constraints the spec -# states in prose (`docs/md/dfnspec.md`, "Rules") and consumers implement in -# code, like JSON Schema's `format`. Each names a subset of the fk's target -# that the value must fall in, which no path or scope can say: most depend -# on where the component's file is bound in a simulation. A consumer that -# doesn't implement a rule skips its check. +# Named rules an fk or a parameter can carry (see `ForeignKey.rule`, +# `Param.rule`): constraints the spec states in prose (`docs/md/dfnspec.md`, +# "Rules") and consumers implement in code, like JSON Schema's `format`. +# Each names a subset of the target that the value must fall in, which no +# path, scope or parameter can say. A consumer that doesn't implement a rule +# skips its check. RULES: dict[str, str] = { - "exchange_models": ( - "one of the two models (exgmnamea, exgmnameb) of the exchange whose " - "MVR6 option binds this mover (GwfExchangeMover.f90:87-93)" - ), "flow_model": ( "in the flow model a GWF-GWT or GWF-GWE exchange pairs with this " "transport model (exg-gwfgwt.f90:124-129); unchecked with flows from " "a budget file" ), - "ssm_package": ( - "in the flow package named by the SSM fileinput row that binds this " - "SPC file (TspSpc.f90:326-349); unchecked if an advanced transport " - "package is paired with it (tsp-ssm.f90:195)" + "no_advanced_transport": ( + "a flow package with no advanced transport package paired with it, " + "since SSM then doesn't apply the SPC file it binds (tsp-ssm.f90:195); " + "with one, the file is unchecked" ), "mover_enabled": ( "a package that plays mover_provider or mover_receiver, i.e. whose " @@ -191,12 +188,19 @@ class ForeignKey(BaseModel): component, it matches the target pk's own scope; across components, it picks the target component's instance. Written `fk = ""` when unscoped. - - `key`, with a `scope` or `rule`: a key set (see `KeySet`) that the - component of the picked instance declares, by name, in its `keys`. For - an instance whose type is known only at runtime (MVR's `id1`). + - `key`, with a `scope` or `param` that picks the instance: a key set + (see `KeySet`) that the instance's component declares, by name, in + its `keys`. For an instance whose type is known only at runtime + (MVR's `id1`). + - `param` alone, one or more of this component's parameters (see + `Param`): the value names the instance one of them is set to (MVR's + `mname1`: `model1` or `model2`). + + With a `key`, `param` names the parameter whose instance declares the + key set (SPC's `bndno`: `target`). A `rule` (see `RULES`) names a subset of the target the value must fall - in. It may stand in for a `scope` (MVR's `mname1`: `exchange_models`) or + in. It may stand in for a `scope` (SSM's `pname`: `flow_model`) or restrict a scoped target further (MVR's `packages.pname`: `mover_enabled`). """ @@ -207,6 +211,30 @@ class ForeignKey(BaseModel): scope: str | None = None key: str | None = None rule: str | None = None + param: str | tuple[str, ...] | None = None + + @field_validator("param", mode="before") + @classmethod + def _check_param(cls, v: Any) -> Any: + if isinstance(v, list | tuple): + v = tuple(v) + if not v: + raise ValueError("an fk's param needs at least one parameter") + if len(v) == 1: + v = v[0] + elif len(set(v)) != len(v): + raise ValueError(f"fk params {list(v)!r} repeat a parameter") + for name in (v,) if isinstance(v, str) else v or (): + if not isinstance(name, str) or not _IDENT_RE.match(name): + raise ValueError(f"fk param {name!r} must be a parameter name") + return v + + @property + def params(self) -> tuple[str, ...]: + """The parameters `param` names, none or more.""" + if self.param is None: + return () + return (self.param,) if isinstance(self.param, str) else self.param @field_validator("path") @classmethod @@ -236,10 +264,22 @@ def _check_rule(cls, v: str | None) -> str | None: @model_validator(mode="after") def _check_form(self) -> "ForeignKey": - if (self.path is None) == (self.key is None): - raise ValueError("an fk needs exactly one of path and key") - if self.key is not None and self.scope is None and self.rule is None: - raise ValueError("an fk with a key needs a scope or rule, which picks the instance") + if self.path is not None: + if self.key is not None or self.param is not None: + raise ValueError("an fk with a path can't have a key or param") + return self + if self.key is not None: + if (self.scope is None) == (self.param is None): + raise ValueError( + "an fk with a key needs one of scope and param, which picks the instance" + ) + if self.param is not None and not isinstance(self.param, str): + raise ValueError("an fk with a key picks its instance by one param") + return self + if self.param is None: + raise ValueError("an fk needs one of path, key and param") + if self.scope is not None or self.rule is not None: + raise ValueError("an fk with only a param can't have a scope or rule") return self @property @@ -283,7 +323,12 @@ def _dump_pk(key: PrimaryKey) -> Any: def _dump_fk(key: ForeignKey) -> Any: - return key.path if key.simple else key.model_dump(exclude_none=True) + if key.simple: + return key.path + data = key.model_dump(exclude_none=True) + if isinstance(key.param, tuple): + data["param"] = list(key.param) + return data PK = Annotated[ @@ -364,6 +409,34 @@ def bound(self) -> str | None: return None +class Param(BaseModel): + """ + A binding parameter: what a component's references need from its + binding, beyond its parent (see `docs/md/dfnspec.md`, "Bindings"). + Declared in the component's `params`, like a function's signature, and + passed by the binding's `args` (see `File`). Named by an fk's + `param`, a cellid's `model`, or a `when`. + + - `type`: what the parameter names, a selector as in `parent`. + - `rule`: as on an fk (see `RULES`), a named subset of the target. + + A binding that passes no argument leaves it unset, i.e. null. Relationally, a + parameter is a column on the child's row, with a foreign key. + """ + + model_config = ConfigDict(frozen=True, extra="forbid") + + type: str | list[str] + rule: str | None = None + + @field_validator("rule") + @classmethod + def _check_rule(cls, v: str | None) -> str | None: + if v is not None and v not in RULES: + raise ValueError(f"parameter rule {v!r} is not one of {sorted(RULES)!r}") + return v + + def _dump_keyset(k: KeySet) -> Any: path = k.path if isinstance(k.path, str) else list(k.path) return path if k.when is None else {"path": path, "when": k.when} @@ -416,11 +489,18 @@ def _check_dynamic(self) -> "String": # only the field's description defines (e.g. SFR's `ic`). Index = bool | Literal["signed"] -# Which model's grid a cellid refers to. `None`: the component's own model. -# `"1"` / `"2"`: the first / second model of the enclosing context, i.e. -# `EXGMNAMEA` / `EXGMNAMEB` under an exchange; under a single model both are -# that model. -CellModel = Literal["1", "2"] | None + +def _check_cell_model(v: Any) -> Any: + if v is not None and not (isinstance(v, str) and _IDENT_RE.match(v)): + raise ValueError(f"a cellid's model {v!r} must be a parameter name") + return v + + +# Which model's grid a cellid refers to: the name of one of the component's +# parameters of type `model` (see `Param`), such as an exchange's `model1`, +# set from `EXGMNAMEA`. `None`, or a parameter left unset where the file is +# bound (GNC's `model1` under a model): the component's own model. +CellModel = Annotated[str | None, AfterValidator(_check_cell_model)] class Integer(InputFieldBase): @@ -475,6 +555,19 @@ class File(InputFieldBase): # A sibling String whose value is the target's ftype token (e.g. `DIS6`), # picking one family among the selector's matches. See `Dfns.ftype_family`. component_ftype: str | None = None + # The arguments this binding passes the target's parameters (see `Param`), + # by name: each a sibling field with an fk (`exgmnamea`), or a parameter + # of this component, passed down (an exchange's `model1`). See + # `_validate_params`. + args: dict[str, str] | None = None + + @field_validator("args") + @classmethod + def _check_args(cls, v: dict[str, str] | None) -> dict[str, str] | None: + for name, value in (v or {}).items(): + if not _IDENT_RE.match(name) or not _IDENT_RE.match(value): + raise ValueError(f"file arg {name!r} = {value!r} must name a column or parameter") + return v Scalar = Annotated[ @@ -1513,6 +1606,9 @@ class ComponentBase(BaseModel): # Named key sets other components' fks can name by `key`, without knowing # this component's type (MVR's `id1`: `mover_provider`). See `KeySet`. keys: dict[str, KeySetT] | None = None + # What this component needs from whatever binds its file, by name (an + # exchange's `model1`, SPC's `target`). See `Param`. + params: dict[str, Param] | None = None @field_validator("keys") @classmethod @@ -1522,6 +1618,14 @@ def _check_key_names(cls, v: dict[str, KeySet] | None) -> dict[str, KeySet] | No raise ValueError(f"key set name {name!r} must be an identifier") return v + @field_validator("params") + @classmethod + def _check_param_names(cls, v: dict[str, Param] | None) -> dict[str, Param] | None: + for name in v or {}: + if not _IDENT_RE.match(name): + raise ValueError(f"parameter name {name!r} must be an identifier") + return v + @model_serializer(mode="wrap") def _serialize(self, handler: Any) -> dict[str, Any]: data = handler(self) @@ -1833,6 +1937,15 @@ def _check_record_keys(fields: dict, outer: "Mapping[str, InputField] | None" = chain.append(scope) +def _param_components(spec: "Dfns", owner: "ComponentBase", name: str) -> set[str]: + """The components a parameter of `owner` can name, by its `type`, or + nothing if `owner` declares no such parameter.""" + param = (owner.params or {}).get(name) + if param is None: + return set() + return {n for n, c in spec.components.items() if admits(param.type, c)} + + def _named_components( spec: "Dfns", owner: "ComponentBase", @@ -1844,8 +1957,8 @@ def _named_components( The components whose instance a column's value can name: whatever its fk's target column names, following fks across components (MVR's `pname1` names a `packages` row, whose `pname` names a package in a - model), or, for a column without an fk that is the pk of a binding row, - the components that row binds (a name file's `pname`). + model), or, for a column without an fk that is a pk and a sibling of a + binding, the components that binding binds (a name file's `pname`). """ if (owner.name, column.name) in seen: return set() @@ -1862,6 +1975,8 @@ def _named_components( for n, c in spec.components.items() if any(admits(s, c) for s in selectors) and admits(c.parent, owner) } + if fk is not None and fk.param is not None and fk.key is None: + return {n for p in fk.params for n in _param_components(spec, owner, p)} if fk is None or fk.path is None: return set() target = spec.components.get(fk.component) if fk.component else owner @@ -1901,10 +2016,27 @@ def _validate_fk_fields(component: "ComponentBase", spec: "Dfns") -> None: Observation fields (see `ObservationField`) are checked like a list item's. """ + def _check_param(field: "InputField", fk: ForeignKey) -> None: + for name in fk.params: + if name not in (component.params or {}): + raise ValueError( + f"Field {field.name!r} fk param={name!r}: {component.name} declares " + f"no such parameter" + ) + def _check_fk(field: "InputField", fk: ForeignKey, fields: dict) -> None: + _check_param(field, fk) if fk.key is not None: _check_key(field, fk, fields) return + if fk.path is None: + # A param alone: the value names the instance a parameter is set to. + if not isinstance(field, String): + raise ValueError( + f"Field {field.name!r} fk param={fk.param!r}: only a string column " + f"may name a parameter's instance" + ) + return assert fk.path is not None and fk.block is not None and fk.field is not None where_fk = f"Field {field.name!r} fk={fk.path!r}" target = component @@ -1961,8 +2093,9 @@ def _check_key(field: "InputField", fk: ForeignKey, fields: dict) -> None: named = sorted(_named_components(spec, component, fields, fields[fk.scope])) where_named = f"the scope {fk.scope!r} can name ({named!r})" else: - named = sorted(spec.components) - where_named = "in the spec" + assert isinstance(fk.param, str) + named = sorted(_param_components(spec, component, fk.param)) + where_named = f"the parameter {fk.param!r} can name" declaring = [spec.components[n] for n in named if fk.key in (spec.components[n].keys or {})] if not declaring: raise ValueError(f"{where_fk}: no component {where_named} declares key {fk.key!r}") @@ -2119,13 +2252,19 @@ def _validate_cellid_fields(component: "ComponentBase") -> None: meaningful as a column in a list item, where each row names its cell(s), or in an observation field, which reads like one. - Which grid a cellid refers to (its `model`) must be unambiguous: an - exchange's cellids refer to one of its two models, never its own, so each - must say which (`"1"` / `"2"`); and a list item naming either model on one - cellid column must name one on every cellid column (GNC's `cellidn`, - `cellidm`, `cellidsj`). + Which grid a cellid refers to (its `model`) must be unambiguous: a + `model` names one of the component's parameters whose type admits a + model (see `Param`); an exchange's cellids refer to one of its two + models, never its own, so each must name one; and a list item naming a + model on one cellid column must name one on every cellid column (GNC's + `cellidn`, `cellidm`, `cellidsj`). """ exchange = getattr(component, "subtype", None) == "exchange" + model_params = { + n + for n, p in (component.params or {}).items() + if any(t in ("model", "*") or t.endswith("-nam") for t in _parents_as_set(p.type)) + } def _cellids(field: "InputField", in_item: bool) -> "list[list[CellID | Array]]": """The cellid columns of each way a row may read `field`: one @@ -2140,7 +2279,12 @@ def _cellids(field: "InputField", in_item: bool) -> "list[list[CellID | Array]]" if exchange and field.model is None: raise ValueError( f"{type(field).__name__} {field.name!r}: an exchange's cellid must say " - f"which model's grid it refers to (model='1' or '2')" + f"which model's grid it refers to, by a parameter" + ) + if field.model is not None and field.model not in model_params: + raise ValueError( + f"{type(field).__name__} {field.name!r}: model={field.model!r} names no " + f"parameter of {component.name} whose type is a model" ) return [[field]] if isinstance(field, List): @@ -2160,8 +2304,8 @@ def _check_item(item: "InputField") -> None: if len({c.model is None for c in cellids}) > 1: names = {c.name: c.model for c in cellids} raise ValueError( - f"Cellid columns {names!r} mix model=None with '1'/'2': if one names " - f"a model, every cellid column in the item must" + f"Cellid columns {names!r} mix model=None with a named model: if one " + f"names a model, every cellid column in the item must" ) for block in (component.blocks or {}).values(): @@ -2173,16 +2317,86 @@ def _check_item(item: "InputField") -> None: def _validate_when_fields(component: "ComponentBase") -> None: """Every field's `when` (at any depth) names a keyword in the - component's options block.""" + component's options block, or one of its parameters (see `Param`), which + switches the field on when it's set. A keyword can't name itself.""" options = (component.blocks or {}).get("options") keywords = {n for n, f in (options.fields if options else {}).items() if isinstance(f, Keyword)} + names = keywords | set(component.params or {}) fields = [f for block in (component.blocks or {}).values() for f in block.fields.values()] for field in [*fields, *(component.observations or {}).values()]: for f in _iter_subfields(field): - if f.when is not None and f.when not in keywords: + if f.when is None: + continue + if f.when not in names: raise ValueError( - f"{f.name!r}: when={f.when!r} names no keyword option of {component.name}" + f"{f.name!r}: when={f.when!r} names no keyword option or parameter of " + f"{component.name}" ) + if f.when == f.name: + raise ValueError(f"{f.name!r}: when={f.when!r} names the field itself") + + +def _validate_args(component: "ComponentBase", spec: "Dfns") -> None: + """ + Each File's `args` pass parameters (see `Param`) of every component it + can bind, as a call passes a function's arguments: + + - each component the File can bind (see `Dfns.children`) declares each + parameter passed; + - each argument names exactly one of a sibling of the File and a + parameter of this component, so nothing is searched; + - a column has an fk, and can name something of the parameter's type + (see `_named_components`); a parameter's type can name something of + the parameter's type. + + A parameter a binder doesn't pass is unset there. Fks, cellids and + `when`s that name a parameter are checked with the fields they're on. + """ + for block in (component.blocks or {}).values(): + for file, row in _iter_files(block.fields): + if not file.args: + continue + where = f"{component.name}: File {file.name!r}" + if file.component is None: + raise ValueError(f"{where}: args without a component") + targets = [ + c for c in spec.children(component.name).values() if admits(file.component, c) + ] + for target in targets: + for name in file.args: + if name not in (target.params or {}): + raise ValueError( + f"{where}: passes {name!r}, which {target.name} doesn't declare" + ) + for name, value in file.args.items(): + at = f"{where}: arg {name}={value!r}" + column = row.get(value) + is_param = value in (component.params or {}) + if (column is None) == (not is_param): + raise ValueError( + f"{at} must name exactly one of a sibling column and a parameter " + f"of {component.name}" + ) + if is_param: + named = _param_components(spec, component, value) + else: + if getattr(column, "fk", None) is None: + raise ValueError(f"{at}: the column has no fk to follow") + named = _named_components(spec, component, row, column) + for target in targets: + param = (target.params or {})[name] + if not named & _param_components(spec, target, name): + raise ValueError(f"{at} names nothing of type {param.type!r}") + + +def _validate_params(component: "ComponentBase", spec: "Dfns") -> None: + """Each parameter's `type` admits some component in the spec.""" + for name, param in (component.params or {}).items(): + if not _param_components(spec, component, name): + raise ValueError( + f"{component.name}: parameter {name!r}: type={param.type!r} admits no " + f"component in the spec" + ) def _obs_columns(field: "InputField") -> list[int]: @@ -2539,6 +2753,10 @@ def _validate_relations(self) -> "Dfns": _validate_fk_fields(component, self) for name, component in self.components.items(): _validate_file_links(component, self) + for name, component in self.components.items(): + _validate_params(component, self) + for name, component in self.components.items(): + _validate_args(component, self) for name, component in self.components.items(): _validate_cellid_fields(component) for name, component in self.components.items():