Skip to content

Commit

Permalink
integrate_nodes_to_master (#1034) - fixing the bug in the kernel gene…
Browse files Browse the repository at this point in the history
…ration when reusing kernels from particle sets that use different particle objects.
  • Loading branch information
CKehl committed Jul 13, 2021
1 parent 173ffc9 commit 9348ae9
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 209 deletions.
11 changes: 7 additions & 4 deletions parcels/collection/collectionaos.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,16 @@ def __init__(self, pclass, lon, lat, depth, time, lonlatdepth_dtype, pid_orig, p
continue

if isinstance(v.initial, Field):
init_field = v.initial
if init_field.grid.ti != 0:
init_field.fieldset.computeTimeChunk(time[0], 0)
for i in range(self.ncount):
init_time = time[i] if time is not None and len(time) > 0 and np.count_nonzero([tval is not None for tval in time]) == len(time) else 0
init_field = v.initial
init_field.fieldset.computeTimeChunk(init_time, 0)
# v.initial.fieldset.computeTimeChunk(time[i], 0)
# v.initial.fieldset.computeTimeChunk(time[0], 0)
if (time[i] is None) or (np.isnan(time[i])):
raise RuntimeError('Cannot initialise a Variable with a Field if no time provided (time-type: {} values: {}). Add a "time=" to ParticleSet construction'.format(type(time), time))
setattr(self._data[i], v.name, init_field[time[i], depth[i], lat[i], lon[i]])
# setattr(self._data[i], v.name, init_field[time[i], depth[i], lat[i], lon[i]])
setattr(self._data[i], v.name, init_field[init_time, depth[i], lat[i], lon[i]])
logger.warning_once("Particle initialisation from field can be very slow as it is computed in scipy mode.")

if v not in initialised:
Expand Down
10 changes: 5 additions & 5 deletions parcels/collection/collectionnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,18 +187,18 @@ def __init__(self, idgen, c_lib_register, pclass, lon, lat, depth, time, lonlatd
if v.name in initialised:
continue
if isinstance(v.initial, Field):
init_time = np.min(time) if time is not None and len(time) > 0 and np.count_nonzero([tval is not None for tval in time]) == len(time) is not None else 0
init_field = v.initial
if init_field.grid.ti != 0:
init_field.fieldset.computeTimeChunk(init_time, 0)
i = 0
ndata = self.begin()
while i < len(self._data):
pdata = ndata.data
# ==== ==== ==== #
if (pdata.time is None) or (np.isnan(pdata.time)):
raise RuntimeError('Cannot initialise a Variable with a Field if no time provided (time-type: {} values: {}). Add a "time=" to ParticleSet construction'.format(type(time), time))
setattr(pdata, v.name, init_field[pdata.time, pdata.depth, pdata.lat, pdata.lon])
init_time = pdata.time if pdata.time not in [None, np.nan] and np.count_nonzero([tval is not None for tval in time]) == len(time) else 0
init_field = v.initial
init_field.fieldset.computeTimeChunk(init_time, 0)
# setattr(pdata, v.name, init_field[pdata.time, pdata.depth, pdata.lat, pdata.lon])
setattr(pdata, v.name, init_field[init_time, pdata.depth, pdata.lat, pdata.lon])
logger.warning_once("Particle initialisation from field can be very slow as it is computed in scipy mode.")
# ==== ==== ==== #
ndata.set_data(pdata)
Expand Down
11 changes: 7 additions & 4 deletions parcels/collection/collectionsoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,13 @@ def __init__(self, pclass, lon, lat, depth, time, lonlatdepth_dtype, pid_orig, p
for i in range(self.ncount):
if (time[i] is None) or (np.isnan(time[i])):
raise RuntimeError('Cannot initialise a Variable with a Field if no time provided (time-type: {} values: {}). Add a "time=" to ParticleSet construction'.format(type(time), time))
v.initial.fieldset.computeTimeChunk(time[i], 0)
self._data[v.name][i] = v.initial[
time[i], depth[i], lat[i], lon[i]
]
init_time = time[i] if time is not None and len(time) > 0 and np.count_nonzero([tval is not None for tval in time]) == len(time) else 0
init_field = v.initial
init_field.fieldset.computeTimeChunk(init_time, 0)
# v.initial.fieldset.computeTimeChunk(time[i], 0)
# v.initial.fieldset.computeTimeChunk(time[0], 0)
# self._data[v.name][i] = v.initial[time[i], depth[i], lat[i], lon[i]]
self._data[v.name][i] = v.initial[init_time, depth[i], lat[i], lon[i]]
logger.warning_once("Particle initialisation from field can be very slow as it is computed in scipy mode.")
elif isinstance(v.initial, attrgetter):
self._data[v.name][:] = v.initial(self)
Expand Down
4 changes: 2 additions & 2 deletions parcels/compilation/codecompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ def __init__(self, cppargs=None, ldargs=None, incdirs=None, libdirs=None, libs=N
self._cppargs += Iflags
self._cppargs += opt_flags + cppargs + arch_flag
self._ldargs = ['-shared']
if len(Lflags) > 0:
self._ldargs += ['-Wl, -rpath=%s' % (":".join(libdirs))]
if len(Lflags) > 0:
self._ldargs += Lflags
if len(lflags) > 0:
self._ldargs += lflags
self._ldargs += ldargs
# if len(Lflags) > 0:
# self._ldargs += ['-Wl, -rpath=%s' % (":".join(libdirs))]
self._ldargs += arch_flag
self._incdirs = incdirs
self._libdirs = libdirs
Expand Down
2 changes: 1 addition & 1 deletion parcels/compilation/codegenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,7 @@ def generate(self, funcname, field_args, const_args, kernel_ast, c_include):
update_pdt = c.If("_next_dt_set == 1",
c.Block([c.Assign("_next_dt_set", "0"), c.Assign("particles->dt[pnum]", "_next_dt")]))

dt_pos = c.If("fabs(endtime - particles->time[pnum])<fabs(particles->dt[pnum])",
dt_pos = c.If("fabs(endtime - particles->time[pnum]) < fabs(particles->dt[pnum])",
c.Block([c.Assign("__dt", "fabs(endtime - particles->time[pnum])"), c.Assign("reset_dt", "1")]),
c.Block([c.Assign("__dt", "fabs(particles->dt[pnum])"), c.Assign("reset_dt", "0")]))
reset_dt = c.If("(reset_dt == 1) && is_equal_dbl(__pdt_prekernels, particles->dt[pnum])",
Expand Down
288 changes: 153 additions & 135 deletions parcels/examples/tutorial_sampling.ipynb

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions parcels/kernel/basekernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ def __del__(self):
self.const_args = None
self.funcvars = None
self.funccode = None
self.py_ast = None
self.dyn_srcs = None
self.static_srcs = None
self._src_files = None
self.lib_file = None
self.log_file = None

@property
def ptype(self):
Expand Down
26 changes: 20 additions & 6 deletions parcels/kernel/kernelaos.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,29 +127,43 @@ def __del__(self):
super(KernelAOS, self).__del__()

def __add__(self, kernel):
if not isinstance(kernel, KernelAOS):
kernel = KernelAOS(self.fieldset, self.ptype, pyfunc=kernel)
return self.merge(kernel, KernelAOS)
mkernel = kernel # do this to avoid rewriting the object put in as parameter
if not isinstance(mkernel, BaseKernel):
mkernel = KernelAOS(self.fieldset, self.ptype, pyfunc=kernel)
elif not isinstance(mkernel, KernelAOS) and kernel.pyfunc is not None:
mkernel = KernelAOS(self.fieldset, self.ptype, pyfunc=mkernel.pyfunc)
return self.merge(mkernel, KernelAOS)

def __radd__(self, kernel):
if not isinstance(kernel, KernelAOS):
kernel = KernelAOS(self.fieldset, self.ptype, pyfunc=kernel)
return kernel.merge(self, KernelAOS)
mkernel = kernel # do this to avoid rewriting the object put in as parameter
if not isinstance(mkernel, BaseKernel):
mkernel = KernelAOS(self.fieldset, self.ptype, pyfunc=kernel)
elif not isinstance(mkernel, KernelAOS) and kernel.pyfunc is not None:
mkernel = KernelAOS(self.fieldset, self.ptype, pyfunc=mkernel.pyfunc)
return mkernel.merge(self, KernelAOS)

def execute_jit(self, pset, endtime, dt):
"""Invokes JIT engine to perform the core update loop"""
# logger.info("Loading fieldset data into jit.")
self.load_fieldset_jit(pset)
# logger.info("Fieldset loaded.")

# logger.info("Adding struct-params for field args ...")
fargs = []
if self.field_args is not None:
fargs += [byref(f.ctypes_struct) for f in self.field_args.values()]
# logger.info("Added struct-params for field args.")
# logger.info("Adding double-params for const args ...")
if self.const_args is not None:
fargs += [c_double(f) for f in self.const_args.values()]
# logger.info("Added double-params for const args.")

pdata = pset.ctypes_struct
if len(fargs) > 0:
# logger.info("Executing kernel with field args")
self._function(c_int(len(pset)), pdata, c_double(endtime), c_double(dt), *fargs)
else:
# logger.info("Executing kernel without field args")
self._function(c_int(len(pset)), pdata, c_double(endtime), c_double(dt))

def execute_python(self, pset, endtime, dt):
Expand Down
18 changes: 12 additions & 6 deletions parcels/kernel/kernelnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,20 @@ def __del__(self):
super(KernelNodes, self).__del__()

def __add__(self, kernel):
if not isinstance(kernel, KernelNodes):
kernel = KernelNodes(self.fieldset, self.ptype, pyfunc=kernel)
return self.merge(kernel, KernelNodes)
mkernel = kernel # do this to avoid rewriting the object put in as parameter
if not isinstance(mkernel, BaseKernel):
mkernel = KernelNodes(self.fieldset, self.ptype, pyfunc=kernel)
elif not isinstance(mkernel, KernelNodes) and kernel.pyfunc is not None:
mkernel = KernelNodes(self.fieldset, self.ptype, pyfunc=mkernel.pyfunc)
return self.merge(mkernel, KernelNodes)

def __radd__(self, kernel):
if not isinstance(kernel, KernelNodes):
kernel = KernelNodes(self.fieldset, self.ptype, pyfunc=kernel)
return kernel.merge(self, KernelNodes)
mkernel = kernel # do this to avoid rewriting the object put in as parameter
if not isinstance(mkernel, BaseKernel):
mkernel = KernelNodes(self.fieldset, self.ptype, pyfunc=kernel)
elif not isinstance(mkernel, KernelNodes) and kernel.pyfunc is not None:
mkernel = KernelNodes(self.fieldset, self.ptype, pyfunc=mkernel.pyfunc)
return mkernel.merge(self, KernelNodes)

def execute_jit(self, pset, endtime, dt):
"""Invokes JIT engine to perform the core update loop"""
Expand Down
59 changes: 41 additions & 18 deletions parcels/kernel/kernelsoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ctypes import byref
from ctypes import c_double
from ctypes import c_int
from ctypes import pointer
from os import path

import numpy as np
Expand Down Expand Up @@ -87,13 +88,14 @@ def __init__(self, fieldset, ptype, pyfunc=None, funcname=None,
assert numkernelargs == 3, \
'Since Parcels v2.0, kernels do only take 3 arguments: particle, fieldset, time !! AND !! Argument order in field interpolation is time, depth, lat, lon.'

self.name = "%s%s" % (ptype.name, self.funcname)
# self.name = "%s%s" % (ptype.name, self.funcname)
self.name = "%s%s" % (self.ptype.name, self.funcname)

# Generate the kernel function and add the outer loop
if self.ptype.uses_jit:
kernelgen = KernelGenerator(fieldset, ptype)
kernel_ccode = kernelgen.generate(deepcopy(self.py_ast),
self.funcvars)
# kernelgen = KernelGenerator(fieldset, ptype)
kernelgen = KernelGenerator(self.fieldset, self.ptype)
kernel_ccode = kernelgen.generate(deepcopy(self.py_ast), self.funcvars)
self.field_args = kernelgen.field_args
self.vector_field_args = kernelgen.vector_field_args
fieldset = self.fieldset
Expand All @@ -105,6 +107,7 @@ def __init__(self, fieldset, ptype, pyfunc=None, funcname=None,
self.field_args[sF_name] = getattr(f, sF_component)
self.const_args = kernelgen.const_args
loopgen = LoopGenerator(fieldset, ptype)
# loopgen = LoopGenerator(self.fieldset, self.ptype)
if path.isfile(self._c_include):
with open(self._c_include, 'r') as f:
c_include_str = f.read()
Expand All @@ -120,18 +123,48 @@ def __init__(self, fieldset, ptype, pyfunc=None, funcname=None,
# else:
# self.src_file = src_file_or_files

def __add__(self, kernel):
mkernel = kernel # do this to avoid rewriting the object put in as parameter
if not isinstance(mkernel, BaseKernel):
mkernel = KernelSOA(self.fieldset, self.ptype, pyfunc=kernel)
elif not isinstance(mkernel, KernelSOA) and kernel.pyfunc is not None:
mkernel = KernelSOA(self.fieldset, self.ptype, pyfunc=mkernel.pyfunc)
return self.merge(mkernel, KernelSOA)

def __radd__(self, kernel):
mkernel = kernel # do this to avoid rewriting the object put in as parameter
if not isinstance(mkernel, BaseKernel):
mkernel = KernelSOA(self.fieldset, self.ptype, pyfunc=kernel)
elif not isinstance(mkernel, KernelSOA) and kernel.pyfunc is not None:
mkernel = KernelSOA(self.fieldset, self.ptype, pyfunc=mkernel.pyfunc)
return mkernel.merge(self, KernelSOA)

def generate_sources(self):
pass

def execute_jit(self, pset, endtime, dt):
"""Invokes JIT engine to perform the core update loop"""
# logger.info("Loading fieldset data into jit.")
self.load_fieldset_jit(pset)
# logger.info("Fieldset loaded.")


#fargs += [c_double(f) for f in self.const_args.values()]
# logger.info("Adding struct-params for field args ...")
fargs = []
if self.field_args is not None:
fargs += [byref(f.ctypes_struct) for f in self.field_args.values()]
# logger.info("Added struct-params for field args.")
# logger.info("Adding double-params for const args ...")
if self.const_args is not None:
fargs += [c_double(f) for f in self.const_args.values()]
# logger.info("Added double-params for const args.")

fargs = [byref(f.ctypes_struct) for f in self.field_args.values()]
fargs += [c_double(f) for f in self.const_args.values()]
particle_data = byref(pset.ctypes_struct)
return self._function(c_int(len(pset)), particle_data,
c_double(endtime), c_double(dt), *fargs)
# logger.info("Executing kernel with field args.")
result = self._function(c_int(len(pset)), particle_data, c_double(endtime), c_double(dt), *fargs)
# logger.info("Finished kernel execution.")
return result

def execute_python(self, pset, endtime, dt):
"""Performs the core update loop via Python"""
Expand Down Expand Up @@ -160,16 +193,6 @@ def __del__(self):
# naming scheme which is required on Windows OS'es to deal with updates to a Parcels' kernel.)
super(KernelSOA, self).__del__()

def __add__(self, kernel):
if not isinstance(kernel, KernelSOA):
kernel = KernelSOA(self.fieldset, self.ptype, pyfunc=kernel)
return self.merge(kernel, KernelSOA)

def __radd__(self, kernel):
if not isinstance(kernel, KernelSOA):
kernel = KernelSOA(self.fieldset, self.ptype, pyfunc=kernel)
return kernel.merge(self, KernelSOA)

def remove_deleted(self, pset, output_file, endtime):
"""
Utility to remove all particles that signalled deletion
Expand Down
10 changes: 6 additions & 4 deletions parcels/particle.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,13 @@ def set_lonlatdepth_dtype(cls, dtype):
cls.depth.dtype = dtype

def update_next_dt(self, next_dt=None):
if next_dt is None or np.isnan(next_dt):
if self._next_dt is not None and not np.isnan(self._next_dt):
# if next_dt is None or np.isnan(next_dt):
if next_dt is None:
# if self._next_dt is not None and not np.isnan(self._next_dt):
if self._next_dt is not None:
self.dt = self._next_dt
# self._next_dt = None
self._next_dt = np.nan
self._next_dt = None
# self._next_dt = np.nan
else:
self._next_dt = next_dt

Expand Down
Loading

0 comments on commit 9348ae9

Please sign in to comment.