cloudnetpy 1.70.2__py3-none-any.whl → 1.71.1__py3-none-any.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.
@@ -588,5 +588,10 @@ ATTRIBUTES = {
588
588
  "lwp": PlotMeta(
589
589
  zero_line=True,
590
590
  ),
591
+ "epsilon": PlotMeta(
592
+ cmap="inferno",
593
+ plot_range=(1e-7, 1e-1),
594
+ log_scale=True,
595
+ ),
591
596
  },
592
597
  }
@@ -0,0 +1,202 @@
1
+ from os import PathLike
2
+ from pathlib import Path
3
+
4
+ import doppy
5
+ import doppy.netcdf
6
+ import netCDF4
7
+ import numpy as np
8
+ import numpy.typing as npt
9
+ import scipy.constants
10
+ from doppy.product.turbulence import HorizontalWind, Options, Turbulence, VerticalWind
11
+ from scipy.interpolate import LinearNDInterpolator, NearestNDInterpolator
12
+
13
+ import cloudnetpy
14
+ from cloudnetpy.output import copy_variables
15
+ from cloudnetpy.utils import get_time, get_uuid
16
+
17
+
18
+ def generate_epsilon_from_lidar(
19
+ doppler_lidar_file: str | PathLike,
20
+ doppler_lidar_wind_file: str | PathLike,
21
+ output_file: str | PathLike,
22
+ uuid: str | None,
23
+ ):
24
+ sliding_window_in_seconds = 3 * 60
25
+ uuid = uuid if uuid is not None else get_uuid()
26
+ opts = _get_options(doppler_lidar_file)
27
+ opts.period = sliding_window_in_seconds
28
+ vert = _vertical_wind_from_doppler_lidar_file(doppler_lidar_file)
29
+ hori = _horizontal_wind_from_doppler_lidar_file(doppler_lidar_wind_file)
30
+ turb = Turbulence.from_winds(vert, hori, opts)
31
+
32
+ with (
33
+ netCDF4.Dataset(Path(doppler_lidar_file), "r") as nc_src,
34
+ doppy.netcdf.Dataset(Path(output_file), format="NETCDF4_CLASSIC") as nc,
35
+ ):
36
+ nc.add_dimension("time")
37
+ nc.add_dimension("height", size=len(turb.height))
38
+ nc.add_time(
39
+ name="time",
40
+ dimensions=("time",),
41
+ standard_name="time",
42
+ long_name="Time UTC",
43
+ data=turb.time,
44
+ dtype="f8",
45
+ )
46
+ nc.add_variable(
47
+ name="height",
48
+ dimensions=("height",),
49
+ units="m",
50
+ data=turb.height,
51
+ standard_name=nc_src["height"].standard_name,
52
+ long_name=nc_src["height"].long_name,
53
+ dtype="f4",
54
+ )
55
+ nc.add_variable(
56
+ name="epsilon",
57
+ dimensions=("time", "height"),
58
+ units="m2 s-3",
59
+ data=turb.turbulent_kinetic_energy_dissipation_rate,
60
+ mask=turb.mask,
61
+ dtype="f4",
62
+ long_name="Dissipation rate of turbulent kinetic energy",
63
+ )
64
+ nc.add_scalar_variable(
65
+ name="ray_accumulation_time",
66
+ units="s",
67
+ long_name="Ray accumulation time",
68
+ data=opts.ray_accumulation_time,
69
+ dtype="f4",
70
+ )
71
+ nc.add_scalar_variable(
72
+ name="rolling_window_period",
73
+ units="s",
74
+ long_name="Rolling window period",
75
+ data=opts.period,
76
+ dtype="f4",
77
+ )
78
+
79
+ nc.add_attribute("file_uuid", uuid)
80
+ nc.add_attribute("cloudnet_file_type", "epsilon-lidar")
81
+ nc.add_attribute("doppy_version", doppy.__version__)
82
+ nc.add_attribute("cloudnetpy_version", cloudnetpy.__version__)
83
+ nc.add_attribute(
84
+ "title",
85
+ "Dissipation rate of turbulent kinetic energy (lidar) "
86
+ f"from {nc_src.location}",
87
+ )
88
+
89
+ copy_attributes_from_src(doppler_lidar_file, output_file)
90
+
91
+ with (
92
+ netCDF4.Dataset(output_file, "r+") as nc_out,
93
+ netCDF4.Dataset(doppler_lidar_file, "r") as nc_src_stare,
94
+ netCDF4.Dataset(doppler_lidar_wind_file, "r") as nc_src_wind,
95
+ ):
96
+ copy_variables(
97
+ nc_src_stare, nc_out, ("latitude", "longitude", "altitude", "source")
98
+ )
99
+ nc_out.source_file_uuids = f"{nc_src_stare.file_uuid}, {nc_src_wind.file_uuid}"
100
+ sources = {nc_src_stare.source, nc_src_wind.source}
101
+ nc_out.source = ", ".join(sources)
102
+ history = (
103
+ f"{get_time()} - epsilon-lidar file created using doppy "
104
+ f"v{doppy.__version__} and cloudnetpy v{cloudnetpy.__version__}\n"
105
+ f"{nc_src_stare.history}\n"
106
+ f"{nc_src_wind.history}"
107
+ )
108
+ history = "\n".join(
109
+ line.strip() for line in history.splitlines() if line.strip()
110
+ )
111
+ nc_out.history = history
112
+ nc_out.references = "https://doi.org/10.1175/2010JTECHA1455.1"
113
+ return uuid
114
+
115
+
116
+ def copy_attributes_from_src(src: str | PathLike, trg: str | PathLike) -> None:
117
+ with netCDF4.Dataset(src, "r") as nc_src, netCDF4.Dataset(trg, "a") as nc_trg:
118
+ for attr in ("year", "month", "day", "location", "Conventions"):
119
+ nc_trg.setncattr(attr, nc_src.getncattr(attr))
120
+
121
+
122
+ def _horizontal_wind_from_doppler_lidar_file(
123
+ doppler_lidar_wind_file: str | PathLike,
124
+ ) -> HorizontalWind:
125
+ with netCDF4.Dataset(doppler_lidar_wind_file, "r") as nc:
126
+ time = _datetime64_from_nc_var(nc["time"])
127
+ height = np.array(nc["height"][:].data, dtype=np.float64)
128
+ uwind = np.array(nc["uwind"][:].data, dtype=np.float64)
129
+ vwind = np.array(nc["vwind"][:].data, dtype=np.float64)
130
+ umask = np.array(nc["uwind"][:].mask, dtype=np.bool_)
131
+ vmask = np.array(nc["vwind"][:].mask, dtype=np.bool_)
132
+ V = np.sqrt(uwind**2 + vwind**2)
133
+ mask = umask | vmask
134
+ t = np.broadcast_to(time[:, None], mask.shape)[~mask]
135
+ h = np.broadcast_to(height[None, :], mask.shape)[~mask]
136
+ interp_linear = LinearNDInterpolator(list(zip(t, h, strict=False)), V[~mask])
137
+ interp_nearest = NearestNDInterpolator(list(zip(t, h, strict=False)), V[~mask])
138
+ T, H = np.meshgrid(time, height, indexing="ij")
139
+ V_linear = interp_linear(T, H)
140
+ V_nearest = interp_nearest(T, H)
141
+ isnan = np.isnan(V_linear)
142
+ V_interp = V_linear
143
+ V_interp[isnan] = V_nearest[isnan]
144
+ if np.isnan(V_interp).any():
145
+ msg = "Unexpected nans"
146
+ raise ValueError(msg)
147
+ return HorizontalWind(time=time, height=height, V=V_interp)
148
+
149
+
150
+ def _get_options(doppler_lidar_file: str | PathLike) -> Options:
151
+ with netCDF4.Dataset(doppler_lidar_file, "r") as nc:
152
+ if "ray_accumulation_time" in nc.variables:
153
+ return Options(ray_accumulation_time=nc["ray_accumulation_time"][:])
154
+ if "pulses_per_ray" in nc.variables:
155
+ prf = _infer_pulse_repetition_frequency(
156
+ np.array(nc["range"][:].data, dtype=np.float64)
157
+ )
158
+ return Options(ray_accumulation_time=float(nc["pulses_per_ray"][:] / prf))
159
+ msg = "Missing ray info"
160
+ raise ValueError(msg)
161
+
162
+
163
+ def _infer_pulse_repetition_frequency(range_: npt.NDArray[np.float64]):
164
+ c = scipy.constants.c
165
+ dist = range_.max() - range_.min()
166
+ round_trip_time = 2 * dist / c
167
+
168
+ T_LOW = 1 / 10_000 # Halo XR instruments operate on lower frequency
169
+ T_HIGH = 1 / 15_000 # Rest should operate on higher frequency
170
+ if round_trip_time / T_HIGH < 1:
171
+ return 15e3
172
+ if round_trip_time / T_LOW < 1:
173
+ return 10e3
174
+ msg = f"Suspiciously large range ({dist}m). " "Cannot infer pulse repetition rate"
175
+ raise ValueError(msg)
176
+
177
+
178
+ def _vertical_wind_from_doppler_lidar_file(
179
+ doppler_lidar_file: str | PathLike,
180
+ ) -> VerticalWind:
181
+ with netCDF4.Dataset(doppler_lidar_file, "r") as nc:
182
+ time = _datetime64_from_nc_var(nc["time"])
183
+ height = np.array(nc["height"][:].data, dtype=np.float64)
184
+ w = np.array(nc["v"][:].data, dtype=np.float64)
185
+ mask = np.array(nc["v"][:].mask, dtype=np.bool_)
186
+ if isinstance(mask, np.ndarray) and mask.any():
187
+ w[mask] = np.nan
188
+
189
+ return VerticalWind(time=time, height=height, w=w, mask=mask)
190
+
191
+
192
+ def _datetime64_from_nc_var(var: netCDF4.Variable) -> npt.NDArray[np.datetime64]:
193
+ return np.array(
194
+ netCDF4.num2date(
195
+ var[:].data,
196
+ units=var.units,
197
+ calendar=var.calendar,
198
+ only_use_cftime_datetimes=False,
199
+ only_use_python_datetimes=True,
200
+ ),
201
+ dtype="datetime64[us]",
202
+ )
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
- MINOR = 70
3
- PATCH = 2
2
+ MINOR = 71
3
+ PATCH = 1
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudnetpy
3
- Version: 1.70.2
3
+ Version: 1.71.1
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -42,6 +42,7 @@ Requires-Python: >=3.10
42
42
  Description-Content-Type: text/markdown
