Source code for pyradtran.models.solver

"""Solver configuration model.

Maps to uvspec keywords: rte_solver, number_of_streams, pseudospherical, deltam.

Reference: libRadtran src_py/solver_options.py
"""

from __future__ import annotations

from pydantic import Field, model_validator

from pyradtran.models.base import UvspecOption

VALID_SOLVERS = frozenset(
    {
        "disort",
        "twostr",
        "mystic",
        "rodents",
        "sslidar",
        "null",
        "sdisort",
        "fdisort1",
        "fdisort2",
        "sos",
        "ftwostr",
        "montecarlo",
        "tzs",
        "sssi",
        "sss",
        "twostrebe",
        "schwarzschild",
        "twomaxrnd",
        "twomaxrnd3C",
        "dynamic_twostream",
        "dynamic_tenstream",
    }
)

VALID_HEAT_UNITS = frozenset({"K_per_day", "W_per_m2_and_dz", "W_per_m3"})

_VALID_DISORT_INTCOR = frozenset({"phase", "moments", "off"})


[docs] class SolverConfig(UvspecOption): """Radiative transfer solver configuration. Attributes: method: Solver name (disort, twostr, mystic, etc.). streams: Number of streams for discrete ordinates solvers. Default: 6. pseudospherical: Enable pseudo-spherical correction (disort/twostr only). deltam: Enable delta-M scaling. dynamic_iterations: Number of iterations for dynamic solvers. dynamic_history: Enable history tracking for dynamic solvers. dynamic_heat_unit: Heat unit for dynamic solvers. """ method: str streams: int = Field(default=6, ge=1) pseudospherical: bool = False deltam: bool = False dynamic_iterations: int | None = Field(default=None, ge=0) dynamic_history: bool = False dynamic_heat_unit: str | None = None disort_intcor: str | None = None disort_spherical_albedo: bool = False schwarzschild_streams: int | None = Field(default=None, ge=1)
[docs] @model_validator(mode="after") def validate_solver(self) -> SolverConfig: if self.method not in VALID_SOLVERS: raise ValueError(f"Unknown solver '{self.method}'. Valid: {sorted(VALID_SOLVERS)}") if self.dynamic_heat_unit is not None and self.dynamic_heat_unit not in VALID_HEAT_UNITS: raise ValueError( f"Invalid dynamic_heat_unit '{self.dynamic_heat_unit}'. " f"Valid: {sorted(VALID_HEAT_UNITS)}" ) if self.disort_intcor is not None and self.disort_intcor not in _VALID_DISORT_INTCOR: raise ValueError( f"Invalid disort_intcor '{self.disort_intcor}'. " f"Valid: {sorted(_VALID_DISORT_INTCOR)}" ) return self
[docs] def to_uvspec_lines(self) -> list[str]: lines: list[str] = [] lines.append(f"rte_solver {self.method}") lines.append(f"number_of_streams {self.streams}") if self.pseudospherical: lines.append("pseudospherical") if self.deltam: lines.append("deltam") if self.disort_intcor is not None: lines.append(f"disort_intcor {self.disort_intcor}") if self.disort_spherical_albedo: lines.append("disort_spherical_albedo") if self.schwarzschild_streams is not None: lines.append(f"schwarzschild_streams {self.schwarzschild_streams}") if self.method.startswith("dynamic_"): prefix = self.method # e.g. "dynamic_twostream" or "dynamic_tenstream" if self.dynamic_iterations is not None: lines.append(f"{prefix}_iterations {self.dynamic_iterations}") if self.dynamic_history: lines.append(f"{prefix}_history") if self.dynamic_heat_unit is not None: lines.append(f"{prefix}_heat_unit {self.dynamic_heat_unit}") return lines
[docs] def to_uvspec_items(self) -> list[tuple[int, str]]: phase = 8 return [(phase, line) for line in self.to_uvspec_lines()]