Skip to content

Base

convection_neutral_profile(temp_start, p_start, temp_lcl, p_levels)

This returns the temperature of an air parcel at the given pressure levels, assuming it follows the dry_profile up until the lcl_temp has been reached, followed by the moist_profile.

Parameters:

Name Type Description Default
temp_start float

Starting temperature of parcel. Units: Kelvin.

required
p_start float

Starting pressure of parcel. Units: Pa.

required
temp_lcl float

Temperature of LCL in K.

required
p_levels ndarray

float [n_p_levels].
Pressure levels to find the temperature of the parcel at. Assumes p_levels is ascending i.e. starts in space. Units: Pa.

required

Returns:

Type Description
ndarray

float [n_p_levels].
Temperature at each pressure level indicated by p_levels.

Source code in isca_tools/convection/base.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
def convection_neutral_profile(temp_start: float, p_start: float, temp_lcl: float,
                               p_levels: np.ndarray) -> np.ndarray:
    """
    This returns the temperature of an air parcel at the given pressure levels, assuming it follows the
    `dry_profile` up until the `lcl_temp` has been reached, followed by the `moist_profile`.

    Args:
        temp_start: Starting temperature of parcel. Units: *Kelvin*.
        p_start: Starting pressure of parcel. Units: *Pa*.
        temp_lcl: Temperature of *LCL* in *K*.
        p_levels: `float [n_p_levels]`.</br>
            Pressure levels to find the temperature of the parcel at.
            Assumes p_levels is ascending i.e. starts in space.
            Units: *Pa*.

    Returns:
        `float [n_p_levels]`.</br>
            Temperature at each pressure level indicated by `p_levels`.

    """
    p_lcl = dry_profile_pressure(temp_start, p_start, temp_lcl)
    temp_dry = dry_profile_temp(temp_start, p_start, p_levels)       # Compute dry profile for all pressure values
    temp_moist = moist_profile(temp_lcl, p_lcl, p_levels[p_levels < p_lcl])
    temp_dry[p_levels < p_lcl] = temp_moist     # Replace dry temperature with moist for pressure below p_lcl
    return temp_dry

dry_profile_pressure(temp_start, p_start, temp_levels)

Given a series of temp_levels, this function returns the pressure corresponding to each temperature, assuming it follows a dry adiabat.

Can also use the pressure of the LCL if the LCL temperature is given as temp_levels.

Parameters:

Name Type Description Default
temp_start float

Starting temperature of parcel. Units: Kelvin.

required
p_start float

Starting pressure of parcel. Units: Pa.

required
temp_levels Union[float, ndarray]

float [n_p_levels].
Temperatures of parcel where we want to findthe corresponding pressure. Units: K.

required

Returns:

Type Description
Union[float, ndarray]

float [n_p_levels].
Pressure at each temperature indicated by temp_levels.

