Source code for pynbody.halo.details.iord_mapping
from __future__ import annotations
import abc
import warnings
import numpy as np
from pynbody.util import binary_search, is_sorted
[docs]
class IordToOffset(abc.ABC):
[docs]
@abc.abstractmethod
def map_ignoring_order(self, i: np.ndarray | int) -> np.ndarray | int:
"""Given an array of iord values, return the corresponding fpos values.
Warning: The returned values are not guaranteed to be in the same order as the input iord array."""
pass
[docs]
class IordToOffsetDense(IordToOffset):
[docs]
def __init__(self, iord_array, max_iord=None):
if max_iord is None:
max_iord = int(iord_array.max())
self._iord_to_offset = np.empty(max_iord + 1, dtype=np.int64)
self._iord_to_offset.fill(-1)
self._iord_to_offset[iord_array] = np.arange(len(iord_array), dtype=np.int64)
[docs]
def map_ignoring_order(self, i):
return self._iord_to_offset[i]
[docs]
class IordToOffsetSparse(IordToOffset):
"""Class for efficiently mapping from iords to offsets in the iord array, even if iord values are large.
WARNING: if a query is made with iords that are not themselves in ascending order, a sort takes place
ahead of the query and therefore the set returned is correct but the ordering is not preserved."""
[docs]
def __init__(self, iord_array):
self._iord = iord_array
self._iord_argsort = np.argsort(iord_array)
[docs]
def map_ignoring_order(self, iord_values: np.ndarray | int) -> np.ndarray | int:
if not hasattr(iord_values, "__len__"):
iord_values = np.array([iord_values])
singleton = True
else:
iord_values = np.asarray(iord_values)
singleton = False
if is_sorted(iord_values) != 1:
iord_values = np.sort(iord_values)
result = binary_search(np.asarray(iord_values), self._iord, self._iord_argsort)
if singleton:
return result[0]
else:
return result
[docs]
class IordOffsetModifier(IordToOffset):
"""A wrapper around an IordToOffset which adds a constant offset to the result of the underlying mapping.
Useful if the iord values e.g. are only available for a single family; then the fpos_offset will correspond
to the first index of that family in the pynbody snapshot.
"""
[docs]
def __init__(self, iord_to_offset: IordToOffset, fpos_offset: int):
self._underlying = iord_to_offset
self._fpos_offset = fpos_offset
[docs]
def map_ignoring_order(self, i: np.ndarray | int) -> np.ndarray | int:
result = self._underlying.map_ignoring_order(i)
result += self._fpos_offset
return result
[docs]
def make_iord_to_offset_mapper(iord: np.ndarray) -> IordToOffset:
"""Given an array of unique integers, iord, make an object which maps from an iord value to offset in the array.
i.e. given an iord array and a subset of values my_iord_values,
make_iord_to_offset_mapper(iord).map_ignoring_order(my_iord_values)
returns the indexes of my_iord_values in the iord array.
"""
min_iord = int(iord.min())
max_iord = int(iord.max())
if (min_iord >= 0) and (max_iord < 2 * len(iord)):
# maximum iord is not very big, just do a direct in-memory mapping for speed
return IordToOffsetDense(iord, max_iord)
else:
# maximum iord is large, so we'll use util.binary_search to save memory at the cost of speed
return IordToOffsetSparse(iord)