10
10
11
11
import parcels
12
12
from parcels ._compat import MPI
13
+ from parcels .tools ._helpers import deprecated , deprecated_made_private
13
14
from parcels .tools .warnings import FileWarning
14
15
15
16
__all__ = ["ParticleFile" ]
@@ -46,31 +47,24 @@ class ParticleFile:
46
47
ParticleFile object that can be used to write particle data to file
47
48
"""
48
49
49
- outputdt = None
50
- particleset = None
51
- parcels_mesh = None
52
- time_origin = None
53
- lonlatdepth_dtype = None
54
-
55
50
def __init__ (self , name , particleset , outputdt = np .inf , chunks = None , create_new_zarrfile = True ):
56
- self .outputdt = outputdt .total_seconds () if isinstance (outputdt , timedelta ) else outputdt
57
- self .chunks = chunks
58
- self .particleset = particleset
59
- self .parcels_mesh = "spherical"
51
+ self ._outputdt = outputdt .total_seconds () if isinstance (outputdt , timedelta ) else outputdt
52
+ self ._chunks = chunks
53
+ self ._particleset = particleset
54
+ self ._parcels_mesh = "spherical"
60
55
if self .particleset .fieldset is not None :
61
- self .parcels_mesh = self .particleset .fieldset .gridset .grids [0 ].mesh
62
- self .time_origin = self .particleset .time_origin
56
+ self ._parcels_mesh = self .particleset .fieldset .gridset .grids [0 ].mesh
63
57
self .lonlatdepth_dtype = self .particleset .particledata .lonlatdepth_dtype
64
- self .maxids = 0
65
- self .pids_written = {}
66
- self .create_new_zarrfile = create_new_zarrfile
67
- self .vars_to_write = {}
58
+ self ._maxids = 0
59
+ self ._pids_written = {}
60
+ self ._create_new_zarrfile = create_new_zarrfile
61
+ self ._vars_to_write = {}
68
62
for var in self .particleset .particledata .ptype .variables :
69
63
if var .to_write :
70
64
self .vars_to_write [var .name ] = var .dtype
71
- self .mpi_rank = MPI .COMM_WORLD .Get_rank () if MPI else 0
65
+ self ._mpi_rank = MPI .COMM_WORLD .Get_rank () if MPI else 0
72
66
self .particleset .fieldset ._particlefile = self
73
- self .analytical = False # Flag to indicate if ParticleFile is used for analytical trajectories
67
+ self ._is_analytical = False # Flag to indicate if ParticleFile is used for analytical trajectories
74
68
75
69
# Reset obs_written of each particle, in case new ParticleFile created for a ParticleSet
76
70
particleset .particledata .setallvardata ("obs_written" , 0 )
@@ -80,11 +74,11 @@ def __init__(self, name, particleset, outputdt=np.inf, chunks=None, create_new_z
80
74
"Conventions" : "CF-1.6/CF-1.7" ,
81
75
"ncei_template_version" : "NCEI_NetCDF_Trajectory_Template_v2.0" ,
82
76
"parcels_version" : parcels .__version__ ,
83
- "parcels_mesh" : self .parcels_mesh ,
77
+ "parcels_mesh" : self ._parcels_mesh ,
84
78
}
85
79
86
80
# Create dictionary to translate datatypes and fill_values
87
- self .fill_value_map = {
81
+ self ._fill_value_map = {
88
82
np .float16 : np .nan ,
89
83
np .float32 : np .nan ,
90
84
np .float64 : np .nan ,
@@ -103,23 +97,82 @@ def __init__(self, name, particleset, outputdt=np.inf, chunks=None, create_new_z
103
97
# But we need to handle incompatibility with MPI mode for now:
104
98
if MPI and MPI .COMM_WORLD .Get_size () > 1 :
105
99
raise ValueError ("Currently, MPI mode is not compatible with directly passing a Zarr store." )
106
- self . fname = name
100
+ fname = name
107
101
else :
108
102
extension = os .path .splitext (str (name ))[1 ]
109
103
if extension in [".nc" , ".nc4" ]:
110
104
raise RuntimeError (
111
105
"Output in NetCDF is not supported anymore. Use .zarr extension for ParticleFile name."
112
106
)
113
107
if MPI and MPI .COMM_WORLD .Get_size () > 1 :
114
- self . fname = os .path .join (name , f"proc{ self .mpi_rank :02d} .zarr" )
108
+ fname = os .path .join (name , f"proc{ self ._mpi_rank :02d} .zarr" )
115
109
if extension in [".zarr" ]:
116
110
warnings .warn (
117
- f"The ParticleFile name contains .zarr extension, but zarr files will be written per processor in MPI mode at { self . fname } " ,
111
+ f"The ParticleFile name contains .zarr extension, but zarr files will be written per processor in MPI mode at { fname } " ,
118
112
FileWarning ,
119
113
stacklevel = 2 ,
120
114
)
121
115
else :
122
- self .fname = name if extension in [".zarr" ] else f"{ name } .zarr"
116
+ fname = name if extension in [".zarr" ] else f"{ name } .zarr"
117
+ self ._fname = fname
118
+
119
+ @property
120
+ def create_new_zarrfile (self ):
121
+ return self ._create_new_zarrfile
122
+
123
+ @property
124
+ def outputdt (self ):
125
+ return self ._outputdt
126
+
127
+ @property
128
+ def chunks (self ):
129
+ return self ._chunks
130
+
131
+ @property
132
+ def particleset (self ):
133
+ return self ._particleset
134
+
135
+ @property
136
+ def fname (self ):
137
+ return self ._fname
138
+
139
+ @property
140
+ def vars_to_write (self ):
141
+ return self ._vars_to_write
142
+
143
+ @property
144
+ def time_origin (self ):
145
+ return self .particleset .time_origin
146
+
147
+ @property
148
+ @deprecated_made_private # TODO: Remove 6 months after v3.1.0
149
+ def parcels_mesh (self ):
150
+ return self ._parcels_mesh
151
+
152
+ @property
153
+ @deprecated_made_private # TODO: Remove 6 months after v3.1.0
154
+ def maxids (self ):
155
+ return self ._maxids
156
+
157
+ @property
158
+ @deprecated_made_private # TODO: Remove 6 months after v3.1.0
159
+ def pids_written (self ):
160
+ return self ._pids_written
161
+
162
+ @property
163
+ @deprecated_made_private # TODO: Remove 6 months after v3.1.0
164
+ def mpi_rank (self ):
165
+ return self ._mpi_rank
166
+
167
+ @property
168
+ @deprecated_made_private # TODO: Remove 6 months after v3.1.0
169
+ def fill_value_map (self ):
170
+ return self ._fill_value_map
171
+
172
+ @property
173
+ @deprecated_made_private # TODO: Remove 6 months after v3.1.0
174
+ def analytical (self ):
175
+ return self ._is_analytical
123
176
124
177
def _create_variables_attribute_dict (self ):
125
178
"""Creates the dictionary with variable attributes.
@@ -133,7 +186,7 @@ def _create_variables_attribute_dict(self):
133
186
"trajectory" : {
134
187
"long_name" : "Unique identifier for each particle" ,
135
188
"cf_role" : "trajectory_id" ,
136
- "_FillValue" : self .fill_value_map [np .int64 ],
189
+ "_FillValue" : self ._fill_value_map [np .int64 ],
137
190
},
138
191
"time" : {"long_name" : "" , "standard_name" : "time" , "units" : "seconds" , "axis" : "T" },
139
192
"lon" : {"long_name" : "" , "standard_name" : "longitude" , "units" : "degrees_east" , "axis" : "X" },
@@ -147,14 +200,17 @@ def _create_variables_attribute_dict(self):
147
200
for vname in self .vars_to_write :
148
201
if vname not in ["time" , "lat" , "lon" , "depth" , "id" ]:
149
202
attrs [vname ] = {
150
- "_FillValue" : self .fill_value_map [self .vars_to_write [vname ]],
203
+ "_FillValue" : self ._fill_value_map [self .vars_to_write [vname ]],
151
204
"long_name" : "" ,
152
205
"standard_name" : vname ,
153
206
"units" : "unknown" ,
154
207
}
155
208
156
209
return attrs
157
210
211
+ @deprecated (
212
+ "ParticleFile.metadata is a dictionary. Use `ParticleFile.metadata['key'] = ...` or other dictionary methods instead."
213
+ ) # TODO: Remove 6 months after v3.1.0
158
214
def add_metadata (self , name , message ):
159
215
"""Add metadata to :class:`parcels.particleset.ParticleSet`.
160
216
@@ -175,21 +231,25 @@ def _convert_varout_name(self, var):
175
231
else :
176
232
return var
177
233
178
- def write_once (self , var ):
234
+ @deprecated_made_private # TODO: Remove 6 months after v3.1.0
235
+ def write_once (self , * args , ** kwargs ):
236
+ return self ._write_once (* args , ** kwargs )
237
+
238
+ def _write_once (self , var ):
179
239
return self .particleset .particledata .ptype [var ].to_write == "once"
180
240
181
241
def _extend_zarr_dims (self , Z , store , dtype , axis ):
182
242
if axis == 1 :
183
- a = np .full ((Z .shape [0 ], self .chunks [1 ]), self .fill_value_map [dtype ], dtype = dtype )
243
+ a = np .full ((Z .shape [0 ], self .chunks [1 ]), self ._fill_value_map [dtype ], dtype = dtype )
184
244
obs = zarr .group (store = store , overwrite = False )["obs" ]
185
245
if len (obs ) == Z .shape [1 ]:
186
246
obs .append (np .arange (self .chunks [1 ]) + obs [- 1 ] + 1 )
187
247
else :
188
- extra_trajs = self .maxids - Z .shape [0 ]
248
+ extra_trajs = self ._maxids - Z .shape [0 ]
189
249
if len (Z .shape ) == 2 :
190
- a = np .full ((extra_trajs , Z .shape [1 ]), self .fill_value_map [dtype ], dtype = dtype )
250
+ a = np .full ((extra_trajs , Z .shape [1 ]), self ._fill_value_map [dtype ], dtype = dtype )
191
251
else :
192
- a = np .full ((extra_trajs ,), self .fill_value_map [dtype ], dtype = dtype )
252
+ a = np .full ((extra_trajs ,), self ._fill_value_map [dtype ], dtype = dtype )
193
253
Z .append (a , axis = axis )
194
254
zarr .consolidate_metadata (store )
195
255
@@ -221,11 +281,11 @@ def write(self, pset, time, indices=None):
221
281
222
282
if len (indices_to_write ) > 0 :
223
283
pids = pset .particledata .getvardata ("id" , indices_to_write )
224
- to_add = sorted (set (pids ) - set (self .pids_written .keys ()))
284
+ to_add = sorted (set (pids ) - set (self ._pids_written .keys ()))
225
285
for i , pid in enumerate (to_add ):
226
- self .pids_written [pid ] = self .maxids + i
227
- ids = np .array ([self .pids_written [p ] for p in pids ], dtype = int )
228
- self .maxids = len (self .pids_written )
286
+ self ._pids_written [pid ] = self ._maxids + i
287
+ ids = np .array ([self ._pids_written [p ] for p in pids ], dtype = int )
288
+ self ._maxids = len (self ._pids_written )
229
289
230
290
once_ids = np .where (pset .particledata .getvardata ("obs_written" , indices_to_write ) == 0 )[0 ]
231
291
if len (once_ids ) > 0 :
@@ -234,7 +294,7 @@ def write(self, pset, time, indices=None):
234
294
235
295
if self .create_new_zarrfile :
236
296
if self .chunks is None :
237
- self .chunks = (len (ids ), 1 )
297
+ self ._chunks = (len (ids ), 1 )
238
298
if pset ._repeatpclass is not None and self .chunks [0 ] < 1e4 :
239
299
warnings .warn (
240
300
f"ParticleFile chunks are set to { self .chunks } , but this may lead to "
@@ -243,37 +303,37 @@ def write(self, pset, time, indices=None):
243
303
FileWarning ,
244
304
stacklevel = 2 ,
245
305
)
246
- if (self .maxids > len (ids )) or (self .maxids > self .chunks [0 ]):
247
- arrsize = (self .maxids , self .chunks [1 ])
306
+ if (self ._maxids > len (ids )) or (self ._maxids > self .chunks [0 ]):
307
+ arrsize = (self ._maxids , self .chunks [1 ])
248
308
else :
249
309
arrsize = (len (ids ), self .chunks [1 ])
250
310
ds = xr .Dataset (
251
311
attrs = self .metadata ,
252
312
coords = {"trajectory" : ("trajectory" , pids ), "obs" : ("obs" , np .arange (arrsize [1 ], dtype = np .int32 ))},
253
313
)
254
314
attrs = self ._create_variables_attribute_dict ()
255
- obs = np .zeros ((self .maxids ), dtype = np .int32 )
315
+ obs = np .zeros ((self ._maxids ), dtype = np .int32 )
256
316
for var in self .vars_to_write :
257
317
varout = self ._convert_varout_name (var )
258
318
if varout not in ["trajectory" ]: # because 'trajectory' is written as coordinate
259
- if self .write_once (var ):
319
+ if self ._write_once (var ):
260
320
data = np .full (
261
321
(arrsize [0 ],),
262
- self .fill_value_map [self .vars_to_write [var ]],
322
+ self ._fill_value_map [self .vars_to_write [var ]],
263
323
dtype = self .vars_to_write [var ],
264
324
)
265
325
data [ids_once ] = pset .particledata .getvardata (var , indices_to_write_once )
266
326
dims = ["trajectory" ]
267
327
else :
268
328
data = np .full (
269
- arrsize , self .fill_value_map [self .vars_to_write [var ]], dtype = self .vars_to_write [var ]
329
+ arrsize , self ._fill_value_map [self .vars_to_write [var ]], dtype = self .vars_to_write [var ]
270
330
)
271
331
data [ids , 0 ] = pset .particledata .getvardata (var , indices_to_write )
272
332
dims = ["trajectory" , "obs" ]
273
333
ds [varout ] = xr .DataArray (data = data , dims = dims , attrs = attrs [varout ])
274
- ds [varout ].encoding ["chunks" ] = self .chunks [0 ] if self .write_once (var ) else self .chunks
334
+ ds [varout ].encoding ["chunks" ] = self .chunks [0 ] if self ._write_once (var ) else self .chunks
275
335
ds .to_zarr (self .fname , mode = "w" )
276
- self .create_new_zarrfile = False
336
+ self ._create_new_zarrfile = False
277
337
else :
278
338
# Either use the store that was provided directly or create a DirectoryStore:
279
339
if issubclass (type (self .fname ), zarr .storage .Store ):
@@ -284,9 +344,9 @@ def write(self, pset, time, indices=None):
284
344
obs = pset .particledata .getvardata ("obs_written" , indices_to_write )
285
345
for var in self .vars_to_write :
286
346
varout = self ._convert_varout_name (var )
287
- if self .maxids > Z [varout ].shape [0 ]:
347
+ if self ._maxids > Z [varout ].shape [0 ]:
288
348
self ._extend_zarr_dims (Z [varout ], store , dtype = self .vars_to_write [var ], axis = 0 )
289
- if self .write_once (var ):
349
+ if self ._write_once (var ):
290
350
if len (once_ids ) > 0 :
291
351
Z [varout ].vindex [ids_once ] = pset .particledata .getvardata (var , indices_to_write_once )
292
352
else :
0 commit comments