Skip to content

Commit

Permalink
test: Enhance Num<Fr> testing (#563)
Browse files Browse the repository at this point in the history
* test: Enhance `Num<Fr>` testing

- Enhanced property testing coverage and introduced new tests for `U64` and `Scalar` types.
- Provided comprehensive test scenarios including basic operations, assertions, checking sign, and "lesser" properties.
- Improved coverage for scalars and u64, encompassing overflow/underflow cases and edge conditions.

Closes #52

* refactor: Refine numeric comparison in `prop_lesser` function

- add a direct comparison from #738
  • Loading branch information
huitseeker authored Nov 10, 2023
1 parent 50491ed commit fe70ad3
Showing 1 changed file with 201 additions and 136 deletions.
337 changes: 201 additions & 136 deletions src/num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ impl<Fr: LurkField> Arbitrary for Num<Fr> {
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<FWrap<Fr>>()
.prop_map(|f| match f.0.to_u64() {
Some(x) => Self::U64(x),
None => Self::Scalar(f.0),
})
.boxed()
prop_oneof![
any::<u64>().prop_map(Num::U64),
any::<FWrap<Fr>>().prop_map(|f| Num::Scalar(f.0)),
]
.boxed()
}
}

Expand Down Expand Up @@ -291,144 +290,210 @@ impl<'de, F: LurkField> Deserialize<'de> for Num<F> {
#[cfg(test)]
mod tests {
use super::*;
use nom::ToUsize;
use proptest::proptest;

use ff::Field;
use pasta_curves::pallas::Scalar;
use pasta_curves::pallas::Scalar as Fr;

#[test]
fn test_add_assign() {
// u64 - u64 - no overflow
let mut a = Num::<Scalar>::U64(5);
a += Num::from(10);
assert_eq!(a, Num::from(15));

// u64 - u64 - overflow
let mut a = Num::from(u64::MAX);
a += Num::from(10);
assert_eq!(a, Num::Scalar(Scalar::from(u64::MAX) + Scalar::from(10)));

// scalar - scalar - no overflow
let mut a = Num::Scalar(Scalar::from(5));
a += Num::Scalar(Scalar::from(10));
assert_eq!(a, Num::Scalar(Scalar::from(5) + Scalar::from(10)));

// scalar - u64 - overflow
let mut a = Num::Scalar(Scalar::from(5));
a += Num::from(u64::MAX);
assert_eq!(a, Num::Scalar(Scalar::from(5) + Scalar::from(u64::MAX)));

// scalar - u64 - overflow
let mut a = Num::from(u64::MAX);
a += Num::Scalar(Scalar::from(5));
assert_eq!(a, Num::Scalar(Scalar::from(5) + Scalar::from(u64::MAX)));
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 1000,
.. ProptestConfig::default()
})]


#[test]
fn prop_add_assign(a in any::<Num<Fr>>(), b in any::<Num<Fr>>()){
let mut c = a;
c += b;

match (a, b) {
(Num::U64(x1), Num::U64(x2)) => {
// does this overflow?
if x1.checked_add(x2).is_none() {
assert_eq!(c, Num::Scalar(Scalar::from(x1) + Scalar::from(x2)));
} else {
assert_eq!(c, Num::U64(x1 + x2));
}
},
(Num::Scalar(x1), Num::U64(x2)) | (Num::U64(x2), Num::Scalar(x1)) => {
assert_eq!(c, Num::Scalar(x1 + Scalar::from(x2)));
},
(Num::Scalar(x1), Num::Scalar(x2)) => {
assert_eq!(c, Num::Scalar(x1 + x2));
}
}
}