43
43
  License-File: LICENSE
44
44
  Requires-Dist: cloudnetpy_qc>=1.15.0
45
+ Requires-Dist: doppy>=0.5.0
45
46
  Requires-Dist: matplotlib
46
47
  Requires-Dist: mwrpy>=1.2.0
47
48
  Requires-Dist: netCDF4
@@ -9,7 +9,7 @@ cloudnetpy/metadata.py,sha256=BDEpgwZ58PHznc1gi11gtNNV4kFiMAmlHnF4huTy7nw,5982
9
9
  cloudnetpy/output.py,sha256=lq4YSeMT_d-j4rlQkKm9KIZ8boupTBBBKV1eUawpmCI,15672
10
10
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  cloudnetpy/utils.py,sha256=U0iMIKPiKLrLVAfs_u9pPuoWYW1RJHcM8dbLF9a4yIA,29796
12
- cloudnetpy/version.py,sha256=ZJmkQTWA_9Ao_G9kBNB4TdpjcMG9CP6xNuk_jl8SpBw,72
12
+ cloudnetpy/version.py,sha256=Jpj6HUmEXjg2ZHK10S2ho3h_9Khi4cQ_qHcHcBrM7AU,72
13
13
  cloudnetpy/categorize/__init__.py,sha256=s-SJaysvVpVVo5kidiruWQO6p3gv2TXwY1wEHYO5D6I,44
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=RcmbKxm2COkE7WEya0mK3yX5rzUbrewRVh3ekm01RtM,10598
15
15
  cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
