Skip to content

Commit 526af07

Browse files
Merge pull request #435 from OceanParcels/changing_FieldList_to_SummedFields
Changing FieldLists to SummedFields
2 parents fc1e93b + 88715a8 commit 526af07

5 files changed

Lines changed: 131 additions & 104 deletions

File tree

parcels/codegenerator.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from parcels.field import Field, VectorField
2-
from parcels.fieldset import FieldList, VectorFieldList
1+
from parcels.field import Field, VectorField, SummedField, SummedVectorField
32
from parcels.loggers import logger
43
import ast
54
import cgen as c
@@ -20,12 +19,12 @@ def __getattr__(self, attr):
2019
if isinstance(getattr(self.obj, attr), Field):
2120
return FieldNode(getattr(self.obj, attr),
2221
ccode="%s->%s" % (self.ccode, attr))
23-
elif isinstance(getattr(self.obj, attr), VectorFieldList):
24-
return VectorFieldListNode(getattr(self.obj, attr),
25-
ccode="%s->%s" % (self.ccode, attr))
26-
elif isinstance(getattr(self.obj, attr), FieldList) or isinstance(getattr(self.obj, attr), list):
27-
return FieldListNode(getattr(self.obj, attr),
28-
ccode="%s->%s" % (self.ccode, attr))
22+
elif isinstance(getattr(self.obj, attr), SummedVectorField):
23+
return SummedVectorFieldNode(getattr(self.obj, attr),
24+
ccode="%s->%s" % (self.ccode, attr))
25+
elif isinstance(getattr(self.obj, attr), SummedField) or isinstance(getattr(self.obj, attr), list):
26+
return SummedFieldNode(getattr(self.obj, attr),
27+
ccode="%s->%s" % (self.ccode, attr))
2928
elif isinstance(getattr(self.obj, attr), VectorField):
3029
return VectorFieldNode(getattr(self.obj, attr),
3130
ccode="%s->%s" % (self.ccode, attr))
@@ -60,24 +59,24 @@ def __init__(self, field, args, var, var2, var3):
6059
self.var3 = var3 # third variable for UVW interpolation
6160

6261

63-
class FieldListNode(IntrinsicNode):
62+
class SummedFieldNode(IntrinsicNode):
6463
def __getitem__(self, attr):
65-
return FieldListEvalNode(self.obj, attr)
64+
return SummedFieldEvalNode(self.obj, attr)
6665

6766

68-
class FieldListEvalNode(IntrinsicNode):
67+
class SummedFieldEvalNode(IntrinsicNode):
6968
def __init__(self, fields, args, var):
7069
self.fields = fields
7170
self.args = args
7271
self.var = var # the variable in which the interpolated field is written
7372

7473

75-
class VectorFieldListNode(IntrinsicNode):
74+
class SummedVectorFieldNode(IntrinsicNode):
7675
def __getitem__(self, attr):
77-
return VectorFieldListEvalNode(self.obj, attr)
76+
return SummedVectorFieldEvalNode(self.obj, attr)
7877

7978

80-
class VectorFieldListEvalNode(IntrinsicNode):
79+
class SummedVectorFieldEvalNode(IntrinsicNode):
8180
def __init__(self, field, args, var, var2, var3):
8281
self.field = field
8382
self.args = args
@@ -210,18 +209,18 @@ def visit_Subscript(self, node):
210209

211210
# If we encounter field evaluation we replace it with a
212211
# temporary variable and put the evaluation call on the stack.
213-
if isinstance(node.value, FieldListNode):
212+
if isinstance(node.value, SummedFieldNode):
214213
tmp = [self.get_tmp() for _ in node.value.obj]
215214
# Insert placeholder node for field eval ...
216-
self.stmt_stack += [FieldListEvalNode(node.value, node.slice, tmp)]
215+
self.stmt_stack += [SummedFieldEvalNode(node.value, node.slice, tmp)]
217216
# .. and return the name of the temporary that will be populated
218217
return ast.Name(id='+'.join(tmp))
219-
elif isinstance(node.value, VectorFieldListNode):
218+
elif isinstance(node.value, SummedVectorFieldNode):
220219
tmp = [self.get_tmp() for _ in node.value.obj.U]
221220
tmp2 = [self.get_tmp() for _ in node.value.obj.U]
222221
tmp3 = [self.get_tmp() if node.value.obj.W else None for _ in node.value.obj.U]
223222
# Insert placeholder node for field eval ...
224-
self.stmt_stack += [VectorFieldListEvalNode(node.value, node.slice, tmp, tmp2, tmp3)]
223+
self.stmt_stack += [SummedVectorFieldEvalNode(node.value, node.slice, tmp, tmp2, tmp3)]
225224
# .. and return the name of the temporary that will be populated
226225
if all(tmp3):
227226
return ast.Tuple([ast.Name(id='+'.join(tmp)), ast.Name(id='+'.join(tmp2)), ast.Name(id='+'.join(tmp3))], ast.Load())
@@ -621,7 +620,7 @@ def visit_FieldNode(self, node):
621620
"""Record intrinsic fields used in kernel"""
622621
self.field_args[node.obj.name] = node.obj
623622