#[test]
fn test_sub_assign() {
// u64 - u64 - no overflow
let mut a = Num::<Scalar>::U64(10);
a -= Num::U64(5);
assert_eq!(a, Num::U64(5));

// u64 - u64 - overflow
let mut a = Num::U64(0);
a -= Num::U64(10);
assert_eq!(a, Num::Scalar(Scalar::from(0) - Scalar::from(10)));

// scalar - scalar - no overflow
let mut a = Num::Scalar(Scalar::from(10));
a -= Num::Scalar(Scalar::from(5));
assert_eq!(a, Num::Scalar(Scalar::from(10) - Scalar::from(5)));

// scalar - u64 - overflow
let mut a = Num::Scalar(Scalar::from(5));
a -= Num::U64(10);
assert_eq!(a, Num::Scalar(Scalar::from(5) - Scalar::from(10)));

// scalar - u64 - no overflow
let mut a = Num::Scalar(Scalar::from(10));
a -= Num::U64(5);
assert_eq!(a, Num::Scalar(Scalar::from(10) - Scalar::from(5)));

// scalar - u64 - overflow
let mut a = Num::U64(5);
a -= Num::Scalar(Scalar::from(10));
assert_eq!(a, Num::Scalar(Scalar::from(5) - Scalar::from(10)));

// scalar - u64 - no overflow
let mut a = Num::U64(10);
a -= Num::Scalar(Scalar::from(5));
assert_eq!(a, Num::Scalar(Scalar::from(10) - Scalar::from(5)));
}
#[test]
fn prop_sub_assign(a in any::<Num<Fr>>(), b in any::<Num<Fr>>()) {
let mut c = a;
c -= b;

match (a, b) {
(Num::U64(x1), Num::U64(x2)) => {
// does this underflow?
if x1.to_usize().checked_sub(x2.to_usize()).is_none(){
assert_eq!(c, Num::Scalar(Scalar::from(x1) - Scalar::from(x2)));
} else {
assert_eq!(c, Num::U64(x1 - x2));
}
},
(Num::Scalar(x1), Num::U64(x2)) => {
assert_eq!(c, Num::Scalar(x1 - Scalar::from(x2)));
},
(Num::U64(x1), Num::Scalar(x2)) => {
assert_eq!(c, Num::Scalar(Scalar::from(x1) - x2));
},
(Num::Scalar(x1), Num::Scalar(x2)) => {
assert_eq!(c, Num::Scalar(x1 - x2));
}
}
}

#[test]
fn test_mul_assign() {
// u64 - u64 - no overflow
let mut a = Num::<Scalar>::U64(5);
a *= Num::U64(10);
assert_eq!(a, Num::U64(5 * 10));

// u64 - u64 - overflow
let mut a = Num::U64(u64::MAX);
a *= Num::U64(10);
assert_eq!(a, Num::Scalar(Scalar::from(u64::MAX) * Scalar::from(10)));

// scalar - scalar - no overflow
let mut a = Num::Scalar(Scalar::from(5));
a *= Num::Scalar(Scalar::from(10));
assert_eq!(a, Num::Scalar(Scalar::from(5) * Scalar::from(10)));

// scalar - u64 - overflow
let mut a = Num::Scalar(Scalar::from(5));
a *= Num::U64(u64::MAX);
assert_eq!(a, Num::Scalar(Scalar::from(5) * Scalar::from(u64::MAX)));

// scalar - u64 - overflow
let mut a = Num::U64(u64::MAX);
a *= Num::Scalar(Scalar::from(5));
assert_eq!(a, Num::Scalar(Scalar::from(5) * Scalar::from(u64::MAX)));
}
#[test]
fn prop_mul_assign(a in any::<Num<Fr>>(), b in any::<Num<Fr>>()){
let mut c = a;
c *= b;

match (a, b) {
(Num::U64(x1), Num::U64(x2)) => {
// does this overflow?
if x1.checked_mul(x2).is_none() {
assert_eq!(c, Num::Scalar(Scalar::from(x1) * Scalar::from(x2)));
} else {
assert_eq!(c, Num::U64(x1 * x2));
}
},
(Num::Scalar(x1), Num::U64(x2)) | (Num::U64(x2), Num::Scalar(x1)) => {
assert_eq!(c, Num::Scalar(x1 * Scalar::from(x2)));
},
(Num::Scalar(x1), Num::Scalar(x2)) => {
assert_eq!(c, Num::Scalar(x1 * x2));
}
}
}

