pycontrails 0.41.0__cp310-cp310-win_amd64.whl → 0.42.2__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pycontrails might be problematic. Click here for more details.

Files changed (40) hide show
  1. pycontrails/_version.py +2 -2
  2. pycontrails/core/airports.py +228 -0
  3. pycontrails/core/cache.py +4 -6
  4. pycontrails/core/datalib.py +13 -6
  5. pycontrails/core/fleet.py +72 -20
  6. pycontrails/core/flight.py +485 -134
  7. pycontrails/core/flightplan.py +238 -0
  8. pycontrails/core/interpolation.py +11 -15
  9. pycontrails/core/met.py +5 -5
  10. pycontrails/core/models.py +4 -0
  11. pycontrails/core/rgi_cython.cp310-win_amd64.pyd +0 -0
  12. pycontrails/core/vector.py +80 -63
  13. pycontrails/datalib/__init__.py +1 -1
  14. pycontrails/datalib/ecmwf/common.py +14 -19
  15. pycontrails/datalib/spire/__init__.py +19 -0
  16. pycontrails/datalib/spire/spire.py +739 -0
  17. pycontrails/ext/bada/__init__.py +6 -6
  18. pycontrails/ext/cirium/__init__.py +2 -2
  19. pycontrails/models/cocip/cocip.py +37 -39
  20. pycontrails/models/cocip/cocip_params.py +37 -30
  21. pycontrails/models/cocip/cocip_uncertainty.py +47 -58
  22. pycontrails/models/cocip/radiative_forcing.py +220 -193
  23. pycontrails/models/cocip/wake_vortex.py +96 -91
  24. pycontrails/models/cocip/wind_shear.py +2 -2
  25. pycontrails/models/emissions/emissions.py +1 -1
  26. pycontrails/models/humidity_scaling.py +266 -9
  27. pycontrails/models/issr.py +2 -2
  28. pycontrails/models/pcr.py +1 -1
  29. pycontrails/models/quantiles/era5_ensemble_quantiles.npy +0 -0
  30. pycontrails/models/quantiles/iagos_quantiles.npy +0 -0
  31. pycontrails/models/sac.py +7 -5
  32. pycontrails/physics/geo.py +5 -3
  33. pycontrails/physics/jet.py +66 -113
  34. pycontrails/utils/json.py +3 -3
  35. {pycontrails-0.41.0.dist-info → pycontrails-0.42.2.dist-info}/METADATA +4 -7
  36. {pycontrails-0.41.0.dist-info → pycontrails-0.42.2.dist-info}/RECORD +40 -34
  37. {pycontrails-0.41.0.dist-info → pycontrails-0.42.2.dist-info}/LICENSE +0 -0
  38. {pycontrails-0.41.0.dist-info → pycontrails-0.42.2.dist-info}/NOTICE +0 -0
  39. {pycontrails-0.41.0.dist-info → pycontrails-0.42.2.dist-info}/WHEEL +0 -0
  40. {pycontrails-0.41.0.dist-info → pycontrails-0.42.2.dist-info}/top_level.txt +0 -0
@@ -20,6 +20,12 @@ try:
20
20
  BADAParams,
21
21
  )
22
22
 
23
+ except ImportError as e:
24
+ raise ImportError(
25
+ 'Failed to import `pycontrails-bada` extension. Install with `pip install "pycontrails-bada'
26
+ ' @ git+ssh://git@github.com/contrailcirrus/pycontrails-bada.git"`'
27
+ ) from e
28
+ else:
23
29
  __all__ = [
24
30
  "BADA",
25
31
  "BADA3",
@@ -33,9 +39,3 @@ try:
33
39
  "bada4",
34
40
  "bada_model",
35
41
  ]
36
-
37
- except ImportError as e:
38
- raise ImportError(
39
- 'Failed to import `pycontrails-bada` extension. Install with `pip install "pycontrails-bada'
40
- ' @ git+ssh://git@github.com/contrailcirrus/pycontrails-bada.git"`'
41
- ) from e
@@ -5,10 +5,10 @@ from __future__ import annotations
5
5
  try:
6
6
  from pycontrails_cirium import Cirium
7
7
 
8
- __all__ = ["Cirium"]
9
-
10
8
  except ImportError as e:
