Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/user_guide/examples/tutorial_diffusion.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"\n",
"The advection component of these kernels is similar to that of the Explicit Euler advection kernel (`AdvectionEE`). In the special case where diffusivity is constant over the entire domain, the diffusion-only kernel {py:func}`parcels.kernels.DiffusionUniformKh` can be used in combination with an advection kernel of choice. Since the diffusivity here is space-independent, gradients are not calculated, increasing efficiency. The diffusion-step can in this case be computed after or before advection, thus allowing you to chain kernels in a list.\n",
"\n",
"Just like velocities, diffusivities are passed to Parcels in the form of {py:func}`parcels.Field` objects. When using {py:func}`parcels.kernels.DiffusionUniformKh`, they should be added to the {py:func}`parcels.FieldSet` object as constant fields, e.g. `fieldset.add_constant_field(\"Kh_zonal\", 1, mesh=\"flat\")`.\n",
"Just like velocities, diffusivities are passed to Parcels in the form of {py:func}`parcels.Field` objects. When using {py:func}`parcels.kernels.DiffusionUniformKh`, they should be added to the {py:func}`parcels.FieldSet` object as constant fields, e.g. `fieldset.add_constant_field(\"Kh_zonal\", 1)`.\n",
"\n",
"To make a central difference approximation for computing the gradient in diffusivity, a resolution for this approximation `dres` is needed: _Parcels_ approximates the gradients in diffusivities by using their values at the particle's location ± `dres` (in both $x$ and $y$). A value of `dres` must be specified and added to the FieldSet by the user (e.g. `fieldset.add_context(\"dres\", 0.01)`). Currently, it is unclear what the best value of `dres` is. From experience, the size of `dres` should be smaller than the spatial resolution of the data, but within reasonable limits of machine precision to avoid numerical errors. We are working on a method to compute gradients differently so that specifying `dres` is not necessary anymore.\n",
"\n",
Expand Down Expand Up @@ -203,7 +203,7 @@
"outputs": [],
"source": [
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds, mesh=\"flat\")\n",
"fieldset.add_constant_field(\"Kh_zonal\", 1, mesh=\"flat\")\n",
"fieldset.add_constant_field(\"Kh_zonal\", 1)\n",
"fieldset.add_context(\"dres\", 0.00005)"
]
},
Expand Down
8 changes: 4 additions & 4 deletions docs/user_guide/examples/tutorial_interaction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@
" \"\"\"Define a fieldset with only diffusion\"\"\"\n",
" ds = simple_UV_dataset(dims=(1, 1, 1, 1), mesh=\"flat\")\n",
" fieldset = parcels.FieldSet.from_sgrid_conventions(ds, mesh=\"flat\")\n",
" fieldset.add_constant_field(\"Kh_zonal\", 0.0005, mesh=\"flat\")\n",
" fieldset.add_constant_field(\"Kh_meridional\", 0.0005, mesh=\"flat\")\n",
" fieldset.add_constant_field(\"Kh_zonal\", 0.0005)\n",
" fieldset.add_constant_field(\"Kh_meridional\", 0.0005)\n",
" return fieldset"
]
},
Expand Down Expand Up @@ -361,7 +361,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Parcels:docs (3.14.6)",
"display_name": "default",
"language": "python",
"name": "python3"
},
Expand All @@ -375,7 +375,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.6"
"version": "3.14.7"
}
},
"nbformat": 4,
Expand Down
4 changes: 2 additions & 2 deletions docs/user_guide/examples/tutorial_stuck_particles.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -821,8 +821,8 @@
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
"fieldset = fieldset.to_windowed_arrays()\n",
"\n",
"fieldset.add_constant_field(\"Kh_zonal\", 5, mesh=\"spherical\")\n",
"fieldset.add_constant_field(\"Kh_meridional\", 5, mesh=\"spherical\")\n",
"fieldset.add_constant_field(\"Kh_zonal\", 5)\n",
"fieldset.add_constant_field(\"Kh_meridional\", 5)\n",
"fieldset.add_context(\"dres\", 0.083)\n",
"\n",
"npart = 10 # number of particles to be released\n",
Expand Down
13 changes: 2 additions & 11 deletions src/parcels/_core/fieldset.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def to_windowed_arrays(self, *, max_levels: int | None = None):
model.to_windowed_arrays(max_levels=max_levels)
return self

def add_constant_field(self, name: str, value, mesh: ptyping.TMesh = "spherical"):
def add_constant_field(self, name: str, value):
"""Wrapper function to add a Field that is constant in space,
useful e.g. when using constant horizontal diffusivity

Expand All @@ -201,18 +201,9 @@ def add_constant_field(self, name: str, value, mesh: ptyping.TMesh = "spherical"
Name of the :class:`parcels.field.Field` object to be added
value :
Value of the constant field
mesh : str
String indicating the type of mesh coordinates,

1. spherical (default): Lat and lon in degree, with a
correction for zonal velocity U near the poles.
2. flat: No conversion, lat/lon are assumed to be in m.
"""
if mesh not in ("flat", "spherical"):
raise ValueError(f"mesh must be one of ['flat', 'spherical']. Got {mesh!r}.")

