99
1010use prost:: Message ;
1111use vortex_array:: ArrayRef ;
12- use vortex_array:: ExecutionCtx ;
13- use vortex_array:: IntoArray ;
14- use vortex_array:: arrays:: Constant ;
15- use vortex_array:: arrays:: ConstantArray ;
16- use vortex_array:: arrays:: ExtensionArray ;
17- use vortex_array:: arrays:: PrimitiveArray ;
1812use vortex_array:: arrays:: ScalarFn as ScalarFnArrayEncoding ;
1913use vortex_array:: arrays:: ScalarFnArray ;
20- use vortex_array:: arrays:: extension:: ExtensionArrayExt ;
2114use vortex_array:: arrays:: scalar_fn:: ScalarFnArrayExt ;
2215use vortex_array:: arrays:: scalar_fn:: ScalarFnArrayView ;
2316use vortex_array:: arrays:: scalar_fn:: plugin:: ScalarFnArrayParts ;
2417use vortex_array:: arrays:: scalar_fn:: plugin:: ScalarFnArrayVTable ;
2518use vortex_array:: dtype:: DType ;
26- use vortex_array:: dtype:: Nullability ;
2719use vortex_array:: dtype:: proto:: dtype as pb;
28- use vortex_array:: expr:: Expression ;
29- use vortex_array:: expr:: union_child_validities;
3020use vortex_array:: match_each_float_ptype;
31- use vortex_array:: scalar:: Scalar ;
32- use vortex_array:: scalar_fn:: Arity ;
33- use vortex_array:: scalar_fn:: ChildName ;
3421use vortex_array:: scalar_fn:: EmptyOptions ;
35- use vortex_array:: scalar_fn:: ExecutionArgs ;
3622use vortex_array:: scalar_fn:: ScalarFnId ;
37- use vortex_array:: scalar_fn:: ScalarFnVTable ;
3823use vortex_array:: scalar_fn:: ScalarFnVTableExt ;
24+ use vortex_array:: scalar_fn:: unstable:: row:: InitializedElement ;
25+ use vortex_array:: scalar_fn:: unstable:: row:: RowFn ;
26+ use vortex_array:: scalar_fn:: unstable:: row:: RowVisitor ;
27+ use vortex_array:: scalar_fn:: unstable:: row:: UninitElementSink ;
3928use vortex_array:: serde:: ArrayChildren ;
40- use vortex_buffer:: Buffer ;
41- use vortex_error:: VortexExpect ;
4229use vortex_error:: VortexResult ;
4330use vortex_error:: vortex_err;
4431use vortex_session:: VortexSession ;
4532use vortex_session:: registry:: CachedId ;
4633
47- use crate :: matcher:: AnyTensor ;
4834use crate :: scalar_fns:: arithmetic:: l2_norm_row;
49- use crate :: utils :: extract_flat_elements ;
50- use crate :: utils :: validate_tensor_float_input ;
35+ use crate :: scalar_fns :: row :: TensorRow ;
36+ use crate :: scalar_fns :: row :: tensor_element_ptype ;
5137
5238/// L2 norm (Euclidean norm) of a tensor or vector column.
5339///
@@ -75,118 +61,49 @@ impl L2Norm {
7561 }
7662}
7763
78- impl ScalarFnVTable for L2Norm {
64+ impl RowFn for L2Norm {
7965 type Options = EmptyOptions ;
8066
67+ const ARG_NAMES : & ' static [ & ' static str ] = & [ "input" ] ;
68+ const INFALLIBLE : bool = true ;
69+
8170 fn id ( & self ) -> ScalarFnId {
8271 static ID : CachedId = CachedId :: new ( "vortex.tensor.l2_norm" ) ;
8372 * ID
8473 }
8574
86- fn arity ( & self , _options : & Self :: Options ) -> Arity {
87- Arity :: Exact ( 1 )
88- }
89-
90- fn child_name ( & self , _options : & Self :: Options , child_idx : usize ) -> ChildName {
91- match child_idx {
92- 0 => ChildName :: from ( "input" ) ,
93- _ => unreachable ! ( "L2Norm must have exactly one child" ) ,
94- }
95- }
96-
97- fn return_dtype ( & self , _options : & Self :: Options , arg_dtypes : & [ DType ] ) -> VortexResult < DType > {
98- let input_dtype = & arg_dtypes[ 0 ] ;
99- let tensor_match = validate_tensor_float_input ( input_dtype) ?;
100- let ptype = tensor_match. element_ptype ( ) ;
101-
102- let nullability = Nullability :: from ( input_dtype. is_nullable ( ) ) ;
103- Ok ( DType :: Primitive ( ptype, nullability) )
75+ fn serialize ( & self , _options : & Self :: Options ) -> VortexResult < Option < Vec < u8 > > > {
76+ Ok ( Some ( vec ! [ ] ) )
10477 }
10578
106- fn execute (
79+ fn deserialize (
10780 & self ,
108- _options : & Self :: Options ,
109- args : & dyn ExecutionArgs ,
110- ctx : & mut ExecutionCtx ,
111- ) -> VortexResult < ArrayRef > {
112- let input_ref = args. get ( 0 ) ?;
113- let row_count = args. row_count ( ) ;
114-
115- let ext = input_ref. dtype ( ) . as_extension ( ) ;
116- let tensor_match = ext
117- . metadata_opt :: < AnyTensor > ( )
118- . vortex_expect ( "L2Norm::return_dtype validated the input tensor metadata" ) ;
119- let tensor_flat_size = tensor_match. list_size ( ) as usize ;
120- let element_ptype = tensor_match. element_ptype ( ) ;
121-
122- let norm_dtype = DType :: Primitive ( element_ptype, ext. nullability ( ) ) ;
123-
124- // Optimize for the constant array case.
125- if let Some ( array) = input_ref. as_opt :: < Constant > ( ) {
126- let scalar = array. scalar ( ) . as_extension ( ) . to_storage_scalar ( ) ;
127-
128- let Some ( elements) = scalar. as_list ( ) . elements ( ) else {
129- return Ok ( ConstantArray :: new ( Scalar :: null ( norm_dtype) , row_count) . into_array ( ) ) ;
130- } ;
131-
132- let norm_scalar = match_each_float_ptype ! ( element_ptype, |T | {
133- let values: Vec <T > = elements
134- . iter( )
135- . map( |element| {
136- element
137- . as_primitive( )
138- . as_:: <T >( )
139- . vortex_expect( "L2Norm::return_dtype validated the float element type" )
140- } )
141- . collect( ) ;
142- let norm = l2_norm_row:: <T >( & values) ;
143-
144- Scalar :: try_new( norm_dtype, Some ( norm. into( ) ) )
145- } ) ?;
146-
147- let norms = ConstantArray :: new ( norm_scalar, row_count) . into_array ( ) ;
148- return Ok ( norms) ;
149- }
150-
151- let input: ExtensionArray = input_ref. execute ( ctx) ?;
152- let validity = input. as_ref ( ) . validity ( ) ?;
153-
154- let storage = input. storage_array ( ) ;
155- let flat = extract_flat_elements ( storage, tensor_flat_size, ctx) ?;
156-
157- match_each_float_ptype ! ( flat. ptype( ) , |T | {
158- let buffer: Buffer <T > = ( 0 ..row_count)
159- . map( |row_index| l2_norm_row( flat. row:: <T >( row_index) ) )
160- . collect( ) ;
161-
162- // SAFETY: The buffer length equals `row_count`, which matches the source validity
163- // length.
164- Ok ( unsafe { PrimitiveArray :: new_unchecked( buffer, validity) } . into_array( ) )
165- } )
81+ _metadata : & [ u8 ] ,
82+ _session : & VortexSession ,
83+ ) -> VortexResult < Self :: Options > {
84+ Ok ( EmptyOptions )
16685 }
16786
168- fn validity (
87+ fn dispatch < V : RowVisitor > (
16988 & self ,
17089 _options : & Self :: Options ,
171- expression : & Expression ,
172- ) -> VortexResult < Option < Expression > > {
173- union_child_validities ( expression)
174- }
175-
176- fn is_strict ( & self , _options : & Self :: Options ) -> bool {
177- true
178- }
179-
180- fn is_infallible ( & self , _options : & Self :: Options ) -> bool {
181- true
90+ args : & [ DType ] ,
91+ visitor : V ,
92+ ) -> VortexResult < V :: VisitResult > {
93+ match_each_float_ptype ! ( tensor_element_ptype( args) ?, |T | {
94+ visitor. visit_into:: <( TensorRow <T >, ) , UninitElementSink <T >, _>( ( ) , |( row, ) , output| {
95+ // SAFETY: `output` is the `UninitElementSink` row supplied for this callback.
96+ unsafe { InitializedElement :: write( output, l2_norm_row( row) ) }
97+ } )
98+ } )
18299 }
183100}
184101
185102/// Metadata for a serialized [`L2Norm`] array: the single `input` child's [`DType`], which carries
186103/// the extension type (`FixedShapeTensor` vs `Vector`), dimension, and nullability that are not
187104/// recoverable from the parent's primitive-float output.
188105#[ derive( Clone , prost:: Message ) ]
189- pub ( super ) struct L2NormMetadata {
106+ struct L2NormMetadata {
190107 #[ prost( message, optional, tag = "1" ) ]
191108 input_dtype : Option < pb:: DType > ,
192109}
@@ -240,6 +157,7 @@ mod tests {
240157 use vortex_array:: arrays:: PrimitiveArray ;
241158 use vortex_array:: arrays:: scalar_fn:: plugin:: ScalarFnArrayPlugin ;
242159 use vortex_array:: dtype:: DType ;
160+ use vortex_array:: dtype:: NativePType ;
243161 use vortex_array:: dtype:: Nullability ;
244162 use vortex_array:: dtype:: PType ;
245163 use vortex_array:: dtype:: extension:: ExtDType ;
@@ -254,13 +172,29 @@ mod tests {
254172 use crate :: utils:: test_helpers:: literal_vector_array;
255173 use crate :: utils:: test_helpers:: tensor_array;
256174 use crate :: utils:: test_helpers:: vector_array;
175+ use crate :: utils:: test_helpers:: zero_width_vector_array;
257176
258- /// Evaluates L2 norm on a tensor/vector array and returns the result as `Vec<f64>`.
259- fn eval_l2_norm ( input : ArrayRef ) -> VortexResult < Vec < f64 > > {
177+ fn evaluate_l2_norm < T : NativePType > ( input : ArrayRef ) -> VortexResult < Vec < T > > {
260178 let result = L2Norm :: try_new ( input) ?;
261179 let mut ctx = SESSION . create_execution_ctx ( ) ;
262- let prim: PrimitiveArray = result. into_array ( ) . execute ( & mut ctx) ?;
263- Ok ( prim. as_slice :: < f64 > ( ) . to_vec ( ) )
180+ let output: PrimitiveArray = result. into_array ( ) . execute ( & mut ctx) ?;
181+
182+ Ok ( output. as_slice :: < T > ( ) . to_vec ( ) )
183+ }
184+
185+ #[ test]
186+ fn test_zero_width_and_empty_inputs ( ) -> VortexResult < ( ) > {
187+ assert_close (
188+ & evaluate_l2_norm ( zero_width_vector_array :: < f64 > ( 3 ) ?) ?,
189+ & [ 0.0 , 0.0 , 0.0 ] ,
190+ ) ;
191+ assert ! ( evaluate_l2_norm:: <f64 >( vector_array:: <f64 >( 2 , & [ ] ) ?) ?. is_empty( ) ) ;
192+ assert_close (
193+ & evaluate_l2_norm ( Vector :: constant_array :: < f64 > ( & [ ] , 3 ) ?) ?,
194+ & [ 0.0 , 0.0 , 0.0 ] ,
195+ ) ;
196+
197+ Ok ( ( ) )
264198 }
265199
266200 #[ rstest]
@@ -274,7 +208,7 @@ mod tests {
274208 #[ case] expected : & [ f64 ] ,
275209 ) -> VortexResult < ( ) > {
276210 let arr = tensor_array ( shape, elements) ?;
277- assert_close ( & eval_l2_norm ( arr) ?, expected) ;
211+ assert_close ( & evaluate_l2_norm ( arr) ?, expected) ;
278212 Ok ( ( ) )
279213 }
280214
@@ -288,7 +222,7 @@ mod tests {
288222 1.0 , 1.0 , 1.0 , // norm = sqrt(3)
289223 ] ,
290224 ) ?;
291- assert_close ( & eval_l2_norm ( arr) ?, & [ 5.0 , 0.0 , 3.0_f64 . sqrt ( ) ] ) ;
225+ assert_close ( & evaluate_l2_norm ( arr) ?, & [ 5.0 , 0.0 , 3.0_f64 . sqrt ( ) ] ) ;
292226 Ok ( ( ) )
293227 }
294228
@@ -301,7 +235,7 @@ mod tests {
301235 3.0 , 4.0 , 0.0 , // norm = 5.0
302236 ] ,
303237 ) ?;
304- assert_close ( & eval_l2_norm ( arr) ?, & [ 1.0 , 5.0 ] ) ;
238+ assert_close ( & evaluate_l2_norm ( arr) ?, & [ 1.0 , 5.0 ] ) ;
305239 Ok ( ( ) )
306240 }
307241
@@ -347,6 +281,50 @@ mod tests {
347281 Ok ( ( ) )
348282 }
349283
284+ #[ test]
285+ fn test_extension_backed_constant_yields_constant_output ( ) -> VortexResult < ( ) > {
286+ let input = Vector :: constant_array ( & [ 3.0f64 , 4.0 ] , 4 ) ?;
287+
288+ let result = L2Norm :: try_new ( input) ?. into_array ( ) ;
289+ let mut ctx = SESSION . create_execution_ctx ( ) ;
290+ let output = result. execute_until :: < Constant > ( & mut ctx) ?;
291+
292+ let constant = output
293+ . as_opt :: < Constant > ( )
294+ . expect ( "L2Norm over constant-backed extension storage must be constant" ) ;
295+ assert_eq ! ( constant. len( ) , 4 ) ;
296+ let norm = constant
297+ . scalar ( )
298+ . as_primitive ( )
299+ . as_ :: < f64 > ( )
300+ . expect ( "norm scalar must be a non-null primitive" ) ;
301+ assert_eq ! ( norm. to_bits( ) , 5.0f64 . to_bits( ) ) ;
302+
303+ Ok ( ( ) )
304+ }
305+
306+ #[ test]
307+ fn test_encoded_constants_match_materialized_rows_bitwise ( ) -> VortexResult < ( ) > {
308+ let row = [ f32:: MAX , 1.0 , -1.0 ] ;
309+ let materialized = vector_array ( 3 , & [ row, row, row] . concat ( ) ) ?;
310+ let storage_constant = Vector :: constant_array ( & row, 3 ) ?;
311+ let literal_constant = literal_vector_array ( & row, 3 ) ;
312+
313+ let expected: Vec < _ > = evaluate_l2_norm :: < f32 > ( materialized) ?
314+ . into_iter ( )
315+ . map ( f32:: to_bits)
316+ . collect ( ) ;
317+ for encoded in [ storage_constant, literal_constant] {
318+ let actual: Vec < _ > = evaluate_l2_norm :: < f32 > ( encoded) ?
319+ . into_iter ( )
320+ . map ( f32:: to_bits)
321+ . collect ( ) ;
322+ assert_eq ! ( actual, expected) ;
323+ }
324+
325+ Ok ( ( ) )
326+ }
327+
350328 /// A constant input whose scalar is null should short-circuit to a null [`ConstantArray`] of
351329 /// the correct primitive dtype and length.
352330 #[ test]
0 commit comments