Skip to content

Commit f8758ab

Browse files
authored
Merge pull request #21 from axiom-data-science/some_updates
Some updates
2 parents 4e57e0b + e3ee69a commit f8758ab

File tree

7 files changed

+148
-28
lines changed

7 files changed

+148
-28
lines changed

docs/whats_new.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# What's New
22

3+
## v0.8.0 (April 2, 2024)
4+
5+
* `time_step_output` behavior has changed — 1 hour by default
6+
* `time_step` is now 5 min by default
7+
* added `Dcrit` parameter for accurately finding where drifters are stranded in tidal flats
8+
* `vertical_mixing` is True by default now
9+
* added seafloor_action option
10+
* fixed some Leeway/3D handling and log messaging
11+
* export_variables are specific to drift_model as needed
12+
* do not drop zeta anymore since used in opendrift
13+
* output_file is now an option
14+
15+
316
## v0.7.1 (February 21, 2024)
417

518
* Small fix to some attributes to be less verbose

particle_tracking_manager/models/opendrift/config.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
},
88
"export_variables": {
99
"default": [
10-
"z"
10+
"z",
11+
"origin_marker"
1112
],
1213
"ptm_level": 2,
1314
"type": "list",
1415
"description": "List of variables to export. Options available with `m.all_export_variables` for a given `drift_model`. ['lon', 'lat', 'ID', 'status'] will always be exported."
1516
},
1617
"radius": {
17-
"default": 0.0,
18+
"default": 1000.0,
1819
"ptm_level": 1,
1920
"type": "float",
2021
"min": 0.0,
@@ -58,6 +59,11 @@
5859
"od_mapping": "general:coastline_action",
5960
"ptm_level": 1
6061
},
62+
"seafloor_action": {
63+
"default": "previous",
64+
"od_mapping": "general:seafloor_action",
65+
"ptm_level": 1
66+
},
6167
"max_speed": {
6268
"default": 2,
6369
"od_mapping": "drift:max_speed"
@@ -198,5 +204,10 @@
198204
"default": "low",
199205
"ptm_level": 2,
200206
"description": "Log verbosity"
207+
},
208+
"Dcrit": {
209+
"default": 0.1,
210+
"od_mapping": "general:seafloor_action_dcrit",
211+
"ptm_level": 3
201212
}
202213
}

