Version: dev

SnowLayerGeometry

Single-layer snow-pack geometry for the CLMU urban canopy thermal stack: given the total snow depth z_sno, computes the bottom-layer thickness, node depth, and top interface depth used by the snow conduction column. Migrated from UrbanCanopy.jl@9200edd4 src/roof_wall_road_snow_temperatures.jl:33 (`SnowLayerGeometry` @component). Implements Oleson et al. (2010, CLM Tech Note Ch. 4) §4.1 Eqs. 4.9-4.10 in the simplest snl = -1 (single snow layer) regime, where Δz_0 = z_sno (pp. 92-93). Parameterized as observed-only: z_sno is the input (parameter); Δz_snow_0, z_node_snow_0, z_interface_snow_neg1 are the algebraic outputs of three identity/linear equations.

.esm schema
1.0.0
Source
components/urban_canopy/temps/snow_layer_geometry.esm

Description

Single-layer snow-pack geometry for the CLMU urban canopy thermal stack: given the total snow depth z_sno, computes the bottom-layer thickness, node depth, and top interface depth used by the snow conduction column. Migrated from UrbanCanopy.jl@9200edd4 src/roof_wall_road_snow_temperatures.jl:33 (SnowLayerGeometry @component). Implements Oleson et al. (2010, CLM Tech Note Ch. 4) §4.1 Eqs. 4.9-4.10 in the simplest snl = -1 (single snow layer) regime, where Δz_0 = z_sno (pp. 92-93). Parameterized as observed-only: z_sno is the input (parameter); Δz_snow_0, z_node_snow_0, z_interface_snow_neg1 are the algebraic outputs of three identity/linear equations.

Reference

_source_ref: UrbanCanopy.jl@9200edd4f15609f20339d3a7d104de8064544310:src/roof_wall_road_snow_temperatures.jl:33 Upstream SnowLayerGeometry is an MTK @component with one @parameters input (z_sno), three time-indexed @variables outputs (Δz_snow_0, z_node_snow_0, z_interface_snow_neg1), and one @constants declaration (zero_m = 0.0 m). Three algebraic equations close the system: Δz_snow_0 ~ z_sno, z_node_snow_0 ~ zero_m - 0.5*Δz_snow_0, z_interface_snow_neg1 ~ zero_m - Δz_snow_0.

The upstream @constants begin zero_m = 0.0, [unit = u"m"] end is encoded here as a zero-default parameter (units m). This mirrors the convention used by sibling observed-only ESM files in this rig that translate @constants to fixed-default parameters so the units flow through MTK’s unit checker: snow_soil_blended_heat_capacity.esm encodes C_ice the same way, and snow_melt_no_layers.esm uses zero_kgm2 / zero_m for the same reason.

The component represents the snl = -1 single-snow-layer case from Oleson pp. 92-93: when z_sno spans the single-layer regime (0.01 ≤ z_sno ≤ 0.03), all snow lives in layer 0, so Δz_0 = z_sno, node z_0 sits at the layer midpoint, and the top interface z_{h,-1} sits at the surface offset by Δz_0. Multi-layer snow (snl < -1, requiring procedural branching on snow depth) is not exposed here — the upstream component’s docstring notes that multi-layer packing logic is procedural and typically handled outside the equation system.

Unicode Δz_snow_0 is preserved from upstream — the schema’s identifier pattern allows it and other temps/ ESM components already use unicode delta in identifiers (snow_melt_no_layers.esm uses Δt, snow_soil_blended_heat_capacity.esm uses Δz).

Observability classification: this component is functionally observed-only — each output variable is given by a single algebraic equation in terms of parameters and the other outputs (no derivatives, no time-dependent dynamics). Test scenarios use time_span: [0, 1] and sample at t=0 to mirror the convention used by sibling observed-only ESM files (snow_soil_blended_heat_capacity.esm, snow_melt_no_layers.esm, total_phase_change_energy.esm).

Tolerance rationale (model-level rel=1.0e-10): three algebraic identity/linear equations (no ODE integration). Δz_snow_0 ~ z_sno is exact assignment; z_node_snow_0 and z_interface_snow_neg1 involve one subtraction (and one halving) of the parameter, so roundoff is at the IEEE-754 ULP level (~1e-16). The upstream test uses rtol = 1.0e-10 (test/roof_wall_road_snow_temperatures_test.jl:33-36) and we mirror that for fidelity. Tighter abs: 1e-12 is asserted on outputs that take their full magnitude from z_sno in the zero-input regime so the assertion does not silently accept a stuck-at-zero regression._

Parameters

