Source code for supy.util._attribution

"""
Near-surface diagnostics attribution module for SUEWS.

Decomposes changes in near-surface variables (T2, q2, U10) between two scenarios
into physically attributable components using exact Shapley decomposition.

Supported Variables
-------------------
- **T2** (2m temperature): Linear flux-gradient profile
- **q2** (2m specific humidity): Linear flux-gradient profile
- **U10** (10m wind speed): Logarithmic momentum profile

Mathematical Foundation
-----------------------
**T2 and q2** follow linear flux-gradient profiles:
    T2 = T_ref + r_h * Q_H / (rho * c_p)
    q2 = q_ref + r_v * Q_E / (rho * L_v)

The change between scenarios can be decomposed as:
    delta_T2 = delta(r * Q_H * gamma)

Using Shapley values for triple products ensures exact closure.

**U10** follows the logarithmic wind profile:
    U10 = (u*/k) * [ln((z-d)/z0m) - psi_m(zeta)]

Which decomposes into:
    U10 = F * (R + S)

Where:
- F = u*/k (forcing term - surface stress)
- R = ln((z-d)/z0m) (roughness term - geometric)
- S = -psi_m(zeta) (stability correction)

The Shapley decomposition for this structure yields:
- Phi_forcing = dF * (P_A + P_B) / 2,  where P = R + S
- Phi_roughness = dR * (F_A + F_B) / 2
- Phi_stability = dS * (F_A + F_B) / 2
"""

from typing import Literal

import pandas as pd

# Import all public functions and classes
from ._result import AttributionResult
from ._t2 import attribute_t2, diagnose_t2
from ._q2 import attribute_q2, diagnose_q2
from ._u10 import attribute_u10, diagnose_u10


# =============================================================================
# Generic Dispatcher Functions
# =============================================================================


[docs] def attribute( df_output_A: pd.DataFrame, df_output_B: pd.DataFrame, variable: Literal["T2", "q2", "U10"] = "T2", **kwargs, ) -> AttributionResult: """ Generic attribution function for near-surface variables. Dispatches to variable-specific implementation internally. Parameters ---------- df_output_A : pd.DataFrame SUEWS output DataFrame for scenario A (reference/baseline) df_output_B : pd.DataFrame SUEWS output DataFrame for scenario B (modified/test) variable : str, optional Variable to attribute: 'T2' (temperature), 'q2' (humidity), or 'U10' (10m wind speed). Default 'T2'. **kwargs Additional keyword arguments passed to the specific function. - For T2: hierarchical (bool), min_flux (float), df_forcing_A/B - For q2: min_flux (float), df_forcing_A/B - For U10: z_ref (float), min_ustar (float) Returns ------- AttributionResult Attribution decomposition results. Examples -------- >>> result = attribute(df_baseline, df_scenario, variable="T2") >>> print(result) >>> result = attribute(df_baseline, df_scenario, variable="q2") >>> print(result) >>> result = attribute(df_baseline, df_scenario, variable="U10") >>> print(result) """ if variable == "T2": return attribute_t2(df_output_A, df_output_B, **kwargs) elif variable == "q2": return attribute_q2(df_output_A, df_output_B, **kwargs) elif variable == "U10": return attribute_u10(df_output_A, df_output_B, **kwargs) else: raise ValueError( f"Unknown variable: {variable}. Supported variables: 'T2', 'q2', 'U10'" )
[docs] def diagnose( df_output: pd.DataFrame, variable: Literal["T2", "q2", "U10"] = "T2", **kwargs, ) -> AttributionResult: """ Generic diagnostic function for near-surface variables. Automatically identifies anomalous values and attributes the causes. Parameters ---------- df_output : pd.DataFrame SUEWS output DataFrame variable : str, optional Variable to diagnose: 'T2' (temperature), 'q2' (humidity), or 'U10' (10m wind speed). Default 'T2'. **kwargs Additional keyword arguments passed to the specific function. - method: 'anomaly', 'extreme', or 'diurnal' - threshold: float for anomaly detection - df_forcing: forcing DataFrame (T2, q2 only) - hierarchical: bool (T2 only) - z_ref: float (U10 only) Returns ------- AttributionResult Attribution decomposition with diagnostic interpretation. Examples -------- >>> result = diagnose(df_output, variable="T2", method="anomaly") >>> print(result) >>> result = diagnose(df_output, variable="q2", method="diurnal") >>> print(result) >>> result = diagnose(df_output, variable="U10", method="extreme") >>> print(result) """ if variable == "T2": return diagnose_t2(df_output, **kwargs) elif variable == "q2": return diagnose_q2(df_output, **kwargs) elif variable == "U10": return diagnose_u10(df_output, **kwargs) else: raise ValueError( f"Unknown variable: {variable}. Supported variables: 'T2', 'q2', 'U10'" )
# Public API __all__ = [ # Variable-specific functions "attribute_t2", "attribute_q2", "attribute_u10", "diagnose_t2", "diagnose_q2", "diagnose_u10", # Generic dispatchers "attribute", "diagnose", # Result container "AttributionResult", ]