@@ -105,8 +105,21 @@ public EncodeResult encode(DType dtype, Object data) {
105105 throw new UnsupportedOperationException ("ALP encode not yet supported for " + ptype );
106106 }
107107
108+ @ Override
109+ public CascadeStep encodeCascade (DType dtype , Object data , CompressorContext ctx ) {
110+ PType ptype = ((DType .Primitive ) dtype ).ptype ();
111+ if (ptype == PType .F64 ) {
112+ return encodeCascadeF64 ((double []) data );
113+ }
114+ return CascadeStep .terminal (encode (dtype , data ));
115+ }
116+
108117 // ── F64 encode ────────────────────────────────────────────────────────────
109118
119+ private record AlpF64Data (int expE , int expF , long [] encodedArr ,
120+ List <Integer > patchIndices , List <Double > patchValues ,
121+ byte [] statsMin , byte [] statsMax ) {}
122+
110123 private static int [] findExponentsF64 (double [] values ) {
111124 int sampleLen = Math .min (SAMPLE_SIZE , values .length );
112125 int bestExpE = 0 , bestExpF = 0 , bestExceptions = sampleLen + 1 ;
@@ -136,7 +149,7 @@ private static int[] findExponentsF64(double[] values) {
136149 return new int []{bestExpE , bestExpF };
137150 }
138151
139- private static EncodeResult encodeF64 (double [] values , DType dtype ) {
152+ private static AlpF64Data computeF64 (double [] values ) {
140153 int n = values .length ;
141154 int [] exps = findExponentsF64 (values );
142155 int expE = exps [0 ], expF = exps [1 ];
@@ -169,45 +182,92 @@ private static EncodeResult encodeF64(double[] values, DType dtype) {
169182
170183 byte [] statsMin = n > 0 ? scalarF64 (min ) : null ;
171184 byte [] statsMax = n > 0 ? scalarF64 (max ) : null ;
185+ return new AlpF64Data (expE , expF , encodedArr , patchIndices , patchValues , statsMin , statsMax );
186+ }
187+
188+ private static EncodeResult encodeF64 (double [] values , DType dtype ) {
189+ AlpF64Data d = computeF64 (values );
190+ int n = values .length ;
172191
173192 MemorySegment encodedBuf = Arena .ofAuto ().allocate ((long ) n * 8 , 8 );
174193 for (int i = 0 ; i < n ; i ++) {
175- encodedBuf .setAtIndex (PTypeIO .LE_LONG , i , encodedArr [i ]);
194+ encodedBuf .setAtIndex (PTypeIO .LE_LONG , i , d . encodedArr () [i ]);
176195 }
177196
178197 EncodeNode encodedNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 0 );
179198
180- if (patchIndices .isEmpty ()) {
199+ if (d . patchIndices () .isEmpty ()) {
181200 byte [] metaBytes = EncodingProtos .ALPMetadata .newBuilder ()
182- .setExpE (expE ) .setExpF (expF ).build ().toByteArray ();
201+ .setExpE (d . expE ()) .setExpF (d . expF () ).build ().toByteArray ();
183202 EncodeNode root = new EncodeNode (EncodingId .VORTEX_ALP ,
184203 ByteBuffer .wrap (metaBytes ), new EncodeNode []{encodedNode }, new int [0 ]);
185- return new EncodeResult (root , List .of (encodedBuf ), statsMin , statsMax );
204+ return new EncodeResult (root , List .of (encodedBuf ), d . statsMin (), d . statsMax () );
186205 }
187206
188- int numPatches = patchIndices .size ();
207+ int numPatches = d . patchIndices () .size ();
189208 MemorySegment idxBuf = Arena .ofAuto ().allocate ((long ) numPatches * 4 , 4 );
190209 MemorySegment valBuf = Arena .ofAuto ().allocate ((long ) numPatches * 8 , 8 );
191210 for (int i = 0 ; i < numPatches ; i ++) {
192- idxBuf .setAtIndex (PTypeIO .LE_INT , i , patchIndices .get (i ));
193- valBuf .setAtIndex (PTypeIO .LE_DOUBLE , i , patchValues .get (i ));
211+ idxBuf .setAtIndex (PTypeIO .LE_INT , i , d . patchIndices () .get (i ));
212+ valBuf .setAtIndex (PTypeIO .LE_DOUBLE , i , d . patchValues () .get (i ));
194213 }
195214
196- EncodingProtos .PatchesMetadata patches = EncodingProtos .PatchesMetadata .newBuilder ()
197- .setLen (numPatches )
198- .setOffset (0 )
199- .setIndicesPtype (DTypeProtos .PType .forNumber (PType .U32 .ordinal ()))
200- .build ();
215+ EncodingProtos .PatchesMetadata patches = buildPatchesMeta (numPatches );
201216 byte [] metaBytes = EncodingProtos .ALPMetadata .newBuilder ()
202- .setExpE (expE ) .setExpF (expF ).setPatches (patches ).build ().toByteArray ();
217+ .setExpE (d . expE ()) .setExpF (d . expF () ).setPatches (patches ).build ().toByteArray ();
203218
204219 EncodeNode idxNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 1 );
205220 EncodeNode valNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 2 );
206221 EncodeNode root = new EncodeNode (EncodingId .VORTEX_ALP ,
207222 ByteBuffer .wrap (metaBytes ),
208223 new EncodeNode []{encodedNode , idxNode , valNode },
209224 new int [0 ]);
210- return new EncodeResult (root , List .of (encodedBuf , idxBuf , valBuf ), statsMin , statsMax );
225+ return new EncodeResult (root , List .of (encodedBuf , idxBuf , valBuf ), d .statsMin (), d .statsMax ());
226+ }
227+
228+ /// Cascade-aware F64 encode: I64 child is an open ChildSlot so the compressor
229+ /// can bitpack it. Patch idx/val buffers (if any) stay in ownedBuffers.
230+ private static CascadeStep encodeCascadeF64 (double [] values ) {
231+ AlpF64Data d = computeF64 (values );
232+ int n = values .length ;
233+
234+ if (d .patchIndices ().isEmpty ()) {
235+ byte [] metaBytes = EncodingProtos .ALPMetadata .newBuilder ()
236+ .setExpE (d .expE ()).setExpF (d .expF ()).build ().toByteArray ();
237+ // children[0] will be filled by the compressor after recursing on the ChildSlot
238+ EncodeNode partialRoot = new EncodeNode (EncodingId .VORTEX_ALP ,
239+ ByteBuffer .wrap (metaBytes ), new EncodeNode [1 ], new int [0 ]);
240+ ChildSlot slot = new ChildSlot (I64_DTYPE , d .encodedArr (), 0 );
241+ return new CascadeStep (partialRoot , List .of (), List .of (slot ), d .statsMin (), d .statsMax ());
242+ }
243+
244+ int numPatches = d .patchIndices ().size ();
245+ MemorySegment idxBuf = Arena .ofAuto ().allocate ((long ) numPatches * 4 , 4 );
246+ MemorySegment valBuf = Arena .ofAuto ().allocate ((long ) numPatches * 8 , 8 );
247+ for (int i = 0 ; i < numPatches ; i ++) {
248+ idxBuf .setAtIndex (PTypeIO .LE_INT , i , d .patchIndices ().get (i ));
249+ valBuf .setAtIndex (PTypeIO .LE_DOUBLE , i , d .patchValues ().get (i ));
250+ }
251+
252+ EncodingProtos .PatchesMetadata patches = buildPatchesMeta (numPatches );
253+ byte [] metaBytes = EncodingProtos .ALPMetadata .newBuilder ()
254+ .setExpE (d .expE ()).setExpF (d .expF ()).setPatches (patches ).build ().toByteArray ();
255+
256+ // ownedBuffers[0]=idxBuf, [1]=valBuf; child I64 will be appended after (starting at idx 2)
257+ EncodeNode idxNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 0 );
258+ EncodeNode valNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 1 );
259+ EncodeNode partialRoot = new EncodeNode (EncodingId .VORTEX_ALP ,
260+ ByteBuffer .wrap (metaBytes ), new EncodeNode []{null , idxNode , valNode }, new int [0 ]);
261+ ChildSlot slot = new ChildSlot (I64_DTYPE , d .encodedArr (), 0 );
262+ return new CascadeStep (partialRoot , List .of (idxBuf , valBuf ), List .of (slot ), d .statsMin (), d .statsMax ());
263+ }
264+
265+ private static EncodingProtos .PatchesMetadata buildPatchesMeta (int numPatches ) {
266+ return EncodingProtos .PatchesMetadata .newBuilder ()
267+ .setLen (numPatches )
268+ .setOffset (0 )
269+ .setIndicesPtype (DTypeProtos .PType .forNumber (PType .U32 .ordinal ()))
270+ .build ();
211271 }
212272
213273 private static byte [] scalarF64 (double v ) {
0 commit comments