mirror of
https://github.com/meteoinfo/MeteoInfo.git
synced 2025-12-08 20:36:05 +00:00
update meteolib functions
This commit is contained in:
parent
bcfd50f1e3
commit
9dd3368e25
@ -18,7 +18,7 @@ import org.meteoinfo.common.util.BigDecimalUtil;
|
||||
import java.awt.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -1420,4 +1420,74 @@ public class MIMath {
|
||||
|
||||
return new double[]{u, v};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort List A, and rearrange List B according to the sorting result of List A.
|
||||
* @param listA The list to be sorted
|
||||
* @param listB The list to be rearranged according to the sorting result of List A
|
||||
*/
|
||||
public static <T extends Comparable<T>, U> void sortAndRearrange(List<T> listA, List<U> listB) {
|
||||
// Check the size of the two list
|
||||
if (listA.size() != listB.size()) {
|
||||
throw new IllegalArgumentException("The two lists must be of the same size");
|
||||
}
|
||||
|
||||
// Create a list containing values and their original indices
|
||||
List<Map.Entry<T, Integer>> entries = new ArrayList<>();
|
||||
for (int i = 0; i < listA.size(); i++) {
|
||||
entries.add(new AbstractMap.SimpleEntry<>(listA.get(i), i));
|
||||
}
|
||||
|
||||
// Sort according to the natural order of values.
|
||||
Collections.sort(entries, Map.Entry.comparingByKey());
|
||||
|
||||
// Save sorted A values
|
||||
List<T> sortedA = new ArrayList<>();
|
||||
// Save sorted B values
|
||||
List<U> sortedB = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<T, Integer> entry : entries) {
|
||||
sortedA.add(entry.getKey());
|
||||
sortedB.add(listB.get(entry.getValue()));
|
||||
}
|
||||
|
||||
// Update origin lists
|
||||
listA.clear();
|
||||
listA.addAll(sortedA);
|
||||
|
||||
listB.clear();
|
||||
listB.addAll(sortedB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort List, and return the sorted index.
|
||||
* @param list The list to be sorted
|
||||
* @return Sorted index list
|
||||
*/
|
||||
public static <T extends Comparable<T>> List<Integer> sort(List<T> list) {
|
||||
// Create a list containing values and their original indices
|
||||
List<Map.Entry<T, Integer>> entries = new ArrayList<>();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
entries.add(new AbstractMap.SimpleEntry<>(list.get(i), i));
|
||||
}
|
||||
|
||||
// Sort according to the natural order of values.
|
||||
Collections.sort(entries, Map.Entry.comparingByKey());
|
||||
|
||||
// Save sorted values
|
||||
List<T> sorted = new ArrayList<>();
|
||||
// Save sorted index values
|
||||
List<Integer> sortIndex = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<T, Integer> entry : entries) {
|
||||
sorted.add(entry.getKey());
|
||||
sortIndex.add(entry.getValue());
|
||||
}
|
||||
|
||||
// Update origin list
|
||||
list.clear();
|
||||
list.addAll(sorted);
|
||||
|
||||
return sortIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1430,7 +1430,7 @@ public class LegendManage {
|
||||
LegendScheme ls;
|
||||
if (isUnique) {
|
||||
try {
|
||||
Array ua = ArrayUtil.unique(array, null);
|
||||
Array ua = ArrayUtil.unique(array, null)[0];
|
||||
List<Number> values = new ArrayList<>();
|
||||
IndexIterator iter = ua.getIndexIterator();
|
||||
while (iter.hasNext()) {
|
||||
|
||||
@ -1,32 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<MeteoInfo File="milconfig.xml" Type="configurefile">
|
||||
<Path OpenPath="D:\Working\MIScript\Jython\mis\common_math\special">
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\3d\jogl"/>
|
||||
<Path OpenPath="D:\Working\MIScript\Jython\mis\array">
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\3d\jogl\plot"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\meteo"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\dataset"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\web"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\others"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\test"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\mywork\music"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\array"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\meteo\calc"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\common_math\integrate"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\common_math"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\common_math\special"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\meteo"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\array\slice"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\meteo\calc"/>
|
||||
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\array"/>
|
||||
</Path>
|
||||
<File>
|
||||
<OpenedFiles>
|
||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\meteo\calc\mixing_ratio_from_relative_humidity.py"/>
|
||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\common_math\special\airy.py"/>
|
||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\common_math\special\lambertw.py"/>
|
||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\meteo\calc\parcel_profile_test.py"/>
|
||||
<OpenedFile File="D:\Working\MIScript\Jython\mis\meteo\calc\parcel_profile_with_lcl.py"/>
|
||||
</OpenedFiles>
|
||||
<RecentFiles>
|
||||
<RecentFile File="D:\Working\MIScript\Jython\mis\meteo\calc\mixing_ratio_from_relative_humidity.py"/>
|
||||
<RecentFile File="D:\Working\MIScript\Jython\mis\common_math\special\airy.py"/>
|
||||
<RecentFile File="D:\Working\MIScript\Jython\mis\common_math\special\lambertw.py"/>
|
||||
<RecentFile File="D:\Working\MIScript\Jython\mis\meteo\calc\parcel_profile_test.py"/>
|
||||
<RecentFile File="D:\Working\MIScript\Jython\mis\meteo\calc\parcel_profile_with_lcl.py"/>
|
||||
</RecentFiles>
|
||||
</File>
|
||||
<Font>
|
||||
|
||||
@ -10,7 +10,42 @@ from itertools import product
|
||||
import mipylib.numeric as np
|
||||
from .. import constants
|
||||
|
||||
__all__ = ['coriolis_parameter','smooth_window','smooth_n_point']
|
||||
__all__ = ['coriolis_parameter','height_to_pressure_std','pressure_to_height_std',
|
||||
'smooth_window','smooth_n_point','wind_speed']
|
||||
|
||||
|
||||
# The following variables are constants for a standard atmosphere
|
||||
t0 = 288. #'kelvin'
|
||||
p0 = 1013.25 #'hPa'
|
||||
gamma = 6.5 #'K/km'
|
||||
|
||||
|
||||
def wind_speed(u, v):
|
||||
r"""Compute the wind speed from u and v-components.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : `m/s`
|
||||
Wind component in the X (East-West) direction
|
||||
v : `m/s`
|
||||
Wind component in the Y (North-South) direction
|
||||
|
||||
Returns
|
||||
-------
|
||||
wind speed: `m/s`
|
||||
Speed of the wind
|
||||
|
||||
See Also
|
||||
--------
|
||||
wind_components
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from mipylib.meteolib.calc import wind_speed
|
||||
>>> wind_speed(10., 10.)
|
||||
<(14.1421356, 'meter / second')>
|
||||
"""
|
||||
return np.hypot(u, v)
|
||||
|
||||
def coriolis_parameter(latitude):
|
||||
r"""Calculate the Coriolis parameter at each point.
|
||||
@ -181,4 +216,52 @@ def smooth_n_point(scalar_grid, n=5, passes=1):
|
||||
raise ValueError('The number of points to use in the smoothing '
|
||||
'calculation must be either 5 or 9.')
|
||||
|
||||
return smooth_window(scalar_grid, window=weights, passes=passes, normalize_weights=False)
|
||||
return smooth_window(scalar_grid, window=weights, passes=passes, normalize_weights=False)
|
||||
|
||||
def height_to_pressure_std(height):
|
||||
r"""Convert height data to pressures using the U.S. standard atmosphere [NOAA1976]_.
|
||||
|
||||
The implementation inverts the formula outlined in [Hobbs1977]_ pg.60-61.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
height : `meters`
|
||||
Atmospheric height
|
||||
|
||||
Returns
|
||||
-------
|
||||
`hPa`
|
||||
Corresponding pressure value(s)
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. math:: p = p_0 e^{\frac{g}{R \Gamma} \text{ln}(1-\frac{Z \Gamma}{T_0})}
|
||||
|
||||
"""
|
||||
gamma_1 = gamma * 1e-3 #to K/m
|
||||
return p0 * (1 - (gamma_1 / t0) * height) ** (constants.g / (constants.Rd * gamma_1))
|
||||
|
||||
|
||||
def pressure_to_height_std(pressure):
|
||||
r"""Convert pressure data to height using the U.S. standard atmosphere [NOAA1976]_.
|
||||
|
||||
The implementation uses the formula outlined in [Hobbs1977]_ pg.60-61.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pressure : `hPa`
|
||||
Atmospheric pressure
|
||||
|
||||
Returns
|
||||
-------
|
||||
`meters`
|
||||
Corresponding height value(s)
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. math:: Z = \frac{T_0}{\Gamma}[1-\frac{p}{p_0}^\frac{R\Gamma}{g}]
|
||||
|
||||
"""
|
||||
gamma_1 = gamma * 1e-3 #to K/m
|
||||
return (t0 / gamma_1) * (1 - (pressure / p0)**(
|
||||
constants.Rd * gamma_1 / constants.g))
|
||||
|
||||
5
meteoinfo-lab/pylib/mipylib/meteolib/calc/exceptions.py
Normal file
5
meteoinfo-lab/pylib/mipylib/meteolib/calc/exceptions.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Contains specific exceptions raised by calculations."""
|
||||
|
||||
|
||||
class InvalidSoundingError(ValueError):
|
||||
"""Raise when a sounding does not meet thermodynamic calculation expectations."""
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -5,11 +5,12 @@ Ported from MetPy.
|
||||
"""
|
||||
|
||||
import mipylib.numeric as np
|
||||
import mipylib.numeric.ma as ma
|
||||
from mipylib.geolib import Geod
|
||||
from ..interpolate import interpolate_1d
|
||||
from ..cbook import broadcast_indices
|
||||
|
||||
__all__ = ['resample_nn_1d', 'nearest_intersection_idx', 'first_derivative', 'gradient',
|
||||
__all__ = ['resample_nn_1d', 'nearest_intersection_idx', 'first_derivative', 'find_intersections', 'gradient',
|
||||
'lat_lon_grid_deltas', 'get_layer_heights', 'find_bounding_indices', 'geospatial_gradient']
|
||||
|
||||
|
||||
@ -58,6 +59,125 @@ def nearest_intersection_idx(a, b):
|
||||
return sign_change_idx
|
||||
|
||||
|
||||
def find_intersections(x, a, b, direction='all', log_x=False):
|
||||
"""Calculate the best estimate of intersection.
|
||||
|
||||
Calculates the best estimates of the intersection of two y-value
|
||||
data sets that share a common x-value set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array-like
|
||||
1-dimensional array of numeric x-values
|
||||
a : array-like
|
||||
1-dimensional array of y-values for line 1
|
||||
b : array-like
|
||||
1-dimensional array of y-values for line 2
|
||||
direction : str, optional
|
||||
specifies direction of crossing. 'all', 'increasing' (a becoming greater than b),
|
||||
or 'decreasing' (b becoming greater than a). Defaults to 'all'.
|
||||
log_x : bool, optional
|
||||
Use logarithmic interpolation along the `x` axis (i.e. for finding intersections
|
||||
in pressure coordinates). Default is False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A tuple (x, y) of array-like with the x and y coordinates of the
|
||||
intersections of the lines.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function implicitly converts `xarray.DataArray` to `pint.Quantity`, with the results
|
||||
given as `pint.Quantity`.
|
||||
|
||||
"""
|
||||
# Change x to logarithmic if log_x=True
|
||||
if log_x is True:
|
||||
x = np.log(x)
|
||||
|
||||
# Find the index of the points just before the intersection(s)
|
||||
nearest_idx = nearest_intersection_idx(a, b)
|
||||
next_idx = nearest_idx + 1
|
||||
|
||||
# Determine the sign of the change
|
||||
sign_change = np.sign(a[next_idx] - b[next_idx])
|
||||
|
||||
# x-values around each intersection
|
||||
_, x0 = _next_non_masked_element(x, nearest_idx)
|
||||
_, x1 = _next_non_masked_element(x, next_idx)
|
||||
|
||||
# y-values around each intersection for the first line
|
||||
_, a0 = _next_non_masked_element(a, nearest_idx)
|
||||
_, a1 = _next_non_masked_element(a, next_idx)
|
||||
|
||||
# y-values around each intersection for the second line
|
||||
_, b0 = _next_non_masked_element(b, nearest_idx)
|
||||
_, b1 = _next_non_masked_element(b, next_idx)
|
||||
|
||||
# Calculate the x-intersection. This comes from finding the equations of the two lines,
|
||||
# one through (x0, a0) and (x1, a1) and the other through (x0, b0) and (x1, b1),
|
||||
# finding their intersection, and reducing with a bunch of algebra.
|
||||
delta_y0 = a0 - b0
|
||||
delta_y1 = a1 - b1
|
||||
intersect_x = (delta_y1 * x0 - delta_y0 * x1) / (delta_y1 - delta_y0)
|
||||
|
||||
# Calculate the y-intersection of the lines. Just plug the x above into the equation
|
||||
# for the line through the a points. One could solve for y like x above, but this
|
||||
# causes weirder unit behavior and seems a little less good numerically.
|
||||
intersect_y = ((intersect_x - x0) / (x1 - x0)) * (a1 - a0) + a0
|
||||
|
||||
# If there's no intersections, return
|
||||
if len(intersect_x) == 0:
|
||||
return intersect_x, intersect_y
|
||||
|
||||
# Return x to linear if log_x is True
|
||||
if log_x is True:
|
||||
intersect_x = np.exp(intersect_x)
|
||||
|
||||
# Check for duplicates
|
||||
duplicate_mask = (np.ediff1d(intersect_x, to_end=1) != 0)
|
||||
|
||||
# Make a mask based on the direction of sign change desired
|
||||
if direction == 'increasing':
|
||||
mask = sign_change > 0
|
||||
elif direction == 'decreasing':
|
||||
mask = sign_change < 0
|
||||
elif direction == 'all':
|
||||
return intersect_x[duplicate_mask], intersect_y[duplicate_mask]
|
||||
else:
|
||||
raise ValueError('Unknown option for direction: {}'.format(direction))
|
||||
|
||||
return intersect_x[mask & duplicate_mask], intersect_y[mask & duplicate_mask]
|
||||
|
||||
|
||||
def _next_non_masked_element(a, idx):
|
||||
"""Return the next non masked element of a masked array.
|
||||
|
||||
If an array is masked, return the next non-masked element (if the given index is masked).
|
||||
If no other unmasked points are after the given masked point, returns none.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : array-like
|
||||
1-dimensional array of numeric values
|
||||
idx : integer
|
||||
Index of requested element
|
||||
|
||||
Returns
|
||||
-------
|
||||
Index of next non-masked element and next non-masked element
|
||||
|
||||
"""
|
||||
try:
|
||||
next_idx = idx + a[idx:].mask.argmin()
|
||||
if ma.is_masked(a[next_idx]):
|
||||
return None, None
|
||||
else:
|
||||
return next_idx, a[next_idx]
|
||||
except (AttributeError, TypeError, IndexError):
|
||||
return idx, a[idx]
|
||||
|
||||
|
||||
def _remove_nans(*variables):
|
||||
"""Remove NaNs from arrays that cause issues with calculations.
|
||||
Takes a variable number of arguments and returns masked arrays in the same
|
||||
@ -73,10 +193,120 @@ def _remove_nans(*variables):
|
||||
# Mask everyone with that joint mask
|
||||
ret = []
|
||||
for v in variables:
|
||||
v = np.asarray(v)
|
||||
ret.append(v[~mask])
|
||||
return ret
|
||||
|
||||
|
||||
def _get_bound_pressure_height(pressure, bound, height=None, interpolate=True):
|
||||
"""Calculate the bounding pressure and height in a layer.
|
||||
|
||||
Given pressure, optional heights and a bound, return either the closest pressure/height
|
||||
or interpolated pressure/height. If no heights are provided, a standard atmosphere
|
||||
([NOAA1976]_) is assumed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pressure : `pint.Quantity`
|
||||
Atmospheric pressures
|
||||
bound : `pint.Quantity`
|
||||
Bound to retrieve (in pressure or height)
|
||||
height : `pint.Quantity`, optional
|
||||
Atmospheric heights associated with the pressure levels. Defaults to using
|
||||
heights calculated from ``pressure`` assuming a standard atmosphere.
|
||||
interpolate : boolean, optional
|
||||
Interpolate the bound or return the nearest. Defaults to True.
|
||||
|
||||
Returns
|
||||
-------
|
||||
`pint.Quantity`
|
||||
The bound pressure and height
|
||||
|
||||
"""
|
||||
# avoid circular import if basic.py ever imports something from tools.py
|
||||
from .basic import height_to_pressure_std, pressure_to_height_std
|
||||
|
||||
# Make sure pressure is monotonically decreasing
|
||||
sort_inds = np.argsort(pressure)[::-1]
|
||||
pressure = pressure[sort_inds]
|
||||
if height is not None:
|
||||
height = height[sort_inds]
|
||||
|
||||
# Bound is given in pressure
|
||||
if bound.check('[length]**-1 * [mass] * [time]**-2'):
|
||||
# If the bound is in the pressure data, we know the pressure bound exactly
|
||||
if bound in pressure:
|
||||
# By making sure this is at least a 1D array we avoid the behavior in numpy
|
||||
# (at least up to 1.19.4) that float32 scalar * Python float -> float64, which
|
||||
# can wreak havok with floating point comparisons.
|
||||
bound_pressure = np.atleast_1d(bound)
|
||||
# If we have heights, we know the exact height value, otherwise return standard
|
||||
# atmosphere height for the pressure
|
||||
if height is not None:
|
||||
bound_height = height[pressure == bound_pressure]
|
||||
else:
|
||||
bound_height = pressure_to_height_std(bound_pressure)
|
||||
# If bound is not in the data, return the nearest or interpolated values
|
||||
else:
|
||||
if interpolate:
|
||||
bound_pressure = bound # Use the user specified bound
|
||||
if height is not None: # Interpolate heights from the height data
|
||||
bound_height = log_interpolate_1d(bound_pressure, pressure, height)
|
||||
else: # If not heights given, use the standard atmosphere
|
||||
bound_height = pressure_to_height_std(bound_pressure)
|
||||
else: # No interpolation, find the closest values
|
||||
idx = (np.abs(pressure - bound)).argmin()
|
||||
bound_pressure = pressure[idx]
|
||||
if height is not None:
|
||||
bound_height = height[idx]
|
||||
else:
|
||||
bound_height = pressure_to_height_std(bound_pressure)
|
||||
|
||||
# Bound is given in height
|
||||
elif bound.check('[length]'):
|
||||
# If there is height data, see if we have the bound or need to interpolate/find nearest
|
||||
if height is not None:
|
||||
if bound in height: # Bound is in the height data
|
||||
bound_height = bound
|
||||
bound_pressure = pressure[height == bound]
|
||||
else: # Bound is not in the data
|
||||
if interpolate:
|
||||
bound_height = bound
|
||||
|
||||
# Need to cast back to the input type since interp (up to at least numpy
|
||||
# 1.13 always returns float64. This can cause upstream users problems,
|
||||
# resulting in something like np.append() to upcast.
|
||||
bound_pressure = np.interp(np.atleast_1d(bound),
|
||||
height, pressure).astype(np.result_type(bound))
|
||||
else:
|
||||
idx = (np.abs(height - bound)).argmin()
|
||||
bound_pressure = pressure[idx]
|
||||
bound_height = height[idx]
|
||||
else: # Don't have heights, so assume a standard atmosphere
|
||||
bound_height = bound
|
||||
bound_pressure = height_to_pressure_std(bound)
|
||||
# If interpolation is on, this is all we need, if not, we need to go back and
|
||||
# find the pressure closest to this and refigure the bounds
|
||||
if not interpolate:
|
||||
idx = (np.abs(pressure - bound_pressure)).argmin()
|
||||
bound_pressure = pressure[idx]
|
||||
bound_height = pressure_to_height_std(bound_pressure)
|
||||
|
||||
# Bound has invalid units
|
||||
else:
|
||||
raise ValueError('Bound must be specified in units of length or pressure.')
|
||||
|
||||
# If the bound is out of the range of the data, we shouldn't extrapolate
|
||||
if not (_greater_or_close(bound_pressure, np.nanmin(pressure))
|
||||
and _less_or_close(bound_pressure, np.nanmax(pressure))):
|
||||
raise ValueError('Specified bound is outside pressure range.')
|
||||
if height is not None and not (_less_or_close(bound_height, np.nanmax(height))
|
||||
and _greater_or_close(bound_height, np.nanmin(height))):
|
||||
raise ValueError('Specified bound is outside height range.')
|
||||
|
||||
return bound_pressure, bound_height
|
||||
|
||||
|
||||
def get_layer_heights(height, depth, *args, **kwargs):
|
||||
"""Return an atmospheric layer from upper air data with the requested bottom and depth.
|
||||
|
||||
@ -167,6 +397,109 @@ def get_layer_heights(height, depth, *args, **kwargs):
|
||||
return ret
|
||||
|
||||
|
||||
def get_layer(pressure, *args, **kwargs):
|
||||
r"""Return an atmospheric layer from upper air data with the requested bottom and depth.
|
||||
|
||||
This function will subset an upper air dataset to contain only the specified layer. The
|
||||
bottom of the layer can be specified with a pressure or height above the surface
|
||||
pressure. The bottom defaults to the surface pressure. The depth of the layer can be
|
||||
specified in terms of pressure or height above the bottom of the layer. If the top and
|
||||
bottom of the layer are not in the data, they are interpolated by default.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pressure : array-like
|
||||
Atmospheric pressure profile
|
||||
args : array-like
|
||||
Atmospheric variable(s) measured at the given pressures
|
||||
height: array-like, optional
|
||||
Atmospheric heights corresponding to the given pressures. Defaults to using
|
||||
heights calculated from ``pressure`` assuming a standard atmosphere [NOAA1976]_.
|
||||
bottom : `pint.Quantity`, optional
|
||||
Bottom of the layer as a pressure or height above the surface pressure. Defaults
|
||||
to the highest pressure or lowest height given.
|
||||
depth : `pint.Quantity`, optional
|
||||
Thickness of the layer as a pressure or height above the bottom of the layer.
|
||||
Defaults to 100 hPa.
|
||||
interpolate : bool, optional
|
||||
Interpolate the top and bottom points if they are not in the given data. Defaults
|
||||
to True.
|
||||
|
||||
Returns
|
||||
-------
|
||||
`pint.Quantity, pint.Quantity`
|
||||
The pressure and data variables of the layer
|
||||
|
||||
Notes
|
||||
-----
|
||||
Only functions on 1D profiles (not higher-dimension vertical cross sections or grids).
|
||||
Also, this will return Pint Quantities even when given xarray DataArray profiles.
|
||||
"""
|
||||
height = kwargs.pop('height', None)
|
||||
bottom = kwargs.pop('bottom', None)
|
||||
depth = kwargs.pop('depth', 100) #'hPa'
|
||||
interpolate = kwargs.pop('interpolate', True)
|
||||
|
||||
# Make sure pressure and datavars are the same length
|
||||
for datavar in args:
|
||||
if len(pressure) != len(datavar):
|
||||
raise ValueError('Pressure and data variables must have the same length.')
|
||||
|
||||
# If the bottom is not specified, make it the surface pressure
|
||||
if bottom is None:
|
||||
bottom = np.max(pressure)
|
||||
|
||||
bottom_pressure, bottom_height = _get_bound_pressure_height(pressure, bottom,
|
||||
height=height,
|
||||
interpolate=interpolate)
|
||||
|
||||
# Calculate the top in whatever units depth is in
|
||||
if depth.check('[length]**-1 * [mass] * [time]**-2'):
|
||||
top = bottom_pressure - depth
|
||||
elif depth.check('[length]'):
|
||||
top = bottom_height + depth
|
||||
else:
|
||||
raise ValueError('Depth must be specified in units of length or pressure')
|
||||
|
||||
top_pressure, _ = _get_bound_pressure_height(pressure, top, height=height,
|
||||
interpolate=interpolate)
|
||||
|
||||
ret = [] # returned data variables in layer
|
||||
|
||||
# Ensure pressures are sorted in ascending order
|
||||
sort_inds = np.argsort(pressure)
|
||||
pressure = pressure[sort_inds]
|
||||
|
||||
# Mask based on top and bottom pressure
|
||||
inds = (_less_or_close(pressure, bottom_pressure)
|
||||
& _greater_or_close(pressure, top_pressure))
|
||||
p_interp = pressure[inds]
|
||||
|
||||
# Interpolate pressures at bounds if necessary and sort
|
||||
if interpolate:
|
||||
# If we don't have the bottom or top requested, append them
|
||||
if not np.any(np.isclose(top_pressure, p_interp)):
|
||||
p_interp = np.sort(np.append(p_interp.m, top_pressure.m))
|
||||
if not np.any(np.isclose(bottom_pressure, p_interp)):
|
||||
p_interp = np.sort(np.append(p_interp.m, bottom_pressure.m))
|
||||
|
||||
ret.append(p_interp[::-1])
|
||||
|
||||
for datavar in args:
|
||||
# Ensure that things are sorted in ascending order
|
||||
datavar = datavar[sort_inds]
|
||||
|
||||
if interpolate:
|
||||
# Interpolate for the possibly missing bottom/top values
|
||||
datavar_interp = log_interpolate_1d(p_interp, pressure, datavar)
|
||||
datavar = datavar_interp
|
||||
else:
|
||||
datavar = datavar[inds]
|
||||
|
||||
ret.append(datavar[::-1])
|
||||
return ret
|
||||
|
||||
|
||||
def find_bounding_indices(arr, values, axis, from_below=True):
|
||||
"""Find the indices surrounding the values within arr along axis.
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import mipylib.numeric as np
|
||||
|
||||
__all__ = ['broadcast_indices']
|
||||
__all__ = ['broadcast_indices', 'validate_choice']
|
||||
|
||||
def broadcast_indices(x, minv, ndim, axis):
|
||||
"""Calculate index values to properly broadcast index array within data array.
|
||||
@ -17,4 +17,36 @@ def broadcast_indices(x, minv, ndim, axis):
|
||||
broadcast_slice[dim] = slice(None)
|
||||
dim_inds = np.arange(x.shape[dim])
|
||||
ret.append(dim_inds[tuple(broadcast_slice)])
|
||||
return tuple(ret)
|
||||
return tuple(ret)
|
||||
|
||||
def validate_choice(options, **kwargs):
|
||||
r"""Confirm that a choice is contained within a set of options.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
options : iterable
|
||||
|
||||
\*\*kwargs
|
||||
Function kwarg names (keys) and their passed-in values (values) for validation
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the choice is not contained within any of these options, present valid options
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from mipylib.meteolib.cbook import validate_choice
|
||||
>>> def try_wrong_choice(color=None):
|
||||
... validate_choice({'blue', 'green', 'yellow'}, color=color)
|
||||
>>> try_wrong_choice(color='red') # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
ValueError: 'red' is not a valid option for color.
|
||||
Valid options are 'yellow', 'green', 'blue'.
|
||||
"""
|
||||
for kw, choice in kwargs.items():
|
||||
if choice not in options:
|
||||
raise ValueError(
|
||||
'{} is not a valid option for {}. '
|
||||
'Valid options are {}.'.format(choice, kw,", ".join(map(repr, options)))
|
||||
)
|
||||
|
||||
@ -1,15 +1,47 @@
|
||||
P0 = 1000. #reference pressure for potential temperature (hPa)
|
||||
R = 8.3144598 #molar gas constant (J / K / mol)
|
||||
Mw = 18.01528 #Molecular weight of water (g / mol)
|
||||
Md = 28.9644 #Nominal molecular weight of dry air at the surface of th Earth (g / mol)
|
||||
Rd = R / Md #Gas constant for dry air at the surface of the Earth (J (K kg)^-1)
|
||||
Lv = 2.501e6 #Latent heat of vaporization for liquid water at 0C (J kg^-1)
|
||||
Cp_d = 1005 #Specific heat at constant pressure for dry air (J kg^-1)
|
||||
epsilon = Mw / Md
|
||||
kappa = 0.286
|
||||
degCtoK = 273.15 # Temperature offset between K and C (deg C)
|
||||
|
||||
# Earth
|
||||
Re = earth_avg_radius = 6371008.7714 # Earth average radius with meters
|
||||
g = 9.8 # Gravitational acceleration (m / s^2)
|
||||
earth_gravity = g = 9.80665 #Gravitational acceleration (m / s^2)
|
||||
G = gravitational_constant = 6.67430e-11 #(m^3 / kg / s^2)
|
||||
GM = geocentric_gravitational_constant = 3986005e8 #(m^3 / s^2)
|
||||
omega = 7292115e-11 # Avg. angular velocity of Earth (rad / s)
|
||||
d = earth_sfc_avg_dist_sun = 149597870700. #(m)
|
||||
S = earth_solar_irradiance = 1360.8 #(W / m^2)
|
||||
delta = earth_max_declination = 23.45 #(degrees)
|
||||
earth_orbit_eccentricity = 0.0167 #(dimensionless)
|
||||
earth_mass = me = geocentric_gravitational_constant / gravitational_constant
|
||||
|
||||
# molar gas constant
|
||||
R = 8.314462618 #molar gas constant (J / K / mol)
|
||||
|
||||
# Water
|
||||
Mw = 18.015268 #Molecular weight of water (g / mol)
|
||||
Rv = water_gas_constant = R / Mw * 1000 #(J / kg / K)
|
||||
rho_l = density_water = 999.97495 #(kg / m^3)
|
||||
wv_specific_heat_ratio = 1.330 #(dimensionless)
|
||||
Cp_v = wv_specific_heat_press = wv_specific_heat_ratio * Rv / (wv_specific_heat_ratio - 1)
|
||||
Cv_v = wv_specific_heat_vol = Cp_v / wv_specific_heat_ratio
|
||||
Cp_l = water_specific_heat = 4219.4 #(J / kg / K)
|
||||
Lv = water_heat_vaporization = 2.50084e6 #Latent heat of vaporization for liquid water at 0C (J kg^-1)
|
||||
Lf = water_heat_fusion = 3.337e5 #(J / kg)
|
||||
Ls = water_heat_sublimation = Lv + Lf
|
||||
Cp_i = ice_specific_heat = 2090 #(J / kg / K)
|
||||
rho_i = density_ice = 917 #(kg / m^3)
|
||||
sat_pressure_0c = 6.112 #Saturation pressure at 0 degree (hPa)
|
||||
T0 = water_triple_point_temperature = 273.16 #(K)
|
||||
|
||||
# Dry air
|
||||
Md = dry_air_molecular_weight = 28.96546e-3 #(kg / mol)
|
||||
Rd = dry_air_gas_constant = R / Md #Gas constant for dry air at the surface of the Earth (J (K kg)^-1)
|
||||
dry_air_spec_heat_ratio = 1.4 #(dimensionless)
|
||||
Cp_d = dry_air_spec_heat_press = dry_air_spec_heat_ratio * Rd / (dry_air_spec_heat_ratio - 1)
|
||||
Cv_d = dry_air_spec_heat_vol = Cp_d / dry_air_spec_heat_ratio
|
||||
degCtoK = 273.15 # Temperature offset between K and C (deg C)
|
||||
rho_d = dry_air_density_stp = 1000. / (Rd * degCtoK)
|
||||
|
||||
# General meteorology constants
|
||||
P0 = 1000. #reference pressure for potential temperature (hPa)
|
||||
kappa = poisson_exponent = Rd / Cp_d
|
||||
gamma_d = dry_adiabatic_lapse_rate = g / Cp_d
|
||||
epsilon = Mw / Md / 1000.
|
||||
T_BASE = 300.
|
||||
omega = 7292115e-11 # Avg. angular velocity of Earth (rad / s)
|
||||
Binary file not shown.
@ -14,12 +14,12 @@ import constants as constants
|
||||
from .calc.thermo import saturation_vapor_pressure, saturation_mixing_ratio
|
||||
|
||||
__all__ = [
|
||||
'cumsimp','dewpoint2rh','dewpoint_rh','dry_lapse','ds2uv',
|
||||
'cumsimp','dewpoint2rh','dewpoint_rh','ds2uv',
|
||||
'flowfun','h2p',
|
||||
'moist_lapse','p2h','qair2rh','rh2dewpoint',
|
||||
'p2h','qair2rh','rh2dewpoint',
|
||||
'sigma_to_pressure','tc2tf',
|
||||
'tf2tc','uv2ds','pressure_to_height_std',
|
||||
'height_to_pressure_std','eof','vapor_pressure'
|
||||
'tf2tc','uv2ds',
|
||||
'eof'
|
||||
]
|
||||
|
||||
def uv2ds(u, v):
|
||||
@ -67,20 +67,7 @@ def p2h(press):
|
||||
return press.array_wrap(r)
|
||||
else:
|
||||
return MeteoMath.press2Height(press)
|
||||
|
||||
def pressure_to_height_std(press):
|
||||
"""
|
||||
Convert pressure data to heights using the U.S. standard atmosphere.
|
||||
|
||||
:param press: (*float*) Pressure - hPa.
|
||||
|
||||
:returns: (*float*) Height - meter.
|
||||
"""
|
||||
t0 = 288.
|
||||
gamma = 6.5
|
||||
p0 = 1013.25
|
||||
h = (t0 / gamma) * (1 - (press / p0)**(constants.Rd * gamma / constants.g)) * 1000
|
||||
return h
|
||||
|
||||
|
||||
def h2p(height):
|
||||
"""
|
||||
@ -95,21 +82,7 @@ def h2p(height):
|
||||
return height.array_wrap(r)
|
||||
else:
|
||||
return MeteoMath.height2Press(height)
|
||||
|
||||
def height_to_pressure_std(height):
|
||||
"""
|
||||
Convert height data to pressures using the U.S. standard atmosphere.
|
||||
|
||||
:param height: (*float*) Height - meter.
|
||||
|
||||
:returns: (*float*) Height - meter.
|
||||
"""
|
||||
t0 = 288.
|
||||
gamma = 6.5
|
||||
p0 = 1013.25
|
||||
height = height * 0.001
|
||||
p = p0 * (1 - (gamma / t0) * height) ** (constants.g / (constants.Rd * gamma))
|
||||
return p
|
||||
|
||||
|
||||
def sigma_to_pressure(sigma, psfc, ptop):
|
||||
r"""Calculate pressure from sigma values.
|
||||
@ -237,6 +210,7 @@ def rh2dewpoint(rh, temp):
|
||||
else:
|
||||
return MeteoMath.rh2dewpoint(rh, temp)
|
||||
|
||||
|
||||
def dewpoint_rh(temperature, rh):
|
||||
r"""Calculate the ambient dewpoint given air temperature and relative humidity.
|
||||
|
||||
@ -260,74 +234,6 @@ def dewpoint_rh(temperature, rh):
|
||||
# warnings.warn('Relative humidity >120%, ensure proper units.')
|
||||
return dewpoint(rh * saturation_vapor_pressure(temperature))
|
||||
|
||||
def dry_lapse(pressure, temperature):
|
||||
"""
|
||||
Calculate the temperature at a level assuming only dry processes
|
||||
operating from the starting point.
|
||||
This function lifts a parcel starting at `temperature`, conserving
|
||||
potential temperature. The starting pressure should be the first item in
|
||||
the `pressure` array.
|
||||
Parameters
|
||||
----------
|
||||
pressure : array_like
|
||||
The atmospheric pressure level(s) of interest
|
||||
temperature : array_like
|
||||
The starting temperature
|
||||
Returns
|
||||
-------
|
||||
array_like
|
||||
The resulting parcel temperature at levels given by `pressure`
|
||||
See Also
|
||||
--------
|
||||
moist_lapse : Calculate parcel temperature assuming liquid saturation
|
||||
processes
|
||||
parcel_profile : Calculate complete parcel profile
|
||||
potential_temperature
|
||||
"""
|
||||
|
||||
return temperature * (pressure / pressure[0])**constants.kappa
|
||||
|
||||
def moist_lapse(pressure, temperature):
|
||||
"""
|
||||
Calculate the temperature at a level assuming liquid saturation processes
|
||||
operating from the starting point.
|
||||
This function lifts a parcel starting at `temperature`. The starting
|
||||
pressure should be the first item in the `pressure` array. Essentially,
|
||||
this function is calculating moist pseudo-adiabats.
|
||||
Parameters
|
||||
----------
|
||||
pressure : array_like
|
||||
The atmospheric pressure level(s) of interest
|
||||
temperature : array_like
|
||||
The starting temperature
|
||||
Returns
|
||||
-------
|
||||
array_like
|
||||
The temperature corresponding to the the starting temperature and
|
||||
pressure levels.
|
||||
See Also
|
||||
--------
|
||||
dry_lapse : Calculate parcel temperature assuming dry adiabatic processes
|
||||
parcel_profile : Calculate complete parcel profile
|
||||
Notes
|
||||
-----
|
||||
This function is implemented by integrating the following differential
|
||||
equation:
|
||||
.. math:: \frac{dT}{dP} = \frac{1}{P} \frac{R_d T + L_v r_s}
|
||||
{C_{pd} + \frac{L_v^2 r_s \epsilon}{R_d T^2}}
|
||||
This equation comes from [1]_.
|
||||
References
|
||||
----------
|
||||
.. [1] Bakhshaii, A. and R. Stull, 2013: Saturated Pseudoadiabats--A
|
||||
Noniterative Approximation. J. Appl. Meteor. Clim., 52, 5-15.
|
||||
"""
|
||||
|
||||
def dt(t, p):
|
||||
rs = saturation_mixing_ratio(p, t)
|
||||
frac = ((constants.Rd * t + constants.Lv * rs) /
|
||||
(constants.Cp_d + (constants.Lv * constants.Lv * rs * constants.epsilon / (constants.Rd * t * t)))).to('kelvin')
|
||||
return frac / p
|
||||
return dt
|
||||
|
||||
def vapor_pressure(pressure, mixing):
|
||||
r"""Calculate water vapor (partial) pressure.
|
||||
@ -355,6 +261,7 @@ def vapor_pressure(pressure, mixing):
|
||||
"""
|
||||
return pressure * mixing / (constants.epsilon + mixing)
|
||||
|
||||
|
||||
def cumsimp(y):
|
||||
"""
|
||||
Simpson-rule column-wise cumulative summation.
|
||||
@ -418,6 +325,7 @@ def cumsimp(y):
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def flowfun(u, v):
|
||||
"""
|
||||
Computes the potential PHI and the streamfunction PSI
|
||||
@ -471,7 +379,8 @@ def flowfun(u, v):
|
||||
psi = (psi+cumsimp(v.T).T - np.tile(cy, [1,lx]))/2
|
||||
|
||||
return psi, phi
|
||||
|
||||
|
||||
|
||||
def eof(x, svd=True, transform=False, return_index=False):
|
||||
"""
|
||||
Empirical Orthogonal Function (EOF) analysis to finds both time series and spatial patterns.
|
||||
@ -537,7 +446,8 @@ def eof(x, svd=True, transform=False, return_index=False):
|
||||
return EOF, E, PC, np.arange(x.shape[0])
|
||||
else:
|
||||
return EOF, E, PC
|
||||
|
||||
|
||||
|
||||
def varimax(x, norm=True, tol=1e-10, it_max=1000):
|
||||
"""
|
||||
Rotate EOFs according to varimax algorithm
|
||||
@ -588,4 +498,4 @@ def varimax(x, norm=True, tol=1e-10, it_max=1000):
|
||||
rr[valid_idx,:] = r
|
||||
r = rr
|
||||
|
||||
return r, TT
|
||||
return r, TT
|
||||
|
||||
@ -840,6 +840,40 @@ class NDArray(object):
|
||||
"""
|
||||
self._array = ArrayUtil.sort(self._array, axis)
|
||||
|
||||
def searchsorted(self, v, side='left', sorter=None):
|
||||
"""
|
||||
Find indices where elements should be inserted to maintain order.
|
||||
|
||||
Find the indices into a sorted array a such that, if the corresponding elements in v were inserted
|
||||
before the indices, the order of a would be preserved.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
v : array_like
|
||||
Input array. If sorter is None, then it must be sorted in ascending order, otherwise sorter must
|
||||
be an array of indices that sort it.
|
||||
|
||||
side : {‘left’, ‘right’}, optional
|
||||
If ‘left’, the index of the first suitable location found is given. If ‘right’, return the last
|
||||
such index. If there is no suitable index, return either 0 or N (where N is the length of a).
|
||||
|
||||
sorter : 1-D array_like, optional
|
||||
Optional array of integer indices that sort array a into ascending order. They are typically
|
||||
the result of argsort.
|
||||
|
||||
Returns
|
||||
-------
|
||||
indices : int or array of ints
|
||||
Array of insertion points with the same shape as v, or an integer if v is a scalar.
|
||||
"""
|
||||
v = NDArray(v)
|
||||
left = True if side == 'left' else False
|
||||
r = ArrayUtil.searchSorted(self._array, v._array, left)
|
||||
if isinstance(r, int):
|
||||
return r
|
||||
else:
|
||||
return self.array_wrap(r)
|
||||
|
||||
def max(self, axis=None):
|
||||
"""
|
||||
Get maximum value along an axis.
|
||||
|
||||
Binary file not shown.
@ -75,7 +75,7 @@ def nonzero(a):
|
||||
a = array(a)
|
||||
ra = ArrayUtil.nonzero(a.asarray())
|
||||
if ra is None:
|
||||
return None
|
||||
return tuple([NDArray([]).astype('int')])
|
||||
|
||||
r = []
|
||||
for aa in ra:
|
||||
|
||||
Binary file not shown.
@ -354,25 +354,35 @@ def zeros_like(a, dtype=None):
|
||||
dtype = _dtype.fromjava(a.dtype)
|
||||
elif isinstance(dtype, basestring):
|
||||
dtype = _dtype.DataType(dtype)
|
||||
|
||||
return NDArray(ArrayUtil.zeros(shape, dtype._dtype))
|
||||
|
||||
def empty_like(a, dtype=None):
|
||||
|
||||
def empty_like(a, dtype=None, shape=None):
|
||||
"""
|
||||
Return a new array with the same shape and type as a given array.
|
||||
|
||||
:param a: (*array*) The shape and data-type of a define these same attributes of the returned array.
|
||||
:param dtype: (*string*) Overrides the data type of the result. Default is ``None``, keep the data
|
||||
type of array ``a``.
|
||||
:param shape: (*list of int*) Overrides the shape of the result. Default is ``None``.
|
||||
|
||||
:returns: Array of uninitialized (arbitrary) data with the same shape and type as prototype.
|
||||
"""
|
||||
shape = a.shape
|
||||
if shape is None:
|
||||
shape = a.shape
|
||||
|
||||
if isinstance(shape, int):
|
||||
shape = [shape]
|
||||
|
||||
if dtype is None:
|
||||
dtype = a.dtype
|
||||
elif isinstance(dtype, basestring):
|
||||
dtype = _dtype.DataType(dtype)
|
||||
|
||||
return NDArray(ArrayUtil.empty(shape, dtype._dtype))
|
||||
|
||||
|
||||
|
||||
def ones_like(a, dtype=None):
|
||||
"""
|
||||
Return an array of ones with the same shape and type as a given array.
|
||||
@ -389,7 +399,8 @@ def ones_like(a, dtype=None):
|
||||
elif isinstance(dtype, basestring):
|
||||
dtype = _dtype.DataType(dtype)
|
||||
return NDArray(ArrayUtil.ones(shape, dtype._dtype))
|
||||
|
||||
|
||||
|
||||
def ones(shape, dtype='float'):
|
||||
"""
|
||||
Create a new aray of given shape and type, filled with ones.
|
||||
@ -415,10 +426,13 @@ def ones(shape, dtype='float'):
|
||||
shapelist.append(shape)
|
||||
else:
|
||||
shapelist = shape
|
||||
|
||||
if isinstance(dtype, basestring):
|
||||
dtype = _dtype.DataType(dtype)
|
||||
|
||||
return NDArray(ArrayUtil.ones(shapelist, dtype._dtype))
|
||||
|
||||
|
||||
|
||||
def full(shape, fill_value, dtype=None):
|
||||
"""
|
||||
Return a new array of given shape and type, filled with fill_value.
|
||||
@ -555,15 +569,16 @@ def repeat(a, repeats, axis=None):
|
||||
"""
|
||||
if isinstance(repeats, int):
|
||||
repeats = [repeats]
|
||||
if isinstance(a, (list, tuple)):
|
||||
a = array(a)
|
||||
if isinstance(a, NDArray):
|
||||
a = a.asarray()
|
||||
repeats = asarray(repeats)
|
||||
|
||||
a = asarray(a)
|
||||
if axis is None:
|
||||
r = ArrayUtil.repeat(a, repeats)
|
||||
r = ArrayUtil.repeat(a._array, repeats._array)
|
||||
else:
|
||||
r = ArrayUtil.repeat(a, repeats, axis)
|
||||
r = ArrayUtil.repeat(a._array, repeats._array, axis)
|
||||
|
||||
return NDArray(r)
|
||||
|
||||
|
||||
def tile(a, repeats):
|
||||
"""
|
||||
@ -2031,7 +2046,10 @@ def concatenate(arrays, axis=0):
|
||||
"""
|
||||
ars = []
|
||||
for a in arrays:
|
||||
if isinstance(a, numbers.Number):
|
||||
a = [a]
|
||||
ars.append(asanyarray(a).asarray())
|
||||
|
||||
r = ArrayUtil.concatenate(ars, axis)
|
||||
return NDArray(r)
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
|
||||
from org.meteoinfo.ndarray.math import ArrayUtil
|
||||
from .. import core as np
|
||||
|
||||
__all__ = ['unique', 'intersect1d']
|
||||
|
||||
__all__ = ['unique', 'ediff1d', 'intersect1d']
|
||||
|
||||
|
||||
def _unique1d(ar, return_index=False, return_inverse=False,
|
||||
return_counts=False):
|
||||
@ -46,28 +49,78 @@ def _unique1d(ar, return_index=False, return_inverse=False,
|
||||
ret += (np.diff(idx),)
|
||||
return ret
|
||||
|
||||
def unique(a, return_index=False, axis=None):
|
||||
|
||||
def unique(a, return_index=False, return_inverse=False, return_counts=False, axis=None):
|
||||
"""
|
||||
Find the unique elements of an array.
|
||||
|
||||
Returns the sorted unique elements of an array.
|
||||
Returns the sorted unique elements of an array. There are three optional
|
||||
outputs in addition to the unique elements:
|
||||
|
||||
:param a: (*array_like*) Array to be sorted.
|
||||
:param return_index: (*bool*) Optional, If True, also return the indices of ar (along the specified
|
||||
axis, if provided, or in the flattened array) that result in the unique array.
|
||||
:param axis: (*int or None*) Optional. Axis along which to operate on. If None, the array is
|
||||
flattened.
|
||||
* the indices of the input array that give the unique values
|
||||
* the indices of the unique array that reconstruct the input array
|
||||
* the number of times each unique value comes up in the input array
|
||||
|
||||
:returns: (*NDArray*) Sorted unique elements of input array.
|
||||
Parameters
|
||||
----------
|
||||
ar : array_like
|
||||
Input array. Unless `axis` is specified, this will be flattened if it
|
||||
is not already 1-D.
|
||||
return_index : bool, optional
|
||||
If True, also return the indices of `ar` (along the specified axis,
|
||||
if provided, or in the flattened array) that result in the unique array.
|
||||
return_inverse : bool, optional
|
||||
If True, also return the indices of the unique array (for the specified
|
||||
axis, if provided) that can be used to reconstruct `ar`.
|
||||
return_counts : bool, optional
|
||||
If True, also return the number of times each unique item appears
|
||||
in `ar`.
|
||||
axis : int or None, optional
|
||||
The axis to operate on. If None, `ar` will be flattened. If an integer,
|
||||
the subarrays indexed by the given axis will be flattened and treated
|
||||
as the elements of a 1-D array with the dimension of the given axis,
|
||||
see the notes for more details. Object arrays or structured arrays
|
||||
that contain objects are not supported if the `axis` kwarg is used. The
|
||||
default is None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
unique : ndarray
|
||||
The sorted unique values.
|
||||
unique_indices : ndarray, optional
|
||||
The indices of the first occurrences of the unique values in the
|
||||
original array. Only provided if `return_index` is True.
|
||||
unique_inverse : ndarray, optional
|
||||
The indices to reconstruct the original array from the
|
||||
unique array. Only provided if `return_inverse` is True.
|
||||
unique_counts : ndarray, optional
|
||||
The number of times each of the unique values comes up in the
|
||||
original array. Only provided if `return_counts` is True.
|
||||
|
||||
See Also
|
||||
--------
|
||||
repeat : Repeat elements of an array.
|
||||
sort : Return a sorted copy of an array.
|
||||
|
||||
Notes
|
||||
-----
|
||||
When an axis is specified the subarrays indexed by the axis are sorted.
|
||||
This is done by making the specified axis the first dimension of the array
|
||||
(move the axis to the first dimension to keep the order of the other axes)
|
||||
and then flattening the subarrays in C order. The flattened subarrays are
|
||||
then viewed as a structured type with each element given a label, with the
|
||||
effect that we end up with a 1-D array of structured types that can be
|
||||
treated in the same way as any other 1-D array. The result is that the
|
||||
flattened subarrays are sorted in lexicographic order starting with the
|
||||
first element.
|
||||
"""
|
||||
if isinstance(a, list):
|
||||
if isinstance(a, (tuple, list)):
|
||||
a = np.array(a)
|
||||
if return_index:
|
||||
r = ArrayUtil.uniqueIndex(a.asarray(), axis)
|
||||
return np.NDArray(r[0]), np.NDArray(r[1])
|
||||
else:
|
||||
r = ArrayUtil.unique(a.asarray(), axis)
|
||||
return np.NDArray(r)
|
||||
|
||||
r = ArrayUtil.unique(a._array, axis, return_index, return_inverse, return_counts)
|
||||
|
||||
return np.NDArray(r[0]) if len(r) == 1 else [np.NDArray(rr) for rr in r]
|
||||
|
||||
|
||||
def intersect1d(ar1, ar2, assume_unique=False, return_indices=False):
|
||||
"""
|
||||
@ -155,4 +208,86 @@ def intersect1d(ar1, ar2, assume_unique=False, return_indices=False):
|
||||
|
||||
return int1d, ar1_indices, ar2_indices
|
||||
else:
|
||||
return int1d
|
||||
return int1d
|
||||
|
||||
|
||||
def ediff1d(ary, to_end=None, to_begin=None):
|
||||
"""
|
||||
The differences between consecutive elements of an array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ary : array_like
|
||||
If necessary, will be flattened before the differences are taken.
|
||||
to_end : array_like, optional
|
||||
Number(s) to append at the end of the returned differences.
|
||||
to_begin : array_like, optional
|
||||
Number(s) to prepend at the beginning of the returned differences.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ediff1d : ndarray
|
||||
The differences. Loosely, this is ``ary.flat[1:] - ary.flat[:-1]``.
|
||||
|
||||
See Also
|
||||
--------
|
||||
diff, gradient
|
||||
|
||||
Notes
|
||||
-----
|
||||
When applied to masked arrays, this function drops the mask information
|
||||
if the `to_begin` and/or `to_end` parameters are used.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> x = np.array([1, 2, 4, 7, 0])
|
||||
>>> np.ediff1d(x)
|
||||
array([ 1, 2, 3, -7])
|
||||
|
||||
>>> np.ediff1d(x, to_begin=-99, to_end=np.array([88, 99]))
|
||||
array([-99, 1, 2, ..., -7, 88, 99])
|
||||
|
||||
The returned array is always 1D.
|
||||
|
||||
>>> y = [[1, 2, 4], [1, 6, 24]]
|
||||
>>> np.ediff1d(y)
|
||||
array([ 1, 2, -3, 5, 18])
|
||||
|
||||
"""
|
||||
conv = np.asanyarray(ary)
|
||||
# Convert to (any) array and ravel:
|
||||
ary = conv.ravel()
|
||||
|
||||
# enforce that the dtype of `ary` is used for the output
|
||||
dtype_req = ary.dtype
|
||||
|
||||
# fast track default case
|
||||
if to_begin is None and to_end is None:
|
||||
return ary[1:] - ary[:-1]
|
||||
|
||||
if to_begin is None:
|
||||
l_begin = 0
|
||||
else:
|
||||
to_begin = np.asanyarray(to_begin)
|
||||
to_begin = to_begin.ravel()
|
||||
l_begin = len(to_begin)
|
||||
|
||||
if to_end is None:
|
||||
l_end = 0
|
||||
else:
|
||||
to_end = np.asanyarray(to_end)
|
||||
to_end = to_end.ravel()
|
||||
l_end = len(to_end)
|
||||
|
||||
# do the calculation in place and copy to_begin and to_end
|
||||
l_diff = max(len(ary) - 1, 0)
|
||||
result = np.empty_like(ary, shape=l_diff + l_begin + l_end)
|
||||
|
||||
if l_begin > 0:
|
||||
result[:l_begin] = to_begin
|
||||
if l_end > 0:
|
||||
result[l_begin + l_diff:] = to_end
|
||||
|
||||
result[l_begin:l_begin+l_diff] = ary[1:] - ary[:-1]
|
||||
|
||||
return result
|
||||
|
||||
@ -4,7 +4,7 @@ from org.meteoinfo.ndarray.math import ArrayUtil
|
||||
|
||||
nomask = False
|
||||
|
||||
__all__ = ['MaskedArray','masked_array','masked_invalid','getdata']
|
||||
__all__ = ['MaskedArray','masked_array','masked_invalid','getdata','getmask','is_masked']
|
||||
|
||||
class MaskedArray(NDArray):
|
||||
"""
|
||||
@ -69,6 +69,92 @@ class MaskedArray(NDArray):
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def __setmask__(self, mask, copy=False):
|
||||
"""
|
||||
Set the mask.
|
||||
|
||||
"""
|
||||
idtype = self.dtype
|
||||
current_mask = self._mask
|
||||
if mask is masked:
|
||||
mask = True
|
||||
|
||||
if current_mask is nomask:
|
||||
# Make sure the mask is set
|
||||
# Just don't do anything if there's nothing to do.
|
||||
if mask is nomask:
|
||||
return
|
||||
current_mask = self._mask = make_mask_none(self.shape, idtype)
|
||||
|
||||
if idtype.names is None:
|
||||
# No named fields.
|
||||
# Hardmask: don't unmask the data
|
||||
if self._hardmask:
|
||||
current_mask |= mask
|
||||
# Softmask: set everything to False
|
||||
# If it's obviously a compatible scalar, use a quick update
|
||||
# method.
|
||||
elif isinstance(mask, (int, float, np.bool, np.number)):
|
||||
current_mask[...] = mask
|
||||
# Otherwise fall back to the slower, general purpose way.
|
||||
else:
|
||||
current_mask.flat = mask
|
||||
else:
|
||||
# Named fields w/
|
||||
mdtype = current_mask.dtype
|
||||
mask = np.asarray(mask)
|
||||
# Mask is a singleton
|
||||
if not mask.ndim:
|
||||
# It's a boolean : make a record
|
||||
if mask.dtype.kind == 'b':
|
||||
mask = np.array(tuple([mask.item()] * len(mdtype)),
|
||||
dtype=mdtype)
|
||||
# It's a record: make sure the dtype is correct
|
||||
else:
|
||||
mask = mask.astype(mdtype)
|
||||
# Mask is a sequence
|
||||
else:
|
||||
# Make sure the new mask is a ndarray with the proper dtype
|
||||
try:
|
||||
copy = None if not copy else True
|
||||
mask = np.array(mask, copy=copy, dtype=mdtype)
|
||||
# Or assume it's a sequence of bool/int
|
||||
except TypeError:
|
||||
mask = np.array([tuple([m] * len(mdtype)) for m in mask],
|
||||
dtype=mdtype)
|
||||
# Hardmask: don't unmask the data
|
||||
if self._hardmask:
|
||||
for n in idtype.names:
|
||||
current_mask[n] |= mask[n]
|
||||
# Softmask: set everything to False
|
||||
# If it's obviously a compatible scalar, use a quick update
|
||||
# method.
|
||||
elif isinstance(mask, (int, float, np.dtyp.bool, np.dtype.number)):
|
||||
current_mask[...] = mask
|
||||
# Otherwise fall back to the slower, general purpose way.
|
||||
else:
|
||||
current_mask.flat = mask
|
||||
# Reshape if needed
|
||||
if current_mask.shape:
|
||||
current_mask.shape = self.shape
|
||||
return
|
||||
|
||||
_set_mask = __setmask__
|
||||
|
||||
@property
|
||||
def mask(self):
|
||||
""" Current mask. """
|
||||
|
||||
# We could try to force a reshape, but that wouldn't work in some
|
||||
# cases.
|
||||
# Return a view so that the dtype and shape cannot be changed in place
|
||||
# This still preserves nomask by identity
|
||||
return self._mask.view()
|
||||
|
||||
@mask.setter
|
||||
def mask(self, value):
|
||||
self.__setmask__(value)
|
||||
|
||||
def filled(self, fill_value=None):
|
||||
"""
|
||||
Return a copy of self, with masked values filled with a given value.
|
||||
@ -164,8 +250,10 @@ class MaskedArray(NDArray):
|
||||
cnt = self.count(axis=axis)
|
||||
return dsum * 1. / cnt
|
||||
|
||||
|
||||
masked_array = MaskedArray
|
||||
|
||||
|
||||
def getdata(a, subok=True):
|
||||
"""
|
||||
Return the data of a masked array as an ndarray.
|
||||
@ -208,6 +296,68 @@ def getdata(a, subok=True):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def getmask(a):
|
||||
"""
|
||||
Return the mask of a masked array, or nomask.
|
||||
|
||||
Return the mask of `a` as an ndarray if `a` is a `MaskedArray` and the
|
||||
mask is not `nomask`, else return `nomask`. To guarantee a full array
|
||||
of booleans of the same shape as a, use `getmaskarray`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : array_like
|
||||
Input `MaskedArray` for which the mask is required.
|
||||
|
||||
See Also
|
||||
--------
|
||||
getdata : Return the data of a masked array as an ndarray.
|
||||
getmaskarray : Return the mask of a masked array, or full array of False.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> a = np.ma.masked_equal([[1,2],[3,4]], 2)
|
||||
>>> a
|
||||
masked_array(
|
||||
data=[[1, --],
|
||||
[3, 4]],
|
||||
mask=[[False, True],
|
||||
[False, False]],
|
||||
fill_value=2)
|
||||
>>> ma.getmask(a)
|
||||
array([[False, True],
|
||||
[False, False]])
|
||||
|
||||
Equivalently use the `MaskedArray` `mask` attribute.
|
||||
|
||||
>>> a.mask
|
||||
array([[False, True],
|
||||
[False, False]])
|
||||
|
||||
Result when mask == `nomask`
|
||||
|
||||
>>> b = np.ma.masked_array([[1,2],[3,4]])
|
||||
>>> b
|
||||
masked_array(
|
||||
data=[[1, 2],
|
||||
[3, 4]],
|
||||
mask=False,
|
||||
fill_value=999999)
|
||||
>>> np.ma.nomask
|
||||
False
|
||||
>>> np.ma.getmask(b) == np.ma.nomask
|
||||
True
|
||||
>>> b.mask == np.ma.nomask
|
||||
True
|
||||
|
||||
"""
|
||||
return getattr(a, '_mask', nomask)
|
||||
|
||||
|
||||
get_mask = getmask
|
||||
|
||||
|
||||
def masked_invalid(a, copy=True):
|
||||
"""
|
||||
Mask an array where invalid values occur (NaNs or infs).
|
||||
@ -244,4 +394,58 @@ def masked_invalid(a, copy=True):
|
||||
else:
|
||||
result = MaskedArray(a)
|
||||
result._mask = condition
|
||||
return result
|
||||
return result
|
||||
|
||||
|
||||
def is_masked(x):
|
||||
"""
|
||||
Determine whether input has masked values.
|
||||
|
||||
Accepts any object as input, but always returns False unless the
|
||||
input is a MaskedArray containing masked values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
Array to check for masked values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
True if `x` is a MaskedArray with masked values, False otherwise.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> import numpy.ma as ma
|
||||
>>> x = ma.masked_equal([0, 1, 0, 2, 3], 0)
|
||||
>>> x
|
||||
masked_array(data=[--, 1, --, 2, 3],
|
||||
mask=[ True, False, True, False, False],
|
||||
fill_value=0)
|
||||
>>> ma.is_masked(x)
|
||||
True
|
||||
>>> x = ma.masked_equal([0, 1, 0, 2, 3], 42)
|
||||
>>> x
|
||||
masked_array(data=[0, 1, 0, 2, 3],
|
||||
mask=False,
|
||||
fill_value=42)
|
||||
>>> ma.is_masked(x)
|
||||
False
|
||||
|
||||
Always returns False if `x` isn't a MaskedArray.
|
||||
|
||||
>>> x = [False, True, False]
|
||||
>>> ma.is_masked(x)
|
||||
False
|
||||
>>> x = 'a string'
|
||||
>>> ma.is_masked(x)
|
||||
False
|
||||
|
||||
"""
|
||||
m = getmask(x)
|
||||
if m is nomask:
|
||||
return False
|
||||
elif m.any():
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -1623,4 +1623,24 @@ public abstract class Array {
|
||||
public void resetLocalIterator() {
|
||||
ii = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
Array o = (Array) obj;
|
||||
if (!Arrays.equals(this.getShape(), o.getShape()))
|
||||
return false;
|
||||
|
||||
if (this.getDataType() != o.getDataType())
|
||||
return false;
|
||||
|
||||
IndexIterator iter = this.getIndexIterator();
|
||||
IndexIterator iterO = o.getIndexIterator();
|
||||
while (iter.hasNext()) {
|
||||
if (!iter.getObjectNext().equals(iterO.getObjectNext())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1566,6 +1566,105 @@ public class ArrayUtil {
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeat elements of an array.
|
||||
*
|
||||
* @param a The value
|
||||
* @param repeats The number of repetitions for each element
|
||||
* @return Repeated array
|
||||
*/
|
||||
public static Array repeat(Array a, Array repeats) {
|
||||
repeats = repeats.copyIfView();
|
||||
Array r;
|
||||
IndexIterator iterA = a.getIndexIterator();
|
||||
Object o;
|
||||
if (repeats.getSize() == 1) {
|
||||
int n = repeats.getInt(0);
|
||||
r = Array.factory(a.getDataType(), new int[]{(int) a.getSize() * n});
|
||||
int i = 0;
|
||||
while(iterA.hasNext()) {
|
||||
o = iterA.getObjectNext();
|
||||
for (int j = 0; j < n; j++) {
|
||||
r.setObject(i * n + j, o);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
int n = 0;
|
||||
for (int i = 0; i < repeats.getSize(); i++) {
|
||||
n += repeats.getInt(i);
|
||||
}
|
||||
r = Array.factory(a.getDataType(), new int[]{n});
|
||||
int idx = 0;
|
||||
int i = 0;
|
||||
while(iterA.hasNext()) {
|
||||
o = iterA.getObjectNext();
|
||||
for (int j = 0; j < repeats.getInt(i); j++) {
|
||||
r.setObject(idx, o);
|
||||
idx += 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeat elements of an array.
|
||||
*
|
||||
* @param a The value
|
||||
* @param repeats The number of repetitions for each element
|
||||
* @param axis The axis
|
||||
* @return Repeated array
|
||||
*/
|
||||
public static Array repeat(Array a, Array repeats, int axis) {
|
||||
repeats = repeats.copyIfView();
|
||||
Array r;
|
||||
if (repeats.getSize() == 1) {
|
||||
int n = repeats.getInt(0);
|
||||
int[] shape = a.getShape();
|
||||
shape[axis] = shape[axis] * n;
|
||||
r = Array.factory(a.getDataType(), shape);
|
||||
Index aindex = a.getIndex();
|
||||
Index index = r.getIndex();
|
||||
int[] current;
|
||||
for (int i = 0; i < r.getSize(); i++) {
|
||||
current = index.getCurrentCounter();
|
||||
current[axis] = current[axis] / n;
|
||||
aindex.set(current);
|
||||
r.setObject(index, a.getObject(aindex));
|
||||
index.incr();
|
||||
}
|
||||
} else {
|
||||
int n = 0;
|
||||
int[] rsum = new int[(int) repeats.getSize()];
|
||||
for (int i = 0; i < repeats.getSize(); i++) {
|
||||
rsum[i] = n;
|
||||
n += repeats.getInt(i);
|
||||
}
|
||||
int[] shape = a.getShape();
|
||||
shape[axis] = n;
|
||||
r = Array.factory(a.getDataType(), shape);
|
||||
Index aindex = a.getIndex();
|
||||
Index index = r.getIndex();
|
||||
int[] current;
|
||||
int idx;
|
||||
for (int i = 0; i < a.getSize(); i++) {
|
||||
current = aindex.getCurrentCounter();
|
||||
idx = current[axis];
|
||||
for (int j = 0; j < repeats.getInt(idx); j++) {
|
||||
current[axis] = rsum[idx] + j;
|
||||
index.set(current);
|
||||
r.setObject(index, a.getObject(aindex));
|
||||
}
|
||||
aindex.incr();
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeat a value n times
|
||||
*
|
||||
@ -2580,7 +2679,7 @@ public class ArrayUtil {
|
||||
* @return The sorted unique elements of an array
|
||||
* @throws InvalidRangeException
|
||||
*/
|
||||
public static Array unique(Array a, Integer axis) throws InvalidRangeException {
|
||||
public static Array[] unique(Array a, Integer axis) throws InvalidRangeException {
|
||||
int n = a.getRank();
|
||||
int[] shape = a.getShape();
|
||||
if (axis == null) {
|
||||
@ -2599,7 +2698,7 @@ public class ArrayUtil {
|
||||
r.setObject(i, tList.get(i));
|
||||
}
|
||||
|
||||
return r;
|
||||
return new Array[]{r};
|
||||
} else {
|
||||
if (axis == -1) {
|
||||
axis = n - 1;
|
||||
@ -2628,7 +2727,7 @@ public class ArrayUtil {
|
||||
ranges.set(axis, range);
|
||||
ArrayMath.setSection(r, ranges, tList.get(i));
|
||||
}
|
||||
return r;
|
||||
return new Array[]{r};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2657,82 +2756,165 @@ public class ArrayUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the unique elements and index of an array.
|
||||
* Find the unique elements and index/inverse/counts.
|
||||
*
|
||||
* @param a Array a
|
||||
* @param axis The axis
|
||||
* @return The sorted unique elements and index of an array
|
||||
* @param returnIndex Return index or not
|
||||
* @param returnInverse Return inverse or not
|
||||
* @param returnCounts Return counts or not
|
||||
* @return The sorted unique elements and index/inverse/counts
|
||||
* @throws InvalidRangeException
|
||||
*/
|
||||
public static Array[] uniqueIndex(Array a, Integer axis) throws InvalidRangeException {
|
||||
public static Array[] unique(Array a, Integer axis, boolean returnIndex, boolean returnInverse,
|
||||
boolean returnCounts) throws InvalidRangeException {
|
||||
int n = a.getRank();
|
||||
int[] shape = a.getShape();
|
||||
List<Array> result = new ArrayList<>();
|
||||
if (axis == null) {
|
||||
TreeMap<Object, Integer> map = new TreeMap<>();
|
||||
List tList = new ArrayList<>();
|
||||
List<Integer> idxList = new ArrayList<>();
|
||||
List<Integer> countList = new ArrayList<>();
|
||||
IndexIterator ii = a.getIndexIterator();
|
||||
Object o;
|
||||
int idx = 0;
|
||||
while (ii.hasNext()) {
|
||||
o = ii.getObjectNext();
|
||||
if (!tList.contains(o)) {
|
||||
tList.add(o);
|
||||
map.put(o, idx);
|
||||
if (returnCounts) {
|
||||
while (ii.hasNext()) {
|
||||
o = ii.getObjectNext();
|
||||
int i = tList.indexOf(o);
|
||||
if (i < 0) {
|
||||
tList.add(o);
|
||||
idxList.add(idx);
|
||||
countList.add(1);
|
||||
} else {
|
||||
countList.set(i, countList.get(i) + 1);
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
} else {
|
||||
while (ii.hasNext()) {
|
||||
o = ii.getObjectNext();
|
||||
if (!tList.contains(o)) {
|
||||
tList.add(o);
|
||||
idxList.add(idx);
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
int[] nShape = new int[]{map.size()};
|
||||
Array r = Array.factory(a.getDataType(), nShape);
|
||||
Array index = Array.factory(DataType.INT, nShape);
|
||||
Iterator iter = map.entrySet().iterator();
|
||||
idx = 0;
|
||||
while(iter.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) iter.next();
|
||||
r.setObject(idx, entry.getKey());
|
||||
index.setInt(idx, (Integer) entry.getValue());
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
return new Array[]{r, index};
|
||||
List<Integer> sortIndex = MIMath.sort(tList);
|
||||
int nv = tList.size();
|
||||
int[] nShape = new int[]{nv};
|
||||
Array r = Array.factory(a.getDataType(), nShape);
|
||||
for (int i = 0; i < nv; i++) {
|
||||
r.setObject(i, tList.get(i));
|
||||
}
|
||||
result.add(r);
|
||||
if (returnIndex) {
|
||||
Array index = Array.factory(DataType.INT, nShape);
|
||||
for (int i = 0; i < nv; i++) {
|
||||
index.setInt(i, idxList.get(sortIndex.get(i)));
|
||||
}
|
||||
result.add(index);
|
||||
}
|
||||
if (returnInverse) {
|
||||
int na = (int) a.getSize();
|
||||
Array inverse = Array.factory(DataType.INT, new int[]{na});
|
||||
ii = a.getIndexIterator();
|
||||
IndexIterator iterInverse = inverse.getIndexIterator();
|
||||
while (ii.hasNext()) {
|
||||
o = ii.getObjectNext();
|
||||
iterInverse.setObjectNext(tList.indexOf(o));
|
||||
}
|
||||
result.add(inverse);
|
||||
}
|
||||
if (returnCounts) {
|
||||
Array counts = Array.factory(DataType.INT, nShape);
|
||||
for (int i = 0; i < nv; i++) {
|
||||
counts.setInt(i, countList.get(sortIndex.get(i)));
|
||||
}
|
||||
result.add(counts);
|
||||
}
|
||||
} else {
|
||||
if (axis == -1) {
|
||||
axis = n - 1;
|
||||
}
|
||||
List<Array> tList = new ArrayList();
|
||||
TreeMap<Array, Integer> map = new TreeMap<>();
|
||||
List<Integer> idxList = new ArrayList<>();
|
||||
List<Integer> countList = new ArrayList<>();
|
||||
Array ta;
|
||||
int nn = shape[axis];
|
||||
shape[axis] = 1;
|
||||
int[] origin = new int[shape.length];
|
||||
for (int i = 0; i < nn; i++) {
|
||||
origin[axis] = i;
|
||||
ta = a.section(origin, shape);
|
||||
if (!tList.contains(ta)) {
|
||||
tList.add(ta);
|
||||
map.put(ta, i);
|
||||
if (returnCounts) {
|
||||
for (int i = 0; i < nn; i++) {
|
||||
origin[axis] = i;
|
||||
ta = a.section(origin, shape);
|
||||
int idx = tList.indexOf(ta);
|
||||
if (idx < 0) {
|
||||
tList.add(ta);
|
||||
idxList.add(i);
|
||||
countList.add(1);
|
||||
} else {
|
||||
countList.set(idx, countList.get(idx) + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < nn; i++) {
|
||||
origin[axis] = i;
|
||||
ta = a.section(origin, shape);
|
||||
if (!tList.contains(ta)) {
|
||||
tList.add(ta);
|
||||
idxList.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
shape[axis] = map.size();
|
||||
|
||||
int nv = tList.size();
|
||||
shape[axis] = nv;
|
||||
Array r = Array.factory(a.getDataType(), shape);
|
||||
Array index = Array.factory(DataType.INT, shape);
|
||||
Array index = Array.factory(DataType.INT, new int[]{nv});
|
||||
List<Range> ranges = new ArrayList<>();
|
||||
for (int i = 0; i < shape.length; i++) {
|
||||
ranges.add(new Range(0, shape[i] - 1, 1));
|
||||
}
|
||||
Range range = ranges.get(axis);
|
||||
Iterator iter = map.keySet().iterator();
|
||||
Iterator iter = tList.iterator();
|
||||
int i = 0;
|
||||
while(iter.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) iter.next();
|
||||
Array arr = (Array) iter.next();
|
||||
range = new Range(i, i, 1);
|
||||
ranges.set(axis, range);
|
||||
ArrayMath.setSection(r, ranges, entry.getKey());
|
||||
index.setInt(i, (Integer) entry.getValue());
|
||||
ArrayMath.setSection(r, ranges, arr);
|
||||
index.setInt(i, idxList.get(i));
|
||||
i += 1;
|
||||
}
|
||||
result.add(r);
|
||||
if (returnIndex) {
|
||||
result.add(index);
|
||||
}
|
||||
if (returnInverse) {
|
||||
int na = (int) a.getSize();
|
||||
Array inverse = Array.factory(DataType.INT, new int[]{na});
|
||||
IndexIterator ii = a.getIndexIterator();
|
||||
IndexIterator iterInverse = inverse.getIndexIterator();
|
||||
while (ii.hasNext()) {
|
||||
Object o = ii.getObjectNext();
|
||||
iterInverse.setObjectNext(tList.indexOf(o));
|
||||
}
|
||||
result.add(inverse);
|
||||
}
|
||||
|
||||
return new Array[]{r, index};
|
||||
if (returnCounts) {
|
||||
Array counts = Array.factory(DataType.INT, shape);
|
||||
for (i = 0; i < nv; i++) {
|
||||
counts.setInt(i, countList.get(i));
|
||||
}
|
||||
result.add(counts);
|
||||
}
|
||||
}
|
||||
|
||||
return result.stream().toArray(Array[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user