Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test/invariant #28

Merged
merged 35 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
aedb9ca
test: add tests from Hyper repo
clemlak Feb 14, 2023
0f99b27
test: add pdf bounds check
clemlak Feb 14, 2023
bb7fd50
test: add gaussian Rust binary
clemlak Feb 16, 2023
4d69a46
test: add notes and fix some tests
clemlak Feb 16, 2023
ead7eff
test: add new Erfc tests
clemlak Feb 16, 2023
41b50e1
test: unused tests
clemlak Feb 16, 2023
22573bf
feat: add erfc return for 0 input
clemlak Feb 16, 2023
3d943b3
feat: add OutOfBounds to ierfc
clemlak Feb 16, 2023
5ba3c6c
test: add TestIerfc
clemlak Feb 16, 2023
c7f3070
test: add ierfc to gaussian
clemlak Feb 16, 2023
2957980
test: update testDiff_ierfc
clemlak Feb 16, 2023
a5f38cf
test: update gaussian to handle signed integers
clemlak Feb 17, 2023
fdb5013
test: update ierfc tests
clemlak Feb 17, 2023
30a1de9
test: add cdf and pdf to gaussian
clemlak Feb 17, 2023
bc72deb
test: add ppf to gaussian
clemlak Feb 17, 2023
91f61a4
test: add cdf function tests
clemlak Feb 17, 2023
a999533
test: add pdf diff fuzz
clemlak Feb 17, 2023
4527663
test: fix ppf signed outputs
clemlak Feb 17, 2023
634cb62
test: add pdf fuzzing tests
clemlak Feb 17, 2023
0c88543
test: add ppf fuzzing tests
clemlak Feb 17, 2023
30a889b
test: update testDiff_cdf to use % approx
clemlak Feb 17, 2023
3c9712b
test: update gaussian
clemlak Feb 17, 2023
118bd17
test: update ppf fuzzing approx check
clemlak Feb 20, 2023
1e803e3
test: update pdf diff test to use rel approx
clemlak Feb 20, 2023
bdb246c
test: update cdf diff test to use different approx
clemlak Feb 20, 2023
17e46a9
test: update gaussian rust ref
clemlak Feb 20, 2023
60d3707
feat: add constant ierfc 0 output for 1 input
clemlak Feb 20, 2023
d54a21b
test: update erfc diff test to use rel approx
clemlak Feb 20, 2023
3c0bf73
test: update ierfc diff test to use diff approx
clemlak Feb 20, 2023
1c23db4
feat: add Infinity revert to ierfc
clemlak Feb 20, 2023
f36887f
test: add ierfc infinity checks and update diff
clemlak Feb 20, 2023
cd20807
test: delete old test
clemlak Feb 20, 2023
cf3db41
test: add overflow check in ierfc assembly
clemlak Feb 20, 2023
cbbda6d
test: update ppf diff test
clemlak Feb 20, 2023
e459bfa
test: update equality checks
clemlak Feb 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added gaussian
Binary file not shown.
23 changes: 22 additions & 1 deletion src/Gaussian.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ library Gaussian {
error Infinity();
error NegativeInfinity();
error Overflow();
error OutOfBounds();

uint256 internal constant HALF_WAD = 0.5 ether;
uint256 internal constant PI = 3_141592653589793238;
Expand Down Expand Up @@ -77,6 +78,10 @@ library Gaussian {
* @custom:source https://mathworld.wolfram.com/Erfc.html.
*/
function erfc(int256 input) internal pure returns (int256 output) {
if (input == 0) {
return 1 ether;
}

uint256 z = abs(input);
int256 t;
int256 step;
Expand Down Expand Up @@ -171,6 +176,11 @@ library Gaussian {
* @custom:source https://mathworld.wolfram.com/InverseErfc.html.
*/
function ierfc(int256 x) internal pure returns (int256 z) {
if (x == 0 || x == 2 ether) revert Infinity();
if (x < 0 || x > 2 ether) revert OutOfBounds();

if (x == SCALAR) return 0;

assembly {
// x >= 2, iszero(x < 2 ? 1 : 0) ? 1 : 0.
if iszero(slt(x, TWO)) {
Expand Down Expand Up @@ -207,7 +217,14 @@ library Gaussian {
int256 r;
assembly {
function muli(pxn, pxd) -> res {
res := sdiv(mul(pxn, pxd), ONE)
res := mul(pxn, pxd)

if iszero(eq(sdiv(res, pxn), pxd)) {
mstore(0, 0x35278d1200000000000000000000000000000000000000000000000000000000)
revert(0, 4)
}

res := sdiv(res, ONE)
}

r := muli(
Expand Down Expand Up @@ -275,12 +292,14 @@ library Gaussian {
*/
function cdf(int256 x) internal pure returns (int256 z) {
int256 negated;

assembly {
let res := sdiv(mul(x, ONE), SQRT2)
negated := add(not(res), 1)
}

int256 _erfc = erfc(negated);

assembly {
z := sdiv(mul(ONE, _erfc), TWO)
}
Expand All @@ -297,9 +316,11 @@ library Gaussian {
*/
function pdf(int256 x) internal pure returns (int256 z) {
int256 e;

assembly {
e := sdiv(mul(add(not(x), 1), x), TWO) // (-x * x) / 2.
}

e = FixedPointMathLib.expWad(e);

assembly {
Expand Down
29 changes: 29 additions & 0 deletions src/test/Cdf.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "forge-std/Test.sol";

import {Gaussian} from "../Gaussian.sol";

contract TestCdf is Test {
function testDiff_cdf(int256 x) public {
vm.assume(x > -2828427124746190093171572875253809907);
vm.assume(x < 2828427124746190093171572875253809907);
string[] memory inputs = new string[](3);
inputs[0] = "./gaussian";
inputs[1] = "cdf";
inputs[2] = vm.toString(x);
bytes memory res = vm.ffi(inputs);
uint256 ref = abi.decode(res, (uint256));
int256 y = Gaussian.cdf(x);

// When outputs are very small, we tolerate a larger error
if (ref < 1_000_000_000 && y < 1_000_000_000) {
// 0.1% of difference
assertApproxEqRel(ref, uint256(y), 0.001 ether);
} else {
// 0.00005% of difference
assertApproxEqRel(ref, uint256(y), 0.0000005 ether);
}
}
}
59 changes: 59 additions & 0 deletions src/test/Erfc.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "forge-std/Test.sol";

import {Gaussian} from "../Gaussian.sol";

contract TestErfc is Test {
function testFuzz_erfc_RevertWhenInputIsTooLow(int256 x) public {
vm.assume(x <= -1999999999999999998000000000000000002);

// TODO: Investigate why the error selector is 0x4d2d75b1
// instead of 0x35278d12
vm.expectRevert();
int256 y = Gaussian.erfc(x);
y;
}

function testFuzz_erfc_RevertWhenInputIsTooHigh(int256 x) public {
vm.assume(x > 1999999999999999998000000000000000002);
vm.expectRevert(Gaussian.Overflow.selector);
int256 y = Gaussian.erfc(x);
y;
}

function testFuzz_erfc_NegativeInputIsBounded(int256 x) public {
vm.assume(x > -1999999999999999998000000000000000002);
vm.assume(x < -0.0000001 ether);
int256 y = Gaussian.erfc(x);
assertGe(y, 1 ether);
assertLe(y, 2 ether);
}

function testFuzz_erfc_PositiveInputIsBounded(int256 x) public {
vm.assume(x > 0.0000001 ether);
vm.assume(x < 1999999999999999998000000000000000002);
int256 y = Gaussian.erfc(x);
assertGe(y, 0 ether);
assertLe(y, 1 ether);
}

function test_erfc_zero_input_returns_one() public {
assertEq(Gaussian.erfc(0), 1 ether);
}

function testDiff_erfc(int256 x) public {
vm.assume(x < 1999999999999999998000000000000000002);
vm.assume(x > -1999999999999999998000000000000000002);
string[] memory inputs = new string[](3);
inputs[0] = "./gaussian";
inputs[1] = "erfc";
inputs[2] = vm.toString(x);
bytes memory res = vm.ffi(inputs);
uint256 ref = abi.decode(res, (uint256));
int256 y = Gaussian.erfc(x);
// Results have a 0.0001% difference
assertApproxEqRel(ref, uint256(y), 0.000001 ether);
}
}
12 changes: 8 additions & 4 deletions src/test/Gaussian.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,16 @@ contract TestGaussian is Test {
function testReference_erfc_Equality(int256 input) public {
vm.assume(input < HIGH);
vm.assume(input > LOW);
vm.assume(input != 0);
int256 actual = Gaussian.erfc(input);
int256 expected = Ref.erfc(input);
assertEq(actual, expected, "erfc-inequality");
}

// todo: investigate, reverts with Infinity() if input is 1 because of rounding down?
function testReference_ierfc_Equality(int256 input) public {
vm.assume(input < HIGH);
vm.assume(input > LOW);
vm.assume(input != 1);
vm.assume(input < 2 ether);
vm.assume(input > 1);
int256 actual = Gaussian.ierfc(input);
int256 expected = Ref.ierfc(input);
assertEq(actual, expected, "ierfc-inequality");
Expand All @@ -175,9 +175,13 @@ contract TestGaussian is Test {
function testReference_cdf_Equality(int256 input) public {
vm.assume(input < HIGH);
vm.assume(input > LOW);
vm.assume(input != 0);
int256 actual = Gaussian.cdf(input);
int256 expected = Ref.cdf(input);
assertEq(actual, expected, "cdf-inequality");
console.logInt(actual);
console.logInt(expected);
// assertEq(actual, expected, "cdf-inequality");
assertApproxEqRel(actual, expected, 0.0015 ether);
}

function testReference_pdf_Equality(int256 input) public {
Expand Down
54 changes: 54 additions & 0 deletions src/test/Ierfc.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "forge-std/Test.sol";

import {Gaussian} from "../Gaussian.sol";

contract TestIerfc is Test {
function testFuzz_ierfc_RevertWhenInputIsOutOfBounds(int256 x) public {
vm.assume(x < 0 || x > 2 ether);
vm.expectRevert(Gaussian.OutOfBounds.selector);
int256 y = Gaussian.ierfc(x);
y;
}

function test_ierfc_InputOneWillTriggerInfinity() public {
vm.expectRevert(Gaussian.Infinity.selector);
int256 y = Gaussian.ierfc(1);
console.logInt(y);
}

function test_ierfc_ZeroTriggersInfinity() public {
vm.expectRevert(Gaussian.Infinity.selector);
int256 y = Gaussian.ierfc(0);
y;
}

function test_ierfc_TwoTriggersInfinity() public {
vm.expectRevert(Gaussian.Infinity.selector);
int256 y = Gaussian.ierfc(2 ether);
y;
}

function testDiff_ierfc(int64 x) public {
vm.assume(x > 0.00001 ether);
vm.assume(x < 2 ether);
string[] memory inputs = new string[](3);
inputs[0] = "./gaussian";
inputs[1] = "ierfc";
inputs[2] = vm.toString(x);
bytes memory res = vm.ffi(inputs);
int256 ref = abi.decode(res, (int256));
int256 y = Gaussian.ierfc(int256(x));

// When inputs are very close to 1, we tolerate a larger error
if (x > 0.99 ether && x < 1.05 ether) {
// 0.15% of difference
assertApproxEqRel(ref, y, 0.0015 ether);
} else {
// 0.0003% of difference
assertApproxEqRel(ref, y, 0.000003 ether);
}
}
}
29 changes: 29 additions & 0 deletions src/test/Pdf.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "forge-std/Test.sol";

import {Gaussian} from "../Gaussian.sol";

contract TestPdf is Test {
function testDiff_pdf(int256 x) public {
vm.assume(x > -2828427124746190093171572875253809907);
vm.assume(x < 2828427124746190093171572875253809907);
string[] memory inputs = new string[](3);
inputs[0] = "./gaussian";
inputs[1] = "pdf";
inputs[2] = vm.toString(x);
bytes memory res = vm.ffi(inputs);
int256 ref = abi.decode(res, (int256));
int256 y = Gaussian.pdf(x);

// When outputs are very small, we tolerate a larger error
if (ref < 1_000_000_000 && y < 1_000_000_000) {
// 0.1% of difference
assertApproxEqRel(ref, y, 0.001 ether);
} else {
// 0.00005% of difference
assertApproxEqRel(ref, y, 0.0000005 ether);
}
}
}
22 changes: 22 additions & 0 deletions src/test/Ppf.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "forge-std/Test.sol";

import {Gaussian} from "../Gaussian.sol";

contract TestPpf is Test {
function testDiff_ppf(int64 x) public {
vm.assume(x > 0.0000001 ether);
vm.assume(x < 1 ether);
string[] memory inputs = new string[](3);
inputs[0] = "./gaussian";
inputs[1] = "ppf";
inputs[2] = vm.toString(x);
bytes memory res = vm.ffi(inputs);
int256 ref = abi.decode(res, (int256));
int256 y = Gaussian.ppf(int256(x));
// Results have a 0.00165% difference
assertApproxEqRel(ref, y, 0.001 ether);
}
}