From c77a8196537ad6c956e213abc2c16bf4c388ad9a Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Wed, 11 Jun 2025 18:04:57 +0200 Subject: [PATCH] Added mat_znx_dft_mul_x_pow_minus_one --- backend/spqlios-arithmetic | 2 +- backend/src/mat_znx_dft_ops.rs | 214 +++++++++++++++++++++++++++++---- backend/src/vec_znx_dft_ops.rs | 96 +++++++++++++++ core/src/automorphism.rs | 4 +- core/src/gglwe_ciphertext.rs | 4 +- core/src/ggsw_ciphertext.rs | 8 +- core/src/keyswitch_key.rs | 4 +- 7 files changed, 297 insertions(+), 35 deletions(-) diff --git a/backend/spqlios-arithmetic b/backend/spqlios-arithmetic index 173b980..0ae9a7b 160000 --- a/backend/spqlios-arithmetic +++ b/backend/spqlios-arithmetic @@ -1 +1 @@ -Subproject commit 173b980c7b8a4f0523d04c2aed061c2e046e846c +Subproject commit 0ae9a7b5adf07ce0b1797562528dab8e28192238 diff --git a/backend/src/mat_znx_dft_ops.rs b/backend/src/mat_znx_dft_ops.rs index 9656dfb..9ed71a0 100644 --- a/backend/src/mat_znx_dft_ops.rs +++ b/backend/src/mat_znx_dft_ops.rs @@ -2,8 +2,8 @@ use crate::ffi::vec_znx_dft::vec_znx_dft_t; use crate::ffi::vmp; use crate::znx_base::{ZnxInfos, ZnxView, ZnxViewMut}; use crate::{ - Backend, FFT64, MatZnxDft, MatZnxDftOwned, MatZnxToMut, MatZnxToRef, Module, Scratch, VecZnxDft, VecZnxDftToMut, - VecZnxDftToRef, + Backend, FFT64, MatZnxDft, MatZnxDftOwned, MatZnxToMut, MatZnxToRef, Module, ScalarZnxAlloc, ScalarZnxDftAlloc, + ScalarZnxDftOps, Scratch, VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, VecZnxDftToMut, VecZnxDftToRef, VecZnxOps, ZnxZero, }; pub trait MatZnxDftAlloc { @@ -38,6 +38,8 @@ pub trait MatZnxDftScratch { b_cols_out: usize, b_size: usize, ) -> usize; + + fn mat_znx_dft_mul_x_pow_minus_one_scratch_space(&self, size: usize, cols_out: usize) -> usize; } /// This trait implements methods for vector matrix product, @@ -52,7 +54,7 @@ pub trait MatZnxDftOps { /// * `row_i`: the index of the row to prepare. /// /// The size of buf can be obtained with [MatZnxDftOps::vmp_prepare_tmp_bytes]. - fn vmp_prepare_row(&self, res: &mut R, res_row: usize, res_col_in: usize, a: &A) + fn mat_znx_dft_set_row(&self, res: &mut R, res_row: usize, res_col_in: usize, a: &A) where R: MatZnxToMut, A: VecZnxDftToRef; @@ -64,11 +66,22 @@ pub trait MatZnxDftOps { /// * `res`: the [VecZnxDft] to on which to extract the row of the [MatZnxDft]. /// * `a`: [MatZnxDft] on which the values are encoded. /// * `row_i`: the index of the row to extract. - fn vmp_extract_row(&self, res: &mut R, a: &A, a_row: usize, a_col_in: usize) + fn mat_znx_dft_get_row(&self, res: &mut R, a: &A, a_row: usize, a_col_in: usize) where R: VecZnxDftToMut, A: MatZnxToRef; + /// Multiplies A by (X^{k} - 1) and stores the result on R. + fn mat_znx_dft_mul_x_pow_minus_one(&self, k: i64, res: &mut R, a: &A, scratch: &mut Scratch) + where + R: MatZnxToMut, + A: MatZnxToRef; + + /// Multiplies A by (X^{k} - 1). + fn mat_znx_dft_mul_x_pow_minus_one_inplace(&self, k: i64, a: &mut A, scratch: &mut Scratch) + where + A: MatZnxToMut; + /// Applies the vector matrix product [VecZnxDft] x [MatZnxDft]. /// The size of `buf` is given by [MatZnxDftOps::vmp_apply_dft_to_dft_tmp_bytes]. /// @@ -149,10 +162,97 @@ impl MatZnxDftScratch for Module { ) as usize } } + + fn mat_znx_dft_mul_x_pow_minus_one_scratch_space(&self, size: usize, cols_out: usize) -> usize { + let xpm1_dft: usize = self.bytes_of_scalar_znx(1); + let xpm1: usize = self.bytes_of_scalar_znx_dft(1); + let tmp: usize = self.bytes_of_vec_znx_dft(cols_out, size); + xpm1_dft + (xpm1 | 2 * tmp) + } } impl MatZnxDftOps for Module { - fn vmp_prepare_row(&self, res: &mut R, res_row: usize, res_col_in: usize, a: &A) + fn mat_znx_dft_mul_x_pow_minus_one(&self, k: i64, res: &mut R, a: &A, scratch: &mut Scratch) + where + R: MatZnxToMut, + A: MatZnxToRef, + { + let mut res: MatZnxDft<&mut [u8], FFT64> = res.to_mut(); + let a: MatZnxDft<&[u8], FFT64> = a.to_ref(); + + #[cfg(debug_assertions)] + { + assert_eq!(res.n(), self.n()); + assert_eq!(a.n(), self.n()); + assert_eq!(res.rows(), a.rows()); + assert_eq!(res.cols_in(), a.cols_in()); + assert_eq!(res.cols_out(), a.cols_out()); + } + + let (mut xpm1_dft, scratch1) = scratch.tmp_scalar_znx_dft(self, 1); + + { + let (mut xpm1, _) = scratch1.tmp_scalar_znx(self, 1); + xpm1.data[0] = 1; + self.vec_znx_rotate_inplace(k, &mut xpm1, 0); + self.svp_prepare(&mut xpm1_dft, 0, &xpm1, 0); + } + + let (mut tmp_0, scratch2) = scratch1.tmp_vec_znx_dft(self, res.cols_out(), res.size()); + let (mut tmp_1, _) = scratch2.tmp_vec_znx_dft(self, res.cols_out(), res.size()); + + (0..res.rows()).for_each(|row_i| { + (0..res.cols_in()).for_each(|col_j| { + self.mat_znx_dft_get_row(&mut tmp_0, &a, row_i, col_j); + + (0..tmp_0.cols()).for_each(|i| { + self.svp_apply(&mut tmp_1, i, &xpm1_dft, 0, &tmp_0, i); + self.vec_znx_dft_sub_ab_inplace(&mut tmp_1, i, &tmp_0, i); + }); + + self.mat_znx_dft_set_row(&mut res, row_i, col_j, &tmp_1); + }); + }) + } + + fn mat_znx_dft_mul_x_pow_minus_one_inplace(&self, k: i64, a: &mut A, scratch: &mut Scratch) + where + A: MatZnxToMut, + { + let mut a: MatZnxDft<&mut [u8], FFT64> = a.to_mut(); + + #[cfg(debug_assertions)] + { + assert_eq!(a.n(), self.n()); + } + + let (mut xpm1_dft, scratch1) = scratch.tmp_scalar_znx_dft(self, 1); + + { + let (mut xpm1, _) = scratch1.tmp_scalar_znx(self, 1); + xpm1.data[0] = 1; + self.vec_znx_rotate_inplace(k, &mut xpm1, 0); + self.svp_prepare(&mut xpm1_dft, 0, &xpm1, 0); + } + + let (mut tmp_0, scratch2) = scratch1.tmp_vec_znx_dft(self, a.cols_out(), a.size()); + let (mut tmp_1, _) = scratch2.tmp_vec_znx_dft(self, a.cols_out(), a.size()); + + (0..a.rows()).for_each(|row_i| { + (0..a.cols_in()).for_each(|col_j| { + self.mat_znx_dft_get_row(&mut tmp_0, &a, row_i, col_j); + + (0..tmp_0.cols()).for_each(|i| { + self.svp_apply(&mut tmp_1, i, &xpm1_dft, 0, &tmp_0, i); + self.vec_znx_dft_sub_ab_inplace(&mut tmp_1, i, &tmp_0, i); + }); + + self.mat_znx_dft_set_row(&mut a, row_i, col_j, &tmp_1); + }); + }) + } + + fn mat_znx_dft_set_row(&self, res: &mut R, res_row: usize, res_col_in: usize, a: &A) where R: MatZnxToMut, A: VecZnxDftToRef, @@ -204,7 +304,7 @@ impl MatZnxDftOps for Module { } } - fn vmp_extract_row(&self, res: &mut R, a: &A, a_row: usize, a_col_in: usize) + fn mat_znx_dft_get_row(&self, res: &mut R, a: &A, a_row: usize, a_col_in: usize) where R: VecZnxDftToMut, A: MatZnxToRef, @@ -376,7 +476,7 @@ mod tests { use super::{MatZnxDftAlloc, MatZnxDftScratch}; #[test] - fn vmp_prepare_row() { + fn vmp_set_row() { let module: Module = Module::::new(16); let basek: usize = 8; let mat_rows: usize = 4; @@ -395,8 +495,8 @@ mod tests { a.fill_uniform(basek, col_out, mat_size, &mut source); module.vec_znx_dft(1, 0, &mut a_dft, col_out, &a, col_out); }); - module.vmp_prepare_row(&mut mat, row_i, col_in, &a_dft); - module.vmp_extract_row(&mut b_dft, &mat, row_i, col_in); + module.mat_znx_dft_set_row(&mut mat, row_i, col_in, &a_dft); + module.mat_znx_dft_get_row(&mut b_dft, &mat, row_i, col_in); assert_eq!(a_dft.raw(), b_dft.raw()); } } @@ -413,10 +513,10 @@ mod tests { let mat_size: usize = 6; let res_size: usize = a_size; - [1, 2].iter().for_each(|in_cols| { - [1, 2].iter().for_each(|out_cols| { - let a_cols: usize = *in_cols; - let res_cols: usize = *out_cols; + [1, 2].iter().for_each(|cols_in| { + [1, 2].iter().for_each(|cols_out| { + let a_cols: usize = *cols_in; + let res_cols: usize = *cols_out; let mat_rows: usize = a_size; let mat_cols_in: usize = a_cols; @@ -456,7 +556,7 @@ mod tests { module.vec_znx_dft(1, 0, &mut c_dft, col_out_i, &tmp, col_out_i); tmp.at_mut(col_out_i, row_i)[idx] = 0 as i64; }); - module.vmp_prepare_row(&mut mat_znx_dft, row_i, col_in_i, &c_dft); + module.mat_znx_dft_set_row(&mut mat_znx_dft, row_i, col_in_i, &c_dft); }); }); @@ -499,11 +599,11 @@ mod tests { let res_size: usize = a_size; let mut source: Source = Source::new([0u8; 32]); - [1, 2].iter().for_each(|in_cols| { - [1, 2].iter().for_each(|out_cols| { + [1, 2].iter().for_each(|cols_in| { + [1, 2].iter().for_each(|cols_out| { (0..res_size).for_each(|shift| { - let a_cols: usize = *in_cols; - let res_cols: usize = *out_cols; + let a_cols: usize = *cols_in; + let res_cols: usize = *cols_out; let mat_rows: usize = a_size; let mat_cols_in: usize = a_cols; @@ -543,7 +643,7 @@ mod tests { module.vec_znx_dft(1, 0, &mut c_dft, col_out_i, &tmp, col_out_i); tmp.at_mut(col_out_i, row_i)[idx] = 0 as i64; }); - module.vmp_prepare_row(&mut mat_znx_dft, row_i, col_in_i, &c_dft); + module.mat_znx_dft_set_row(&mut mat_znx_dft, row_i, col_in_i, &c_dft); }); }); @@ -601,13 +701,13 @@ mod tests { let mat_size: usize = 6; let res_size: usize = a_size; - [1, 2].iter().for_each(|in_cols| { - [1, 2].iter().for_each(|out_cols| { + [1, 2].iter().for_each(|cols_in| { + [1, 2].iter().for_each(|cols_out| { [1, 3, 6].iter().for_each(|digits| { let mut source: Source = Source::new([0u8; 32]); - let a_cols: usize = *in_cols; - let res_cols: usize = *out_cols; + let a_cols: usize = *cols_in; + let res_cols: usize = *cols_out; let mat_rows: usize = a_size; let mat_cols_in: usize = a_cols; @@ -652,7 +752,7 @@ mod tests { module.vec_znx_dft(1, 0, &mut c_dft, col_out_i, &tmp, col_out_i); tmp.at_mut(col_out_i, limb)[idx] = 0 as i64; }); - module.vmp_prepare_row(&mut mat_znx_dft, row_i, col_in_i, &c_dft); + module.mat_znx_dft_set_row(&mut mat_znx_dft, row_i, col_in_i, &c_dft); }); }); @@ -697,4 +797,70 @@ mod tests { }); }); } + + #[test] + fn mat_znx_dft_mul_x_pow_minus_one() { + let log_n: i32 = 5; + let n: usize = 1 << log_n; + + let module: Module = Module::::new(n); + let basek: usize = 8; + let rows: usize = 2; + let cols_in: usize = 2; + let cols_out: usize = 2; + let size: usize = 4; + + let mut scratch: ScratchOwned = ScratchOwned::new(module.mat_znx_dft_mul_x_pow_minus_one_scratch_space(size, cols_out)); + + let mut mat_want: MatZnxDft, FFT64> = module.new_mat_znx_dft(rows, cols_in, cols_out, size); + let mut mat_have: MatZnxDft, FFT64> = module.new_mat_znx_dft(rows, cols_in, cols_out, size); + + let mut tmp: VecZnx> = module.new_vec_znx(1, size); + let mut tmp_dft: VecZnxDft, FFT64> = module.new_vec_znx_dft(cols_out, size); + + let mut source: Source = Source::new([0u8; 32]); + + (0..mat_want.rows()).for_each(|row_i| { + (0..mat_want.cols_in()).for_each(|col_i| { + (0..cols_out).for_each(|j| { + tmp.fill_uniform(basek, 0, size, &mut source); + module.vec_znx_dft(1, 0, &mut tmp_dft, j, &tmp, 0); + }); + + module.mat_znx_dft_set_row(&mut mat_want, row_i, col_i, &tmp_dft); + }); + }); + + let k: i64 = 1; + + module.mat_znx_dft_mul_x_pow_minus_one(k, &mut mat_have, &mat_want, scratch.borrow()); + + let mut have: VecZnx> = module.new_vec_znx(cols_out, size); + let mut want: VecZnx> = module.new_vec_znx(cols_out, size); + let mut tmp_big: VecZnxBig, FFT64> = module.new_vec_znx_big(1, size); + + (0..mat_want.rows()).for_each(|row_i| { + (0..mat_want.cols_in()).for_each(|col_i| { + module.mat_znx_dft_get_row(&mut tmp_dft, &mat_want, row_i, col_i); + + (0..cols_out).for_each(|j| { + module.vec_znx_idft(&mut tmp_big, 0, &tmp_dft, j, scratch.borrow()); + // module.vec_znx_big_normalize(basek, &mut want, j, &tmp_big, 0, scratch.borrow()); + module.vec_znx_big_normalize(basek, &mut tmp, 0, &tmp_big, 0, scratch.borrow()); + module.vec_znx_rotate(k, &mut want, j, &tmp, 0); + module.vec_znx_sub_ab_inplace(&mut want, j, &tmp, 0); + module.vec_znx_normalize_inplace(basek, &mut want, j, scratch.borrow()); + }); + + module.mat_znx_dft_get_row(&mut tmp_dft, &mat_have, row_i, col_i); + + (0..cols_out).for_each(|j| { + module.vec_znx_idft(&mut tmp_big, 0, &tmp_dft, j, scratch.borrow()); + module.vec_znx_big_normalize(basek, &mut have, j, &tmp_big, 0, scratch.borrow()); + }); + + assert_eq!(have, want) + }); + }); + } } diff --git a/backend/src/vec_znx_dft_ops.rs b/backend/src/vec_znx_dft_ops.rs index 963de18..5892155 100644 --- a/backend/src/vec_znx_dft_ops.rs +++ b/backend/src/vec_znx_dft_ops.rs @@ -53,6 +53,22 @@ pub trait VecZnxDftOps { R: VecZnxDftToMut, A: VecZnxDftToRef; + fn vec_znx_dft_sub(&self, res: &mut R, res_col: usize, a: &A, a_col: usize, b: &D, b_col: usize) + where + R: VecZnxDftToMut, + A: VecZnxDftToRef, + D: VecZnxDftToRef; + + fn vec_znx_dft_sub_ab_inplace(&self, res: &mut R, res_col: usize, a: &A, a_col: usize) + where + R: VecZnxDftToMut, + A: VecZnxDftToRef; + + fn vec_znx_dft_sub_ba_inplace(&self, res: &mut R, res_col: usize, a: &A, a_col: usize) + where + R: VecZnxDftToMut, + A: VecZnxDftToRef; + fn vec_znx_dft_copy(&self, step: usize, offset: usize, res: &mut R, res_col: usize, a: &A, a_col: usize) where R: VecZnxDftToMut, @@ -150,6 +166,86 @@ impl VecZnxDftOps for Module { } } + fn vec_znx_dft_sub(&self, res: &mut R, res_col: usize, a: &A, a_col: usize, b: &D, b_col: usize) + where + R: VecZnxDftToMut, + A: VecZnxDftToRef, + D: VecZnxDftToRef, + { + let mut res_mut: VecZnxDft<&mut [u8], FFT64> = res.to_mut(); + let a_ref: VecZnxDft<&[u8], FFT64> = a.to_ref(); + let b_ref: VecZnxDft<&[u8], FFT64> = b.to_ref(); + + let min_size: usize = res_mut.size().min(a_ref.size()).min(b_ref.size()); + + unsafe { + (0..min_size).for_each(|j| { + vec_znx_dft::vec_dft_sub( + self.ptr, + res_mut.at_mut_ptr(res_col, j) as *mut vec_znx_dft::vec_znx_dft_t, + 1, + a_ref.at_ptr(a_col, j) as *const vec_znx_dft::vec_znx_dft_t, + 1, + b_ref.at_ptr(b_col, j) as *const vec_znx_dft::vec_znx_dft_t, + 1, + ); + }); + } + (min_size..res_mut.size()).for_each(|j| { + res_mut.zero_at(res_col, j); + }) + } + + fn vec_znx_dft_sub_ab_inplace(&self, res: &mut R, res_col: usize, a: &A, a_col: usize) + where + R: VecZnxDftToMut, + A: VecZnxDftToRef, + { + let mut res_mut: VecZnxDft<&mut [u8], FFT64> = res.to_mut(); + let a_ref: VecZnxDft<&[u8], FFT64> = a.to_ref(); + + let min_size: usize = res_mut.size().min(a_ref.size()); + + unsafe { + (0..min_size).for_each(|j| { + vec_znx_dft::vec_dft_sub( + self.ptr, + res_mut.at_mut_ptr(res_col, j) as *mut vec_znx_dft::vec_znx_dft_t, + 1, + res_mut.at_ptr(res_col, j) as *const vec_znx_dft::vec_znx_dft_t, + 1, + a_ref.at_ptr(a_col, j) as *const vec_znx_dft::vec_znx_dft_t, + 1, + ); + }); + } + } + + fn vec_znx_dft_sub_ba_inplace(&self, res: &mut R, res_col: usize, a: &A, a_col: usize) + where + R: VecZnxDftToMut, + A: VecZnxDftToRef, + { + let mut res_mut: VecZnxDft<&mut [u8], FFT64> = res.to_mut(); + let a_ref: VecZnxDft<&[u8], FFT64> = a.to_ref(); + + let min_size: usize = res_mut.size().min(a_ref.size()); + + unsafe { + (0..min_size).for_each(|j| { + vec_znx_dft::vec_dft_sub( + self.ptr, + res_mut.at_mut_ptr(res_col, j) as *mut vec_znx_dft::vec_znx_dft_t, + 1, + a_ref.at_ptr(a_col, j) as *const vec_znx_dft::vec_znx_dft_t, + 1, + res_mut.at_ptr(res_col, j) as *const vec_znx_dft::vec_znx_dft_t, + 1, + ); + }); + } + } + fn vec_znx_dft_copy(&self, step: usize, offset: usize, res: &mut R, res_col: usize, a: &A, a_col: usize) where R: VecZnxDftToMut, diff --git a/core/src/automorphism.rs b/core/src/automorphism.rs index 27ea44a..f91ba41 100644 --- a/core/src/automorphism.rs +++ b/core/src/automorphism.rs @@ -70,7 +70,7 @@ impl> GetRow for AutomorphismKey { col_j: usize, res: &mut GLWECiphertextFourier, ) { - module.vmp_extract_row(&mut res.data, &self.key.0.data, row_i, col_j); + module.mat_znx_dft_get_row(&mut res.data, &self.key.0.data, row_i, col_j); } } @@ -82,7 +82,7 @@ impl + AsRef<[u8]>> SetRow for AutomorphismKey { col_j: usize, a: &GLWECiphertextFourier, ) { - module.vmp_prepare_row(&mut self.key.0.data, row_i, col_j, &a.data); + module.mat_znx_dft_set_row(&mut self.key.0.data, row_i, col_j, &a.data); } } diff --git a/core/src/gglwe_ciphertext.rs b/core/src/gglwe_ciphertext.rs index 22d6749..e9f9684 100644 --- a/core/src/gglwe_ciphertext.rs +++ b/core/src/gglwe_ciphertext.rs @@ -219,7 +219,7 @@ impl> GetRow for GGLWECiphertext { col_j: usize, res: &mut GLWECiphertextFourier, ) { - module.vmp_extract_row(&mut res.data, &self.data, row_i, col_j); + module.mat_znx_dft_get_row(&mut res.data, &self.data, row_i, col_j); } } @@ -231,6 +231,6 @@ impl + AsRef<[u8]>> SetRow for GGLWECiphertext { col_j: usize, a: &GLWECiphertextFourier, ) { - module.vmp_prepare_row(&mut self.data, row_i, col_j, &a.data); + module.mat_znx_dft_set_row(&mut self.data, row_i, col_j, &a.data); } } diff --git a/core/src/ggsw_ciphertext.rs b/core/src/ggsw_ciphertext.rs index 82e0e81..ff1f1e7 100644 --- a/core/src/ggsw_ciphertext.rs +++ b/core/src/ggsw_ciphertext.rs @@ -429,7 +429,7 @@ impl + AsRef<[u8]>> GGSWCiphertext { module.vec_znx_dft(1, 0, &mut ci_dft, col_i, &tmp_res.data, col_i); }); - module.vmp_prepare_row(&mut self.data, row_i, 0, &ci_dft); + module.mat_znx_dft_set_row(&mut self.data, row_i, 0, &ci_dft); // Generates // @@ -525,7 +525,7 @@ impl + AsRef<[u8]>> GGSWCiphertext { module.vec_znx_dft(1, 0, &mut ci_dft, col_i, &tmp_res.data, col_i); }); - module.vmp_prepare_row(&mut self.data, row_i, 0, &ci_dft); + module.mat_znx_dft_set_row(&mut self.data, row_i, 0, &ci_dft); // Generates // @@ -688,7 +688,7 @@ impl> GetRow for GGSWCiphertext { col_j: usize, res: &mut GLWECiphertextFourier, ) { - module.vmp_extract_row(&mut res.data, &self.data, row_i, col_j); + module.mat_znx_dft_get_row(&mut res.data, &self.data, row_i, col_j); } } @@ -700,6 +700,6 @@ impl + AsRef<[u8]>> SetRow for GGSWCiphertext, ) { - module.vmp_prepare_row(&mut self.data, row_i, col_j, &a.data); + module.mat_znx_dft_set_row(&mut self.data, row_i, col_j, &a.data); } } diff --git a/core/src/keyswitch_key.rs b/core/src/keyswitch_key.rs index 56d42b4..fd4da76 100644 --- a/core/src/keyswitch_key.rs +++ b/core/src/keyswitch_key.rs @@ -75,7 +75,7 @@ impl> GetRow for GLWESwitchingKey { col_j: usize, res: &mut GLWECiphertextFourier, ) { - module.vmp_extract_row(&mut res.data, &self.0.data, row_i, col_j); + module.mat_znx_dft_get_row(&mut res.data, &self.0.data, row_i, col_j); } } @@ -87,7 +87,7 @@ impl + AsRef<[u8]>> SetRow for GLWESwitchingKey col_j: usize, a: &GLWECiphertextFourier, ) { - module.vmp_prepare_row(&mut self.0.data, row_i, col_j, &a.data); + module.mat_znx_dft_set_row(&mut self.0.data, row_i, col_j, &a.data); } }