Skip to content

Commit f0f532c

Browse files
Merge branch 'master' into fix-mitgcm-time-dimension
2 parents 1f63b30 + 0136cb0 commit f0f532c

17 files changed

Lines changed: 149 additions & 53 deletions

environment_py2_linux.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ dependencies:
2323
- scipy>=0.16.0
2424
- six >=1.10.0
2525
- xarray>=0.5.1
26+
- netcdftime
27+
- cftime
2628
- pip:
2729
- pytest>=2.7.0
2830
- nbval

environment_py2_osx.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ dependencies:
2323
- scipy>=0.16.0
2424
- six>=1.10.0
2525
- xarray>=0.5.1
26+
- netcdftime
27+
- cftime
2628
- pip:
2729
- pytest>=2.7.0
2830
- nbval

environment_py2_win.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ dependencies:
2222
- scipy>=0.16.0
2323
- six>=1.10.0
2424
- xarray>=0.5.1
25+
- netcdftime
26+
- cftime
2527
- pip:
2628
- pytest>=2.7.0
2729
- nbval

environment_py3_linux.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ dependencies:
2222
- scipy>=0.16.0
2323
- six >=1.10.0
2424
- xarray>=0.5.1
25+
- netcdftime
26+
- cftime
2527
- pip:
2628
- pytest>=2.7.0
2729
- nbval

environment_py3_osx.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ dependencies:
2222
- scipy>=0.16.0
2323
- six>=1.10.0
2424
- xarray>=0.5.1
25+
- netcdftime
26+
- cftime
2527
- pip:
2628
- pytest>=2.7.0
2729
- nbval

environment_py3_win.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ dependencies:
2121
- scipy>=0.16.0
2222
- six>=1.10.0
2323
- xarray>=0.5.1
24+
- netcdftime
25+
- cftime
2426
- pip:
2527
- pytest>=2.7.0
2628
- nbval