NameUnitsDefaultDescription
z_snom0.0Total snow depth (Oleson Ch. 4 §4.1 — single-layer regime input, 0.01 ≤ z_sno ≤ 0.03 m for snl = -1)
zero_mm0.0Zero depth reference for the snow column (upstream @constants zero_m = 0.0 m; encoded as a fixed-default parameter so the units flow through MTK’s unit checker)

Observed

NameUnitsDefaultDescription
Δz_snow_0mThickness of bottom snow layer (layer 0); Eq. 4.9 single-layer case: Δz_0 = z_sno
z_node_snow_0mNode depth of bottom snow layer (Oleson Eq. 4.9): z_0 = z_{h,0} - 0.5·Δz_0 with z_{h,0} = 0 (surface), so z_0 = -0.5·Δz_0 (negative = above ground)
z_interface_snow_neg1mInterface depth above bottom snow layer (Oleson Eq. 4.10): z_{h,-1} = z_{h,0} - Δz_0 with z_{h,0} = 0, so z_{h,-1} = -Δz_0 (negative = above ground)

Observed expressions

$$ Δz_{snow\,0} = z_{sno} $$

Thickness of bottom snow layer (layer 0); Eq. 4.9 single-layer case: Δz_0 = z_sno

$$ z_{node\,snow\,0} = zero_{m} - 0.5 \cdot Δz_{snow\,0} $$

Node depth of bottom snow layer (Oleson Eq. 4.9): z_0 = z{h,0} - 0.5·Δz_0 with z_{h,0} = 0 (surface), so z_0 = -0.5·Δz_0 (negative = above ground)_

$$ z_{interface\,snow\,neg1} = zero_{m} - Δz_{snow\,0} $$

Interface depth above bottom snow layer (Oleson Eq. 4.10): z{h,-1} = z_{h,0} - Δz_0 with z_{h,0} = 0, so z_{h,-1} = -Δz_0 (negative = above ground)_

Analyses

single_layer_geometry_sweep

Sweep snow depth z_sno across the single-layer band (0.01-0.03 m per Oleson pp. 92-93) at 11 evenly spaced points. Surfaces the three linear/identity outputs as functions of z_sno: Δz_snow_0 traces the 1:1 identity line, z_node_snow_0 traces a slope -1/2 line through the origin, and z_interface_snow_neg1 traces a slope -1 line. The three lines fan out from the origin and confirm the layer-midpoint placement geometrically.

Time span: 0.0 → 1.0

Swept over: z_sno

Δz_snow_0 vs z_sno: 1:1 identity line over the single-layer band per Eq. 4.9 (single-layer case).

z_node_snow_0 vs z_sno: slope -1/2 line per Eq. 4.9 (layer-midpoint node placement).

z_interface_snow_neg1 vs z_sno: slope -1 line per Eq. 4.10 (top interface at z_{h,0} - Δz_0 with z_{h,0} = 0).

Raw .esm

