From f64f0ecd23311f7f9e5b1e226c2d7fa104a16de2 Mon Sep 17 00:00:00 2001 From: dante <45801863+alexander-camuto@users.noreply.github.com> Date: Wed, 24 Jul 2024 07:58:46 -0400 Subject: [PATCH] fix: instance order when using processed params (#829) --- .github/workflows/rust.yml | 2 + benches/relu.rs | 3 +- examples/conv2d_mnist/main.rs | 42 +- examples/mlp_4d_einsum.rs | 41 +- src/circuit/ops/chip.rs | 4 +- src/circuit/ops/errors.rs | 6 +- src/circuit/ops/hybrid.rs | 10 +- src/circuit/ops/layouts.rs | 788 ++++++++++++++++++---------------- src/circuit/ops/lookup.rs | 14 +- src/circuit/ops/mod.rs | 19 +- src/circuit/ops/poly.rs | 2 +- src/circuit/ops/region.rs | 213 ++++----- src/circuit/table.rs | 45 +- src/execute.rs | 26 +- src/fieldutils.rs | 62 +-- src/graph/input.rs | 6 +- src/graph/mod.rs | 48 ++- src/graph/model.rs | 27 +- src/graph/utilities.rs | 15 +- src/lib.rs | 3 +- src/python.rs | 10 +- src/tensor/mod.rs | 489 ++++++--------------- src/tensor/ops.rs | 607 ++++++++++++++------------ src/tensor/val.rs | 132 +++--- src/tensor/var.rs | 13 +- src/wasm.rs | 89 ++-- tests/integration_tests.rs | 23 +- tests/wasm/model.compiled | Bin 1681 -> 1721 bytes tests/wasm/pk.key | Bin 247299 -> 247299 bytes tests/wasm/vk.key | Bin 1287 -> 1287 bytes 30 files changed, 1325 insertions(+), 1414 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1775e6d8f..d27de4d0f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -260,6 +260,8 @@ jobs: run: cargo nextest run --release --verbose tests::mock_hashed_input_::t --test-threads 32 - name: hashed params run: cargo nextest run --release --verbose tests::mock_hashed_params_::t --test-threads 32 + - name: hashed params public inputs + run: cargo nextest run --release --verbose tests::mock_hashed_params_public_inputs_::t --test-threads 32 - name: hashed outputs run: cargo nextest run --release --verbose tests::mock_hashed_output_::t --test-threads 32 - name: hashed inputs + params + outputs diff --git a/benches/relu.rs b/benches/relu.rs index 061ac2e39..ed39735e1 100644 --- a/benches/relu.rs +++ b/benches/relu.rs @@ -2,6 +2,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Through use ezkl::circuit::region::RegionCtx; use ezkl::circuit::table::Range; use ezkl::circuit::{ops::lookup::LookupOp, BaseConfig as Config, CheckMode}; +use ezkl::fieldutils::IntegerRep; use ezkl::pfsys::create_proof_circuit; use ezkl::pfsys::TranscriptType; use ezkl::pfsys::{create_keys, srs::gen_srs}; @@ -84,7 +85,7 @@ fn runrelu(c: &mut Criterion) { }; let input: Tensor> = - Tensor::::from((0..len).map(|_| rng.gen_range(0..10))).into(); + Tensor::::from((0..len).map(|_| rng.gen_range(0..10))).into(); let circuit = NLCircuit { input: ValTensor::from(input.clone()), diff --git a/examples/conv2d_mnist/main.rs b/examples/conv2d_mnist/main.rs index 2825c1b59..25aa64ca3 100644 --- a/examples/conv2d_mnist/main.rs +++ b/examples/conv2d_mnist/main.rs @@ -2,8 +2,7 @@ use ezkl::circuit::region::RegionCtx; use ezkl::circuit::{ ops::lookup::LookupOp, ops::poly::PolyOp, BaseConfig as PolyConfig, CheckMode, }; -use ezkl::fieldutils; -use ezkl::fieldutils::i32_to_felt; +use ezkl::fieldutils::{self, integer_rep_to_felt, IntegerRep}; use ezkl::tensor::*; use halo2_proofs::dev::MockProver; use halo2_proofs::poly::commitment::Params; @@ -42,8 +41,8 @@ const NUM_INNER_COLS: usize = 1; struct Config< const LEN: usize, //LEN = CHOUT x OH x OW flattened //not supported yet in rust stable const CLASSES: usize, - const LOOKUP_MIN: i64, - const LOOKUP_MAX: i64, + const LOOKUP_MIN: IntegerRep, + const LOOKUP_MAX: IntegerRep, // Convolution const KERNEL_HEIGHT: usize, const KERNEL_WIDTH: usize, @@ -66,8 +65,8 @@ struct Config< struct MyCircuit< const LEN: usize, //LEN = CHOUT x OH x OW flattened const CLASSES: usize, - const LOOKUP_MIN: i64, - const LOOKUP_MAX: i64, + const LOOKUP_MIN: IntegerRep, + const LOOKUP_MAX: IntegerRep, // Convolution const KERNEL_HEIGHT: usize, const KERNEL_WIDTH: usize, @@ -90,8 +89,8 @@ struct MyCircuit< impl< const LEN: usize, const CLASSES: usize, - const LOOKUP_MIN: i64, - const LOOKUP_MAX: i64, + const LOOKUP_MIN: IntegerRep, + const LOOKUP_MAX: IntegerRep, // Convolution const KERNEL_HEIGHT: usize, const KERNEL_WIDTH: usize, @@ -316,7 +315,11 @@ pub fn runconv() { .test_set_length(10_000) .finalize(); - let mut train_data = Tensor::from(trn_img.iter().map(|x| i32_to_felt::(*x as i32 / 16))); + let mut train_data = Tensor::from( + trn_img + .iter() + .map(|x| integer_rep_to_felt::(*x as IntegerRep / 16)), + ); train_data.reshape(&[50_000, 28, 28]).unwrap(); let mut train_labels = Tensor::from(trn_lbl.iter().map(|x| *x as f32)); @@ -344,8 +347,8 @@ pub fn runconv() { .map(|fl| { let dx = fl * 32_f32; let rounded = dx.round(); - let integral: i32 = unsafe { rounded.to_int_unchecked() }; - fieldutils::i32_to_felt(integral) + let integral: IntegerRep = unsafe { rounded.to_int_unchecked() }; + fieldutils::integer_rep_to_felt(integral) }), ); @@ -356,7 +359,8 @@ pub fn runconv() { let l0_kernels = l0_kernels.try_into().unwrap(); - let mut l0_bias = Tensor::::from((0..OUT_CHANNELS).map(|_| fieldutils::i32_to_felt(0))); + let mut l0_bias = + Tensor::::from((0..OUT_CHANNELS).map(|_| fieldutils::integer_rep_to_felt(0))); l0_bias.set_visibility(&ezkl::graph::Visibility::Private); let l0_bias = l0_bias.try_into().unwrap(); @@ -364,8 +368,8 @@ pub fn runconv() { let mut l2_biases = Tensor::::from(myparams.biases.into_iter().map(|fl| { let dx = fl * 32_f32; let rounded = dx.round(); - let integral: i32 = unsafe { rounded.to_int_unchecked() }; - fieldutils::i32_to_felt(integral) + let integral: IntegerRep = unsafe { rounded.to_int_unchecked() }; + fieldutils::integer_rep_to_felt(integral) })); l2_biases.set_visibility(&ezkl::graph::Visibility::Private); l2_biases.reshape(&[l2_biases.len(), 1]).unwrap(); @@ -375,8 +379,8 @@ pub fn runconv() { let mut l2_weights = Tensor::::from(myparams.weights.into_iter().flatten().map(|fl| { let dx = fl * 32_f32; let rounded = dx.round(); - let integral: i32 = unsafe { rounded.to_int_unchecked() }; - fieldutils::i32_to_felt(integral) + let integral: IntegerRep = unsafe { rounded.to_int_unchecked() }; + fieldutils::integer_rep_to_felt(integral) })); l2_weights.set_visibility(&ezkl::graph::Visibility::Private); l2_weights.reshape(&[CLASSES, LEN]).unwrap(); @@ -402,13 +406,13 @@ pub fn runconv() { l2_params: [l2_weights, l2_biases], }; - let public_input: Tensor = vec![ - -25124i32, -19304, -16668, -4399, -6209, -4548, -2317, -8349, -6117, -23461, + let public_input: Tensor = vec![ + -25124, -19304, -16668, -4399, -6209, -4548, -2317, -8349, -6117, -23461, ] .into_iter() .into(); - let pi_inner: Tensor = public_input.map(i32_to_felt::); + let pi_inner: Tensor = public_input.map(integer_rep_to_felt::); println!("MOCK PROVING"); let now = Instant::now(); diff --git a/examples/mlp_4d_einsum.rs b/examples/mlp_4d_einsum.rs index bec3ecb7a..30a6f31dc 100644 --- a/examples/mlp_4d_einsum.rs +++ b/examples/mlp_4d_einsum.rs @@ -2,7 +2,7 @@ use ezkl::circuit::region::RegionCtx; use ezkl::circuit::{ ops::lookup::LookupOp, ops::poly::PolyOp, BaseConfig as PolyConfig, CheckMode, }; -use ezkl::fieldutils::i32_to_felt; +use ezkl::fieldutils::{integer_rep_to_felt, IntegerRep}; use ezkl::tensor::*; use halo2_proofs::dev::MockProver; use halo2_proofs::{ @@ -23,8 +23,8 @@ struct MyConfig { #[derive(Clone)] struct MyCircuit< const LEN: usize, //LEN = CHOUT x OH x OW flattened - const LOOKUP_MIN: i64, - const LOOKUP_MAX: i64, + const LOOKUP_MIN: IntegerRep, + const LOOKUP_MAX: IntegerRep, > { // Given the stateless MyConfig type information, a DNN trace is determined by its input and the parameters of its layers. // Computing the trace still requires a forward pass. The intermediate activations are stored only by the layouter. @@ -34,7 +34,7 @@ struct MyCircuit< _marker: PhantomData, } -impl Circuit +impl Circuit for MyCircuit { type Config = MyConfig; @@ -215,33 +215,33 @@ pub fn runmlp() { #[cfg(not(target_arch = "wasm32"))] env_logger::init(); // parameters - let mut l0_kernel: Tensor = Tensor::::new( + let mut l0_kernel: Tensor = Tensor::::new( Some(&[10, 0, 0, -1, 0, 10, 1, 0, 0, 1, 10, 0, 1, 0, 0, 10]), &[4, 4], ) .unwrap() - .map(i32_to_felt); + .map(integer_rep_to_felt); l0_kernel.set_visibility(&ezkl::graph::Visibility::Private); - let mut l0_bias: Tensor = Tensor::::new(Some(&[0, 0, 0, 1]), &[4, 1]) + let mut l0_bias: Tensor = Tensor::::new(Some(&[0, 0, 0, 1]), &[4, 1]) .unwrap() - .map(i32_to_felt); + .map(integer_rep_to_felt); l0_bias.set_visibility(&ezkl::graph::Visibility::Private); - let mut l2_kernel: Tensor = Tensor::::new( + let mut l2_kernel: Tensor = Tensor::::new( Some(&[0, 3, 10, -1, 0, 10, 1, 0, 0, 1, 0, 12, 1, -2, 32, 0]), &[4, 4], ) .unwrap() - .map(i32_to_felt); + .map(integer_rep_to_felt); l2_kernel.set_visibility(&ezkl::graph::Visibility::Private); // input data, with 1 padding to allow for bias - let input: Tensor> = Tensor::::new(Some(&[-30, -21, 11, 40]), &[4, 1]) + let input: Tensor> = Tensor::::new(Some(&[-30, -21, 11, 40]), &[4, 1]) .unwrap() .into(); - let mut l2_bias: Tensor = Tensor::::new(Some(&[0, 0, 0, 1]), &[4, 1]) + let mut l2_bias: Tensor = Tensor::::new(Some(&[0, 0, 0, 1]), &[4, 1]) .unwrap() - .map(i32_to_felt); + .map(integer_rep_to_felt); l2_bias.set_visibility(&ezkl::graph::Visibility::Private); let circuit = MyCircuit::<4, -8192, 8192> { @@ -251,12 +251,12 @@ pub fn runmlp() { _marker: PhantomData, }; - let public_input: Vec = unsafe { + let public_input: Vec = unsafe { vec![ - (531f32 / 128f32).round().to_int_unchecked::(), - (103f32 / 128f32).round().to_int_unchecked::(), - (4469f32 / 128f32).round().to_int_unchecked::(), - (2849f32 / 128f32).to_int_unchecked::(), + (531f32 / 128f32).round().to_int_unchecked::(), + (103f32 / 128f32).round().to_int_unchecked::(), + (4469f32 / 128f32).round().to_int_unchecked::(), + (2849f32 / 128f32).to_int_unchecked::(), ] }; @@ -265,7 +265,10 @@ pub fn runmlp() { let prover = MockProver::run( K as u32, &circuit, - vec![public_input.iter().map(|x| i32_to_felt::(*x)).collect()], + vec![public_input + .iter() + .map(|x| integer_rep_to_felt::(*x)) + .collect()], ) .unwrap(); prover.assert_satisfied(); diff --git a/src/circuit/ops/chip.rs b/src/circuit/ops/chip.rs index acefa1f5f..1316d5f98 100644 --- a/src/circuit/ops/chip.rs +++ b/src/circuit/ops/chip.rs @@ -22,7 +22,7 @@ use crate::{ table::{Range, RangeCheck, Table}, utils, }, - tensor::{IntoI64, Tensor, TensorType, ValTensor, VarTensor}, + tensor::{Tensor, TensorType, ValTensor, VarTensor}, }; use std::{collections::BTreeMap, marker::PhantomData}; @@ -327,7 +327,7 @@ pub struct BaseConfig { _marker: PhantomData, } -impl BaseConfig { +impl BaseConfig { /// Returns a new [BaseConfig] with no inputs, no selectors, and no tables. pub fn dummy(col_size: usize, num_inner_cols: usize) -> Self { Self { diff --git a/src/circuit/ops/errors.rs b/src/circuit/ops/errors.rs index ea97e0d05..e06534e18 100644 --- a/src/circuit/ops/errors.rs +++ b/src/circuit/ops/errors.rs @@ -1,6 +1,6 @@ use std::convert::Infallible; -use crate::tensor::TensorError; +use crate::{fieldutils::IntegerRep, tensor::TensorError}; use halo2_proofs::plonk::Error as PlonkError; use thiserror::Error; @@ -57,7 +57,7 @@ pub enum CircuitError { InvalidConversion(#[from] Infallible), /// Invalid min/max lookup range #[error("invalid min/max lookup range: min: {0}, max: {1}")] - InvalidMinMaxRange(i64, i64), + InvalidMinMaxRange(IntegerRep, IntegerRep), /// Missing product in einsum #[error("missing product in einsum")] MissingEinsumProduct, @@ -81,7 +81,7 @@ pub enum CircuitError { MissingSelectors(String), /// Table lookup error #[error("value ({0}) out of range: ({1}, {2})")] - TableOOR(i64, i64, i64), + TableOOR(IntegerRep, IntegerRep, IntegerRep), /// Loookup not configured #[error("lookup not configured: {0}")] LookupNotConfigured(String), diff --git a/src/circuit/ops/hybrid.rs b/src/circuit/ops/hybrid.rs index 7fd15f082..118ffeee6 100644 --- a/src/circuit/ops/hybrid.rs +++ b/src/circuit/ops/hybrid.rs @@ -1,7 +1,7 @@ use super::*; use crate::{ circuit::{layouts, utils, Tolerance}, - fieldutils::i64_to_felt, + fieldutils::integer_rep_to_felt, graph::multiplier_to_scale, tensor::{self, Tensor, TensorType, ValTensor}, }; @@ -71,7 +71,7 @@ pub enum HybridOp { }, } -impl Op for HybridOp { +impl Op for HybridOp { /// fn requires_homogenous_input_scales(&self) -> Vec { match self { @@ -184,8 +184,8 @@ impl Op config, region, values[..].try_into()?, - i64_to_felt(input_scale.0 as i64), - i64_to_felt(output_scale.0 as i64), + integer_rep_to_felt(input_scale.0 as i128), + integer_rep_to_felt(output_scale.0 as i128), )? } else { layouts::nonlinearity( @@ -209,7 +209,7 @@ impl Op config, region, values[..].try_into()?, - i64_to_felt(denom.0 as i64), + integer_rep_to_felt(denom.0 as i128), )? } else { layouts::nonlinearity( diff --git a/src/circuit/ops/layouts.rs b/src/circuit/ops/layouts.rs index 766365e75..9e7fa902b 100644 --- a/src/circuit/ops/layouts.rs +++ b/src/circuit/ops/layouts.rs @@ -13,12 +13,12 @@ use maybe_rayon::{ slice::ParallelSliceMut, }; -use self::tensor::{create_constant_tensor, create_zero_tensor, IntoI64}; +use self::tensor::{create_constant_tensor, create_zero_tensor}; use super::{chip::BaseConfig, region::RegionCtx}; use crate::{ circuit::{ops::base::BaseOp, utils}, - fieldutils::{felt_to_i64, i64_to_felt}, + fieldutils::{felt_to_integer_rep, integer_rep_to_felt, IntegerRep}, tensor::{ create_unit_tensor, get_broadcasted_shape, ops::{accumulated, add, mult, sub}, @@ -30,7 +30,7 @@ use super::*; use crate::circuit::ops::lookup::LookupOp; /// Same as div but splits the division into N parts -pub(crate) fn loop_div( +pub(crate) fn loop_div( config: &BaseConfig, region: &mut RegionCtx, value: &[ValTensor; 1], @@ -44,8 +44,10 @@ pub(crate) fn loop_div (2_i64.pow(F::S - 4)) { - divisor = i64_to_felt(felt_to_i64(divisor) / 2); + while felt_to_integer_rep(divisor) % 2 == 0 + && felt_to_integer_rep(divisor) > (2_i128.pow(F::S - 4)) + { + divisor = integer_rep_to_felt(felt_to_integer_rep(divisor) / 2); num_parts += 1; } @@ -54,9 +56,9 @@ pub(crate) fn loop_div( +pub(crate) fn div( config: &BaseConfig, region: &mut RegionCtx, value: &[ValTensor; 1], @@ -78,7 +80,7 @@ pub(crate) fn div = if is_assigned { - let input_evals = input.get_int_evals()?; - tensor::ops::nonlinearities::const_div(&input_evals.clone(), felt_to_i64(div) as f64) - .par_iter() - .map(|x| Value::known(i64_to_felt(*x))) - .collect::>>() - .into() + let input_evals = input.int_evals()?; + tensor::ops::nonlinearities::const_div( + &input_evals.clone(), + felt_to_integer_rep(div) as f64, + ) + .par_iter() + .map(|x| Value::known(integer_rep_to_felt(*x))) + .collect::>>() + .into() } else { Tensor::new( Some(&vec![Value::::unknown(); input.len()]), @@ -130,7 +135,7 @@ pub(crate) fn div( +pub(crate) fn recip( config: &BaseConfig, region: &mut RegionCtx, value: &[ValTensor; 1], @@ -140,14 +145,14 @@ pub(crate) fn recip 0 { - i64_to_felt(integer_input_scale * integer_output_scale / range_check_len) + integer_rep_to_felt(integer_input_scale * integer_output_scale / range_check_len) } else { F::ONE }; @@ -157,14 +162,14 @@ pub(crate) fn recip = if is_assigned { - let input_evals = input.get_int_evals()?; + let input_evals = input.int_evals()?; tensor::ops::nonlinearities::recip( &input_evals, - felt_to_i64(input_scale) as f64, - felt_to_i64(output_scale) as f64, + felt_to_integer_rep(input_scale) as f64, + felt_to_integer_rep(output_scale) as f64, ) .par_iter() - .map(|x| Value::known(i64_to_felt(*x))) + .map(|x| Value::known(integer_rep_to_felt(*x))) .collect::>>() .into() } else { @@ -190,8 +195,8 @@ pub(crate) fn recip::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 2, 3, 0, 4, -1, 3, 1, 6]), /// &[1, 3, 3], /// ).unwrap()); -/// let y = ValTensor::from_i64_tensor(Tensor::::new( +/// let y = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 5, 10, -4, 2, -1, 2, 0, 1]), /// &[1, 3, 3], /// ).unwrap()); -/// assert_eq!(dot::(&dummy_config, &mut dummy_region, &[x, y]).unwrap().get_int_evals().unwrap()[0], 86); +/// assert_eq!(dot::(&dummy_config, &mut dummy_region, &[x, y]).unwrap().int_evals().unwrap()[0], 86); /// ``` -pub fn dot( +pub fn dot( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -358,179 +365,181 @@ pub fn dot( /// Computes the einstein sum of a set of tensors. /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::einsum; /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// /// // matmul case -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 3, 2, 1, 1, 1]), /// &[3, 2], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "ij,jk->ik").unwrap(); -/// let expected = Tensor::::new(Some(&[8, 9, 5, 5]), &[2, 2]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[8, 9, 5, 5]), &[2, 2]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// // element wise multiplication -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 2, 3, 4, 3, 4, 5]), /// &[3, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 1, 2, 3, 1, 2, 3]), /// &[3, 3], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "ij,ij->ij").unwrap(); -/// let expected = Tensor::::new(Some(&[1, 4, 9, 2, 6, 12, 3, 8, 15]), &[3, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1, 4, 9, 2, 6, 12, 3, 8, 15]), &[3, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// /// // dot product of A with the transpose of B. -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 2, 3, 4, 3, 4, 5]), /// &[3, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 1, 2, 3, 1, 2, 3]), /// &[3, 3], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "ik,jk->ij").unwrap(); -/// let expected = Tensor::::new(Some(&[14, 14, 14, 20, 20, 20, 26, 26, 26]), &[3, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[14, 14, 14, 20, 20, 20, 26, 26, 26]), &[3, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// // dot product -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 2, 3, 4, 3, 4, 5]), /// &[3, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 1, 2, 3, 1, 2, 3]), /// &[3, 3], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "ik,ik->i").unwrap(); -/// let expected = Tensor::::new(Some(&[14, 20, 26]), &[3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[14, 20, 26]), &[3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// /// // dot product -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3]), /// &[3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3]), /// &[3], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "i,i->").unwrap(); -/// let expected = Tensor::::new(Some(&[14]), &[1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[14]), &[1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// /// // wut ? -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 2, 3, 4, 3, 4, 5, 1, 2, 3, 2, 3, 4, 3, 4, 5]), /// &[3, 3, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[4, 5, 7, 8]), /// &[2, 2], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "anm,bm->ba").unwrap(); -/// let expected = Tensor::::new(Some(&[68, 80, 95, 113, 134, 158]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[68, 80, 95, 113, 134, 158]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// // wutttttt ? -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 2, 3, 4, 3, 4, 5, 1, 2, 3, 2, 3, 4, 3, 4, 5]), /// &[3, 3, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[4, 5, 7, 8]), /// &[2, 2], /// ).unwrap()); -/// let z = ValTensor::from_i64_tensor(Tensor::::new( +/// let z = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[4, 5, 7, 8, 9, 9]), /// &[2, 3], /// ).unwrap()); /// /// let result = einsum::(&dummy_config, &mut dummy_region, &[z, x, k], "bn,anm,bm->ba").unwrap(); -/// let expected = Tensor::::new(Some(&[390, 414, 534, 994, 1153, 1384]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[390, 414, 534, 994, 1153, 1384]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// /// // contraction with a single common axis -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 2, 3, 4, 3, 4, 5, 1, 2, 3, 2, 3, 4, 3, 4, 5]), /// &[3, 3, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[4, 5, 7, 8]), /// &[2, 2], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "abc,cd->").unwrap(); -/// let expected = Tensor::::new(Some(&[648]), &[1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[648]), &[1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// // contraction with no common axes (outer product) -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 2, 3, 4, 3, 4, 5, 1, 2, 3, 2, 3, 4, 3, 4, 5]), /// &[3, 3, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[4, 5, 7, 8]), /// &[2, 2], /// ).unwrap()); /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "abc,ed->").unwrap(); -/// let expected = Tensor::::new(Some(&[1296]), &[1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1296]), &[1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// // trivial axes mapping -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[4, 5, 7, 8]), /// &[2, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[4, 5]), /// &[2], /// ).unwrap()); /// /// let result = einsum::(&dummy_config, &mut dummy_region, &[x.clone(), k.clone()], "mk,k->m").unwrap(); -/// let expected = Tensor::::new(Some(&[41, 68]), &[2]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[41, 68]), &[2]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// let result = einsum::(&dummy_config, &mut dummy_region, &[x, k], "mk,k->mn").unwrap(); -/// let expected = Tensor::::new(Some(&[41, 68]), &[2, 1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[41, 68]), &[2, 1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[0, 0, 0, 3]), /// &[1, 4], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[213, 227, 74, 77]), /// &[4], /// ).unwrap()); /// /// let result = einsum::(&dummy_config, &mut dummy_region, &[x.clone(), k.clone()], "mk,k->ma").unwrap(); -/// let expected = Tensor::::new(Some(&[231]), &[1, 1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[231]), &[1, 1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// // subtle difference /// let result = einsum::(&dummy_config, &mut dummy_region, &[x.clone(), k.clone()], "mk,n->ma").unwrap(); -/// let expected = Tensor::::new(Some(&[1773]), &[1, 1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1773]), &[1, 1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// ``` /// -pub fn einsum( +pub fn einsum( config: &BaseConfig, region: &mut RegionCtx, inputs: &[ValTensor], @@ -747,7 +756,7 @@ pub fn einsum( +fn _sort_ascending( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -758,11 +767,11 @@ fn _sort_ascending>>() } else { Tensor::new( @@ -791,7 +800,7 @@ fn _sort_ascending( +fn _select_topk( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -809,27 +818,29 @@ fn _select_topk::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2,3], /// ).unwrap()); /// let result = topk_axes::(&dummy_config, &mut dummy_region, &[x], 2, 1, true).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[15, 2, 1, 1]), /// &[2,2], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn topk_axes( +pub fn topk_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -849,7 +860,7 @@ pub fn topk_axes( +fn select( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -867,7 +878,7 @@ fn select( let output: ValTensor = if is_assigned && region.witness_gen() { let felt_evals = input.get_felt_evals()?; index - .get_int_evals()? + .int_evals()? .par_iter() .map(|x| Value::known(felt_evals.get(&[*x as usize]))) .collect::>>() @@ -888,7 +899,7 @@ fn select( Ok(assigned_output) } -fn one_hot( +fn one_hot( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -902,10 +913,10 @@ fn one_hot( let is_assigned = !input.any_unknowns()?; let output: ValTensor = if is_assigned { - let int_evals = input.get_int_evals()?; + let int_evals = input.int_evals()?; let res = tensor::ops::one_hot(&int_evals, num_classes, 1)?; res.par_iter() - .map(|x| Value::known(i64_to_felt(*x))) + .map(|x| Value::known(integer_rep_to_felt(*x))) .collect::>() } else { Tensor::new( @@ -940,9 +951,7 @@ fn one_hot( } /// Dynamic lookup -pub(crate) fn dynamic_lookup< - F: PrimeField + TensorType + PartialOrd + std::hash::Hash + IntoI64, ->( +pub(crate) fn dynamic_lookup( config: &BaseConfig, region: &mut RegionCtx, lookups: &[ValTensor; 2], @@ -1038,7 +1047,7 @@ pub(crate) fn dynamic_lookup< } /// Shuffle arg -pub(crate) fn shuffles( +pub(crate) fn shuffles( config: &BaseConfig, region: &mut RegionCtx, input: &[ValTensor; 1], @@ -1104,7 +1113,7 @@ pub(crate) fn shuffles( +pub(crate) fn one_hot_axis( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -1157,7 +1166,7 @@ pub(crate) fn one_hot_axis( +pub(crate) fn gather( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -1185,9 +1194,7 @@ pub(crate) fn gather( +pub(crate) fn gather_elements( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -1210,7 +1217,7 @@ pub(crate) fn gather_elements< } /// Gather accumulated layout -pub(crate) fn gather_nd( +pub(crate) fn gather_nd( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -1261,9 +1268,7 @@ pub(crate) fn gather_nd( +pub(crate) fn linearize_element_index( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -1368,9 +1373,7 @@ pub(crate) fn linearize_element_index< /// If indices_shape[-1] == r-b, since the rank of indices is q, indices can be thought of as N (q-b-1)-dimensional tensors containing 1-D tensors of dimension r-b, where N is an integer equals to the product of 1 and all the elements in the batch dimensions of the indices_shape. /// Let us think of each such r-b ranked tensor as indices_slice. Each scalar value corresponding to data[0:b-1,indices_slice] is filled into the corresponding location of the (q-b-1)-dimensional tensor to form the output tensor (Example 1 below) /// If indices_shape[-1] < r-b, since the rank of indices is q, indices can be thought of as N (q-b-1)-dimensional tensor containing 1-D tensors of dimension < r-b. Let us think of each such tensors as indices_slice. Each tensor slice corresponding to data[0:b-1, indices_slice , :] is filled into the corresponding location of the (q-b-1)-dimensional tensor to form the output tensor (Examples 2, 3, 4 and 5 below) -pub(crate) fn linearize_nd_index< - F: PrimeField + TensorType + PartialOrd + std::hash::Hash + IntoI64, ->( +pub(crate) fn linearize_nd_index( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -1496,9 +1499,9 @@ pub(crate) fn linearize_nd_index< // assert than res is less than the product of the dims if region.witness_gen() { assert!( - res.get_int_evals()? + res.int_evals()? .iter() - .all(|x| *x < dims.iter().product::() as i64), + .all(|x| *x < dims.iter().product::() as IntegerRep), "res is greater than the product of the dims {} (coord={}, index_dim_multiplier={}, res={})", dims.iter().product::(), index_val.show(), @@ -1520,7 +1523,7 @@ pub(crate) fn linearize_nd_index< } pub(crate) fn get_missing_set_elements< - F: PrimeField + TensorType + PartialOrd + std::hash::Hash + IntoI64, + F: PrimeField + TensorType + PartialOrd + std::hash::Hash, >( config: &BaseConfig, region: &mut RegionCtx, @@ -1534,8 +1537,8 @@ pub(crate) fn get_missing_set_elements< let is_assigned = !input.any_unknowns()? && !fullset.any_unknowns()?; let mut claimed_output: ValTensor = if is_assigned { - let input_evals = input.get_int_evals()?; - let mut fullset_evals = fullset.get_int_evals()?.into_iter().collect::>(); + let input_evals = input.int_evals()?; + let mut fullset_evals = fullset.int_evals()?.into_iter().collect::>(); // get the difference between the two vectors for eval in input_evals.iter() { @@ -1553,7 +1556,7 @@ pub(crate) fn get_missing_set_elements< fullset_evals .par_iter() - .map(|x| Value::known(i64_to_felt(*x))) + .map(|x| Value::known(integer_rep_to_felt(*x))) .collect::>>() .into() } else { @@ -1585,9 +1588,7 @@ pub(crate) fn get_missing_set_elements< } /// Gather accumulated layout -pub(crate) fn scatter_elements< - F: PrimeField + TensorType + PartialOrd + std::hash::Hash + IntoI64, ->( +pub(crate) fn scatter_elements( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 3], @@ -1605,14 +1606,14 @@ pub(crate) fn scatter_elements< let is_assigned = !input.any_unknowns()? && !index.any_unknowns()? && !src.any_unknowns()?; let claimed_output: ValTensor = if is_assigned && region.witness_gen() { - let input_inner = input.get_int_evals()?; - let index_inner = index.get_int_evals()?.map(|x| x as usize); - let src_inner = src.get_int_evals()?; + let input_inner = input.int_evals()?; + let index_inner = index.int_evals()?.map(|x| x as usize); + let src_inner = src.int_evals()?; let res = tensor::ops::scatter(&input_inner, &index_inner, &src_inner, dim)?; res.par_iter() - .map(|x| Value::known(i64_to_felt(*x))) + .map(|x| Value::known(integer_rep_to_felt(*x))) .collect::>>() .into() } else { @@ -1669,7 +1670,7 @@ pub(crate) fn scatter_elements< } /// Scatter Nd -pub(crate) fn scatter_nd( +pub(crate) fn scatter_nd( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 3], @@ -1684,14 +1685,14 @@ pub(crate) fn scatter_nd = if is_assigned && region.witness_gen() { - let input_inner = input.get_int_evals()?; - let index_inner = index.get_int_evals()?.map(|x| x as usize); - let src_inner = src.get_int_evals()?; + let input_inner = input.int_evals()?; + let index_inner = index.int_evals()?.map(|x| x as usize); + let src_inner = src.int_evals()?; let res = tensor::ops::scatter_nd(&input_inner, &index_inner, &src_inner)?; res.par_iter() - .map(|x| Value::known(i64_to_felt(*x))) + .map(|x| Value::known(integer_rep_to_felt(*x))) .collect::>>() .into() } else { @@ -1750,24 +1751,26 @@ pub(crate) fn scatter_nd::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = sum::(&dummy_config, &mut dummy_region, &[x]).unwrap(); /// let expected = 21; -/// assert_eq!(result.get_int_evals().unwrap()[0], expected); +/// assert_eq!(result.int_evals().unwrap()[0], expected); /// ``` -pub fn sum( +pub fn sum( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -1852,24 +1855,26 @@ pub fn sum( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::prod; /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = prod::(&dummy_config, &mut dummy_region, &[x]).unwrap(); /// let expected = 0; -/// assert_eq!(result.get_int_evals().unwrap()[0], expected); +/// assert_eq!(result.int_evals().unwrap()[0], expected); /// ``` -pub fn prod( +pub fn prod( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -1949,7 +1954,7 @@ pub fn prod } /// Axes wise op wrapper -fn axes_wise_op( +fn axes_wise_op( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2013,27 +2018,29 @@ fn axes_wise_op::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = prod_axes::(&dummy_config, &mut dummy_region, &[x], &[1]).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[60, 0]), /// &[2, 1], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn prod_axes( +pub fn prod_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2047,27 +2054,29 @@ pub fn prod_axes::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = sum_axes::(&dummy_config, &mut dummy_region, &[x], &[1]).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[19, 2]), /// &[2, 1], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn sum_axes( +pub fn sum_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2081,27 +2090,29 @@ pub fn sum_axes::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = argmax_axes::(&dummy_config, &mut dummy_region, &[x], 1).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[1, 0]), /// &[2, 1], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn argmax_axes( +pub fn argmax_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2121,26 +2132,28 @@ pub fn argmax_axes::new( +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = max_axes::(&dummy_config, &mut dummy_region, &[x], &[1]).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[15, 1]), /// &[2, 1], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn max_axes( +pub fn max_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2155,27 +2168,29 @@ pub fn max_axes::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = argmin_axes::(&dummy_config, &mut dummy_region, &[x], 1).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[0, 2]), /// &[2, 1], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn argmin_axes( +pub fn argmin_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2199,27 +2214,29 @@ pub fn argmin_axes::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = min_axes::(&dummy_config, &mut dummy_region, &[x], &[1]).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[2, 0]), /// &[2, 1], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn min_axes( +pub fn min_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2231,7 +2248,7 @@ pub fn min_axes( +pub(crate) fn pairwise( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2389,27 +2406,29 @@ pub(crate) fn pairwise::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = mean_of_squares_axes::(&dummy_config, &mut dummy_region, &[x], &[1]).unwrap(); -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[78, 1]), /// &[2, 1], /// ).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn mean_of_squares_axes( +pub fn mean_of_squares_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2425,7 +2444,7 @@ pub fn mean_of_squares_axes( +pub(crate) fn expand( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2444,28 +2463,30 @@ pub(crate) fn expand::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 12, 6, 4, 5, 6]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap()); /// let result = greater::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 1, 1, 0, 0, 0]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[0, 1, 1, 0, 0, 0]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn greater( +pub fn greater( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2494,29 +2515,31 @@ pub fn greater::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 12, 6, 4, 3, 2]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 4]), /// &[2, 3], /// ).unwrap()); /// let result = greater_equal::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 1, 1, 0, 0]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1, 1, 1, 1, 0, 0]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn greater_equal( +pub fn greater_equal( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2545,30 +2568,32 @@ pub fn greater_equal::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 5, 4, 5, 1]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap()); /// let result = less::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 1, 0, 0, 0, 1]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[0, 1, 0, 0, 0, 1]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` /// -pub fn less( +pub fn less( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2584,30 +2609,32 @@ pub fn less /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::less_equal; /// /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let a = ValTensor::from_i64_tensor(Tensor::::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 5, 4, 5, 1]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap()); /// let result = less_equal::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 0, 1, 1, 1]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1, 1, 0, 1, 1, 1]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` /// -pub fn less_equal( +pub fn less_equal( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2623,29 +2650,31 @@ pub fn less_equal::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 1, 1, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 1, 0, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = and::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 0, 1, 0, 1, 0]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1, 0, 1, 0, 1, 0]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn and( +pub fn and( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2665,29 +2694,31 @@ pub fn and( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::or; /// /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let a = ValTensor::from_i64_tensor(Tensor::::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 1, 1, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 1, 0, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = or::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 1, 1, 1, 0]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1, 1, 1, 1, 1, 0]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn or( +pub fn or( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2711,29 +2742,31 @@ pub fn or( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::equals; /// /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let a = ValTensor::from_i64_tensor(Tensor::::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 1, 1, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 1, 0, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = equals::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 0, 1, 0, 1, 1]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1, 0, 1, 0, 1, 1]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn equals( +pub fn equals( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2743,7 +2776,7 @@ pub fn equals( +pub(crate) fn equals_zero( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2783,30 +2816,32 @@ pub(crate) fn equals_zero::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 1, 1, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 1, 0, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = xor::(&dummy_config, &mut dummy_region, &[a,b]).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 1, 0, 1, 0, 0]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[0, 1, 0, 1, 0, 0]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` /// -pub fn xor( +pub fn xor( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -2837,25 +2872,27 @@ pub fn xor( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::not; /// /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 1, 1, 1, 1, 0]), /// &[2, 3], /// ).unwrap()); /// let result = not::(&dummy_config, &mut dummy_region, &[x]).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 1]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 1]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn not( +pub fn not( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2878,33 +2915,35 @@ pub fn not( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::iff; /// /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let mask = ValTensor::from_i64_tensor(Tensor::::new( +/// let mask = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 1, 0, 1, 0]), /// &[2, 3], /// ).unwrap()); -/// let a = ValTensor::from_i64_tensor(Tensor::::new( +/// let a = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[7, 8, 9, 10, 11, 12]), /// &[2, 3], /// ).unwrap()); /// let result = iff::(&dummy_config, &mut dummy_region, &[mask, a, b]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 8, 3, 10, 5, 12]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[1, 8, 3, 10, 5, 12]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn iff( +pub fn iff( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 3], @@ -2934,25 +2973,27 @@ pub fn iff( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::neg; /// /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap()); /// let result = neg::(&dummy_config, &mut dummy_region, &[x]).unwrap(); -/// let expected = Tensor::::new(Some(&[-2, -1, -2, -1, -1, -1]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[-2, -1, -2, -1, -1, -1]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn neg( +pub fn neg( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -2965,30 +3006,32 @@ pub fn neg( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::sumpool; /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// use ezkl::tensor::ValTensor; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 2, 3, 0, 4, -1, 3, 1, 6]), /// &[1, 1, 3, 3], /// ).unwrap()); /// let pooled = sumpool::(&dummy_config, &mut dummy_region, &[x.clone()], &vec![(0, 0); 2], &vec![1;2], &vec![2, 2], false).unwrap(); -/// let expected: Tensor = Tensor::::new(Some(&[11, 8, 8, 10]), &[1, 1, 2, 2]).unwrap(); -/// assert_eq!(pooled.get_int_evals().unwrap(), expected); +/// let expected: Tensor = Tensor::::new(Some(&[11, 8, 8, 10]), &[1, 1, 2, 2]).unwrap(); +/// assert_eq!(pooled.int_evals().unwrap(), expected); /// /// // This time with normalization /// let pooled = sumpool::(&dummy_config, &mut dummy_region, &[x], &vec![(0, 0); 2], &vec![1;2], &vec![2, 2], true).unwrap(); -/// let expected: Tensor = Tensor::::new(Some(&[3, 2, 2, 3]), &[1, 1, 2, 2]).unwrap(); -/// assert_eq!(pooled.get_int_evals().unwrap(), expected); +/// let expected: Tensor = Tensor::::new(Some(&[3, 2, 2, 3]), &[1, 1, 2, 2]).unwrap(); +/// assert_eq!(pooled.int_evals().unwrap(), expected); /// ``` -pub fn sumpool( +pub fn sumpool( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor], @@ -3045,26 +3088,28 @@ pub fn sumpool::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 2, 3, 0, 4, -1, 3, 1, 6]), /// &[1, 1, 3, 3], /// ).unwrap()); /// let pooled = max_pool::(&dummy_config, &mut dummy_region, &[x], &vec![(0, 0); 2], &vec![1;2], &vec![2;2]).unwrap(); -/// let expected: Tensor = Tensor::::new(Some(&[5, 4, 4, 6]), &[1, 1, 2, 2]).unwrap(); -/// assert_eq!(pooled.get_int_evals().unwrap(), expected); +/// let expected: Tensor = Tensor::::new(Some(&[5, 4, 4, 6]), &[1, 1, 2, 2]).unwrap(); +/// assert_eq!(pooled.int_evals().unwrap(), expected); /// /// ``` -pub fn max_pool( +pub fn max_pool( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3144,134 +3189,130 @@ pub fn max_pool::new(Some(&[6, 0, 12, 4, 0, 8, 0, 0, 3, 0, 0, 2]), &[1, 2, 2, 3]).unwrap()); -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let c = ValTensor::from_integer_rep_tensor(Tensor::::new(Some(&[6, 0, 12, 4, 0, 8, 0, 0, 3, 0, 0, 2]), &[1, 2, 2, 3]).unwrap()); +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); /// /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, c], &vec![(1, 1); 2], &vec![1;2], &vec![2;2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 32, 0, 32, 0, 6, 0, 12, 0, 4, 0, 8, 0, 4, 0, 8, 0, 0, 0, 3, 0, 0, 0, 2]), &[1, 2, 3, 4]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[0, 32, 0, 32, 0, 6, 0, 12, 0, 4, 0, 8, 0, 4, 0, 8, 0, 0, 0, 3, 0, 0, 0, 2]), &[1, 2, 3, 4]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[3, 1, 1, 5]), /// &[1, 1, 2, 2], /// ).unwrap()); /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, k], &vec![(0, 0); 2], &vec![0;2], &vec![1;2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[6, 14, 4, 2, 17, 21, 0, 1, 5]), &[1, 1, 3, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[6, 14, 4, 2, 17, 21, 0, 1, 5]), &[1, 1, 3, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[3, 1, 1, 5]), /// &[1, 1, 2, 2], /// ).unwrap()); /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, k], &vec![(1, 1); 2], &vec![0;2], &vec![1;2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[17]), &[1, 1, 1, 1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[17]), &[1, 1, 1, 1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[3, 1, 1, 5]), /// &[1, 1, 2, 2], /// ).unwrap()); /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, k], &vec![(1, 1); 2], &vec![0;2], &vec![2; 2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[10, 4, 0, 3]), &[1, 1, 2, 2]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[10, 4, 0, 3]), &[1, 1, 2, 2]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[3, 1, 1, 5]), /// &[1, 1, 2, 2], /// ).unwrap()); /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, k], &vec![(0, 0); 2], &vec![0;2], &vec![2; 2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[6, 2, 12, 4, 2, 10, 4, 20, 0, 0, 3, 1, 0, 0, 1, 5]), &[1, 1, 4, 4]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[6, 2, 12, 4, 2, 10, 4, 20, 0, 0, 3, 1, 0, 0, 1, 5]), &[1, 1, 4, 4]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[3, 2]), /// &[1, 1, 2, 1], /// ).unwrap()); /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, k], &vec![(1, 1); 2], &vec![0;2], &vec![2; 2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 0]), &[1, 1, 2, 1]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[0, 0]), &[1, 1, 2, 1]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[3, 2]), /// &[1, 1, 2, 1], /// ).unwrap()); /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, k], &vec![(0, 0); 2], &vec![0;2], &vec![2; 2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[6, 0, 12, 4, 0, 8, 0, 0, 3, 0, 0, 2]), &[1, 1, 4, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[6, 0, 12, 4, 0, 8, 0, 0, 3, 0, 0, 2]), &[1, 1, 4, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// -/// let c = ValTensor::from_i64_tensor(Tensor::::new(Some(&[6, 0, 12, 4, 0, 8, 0, 0, 3, 0, 0, 2]), &[1, 2, 2, 3]).unwrap()); -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let c = ValTensor::from_integer_rep_tensor(Tensor::::new(Some(&[6, 0, 12, 4, 0, 8, 0, 0, 3, 0, 0, 2]), &[1, 2, 2, 3]).unwrap()); +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 4, 0, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); /// /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, c], &vec![(1, 1); 2], &vec![0;2], &vec![2;2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 32, 0, 0, 6, 0, 0, 4, 0, 0, 0, 0]), &[1, 2, 2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let expected = Tensor::::new(Some(&[0, 32, 0, 0, 6, 0, 0, 4, 0, 0, 0, 0]), &[1, 2, 2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[3, 8, 0, 8, 4, 9, 8, 1, 8]), /// &[1, 1, 3, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 0, 4, 6]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1]), /// &[1], /// ).unwrap()); /// let result = deconv::(&dummy_config, &mut dummy_region, &[x, k, b], &vec![(1, 1); 2], &vec![0;2], &vec![1;2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[55, 58, 66, 69]), &[1, 1, 2, 2]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[55, 58, 66, 69]), &[1, 1, 2, 2]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// ``` pub fn deconv< - F: PrimeField - + TensorType - + PartialOrd - + std::hash::Hash - + std::marker::Send - + std::marker::Sync - + IntoI64, + F: PrimeField + TensorType + PartialOrd + std::hash::Hash + std::marker::Send + std::marker::Sync, >( config: &BaseConfig, region: &mut RegionCtx, @@ -3376,76 +3417,72 @@ pub fn deconv< /// // expected ouputs are taken from pytorch torch.nn.functional.conv2d /// /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::circuit::ops::layouts::conv; /// use ezkl::tensor::val::ValTensor; /// use halo2curves::bn256::Fr as Fp; /// use ezkl::circuit::region::RegionCtx; +/// use ezkl::circuit::region::RegionSettings; /// use ezkl::circuit::BaseConfig; /// /// let dummy_config = BaseConfig::dummy(12, 2); -/// let mut dummy_region = RegionCtx::new_dummy(0,2,true,true); +/// let mut dummy_region = RegionCtx::new_dummy(0,2,RegionSettings::all_true()); /// -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 2, 3, 0, 4, -1, 3, 1, 6]), /// &[1, 1, 3, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 1, 1, 1]), /// &[1, 1, 2, 2], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[0]), /// &[1], /// ).unwrap()); /// let result = conv::(&dummy_config, &mut dummy_region, &[x, k, b], &vec![(0, 0); 2], &vec![1;2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[31, 16, 8, 26]), &[1, 1, 2, 2]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[31, 16, 8, 26]), &[1, 1, 2, 2]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// // Now test single channel -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 2, 3, 0, 4, -1, 3, 1, 6, 5, 2, 3, 0, 4, -1, 3, 1, 6]), /// &[1, 2, 3, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 1, 1, 1, 5, 2, 1, 1]), /// &[2, 1, 2, 2], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 1]), /// &[2], /// ).unwrap()); /// /// let result = conv::(&dummy_config, &mut dummy_region, &[x, k, b], &vec![(0, 0); 2], &vec![1;2], 2).unwrap(); -/// let expected = Tensor::::new(Some(&[32, 17, 9, 27, 34, 20, 13, 26]), &[1, 2, 2, 2]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[32, 17, 9, 27, 34, 20, 13, 26]), &[1, 2, 2, 2]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// /// // Now test multi channel -/// let x = ValTensor::from_i64_tensor(Tensor::::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 2, 3, 0, 4, -1, 3, 1, 6, 5, 2, 3, 0, 4, -1, 3, 1, 6]), /// &[1, 2, 3, 3], /// ).unwrap()); -/// let k = ValTensor::from_i64_tensor(Tensor::::new( +/// let k = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[5, 1, 1, 1, 5, 2, 1, 1, 5, 3, 1, 1, 5, 4, 1, 1, 5, 1, 1, 1, 5, 2, 1, 1, 5, 3, 1, 1, 5, 4, 1, 1]), /// &[4, 2, 2, 2], /// ).unwrap()); -/// let b = ValTensor::from_i64_tensor(Tensor::::new( +/// let b = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[1, 1, 1, 1]), /// &[4], /// ).unwrap()); /// /// let result =conv(&dummy_config, &mut dummy_region, &[x, k, b], &vec![(0, 0); 2], &vec![1;2], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[65, 36, 21, 52, 73, 48, 37, 48, 65, 36, 21, 52, 73, 48, 37, 48]), &[1, 4, 2, 2]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[65, 36, 21, 52, 73, 48, 37, 48, 65, 36, 21, 52, 73, 48, 37, 48]), &[1, 4, 2, 2]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` /// pub fn conv< - F: PrimeField - + TensorType - + PartialOrd - + std::hash::Hash - + std::marker::Send - + std::marker::Sync - + IntoI64, + F: PrimeField + TensorType + PartialOrd + std::hash::Hash + std::marker::Send + std::marker::Sync, >( config: &BaseConfig, region: &mut RegionCtx, @@ -3624,7 +3661,7 @@ pub fn conv< } /// Power accumulated layout -pub(crate) fn pow( +pub(crate) fn pow( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3640,7 +3677,7 @@ pub(crate) fn pow( +pub(crate) fn rescale( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor], @@ -3662,7 +3699,7 @@ pub(crate) fn rescale( +pub(crate) fn reshape( values: &[ValTensor; 1], new_dims: &[usize], ) -> Result, CircuitError> { @@ -3672,7 +3709,7 @@ pub(crate) fn reshape( +pub(crate) fn move_axis( values: &[ValTensor; 1], source: usize, destination: usize, @@ -3683,7 +3720,7 @@ pub(crate) fn move_axis( +pub(crate) fn resize( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3697,7 +3734,7 @@ pub(crate) fn resize( +pub(crate) fn slice( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3720,7 +3757,7 @@ pub(crate) fn slice( +pub(crate) fn trilu( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3744,7 +3781,7 @@ pub(crate) fn trilu( +pub(crate) fn concat( values: &[ValTensor], axis: &usize, ) -> Result, CircuitError> { @@ -3756,7 +3793,7 @@ pub(crate) fn concat( +pub(crate) fn identity( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3771,9 +3808,7 @@ pub(crate) fn identity( +pub(crate) fn boolean_identity( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3809,7 +3844,7 @@ pub(crate) fn boolean_identity< } /// Downsample layout -pub(crate) fn downsample( +pub(crate) fn downsample( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3826,9 +3861,7 @@ pub(crate) fn downsample( +pub(crate) fn enforce_equality( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -3852,7 +3885,7 @@ pub(crate) fn enforce_equality< } /// layout for range check. -pub(crate) fn range_check( +pub(crate) fn range_check( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -3907,9 +3940,9 @@ pub(crate) fn range_check &range.1 { return Err(CircuitError::TableOOR(*v, range.0, range.1)); @@ -3931,7 +3964,7 @@ pub(crate) fn range_check( +pub(crate) fn nonlinearity( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -4028,22 +4061,22 @@ pub(crate) fn nonlinearity( +pub(crate) fn argmax( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], ) -> Result, CircuitError> { // this is safe because we later constrain it let argmax = values[0] - .get_int_evals()? + .int_evals()? .into_par_iter() .enumerate() // we value the first index in the case of a tie - .max_by_key(|(idx, value)| (*value, -(*idx as i64))) - .map(|(idx, _)| idx as i64); + .max_by_key(|(idx, value)| (*value, -(*idx as IntegerRep))) + .map(|(idx, _)| idx as IntegerRep); let argmax_val: ValTensor = match argmax { None => Tensor::new(Some(&[Value::::unknown()]), &[1])?.into(), - Some(i) => Tensor::new(Some(&[Value::known(i64_to_felt::(i))]), &[1])?.into(), + Some(i) => Tensor::new(Some(&[Value::known(integer_rep_to_felt::(i))]), &[1])?.into(), }; let assigned_argmax: ValTensor = @@ -4064,22 +4097,22 @@ pub(crate) fn argmax( +pub(crate) fn argmin( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], ) -> Result, CircuitError> { // this is safe because we later constrain it let argmin = values[0] - .get_int_evals()? + .int_evals()? .into_par_iter() .enumerate() // we value the first index in the case of a tie - .min_by_key(|(idx, value)| (*value, (*idx as i64))) - .map(|(idx, _)| idx as i64); + .min_by_key(|(idx, value)| (*value, (*idx as IntegerRep))) + .map(|(idx, _)| idx as IntegerRep); let argmin_val: ValTensor = match argmin { None => Tensor::new(Some(&[Value::::unknown()]), &[1])?.into(), - Some(i) => Tensor::new(Some(&[Value::known(i64_to_felt::(i))]), &[1])?.into(), + Some(i) => Tensor::new(Some(&[Value::known(integer_rep_to_felt::(i))]), &[1])?.into(), }; let assigned_argmin: ValTensor = @@ -4100,7 +4133,7 @@ pub(crate) fn argmin( +pub(crate) fn max( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -4112,7 +4145,7 @@ pub(crate) fn max( +pub(crate) fn min( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -4122,7 +4155,7 @@ pub(crate) fn min( +fn multi_dim_axes_op( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -4225,7 +4258,7 @@ fn multi_dim_axes_op( +pub(crate) fn softmax_axes( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -4246,7 +4279,7 @@ pub(crate) fn softmax_axes( +pub(crate) fn percent( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -4282,25 +4315,27 @@ pub(crate) fn percent::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[2, 2, 3, 2, 2, 0]), /// &[2, 3], /// ).unwrap()); /// let result = softmax::(&dummy_config, &mut dummy_region, &[x], 128.0.into(), (128.0 * 128.0).into()).unwrap(); /// // doubles the scale of the input -/// let expected = Tensor::::new(Some(&[2734, 2734, 2756, 2734, 2734, 2691]), &[2, 3]).unwrap(); -/// assert_eq!(result.get_int_evals().unwrap(), expected); +/// let expected = Tensor::::new(Some(&[2734, 2734, 2756, 2734, 2734, 2691]), &[2, 3]).unwrap(); +/// assert_eq!(result.int_evals().unwrap(), expected); /// ``` -pub fn softmax( +pub fn softmax( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 1], @@ -4328,26 +4363,28 @@ pub fn softmax::new( +/// let x = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[100, 200, 300, 400, 500, 600]), /// &[2, 3], /// ).unwrap()); -/// let y = ValTensor::from_i64_tensor(Tensor::::new( +/// let y = ValTensor::from_integer_rep_tensor(Tensor::::new( /// Some(&[101, 201, 302, 403, 503, 603]), /// &[2, 3], /// ).unwrap()); /// let result = range_check_percent::(&dummy_config, &mut dummy_region, &[x, y], 1024.0.into(), 1.0).unwrap(); /// ``` -pub fn range_check_percent( +pub fn range_check_percent( config: &BaseConfig, region: &mut RegionCtx, values: &[ValTensor; 2], @@ -4372,9 +4409,9 @@ pub fn range_check_percent Range { let range = (max_len - 1) as f64 / 2_f64; - let range = range as i64; + let range = range as IntegerRep; (-range, range) } /// Matches a [Op] to an operation in the `tensor::ops` module. - pub(crate) fn f( + pub(crate) fn f( &self, x: &[Tensor], ) -> Result, TensorError> { - let x = x[0].clone().map(|x| felt_to_i64(x)); + let x = x[0].clone().map(|x| felt_to_integer_rep(x)); let res = match &self { LookupOp::Abs => Ok(tensor::ops::abs(&x)?), LookupOp::Ceil { scale } => Ok(tensor::ops::nonlinearities::ceil(&x, scale.into())), @@ -227,13 +227,13 @@ impl LookupOp { } }?; - let output = res.map(|x| i64_to_felt(x)); + let output = res.map(|x| integer_rep_to_felt(x)); Ok(ForwardResult { output }) } } -impl Op for LookupOp { +impl Op for LookupOp { /// Returns a reference to the Any trait. fn as_any(&self) -> &dyn Any { self diff --git a/src/circuit/ops/mod.rs b/src/circuit/ops/mod.rs index ee1c162e3..47aee6529 100644 --- a/src/circuit/ops/mod.rs +++ b/src/circuit/ops/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{ graph::quantize_tensor, - tensor::{self, IntoI64, Tensor, TensorType, ValTensor}, + tensor::{self, Tensor, TensorType, ValTensor}, }; use halo2curves::ff::PrimeField; @@ -31,12 +31,12 @@ pub use errors::CircuitError; /// A struct representing the result of a forward pass. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct ForwardResult { +pub struct ForwardResult { pub(crate) output: Tensor, } /// A trait representing operations that can be represented as constraints in a circuit. -pub trait Op: +pub trait Op: std::fmt::Debug + Send + Sync + Any { /// Returns a string representation of the operation. @@ -75,7 +75,7 @@ pub trait Op &dyn Any; } -impl Clone for Box> { +impl Clone for Box> { fn clone(&self) -> Self { self.clone_dyn() } @@ -142,7 +142,7 @@ pub struct Input { pub datum_type: InputType, } -impl Op for Input { +impl Op for Input { fn out_scale(&self, _: Vec) -> Result { Ok(self.scale) } @@ -197,7 +197,7 @@ impl Op #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub struct Unknown; -impl Op for Unknown { +impl Op for Unknown { fn out_scale(&self, _: Vec) -> Result { Ok(0) } @@ -224,7 +224,7 @@ impl Op /// #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Constant { +pub struct Constant { /// pub quantized_values: Tensor, /// @@ -234,7 +234,7 @@ pub struct Constant>, } -impl Constant { +impl Constant { /// pub fn new(quantized_values: Tensor, raw_values: Tensor) -> Self { Self { @@ -267,8 +267,7 @@ impl< + PartialOrd + std::hash::Hash + Serialize - + for<'de> Deserialize<'de> - + IntoI64, + + for<'de> Deserialize<'de>, > Op for Constant { fn as_any(&self) -> &dyn Any { diff --git a/src/circuit/ops/poly.rs b/src/circuit/ops/poly.rs index 8603dc4ee..4bd31883d 100644 --- a/src/circuit/ops/poly.rs +++ b/src/circuit/ops/poly.rs @@ -100,7 +100,7 @@ impl< + std::hash::Hash + Serialize + for<'de> Deserialize<'de> - + IntoI64, + , > Op for PolyOp { /// Returns a reference to the Any trait. diff --git a/src/circuit/ops/region.rs b/src/circuit/ops/region.rs index e07dde54c..f420dac83 100644 --- a/src/circuit/ops/region.rs +++ b/src/circuit/ops/region.rs @@ -1,5 +1,6 @@ use crate::{ circuit::table::Range, + fieldutils::IntegerRep, tensor::{Tensor, TensorType, ValTensor, ValType, VarTensor}, }; #[cfg(not(target_arch = "wasm32"))] @@ -11,7 +12,6 @@ use halo2_proofs::{ use halo2curves::ff::PrimeField; use itertools::Itertools; use maybe_rayon::iter::ParallelExtend; -use portable_atomic::AtomicI64 as AtomicInt; use std::{ cell::RefCell, collections::{HashMap, HashSet}, @@ -86,6 +86,78 @@ impl ShuffleIndex { } } +#[derive(Debug, Clone)] +/// Some settings for a region to differentiate it across the different phases of proof generation +pub struct RegionSettings { + /// whether we are in witness generation mode + pub witness_gen: bool, + /// whether we should check range checks for validity + pub check_range: bool, +} + +#[allow(unsafe_code)] +unsafe impl Sync for RegionSettings {} +#[allow(unsafe_code)] +unsafe impl Send for RegionSettings {} + +impl RegionSettings { + /// Create a new region settings + pub fn new(witness_gen: bool, check_range: bool) -> RegionSettings { + RegionSettings { + witness_gen, + check_range, + } + } + + /// Create a new region settings with all true + pub fn all_true() -> RegionSettings { + RegionSettings { + witness_gen: true, + check_range: true, + } + } + + /// Create a new region settings with all false + pub fn all_false() -> RegionSettings { + RegionSettings { + witness_gen: false, + check_range: false, + } + } +} + +#[derive(Debug, Default, Clone)] +/// Region statistics +pub struct RegionStatistics { + /// the current maximum value of the lookup inputs + pub max_lookup_inputs: IntegerRep, + /// the current minimum value of the lookup inputs + pub min_lookup_inputs: IntegerRep, + /// the current maximum value of the range size + pub max_range_size: IntegerRep, + /// the current set of used lookups + pub used_lookups: HashSet, + /// the current set of used range checks + pub used_range_checks: HashSet, +} + +impl RegionStatistics { + /// update the statistics with another set of statistics + pub fn update(&mut self, other: &RegionStatistics) { + self.max_lookup_inputs = self.max_lookup_inputs.max(other.max_lookup_inputs); + self.min_lookup_inputs = self.min_lookup_inputs.min(other.min_lookup_inputs); + self.max_range_size = self.max_range_size.max(other.max_range_size); + self.used_lookups.extend(other.used_lookups.clone()); + self.used_range_checks + .extend(other.used_range_checks.clone()); + } +} + +#[allow(unsafe_code)] +unsafe impl Sync for RegionStatistics {} +#[allow(unsafe_code)] +unsafe impl Send for RegionStatistics {} + #[derive(Debug)] /// A context for a region pub struct RegionCtx<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> { @@ -95,13 +167,8 @@ pub struct RegionCtx<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Ha num_inner_cols: usize, dynamic_lookup_index: DynamicLookupIndex, shuffle_index: ShuffleIndex, - used_lookups: HashSet, - used_range_checks: HashSet, - max_lookup_inputs: i64, - min_lookup_inputs: i64, - max_range_size: i64, - witness_gen: bool, - check_lookup_range: bool, + statistics: RegionStatistics, + settings: RegionSettings, assigned_constants: ConstantsMap, } @@ -153,12 +220,17 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a /// pub fn witness_gen(&self) -> bool { - self.witness_gen + self.settings.witness_gen } /// - pub fn check_lookup_range(&self) -> bool { - self.check_lookup_range + pub fn check_range(&self) -> bool { + self.settings.check_range + } + + /// + pub fn statistics(&self) -> &RegionStatistics { + &self.statistics } /// Create a new region context @@ -173,13 +245,8 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a linear_coord, dynamic_lookup_index: DynamicLookupIndex::default(), shuffle_index: ShuffleIndex::default(), - used_lookups: HashSet::new(), - used_range_checks: HashSet::new(), - max_lookup_inputs: 0, - min_lookup_inputs: 0, - max_range_size: 0, - witness_gen: true, - check_lookup_range: true, + statistics: RegionStatistics::default(), + settings: RegionSettings::all_true(), assigned_constants: HashMap::new(), } } @@ -195,39 +262,12 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a new_self.assigned_constants = constants; new_self } - /// Create a new region context from a wrapped region - pub fn from_wrapped_region( - region: Option>>, - row: usize, - num_inner_cols: usize, - dynamic_lookup_index: DynamicLookupIndex, - shuffle_index: ShuffleIndex, - ) -> RegionCtx<'a, F> { - let linear_coord = row * num_inner_cols; - RegionCtx { - region, - num_inner_cols, - linear_coord, - row, - dynamic_lookup_index, - shuffle_index, - used_lookups: HashSet::new(), - used_range_checks: HashSet::new(), - max_lookup_inputs: 0, - min_lookup_inputs: 0, - max_range_size: 0, - witness_gen: false, - check_lookup_range: false, - assigned_constants: HashMap::new(), - } - } /// Create a new region context pub fn new_dummy( row: usize, num_inner_cols: usize, - witness_gen: bool, - check_lookup_range: bool, + settings: RegionSettings, ) -> RegionCtx<'a, F> { let region = None; let linear_coord = row * num_inner_cols; @@ -239,13 +279,8 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a row, dynamic_lookup_index: DynamicLookupIndex::default(), shuffle_index: ShuffleIndex::default(), - used_lookups: HashSet::new(), - used_range_checks: HashSet::new(), - max_lookup_inputs: 0, - min_lookup_inputs: 0, - max_range_size: 0, - witness_gen, - check_lookup_range, + statistics: RegionStatistics::default(), + settings, assigned_constants: HashMap::new(), } } @@ -255,8 +290,7 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a row: usize, linear_coord: usize, num_inner_cols: usize, - witness_gen: bool, - check_lookup_range: bool, + settings: RegionSettings, ) -> RegionCtx<'a, F> { let region = None; RegionCtx { @@ -266,13 +300,8 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a row, dynamic_lookup_index: DynamicLookupIndex::default(), shuffle_index: ShuffleIndex::default(), - used_lookups: HashSet::new(), - used_range_checks: HashSet::new(), - max_lookup_inputs: 0, - min_lookup_inputs: 0, - max_range_size: 0, - witness_gen, - check_lookup_range, + statistics: RegionStatistics::default(), + settings, assigned_constants: HashMap::new(), } } @@ -323,12 +352,9 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a ) -> Result<(), CircuitError> { let row = AtomicUsize::new(self.row()); let linear_coord = AtomicUsize::new(self.linear_coord()); - let max_lookup_inputs = AtomicInt::new(self.max_lookup_inputs()); - let min_lookup_inputs = AtomicInt::new(self.min_lookup_inputs()); - let lookups = Arc::new(Mutex::new(self.used_lookups.clone())); - let range_checks = Arc::new(Mutex::new(self.used_range_checks.clone())); - let dynamic_lookup_index = Arc::new(Mutex::new(self.dynamic_lookup_index.clone())); + let statistics = Arc::new(Mutex::new(self.statistics.clone())); let shuffle_index = Arc::new(Mutex::new(self.shuffle_index.clone())); + let dynamic_lookup_index = Arc::new(Mutex::new(self.dynamic_lookup_index.clone())); let constants = Arc::new(Mutex::new(self.assigned_constants.clone())); *output = output.par_enum_map(|idx, _| { @@ -342,8 +368,7 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a starting_offset, starting_linear_coord, self.num_inner_cols, - self.witness_gen, - self.check_lookup_range, + self.settings.clone(), ); let res = inner_loop_function(idx, &mut local_reg); // we update the offset and constants @@ -353,14 +378,9 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a Ordering::SeqCst, ); - max_lookup_inputs.fetch_max(local_reg.max_lookup_inputs(), Ordering::SeqCst); - min_lookup_inputs.fetch_min(local_reg.min_lookup_inputs(), Ordering::SeqCst); // update the lookups - let mut lookups = lookups.lock().unwrap(); - lookups.extend(local_reg.used_lookups()); - // update the range checks - let mut range_checks = range_checks.lock().unwrap(); - range_checks.extend(local_reg.used_range_checks()); + let mut statistics = statistics.lock().unwrap(); + statistics.update(local_reg.statistics()); // update the dynamic lookup index let mut dynamic_lookup_index = dynamic_lookup_index.lock().unwrap(); dynamic_lookup_index.update(&local_reg.dynamic_lookup_index); @@ -374,20 +394,11 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a res })?; self.linear_coord = linear_coord.into_inner(); - #[allow(trivial_numeric_casts)] - { - self.max_lookup_inputs = max_lookup_inputs.into_inner(); - self.min_lookup_inputs = min_lookup_inputs.into_inner(); - } self.row = row.into_inner(); - self.used_lookups = Arc::try_unwrap(lookups) + self.statistics = Arc::try_unwrap(statistics) .map_err(|e| CircuitError::GetLookupsError(format!("{:?}", e)))? .into_inner() .map_err(|e| CircuitError::GetLookupsError(format!("{:?}", e)))?; - self.used_range_checks = Arc::try_unwrap(range_checks) - .map_err(|e| CircuitError::GetRangeChecksError(format!("{:?}", e)))? - .into_inner() - .map_err(|e| CircuitError::GetRangeChecksError(format!("{:?}", e)))?; self.dynamic_lookup_index = Arc::try_unwrap(dynamic_lookup_index) .map_err(|e| CircuitError::GetDynamicLookupError(format!("{:?}", e)))? .into_inner() @@ -411,11 +422,11 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a ) -> Result<(), CircuitError> { let (mut min, mut max) = (0, 0); for i in inputs { - max = max.max(i.get_int_evals()?.into_iter().max().unwrap_or_default()); - min = min.min(i.get_int_evals()?.into_iter().min().unwrap_or_default()); + max = max.max(i.int_evals()?.into_iter().max().unwrap_or_default()); + min = min.min(i.int_evals()?.into_iter().min().unwrap_or_default()); } - self.max_lookup_inputs = self.max_lookup_inputs.max(max); - self.min_lookup_inputs = self.min_lookup_inputs.min(min); + self.statistics.max_lookup_inputs = self.statistics.max_lookup_inputs.max(max); + self.statistics.min_lookup_inputs = self.statistics.min_lookup_inputs.min(min); Ok(()) } @@ -427,7 +438,7 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a let range_size = (range.1 - range.0).abs(); - self.max_range_size = self.max_range_size.max(range_size); + self.statistics.max_range_size = self.statistics.max_range_size.max(range_size); Ok(()) } @@ -442,13 +453,13 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a lookup: LookupOp, inputs: &[ValTensor], ) -> Result<(), CircuitError> { - self.used_lookups.insert(lookup); + self.statistics.used_lookups.insert(lookup); self.update_max_min_lookup_inputs(inputs) } /// add used range check pub fn add_used_range_check(&mut self, range: Range) -> Result<(), CircuitError> { - self.used_range_checks.insert(range); + self.statistics.used_range_checks.insert(range); self.update_max_min_lookup_range(range) } @@ -489,27 +500,27 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a /// get used lookups pub fn used_lookups(&self) -> HashSet { - self.used_lookups.clone() + self.statistics.used_lookups.clone() } /// get used range checks pub fn used_range_checks(&self) -> HashSet { - self.used_range_checks.clone() + self.statistics.used_range_checks.clone() } /// max lookup inputs - pub fn max_lookup_inputs(&self) -> i64 { - self.max_lookup_inputs + pub fn max_lookup_inputs(&self) -> IntegerRep { + self.statistics.max_lookup_inputs } /// min lookup inputs - pub fn min_lookup_inputs(&self) -> i64 { - self.min_lookup_inputs + pub fn min_lookup_inputs(&self) -> IntegerRep { + self.statistics.min_lookup_inputs } /// max range check - pub fn max_range_size(&self) -> i64 { - self.max_range_size + pub fn max_range_size(&self) -> IntegerRep { + self.statistics.max_range_size } /// Assign a valtensor to a vartensor diff --git a/src/circuit/table.rs b/src/circuit/table.rs index 549540508..37e928125 100644 --- a/src/circuit/table.rs +++ b/src/circuit/table.rs @@ -11,17 +11,17 @@ use maybe_rayon::prelude::{IntoParallelIterator, ParallelIterator}; use crate::{ circuit::CircuitError, - fieldutils::i64_to_felt, - tensor::{IntoI64, Tensor, TensorType}, + fieldutils::{integer_rep_to_felt, IntegerRep}, + tensor::{Tensor, TensorType}, }; use crate::circuit::lookup::LookupOp; /// The range of the lookup table. -pub type Range = (i64, i64); +pub type Range = (IntegerRep, IntegerRep); /// The safety factor for the range of the lookup table. -pub const RANGE_MULTIPLIER: i64 = 2; +pub const RANGE_MULTIPLIER: IntegerRep = 2; /// The safety factor offset for the number of rows in the lookup table. pub const RESERVED_BLINDING_ROWS_PAD: usize = 3; @@ -96,21 +96,22 @@ pub struct Table { _marker: PhantomData, } -impl Table { +impl Table { /// get column index given input pub fn get_col_index(&self, input: F) -> F { // range is split up into chunks of size col_size, find the chunk that input is in - let chunk = - (crate::fieldutils::felt_to_i64(input) - self.range.0).abs() / (self.col_size as i64); + let chunk = (crate::fieldutils::felt_to_integer_rep(input) - self.range.0).abs() + / (self.col_size as IntegerRep); - i64_to_felt(chunk) + integer_rep_to_felt(chunk) } /// get first_element of column pub fn get_first_element(&self, chunk: usize) -> (F, F) { - let chunk = chunk as i64; + let chunk = chunk as IntegerRep; // we index from 1 to prevent soundness issues - let first_element = i64_to_felt(chunk * (self.col_size as i64) + self.range.0); + let first_element = + integer_rep_to_felt(chunk * (self.col_size as IntegerRep) + self.range.0); let op_f = self .nonlinearity .f(&[Tensor::from(vec![first_element].into_iter())]) @@ -130,12 +131,12 @@ impl Table< } /// -pub fn num_cols_required(range_len: i64, col_size: usize) -> usize { +pub fn num_cols_required(range_len: IntegerRep, col_size: usize) -> usize { // number of cols needed to store the range - (range_len / (col_size as i64)) as usize + 1 + (range_len / (col_size as IntegerRep)) as usize + 1 } -impl Table { +impl Table { /// Configures the table. pub fn configure( cs: &mut ConstraintSystem, @@ -202,7 +203,7 @@ impl Table< let smallest = self.range.0; let largest = self.range.1; - let inputs: Tensor = Tensor::from(smallest..=largest).map(|x| i64_to_felt(x)); + let inputs: Tensor = Tensor::from(smallest..=largest).map(|x| integer_rep_to_felt(x)); let evals = self.nonlinearity.f(&[inputs.clone()])?; let chunked_inputs = inputs.chunks(self.col_size); @@ -272,12 +273,12 @@ pub struct RangeCheck { _marker: PhantomData, } -impl RangeCheck { +impl RangeCheck { /// get first_element of column pub fn get_first_element(&self, chunk: usize) -> F { - let chunk = chunk as i64; + let chunk = chunk as IntegerRep; // we index from 1 to prevent soundness issues - i64_to_felt(chunk * (self.col_size as i64) + self.range.0) + integer_rep_to_felt(chunk * (self.col_size as IntegerRep) + self.range.0) } /// @@ -293,14 +294,14 @@ impl RangeC /// get column index given input pub fn get_col_index(&self, input: F) -> F { // range is split up into chunks of size col_size, find the chunk that input is in - let chunk = - (crate::fieldutils::felt_to_i64(input) - self.range.0).abs() / (self.col_size as i64); + let chunk = (crate::fieldutils::felt_to_integer_rep(input) - self.range.0).abs() + / (self.col_size as IntegerRep); - i64_to_felt(chunk) + integer_rep_to_felt(chunk) } } -impl RangeCheck { +impl RangeCheck { /// Configures the table. pub fn configure(cs: &mut ConstraintSystem, range: Range, logrows: usize) -> RangeCheck { log::debug!("range check range: {:?}", range); @@ -350,7 +351,7 @@ impl RangeC let smallest = self.range.0; let largest = self.range.1; - let inputs: Tensor = Tensor::from(smallest..=largest).map(|x| i64_to_felt(x)); + let inputs: Tensor = Tensor::from(smallest..=largest).map(|x| integer_rep_to_felt(x)); let chunked_inputs = inputs.chunks(self.col_size); self.is_assigned = true; diff --git a/src/execute.rs b/src/execute.rs index 894b5df13..662a3b5a8 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,3 +1,4 @@ +use crate::circuit::region::RegionSettings; use crate::circuit::CheckMode; #[cfg(not(target_arch = "wasm32"))] use crate::commands::CalibrationTarget; @@ -785,6 +786,8 @@ pub(crate) async fn gen_witness( let commitment: Commitments = settings.run_args.commitment.into(); + let region_settings = RegionSettings::all_true(); + let start_time = Instant::now(); let witness = if settings.module_requires_polycommit() { if get_srs_path(settings.run_args.logrows, srs_path.clone(), commitment).exists() { @@ -799,8 +802,7 @@ pub(crate) async fn gen_witness( &mut input, vk.as_ref(), Some(&srs), - true, - true, + region_settings, )? } Commitments::IPA => { @@ -814,8 +816,7 @@ pub(crate) async fn gen_witness( &mut input, vk.as_ref(), Some(&srs), - true, - true, + region_settings, )? } } @@ -825,12 +826,16 @@ pub(crate) async fn gen_witness( &mut input, vk.as_ref(), None, - true, - true, + region_settings, )? } } else { - circuit.forward::>(&mut input, vk.as_ref(), None, true, true)? + circuit.forward::>( + &mut input, + vk.as_ref(), + None, + region_settings, + )? }; // print each variable tuple (symbol, value) as symbol=value @@ -1023,6 +1028,8 @@ pub(crate) async fn calibrate( use std::collections::HashMap; use tabled::Table; + use crate::fieldutils::IntegerRep; + let data = GraphData::from_path(data)?; // load the pre-generated settings let settings = GraphSettings::load(&settings_path)?; @@ -1131,7 +1138,7 @@ pub(crate) async fn calibrate( param_scale, scale_rebase_multiplier, div_rebasing, - lookup_range: (i64::MIN, i64::MAX), + lookup_range: (IntegerRep::MIN, IntegerRep::MAX), ..settings.run_args.clone() }; @@ -1171,8 +1178,7 @@ pub(crate) async fn calibrate( &mut data.clone(), None, None, - true, - false, + RegionSettings::all_true(), ) .map_err(|e| format!("failed to forward: {}", e))?; diff --git a/src/fieldutils.rs b/src/fieldutils.rs index 5c8eaa827..4484fab27 100644 --- a/src/fieldutils.rs +++ b/src/fieldutils.rs @@ -2,17 +2,11 @@ use halo2_proofs::arithmetic::Field; /// Utilities for converting from Halo2 PrimeField types to integers (and vice-versa). use halo2curves::ff::PrimeField; -/// Converts an i32 to a PrimeField element. -pub fn i32_to_felt(x: i32) -> F { - if x >= 0 { - F::from(x as u64) - } else { - -F::from(x.unsigned_abs() as u64) - } -} +/// Integer representation of a PrimeField element. +pub type IntegerRep = i128; /// Converts an i64 to a PrimeField element. -pub fn i64_to_felt(x: i64) -> F { +pub fn integer_rep_to_felt(x: IntegerRep) -> F { if x >= 0 { F::from_u128(x as u128) } else { @@ -20,24 +14,9 @@ pub fn i64_to_felt(x: i64) -> F { } } -/// Converts a PrimeField element to an i32. -pub fn felt_to_i32(x: F) -> i32 { - if x > F::from(i32::MAX as u64) { - let rep = (-x).to_repr(); - let negtmp: &[u8] = rep.as_ref(); - let lower_32 = u32::from_le_bytes(negtmp[..4].try_into().unwrap()); - -(lower_32 as i32) - } else { - let rep = (x).to_repr(); - let tmp: &[u8] = rep.as_ref(); - let lower_32 = u32::from_le_bytes(tmp[..4].try_into().unwrap()); - lower_32 as i32 - } -} - /// Converts a PrimeField element to an f64. pub fn felt_to_f64(x: F) -> f64 { - if x > F::from_u128(i64::MAX as u128) { + if x > F::from_u128(IntegerRep::MAX as u128) { let rep = (-x).to_repr(); let negtmp: &[u8] = rep.as_ref(); let lower_128: u128 = u128::from_le_bytes(negtmp[..16].try_into().unwrap()); @@ -51,17 +30,17 @@ pub fn felt_to_f64(x: F) -> f64 { } /// Converts a PrimeField element to an i64. -pub fn felt_to_i64(x: F) -> i64 { - if x > F::from_u128(i64::MAX as u128) { +pub fn felt_to_integer_rep(x: F) -> IntegerRep { + if x > F::from_u128(IntegerRep::MAX as u128) { let rep = (-x).to_repr(); let negtmp: &[u8] = rep.as_ref(); let lower_128: u128 = u128::from_le_bytes(negtmp[..16].try_into().unwrap()); - -(lower_128 as i64) + -(lower_128 as IntegerRep) } else { let rep = (x).to_repr(); let tmp: &[u8] = rep.as_ref(); let lower_128: u128 = u128::from_le_bytes(tmp[..16].try_into().unwrap()); - lower_128 as i64 + lower_128 as IntegerRep } } @@ -73,33 +52,24 @@ mod test { #[test] fn test_conv() { - let res: F = i32_to_felt(-15i32); + let res: F = integer_rep_to_felt(-15); assert_eq!(res, -F::from(15)); - let res: F = i32_to_felt(2_i32.pow(17)); + let res: F = integer_rep_to_felt(2_i128.pow(17)); assert_eq!(res, F::from(131072)); - let res: F = i64_to_felt(-15i64); + let res: F = integer_rep_to_felt(-15); assert_eq!(res, -F::from(15)); - let res: F = i64_to_felt(2_i64.pow(17)); + let res: F = integer_rep_to_felt(2_i128.pow(17)); assert_eq!(res, F::from(131072)); } #[test] - fn felttoi32() { - for x in -(2i32.pow(16))..(2i32.pow(16)) { - let fieldx: F = i32_to_felt::(x); - let xf: i32 = felt_to_i32::(fieldx); - assert_eq!(x, xf); - } - } - - #[test] - fn felttoi64() { - for x in -(2i64.pow(20))..(2i64.pow(20)) { - let fieldx: F = i64_to_felt::(x); - let xf: i64 = felt_to_i64::(fieldx); + fn felttointegerrep() { + for x in -(2_i128.pow(16))..(2_i128.pow(16)) { + let fieldx: F = integer_rep_to_felt::(x); + let xf: i128 = felt_to_integer_rep::(fieldx); assert_eq!(x, xf); } } diff --git a/src/graph/input.rs b/src/graph/input.rs index f888392eb..e3f324c64 100644 --- a/src/graph/input.rs +++ b/src/graph/input.rs @@ -1,7 +1,7 @@ use super::errors::GraphError; use super::quantize_float; use crate::circuit::InputType; -use crate::fieldutils::i64_to_felt; +use crate::fieldutils::integer_rep_to_felt; #[cfg(not(target_arch = "wasm32"))] use crate::graph::postgres::Client; #[cfg(not(target_arch = "wasm32"))] @@ -128,7 +128,7 @@ impl FileSourceInner { /// Convert to a field element pub fn to_field(&self, scale: crate::Scale) -> Fp { match self { - FileSourceInner::Float(f) => i64_to_felt(quantize_float(f, 0.0, scale).unwrap()), + FileSourceInner::Float(f) => integer_rep_to_felt(quantize_float(f, 0.0, scale).unwrap()), FileSourceInner::Bool(f) => { if *f { Fp::one() @@ -150,7 +150,7 @@ impl FileSourceInner { 0.0 } } - FileSourceInner::Field(f) => crate::fieldutils::felt_to_i64(*f) as f64, + FileSourceInner::Field(f) => crate::fieldutils::felt_to_integer_rep(*f) as f64, } } } diff --git a/src/graph/mod.rs b/src/graph/mod.rs index 70f00d557..028f6cdcb 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -34,10 +34,10 @@ use self::input::{FileSource, GraphData}; use self::modules::{GraphModules, ModuleConfigs, ModuleForwardResult, ModuleSizes}; use crate::circuit::lookup::LookupOp; use crate::circuit::modules::ModulePlanner; -use crate::circuit::region::ConstantsMap; +use crate::circuit::region::{ConstantsMap, RegionSettings}; use crate::circuit::table::{num_cols_required, Range, Table, RESERVED_BLINDING_ROWS_PAD}; use crate::circuit::{CheckMode, InputType}; -use crate::fieldutils::felt_to_f64; +use crate::fieldutils::{felt_to_f64, IntegerRep}; use crate::pfsys::PrettyElements; use crate::tensor::{Tensor, ValTensor}; use crate::{RunArgs, EZKL_BUF_CAPACITY}; @@ -69,13 +69,14 @@ pub use vars::*; use crate::pfsys::field_to_string; /// The safety factor for the range of the lookup table. -pub const RANGE_MULTIPLIER: i64 = 2; +pub const RANGE_MULTIPLIER: IntegerRep = 2; /// The maximum number of columns in a lookup table. pub const MAX_NUM_LOOKUP_COLS: usize = 12; /// Max representation of a lookup table input -pub const MAX_LOOKUP_ABS: i64 = (MAX_NUM_LOOKUP_COLS as i64) * 2_i64.pow(MAX_PUBLIC_SRS); +pub const MAX_LOOKUP_ABS: IntegerRep = + (MAX_NUM_LOOKUP_COLS as IntegerRep) * 2_i128.pow(MAX_PUBLIC_SRS); #[cfg(not(target_arch = "wasm32"))] lazy_static! { @@ -126,11 +127,11 @@ pub struct GraphWitness { /// Any hashes of outputs generated during the forward pass pub processed_outputs: Option, /// max lookup input - pub max_lookup_inputs: i64, + pub max_lookup_inputs: IntegerRep, /// max lookup input - pub min_lookup_inputs: i64, + pub min_lookup_inputs: IntegerRep, /// max range check size - pub max_range_size: i64, + pub max_range_size: IntegerRep, } impl GraphWitness { @@ -804,18 +805,26 @@ impl GraphCircuit { // the ordering here is important, we want the inputs to come before the outputs // as they are configured in that order as Column let mut public_inputs: Vec = vec![]; - if self.settings().run_args.input_visibility.is_public() { - public_inputs.extend(self.graph_witness.inputs.clone().into_iter().flatten()) - } else if let Some(processed_inputs) = &data.processed_inputs { + + // we first process the inputs + if let Some(processed_inputs) = &data.processed_inputs { public_inputs.extend(processed_inputs.get_instances().into_iter().flatten()); } + // we then process the params if let Some(processed_params) = &data.processed_params { public_inputs.extend(processed_params.get_instances().into_iter().flatten()); } + // if the inputs are public, we add them to the public inputs AFTER the processed params as they are configured in that order as Column + if self.settings().run_args.input_visibility.is_public() { + public_inputs.extend(self.graph_witness.inputs.clone().into_iter().flatten()) + } + + // if the outputs are public, we add them to the public inputs if self.settings().run_args.output_visibility.is_public() { public_inputs.extend(self.graph_witness.outputs.clone().into_iter().flatten()); + // if the outputs are processed, we add the processed outputs to the public inputs } else if let Some(processed_outputs) = &data.processed_outputs { public_inputs.extend(processed_outputs.get_instances().into_iter().flatten()); } @@ -1036,12 +1045,12 @@ impl GraphCircuit { fn calc_safe_lookup_range(min_max_lookup: Range, lookup_safety_margin: f64) -> Range { ( - (lookup_safety_margin * min_max_lookup.0 as f64).floor() as i64, - (lookup_safety_margin * min_max_lookup.1 as f64).ceil() as i64, + (lookup_safety_margin * min_max_lookup.0 as f64).floor() as IntegerRep, + (lookup_safety_margin * min_max_lookup.1 as f64).ceil() as IntegerRep, ) } - fn calc_num_cols(range_len: i64, max_logrows: u32) -> usize { + fn calc_num_cols(range_len: IntegerRep, max_logrows: u32) -> usize { let max_col_size = Table::::cal_col_size(max_logrows as usize, RESERVED_BLINDING_ROWS); num_cols_required(range_len, max_col_size) } @@ -1049,7 +1058,7 @@ impl GraphCircuit { fn table_size_logrows( &self, safe_lookup_range: Range, - max_range_size: i64, + max_range_size: IntegerRep, ) -> Result { // pick the range with the largest absolute size safe_lookup_range or max_range_size let safe_range = std::cmp::max( @@ -1068,7 +1077,7 @@ impl GraphCircuit { pub fn calc_min_logrows( &mut self, min_max_lookup: Range, - max_range_size: i64, + max_range_size: IntegerRep, max_logrows: Option, lookup_safety_margin: f64, ) -> Result<(), GraphError> { @@ -1086,7 +1095,7 @@ impl GraphCircuit { (safe_lookup_range.1.saturating_sub(safe_lookup_range.0)).saturating_abs(); // check if has overflowed max lookup input - if lookup_size > (MAX_LOOKUP_ABS as f64 / lookup_safety_margin).floor() as i64 { + if lookup_size > (MAX_LOOKUP_ABS as f64 / lookup_safety_margin).floor() as IntegerRep { return Err(GraphError::LookupRangeTooLarge( lookup_size.unsigned_abs() as usize )); @@ -1166,7 +1175,7 @@ impl GraphCircuit { &self, k: u32, safe_lookup_range: Range, - max_range_size: i64, + max_range_size: IntegerRep, ) -> bool { // if num cols is too large then the extended k is too large if Self::calc_num_cols(safe_lookup_range.1 - safe_lookup_range.0, k) > MAX_NUM_LOOKUP_COLS @@ -1224,8 +1233,7 @@ impl GraphCircuit { inputs: &mut [Tensor], vk: Option<&VerifyingKey>, srs: Option<&Scheme::ParamsProver>, - witness_gen: bool, - check_lookup: bool, + region_settings: RegionSettings, ) -> Result { let original_inputs = inputs.to_vec(); @@ -1274,7 +1282,7 @@ impl GraphCircuit { let mut model_results = self.model() - .forward(inputs, &self.settings().run_args, witness_gen, check_lookup)?; + .forward(inputs, &self.settings().run_args, region_settings)?; if visibility.output.requires_processing() { let module_outlets = visibility.output.overwrites_inputs(); diff --git a/src/graph/model.rs b/src/graph/model.rs index afca18885..8fe485848 100644 --- a/src/graph/model.rs +++ b/src/graph/model.rs @@ -7,10 +7,12 @@ use super::GraphSettings; use crate::circuit::hybrid::HybridOp; use crate::circuit::region::ConstantsMap; use crate::circuit::region::RegionCtx; +use crate::circuit::region::RegionSettings; use crate::circuit::table::Range; use crate::circuit::Input; use crate::circuit::InputType; use crate::circuit::Unknown; +use crate::fieldutils::IntegerRep; use crate::tensor::ValType; use crate::{ circuit::{lookup::LookupOp, BaseConfig as PolyConfig, CheckMode, Op}, @@ -64,11 +66,11 @@ pub struct ForwardResult { /// The outputs of the forward pass. pub outputs: Vec>, /// The maximum value of any input to a lookup operation. - pub max_lookup_inputs: i64, + pub max_lookup_inputs: IntegerRep, /// The minimum value of any input to a lookup operation. - pub min_lookup_inputs: i64, + pub min_lookup_inputs: IntegerRep, /// The max range check size - pub max_range_size: i64, + pub max_range_size: IntegerRep, } impl From for ForwardResult { @@ -116,11 +118,11 @@ pub struct DummyPassRes { /// range checks pub range_checks: HashSet, /// max lookup inputs - pub max_lookup_inputs: i64, + pub max_lookup_inputs: IntegerRep, /// min lookup inputs - pub min_lookup_inputs: i64, + pub min_lookup_inputs: IntegerRep, /// min range check - pub max_range_size: i64, + pub max_range_size: IntegerRep, /// outputs pub outputs: Vec>, } @@ -545,7 +547,7 @@ impl Model { }) .collect::, GraphError>>()?; - let res = self.dummy_layout(run_args, &inputs, false, false)?; + let res = self.dummy_layout(run_args, &inputs, RegionSettings::all_false())?; // if we're using percentage tolerance, we need to add the necessary range check ops for it. @@ -588,14 +590,13 @@ impl Model { &self, model_inputs: &[Tensor], run_args: &RunArgs, - witness_gen: bool, - check_lookup: bool, + region_settings: RegionSettings, ) -> Result { let valtensor_inputs: Vec> = model_inputs .iter() .map(|x| x.map(|elem| ValType::Value(Value::known(elem))).into()) .collect(); - let res = self.dummy_layout(run_args, &valtensor_inputs, witness_gen, check_lookup)?; + let res = self.dummy_layout(run_args, &valtensor_inputs, region_settings)?; Ok(res.into()) } @@ -1390,8 +1391,7 @@ impl Model { &self, run_args: &RunArgs, inputs: &[ValTensor], - witness_gen: bool, - check_lookup: bool, + region_settings: RegionSettings, ) -> Result { debug!("calculating num of constraints using dummy model layout..."); @@ -1410,8 +1410,7 @@ impl Model { vars: ModelVars::new_dummy(), }; - let mut region = - RegionCtx::new_dummy(0, run_args.num_inner_cols, witness_gen, check_lookup); + let mut region = RegionCtx::new_dummy(0, run_args.num_inner_cols, region_settings); let outputs = self.layout_nodes(&mut model_config, &mut region, &mut results)?; diff --git a/src/graph/utilities.rs b/src/graph/utilities.rs index 0b26f1f21..0ec224440 100644 --- a/src/graph/utilities.rs +++ b/src/graph/utilities.rs @@ -9,6 +9,7 @@ use crate::circuit::lookup::LookupOp; #[cfg(not(target_arch = "wasm32"))] use crate::circuit::poly::PolyOp; use crate::circuit::Op; +use crate::fieldutils::IntegerRep; use crate::tensor::{Tensor, TensorError, TensorType}; use halo2curves::bn256::Fr as Fp; use halo2curves::ff::PrimeField; @@ -50,16 +51,20 @@ use tract_onnx::tract_hir::{ /// * `dims` - the dimensionality of the resulting [Tensor]. /// * `shift` - offset used in the fixed point representation. /// * `scale` - `2^scale` used in the fixed point representation. -pub fn quantize_float(elem: &f64, shift: f64, scale: crate::Scale) -> Result { +pub fn quantize_float( + elem: &f64, + shift: f64, + scale: crate::Scale, +) -> Result { let mult = scale_to_multiplier(scale); - let max_value = ((i64::MAX as f64 - shift) / mult).round(); // the maximum value that can be represented w/o sig bit truncation + let max_value = ((IntegerRep::MAX as f64 - shift) / mult).round(); // the maximum value that can be represented w/o sig bit truncation if *elem > max_value { return Err(TensorError::SigBitTruncationError); } // we parallelize the quantization process as it seems to be quite slow at times - let scaled = (mult * *elem + shift).round() as i64; + let scaled = (mult * *elem + shift).round() as IntegerRep; Ok(scaled) } @@ -70,7 +75,7 @@ pub fn quantize_float(elem: &f64, shift: f64, scale: crate::Scale) -> Result f64 { - let int_rep = crate::fieldutils::felt_to_i64(felt); + let int_rep = crate::fieldutils::felt_to_integer_rep(felt); let multiplier = scale_to_multiplier(scale); int_rep as f64 / multiplier - shift } @@ -1424,7 +1429,7 @@ pub fn quantize_tensor( visibility: &Visibility, ) -> Result, TensorError> { let mut value: Tensor = const_value.par_enum_map(|_, x| { - Ok::<_, TensorError>(crate::fieldutils::i64_to_felt::(quantize_float( + Ok::<_, TensorError>(crate::fieldutils::integer_rep_to_felt::(quantize_float( &(x).into(), 0.0, scale, diff --git a/src/lib.rs b/src/lib.rs index 2c73e3ae1..40b2803c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ use std::str::FromStr; use circuit::{table::Range, CheckMode, Tolerance}; use clap::Args; +use fieldutils::IntegerRep; use graph::Visibility; use halo2_proofs::poly::{ ipa::commitment::IPACommitmentScheme, kzg::commitment::KZGCommitmentScheme, @@ -243,7 +244,7 @@ pub struct RunArgs { #[arg(long, default_value = "1", value_hint = clap::ValueHint::Other)] pub scale_rebase_multiplier: u32, /// The min and max elements in the lookup table input column - #[arg(short = 'B', long, value_parser = parse_key_val::, default_value = "-32768->32768")] + #[arg(short = 'B', long, value_parser = parse_key_val::, default_value = "-32768->32768")] pub lookup_range: Range, /// The log_2 number of rows #[arg(short = 'K', long, default_value = "17", value_hint = clap::ValueHint::Other)] diff --git a/src/python.rs b/src/python.rs index eacf56dc9..69fa7be82 100644 --- a/src/python.rs +++ b/src/python.rs @@ -6,7 +6,7 @@ use crate::circuit::modules::poseidon::{ use crate::circuit::modules::Module; use crate::circuit::{CheckMode, Tolerance}; use crate::commands::*; -use crate::fieldutils::{felt_to_i64, i64_to_felt}; +use crate::fieldutils::{felt_to_integer_rep, integer_rep_to_felt, IntegerRep}; use crate::graph::modules::POSEIDON_LEN_GRAPH; use crate::graph::TestDataSource; use crate::graph::{ @@ -331,9 +331,9 @@ fn felt_to_big_endian(felt: PyFelt) -> PyResult { #[pyfunction(signature = ( felt, ))] -fn felt_to_int(felt: PyFelt) -> PyResult { +fn felt_to_int(felt: PyFelt) -> PyResult { let felt = crate::pfsys::string_to_field::(&felt); - let int_rep = felt_to_i64(felt); + let int_rep = felt_to_integer_rep(felt); Ok(int_rep) } @@ -357,7 +357,7 @@ fn felt_to_int(felt: PyFelt) -> PyResult { ))] fn felt_to_float(felt: PyFelt, scale: crate::Scale) -> PyResult { let felt = crate::pfsys::string_to_field::(&felt); - let int_rep = felt_to_i64(felt); + let int_rep = felt_to_integer_rep(felt); let multiplier = scale_to_multiplier(scale); let float_rep = int_rep as f64 / multiplier; Ok(float_rep) @@ -385,7 +385,7 @@ fn felt_to_float(felt: PyFelt, scale: crate::Scale) -> PyResult { fn float_to_felt(input: f64, scale: crate::Scale) -> PyResult { let int_rep = quantize_float(&input, 0.0, scale) .map_err(|_| PyIOError::new_err("Failed to quantize input"))?; - let felt = i64_to_felt(int_rep); + let felt = integer_rep_to_felt(int_rep); Ok(crate::pfsys::field_to_string::(&felt)) } diff --git a/src/tensor/mod.rs b/src/tensor/mod.rs index 6a8912b16..93836db06 100644 --- a/src/tensor/mod.rs +++ b/src/tensor/mod.rs @@ -9,7 +9,7 @@ pub mod var; pub use errors::TensorError; -use halo2curves::{bn256::Fr, ff::PrimeField}; +use halo2curves::ff::PrimeField; use maybe_rayon::{ prelude::{ IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, @@ -26,7 +26,7 @@ use instant::Instant; use crate::{ circuit::utils, - fieldutils::{felt_to_i32, felt_to_i64, i32_to_felt, i64_to_felt}, + fieldutils::{integer_rep_to_felt, IntegerRep}, graph::Visibility, }; @@ -108,7 +108,7 @@ impl TensorType for f32 { Some(0.0) } - // f32 doesnt impl Ord so we cant just use max like we can for i32, usize. + // f32 doesnt impl Ord so we cant just use max like we can for IntegerRep, usize. // A comparison between f32s needs to handle NAN values. fn tmax(&self, other: &Self) -> Option { match (self.is_nan(), other.is_nan()) { @@ -131,7 +131,7 @@ impl TensorType for f64 { Some(0.0) } - // f32 doesnt impl Ord so we cant just use max like we can for i32, usize. + // f32 doesnt impl Ord so we cant just use max like we can for IntegerRep, usize. // A comparison between f32s needs to handle NAN values. fn tmax(&self, other: &Self) -> Option { match (self.is_nan(), other.is_nan()) { @@ -150,8 +150,7 @@ impl TensorType for f64 { } tensor_type!(bool, Bool, false, true); -tensor_type!(i64, Int64, 0, 1); -tensor_type!(i32, Int32, 0, 1); +tensor_type!(IntegerRep, IntegerRep, 0, 1); tensor_type!(usize, USize, 0, 1); tensor_type!((), Empty, (), ()); tensor_type!(utils::F32, F32, utils::F32(0.0), utils::F32(1.0)); @@ -316,92 +315,6 @@ impl DerefMut for Tensor { self.inner.deref_mut() } } -/// Convert to i64 trait -pub trait IntoI64 { - /// Convert to i64 - fn into_i64(self) -> i64; - - /// From i64 - fn from_i64(i: i64) -> Self; -} - -impl IntoI64 for i64 { - fn into_i64(self) -> i64 { - self - } - fn from_i64(i: i64) -> i64 { - i - } -} - -impl IntoI64 for i32 { - fn into_i64(self) -> i64 { - self as i64 - } - fn from_i64(i: i64) -> Self { - i as i32 - } -} - -impl IntoI64 for usize { - fn into_i64(self) -> i64 { - self as i64 - } - fn from_i64(i: i64) -> Self { - i as usize - } -} - -impl IntoI64 for f32 { - fn into_i64(self) -> i64 { - self as i64 - } - fn from_i64(i: i64) -> Self { - i as f32 - } -} - -impl IntoI64 for f64 { - fn into_i64(self) -> i64 { - self as i64 - } - fn from_i64(i: i64) -> Self { - i as f64 - } -} - -impl IntoI64 for () { - fn into_i64(self) -> i64 { - 0 - } - fn from_i64(_: i64) -> Self {} -} - -impl IntoI64 for Fr { - fn into_i64(self) -> i64 { - felt_to_i64(self) - } - fn from_i64(i: i64) -> Self { - i64_to_felt::(i) - } -} - -impl IntoI64 for Value { - fn into_i64(self) -> i64 { - let mut res = vec![]; - self.map(|x| res.push(x.into_i64())); - - if res.is_empty() { - 0 - } else { - res[0] - } - } - - fn from_i64(i: i64) -> Self { - Value::known(F::from_i64(i)) - } -} impl PartialEq for Tensor { fn eq(&self, other: &Tensor) -> bool { @@ -431,42 +344,6 @@ where } } -impl From, F>>> - for Tensor -{ - fn from(value: Tensor, F>>) -> Tensor { - let mut output = Vec::new(); - value.map(|x| { - x.evaluate().value().map(|y| { - let e = felt_to_i32(*y); - output.push(e); - e - }) - }); - Tensor::new(Some(&output), value.dims()).unwrap() - } -} - -impl From>> - for Tensor -{ - fn from(value: Tensor>) -> Tensor { - let mut output = Vec::new(); - value.map(|x| { - let mut i = 0; - x.value().map(|y| { - let e = felt_to_i32(*y); - output.push(e); - i += 1; - }); - if i == 0 { - output.push(0); - } - }); - Tensor::new(Some(&output), value.dims()).unwrap() - } -} - impl From, F>>> for Tensor> { @@ -479,24 +356,6 @@ impl From From>> for Tensor { - fn from(t: Tensor>) -> Tensor { - let mut output = Vec::new(); - t.map(|x| { - let mut i = 0; - x.map(|y| { - let e = felt_to_i32(y); - output.push(e); - i += 1; - }); - if i == 0 { - output.push(0); - } - }); - Tensor::new(Some(&output), t.dims()).unwrap() - } -} - impl From>> for Tensor>> { @@ -508,20 +367,10 @@ impl From>> } } -impl From> for Tensor> { - fn from(t: Tensor) -> Tensor> { +impl From> for Tensor> { + fn from(t: Tensor) -> Tensor> { let mut ta: Tensor> = - Tensor::from((0..t.len()).map(|i| Value::known(i32_to_felt::(t[i])))); - // safe to unwrap as we know the dims are correct - ta.reshape(t.dims()).unwrap(); - ta - } -} - -impl From> for Tensor> { - fn from(t: Tensor) -> Tensor> { - let mut ta: Tensor> = - Tensor::from((0..t.len()).map(|i| Value::known(i64_to_felt::(t[i])))); + Tensor::from((0..t.len()).map(|i| Value::known(integer_rep_to_felt::(t[i])))); // safe to unwrap as we know the dims are correct ta.reshape(t.dims()).unwrap(); ta @@ -633,7 +482,8 @@ impl Tensor { /// /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(None, &[3, 3, 3]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(None, &[3, 3, 3]).unwrap(); /// /// a.set(&[0, 0, 1], 10); /// assert_eq!(a[0 + 0 + 1], 10); @@ -650,7 +500,8 @@ impl Tensor { /// /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(None, &[2, 3, 5]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(None, &[2, 3, 5]).unwrap(); /// /// a[1*15 + 1*5 + 1] = 5; /// assert_eq!(a.get(&[1, 1, 1]), 5); @@ -664,7 +515,8 @@ impl Tensor { /// /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(None, &[2, 3, 5]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(None, &[2, 3, 5]).unwrap(); /// /// a[1*15 + 1*5 + 1] = 5; /// assert_eq!(a.get(&[1, 1, 1]), 5); @@ -684,11 +536,12 @@ impl Tensor { /// Pad to a length that is divisible by n /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(Some(&[1,2,3,4,5,6]), &[2, 3]).unwrap(); - /// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 0, 0]), &[8]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1,2,3,4,5,6]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 0, 0]), &[8]).unwrap(); /// assert_eq!(a.pad_to_zero_rem(4, 0).unwrap(), expected); /// - /// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 0, 0, 0]), &[9]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 0, 0, 0]), &[9]).unwrap(); /// assert_eq!(a.pad_to_zero_rem(9, 0).unwrap(), expected); /// ``` pub fn pad_to_zero_rem(&self, n: usize, pad: T) -> Result, TensorError> { @@ -704,7 +557,8 @@ impl Tensor { /// /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(None, &[2, 3, 5]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(None, &[2, 3, 5]).unwrap(); /// /// let flat_index = 1*15 + 1*5 + 1; /// a[1*15 + 1*5 + 1] = 5; @@ -731,8 +585,9 @@ impl Tensor { /// Get a slice from the Tensor. /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); - /// let mut b = Tensor::::new(Some(&[1, 2]), &[2]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); + /// let mut b = Tensor::::new(Some(&[1, 2]), &[2]).unwrap(); /// /// assert_eq!(a.get_slice(&[0..2]).unwrap(), b); /// ``` @@ -782,9 +637,10 @@ impl Tensor { /// Set a slice of the Tensor. /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[2, 3]).unwrap(); - /// let b = Tensor::::new(Some(&[1, 2, 3, 1, 2, 3]), &[2, 3]).unwrap(); - /// a.set_slice(&[1..2], &Tensor::::new(Some(&[1, 2, 3]), &[1, 3]).unwrap()).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[2, 3]).unwrap(); + /// let b = Tensor::::new(Some(&[1, 2, 3, 1, 2, 3]), &[2, 3]).unwrap(); + /// a.set_slice(&[1..2], &Tensor::::new(Some(&[1, 2, 3]), &[1, 3]).unwrap()).unwrap(); /// assert_eq!(a, b); /// ``` pub fn set_slice( @@ -845,6 +701,7 @@ impl Tensor { /// /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// let a = Tensor::::new(None, &[3, 3, 3]).unwrap(); /// /// assert_eq!(a.get_index(&[2, 2, 2]), 26); @@ -868,12 +725,13 @@ impl Tensor { /// /// ``` /// use ezkl::tensor::Tensor; - /// let a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[6]).unwrap(); - /// let expected = Tensor::::new(Some(&[1, 2, 3, 3, 4, 5, 5, 6]), &[8]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[6]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 2, 3, 3, 4, 5, 5, 6]), &[8]).unwrap(); /// assert_eq!(a.duplicate_every_n(3, 1, 0).unwrap(), expected); /// assert_eq!(a.duplicate_every_n(7, 1, 0).unwrap(), a); /// - /// let expected = Tensor::::new(Some(&[1, 1, 2, 3, 3, 4, 5, 5, 6]), &[9]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 1, 2, 3, 3, 4, 5, 5, 6]), &[9]).unwrap(); /// assert_eq!(a.duplicate_every_n(3, 1, 2).unwrap(), expected); /// /// ``` @@ -900,8 +758,9 @@ impl Tensor { /// /// ``` /// use ezkl::tensor::Tensor; - /// let a = Tensor::::new(Some(&[1, 2, 3, 3, 4, 5, 6, 6]), &[8]).unwrap(); - /// let expected = Tensor::::new(Some(&[1, 2, 3, 3, 5, 6, 6]), &[7]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let a = Tensor::::new(Some(&[1, 2, 3, 3, 4, 5, 6, 6]), &[8]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 2, 3, 3, 5, 6, 6]), &[7]).unwrap(); /// assert_eq!(a.remove_every_n(4, 1, 0).unwrap(), expected); /// /// @@ -935,14 +794,15 @@ impl Tensor { /// WARN: assumes indices are in ascending order for speed /// ``` /// use ezkl::tensor::Tensor; - /// let a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[6]).unwrap(); - /// let expected = Tensor::::new(Some(&[1, 2, 3, 6]), &[4]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[6]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 2, 3, 6]), &[4]).unwrap(); /// let mut indices = vec![3, 4]; /// assert_eq!(a.remove_indices(&mut indices, true).unwrap(), expected); /// /// - /// let a = Tensor::::new(Some(&[52, -245, 153, 13, -4, -56, -163, 249, -128, -172, 396, 143, 2, -96, 504, -44, -158, -393, 61, 95, 191, 74, 64, -219, 553, 104, 235, 222, 44, -216, 63, -251, 40, -140, 112, -355, 60, 123, 26, -116, -89, -200, -109, 168, 135, -34, -99, -54, 5, -81, 322, 87, 4, -139, 420, 92, -295, -12, 262, -1, 26, -48, 231, 1, -335, 244, 188, -4, 5, -362, 57, -198, -184, -117, 40, 305, 49, 30, -59, -26, -37, 96]), &[82]).unwrap(); - /// let b = Tensor::::new(Some(&[52, -245, 153, 13, -4, -56, -163, 249, -128, -172, 396, 143, 2, -96, 504, -44, -158, -393, 61, 95, 191, 74, 64, -219, 553, 104, 235, 222, 44, -216, 63, -251, 40, -140, 112, -355, 60, 123, 26, -116, -89, -200, -109, 168, 135, -34, -99, -54, 5, -81, 322, 87, 4, -139, 420, 92, -295, -12, 262, -1, 26, -48, 231, -335, 244, 188, 5, -362, 57, -198, -184, -117, 40, 305, 49, 30, -59, -26, -37, 96]), &[80]).unwrap(); + /// let a = Tensor::::new(Some(&[52, -245, 153, 13, -4, -56, -163, 249, -128, -172, 396, 143, 2, -96, 504, -44, -158, -393, 61, 95, 191, 74, 64, -219, 553, 104, 235, 222, 44, -216, 63, -251, 40, -140, 112, -355, 60, 123, 26, -116, -89, -200, -109, 168, 135, -34, -99, -54, 5, -81, 322, 87, 4, -139, 420, 92, -295, -12, 262, -1, 26, -48, 231, 1, -335, 244, 188, -4, 5, -362, 57, -198, -184, -117, 40, 305, 49, 30, -59, -26, -37, 96]), &[82]).unwrap(); + /// let b = Tensor::::new(Some(&[52, -245, 153, 13, -4, -56, -163, 249, -128, -172, 396, 143, 2, -96, 504, -44, -158, -393, 61, 95, 191, 74, 64, -219, 553, 104, 235, 222, 44, -216, 63, -251, 40, -140, 112, -355, 60, 123, 26, -116, -89, -200, -109, 168, 135, -34, -99, -54, 5, -81, 322, 87, 4, -139, 420, 92, -295, -12, 262, -1, 26, -48, 231, -335, 244, 188, 5, -362, 57, -198, -184, -117, 40, 305, 49, 30, -59, -26, -37, 96]), &[80]).unwrap(); /// let mut indices = vec![63, 67]; /// assert_eq!(a.remove_indices(&mut indices, true).unwrap(), b); /// ``` @@ -972,6 +832,7 @@ impl Tensor { ///Reshape the tensor /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// let mut a = Tensor::::new(None, &[3, 3, 3]).unwrap(); /// a.reshape(&[9, 3]); /// assert_eq!(a.dims(), &[9, 3]); @@ -1006,22 +867,23 @@ impl Tensor { /// Move axis of the tensor /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// let mut a = Tensor::::new(None, &[3, 3, 3]).unwrap(); /// let b = a.move_axis(0, 2).unwrap(); /// assert_eq!(b.dims(), &[3, 3, 3]); /// - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 1, 2]).unwrap(); - /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6]), &[1, 2, 3]).unwrap(); + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 1, 2]).unwrap(); + /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6]), &[1, 2, 3]).unwrap(); /// let b = a.move_axis(0, 2).unwrap(); /// assert_eq!(b, expected); /// - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); - /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); + /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); /// let b = a.move_axis(1, 2).unwrap(); /// assert_eq!(b, expected); /// - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); - /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); + /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); /// let b = a.move_axis(2, 1).unwrap(); /// assert_eq!(b, expected); /// ``` @@ -1086,22 +948,23 @@ impl Tensor { /// Swap axes of the tensor /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// let mut a = Tensor::::new(None, &[3, 3, 3]).unwrap(); /// let b = a.swap_axes(0, 2).unwrap(); /// assert_eq!(b.dims(), &[3, 3, 3]); /// - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 1, 2]).unwrap(); - /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6]), &[2, 1, 3]).unwrap(); + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 1, 2]).unwrap(); + /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6]), &[2, 1, 3]).unwrap(); /// let b = a.swap_axes(0, 2).unwrap(); /// assert_eq!(b, expected); /// - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); - /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); + /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); /// let b = a.swap_axes(1, 2).unwrap(); /// assert_eq!(b, expected); /// - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); - /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 3, 2]).unwrap(); + /// let mut expected = Tensor::::new(Some(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12]), &[2, 2, 3]).unwrap(); /// let b = a.swap_axes(2, 1).unwrap(); /// assert_eq!(b, expected); /// ``` @@ -1148,9 +1011,10 @@ impl Tensor { /// Broadcasts the tensor to a given shape /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(Some(&[1, 2, 3]), &[3, 1]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 2, 3]), &[3, 1]).unwrap(); /// - /// let mut expected = Tensor::::new(Some(&[1, 1, 1, 2, 2, 2, 3, 3, 3]), &[3, 3]).unwrap(); + /// let mut expected = Tensor::::new(Some(&[1, 1, 1, 2, 2, 2, 3, 3, 3]), &[3, 3]).unwrap(); /// assert_eq!(a.expand(&[3, 3]).unwrap(), expected); /// /// ``` @@ -1204,6 +1068,7 @@ impl Tensor { ///Flatten the tensor shape /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// let mut a = Tensor::::new(None, &[3, 3, 3]).unwrap(); /// a.flatten(); /// assert_eq!(a.dims(), &[27]); @@ -1217,8 +1082,9 @@ impl Tensor { /// Maps a function to tensors /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); - /// let mut c = a.map(|x| i32::pow(x,2)); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); + /// let mut c = a.map(|x| IntegerRep::pow(x,2)); /// assert_eq!(c, Tensor::from([1, 16].into_iter())) /// ``` pub fn map G, G: TensorType>(&self, mut f: F) -> Tensor { @@ -1231,8 +1097,9 @@ impl Tensor { /// Maps a function to tensors and enumerates /// ``` /// use ezkl::tensor::{Tensor, TensorError}; - /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); - /// let mut c = a.enum_map::<_,_,TensorError>(|i, x| Ok(i32::pow(x + i as i32, 2))).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); + /// let mut c = a.enum_map::<_,_,TensorError>(|i, x| Ok(IntegerRep::pow(x + i as IntegerRep, 2))).unwrap(); /// assert_eq!(c, Tensor::from([1, 25].into_iter())); /// ``` pub fn enum_map Result, G: TensorType, E: Error>( @@ -1254,8 +1121,9 @@ impl Tensor { /// Maps a function to tensors and enumerates in parallel /// ``` /// use ezkl::tensor::{Tensor, TensorError}; - /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); - /// let mut c = a.par_enum_map::<_,_,TensorError>(|i, x| Ok(i32::pow(x + i as i32, 2))).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); + /// let mut c = a.par_enum_map::<_,_,TensorError>(|i, x| Ok(IntegerRep::pow(x + i as IntegerRep, 2))).unwrap(); /// assert_eq!(c, Tensor::from([1, 25].into_iter())); /// ``` pub fn par_enum_map< @@ -1284,8 +1152,9 @@ impl Tensor { /// Get last elem from Tensor /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); - /// let mut b = Tensor::::new(Some(&[3]), &[1]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); + /// let mut b = Tensor::::new(Some(&[3]), &[1]).unwrap(); /// /// assert_eq!(a.last().unwrap(), b); /// ``` @@ -1308,8 +1177,9 @@ impl Tensor { /// Maps a function to tensors and enumerates in parallel /// ``` /// use ezkl::tensor::{Tensor, TensorError}; - /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); - /// let mut c = a.par_enum_map::<_,_,TensorError>(|i, x| Ok(i32::pow(x + i as i32, 2))).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 4]), &[2]).unwrap(); + /// let mut c = a.par_enum_map::<_,_,TensorError>(|i, x| Ok(IntegerRep::pow(x + i as IntegerRep, 2))).unwrap(); /// assert_eq!(c, Tensor::from([1, 25].into_iter())); /// ``` pub fn par_enum_map_mut_filtered< @@ -1332,103 +1202,13 @@ impl Tensor { } } -#[cfg(feature = "metal")] -#[allow(unsafe_code)] -/// Perform a tensor operation on the GPU using Metal. -pub fn metal_tensor_op( - v: &Tensor, - w: &Tensor, - op: &str, -) -> Tensor { - assert_eq!(v.dims(), w.dims()); - - log::trace!("------------------------------------------------"); - - let start = Instant::now(); - let v = v - .par_enum_map(|_, x| Ok::<_, TensorError>(x.into_i64())) - .unwrap(); - let w = w - .par_enum_map(|_, x| Ok::<_, TensorError>(x.into_i64())) - .unwrap(); - log::trace!("Time to map tensors: {:?}", start.elapsed()); - - objc::rc::autoreleasepool(|| { - // create function pipeline. - // this compiles the function, so a pipline can't be created in performance sensitive code. - - let pipeline = &PIPELINES[op]; - - let length = v.len() as u64; - let size = length * core::mem::size_of::() as u64; - assert_eq!(v.len(), w.len()); - - let start = Instant::now(); - - let buffer_a = DEVICE.new_buffer_with_data( - unsafe { std::mem::transmute(v.as_ptr()) }, - size, - MTLResourceOptions::StorageModeShared, - ); - let buffer_b = DEVICE.new_buffer_with_data( - unsafe { std::mem::transmute(w.as_ptr()) }, - size, - MTLResourceOptions::StorageModeShared, - ); - let buffer_result = DEVICE.new_buffer( - size, // the operation will return an array with the same size. - MTLResourceOptions::StorageModeShared, - ); - - log::trace!("Time to load buffers: {:?}", start.elapsed()); - - // for sending commands, a command buffer is needed. - let start = Instant::now(); - let command_buffer = QUEUE.new_command_buffer(); - log::trace!("Time to load command buffer: {:?}", start.elapsed()); - // to write commands into a buffer an encoder is needed, in our case a compute encoder. - let start = Instant::now(); - let compute_encoder = command_buffer.new_compute_command_encoder(); - compute_encoder.set_compute_pipeline_state(&pipeline); - compute_encoder.set_buffers( - 0, - &[Some(&buffer_a), Some(&buffer_b), Some(&buffer_result)], - &[0; 3], - ); - log::trace!("Time to load compute encoder: {:?}", start.elapsed()); - - // specify thread count and organization - let start = Instant::now(); - let grid_size = MTLSize::new(length, 1, 1); - let threadgroup_size = MTLSize::new(length, 1, 1); - compute_encoder.dispatch_threads(grid_size, threadgroup_size); - log::trace!("Time to dispatch threads: {:?}", start.elapsed()); - - // end encoding and execute commands - let start = Instant::now(); - compute_encoder.end_encoding(); - command_buffer.commit(); - - command_buffer.wait_until_completed(); - log::trace!("Time to commit: {:?}", start.elapsed()); - - let start = Instant::now(); - let ptr = buffer_result.contents() as *const i64; - let len = buffer_result.length() as usize / std::mem::size_of::(); - let slice = unsafe { core::slice::from_raw_parts(ptr, len) }; - let res = Tensor::new(Some(&slice.to_vec()), &v.dims()).unwrap(); - log::trace!("Time to get result: {:?}", start.elapsed()); - - res.map(|x| T::from_i64(x)) - }) -} - impl Tensor> { /// Flattens a tensor of tensors /// ``` /// use ezkl::tensor::Tensor; - /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[2, 3]).unwrap(); - /// let mut b = Tensor::::new(Some(&[1, 4]), &[2, 1]).unwrap(); + /// use ezkl::fieldutils::IntegerRep; + /// let mut a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[2, 3]).unwrap(); + /// let mut b = Tensor::::new(Some(&[1, 4]), &[2, 1]).unwrap(); /// let mut c = Tensor::new(Some(&[a,b]), &[2]).unwrap(); /// let mut d = c.combine().unwrap(); /// assert_eq!(d.dims(), &[8]); @@ -1444,9 +1224,7 @@ impl Tensor> { } } -impl + std::marker::Send + std::marker::Sync + IntoI64> Add - for Tensor -{ +impl + std::marker::Send + std::marker::Sync> Add for Tensor { type Output = Result, TensorError>; /// Adds tensors. /// # Arguments @@ -1456,42 +1234,43 @@ impl + std::marker::Send + std::marker::Sync + I /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use std::ops::Add; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2, 3, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = x.add(k).unwrap(); - /// let expected = Tensor::::new(Some(&[4, 4, 4, 2, 2, 2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 4, 4, 2, 2, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 1D casting - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2]), /// &[1]).unwrap(); /// let result = x.add(k).unwrap(); - /// let expected = Tensor::::new(Some(&[4, 3, 4, 3, 3, 3]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 3, 4, 3, 3, 3]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// /// // Now test 2D casting - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2, 3]), /// &[2]).unwrap(); /// let result = x.add(k).unwrap(); - /// let expected = Tensor::::new(Some(&[4, 3, 4, 4, 4, 4]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 3, 4, 4, 4, 4]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` fn add(self, rhs: Self) -> Self::Output { @@ -1525,13 +1304,14 @@ impl + std::marker::Send + std::marker::Sync> Ne /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use std::ops::Neg; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = x.neg(); - /// let expected = Tensor::::new(Some(&[-2, -1, -2, -1, -1, -1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[-2, -1, -2, -1, -1, -1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` fn neg(self) -> Self { @@ -1544,9 +1324,7 @@ impl + std::marker::Send + std::marker::Sync> Ne } } -impl + std::marker::Send + std::marker::Sync + IntoI64> Sub - for Tensor -{ +impl + std::marker::Send + std::marker::Sync> Sub for Tensor { type Output = Result, TensorError>; /// Subtracts tensors. /// # Arguments @@ -1556,43 +1334,44 @@ impl + std::marker::Send + std::marker::Sync + I /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use std::ops::Sub; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2, 3, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = x.sub(k).unwrap(); - /// let expected = Tensor::::new(Some(&[0, -2, 0, 0, 0, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, -2, 0, 0, 0, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 1D sub - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2]), /// &[1], /// ).unwrap(); /// let result = x.sub(k).unwrap(); - /// let expected = Tensor::::new(Some(&[0, -1, 0, -1, -1, -1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, -1, 0, -1, -1, -1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 2D sub - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2, 3]), /// &[2], /// ).unwrap(); /// let result = x.sub(k).unwrap(); - /// let expected = Tensor::::new(Some(&[0, -1, 0, -2, -2, -2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, -1, 0, -2, -2, -2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` fn sub(self, rhs: Self) -> Self::Output { @@ -1618,9 +1397,7 @@ impl + std::marker::Send + std::marker::Sync + I } } -impl + std::marker::Send + std::marker::Sync + IntoI64> Mul - for Tensor -{ +impl + std::marker::Send + std::marker::Sync> Mul for Tensor { type Output = Result, TensorError>; /// Elementwise multiplies tensors. /// # Arguments @@ -1630,41 +1407,42 @@ impl + std::marker::Send + std::marker::Sync + I /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use std::ops::Mul; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2, 3, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = x.mul(k).unwrap(); - /// let expected = Tensor::::new(Some(&[4, 3, 4, 1, 1, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 3, 4, 1, 1, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 1D mult - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2]), /// &[1]).unwrap(); /// let result = x.mul(k).unwrap(); - /// let expected = Tensor::::new(Some(&[4, 2, 4, 2, 2, 2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 2, 4, 2, 2, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 2D mult - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); - /// let k = Tensor::::new( + /// let k = Tensor::::new( /// Some(&[2, 2]), /// &[2]).unwrap(); /// let result = x.mul(k).unwrap(); - /// let expected = Tensor::::new(Some(&[4, 2, 4, 2, 2, 2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 2, 4, 2, 2, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` fn mul(self, rhs: Self) -> Self::Output { @@ -1690,7 +1468,7 @@ impl + std::marker::Send + std::marker::Sync + I } } -impl + std::marker::Send + std::marker::Sync + IntoI64> Tensor { +impl + std::marker::Send + std::marker::Sync> Tensor { /// Elementwise raise a tensor to the nth power. /// # Arguments /// @@ -1699,13 +1477,14 @@ impl + std::marker::Send + std::marker::Sync + I /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use std::ops::Mul; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = x.pow(3).unwrap(); - /// let expected = Tensor::::new(Some(&[8, 3375, 8, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[8, 3375, 8, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn pow(&self, mut exp: u32) -> Result { @@ -1739,30 +1518,31 @@ impl + std::marker::Send + std::marker::Sync> Di /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use std::ops::Div; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 1, 4, 1, 1, 4]), /// &[2, 3], /// ).unwrap(); - /// let y = Tensor::::new( + /// let y = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = x.div(y).unwrap(); - /// let expected = Tensor::::new(Some(&[2, 1, 2, 1, 1, 4]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 1, 2, 1, 1, 4]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // test 1D casting - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 1, 4, 1, 1, 4]), /// &[2, 3], /// ).unwrap(); - /// let y = Tensor::::new( + /// let y = Tensor::::new( /// Some(&[2]), /// &[1], /// ).unwrap(); /// let result = x.div(y).unwrap(); - /// let expected = Tensor::::new(Some(&[2, 0, 2, 0, 0, 2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 0, 2, 0, 0, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` fn div(self, rhs: Self) -> Self::Output { @@ -1789,17 +1569,18 @@ impl + std::marker::Send + std::marker::Sync> Re /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use std::ops::Rem; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 1, 4, 1, 1, 4]), /// &[2, 3], /// ).unwrap(); - /// let y = Tensor::::new( + /// let y = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = x.rem(y).unwrap(); - /// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` fn rem(self, rhs: Self) -> Self::Output { @@ -1883,25 +1664,25 @@ mod tests { #[test] fn tensor_clone() { - let x = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); + let x = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); assert_eq!(x, x.clone()); } #[test] fn tensor_eq() { - let a = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); - let mut b = Tensor::::new(Some(&[1, 2, 3]), &[3, 1]).unwrap(); + let a = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); + let mut b = Tensor::::new(Some(&[1, 2, 3]), &[3, 1]).unwrap(); b.reshape(&[3]).unwrap(); - let c = Tensor::::new(Some(&[1, 2, 4]), &[3]).unwrap(); - let d = Tensor::::new(Some(&[1, 2, 4]), &[3, 1]).unwrap(); + let c = Tensor::::new(Some(&[1, 2, 4]), &[3]).unwrap(); + let d = Tensor::::new(Some(&[1, 2, 4]), &[3, 1]).unwrap(); assert_eq!(a, b); assert_ne!(a, c); assert_ne!(a, d); } #[test] fn tensor_slice() { - let a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[2, 3]).unwrap(); - let b = Tensor::::new(Some(&[1, 4]), &[2, 1]).unwrap(); + let a = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[2, 3]).unwrap(); + let b = Tensor::::new(Some(&[1, 4]), &[2, 1]).unwrap(); assert_eq!(a.get_slice(&[0..2, 0..1]).unwrap(), b); } diff --git a/src/tensor/ops.rs b/src/tensor/ops.rs index 0b44054dc..8fc78a940 100644 --- a/src/tensor/ops.rs +++ b/src/tensor/ops.rs @@ -1,5 +1,8 @@ -use super::{IntoI64, TensorError}; -use crate::tensor::{Tensor, TensorType}; +use super::TensorError; +use crate::{ + fieldutils::IntegerRep, + tensor::{Tensor, TensorType}, +}; use itertools::Itertools; use maybe_rayon::{iter::ParallelIterator, prelude::IntoParallelRefIterator}; pub use std::ops::{Add, Mul, Neg, Sub}; @@ -12,89 +15,90 @@ pub use std::ops::{Add, Mul, Neg, Sub}; /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::trilu; -/// let a = Tensor::::new( +/// let a = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[1, 3, 2], /// ).unwrap(); /// let result = trilu(&a, 1, true).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 2, 0, 0, 0, 0]), &[1, 3, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, 2, 0, 0, 0, 0]), &[1, 3, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 1, false).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[1, 3, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[1, 3, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 0, true).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 0, 4, 0, 0]), &[1, 3, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 0, 4, 0, 0]), &[1, 3, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 0, false).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 0, 3, 4, 5, 6]), &[1, 3, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 0, 3, 4, 5, 6]), &[1, 3, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, -1, true).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 0, 6]), &[1, 3, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 0, 6]), &[1, 3, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, -1, false).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 0, 3, 0, 5, 6]), &[1, 3, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, 0, 3, 0, 5, 6]), &[1, 3, 2]).unwrap(); /// assert_eq!(result, expected); /// -/// let a = Tensor::::new( +/// let a = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[1, 2, 3], /// ).unwrap(); /// let result = trilu(&a, 1, true).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 2, 3, 0, 0, 6]), &[1, 2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, 2, 3, 0, 0, 6]), &[1, 2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 1, false).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 0, 4, 5, 6]), &[1, 2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 0, 4, 5, 6]), &[1, 2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 0, true).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 0, 5, 6]), &[1, 2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 0, 5, 6]), &[1, 2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 0, false).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 0, 0, 4, 5, 0]), &[1, 2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 0, 0, 4, 5, 0]), &[1, 2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, -1, true).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[1, 2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[1, 2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, -1, false).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 0, 0, 4, 0, 0]), &[1, 2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, 0, 0, 4, 0, 0]), &[1, 2, 3]).unwrap(); /// assert_eq!(result, expected); /// -/// let a = Tensor::::new( +/// let a = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9]), /// &[1, 3, 3], /// ).unwrap(); /// let result = trilu(&a, 1, true).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 2, 3, 0, 0, 6, 0, 0, 0]), &[1, 3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, 2, 3, 0, 0, 6, 0, 0, 0]), &[1, 3, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 1, false).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 0, 4, 5, 6, 7, 8, 9]), &[1, 3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 0, 4, 5, 6, 7, 8, 9]), &[1, 3, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 0, true).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 0, 5, 6, 0, 0, 9]), &[1, 3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 0, 5, 6, 0, 0, 9]), &[1, 3, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, 0, false).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 0, 0, 4, 5, 0, 7, 8, 9]), &[1, 3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 0, 0, 4, 5, 0, 7, 8, 9]), &[1, 3, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, -1, true).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 0, 8, 9]), &[1, 3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 0, 8, 9]), &[1, 3, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = trilu(&a, -1, false).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 0, 0, 4, 0, 0, 7, 8, 0]), &[1, 3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, 0, 0, 4, 0, 0, 7, 8, 0]), &[1, 3, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn trilu( @@ -148,40 +152,41 @@ pub fn trilu( /// ``` /// /// -/// let a = Tensor::::new( +/// let a = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap(); /// let result = resize(&a, &[1, 2]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6]), &[2, 6]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6]), &[2, 6]).unwrap(); /// assert_eq!(result, expected); /// /// -/// let a = Tensor::::new( +/// let a = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap(); /// let result = resize(&a, &[2, 2]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 4, 4, 5, 5, 6, 6]), &[4, 6]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 4, 4, 5, 5, 6, 6]), &[4, 6]).unwrap(); /// assert_eq!(result, expected); /// /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::resize; -/// let a = Tensor::::new( +/// let a = Tensor::::new( /// Some(&[1, 2, 3, 4]), /// &[2, 2], /// ).unwrap(); /// let result = resize(&a, &[2, 2]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4]), &[4, 4]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4]), &[4, 4]).unwrap(); /// assert_eq!(result, expected); /// /// -/// let a = Tensor::::new( +/// let a = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[3, 2], /// ).unwrap(); /// let result = resize(&a, &[2, 3]).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 5, 5, 5, 6, 6, 6]), &[6, 6]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 5, 5, 5, 6, 6, 6]), &[6, 6]).unwrap(); /// assert_eq!(result, expected); /// /// @@ -226,32 +231,33 @@ pub fn resize( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::add; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); -/// let k = Tensor::::new( +/// let k = Tensor::::new( /// Some(&[2, 3, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = add(&[x, k]).unwrap(); -/// let expected = Tensor::::new(Some(&[4, 4, 4, 2, 2, 2]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[4, 4, 4, 2, 2, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 1D casting -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); -/// let k = Tensor::::new( +/// let k = Tensor::::new( /// Some(&[2]), /// &[1]).unwrap(); /// let result = add(&[x, k]).unwrap(); -/// let expected = Tensor::::new(Some(&[4, 3, 4, 3, 3, 3]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[4, 3, 4, 3, 3, 3]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` -pub fn add + std::marker::Send + std::marker::Sync + IntoI64>( +pub fn add + std::marker::Send + std::marker::Sync>( t: &[Tensor], ) -> Result, TensorError> { // calculate value of output @@ -272,33 +278,34 @@ pub fn add + std::marker::Send + std::marker::Sy /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::sub; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); -/// let k = Tensor::::new( +/// let k = Tensor::::new( /// Some(&[2, 3, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = sub(&[x, k]).unwrap(); -/// let expected = Tensor::::new(Some(&[0, -2, 0, 0, 0, 0]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, -2, 0, 0, 0, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 1D sub -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); -/// let k = Tensor::::new( +/// let k = Tensor::::new( /// Some(&[2]), /// &[1], /// ).unwrap(); /// let result = sub(&[x, k]).unwrap(); -/// let expected = Tensor::::new(Some(&[0, -1, 0, -1, -1, -1]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, -1, 0, -1, -1, -1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` -pub fn sub + std::marker::Send + std::marker::Sync + IntoI64>( +pub fn sub + std::marker::Send + std::marker::Sync>( t: &[Tensor], ) -> Result, TensorError> { // calculate value of output @@ -318,32 +325,33 @@ pub fn sub + std::marker::Send + std::marker::Sy /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::mult; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); -/// let k = Tensor::::new( +/// let k = Tensor::::new( /// Some(&[2, 3, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = mult(&[x, k]).unwrap(); -/// let expected = Tensor::::new(Some(&[4, 3, 4, 1, 1, 1]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[4, 3, 4, 1, 1, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// // Now test 1D mult -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[2, 1, 2, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); -/// let k = Tensor::::new( +/// let k = Tensor::::new( /// Some(&[2]), /// &[1]).unwrap(); /// let result = mult(&[x, k]).unwrap(); -/// let expected = Tensor::::new(Some(&[4, 2, 4, 2, 2, 2]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[4, 2, 4, 2, 2, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` -pub fn mult + std::marker::Send + std::marker::Sync + IntoI64>( +pub fn mult + std::marker::Send + std::marker::Sync>( t: &[Tensor], ) -> Result, TensorError> { // calculate value of output @@ -365,25 +373,26 @@ pub fn mult + std::marker::Send + std::marker::S /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::downsample; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap(); /// let result = downsample(&x, 0, 1, 1).unwrap(); -/// let expected = Tensor::::new(Some(&[4, 5, 6]), &[1, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[4, 5, 6]), &[1, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = downsample(&x, 1, 2, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 3, 4, 6]), &[2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 3, 4, 6]), &[2, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let result = downsample(&x, 1, 2, 1).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 5]), &[2, 1]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 5]), &[2, 1]).unwrap(); /// assert_eq!(result, expected); /// /// let result = downsample(&x, 1, 2, 2).unwrap(); -/// let expected = Tensor::::new(Some(&[3, 6]), &[2, 1]).unwrap(); +/// let expected = Tensor::::new(Some(&[3, 6]), &[2, 1]).unwrap(); /// assert_eq!(result, expected); pub fn downsample( input: &Tensor, @@ -434,8 +443,9 @@ pub fn downsample( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::gather; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[2, 3], /// ).unwrap(); @@ -444,7 +454,7 @@ pub fn downsample( /// &[2], /// ).unwrap(); /// let result = gather(&x, &index, 1).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 4, 5]), &[2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 4, 5]), &[2, 2]).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn gather( @@ -500,6 +510,7 @@ pub fn gather( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::scatter; /// let x = Tensor::::new( /// Some(&[1.0, 2.0, 3.0, 4.0]), @@ -561,8 +572,9 @@ pub fn scatter( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::gather_elements; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[1, 2, 3, 4]), /// &[2, 2], /// ).unwrap(); @@ -571,7 +583,7 @@ pub fn scatter( /// &[2, 2], /// ).unwrap(); /// let result = gather_elements(&x, &index, 1).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 1, 4, 3]), &[2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 1, 4, 3]), &[2, 2]).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn gather_elements( @@ -618,8 +630,9 @@ pub fn gather_elements( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::gather_nd; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[0, 1, 2, 3]), /// &[2, 2], /// ).unwrap(); @@ -628,7 +641,7 @@ pub fn gather_elements( /// &[2, 2], /// ).unwrap(); /// let result = gather_nd(&x, &index, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 3]), &[2]).unwrap(); +/// let expected = Tensor::::new(Some(&[0, 3]), &[2]).unwrap(); /// assert_eq!(result, expected); /// /// let index = Tensor::::new( @@ -636,10 +649,10 @@ pub fn gather_elements( /// &[2, 1], /// ).unwrap(); /// let result = gather_nd(&x, &index, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 3, 0, 1]), &[2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 3, 0, 1]), &[2, 2]).unwrap(); /// assert_eq!(result, expected); /// -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[0, 1, 2, 3, 4, 5, 6, 7]), /// &[2, 2, 2], /// ).unwrap(); @@ -648,7 +661,7 @@ pub fn gather_elements( /// &[2, 2], /// ).unwrap(); /// let result = gather_nd(&x, &index, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let index = Tensor::::new( @@ -656,7 +669,7 @@ pub fn gather_elements( /// &[2, 1, 2], /// ).unwrap(); /// let result = gather_nd(&x, &index, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 1, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 1, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let index = Tensor::::new( @@ -664,7 +677,7 @@ pub fn gather_elements( /// &[2, 1], /// ).unwrap(); /// let result = gather_nd(&x, &index, 1).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let index = Tensor::::new( @@ -672,7 +685,7 @@ pub fn gather_elements( /// &[2, 2, 3], /// ).unwrap(); /// let result = gather_nd(&x, &index, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 3, 4, 5]), &[2, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let index = Tensor::::new( @@ -680,7 +693,7 @@ pub fn gather_elements( /// &[2, 2, 2], /// ).unwrap(); /// let result = gather_nd(&x, &index, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 3, 0, 1, 6, 7, 4, 5]), &[2, 2, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 3, 0, 1, 6, 7, 4, 5]), &[2, 2, 2]).unwrap(); /// assert_eq!(result, expected); /// /// let index = Tensor::::new( @@ -688,7 +701,7 @@ pub fn gather_elements( /// &[2, 3], /// ).unwrap(); /// let result = gather_nd(&x, &index, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 7]), &[2]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 7]), &[2]).unwrap(); /// assert_eq!(result, expected); /// pub fn gather_nd( @@ -798,8 +811,9 @@ pub fn gather_nd( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::scatter_nd; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6, 7, 8]), /// &[8], /// ).unwrap(); @@ -808,15 +822,15 @@ pub fn gather_nd( /// Some(&[4, 3, 1, 7]), /// &[4, 1], /// ).unwrap(); -/// let src = Tensor::::new( +/// let src = Tensor::::new( /// Some(&[9, 10, 11, 12]), /// &[4], /// ).unwrap(); /// let result = scatter_nd(&x, &index, &src).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 11, 3, 10, 9, 6, 7, 12]), &[8]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 11, 3, 10, 9, 6, 7, 12]), &[8]).unwrap(); /// assert_eq!(result, expected); /// -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, /// 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, /// 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, @@ -829,7 +843,7 @@ pub fn gather_nd( /// &[2, 1], /// ).unwrap(); /// -/// let src = Tensor::::new( +/// let src = Tensor::::new( /// Some(&[5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, /// 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, /// ]), @@ -838,7 +852,7 @@ pub fn gather_nd( /// /// let result = scatter_nd(&x, &index, &src).unwrap(); /// -/// let expected = Tensor::::new( +/// let expected = Tensor::::new( /// Some(&[5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, /// 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, /// 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, @@ -847,7 +861,7 @@ pub fn gather_nd( /// ).unwrap(); /// assert_eq!(result, expected); /// -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6, 7, 8]), /// &[2, 4], /// ).unwrap(); @@ -856,15 +870,15 @@ pub fn gather_nd( /// Some(&[0, 1]), /// &[2, 1], /// ).unwrap(); -/// let src = Tensor::::new( +/// let src = Tensor::::new( /// Some(&[9, 10]), /// &[2], /// ).unwrap(); /// let result = scatter_nd(&x, &index, &src).unwrap(); -/// let expected = Tensor::::new(Some(&[9, 9, 9, 9, 10, 10, 10, 10]), &[2, 4]).unwrap(); +/// let expected = Tensor::::new(Some(&[9, 9, 9, 9, 10, 10, 10, 10]), &[2, 4]).unwrap(); /// assert_eq!(result, expected); /// -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6, 7, 8]), /// &[2, 4], /// ).unwrap(); @@ -873,12 +887,12 @@ pub fn gather_nd( /// Some(&[0, 1]), /// &[1, 1, 2], /// ).unwrap(); -/// let src = Tensor::::new( +/// let src = Tensor::::new( /// Some(&[9]), /// &[1, 1], /// ).unwrap(); /// let result = scatter_nd(&x, &index, &src).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 9, 3, 4, 5, 6, 7, 8]), &[2, 4]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 9, 3, 4, 5, 6, 7, 8]), &[2, 4]).unwrap(); /// assert_eq!(result, expected); /// ```` /// @@ -926,13 +940,14 @@ pub fn scatter_nd( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::abs; -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[-2, 15, 2, -1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = abs(&x).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 15, 2, 1, 1, 0]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 15, 2, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn abs + std::cmp::Ord + Neg>( @@ -951,16 +966,17 @@ pub fn abs + std::cmp::Ord + Neg>( /// Intercalates values into a tensor along a given axis. /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::intercalate_values; /// -/// let tensor = Tensor::::new(Some(&[1, 2, 3, 4]), &[2, 2]).unwrap(); +/// let tensor = Tensor::::new(Some(&[1, 2, 3, 4]), &[2, 2]).unwrap(); /// let result = intercalate_values(&tensor, 0, 2, 1).unwrap(); /// -/// let expected = Tensor::::new(Some(&[1, 0, 2, 3, 0, 4]), &[2, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 0, 2, 3, 0, 4]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// let result = intercalate_values(&expected, 0, 2, 0).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 0, 2, 0, 0, 0, 3, 0, 4]), &[3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 0, 2, 0, 0, 0, 3, 0, 4]), &[3, 3]).unwrap(); /// /// assert_eq!(result, expected); /// @@ -1005,24 +1021,25 @@ pub fn intercalate_values( /// One hot encodes a tensor along a given axis. /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::one_hot; -/// let tensor = Tensor::::new(Some(&[1, 2, 3, 4]), &[2, 2]).unwrap(); +/// let tensor = Tensor::::new(Some(&[1, 2, 3, 4]), &[2, 2]).unwrap(); /// let result = one_hot(&tensor, 5, 2).unwrap(); -/// let expected = Tensor::::new(Some(&[0, 1, 0, 0, 0, +/// let expected = Tensor::::new(Some(&[0, 1, 0, 0, 0, /// 0, 0, 1, 0, 0, /// 0, 0, 0, 1, 0, /// 0, 0, 0, 0, 1]), &[2, 2, 5]).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn one_hot( - tensor: &Tensor, + tensor: &Tensor, num_classes: usize, axis: usize, -) -> Result, TensorError> { +) -> Result, TensorError> { let mut output_dims = tensor.dims().to_vec(); output_dims.insert(axis, num_classes); - let mut output: Tensor = Tensor::new(None, &output_dims)?; + let mut output: Tensor = Tensor::new(None, &output_dims)?; let cartesian_coord = output .dims() @@ -1069,20 +1086,21 @@ pub fn one_hot( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::pad; /// -/// let x = Tensor::::new( +/// let x = Tensor::::new( /// Some(&[5, 2, 3, 0, 4, -1, 3, 1, 6]), /// &[1, 1, 3, 3], /// ).unwrap(); -/// let result = pad::(&x, vec![(0, 0), (0, 0), (1, 1), (1, 1)], 0).unwrap(); -/// let expected = Tensor::::new( +/// let result = pad::(&x, vec![(0, 0), (0, 0), (1, 1), (1, 1)], 0).unwrap(); +/// let expected = Tensor::::new( /// Some(&[0, 0, 0, 0, 0, 0, 5, 2, 3, 0, 0, 0, 4, -1, 0, 0, 3, 1, 6, 0, 0, 0, 0, 0, 0]), /// &[1, 1, 5, 5], /// ).unwrap(); /// assert_eq!(result, expected); /// -/// let result = pad::(&x, vec![(1, 1), (1, 1)], 2).unwrap(); +/// let result = pad::(&x, vec![(1, 1), (1, 1)], 2).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn pad( @@ -1128,37 +1146,38 @@ pub fn pad( /// # Examples /// ``` /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::concat; /// // tested against pytorch outputs for reference :) /// /// // 1D example -/// let x = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); -/// let y = Tensor::::new(Some(&[4, 5, 6]), &[3]).unwrap(); +/// let x = Tensor::::new(Some(&[1, 2, 3]), &[3]).unwrap(); +/// let y = Tensor::::new(Some(&[4, 5, 6]), &[3]).unwrap(); /// let result = concat(&[&x, &y], 0).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[6]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[6]).unwrap(); /// assert_eq!(result, expected); /// /// // 2D example -/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 2]).unwrap(); -/// let y = Tensor::::new(Some(&[7, 8, 9]), &[3, 1]).unwrap(); +/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 2]).unwrap(); +/// let y = Tensor::::new(Some(&[7, 8, 9]), &[3, 1]).unwrap(); /// let result = concat(&[&x, &y], 1).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 7, 3, 4, 8, 5, 6, 9]), &[3, 3]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 7, 3, 4, 8, 5, 6, 9]), &[3, 3]).unwrap(); /// assert_eq!(result, expected); /// /// /// 4D example -/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), &[2, 2, 2, 2]).unwrap(); -/// let y = Tensor::::new(Some(&[17, 18, 19, 20, 21, 22, 23, 14]), &[2, 2, 1, 2]).unwrap(); +/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), &[2, 2, 2, 2]).unwrap(); +/// let y = Tensor::::new(Some(&[17, 18, 19, 20, 21, 22, 23, 14]), &[2, 2, 1, 2]).unwrap(); /// let result = concat(&[&x, &y], 2).unwrap(); -/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 17, 18, 5, 6, 7, 8, 19, 20, 9, 10, 11, 12, 21, 22, 13, 14, 15, 16, 23, 14]), &[2, 2, 3, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 17, 18, 5, 6, 7, 8, 19, 20, 9, 10, 11, 12, 21, 22, 13, 14, 15, 16, 23, 14]), &[2, 2, 3, 2]).unwrap(); /// assert_eq!(result, expected); /// /// /// // 5D example -/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), &[8, 1, 1, 1, 2]).unwrap(); -/// let y = Tensor::::new(Some(&[17, 18, 19, 20, 21, 22, 23, 14]), &[4, 1, 1, 1, 2]).unwrap(); +/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), &[8, 1, 1, 1, 2]).unwrap(); +/// let y = Tensor::::new(Some(&[17, 18, 19, 20, 21, 22, 23, 14]), &[4, 1, 1, 1, 2]).unwrap(); /// let result = concat(&[&x, &y], 0).unwrap(); /// -/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 14]), &[12, 1, 1, 1, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 14]), &[12, 1, 1, 1, 2]).unwrap(); /// assert_eq!(result, expected); /// /// ``` @@ -1230,20 +1249,21 @@ pub fn concat( /// ``` /// // tested against pytorch output /// use ezkl::tensor::Tensor; +/// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::slice; -/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 2]).unwrap(); +/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 2]).unwrap(); /// let result = slice(&x, &0, &1, &2).unwrap(); -/// let expected = Tensor::::new(Some(&[3, 4]), &[1, 2]).unwrap(); +/// let expected = Tensor::::new(Some(&[3, 4]), &[1, 2]).unwrap(); /// assert_eq!(result, expected); /// -/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 2]).unwrap(); +/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6]), &[3, 2]).unwrap(); /// let result = slice(&x, &1, &1, &2).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 4, 6]), &[3, 1]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 4, 6]), &[3, 1]).unwrap(); /// assert_eq!(result, expected); /// -/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 2, 3]).unwrap(); +/// let x = Tensor::::new(Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), &[2, 2, 3]).unwrap(); /// let result = slice(&x, &2, &1, &2).unwrap(); -/// let expected = Tensor::::new(Some(&[2, 5, 8, 11]), &[2, 2, 1]).unwrap(); +/// let expected = Tensor::::new(Some(&[2, 5, 8, 11]), &[2, 2, 1]).unwrap(); /// assert_eq!(result, expected); /// ``` /// @@ -1283,21 +1303,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// /// use ezkl::tensor::ops::nonlinearities::ceil; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[3, 2], /// ).unwrap(); /// let result = ceil(&x, 2.0); - /// let expected = Tensor::::new(Some(&[2, 2, 4, 4, 6, 6]), &[3, 2]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 2, 4, 4, 6, 6]), &[3, 2]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn ceil(a: &Tensor, scale: f64) -> Tensor { + pub fn ceil(a: &Tensor, scale: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale; let rounded = kix.ceil() * scale; - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1309,20 +1330,21 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::floor; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[3, 2], /// ).unwrap(); /// let result = floor(&x, 2.0); - /// let expected = Tensor::::new(Some(&[0, 2, 2, 4, 4, 6]), &[3, 2]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 2, 2, 4, 4, 6]), &[3, 2]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn floor(a: &Tensor, scale: f64) -> Tensor { + pub fn floor(a: &Tensor, scale: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale; let rounded = kix.floor() * scale; - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1334,20 +1356,21 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::round; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[3, 2], /// ).unwrap(); /// let result = round(&x, 2.0); - /// let expected = Tensor::::new(Some(&[2, 2, 4, 4, 6, 6]), &[3, 2]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 2, 4, 4, 6, 6]), &[3, 2]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn round(a: &Tensor, scale: f64) -> Tensor { + pub fn round(a: &Tensor, scale: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale; let rounded = kix.round() * scale; - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1359,20 +1382,21 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::round_half_to_even; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[1, 2, 3, 4, 5, 6]), /// &[3, 2], /// ).unwrap(); /// let result = round_half_to_even(&x, 2.0); - /// let expected = Tensor::::new(Some(&[0, 2, 4, 4, 4, 6]), &[3, 2]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 2, 4, 4, 4, 6]), &[3, 2]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn round_half_to_even(a: &Tensor, scale: f64) -> Tensor { + pub fn round_half_to_even(a: &Tensor, scale: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale; let rounded = kix.round_ties_even() * scale; - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1384,21 +1408,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::pow; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = pow(&x, 1.0, 2.0); - /// let expected = Tensor::::new(Some(&[4, 225, 4, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 225, 4, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn pow(a: &Tensor, scale_input: f64, power: f64) -> Tensor { + pub fn pow(a: &Tensor, scale_input: f64, power: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let kix = scale_input * (kix).powf(power); let rounded = kix.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1409,13 +1434,14 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::kronecker_delta; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = kronecker_delta(&x); - /// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` pub fn kronecker_delta( @@ -1440,38 +1466,39 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::sigmoid; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = sigmoid(&x, 1.0); - /// let expected = Tensor::::new(Some(&[1, 1, 1, 1, 1, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 1, 1, 1, 1, 1]), &[2, 3]).unwrap(); /// /// assert_eq!(result, expected); - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[65536]), /// &[1], /// ).unwrap(); /// let result = sigmoid(&x, 65536.0); - /// let expected = Tensor::::new(Some(&[47911]), &[1]).unwrap(); + /// let expected = Tensor::::new(Some(&[47911]), &[1]).unwrap(); /// assert_eq!(result, expected); /// /// /// assert_eq!(result, expected); - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[256]), /// &[1], /// ).unwrap(); /// let result = sigmoid(&x, 256.0); - /// let expected = Tensor::::new(Some(&[187]), &[1]).unwrap(); + /// let expected = Tensor::::new(Some(&[187]), &[1]).unwrap(); /// /// ``` - pub fn sigmoid(a: &Tensor, scale_input: f64) -> Tensor { + pub fn sigmoid(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input / (1.0 + (-kix).exp()); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1490,18 +1517,19 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::hardswish; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[-12, -3, 2, 1, 1, 15]), /// &[2, 3], /// ).unwrap(); /// let result = hardswish(&x, 1.0); - /// let expected = Tensor::::new(Some(&[0, 0, 2, 1, 1, 15]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 0, 2, 1, 1, 15]), &[2, 3]).unwrap(); /// /// assert_eq!(result, expected); /// /// ``` - pub fn hardswish(a: &Tensor, scale_input: f64) -> Tensor { + pub fn hardswish(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let res = if kix <= -3.0 { @@ -1512,7 +1540,7 @@ pub mod nonlinearities { kix * (kix + 3.0) / 6.0 }; let rounded = (res * scale_input).round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1526,32 +1554,33 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::exp; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = exp(&x, 1.0); - /// let expected = Tensor::::new(Some(&[7, 3269017, 7, 3, 3, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[7, 3269017, 7, 3, 3, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[37, 12, 41]), /// &[3], /// ).unwrap(); /// let result = exp(&x, 512.0); /// - /// let expected = Tensor::::new(Some(&[550, 524, 555]), &[3]).unwrap(); + /// let expected = Tensor::::new(Some(&[550, 524, 555]), &[3]).unwrap(); /// /// assert_eq!(result, expected); /// ``` - pub fn exp(a: &Tensor, scale_input: f64) -> Tensor { + pub fn exp(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.exp(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1565,32 +1594,33 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::ln; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 3000]), /// &[2, 3], /// ).unwrap(); /// let result = ln(&x, 1.0); - /// let expected = Tensor::::new(Some(&[1, 3, 1, 0, 0, 8]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 3, 1, 0, 0, 8]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// /// - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[37, 12, 41]), /// &[3], /// ).unwrap(); /// let result = ln(&x, 512.0); /// - /// let expected = Tensor::::new(Some(&[-1345, -1922, -1293]), &[3]).unwrap(); + /// let expected = Tensor::::new(Some(&[-1345, -1922, -1293]), &[3]).unwrap(); /// /// assert_eq!(result, expected); /// ``` - pub fn ln(a: &Tensor, scale_input: f64) -> Tensor { + pub fn ln(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.ln(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1601,16 +1631,17 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::sign; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[-2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = sign(&x); - /// let expected = Tensor::::new(Some(&[-1, 1, 1, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[-1, 1, 1, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn sign(a: &Tensor) -> Tensor { + pub fn sign(a: &Tensor) -> Tensor { a.par_enum_map(|_, a_i| Ok::<_, TensorError>(a_i.signum())) .unwrap() } @@ -1624,21 +1655,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::sqrt; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = sqrt(&x, 1.0); - /// let expected = Tensor::::new(Some(&[2, 5, 3, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 5, 3, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn sqrt(a: &Tensor, scale_input: f64) -> Tensor { + pub fn sqrt(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.sqrt(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1652,21 +1684,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::rsqrt; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let result = rsqrt(&x, 1.0); - /// let expected = Tensor::::new(Some(&[1, 0, 0, 1, 1, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 0, 0, 1, 1, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn rsqrt(a: &Tensor, scale_input: f64) -> Tensor { + pub fn rsqrt(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input / (kix.sqrt() + f64::EPSILON); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1679,21 +1712,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::cos; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = cos(&x, 2.0); - /// let expected = Tensor::::new(Some(& [-1, 2, -1, 2, 2, 2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(& [-1, 2, -1, 2, 2, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn cos(a: &Tensor, scale_input: f64) -> Tensor { + pub fn cos(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.cos(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1706,21 +1740,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::acos; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = acos(&x, 1.0); - /// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 0, 0, 0, 0, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn acos(a: &Tensor, scale_input: f64) -> Tensor { + pub fn acos(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.acos(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1733,21 +1768,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::cosh; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = cosh(&x, 1.0); - /// let expected = Tensor::::new(Some(&[27, 36002449669, 1490, 2, 2, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[27, 36002449669, 1490, 2, 2, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn cosh(a: &Tensor, scale_input: f64) -> Tensor { + pub fn cosh(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.cosh(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1760,21 +1796,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::acosh; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = acosh(&x, 1.0); - /// let expected = Tensor::::new(Some(& [2, 4, 3, 0, 0, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(& [2, 4, 3, 0, 0, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn acosh(a: &Tensor, scale_input: f64) -> Tensor { + pub fn acosh(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.acosh(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1787,21 +1824,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::sin; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = sin(&x, 128.0); - /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn sin(a: &Tensor, scale_input: f64) -> Tensor { + pub fn sin(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.sin(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1814,21 +1852,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::asin; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = asin(&x, 128.0); - /// let expected = Tensor::::new(Some(& [4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(& [4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn asin(a: &Tensor, scale_input: f64) -> Tensor { + pub fn asin(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.asin(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1841,21 +1880,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::sinh; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = sinh(&x, 2.0); - /// let expected = Tensor::::new(Some(&[7, 268337, 55, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[7, 268337, 55, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn sinh(a: &Tensor, scale_input: f64) -> Tensor { + pub fn sinh(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.sinh(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1868,21 +1908,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::asinh; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = asinh(&x, 128.0); - /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn asinh(a: &Tensor, scale_input: f64) -> Tensor { + pub fn asinh(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.asinh(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1895,21 +1936,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::tan; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = tan(&x, 64.0); - /// let expected = Tensor::::new(Some(&[4, 26, 8, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 26, 8, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn tan(a: &Tensor, scale_input: f64) -> Tensor { + pub fn tan(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.tan(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1922,21 +1964,22 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::atan; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = atan(&x, 128.0); - /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn atan(a: &Tensor, scale_input: f64) -> Tensor { + pub fn atan(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.atan(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1950,22 +1993,23 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::tanh; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = tanh(&x, 128.0); - /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 25, 8, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn tanh(a: &Tensor, scale_input: f64) -> Tensor { + pub fn tanh(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.tanh(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -1979,22 +2023,23 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::atanh; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[4, 25, 8, 2, 2, 0]), /// &[2, 3], /// ).unwrap(); /// let result = atanh(&x, 32.0); - /// let expected = Tensor::::new(Some(&[4, 34, 8, 2, 2, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[4, 34, 8, 2, 2, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn atanh(a: &Tensor, scale_input: f64) -> Tensor { + pub fn atanh(a: &Tensor, scale_input: f64) -> Tensor { a.par_enum_map(|_, a_i| { let kix = (a_i as f64) / scale_input; let fout = scale_input * kix.atanh(); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -2008,16 +2053,17 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::erffunc; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[5, 28, 9, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = erffunc(&x, 128.0); - /// let expected = Tensor::::new(Some(&[6, 31, 10, 1, 1, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[6, 31, 10, 1, 1, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn erffunc(a: &Tensor, scale_input: f64) -> Tensor { + pub fn erffunc(a: &Tensor, scale_input: f64) -> Tensor { const NCOEF: usize = 28; const COF: [f64; 28] = [ -1.3026537197817094, @@ -2078,7 +2124,7 @@ pub mod nonlinearities { let kix = (a_i as f64) / scale_input; let fout = scale_input * erf(kix); let rounded = fout.round(); - Ok::<_, TensorError>(rounded as i64) + Ok::<_, TensorError>(rounded as IntegerRep) }) .unwrap() } @@ -2092,23 +2138,24 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::leakyrelu; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, -5]), /// &[2, 3], /// ).unwrap(); /// let result = leakyrelu(&x, 0.1); - /// let expected = Tensor::::new(Some(&[2, 15, 2, 1, 1, -1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 15, 2, 1, 1, -1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn leakyrelu(a: &Tensor, slope: f64) -> Tensor { + pub fn leakyrelu(a: &Tensor, slope: f64) -> Tensor { a.par_enum_map(|_, a_i| { let rounded = if a_i < 0 { let d_inv_x = (slope) * (a_i as f64); - d_inv_x.round() as i64 + d_inv_x.round() as IntegerRep } else { let d_inv_x = a_i as f64; - d_inv_x.round() as i64 + d_inv_x.round() as IntegerRep }; Ok::<_, TensorError>(rounded) }) @@ -2122,23 +2169,24 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::max; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, -5]), /// &[2, 3], /// ).unwrap(); /// let result = max(&x, 1.0, 1.0); - /// let expected = Tensor::::new(Some(&[2, 15, 2, 1, 1, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 15, 2, 1, 1, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn max(a: &Tensor, scale_input: f64, threshold: f64) -> Tensor { + pub fn max(a: &Tensor, scale_input: f64, threshold: f64) -> Tensor { // calculate value of output a.par_enum_map(|_, a_i| { let d_inv_x = (a_i as f64) / scale_input; let rounded = if d_inv_x <= threshold { - (threshold * scale_input).round() as i64 + (threshold * scale_input).round() as IntegerRep } else { - (d_inv_x * scale_input).round() as i64 + (d_inv_x * scale_input).round() as IntegerRep }; Ok::<_, TensorError>(rounded) }) @@ -2152,23 +2200,24 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::min; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, -5]), /// &[2, 3], /// ).unwrap(); /// let result = min(&x, 1.0, 2.0); - /// let expected = Tensor::::new(Some(&[2, 2, 2, 1, 1, -5]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[2, 2, 2, 1, 1, -5]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn min(a: &Tensor, scale_input: f64, threshold: f64) -> Tensor { + pub fn min(a: &Tensor, scale_input: f64, threshold: f64) -> Tensor { // calculate value of output a.par_enum_map(|_, a_i| { let d_inv_x = (a_i as f64) / scale_input; let rounded = if d_inv_x >= threshold { - (threshold * scale_input).round() as i64 + (threshold * scale_input).round() as IntegerRep } else { - (d_inv_x * scale_input).round() as i64 + (d_inv_x * scale_input).round() as IntegerRep }; Ok::<_, TensorError>(rounded) }) @@ -2183,20 +2232,21 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::const_div; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 7, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let k = 2.0; /// let result = const_div(&x, k); - /// let expected = Tensor::::new(Some(&[1, 1, 1, 4, 1, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 1, 1, 4, 1, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn const_div(a: &Tensor, denom: f64) -> Tensor { + pub fn const_div(a: &Tensor, denom: f64) -> Tensor { a.par_enum_map(|_, a_i| { let d_inv_x = (a_i as f64) / (denom); - Ok::<_, TensorError>(d_inv_x.round() as i64) + Ok::<_, TensorError>(d_inv_x.round() as IntegerRep) }) .unwrap() } @@ -2209,22 +2259,23 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::recip; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 7, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let k = 2_f64; /// let result = recip(&x, 1.0, k); - /// let expected = Tensor::::new(Some(&[1, 2, 1, 0, 2, 2]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 2, 1, 0, 2, 2]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn recip(a: &Tensor, input_scale: f64, out_scale: f64) -> Tensor { + pub fn recip(a: &Tensor, input_scale: f64, out_scale: f64) -> Tensor { a.par_enum_map(|_, a_i| { let rescaled = (a_i as f64) / input_scale; let denom = (1_f64) / (rescaled + f64::EPSILON); let d_inv_x = out_scale * denom; - Ok::<_, TensorError>(d_inv_x.round() as i64) + Ok::<_, TensorError>(d_inv_x.round() as IntegerRep) }) .unwrap() } @@ -2235,20 +2286,21 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::zero_recip; /// let k = 2_f64; /// let result = zero_recip(1.0); - /// let expected = Tensor::::new(Some(&[4503599627370496]), &[1]).unwrap(); + /// let expected = Tensor::::new(Some(&[4503599627370496]), &[1]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn zero_recip(out_scale: f64) -> Tensor { - let a = Tensor::::new(Some(&[0]), &[1]).unwrap(); + pub fn zero_recip(out_scale: f64) -> Tensor { + let a = Tensor::::new(Some(&[0]), &[1]).unwrap(); a.par_enum_map(|_, a_i| { let rescaled = a_i as f64; let denom = (1_f64) / (rescaled + f64::EPSILON); let d_inv_x = out_scale * denom; - Ok::<_, TensorError>(d_inv_x.round() as i64) + Ok::<_, TensorError>(d_inv_x.round() as IntegerRep) }) .unwrap() } @@ -2261,18 +2313,19 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::greater_than; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 7, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let k = 2.0; /// let result = greater_than(&x, k); - /// let expected = Tensor::::new(Some(&[0, 0, 0, 1, 0, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 0, 0, 1, 0, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn greater_than(a: &Tensor, b: f64) -> Tensor { - a.par_enum_map(|_, a_i| Ok::<_, TensorError>(i64::from((a_i as f64 - b) > 0_f64))) + pub fn greater_than(a: &Tensor, b: f64) -> Tensor { + a.par_enum_map(|_, a_i| Ok::<_, TensorError>(IntegerRep::from((a_i as f64 - b) > 0_f64))) .unwrap() } @@ -2284,18 +2337,19 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::greater_than_equal; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 7, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let k = 2.0; /// let result = greater_than_equal(&x, k); - /// let expected = Tensor::::new(Some(&[1, 0, 1, 1, 0, 0]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 0, 1, 1, 0, 0]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn greater_than_equal(a: &Tensor, b: f64) -> Tensor { - a.par_enum_map(|_, a_i| Ok::<_, TensorError>(i64::from((a_i as f64 - b) >= 0_f64))) + pub fn greater_than_equal(a: &Tensor, b: f64) -> Tensor { + a.par_enum_map(|_, a_i| Ok::<_, TensorError>(IntegerRep::from((a_i as f64 - b) >= 0_f64))) .unwrap() } @@ -2306,20 +2360,21 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::less_than; /// - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 7, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let k = 2.0; /// /// let result = less_than(&x, k); - /// let expected = Tensor::::new(Some(&[0, 1, 0, 0, 1, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[0, 1, 0, 0, 1, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn less_than(a: &Tensor, b: f64) -> Tensor { - a.par_enum_map(|_, a_i| Ok::<_, TensorError>(i64::from((a_i as f64 - b) < 0_f64))) + pub fn less_than(a: &Tensor, b: f64) -> Tensor { + a.par_enum_map(|_, a_i| Ok::<_, TensorError>(IntegerRep::from((a_i as f64 - b) < 0_f64))) .unwrap() } @@ -2330,20 +2385,21 @@ pub mod nonlinearities { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::nonlinearities::less_than_equal; /// - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 1, 2, 7, 1, 1]), /// &[2, 3], /// ).unwrap(); /// let k = 2.0; /// /// let result = less_than_equal(&x, k); - /// let expected = Tensor::::new(Some(&[1, 1, 1, 0, 1, 1]), &[2, 3]).unwrap(); + /// let expected = Tensor::::new(Some(&[1, 1, 1, 0, 1, 1]), &[2, 3]).unwrap(); /// assert_eq!(result, expected); /// ``` - pub fn less_than_equal(a: &Tensor, b: f64) -> Tensor { - a.par_enum_map(|_, a_i| Ok::<_, TensorError>(i64::from((a_i as f64 - b) <= 0_f64))) + pub fn less_than_equal(a: &Tensor, b: f64) -> Tensor { + a.par_enum_map(|_, a_i| Ok::<_, TensorError>(IntegerRep::from((a_i as f64 - b) <= 0_f64))) .unwrap() } } @@ -2359,17 +2415,18 @@ pub mod accumulated { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::accumulated::dot; /// - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[5, 2]), /// &[2], /// ).unwrap(); - /// let y = Tensor::::new( + /// let y = Tensor::::new( /// Some(&[5, 5]), /// &[2], /// ).unwrap(); - /// let expected = Tensor::::new( + /// let expected = Tensor::::new( /// Some(&[25, 35]), /// &[2], /// ).unwrap(); @@ -2408,13 +2465,14 @@ pub mod accumulated { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::accumulated::sum; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = sum(&x, 1).unwrap(); - /// let expected = Tensor::::new( + /// let expected = Tensor::::new( /// Some(&[2, 17, 19, 20, 21, 21]), /// &[6], /// ).unwrap(); @@ -2445,13 +2503,14 @@ pub mod accumulated { /// # Examples /// ``` /// use ezkl::tensor::Tensor; + /// use ezkl::fieldutils::IntegerRep; /// use ezkl::tensor::ops::accumulated::prod; - /// let x = Tensor::::new( + /// let x = Tensor::::new( /// Some(&[2, 15, 2, 1, 1, 0]), /// &[2, 3], /// ).unwrap(); /// let result = prod(&x, 1).unwrap(); - /// let expected = Tensor::::new( + /// let expected = Tensor::::new( /// Some(&[2, 30, 60, 60, 60, 0]), /// &[6], /// ).unwrap(); diff --git a/src/tensor/val.rs b/src/tensor/val.rs index f79d74005..f9bd6d29d 100644 --- a/src/tensor/val.rs +++ b/src/tensor/val.rs @@ -1,4 +1,4 @@ -use crate::circuit::region::ConstantsMap; +use crate::{circuit::region::ConstantsMap, fieldutils::felt_to_integer_rep}; use maybe_rayon::slice::Iter; use super::{ @@ -54,6 +54,44 @@ pub enum ValType, F), } +impl From> for IntegerRep { + fn from(val: ValType) -> Self { + match val { + ValType::Value(v) => { + let mut output = 0; + let mut i = 0; + v.map(|y| { + let e = felt_to_integer_rep(y); + output = e; + i += 1; + }); + output + } + ValType::AssignedValue(v) => { + let mut output = 0; + let mut i = 0; + v.evaluate().map(|y| { + let e = felt_to_integer_rep(y); + output = e; + i += 1; + }); + output + } + ValType::PrevAssigned(v) | ValType::AssignedConstant(v, ..) => { + let mut output = 0; + let mut i = 0; + v.value().map(|y| { + let e = felt_to_integer_rep(*y); + output = e; + i += 1; + }); + output + } + ValType::Constant(v) => felt_to_integer_rep(v), + } + } +} + impl ValType { /// Returns the inner cell of the [ValType]. pub fn cell(&self) -> Option { @@ -121,44 +159,6 @@ impl From> for i32 { - fn from(val: ValType) -> Self { - match val { - ValType::Value(v) => { - let mut output = 0_i32; - let mut i = 0; - v.map(|y| { - let e = felt_to_i32(y); - output = e; - i += 1; - }); - output - } - ValType::AssignedValue(v) => { - let mut output = 0_i32; - let mut i = 0; - v.evaluate().map(|y| { - let e = felt_to_i32(y); - output = e; - i += 1; - }); - output - } - ValType::PrevAssigned(v) | ValType::AssignedConstant(v, ..) => { - let mut output = 0_i32; - let mut i = 0; - v.value().map(|y| { - let e = felt_to_i32(*y); - output = e; - i += 1; - }); - output - } - ValType::Constant(v) => felt_to_i32(v), - } - } -} - impl From for ValType { fn from(t: F) -> ValType { ValType::Constant(t) @@ -317,8 +317,8 @@ impl From>> f impl ValTensor { /// Allocate a new [ValTensor::Value] from the given [Tensor] of [i64]. - pub fn from_i64_tensor(t: Tensor) -> ValTensor { - let inner = t.map(|x| ValType::Value(Value::known(i64_to_felt(x)))); + pub fn from_integer_rep_tensor(t: Tensor) -> ValTensor { + let inner = t.map(|x| ValType::Value(Value::known(integer_rep_to_felt(x)))); inner.into() } @@ -521,9 +521,9 @@ impl ValTensor { } /// Calls `int_evals` on the inner tensor. - pub fn get_int_evals(&self) -> Result, TensorError> { + pub fn int_evals(&self) -> Result, TensorError> { // finally convert to vector of integers - let mut integer_evals: Vec = vec![]; + let mut integer_evals: Vec = vec![]; match self { ValTensor::Value { inner: v, dims: _, .. @@ -531,25 +531,26 @@ impl ValTensor { // we have to push to an externally created vector or else vaf.map() returns an evaluation wrapped in Value<> (which we don't want) let _ = v.map(|vaf| match vaf { ValType::Value(v) => v.map(|f| { - integer_evals.push(crate::fieldutils::felt_to_i64(f)); + integer_evals.push(crate::fieldutils::felt_to_integer_rep(f)); }), ValType::AssignedValue(v) => v.map(|f| { - integer_evals.push(crate::fieldutils::felt_to_i64(f.evaluate())); + integer_evals.push(crate::fieldutils::felt_to_integer_rep(f.evaluate())); }), ValType::PrevAssigned(v) | ValType::AssignedConstant(v, ..) => { v.value_field().map(|f| { - integer_evals.push(crate::fieldutils::felt_to_i64(f.evaluate())); + integer_evals + .push(crate::fieldutils::felt_to_integer_rep(f.evaluate())); }) } ValType::Constant(v) => { - integer_evals.push(crate::fieldutils::felt_to_i64(v)); + integer_evals.push(crate::fieldutils::felt_to_integer_rep(v)); Value::unknown() } }); } _ => return Err(TensorError::WrongMethod), }; - let mut tensor: Tensor = integer_evals.into_iter().into(); + let mut tensor: Tensor = integer_evals.into_iter().into(); match tensor.reshape(self.dims()) { _ => {} }; @@ -1002,25 +1003,22 @@ impl ValTensor { } /// A [String] representation of the [ValTensor] for display, for example in showing intermediate values in a computational graph. pub fn show(&self) -> String { - match self.clone() { - ValTensor::Value { - inner: v, dims: _, .. - } => { - let r: Tensor = v.map(|x| x.into()); - if r.len() > 10 { - let start = r[..5].to_vec(); - let end = r[r.len() - 5..].to_vec(); - // print the two split by ... in the middle - format!( - "[{} ... {}]", - start.iter().map(|x| format!("{}", x)).join(", "), - end.iter().map(|x| format!("{}", x)).join(", ") - ) - } else { - format!("{:?}", r) - } - } - _ => "ValTensor not PrevAssigned".into(), + let r = match self.int_evals() { + Ok(v) => v, + Err(_) => return "ValTensor not PrevAssigned".into(), + }; + + if r.len() > 10 { + let start = r[..5].to_vec(); + let end = r[r.len() - 5..].to_vec(); + // print the two split by ... in the middle + format!( + "[{} ... {}]", + start.iter().map(|x| format!("{}", x)).join(", "), + end.iter().map(|x| format!("{}", x)).join(", ") + ) + } else { + format!("{:?}", r) } } } diff --git a/src/tensor/var.rs b/src/tensor/var.rs index e007807a6..677c855c6 100644 --- a/src/tensor/var.rs +++ b/src/tensor/var.rs @@ -368,7 +368,7 @@ impl VarTensor { .sum::(); let dims = &dims[*idx]; // this should never ever fail - let t: Tensor = Tensor::new(None, dims).unwrap(); + let t: Tensor = Tensor::new(None, dims).unwrap(); Ok(t.enum_map(|coord, _| { let (x, y, z) = self.cartesian_coord(offset + coord); region.assign_advice_from_instance( @@ -497,7 +497,7 @@ impl VarTensor { let (x, y, z) = self.cartesian_coord(offset + coord * step); if matches!(check_mode, CheckMode::SAFE) && coord > 0 && z == 0 && y == 0 { // assert that duplication occurred correctly - assert_eq!(Into::::into(k.clone()), Into::::into(v[coord - 1].clone())); + assert_eq!(Into::::into(k.clone()), Into::::into(v[coord - 1].clone())); }; let cell = self.assign_value(region, offset, k.clone(), coord * step, constants)?; @@ -533,13 +533,14 @@ impl VarTensor { if matches!(check_mode, CheckMode::SAFE) { // during key generation this will be 0 so we use this as a flag to check // TODO: this isn't very safe and would be better to get the phase directly - let is_assigned = !Into::>::into(res.clone().get_inner().unwrap()) + let res_evals = res.int_evals().unwrap(); + let is_assigned = res_evals .iter() .all(|&x| x == 0); - if is_assigned { + if !is_assigned { assert_eq!( - Into::>::into(values.get_inner().unwrap()), - Into::>::into(res.get_inner().unwrap()) + values.int_evals().unwrap(), + res_evals )}; } diff --git a/src/wasm.rs b/src/wasm.rs index 4a3edfa16..5d8bad90d 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,41 +1,52 @@ -use crate::circuit::modules::polycommit::PolyCommitChip; -use crate::circuit::modules::poseidon::spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}; -use crate::circuit::modules::poseidon::PoseidonChip; -use crate::circuit::modules::Module; -use crate::fieldutils::felt_to_i64; -use crate::fieldutils::i64_to_felt; -use crate::graph::modules::POSEIDON_LEN_GRAPH; -use crate::graph::quantize_float; -use crate::graph::scale_to_multiplier; -use crate::graph::{GraphCircuit, GraphSettings}; -use crate::pfsys::create_proof_circuit; -use crate::pfsys::evm::aggregation_kzg::AggregationCircuit; -use crate::pfsys::evm::aggregation_kzg::PoseidonTranscript; -use crate::pfsys::verify_proof_circuit; -use crate::pfsys::TranscriptType; -use crate::tensor::TensorType; -use crate::CheckMode; -use crate::Commitments; -use console_error_panic_hook; -use halo2_proofs::plonk::*; -use halo2_proofs::poly::commitment::{CommitmentScheme, ParamsProver}; -use halo2_proofs::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; -use halo2_proofs::poly::ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - strategy::SingleStrategy as IPASingleStrategy, +use crate::{ + circuit::{ + modules::{ + polycommit::PolyCommitChip, + poseidon::{ + spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}, + PoseidonChip, + }, + Module, + }, + region::RegionSettings, + }, + fieldutils::{felt_to_integer_rep, integer_rep_to_felt}, + graph::{ + modules::POSEIDON_LEN_GRAPH, quantize_float, scale_to_multiplier, GraphCircuit, + GraphSettings, + }, + pfsys::{ + create_proof_circuit, + evm::aggregation_kzg::{AggregationCircuit, PoseidonTranscript}, + verify_proof_circuit, TranscriptType, + }, + tensor::TensorType, + CheckMode, Commitments, }; -use halo2_proofs::poly::kzg::multiopen::ProverSHPLONK; -use halo2_proofs::poly::kzg::multiopen::VerifierSHPLONK; -use halo2_proofs::poly::kzg::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - strategy::SingleStrategy as KZGSingleStrategy, +use console_error_panic_hook; +use halo2_proofs::{ + plonk::*, + poly::{ + commitment::{CommitmentScheme, ParamsProver}, + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + multiopen::{ProverIPA, VerifierIPA}, + strategy::SingleStrategy as IPASingleStrategy, + }, + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy as KZGSingleStrategy, + }, + VerificationStrategy, + }, }; -use halo2_proofs::poly::VerificationStrategy; use halo2_solidity_verifier::encode_calldata; -use halo2curves::bn256::{Bn256, Fr, G1Affine}; -use halo2curves::ff::{FromUniformBytes, PrimeField}; -use snark_verifier::loader::native::NativeLoader; -use snark_verifier::system::halo2::transcript::evm::EvmTranscript; +use halo2curves::{ + bn256::{Bn256, Fr, G1Affine}, + ff::{FromUniformBytes, PrimeField}, +}; +use snark_verifier::{loader::native::NativeLoader, system::halo2::transcript::evm::EvmTranscript}; use std::str::FromStr; use wasm_bindgen::prelude::*; use wasm_bindgen_console_logger::DEFAULT_LOGGER; @@ -113,7 +124,7 @@ pub fn feltToInt( let felt: Fr = serde_json::from_slice(&array[..]) .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; Ok(wasm_bindgen::Clamped( - serde_json::to_vec(&felt_to_i64(felt)) + serde_json::to_vec(&felt_to_integer_rep(felt)) .map_err(|e| JsError::new(&format!("Failed to serialize integer: {}", e)))?, )) } @@ -127,7 +138,7 @@ pub fn feltToFloat( ) -> Result { let felt: Fr = serde_json::from_slice(&array[..]) .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; - let int_rep = felt_to_i64(felt); + let int_rep = felt_to_integer_rep(felt); let multiplier = scale_to_multiplier(scale); Ok(int_rep as f64 / multiplier) } @@ -141,7 +152,7 @@ pub fn floatToFelt( ) -> Result>, JsError> { let int_rep = quantize_float(&input, 0.0, scale).map_err(|e| JsError::new(&format!("{}", e)))?; - let felt = i64_to_felt(int_rep); + let felt = integer_rep_to_felt(int_rep); let vec = crate::pfsys::field_to_string::(&felt); Ok(wasm_bindgen::Clamped(serde_json::to_vec(&vec).map_err( |e| JsError::new(&format!("Failed to serialize a float to felt{}", e)), @@ -275,7 +286,7 @@ pub fn genWitness( .map_err(|e| JsError::new(&format!("{}", e)))?; let witness = circuit - .forward::>(&mut input, None, None, false, false) + .forward::>(&mut input, None, None, RegionSettings::all_false()) .map_err(|e| JsError::new(&format!("{}", e)))?; serde_json::to_vec(&witness) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 33e9206e2..097fd8d78 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3,7 +3,7 @@ mod native_tests { use ezkl::circuit::Tolerance; - use ezkl::fieldutils::{felt_to_i64, i64_to_felt}; + use ezkl::fieldutils::{felt_to_integer_rep, integer_rep_to_felt, IntegerRep}; // use ezkl::circuit::table::RESERVED_BLINDING_ROWS_PAD; use ezkl::graph::input::{FileSource, FileSourceInner, GraphData}; use ezkl::graph::{DataSource, GraphSettings, GraphWitness}; @@ -646,6 +646,15 @@ mod native_tests { test_dir.close().unwrap(); } + #(#[test_case(TESTS[N])])* + fn mock_hashed_params_public_inputs_(test: &str) { + crate::native_tests::init_binary(); + let test_dir = TempDir::new(test).unwrap(); + let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); + mock(path, test.to_string(), "public", "hashed", "private", 1, "resources", None, 0.0); + test_dir.close().unwrap(); + } + #(#[test_case(TESTS[N])])* fn mock_fixed_inputs_(test: &str) { crate::native_tests::init_binary(); @@ -1413,10 +1422,10 @@ mod native_tests { let perturbation = if v == &halo2curves::bn256::Fr::zero() { halo2curves::bn256::Fr::zero() } else { - i64_to_felt( - (felt_to_i64(*v) as f32 + integer_rep_to_felt( + (felt_to_integer_rep(*v) as f32 * (rand::thread_rng().gen_range(-0.01..0.01) * tolerance)) - as i64, + as IntegerRep, ) }; @@ -1436,10 +1445,10 @@ mod native_tests { let perturbation = if v == &halo2curves::bn256::Fr::zero() { halo2curves::bn256::Fr::from(2) } else { - i64_to_felt( - (felt_to_i64(*v) as f32 + integer_rep_to_felt( + (felt_to_integer_rep(*v) as f32 * (rand::thread_rng().gen_range(0.02..0.1) * tolerance)) - as i64, + as IntegerRep, ) }; *v + perturbation diff --git a/tests/wasm/model.compiled b/tests/wasm/model.compiled index 9e6eeeeedd8c603df3ddc12b255c31af24e90b3c..d36215c15add6fdcdc7978318007db1c7e032438 100644 GIT binary patch delta 32 dcmbQpyOVdrTNZ);P{05Mo8Pi5VVrnC0ss$64!{5a delta 12 UcmdnVJCS$8Tb9j#S(Y#Y03<;LUjP6A diff --git a/tests/wasm/pk.key b/tests/wasm/pk.key index 4c8101be93f280b9a960d13674d83bf5f7366c1b..27ae1b13f0af558ff595f84e2947f26d3d3bd3b6 100644 GIT binary patch delta 31323 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 @+QW55r3ve3MI_w=9T3x)lume-nVR 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 G0|X%CF5eFT delta 31148 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 Q#FT8?n570hCzD46z}??vSO5S3