@@ -101,7 +101,7 @@ cloudnetpy/model_evaluation/tests/unit/test_plotting.py,sha256=h9V8JKmrO4v9bOvv-
101
101
  cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=Ra3r4V0qbqkpDuaTYvEIbaasl0nZ5gmTLR4eGC0glBQ,9724
102
102
  cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=Ia_VrLdV2NstX5gbx_3AZTOAlrgLAy_xFZ8fHYVX0xI,3817
103
103
  cloudnetpy/plotting/__init__.py,sha256=lg9Smn4BI0dVBgnDLC3JVJ4GmwoSnO-qoSd4ApvwV6Y,107
104
- cloudnetpy/plotting/plot_meta.py,sha256=RTGpvXXcCUawLasAeiAaCvNxfeUyf9Cd_1nG77kxv5A,17257
104
+ cloudnetpy/plotting/plot_meta.py,sha256=qfyZJNis937uM-NJseer8i4FO7I_v5jhQPyFl5Uszi8,17390
105
105
  cloudnetpy/plotting/plotting.py,sha256=Hi_n3TjzqR2KjZgalFHGxsb7jTsqub9nWzZyscfk6xA,38385
106
106
  cloudnetpy/products/__init__.py,sha256=2hRb5HG9hNrxH1if5laJkLeFeaZCd5W1q3hh4ewsX0E,273
