Skip to content

Fix ChunkCachedArray indexer support - #2921

Open
VeckoTheGecko wants to merge 4 commits into
Parcels-code:mainfrom
VeckoTheGecko:push-ozvvtmlnvkor
Open

VeckoTheGecko wants to merge 4 commits into
Parcels-code:mainfrom
VeckoTheGecko:push-ozvvtmlnvkor

Conversation

@VeckoTheGecko

@VeckoTheGecko VeckoTheGecko commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Description

An LLM generated summary of the changes:

src/parcels/_chunk_cached_array/core.py (changes to _raw_vindex and _vindex_get):

  • Broadcasting and multi-dimensional index arrays: the index arrays are now broadcast together and flattened before the existing chunk lookup, and the output is reshaped to the broadcast shape. Before, it assumed equal-length 1D arrays. Arrays with more than one dimension came back with the wrong shape and no error.
  • Empty index arrays: these now return an empty array of the right shape. Before, they raised IndexError.
  • Bounds checking: an index >= size or < -size now raises an IndexError in numpy's style, e.g. index 6 is out of bounds for axis 0 with size 6. Before, it raised an unclear ValueError from np.ravel_multi_index.
  • Slices: an indexer containing a slice now raises NotImplementedError with a message saying to use integer arrays instead. Before, it failed with a confusing TypeError.
  • The path Parcels uses (equal-length 1D arrays) works as before. Each call now also does a broadcast and a bounds check

Note here that slice support isn't offered. I'm not sure what the usecase for this even was (cc @erikvansebille ?). Either way - now it clearly errors out so that authors of interpolators have a better idea of what's happening

I'm not sure the performance cost of the bounds checks above are small

Overhead is small. I timed a synthetic 4D array with random indices and a warm cache:

Particles vindex call Bounds check Share
1,000 2.6 ms 0.01 ms 0.2%
100,000 13.5 ms 0.08 ms 0.6%
1,000,000 117 ms 0.85 ms 0.7%

Checklist

AI Disclosure

  • This PR contains AI-generated content.
    • I have tested any AI-generated content in my PR.
    • I take responsibility for any AI-generated content in my PR.
    • Describe how you used it (e.g., by pasting your prompt): asked the LLM to implement changes to resolve the failing test cases. Also used it to determine the overhead of the bounds checking

Also bounds-check indices (raising IndexError, matching numpy) and raise
NotImplementedError for slices in vectorized indexers.
Comment on lines +99 to +106
# Step 0: Broadcast the index arrays and flatten them into a list of points,
# restoring the broadcast shape at the end.
broadcast = np.broadcast_arrays(*indices)
out_shape = broadcast[0].shape
indices = tuple(idx.ravel() for idx in broadcast)
n_points = int(np.prod(out_shape))
if n_points == 0:
return np.empty(out_shape, dtype=self.array.dtype)

@VeckoTheGecko VeckoTheGecko Sep 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we were assuming for vectorized indexing only 1D arrays for vectorized indexing, which doesn't match 1-to-1 with numpy

Numpy allows vectorized indexing as long as the shapes broadcast against each other

>>> import numpy as np
>>> np.array([[1,2,3,4], [7,8,9,10]])[[0,0],[1,1]]
array([2, 2])
>>> a = np.array([0,0])
>>> b = np.array([[1,1]])
>>> np.array([[1,2,3,4], [7,8,9,10]])[a, b]
array([[2, 2]])

>>> np.broadcast([1,2,3],[[1,2,3]])
<numpy.broadcast object at 0x1066040c0>
>>> np.broadcast([1,2,3],[[1,2,3,4]])
Traceback (most recent call last):
  File "<python-input-13>", line 1, in <module>
    np.broadcast([1,2,3],[[1,2,3,4]])
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
ValueError: shape mismatch: objects cannot be broadcast to a single shape.  Mismatch is between arg 0 with shape (3,) and arg 1 with shape (1, 4).
>>> 

Us supporting this helps with the reliability of our implementation, and (AFAICT) is at little to no cost

@VeckoTheGecko VeckoTheGecko changed the title Add hypothesis strategy for out-of-bounds vectorized indexers Fix ChunkCachedArray indexer support Sep 30, 2026

@erikvansebille erikvansebille left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, two comments below

out_of_bounds = (idx < -size) | (idx >= size)
if out_of_bounds.any():
raise IndexError(f"index {idx[out_of_bounds][0]} is out of bounds for axis {d} with size {size}")
normalized.append(np.where(idx < 0, idx + size, idx))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not pre-allocate the normalised list? We do know how long it will be, don't we?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this will have much of a measurable benefit. len(indices) is only 3 or 4

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is len(indices) not the number of particles, which can be order millions? What is indices then?

Comment thread src/parcels/_chunk_cached_array/core.py
@VeckoTheGecko

Copy link
Copy Markdown
Contributor Author

you're able to quickly check if this works for you by setting

[dependencies]
parcels = {git = "https://github.com/VeckoTheGecko/parcels", rev="push-ozvvtmlnvkor"}

in your pixi.toml

cc @j-atkins @erikvansebille

@erikvansebille

Copy link
Copy Markdown
Member

you're able to quickly check if this works for you by setting

[dependencies]
parcels = {git = "https://github.com/VeckoTheGecko/parcels", rev="push-ozvvtmlnvkor"}

in your pixi.toml

cc @j-atkins @erikvansebille

I can't test this now because my laptop died. Will have to wait until at least next week

@j-atkins

j-atkins commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

you're able to quickly check if this works for you by setting

[dependencies]
parcels = {git = "https://github.com/VeckoTheGecko/parcels", rev="push-ozvvtmlnvkor"}

in your pixi.toml

cc @j-atkins @erikvansebille

Yes, this seems to be working now!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

3 participants