624-
def visit_FieldListNode(self, node):
623+
def visit_SummedFieldNode(self, node):
625624
"""Record intrinsic fields used in kernel"""
626625
for fld in node.obj:
627626
self.field_args[fld.name] = fld
@@ -630,7 +629,7 @@ def visit_VectorFieldNode(self, node):
630629
"""Record intrinsic fields used in kernel"""
631630
self.vector_field_args[node.obj.name] = node.obj
632631

633-
def visit_VectorFieldListNode(self, node):
632+
def visit_SummedVectorFieldNode(self, node):
634633
"""Record intrinsic fields used in kernel"""
635634
for fld in node.obj.U:
636635
self.field_args[fld.name] = fld
@@ -672,7 +671,7 @@ def visit_VectorFieldEvalNode(self, node):
672671
node.ccode = c.Block([c.Assign("err", ccode_eval),
673672
conv_stat, c.Statement("CHECKERROR(err)")])
674673

675-
def visit_FieldListEvalNode(self, node):
674+
def visit_SummedFieldEvalNode(self, node):
676675
self.visit(node.fields)
677676
self.visit(node.args)
678677
cstat = []
@@ -683,7 +682,7 @@ def visit_FieldListEvalNode(self, node):
683682
cstat += [c.Assign("err", ccode_eval), conv_stat, c.Statement("CHECKERROR(err)")]
684683
node.ccode = c.Block(cstat)
685684

686-
def visit_VectorFieldListEvalNode(self, node):
685+
def visit_SummedVectorFieldEvalNode(self, node):
687686
self.visit(node.field)
688687
self.visit(node.args)
689688
cstat = []

parcels/examples/tutorial_FieldLists.ipynb renamed to parcels/examples/tutorial_SummedFields.ipynb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"## Tutorial on how to combine different Fields for advection into a `FieldList` object"
7+
"## Tutorial on how to combine different Fields for advection into a `SummedField` object"
88
]
99
},
1010
{
@@ -13,9 +13,9 @@
1313
"source": [
1414
"In some oceanographic applications, you may want to advect particles using a combination of different velocity data sets. For example, particles at the surface are transported by a combination of geostrophic, Ekman and Stokes flow. And often, these flows are not even on the same grid.\n",
1515
"\n",
16-
"One option would be to write a `Kernel` that computes the movement of particles due to each of these flows. However, in Parcels it is possible to directly combine different flows (without interpolation) and feed them into the built-in `AdvectionRK4` kernel. For that, we use so-called `FieldList` objects.\n",
16+
"One option would be to write a `Kernel` that computes the movement of particles due to each of these flows. However, in Parcels it is possible to directly combine different flows (without interpolation) and feed them into the built-in `AdvectionRK4` kernel. For that, we use so-called `SummedField` objects.\n",
1717
"\n",
18-
"This tutorial shows how to use these `FieldLists` with a very idealised example. We start by importing the relevant modules."
18+
"This tutorial shows how to use these `SummedField` with a very idealised example. We start by importing the relevant modules."
1919
]
2020
},
2121
{
@@ -123,7 +123,7 @@
123123
"cell_type": "markdown",
124124
"metadata": {},
125125
"source": [
126-
"Now comes the trick of the `FieldLists`. We can simply define a new `FieldSet` with a list of different `Fields`, as in `U=[U, Ustokes]`."
126+
"Now comes the trick of the `SummedFields`. We can simply define a new `FieldSet` with a summation of different `Fields`, as in `U=U+Ustokes`."
127127
]
128128
},
129129
{
@@ -132,7 +132,7 @@
132132
"metadata": {},
133133
"outputs": [],
134134
"source": [
135-
"fieldset = FieldSet(U=[U, Ustokes], V=[V, Vstokes])"
135+
"fieldset = FieldSet(U=U+Ustokes, V=V+Vstokes)"
136136
]
137137
},
138138
{
@@ -177,9 +177,9 @@
177177
"cell_type": "markdown",
178178
"metadata": {},
179179
"source": [
180-
"What happens under the hood is that each `Field` in the `FieldList` is interpolated separately to the particle location, and that the different velocities are added in each step of the RK4 advection. So `FieldLists` are effortless to users\n",
180+
"What happens under the hood is that each `Field` in the `SummedField` is interpolated separately to the particle location, and that the different velocities are added in each step of the RK4 advection. So `SummedFields` are effortless to users\n",
181181
"\n",
182-
"Note that `FieldLists` work for any type of `Field`, not only for velocities. Any call to a `Field` interpolation (`fieldset.fld[time, lon, lat, depth]`) will return the sum of all `Fields` in the list if `fld` is a `FieldList`."
182+
"Note that `SummedFields` work for any type of `Field`, not only for velocities. Any call to a `Field` interpolation (`fieldset.fld[time, lon, lat, depth]`) will return the sum of all `Fields` in the list if `fld` is a `SummedField`."
183183
]
184184
},
185185
{
@@ -206,7 +206,7 @@
206206
"name": "python",
207207
"nbconvert_exporter": "python",
208208
"pygments_lexer": "ipython2",
209-
"version": "2.7.15"
209+
"version": "2.7.14"
210210
}
211211
},
212212
"nbformat": 4,

