Skip to content

Commit 75c76aa

Browse files
authored
A variety of small changes (#52)
* Added test to make sure CIOFSOP_max is updated without closing kernel * enforce if hatched==1, stage_fraction=None (initially, for seeding) * updated config descriptions * updated some default values in config classes * updated version number for new release
1 parent fc38970 commit 75c76aa

File tree

7 files changed

+94
-92
lines changed

7 files changed

+94
-92
lines changed

docs/tutorial.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ m.o.elements
164164

165165
```{code-cell} ipython3
166166
m = ptm.OpenDriftModel(drift_model="LarvalFish", lon=-89.85, lat=28.8, number=10, steps=45,
167-
do3D=True, seed_seafloor=True, hatched=1,
167+
do3D=True, seed_seafloor=True, hatched=1, stage_fraction=None,
168168
z=None,
169169
ocean_model="TXLA",
170170
start_time="2009-11-19T12:00",

docs/whats_new.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# What's New
22

3-
## unreleased
3+
## v0.12.2 (April 15, 2025)
44

55
* fixed issue when setting `vertical_mixing` False for `OpenOil` was not passed through correctly
66
* now `do3d=False` and `vertical_mixing=True` can be set at the same time.
@@ -9,6 +9,9 @@
99
* `OilTypeEnum` was reordered so the first 5 oils are most relevant to Alaska work.
1010
* Reverted `time_step` units back to seconds from minutes. It was being mapped unexpectedly in `OpenDrift` so using seconds is better.
1111
* Updated list of options for `object_type` and it is now fully consistent with that in `OpenDrift`.
12+
* Now enforce that for Leeway model if `hatched==1` then `stage_fraction=None`, for seeding.
13+
* Updated descriptions in a lot of parameters in `config_the_manager.py` and `config_opendrift.py`.
14+
* Updated defaults in config classes.
1215

1316

1417
## v0.12.1 (April 8, 2025)

particle_tracking_manager/config_the_manager.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class TheManagerConfig(BaseModel):
110110
)
111111
start_time: datetime | None = Field(
112112
datetime(2022, 1, 1),
113-
description="Start time for drifter simulation.",
113+
description="Start time for drifter simulation. start_time or end_time must be input.",
114114
json_schema_extra=dict(ptm_level=1),
115115
)
116116
start_time_end: datetime | None = Field(
@@ -149,7 +149,7 @@ class TheManagerConfig(BaseModel):
149149
)
150150
end_time: datetime | None = Field(
151151
None,
152-
description="The end of the simulation. steps, end_time, or duration must be input by user.",
152+
description="The end of the simulation. steps, end_time, or duration must be input by user. start_time or end_time must be input.",
153153
json_schema_extra=dict(ptm_level=1),
154154
)
155155
# OceanModelEnum was created dynamically and will trigger an issue with mypy, so we suppress it
@@ -170,7 +170,7 @@ class TheManagerConfig(BaseModel):
170170
)
171171
use_static_masks: bool = Field(
172172
True,
173-
description="Set to True to use static masks for known models instead of wetdry masks.",
173+
description="If False, use static ocean model land masks. This saves some computation time but since the available ocean models run in wet/dry mode, it is inconsistent with the ROMS output files in some places since the drifters may be allowed (due to the static mask) to enter a cell they wouldn't otherwise. However, it doesn't make much of a difference for simulations that aren't in the tidal flats. Use the time-varying wet/dry masks (set to True) if drifters are expected to run in the tidal flats. This costs some more computational time but is fully consistent with the ROMS output files.",
174174
json_schema_extra=dict(ptm_level=3),
175175
)
176176
output_file: PathLike[str] | None = Field(
@@ -197,7 +197,7 @@ class TheManagerConfig(BaseModel):
197197

198198
horizontal_diffusivity: float | None = Field(
199199
default=None,
200-
description="Add horizontal diffusivity (random walk)",
200+
description="Add horizontal diffusivity (random walk). For known ocean models, the value is calculated as the approximate horizontal grid resolution for the selected ocean model times an estimate of the scale of sub-gridscale velocity of 0.1 m/s.",
201201
title="Horizontal Diffusivity",
202202
ge=0,
203203
le=100000,
@@ -379,13 +379,11 @@ def calculate_config_times(self) -> Self:
379379
return self
380380

381381
@computed_field
382-
# @property
383382
def ocean_model_config(self) -> OceanModelConfig:
384383
"""Select ocean model config based on ocean_model."""
385384
return ocean_model_registry.get(self.ocean_model)
386385

387386
@computed_field
388-
# @property
389387
def ocean_model_simulation(self) -> OceanModelSimulation:
390388
"""Select ocean model simulation based on ocean_model."""
391389
inputs = {

particle_tracking_manager/models/opendrift/config_opendrift.py

Lines changed: 43 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class OpenDriftConfig(TheManagerConfig):
3535

3636
drift_model: DriftModelEnum = Field(
3737
default=DriftModelEnum.OceanDrift, # .value,
38-
description="Drift model to use for simulation.",
38+
description="Scenario to use for simulation.",
3939
)
4040

4141
save_interpolator: bool = Field(
@@ -65,7 +65,7 @@ class OpenDriftConfig(TheManagerConfig):
6565
default=1000.0,
6666
ge=0.0,
6767
le=1000000,
68-
description="Radius around each lon-lat pair, within which particles will be randomly seeded.",
68+
description="Radius around each lon-lat pair, within which particles will be seeded according to `radius_type`.",
6969
json_schema_extra=dict(
7070
ptm_level=2,
7171
units="m",
@@ -74,7 +74,7 @@ class OpenDriftConfig(TheManagerConfig):
7474

7575
radius_type: RadiusTypeEnum = Field(
7676
default=RadiusTypeEnum.gaussian, # .value,
77-
description="Radius type. Options: 'gaussian' or 'uniform'.",
77+
description="Distribution for seeding particles around location. Options: 'gaussian' or 'uniform'.",
7878
json_schema_extra=dict(
7979
ptm_level=3,
8080
),
@@ -83,7 +83,7 @@ class OpenDriftConfig(TheManagerConfig):
8383
# OpenDriftSimulation parameters
8484

8585
max_speed: float = Field(
86-
default=5.0,
86+
default=7.0,
8787
description="Typical maximum speed of elements, used to estimate reader buffer size",
8888
gt=0,
8989
title="Maximum speed",
@@ -95,15 +95,15 @@ class OpenDriftConfig(TheManagerConfig):
9595
)
9696

9797
use_auto_landmask: bool = Field(
98-
default=True,
99-
description="A built-in GSHHG global landmask is used if True, otherwise landmask is taken from reader or fallback value.",
98+
default=False,
99+
description="If True, use a global-scale land mask from https://www.generic-mapping-tools.org/remote-datasets/earth-mask.html. Dataset scale selected is `auto`. If False, use the land mask from the ocean model.",
100100
title="Use Auto Landmask",
101101
json_schema_extra={"od_mapping": "general:use_auto_landmask", "ptm_level": 3},
102102
)
103103

104104
coastline_action: CoastlineActionEnum = Field(
105105
default=CoastlineActionEnum.stranding, # .value,
106-
description="None means that objects may also move over land. stranding means that objects are deactivated if they hit land. previous means that objects will move back to the previous location if they hit land",
106+
description="This controls particle behavior at the coastline. Use `previous` for a particle to move back to its previous location if it hits land. Use `stranding` to have a particle stick (that is, become deactivated) where it interacts with land. With None, objects may also move over land.",
107107
title="Coastline Action",
108108
json_schema_extra={"od_mapping": "general:coastline_action", "ptm_level": 2},
109109
)
@@ -151,16 +151,6 @@ class OpenDriftConfig(TheManagerConfig):
151151
TheManagerConfig.model_fields["number"],
152152
Field(json_schema_extra=dict(od_mapping="seed:number")),
153153
)
154-
# These don't properly map the way I expect in OpenDrift. It is better to leave time_step as not
155-
# associated with an `od_mapping` to avoid confusion.
156-
# time_step: float = FieldInfo.merge_field_infos(
157-
# TheManagerConfig.model_fields["time_step"],
158-
# Field(json_schema_extra=dict(od_mapping="general:time_step_minutes")),
159-
# )
160-
# time_step_output: float = FieldInfo.merge_field_infos(
161-
# TheManagerConfig.model_fields["time_step_output"],
162-
# Field(json_schema_extra=dict(od_mapping="general:time_step_output_minutes")),
163-
# )
164154

165155
model_config = {
166156
"validate_defaults": True,
@@ -368,11 +358,11 @@ def check_plot_prefix_enum(self) -> Self:
368358
class LeewayModelConfig(OpenDriftConfig):
369359
"""Leeway model configuration for OpenDrift."""
370360

371-
drift_model: DriftModelEnum = DriftModelEnum.Leeway # .value
361+
drift_model: DriftModelEnum = DriftModelEnum.Leeway
372362

373363
object_type: ObjectTypeEnum = Field(
374364
default=ObjectTypeEnum("Person-in-water (PIW), unknown state (mean values)"),
375-
description="Leeway object category for this simulation",
365+
description="Leeway object category for this simulation. List is originally from USCG technical reports. More details here: https://github.com/OpenDrift/opendrift/blob/master/opendrift/models/OBJECTPROP.DAT.",
376366
title="Object Type",
377367
json_schema_extra={"od_mapping": "seed:object_type", "ptm_level": 1},
378368
)
@@ -406,14 +396,14 @@ class OceanDriftModelConfig(OpenDriftConfig):
406396

407397
seed_seafloor: bool = Field(
408398
default=False,
409-
description="Elements are seeded at seafloor, and seeding depth (z) is neglected.",
399+
description="Elements are seeded at seafloor, and seeding depth (z) is neglected and must be None.",
410400
title="Seed Seafloor",
411401
json_schema_extra={"od_mapping": "seed:seafloor", "ptm_level": 2},
412402
)
413403

414404
diffusivitymodel: DiffusivityModelEnum = Field(
415-
default=DiffusivityModelEnum.environment, # .value,
416-
description="Algorithm/source used for profile of vertical diffusivity. Environment means that diffusivity is acquired from readers or environment constants/fallback.",
405+
default="windspeed_Large1994",
406+
description="Algorithm/source used for profile of vertical diffusivity. Environment means that diffusivity is acquired from readers or environment constants/fallback. Parameterizations based on wind speed are also available.",
417407
title="Diffusivity model",
418408
json_schema_extra={
419409
"units": "seconds",
@@ -423,8 +413,8 @@ class OceanDriftModelConfig(OpenDriftConfig):
423413
)
424414

425415
mixed_layer_depth: float = Field(
426-
default=50,
427-
description="Fallback value for ocean_mixed_layer_thickness if not available from any reader",
416+
default=10,
417+
description="mixed_layer_depth controls how deep the vertical diffusivity profile reaches. This sets the fallback value for ocean_mixed_layer_thickness if not available from any reader.",
428418
title="Mixed Layer Depth",
429419
ge=0.0,
430420
json_schema_extra={
@@ -436,7 +426,7 @@ class OceanDriftModelConfig(OpenDriftConfig):
436426

437427
seafloor_action: SeafloorActionEnum = Field(
438428
default=SeafloorActionEnum.lift_to_seafloor, # .value,
439-
description="deactivate: elements are deactivated; lift_to_seafloor: elements are lifted to seafloor level; previous: elements are moved back to previous position; none; seafloor is ignored.",
429+
description="This controls particle behavior at the seafloor. Use `deactivate` to stick particles to the seafloor at the point of interaction. Use `lift_to_seafloor` to elevate particles up to seabed if below. User `previous` to move elements back to previous position. Use None to ignore seafloor.",
440430
title="Seafloor Action",
441431
json_schema_extra={
442432
"od_mapping": "general:seafloor_action",
@@ -459,7 +449,7 @@ class OceanDriftModelConfig(OpenDriftConfig):
459449

460450
vertical_mixing_timestep: float = Field(
461451
default=60,
462-
description="Time step used for inner loop of vertical mixing.",
452+
description="Time step used for inner (fast) loop of the vertical mixing model. Set this smaller to increase frequency of vertical mixing calculation; number of loops is calculated as int(self.time_step/vertical_mixing_timestep) so vertical_mixing_timestep must be smaller than time_step.",
463453
title="Vertical Mixing Timestep",
464454
ge=0.1,
465455
le=3600,
@@ -472,7 +462,7 @@ class OceanDriftModelConfig(OpenDriftConfig):
472462

473463
wind_drift_factor: float = Field(
474464
default=0.02,
475-
description="Elements at surface are moved with this fraction of the wind vector, in addition to currents and Stokes drift",
465+
description="Elements at surface are moved with this fraction of the wind vector, in addition to currents and Stokes drift.",
476466
title="Wind Drift Factor",
477467
ge=0,
478468
json_schema_extra={
@@ -484,7 +474,7 @@ class OceanDriftModelConfig(OpenDriftConfig):
484474

485475
vertical_mixing: bool = Field(
486476
default=False,
487-
description="Activate vertical mixing scheme with inner loop",
477+
description="Activate vertical mixing scheme. Vertical mixing includes movement due to buoyancy and turbulent mixing.",
488478
title="Vertical Mixing",
489479
json_schema_extra={
490480
"od_mapping": "drift:vertical_mixing",
@@ -507,7 +497,7 @@ class OpenOilModelConfig(OceanDriftModelConfig):
507497

508498
m3_per_hour: float = Field(
509499
default=1,
510-
description="The amount (volume) of oil released per hour (or total amount if release is instantaneous)",
500+
description="The amount (volume) of oil released per hour (or total amount if release is instantaneous).",
511501
title="M3 Per Hour",
512502
gt=0,
513503
json_schema_extra={
@@ -592,7 +582,7 @@ class OpenOilModelConfig(OceanDriftModelConfig):
592582

593583
emulsification: bool = Field(
594584
default=True,
595-
description="Surface oil is emulsified, i.e. water droplets are mixed into oil due to wave mixing, with resulting increase of viscosity.",
585+
description="If True, surface oil is emulsified, i.e. water droplets are mixed into oil due to wave mixing, with resulting increase of viscosity.",
596586
title="Emulsification",
597587
json_schema_extra={
598588
"od_mapping": "processes:emulsification",
@@ -602,7 +592,7 @@ class OpenOilModelConfig(OceanDriftModelConfig):
602592

603593
dispersion: bool = Field(
604594
default=True,
605-
description="Oil is removed from simulation (dispersed), if entrained as very small droplets.",
595+
description="If True, oil is removed from simulation (dispersed), if entrained as very small droplets.",
606596
title="Dispersion",
607597
json_schema_extra={
608598
"od_mapping": "processes:dispersion",
@@ -612,7 +602,7 @@ class OpenOilModelConfig(OceanDriftModelConfig):
612602

613603
evaporation: bool = Field(
614604
default=True,
615-
description="Surface oil is evaporated.",
605+
description="If True, surface oil is evaporated.",
616606
title="Evaporation",
617607
json_schema_extra={
618608
"od_mapping": "processes:evaporation",
@@ -621,7 +611,7 @@ class OpenOilModelConfig(OceanDriftModelConfig):
621611
)
622612

623613
update_oilfilm_thickness: bool = Field(
624-
default=False,
614+
default=True,
625615
description="If True, Oil film thickness is calculated at each time step. If False, oil film thickness is kept constant with value provided at seeding.",
626616
title="Update Oilfilm Thickness",
627617
json_schema_extra={
@@ -631,8 +621,8 @@ class OpenOilModelConfig(OceanDriftModelConfig):
631621
)
632622

633623
biodegradation: bool = Field(
634-
default=False,
635-
description="Oil mass is biodegraded (eaten by bacteria).",
624+
default=True,
625+
description="If True, oil mass is biodegraded (eaten by bacteria).",
636626
title="Biodegradation",
637627
json_schema_extra={
638628
"od_mapping": "processes:biodegradation",
@@ -643,57 +633,17 @@ class OpenOilModelConfig(OceanDriftModelConfig):
643633
# overwrite the defaults from OceanDriftModelConfig for a few inherited parameters,
644634
# but don't want to have to repeat the full definition
645635
current_uncertainty: float = FieldInfo.merge_field_infos(
646-
OceanDriftModelConfig.model_fields["current_uncertainty"], Field(default=0.05)
636+
OceanDriftModelConfig.model_fields["current_uncertainty"], Field(default=0.0)
647637
)
648638
wind_uncertainty: float = FieldInfo.merge_field_infos(
649-
OceanDriftModelConfig.model_fields["wind_uncertainty"], Field(default=0.5)
639+
OceanDriftModelConfig.model_fields["wind_uncertainty"], Field(default=0.0)
650640
)
651641
wind_drift_factor: float = FieldInfo.merge_field_infos(
652642
OceanDriftModelConfig.model_fields["wind_drift_factor"], Field(default=0.03)
653643
)
654-
# OpenDrift's default is for vertical_mixing to be True but that conflicts with do3D default of False
655-
# vertical_mixing: bool = FieldInfo.merge_field_infos(OceanDriftModelConfig.model_fields['vertical_mixing'],
656-
# Field(default=True))
657-
658-
# @property
659-
# def oil_type_input(self) -> str | None:
660-
# """Save oil type input with both name and id"""
661-
# if self.drift_model == "OpenOil":
662-
# return self.oil_type
663-
# return None
664-
665-
# @model_validator(mode="after")
666-
# def clean_oil_type_string(self) -> Self:
667-
# """remove id from oil_type string if needed"""
668-
# if self.drift_model == "OpenOil":
669-
# # only keep first part of string, which is the name of the oil
670-
# self.oil_type = self.oil_type_input.split(" (")[0]
671-
# return self
672-
673-
# @model_validator(mode="before")
674-
# def validate_oil_type_by_id_from_name(cls, values):
675-
# """Validate oil type by id from name."""
676-
# name_id = values.get('oil_type')
677-
# if name_id not in OceanModelEnum.__members__:
678-
# raise ValueError(f"Invalid ocean model name_id: {name_id}")
679-
# return values
680-
681-
# @field_validator("oil_type", mode="before")
682-
# def map_oil_type_to_name(cls, v):
683-
# """Map input oil type to enum value (which is the oil type name)."""
684-
# if (
685-
# v in OilTypeEnum.__members__
686-
# ): # Check if it matches an Enum name (which is the oil type id)
687-
# import pdb; pdb.set_trace()
688-
# return OilTypeEnum.title # then return title or label
689-
# for (
690-
# enum_member
691-
# ) in (
692-
# OilTypeEnum
693-
# ): # Check if it matches an Enum value (which is the oil type (id, title) tuple)
694-
# if enum_member.value == v:
695-
# return enum_member.title # then return title or label
696-
# raise ValueError(f"Invalid value or name '{v}' for OilTypeEnum")
644+
vertical_mixing: bool = FieldInfo.merge_field_infos(
645+
OceanDriftModelConfig.model_fields["vertical_mixing"], Field(default=True)
646+
)
697647

698648
@field_validator("oil_type", mode="before")
699649
def map_id_to_oil_type_tuple(cls, v):
@@ -739,9 +689,9 @@ class LarvalFishModelConfig(OceanDriftModelConfig):
739689
},
740690
)
741691

742-
stage_fraction: float = Field(
692+
stage_fraction: float | None = Field(
743693
default=0.0,
744-
description="Seeding value of stage_fraction. stage_fraction tracks percentage of development time completed, from 0 to 1, where a value of 1 means the egg has hatched. If `hatched==1` then `stage_fraction` is ignored.",
694+
description="Seeding value of stage_fraction. stage_fraction tracks percentage of development time completed, from 0 to 1, where a value of 1 means the egg has hatched. If `hatched==1` then `stage_fraction` is ignored in `OpenDrift`, but has to be None.",
745695
title="Stage Fraction",
746696
ge=0,
747697
le=1,
@@ -815,6 +765,17 @@ def check_vertical_mixing(self) -> Self:
815765

816766
return self
817767

768+
@model_validator(mode="after")
769+
def check_hatched_and_stage_fraction(self) -> Self:
770+
"""If hatched==1, stage_fraction should be None.
771+
772+
This only applies for seeding, not for the simulation.
773+
"""
774+
775+
if self.hatched == 1 and self.stage_fraction is not None:
776+
raise ValueError("If hatched==1, stage_fraction should be None.")
777+
return self
778+
818779

819780
open_drift_mapper: dict[str, type[OpenDriftConfig]] = {
820781
"OceanDrift": OceanDriftModelConfig,

0 commit comments

Comments
 (0)