diff --git a/executor/src/witgen/block_processor.rs b/executor/src/witgen/block_processor.rs index 107f202b4..492eba71a 100644 --- a/executor/src/witgen/block_processor.rs +++ b/executor/src/witgen/block_processor.rs @@ -8,7 +8,7 @@ use powdr_number::FieldElement; use super::{ data_structures::finalizable_data::FinalizableData, processor::{OuterQuery, Processor}, - rows::UnknownStrategy, + rows::{RowIndex, UnknownStrategy}, sequence_iterator::{Action, ProcessingSequenceIterator, SequenceStep}, EvalError, EvalValue, FixedData, IncompleteCause, MutableState, QueryCallback, }; @@ -27,7 +27,7 @@ pub struct BlockProcessor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> { impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> BlockProcessor<'a, 'b, 'c, T, Q> { pub fn new( - row_offset: u64, + row_offset: RowIndex, data: FinalizableData<'a, T>, mutable_state: &'c mut MutableState<'a, 'b, T, Q>, identities: &'c [&'a Identity>], @@ -105,12 +105,11 @@ mod tests { use crate::{ constant_evaluator::generate, witgen::{ - data_structures::column_map::FixedColumnMap, - data_structures::finalizable_data::FinalizableData, + data_structures::{column_map::FixedColumnMap, finalizable_data::FinalizableData}, global_constraints::GlobalConstraints, identity_processor::Machines, machines::FixedLookup, - rows::RowFactory, + rows::{RowFactory, RowIndex}, sequence_iterator::{DefaultSequenceIterator, ProcessingSequenceIterator}, unused_query_callback, FixedData, MutableState, QueryCallback, }, @@ -161,7 +160,8 @@ mod tests { .collect(); let data = FinalizableData::with_initial_rows_in_progress( &columns, - (0..fixed_data.degree).map(|i| row_factory.fresh_row(i)), + (0..fixed_data.degree) + .map(|i| row_factory.fresh_row(RowIndex::from_degree(i, fixed_data.degree))), ); let mut mutable_state = MutableState { @@ -169,7 +169,7 @@ mod tests { machines: Machines::from(machines.iter_mut()), query_callback: &mut query_callback, }; - let row_offset = 0; + let row_offset = RowIndex::from_degree(0, fixed_data.degree); let identities = analyzed.identities.iter().collect::>(); let witness_cols = fixed_data.witness_cols.keys().collect(); diff --git a/executor/src/witgen/generator.rs b/executor/src/witgen/generator.rs index f125cf578..61f2d026e 100644 --- a/executor/src/witgen/generator.rs +++ b/executor/src/witgen/generator.rs @@ -16,7 +16,7 @@ use super::block_processor::BlockProcessor; use super::data_structures::column_map::WitnessColumnMap; use super::global_constraints::GlobalConstraints; use super::machines::{FixedLookup, Machine}; -use super::rows::{Row, RowFactory}; +use super::rows::{Row, RowFactory, RowIndex}; use super::sequence_iterator::{DefaultSequenceIterator, ProcessingSequenceIterator}; use super::vm_processor::VmProcessor; use super::{EvalResult, FixedData, MutableState, QueryCallback}; @@ -174,8 +174,8 @@ impl<'a, T: FieldElement> Generator<'a, T> { let data = FinalizableData::with_initial_rows_in_progress( &self.witnesses, [ - row_factory.fresh_row(self.fixed_data.degree - 1), - row_factory.fresh_row(0), + row_factory.fresh_row(RowIndex::from_i64(-1, self.fixed_data.degree)), + row_factory.fresh_row(RowIndex::from_i64(0, self.fixed_data.degree)), ] .into_iter(), ); @@ -190,7 +190,7 @@ impl<'a, T: FieldElement> Generator<'a, T> { .filter_map(|identity| identity.contains_next_ref().then_some(*identity)) .collect::>(); let mut processor = BlockProcessor::new( - self.fixed_data.degree - 1, + RowIndex::from_i64(-1, self.fixed_data.degree), data, mutable_state, &identities_with_next_reference, @@ -223,7 +223,7 @@ impl<'a, T: FieldElement> Generator<'a, T> { [first_row].into_iter(), ); let mut processor = VmProcessor::new( - row_offset, + RowIndex::from_degree(row_offset, self.fixed_data.degree), self.fixed_data, &self.identities, &self.witnesses, diff --git a/executor/src/witgen/machines/block_machine.rs b/executor/src/witgen/machines/block_machine.rs index e048b68fb..f0c395e4b 100644 --- a/executor/src/witgen/machines/block_machine.rs +++ b/executor/src/witgen/machines/block_machine.rs @@ -8,7 +8,7 @@ use crate::witgen::data_structures::finalizable_data::FinalizableData; use crate::witgen::global_constraints::GlobalConstraints; use crate::witgen::identity_processor::IdentityProcessor; use crate::witgen::processor::OuterQuery; -use crate::witgen::rows::{CellValue, RowFactory, RowPair, UnknownStrategy}; +use crate::witgen::rows::{CellValue, Row, RowFactory, RowIndex, RowPair, UnknownStrategy}; use crate::witgen::sequence_iterator::{ProcessingSequenceCache, ProcessingSequenceIterator}; use crate::witgen::util::try_to_simple_poly; use crate::witgen::{machines::Machine, EvalError, EvalValue, IncompleteCause}; @@ -114,12 +114,15 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> { .map(|(block_size, connecting_rhs)| { assert!(block_size <= fixed_data.degree as usize); let row_factory = RowFactory::new(fixed_data, global_range_constraints.clone()); - // Start out with a block filled with unknown values so that we do not have to deal with wrap-around - // when storing machine witness data. - // This will be filled with the default block in `take_witness_col_values` + // Because block shapes are not always rectangular, we add the last block to the data at the + // beginning. It starts out with unknown values. Should the first block decide to write to + // rows < 0, they will be writen to this block. + // In `take_witness_col_values()`, this block will be removed and its values will be used to + // construct the "default" block used to fill up unused rows. + let start_index = RowIndex::from_i64(-(block_size as i64), fixed_data.degree); let data = FinalizableData::with_initial_rows_in_progress( witness_cols, - (0..block_size).map(|i| row_factory.fresh_row(i as DegreeType)), + (0..block_size).map(|i| row_factory.fresh_row(start_index + i)), ); BlockMachine { name, @@ -192,7 +195,7 @@ impl<'a, T: FieldElement> Machine<'a, T> for BlockMachine<'a, T> { if !self.connecting_rhs.contains(right) || kind != IdentityKind::Plookup { return None; } - let previous_len = self.rows() as usize; + let previous_len = self.data.len(); Some({ let result = self.process_plookup_internal(mutable_state, left, right); if let Ok(assignments) = &result { @@ -231,25 +234,38 @@ impl<'a, T: FieldElement> Machine<'a, T> for BlockMachine<'a, T> { .map(|(v, known)| known.then_some(v)) .collect::>(); + // Remove the "last" block added to the beginning of self.data. + // It contains the values the first block wrote to it and otherwise unknown values. + let dummy_block = values.drain(0..self.block_size).collect::>(); + // For all constraints to be satisfied, unused cells have to be filled with valid values. // We do this, we construct a default block, by repeating the first input to the block machine. values.resize(self.fixed_data.degree as usize, None); - let second_block_values = values.iter().skip(self.block_size).take(self.block_size); - - // The first block is a dummy block (filled mostly with None), the second block is the first block - // resulting of an actual evaluation. - // However, if the block machine already sets some registers in the last row of the previous block, - // they will be set in the "dummy block". In this case, we want to use these values. - // As a result, the default block consists of values of the first block if they are set, otherwise - // the values of the second block. + // Use the block as the default block. However, it needs to be merged with the dummy block, + // to handle blocks of non-rectangular shape. + // For example, let's say, the situation might look like this (block size = 3 in this example): + // Row Latch C1 C2 C3 + // -3 0 + // -2 0 + // -1 1 X <- This value belongs to the first block + // 0 0 X X X + // 1 0 X X X + // 2 1 X X X <- This value belongs to the second block + // + // The following code constructs the default block as follows: + // - All values will come from rows 0-2, EXCEPT + // - In the last row, the value of C3 is whatever value was written to the dummy block + // + // Constructed like this, we can repeat the default block forever. + // // TODO: Determine the row-extend per column - let default_block = values - .iter() - .take(self.block_size) - .zip(second_block_values) - .map(|(first_block, second_block)| { - first_block.or(*second_block).unwrap_or_default() + let first_block_values = values.iter().take(self.block_size); + let default_block = dummy_block + .into_iter() + .zip(first_block_values) + .map(|(dummy_block, first_block)| { + dummy_block.or(*first_block).unwrap_or_default() }) .collect::>(); @@ -292,8 +308,20 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> { } } + /// Returns the current number of rows *not counting the dummy block* inserted at the beginning + /// (with row numbers (-block_size..0)). fn rows(&self) -> DegreeType { - self.data.len() as DegreeType + (self.data.len() - self.block_size) as DegreeType + } + + fn last_row_index(&self) -> RowIndex { + RowIndex::from_i64(self.rows() as i64 - 1, self.fixed_data.degree) + } + + fn get_row(&self, row: RowIndex) -> &Row<'a, T> { + // The first block is a dummy block corresponding to rows (-block_size, 0), + // so we have to add the block size to the row index. + &self.data[(row + self.block_size).into()] } fn process_plookup_internal<'b, Q: QueryCallback>( @@ -314,16 +342,16 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> { if left.iter().all(|v| v.is_constant()) && self.rows() > 0 { // All values on the left hand side are known, check if this is a query // to the last row. - let row = self.rows() - 1; + let row_index = self.last_row_index(); - let current = &self.data[row as usize]; + let current = &self.get_row(row_index); // We don't have the next row, because it would be the first row of the next block. // We'll use a fresh row instead. - let next = self.row_factory.fresh_row(row + 1); + let next = self.row_factory.fresh_row(row_index + 1); let row_pair = RowPair::new( current, &next, - row, + row_index, self.fixed_data, UnknownStrategy::Unknown, ); @@ -400,13 +428,12 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> { sequence_iterator: &mut ProcessingSequenceIterator, ) -> Result, EvalError> { // We start at the last row of the previous block. - let row_offset = self.rows() - 1; + let row_offset = self.last_row_index(); // Make the block two rows larger than the block size, it includes the last row of the previous block // and the first row of the next block. let block = FinalizableData::with_initial_rows_in_progress( &self.witness_cols, - (0..(self.block_size + 2)) - .map(|i| self.row_factory.fresh_row(i as DegreeType + row_offset)), + (0..(self.block_size + 2)).map(|i| self.row_factory.fresh_row(row_offset + i)), ); let mut processor = BlockProcessor::new( row_offset, @@ -439,9 +466,8 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> { // 1. Ignore the first row of the next block: new_block.pop(); // 2. Merge the last row of the previous block - let last_row_index = self.rows() as usize - 1; let updated_last_row = new_block.get_mut(0).unwrap(); - for (poly_id, existing_value) in self.data[last_row_index].iter() { + for (poly_id, existing_value) in self.get_row(self.last_row_index()).iter() { if let CellValue::Known(v) = existing_value.value { if updated_last_row[&poly_id].value.is_known() && updated_last_row[&poly_id].value != existing_value.value diff --git a/executor/src/witgen/processor.rs b/executor/src/witgen/processor.rs index 2ed4ace7f..fe12ee05a 100644 --- a/executor/src/witgen/processor.rs +++ b/executor/src/witgen/processor.rs @@ -13,7 +13,7 @@ use super::{ affine_expression::AffineExpression, data_structures::{column_map::WitnessColumnMap, finalizable_data::FinalizableData}, identity_processor::IdentityProcessor, - rows::{CellValue, Row, RowPair, RowUpdater, UnknownStrategy}, + rows::{CellValue, Row, RowIndex, RowPair, RowUpdater, UnknownStrategy}, Constraints, EvalError, EvalValue, FixedData, MutableState, QueryCallback, }; @@ -53,7 +53,7 @@ pub struct IdentityResult { /// - `'c`: The duration of this Processor's lifetime (e.g. the reference to the identity processor) pub struct Processor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> { /// The global index of the first row of [Processor::data]. - row_offset: u64, + row_offset: RowIndex, /// The rows that are being processed. data: FinalizableData<'a, T>, /// The mutable state @@ -72,7 +72,7 @@ pub struct Processor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> { impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> Processor<'a, 'b, 'c, T, Q> { pub fn new( - row_offset: u64, + row_offset: RowIndex, data: FinalizableData<'a, T>, mutable_state: &'c mut MutableState<'a, 'b, T, Q>, fixed_data: &'a FixedData<'a, T>, @@ -129,7 +129,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> Processor<'a, 'b, 'c, T, pub fn latch_value(&self, row_index: usize) -> Option { let row_pair = RowPair::from_single_row( &self.data[row_index], - row_index as u64 + self.row_offset, + self.row_offset + row_index as u64, self.fixed_data, UnknownStrategy::Unknown, ); @@ -377,7 +377,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> Processor<'a, 'b, 'c, T, RowPair::new( &self.data[row_index - 1], proposed_row, - row_index as DegreeType + self.row_offset - 1, + self.row_offset + (row_index - 1) as DegreeType, self.fixed_data, UnknownStrategy::Zero, ) @@ -387,7 +387,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> Processor<'a, 'b, 'c, T, // Because we never access the next row, we can use [RowPair::from_single_row] here. false => RowPair::from_single_row( proposed_row, - row_index as DegreeType + self.row_offset, + self.row_offset + row_index as DegreeType, self.fixed_data, UnknownStrategy::Zero, ), diff --git a/executor/src/witgen/query_processor.rs b/executor/src/witgen/query_processor.rs index e5240ff85..aaddd9f82 100644 --- a/executor/src/witgen/query_processor.rs +++ b/executor/src/witgen/query_processor.rs @@ -8,7 +8,10 @@ use powdr_ast::analyzed::{ use powdr_number::{DegreeType, FieldElement}; use powdr_pil_analyzer::evaluator::{self, Custom, EvalError, SymbolLookup, Value}; -use super::{rows::RowPair, Constraint, EvalResult, EvalValue, FixedData, IncompleteCause}; +use super::{ + rows::{RowIndex, RowPair}, + Constraint, EvalResult, EvalValue, FixedData, IncompleteCause, +}; /// Computes value updates that result from a query. pub struct QueryProcessor<'a, 'b, T: FieldElement, QueryCallback: Send + Sync> { @@ -81,7 +84,7 @@ impl<'a, 'b, T: FieldElement, QueryCallback: super::QueryCallback> rows: &RowPair, ) -> Result { let arguments = vec![Rc::new(Value::Integer(num_bigint::BigInt::from( - rows.current_row_index, + u64::from(rows.current_row_index), )))]; let symbols = Symbols { fixed_data: self.fixed_data, @@ -141,12 +144,13 @@ impl<'a, T: FieldElement> SymbolLookup<'a, T, Reference<'a>> for Symbols<'a, T> }; Ok(Value::FieldElement(match function.poly_id.ptype { PolynomialType::Committed | PolynomialType::Intermediate => { - let next = self - .rows - .is_row_number_next(DegreeType::try_from(row).unwrap()) - .map_err(|_| { - EvalError::OutOfBounds(format!("Referenced row outside of window: {row}")) - })?; + let row_index = RowIndex::from_degree( + DegreeType::try_from(row).unwrap(), + self.fixed_data.degree, + ); + let next = self.rows.is_row_number_next(row_index).map_err(|_| { + EvalError::OutOfBounds(format!("Referenced row outside of window: {row}")) + })?; let poly_ref = AlgebraicReference { name: function.name.to_string(), poly_id: function.poly_id, diff --git a/executor/src/witgen/rows.rs b/executor/src/witgen/rows.rs index 2bc72bb25..6e90f616b 100644 --- a/executor/src/witgen/rows.rs +++ b/executor/src/witgen/rows.rs @@ -1,4 +1,8 @@ -use std::{collections::HashSet, fmt::Debug}; +use std::{ + collections::HashSet, + fmt::Debug, + ops::{Add, Sub}, +}; use itertools::Itertools; use powdr_ast::analyzed::{AlgebraicExpression as Expression, AlgebraicReference, PolyID}; @@ -16,6 +20,86 @@ use super::{ FixedData, }; +/// A small wrapper around a row index, which knows the total number of rows. +/// When converted to DegreeType or usize, it will be reduced modulo the number of rows +/// (handling negative indices as well). +#[derive(Clone, Copy)] +pub struct RowIndex { + index: i64, + num_rows: DegreeType, +} + +impl From for DegreeType { + fn from(row_index: RowIndex) -> Self { + // Ensure that 0 <= index < num_rows + if row_index.index >= 0 { + (row_index.index as DegreeType) % row_index.num_rows + } else { + assert!(row_index.index > -(row_index.num_rows as i64)); + row_index.num_rows - (-row_index.index as DegreeType) + } + } +} + +impl From for usize { + fn from(row_index: RowIndex) -> Self { + DegreeType::from(row_index).try_into().unwrap() + } +} + +impl RowIndex { + pub fn from_i64(index: i64, num_rows: DegreeType) -> Self { + Self { index, num_rows } + } + + pub fn from_degree(index: DegreeType, num_rows: DegreeType) -> Self { + Self { + index: index.try_into().unwrap(), + num_rows, + } + } +} + +impl Add for RowIndex +where + i64: TryFrom, + >::Error: std::fmt::Debug, +{ + type Output = RowIndex; + + fn add(self, rhs: T) -> RowIndex { + RowIndex { + index: self.index + i64::try_from(rhs).unwrap(), + num_rows: self.num_rows, + } + } +} + +impl Sub for RowIndex { + type Output = i64; + + fn sub(self, rhs: RowIndex) -> i64 { + assert_eq!(self.num_rows, rhs.num_rows); + let num_rows = i64::try_from(self.num_rows).unwrap(); + let lhs = i64::try_from(DegreeType::from(self)).unwrap(); + let rhs = i64::try_from(DegreeType::from(rhs)).unwrap(); + let diff = lhs - rhs; + if diff <= -num_rows / 2 { + diff + num_rows + } else if diff >= num_rows / 2 { + diff - num_rows + } else { + diff + } + } +} + +impl std::fmt::Display for RowIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.index) + } +} + #[derive(Clone, PartialEq, Debug)] pub enum CellValue { Known(T), @@ -165,7 +249,7 @@ impl<'a, T: FieldElement> RowFactory<'a, T> { } } - pub fn fresh_row(&self, row: DegreeType) -> Row<'a, T> { + pub fn fresh_row(&self, row: RowIndex) -> Row<'a, T> { WitnessColumnMap::from( self.global_range_constraints .witness_constraints @@ -173,7 +257,7 @@ impl<'a, T: FieldElement> RowFactory<'a, T> { .map(|(poly_id, range_constraint)| { let name = self.fixed_data.column_name(&poly_id); let value = match ( - self.fixed_data.external_witness(row, &poly_id), + self.fixed_data.external_witness(row.into(), &poly_id), range_constraint.as_ref(), ) { (Some(external_witness), _) => CellValue::Known(external_witness), @@ -202,14 +286,14 @@ impl From> for WitnessColumnMap { pub struct RowUpdater<'row, 'a, T: FieldElement> { current: &'row mut Row<'a, T>, next: &'row mut Row<'a, T>, - current_row_index: DegreeType, + current_row_index: RowIndex, } impl<'row, 'a, T: FieldElement> RowUpdater<'row, 'a, T> { pub fn new( current: &'row mut Row<'a, T>, next: &'row mut Row<'a, T>, - current_row_index: DegreeType, + current_row_index: RowIndex, ) -> Self { Self { current, @@ -247,7 +331,7 @@ impl<'row, 'a, T: FieldElement> RowUpdater<'row, 'a, T> { } } - fn row_number(&self, poly: &AlgebraicReference) -> DegreeType { + fn row_number(&self, poly: &AlgebraicReference) -> RowIndex { match poly.next { false => self.current_row_index, true => self.current_row_index + 1, @@ -268,7 +352,7 @@ pub enum UnknownStrategy { pub struct RowPair<'row, 'a, T: FieldElement> { pub current: &'row Row<'a, T>, pub next: Option<&'row Row<'a, T>>, - pub current_row_index: DegreeType, + pub current_row_index: RowIndex, fixed_data: &'a FixedData<'a, T>, unknown_strategy: UnknownStrategy, } @@ -277,7 +361,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> { pub fn new( current: &'row Row<'a, T>, next: &'row Row<'a, T>, - current_row_index: DegreeType, + current_row_index: RowIndex, fixed_data: &'a FixedData<'a, T>, unknown_strategy: UnknownStrategy, ) -> Self { @@ -293,7 +377,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> { /// Creates a new row pair from a single row, setting the next row to None. pub fn from_single_row( current: &'row Row<'a, T>, - current_row_index: DegreeType, + current_row_index: RowIndex, fixed_data: &'a FixedData<'a, T>, unknown_strategy: UnknownStrategy, ) -> Self { @@ -335,7 +419,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> { pub fn evaluate<'b>(&self, expr: &'b Expression) -> AffineResult<&'b AlgebraicReference, T> { ExpressionEvaluator::new(SymoblicWitnessEvaluator::new( self.fixed_data, - self.current_row_index, + self.current_row_index.into(), self, )) .evaluate(expr) @@ -343,7 +427,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> { /// Returns Ok(true) if the given row number references the "next" row, /// Ok(false) if it references the "current" row and Err if it is out of range. - pub fn is_row_number_next(&self, row_number: DegreeType) -> Result { + pub fn is_row_number_next(&self, row_number: RowIndex) -> Result { match row_number - self.current_row_index { 0 => Ok(false), 1 => Ok(true), diff --git a/executor/src/witgen/vm_processor.rs b/executor/src/witgen/vm_processor.rs index 2b94a63fa..7dc093bc1 100644 --- a/executor/src/witgen/vm_processor.rs +++ b/executor/src/witgen/vm_processor.rs @@ -15,7 +15,7 @@ use crate::witgen::IncompleteCause; use super::data_structures::finalizable_data::FinalizableData; use super::processor::{OuterQuery, Processor}; -use super::rows::{Row, RowFactory, UnknownStrategy}; +use super::rows::{Row, RowFactory, RowIndex, UnknownStrategy}; use super::{Constraints, EvalError, EvalValue, FixedData, MutableState, QueryCallback}; /// Maximal period checked during loop detection. @@ -64,7 +64,7 @@ pub struct VmProcessor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> { impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> VmProcessor<'a, 'b, 'c, T, Q> { pub fn new( - row_offset: DegreeType, + row_offset: RowIndex, fixed_data: &'a FixedData<'a, T>, identities: &[&'a Identity>], witnesses: &'c HashSet, @@ -86,7 +86,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> VmProcessor<'a, 'b, 'c, T ); VmProcessor { - row_offset, + row_offset: row_offset.into(), witnesses: witnesses.clone(), fixed_data, identities_with_next_ref: identities_with_next, @@ -238,7 +238,8 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> VmProcessor<'a, 'b, 'c, T if row_index == self.processor.len() as DegreeType - 1 { self.processor.set_row( self.processor.len(), - self.row_factory.fresh_row(row_index + 1), + self.row_factory + .fresh_row(RowIndex::from_degree(row_index, self.fixed_data.degree) + 1), ); } }