code review.

This commit is contained in:
Quinn Dougherty 2022-04-20 18:48:04 -04:00
parent a5cb34ff7f
commit 264d970348
8 changed files with 164 additions and 175 deletions

View File

@ -1,12 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# This script is only relevant if you're rolling nixos.
# Esy (a bisect_ppx dependency/build tool) is borked on nixos without using an FHS shell. https://github.com/esy/esy/issues/858
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
set -x set -x
fhsShellName="squiggle-development" fhsShellName="squiggle-development"
# Esy (a bisect_ppx dependency/build tool) is borked on nixos without using an FHS shell. https://github.com/esy/esy/issues/858
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn]; runScript = \"yarn\"; }).env" fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn]; runScript = \"yarn\"; }).env"
nix-shell - <<<"$fhsShellDotNix" nix-shell - <<<"$fhsShellDotNix"
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
theLd=$(patchelf --print-interpreter $(which mkdir)) theLd=$(patchelf --print-interpreter $(which mkdir))
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe

View File

@ -1,44 +1,26 @@
import { errorValueToString } from "../../src/js/index"; // import { errorValueToString } from "../../src/js/index";
import * as fc from "fast-check"; import * as fc from "fast-check";
import { testRun } from "./TestHelpers"; import { testRun } from "./TestHelpers";
describe("Jstat: cumulative density function", () => { describe("cumulative density function of a normal distribution", () => {
test("of a normal distribution at 3 stdevs to the right of the mean is within epsilon of 1", () => { test("at 3 stdevs to the right of the mean is near 1", () => {
fc.assert( fc.assert(
fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => { fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => {
let squiggleString = `cdf(normal(${mean}, ${stdev}), ${ let threeStdevsAboveMean = mean + 3 * stdev;
mean + 3 * stdev let squiggleString = `cdf(normal(${mean}, ${stdev}), ${threeStdevsAboveMean})`;
})`;
let squiggleResult = testRun(squiggleString); let squiggleResult = testRun(squiggleString);
let epsilon = 5e-3; expect(squiggleResult.value).toBeCloseTo(1);
switch (squiggleResult.tag) {
case "Error":
expect(errorValueToString(squiggleResult.value)).toEqual(
"<Test cases don't seem to be finding this>"
);
case "Ok":
expect(squiggleResult.value.value).toBeGreaterThan(1 - epsilon);
}
}) })
); );
}); });
test("of a normal distribution at 3 stdevs to the left of the mean is within epsilon of 0", () => { test("at 3 stdevs to the left of the mean is near 0", () => {
fc.assert( fc.assert(
fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => { fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => {
let squiggleString = `cdf(normal(${mean}, ${stdev}), ${ let threeStdevsBelowMean = mean - 3 * stdev;
mean - 3 * stdev let squiggleString = `cdf(normal(${mean}, ${stdev}), ${threeStdevsBelowMean})`;
})`;
let squiggleResult = testRun(squiggleString); let squiggleResult = testRun(squiggleString);
let epsilon = 5e-3; expect(squiggleResult.value).toBeCloseTo(0);
switch (squiggleResult.tag) {
case "Error":
expect(errorValueToString(squiggleResult.value)).toEqual(
"<Test cases don't seem to be finding this>"
);
case "Ok":
expect(squiggleResult.value.value).toBeLessThan(epsilon);
}
}) })
); );
}); });

View File