particle_tracking_manager/models/opendrift/opendrift.py

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class OpenDriftModel(ParticleTrackingManager):
8484
coastline_action : str, optional
8585
Action to perform if a drifter hits the coastline, by default "previous". Options
8686
are 'stranding', 'previous'.
87+
seafloor_action : str, optional
88+
Action to perform if a drifter hits the seafloor, by default "deactivate". Options
89+
are 'deactivate', 'previous', 'lift_to_seafloor'.
8790
max_speed : int
8891
Typical maximum speed of elements, used to estimate reader buffer size.
8992
wind_drift_factor : float
@@ -173,6 +176,7 @@ def __init__(
173176
stokes_drift: bool = config_model["stokes_drift"]["default"],
174177
mixed_layer_depth: float = config_model["mixed_layer_depth"]["default"],
175178
coastline_action: str = config_model["coastline_action"]["default"],
179+
seafloor_action: str = config_model["seafloor_action"]["default"],
176180
max_speed: int = config_model["max_speed"]["default"],
177181
wind_drift_factor: float = config_model["wind_drift_factor"]["default"],
178182
wind_drift_depth: float = config_model["wind_drift_depth"]["default"],
@@ -319,6 +323,15 @@ def __setattr_model__(self, name: str, value) -> None:
319323
self.config_model[name]["value"] = value
320324
self._update_config()
321325

326+
if name == "ocean_model":
327+
if value == "NWGOA":
328+
self.Dcrit = 0.5
329+
elif "CIOFS" in value:
330+
self.Dcrit = 0.3
331+
else:
332+
self.Dcrit = 0.1
333+
self.logger.info(f"For ocean_model {value}, setting Dcrit to {self.Dcrit}.")
334+
322335
if name in ["ocean_model", "horizontal_diffusivity"]:
323336

324337
# just set the value and move on if purposely setting a non-None value
@@ -332,12 +345,11 @@ def __setattr_model__(self, name: str, value) -> None:
332345
# in all other cases that ocean_model is a known model, want to use the
333346
# grid-dependent value
334347
elif self.ocean_model in _KNOWN_MODELS:
335-
print(name, value)
336-
self.logger.info(
337-
"Setting horizontal_diffusivity parameter to one tuned to reader model"
338-
)
339348

340349
hdiff = self.calc_known_horizontal_diffusivity()
350+
self.logger.info(
351+
f"Setting horizontal_diffusivity parameter to one tuned to reader model of value {hdiff}."
352+
)
341353
# when editing the __dict__ directly have to also update config_model
342354
self.__dict__["horizontal_diffusivity"] = hdiff
343355
self.config_model["horizontal_diffusivity"]["value"] = hdiff
@@ -381,9 +393,21 @@ def __setattr_model__(self, name: str, value) -> None:
381393

382394
# Leeway doesn't have this option available
383395
if name == "do3D" and not value and self.drift_model != "Leeway":
396+
self.logger.info("do3D is False so disabling vertical motion.")
384397
self.o.disable_vertical_motion()
385-
elif name == "do3D" and value:
398+
elif name == "do3D" and not value and self.drift_model == "Leeway":
399+
self.logger.info(
400+
"do3D is False but drift_model is Leeway so doing nothing."
401+
)
402+
403+
if name == "do3D" and value and self.drift_model != "Leeway":
404+
self.logger.info("do3D is True so turning on vertical advection.")
386405
self.o.set_config("drift:vertical_advection", True)
406+
elif name == "do3D" and value and self.drift_model == "Leeway":
407+
self.logger.info(
408+
"do3D is True but drift_model is Leeway so " "changing do3D to False."
409+
)
410+
self.do3D = False
387411

388412
# Make sure vertical_mixing_timestep equals default value if vertical_mixing False
389413
if name in ["vertical_mixing", "vertical_mixing_timestep"]:
@@ -464,6 +488,25 @@ def __setattr_model__(self, name: str, value) -> None:
464488
self.__dict__["stokes_drift"] = False
465489
self.config_model["stokes_drift"]["value"] = False
466490

491+
# Add export variables for certain drift_model values
492+
# drift_model is always set initially only
493+
if name == "export_variables" and self.drift_model == "OpenOil":
494+
oil_vars = [
495+
"mass_oil",
496+
"density",
497+
"mass_evaporated",
498+
"mass_dispersed",
499+
"mass_biodegraded",
500+
"viscosity",
501+
"water_fraction",
502+
]
503+
self.__dict__["export_variables"] += oil_vars
504+
self.config_model["export_variables"]["value"] += oil_vars
505+
elif name == "export_variables" and self.drift_model == "Leeway":
506+
vars = ["object_type"]
507+
self.__dict__["export_variables"] += vars
508+
self.config_model["export_variables"]["value"] += vars
509+
467510
self._update_config()
468511

469512
def run_add_reader(
@@ -617,14 +660,13 @@ def run_add_reader(
617660

618661
drop_vars += [
619662
"wetdry_mask_psi",
620-
"zeta",
621663
]
622664
if self.ocean_model == "CIOFS":
623665

624666
loc_local = "/mnt/vault/ciofs/HINDCAST/ciofs_kerchunk.parq"
625667
loc_remote = "http://xpublish-ciofs.srv.axds.co/datasets/ciofs_hindcast/zarr/"
626668

627-
elif self.ocean_model.upper() == "CIOFSOP":
669+
elif self.ocean_model == "CIOFSOP":
628670

629671
standard_name_mapping.update(
630672
{
@@ -674,7 +716,6 @@ def run_add_reader(
674716
# For NWGOA, need to calculate wetdry mask from a variable
675717
if self.ocean_model == "NWGOA" and not self.use_static_masks:
676718
ds["wetdry_mask_rho"] = (~ds.zeta.isnull()).astype(int)
677-
ds.drop_vars("zeta", inplace=True)
678719

679720
# For CIOFSOP need to rename u/v to have "East" and "North" in the variable names
680721
# so they aren't rotated in the ROMS reader (the standard names have to be x/y not east/north)
@@ -683,7 +724,14 @@ def run_add_reader(
683724
# grid = xr.open_dataset("/mnt/vault/ciofs/HINDCAST/nos.ciofs.romsgrid.nc")
684725
# ds["angle"] = grid["angle"]
685726

686-
units_date = pd.Timestamp(ds.ocean_time.attrs["units"].split("since ")[1])
727+
try:
728+
units_date = pd.Timestamp(
729+
ds.ocean_time.attrs["units"].split("since ")[1]
730+
)
731+
except KeyError: # for remote
732+
units_date = pd.Timestamp(
733+
ds.ocean_time.encoding["units"].split("since ")[0]
734+
)
687735
# use reader start time if not otherwise input
688736
if self.start_time is None:
689737
self.logger.info("setting reader start_time as simulation start_time")
@@ -787,11 +835,18 @@ def run_drifters(self):
787835
}
788836

789837
self.o._config = config_input_to_opendrift # only OpenDrift config
838+
839+
output_file = (
840+
self.output_file
841+
or f"output-results_{datetime.datetime.utcnow():%Y-%m-%dT%H%M:%SZ}.nc"
842+
)
843+
790844
self.o.run(
791845
time_step=timedir * self.time_step,
846+
time_step_output=self.time_step_output,
792847
steps=self.steps,
793848
export_variables=self.export_variables,
794-
outfile=f"output-results_{datetime.datetime.utcnow():%Y-%m-%dT%H%M:%SZ}.nc",
849+
outfile=output_file,
795850
)
796851

797852
self.o._config = full_config # reinstate config
@@ -902,6 +957,30 @@ def export_variables(self):
902957

903958
return self.o.export_variables
904959

960+
def drift_model_config(self, ptm_level=[1, 2, 3], prefix=""):
961+
"""Show config for this drift model selection.
962+
963+
This shows all PTM-controlled parameters for the OpenDrift
964+
drift model selected and their current values, at the selected ptm_level
965+
of importance.
966+
967+
Parameters
968+
----------
969+
ptm_level : int, list, optional
970+
Options are 1, 2, 3, or lists of combinations. Use [1,2,3] for all.
971+
Default is 1.
972+
prefix : str, optional
973+
prefix to search config for, only for OpenDrift parameters (not PTM).
974+
"""
975+
976+
return [
977+
(key, value_dict["value"])
978+
for key, value_dict in self.show_config(
979+
substring=":", ptm_level=ptm_level, level=[1, 2, 3], prefix=prefix
980+
).items()
981+
if "value" in value_dict
982+
]
983+
905984
def get_configspec(self, prefix, substring, excludestring, level, ptm_level):
906985
"""Copied from OpenDrift, then modified."""
907986

particle_tracking_manager/plotting.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import opendrift
44

55

6-
def plot_dest(o):
6+
def plot_dest(o, filename):
77
"""This is copied from an opendrift example."""
88

99
import cmocean
@@ -24,4 +24,6 @@ def plot_dest(o):
2424
vmin=0,
2525
vmax=vmax,
2626
cmap=cmap,
27+
fast=True,
28+
filename=filename,
2729
)

particle_tracking_manager/the_manager.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ class ParticleTrackingManager:
7171
run_forward : bool, optional
7272
True to run forward in time, False to run backward, by default True
7373
time_step : int, optional
74-
Time step in seconds, options >0, <86400 (1 day in seconds), by default 3600
75-
time_step_output : int, optional
76-
How often to output model output, in seconds. Should be a multiple of time_step.
77-
By default will take the value of time_step (this change occurs in the model).
74+
Time step in seconds, options >0, <86400 (1 day in seconds), by default 300.
75+
time_step_output : int, Timedelta, optional
76+
How often to output model output. Should be a multiple of time_step.
77+
By default 3600.
7878
steps : int, optional
7979
Number of time steps to run in simulation. Options >0.
8080
steps, end_time, or duration must be input by user. By default steps is 3 and
@@ -129,6 +129,8 @@ class ParticleTrackingManager:
129129
with a user-input ocean_model, you can drop the wetdry_mask_rho etc variables from the
130130
dataset before inputting to PTM. Setting this to True may save computation time but
131131
will be less accurate, especially in the tidal flat regions of the model.
132+
output_file : Optional[str], optional
133+
Name of output file to save, by default None. If None, default is set in the model.
132134
133135
Notes
134136
-----
@@ -176,6 +178,7 @@ def __init__(
176178
do3D: bool = config_ptm["do3D"]["default"],
177179
vertical_mixing: bool = config_ptm["vertical_mixing"]["default"],
178180
use_static_masks: bool = config_ptm["use_static_masks"]["default"],
181+
output_file: Optional[str] = config_ptm["output_file"]["default"],
179182
**kw,
180183
) -> None:
181184
"""Inputs necessary for any particle tracking."""
@@ -328,7 +331,7 @@ def __setattr__(self, name: str, value) -> None:
328331
# this is not a user-defined option
329332
if -180 < self.lon < 0:
330333
self.__dict__["lon"] += 360
331-
self.config_ptm["lon"]["value"] = False
334+
self.config_ptm["lon"]["value"] += 360 # this isn't really used
332335

333336
if name == "surface_only" and value:
334337
self.logger.info(

particle_tracking_manager/the_manager_config.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,19 @@
6767
"float",
6868
"datetime.timedelta"
6969
],
70-
"default": 3600,
70+
"default": 300,
7171
"min": 1,
7272
"max": 86400,
7373
"units": "seconds",
7474
"description": "Interval between particles updates, in seconds or as timedelta.",
75-
"ptm_level": 1
75+
"ptm_level": 2
7676
},
7777
"time_step_output": {
7878
"type": [
7979
"float",
8080
"datetime.timedelta"
8181
],
82-
"default": "None",
82+
"default": 3600,
8383
"min": 1,
8484
"max": 604800,
8585
"units": "seconds",
@@ -143,7 +143,7 @@
143143
"ptm_level": 1
144144
},
145145
"vertical_mixing": {
146-
"default": false,
146+
"default": true,
147147
"od_mapping": "drift:vertical_mixing",
148148
"ptm_level": 1
149149
},
@@ -162,5 +162,11 @@
162162
"default": false,
163163
"ptm_level": 2,
164164
"description": "Set to True to use static masks for known models instead of wetdry masks. If False, the masks are change in time."
165+
},
166+
"output_file": {
167+
"type": "str",
168+
"default": "None",
169+
"description": "Name of file to write output to. If None, default name is used.",
170+
"ptm_level": 3
165171
}
166172
}

tests/test_manager.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ def test_ocean_model_not_None(mock_reader_metadata):
9797
m.has_added_reader = True
9898

9999

100+
@pytest.mark.slow
100101
def test_parameter_passing():
101102
"""make sure parameters passed into package make it to simulation runtime."""
102103

103-
ts = 2 * 3600
104+
ts = 5
104105
diffmodel = "windspeed_Sundby1983"
105106
use_auto_landmask = True
106107
vertical_mixing = True
@@ -223,21 +224,26 @@ def test_input_too_many_end_of_simulation():
223224

224225

225226
def test_changing_end_of_simulation():
226-
"""change end_time, steps, and duration and make sure others are updated accordingly."""
227+
"""change end_time, steps, and duration
228+
229+
and make sure others are updated accordingly.
230+
This accounts for the default time_step of 300 seconds.
231+
232+
"""
227233

228234
m = ptm.OpenDriftModel(start_time=pd.Timestamp("2000-1-1"))
229235
m.start_time = pd.Timestamp("2000-1-2")
230236
m.end_time = pd.Timestamp("2000-1-3")
231-
assert m.steps == 24
237+
assert m.steps == 288
232238
assert m.duration == pd.Timedelta("1 days 00:00:00")
233239

234240
m.steps = 48
235-
assert m.end_time == pd.Timestamp("2000-1-4")
236-
assert m.duration == pd.Timedelta("2 days 00:00:00")
241+
assert m.end_time == pd.Timestamp("2000-01-02 04:00:00")
242+
assert m.duration == pd.Timedelta("0 days 04:00:00")
237243

238244
m.duration = pd.Timedelta("2 days 12:00:00")
239245
assert m.end_time == pd.Timestamp("2000-01-04 12:00:00")
240-
assert m.steps == 60
246+
assert m.steps == 720
241247

242248

243249
class TestTheManager(unittest.TestCase):

0 commit comments

Comments
 (0)