1414from parcels .grid import GridType
1515from parcels .interaction .interactionkernel import InteractionKernel
1616from parcels .kernel import Kernel
17- from parcels .particle import Particle
17+ from parcels .particle import Particle , Variable
1818from parcels .particledata import ParticleData , ParticleDataIterator
1919from parcels .particlefile import ParticleFile
2020from parcels .tools .converters import _get_cftime_calendars , convert_to_flat_array
@@ -54,6 +54,8 @@ class ParticleSet:
5454 Optional list of initial depth values for particles. Default is 0m
5555 time :
5656 Optional list of initial time values for particles. Default is fieldset.U.grid.time[0]
57+ repeatdt : datetime.timedelta or float, optional
58+ Optional interval on which to repeat the release of the ParticleSet. Either timedelta object, or float in seconds.
5759 lonlatdepth_dtype :
5860 Floating precision for lon, lat, depth particle coordinates.
5961 It is either np.float32 or np.float64. Default is np.float32 if fieldset.U.interp_method is 'linear'
@@ -82,12 +84,49 @@ def __init__(
8284 ** kwargs ,
8385 ):
8486 self .particledata = None
87+ self ._repeat_starttime = None
88+ self ._repeatlon = None
89+ self ._repeatlat = None
90+ self ._repeatdepth = None
91+ self ._repeatpclass = None
92+ self ._repeatkwargs = None
8593 self ._kernel = None
8694 self ._interaction_kernel = None
8795
8896 self .fieldset = fieldset
8997 self ._pclass = pclass
9098
99+ # ==== first: create a new subclass of the pclass that includes the required variables ==== #
100+ # ==== see dynamic-instantiation trick here: https://www.python-course.eu/python3_classes_and_type.php ==== #
101+ class_name = pclass .__name__
102+ array_class = None
103+ if class_name not in dir ():
104+
105+ def ArrayClass_init (self , * args , ** kwargs ):
106+ fieldset = kwargs .get ("fieldset" , None )
107+ ngrids = kwargs .get ("ngrids" , None )
108+ if type (self ).ngrids .initial < 0 :
109+ numgrids = ngrids
110+ if numgrids is None and fieldset is not None :
111+ numgrids = fieldset .gridset_size
112+ assert numgrids is not None , "Neither fieldsets nor number of grids are specified - exiting."
113+ type(self ).ngrids .initial = numgrids
114+ self .ngrids = type (self ).ngrids .initial
115+ if self .ngrids >= 0 :
116+ self .ei = np .zeros (self .ngrids , dtype = np .int32 )
117+ super (type (self ), self ).__init__ (* args , ** kwargs )
118+
119+ array_class_vdict = {
120+ "ngrids" : Variable ("ngrids" , dtype = np .int32 , to_write = False , initial = - 1 ),
121+ "ei" : Variable ("ei" , dtype = np .int32 , to_write = False ),
122+ "__init__" : ArrayClass_init ,
123+ }
124+ array_class = type (class_name , (pclass ,), array_class_vdict )
125+ else :
126+ array_class = locals ()[class_name ]
127+ # ==== dynamic re-classing completed ==== #
128+ _pclass = array_class
129+
91130 lon = np .empty (shape = 0 ) if lon is None else convert_to_flat_array (lon )
92131 lat = np .empty (shape = 0 ) if lat is None else convert_to_flat_array (lat )
93132
@@ -132,7 +171,7 @@ def __init__(
132171 ), f"{ kwvar } and positions (lon, lat, depth) don't have the same lengths."
133172
134173 self .particledata = ParticleData (
135- self . _pclass ,
174+ _pclass ,
136175 lon = lon ,
137176 lat = lat ,
138177 depth = depth ,
@@ -317,7 +356,9 @@ def populate_indices(self):
317356 self .particledata .data ["ei" ][:, i ] = idx # assumes that we are in the surface layer (zi=0)
318357
319358 @classmethod
320- def from_list (cls , fieldset , pclass , lon , lat , depth = None , time = None , lonlatdepth_dtype = None , ** kwargs ):
359+ def from_list (
360+ cls , fieldset , pclass , lon , lat , depth = None , time = None , repeatdt = None , lonlatdepth_dtype = None , ** kwargs
361+ ):
321362 """Initialise the ParticleSet from lists of lon and lat.
322363
323364 Parameters
@@ -334,6 +375,8 @@ def from_list(cls, fieldset, pclass, lon, lat, depth=None, time=None, lonlatdept
334375 Optional list of initial depth values for particles. Default is 0m
335376 time :
336377 Optional list of start time values for particles. Default is fieldset.U.time[0]
378+ repeatdt :
379+ Optional interval (in seconds) on which to repeat the release of the ParticleSet (Default value = None)
337380 lonlatdepth_dtype :
338381 Floating precision for lon, lat, depth particle coordinates.
339382 It is either np.float32 or np.float64. Default is np.float32 if fieldset.U.interp_method is 'linear'
@@ -349,6 +392,7 @@ def from_list(cls, fieldset, pclass, lon, lat, depth=None, time=None, lonlatdept
349392 lat = lat ,
350393 depth = depth ,
351394 time = time ,
395+ repeatdt = repeatdt ,
352396 lonlatdepth_dtype = lonlatdepth_dtype ,
353397 ** kwargs ,
354398 )
@@ -363,6 +407,7 @@ def from_line(
363407 size ,
364408 depth = None ,
365409 time = None ,
410+ repeatdt = None ,
366411 lonlatdepth_dtype = None ,
367412 ** kwargs ,
368413 ):
@@ -388,6 +433,8 @@ def from_line(
388433 Optional list of initial depth values for particles. Default is 0m
389434 time :
390435 Optional start time value for particles. Default is fieldset.U.time[0]
436+ repeatdt :
437+ Optional interval (in seconds) on which to repeat the release of the ParticleSet (Default value = None)
391438 lonlatdepth_dtype :
392439 Floating precision for lon, lat, depth particle coordinates.
393440 It is either np.float32 or np.float64. Default is np.float32 if fieldset.U.interp_method is 'linear'
@@ -404,6 +451,7 @@ def from_line(
404451 lat = lat ,
405452 depth = depth ,
406453 time = time ,
454+ repeatdt = repeatdt ,
407455 lonlatdepth_dtype = lonlatdepth_dtype ,
408456 ** kwargs ,
409457 )
@@ -480,6 +528,7 @@ def from_field(
480528 mode = "monte_carlo" ,
481529 depth = None ,
482530 time = None ,
531+ repeatdt = None ,
483532 lonlatdepth_dtype = None ,
484533 ):
485534 """Initialise the ParticleSet randomly drawn according to distribution from a field.
@@ -500,6 +549,8 @@ def from_field(
500549 Optional list of initial depth values for particles. Default is 0m
501550 time :
502551 Optional start time value for particles. Default is fieldset.U.time[0]
552+ repeatdt :
553+ Optional interval (in seconds) on which to repeat the release of the ParticleSet (Default value = None)
503554 lonlatdepth_dtype :
504555 Floating precision for lon, lat, depth particle coordinates.
505556 It is either np.float32 or np.float64. Default is np.float32 if fieldset.U.interp_method is 'linear'
@@ -515,11 +566,12 @@ def from_field(
515566 depth = depth ,
516567 time = time ,
517568 lonlatdepth_dtype = lonlatdepth_dtype ,
569+ repeatdt = repeatdt ,
518570 )
519571
520572 @classmethod
521573 def from_particlefile (
522- cls , fieldset , pclass , filename , restart = True , restarttime = None , lonlatdepth_dtype = None , ** kwargs
574+ cls , fieldset , pclass , filename , restart = True , restarttime = None , repeatdt = None , lonlatdepth_dtype = None , ** kwargs
523575 ):
524576 """Initialise the ParticleSet from a zarr ParticleFile.
525577 This creates a new ParticleSet based on locations of all particles written
@@ -540,13 +592,24 @@ def from_particlefile(
540592 time at which the Particles will be restarted. Default is the last time written.
541593 Alternatively, restarttime could be a time value (including np.datetime64) or
542594 a callable function such as np.nanmin. The last is useful when running with dt < 0.
595+ repeatdt : datetime.timedelta or float, optional
596+ Optional interval on which to repeat the release of the ParticleSet. Either timedelta object, or float in seconds.
543597 lonlatdepth_dtype :
544598 Floating precision for lon, lat, depth particle coordinates.
545599 It is either np.float32 or np.float64. Default is np.float32 if fieldset.U.interp_method is 'linear'
546600 and np.float64 if the interpolation method is 'cgrid_velocity'
547601 **kwargs :
548602 Keyword arguments passed to the particleset constructor.
549603 """
604+ if repeatdt is not None :
605+ warnings .warn (
606+ f"Note that the `repeatdt` argument is not retained from { filename } , and that "
607+ "setting a new repeatdt will start particles from the _new_ particle "
608+ "locations." ,
609+ ParticleSetWarning ,
610+ stacklevel = 2 ,
611+ )
612+
550613 pfile = xr .open_zarr (str (filename ))
551614 pfile_vars = [v for v in pfile .data_vars ]
552615
@@ -612,6 +675,7 @@ def from_particlefile(
612675 time = vars ["time" ],
613676 pid_orig = vars ["id" ],
614677 lonlatdepth_dtype = lonlatdepth_dtype ,
678+ repeatdt = repeatdt ,
615679 ** kwargs ,
616680 )
617681
0 commit comments