tephpy.calc =========== .. py:module:: tephpy.calc .. autoapi-nested-parse:: Tephigram-native thermodynamic analysis over :mod:`metpy.calc` (spec §3.3). Physics is delegated to MetPy; only tephigram-native compositions live here, and everything returns pint quantities on the shared registry (spec §5). Sounding-level functions take a :class:`~tephpy.sounding.Sounding` — constructing one already validates units, monotonic pressure, and dewpoint ≤ temperature — while :func:`normand_point` is the one quantity-level function. MetPy stays behind function-local imports so that ``import tephpy`` stays light (spec §10 item 10). Analysis results distinguish "does not exist" from "zero" (spec §6): ``metpy.calc`` returns NaN quantities for a missing LFC/EL and ``0 J/kg`` — never NaN — for zero CAPE/CIN, and tephpy passes both through, documented per :class:`SoundingIndices` field. .. !! processed by numpydoc !! Classes ------- .. autoapisummary:: tephpy.calc.Profile tephpy.calc.SoundingIndices Functions --------- .. autoapisummary:: tephpy.calc.normand_point tephpy.calc.parcel_path tephpy.calc.indices Module Contents --------------- .. py:class:: Profile One computed parcel ascent, ready to plot (spec §3.3). Plain plottable data: ``plot_profile`` draws it and the shading builders consume it, and neither re-derives the LCL. Construction mirrors ``Sounding``: bare arrays take the ``units=`` mapping, fields are dimension-checked quantities on the shared registry, and validation happens at construction. :Attributes: **pressure** : :obj:`pint.Quantity` Path pressures, surface-first (strictly decreasing), at least two levels. **temperature** : :obj:`pint.Quantity` Parcel temperatures along the path. **lcl_pressure** : :obj:`pint.Quantity` Scalar pressure of the Normand's point the path actually uses — the corrected one when a correction was requested — inside the path's pressure span. **lcl_temperature** : :obj:`pint.Quantity` Scalar temperature at that point. **parcel** : :class:`python:str` The lifted parcel: ``"surface"`` or ``"mixed-layer"``. **label** : :class:`python:str` or :data:`python:None` Legend text; ``None`` means no legend entry. **units** : mapping of :class:`python:str` to :class:`python:str`, optional Construction-only (not stored): unit strings for bare-array fields, keyed by field name (spec §5). .. !! processed by numpydoc !! .. py:attribute:: pressure :type: pint.Quantity .. py:attribute:: temperature :type: pint.Quantity .. py:attribute:: lcl_pressure :type: pint.Quantity .. py:attribute:: lcl_temperature :type: pint.Quantity .. py:attribute:: parcel :type: Literal['surface', 'mixed-layer'] :value: 'surface' .. py:attribute:: label :type: str | None :value: None .. py:attribute:: units :type: dataclasses.InitVar[collections.abc.Mapping[str, str] | None] :value: None .. py:class:: SoundingIndices Derived thermodynamic parameters for one sounding (spec §3.3). Ten scalar quantity fields, each dimension-checked at construction. There is no cross-field validation: NaN fields are answers, not errors — analysis results distinguish "does not exist" (NaN) from "zero" (spec §6). :Attributes: **cape** : :obj:`pint.Quantity` Convective available potential energy (J/kg); ``0 J/kg`` — never NaN — when the parcel has no positive-buoyancy region. **cin** : :obj:`pint.Quantity` Convective inhibition (J/kg, non-positive); ``0 J/kg`` when there is no LFC or no negative-buoyancy region below it. **lcl_pressure** : :obj:`pint.Quantity` Pressure of the lifting condensation level the parcel uses (the corrected one when a correction was requested); always defined. **lcl_temperature** : :obj:`pint.Quantity` Temperature at that level; always defined. **lfc_pressure** : :obj:`pint.Quantity` Pressure of the level of free convection; NaN when the parcel never becomes positively buoyant. **lfc_temperature** : :obj:`pint.Quantity` Temperature at that level; NaN with `lfc_pressure`. **el_pressure** : :obj:`pint.Quantity` Pressure of the equilibrium level; NaN when it does not exist — including while ``cape > 0`` with the parcel still buoyant at the profile top. **el_temperature** : :obj:`pint.Quantity` Temperature at that level; NaN with `el_pressure`. **theta_w** : :obj:`pint.Quantity` Wet-bulb potential temperature of the lifted parcel, evaluated at the parcel start, so it follows the ``parcel=`` option; always defined. **lifted_index** : :obj:`pint.Quantity` Lifted index (a temperature difference at 500 hPa); NaN when the profile tops out below 500 hPa. **units** : mapping of :class:`python:str` to :class:`python:str`, optional Construction-only (not stored): unit strings for bare scalar fields, keyed by field name (spec §5). .. !! processed by numpydoc !! .. py:attribute:: cape :type: pint.Quantity .. py:attribute:: cin :type: pint.Quantity .. py:attribute:: lcl_pressure :type: pint.Quantity .. py:attribute:: lcl_temperature :type: pint.Quantity .. py:attribute:: lfc_pressure :type: pint.Quantity .. py:attribute:: lfc_temperature :type: pint.Quantity .. py:attribute:: el_pressure :type: pint.Quantity .. py:attribute:: el_temperature :type: pint.Quantity .. py:attribute:: theta_w :type: pint.Quantity .. py:attribute:: lifted_index :type: pint.Quantity .. py:attribute:: units :type: dataclasses.InitVar[collections.abc.Mapping[str, str] | None] :value: None .. py:function:: normand_point(pressure: object, temperature: object, dewpoint: object, *, units: collections.abc.Mapping[str, str] | None = None) -> tuple[pint.Quantity, pint.Quantity] Construct Normand's point — the LCL — for one parcel (spec §3.3). The geometric construction: the dry adiabat through (`pressure`, `temperature`) meets the humidity mixing-ratio line through (`pressure`, `dewpoint`) at the lifting condensation level. This is always the uncorrected construction; the operational cloud-base correction is :func:`parcel_path`'s concern. :Parameters: **pressure** : :obj:`pint.Quantity` or :class:`python:float` Scalar parcel pressure; a bare value takes the ``units=`` mapping. **temperature** : :obj:`pint.Quantity` or :class:`python:float` Scalar parcel temperature. **dewpoint** : :obj:`pint.Quantity` or :class:`python:float` Scalar parcel dewpoint; must not exceed `temperature` (equality — saturation — is physical, and puts Normand's point at the parcel). **units** : mapping of :class:`python:str` to :class:`python:str`, optional Unit strings for bare values, keyed by argument name, e.g. ``units={"pressure": "hPa", "temperature": "degC"}`` (spec §5). :Returns: :class:`python:tuple` of :obj:`pint.Quantity` The scalar ``(pressure, temperature)`` of Normand's point, in hPa and degrees Celsius. :Raises: :obj:`TephpyUnitsError ` For unit-less bare values, ambiguous or unparsable units, or the wrong dimensionality. :obj:`DewpointExceedsTemperatureError ` If `dewpoint` exceeds `temperature`. :obj:`TephpyValidationError ` If an argument is not a scalar. .. !! processed by numpydoc !! .. py:function:: parcel_path(snd: tephpy.sounding.Sounding, *, parcel: Literal['surface', 'mixed-layer'] = 'surface', cloud_base_correction: object = None, label: str | None = None) -> Profile Compute a parcel's ascent path over the sounding's span (spec §3.3). Dry adiabat from the parcel start to Normand's point, then moist adiabat to the profile top. Both legs sample the background moist adiabats' 5 hPa step, the moist leg is integrated with ``metpy.calc.moist_lapse(..., reference_pressure=lcl_pressure)`` — same integrator, same sampling, same anchoring as the background family — and the LCL vertex is spliced in exactly. :Parameters: **snd** : :obj:`Sounding ` The environment sounding; must carry dewpoint. **parcel** : :class:`python:str`, default: "surface" The lifted parcel: ``"surface"`` starts from the lowest level; ``"mixed-layer"`` starts from :func:`metpy.calc.mixed_parcel` (its 100 hPa default depth is the operational convention). **cloud_base_correction** : :obj:`pint.Quantity`, optional A pressure-dimension correction added to the LCL pressure, applied only when explicitly requested; the operational -25 mb value lives in ``tephpy._constants.CLOUD_BASE_CORRECTION``. The corrected LCL temperature is re-read from the dry adiabat at the corrected pressure. **label** : :class:`python:str`, optional Legend text for the profile; ``None`` means no legend entry. :Returns: :obj:`Profile ` The parcel path, surface-first, with the LCL it actually uses. :Raises: :obj:`MissingDataError ` If the sounding has no dewpoint. :obj:`ProfileTooShortError ` If the profile tops out at or below the LCL the path would use (the corrected one when a correction is requested), or a mixed-layer parcel is requested but the sounding spans less than the mixed-layer depth. :obj:`TephpyUnitsError ` If `cloud_base_correction` is not a pressure-dimension quantity. :obj:`TephpyValidationError ` If the selected parcel start has an undefined (NaN) temperature or dewpoint, or the correction places the LCL below the parcel start. :obj:`ValueError` If `parcel` is not a known option. .. !! processed by numpydoc !! .. py:function:: indices(snd: tephpy.sounding.Sounding, *, parcel: Literal['surface', 'mixed-layer'] = 'surface', cloud_base_correction: object = None) -> SoundingIndices Compute the derived thermodynamic parameters (spec §3.3). The mechanism: derive the parcel curve on the environment levels under the same parcel-selection and correction rules as :func:`parcel_path`, then feed it to the generic :mod:`metpy.calc` functions that take a parcel-profile argument (``cape_cin``, ``lfc``, ``el``, ``lifted_index``). With the defaults this reduces to plain surface-parcel delegation. The ``lcl_*`` fields report the point the path uses (corrected when requested) and `theta_w` the parcel start, mirroring :class:`Profile`. `theta_w` is computed with ``wet_bulb_potential_temperature``, whose Davies-Jones formulation differs from the moist-adiabat integrator by ≲0.1 °C: the path is drawn by the integrator, the number by the named function (spec §3.3). :Parameters: **snd** : :obj:`Sounding ` The environment sounding; must carry dewpoint. **parcel** : :class:`python:str`, default: "surface" The lifted parcel, as for :func:`parcel_path`. **cloud_base_correction** : :obj:`pint.Quantity`, optional The LCL correction, as for :func:`parcel_path`. :Returns: :obj:`SoundingIndices ` The ten derived parameters, with the spec §6 NaN-versus-zero semantics documented per field. :Raises: :obj:`MissingDataError ` If the sounding has no dewpoint. :obj:`ProfileTooShortError ` If the profile tops out at or below the LCL the parcel would use, or a mixed-layer parcel is requested but the sounding spans less than the mixed-layer depth. :obj:`TephpyUnitsError ` If `cloud_base_correction` is not a pressure-dimension quantity. :obj:`TephpyValidationError ` If the selected parcel start has an undefined (NaN) temperature or dewpoint, or the correction places the LCL below the parcel start. :obj:`ValueError` If `parcel` is not a known option. .. !! processed by numpydoc !!