# Copyright (c) 2024 CNES
#
# All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
"""
Regular grids
=============
"""
from __future__ import annotations
import numpy
from . import core, interface
[docs]
class Grid2D:
"""2D Cartesian Grid.
Args:
x (pyinterp.Axis): X-Axis.
y (pyinterp.Axis): Y-Axis.
array (numpy.ndarray): Discrete representation of a continuous function
on a uniform 2-dimensional grid.
increasing_axes: Optional string indicating how to ensure that the grid
axes are increasing. If axes are decreasing, the axes and grid
provided will be flipped in place or copied before being flipped. By
default, the decreasing axes are not modified.
Examples:
>>> import numpy as np
>>> import pyinterp
>>> x_axis = pyinterp.Axis(numpy.arange(-180.0, 180.0, 1.0),
... is_circle=True)
>>> y_axis = pyinterp.Axis(numpy.arange(-80.0, 80.0, 1.0),
... is_circle=False)
>>> array = numpy.zeros((len(x_axis), len(y_axis)))
>>> grid = pyinterp.Grid2D(x_axis, y_axis, array)
>>> grid
<pyinterp.grid.Grid2D>
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]])
Axis:
* x: <pyinterp.axis.Axis>
min_value: -180.0
max_value: 179.0
step: 1.0
is_circle: True
* y: <pyinterp.axis.Axis>
min_value: -80.0
max_value: 79.0
step: 1.0
is_circle: False
"""
#: The number of grid dimensions handled by this object.
_DIMENSIONS = 2
def __init__(self, *args, increasing_axes: str | None = None):
prefix = ''
for item in args:
if isinstance(item, core.TemporalAxis):
prefix = 'Temporal'
break
_class = f'{prefix}Grid{self._DIMENSIONS}D' + \
interface._core_class_suffix(args[-1], handle_integer=True)
if increasing_axes is not None:
if increasing_axes not in ['inplace', 'copy']:
raise ValueError('increasing_axes '
f'{increasing_axes!r} is not defined')
inplace = increasing_axes == 'inplace'
# Tuple does not support item assignment
args = list(args) # type: ignore[assignment]
for idx, item in enumerate(args):
if isinstance(item,
(core.Axis,
core.TemporalAxis)) and not item.is_ascending():
args[idx] = item.flip( # type: ignore[index]
inplace=inplace)
args[-1] = numpy.flip( # type: ignore[index]
args[-1], axis=idx)
self._instance = getattr(core, _class)(*args)
self._prefix = prefix
[docs]
def __repr__(self):
"""Called by the ``repr()`` built-in function to compute the string
representation of this instance."""
def pad(string, length):
"""Pad a string to a given length."""
return '\n'.join([(' ' * length if ix else '') + line
for ix, line in enumerate(string.split('\n'))])
result = [
f'<{self.__module__}.{self.__class__.__name__}>',
repr(self.array),
]
result.append('Axis:')
for item in dir(self):
attr = getattr(self, item)
if isinstance(attr, (core.Axis, core.TemporalAxis)):
prefix = f'* {item}: '
result.append(f' {prefix}{pad(repr(attr), len(prefix))}')
return '\n'.join(result)
@property
def x(self) -> core.Axis:
"""Gets the X-Axis handled by this instance.
Returns:
X-Axis.
"""
return self._instance.x
@property
def y(self) -> core.Axis:
"""Gets the Y-Axis handled by this instance.
Returns:
Y-Axis.
"""
return self._instance.y
@property
def array(self) -> numpy.ndarray:
"""Gets the values handled by this instance.
Returns:
numpy.ndarray: values.
"""
return self._instance.array
[docs]
class Grid3D(Grid2D):
"""3D Cartesian Grid.
Args:
x (pyinterp.Axis): X-Axis.
y (pyinterp.Axis, pyinterp.TemporalAxis): Y-Axis.
z (pyinterp.Axis): Z-Axis.
array (numpy.ndarray): Discrete representation of a continuous function
on a uniform 3-dimensional grid.
increasing_axes: Ensure that the axes of the grid are increasing. If
this is not the case, the axes and grid provided will be flipped.
Default to False.
.. note::
If the Z axis is a :py:class:`temporal axis
<pyinterp.TemporalAxis>`, the grid will handle this axis during
interpolations as a time axis.
Examples:
>>> import numpy as np
>>> import pyinterp
>>> x_axis = pyinterp.Axis(numpy.arange(-180.0, 180.0, 1.0),
... is_circle=True)
>>> y_axis = pyinterp.Axis(numpy.arange(-80.0, 80.0, 1.0),
... is_circle=False)
>>> z_axis = pyinterp.TemporalAxis(
... numpy.array(['2000-01-01'], dtype="datetime64[s]"))
>>> array = numpy.zeros((len(x_axis), len(y_axis), len(z_axis)))
>>> grid = pyinterp.Grid3D(x_axis, y_axis, z_axis, array)
"""
_DIMENSIONS = 3
def __init__(self, *args, increasing_axes: str | None = None):
super().__init__(*args, increasing_axes=increasing_axes)
@property
def z(self) -> core.Axis | core.TemporalAxis:
"""Gets the Z-Axis handled by this instance.
Returns:
Z-Axis.
"""
return self._instance.z
[docs]
class Grid4D(Grid3D):
"""4D Cartesian Grid.
Args:
x (pyinterp.Axis): X-Axis.
y (pyinterp.Axis): Y-Axis.
z (pyinterp.Axis, pyinterp.TemporalAxis): Z-Axis.
u (pyinterp.Axis): U-Axis.
array (numpy.ndarray): Discrete representation of a continuous
function on a uniform 4-dimensional grid.
increasing_axes: Ensure that the axes of the grid are increasing.
If this is not the case, the axes and grid provided will be
flipped. Default to False.
.. note::
If the Z axis is a temporal axis, the grid will handle this axis
during interpolations as a time axis.
"""
_DIMENSIONS = 4
def __init__(self, *args, increasing_axes: str | None = None):
super().__init__(*args, increasing_axes=increasing_axes)
@property
def u(self) -> core.Axis:
"""Gets the U-Axis handled by this instance.
Returns:
U-Axis.
"""
return self._instance.u
def _core_variate_interpolator(instance: object, interpolator: str, **kwargs):
"""Obtain the interpolator from the string provided."""
if isinstance(instance, Grid2D):
dimensions = instance._DIMENSIONS
# 4D interpolation uses the 3D interpolator
if dimensions > 3:
dimensions -= 1
else:
raise TypeError('instance is not an object handling a grid.')
prefix = instance._prefix
if interpolator == 'bilinear':
return getattr(core, f'{prefix}Bilinear{dimensions}D')(**kwargs)
if interpolator == 'nearest':
return getattr(core, f'{prefix}Nearest{dimensions}D')(**kwargs)
if interpolator == 'inverse_distance_weighting':
return getattr(
core, f'{prefix}InverseDistanceWeighting{dimensions}D')(**kwargs)
raise ValueError(f'interpolator {interpolator!r} is not defined')