11
9
  raise ImportError(
12
10
  "Failed to import `pycontrails-cirium` extension. Install with `pip install"
13
11
  ' "pycontrails-cirium @ git+ssh://git@github.com/contrailcirrus/pycontrails-cirium.git"`'
14
12
  ) from e
13
+ else:
14
+ __all__ = ["Cirium"]
@@ -7,6 +7,7 @@ import warnings
7
7
  from typing import Any, NoReturn, Sequence, overload
8
8
 
9
9
  import numpy as np
10
+ import numpy.typing as npt
10
11
  import pandas as pd
11
12
  import xarray as xr
12
13
  from overrides import overrides
@@ -257,7 +258,7 @@ class Cocip(Model):
257
258
  contrail_dataset: xr.Dataset | None
258
259
 
259
260
  #: Array of np.datetime64 time steps for contrail evolution
260
- timesteps: np.ndarray
261
+ timesteps: npt.NDArray[np.datetime64]
261
262
 
262
263
  #: Parallel copy of flight waypoints after SAC filter applied
263
264
  _sac_flight: Flight
@@ -376,8 +377,9 @@ class Cocip(Model):
376
377
  # Save humidity scaling type to output attrs
377
378
  # NOTE: Do this after _process_flight because that method automatically
378
379
  # broadcasts all numeric source params.
379
- if self.params["humidity_scaling"] is not None:
380
- for k, v in self.params["humidity_scaling"].description.items():
380
+ humidity_scaling = self.params["humidity_scaling"]
381
+ if humidity_scaling is not None:
382
+ for k, v in humidity_scaling.description.items():
381
383
  self.source.attrs[f"humidity_scaling_{k}"] = v
382
384
 
383
385
  if isinstance(self.source, Fleet):
@@ -488,7 +490,7 @@ class Cocip(Model):
488
490
  self.source.setdefault("level", self.source.level)
489
491
  self.source.setdefault("air_pressure", self.source.air_pressure)
490
492
 
491
- core_columns = ["longitude", "latitude", "altitude", "time"]
493
+ core_columns = ("longitude", "latitude", "altitude", "time")
492
494
  for col in core_columns:
493
495
  if np.isnan(self.source[col]).any():
494
496
  raise ValueError(
@@ -532,23 +534,20 @@ class Cocip(Model):
532
534
  # STEP 4: Begin met interpolation
533
535
  # Unfortunately we use both "u_wind" and "eastward_wind" to refer to the
534
536
  # same variable, so the logic gets a bit more complex.
535
- scale_humidity = (self.params["humidity_scaling"] is not None) and (
536
- "specific_humidity" not in self.source
537
- )
538
- variables = {
539
- "air_temperature": "air_temperature",
540
- "specific_humidity": "specific_humidity",
541
- "eastward_wind": "u_wind",
542
- "northward_wind": "v_wind",
543
- }
544
- for met_variable, fl_variable in variables.items():
545
- interpolate_met(met, self.source, met_variable, fl_variable, **self.interp_kwargs)
537
+ humidity_scaling = self.params["humidity_scaling"]
538
+ scale_humidity = humidity_scaling is not None and "specific_humidity" not in self.source
539
+ verbose_outputs = self.params["verbose_outputs"]
540
+
541
+ interpolate_met(met, self.source, "air_temperature", **self.interp_kwargs)
542
+ interpolate_met(met, self.source, "specific_humidity", **self.interp_kwargs)
543
+ interpolate_met(met, self.source, "eastward_wind", "u_wind", **self.interp_kwargs)
544
+ interpolate_met(met, self.source, "northward_wind", "v_wind", **self.interp_kwargs)
546
545
 
547
546
  if scale_humidity:
548
- self.params["humidity_scaling"].eval(self.source, copy_source=False)
547
+ humidity_scaling.eval(self.source, copy_source=False)
549
548
 
550
549
  # if humidity_scaling isn't defined, add rhi to source for verbose_outputs
551
- elif self.params["verbose_outputs"]:
550
+ elif verbose_outputs:
552
551
  self.source["rhi"] = thermo.rhi(
553
552
  self.source["specific_humidity"],
554
553
  self.source["air_temperature"],
@@ -556,7 +555,7 @@ class Cocip(Model):
556
555
  )
557
556
 
558
557
  # Cache extra met properties for post-analysis
559
- if self.params["verbose_outputs"]:
558
+ if verbose_outputs:
560
559
  interpolate_met(met, self.source, "tau_cirrus", **self.interp_kwargs)
561
560
 
562
561
  # handle ECMWF/GFS ciwc variables
@@ -568,6 +567,9 @@ class Cocip(Model):
568
567
  self.source["rho_air"] = thermo.rho_d(
569
568
  self.source["air_temperature"], self.source.air_pressure
570
569
  )
570
+ self.source["sdr"] = geo.solar_direct_radiation(
571
+ self.source["longitude"], self.source["latitude"], self.source["time"]
572
+ )
571
573
 
572
574
  # STEP 5: Calculate segment-specific properties if they are not already attached
573
575
  if "true_airspeed" not in self.source:
@@ -579,7 +581,8 @@ class Cocip(Model):
579
581
  )
