Skip to content

fix(rust/sedona-raster-functions): RasterExecutor always call the closure only once for scalar raster input#559

Merged
Kontinuation merged 3 commits intoapache:mainfrom
Kontinuation:kontinuation/fix-scalar-raster-broadcast
Feb 1, 2026
Merged

fix(rust/sedona-raster-functions): RasterExecutor always call the closure only once for scalar raster input#559
Kontinuation merged 3 commits intoapache:mainfrom
Kontinuation:kontinuation/fix-scalar-raster-broadcast

Conversation

@Kontinuation
Copy link
Member

Summary

  • Fix RasterExecutor::execute_raster_void() to broadcast scalar rasters across num_iterations() when other args are arrays.
  • Add regression tests for scalar raster + array coordinate inputs in rs_worldcoordinate and rs_rastercoordinate.

Why

DataFusion can invoke these scalar UDFs with a scalar raster and array coordinate arguments. Previously the executor only invoked the per-row closure once for scalar rasters, producing output arrays of length 1 instead of the expected batch length.

Testing

  • cargo test -p sedona-raster-functions

@Kontinuation Kontinuation changed the title Fix RasterExecutor scalar raster broadcasting fix(rust/sedona-raster-functions): Fix RasterExecutor scalar raster broadcasting Jan 29, 2026
@Kontinuation Kontinuation changed the title fix(rust/sedona-raster-functions): Fix RasterExecutor scalar raster broadcasting fix(rust/sedona-raster-functions): RasterExecutor always call the closure only once for scalar raster input Jan 29, 2026
@Kontinuation Kontinuation marked this pull request as draft January 29, 2026 05:49
@Kontinuation Kontinuation requested a review from Copilot January 29, 2026 05:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a bug in the RasterExecutor where scalar raster inputs were only processed once instead of being broadcast across all iterations when combined with array arguments, causing output arrays to have incorrect lengths.

Changes:

  • Modified execute_raster_void() to iterate num_iterations times for scalar rasters, broadcasting the scalar value
  • Added regression tests for scalar raster with array coordinate inputs in both world-to-raster and raster-to-world coordinate functions

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
rust/sedona-raster-functions/src/executor.rs Fixed scalar raster broadcasting logic to iterate across all batch rows
rust/sedona-raster-functions/src/rs_worldcoordinate.rs Added tests for scalar raster + array coordinates in raster-to-world conversion
rust/sedona-raster-functions/src/rs_rastercoordinate.rs Added tests for scalar raster + array coordinates in world-to-raster conversion

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Kontinuation Kontinuation force-pushed the kontinuation/fix-scalar-raster-broadcast branch from 9b05141 to efb42d9 Compare January 29, 2026 06:33
@Kontinuation Kontinuation marked this pull request as ready for review January 29, 2026 08:39
pub fn execute_raster_void<F>(&self, mut func: F) -> Result<()>
where
F: FnMut(usize, Option<RasterRefImpl<'_>>) -> Result<()>,
F: FnMut(usize, Option<&RasterRefImpl<'_>>) -> Result<()>,
Copy link
Member Author

Choose a reason for hiding this comment

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

Passing reference instead of moving value into the closure to avoid repeatedly constructing RasterRefImpl from raster array or cloning it. We will not want to persist the RasterRefImpl value passed in for most of the time.

Copy link
Member

@paleolimbot paleolimbot left a comment

Choose a reason for hiding this comment

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

Just a few comments on how to make the tests less verbose for the future LLMs/people implementing new functions that will probably copy them. Thank you!

Comment on lines 401 to 402
let world_x = Arc::new(arrow_array::Float64Array::from(vec![2.0, 2.0, 2.0]));
let world_y = Arc::new(arrow_array::Float64Array::from(vec![3.0, 3.0, 3.0]));
Copy link
Member

Choose a reason for hiding this comment

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

This particular test seems like it might benefit from having >1 unique value in the input.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed the array argument to have non-unique values.

Comment on lines 443 to 453
let raster_struct = rasters.as_any().downcast_ref::<StructArray>().unwrap();
let raster_struct_one = raster_struct
.slice(1, 1)
.as_any()
.downcast_ref::<StructArray>()
.unwrap()
.clone();
let scalar_raster = ScalarValue::Struct(Arc::new(raster_struct_one));

let world_x = Arc::new(arrow_array::Float64Array::from(vec![2.0, 2.0, 2.0]));
let world_y = Arc::new(arrow_array::Float64Array::from(vec![3.0, 3.0, 3.0]));
Copy link
Member

Choose a reason for hiding this comment

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

Same comments as above (I think the scalar raster can be generated more compactly and probably more than one unique value in the output would be a better test).

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed the array argument to have non-unique values.

Fixes RasterExecutor::execute_raster_void() to broadcast scalar raster inputs across num_iterations when other arguments are arrays, and tightens coordinate UDF tests to cover scalar raster + array coordinates.
@Kontinuation Kontinuation force-pushed the kontinuation/fix-scalar-raster-broadcast branch from 1fbc755 to a5ab298 Compare January 30, 2026 08:32
Copy link
Member

@paleolimbot paleolimbot left a comment

Choose a reason for hiding this comment

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

Thanks!

I think it is worth one more round of making these compact. There are a lot of raster tests in our future!

Comment on lines 401 to 414
let expected_coords: Vec<Option<i64>> = world_x
.iter()
.zip(world_y.iter())
.map(|(x, y)| match (x, y) {
(Some(x), Some(y)) => {
let (rx, ry) = to_raster_coordinate(&raster_ref, x, y).unwrap();
Some(match coord {
Coord::X => rx,
Coord::Y => ry,
})
}
_ => None,
})
.collect();
Copy link
Member

Choose a reason for hiding this comment

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

It would probably be better (and more compact) to hard-code these values. This is a bit circular since it's essentially the same as the UDF implementation.

(Also for other tests)

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense. My initial attempt is to test the RasterExecutor but not the RS function itself. Hardcoding expected values is the correct approach since this test lives in rs_rastercoordinate.rs so it should test the correctness of the RS function as its primary goal.

// Use raster 1 (invertible) as scalar.
let rasters = generate_test_rasters(2, Some(0)).unwrap();
let raster_struct = rasters.as_any().downcast_ref::<StructArray>().unwrap();
let scalar_raster = ScalarValue::try_from_array(raster_struct, 1).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let scalar_raster = ScalarValue::try_from_array(raster_struct, 1).unwrap();
let scalar_raster = ScalarValue::try_from_array(&rasters, 1).unwrap();

I know this was my suggestion but I think it can be even more compact 😬

(Also for the other tests)

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed redundant code generated by LLM

Copy link
Member

@paleolimbot paleolimbot left a comment

Choose a reason for hiding this comment

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

Thank you!

@Kontinuation Kontinuation merged commit 402dcb3 into apache:main Feb 1, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants