Skip to content

mortie.morton_index

The numpy-only surface: the packed-word scalar and the decimal parse functions. Nothing here imports pandas.

The pandas MortonIndexDtype / MortonIndexArray pair is re-exported from this module (mortie.morton_index.MortonIndexArray resolves), but it is defined in mortie.pandas and documented there.

The morton_index datatype: the numpy-only surface over packed words.

The scalar and decimal parse surface of the packed 64-bit decimal-Morton MOC kernel (issue #35, phase 5). The pandas ExtensionArray skin over the same words lives in :mod:mortie.pandas (issue #135) and is re-exported from here.

The kernel lives in Rust (src_rust/src/decimal_morton.rs); this module is the user-facing surface. Storage is raw uint64 packed words (issue #58; zero-copy over the kernel's bit layout [4-bit prefix | 54-bit body | 6-bit suffix]). The word is unsigned, so the Z-order is simply the raw word order -- base cells 7..=11 (prefix 8..=12) set bit 63 and sort after the northern cells with no special casing, and comparisons/sort operate on the words directly. Domain operations (coarsen/order/base_cell) and the (nested, depth) <-> word bridge delegate to the vectorized Rust bindings; no arithmetic operators are defined (raw arithmetic on packed words is meaningless).

pandas is an optional dependency: importing mortie succeeds with only numpy installed. Nothing in this module touches pandas; the ExtensionArray names resolve by importing :mod:mortie.pandas on demand, and a clear ImportError is raised if they are touched without pandas installed.

MortonIndexScalar

Bases: uint64

A packed morton_index word that displays as its decimal string.

Element access and iteration on a MortonIndexArray yield this type (issue #104), so a downstream f"{shard_key}" prints the decimal Morton id (-31123 style) rather than the raw packed word. It subclasses numpy.uint64: comparisons, hashing, and int() (the packed word) behave exactly like the word itself; only str/repr differ. The empty sentinel renders "<NA>"; a word with an invalid prefix renders "<invalid 0x...>" rather than raising (a repr must never raise).

Construct it from a packed word (an int or numpy.uint64) exactly as you would a numpy.uint64; the constructor is inherited unchanged.

Source code in mortie/morton_index.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
class MortonIndexScalar(np.uint64):
    """A packed ``morton_index`` word that displays as its decimal string.

    Element access and iteration on a ``MortonIndexArray`` yield this type
    (issue #104), so a downstream ``f"{shard_key}"`` prints the decimal Morton
    id (``-31123`` style) rather than the raw packed word. It subclasses
    ``numpy.uint64``: comparisons, hashing, and ``int()`` (the packed word)
    behave exactly like the word itself; only ``str``/``repr`` differ. The
    empty sentinel renders ``"<NA>"``; a word with an invalid prefix renders
    ``"<invalid 0x...>"`` rather than raising (a repr must never raise).

    Construct it from a packed word (an ``int`` or ``numpy.uint64``) exactly as
    you would a ``numpy.uint64``; the constructor is inherited unchanged.
    """

    def __str__(self):
        """Render the word as its decimal Morton string.

        Returns
        -------
        str
            The decimal Morton id, ``"<NA>"`` for the empty sentinel, or
            ``"<invalid 0x...>"`` for a word with an invalid prefix.
        """
        word = int(self)
        if word == 0:
            return "<NA>"
        try:
            return _rustie.rust_mi_decimal_repr(
                np.asarray([word], dtype=np.uint64)
            )[0]
        except ValueError:
            return f"<invalid {word:#018x}>"

    __repr__ = __str__

    def __format__(self, spec):
        """Format the decimal Morton string, not the packed word.

        numpy's numeric ``__format__`` would print the packed word; the display
        form of a morton_index is its decimal string, so ``f"{shard_key}"`` (and
        any string spec, e.g. ``">10"``) formats that instead. ``int(self)``
        remains the escape hatch to format the raw word numerically. Old-style
        ``"%d" % key`` bypasses ``__format__`` entirely and emits the raw word.

        Parameters
        ----------
        spec : str
            A standard format spec, applied to the decimal string.

        Returns
        -------
        str
            The formatted decimal Morton string.
        """
        return format(str(self), spec)

    def __reduce__(self):
        """Pickle as a ``MortonIndexScalar`` rather than a bare ``uint64``.

        numpy scalars pickle through ``multiarray.scalar``, which rebuilds the
        bare ``np.uint64`` and would silently drop the decimal display on any
        process boundary (multiprocessing/dask); rebuild the wrapper instead.

        Returns
        -------
        tuple
            The ``(callable, args)`` pair pickle uses to rebuild the wrapper.
        """
        return (type(self), (int(self),))

__format__(spec)

Format the decimal Morton string, not the packed word.

numpy's numeric __format__ would print the packed word; the display form of a morton_index is its decimal string, so f"{shard_key}" (and any string spec, e.g. ">10") formats that instead. int(self) remains the escape hatch to format the raw word numerically. Old-style "%d" % key bypasses __format__ entirely and emits the raw word.

Parameters:

Name Type Description Default
spec str

A standard format spec, applied to the decimal string.

required

Returns:

Type Description
str

The formatted decimal Morton string.

Source code in mortie/morton_index.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def __format__(self, spec):
    """Format the decimal Morton string, not the packed word.

    numpy's numeric ``__format__`` would print the packed word; the display
    form of a morton_index is its decimal string, so ``f"{shard_key}"`` (and
    any string spec, e.g. ``">10"``) formats that instead. ``int(self)``
    remains the escape hatch to format the raw word numerically. Old-style
    ``"%d" % key`` bypasses ``__format__`` entirely and emits the raw word.

    Parameters
    ----------
    spec : str
        A standard format spec, applied to the decimal string.

    Returns
    -------
    str
        The formatted decimal Morton string.
    """
    return format(str(self), spec)

__reduce__()

Pickle as a MortonIndexScalar rather than a bare uint64.

numpy scalars pickle through multiarray.scalar, which rebuilds the bare np.uint64 and would silently drop the decimal display on any process boundary (multiprocessing/dask); rebuild the wrapper instead.

Returns:

Type Description
tuple

The (callable, args) pair pickle uses to rebuild the wrapper.

Source code in mortie/morton_index.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def __reduce__(self):
    """Pickle as a ``MortonIndexScalar`` rather than a bare ``uint64``.

    numpy scalars pickle through ``multiarray.scalar``, which rebuilds the
    bare ``np.uint64`` and would silently drop the decimal display on any
    process boundary (multiprocessing/dask); rebuild the wrapper instead.

    Returns
    -------
    tuple
        The ``(callable, args)`` pair pickle uses to rebuild the wrapper.
    """
    return (type(self), (int(self),))

__str__()

Render the word as its decimal Morton string.

Returns:

Type Description
str

The decimal Morton id, "<NA>" for the empty sentinel, or "<invalid 0x...>" for a word with an invalid prefix.

Source code in mortie/morton_index.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def __str__(self):
    """Render the word as its decimal Morton string.

    Returns
    -------
    str
        The decimal Morton id, ``"<NA>"`` for the empty sentinel, or
        ``"<invalid 0x...>"`` for a word with an invalid prefix.
    """
    word = int(self)
    if word == 0:
        return "<NA>"
    try:
        return _rustie.rust_mi_decimal_repr(
            np.asarray([word], dtype=np.uint64)
        )[0]
    except ValueError:
        return f"<invalid {word:#018x}>"

decimal_to_word(s, dtype=np.uint64)

Parse one decimal Morton string into its packed word (issue #114).

The scalar inverse of the decode-through-kernel repr, and the public counterpart to :meth:MortonIndexArray.decimal_repr: sign column + leading base digit (1..6), one 1..4 digit per order, and an optional terminal p kind suffix (spec section 4, issue #120). A p-marked string (legal only at order 29) yields the POINT word; an unmarked string always yields the AREA word -- the tie-break for the one ambiguous form, and fully backward compatible (every pre-suffix string is unmarked).

numpy-only: calling this imports no pandas, so it is usable from hot per-key parse paths. Use :func:decimals_to_words for arrays.

Parameters:

Name Type Description Default
s str

The decimal Morton id, e.g. "-31123".

required
dtype type

The return shape. np.uint64 (default) returns the bare packed word, staying numpy-native for hot loops; int returns a Python int; :class:MortonIndexScalar returns a word that displays back as its decimal string. "uint64" / np.dtype("uint64") are accepted spellings of the default.

uint64

Returns:

Type Description
uint64 or int or MortonIndexScalar

The packed word, in the shape requested by dtype.

Raises:

Type Description
ValueError

If s is a malformed decimal Morton id.

TypeError

If dtype is not np.uint64 (or a spelling of it), int, or :class:MortonIndexScalar.

See Also

decimals_to_words : The vectorized array counterpart.

Source code in mortie/morton_index.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def decimal_to_word(s, dtype=np.uint64):
    """Parse one decimal Morton string into its packed word (issue #114).

    The scalar inverse of the decode-through-kernel repr, and the public
    counterpart to :meth:`MortonIndexArray.decimal_repr`: sign column +
    leading base digit (``1..6``), one ``1..4`` digit per order, and an
    optional terminal ``p`` kind suffix (spec section 4, issue #120). A
    ``p``-marked string (legal only at order 29) yields the POINT word; an
    unmarked string always yields the AREA word -- the tie-break for the one
    ambiguous form, and fully backward compatible (every pre-suffix string
    is unmarked).

    numpy-only: calling this imports no pandas, so it is usable from hot
    per-key parse paths. Use :func:`decimals_to_words` for arrays.

    Parameters
    ----------
    s : str
        The decimal Morton id, e.g. ``"-31123"``.
    dtype : type, optional
        The return shape. ``np.uint64`` (default) returns the bare packed
        word, staying numpy-native for hot loops; ``int`` returns a Python
        int; :class:`MortonIndexScalar` returns a word that displays back as
        its decimal string. ``"uint64"`` / ``np.dtype("uint64")`` are
        accepted spellings of the default.

    Returns
    -------
    numpy.uint64 or int or MortonIndexScalar
        The packed word, in the shape requested by ``dtype``.

    Raises
    ------
    ValueError
        If ``s`` is a malformed decimal Morton id.
    TypeError
        If ``dtype`` is not ``np.uint64`` (or a spelling of it), ``int``, or
        :class:`MortonIndexScalar`.

    See Also
    --------
    decimals_to_words : The vectorized array counterpart.
    """
    word = int(_rustie.rust_mi_from_decimal([s])[0])
    if dtype is int:
        return word
    # `issubclass`, not `is`: a MortonIndexScalar subclass must round-trip as
    # itself rather than silently downgrading to a bare uint64.
    if isinstance(dtype, type) and issubclass(dtype, MortonIndexScalar):
        return dtype(word)
    # Only a dtype *spelling* is accepted here -- `np.dtype(np.uint64(0))`
    # happens to succeed on an instance, which would let a stray value through.
    if isinstance(dtype, (type, str, np.dtype)):
        try:
            requested = np.dtype(dtype)
        except TypeError:
            requested = None
        if requested == np.uint64:
            return np.uint64(word)
    raise TypeError(
        f"decimal_to_word dtype must be np.uint64 (the default), int, or "
        f"MortonIndexScalar; got {dtype!r}"
    )

decimals_to_words(decimals)

Parse an array of decimal Morton strings into packed words (issue #114).

The vectorized inverse of :meth:MortonIndexArray.to_decimal, parsed in Rust in one pass. Shape is preserved; the result is always uint64. numpy-only, like :func:decimal_to_word.

Parameters:

Name Type Description Default
decimals array_like of str

Decimal Morton ids, of any shape. A bare str is rejected -- see Raises.

required

Returns:

Type Description
ndarray

uint64 packed words, in the shape of decimals.

Raises:

Type Description
ValueError

Naming the first malformed id, in input order.

TypeError

For non-string input -- a scalar string included, since np.asarray would make it a 0-d array; use :func:decimal_to_word.

Source code in mortie/morton_index.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
def decimals_to_words(decimals):
    """Parse an array of decimal Morton strings into packed words (issue #114).

    The vectorized inverse of :meth:`MortonIndexArray.to_decimal`, parsed in
    Rust in one pass. Shape is preserved; the result is always ``uint64``.
    numpy-only, like :func:`decimal_to_word`.

    Parameters
    ----------
    decimals : array_like of str
        Decimal Morton ids, of any shape. A bare ``str`` is rejected -- see
        ``Raises``.

    Returns
    -------
    numpy.ndarray
        ``uint64`` packed words, in the shape of ``decimals``.

    Raises
    ------
    ValueError
        Naming the first malformed id, in input order.
    TypeError
        For non-string input -- a scalar string included, since
        ``np.asarray`` would make it a 0-d array; use
        :func:`decimal_to_word`.
    """
    if isinstance(decimals, str):
        raise TypeError(
            "decimals_to_words expects a sequence of decimal Morton strings; "
            "for a single id use decimal_to_word"
        )
    if isinstance(decimals, (list, tuple)) and len(decimals) == 0:
        # numpy types an empty list as float64, which the dtype guard below
        # would reject. Handled here, ahead of the guard, so that the guard
        # stays purely dtype-driven -- an empty *array* of the wrong dtype is
        # still a wrong dtype, and must not pass just because it has no data.
        return np.empty(0, dtype=np.uint64)
    arr = np.asarray(decimals)
    if arr.dtype.kind not in ("U", "O"):
        # Do not let numpy's str-coercion silently turn e.g. the integer 1
        # into the order-0 id "1"; a parse surface takes strings only.
        raise TypeError(
            f"decimals_to_words expects decimal Morton strings, got an array "
            f"of dtype {arr.dtype!r}"
        )
    flat = arr.ravel().tolist()
    if arr.dtype.kind == "O" and not all(isinstance(s, str) for s in flat):
        # Object arrays can hold anything; name the surface and the offender
        # rather than leaking a bare PyO3 extraction message.
        bad = next(s for s in flat if not isinstance(s, str))
        raise TypeError(
            f"decimals_to_words expects decimal Morton strings, got "
            f"{type(bad).__name__} ({bad!r})"
        )
    return _rustie.rust_mi_from_decimal(flat).reshape(arr.shape)