580
582
  if "segment_length" not in self.source:
581
583
  self.source["segment_length"] = self.source.segment_length()
582
- max_ = np.nanmax(self.source["segment_length"])
584
+
585
+ max_ = self.source.max_distance_gap
583
586
  lim_ = self.params["max_seg_length_m"]
584
587
  if max_ > 0.9 * lim_:
585
588
  warnings.warn(
@@ -867,8 +870,9 @@ class Cocip(Model):
867
870
  **self.interp_kwargs,
868
871
  )
869
872
 
870
- if self.params["humidity_scaling"] is not None:
871
- self.params["humidity_scaling"].eval(contrail_1, copy_source=False)
873
+ humidity_scaling = self.params["humidity_scaling"]
874
+ if humidity_scaling is not None:
875
+ humidity_scaling.eval(contrail_1, copy_source=False)
872
876
  else:
873
877
  contrail_1["air_pressure"] = contrail_1.air_pressure
874
878
  contrail_1["rhi"] = thermo.rhi(
@@ -1274,15 +1278,6 @@ class Cocip(Model):
1274
1278
  Flight or list of Flight objects with empty variables.
1275
1279
  """
1276
1280
 
1277
- empty = np.full(shape=len(self.source), fill_value=np.nan)
1278
-
1279
- self.source["sdr"] = empty.copy()
1280
- self.source["rsr"] = empty.copy()
1281
- self.source["olr"] = empty.copy()
1282
- self.source["rf_sw"] = empty.copy()
1283
- self.source["rf_lw"] = empty.copy()
1284
- self.source["rf_net"] = empty.copy()
1285
-
1286
1281
  intersection = self.source.data.pop("_met_intersection")
1287
1282
  zeros_and_nans = np.where(intersection, 0.0, np.nan)
1288
1283
  self.source["ef"] = zeros_and_nans.copy()
@@ -1830,9 +1825,9 @@ def calc_radiative_properties(contrail: GeoVectorDataset, params: dict[str, Any]
1830
1825
 
1831
1826
  def calc_contrail_properties(
1832
1827
  contrail: GeoVectorDataset,
1833
- effective_vertical_resolution: float | np.ndarray,
1834
- wind_shear_enhancement_exponent: float | np.ndarray,
1835
- sedimentation_impact_factor: float | np.ndarray,
1828
+ effective_vertical_resolution: float | npt.NDArray[np.float_],
1829
+ wind_shear_enhancement_exponent: float | npt.NDArray[np.float_],
1830
+ sedimentation_impact_factor: float | npt.NDArray[np.float_],
1836
1831
  radiative_heating_effects: bool,
1837
1832
  ) -> None:
1838
1833
  """Calculate geometric and ice-related properties of contrail.
@@ -1859,10 +1854,12 @@ def calc_contrail_properties(
1859
1854
  ----------
1860
1855
  contrail : GeoVectorDataset
1861
1856
  Grid points with many precomputed keys.
1862
- effective_vertical_resolution, wind_shear_enhancement_exponent : float | np.ndarray
1863
- Passed into :func:`wind_shear.wind_shear_enhancement_factor` function.
1864
- sedimentation_impact_factor: float | np.ndarray
1865
- Passed into `contrail_properties.vertical_diffusivity` function.
1857
+ effective_vertical_resolution : float | npt.NDArray[np.float_]
1858
+ Passed into :func:`wind_shear.wind_shear_enhancement_factor`.
1859
+ wind_shear_enhancement_exponent : float | npt.NDArray[np.float_]
1860
+ Passed into :func:`wind_shear.wind_shear_enhancement_factor`.
1861
+ sedimentation_impact_factor: float | npt.NDArray[np.float_]
1862
+ Passed into `contrail_properties.vertical_diffusivity`.
1866
1863
  radiative_heating_effects: bool
1867
1864
  Include radiative heating effects on contrail cirrus properties.
1868
1865
  """
@@ -2141,8 +2138,9 @@ def calc_timestep_contrail_evolution(
2141
2138
 
2142
2139
  interpolate_met(met, contrail_2, "specific_humidity", **interp_kwargs)
2143
2140
 
2144
- if params["humidity_scaling"] is not None:
2145
- params["humidity_scaling"].eval(contrail_2, copy_source=False)
2141
+ humidity_scaling = params["humidity_scaling"]
2142
+ if humidity_scaling is not None:
2143
+ humidity_scaling.eval(contrail_2, copy_source=False)
2146
2144
  else:
2147
2145
  contrail_2["air_pressure"] = contrail_2.air_pressure
2148
2146
  contrail_2["rhi"] = thermo.rhi(
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
  import dataclasses
9
9
 
10
10
  import numpy as np
11
+ import numpy.typing as npt
11
12
 
12
13
  from pycontrails.core.models import ModelParams
13
14
  from pycontrails.models.aircraft_performance import AircraftPerformance
@@ -15,6 +16,39 @@ from pycontrails.models.emissions.emissions import EmissionsParams
15
16
  from pycontrails.models.humidity_scaling import HumidityScaling
16
17
 
17
18
 
19
+ def _radius_threshold_um() -> npt.NDArray[np.float32]:
20
+ return np.array([5.0, 9.5, 23.0, 190.0, 310.0], dtype=np.float32)
21
+
22
+
23
+ def _habit_distributions() -> npt.NDArray[np.float32]:
24
+ return np.array(
25
+ [
26
+ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
27
+ [0.0, 0.3, 0.0, 0.0, 0.0, 0.0, 0.7, 0.0],
28
+ [0.0, 0.3, 0.0, 0.0, 0.3, 0.0, 0.4, 0.0],
29
+ [0.0, 0.5, 0.0, 0.0, 0.15, 0.35, 0.0, 0.0],
30
+ [0.0, 0.45, 0.45, 0.1, 0.0, 0.0, 0.0, 0.0],
31
+ [0.0, 0.0, 0.0, 0.03, 0.97, 0.0, 0.0, 0.0],
32
+ ],
33
+ dtype=np.float32,
34
+ )
35
+
36
+
37
+ def _habits() -> npt.NDArray[np.str_]:
38
+ return np.array(
39
+ [
40
+ "Sphere",
41
+ "Solid column",
42
+ "Hollow column",
43
+ "Rough aggregate",
44
+ "Rosette-6",
45
+ "Plate",
46
+ "Droxtal",
47
+ "Myhre",
48
+ ]
49
+ )
50
+
51
+
18
52
  @dataclasses.dataclass
19
53
  class CocipParams(ModelParams):
20
54
  """Model parameters required by the CoCiP models."""
@@ -147,45 +181,18 @@ class CocipParams(ModelParams):
147
181
  #: Radius threshold for regime bins, [:math:`\mu m`]
148
182
  #: This is the row index label for ``habit_distributions``.
149
183
  #: See Table 2 in :cite:`schumannEffectiveRadiusIce2011`.
150
- radius_threshold_um: np.ndarray = dataclasses.field(
151
- default_factory=lambda: np.array([5.0, 9.5, 23.0, 190.0, 310.0], dtype=np.float32)
152
- )
184
+ radius_threshold_um: np.ndarray = dataclasses.field(default_factory=_radius_threshold_um)
153
185
 
154
186
  #: Particle habit (shape) types.
155
187
  #: This is the column index label for ``habit_distributions``.
156
188
  #: See Table 2 in :cite:`schumannEffectiveRadiusIce2011`.
157
- habits: np.ndarray = dataclasses.field(
158
- default_factory=lambda: np.array(
159
- [
160
- "Sphere",
161
- "Solid column",
162
- "Hollow column",
163
- "Rough aggregate",
164
- "Rosette-6",
165
- "Plate",
166
- "Droxtal",
167
- "Myhre",
168
- ]
169
- )
170
- )
189
+ habits: np.ndarray = dataclasses.field(default_factory=_habits)
171
190
 
172
191
  #: Mix of ice particle habits in each radius regime.
173
192
  #: Rows indexes are ``radius_threshold_um`` elements.
174
193
  #: Columns indexes are ``habits`` particle habit type.
175
194
  #: See Table 2 from :cite:`schumannEffectiveRadiusIce2011`.
176
- habit_distributions: np.ndarray = dataclasses.field(
177
- default_factory=lambda: np.array(
178
- [
179
- [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
180
- [0.0, 0.3, 0.0, 0.0, 0.0, 0.0, 0.7, 0.0],
181
- [0.0, 0.3, 0.0, 0.0, 0.3, 0.0, 0.4, 0.0],
182
- [0.0, 0.5, 0.0, 0.0, 0.15, 0.35, 0.0, 0.0],
183
- [0.0, 0.45, 0.45, 0.1, 0.0, 0.0, 0.0, 0.0],
184
- [0.0, 0.0, 0.0, 0.03, 0.97, 0.0, 0.0, 0.0],
185
- ],
186
- dtype=np.float32,
187
- )
188
- )
195
+ habit_distributions: np.ndarray = dataclasses.field(default_factory=_habit_distributions)
189
196
 
190
197
  #: Scale shortwave radiative forcing.
191
198
  #: Primarily used to support uncertainty estimation.
@@ -7,11 +7,12 @@ from dataclasses import asdict, dataclass
7
7
  from typing import Any, ClassVar
8
8
 
9
9
  import numpy as np
10
+ import numpy.typing as npt
10
11
  from scipy import stats
11
12
  from scipy.stats.distributions import rv_frozen
12
13
 
14
+ from pycontrails.models.cocip import cocip_params
13
15
  from pycontrails.models.cocip.cocip_params import CocipParams
14
- from pycontrails.models.humidity_scaling import ExponentialBoostHumidityScaling
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
@@ -32,7 +33,7 @@ class habit_dirichlet(rv_frozen):
32
33
  def __init__(self, C: float = 96.0):
33
34
  self.C = C
34
35
 
35
- def rvs(self, *args: Any, **kwds: Any) -> np.ndarray:
36
+ def rvs(self, *args: Any, **kwds: Any) -> npt.NDArray[np.float32]:
36
37
  """Generate sample set of habit distributions.
37
38
 
38
39
  Sampled using dirichlet distribution.
@@ -46,20 +47,25 @@ class habit_dirichlet(rv_frozen):
46
47
 
47
48
  Returns
48
49
  -------
49
- np.ndarray
50
+ npt.NDArray[np.float32]
50
51
  Sampled habit weight distribution with the same shape
51
52
  as :attr:`CocipParams().habit_distributions`
52
53
  """
53
- if args or ("size" in kwds and kwds["size"] is not None and kwds["size"] > 1):
54
+ if args or (kwds.get("size") is not None and kwds["size"] > 1):
54
55
  raise ValueError("habit_dirichlet distribution only supports creating one rv at a time")
55
56
 
56
- default_distributions = CocipParams().habit_distributions
57
- alpha_i = [0.5 + self.C * G_i for G_i in default_distributions]
58
- distr = [stats.dirichlet(a) for a in alpha_i]
57
+ default_distributions = cocip_params._habit_distributions()
58
+ alpha_i = 0.5 + self.C * default_distributions
59
+
60
+ # In the first distribution, we assume all ice particles are droxtals
61
+ # There is no way to quantify the uncertainty in this assumption
62
+ # Consequently, we leave the first distribution in default_distributions
63
+ # alone, and only perturb the rest
64
+ distr_list = [stats.dirichlet(a) for a in alpha_i[1:]]
59
65
 
60
66
  habit_weights = default_distributions.copy()
61
- for i in range(1, habit_weights.shape[0]):
62
- habit_weights[i] = distr[i].rvs(**kwds)
67
+ for i, distr in enumerate(distr_list, start=1):
68
+ habit_weights[i] = distr.rvs(**kwds)
63
69
 
64
70
  return habit_weights
65
71
 
@@ -81,17 +87,19 @@ class CocipUncertaintyParams(CocipParams):
81
87
  >>> import scipy.stats
82
88
  >>> from pycontrails.models.cocip import CocipUncertaintyParams
83
89
 
84
- # Override the `rhi_adj` field from `CocipParams` with a value in [0.97, 0.99]
85
- >>> distr = scipy.stats.uniform(loc=0.97, scale=0.02)
86
- >>> params = CocipUncertaintyParams(seed=123, rhi_adj_uncertainty=distr)
87
- >>> params.rhi_adj
88
- 0.98364703
90
+ >>> # Override the 'initial_wake_vortex_depth' field from
91
+ >>> # CocipParams with a uniform value in [0.4, 0.6]
92
+ >>> distr = scipy.stats.uniform(loc=0.4, scale=0.2)
93
+ >>> params = CocipUncertaintyParams(seed=123, initial_wake_vortex_depth_uncertainty=distr)
94
+ >>> params.initial_wake_vortex_depth
95
+ 0.41076420
89
96
 
90
- # Once seeded, calling the class again gives a new value
91
- >>> params = CocipUncertaintyParams(rhi_adj_uncertainty=distr)
92
- >>> params.rhi_adj
93
- 0.98310570
97
+ >>> # Once seeded, calling the class again gives a new value
98
+ >>> params = CocipUncertaintyParams(initial_wake_vortex_depth=distr)
99
+ >>> params.initial_wake_vortex_depth
100
+ 0.43526372
94
101
 
102
+ >>> # To retain the default value, set the uncertainty to None
95
103
  >>> params = CocipUncertaintyParams(rf_lw_enhancement_factor_uncertainty=None)
96
104
  >>> params.rf_lw_enhancement_factor
97
105
  1.0
@@ -106,23 +114,6 @@ class CocipUncertaintyParams(CocipParams):
106
114
  #: Reseed the random generator defined in ``__post_init__``
107
115
  seed: int | None = None
108
116
 
109
- # This might need fixing!
110
- # Whenever we overhaul / redo an uncertainty analysis, we may need to change
111
- # how it interacts with a humidity scaler
112
- humidity_scaling: ExponentialBoostHumidityScaling = ExponentialBoostHumidityScaling()
113
-
114
- #: Parameters for specific humidity and RHi enhancement
115
- #: This assumes Cocip.Params.humidity_scaling is an
116
- #: :class:`ExponentialBoostHumidityScaling`` instance.
117
- rhi_adj_uncertainty: rv_frozen | None = stats.norm(
118
- loc=ExponentialBoostHumidityScaling.default_params.rhi_adj, scale=0.1
119
- )
120
- rhi_boost_exponent_uncertainty: rv_frozen = stats.triang(
121
- loc=1.0,
122
- c=ExponentialBoostHumidityScaling.default_params.rhi_boost_exponent - 1.0,
123
- scale=1.0,
124
- )
125
-
126
117
  #: Schumann takes ``wind_shear_enhancement_exponent`` = 0.5 and discusses the case of 0 and 2/3
127
118
  #: as possibilities.
128
119
  #: With a value of 0, wind shear is not enhanced.
@@ -172,7 +163,7 @@ class CocipUncertaintyParams(CocipParams):
172
163
  #: where :math:`\text{G}_{i}` is the approximate habit weight distributions
173
164
  #: defined in :attr:`CocipParams().habit_distributions`.
174
165
  #: Higher values of :math:`\text{C}` correspond to higher confidence in initial estimates.
175
- habit_distributions_uncertainty: rv_frozen | None = habit_dirichlet(C=96)
166
+ habit_distributions_uncertainty: rv_frozen | None = habit_dirichlet(C=96.0)
176
167
 
177
168
  def __post_init__(self) -> None:
178
169
  """Override values of model parameters according to ranges."""
@@ -221,7 +212,7 @@ class CocipUncertaintyParams(CocipParams):
221
212
 
222
213
  return out
223
214
 
224
- def rvs(self, size: None | int = None) -> dict[str, float | np.ndarray]:
215
+ def rvs(self, size: None | int = None) -> dict[str, float | npt.NDArray[np.float_]]:
225
216
  """Call each distribution's `rvs` method to generate random parameters.
226
217
 
227
218
  Seed calls to `rvs` with class variable `rng`.
@@ -233,7 +224,7 @@ class CocipUncertaintyParams(CocipParams):
233
224
 
234
225
  Returns
235
226
  -------
236
- dict[str, float | np.ndarray]
227
+ dict[str, float | npt.NDArray[np.float_]]
237
228
  Dictionary of random parameters. Dictionary keys consists of names of parameters in
238
229
  `CocipParams` to be overridden by random value.
239
230
 
@@ -244,26 +235,24 @@ class CocipUncertaintyParams(CocipParams):
244
235
  >>> params = CocipUncertaintyParams(seed=456)
245
236
  >>> pprint(params.rvs())
246
237
  {'habit_distributions': array([[0.0000000e+00, 0.0000000e+00, 0.0000000e+00, 0.0000000e+00,
247
- 0.0000000e+00, 0.0000000e+00, 1.0000000e+00, 0.0000000e+00],
248
- [3.6062175e-04, 2.9157758e-01, 6.5901526e-04, 1.3415117e-03,
249
- 5.0444747e-03, 2.6956137e-02, 6.7330402e-01, 7.5663364e-04],
250
- [2.3834489e-03, 3.6896354e-01, 1.3977488e-03, 4.7844747e-04,
251
- 3.0474603e-01, 4.8765671e-03, 3.1715181e-01, 2.4031256e-06],
252
- [4.0944587e-03, 5.1772928e-01, 4.8383059e-05, 1.3166944e-03,
253
- 1.0002965e-01, 3.7468293e-01, 3.9641498e-04, 1.7021718e-03],
254
- [1.5192166e-05, 3.8423434e-01, 5.0536448e-01, 9.4547205e-02,
255
- 1.1378267e-03, 3.3812344e-03, 4.6305601e-03, 6.6891452e-03],
256
- [1.1620003e-03, 2.3161094e-03, 1.6705607e-04, 2.0091899e-02,
257
- 9.7001791e-01, 1.5312615e-03, 3.9347797e-03, 7.7895907e-04]],
258
- dtype=float32),
259
- 'initial_wake_vortex_depth': 0.4752143043559352,
260
- 'nvpm_ei_n_enhancement_factor': 1.0094726146454185,
261
- 'rf_lw_enhancement_factor': 0.9718354129386728,
262
- 'rf_sw_enhancement_factor': 1.016450567639041,
263
- 'rhi_adj': 1.0293074966694602,
264
- 'rhi_boost_exponent': 1.3308280817638443,
265
- 'sedimentation_impact_factor': 0.6433086271594898,
266
- 'wind_shear_enhancement_exponent': 0.31735944665388505}
238
+ 0.0000000e+00, 0.0000000e+00, 1.0000000e+00, 0.0000000e+00],
239
+ [1.5554131e-02, 2.1363135e-01, 7.7715185e-03, 1.7690966e-02,
240
+ 3.1576434e-03, 3.2992734e-06, 7.4009895e-01, 2.0921326e-03],
241
+ [3.8193921e-03, 2.1235342e-01, 3.3554080e-04, 5.2846869e-04,
242
+ 3.1945917e-01, 4.8709914e-04, 4.6250960e-01, 5.0730183e-04],
243
+ [5.7327619e-04, 4.7781631e-01, 4.2596990e-03, 6.7235163e-04,
244
+ 1.4447135e-01, 3.6184600e-01, 1.0150939e-02, 2.1006212e-04],
245
+ [1.5397545e-02, 4.0522218e-01, 4.2781001e-01, 1.4331797e-01,
246
+ 7.1088417e-04, 9.4511814e-04, 3.3900745e-03, 3.2062260e-03],
247
+ [7.9063961e-04, 3.0336906e-03, 7.7571563e-04, 2.0577813e-02,
248
+ 9.4205803e-01, 4.3379897e-03, 3.6786550e-03, 2.4747452e-02]],
249
+ dtype=float32),
250
+ 'initial_wake_vortex_depth': 0.39805019708566847,
251
+ 'nvpm_ei_n_enhancement_factor': 0.9371878437312526,
252
+ 'rf_lw_enhancement_factor': 1.1017491252832377,
253
+ 'rf_sw_enhancement_factor': 0.99721639115012,
254
+ 'sedimentation_impact_factor': 0.5071779847244678,
255
+ 'wind_shear_enhancement_exponent': 0.34100931239701004}
267
256
  """
268
257
  return {
269
258
  param: distr.rvs(size=size, random_state=self.rng)