@@ -230,17 +230,44 @@ class UltraEccOpsTable {
230230 size_t current_subtable_idx = 0 ; // index of the current subtable being constructed
231231 UltraOpsTable table;
232232
233+ // For fixed-location append functionality (ultra ops only)
234+ std::optional<size_t > fixed_append_offset;
235+ bool has_fixed_append = false ;
236+
233237 public:
234238 size_t size () const { return table.size (); }
235- size_t ultra_table_size () const { return table.size () * NUM_ROWS_PER_OP; }
239+ size_t ultra_table_size () const
240+ {
241+ size_t base_size = table.size () * NUM_ROWS_PER_OP;
242+ if (has_fixed_append && fixed_append_offset.has_value ()) {
243+ // Include zeros gap and final subtable at fixed location
244+ size_t last_subtable_size = 0 ;
245+ if (!table.get ().empty ()) {
246+ // The last subtable in deque is the fixed-location one
247+ last_subtable_size = table.get ().back ().size () * NUM_ROWS_PER_OP;
248+ }
249+ return std::max (base_size, fixed_append_offset.value () + last_subtable_size);
250+ }
251+ return base_size;
252+ }
236253 size_t current_ultra_subtable_size () const { return table.get ()[current_subtable_idx].size () * NUM_ROWS_PER_OP; }
237254 size_t previous_ultra_table_size () const { return (ultra_table_size () - current_ultra_subtable_size ()); }
238255 void create_new_subtable (size_t size_hint = 0 ) { table.create_new_subtable (size_hint); }
239256 void push (const UltraOp& op) { table.push (op); }
240- void merge (MergeSettings settings = MergeSettings::PREPEND)
257+ void merge (MergeSettings settings = MergeSettings::PREPEND, std::optional< size_t > offset = std:: nullopt )
241258 {
242- table.merge (settings);
243- current_subtable_idx = settings == MergeSettings::PREPEND ? 0 : table.num_subtables () - 1 ;
259+ if (settings == MergeSettings::APPEND) {
260+ // All appends are treated as fixed-location for ultra ops
261+ ASSERT (!has_fixed_append, " Can only perform fixed-location append once" );
262+ // Set fixed location at which to append ultra ops. If nullopt --> append right after prepended tables
263+ fixed_append_offset = offset;
264+ has_fixed_append = true ;
265+ table.merge (settings);
266+ current_subtable_idx = table.num_subtables () - 1 ;
267+ } else { // MergeSettings::PREPEND
268+ table.merge (settings);
269+ current_subtable_idx = 0 ;
270+ }
244271 }
245272
246273 std::vector<UltraOp> get_reconstructed () const { return table.get_reconstructed (); }
@@ -249,25 +276,30 @@ class UltraEccOpsTable {
249276 ColumnPolynomials construct_table_columns () const
250277 {
251278 const size_t poly_size = ultra_table_size ();
279+
280+ if (has_fixed_append) {
281+ // Handle fixed-location append: prepended tables first, then appended table at fixed offset
282+ return construct_column_polynomials_with_fixed_append (poly_size);
283+ }
284+
285+ // Normal case: all subtables in order
252286 const size_t subtable_start_idx = 0 ; // include all subtables
253287 const size_t subtable_end_idx = table.num_subtables ();
254-
255288 return construct_column_polynomials_from_subtables (poly_size, subtable_start_idx, subtable_end_idx);
256289 }
257290
258291 // Construct the columns of the previous full ultra ecc ops table
259292 ColumnPolynomials construct_previous_table_columns () const
260293 {
261-
262294 const size_t poly_size = previous_ultra_table_size ();
263295 const size_t subtable_start_idx = current_subtable_idx == 0 ? 1 : 0 ;
264296 const size_t subtable_end_idx = current_subtable_idx == 0 ? table.num_subtables () : table.num_subtables () - 1 ;
265297
266298 return construct_column_polynomials_from_subtables (poly_size, subtable_start_idx, subtable_end_idx);
267299 }
268300
269- // Construct the columns of the current ultra ecc ops subtable which is either the first or the last one depening on
270- // whether it has been prepended or appended
301+ // Construct the columns of the current ultra ecc ops subtable which is either the first or the last one
302+ // depening on whether it has been prepended or appended
271303 ColumnPolynomials construct_current_ultra_ops_subtable_columns () const
272304 {
273305 const size_t poly_size = current_ultra_subtable_size ();
@@ -278,6 +310,60 @@ class UltraEccOpsTable {
278310 }
279311
280312 private:
313+ /* *
314+ * @brief Write a single UltraOp to the column polynomials at the given position
315+ * @details Each op is written across 2 rows (NUM_ROWS_PER_OP)
316+ * @param column_polynomials The column polynomials to write to
317+ * @param op The operation to write
318+ * @param row_idx The starting row index (will write to row_idx and row_idx+1)
319+ */
320+ static void write_op_to_polynomials (ColumnPolynomials& column_polynomials, const UltraOp& op, const size_t row_idx)
321+ {
322+ column_polynomials[0 ].at (row_idx) = !op.op_code .is_random_op ? op.op_code .value () : op.op_code .random_value_1 ;
323+ column_polynomials[1 ].at (row_idx) = op.x_lo ;
324+ column_polynomials[2 ].at (row_idx) = op.x_hi ;
325+ column_polynomials[3 ].at (row_idx) = op.y_lo ;
326+ column_polynomials[0 ].at (row_idx + 1 ) = !op.op_code .is_random_op ? 0 : op.op_code .random_value_2 ;
327+ column_polynomials[1 ].at (row_idx + 1 ) = op.y_hi ;
328+ column_polynomials[2 ].at (row_idx + 1 ) = op.z_1 ;
329+ column_polynomials[3 ].at (row_idx + 1 ) = op.z_2 ;
330+ }
331+
332+ /* *
333+ * @brief Construct polynomials with fixed-location append
334+ * @details Process prepended subtables first, then place the appended subtable at the fixed offset
335+ */
336+ ColumnPolynomials construct_column_polynomials_with_fixed_append (const size_t poly_size) const
337+ {
338+ ColumnPolynomials column_polynomials;
339+ for (auto & poly : column_polynomials) {
340+ poly = Polynomial<Fr>(poly_size); // Initialized to zeros
341+ }
342+
343+ // Process all prepended subtables (all except last)
344+ size_t i = 0 ;
345+ for (size_t subtable_idx = 0 ; subtable_idx < table.num_subtables () - 1 ; ++subtable_idx) {
346+ const auto & subtable = table.get ()[subtable_idx];
347+ for (const auto & op : subtable) {
348+ write_op_to_polynomials (column_polynomials, op, i);
349+ i += NUM_ROWS_PER_OP;
350+ }
351+ }
352+
353+ // Place the appended subtable at the fixed offset
354+ size_t append_position = fixed_append_offset.value_or (i);
355+ const auto & appended_subtable = table.get ()[table.num_subtables () - 1 ];
356+
357+ size_t j = append_position;
358+ for (const auto & op : appended_subtable) {
359+ write_op_to_polynomials (column_polynomials, op, j);
360+ j += NUM_ROWS_PER_OP;
361+ }
362+
363+ // Any gap between prepended tables and appended table remains zeros (from initialization)
364+ return column_polynomials;
365+ }
366+
281367 /* *
282368 * @brief Construct polynomials corresponding to the columns of the reconstructed ultra ops table for the given
283369 * range of subtables
@@ -297,17 +383,8 @@ class UltraEccOpsTable {
297383 for (size_t subtable_idx = subtable_start_idx; subtable_idx < subtable_end_idx; ++subtable_idx) {
298384 const auto & subtable = table.get ()[subtable_idx];
299385 for (const auto & op : subtable) {
300- column_polynomials[0 ].at (i) = !op.op_code .is_random_op ? op.op_code .value () : op.op_code .random_value_1 ;
301- column_polynomials[1 ].at (i) = op.x_lo ;
302- column_polynomials[2 ].at (i) = op.x_hi ;
303- column_polynomials[3 ].at (i) = op.y_lo ;
304- i++;
305- column_polynomials[0 ].at (i) = !op.op_code .is_random_op ? 0 : op.op_code .random_value_2 ;
306- // only the first 'op' field is utilized
307- column_polynomials[1 ].at (i) = op.y_hi ;
308- column_polynomials[2 ].at (i) = op.z_1 ;
309- column_polynomials[3 ].at (i) = op.z_2 ;
310- i++;
386+ write_op_to_polynomials (column_polynomials, op, i);
387+ i += NUM_ROWS_PER_OP;
311388 }
312389 }
313390 return column_polynomials;
0 commit comments