@@ -58,9 +58,6 @@ class ArgoFloat:
5858# SECTION: Kernels
5959# =====================================================
6060
61- # TODO: need to add back in the shallow bathymetry checks (to phases 0 and 2?!)
62- # TODO: can this be refactored as well to a helper function?
63-
6461
6562def _argo_float_vertical_movement (particles , fieldset ):
6663 # Split particles based on their current cycle_phase
@@ -73,26 +70,21 @@ def _argo_float_vertical_movement(particles, fieldset):
7370 # Phase 0: Sinking with vertical_speed until depth is driftdepth
7471 ptcls0 .dz += particles .vertical_speed * ptcls0 .dt
7572 loc_bathy = fieldset .bathymetry .eval (ptcls0 .time , ptcls0 .z , ptcls0 .lat , ptcls0 .lon )
76- driftdepth_mask = ptcls0 .z + ptcls0 .dz >= particles .drift_depth
77- bathy_mask = ptcls0 .z + ptcls0 .dz >= loc_bathy
78- next_phase = np .logical_and (
79- driftdepth_mask , bathy_mask
80- ) # combined mask; not at drift depth yet and not hitting bathymetry
73+ driftdepth_mask = ptcls0 .z + ptcls0 .dz <= particles .drift_depth # noqa:has reached drift depth
74+ bathysafe_mask = ptcls0 .z + ptcls0 .dz >= loc_bathy # noqa:has not reached bathymetry
75+ next_phase = np .logical_and (driftdepth_mask , bathysafe_mask )
8176 ptcls0 .cycle_phase [next_phase ] = 1
82- ptcls0 .dz [next_phase ] = (
83- particles .drift_depth - ptcls0 .z [next_phase ]
84- ) # avoid overshoot
77+ ptcls0 .dz [next_phase ] = particles .drift_depth - ptcls0 .z [next_phase ] # noqa:avoid overshoot
8578
8679 # Phase 0.5: Check for grounding at bathymetry and raise if necessary
87- ptcls0 .grounded [~ bathy_mask ] = 1
88- if np .any (~ bathy_mask ):
89- print (
90- "Shallow bathymetry warning: Argo float grounded at bathymetry depth during sinking to drift depth. Raising by 50m above bathymetry and continuing cycle."
91- )
92- ptcls0 .dz [~ bathy_mask ] = (
93- loc_bathy [~ bathy_mask ] - ptcls0 .z [~ bathy_mask ] + 50.0
94- ) # raise to 50m above bathymetry
95- ptcls0 .cycle_phase [~ bathy_mask ] = 1
80+ _handle_grounding (
81+ ptcls0 ,
82+ bathysafe_mask ,
83+ loc_bathy ,
84+ fieldset ,
85+ "sinking to drift depth" ,
86+ target_phase = 1 ,
87+ )
9688
9789 # Phase 1: Drifting at depth for drifttime seconds
9890 ptcls1 .drift_age += ptcls1 .dt
@@ -103,34 +95,27 @@ def _argo_float_vertical_movement(particles, fieldset):
10395 # Phase 2: Sinking further to maxdepth
10496 ptcls2 .dz += particles .vertical_speed * ptcls2 .dt
10597 loc_bathy = fieldset .bathymetry .eval (ptcls2 .time , ptcls2 .z , ptcls2 .lat , ptcls2 .lon )
106- maxdepth_mask = ptcls2 .z + ptcls2 .dz >= particles .max_depth
107- bathy_mask = ptcls2 .z + ptcls2 .dz >= loc_bathy
108- next_phase = np .logical_and (
109- maxdepth_mask , bathy_mask
110- ) # combined mask; not at max depth yet and not hitting bathymetry
98+ maxdepth_mask = ptcls2 .z + ptcls2 .dz <= particles .max_depth # noqa:has reached max depth
99+ bathysafe_mask = ptcls2 .z + ptcls2 .dz >= loc_bathy # noqa:has not reached bathymetry
100+ next_phase = np .logical_and (maxdepth_mask , bathysafe_mask )
111101 ptcls2 .cycle_phase [next_phase ] = 3
112- ptcls2 .dz [next_phase ] = (
113- particles .max_depth - ptcls2 .z [next_phase ]
114- ) # avoid overshoot
102+ ptcls2 .dz [next_phase ] = particles .max_depth - ptcls2 .z [next_phase ] # noqa:avoid overshoot
115103
116104 # Phase 2.5: Check for grounding at bathymetry and raise if necessary
117- ptcls2 .grounded [~ bathy_mask ] = 1
118- if np .any (~ bathy_mask ):
119- print (
120- "Shallow bathymetry warning: Argo float grounded at bathymetry depth during sinking to max depth. Raising by 50m above bathymetry and continuing cycle."
121- )
122- ptcls2 .dz [~ bathy_mask ] = (
123- loc_bathy [~ bathy_mask ] - ptcls2 .z [~ bathy_mask ] + 50.0
124- ) # raise to 50m above bathymetry
125- ptcls2 .cycle_phase [~ bathy_mask ] = 3
105+ _handle_grounding (
106+ ptcls2 ,
107+ bathysafe_mask ,
108+ loc_bathy ,
109+ fieldset ,
110+ "sinking to max depth" ,
111+ target_phase = 3 ,
112+ )
126113
127114 # Phase 3: Rising with vertical_speed until at surface
128115 ptcls3 .dz -= particles .vertical_speed * ptcls3 .dt
129- next_phase = ptcls3 .z + ptcls3 .dz < = particles .min_depth
116+ next_phase = ptcls3 .z + ptcls3 .dz > = particles .min_depth
130117 ptcls3 .cycle_phase [next_phase ] = 4
131- ptcls3 .dz [next_phase ] = (
132- particles .min_depth - ptcls3 .z [next_phase ]
133- ) # avoid overshoot
118+ ptcls3 .dz [next_phase ] = particles .min_depth - ptcls3 .z [next_phase ] # noqa:avoid overshoot
134119
135120 # Phase 4: Transmitting at surface until cycletime is reached
136121 next_phase = ptcls4 .cycle_age >= particles .cycle_days * 86400
@@ -148,21 +133,38 @@ def _keep_at_surface(particles, fieldset):
148133
149134
150135def _check_error (particles , fieldset ):
151- errors = particles .state >= 50 # captures all Errors
152- # TODO: check print statements are as expected
136+ errors = particles .state >= 50
137+ if not np .any (errors ):
138+ return
139+
140+ error_ints = particles .state [errors ].astype (int )
141+ error_times , error_lats , error_lons = _format_log_metadata (
142+ particles , errors , fieldset
143+ )
144+
145+ error_details = ", " .join (
146+ f"{ _STATUS_CODE_NAMES .get (err , str (err ))} at time(s): { t } , lat(s): { lat } , lon(s): { lon } "
147+ for err , lat , lon , t in zip (
148+ error_ints , error_lats , error_lons , error_times , strict = True
149+ )
150+ )
153151 print (
154- "WARNING: Error(s) found during Argo Float simulation but the expedition will continue..."
155- f"\n \n Error code(s): { ', ' .join (_STATUS_CODE_NAMES .get (error , str (error )) + 'at time: ' + str (particles .time [errors ][i ]) + ', lat: ' + str (particles .lat [errors ][i ]) + ', lon: ' + str (particles .lon [errors ][i ]) for i , error in enumerate (particles .state [errors ]))} "
156- "\n \n If ErrorOutOfBounds, consider reducing the lifetime in Argo Float config (the fieldset spatial bounds are constrained under-the-hood). For further advice please contact the VirtualShip team via GitHub (https://github.com/Parcels-code/virtualship/issues) or email (virtualship@uu.nl)."
157- "\n Carrying on with the expedition..."
152+ "WARNING: Error(s) found during Argo Float simulation but the expedition will continue...\n \n "
153+ f"Error code(s): { error_details } \n \n "
154+ "If ErrorOutOfBounds, consider reducing the lifetime in Argo Float config "
155+ "(the fieldset spatial bounds are constrained under-the-hood). For further advice "
156+ "please contact the VirtualShip team via GitHub (https://github.com/Parcels-code/virtualship/issues) "
157+ "or email (virtualship@uu.nl).\n "
158+ "Carrying on with the expedition..."
158159 )
160+
159161 particles .state [errors ] = StatusCode .Delete
160162
161163
162164def _argo_sample_temperature (particles , fieldset ):
163- # Phase 3: ascending — sample temperature; NaN otherwise
165+ # Phase 3: ascending — sample temperature
164166 phase_mask = particles .cycle_phase == 3
165- depth_mask = particles .z < particles .min_depth
167+ depth_mask = particles .z < particles .min_depth # still ascending
166168 sampling_particles = particles [np .logical_and (phase_mask , depth_mask )]
167169 sampling_particles .temperature = fieldset .T [
168170 sampling_particles .time ,
@@ -173,9 +175,9 @@ def _argo_sample_temperature(particles, fieldset):
173175
174176
175177def _argo_sample_salinity (particles , fieldset ):
176- # Phase 3: ascending — sample salinity; NaN otherwise
178+ # Phase 3: ascending — sample salinity
177179 phase_mask = particles .cycle_phase == 3
178- depth_mask = particles .z < particles .min_depth
180+ depth_mask = particles .z < particles .min_depth # still ascending
179181 sampling_particles = particles [np .logical_and (phase_mask , depth_mask )]
180182 sampling_particles .salinity = fieldset .S [
181183 sampling_particles .time ,
@@ -185,6 +187,48 @@ def _argo_sample_salinity(particles, fieldset):
185187 ]
186188
187189
190+ # =====================================================
191+ # SECTION: Helper Functions
192+ # =====================================================
193+
194+
195+ def _handle_grounding (
196+ ptcls_subset , bathysafe_mask , loc_bathy , fieldset , phase_name , target_phase
197+ ):
198+ """Handle grounding logic, logging warnings, and raising particles above bathymetry."""
199+ grounded_mask = ~ bathysafe_mask
200+ if not np .any (grounded_mask ):
201+ return
202+
203+ ptcls_subset .grounded [grounded_mask ] = 1
204+
205+ # extract log data
206+ times , lats , lons = _format_log_metadata (ptcls_subset , grounded_mask , fieldset )
207+
208+ print (
209+ f"Shallow bathymetry warning: Argo float grounded at bathymetry during { phase_name } "
210+ f"(time(s): { times } , lat(s): { lats } , lon(s): { lons } ). "
211+ f"Raising by 50m above bathymetry and continuing cycle."
212+ )
213+
214+ # adjust vertical displacement to be 50m above bathymetry and transition phase
215+ ptcls_subset .dz [grounded_mask ] = (
216+ loc_bathy [grounded_mask ] - ptcls_subset .z [grounded_mask ] + 50.0
217+ )
218+ ptcls_subset .cycle_phase [grounded_mask ] = target_phase
219+
220+
221+ def _format_log_metadata (ptcls_subset , mask , fieldset ):
222+ """Extracts and formats timestamps, latitudes, and longitudes for particles."""
223+ lats = ptcls_subset .lat [mask ].astype (float )
224+ lons = ptcls_subset .lon [mask ].astype (float )
225+
226+ time_origin = fieldset .U .data .time [0 ].values
227+ times = ptcls_subset .time [mask ].astype ("timedelta64[s]" ) + time_origin
228+
229+ return times , lats , lons
230+
231+
188232# =====================================================
189233# SECTION: Instrument Class
190234# =====================================================
0 commit comments