107
107
  cloudnetpy/products/classification.py,sha256=KwAiBSgFwDqhM114NIgYiUjj8HoYc7gAlc8E1QgcSig,8207
@@ -109,6 +109,7 @@ cloudnetpy/products/der.py,sha256=soypE7uSEP4uHUCCQVEhyXsKY6e9mzV9B_2S5GUizqk,12
109
109
  cloudnetpy/products/drizzle.py,sha256=58C9Mo6oRXR8KpbVPghbJvHvFX9GfS3xUp058pbf0qw,10804
110
110
  cloudnetpy/products/drizzle_error.py,sha256=4GwlHRtNbk9ks7bGtXCco-wXbcDOKeAQwKmbhzut6Qk,6132
111
111
  cloudnetpy/products/drizzle_tools.py,sha256=HLxUQ89mFNo6IIe6Cj3ZH-TPkJdpMxKCOt4cOOmcLs0,11002
112
+ cloudnetpy/products/epsilon.py,sha256=fthijjUAa7bEhoObRiTOGr4_uRgCpXSfqxIOPD2NGT4,7617
112
113
  cloudnetpy/products/ier.py,sha256=70jyYrhO-kAHuQ3CpDVcyKKuHT3c9Eby6ADHHlPjAFY,5986
113
114
  cloudnetpy/products/iwc.py,sha256=FQtYFYnMNuDnjuK1ee3zOfzPtKsoWH95CXtEz1iplZI,9462
114
115
  cloudnetpy/products/lwc.py,sha256=sl6Al2tuH3KkCBrPbWTmuz3jlD5UQJ4D6qBsn1tt2CQ,18962
@@ -116,9 +117,9 @@ cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe5
116
117
  cloudnetpy/products/mwr_tools.py,sha256=rd7UC67O4fsIE5SaHVZ4qWvUJTj41ZGwgQWPwZzOM14,5377
117
118
  cloudnetpy/products/product_tools.py,sha256=uu4l6reuGbPcW3TgttbaSrqIKbyYGhBVTdnC7opKvmg,11101
118
119
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
119
- cloudnetpy-1.70.2.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
120
- cloudnetpy-1.70.2.dist-info/METADATA,sha256=3uZXezdrr0wokIJ1UEEAHOb0olG14cgknYFO14RcKlw,5844
121
- cloudnetpy-1.70.2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
122
- cloudnetpy-1.70.2.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
123
- cloudnetpy-1.70.2.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
124
- cloudnetpy-1.70.2.dist-info/RECORD,,
120
+ cloudnetpy-1.71.1.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
121
+ cloudnetpy-1.71.1.dist-info/METADATA,sha256=-4IIIVCrDs5UmXIrXQmlWzZ-Q9sV5SVodXEjgh_kdqc,5872
122
+ cloudnetpy-1.71.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
123
+ cloudnetpy-1.71.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
124
+ cloudnetpy-1.71.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
125
+ cloudnetpy-1.71.1.dist-info/RECORD,,