Skip to content

Commit 6d0ec17

Browse files
committed
update spec
1 parent 1c92ed8 commit 6d0ec17

1 file changed

Lines changed: 68 additions & 50 deletions

File tree

docs/md/dfnspec.md

Lines changed: 68 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
- [Integer](#integer)
4646
- [Type-specific attributes](#type-specific-attributes-3)
4747
- [`valid`](#valid-1)
48-
- [`dimension`](#dimension)
4948
- [`time_series`](#time_series)
5049
- [`pk`](#pk-1)
5150
- [`fk`](#fk-1)
@@ -75,7 +74,9 @@
7574
- [Array dimensions](#array-dimensions)
7675
- [Derived dimensions](#derived-dimensions)
7776
- [Row-level column lookups](#row-level-column-lookups)
77+
- [Intra-record sibling references](#intra-record-sibling-references)
7878
- [Scope and resolution](#scope-and-resolution)
79+
- [Dimension scope](#dimension-scope)
7980
- [Primary/foreign keys](#primaryforeign-keys)
8081
- [Examples](#examples)
8182

@@ -94,7 +95,7 @@ Component definitions consist primarily of a name, zero or more block definition
9495
- `blocks`: block definitions
9596
- `parent`: parent component(s)
9697
- `schema_version`: DFN schema version
97-
- `derived_dims`: dimensions computed from other dimensions
98+
- `dims`: named dimensions (field-backed or derived) available for use in array shapes
9899

99100
Components may refer to, i.e. be constrained by, other components. Cross-component constraints include parent-child relations, solution compatibility, and format variants.
100101

@@ -334,10 +335,6 @@ Type `integer`.
334335

335336
`[integer] | null`. Permitted values (enumeration constraint). Empty list is treated as absent.
336337

337-
###### `dimension`
338-
339-
`"record" | "component" | "model" | "simulation" | true | false | null (default: null)`. Declares this field as a dimension source and specifies its scope. See [Scope and resolution](#scope-and-resolution). A `true` value indicates `"component"` scope; `false` and `null` are equivalent.
340-
341338
###### `time_series`
342339

343340
`boolean (default: false)`. Marks fields where the parser accepts either a numeric literal or a time-series name (referencing a `utl-ts` object). Not inferable from structural type. Also appears on array fields (where it references a `utl-tas` object instead). Note that `utl-tas` currently only works with layered arrays, not full-grid arrays, though generalizing has been considered.
@@ -416,12 +413,6 @@ Each declared shape expression is either a global dimension name (explicit or de
416413

417414
`string | null (default: null)`. Names the field (within the same component) whose runtime length determines how many times this field is read sequentially within an array block, with each reading appended to an accumulated sequence. See `repeat` section below.
418415

419-
###### `dimension`
420-
421-
`"component" | "model" | "simulation" | null (default: null)`. Marks this self-sizing array as a dimension source at the given scope: the array's name may appear in other arrays' `shape` expressions to mean "one value per element of this array." Valid only on self-sizing arrays (`shape` must be empty); a schema error on arrays with a declared shape.
422-
423-
Because a self-sizing array is read inline and dynamically sized, the parser knows its element count immediately after reading the line — no separate integer field is needed to declare the size in advance. That count is what other arrays reference when they name this array in their `shape`. The dtype of the dimension-source array is not constrained: a string array whose elements are named identifiers (e.g. auxiliary variable names) and a numeric array whose elements happen to fix a count both provide the same thing to the shape system — a dynamic integer size. `"record"` scope is not valid for arrays.
424-
425416
#### Record
426417

427418
Type `record`. Product type. In MF6 input files, records appear on a single line. Record subfields may or may not be `tagged`. While blocks can be considered product types also, in the DFN specification only records are considered fields; blocks are considered named collections of related fields.
@@ -458,15 +449,33 @@ Type `list`. Collection type. Unlimited but for one rule: a list may not contain
458449

459450
### Array dimensions
460451

461-
A field defined in one component can be referenced by name in the `shape` expression of an array field in the same or another component. Two field types may serve as dimension sources:
452+
Dimensions are declared at the component level via the `dims` map, not on individual fields. Each entry in `dims` is a `DimDef`:
453+
454+
```yaml
455+
dims:
456+
nlay:
457+
field: nlay # backed by an integer field named 'nlay' in this component
458+
scope: model
459+
nodes:
460+
expr: "nlay * nrow * ncol" # derived from other dims
461+
scope: model
462+
nper:
463+
field: nper
464+
scope: simulation
465+
```
466+
467+
A `DimDef` has exactly one of:
468+
- `field`: the name of an `integer` field in this component that provides the dimension value
469+
- `expr`: a Python arithmetic expression that derives the dimension from other known dims
462470

463-
- `integer` fields with `dimension: true`
464-
- `array` fields with `dtype: "string"` and `dimension: true` — the array's name becomes a valid shape dim meaning "one value per element of this string array"
471+
And a `scope` (see [Scope and resolution](#scope-and-resolution)) that controls which other components can see it.
472+
473+
Self-sizing `array` fields (those with `shape: []`) may also serve as dimension sources: any such array's name may appear in a `shape` expression to mean "one element per item in this array." These are registered in `dims` with `field` pointing to the array name.
465474

466475
Shape expressions for non-string arrays may use one of three structural forms. All three may additionally carry a bound annotation:
467476

468477
- **Dim reference** (`^[A-Za-z_]\w*$`): a plain identifier resolved via the scope chain (explicit → derived → inherited dims). When the array is a subfield of a record and the identifier does not resolve globally, resolution falls back to intra-record sibling scope (see below).
469-
- **Intra-record sibling reference**: a dim reference that names a sibling `integer` or `dimension: true` `array` in the same enclosing record. Makes the record a variadic tuple whose width varies per row. Valid only when the array is a subfield of a record. See below.
478+
- **Intra-record sibling reference**: a dim reference that names a sibling `integer` in the same enclosing record. Makes the record a variadic tuple whose width varies per row. Valid only when the array is a subfield of a record. See below.
470479
- **Row-level column lookup** (`block.column(fk_field)`): a cross-list per-row quantity, valid only for array subfields of records. See below.
471480

472481
Any dim reference (either of the first two forms) may carry a **bound annotation** prefix (`<`, `>`, `<=`, or `>=`). The dim portion validates normally; the bound is advisory and is not enforced by the MF6 parser.
@@ -475,29 +484,37 @@ A shape expression that does not match one of these forms is a schema validation
475484

476485
#### Derived dimensions
477486

478-
The optional component attribute `derived_dims` maps dimension names to expressions with which to evaluate the dimension size. Expressions use Python arithmetic syntax. Operands may be:
487+
A `DimDef` with an `expr` rather than a `field` is a derived dimension. Its expression uses Python arithmetic syntax. Operands may be:
479488

480-
- Explicit dimensions: any `dimension: true` field in this component
481-
- Derived dimensions: another derived dimension; circular dependencies are a schema error
489+
- Explicit dimensions: any field-backed dim in this component's `dims`
490+
- Other derived dims: another derived dim in this component; circular dependencies are a schema error
482491
- Functions of columns in a tabular list block: `sum(block.list.column)` sums the integer values of `column` across all rows of list field `list` in block `block`. When the list field shares its name with its containing block — the MF6 convention — the block qualifier may be omitted: `sum(list.column)`.
483492

493+
Derived dims carry the same `scope` as field-backed dims and are visible to other components under the same rules.
494+
484495
Canonical examples:
485496

486497
```yaml
487-
# gwf-dis: nodes is derived; ncpl is also derived to give packages a uniform
488-
# per-layer cell count dim regardless of discretization type (DISV has ncpl
489-
# as an explicit dim; DIS derives it from nrow * ncol)
490-
nodes: "nlay * nrow * ncol"
491-
ncpl: "nrow * ncol"
492-
493-
# gwf-disv: ncpl is explicit; nodes is derived
494-
nodes: "nlay * ncpl"
495-
496-
# gwf-lak
497-
total_lake_connections: "sum(packagedata.nlakeconn)"
498-
499-
# gwf-evt
500-
nseg_minus_1: "nseg - 1"
498+
dims:
499+
# gwf-dis: nodes and ncpl are derived to give packages a uniform dim
500+
# regardless of discretization type (DISV has ncpl explicit; DIS derives it)
501+
nlay: {field: nlay, scope: model}
502+
nrow: {field: nrow, scope: model}
503+
ncol: {field: ncol, scope: model}
504+
ncpl: {expr: "nrow * ncol", scope: model}
505+
nodes: {expr: "nlay * nrow * ncol", scope: model}
506+
ncelldim: {expr: "3", scope: model}
507+
508+
# gwf-disv: ncpl is explicit; nodes is derived
509+
ncpl: {field: ncpl, scope: model}
510+
nodes: {expr: "nlay * ncpl", scope: model}
511+
ncelldim: {expr: "2", scope: model}
512+
513+
# gwf-lak
514+
total_lake_connections: {expr: "sum(packagedata.nlakeconn)", scope: component}
515+
516+
# sim-tdis
517+
nper: {field: nper, scope: simulation}
501518
```
502519

503520
#### Row-level column lookups
@@ -557,7 +574,7 @@ connectiondata:
557574

558575
#### Intra-record sibling references
559576

560-
A record may also be a variadic tuple without any FK-linked list. If a record contains an `integer` field (or a string-dim `array`) followed by an `array` whose shape names that preceding field, the record is self-describing: it carries its own count. The `array`'s shape element is a plain identifier that resolves to the sibling field, not to a global dim.
577+
A record may also be a variadic tuple without any FK-linked list. If a record contains an `integer` field followed by an `array` whose shape names that field, the record is self-describing: it carries its own count. The `array`'s shape element is a plain identifier that resolves to the sibling field, not to a global dim.
561578

562579
```yaml
563580
connectiondata:
@@ -580,7 +597,7 @@ connectiondata:
580597

581598
Validation rules:
582599
- Valid only when the array is a subfield of a record (not a top-level block field).
583-
- The sibling field must be an `integer` with `dimension: "record"`. The `"record"` scope annotation makes the role explicit and prevents accidental resolution of unrelated integer fields.
600+
- The sibling field must be an `integer`. No special annotation is required on the sibling.
584601
- Resolution order: the scope chain is tried first; sibling resolution is only the fallback when the identifier does not resolve globally.
585602

586603
#### Bound-annotated shape expressions
@@ -598,28 +615,29 @@ sfacval:
598615

599616
Dim references (plain identifiers) resolve in this order:
600617

601-
1. Local explicit dims: `integer` fields with `dimension` set and self-sizing `array` fields (i.e. `shape: []`) with `dimension` set, in this component
602-
2. Local derived dims: entries in this component's `derived_dims`, resolved in dependency order
603-
3. Inherited dims: explicit dims from other components in the spec (filtered by scope — `"model"` dims available to packages in the same model, `"simulation"` dims available to all)
604-
4. Intra-record siblings with `dimension: "record"`: a sibling `integer` in the same enclosing record that has been explicitly marked as a per-row inline count — fallback when steps 1–3 all fail and the array is inside a record.
618+
1. **Local dims**: entries in this component's `dims` map — both field-backed and derived — resolved in dependency order.
619+
2. **Inherited dims**: dims from other components in the spec, filtered by their `scope` and the requesting component's `parent` attribute (see below).
620+
3. **Intra-record sibling**: a sibling `integer` in the same enclosing record — fallback when steps 1–2 fail and the array is inside a record.
605621

606622
Row-level column lookups and bound annotations (`<dim` etc.) are not resolved via this scope chain. Row-level lookups are evaluated per row at parse time using the FK relationship. Bound annotations are advisory only.
607623

608-
#### Explicit dimension scope
624+
#### Dimension scope
609625

610-
The `dimension` attribute is categorical, not binary. Valid values differ by field type:
626+
Each `DimDef` carries a `scope: "component" | "model" | "simulation"` that determines which other components can inherit it:
611627

612-
- For `integer`: `"self" | "model" | "simulation" | true | false | null`
613-
- For `array` (self-sizing only, i.e. `shape: []`): `"component" | "model" | "simulation" | null`
628+
- **`"component"`**: only visible within this component (or to subpackages that list this component as their explicit parent).
629+
- **`"model"`**: visible to any component that can share the same model instance, determined dynamically from `parent` attributes. A dim defined in component A (with `scope: "model"`) is visible to component B when:
630+
- A's `parent` contains a concrete model-name entry (e.g. `"gwf-nam"`, `"chf-nam"`) — meaning A is model-attached, and
631+
- B's `parent` contains either that same model-name entry, or a generic type (`"model"`, `"package"`, `"*"`) — meaning B can belong to the same model.
632+
633+
This means `"model"` scope is not tied to any specific model type. A package that can attach to any model (e.g. a utility with `parent: "package"`) inherits model-scoped dims from whichever grid discretization is in its model.
614634
615-
Meanings:
616-
- `null` (default): not a dimension source.
617-
- `"record"`: intra-record scope. The integer field is an inline count on the same record row. Valid only on `integer`. Makes the sibling-resolution fallback (scope level 4) explicit and annotated.
618-
- `"component"`: available within this component.
619-
- `"model"`: exported to model scope; available to all packages whose parent is the same model. Example: `nrow`, `ncol`, `nlay` in `gwf-dis` are model-scoped — accessible to `gwf-chd`, `gwf-wel`, and all other packages in the same GWF model.
620-
- `"simulation"`: exported to simulation scope; available to all components. Example: `nper` in `sim-tdis`.
635+
- **`"simulation"`**: always visible to all components.
621636
622-
Dimension `true` is equivalent to `"component"`, `false` to `null`.
637+
Examples:
638+
- `nrow`, `ncol`, `nlay` in `gwf-dis` — `scope: "model"` — accessible to `gwf-chd`, `gwf-wel`, `utl-spca` (which has `parent: "package"`), and any other component attached to the same GWF model.
639+
- `nper` in `sim-tdis` — `scope: "simulation"` — accessible everywhere.
640+
- `total_lake_connections` in `gwf-lak` — `scope: "component"` — private to that component.
623641

624642
### Primary/foreign keys
625643

0 commit comments

Comments
 (0)