Example of a wind retrieval in a tornado over Sydney#
This shows an example of how to retrieve winds from 4 radars over Sydney.
We use smoothing to decrease the magnitude of the updraft in the region of the mesocyclone. The reduction of noise also helps the solution converge much faster since the cost function is smoother and therefore less susecptible to find a local minimum that is in noise.
The observational constraint is reduced to 0.01 from the usual 1 because we are factoring in many more data points as we are using 4 radars instead of the two in the Darwin example.
This example uses pooch to download the data files.
import pydda
import matplotlib.pyplot as plt
import numpy as np
grid1_path = pydda.tests.get_sample_file("grid1_sydney.nc")
grid2_path = pydda.tests.get_sample_file("grid2_sydney.nc")
grid3_path = pydda.tests.get_sample_file("grid3_sydney.nc")
grid4_path = pydda.tests.get_sample_file("grid4_sydney.nc")
grid1 = pydda.io.read_grid(grid1_path)
grid2 = pydda.io.read_grid(grid2_path)
grid3 = pydda.io.read_grid(grid3_path)
grid4 = pydda.io.read_grid(grid4_path)
# Set initialization and do retrieval
grid1 = pydda.initialization.make_constant_wind_field(grid1, vel_field="VRADH_corr")
new_grids, _ = pydda.retrieval.get_dd_wind_field(
[grid1, grid2, grid3, grid4],
Co=1e-2,
Cm=256.0,
Cx=10,
Cy=10,
Cz=10,
vel_name="VRADH_corr",
refl_field="DBZH",
mask_outside_opt=True,
wind_tol=0.5,
max_iterations=200,
engine="scipy",
)
# Make a neat plot
fig = plt.figure(figsize=(10, 7))
ax = pydda.vis.plot_horiz_xsection_quiver_map(
new_grids,
background_field="DBZH",
level=3,
show_lobes=False,
bg_grid_no=3,
vmin=0,
vmax=60,
quiverkey_len=20.0,
w_vel_contours=[1.0, 3.0, 5.0, 10.0, 20.0],
quiver_spacing_x_km=2.0,
quiver_spacing_y_km=2.0,
quiverkey_loc="top",
colorbar_contour_flag=True,
cmap="ChaseSpectral",
)
ax.set_xticks(np.arange(150.5, 153, 0.1))
ax.set_yticks(np.arange(-36, -32.0, 0.1))
ax.set_xlim([151.0, 151.35])
ax.set_ylim([-34.15, -33.9])
plt.show()
## You are using the Python ARM Radar Toolkit (Py-ART), an open source
## library for working with weather radar data. Py-ART is partly supported
## by the U.S. Department of Energy Office of Science as part of
## the Atmospheric Radiation Measurement (ARM) User Facility.
##
## If you use this software to prepare a publication, please cite:
##
## JJ Helmus and SM Collis, JORS 2016, doi: 10.5334/jors.119
Welcome to PyDDA 2.5.0
If you are using PyDDA in your publications, please cite:
Jackson et al. (2020) Journal of Open Research Science
Detecting Jax...
Jax/JaxOpt are not installed on your system, unable to use Jax engine.
Detecting TensorFlow...
Unable to load both TensorFlow and tensorflow-probability. TensorFlow engine disabled.
No module named 'tensorflow'
False
Calculating weights for radars 0 and 1
Calculating weights for radars 0 and 2
Calculating weights for radars 0 and 3
Calculating weights for radars 1 and 0
Calculating weights for radars 1 and 2
Calculating weights for radars 1 and 3
Calculating weights for radars 2 and 0
Calculating weights for radars 2 and 1
Calculating weights for radars 2 and 3
Calculating weights for radars 3 and 0
Calculating weights for radars 3 and 1
Calculating weights for radars 3 and 2
Calculating weights for models...
Starting solver
rmsVR = 7.359750110231316
Total points: 399749
The max of w_init is 0.0
Total number of model points: 0
Nfeval | Jvel | Jmass | Jsmooth | Jbg | Jvort | Jmodel | Jpoint | Max w
0|4080.4252| 0.0000| 0.0000| 0.0000| 0.0000| 0.0000| 0.0000| 0.0000
The gradient of the cost functions is 0.1119091690376132
Nfeval | Jvel | Jmass | Jsmooth | Jbg | Jvort | Jmodel | Jpoint | Max w
10| 92.4335| 17.0200| 0.0001| 0.0000| 0.0000| 0.0000| 0.0000| 48.1870
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[1], line 17
13 grid4 = pydda.io.read_grid(grid4_path)
14
15 # Set initialization and do retrieval
16 grid1 = pydda.initialization.make_constant_wind_field(grid1, vel_field="VRADH_corr")
---> 17 new_grids, _ = pydda.retrieval.get_dd_wind_field(
18 [grid1, grid2, grid3, grid4],
19 Co=1e-2,
20 Cm=256.0,
File ~/work/PyDDA/PyDDA/pydda/retrieval/wind_retrieve.py:1534, in get_dd_wind_field(Grids, u_init, v_init, w_init, engine, **kwargs)
1527 w_init = new_grids[0]["w"].values.squeeze()
1529 if (
1530 engine.lower() == "scipy"
1531 or engine.lower() == "jax"
1532 or engine.lower() == "auglag"
1533 ):
-> 1534 return _get_dd_wind_field_scipy(
1535 new_grids, u_init, v_init, w_init, engine, **kwargs
1536 )
1537 elif engine.lower() == "tensorflow":
1538 return _get_dd_wind_field_tensorflow(
1539 new_grids, u_init, v_init, w_init, **kwargs
1540 )
File ~/work/PyDDA/PyDDA/pydda/retrieval/wind_retrieve.py:644, in _get_dd_wind_field_scipy(Grids, u_init, v_init, w_init, engine, points, vel_name, refl_field, u_back, v_back, z_back, frz, Co, Cm, Cx, Cy, Cz, Cb, Cv, Cmod, Cpoint, cvtol, gtol, Jveltol, Ut, Vt, low_pass_filter, mask_outside_opt, weights_obs, weights_model, weights_bg, max_iterations, mask_w_outside_opt, filter_type, filter_window, filter_order, leise_nstep, min_bca, max_bca, upper_bc, model_fields, output_cost_functions, roi, wind_tol, tolerance, const_boundary_cond, max_wind_mag, parallel)
642 parameters.print_out = False
643 if engine.lower() == "scipy":
--> 644 winds = fmin_l_bfgs_b(
645 J_function,
646 winds,
647 args=(parameters,),
648 maxiter=max_iterations,
649 pgtol=tolerance,
650 bounds=bounds,
651 fprime=grad_J,
652 callback=_vert_velocity_callback,
653 )
654 else:
656 def loss_and_gradient(x):
File /usr/share/miniconda/envs/pydda-docs/lib/python3.14/site-packages/scipy/optimize/_lbfgsb_py.py:259, in fmin_l_bfgs_b(func, x0, fprime, args, approx_grad, bounds, m, factr, pgtol, epsilon, maxfun, maxiter, callback, maxls)
249 callback = _wrap_callback(callback)
250 opts = {'maxcor': m,
251 'ftol': factr * np.finfo(float).eps,
252 'gtol': pgtol,
(...) 256 'callback': callback,
257 'maxls': maxls}
--> 259 res = _minimize_lbfgsb(fun, x0, args=args, jac=jac, bounds=bounds,
260 **opts)
261 d = {'grad': res['jac'],
262 'task': res['message'],
263 'funcalls': res['nfev'],
264 'nit': res['nit'],
265 'warnflag': res['status']}
266 f = res['fun']
File /usr/share/miniconda/envs/pydda-docs/lib/python3.14/site-packages/scipy/optimize/_lbfgsb_py.py:420, in _minimize_lbfgsb(fun, x0, args, jac, bounds, maxcor, ftol, gtol, eps, maxfun, maxiter, callback, maxls, finite_diff_rel_step, workers, **unknown_options)
412 _lbfgsb.setulb(m, x, low_bnd, upper_bnd, nbd, f, g, factr, pgtol, wa,
413 iwa, task, lsave, isave, dsave, maxls, ln_task)
415 if task[0] == 3:
416 # The minimization routine wants f and g at the current x.
417 # Note that interruptions due to maxfun are postponed
418 # until the completion of the current minimization iteration.
419 # Overwrite f and g:
--> 420 f, g = func_and_grad(x)
421 elif task[0] == 1:
422 # new iteration
423 n_iterations += 1
File /usr/share/miniconda/envs/pydda-docs/lib/python3.14/site-packages/scipy/optimize/_differentiable_functions.py:412, in ScalarFunction.fun_and_grad(self, x)
410 if not np.array_equal(x, self.x):
411 self._update_x(x)
--> 412 self._update_fun()
413 self._update_grad()
414 return self.f, self.g
File /usr/share/miniconda/envs/pydda-docs/lib/python3.14/site-packages/scipy/optimize/_differentiable_functions.py:362, in ScalarFunction._update_fun(self)
360 def _update_fun(self):
361 if not self.f_updated:
--> 362 fx = self._wrapped_fun(self.x)
363 self._nfev += 1
364 if fx < self._lowest_f:
File /usr/share/miniconda/envs/pydda-docs/lib/python3.14/site-packages/scipy/_lib/_util.py:545, in _ScalarFunctionWrapper.__call__(self, x)
542 def __call__(self, x):
543 # Send a copy because the user may overwrite it.
544 # The user of this class might want `x` to remain unchanged.
--> 545 fx = self.f(np.copy(x), *self.args)
546 self.nfev += 1
548 # Make sure the function returns a true scalar
File ~/work/PyDDA/PyDDA/pydda/cost_functions/cost_functions.py:207, in J_function(winds, parameters)
204 Jmass = 0
206 if parameters.Cx > 0 or parameters.Cy > 0 or parameters.Cz > 0:
--> 207 Jsmooth = _cost_functions_numpy.calculate_smoothness_cost(
208 winds[0],
209 winds[1],
210 winds[2],
211 parameters.dx,
212 parameters.dy,
213 parameters.dz,
214 Cx=parameters.Cx,
215 Cy=parameters.Cy,
216 Cz=parameters.Cz,
217 )
218 else:
219 Jsmooth = 0
File ~/work/PyDDA/PyDDA/pydda/cost_functions/_cost_functions_numpy.py:217, in calculate_smoothness_cost(u, v, w, dx, dy, dz, Cx, Cy, Cz)
215 dvdz = np.gradient(v, dz, axis=0)
216 dwdx = np.gradient(w, dx, axis=2)
--> 217 dwdy = np.gradient(w, dy, axis=1)
218 dwdz = np.gradient(w, dz, axis=0)
220 x_term = (
221 Cx
222 * (
(...) 227 ** 2
228 )
File /usr/share/miniconda/envs/pydda-docs/lib/python3.14/site-packages/numpy/lib/_function_base_impl.py:1324, in gradient(f, axis, edge_order, *varargs)
1321 slice4[axis] = slice(2, None)
1323 if uniform_spacing:
-> 1324 out[tuple(slice1)] = (f[tuple(slice4)] - f[tuple(slice2)]) / (2. * ax_dx)
1325 else:
1326 dx1 = ax_dx[0:-1]
KeyboardInterrupt: