Only apply the cyclostrophic deprecation shim when the argument is passed - #1315
Only apply the cyclostrophic deprecation shim when the argument is passed#1315dylanpulver wants to merge 1 commit into
Conversation
compute_angular_windspeeds declares `cyclostrophic: Optional[bool] = False` but guards its deprecation shim with `if cyclostrophic is not None`. Because `False is not None`, the shim ran on every call, including the internal one from compute_windfields_sparse which never passes the argument. That raised a DeprecationWarning for an argument nobody passed, and overwrote the caller's own `model_kwargs["cyclostrophic"]` with False, making the setting unreachable through the very route the deprecation message recommends. Use None as the sentinel default, and copy model_kwargs so the shim cannot mutate the caller's dict. Existing default behaviour is unchanged: without the injected False, each model falls back to its own signature default (False for H1980, H08 and ER11, True for H10), which is what TropCyclone.from_tracks already documents. Fixes CLIMADA-project#1209
aa0aeb5 to
a1b74d1
Compare
peanutfun
left a comment
There was a problem hiding this comment.
@dylanpulver Thank you for your contribution! I would appreciate if you used your advertised agentic AI systems to shorten your PR description to only include relevant information. It took me some time to figure out what this PR was actually about.
While your changes are obvious and solid, the tests are not. They re-define data that is already present in other tests. Please make sure testing and reference data is only defined once, and then "shared" between tests that are using them. See my comments below.
| def _er_2011_setup(self): | ||
| """Track/centroid setup shared by the ``compute_angular_windspeeds`` tests. | ||
|
|
||
| Only the second track node is asserted on, since ``compute_angular_windspeeds`` | ||
| zeroes out the first one. | ||
| """ | ||
| d_centr = KM_TO_M * np.array( | ||
| [[35, 70, 75, 220], [30, 150, 1000, 300]], dtype=float | ||
| ) | ||
| si_track = xr.Dataset( | ||
| { | ||
| "rad": ("time", KM_TO_M * np.array([75.0, 40.0])), | ||
| "vmax": ("time", [35.0, 40.0]), | ||
| "lat": ("time", [20.0, 27.0]), | ||
| "cp": ("time", [4.98665369e-05, 6.61918149e-05]), | ||
| } | ||
| ) | ||
| mask = np.array( | ||
| [[True, True, True, True], [True, False, True, True]], dtype=bool | ||
| ) | ||
| return si_track, d_centr, mask |
There was a problem hiding this comment.
This is taken from the test_er_2011_pass test. Please share this information between the tests instead of re-defining it.
| # guard against the setting being silently dropped, which yields the | ||
| # Coriolis-corrected profile instead | ||
| self.assertFalse( | ||
| np.allclose(v_ang_norm[1], self.ER11_NODE1_WITH_CORIOLIS, atol=1e-4) | ||
| ) |
There was a problem hiding this comment.
This is trivial. If the data is equal to ER11_NODE1_CYCLOSTROPHIC, it cannot be equal to ER11_NODE1_WITH_CORIOLIS
| # guard against the setting being silently dropped, which yields the | |
| # Coriolis-corrected profile instead | |
| self.assertFalse( | |
| np.allclose(v_ang_norm[1], self.ER11_NODE1_WITH_CORIOLIS, atol=1e-4) | |
| ) |
| ) | ||
|
|
||
| def test_compute_angular_windspeeds_no_spurious_deprecation(self): | ||
| """No DeprecationWarning unless the deprecated argument is actually passed.""" |
There was a problem hiding this comment.
This only checks if there is no deprecation warning. The check for the warning when passing cyclostrophic is missing. (This is what I read the test is intended to do?)
| # Reference values for the second track node (r_max = 40 km, v_max = 40 m/s, | ||
| # f = 6.61918149e-05 1/s) from equation (36) of Emanuel and Rotunno 2011, | ||
| # M = M_max * 2 * (r/r_max)^2 / (1 + (r/r_max)^2) and v = M / r. | ||
| # Cyclostrophic: M_max = r_max * v_max = 1.6e6 m^2/s, so at r = 30 km, | ||
| # v = 1.6e6 * 2 * 0.5625 / 1.5625 / 30e3 = 38.4 m/s. | ||
| ER11_NODE1_CYCLOSTROPHIC = [38.4, 0.0, 3.194888178913738, 10.480349344978167] | ||
| # Non-cyclostrophic: M_max additionally contains 0.5 * f * r_max^2 = 52953.45 m^2/s. | ||
| ER11_NODE1_WITH_CORIOLIS = [39.670883, 0.0, 3.300626, 10.827206] |
There was a problem hiding this comment.
Reference values are also already defined in the test_er_2011_pass. Please avoid re-definition and use a single data source.
Changes proposed in this PR:
cyclostrophicargument ofcompute_angular_windspeedsaNonesentinel default instead ofFalse, so the deprecation shim only fires when a caller actually passes it.model_kwargsbefore the shim writes into it, so a caller's dict is never modified in place.This PR fixes #1209
compute_angular_windspeedsdeclarescyclostrophic: Optional[bool] = Falsebut guards the shim withif cyclostrophic is not None:. SinceFalse is not None, the shim runs on every call — including the internal one fromcompute_windfields_sparse, which never passes the argument. So aDeprecationWarningis raised for an argument nobody passed (the 44 occurrences in #1209), andmodel_kwargs["cyclostrophic"] = cyclostrophicthen overwrites the caller's own setting withFalse. That makescyclostrophicunreachable viamodel_kwargs, the exact route the deprecation message points users to.Measured on
developwith the ER11 setup fromtest_er_2011_pass(r_max = 40 km, v_max = 40 m/s, f = 6.61918149e-05 1/s), at r = 30 km:_stat_er_2011(..., cyclostrophic=True)_stat_er_2011(..., cyclostrophic=False)compute_angular_windspeeds(..., model_kwargs={"cyclostrophic": True})The caller's dict comes back as
{'cyclostrophic': False}. End to end,TropCyclone.from_tracks(tracks, centroids, model="ER11", model_kwargs={"cyclostrophic": True})returns an intensity matrix bit-identical to the default call.38.4 m/s is equation (36) of Emanuel and Rotunno (2011),
M = M_max · 2(r/r_max)² / (1 + (r/r_max)²)withM_max = r_max·v_max = 1.6e6 m²/s:1.6e6 · 2 · 0.5625 / 1.5625 / 30e3 = 38.4. The non-cyclostrophic branch adds0.5·f·r_max² = 52953.45 m²/stoM_max, giving 39.670883.No change to existing default behaviour: without the injected
cyclostrophic=False, each model falls back to its own signature default —Falsefor H1980, H08 and ER11,Truefor H10 — which is whatTropCyclone.from_tracksalready documents. H10 ignores the flag either way, so its spurious "this setting is ignored" log line also stops. No stored reference value changes; numbers move only for callers who explicitly asked forcyclostrophicand were previously ignored.Introduced in cb97195. Existing tests call
_stat_holland_1980and_stat_er_2011directly, so nothing covered the dispatcher's kwarg forwarding. Three tests added toTestWindfieldHelpers, asserting literals derived from Emanuel and Rotunno (2011) eq. (36) rather than from the code.test_trop_cyclone_windfields.pygoes 9 passed → 12 passed, and all three fail against the unpatched file. Acrosstest_trop_cyclone.py+test_trop_cyclone_windfields.pythe count goes 18 passed → 21 passed with no regression, and thecyclostrophicdeprecation warnings raised by that scope drop from 43 to 0.Two checklist boxes are left unticked deliberately.
test_cross_antimeridianwasdeselected locally because it downloads the coast-distance grid from Zenodo, which
returned 504 here — CI caches that data, so it should run, but I have not proven it
green either way.
pylint --rcfile=.pylintrcreports 9.52/10 on both changed fileswith every message pre-existing and structural, and none introduced by this diff.
Written with AI assistance; the wind speeds above are function output, and 38.4 m/s
is hand-derived from the published equation.
PR Author Checklist
develop)PR Reviewer Checklist