Version: dev

WasteHeatAirConditioning

Waste heat and air-conditioning heat removal aggregation for the urban canopy thermal column. Migrated from UrbanCanopy.jl@9200edd4 src/roof_wall_road_snow_temperatures.jl:440 (`WasteHeatAirConditioning` @component). Implements Oleson et al. (2010, CLM Tech Note Ch. 4) Eqs. 4.55-4.56: the unclamped total waste heat H_wasteheat_unclamped distributes heating/cooling fluxes from the roof and the two walls (sunlit and shaded) into the canyon air, weighted by the efficiency factors f_heat = 1/0.75 and f_cool = 1/0.25, the roof area fraction W_roof, and the canyon-wall geometry factor (1 - W_roof) * H_W per Eq. 4.55; this is clamped at H_wasteheat_max = 100 W/m^2 to produce H_wasteheat. The air-conditioning heat removal H_aircond is the sum of the three cooling fluxes (F_cool_roof + F_cool_sunwall + F_cool_shdwall) per Eq. 4.56. Parameterized as observed-only with three algebraic equations: H_wasteheat_unclamped, H_wasteheat, H_aircond are state variables; F_heat_roof, F_cool_roof, F_heat_sunwall, F_cool_sunwall, F_heat_shdwall, F_cool_shdwall, W_roof, H_W are inputs (parameters in standalone form).

Domain
urban_canopy
Subdomain
temps
Type
model
.esm schema
1.0.0
Source
components/urban_canopy/temps/waste_heat_ac.esm
Tags
#urban_canopy #temps #model #temperatures #hvac #waste_heat #observed-only #clmu

Description

Waste heat and air-conditioning heat removal aggregation for the urban canopy thermal column. Migrated from UrbanCanopy.jl@9200edd4 src/roof_wall_road_snow_temperatures.jl:440 (WasteHeatAirConditioning @component). Implements Oleson et al. (2010, CLM Tech Note Ch. 4) Eqs. 4.55-4.56: the unclamped total waste heat H_wasteheat_unclamped distributes heating/cooling fluxes from the roof and the two walls (sunlit and shaded) into the canyon air, weighted by the efficiency factors f_heat = 1/0.75 and f_cool = 1/0.25, the roof area fraction W_roof, and the canyon-wall geometry factor (1 - W_roof) * H_W per Eq. 4.55; this is clamped at H_wasteheat_max = 100 W/m^2 to produce H_wasteheat. The air-conditioning heat removal H_aircond is the sum of the three cooling fluxes (F_cool_roof + F_cool_sunwall + F_cool_shdwall) per Eq. 4.56. Parameterized as observed-only with three algebraic equations: H_wasteheat_unclamped, H_wasteheat, H_aircond are state variables; F_heat_roof, F_cool_roof, F_heat_sunwall, F_cool_sunwall, F_heat_shdwall, F_cool_shdwall, W_roof, H_W are inputs (parameters in standalone form).

Reference

_source_ref: UrbanCanopy.jl@9200edd4f15609f20339d3a7d104de8064544310:src/roof_wall_road_snow_temperatures.jl:440 Upstream WasteHeatAirConditioning is an MTK @component with eight @parameters (F_heat_roof, F_cool_roof, F_heat_sunwall, F_cool_sunwall, F_heat_shdwall, F_cool_shdwall, W_roof, H_W), three time-indexed @variables (H_wasteheat_unclamped, H_wasteheat, H_aircond), and three algebraic equations (no derivatives). Identical equation structure preserved here.

The upstream component declares three @constants, all encoded here as parameters with the upstream default values: f_heat = 1.0/0.75 (heating efficiency factor, dimensionless), f_cool = 1.0/0.25 = 4.0 (cooling efficiency factor, dimensionless), and H_wasteheat_max = 100.0 W/m^2 (maximum waste heat limit on the clamped output). f_heat is encoded as the exact ratio 1.0/0.75 = 1.3333333333333333… in IEEE-754 — the upstream Julia literal 1.0/0.75 resolves to the same double; preserving the value as a parameter default keeps the rational structure visible and matches the convention used by sibling files (heating_cooling_flux.esm preserves alpha_CN = 0.5 and zero_flux = 0.0 W/m^2 as parameters with upstream default values).

The clamp uses min(H_wasteheat_unclamped, H_wasteheat_max) — the upstream uses Julia’s two-argument min which the ESS AST exposes as the min op. When H_wasteheat_unclamped <= H_wasteheat_max, H_wasteheat = H_wasteheat_unclamped (pass-through); when H_wasteheat_unclamped > H_wasteheat_max, H_wasteheat = H_wasteheat_max = 100 W/m^2 exactly. The equality case (H_wasteheat_unclamped == H_wasteheat_max) is well-defined by min semantics — both branches give the same numeric value.

The Eq. 4.56 air-conditioning heat removal H_aircond is a simple three-term sum of cooling fluxes (F_cool_roof + F_cool_sunwall + F_cool_shdwall). It is decoupled from H_wasteheat and the geometry parameters W_roof, H_W — exercised independently in the H_aircond_decoupled_from_geometry test.

