Skip to content

Commit befa261

Browse files
Revert "Remove add constant method" (#2503)
1 parent 7e9b7da commit befa261

File tree

11 files changed

+59
-24
lines changed

11 files changed

+59
-24
lines changed

docs/user_guide/examples/tutorial_Argofloats.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@
131131
"# Convert to SGRID-compliant dataset and create FieldSet\n",
132132
"ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n",
133133
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
134-
"fieldset.constants[\"mindepth\"] = 1.0\n",
134+
"fieldset.add_constant(\"mindepth\", 1.0)\n",
135135
"\n",
136136
"# Define a new Particle type including extra Variables\n",
137137
"ArgoParticle = parcels.Particle.add_variable(\n",

docs/user_guide/examples/tutorial_croco_3D.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
9292
"\n",
9393
"# Add the critical depth (`hc`) as a constant to the fieldset\n",
94-
"fieldset.constants[\"hc\"] = ds_fields.hc.item()"
94+
"fieldset.add_constant(\"hc\", ds_fields.hc.item())"
9595
]
9696
},
9797
{
@@ -204,7 +204,7 @@
204204
"source": [
205205
"fieldset_noW = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
206206
"fieldset_noW.W.data[:] = 0.0\n",
207-
"fieldset_noW.constants[\"hc\"] = ds_fields.hc.item()\n",
207+
"fieldset_noW.add_constant(\"hc\", ds_fields.hc.item())\n",
208208
"\n",
209209
"X, Z = np.meshgrid(\n",
210210
" [40e3, 80e3, 120e3],\n",

docs/user_guide/examples/tutorial_diffusion.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"\n",
9191
"Just like velocities, diffusivities are passed to Parcels in the form of `Field` objects. When using `DiffusionUniformKh`, they should be added to the `FieldSet` object as constant fields, e.g. `fieldset.add_constant_field(\"Kh_zonal\", 1, mesh=\"flat\")`.\n",
9292
"\n",
93-
"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.constants[\"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",
93+
"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_constant(\"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",
9494
"\n",
9595
"## Example: Impermeable Diffusivity Profile\n",
9696
"\n",
@@ -206,7 +206,7 @@
206206
"source": [
207207
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds, mesh=\"flat\")\n",
208208
"fieldset.add_constant_field(\"Kh_zonal\", 1, mesh=\"flat\")\n",
209-
"fieldset.constants[\"dres\"] = 0.00005"
209+
"fieldset.add_constant(\"dres\", 0.00005)"
210210
]
211211
},
212212
{
@@ -537,7 +537,7 @@
537537
")\n",
538538
"fieldset.add_field(cell_areas_field)\n",
539539
"\n",
540-
"fieldset.constants[\"Cs\"] = 0.1"
540+
"fieldset.add_constant(\"Cs\", 0.1)"
541541
]
542542
},
543543
{

docs/user_guide/v4-migration.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ Version 4 of Parcels is unreleased at the moment. The information in this migrat
2121
## FieldSet
2222

2323
- `interp_method` has to be an Interpolation function, instead of a string.
24-
- `add_constant` has been removed as a method. Instead, use direct dictionary assignment (`fieldset.constants[key] = value`)
2524

2625
## Particle
2726

src/parcels/_core/fieldset.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from parcels._core.field import Field, VectorField
1414
from parcels._core.utils import sgrid
15+
from parcels._core.utils.string import _assert_str_and_python_varname
1516
from parcels._core.utils.time import get_datetime_type_calendar
1617
from parcels._core.utils.time import is_compatible as datetime_is_compatible
1718
from parcels._core.uxgrid import UxGrid
@@ -152,6 +153,25 @@ def add_constant_field(self, name: str, value, mesh: Mesh = "spherical"):
152153
grid = XGrid(xgrid, mesh=mesh)
153154
self.add_field(Field(name, ds[name], grid, interp_method=XConstantField))
154155

156+
def add_constant(self, name, value):
157+
"""Add a constant to the FieldSet.
158+
159+
Parameters
160+
----------
161+
name : str
162+
Name of the constant
163+
value :
164+
Value of the constant
165+
166+
"""
167+
_assert_str_and_python_varname(name)
168+
169+
if name in self.constants:
170+
raise ValueError(f"FieldSet already has a constant with name '{name}'")
171+
if not isinstance(value, (float, np.floating, int, np.integer)):
172+
raise ValueError(f"FieldSet constants have to be of type float or int, got a {type(value)}")
173+
self.constants[name] = value
174+
155175
@property
156176
def gridset(self) -> list[BaseGrid]:
157177
grids = []

src/parcels/_core/kernel.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,29 +142,29 @@ def check_fieldsets_in_kernels(self, pyfunc): # TODO v4: this can go into anoth
142142
raise ValueError('ParticleClass requires a "next_dt" for AdvectionRK45 Kernel.')
143143
if not hasattr(self.fieldset, "RK45_tol"):
144144
warnings.warn(
145-
"Setting RK45 tolerance to 10 m. Set fieldset.constants['RK45_tol'] = [distance] to change.",
145+
"Setting RK45 tolerance to 10 m. Use fieldset.add_constant('RK45_tol', [distance]) to change.",
146146
KernelWarning,
147147
stacklevel=2,
148148
)
149-
self.fieldset.constants["RK45_tol"] = 10
149+
self.fieldset.add_constant("RK45_tol", 10)
150150
if self.fieldset.U.grid._mesh == "spherical":
151151
self.fieldset.RK45_tol /= (
152152
1852 * 60
153153
) # TODO does not account for zonal variation in meter -> degree conversion
154154
if not hasattr(self.fieldset, "RK45_min_dt"):
155155
warnings.warn(
156-
"Setting RK45 minimum timestep to 1 s. Set fieldset.constants['RK45_min_dt'] = [timestep] to change.",
156+
"Setting RK45 minimum timestep to 1 s. Use fieldset.add_constant('RK45_min_dt', [timestep]) to change.",
157157
KernelWarning,
158158
stacklevel=2,
159159
)
160-
self.fieldset.constants["RK45_min_dt"] = 1
160+
self.fieldset.add_constant("RK45_min_dt", 1)
161161
if not hasattr(self.fieldset, "RK45_max_dt"):
162162
warnings.warn(
163-
"Setting RK45 maximum timestep to 1 day. Set fieldset.constants['RK45_max_dt'] = [timestep] to change.",
163+
"Setting RK45 maximum timestep to 1 day. Use fieldset.add_constant('RK45_max_dt', [timestep]) to change.",
164164
KernelWarning,
165165
stacklevel=2,
166166
)
167-
self.fieldset.constants["RK45_max_dt"] = 60 * 60 * 24
167+
self.fieldset.add_constant("RK45_max_dt", 60 * 60 * 24)
168168

169169
def merge(self, kernel):
170170
if not isinstance(kernel, type(self)):

tests/test_advection.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,14 +297,14 @@ def test_moving_eddy(kernel, rtol):
297297
ds["Kh"] = (["time", "depth", "YG", "XG"], np.full(ds["U"].shape, 0))
298298
fieldset.add_field(Field("Kh", ds["Kh"], grid, interp_method=XLinear), "Kh_zonal")
299299
fieldset.add_field(Field("Kh", ds["Kh"], grid, interp_method=XLinear), "Kh_meridional")
300-
fieldset.constants["dres"] = 0.1
300+
fieldset.add_constant("dres", 0.1)
301301

302302
start_lon, start_lat, start_z = 12000, 12500, 12500
303303
dt = np.timedelta64(30, "m")
304304
endtime = np.timedelta64(1, "h")
305305

306306
if kernel == AdvectionRK45:
307-
fieldset.constants["RK45_tol"] = rtol
307+
fieldset.add_constant("RK45_tol", rtol)
308308

309309
pset = ParticleSet(
310310
fieldset, pclass=DEFAULT_PARTICLES[kernel], lon=start_lon, lat=start_lat, z=start_z, time=np.timedelta64(0, "s")
@@ -346,8 +346,8 @@ def test_decaying_moving_eddy(kernel, rtol):
346346
endtime = np.timedelta64(23, "h")
347347

348348
if kernel == AdvectionRK45:
349-
fieldset.constants["RK45_tol"] = rtol
350-
fieldset.constants["RK45_min_dt"] = 10 * 60
349+
fieldset.add_constant("RK45_tol", rtol)
350+
fieldset.add_constant("RK45_min_dt", 10 * 60)
351351

352352
pset = ParticleSet(
353353
fieldset, pclass=DEFAULT_PARTICLES[kernel], lon=start_lon, lat=start_lat, time=np.timedelta64(0, "s")
@@ -403,7 +403,7 @@ def test_stommelgyre_fieldset(kernel, rtol, grid_type):
403403
)
404404

405405
if kernel == AdvectionRK45:
406-
fieldset.constants["RK45_tol"] = rtol
406+
fieldset.add_constant("RK45_tol", rtol)
407407

408408
def UpdateP(particles, fieldset): # pragma: no cover
409409
particles.p = fieldset.P[particles.time, particles.z, particles.lat, particles.lon]
@@ -443,7 +443,7 @@ def test_peninsula_fieldset(kernel, rtol, grid_type):
443443
)
444444

445445
if kernel == AdvectionRK45:
446-
fieldset.constants["RK45_tol"] = rtol
446+
fieldset.add_constant("RK45_tol", rtol)
447447

448448
def UpdateP(particles, fieldset): # pragma: no cover
449449
particles.p = fieldset.P[particles.time, particles.z, particles.lat, particles.lon]

tests/test_diffusion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def test_fieldKh_SpatiallyVaryingDiffusion(mesh, kernel):
6464
ds["Kh_zonal"] = (["time", "depth", "YG", "XG"], np.full((2, 1, ydim, xdim), Kh))
6565
ds["Kh_meridional"] = (["time", "depth", "YG", "XG"], np.full((2, 1, ydim, xdim), Kh))
6666
fieldset = FieldSet.from_sgrid_conventions(ds, mesh=mesh)
67-
fieldset.constants["dres"] = float(ds["lon"][1] - ds["lon"][0])
67+
fieldset.add_constant("dres", float(ds["lon"][1] - ds["lon"][0]))
6868

6969
npart = 10000
7070

@@ -84,7 +84,7 @@ def test_randomexponential(lambd):
8484
npart = 1000
8585

8686
# Rate parameter for random.expovariate
87-
fieldset.constants["lambd"] = lambd
87+
fieldset.add_constant("lambd", lambd)
8888

8989
# Set random seed
9090
np.random.seed(1234)

tests/test_fieldset.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ def test_fieldset_init_wrong_types():
3636
FieldSet([1.0, 2.0, 3.0])
3737

3838

39+
def test_fieldset_add_constant(fieldset):
40+
fieldset.add_constant("test_constant", 1.0)
41+
assert fieldset.test_constant == 1.0
42+
43+
44+
def test_fieldset_add_constant_int_name(fieldset):
45+
with pytest.raises(TypeError, match="Expected a string for variable name, got int instead."):
46+
fieldset.add_constant(123, 1.0)
47+
48+
49+
@pytest.mark.parametrize("name", ["a b", "123", "while"])
50+
def test_fieldset_add_constant_invalid_name(fieldset, name):
51+
with pytest.raises(ValueError, match=r"Received invalid Python variable name.*"):
52+
fieldset.add_constant(name, 1.0)
53+
54+
3955
def test_fieldset_add_constant_field(fieldset):
4056
fieldset.add_constant_field("test_constant_field", 1.0)
4157

tests/test_particlefile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def test_variable_written_once():
189189
def test_pset_repeated_release_delayed_adding_deleting(fieldset, tmp_zarrfile, dt, maxvar):
190190
"""Tests that if particles are released and deleted based on age that resulting output file is correct."""
191191
npart = 10
192-
fieldset.constants["maxvar"] = maxvar
192+
fieldset.add_constant("maxvar", maxvar)
193193

194194
MyParticle = Particle.add_variable(
195195
[Variable("sample_var", initial=0.0), Variable("v_once", dtype=np.float64, initial=0.0, to_write="once")]

0 commit comments

Comments
 (0)