parcels/examples/example_globcurrent.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def test_globcurrent_fieldset_advancetime(mode, dt, substart, subend, lonstart,
4949
fieldsetall = set_globcurrent_fieldset(files[0:10], full_load=True)
5050
psetall = ParticleSet.from_list(fieldset=fieldsetall, pclass=ptype[mode], lon=[lonstart], lat=[latstart])
5151
if dt < 0:
52-
psetsub[0].time = fieldsetsub.U.time[-1]
53-
psetall[0].time = fieldsetall.U.time[-1]
52+
psetsub[0].time = fieldsetsub.U.grid.time[-1]
53+
psetall[0].time = fieldsetall.U.grid.time[-1]
5454

5555
for i in irange:
5656
psetsub.execute(AdvectionRK4, runtime=delta(days=1), dt=dt)
@@ -108,7 +108,7 @@ def test_globcurrent_variable_fromfield(mode, dt):
108108

109109
class MyParticle(ptype[mode]):
110110
sample_var = Variable('sample_var', initial=fieldset.U)
111-
time = fieldset.U.time[0] if dt > 0 else fieldset.U.time[-1]
111+
time = fieldset.U.grid.time[0] if dt > 0 else fieldset.U.grid.time[-1]
112112
pset = ParticleSet(fieldset, pclass=MyParticle, lon=[25], lat=[-35], time=time)
113113

114114
pset.execute(AdvectionRK4, runtime=delta(days=1), dt=dt)

parcels/field.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from parcels.tools.loggers import logger
22
from parcels.tools.converters import unitconverters_map, UnitConverter, Geographic, GeographicPolar
3+
from parcels.tools.converters import TimeConverter
34
from parcels.tools.error import FieldSamplingError, TimeExtrapolationError
45
from collections import Iterable
56
from py import path
@@ -44,7 +45,7 @@ class Field(object):
4445
:param transpose: Transpose data to required (lon, lat) layout
4546
:param vmin: Minimum allowed value on the field. Data below this value are set to zero
4647
:param vmax: Maximum allowed value on the field. Data above this value are set to zero
47-
:param time_origin: Time origin (datetime or np.datetime64 object) of the time axis (only if grid is None)
48+
:param time_origin: Time origin (TimeConverter object) of the time axis (only if grid is None)
4849
:param interp_method: Method for interpolation. Either 'linear' or 'nearest'
4950
:param allow_time_extrapolation: boolean whether to allow for extrapolation in time
5051
(i.e. beyond the last available time snapshot)
@@ -57,6 +58,7 @@ def __init__(self, name, data, lon=None, lat=None, depth=None, time=None, grid=N
5758
interp_method='linear', allow_time_extrapolation=None, time_periodic=False, **kwargs):
5859
self.name = name
5960
self.data = data
61+
time_origin = TimeConverter(0) if time_origin is None else time_origin
6062
if grid:
6163
self.grid = grid
6264
else:
@@ -68,7 +70,6 @@ def __init__(self, name, data, lon=None, lat=None, depth=None, time=None, grid=N
6870
self.lon = self.grid.lon
6971
self.lat = self.grid.lat
7072
self.depth = self.grid.depth
71-
self.time = self.grid.time
7273
fieldtype = self.name if fieldtype is None else fieldtype
7374
if self.grid.mesh == 'flat' or (fieldtype not in unitconverters_map.keys()):
7475
self.units = UnitConverter()
@@ -181,15 +182,12 @@ def from_netcdf(cls, filenames, variable, dimensions, indices=None, grid=None,
181182
timeslices = np.array(timeslices)
182183
time = np.concatenate(timeslices)
183184
dataFiles = np.concatenate(np.array(dataFiles))
184-
if isinstance(time[0], np.datetime64):
185-
time_origin = time[0]
186-
time = (time - time_origin) / np.timedelta64(1, 's')
187-
else:
188-
time_origin = None
189-
assert(np.all((time[1:]-time[:-1]) > 0))
190-
191185
if time.size == 1 and time[0] is None:
192186
time[0] = 0
187+
time_origin = TimeConverter(time[0])
188+
time = time_origin.reltime(time)
189+
assert(np.all((time[1:]-time[:-1]) > 0))
190+
193191
if len(lon.shape) == 1:
194192
if len(depth.shape) == 1:
195193
grid = RectilinearZGrid(lon, lat, depth, time, time_origin=time_origin, mesh=mesh)
@@ -815,7 +813,7 @@ def write(self, filename, varname=None):
815813
else:
816814
raise NotImplementedError('Field.write only implemented for RectilinearZGrid and CurvilinearZGrid')
817815

818-
attrs = {'units': 'seconds since ' + str(self.grid.time_origin)} if self.grid.time_origin else {}
816+
attrs = {'units': 'seconds since ' + str(self.grid.time_origin)} if self.grid.time_origin.calendar else {}
819817
time_counter = xr.DataArray(self.grid.time,
820818
dims=['time_counter'],
821819
attrs=attrs)
@@ -842,9 +840,7 @@ def computeTimeChunk(self, data, tindex):
842840
with NetcdfFileBuffer(self.dataFiles[g.ti+tindex], self.dimensions, self.indices, self.netcdf_engine) as filebuffer:
843841
filebuffer.name = filebuffer.parse_name(self.dimensions, self.name)
844842
time_data = filebuffer.time
845-
if isinstance(time_data[0], np.datetime64):
846-
assert isinstance(time_data[0], type(g.time_origin)), ('Field %s stores times as dates, but time_origin is not defined ' % self.name)
847-
time_data = (time_data - g.time_origin) / np.timedelta64(1, 's')
843+
time_data = g.time_origin.reltime(time_data)
848844
ti = (time_data <= g.time[tindex]).argmin() - 1
849845
if len(filebuffer.dataset[filebuffer.name].shape) == 2:
850846
data[tindex, 0, :, :] = filebuffer.data[:, :]

parcels/fieldset.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from parcels.gridset import GridSet
33
from parcels.grid import RectilinearZGrid
44
from parcels.tools.loggers import logger
5+
from parcels.tools.converters import TimeConverter
56
import numpy as np
67
from os import path
78
from glob import glob
@@ -77,7 +78,7 @@ def from_data(cls, data, dimensions, transpose=False, mesh='spherical',
7778
lat = dims['lat']
7879
depth = np.zeros(1, dtype=np.float32) if 'depth' not in dims else dims['depth']
7980
time = np.zeros(1, dtype=np.float64) if 'time' not in dims else dims['time']
80-
grid = RectilinearZGrid(lon, lat, depth, time, mesh=mesh)
81+
grid = RectilinearZGrid(lon, lat, depth, time, time_origin=TimeConverter(), mesh=mesh)
8182

8283
fields[name] = Field(name, datafld, grid=grid, transpose=transpose,
8384
allow_time_extrapolation=allow_time_extrapolation, time_periodic=time_periodic, **kwargs)
@@ -124,11 +125,10 @@ def check_complete(self):
124125
if len(g.time) == 1:
125126
continue
126127
assert isinstance(g.time_origin, type(self.time_origin)), 'time origins of different grids must be have the same type'
127-
if g.time_origin:
128-
g.time = g.time + (g.time_origin - self.time_origin) / np.timedelta64(1, 's')
129-
if g.defer_load:
130-
g.time_full = g.time_full + (g.time_origin - self.time_origin) / np.timedelta64(1, 's')
131-
g.time_origin = self.time_origin
128+
g.time = g.time + self.time_origin.reltime(g.time_origin)
129+
if g.defer_load:
130+
g.time_full = g.time_full + self.time_origin.reltime(g.time_origin)
131+
g.time_origin = self.time_origin
132132
if not hasattr(self, 'UV'):
133133
if isinstance(self.U, SummedField):
134134
self.add_vector_field(SummedVectorField('UV', self.U, self.V))
@@ -184,7 +184,8 @@ def from_netcdf(cls, filenames, variables, dimensions, indices=None,
184184
if not isinstance(paths, list):
185185
paths = sorted(glob(str(paths)))
186186
if len(paths) == 0:
187-
raise IOError("FieldSet files not found: %s" % str(paths))
187+
notfound_paths = filenames[var] if type(filenames) is dict else filenames
188+
raise IOError("FieldSet files not found: %s" % str(notfound_paths))
188189
for fp in paths:
189190
if not path.exists(fp):
190191
raise IOError("FieldSet file not found: %s" % str(fp))

parcels/grid.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from parcels.tools.loggers import logger
2+
from parcels.tools.converters import TimeConverter
23
import numpy as np
34
from ctypes import Structure, c_int, c_float, c_double, POINTER, cast, c_void_p, pointer
45
from enum import IntEnum
5-
import datetime
66

77
__all__ = ['GridCode', 'RectilinearZGrid', 'RectilinearSGrid', 'CurvilinearZGrid', 'CurvilinearSGrid', 'CGrid']
88

@@ -39,11 +39,8 @@ def __init__(self, lon, lat, time, time_origin, mesh):
3939
assert isinstance(self.time[0], (np.integer, np.floating, float, int)), 'Time vector must be an array of int or floats'
4040
logger.warning_once("Casting time data to np.float64")
4141
self.time = self.time.astype(np.float64)
42-
self.time_origin = time_origin
43-
if self.time_origin:
44-
if isinstance(self.time_origin, datetime.datetime):
45-
self.time_origin = np.datetime64(self.time_origin)
46-
assert isinstance(self.time_origin, np.datetime64), 'If defined, time_origin must be a datetime.datetime or a np.datetime64'
42+
self.time_origin = TimeConverter() if time_origin is None else time_origin
43+
assert isinstance(self.time_origin, TimeConverter), 'time_origin needs to be a TimeConverter object'
4744
self.mesh = mesh
4845
self.cstruct = None
4946
self.cell_edge_sizes = {}
@@ -214,7 +211,7 @@ class RectilinearZGrid(RectilinearGrid):
214211
:param depth: Vector containing the vertical coordinates of the grid, which are z-coordinates.
215212
The depth of the different layers is thus constant.
216213
:param time: Vector containing the time coordinates of the grid
217-
:param time_origin: Time origin (datetime or np.datetime64 object) of the time axis
214+
:param time_origin: Time origin (TimeConverter object) of the time axis
218215
:param mesh: String indicating the type of mesh coordinates and
219216
units used during velocity interpolation:
220217
@@ -251,7 +248,7 @@ class RectilinearSGrid(RectilinearGrid):
251248
the number of the layer and the time is depth is a 4D array.
252249
depth array is either a 4D array[xdim][ydim][zdim][tdim] or a 3D array[xdim][ydim[zdim].
253250
:param time: Vector containing the time coordinates of the grid
254-
:param time_origin: Time origin (datetime or np.datetime64 object) of the time axis
251+
:param time_origin: Time origin (TimeConverter object) of the time axis
255252
:param mesh: String indicating the type of mesh coordinates and
256253
units used during velocity interpolation:
257254
@@ -343,7 +340,7 @@ class CurvilinearZGrid(CurvilinearGrid):
343340
:param depth: Vector containing the vertical coordinates of the grid, which are z-coordinates.
344341
The depth of the different layers is thus constant.
345342
:param time: Vector containing the time coordinates of the grid
346-
:param time_origin: Time origin (datetime or np.datetime64 object) of the time axis
343+
:param time_origin: Time origin (TimeConverter object) of the time axis
347344
:param mesh: String indicating the type of mesh coordinates and
348345
units used during velocity interpolation:
349346
@@ -379,7 +376,7 @@ class CurvilinearSGrid(CurvilinearGrid):
379376
the number of the layer and the time is depth is a 4D array.
380377
depth array is either a 4D array[xdim][ydim][zdim][tdim] or a 3D array[xdim][ydim[zdim].
381378
:param time: Vector containing the time coordinates of the grid
382-
:param time_origin: Time origin (datetime or np.datetime64 object) of the time axis
379+
:param time_origin: Time origin (TimeConverter object) of the time axis
383380
:param mesh: String indicating the type of mesh coordinates and
384381
units used during velocity interpolation:
385382

0 commit comments

Comments
 (0)