parcels/field.py

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
CurvilinearSGrid, CGrid, GridCode)
1212

1313

14-
__all__ = ['Field', 'VectorField', 'Geographic', 'GeographicPolar', 'GeographicSquare', 'GeographicPolarSquare']
14+
__all__ = ['Field', 'VectorField', 'SummedField', 'SummedVectorField',
15+
'Geographic', 'GeographicPolar', 'GeographicSquare', 'GeographicPolarSquare']
1516

1617

1718
class FieldSamplingError(RuntimeError):
@@ -164,7 +165,7 @@ class Field(object):
164165
2. flat: No conversion, lat/lon are assumed to be in m.
165166
:param grid: :class:`parcels.grid.Grid` object containing all the lon, lat depth, time
166167
mesh and time_origin information. Can be constructed from any of the Grid objects
167-
:param fieldtype: Type of Field to be used for UnitConverter when using FieldLists
168+
:param fieldtype: Type of Field to be used for UnitConverter when using SummedFields
168169
(either 'U', 'V', 'Kh_zonal', 'Kh_Meridional' or None)
169170
:param transpose: Transpose data to required (lon, lat) layout
170171
:param vmin: Minimum allowed value on the field. Data below this value are set to zero
@@ -976,6 +977,42 @@ def computeTimeChunk(self, data, tindex):
976977

977978
return data
978979

980+
def __add__(self, field):
981+
if isinstance(self, Field) and isinstance(field, Field):
982+
return SummedField([self, field])
983+
elif isinstance(field, SummedField):
984+
field.insert(0, self)
985+
return field
986+
987+
988+
class SummedField(list):
989+
"""Class SummedField is a list of Fields over which Field interpolation
990+
is summed. This can e.g. be used when combining multiple flow fields,
991+
where the total flow is the sum of all the individual flows.
992+
Note that the individual Fields can be on different Grids.
993+
Also note that, since SummedFields are lists, the individual Fields can
994+
still be queried through their list index (e.g. SummedField[1]).
995+
"""
996+
def eval(self, time, x, y, z, applyConversion=True):
997+
tmp = 0
998+
for fld in self:
999+
tmp += fld.eval(time, x, y, z, applyConversion)
1000+
return tmp
1001+
1002+
def __getitem__(self, key):
1003+
if isinstance(key, int):
1004+
return list.__getitem__(self, key)
1005+
else:
1006+
return self.eval(*key)
1007+
1008+
def __add__(self, field):
1009+
if isinstance(field, Field):
1010+
self.append(field)
1011+
elif isinstance(field, SummedField):
1012+
for fld in field:
1013+
self.append(fld)
1014+
return self
1015+
9791016

9801017
class VectorField(object):
9811018
"""Class VectorField stores 2 or 3 fields which defines together a vector field.
@@ -1150,6 +1187,49 @@ def ccode_eval(self, varU, varV, varW, U, V, W, t, x, y, z):
11501187
% (varU, varV, U.interp_method.upper())
11511188

11521189

1190+
class SummedVectorField(list):
1191+
"""Class SummedVectorField stores 2 or 3 SummedFields which defines together a vector field.
1192+
This enables to interpolate them as one single vector SummedField in the kernels.
1193+
1194+
:param name: Name of the vector field
1195+
:param U: SummedField defining the zonal component
1196+
:param V: SummedField defining the meridional component
1197+
:param W: SummedField defining the vertical component (default: None)
1198+
"""
1199+
1200+
def __init__(self, name, U, V, W=None):
1201+
self.name = name
1202+
self.U = U
1203+
self.V = V
1204+
self.W = W
1205+
1206+
def eval(self, time, x, y, z):
1207+
zonal = meridional = vertical = 0
1208+
if self.W is not None:
1209+
for (U, V, W) in zip(self.U, self.V, self.W):
1210+
vfld = VectorField(self.name, U, V, W)
1211+
vfld.fieldset = self.fieldset
1212+
(tmp1, tmp2, tmp3) = vfld.eval(time, x, y, z)
1213+
zonal += tmp1
1214+
meridional += tmp2
1215+
vertical += tmp3
1216+
return (zonal, meridional, vertical)
1217+
else:
1218+
for (U, V) in zip(self.U, self.V):
1219+
vfld = VectorField(self.name, U, V)
1220+
vfld.fieldset = self.fieldset
1221+
(tmp1, tmp2) = vfld.eval(time, x, y, z)
1222+
zonal += tmp1
1223+
meridional += tmp2
1224+
return (zonal, meridional)
1225+
1226+
def __getitem__(self, key):
1227+
if isinstance(key, int):
1228+
return list.__getitem__(self, key)
1229+
else:
1230+
return self.eval(*key)
1231+
1232+
11531233
class NetcdfFileBuffer(object):
11541234
""" Class that encapsulates and manages deferred access to file data. """
11551235

0 commit comments

Comments
 (0)