#!/usr/bin/env python
# -*- coding: utf8 -*-
#
# Copyright or © or Copr. Actimar/IFREMER (2013-2015)
#
# This software is a computer program whose purpose is to provide
# utilities for handling oceanographic and atmospheric data,
# with the ultimate goal of validating the MARS model from IFREMER.
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
#
import cdms2
from vacumm.misc.grid.regridding import shift2d, shift1d
__all__ = ['ARAKAWA_LOCATIONS', 'ARAKAWA_POSITIONS', 'ArakawaGrid', 'CGrid', 'AGrid',
'ArakawaGridTransfer']
#: Strings for naming standard physical ARAKAWA_LOCATIONS on grid
ARAKAWA_LOCATIONS = [
't', # Thermodynamical quantities
'u', # Zonal momentum quantities
'v', # Meridional momentum quantities
'w', # Vertical momentum quantities
'f', # Vorticity quantities
]
locations = ARAKAWA_LOCATIONS # compat
#: Alias for :attr:`ARAKAWA_LOCATIONS`
ARAKAWA_POSITIONS = ARAKAWA_LOCATIONS
positions = ARAKAWA_POSITIONS # compat
#: Upper-case version of :attr:`ARAKAWA_LOCATIONS`
ARAKAWA_LOCATIONS_UPPER = [l.upper() for l in ARAKAWA_LOCATIONS]
Locations = ARAKAWA_LOCATIONS_UPPER# compat
# TODO: Add special ARAKAWA_LOCATIONS such as uw, vw and fw at the same place?
#: Grid types
ARAKAWA_GRID_TYPES = ['A', 'C']
grid_types = ARAKAWA_GRID_TYPES # compat
from vacumm import VACUMMError
class ArakawaGridError(VACUMMError):
pass
class _ArakawaInterp_(object):
def _xy_interp_(self, var, p0, p1, copy, **kwargs):
# No grid
if var.getGrid() is None:
if copy: var = var.clone()
return var
# Delta
dloc = self.delta_loc(p0, p1)
# Interpolate
var = shift2d(var, ishift=dloc[0], jshift=dloc[1], copy=True, **kwargs)
return var
def _z_interp_(self, var, p0, p1, copy, **kwargs):
# No vertical axis (use vacumm.misc.grid.get_zdim?)
if var.getLevel() is None:
if copy: var = var.clone()
return var
# Z axis index
axis = var.getOrder().index('z')
# Delta
dloc = self.delta_loc(p0, p1)
# Interpolate
var = shift1d(var, shift=dloc[2], axis=axis, copy=True, **kwargs)
return var
@classmethod
def is_valid_loc(cls, p):
"""Check if a location is valid"""
p = str(p).lower()
if p=='': return 't'
if p not in ARAKAWA_LOCATIONS:
raise ArakawaGridError('Bad location in Arakawa grid: %s. '
'Please use one of: %s'%(p, ARAKAWA_LOCATIONS))
return p
[docs]class ArakawaGrid(_ArakawaInterp_):
"""Base class that provides a facotry and declares grid operations"""
[docs] @staticmethod
def factory(arg):
"""Guess the grid type and class, and instantiate it
:Params:
- **arg**: An explicit grid type (:attr:`ARAKAWA_GRID_TYPES`), or
an object with a either :attr:`grid_type`, :attr:`arakawa_grid_type` or
:attr:`grid_type` attribute.
:Return: A :class:`ArakawaGrid` children object or ``None`` if grid type
has not been guessed.
"""
if arg in [AGrid, CGrid]: return arg()
if isinstance(arg, ArakawaGrid): return arg
if str(arg).upper() in ARAKAWA_GRID_TYPES:
gt = arg
else:
gt = get_grid_type(arg)
if gt is None: return
return eval(gt.upper()+'Grid')()
def __getitem__(self, p):
p = self.is_valid_loc(p)
return getattr(self, p)
def __str__(self):
return self.grid_type
[docs] def are_same_locs(self, p0, p1):
"""Check if to ARAKAWA_POSITIONS are the same
:Example:
>>> mygrid.are_same_locs('t', 'u')
"""
p0 = self.is_valid_loc(p0)
p1 = self.is_valid_loc(p1)
self[p0] == self[p1]
[docs] def delta_loc(self, p0, p1):
"""Difference of relative ARAKAWA_LOCATIONS
:Return: ``dx,dy,dz``
"""
p0 = self.is_valid_loc(p0)
p1 = self.is_valid_loc(p1)
return self[p1][0]-self[p0][0], self[p1][1]-self[p0][1], self[p1][2]-self[p0][2]
[docs] def interp(self, var, p0, p1, copy=False, mode=None, zfirst=True, **kwargs):
"""Interpolate a variable from one location to another one using
:func:`vacumm.misc.grid.regridding.shift2d` (horizontal) and
:func:`vacumm.misc.grid.regridding.shift1d` (vertical)
.. note:: It does not change the attributes.
:Params:
- **var**: MV2 array with a grid.
- **p0/1**: Valid ARAKAWA_LOCATIONS: 0=source, 1=destination.
If p0 is None, it is guessed with :func:`vacumm.data.cf.get_loc`.
- **mode**, optional: Interpolation mode at boundaries
(see :func:`~vacumm.misc.grid.regridding.shift2d`).
- **zfirst**, optional: Perform the vertical interpolation first,
then the horizontal interpolation.
- **copy**, optional: Copy the variable if same location?
:Example:
>>> u3d_t = CGrid().interp(u3d_u, 'u', 't', mode='extrap')
"""
# Valid ARAKAWA_LOCATIONS
if p0 is None:
from vacumm.data.cf import get_loc
p0 = get_loc(var)
# if p0 is None:
# raise ArakawaGridError("Can't guess location of variable: "+var.id)
p0 = self.is_valid_loc(p0)
p1 = self.is_valid_loc(p1)
# Same location or no grid
if self[p0]==self[p1]:
if copy: var = var.clone()
return var
# Interpolation order
interpmets = self._z_interp_, self._xy_interp_
if not zfirst:
interpmets = interpmets[::-1]
# Interpolations
for interpmet in interpmets:
var = interpmet(var, p0, p1, copy, mode=mode, **kwargs)
# Special attributes
from vacumm.data.cf import set_loc
set_loc(var, p1)
set_grid_type(var, self.grid_type)
return var
loc2loc = interp
[docs]class AGrid(ArakawaGrid):
"""A Arakawa grid"""
# Positions relative to T point
t = 0, 0, 0
u = 0, 0, 0
v = 0, 0, 0
w = 0, 0, 0
f = 0, 0, 0
grid_type = gtype = 'A'
[docs]class CGrid(ArakawaGrid):
"""C Arakawa grid"""
# Positions relative to T point
t = 0, 0, 0
u = 1, 0, 0
v = 0, 1, 0
w = 0, 0, 1
f = 1, 1, 0
grid_type = gtype = 'C'
[docs]class ArakawaGridTransfer(_ArakawaInterp_):
"""To interpolate variables from one grid type to another
.. note:: This classes does not interpolate between two grids, it just interpolates
between relative ARAKAWA_POSITIONS. For general interpolations, please use
:func:`~vacumm.grid.regridding.regrid2d`.
:Example:
>>> sst_a_v = ArakawaGridTransfer('C','A').interp(sst_c_t, 't', 'v')
"""
def __init__(self, grid0, grid1):
"""
:Params:
- **grid0/1**: Arguments to :meth:`~Arakawa.factory`
"""
# Get ArakawaGrid objects
self.grid0 = ArakawaGrid.factory(grid0)
self.grid1 = ArakawaGrid.factory(grid1)
[docs] def delta_loc(self, p0, p1=None):
"""Difference of relative ARAKAWA_LOCATIONS
:Return: ``dx,dy,dz``
"""
p0 = self.grid0.is_valid_loc(p0)
if p1 is None:
p1 = p0
else:
p1 = self.grid0.is_valid_loc(p1)
return (self.grid1[p1][0]-self.grid0[p0][0], self.grid1[p1][1]-self.grid0[p0][1],
self.grid1[p1][2]-self.grid0[p0][2])
[docs] def interp(self, var, p0=None, p1=None, copy=False, mode=None, zfirst=True, **kwargs):
"""Interpolate a variable from one location to another one using
:func:`vacumm.misc.grid.regridding.shift2d` (horizontal) and
:func:`vacumm.misc.grid.regridding.shift1d` (vertical)
.. note:: It does not change the attributes.
:Params:
- **var**: MV2 array with a grid.
- **p0/1**: Valid ARAKAWA_LOCATIONS: 0=source, 1=destination.
If p0 is None, it is guessed with :func:`vacumm.data.cf.get_loc`.
If p1 is None, it defaults to p0.
- **mode**, optional: Interpolation mode at boundaries
(see :func:`~vacumm.misc.grid.regridding.shift2d`).
- **copy**, optional: Copy the variable if same location?
:Example:
>>> sst_a_v = ArakawaGridTransfer('C','A').interp(sst_c_t, 't', 'v')
"""
# Valid location
if p0 is None:
from vacumm.data.cf import get_loc
p0 = get_loc(var)
# if p0 is None:
# raise ArakawaGridError("Can't guess location of variable: "+var.id)
p0 = self.is_valid_loc(p0)
if p1 is None:
p1 = p0
else:
p1 = self.grid0.is_valid_loc(p1)
# Same location or no grid
if self.grid0[p0]==self.grid1[p1]:
if copy: var = var.clone()
return var
# Interpolation order
interpmets = self._z_interp_, self._xy_interp_
if not zfirst:
interpmets = interpmets[::-1]
# Interpolations
for interpmet in interpmets:
var = interpmet(var, p0, p1, copy, mode=mode, **kwargs)
# Special attributes
from vacumm.data.cf import set_loc
set_loc(var, p1)
set_grid_type(var, self.grid1.grid_type)
return var
_cdms2_atts = ['_vacumm_arakawa_grid_type', '_arakawa_grid_type']
_other_atts = ['arakawa_grid_type', 'grid_type']
def get_grid_type(var):
"""Guess the Arakawa grid type
It search for the following attributes: :attr:`arakawa_grid_type`, :attr:`grid_type`
and :attr:`_vacumm_arakawa_grid_type`.
:Params:
- **var**: A :mod:`cdms2` variable or grid, an :class:`ArakawaGrid` instance or
a :class:`~vacumm.data.misc.dataset.Dataset` instance.
If var is a :mod:`cdms2` variable, it also check its grid if defined.
:Return: An Arakawa grid upper-base letter, like 'C'
"""
vv = [var]
if cdms2.isVariable(var):
grid = var.getGrid()
if grid is not None:
vv.append(grid)
for v in vv:
for att in _cdms2_atts+_other_atts:
if hasattr(v, att):
gt = getattr(a, att)
if gt is None: return
return str(gt).upper()
def _set_clean_atts_(var, atts, value):
for att in atts:
if hasattr(var, att): delattr(var, att)
if value is not None:
setattr(var, atts[0], value)
def set_grid_type(var, gtype):
"""Set an attribute so that var is identified as being on the specified Arakawa
grid type.
If var is a :mod:`cdms2` variable or grid, it sets the
:attr:`_vacumm_arakawa_grid_type` attribute,
else it sets the :attr:`arakawa_grid_type` attribute.
:Params:
- **var**: A :mod:`cdms2` variable or grid, a
:class:`~vacumm.data.misc.dataset.Dataset` instance.
- **gtype**: None or one of the :attr:`grid_type` letters.
"""
if gtype is not None:
gtype = str(gtype).upper()
if cdms2.isVariable(var) or cdms2.isGrid(var):
vv = [var]
if cdms2.isVariable(var):
grid = var.getGrid()
if grid is not None: vv.append(grid)
for v in vv:
_set_clean_atts_(v, _cdms2_atts, gtype)
else:
_set_clean_atts_(var, _other_atts, gtype)
return gtype