Beispiel #1
0
    def from_data(cls, data, dimensions, transpose=False, mesh='spherical',
                  allow_time_extrapolation=None, time_periodic=False, **kwargs):
        """Initialise FieldSet object from raw data

        :param data: Dictionary mapping field names to numpy arrays.
               Note that at least a 'U' and 'V' numpy array need to be given, and that
               the built-in Advection kernels assume that U and V are in m/s

               1. If data shape is [xdim, ydim], [xdim, ydim, zdim], [xdim, ydim, tdim] or [xdim, ydim, zdim, tdim],
                  whichever is relevant for the dataset, use the flag transpose=True
               2. If data shape is [ydim, xdim], [zdim, ydim, xdim], [tdim, ydim, xdim] or [tdim, zdim, ydim, xdim],
                  use the flag transpose=False (default value)
               3. If data has any other shape, you first need to reorder it
        :param dimensions: Dictionary mapping field dimensions (lon,
               lat, depth, time) to numpy arrays.
               Note that dimensions can also be a dictionary of dictionaries if
               dimension names are different for each variable
               (e.g. dimensions['U'], dimensions['V'], etc).
        :param transpose: Boolean whether to transpose data on read-in
        :param mesh: String indicating the type of mesh coordinates and
               units used during velocity interpolation, see also https://nbviewer.jupyter.org/github/OceanParcels/parcels/blob/master/parcels/examples/tutorial_unitconverters.ipynb:

               1. spherical (default): Lat and lon in degree, with a
                  correction for zonal velocity U near the poles.
               2. flat: No conversion, lat/lon are assumed to be in m.
        :param allow_time_extrapolation: boolean whether to allow for extrapolation
               (i.e. beyond the last available time snapshot)
               Default is False if dimensions includes time, else True
        :param time_periodic: boolean whether to loop periodically over the time component of the FieldSet
               This flag overrides the allow_time_interpolation and sets it to False
        """

        fields = {}
        for name, datafld in data.items():
            # Use dimensions[name] if dimensions is a dict of dicts
            dims = dimensions[name] if name in dimensions else dimensions
            cls.checkvaliddimensionsdict(dims)

            if allow_time_extrapolation is None:
                allow_time_extrapolation = False if 'time' in dims else True

            lon = dims['lon']
            lat = dims['lat']
            depth = np.zeros(1, dtype=np.float32) if 'depth' not in dims else dims['depth']
            time = np.zeros(1, dtype=np.float64) if 'time' not in dims else dims['time']
            time = np.array(time) if not isinstance(time, np.ndarray) else time
            if isinstance(time[0], np.datetime64):
                time_origin = TimeConverter(time[0])
                time = np.array([time_origin.reltime(t) for t in time])
            else:
                time_origin = TimeConverter(0)
            grid = Grid.create_grid(lon, lat, depth, time, time_origin=time_origin, mesh=mesh)
            if 'creation_log' not in kwargs.keys():
                kwargs['creation_log'] = 'from_data'

            fields[name] = Field(name, datafld, grid=grid, transpose=transpose,
                                 allow_time_extrapolation=allow_time_extrapolation, time_periodic=time_periodic, **kwargs)
        u = fields.pop('U', None)
        v = fields.pop('V', None)
        return cls(u, v, fields=fields)
