Coverage for install/scipp/core/operations.py: 62%
91 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-17 01:51 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-17 01:51 +0000
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3# @author Matthew Andrew
4from __future__ import annotations
6from typing import Any, Literal, TypeVar, overload
8from .._scipp import core as _cpp
9from ..typing import ScippIndex, VariableLikeType
10from ._cpp_wrapper_util import call_func as _call_cpp_func
11from .comparison import identical
12from .cpp_classes import DataArray, Dataset, DatasetError, Variable
13from .data_group import DataGroup
14from .unary import to_unit
16_T = TypeVar('_T', Variable, DataGroup[object])
17_VarDa = TypeVar('_VarDa', Variable, DataArray)
18_DsDg = TypeVar('_DsDg', Dataset, DataGroup[object])
19_E1 = TypeVar('_E1')
20_E2 = TypeVar('_E2')
23def islinspace(x: _T, dim: str | None = None) -> _T:
24 """Check if the values of a variable are evenly spaced.
26 Parameters
27 ----------
28 x:
29 Variable to check.
30 dim:
31 Optional variable for the dim to check from the Variable.
33 Returns
34 -------
35 :
36 Variable of value True if the variable contains regularly
37 spaced values, variable of value False otherwise.
38 """
39 if dim is None:
40 return _call_cpp_func(_cpp.islinspace, x) # type: ignore[return-value]
41 else:
42 return _call_cpp_func(_cpp.islinspace, x, dim) # type: ignore[return-value]
45def issorted(
46 x: _T, dim: str, order: Literal['ascending', 'descending'] = 'ascending'
47) -> _T:
48 """Check if the values of a variable are sorted.
50 - If ``order`` is 'ascending',
51 check if values are non-decreasing along ``dim``.
52 - If ``order`` is 'descending',
53 check if values are non-increasing along ``dim``.
55 Parameters
56 ----------
57 x:
58 Variable to check.
59 dim:
60 Dimension along which order is checked.
61 order:
62 Sorting order.
64 Returns
65 -------
66 :
67 Variable containing one less dim than the original
68 variable with the corresponding boolean value for whether
69 it was sorted along the given dim for the other dimensions.
71 See Also
72 --------
73 scipp.allsorted
74 """
75 return _call_cpp_func(_cpp.issorted, x, dim, order) # type: ignore[return-value]
78@overload
79def allsorted(
80 x: Variable, dim: str, order: Literal['ascending', 'descending'] = 'ascending'
81) -> bool: ...
84@overload
85def allsorted(
86 x: DataGroup[object],
87 dim: str,
88 order: Literal['ascending', 'descending'] = 'ascending',
89) -> DataGroup[object]: ...
92def allsorted(
93 x: Variable | DataGroup[object],
94 dim: str,
95 order: Literal['ascending', 'descending'] = 'ascending',
96) -> bool | DataGroup[object]:
97 """Check if all values of a variable are sorted.
99 - If ``order`` is 'ascending',
100 check if values are non-decreasing along ``dim``.
101 - If ``order`` is 'descending',
102 check if values are non-increasing along ``dim``.
104 Parameters
105 ----------
106 x:
107 Variable to check.
108 dim:
109 Dimension along which order is checked.
110 order:
111 Sorting order.
113 Returns
114 -------
115 :
116 True if the variable values are monotonously ascending or
117 descending (depending on the requested order), False otherwise.
119 See Also
120 --------
121 scipp.issorted
122 """
123 return _call_cpp_func(_cpp.allsorted, x, dim, order)
126def sort(
127 x: VariableLikeType,
128 key: str | Variable,
129 order: Literal['ascending', 'descending'] = 'ascending',
130) -> VariableLikeType:
131 """Sort variable along a dimension by a sort key or dimension label.
133 - If ``order`` is 'ascending',
134 sort such that values are non-decreasing according to ``key``.
135 - If ``order`` is 'descending',
136 sort such that values are non-increasing according to ``key``.
138 Parameters
139 ----------
140 x: scipp.typing.VariableLike
141 Data to be sorted.
142 key:
143 Either a 1D variable sort key or a dimension label.
144 order:
145 Sorting order.
147 Returns
148 -------
149 : scipp.typing.VariableLike
150 The sorted equivalent of the input with the same type.
152 Raises
153 ------
154 scipp.DimensionError
155 If the key is a Variable that does not have exactly 1 dimension.
156 """
157 return _call_cpp_func(_cpp.sort, x, key, order) # type: ignore[return-value]
160def values(x: VariableLikeType) -> VariableLikeType:
161 """Return the input without variances.
163 Parameters
164 ----------
165 x: scipp.typing.VariableLike
166 Input data.
168 Returns
169 -------
170 : scipp.typing.VariableLike
171 The same as the input but without variances.
173 See Also
174 --------
175 scipp.variances, scipp.stddevs
176 """
177 return _call_cpp_func(_cpp.values, x) # type: ignore[return-value]
180def variances(x: VariableLikeType) -> VariableLikeType:
181 """Return the input's variances as values.
183 Parameters
184 ----------
185 x: scipp.typing.VariableLike
186 Input data with variances.
188 Returns
189 -------
190 : scipp.typing.VariableLike
191 The same as the input but with values set to the input's variances
192 and without variances itself.
194 See Also
195 --------
196 scipp.values, scipp.stddevs
197 """
198 return _call_cpp_func(_cpp.variances, x) # type: ignore[return-value]
201def stddevs(x: VariableLikeType) -> VariableLikeType:
202 """Return the input's standard deviations as values.
204 Parameters
205 ----------
206 x: scipp.typing.VariableLike
207 Input data with variances.
209 Returns
210 -------
211 : scipp.typing.VariableLike
212 The same as the input but with values set to standard deviations computed
213 from the input's variances and without variances itself.
215 See Also
216 --------
217 scipp.values, scipp.variances
218 """
219 return _call_cpp_func(_cpp.stddevs, x) # type: ignore[return-value]
222def where(condition: Variable, x: Variable, y: Variable) -> Variable:
223 """Return elements chosen from x or y depending on condition.
225 Parameters
226 ----------
227 condition:
228 Variable with dtype=bool.
229 x:
230 Variable with values from which to choose.
231 y:
232 Variable with values from which to choose.
234 Returns
235 -------
236 :
237 Variable with elements from x where condition is True
238 and elements from y elsewhere.
239 """
240 return _call_cpp_func(_cpp.where, condition, x, y) # type: ignore[return-value]
243def to(
244 var: Variable,
245 *,
246 unit: _cpp.Unit | str | None = None,
247 dtype: Any | None = None,
248 copy: bool = True,
249) -> Variable:
250 """Converts a Variable or DataArray to a different dtype and/or a different unit.
252 If the dtype and unit are both unchanged and ``copy`` is `False`,
253 the object is returned without making a deep copy.
255 This method will choose whether to do the dtype or units translation first, by
256 using the following rules in order:
258 - If either the input or output dtype is float64, the unit translation will be done
259 on the float64 type
260 - If either the input or output dtype is float32, the unit translation will be done
261 on the float32 type
262 - If both the input and output dtypes are integer types, the unit translation will
263 be done on the larger type
264 - In other cases, the dtype is converted first and then the unit translation is done
266 Parameters
267 ----------
268 unit:
269 Target unit. If ``None``, the unit is unchanged.
270 dtype:
271 Target dtype. If ``None``, the dtype is unchanged.
272 copy:
273 If ``False``, return the input object if possible.
274 If ``True``, the function always returns a new object.
276 Returns
277 -------
278 : Same as input
279 New object with specified dtype and unit.
281 Raises
282 ------
283 scipp.DTypeError
284 If the input cannot be converted to the given dtype.
285 scipp.UnitError
286 If the input cannot be converted to the given unit.
288 See Also
289 --------
290 scipp.to_unit, scipp.DataArray.astype, scipp.Variable.astype
291 """
292 if unit is None and dtype is None:
293 raise ValueError("Must provide dtype or unit or both")
295 if dtype is None:
296 return to_unit(var, unit, copy=copy)
298 if unit is None:
299 return var.astype(dtype, copy=copy)
301 if dtype == _cpp.DType.float64:
302 convert_dtype_first = True
303 elif var.dtype == _cpp.DType.float64:
304 convert_dtype_first = False
305 elif dtype == _cpp.DType.float32:
306 convert_dtype_first = True
307 elif var.dtype == _cpp.DType.float32:
308 convert_dtype_first = False
309 elif var.dtype == _cpp.DType.int64 and dtype == _cpp.DType.int32:
310 convert_dtype_first = False
311 elif var.dtype == _cpp.DType.int32 and dtype == _cpp.DType.int64:
312 convert_dtype_first = True
313 else:
314 convert_dtype_first = True
316 if convert_dtype_first:
317 return to_unit(var.astype(dtype, copy=copy), unit=unit, copy=False)
318 else:
319 return to_unit(var, unit=unit, copy=copy).astype(dtype, copy=False)
322def merge(lhs: _DsDg, rhs: _DsDg) -> _DsDg:
323 """Merge two datasets or data groups into one.
325 If an item appears in both inputs, it must have an identical value in both.
327 Parameters
328 ----------
329 lhs: Dataset | DataGroup
330 First dataset or data group.
331 rhs: Dataset | DataGroup
332 Second dataset or data group.
334 Returns
335 -------
336 : Dataset | DataGroup
337 A new object that contains the union of all data items,
338 coords, masks and attributes.
340 Raises
341 ------
342 scipp.DatasetError
343 If there are conflicting items with different content.
344 """
345 # Check both arguments to make `_cpp.merge` raise TypeError on mismatch.
346 if isinstance(lhs, Dataset) or isinstance(rhs, Dataset): # type: ignore[redundant-expr]
347 return _call_cpp_func(_cpp.merge, lhs, rhs) # type: ignore[return-value]
348 return _merge_data_group(lhs, rhs)
351def _generic_identical(a: Any, b: Any) -> bool:
352 try:
353 return identical(a, b)
354 except TypeError:
355 from numpy import array_equal
357 try:
358 return array_equal(a, b)
359 except TypeError:
360 return a == b # type: ignore[no-any-return]
363def _merge_data_group(lhs: DataGroup[_E1], rhs: DataGroup[_E2]) -> DataGroup[_E1 | _E2]:
364 res: DataGroup[_E1 | _E2] = DataGroup(dict(lhs))
365 for k, v in rhs.items():
366 if k in res and not _generic_identical(res[k], v):
367 raise DatasetError(f"Cannot merge data groups. Mismatch in item {k}")
368 res[k] = v
369 return res
372def _raw_positional_index(
373 sizes: dict[str, int], coord: Variable, index: slice | Variable
374) -> tuple[str, list[int | Variable]]:
375 dim, *inds = _call_cpp_func(
376 _cpp.label_based_index_to_positional_index,
377 list(sizes.keys()),
378 list(sizes.values()),
379 coord,
380 index,
381 )
382 return dim, inds # type: ignore[return-value]
385def label_based_index_to_positional_index(
386 sizes: dict[str, int],
387 coord: Variable,
388 index: slice | Variable,
389) -> ScippIndex:
390 """Returns the positional index equivalent to label based indexing
391 the coord with values."""
392 dim, inds = _raw_positional_index(sizes, coord, index)
393 # Length of inds is 1 if index was a variable
394 if len(inds) == 1:
395 if inds[0] < 0 or sizes[dim] <= inds[0]:
396 # If index is a variable and the coord is a bin-edge coord
397 # - if the index is less than any edge then inds[0] is -1
398 # - if the index is greater than any edge then inds[0] is sizes[dim]
399 raise IndexError(
400 f"Value {index} is not contained in the bin-edge coord {coord}"
401 )
402 return (dim, inds[0])
403 return (dim, slice(*inds))
406def as_const(x: _VarDa) -> _VarDa:
407 """Return a copy with the readonly flag set."""
408 return _call_cpp_func(_cpp.as_const, x) # type: ignore[return-value]