Observability classification: this component is functionally observed-only — every output variable is given by a single algebraic equation in terms of the parameters, and the (t) time index has no role since there are no derivatives. Test scenarios use time_span: [0, 1] and sample at t=0 to mirror the convention used by sibling observed-only ESM files in this rig (heating_cooling_flux.esm, snow_capping_runoff.esm, total_phase_change_energy.esm).

Tolerance rationale (model-level rel=1e-9): observed-only algebraic system (no ODE integration); the equations involve only multiplication, addition, subtraction, and min — all at IEEE-754 ULP precision (~1e-16). 1e-9 leaves comfortable headroom above ULP roundoff and matches the convention used by sibling observed-only files (heating_cooling_flux.esm uses 1e-9 for the analogous Crank-Nicholson + ifelse equations). Tighter abs: 1e-12 is asserted on outputs that are exactly zero by construction so the assertion does not silently accept a stuck-at-1e-3 regression._

Parameters

NameUnitsDefaultDescription
f_heat11.3333333333333333Heating efficiency factor (dimensionless) — upstream @constants f_heat = 1.0/0.75; multiplies the heating-flux terms in Eq. 4.55 to convert the building floor heating flux into the total waste heat dumped to the canyon air
f_cool14.0Cooling efficiency factor (dimensionless) — upstream @constants f_cool = 1.0/0.25 = 4.0; multiplies the cooling-flux terms in Eq. 4.55 to convert the building floor cooling flux into the total waste heat dumped to the canyon air (cooling efficiency is lower than heating, so f_cool > f_heat)
H_wasteheat_maxW/m^2100.0Maximum waste heat limit (W/m^2) — upstream @constants H_wasteheat_max = 100; the min clamp in Eq. 4.55 caps the unclamped waste heat at this value to avoid unphysical heat injection into the canyon air during extreme HVAC demand
F_heat_roofW/m^20.0Heating flux for roof (W/m^2) — input from a HeatingCoolingFlux instance attached to the roof column; non-negative magnitude of the HVAC heating demand at the roof
F_cool_roofW/m^20.0Cooling flux for roof (W/m^2) — input from a HeatingCoolingFlux instance attached to the roof column; non-negative magnitude of the HVAC cooling demand at the roof
F_heat_sunwallW/m^20.0Heating flux for sunlit wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the sunlit-wall column
F_cool_sunwallW/m^20.0Cooling flux for sunlit wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the sunlit-wall column
F_heat_shdwallW/m^20.0Heating flux for shaded wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the shaded-wall column
F_cool_shdwallW/m^20.0Cooling flux for shaded wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the shaded-wall column
W_roof10.3Roof fraction (dimensionless) — fraction of the urban canyon footprint occupied by roof. At W_roof = 1, only the roof term contributes to Eq. 4.55; at W_roof = 0, only the wall terms contribute (weighted by H_W)
H_W11.0Height-to-width ratio (dimensionless) — canyon-wall geometry factor that scales the wall contribution to Eq. 4.55. At H_W = 0, the walls contribute nothing regardless of W_roof; at H_W = 1, the walls have unit weighting on the (1 - W_roof) road-area fraction

Observed

NameUnitsDefaultDescription
H_wasteheat_unclampedW/m^2Unclamped total waste heat (Oleson Eq. 4.55, before the max clamp): W_roof * (f_heat * F_heat_roof + f_cool * F_cool_roof) + (1 - W_roof) * H_W * (f_heat * F_heat_sunwall + f_cool * F_cool_sunwall + f_heat * F_heat_shdwall + f_cool * F_cool_shdwall)
H_wasteheatW/m^2Total waste heat clamped at H_wasteheat_max (Oleson Eq. 4.55): min(H_wasteheat_unclamped, H_wasteheat_max). This is the waste heat dumped to the canyon air after the upper-bound cap is applied
H_aircondW/m^2Heat removed by air conditioning (Oleson Eq. 4.56): F_cool_roof + F_cool_sunwall + F_cool_shdwall — sum of cooling fluxes from all three building surfaces

Observed expressions

$$ H_{wasteheat\,unclamped} = W_{roof} \cdot \left(f_{heat} \cdot F_{heat\,roof} + f_{cool} \cdot F_{cool\,roof}\right) + \left(1.0 - W_{roof}\right) \cdot H_{W} \cdot \left(f_{heat} \cdot F_{heat\,sunwall} + f_{cool} \cdot F_{cool\,sunwall} + f_{heat} \cdot F_{heat\,shdwall} + f_{cool} \cdot F_{cool\,shdwall}\right) $$

Unclamped total waste heat (Oleson Eq. 4.55, before the max clamp): W_roof * (f_heat * F_heat_roof + f_cool * F_cool_roof) + (1 - W_roof) * H_W * (f_heat * F_heat_sunwall + f_cool * F_cool_sunwall + f_heat * F_heat_shdwall + f_cool * F_cool_shdwall)

$$ H_{wasteheat} = \mathrm{min}\left(H_{wasteheat\,unclamped}, H_{wasteheat\,max}\right) $$