Source code in isca_tools/convection/base.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def dry_profile_pressure(temp_start: float, p_start: float,
                         temp_levels: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
    """
    Given a series of `temp_levels`, this function returns the pressure corresponding to each temperature, assuming
    it follows a dry adiabat.

    Can also use the pressure of the LCL if the LCL temperature is given as `temp_levels`.

    Args:
        temp_start: Starting temperature of parcel. Units: *Kelvin*.
        p_start: Starting pressure of parcel. Units: *Pa*.
        temp_levels: `float [n_p_levels]`.</br>
            Temperatures of parcel where we want to findthe corresponding pressure. Units: *K*.

    Returns:
        `float [n_p_levels]`.</br>
            Pressure at each temperature indicated by `temp_levels`.
    """
    return p_start * (temp_levels / temp_start) ** (1/kappa)

dry_profile_temp(temp_start, p_start, p_levels)

Returns the temperature of an air parcel at the given pressure levels, assuming it follows the dry adiabat.

Parameters:

Name Type Description Default
temp_start float

Starting temperature of parcel. Units: Kelvin.

required
p_start float

Starting pressure of parcel. Units: Pa.

required
p_levels ndarray

float [n_p_levels].
Pressure levels to find the temperature of the parcel at. Units: Pa.

required

Returns:

Type Description
ndarray

float [n_p_levels].
Temperature at each pressure level indicated by p_levels.

Source code in isca_tools/convection/base.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def dry_profile_temp(temp_start: float, p_start: float, p_levels: np.ndarray) -> np.ndarray:
    """
    Returns the temperature of an air parcel at the given pressure levels, assuming it follows the dry adiabat.

    Args:
        temp_start: Starting temperature of parcel. Units: *Kelvin*.
        p_start: Starting pressure of parcel. Units: *Pa*.
        p_levels: `float [n_p_levels]`.</br>
            Pressure levels to find the temperature of the parcel at. Units: *Pa*.

    Returns:
        `float [n_p_levels]`.</br>
            Temperature at each pressure level indicated by `p_levels`.
    """
    return temp_start * (p_levels/p_start)**kappa

equivalent_potential_temp(temp, pressure, sphum=None, p_ref=100000.0)

Returns the virtual potential temperature using equation 9.40 of Holton 2004 textbook.

For a saturated parcel, this is \(\theta_e = \theta \exp (L_v q_s/c_pT)\).

For an unsaturated parcel (used if sphum given), it is \(\theta_e = \theta \exp (L_v q/c_pT_{LCL})\).

where \(q\) (\(q_s\)) is the (saturation) mixing ratio, \(T_{LCL}\) is the lifting condensation level temperature (using equation 10 from Bolton 1980) and \(\theta\) is the potential temperature.

Parameters:

Name Type Description Default
temp Union[float, ndarray]

float [n_p_levels] Temperature in K to find virtual potential temperature at.

required
pressure Union[float, ndarray]

float [n_p_levels] Pressure levels in Pa corresponding to the temperatures given.

required
sphum Optional[float]

float [n_p_levels] Specific humidity of parcel at each pressure level in kg/kg. If not given, assumes saturated.

None
p_ref float

Reference pressure in Pa used to compute the potential temperature

100000.0

Returns:

Type Description
Union[float, ndarray]

float [n_p_levels] Equivalent potential temperature in K at given pressure levels.

Source code in isca_tools/convection/base.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
def equivalent_potential_temp(temp: Union[float, np.ndarray], pressure: Union[float, np.ndarray],
                              sphum: Optional[float] = None, p_ref: float = 1e5) -> Union[float, np.ndarray]:
    """
    Returns the virtual potential temperature using equation 9.40 of *Holton 2004* textbook.

    For a saturated parcel, this is $\\theta_e = \\theta \\exp (L_v q_s/c_pT)$.

    For an unsaturated parcel (used if `sphum` given), it is $\\theta_e = \\theta \\exp (L_v q/c_pT_{LCL})$.

    where $q$ ($q_s$) is the (saturation) mixing ratio, $T_{LCL}$ is the lifting condensation level temperature
    (using equation 10 from *Bolton 1980*) and $\\theta$ is the potential temperature.

    Args:
        temp: `float [n_p_levels]`
            Temperature in *K* to find virtual potential temperature at.
        pressure: `float [n_p_levels]`
            Pressure levels in *Pa* corresponding to the temperatures given.
        sphum: `float [n_p_levels]`
            Specific humidity of parcel at each pressure level in *kg/kg*. If not given, assumes saturated.
        p_ref: Reference pressure in *Pa* used to compute the potential temperature

    Returns:
        `float [n_p_levels]`
            Equivalent potential temperature in *K* at given pressure levels.
    """
    if sphum is None:
        mix_ratio = mixing_ratio_from_sphum(sphum_sat(temp, pressure))
        temp_lcl = temp
    else:
        mix_ratio = mixing_ratio_from_sphum(sphum)
        temp_lcl = lcl_temp_bolton(temp, rh_from_sphum(sphum, temp, pressure))
    temp_pot = potential_temp(temp, pressure, p_ref)
    return temp_pot * np.exp(L_v * mix_ratio / (c_p * temp_lcl))

lapse_moist(temp, total_pressure, pressure_coords=False)

Returns the saturated moist adiabatic lapse rate, \(\Gamma_s = -dT/dz\), at a given temperature.

Comes from equation D.10 in Holton 2004.

Parameters:

Name Type Description Default
temp Union[float, ndarray]

Temperature to compute lapse rate at. Units: Kelvin.

required
total_pressure Union[float, ndarray]

Atmospheric pressure at altitude considered, \(p\), in Pa.

required
pressure_coords bool

If True, will return \(dT/dp\), otherwise will return \(-dT/dz\).

False

Returns: Saturated moist adiabatic lapse rate. Units: \(Km^{-1}\).

Source code in isca_tools/convection/base.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def lapse_moist(temp: Union[float, np.ndarray], total_pressure: Union[float, np.ndarray],
                pressure_coords: bool = False) -> Union[float, np.ndarray]:
    """
    Returns the saturated moist adiabatic lapse rate, $\Gamma_s = -dT/dz$, at a given temperature.

    Comes from equation D.10 in Holton 2004.

    Args:
        temp: Temperature to compute lapse rate at. Units: *Kelvin*.
        total_pressure: Atmospheric pressure at altitude considered, $p$, in *Pa*.
        pressure_coords: If `True`, will return $dT/dp$, otherwise will return $-dT/dz$.
    Returns:
        Saturated moist adiabatic lapse rate. Units: $Km^{-1}$.
    """
    e_s = saturation_vapor_pressure(temp)
    w_s = mixing_ratio_from_partial_pressure(e_s, total_pressure)   # saturation mixing ratio
    neg_dT_dz = lapse_dry * (1 + L_v*w_s / (R * temp)) / (1 + epsilon * L_v**2*w_s/(c_p * R * temp**2))
    if pressure_coords:
        return R * temp * neg_dT_dz / (total_pressure * g)
    else:
        return neg_dT_dz

lcl_metpy(temp_2m, sphum_2m, pressure_surf)

Returns the pressure and temperature of the lifting condensation level, LCL.

Parameters:

Name Type Description Default
temp_2m DataArray

Near-surface (2m) air temperature in Kelvin.

required
sphum_2m DataArray

Near-surface (2m) specific humidity in kg/kg.

required
pressure_surf DataArray

Surface pressure in Pa.

required

Returns:

Name Type Description
p_lcl DataArray

LCL pressure in Pa.

temp_lcl DataArray

LCL temperature in Kelvin.

Source code in isca_tools/convection/base.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def lcl_metpy(temp_2m: xr.DataArray, sphum_2m: xr.DataArray,
              pressure_surf: xr.DataArray) -> Tuple[xr.DataArray, xr.DataArray]:
    """
    Returns the pressure and temperature of the lifting condensation level, LCL.

    Args:
        temp_2m: Near-surface (2m) air temperature in *Kelvin*.
        sphum_2m: Near-surface (2m) specific humidity in *kg/kg*.
        pressure_surf: Surface pressure in *Pa*.

    Returns:
        p_lcl: LCL pressure in *Pa*.
        temp_lcl: LCL temperature in *Kelvin*.
    """
    # Extract and convert to units
    q = sphum_2m.values * metpy.units.units('kg/kg')
    T = temp_2m.values * metpy.units.units.kelvin
    P = pressure_surf.values  * metpy.units.units.pascal

    # Calculate dewpoint from mixing ratio
    dewpoint = metpy.calc.dewpoint_from_specific_humidity(P, q)
    lcl_pressure, lcl_temperature = metpy.calc.lcl(P, T, dewpoint)
    dims = temp_2m.dims
    coords = temp_2m.coords

    # Convert MetPy Quantities to numpy arrays and units to attributes
    lcl_pressure_xr = xr.DataArray(
        data=lcl_pressure.magnitude,
        dims=dims,
        coords=coords,
        name='lcl_pressure',
        attrs={'units': str(lcl_pressure.units), 'long_name': 'LCL pressure'}
    )

    lcl_temperature_xr = xr.DataArray(
        data=lcl_temperature.magnitude,
        dims=dims,
        coords=coords,
        name='lcl_temperature',
        attrs={'units': str(lcl_temperature.units), 'long_name': 'LCL temperature'}
    )
    return lcl_pressure_xr, lcl_temperature_xr

lcl_temp_bolton(temp_surf, rh_surf)

Returns the temperature of the lifting condensation level, LCL, given the surface temperature and relative humidity.

Equation comes from Equation 22 in Bolton 1980.

Parameters:

Name Type Description Default
temp_surf ndarray

Surface temperature in Kelvin.

required
rh_surf ndarray

Percentage surface relative humidity (\(0 < rh < 100\)).

required

Returns:

Type Description
ndarray

Temperature of LCL in Kelvin.

Source code in isca_tools/convection/base.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def lcl_temp_bolton(temp_surf: np.ndarray, rh_surf: np.ndarray) -> np.ndarray:
    """
    Returns the temperature of the lifting condensation level, *LCL*, given the surface
    temperature and relative humidity.

    Equation comes from *Equation 22* in *Bolton 1980*.

    Args:
        temp_surf: Surface temperature in *Kelvin*.
        rh_surf: Percentage surface relative humidity ($0 < rh < 100$).

    Returns:
        Temperature of *LCL* in *Kelvin*.
    """
    return 1 / (1/(temp_surf-55) - np.log(rh_surf/100)/2840) + 55

moist_profile(temp_start, p_start, p_levels)

Returns the temperature of an air parcel at the given pressure levels, assuming it follows the saturated moist adiabat.

Parameters:

Name Type Description Default
temp_start float

Starting temperature of parcel. Units: Kelvin.

required
p_start float

Starting pressure of parcel. Units: Pa.

required
p_levels ndarray

float [n_p_levels].
Pressure levels to find the temperature of the parcel at. Assumes p_levels is ascending i.e. starts in space. Units: Pa.

required

Returns:

Type Description
ndarray

float [n_p_levels].

ndarray

Temperature at each pressure level indicated by p_levels.

Source code in isca_tools/convection/base.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def moist_profile(temp_start: float, p_start: float, p_levels: np.ndarray) -> np.ndarray:
    """
    Returns the temperature of an air parcel at the given pressure levels, assuming it follows the saturated moist
    adiabat.

    Args:
        temp_start: Starting temperature of parcel. Units: *Kelvin*.
        p_start: Starting pressure of parcel. Units: *Pa*.
        p_levels: `float [n_p_levels]`.</br>
            Pressure levels to find the temperature of the parcel at.
            Assumes p_levels is ascending i.e. starts in space.
            Units: *Pa*.

    Returns:
        `float [n_p_levels]`.</br>
        Temperature at each pressure level indicated by `p_levels`.
    """

    # Solve for all pressure levels with lower value than starting pressure
    p_lower = p_levels[p_levels <= p_start][::-1]
    if len(p_lower) > 0:
        if p_start not in p_lower:
            added_p_start = True
            p_lower = np.concatenate(([p_start], p_lower))
        else:
            added_p_start = False
        temp_lower = odeint(lapse_moist, temp_start, p_lower, args=(True,)).flatten()
        if added_p_start:
            # If had to add starting pressure, remove it so only have temperatures corresponding to pressures in
            # p_levels.
            temp_lower = temp_lower[1:]
            p_lower = p_lower[1:]
    else:
        temp_lower = np.array([])

    # Solve for all pressure levels with higher value than starting pressure
    p_higher = p_levels[p_levels > p_start]
    if len(p_higher) > 0:
        if p_start not in p_higher:
            added_p_start = True
            p_higher = np.concatenate(([p_start], p_higher))
        else:
            added_p_start = False
        temp_higher = odeint(lapse_moist, temp_start, p_higher, args=(True,)).flatten()
        if added_p_start:
            # If had to add starting pressure, remove it so only have temperatures corresponding to pressures in
            # p_levels.
            temp_higher = temp_higher[1:]
            p_higher = p_higher[1:]
    else:
        temp_higher = np.array([])

    # Combine all temperature values found
    p_descend = np.concatenate((p_higher, p_lower))
    temp_descend = np.concatenate((temp_higher, temp_lower))
    # Rearrange temperature values so match how p_levels ordered in input.
    temp_final = temp_descend[numpy_indexed.indices(p_descend, p_levels)].flatten()

    # Old manual way of doing it
    # p_done = np.asarray([p_start]).reshape(-1, 1)
    # p_todo = p_levels
    # temp_levels = np.zeros_like(p_levels)
    # # For each pressure level, compute lapse rate based on pressure level closest to it and then update
    # # temperature of that level assuming lapse rate stays constant between the levels.
    # while len(p_todo) > 0:
    #     # Find the two pressure levels with the minimum distance between them such that on is in the set p_done
    #     # and one is in the set p_todo.
    #     # Want two closest as assuming lapse rate is constant between the levels
    #     diff = np.abs(p_done - p_todo)
    #     done_ind, todo_ind = np.argwhere(diff == np.min(diff))[0]
    #     if done_ind == 0:
    #         # First index is the inputted start values
    #         p_ref = p_start
    #         temp_ref = temp_start
    #     else:
    #         p_ind = np.where(p_levels == p_done[done_ind])[0]
    #         p_ref = p_levels[p_ind]
    #         temp_ref = temp_levels[p_ind]
    #
    #     # Compute lapse rate based on temperature and pressure of level in the done set.
    #     dT_dp = lapse_moist(temp_ref, p_ref, True)
    #     temp_level_ind = np.where(p_levels == p_todo[todo_ind])[0]
    #
    #     # Update temperature at level considering - assume constant lapse rate between levels.
    #     temp_levels[temp_level_ind] = temp_ref + (p_levels[temp_level_ind]-p_ref) * dT_dp
    #
    #     # Transfer pressure level from p_todo to p_done for next iteration
    #     p_done = np.append(p_done, np.asarray(p_todo[todo_ind]).reshape(-1, 1), axis=0)
    #     p_todo = np.delete(p_todo, todo_ind)
    return temp_final

potential_temp(temp, pressure, p_ref=100000.0)

Returns the potential temperature: \(\theta = T\left(\frac{p_{ref}}{p}\right)^{\kappa}\)

Parameters:

Name Type Description Default
temp Union[float, ndarray]

float [n_p_levels] Temperature in K to find potential temperature at.

required
pressure Union[float, ndarray]

float [n_p_levels] Pressure levels in Pa corresponding to the temperatures given.

required
p_ref float

Reference pressure in Pa used to compute the potential temperature

100000.0

Returns:

Type Description
Union[float, ndarray]

float [n_p_levels] Potential temperature in K at each pressure level.

Source code in isca_tools/convection/base.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
def potential_temp(temp: Union[float, np.ndarray], pressure: Union[float, np.ndarray],
                   p_ref: float = 1e5) -> Union[float, np.ndarray]:
    """
    Returns the potential temperature: $\\theta = T\\left(\\frac{p_{ref}}{p}\\right)^{\\kappa}$

    Args:
        temp: `float [n_p_levels]`
            Temperature in *K* to find potential temperature at.
        pressure: `float [n_p_levels]`
            Pressure levels in *Pa* corresponding to the temperatures given.
        p_ref: Reference pressure in *Pa* used to compute the potential temperature

    Returns:
        `float [n_p_levels]`
            Potential temperature in *K* at each pressure level.
    """
    return temp * (p_ref / pressure)**kappa