Raw .esm JSON (this component)
{
  "reference": {
    "notes": "source_ref: UrbanCanopy.jl@9200edd4f15609f20339d3a7d104de8064544310:src/roof_wall_road_snow_temperatures.jl:33\nUpstream `SnowLayerGeometry` is an MTK `@component` with one `@parameters` input (z_sno), three time-indexed `@variables` outputs (Δz_snow_0, z_node_snow_0, z_interface_snow_neg1), and one `@constants` declaration (zero_m = 0.0 m). Three algebraic equations close the system: Δz_snow_0 ~ z_sno, z_node_snow_0 ~ zero_m - 0.5*Δz_snow_0, z_interface_snow_neg1 ~ zero_m - Δz_snow_0.\n\nThe upstream `@constants begin zero_m = 0.0, [unit = u\"m\"] end` is encoded here as a zero-default parameter (units m). This mirrors the convention used by sibling observed-only ESM files in this rig that translate `@constants` to fixed-default parameters so the units flow through MTK's unit checker: snow_soil_blended_heat_capacity.esm encodes C_ice the same way, and snow_melt_no_layers.esm uses zero_kgm2 / zero_m for the same reason.\n\nThe component represents the snl = -1 single-snow-layer case from Oleson pp. 92-93: when z_sno spans the single-layer regime (0.01 ≤ z_sno ≤ 0.03), all snow lives in layer 0, so Δz_0 = z_sno, node z_0 sits at the layer midpoint, and the top interface z_{h,-1} sits at the surface offset by Δz_0. Multi-layer snow (snl < -1, requiring procedural branching on snow depth) is not exposed here — the upstream component's docstring notes that multi-layer packing logic is procedural and typically handled outside the equation system.\n\nUnicode `Δz_snow_0` is preserved from upstream — the schema's identifier pattern allows it and other temps/ ESM components already use unicode delta in identifiers (snow_melt_no_layers.esm uses `Δt`, snow_soil_blended_heat_capacity.esm uses `Δz`).\n\nObservability classification: this component is functionally observed-only — each output variable is given by a single algebraic equation in terms of parameters and the other outputs (no derivatives, no time-dependent dynamics). Test scenarios use `time_span: [0, 1]` and sample at t=0 to mirror the convention used by sibling observed-only ESM files (snow_soil_blended_heat_capacity.esm, snow_melt_no_layers.esm, total_phase_change_energy.esm).\n\nTolerance rationale (model-level rel=1.0e-10): three algebraic identity/linear equations (no ODE integration). Δz_snow_0 ~ z_sno is exact assignment; z_node_snow_0 and z_interface_snow_neg1 involve one subtraction (and one halving) of the parameter, so roundoff is at the IEEE-754 ULP level (~1e-16). The upstream test uses `rtol = 1.0e-10` (test/roof_wall_road_snow_temperatures_test.jl:33-36) and we mirror that for fidelity. Tighter `abs: 1e-12` is asserted on outputs that take their full magnitude from z_sno in the zero-input regime so the assertion does not silently accept a stuck-at-zero regression."
  },
  "tolerance": {
    "rel": 1e-10
  },
  "variables": {
    "z_sno": {
      "type": "parameter",
      "units": "m",
      "default": 0.0,
      "description": "Total snow depth (Oleson Ch. 4 §4.1 — single-layer regime input, 0.01 ≤ z_sno ≤ 0.03 m for snl = -1)"
    },
    "zero_m": {
      "type": "parameter",
      "units": "m",
      "default": 0.0,
      "description": "Zero depth reference for the snow column (upstream @constants zero_m = 0.0 m; encoded as a fixed-default parameter so the units flow through MTK's unit checker)"
    },
    "Δz_snow_0": {
      "type": "unknown",
      "units": "m",
      "description": "Thickness of bottom snow layer (layer 0); Eq. 4.9 single-layer case: Δz_0 = z_sno"
    },
    "z_node_snow_0": {
      "type": "unknown",
      "units": "m",
      "description": "Node depth of bottom snow layer (Oleson Eq. 4.9): z_0 = z_{h,0} - 0.5·Δz_0 with z_{h,0} = 0 (surface), so z_0 = -0.5·Δz_0 (negative = above ground)"
    },
    "z_interface_snow_neg1": {
      "type": "unknown",
      "units": "m",
      "description": "Interface depth above bottom snow layer (Oleson Eq. 4.10): z_{h,-1} = z_{h,0} - Δz_0 with z_{h,0} = 0, so z_{h,-1} = -Δz_0 (negative = above ground)"
    }
  },
  "equations": [
    {
      "lhs": "Δz_snow_0",
      "rhs": "z_sno"
    },
    {
      "lhs": "z_node_snow_0",
      "rhs": {
        "op": "-",
        "args": [
          "zero_m",
          {
            "op": "*",
            "args": [
              0.5,
              "Δz_snow_0"
            ]
          }
        ]
      }
    },
    {
      "lhs": "z_interface_snow_neg1",
      "rhs": {
        "op": "-",
        "args": [
          "zero_m",
          "Δz_snow_0"
        ]
      }
    }
  ],
  "tests": [
    {
      "id": "upstream_equation_verification",
      "description": "Reproduces upstream `SnowLayerGeometry - Equation Verification` test (UrbanCanopy.jl test/roof_wall_road_snow_temperatures_test.jl:23-40) at the single-layer canonical depth z_sno = 0.02 m (inside the 0.01-0.03 m single-layer band, pp. 92-93). Expected: Δz_snow_0 = 0.02 m (identity), z_node_snow_0 = -0.01 m (midpoint), z_interface_snow_neg1 = -0.02 m (top interface at -Δz_0). Anchors fidelity to the upstream Julia component at the same parameter value with rtol = 1.0e-10.",
      "parameter_overrides": {
        "z_sno": 0.02
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "Δz_snow_0",
          "time": 0.0,
          "expected": 0.02
        },
        {
          "variable": "z_node_snow_0",
          "time": 0.0,
          "expected": -0.01
        },
        {
          "variable": "z_interface_snow_neg1",
          "time": 0.0,
          "expected": -0.02
        }
      ]
    },
    {
      "id": "zero_snow_depth",
      "description": "Sign-agnostic zero-depth limit: at z_sno = 0 all three outputs collapse to zero (Δz_0 = 0, z_0 = 0, z_{h,-1} = 0). Confirms the equations propagate exact zero and the linear/identity equations are not silently biased; an abs tolerance is asserted so a stuck-at-zero regression cannot pass.",
      "parameter_overrides": {
        "z_sno": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "Δz_snow_0",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-12
          }
        },
        {
          "variable": "z_node_snow_0",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-12
          }
        },
        {
          "variable": "z_interface_snow_neg1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-12
          }
        }
      ]
    },
    {
      "id": "thin_snow_lower_bound",
      "description": "Lower end of the single-layer band: z_sno = 0.01 m (the threshold for snl = -1 per Oleson pp. 92-93). Δz_snow_0 = 0.01, z_node_snow_0 = -0.005, z_interface_snow_neg1 = -0.01. Sweeps the small-value side of the linear identity to confirm node placement at the layer midpoint.",
      "parameter_overrides": {
        "z_sno": 0.01
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "Δz_snow_0",
          "time": 0.0,
          "expected": 0.01
        },
        {
          "variable": "z_node_snow_0",
          "time": 0.0,
          "expected": -0.005
        },
        {
          "variable": "z_interface_snow_neg1",
          "time": 0.0,
          "expected": -0.01
        }
      ]
    },
    {
      "id": "thick_snow_upper_bound",
      "description": "Upper end of the single-layer band: z_sno = 0.03 m (the threshold for transition to snl < -1 per Oleson pp. 92-93). Δz_snow_0 = 0.03, z_node_snow_0 = -0.015, z_interface_snow_neg1 = -0.03. Exercises the high end of the band; outside this regime the upstream model would assign more layers (procedural, not modeled here).",
      "parameter_overrides": {
        "z_sno": 0.03
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "Δz_snow_0",
          "time": 0.0,
          "expected": 0.03
        },
        {
          "variable": "z_node_snow_0",
          "time": 0.0,
          "expected": -0.015
        },
        {
          "variable": "z_interface_snow_neg1",
          "time": 0.0,
          "expected": -0.03
        }
      ]
    },
    {
      "id": "node_at_layer_midpoint",
      "description": "Invariant: the node z_0 always sits at the midpoint of layer 0, halfway between the surface (z = 0) and the top interface z_{h,-1}. Equivalently, z_node_snow_0 = 0.5 · z_interface_snow_neg1. Asserted at z_sno = 0.025 m via both outputs to lock the relationship between Eqs. 4.9 and 4.10.",
      "parameter_overrides": {
        "z_sno": 0.025
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "Δz_snow_0",
          "time": 0.0,
          "expected": 0.025
        },
        {
          "variable": "z_node_snow_0",
          "time": 0.0,
          "expected": -0.0125
        },
        {
          "variable": "z_interface_snow_neg1",
          "time": 0.0,
          "expected": -0.025
        }
      ]
    }
  ],
  "analyses": [
    {
      "id": "single_layer_geometry_sweep",
      "description": "Sweep snow depth z_sno across the single-layer band (0.01-0.03 m per Oleson pp. 92-93) at 11 evenly spaced points. Surfaces the three linear/identity outputs as functions of z_sno: Δz_snow_0 traces the 1:1 identity line, z_node_snow_0 traces a slope -1/2 line through the origin, and z_interface_snow_neg1 traces a slope -1 line. The three lines fan out from the origin and confirm the layer-midpoint placement geometrically.",
      "initial_state": {},
      "parameters": {},
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "parameter_sweep": {
        "type": "cartesian",
        "dimensions": [
          {
            "parameter": "z_sno",
            "range": {
              "start": 0.01,
              "stop": 0.03,
              "count": 11,
              "scale": "linear"
            }
          }
        ]
      },
      "plots": [
        {
          "id": "layer_thickness",
          "type": "line",
          "description": "Δz_snow_0 vs z_sno: 1:1 identity line over the single-layer band per Eq. 4.9 (single-layer case).",
          "x": {
            "variable": "z_sno",
            "label": "z_sno (m)"
          },
          "y": {
            "variable": "Δz_snow_0",
            "label": "Δz_snow_0 (m)"
          }
        },
        {
          "id": "node_depth",
          "type": "line",
          "description": "z_node_snow_0 vs z_sno: slope -1/2 line per Eq. 4.9 (layer-midpoint node placement).",
          "x": {
            "variable": "z_sno",
            "label": "z_sno (m)"
          },
          "y": {
            "variable": "z_node_snow_0",
            "label": "z_node_snow_0 (m)"
          }
        },
        {
          "id": "top_interface_depth",
          "type": "line",
          "description": "z_interface_snow_neg1 vs z_sno: slope -1 line per Eq. 4.10 (top interface at z_{h,0} - Δz_0 with z_{h,0} = 0).",
          "x": {
            "variable": "z_sno",
            "label": "z_sno (m)"
          },
          "y": {
            "variable": "z_interface_snow_neg1",
            "label": "z_interface_snow_neg1 (m)"
          }
        }
      ]
    }
  ]
}