Total waste heat clamped at H_wasteheat_max (Oleson Eq. 4.55): min(H_wasteheat_unclamped, H_wasteheat_max). This is the waste heat dumped to the canyon air after the upper-bound cap is applied

$$ H_{aircond} = F_{cool\,roof} + F_{cool\,sunwall} + F_{cool\,shdwall} $$

Heat removed by air conditioning (Oleson Eq. 4.56): F_cool_roof + F_cool_sunwall + F_cool_shdwall — sum of cooling fluxes from all three building surfaces

Analyses

wasteheat_vs_roof_heating_flux_with_clamp

Sweep F_heat_roof from 0 to a value large enough to drive the unclamped waste heat past H_wasteheat_max = 100 W/m^2, with W_roof = 1 (all-roof canyon so walls do not contribute) and all cooling fluxes = 0. Unclamped grows linearly as f_heat * F_heat_roof = (4/3) * F_heat_roof; H_wasteheat tracks it until F_heat_roof = 75 W/m^2 (where unclamped = 100) and then saturates at 100. Surfaces the saturation knee of the Eq. 4.55 clamp directly.

Time span: 0.0 → 1.0

Swept over: F_heat_roof

Unclamped waste heat vs F_heat_roof: a straight line through the origin with slope f_heat = 4/3 per Eq. 4.55.

Clamped waste heat vs F_heat_roof: tracks the unclamped curve up to F_heat_roof = 75 W/m^2 (where the unclamped value equals H_wasteheat_max = 100) and then saturates flat at 100 W/m^2 — the Eq. 4.55 min cap.

wasteheat_vs_W_roof_and_H_W_geometry

Joint sweep over (W_roof, H_W) at fixed efficiency-weighted flux contributions to surface the canyon-geometry weighting in Eq. 4.55. Fixed fluxes are chosen so the roof and wall contributions are roughly comparable (F_heat_roof = F_cool_roof = 10, F_heat_sunwall = F_cool_sunwall = F_heat_shdwall = F_cool_shdwall = 6, all in W/m^2). The unclamped waste heat is a bilinear function of W_roof and H_W with a saddle structure: it grows with H_W at fixed W_roof < 1 (more wall contribution), and grows with W_roof at fixed H_W < (roof / wall) (more roof contribution).

Time span: 0.0 → 1.0

Swept over: W_roof, H_W

H_wasteheat_unclamped as a bilinear function of (W_roof, H_W) per Eq. 4.55: the roof term is linear in W_roof and the wall term is bilinear in (1 - W_roof) * H_W.

H_wasteheat (clamped) over the same (W_roof, H_W) grid: same shape as the unclamped surface where it remains below 100 W/m^2, but flattens at 100 W/m^2 in the upper-right where the wall contribution drives the sum past the cap.

Raw .esm