if self.constant_model is None:
self.constant_model = create_empty_constant_field_model(mesh)
self.constant_model = create_empty_constant_field_model(self.mesh)

self.constant_model.data[name] = (["time", "depth", "lat", "lon"], np.full((1, 1, 1, 1), value))

Expand Down
5 changes: 3 additions & 2 deletions src/parcels/_core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,9 @@ def assert_vector_field_components_in_dataset(ds: xr.Dataset, vector_fields: pty
return


def create_empty_constant_field_model(mesh: ptyping.TMesh) -> StructuredModelData:
def create_empty_constant_field_model(mesh: SphericalMesh | FlatMesh) -> StructuredModelData:
"""Create a empty model for constant fields with the given mesh type."""
mesh_: ptyping.TMesh = "flat" if isinstance(mesh, FlatMesh) else mesh
return StructuredModelData.from_sgrid_conventions(
xr.Dataset(
{},
Expand All @@ -319,7 +320,7 @@ def create_empty_constant_field_model(mesh: ptyping.TMesh) -> StructuredModelDat
),
),
),
mesh=mesh,
mesh=mesh_,
vector_fields={},
)

Expand Down
4 changes: 2 additions & 2 deletions src/parcels/kernels/_advectiondiffusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ def DiffusionUniformKh(particles, fieldset): # pragma: no cover

Assumes that fieldset has constant fields `Kh_zonal` and `Kh_meridional`.
These can be added via e.g.
`fieldset.add_constant_field("Kh_zonal", kh_zonal, mesh=mesh)`
`fieldset.add_constant_field("Kh_zonal", kh_zonal)`
or
`fieldset.add_constant_field("Kh_meridional", kh_meridional, mesh=mesh)`
`fieldset.add_constant_field("Kh_meridional", kh_meridional)`
where mesh is either 'flat' or 'spherical'

This kernel assumes diffusivity gradients are zero and is therefore more efficient.
Expand Down
4 changes: 2 additions & 2 deletions tests/test_diffusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def test_fieldKh_Brownian(mesh):
ds["lon"].data = np.array([-1e6, 1e6])
ds["lat"].data = np.array([-1e6, 1e6])
fieldset = FieldSet.from_sgrid_conventions(ds, mesh=mesh)
fieldset.add_constant_field("Kh_zonal", kh_zonal, mesh=mesh)
fieldset.add_constant_field("Kh_meridional", kh_meridional, mesh=mesh)
fieldset.add_constant_field("Kh_zonal", kh_zonal)
fieldset.add_constant_field("Kh_meridional", kh_meridional)

npart = 100
runtime = np.timedelta64(2, "h")
Expand Down
10 changes: 5 additions & 5 deletions tests/test_fieldset.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def fieldset_two_models():
fset2 = FieldSet.from_sgrid_conventions(ds2, mesh="flat", vector_fields={"UV_wind": ("U_wind", "V_wind")})
fset2.add_context("my_value", 2.0)
fset2.add_context("my_list", [1, 2, "hello"])
fset2.add_constant_field("constant_field", 3.0, mesh="flat")
fset2.add_constant_field("constant_field", 3.0)
return fset1 + fset2


Expand Down Expand Up @@ -74,7 +74,7 @@ def test_fieldset_add_context_invalid_name(fieldset, name):


def test_fieldset_add_constant_field(fieldset):
fieldset.add_constant_field("test_constant_field", 1.0, mesh="flat")
fieldset.add_constant_field("test_constant_field", 1.0)

# Get a point in the domain
time = ds["time"].mean()
Expand All @@ -91,7 +91,7 @@ def test_fieldset_gridset(fieldset):
assert fieldset.fields["UV"].grid in fieldset.gridset
assert len(fieldset.gridset) == 1

fieldset.add_constant_field("constant_field", 1.0, mesh="flat")
fieldset.add_constant_field("constant_field", 1.0)
assert len(fieldset.gridset) == 2


Expand Down Expand Up @@ -238,7 +238,7 @@ def test_multi_model_time_interval():
ds3["time"] = (ds3["time"].dims, ds3["time"].data + np.timedelta64(timedelta(days=2)), ds3["time"].attrs)
fieldset += FieldSet.from_sgrid_conventions(ds3, mesh="flat")

fieldset.add_constant_field("constant_field", 1.0, mesh="flat")
fieldset.add_constant_field("constant_field", 1.0)

assert len(fieldset.models) == 3
assert fieldset.constant_model is not None
Expand All @@ -258,7 +258,7 @@ def test_multi_model_nonoverlapping_time_interval():
ds3["time"] = (ds3["time"].dims, ds3["time"].data + np.timedelta64(timedelta(days=2000)), ds3["time"].attrs)
fieldset += FieldSet.from_sgrid_conventions(ds3, mesh="flat")

fieldset.add_constant_field("constant_field", 1.0, mesh="flat")
fieldset.add_constant_field("constant_field", 1.0)

assert len(fieldset.models) == 3
assert fieldset.constant_model is not None
Expand Down