Beispiel #2
0
class Grid(object):
    """Grid class that defines a (spatial and temporal) grid on which Fields are defined

    """

    def __init__(self, lon, lat, time, time_origin, mesh):
        self.lon = lon
        self.lat = lat
        self.time = np.zeros(1, dtype=np.float64) if time is None else time
        if not self.lon.dtype == np.float32:
            logger.warning_once("Casting lon data to np.float32")
            self.lon = self.lon.astype(np.float32)
        if not self.lat.dtype == np.float32:
            logger.warning_once("Casting lat data to np.float32")
            self.lat = self.lat.astype(np.float32)
        if not self.time.dtype == np.float64:
            assert isinstance(self.time[0], (np.integer, np.floating, float, int)), 'Time vector must be an array of int or floats'
            logger.warning_once("Casting time data to np.float64")
            self.time = self.time.astype(np.float64)
        self.time_full = self.time  # needed for deferred_loaded Fields
        self.time_origin = TimeConverter() if time_origin is None else time_origin
        assert isinstance(self.time_origin, TimeConverter), 'time_origin needs to be a TimeConverter object'
        self.mesh = mesh
        self.cstruct = None
        self.cell_edge_sizes = {}
        self.zonal_periodic = False
        self.zonal_halo = 0
        self.meridional_halo = 0
        self.lat_flipped = False
        self.defer_load = False
        self.lonlat_minmax = np.array([np.nanmin(lon), np.nanmax(lon), np.nanmin(lat), np.nanmax(lat)], dtype=np.float32)
        self.periods = 0

    @staticmethod
    def create_grid(lon, lat, depth, time, time_origin, mesh, **kwargs):
        if len(lon.shape) == 1:
            if depth is None or len(depth.shape) == 1:
                return RectilinearZGrid(lon, lat, depth, time, time_origin=time_origin, mesh=mesh, **kwargs)
            else:
                return RectilinearSGrid(lon, lat, depth, time, time_origin=time_origin, mesh=mesh, **kwargs)
        else:
            if depth is None or len(depth.shape) == 1:
                return CurvilinearZGrid(lon, lat, depth, time, time_origin=time_origin, mesh=mesh, **kwargs)
            else:
                return CurvilinearSGrid(lon, lat, depth, time, time_origin=time_origin, mesh=mesh, **kwargs)

    @property
    def ctypes_struct(self):
        # This is unnecessary for the moment, but it could be useful when going will fully unstructured grids
        self.cgrid = cast(pointer(self.child_ctypes_struct), c_void_p)
        cstruct = CGrid(self.gtype, self.cgrid.value)
        return cstruct

    @property
    def child_ctypes_struct(self):
        """Returns a ctypes struct object containing all relevant
        pointers and sizes for this grid."""

        class CStructuredGrid(Structure):
            # z4d is only to have same cstruct as RectilinearSGrid
            _fields_ = [('xdim', c_int), ('ydim', c_int), ('zdim', c_int),
                        ('tdim', c_int), ('z4d', c_int),
                        ('mesh_spherical', c_int), ('zonal_periodic', c_int),
                        ('tfull_min', c_double), ('tfull_max', c_double), ('periods', POINTER(c_int)),
                        ('lonlat_minmax', POINTER(c_float)),
                        ('lon', POINTER(c_float)), ('lat', POINTER(c_float)),
                        ('depth', POINTER(c_float)), ('time', POINTER(c_double))
                        ]

        # Create and populate the c-struct object
        if not self.cstruct:  # Not to point to the same grid various times if grid in various fields
            if not isinstance(self.periods, c_int):
                self.periods = c_int()
                self.periods.value = 0
            self.cstruct = CStructuredGrid(self.xdim, self.ydim, self.zdim,
                                           self.tdim, self.z4d,
                                           self.mesh == 'spherical', self.zonal_periodic,
                                           self.time_full[0], self.time_full[-1], pointer(self.periods),
                                           self.lonlat_minmax.ctypes.data_as(POINTER(c_float)),
                                           self.lon.ctypes.data_as(POINTER(c_float)),
                                           self.lat.ctypes.data_as(POINTER(c_float)),
                                           self.depth.ctypes.data_as(POINTER(c_float)),
                                           self.time.ctypes.data_as(POINTER(c_double)))
        return self.cstruct

    def lon_grid_to_target(self):
        if self.lon_remapping:
            self.lon = self.lon_remapping.to_target(self.lon)

    def lon_grid_to_source(self):
        if self.lon_remapping:
            self.lon = self.lon_remapping.to_source(self.lon)

    def lon_particle_to_target(self, lon):
        if self.lon_remapping:
            return self.lon_remapping.particle_to_target(lon)
        return lon

    def advancetime(self, grid_new):
        assert isinstance(grid_new.time_origin, type(self.time_origin)), 'time_origin of new and old grids must be either both None or both a date'
        if self.time_origin:
            grid_new.time = grid_new.time + self.time_origin.reltime(grid_new.time_origin)
        if len(grid_new.time) != 1:
            raise RuntimeError('New FieldSet needs to have only one snapshot')
        if grid_new.time > self.time[-1]:  # forward in time, so appending at end
            self.time = np.concatenate((self.time[1:], grid_new.time))
            return 1
        elif grid_new.time < self.time[0]:  # backward in time, so prepending at start
            self.time = np.concatenate((grid_new.time, self.time[:-1]))
            return -1
        else:
            raise RuntimeError("Time of field_new in Field.advancetime() overlaps with times in old Field")

    def check_zonal_periodic(self):
        if self.zonal_periodic or self.mesh == 'flat':
            return
        dx = (self.lon[1:] - self.lon[:-1]) if len(self.lon.shape) == 1 else self.lon[0, 1:] - self.lon[0, :-1]
        dx = np.where(dx < -180, dx+360, dx)
        dx = np.where(dx > 180, dx-360, dx)
        self.zonal_periodic = sum(dx) > 359.9

    def add_Sdepth_periodic_halo(self, zonal, meridional, halosize):
        if zonal:
            if len(self.depth.shape) == 3:
                self.depth = np.concatenate((self.depth[:, :, -halosize:], self.depth,
                                             self.depth[:, :, 0:halosize]), axis=len(self.depth.shape) - 1)
                assert self.depth.shape[2] == self.xdim, "Third dim must be x."
            else:
                self.depth = np.concatenate((self.depth[:, :, :, -halosize:], self.depth,
                                             self.depth[:, :, :, 0:halosize]), axis=len(self.depth.shape) - 1)
                assert self.depth.shape[3] == self.xdim, "Fourth dim must be x."
        if meridional:
            if len(self.depth.shape) == 3:
                self.depth = np.concatenate((self.depth[:, -halosize:, :], self.depth,
                                             self.depth[:, 0:halosize, :]), axis=len(self.depth.shape) - 2)
                assert self.depth.shape[1] == self.ydim, "Second dim must be y."
            else:
                self.depth = np.concatenate((self.depth[:, :, -halosize:, :], self.depth,
                                             self.depth[:, :, 0:halosize, :]), axis=len(self.depth.shape) - 2)
                assert self.depth.shape[2] == self.ydim, "Third dim must be y."

    def computeTimeChunk(self, f, time, signdt):
        nextTime_loc = np.infty * signdt
        periods = self.periods.value if isinstance(self.periods, c_int) else self.periods
        if self.update_status == 'not_updated':
            if self.ti >= 0:
                if (time - periods*(self.time_full[-1]-self.time_full[0]) < self.time[0] or time - periods*(self.time_full[-1]-self.time_full[0]) > self.time[2]):
                    self.ti = -1  # reset
                elif (time - periods*(self.time_full[-1]-self.time_full[0]) < self.time_full[0] or time - periods*(self.time_full[-1]-self.time_full[0]) >= self.time_full[-1]):
                    self.ti = -1  # reset
                elif signdt >= 0 and time - periods*(self.time_full[-1]-self.time_full[0]) >= self.time[1] and self.ti < len(self.time_full)-3:
                    self.ti += 1
                    self.time = self.time_full[self.ti:self.ti+3]
                    self.update_status = 'updated'
                elif signdt == -1 and time - periods*(self.time_full[-1]-self.time_full[0]) <= self.time[1] and self.ti > 0:
                    self.ti -= 1
                    self.time = self.time_full[self.ti:self.ti+3]
                    self.update_status = 'updated'
            if self.ti == -1:
                self.time = self.time_full
                self.ti, _ = f.time_index(time)
                periods = self.periods.value if isinstance(self.periods, c_int) else self.periods
                if self.ti > 0 and signdt == -1:
                    self.ti -= 1
                if self.ti >= len(self.time_full) - 2:
                    self.ti = len(self.time_full) - 3
                self.time = self.time_full[self.ti:self.ti+3]
                self.tdim = 3
                self.update_status = 'first_updated'
            if signdt >= 0 and (self.ti < len(self.time_full)-3 or not f.allow_time_extrapolation):
                nextTime_loc = self.time[2] + periods*(self.time_full[-1]-self.time_full[0])
            elif signdt == -1 and (self.ti > 0 or not f.allow_time_extrapolation):
                nextTime_loc = self.time[0] + periods*(self.time_full[-1]-self.time_full[0])
        return nextTime_loc