Raw .esm JSON (this component)
{
  "reference": {
    "notes": "source_ref: UrbanCanopy.jl@9200edd4f15609f20339d3a7d104de8064544310:src/roof_wall_road_snow_temperatures.jl:440\nUpstream `WasteHeatAirConditioning` is an MTK `@component` with eight `@parameters` (F_heat_roof, F_cool_roof, F_heat_sunwall, F_cool_sunwall, F_heat_shdwall, F_cool_shdwall, W_roof, H_W), three time-indexed `@variables` (H_wasteheat_unclamped, H_wasteheat, H_aircond), and three algebraic equations (no derivatives). Identical equation structure preserved here.\n\nThe upstream component declares three `@constants`, all encoded here as parameters with the upstream default values: f_heat = 1.0/0.75 (heating efficiency factor, dimensionless), f_cool = 1.0/0.25 = 4.0 (cooling efficiency factor, dimensionless), and H_wasteheat_max = 100.0 W/m^2 (maximum waste heat limit on the clamped output). f_heat is encoded as the exact ratio 1.0/0.75 = 1.3333333333333333... in IEEE-754 — the upstream Julia literal `1.0/0.75` resolves to the same double; preserving the value as a parameter default keeps the rational structure visible and matches the convention used by sibling files (heating_cooling_flux.esm preserves alpha_CN = 0.5 and zero_flux = 0.0 W/m^2 as parameters with upstream default values).\n\nThe clamp uses `min(H_wasteheat_unclamped, H_wasteheat_max)` — the upstream uses Julia's two-argument `min` which the ESS AST exposes as the `min` op. When H_wasteheat_unclamped <= H_wasteheat_max, H_wasteheat = H_wasteheat_unclamped (pass-through); when H_wasteheat_unclamped > H_wasteheat_max, H_wasteheat = H_wasteheat_max = 100 W/m^2 exactly. The equality case (H_wasteheat_unclamped == H_wasteheat_max) is well-defined by `min` semantics — both branches give the same numeric value.\n\nThe Eq. 4.56 air-conditioning heat removal H_aircond is a simple three-term sum of cooling fluxes (F_cool_roof + F_cool_sunwall + F_cool_shdwall). It is decoupled from H_wasteheat and the geometry parameters W_roof, H_W — exercised independently in the H_aircond_decoupled_from_geometry test.\n\nObservability classification: this component is functionally observed-only — every output variable is given by a single algebraic equation in terms of the parameters, and the `(t)` time index has no role since there are no derivatives. Test scenarios use `time_span: [0, 1]` and sample at t=0 to mirror the convention used by sibling observed-only ESM files in this rig (heating_cooling_flux.esm, snow_capping_runoff.esm, total_phase_change_energy.esm).\n\nTolerance rationale (model-level rel=1e-9): observed-only algebraic system (no ODE integration); the equations involve only multiplication, addition, subtraction, and `min` — all at IEEE-754 ULP precision (~1e-16). 1e-9 leaves comfortable headroom above ULP roundoff and matches the convention used by sibling observed-only files (heating_cooling_flux.esm uses 1e-9 for the analogous Crank-Nicholson + ifelse equations). Tighter `abs: 1e-12` is asserted on outputs that are exactly zero by construction so the assertion does not silently accept a stuck-at-1e-3 regression."
  },
  "tolerance": {
    "rel": 1e-09
  },
  "variables": {
    "f_heat": {
      "type": "parameter",
      "units": "1",
      "default": 1.3333333333333333,
      "description": "Heating efficiency factor (dimensionless) — upstream `@constants` f_heat = 1.0/0.75; multiplies the heating-flux terms in Eq. 4.55 to convert the building floor heating flux into the total waste heat dumped to the canyon air"
    },
    "f_cool": {
      "type": "parameter",
      "units": "1",
      "default": 4.0,
      "description": "Cooling efficiency factor (dimensionless) — upstream `@constants` f_cool = 1.0/0.25 = 4.0; multiplies the cooling-flux terms in Eq. 4.55 to convert the building floor cooling flux into the total waste heat dumped to the canyon air (cooling efficiency is lower than heating, so f_cool > f_heat)"
    },
    "H_wasteheat_max": {
      "type": "parameter",
      "units": "W/m^2",
      "default": 100.0,
      "description": "Maximum waste heat limit (W/m^2) — upstream `@constants` H_wasteheat_max = 100; the `min` clamp in Eq. 4.55 caps the unclamped waste heat at this value to avoid unphysical heat injection into the canyon air during extreme HVAC demand"
    },
    "F_heat_roof": {
      "type": "parameter",
      "units": "W/m^2",
      "default": 0.0,
      "description": "Heating flux for roof (W/m^2) — input from a HeatingCoolingFlux instance attached to the roof column; non-negative magnitude of the HVAC heating demand at the roof"
    },
    "F_cool_roof": {
      "type": "parameter",
      "units": "W/m^2",
      "default": 0.0,
      "description": "Cooling flux for roof (W/m^2) — input from a HeatingCoolingFlux instance attached to the roof column; non-negative magnitude of the HVAC cooling demand at the roof"
    },
    "F_heat_sunwall": {
      "type": "parameter",
      "units": "W/m^2",
      "default": 0.0,
      "description": "Heating flux for sunlit wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the sunlit-wall column"
    },
    "F_cool_sunwall": {
      "type": "parameter",
      "units": "W/m^2",
      "default": 0.0,
      "description": "Cooling flux for sunlit wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the sunlit-wall column"
    },
    "F_heat_shdwall": {
      "type": "parameter",
      "units": "W/m^2",
      "default": 0.0,
      "description": "Heating flux for shaded wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the shaded-wall column"
    },
    "F_cool_shdwall": {
      "type": "parameter",
      "units": "W/m^2",
      "default": 0.0,
      "description": "Cooling flux for shaded wall (W/m^2) — input from a HeatingCoolingFlux instance attached to the shaded-wall column"
    },
    "W_roof": {
      "type": "parameter",
      "units": "1",
      "default": 0.3,
      "description": "Roof fraction (dimensionless) — fraction of the urban canyon footprint occupied by roof. At W_roof = 1, only the roof term contributes to Eq. 4.55; at W_roof = 0, only the wall terms contribute (weighted by H_W)"
    },
    "H_W": {
      "type": "parameter",
      "units": "1",
      "default": 1.0,
      "description": "Height-to-width ratio (dimensionless) — canyon-wall geometry factor that scales the wall contribution to Eq. 4.55. At H_W = 0, the walls contribute nothing regardless of W_roof; at H_W = 1, the walls have unit weighting on the (1 - W_roof) road-area fraction"
    },
    "H_wasteheat_unclamped": {
      "type": "unknown",
      "units": "W/m^2",
      "description": "Unclamped total waste heat (Oleson Eq. 4.55, before the max clamp): W_roof * (f_heat * F_heat_roof + f_cool * F_cool_roof) + (1 - W_roof) * H_W * (f_heat * F_heat_sunwall + f_cool * F_cool_sunwall + f_heat * F_heat_shdwall + f_cool * F_cool_shdwall)"
    },
    "H_wasteheat": {
      "type": "unknown",
      "units": "W/m^2",
      "description": "Total waste heat clamped at H_wasteheat_max (Oleson Eq. 4.55): min(H_wasteheat_unclamped, H_wasteheat_max). This is the waste heat dumped to the canyon air after the upper-bound cap is applied"
    },
    "H_aircond": {
      "type": "unknown",
      "units": "W/m^2",
      "description": "Heat removed by air conditioning (Oleson Eq. 4.56): F_cool_roof + F_cool_sunwall + F_cool_shdwall — sum of cooling fluxes from all three building surfaces"
    }
  },
  "equations": [
    {
      "lhs": "H_wasteheat_unclamped",
      "rhs": {
        "op": "+",
        "args": [
          {
            "op": "*",
            "args": [
              "W_roof",
              {
                "op": "+",
                "args": [
                  {
                    "op": "*",
                    "args": [
                      "f_heat",
                      "F_heat_roof"
                    ]
                  },
                  {
                    "op": "*",
                    "args": [
                      "f_cool",
                      "F_cool_roof"
                    ]
                  }
                ]
              }
            ]
          },
          {
            "op": "*",
            "args": [
              {
                "op": "-",
                "args": [
                  1.0,
                  "W_roof"
                ]
              },
              "H_W",
              {
                "op": "+",
                "args": [
                  {
                    "op": "*",
                    "args": [
                      "f_heat",
                      "F_heat_sunwall"
                    ]
                  },
                  {
                    "op": "*",
                    "args": [
                      "f_cool",
                      "F_cool_sunwall"
                    ]
                  },
                  {
                    "op": "*",
                    "args": [
                      "f_heat",
                      "F_heat_shdwall"
                    ]
                  },
                  {
                    "op": "*",
                    "args": [
                      "f_cool",
                      "F_cool_shdwall"
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    },
    {
      "lhs": "H_wasteheat",
      "rhs": {
        "op": "min",
        "args": [
          "H_wasteheat_unclamped",
          "H_wasteheat_max"
        ]
      }
    },
    {
      "lhs": "H_aircond",
      "rhs": {
        "op": "+",
        "args": [
          "F_cool_roof",
          "F_cool_sunwall",
          "F_cool_shdwall"
        ]
      }
    }
  ],
  "tests": [
    {
      "id": "upstream_equation_verification",
      "description": "Reproduces upstream `WasteHeatAirConditioning - Equation Verification` test (UrbanCanopy.jl test/roof_wall_road_snow_temperatures_test.jl:369). Canonical case: F_heat_roof=10, F_cool_roof=5, F_heat_sunwall=8, F_cool_sunwall=3, F_heat_shdwall=7, F_cool_shdwall=2 (W/m^2), W_roof=0.3, H_W=1.0. With f_heat=4/3 and f_cool=4 the unclamped waste heat is 0.3*((4/3)*10 + 4*5) + 0.7*1.0*((4/3)*8 + 4*3 + (4/3)*7 + 4*2) = 0.3*(40/3 + 20) + 0.7*40 = 0.3*100/3 + 28 = 10 + 28 = 38 W/m^2 (below the 100 W/m^2 cap, so the clamp is a pass-through). H_aircond = 5 + 3 + 2 = 10 W/m^2. Anchors fidelity to the upstream Julia component at this canonical fixture point.",
      "parameter_overrides": {
        "F_heat_roof": 10.0,
        "F_cool_roof": 5.0,
        "F_heat_sunwall": 8.0,
        "F_cool_sunwall": 3.0,
        "F_heat_shdwall": 7.0,
        "F_cool_shdwall": 2.0,
        "W_roof": 0.3,
        "H_W": 1.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 38.0
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 38.0
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 10.0
        }
      ]
    },
    {
      "id": "upstream_max_clamping",
      "description": "Reproduces upstream `WasteHeatAirConditioning - Max Clamping` test (UrbanCanopy.jl test/roof_wall_road_snow_temperatures_test.jl:393). 10x larger fluxes than the canonical case: F_heat_roof=100, F_cool_roof=50, F_heat_sunwall=80, F_cool_sunwall=30, F_heat_shdwall=70, F_cool_shdwall=20 (W/m^2), W_roof=0.3, H_W=1.0. The unclamped result is 0.3*((4/3)*100 + 4*50) + 0.7*1.0*((4/3)*80 + 4*30 + (4/3)*70 + 4*20) = 0.3*(400/3 + 200) + 0.7*400 = 0.3*1000/3 + 280 = 100 + 280 = 380 W/m^2, which exceeds H_wasteheat_max = 100, so the clamp activates and H_wasteheat = 100 W/m^2 exactly. H_aircond = 50 + 30 + 20 = 100 W/m^2. Verifies the `min` clamp pins the output at the configured ceiling.",
      "parameter_overrides": {
        "F_heat_roof": 100.0,
        "F_cool_roof": 50.0,
        "F_heat_sunwall": 80.0,
        "F_cool_sunwall": 30.0,
        "F_heat_shdwall": 70.0,
        "F_cool_shdwall": 20.0,
        "W_roof": 0.3,
        "H_W": 1.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 380.0
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 100.0
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 100.0
        }
      ]
    },
    {
      "id": "all_zero_inputs",
      "description": "Zero-flux limit: every heating and cooling flux is zero (no HVAC demand from any building surface). The unclamped waste heat is 0 regardless of W_roof and H_W, the clamp passes through (min(0, 100) = 0), and the cooling sum is 0. Exercises the additive-zero limit of Eqs. 4.55-4.56 and confirms the geometry parameters do not inject spurious heat when the flux inputs are off.",
      "parameter_overrides": {
        "F_heat_roof": 0.0,
        "F_cool_roof": 0.0,
        "F_heat_sunwall": 0.0,
        "F_cool_sunwall": 0.0,
        "F_heat_shdwall": 0.0,
        "F_cool_shdwall": 0.0,
        "W_roof": 0.3,
        "H_W": 1.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-12
          }
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-12
          }
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-12
          }
        }
      ]
    },
    {
      "id": "exact_max_clamp_boundary",
      "description": "Boundary case: F_cool_roof=25 alone gives unclamped = 0.3*((4/3)*0 + 4*25) + 0.7*1*0 = 0.3*100 = 30; raising the roof terms so the unclamped result lands exactly at the clamp surfaces the `min(x, x) = x` branch. Concretely, F_cool_roof = 250/3 (≈83.333) and W_roof = 1, H_W arbitrary gives unclamped = 1*((4/3)*0 + 4*(250/3)) = 1000/3 ≈ 333.333 — too large. Use F_cool_roof = 25 with W_roof = 1: unclamped = 4*25 = 100 = H_wasteheat_max exactly. min(100, 100) = 100 by `min` semantics. Verifies the equality branch of the `min` clamp.",
      "parameter_overrides": {
        "F_heat_roof": 0.0,
        "F_cool_roof": 25.0,
        "F_heat_sunwall": 0.0,
        "F_cool_sunwall": 0.0,
        "F_heat_shdwall": 0.0,
        "F_cool_shdwall": 0.0,
        "W_roof": 1.0,
        "H_W": 1.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 100.0
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 100.0
        }
      ]
    },
    {
      "id": "W_roof_one_only_roof_contributes",
      "description": "Limit case W_roof = 1 (all-roof canyon): the wall coefficient (1 - W_roof) = 0 zeroes out the wall terms regardless of H_W and the wall fluxes. Unclamped waste heat collapses to f_heat * F_heat_roof + f_cool * F_cool_roof = (4/3)*15 + 4*6 = 20 + 24 = 44 W/m^2. The clamp passes through (44 < 100). H_aircond remains decoupled from W_roof and sums all three cooling fluxes (6 + 9 + 4 = 19). Isolates the roof contribution to Eq. 4.55 and confirms the (1 - W_roof) factor cleanly removes the wall terms.",
      "parameter_overrides": {
        "F_heat_roof": 15.0,
        "F_cool_roof": 6.0,
        "F_heat_sunwall": 8.0,
        "F_cool_sunwall": 9.0,
        "F_heat_shdwall": 7.0,
        "F_cool_shdwall": 4.0,
        "W_roof": 1.0,
        "H_W": 2.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 44.0
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 44.0
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 19.0
        }
      ]
    },
    {
      "id": "W_roof_zero_only_walls_contribute",
      "description": "Limit case W_roof = 0 (no roof in canyon, e.g. open-pit street geometry): the roof coefficient W_roof = 0 zeroes out the roof terms regardless of the roof fluxes. Unclamped waste heat collapses to (1 - W_roof) * H_W * (sum of wall efficiency-weighted fluxes) = 1.0 * 1.5 * ((4/3)*6 + 4*3 + (4/3)*9 + 4*5) = 1.5 * (8 + 12 + 12 + 20) = 1.5 * 52 = 78 W/m^2. The clamp passes through (78 < 100). Isolates the wall contribution and confirms H_W scales it linearly. H_aircond = 99 + 3 + 5 = 107 (decoupled, larger than H_wasteheat).",
      "parameter_overrides": {
        "F_heat_roof": 12.0,
        "F_cool_roof": 99.0,
        "F_heat_sunwall": 6.0,
        "F_cool_sunwall": 3.0,
        "F_heat_shdwall": 9.0,
        "F_cool_shdwall": 5.0,
        "W_roof": 0.0,
        "H_W": 1.5
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 78.0
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 78.0
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 107.0
        }
      ]
    },
    {
      "id": "H_W_zero_no_wall_contribution",
      "description": "Limit case H_W = 0 (flat canyon, no walls): the wall coefficient (1 - W_roof) * H_W = 0 zeroes out the wall terms regardless of W_roof and the wall fluxes. Unclamped waste heat collapses to the roof-only term W_roof * (f_heat * F_heat_roof + f_cool * F_cool_roof) = 0.5 * ((4/3)*12 + 4*9) = 0.5 * (16 + 36) = 26 W/m^2. The clamp passes through. Verifies the canyon-geometry factor cleanly removes the wall contribution and exercises a different W_roof value (0.5) than the canonical 0.3.",
      "parameter_overrides": {
        "F_heat_roof": 12.0,
        "F_cool_roof": 9.0,
        "F_heat_sunwall": 80.0,
        "F_cool_sunwall": 60.0,
        "F_heat_shdwall": 70.0,
        "F_cool_shdwall": 50.0,
        "W_roof": 0.5,
        "H_W": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 26.0
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 26.0
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 119.0
        }
      ]
    },
    {
      "id": "H_aircond_decoupled_from_geometry",
      "description": "H_aircond depends only on the three cooling fluxes and NOT on W_roof, H_W, or any heating flux. Vary the geometry to extreme values (W_roof = 0.5, H_W = 5) and the heating fluxes to large values (F_heat_* = 200) while keeping the cooling fluxes at the canonical 5/3/2 W/m^2. H_aircond must still equal 5 + 3 + 2 = 10. Verifies Eq. 4.56 is mechanically decoupled from Eq. 4.55 — the clamp surfaces H_wasteheat at H_wasteheat_max = 100 in this scenario since H_wasteheat_unclamped is well above the cap.",
      "parameter_overrides": {
        "F_heat_roof": 200.0,
        "F_cool_roof": 5.0,
        "F_heat_sunwall": 200.0,
        "F_cool_sunwall": 3.0,
        "F_heat_shdwall": 200.0,
        "F_cool_shdwall": 2.0,
        "W_roof": 0.5,
        "H_W": 5.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 100.0
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 10.0
        }
      ]
    },
    {
      "id": "cooling_only_no_heating",
      "description": "Cooling-dominant regime (summer afternoon, all surfaces above the comfort window): every heating flux is zero, every cooling flux is positive. F_cool_roof=8, F_cool_sunwall=12 (hot sunlit wall), F_cool_shdwall=6 (cooler shaded wall), W_roof=0.4, H_W=1.2. Unclamped = 0.4*(0 + 4*8) + 0.6*1.2*(0 + 4*12 + 0 + 4*6) = 0.4*32 + 0.72*(48 + 24) = 12.8 + 0.72*72 = 12.8 + 51.84 = 64.64 W/m^2. Clamp passes through (64.64 < 100). H_aircond = 8 + 12 + 6 = 26 W/m^2. Confirms the cooling-only branch of the efficiency-weighted sum.",
      "parameter_overrides": {
        "F_heat_roof": 0.0,
        "F_cool_roof": 8.0,
        "F_heat_sunwall": 0.0,
        "F_cool_sunwall": 12.0,
        "F_heat_shdwall": 0.0,
        "F_cool_shdwall": 6.0,
        "W_roof": 0.4,
        "H_W": 1.2
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 64.64
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 64.64
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 26.0
        }
      ]
    },
    {
      "id": "heating_only_no_cooling",
      "description": "Heating-dominant regime (winter night, all surfaces below the comfort window): every cooling flux is zero, every heating flux is positive. F_heat_roof=12, F_heat_sunwall=15, F_heat_shdwall=18, W_roof=0.25, H_W=2.0. Unclamped = 0.25*((4/3)*12 + 0) + 0.75*2.0*((4/3)*15 + 0 + (4/3)*18 + 0) = 0.25*16 + 1.5*(20 + 24) = 4 + 1.5*44 = 4 + 66 = 70 W/m^2. Clamp passes through (70 < 100). H_aircond = 0 + 0 + 0 = 0 exactly (decoupled from heating fluxes). Confirms the heating-only branch of the efficiency-weighted sum and that H_aircond cleanly drops out when cooling is off.",
      "parameter_overrides": {
        "F_heat_roof": 12.0,
        "F_cool_roof": 0.0,
        "F_heat_sunwall": 15.0,
        "F_cool_sunwall": 0.0,
        "F_heat_shdwall": 18.0,
        "F_cool_shdwall": 0.0,
        "W_roof": 0.25,
        "H_W": 2.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 70.0
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 70.0
        },
        {
          "variable": "H_aircond",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-12
          }
        }
      ]
    },
    {
      "id": "just_above_clamp_boundary",
      "description": "Strict-clamp boundary case: unclamped is set just above H_wasteheat_max via F_cool_roof = 25.01 with W_roof = 1 (all-roof canyon, walls drop out). Unclamped = 1*(0 + 4*25.01) = 100.04, which is greater than H_wasteheat_max = 100, so min(100.04, 100) = 100 exactly. Verifies that the `min` clamp activates at strict overshoot, not equality, and pins the output at exactly the ceiling value.",
      "parameter_overrides": {
        "F_heat_roof": 0.0,
        "F_cool_roof": 25.01,
        "F_heat_sunwall": 0.0,
        "F_cool_sunwall": 0.0,
        "F_heat_shdwall": 0.0,
        "F_cool_shdwall": 0.0,
        "W_roof": 1.0,
        "H_W": 1.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "H_wasteheat_unclamped",
          "time": 0.0,
          "expected": 100.04
        },
        {
          "variable": "H_wasteheat",
          "time": 0.0,
          "expected": 100.0
        }
      ]
    }
  ],
  "analyses": [
    {
      "id": "wasteheat_vs_roof_heating_flux_with_clamp",
      "description": "Sweep F_heat_roof from 0 to a value large enough to drive the unclamped waste heat past H_wasteheat_max = 100 W/m^2, with W_roof = 1 (all-roof canyon so walls do not contribute) and all cooling fluxes = 0. Unclamped grows linearly as f_heat * F_heat_roof = (4/3) * F_heat_roof; H_wasteheat tracks it until F_heat_roof = 75 W/m^2 (where unclamped = 100) and then saturates at 100. Surfaces the saturation knee of the Eq. 4.55 clamp directly.",
      "initial_state": {},
      "parameters": {
        "F_cool_roof": 0.0,
        "F_heat_sunwall": 0.0,
        "F_cool_sunwall": 0.0,
        "F_heat_shdwall": 0.0,
        "F_cool_shdwall": 0.0,
        "W_roof": 1.0,
        "H_W": 1.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "parameter_sweep": {
        "type": "cartesian",
        "dimensions": [
          {
            "parameter": "F_heat_roof",
            "range": {
              "start": 0.0,
              "stop": 150.0,
              "count": 31,
              "scale": "linear"
            }
          }
        ]
      },
      "plots": [
        {
          "id": "H_wasteheat_unclamped_curve",
          "type": "line",
          "description": "Unclamped waste heat vs F_heat_roof: a straight line through the origin with slope f_heat = 4/3 per Eq. 4.55.",
          "x": {
            "variable": "F_heat_roof",
            "label": "F_heat_roof (W/m^2)"
          },
          "y": {
            "variable": "H_wasteheat_unclamped",
            "label": "H_wasteheat_unclamped (W/m^2)"
          }
        },
        {
          "id": "H_wasteheat_clamped_curve",
          "type": "line",
          "description": "Clamped waste heat vs F_heat_roof: tracks the unclamped curve up to F_heat_roof = 75 W/m^2 (where the unclamped value equals H_wasteheat_max = 100) and then saturates flat at 100 W/m^2 — the Eq. 4.55 `min` cap.",
          "x": {
            "variable": "F_heat_roof",
            "label": "F_heat_roof (W/m^2)"
          },
          "y": {
            "variable": "H_wasteheat",
            "label": "H_wasteheat (W/m^2)"
          }
        }
      ]
    },
    {
      "id": "wasteheat_vs_W_roof_and_H_W_geometry",
      "description": "Joint sweep over (W_roof, H_W) at fixed efficiency-weighted flux contributions to surface the canyon-geometry weighting in Eq. 4.55. Fixed fluxes are chosen so the roof and wall contributions are roughly comparable (F_heat_roof = F_cool_roof = 10, F_heat_sunwall = F_cool_sunwall = F_heat_shdwall = F_cool_shdwall = 6, all in W/m^2). The unclamped waste heat is a bilinear function of W_roof and H_W with a saddle structure: it grows with H_W at fixed W_roof < 1 (more wall contribution), and grows with W_roof at fixed H_W < (roof / wall) (more roof contribution).",
      "initial_state": {},
      "parameters": {
        "F_heat_roof": 10.0,
        "F_cool_roof": 10.0,
        "F_heat_sunwall": 6.0,
        "F_cool_sunwall": 6.0,
        "F_heat_shdwall": 6.0,
        "F_cool_shdwall": 6.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "parameter_sweep": {
        "type": "cartesian",
        "dimensions": [
          {
            "parameter": "W_roof",
            "range": {
              "start": 0.0,
              "stop": 1.0,
              "count": 11,
              "scale": "linear"
            }
          },
          {
            "parameter": "H_W",
            "range": {
              "start": 0.0,
              "stop": 3.0,
              "count": 11,
              "scale": "linear"
            }
          }
        ]
      },
      "plots": [
        {
          "id": "H_wasteheat_unclamped_heatmap",
          "type": "heatmap",
          "description": "H_wasteheat_unclamped as a bilinear function of (W_roof, H_W) per Eq. 4.55: the roof term is linear in W_roof and the wall term is bilinear in (1 - W_roof) * H_W.",
          "x": {
            "variable": "W_roof",
            "label": "W_roof (dimensionless)"
          },
          "y": {
            "variable": "H_W",
            "label": "H_W (dimensionless)"
          },
          "value": {
            "variable": "H_wasteheat_unclamped",
            "reduce": "final"
          }
        },
        {
          "id": "H_wasteheat_heatmap",
          "type": "heatmap",
          "description": "H_wasteheat (clamped) over the same (W_roof, H_W) grid: same shape as the unclamped surface where it remains below 100 W/m^2, but flattens at 100 W/m^2 in the upper-right where the wall contribution drives the sum past the cap.",
          "x": {
            "variable": "W_roof",
            "label": "W_roof (dimensionless)"
          },
          "y": {
            "variable": "H_W",
            "label": "H_W (dimensionless)"
          },
          "value": {
            "variable": "H_wasteheat",
            "reduce": "final"
          }
        }
      ]
    }
  ]
}