@ -7,7 +7,7 @@ import {
import { testRun } from "./TestHelpers"; import { testRun } from "./TestHelpers";
import * as fc from "fast-check"; import * as fc from "fast-check";
describe("Squiggle is whitespace insensitive", () => { describe("Squiggle's parser is whitespace insensitive", () => {
test("when assigning a distribution to a name and calling that name", () => { test("when assigning a distribution to a name and calling that name", () => {
/* /*
* intersperse varying amounts of whitespace in a squiggle string * intersperse varying amounts of whitespace in a squiggle string
@ -27,9 +27,8 @@ describe("Squiggle is whitespace insensitive", () => {
let squiggleOutput = testRun( let squiggleOutput = testRun(
squiggleString("", "", "", "", "", "", "", "") squiggleString("", "", "", "", "", "", "", "")
); );
/*
* Add "\n" to this when multiline is introduced. // Add "\n" to this when multiline is introduced.
*/
let whitespaceGen = () => { let whitespaceGen = () => {
return fc.constantFrom("", " ", "\t", " ", " ", " ", " "); return fc.constantFrom("", " ", "\t", " ", " ", " ", " ");
}; };

View File

@ -1,49 +1,39 @@
import { errorValueToString } from "../../src/js/index"; // import { errorValueToString } from "../../src/js/index";
import { testRun, failDefault, expectErrorToBeBounded } from "./TestHelpers"; // import { testRun, expectErrorToBeBounded } from "./TestHelpers";
import * as fc from "fast-check"; // import * as fc from "fast-check";
describe("Mean of mixture is weighted average of means", () => { // describe("Mean of mixture is weighted average of means", () => {
test("mx(beta(a,b), lognormal(m,s), [x,y])", () => { // test("mx(beta(a,b), lognormal(m,s), [x,y])", () => {
fc.assert( // fc.assert(
fc.property( // fc.property(
fc.float({ min: 1e-1 }), // alpha // fc.float({ min: 1e-1 }), // alpha
fc.float({ min: 1 }), // beta // fc.float({ min: 1 }), // beta
fc.float(), // mu // fc.float(), // mu
fc.float({ min: 1e-1 }), // sigma // fc.float({ min: 1e-1 }), // sigma
fc.float({ min: 1e-7 }), // fc.float({ min: 1e-7 }),
fc.float({ min: 1e-7 }), // fc.float({ min: 1e-7 }),
(a, b, m, s, x, y) => { // (a, b, m, s, x, y) => {
let squiggleString = `mean(mx(beta(${a},${b}), lognormal(${m},${s}), [${x}, ${y}]))`; // let squiggleString = `mean(mixture(beta(${a},${b}), lognormal(${m},${s}), [${x}, ${y}]))`;
let res = testRun(squiggleString); // let res = testRun(squiggleString);
switch (res.tag) { // let weightDenom = x + y;
case "Error": // let betaWeight = x / weightDenom;
expect(errorValueToString(res.value)).toEqual( // let lognormalWeight = y / weightDenom;
"<I wonder if test cases will find this>" // let betaMean = 1 / (1 + b / a);
); // let lognormalMean = m + s ** 2 / 2;
break; // if (res.tag == "number") {
case "Ok": // expectErrorToBeBounded(
let weightDenom = x + y; // res.value,
let betaWeight = x / weightDenom; // betaWeight * betaMean + lognormalWeight * lognormalMean,
let lognormalWeight = y / weightDenom; // 1,
let betaMean = 1 / (1 + b / a); // 2
let lognormalMean = m + s ** 2 / 2; // );
if (res.value.tag == "number") { // } else {
expectErrorToBeBounded( // expect(res.value).toEqual("some error message");
res.value.value, // }
betaWeight * betaMean + lognormalWeight * lognormalMean, // }
1, // )
-1 // );
); // });
} else { // });
expect(res.value.value).toEqual("some error message");
} describe("vacuous", () => test("vacuous", () => expect(true).toEqual(true)));
break;
default:
failDefault();
break;
}
}
)
);
});
});

View File

@ -1,5 +1,5 @@
import { Distribution } from "../../src/js/index"; import { Distribution } from "../../src/js/index";
import { expectErrorToBeBounded, failDefault } from "./TestHelpers"; // import { expectErrorToBeBounded, failDefault } from "./TestHelpers";
import * as fc from "fast-check"; import * as fc from "fast-check";
// Beware: float64Array makes it appear in an infinite loop. // Beware: float64Array makes it appear in an infinite loop.
@ -11,20 +11,53 @@ let arrayGen = () =>
noNaN: true, noNaN: true,
}); });
describe("SampleSet: cdf", () => { describe("cumulative density function", () => {
let n = 10000; let n = 10000;
test("at the highest number in the distribution is within epsilon of 1", () => {
// // We should obtain the math here.
// test("'s codomain is bounded above", () => {
// fc.assert(
// fc.property(arrayGen(), fc.float(), (xs_, x) => {
// let xs = Array.from(xs_);
// // Should compute with squiggle strings once interpreter has `sample`
// let dist = new Distribution(
// { tag: "SampleSet", value: xs },
// { sampleCount: n, xyPointLength: 100 }
// );
// let cdfValue = dist.cdf(x).value;
// let epsilon = 5e-7
// expect(cdfValue).toBeLessThanOrEqual(1 + epsilon)
// })
// );
// })
test("'s codomain is bounded below", () => {
fc.assert( fc.assert(
fc.property(arrayGen(), (xs) => { fc.property(arrayGen(), fc.float(), (xs_, x) => {
let ys = Array.from(xs); let xs = Array.from(xs_);
let max = Math.max(...ys); // Should compute with squiggle strings once interpreter has `sample`
// Should compute with squiglge strings once interpreter has `sample`
let dist = new Distribution( let dist = new Distribution(
{ tag: "SampleSet", value: ys }, { tag: "SampleSet", value: xs },
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(x).value;
expect(cdfValue).toBeGreaterThanOrEqual(0);
})
);
});
test("at the highest number in the sample is close to 1", () => {
fc.assert(
fc.property(arrayGen(), (xs_) => {
let xs = Array.from(xs_);
let max = Math.max(...xs);
// Should compute with squiggle strings once interpreter has `sample`
let dist = new Distribution(
{ tag: "SampleSet", value: xs },
{ sampleCount: n, xyPointLength: 100 } { sampleCount: n, xyPointLength: 100 }
); );
let cdfValue = dist.cdf(max).value; let cdfValue = dist.cdf(max).value;
let min = Math.min(...ys); let min = Math.min(...xs);
let epsilon = 5e-3; let epsilon = 5e-3;
if (max - min < epsilon) { if (max - min < epsilon) {
expect(cdfValue).toBeLessThan(1 - epsilon); expect(cdfValue).toBeLessThan(1 - epsilon);
@ -38,16 +71,16 @@ describe("SampleSet: cdf", () => {
// I may simply be mistaken about the math here. // I may simply be mistaken about the math here.
// test("at the lowest number in the distribution is within epsilon of 0", () => { // test("at the lowest number in the distribution is within epsilon of 0", () => {
// fc.assert( // fc.assert(
// fc.property(arrayGen(), (xs) => { // fc.property(arrayGen(), (xs_) => {
// let ys = Array.from(xs); // let xs = Array.from(xs_);
// let min = Math.min(...ys); // let min = Math.min(...xs);
// // Should compute with squiggle strings once interpreter has `sample` // // Should compute with squiggle strings once interpreter has `sample`
// let dist = new Distribution( // let dist = new Distribution(
// { tag: "SampleSet", value: ys }, // { tag: "SampleSet", value: xs },
// { sampleCount: n, xyPointLength: 100 } // { sampleCount: n, xyPointLength: 100 }
// ); // );
// let cdfValue = dist.cdf(min).value; // let cdfValue = dist.cdf(min).value;
// let max = Math.max(...ys); // let max = Math.max(...xs);
// let epsilon = 5e-3; // let epsilon = 5e-3;
// if (max - min < epsilon) { // if (max - min < epsilon) {
// expect(cdfValue).toBeGreaterThan(4 * epsilon); // expect(cdfValue).toBeGreaterThan(4 * epsilon);
@ -61,14 +94,14 @@ describe("SampleSet: cdf", () => {
// I believe this is true, but due to bugs can't get the test to pass. // I believe this is true, but due to bugs can't get the test to pass.
// test("is <= 1 everywhere with equality when x is higher than the max", () => { // test("is <= 1 everywhere with equality when x is higher than the max", () => {
// fc.assert( // fc.assert(
// fc.property(arrayGen(), fc.float(), (xs, x) => { // fc.property(arrayGen(), fc.float(), (xs_, x) => {
// let ys = Array.from(xs); // let xs = Array.from(xs_);
// let dist = new Distribution( // let dist = new Distribution(
// { tag: "SampleSet", value: ys }, // { tag: "SampleSet", value: xs },
// { sampleCount: n, xyPointLength: 100 } // { sampleCount: n, xyPointLength: 100 }
// ); // );
// let cdfValue = dist.cdf(x).value; // let cdfValue = dist.cdf(x).value;
// let max = Math.max(...ys) // let max = Math.max(...xs)
// if (x > max) { // if (x > max) {
// let epsilon = (x - max) / x // let epsilon = (x - max) / x
// expect(cdfValue).toBeGreaterThan(1 * (1 - epsilon)); // expect(cdfValue).toBeGreaterThan(1 * (1 - epsilon));
@ -81,16 +114,16 @@ describe("SampleSet: cdf", () => {
// ); // );
// }); // });
test("is >= 0 everywhere with equality when x is lower than the min", () => { test("is non-negative everywhere with zero when x is lower than the min", () => {
fc.assert( fc.assert(
fc.property(arrayGen(), fc.float(), (xs, x) => { fc.property(arrayGen(), fc.float(), (xs_, x) => {
let ys = Array.from(xs); let xs = Array.from(xs_);
let dist = new Distribution( let dist = new Distribution(
{ tag: "SampleSet", value: ys }, { tag: "SampleSet", value: xs },
{ sampleCount: n, xyPointLength: 100 } { sampleCount: n, xyPointLength: 100 }
); );
let cdfValue = dist.cdf(x).value; let cdfValue = dist.cdf(x).value;
if (x < Math.min(...ys)) { if (x < Math.min(...xs)) {
expect(cdfValue).toEqual(0); expect(cdfValue).toEqual(0);
} else { } else {
expect(cdfValue).toBeGreaterThan(0); expect(cdfValue).toBeGreaterThan(0);
@ -101,18 +134,18 @@ describe("SampleSet: cdf", () => {
}); });
// // I no longer believe this is true. // // I no longer believe this is true.
// describe("SampleSet: pdf", () => { // describe("probability density function", () => {
// let n = 1000; // let n = 1000;
// //
// test("assigns to the max at most the weight of the mean", () => { // test("assigns to the max at most the weight of the mean", () => {
// fc.assert( // fc.assert(
// fc.property(arrayGen(), (xs) => { // fc.property(arrayGen(), (xs_) => {
// let ys = Array.from(xs); // let xs = Array.from(xs_);
// let max = Math.max(...ys); // let max = Math.max(...xs);
// let mean = ys.reduce((a, b) => a + b, 0.0) / ys.length; // let mean = xs.reduce((a, b) => a + b, 0.0) / ys.length;
// // Should be from squiggleString once interpreter exposes sampleset // // Should be from squiggleString once interpreter exposes sampleset
// let dist = new Distribution( // let dist = new Distribution(
// { tag: "SampleSet", value: ys }, // { tag: "SampleSet", value: xs },
// { sampleCount: n, xyPointLength: 100 } // { sampleCount: n, xyPointLength: 100 }
// ); // );
// let pdfValueMean = dist.pdf(mean).value; // let pdfValueMean = dist.pdf(mean).value;
@ -128,21 +161,21 @@ describe("SampleSet: cdf", () => {
// }); // });
// This should be true, but I can't get it to work. // This should be true, but I can't get it to work.
// describe("SampleSet: mean is mean", () => { // describe("mean is mean", () => {
// test("mean(samples(xs)) sampling twice as widely as the input", () => { // test("when sampling twice as widely as the input", () => {
// fc.assert( // fc.assert(
// fc.property( // fc.property(
// fc.float64Array({ minLength: 10, maxLength: 100000 }), // fc.float64Array({ minLength: 10, maxLength: 100000 }),
// (xs) => { // (xs_) => {
// let ys = Array.from(xs); // let xs = Array.from(xs_);
// let n = ys.length; // let n = xs.length;
// let dist = new Distribution( // let dist = new Distribution(
// { tag: "SampleSet", value: ys }, // { tag: "SampleSet", value: xs },
// { sampleCount: 2 * n, xyPointLength: 4 * n } // { sampleCount: 2 * n, xyPointLength: 4 * n }
// ); // );
// let mean = dist.mean() // let mean = dist.mean()
// if (typeof mean.value == "number") { // if (typeof mean.value == "number") {
// expectErrorToBeBounded(mean.value, ys.reduce((a, b) => a + b, 0.0) / n, 5e-1, 1) // expectErrorToBeBounded(mean.value, xs.reduce((a, b) => a + b, 0.0) / n, 5e-1, 1)
// } else { // } else {
// failDefault() // failDefault()
// } // }
@ -151,20 +184,20 @@ describe("SampleSet: cdf", () => {
// ); // );
// }); // });
// //
// test("mean(samples(xs)) sampling half as widely as the input", () => { // test("when sampling half as widely as the input", () => {
// fc.assert( // fc.assert(
// fc.property( // fc.property(
// fc.float64Array({ minLength: 10, maxLength: 100000 }), // fc.float64Array({ minLength: 10, maxLength: 100000 }),
// (xs) => { // (xs_) => {
// let ys = Array.from(xs); // let xs = Array.from(xs_);
// let n = ys.length; // let n = xs.length;
// let dist = new Distribution( // let dist = new Distribution(
// { tag: "SampleSet", value: ys }, // { tag: "SampleSet", value: xs },
// { sampleCount: Math.floor(n / 2), xyPointLength: 4 * n } // { sampleCount: Math.floor(n / 2), xyPointLength: 4 * n }
// ); // );
// let mean = dist.mean() // let mean = dist.mean()
// if (typeof mean.value == "number") { // if (typeof mean.value == "number") {
// expectErrorToBeBounded(mean.value, ys.reduce((a, b) => a + b, 0.0) / n, 5e-1, 1) // expectErrorToBeBounded(mean.value, xs.reduce((a, b) => a + b, 0.0) / n, 5e-1, 1)
// } else { // } else {
// failDefault() // failDefault()
// } // }

View File

@ -1,29 +1,21 @@
import { errorValueToString } from "../../src/js/index"; // import { errorValueToString } from "../../src/js/index";
import { testRun } from "./TestHelpers"; import { testRun } from "./TestHelpers";
import * as fc from "fast-check"; import * as fc from "fast-check";
describe("Scalar manipulation is well-modeled by javascript math", () => { describe("Scalar manipulation is well-modeled by javascript math", () => {
test("in the case of logarithms (with assignment)", () => { test("in the case of natural logarithms", () => {
fc.assert( fc.assert(
fc.property(fc.float(), (x) => { fc.property(fc.float(), (x) => {
let squiggleString = `x = log(${x}); x`; let squiggleString = `log(${x})`;
let squiggleResult = testRun(squiggleString); let squiggleResult = testRun(squiggleString);
if (x == 0) { if (x == 0) {
expect(squiggleResult.value).toEqual({ expect(squiggleResult.value).toEqual(-Infinity);
tag: "number",
value: -Infinity,
});
} else if (x < 0) { } else if (x < 0) {
expect(squiggleResult.value).toEqual({ expect(squiggleResult.value).toEqual(
tag: "RETodo", "somemessage (confused why a test case hasn't pointed out to me that this message is bogus)"
value: );
"somemessage (confused why a test case hasn't pointed out to me that this message is bogus)",
});
} else { } else {
expect(squiggleResult.value).toEqual({ expect(squiggleResult.value).toEqual(Math.log(x));
tag: "number",
value: Math.log(x),
});
} }
}) })
); );
@ -34,17 +26,7 @@ describe("Scalar manipulation is well-modeled by javascript math", () => {
fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => { fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => {
let squiggleString = `x = ${x}; y = ${y}; z = ${z}; x + y + z`; let squiggleString = `x = ${x}; y = ${y}; z = ${z}; x + y + z`;
let squiggleResult = testRun(squiggleString); let squiggleResult = testRun(squiggleString);
switch (squiggleResult.tag) { expect(squiggleResult.value).toBeCloseTo(x + y + z);
case "Error":
expect(errorValueToString(squiggleResult.value)).toEqual(
"some message (hopefully a test case points it out to me)"
);
case "Ok":
expect(squiggleResult.value).toEqual({
tag: "number",
value: x + y + z,
});
}
}) })
); );
}); });

View File

@ -3,30 +3,17 @@ import { testRun } from "./TestHelpers";
import * as fc from "fast-check"; import * as fc from "fast-check";
describe("Symbolic mean", () => { describe("Symbolic mean", () => {
let triangularInputError = {
tag: "Error",
value: {
tag: "RETodo",
value: "Triangular values must be increasing order.",
},
};
test("mean(triangular(x,y,z))", () => { test("mean(triangular(x,y,z))", () => {
fc.assert( fc.assert(
fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => { fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => {
let res = testRun(`mean(triangular(${x},${y},${z}))`);
if (!(x < y && y < z)) { if (!(x < y && y < z)) {
expect(res).toEqual(triangularInputError); try {
} else { let squiggleResult = testRun(`mean(triangular(${x},${y},${z}))`);
switch (res.tag) { expect(squiggleResult.value).toBeCloseTo((x + y + z) / 3);
case "Error": } catch (err) {
expect(errorValueToString(res.value)).toEqual( expect((err as Error).message).toEqual(
"<Test cases don't seem to be finding this>" "Expected squiggle expression to evaluate but got error: TODO: Triangular values must be increasing order."
); );
case "Ok":
expect(res.value).toEqual({
tag: "number",
value: (x + y + z) / 3,
});
} }
} }
}) })

View File

@ -1,17 +1,31 @@
import { import {
run, run,
Distribution, // Distribution,
squiggleExpression, squiggleExpression,
errorValueToString, errorValueToString,
errorValue, // errorValue,
result, // result,
} from "../../src/js/index"; } from "../../src/js/index";
export function testRun(x: string): any { export function testRunR(x: string): any {
//: result<squiggleExpression, errorValue> => { //: result<squiggleExpression, errorValue> => {
return run(x, { sampleCount: 1000, xyPointLength: 100 }); return run(x, { sampleCount: 1000, xyPointLength: 100 });
} }
export function testRun(x: string): squiggleExpression {
let squiggleResult = run(x, { sampleCount: 1000, xyPointLength: 100 });
// return squiggleResult.value
if (squiggleResult.tag === "Ok") {
return squiggleResult.value;
} else {
throw new Error(
`Expected squiggle expression to evaluate but got error: ${errorValueToString(
squiggleResult.value
)}`
);
}
}
export function failDefault() { export function failDefault() {
expect("be reached").toBe("codepath should never"); expect("be reached").toBe("codepath should never");
} }