#[test]
fn test_div_assign() {
// u64 - u64 - no overflow
let mut a = Num::<Scalar>::U64(10);
a /= Num::U64(5);
assert_eq!(a, Num::U64(10 / 5));

let mut a = Num::<Scalar>::U64(10);
a /= Num::U64(3);

// The result is not a Num::U64.
assert!(matches!(a, Num::<Scalar>::Scalar(_)));

a *= Num::U64(3);
assert_eq!(a, Num::<Scalar>::Scalar(Scalar::from(10)));

// scalar - scalar - no overflow
let mut a = Num::Scalar(Scalar::from(10));
a /= Num::Scalar(Scalar::from(5));
assert_eq!(
a,
Num::Scalar(Scalar::from(10) * Scalar::from(5).invert().unwrap())
);

// scalar - u64 - no overflow
let mut a = Num::Scalar(Scalar::from(10));
a /= Num::U64(5);
assert_eq!(
a,
Num::Scalar(Scalar::from(10) * Scalar::from(5).invert().unwrap())
);

// scalar - u64 - no overflow
let mut a = Num::U64(10);
a /= Num::Scalar(Scalar::from(5));
assert_eq!(
a,
Num::Scalar(Scalar::from(10) * Scalar::from(5).invert().unwrap())
);
#[test]
fn prop_div_assign(a in any::<Num<Fr>>(), b in any::<Num<Fr>>().prop_filter("divisor must be non-zero", |b| !b.is_zero())){
let mut c = a;
c /= b;

match (a, b) {
(Num::U64(x1), Num::U64(x2)) => {
// is x1 an exact multiple?
if let Some(x) = x1.checked_rem_euclid(x2){
if x == 0 {
assert_eq!(c, Num::U64(x1 / x2));

} else {
c *= b;
assert_eq!(c, Num::Scalar(Scalar::from(x1)));
}
} else {
unreachable!("excluded by prop_filter")
}
},
(Num::U64(x1), Num::Scalar(_)) => {
c *= b;
assert_eq!(c, Num::Scalar(Scalar::from(x1)));
},
(Num::Scalar(_), _) => {
c *= b;
assert_eq!(c, a);
}
}
}

#[test]
fn prop_mosts(a in any::<Num<Fr>>()) {
if a.is_negative() {
assert!(a >= Num::most_negative());
assert!(a < Num::most_positive());
} else {
assert!(a <= Num::most_positive());
assert!(a > Num::most_negative());
}
}

#[test]
fn prop_sign_and_sub(a in any::<Num<Fr>>(), b in any::<Num<Fr>>()) {
let mut c = a;
c -= b;

if a.is_negative() {
// does this "underflow" (in the sense of consistency w/ Num::is_less_than)
let mut underflow_boundary = Num::most_negative();
underflow_boundary += b;

if a >= underflow_boundary {
assert!(c.is_negative());
}
} else if b.is_negative() {
// does this "overflow" (in the sense of consistency w/ Num::is_less_than)
// b negative, a - b = a + |b| which can be greater than most_positive
let mut overflow_boundary = Num::most_positive();
overflow_boundary -= b;

if a <= overflow_boundary {
assert!(a.is_less_than(&c));
}
} else {
assert!(c.is_less_than(&a));
}
}

#[test]
fn prop_lesser(a in any::<Num<Fr>>(), b in any::<Num<Fr>>()) {
// operands should be distinct for is_less_than
prop_assume!(a != b);

// anti-symmetry
if a.is_less_than(&b) && b.is_less_than(&a) {
assert_eq!(a, b);
}

match (a, b) {
(Num::U64(x1), Num::U64(x2)) => {
assert_eq!(a.is_less_than(&b), x1 < x2);
},
(Num::Scalar(x1), Num::Scalar(x2)) => {
// this time, we express the conditions in terms of the
// saclar operation
let underflow = {
let mut underflow_boundary = Scalar::most_negative();
underflow_boundary += x2;
x1 > underflow_boundary
};
let overflow = {
let mut overflow_boundary = Scalar::most_positive();
overflow_boundary -= x2;
x1 > overflow_boundary
};
let diff = Num::Scalar(x1 - x2);
if !underflow && !overflow {
assert_eq!(a.is_less_than(&b), diff.is_negative());
}

let same_sign = !(x1.is_negative() ^ x2.is_negative());
assert_eq!(a.is_less_than(&b), (same_sign && diff.is_negative()) || (!same_sign && a.is_negative()));
},
(Num::U64(x1), Num::Scalar(x2)) if !x2.is_negative() => {
assert_eq!(a.is_less_than(&b), Scalar::from(x1) < x2);
},
(Num::U64(_), Num::Scalar(_)) => { // right_operand.is_negative()
assert!(!a.is_less_than(&b));
},
(Num::Scalar(x1), Num::U64(x2)) if !x1.is_negative() => {
assert_eq!(a.is_less_than(&b), x1 < Scalar::from(x2));
}
(Num::Scalar(_), Num::U64(_)) => { // left_operand.is_negative()
assert!(a.is_less_than(&b));
},
}
}
}

#[test]
Expand Down

1 comment on commit fe70ad3

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Table of Contents

Overview

This benchmark report shows the Fibonacci GPU benchmark.
NVIDIA GeForce RTX 4070
AMD Ryzen 9 3950X 16-Core Processor
125.711 GB RAM

Benchmark Results

LEM Fibonacci Prove - rc = 100

fib-ref=50491ed9ad28b328c988410afdff7644a27154e1 fib-ref=fe70ad3079f5535261011dd95f1131fbe90b3b4e
num-100 3.92 s (✅ 1.00x) 4.00 s (✅ 1.02x slower)
num-200 8.92 s (✅ 1.00x) 8.70 s (✅ 1.02x faster)

LEM Fibonacci Prove - rc = 600

fib-ref=50491ed9ad28b328c988410afdff7644a27154e1 fib-ref=fe70ad3079f5535261011dd95f1131fbe90b3b4e
num-100 3.06 s (✅ 1.00x) 3.06 s (✅ 1.00x faster)
num-200 7.02 s (✅ 1.00x) 7.00 s (✅ 1.00x faster)

Made with criterion-table

Please sign in to comment.