Merge branch 'staging' into Components-improvement-april-2

* staging: (41 commits)
  Fixed tests
  Samples_test -> SampleSetDist_test
  Bandwidth -> SampleSetDist_Bandwidth
  Added genType to SampleSetDist to make pass tests, other minor fixes
  Power should be ** to be consistent
  Cleaned up resultStringToResultError
  Cleanup from previous refactor
  Start of refactor for toPointSetDist
  Gave SampleSetDist a private type
  Namechange: Exponential -> Power
  Cleanup and commenting for PR
  Added more tests to JS__Test.ts, and added SampleN functionality to SampleSetDist
  Added tests for index.js and fixed some corresponding functionality
  Minor refactor of DistributionOperation Constructors
  Fix from CR
  Added to index.ts
  Added a bunch of manual functions for DistributionOperation
  Update README.md
  Cleanup from merge
  Added sparkline and toString to ReducerInterface
  ...
This commit is contained in:
Ozzie Gooen 2022-04-09 22:04:08 -04:00
commit ce4eaa7daa
42 changed files with 1181 additions and 307 deletions

View File

@ -1,4 +1,5 @@
# Squiggle # Squiggle
![Packages check](https://github.com/QURIresearch/squiggle/actions/workflows/ci.yaml/badge.svg)
This is an experiment DSL/language for making probabilistic estimates. The full story can be found [here](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3). This is an experiment DSL/language for making probabilistic estimates. The full story can be found [here](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3).

View File

@ -13,6 +13,9 @@ Other:
yarn start # listens to files and recompiles at every mutation yarn start # listens to files and recompiles at every mutation
yarn test yarn test
yarn test:watch # keeps an active session and runs all tests at every mutation yarn test:watch # keeps an active session and runs all tests at every mutation
# where o := open in osx and o := xdg-open in linux,
yarn coverage; o _coverage/index.html # produces coverage report and opens it in browser
``` ```
# TODO: clean up this README.md # TODO: clean up this README.md

View File

@ -4,10 +4,10 @@ open Expect
describe("Bandwidth", () => { describe("Bandwidth", () => {
test("nrd0()", () => { test("nrd0()", () => {
let data = [1., 4., 3., 2.] let data = [1., 4., 3., 2.]
expect(Bandwidth.nrd0(data)) -> toEqual(0.7625801874014622) expect(SampleSetDist_Bandwidth.nrd0(data)) -> toEqual(0.7625801874014622)
}) })
test("nrd()", () => { test("nrd()", () => {
let data = [1., 4., 3., 2.] let data = [1., 4., 3., 2.]
expect(Bandwidth.nrd(data)) -> toEqual(0.8981499984950554) expect(SampleSetDist_Bandwidth.nrd(data)) -> toEqual(0.8981499984950554)
}) })
}) })

View File

@ -0,0 +1,103 @@
open Jest
open Expect
let env: DistributionOperation.env = {
sampleCount: 100,
xyPointLength: 100,
}
let {
normalDist5,
normalDist10,
normalDist20,
normalDist,
uniformDist,
betaDist,
lognormalDist,
cauchyDist,
triangularDist,
exponentialDist,
} = module(GenericDist_Fixtures)
let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev}))
let {toFloat, toDist, toString, toError} = module(DistributionOperation.Output)
let {run} = module(DistributionOperation)
let {fmap} = module(DistributionOperation.Output)
let run = run(~env)
let outputMap = fmap(~env)
let toExt: option<'a> => 'a = E.O.toExt(
"Should be impossible to reach (This error is in test file)",
)
describe("sparkline", () => {
let runTest = (
name: string,
dist: GenericDist_Types.genericDist,
expected: DistributionOperation.outputType,
) => {
test(name, () => {
let result = DistributionOperation.run(~env, FromDist(ToString(ToSparkline(20)), dist))
expect(result)->toEqual(expected)
})
}
runTest(
"normal",
normalDist,
String(`▁▁▁▁▁▂▄▆▇██▇▆▄▂▁▁▁▁▁`),
)
runTest(
"uniform",
uniformDist,
String(`████████████████████`),
)
runTest("beta", betaDist, String(`▁▄▇████▇▆▅▄▃▃▂▁▁▁▁▁▁`))
runTest(
"lognormal",
lognormalDist,
String(`▁█▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁`),
)
runTest(
"cauchy",
cauchyDist,
String(`▁▁▁▁▁▁▁▁▁██▁▁▁▁▁▁▁▁▁`),
)
runTest(
"triangular",
triangularDist,
String(`▁▁▂▃▄▅▆▇████▇▆▅▄▃▂▁▁`),
)
runTest(
"exponential",
exponentialDist,
String(`█▅▄▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁`),
)
})
describe("toPointSet", () => {
test("on symbolic normal distribution", () => {
let result =
run(FromDist(ToDist(ToPointSet), normalDist5))
->outputMap(FromDist(ToFloat(#Mean)))
->toFloat
->toExt
expect(result)->toBeSoCloseTo(5.0, ~digits=0)
})
test("on sample set", () => {
let result =
run(FromDist(ToDist(ToPointSet), normalDist5))
->outputMap(FromDist(ToDist(ToSampleSet(1000))))
->outputMap(FromDist(ToDist(ToPointSet)))
->outputMap(FromDist(ToFloat(#Mean)))
->toFloat
->toExt
expect(result)->toBeSoCloseTo(5.0, ~digits=-1)
})
})

View File

@ -0,0 +1,11 @@
let normalDist5: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0}))
let normalDist10: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 10.0, stdev: 2.0}))
let normalDist20: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 20.0, stdev: 2.0}))
let normalDist: GenericDist_Types.genericDist = normalDist5
let betaDist: GenericDist_Types.genericDist = Symbolic(#Beta({alpha: 2.0, beta: 5.0}))
let lognormalDist: GenericDist_Types.genericDist = Symbolic(#Lognormal({mu: 0.0, sigma: 1.0}))
let cauchyDist: GenericDist_Types.genericDist = Symbolic(#Cauchy({local: 1.0, scale: 1.0}))
let triangularDist: GenericDist_Types.genericDist = Symbolic(#Triangular({low: 1.0, medium: 2.0, high: 3.0}))
let exponentialDist: GenericDist_Types.genericDist = Symbolic(#Exponential({rate: 2.0}))
let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0}))

View File

@ -0,0 +1,70 @@
open Jest
open Expect
open TestHelpers
// TODO: use Normal.make (etc.), but preferably after the new validation dispatch is in.
let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev}))
let mkBeta = (alpha, beta) => GenericDist_Types.Symbolic(#Beta({alpha: alpha, beta: beta}))
let mkExponential = rate => GenericDist_Types.Symbolic(#Exponential({rate: rate}))
let mkUniform = (low, high) => GenericDist_Types.Symbolic(#Uniform({low: low, high: high}))
let mkCauchy = (local, scale) => GenericDist_Types.Symbolic(#Cauchy({local: local, scale: scale}))
let mkLognormal = (mu, sigma) => GenericDist_Types.Symbolic(#Lognormal({mu: mu, sigma: sigma}))
describe("mixture", () => {
testAll("fair mean of two normal distributions", list{(0.0, 1e2), (-1e1, -1e-4), (-1e1, 1e2), (-1e1, 1e1)}, tup => { // should be property
let (mean1, mean2) = tup
let meanValue = {
run(Mixture([(mkNormal(mean1, 9e-1), 0.5), (mkNormal(mean2, 9e-1), 0.5)]))
-> outputMap(FromDist(ToFloat(#Mean)))
}
meanValue -> unpackFloat -> expect -> toBeSoCloseTo((mean1 +. mean2) /. 2.0, ~digits=-1)
})
testAll(
"weighted mean of a beta and an exponential",
// This would not survive property testing, it was easy for me to find cases that NaN'd out.
list{((128.0, 1.0), 2.0), ((2e-1, 64.0), 16.0), ((1e0, 1e0), 64.0)},
tup => {
let ((alpha, beta), rate) = tup
let betaWeight = 0.25
let exponentialWeight = 0.75
let meanValue = {
run(Mixture(
[
(mkBeta(alpha, beta), betaWeight),
(mkExponential(rate), exponentialWeight)
]
)) -> outputMap(FromDist(ToFloat(#Mean)))
}
let betaMean = 1.0 /. (1.0 +. beta /. alpha)
let exponentialMean = 1.0 /. rate
meanValue
-> unpackFloat
-> expect
-> toBeSoCloseTo(
betaWeight *. betaMean +. exponentialWeight *. exponentialMean,
~digits=-1
)
}
)
testAll(
"weighted mean of lognormal and uniform",
// Would not survive property tests: very easy to find cases that NaN out.
list{((-1e2,1e1), (2e0,1e0)), ((-1e-16,1e-16), (1e-8,1e0)), ((0.0,1e0), (1e0,1e-2))},
tup => {
let ((low, high), (mu, sigma)) = tup
let uniformWeight = 0.6
let lognormalWeight = 0.4
let meanValue = {
run(Mixture([(mkUniform(low, high), uniformWeight), (mkLognormal(mu, sigma), lognormalWeight)]))
-> outputMap(FromDist(ToFloat(#Mean)))
}
let uniformMean = (low +. high) /. 2.0
let lognormalMean = mu +. sigma ** 2.0 /. 2.0
meanValue
-> unpackFloat
-> expect
-> toBeSoCloseTo(uniformWeight *. uniformMean +. lognormalWeight *. lognormalMean, ~digits=-1)
}
)
})

View File

@ -0,0 +1,41 @@
open Jest
open TestHelpers
describe("Continuous and discrete splits", () => {
makeTest(
"splits (1)",
SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete([1.432, 1.33455, 2.0]),
([1.432, 1.33455, 2.0], E.FloatFloatMap.empty()),
)
makeTest(
"splits (2)",
SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete([
1.432,
1.33455,
2.0,
2.0,
2.0,
2.0,
]) |> (((c, disc)) => (c, disc |> E.FloatFloatMap.toArray)),
([1.432, 1.33455], [(2.0, 4.0)]),
)
let makeDuplicatedArray = count => {
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
E.A.concatMany([sorted, sorted, sorted, sorted]) |> Belt.SortArray.stableSortBy(_, compare)
}
let (_, discrete1) = SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete(
makeDuplicatedArray(10),
)
let toArr1 = discrete1 |> E.FloatFloatMap.toArray
makeTest("splitMedium at count=10", toArr1 |> Belt.Array.length, 10)
let (_c, discrete2) = SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete(
makeDuplicatedArray(500),
)
let toArr2 = discrete2 |> E.FloatFloatMap.toArray
makeTest("splitMedium at count=500", toArr2 |> Belt.Array.length, 500)
})

View File

@ -0,0 +1,161 @@
open Jest
open Expect
open TestHelpers
// TODO: use Normal.make (but preferably after teh new validation dispatch is in)
let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev}))
describe("(Symbolic) normalize", () => {
testAll("has no impact on normal distributions", list{-1e8, -1e-2, 0.0, 1e-4, 1e16}, mean => {
let normalValue = mkNormal(mean, 2.0)
let normalizedValue = run(FromDist(ToDist(Normalize), normalValue))
normalizedValue
-> unpackDist
-> expect
-> toEqual(normalValue)
})
})
describe("(Symbolic) mean", () => {
testAll("of normal distributions", list{-1e8, -16.0, -1e-2, 0.0, 1e-4, 32.0, 1e16}, mean => {
run(FromDist(ToFloat(#Mean), mkNormal(mean, 4.0)))
-> unpackFloat
-> expect
-> toBeCloseTo(mean)
})
Skip.test("of normal(0, -1) (it NaNs out)", () => {
run(FromDist(ToFloat(#Mean), mkNormal(1e1, -1e0)))
-> unpackFloat
-> expect
-> ExpectJs.toBeFalsy
})
test("of normal(0, 1e-8) (it doesn't freak out at tiny stdev)", () => {
run(FromDist(ToFloat(#Mean), mkNormal(0.0, 1e-8)))
-> unpackFloat
-> expect
-> toBeCloseTo(0.0)
})
testAll("of exponential distributions", list{1e-7, 2.0, 10.0, 100.0}, rate => {
let meanValue = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Exponential({rate: rate}))))
meanValue -> unpackFloat -> expect -> toBeCloseTo(1.0 /. rate) // https://en.wikipedia.org/wiki/Exponential_distribution#Mean,_variance,_moments,_and_median
})
test("of a cauchy distribution", () => {
let meanValue = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Cauchy({local: 1.0, scale: 1.0}))))
meanValue
-> unpackFloat
-> expect
-> toBeCloseTo(2.01868297874546)
//-> toBe(GenDistError(Other("Cauchy distributions may have no mean value.")))
})
testAll("of triangular distributions", list{(1.0,2.0,3.0), (-1e7,-1e-7,1e-7), (-1e-7,1e0,1e7), (-1e-16,0.0,1e-16)}, tup => {
let (low, medium, high) = tup
let meanValue = run(FromDist(
ToFloat(#Mean),
GenericDist_Types.Symbolic(#Triangular({low: low, medium: medium, high: high}))
))
meanValue
-> unpackFloat
-> expect
-> toBeCloseTo((low +. medium +. high) /. 3.0) // https://www.statology.org/triangular-distribution/
})
// TODO: nonpositive inputs are SUPPOSED to crash.
testAll("of beta distributions", list{(1e-4, 6.4e1), (1.28e2, 1e0), (1e-16, 1e-16), (1e16, 1e16), (-1e4, 1e1), (1e1, -1e4)}, tup => {
let (alpha, beta) = tup
let meanValue = run(FromDist(
ToFloat(#Mean),
GenericDist_Types.Symbolic(#Beta({alpha: alpha, beta: beta}))
))
meanValue
-> unpackFloat
-> expect
-> toBeCloseTo(1.0 /. (1.0 +. (beta /. alpha))) // https://en.wikipedia.org/wiki/Beta_distribution#Mean
})
// TODO: When we have our theory of validators we won't want this to be NaN but to be an error.
test("of beta(0, 0)", () => {
let meanValue = run(FromDist(
ToFloat(#Mean),
GenericDist_Types.Symbolic(#Beta({alpha: 0.0, beta: 0.0}))
))
meanValue
-> unpackFloat
-> expect
-> ExpectJs.toBeFalsy
})
testAll("of lognormal distributions", list{(2.0, 4.0), (1e-7, 1e-2), (-1e6, 10.0), (1e3, -1e2), (-1e8, -1e4), (1e2, 1e-5)}, tup => {
let (mu, sigma) = tup
let meanValue = run(FromDist(
ToFloat(#Mean),
GenericDist_Types.Symbolic(#Lognormal({mu: mu, sigma: sigma}))
))
meanValue
-> unpackFloat
-> expect
-> toBeCloseTo(Js.Math.exp(mu +. sigma ** 2.0 /. 2.0 )) // https://brilliant.org/wiki/log-normal-distribution/
})
testAll("of uniform distributions", list{(1e-5, 12.345), (-1e4, 1e4), (-1e16, -1e2), (5.3e3, 9e9)}, tup => {
let (low, high) = tup
let meanValue = run(FromDist(
ToFloat(#Mean),
GenericDist_Types.Symbolic(#Uniform({low: low, high: high}))
))
meanValue
-> unpackFloat
-> expect
-> toBeCloseTo((low +. high) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments
})
test("of a float", () => {
let meanValue = run(FromDist(
ToFloat(#Mean),
GenericDist_Types.Symbolic(#Float(7.7))
))
meanValue -> unpackFloat -> expect -> toBeCloseTo(7.7)
})
})
describe("Normal distribution with sparklines", () => {
let parameterWiseAdditionPdf = (n1: SymbolicDistTypes.normal, n2: SymbolicDistTypes.normal) => {
let normalDistAtSumMeanConstr = SymbolicDist.Normal.add(n1, n2)
let normalDistAtSumMean: SymbolicDistTypes.normal = switch normalDistAtSumMeanConstr {
| #Normal(params) => params
}
x => SymbolicDist.Normal.pdf(x, normalDistAtSumMean)
}
let normalDistAtMean5: SymbolicDistTypes.normal = {mean: 5.0, stdev: 2.0}
let normalDistAtMean10: SymbolicDistTypes.normal = {mean: 10.0, stdev: 2.0}
let range20Float = E.A.Floats.range(0.0, 20.0, 20) // [0.0,1.0,2.0,3.0,4.0,...19.0,]
test("mean=5 pdf", () => {
let pdfNormalDistAtMean5 = x => SymbolicDist.Normal.pdf(x, normalDistAtMean5)
let sparklineMean5 = fnImage(pdfNormalDistAtMean5, range20Float)
Sparklines.create(sparklineMean5, ())
-> expect
-> toEqual(`▁▂▃▆██▇▅▂▁▁▁▁▁▁▁▁▁▁▁`)
})
test("parameter-wise addition of two normal distributions", () => {
let sparklineMean15 = normalDistAtMean5 -> parameterWiseAdditionPdf(normalDistAtMean10) -> fnImage(range20Float)
Sparklines.create(sparklineMean15, ())
-> expect
-> toEqual(`▁▁▁▁▁▁▁▁▁▂▃▄▆███▇▅▄▂`)
})
test("mean=10 cdf", () => {
let cdfNormalDistAtMean10 = x => SymbolicDist.Normal.cdf(x, normalDistAtMean10)
let sparklineMean10 = fnImage(cdfNormalDistAtMean10, range20Float)
Sparklines.create(sparklineMean10, ())
-> expect
-> toEqual(`▁▁▁▁▁▁▁▁▂▄▅▇████████`)
})
})

View File

@ -1,76 +0,0 @@
open Jest
open Expect
let env: DistributionOperation.env = {
sampleCount: 100,
xyPointLength: 100,
}
let normalDist5: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0}))
let normalDist10: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 10.0, stdev: 2.0}))
let normalDist20: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 20.0, stdev: 2.0}))
let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0}))
let {toFloat, toDist, toString, toError} = module(DistributionOperation.Output)
let {run} = module(DistributionOperation)
let {fmap} = module(DistributionOperation.Output)
let run = run(~env)
let outputMap = fmap(~env)
let toExt: option<'a> => 'a = E.O.toExt(
"Should be impossible to reach (This error is in test file)",
)
describe("normalize", () => {
test("has no impact on normal dist", () => {
let result = run(FromDist(ToDist(Normalize), normalDist5))
expect(result)->toEqual(Dist(normalDist5))
})
})
describe("mean", () => {
test("for a normal distribution", () => {
let result = DistributionOperation.run(~env, FromDist(ToFloat(#Mean), normalDist5))
expect(result)->toEqual(Float(5.0))
})
})
describe("mixture", () => {
test("on two normal distributions", () => {
let result =
run(Mixture([(normalDist10, 0.5), (normalDist20, 0.5)]))
->outputMap(FromDist(ToFloat(#Mean)))
->toFloat
->toExt
expect(result)->toBeCloseTo(15.28)
})
})
describe("toPointSet", () => {
test("on symbolic normal distribution", () => {
let result =
run(FromDist(ToDist(ToPointSet), normalDist5))
->outputMap(FromDist(ToFloat(#Mean)))
->toFloat
->toExt
expect(result)->toBeCloseTo(5.09)
})
test("on sample set distribution with under 4 points", () => {
let result =
run(FromDist(ToDist(ToPointSet), SampleSet([0.0, 1.0, 2.0, 3.0])))->outputMap(
FromDist(ToFloat(#Mean)),
)
expect(result)->toEqual(GenDistError(Other("Converting sampleSet to pointSet failed")))
})
Skip.test("on sample set", () => {
let result =
run(FromDist(ToDist(ToPointSet), normalDist5))
->outputMap(FromDist(ToDist(ToSampleSet(1000))))
->outputMap(FromDist(ToDist(ToPointSet)))
->outputMap(FromDist(ToFloat(#Mean)))
->toFloat
->toExt
expect(result)->toBeCloseTo(5.09)
})
})

View File

@ -1,34 +1,87 @@
import { run } from '../src/js/index'; import { run, GenericDist, resultMap, makeSampleSetDist } from "../src/js/index";
let testRun = (x: string) => { let testRun = (x: string) => {
let result = run(x) let result = run(x);
if(result.tag == 'Ok'){ if (result.tag == "Ok") {
return { tag: 'Ok', value: result.value.exports } return { tag: "Ok", value: result.value.exports };
} } else {
else { return result;
return result
}
} }
};
describe("Simple calculations and results", () => { describe("Simple calculations and results", () => {
test("mean(normal(5,2))", () => { test("mean(normal(5,2))", () => {
expect(testRun("mean(normal(5,2))")).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 5 } ] }) expect(testRun("mean(normal(5,2))")).toEqual({
}) tag: "Ok",
value: [{ NAME: "Float", VAL: 5 }],
});
});
test("10+10", () => { test("10+10", () => {
let foo = testRun("10 + 10") let foo = testRun("10 + 10");
expect(foo).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 20 } ] }) expect(foo).toEqual({ tag: "Ok", value: [{ NAME: "Float", VAL: 20 }] });
}) });
}) });
describe("Log function", () => { describe("Log function", () => {
test("log(1) = 0", () => { test("log(1) = 0", () => {
let foo = testRun("log(1)") let foo = testRun("log(1)");
expect(foo).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 0} ]}) expect(foo).toEqual({ tag: "Ok", value: [{ NAME: "Float", VAL: 0 }] });
}) });
}) });
describe("Multimodal too many weights error", () => { describe("Multimodal too many weights error", () => {
test("mm(0,0,[0,0,0])", () => { test("mm(0,0,[0,0,0])", () => {
let foo = testRun("mm(0,0,[0,0,0])") let foo = testRun("mm(0,0,[0,0,0])");
expect(foo).toEqual({ "tag": "Error", "value": "Function multimodal error: Too many weights provided" }) expect(foo).toEqual({
}) tag: "Error",
value: "Function multimodal error: Too many weights provided",
});
});
});
describe("GenericDist", () => {
//It's important that sampleCount is less than 9. If it's more, than that will create randomness
//Also, note, the value should be created using makeSampleSetDist() later on.
let env = { sampleCount: 8, xyPointLength: 100 };
let dist = new GenericDist(
{ tag: "SampleSet", value: [3, 4, 5, 6, 6, 7, 10, 15, 30] },
env
);
let dist2 = new GenericDist(
{ tag: "SampleSet", value: [20, 22, 24, 29, 30, 35, 38, 44, 52] },
env
);
test("mean", () => {
expect(dist.mean().value).toBeCloseTo(3.737);
});
test("pdf", () => {
expect(dist.pdf(5.0).value).toBeCloseTo(0.0431);
});
test("cdf", () => {
expect(dist.cdf(5.0).value).toBeCloseTo(0.155);
});
test("inv", () => {
expect(dist.inv(0.5).value).toBeCloseTo(9.458);
});
test("toPointSet", () => {
expect(
resultMap(dist.toPointSet(), (r: GenericDist) => r.toString()).value.value
).toBe("Point Set Distribution");
});
test("toSparkline", () => {
expect(dist.toSparkline(20).value).toBe("▁▁▃▅███▆▄▃▂▁▁▂▂▃▂▁▁▁");
});
test("algebraicAdd", () => {
expect(
resultMap(dist.algebraicAdd(dist2), (r: GenericDist) => r.toSparkline(20))
.value.value
).toBe("▁▁▂▄▆████▇▆▄▄▃▃▃▂▁▁▁");
});
test("pointwiseAdd", () => {
expect(
resultMap(dist.pointwiseAdd(dist2), (r: GenericDist) => r.toSparkline(20))
.value.value
).toBe("▁▂▅██▅▅▅▆▇█▆▅▃▃▂▂▁▁▁");
});
}); });

View File

@ -9,6 +9,3 @@ let expectParseToBe = (expr: string, answer: string) =>
let expectEvalToBe = (expr: string, answer: string) => let expectEvalToBe = (expr: string, answer: string) =>
Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer) Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
// Current configuration does not ignore this file so we have to have a test
test("test helpers", () => expect(1)->toBe(1))

View File

@ -30,6 +30,9 @@ describe("eval on distribution functions", () => {
testEval("mean(normal(5,2))", "Ok(5)") testEval("mean(normal(5,2))", "Ok(5)")
testEval("mean(lognormal(1,2))", "Ok(20.085536923187668)") testEval("mean(lognormal(1,2))", "Ok(20.085536923187668)")
}) })
describe("toString", () => {
testEval("toString(normal(5,2))", "Ok('Normal(5,2)')")
})
describe("normalize", () => { describe("normalize", () => {
testEval("normalize(normal(5,2))", "Ok(Normal(5,2))") testEval("normalize(normal(5,2))", "Ok(Normal(5,2))")
}) })

View File

@ -1,47 +0,0 @@
open Jest
open Expect
let makeTest = (~only=false, str, item1, item2) =>
only
? Only.test(str, () => expect(item1) -> toEqual(item2))
: test(str, () => expect(item1) -> toEqual(item2))
describe("Lodash", () =>
describe("Lodash", () => {
makeTest(
"split",
SampleSet.Internals.T.splitContinuousAndDiscrete([1.432, 1.33455, 2.0]),
([1.432, 1.33455, 2.0], E.FloatFloatMap.empty()),
)
makeTest(
"split",
SampleSet.Internals.T.splitContinuousAndDiscrete([
1.432,
1.33455,
2.0,
2.0,
2.0,
2.0,
]) |> (((c, disc)) => (c, disc |> E.FloatFloatMap.toArray)),
([1.432, 1.33455], [(2.0, 4.0)]),
)
let makeDuplicatedArray = count => {
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
E.A.concatMany([sorted, sorted, sorted, sorted]) |> Belt.SortArray.stableSortBy(_, compare)
}
let (_, discrete) = SampleSet.Internals.T.splitContinuousAndDiscrete(
makeDuplicatedArray(10),
)
let toArr = discrete |> E.FloatFloatMap.toArray
makeTest("splitMedium", toArr |> Belt.Array.length, 10)
let (_c, discrete) = SampleSet.Internals.T.splitContinuousAndDiscrete(
makeDuplicatedArray(500),
)
let toArr = discrete |> E.FloatFloatMap.toArray
makeTest("splitMedium", toArr |> Belt.Array.length, 500)
})
)

View File

@ -1,33 +0,0 @@
open Jest
open Expect
open Js.Array
open SymbolicDist
let makeTest = (~only=false, str, item1, item2) =>
only
? Only.test(str, () => expect(item1) -> toEqual(item2))
: test(str, () => expect(item1) -> toEqual(item2))
let pdfImage = (thePdf, inps) => map(thePdf, inps)
let parameterWiseAdditionHelper = (n1: SymbolicDistTypes.normal, n2: SymbolicDistTypes.normal) => {
let normalDistAtSumMeanConstr = Normal.add(n1, n2)
let normalDistAtSumMean: SymbolicDistTypes.normal = switch normalDistAtSumMeanConstr {
| #Normal(params) => params
}
x => Normal.pdf(x, normalDistAtSumMean)
}
describe("Normal distribution with sparklines", () => {
let normalDistAtMean5: SymbolicDistTypes.normal = {mean: 5.0, stdev: 2.0}
let normalDistAtMean10: SymbolicDistTypes.normal = {mean: 10.0, stdev: 2.0}
let range20Float = E.A.rangeFloat(0, 20) // [0.0,1.0,2.0,3.0,4.0,...19.0,]
let pdfNormalDistAtMean5 = x => Normal.pdf(x, normalDistAtMean5)
let sparklineMean5 = pdfImage(pdfNormalDistAtMean5, range20Float)
makeTest("mean=5", Sparklines.create(sparklineMean5, ()), `▁▂▃▅███▅▃▂▁▁▁▁▁▁▁▁▁▁▁`)
let sparklineMean15 = normalDistAtMean5 -> parameterWiseAdditionHelper(normalDistAtMean10) -> pdfImage(range20Float)
makeTest("parameter-wise addition of two normal distributions", Sparklines.create(sparklineMean15, ()), `▁▁▁▁▁▁▁▁▁▁▂▃▅▇███▇▅▃▂`)
})

View File

@ -0,0 +1,26 @@
open Jest
open Expect
let makeTest = (~only=false, str, item1, item2) =>
only
? Only.test(str, () => expect(item1) -> toEqual(item2))
: test(str, () => expect(item1) -> toEqual(item2))
let {toFloat, toDist, toString, toError, fmap} = module(DistributionOperation.Output)
let fnImage = (theFn, inps) => Js.Array.map(theFn, inps)
let env: DistributionOperation.env = {
sampleCount: 100,
xyPointLength: 100,
}
let run = DistributionOperation.run(~env)
let outputMap = fmap(~env)
let unreachableInTestFileMessage = "Should be impossible to reach (This error is in test file)"
let toExtFloat: option<float> => float = E.O.toExt(unreachableInTestFileMessage)
let toExtDist: option<GenericDist_Types.genericDist> => GenericDist_Types.genericDist = E.O.toExt(unreachableInTestFileMessage)
// let toExt: option<'a> => 'a = E.O.toExt(unreachableInTestFileMessage)
let unpackFloat = x => x -> toFloat -> toExtFloat
let unpackDist = y => y -> toDist -> toExtDist

View File

@ -33,7 +33,7 @@
"gentypeconfig": { "gentypeconfig": {
"language": "typescript", "language": "typescript",
"module": "commonjs", "module": "commonjs",
"shims": {}, "shims": {"Js": "Js"},
"debug": { "debug": {
"all": false, "all": false,
"basic": false "basic": false
@ -47,7 +47,7 @@
[ [
"../../node_modules/bisect_ppx/ppx", "../../node_modules/bisect_ppx/ppx",
"--exclude-files", "--exclude-files",
".*_Test\\.res$$" ".*_test\\.res$$"
] ]
] ]
} }

View File

@ -1,8 +1,13 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = { module.exports = {
preset: 'ts-jest', preset: "ts-jest",
testEnvironment: 'node', testEnvironment: "node",
setupFilesAfterEnv: [ setupFilesAfterEnv: [
"<rootdir>/../../node_modules/bisect_ppx/src/runtime/js/jest.bs.js" "<rootdir>/../../node_modules/bisect_ppx/src/runtime/js/jest.bs.js",
],
testPathIgnorePatterns: [
".*Fixtures.bs.js",
"/node_modules/",
".*Helpers.bs.js",
], ],
}; };

View File

@ -10,7 +10,7 @@
"test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'", "test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'",
"test": "jest", "test": "jest",
"test:watch": "jest --watchAll", "test:watch": "jest --watchAll",
"coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; ./node_modules/.bin/bisect-ppx-report html", "coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report html",
"all": "yarn build && yarn bundle && yarn test" "all": "yarn build && yarn bundle && yarn test"
}, },
"keywords": [ "keywords": [

View File

@ -1,17 +1,228 @@
import {runAll} from '../rescript/ProgramEvaluator.gen'; import { runAll } from "../rescript/ProgramEvaluator.gen";
import type { Inputs_SamplingInputs_t as SamplingInputs, exportEnv, exportType, exportDistribution} from '../rescript/ProgramEvaluator.gen'; import type {
export type { SamplingInputs, exportEnv, exportDistribution } Inputs_SamplingInputs_t as SamplingInputs,
export type {t as DistPlus} from '../rescript/OldInterpreter/DistPlus.gen'; exportEnv,
exportType,
exportDistribution,
} from "../rescript/ProgramEvaluator.gen";
export type { SamplingInputs, exportEnv, exportDistribution };
export type { t as DistPlus } from "../rescript/OldInterpreter/DistPlus.gen";
import {
genericDist,
env,
resultDist,
resultFloat,
resultString,
} from "../rescript/TypescriptInterface.gen";
export {makeSampleSetDist} from "../rescript/TypescriptInterface.gen";
import {
Constructors_mean,
Constructors_sample,
Constructors_pdf,
Constructors_cdf,
Constructors_inv,
Constructors_normalize,
Constructors_toPointSet,
Constructors_toSampleSet,
Constructors_truncate,
Constructors_inspect,
Constructors_toString,
Constructors_toSparkline,
Constructors_algebraicAdd,
Constructors_algebraicMultiply,
Constructors_algebraicDivide,
Constructors_algebraicSubtract,
Constructors_algebraicLogarithm,
Constructors_algebraicPower,
Constructors_pointwiseAdd,
Constructors_pointwiseMultiply,
Constructors_pointwiseDivide,
Constructors_pointwiseSubtract,
Constructors_pointwiseLogarithm,
Constructors_pointwisePower,
} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen";
export let defaultSamplingInputs: SamplingInputs = { export let defaultSamplingInputs: SamplingInputs = {
sampleCount: 10000, sampleCount: 10000,
outputXYPoints: 10000, outputXYPoints: 10000,
pointDistLength : 1000 pointDistLength: 1000,
};
export function run(
squiggleString: string,
samplingInputs?: SamplingInputs,
environment?: exportEnv
): { tag: "Ok"; value: exportType } | { tag: "Error"; value: string } {
let si: SamplingInputs = samplingInputs
? samplingInputs
: defaultSamplingInputs;
let env: exportEnv = environment ? environment : [];
return runAll(squiggleString, si, env);
} }
export function run(squiggleString : string, samplingInputs? : SamplingInputs, environment?: exportEnv) : { tag: "Ok"; value: exportType } //This is clearly not fully typed. I think later we should use a functional library to
| { tag: "Error"; value: string } { // provide a better Either type and corresponding functions.
let si : SamplingInputs = samplingInputs ? samplingInputs : defaultSamplingInputs type result =
let env : exportEnv = environment ? environment : [] | {
return runAll(squiggleString, si, env) tag: "Ok";
value: any;
}
| {
tag: "Error";
value: any;
};
export function resultMap(r: result, mapFn: any): result {
if (r.tag === "Ok") {
return { tag: "Ok", value: mapFn(r.value) };
} else {
return r;
}
}
export function resultExn(r: result): any {
r.value
}
export class GenericDist {
t: genericDist;
env: env;
constructor(t: genericDist, env: env) {
this.t = t;
this.env = env;
return this;
}
mapResultDist(r: resultDist) {
return resultMap(r, (v: genericDist) => new GenericDist(v, this.env));
}
mean() {
return Constructors_mean({ env: this.env }, this.t);
}
sample(): resultFloat {
return Constructors_sample({ env: this.env }, this.t);
}
pdf(n: number): resultFloat {
return Constructors_pdf({ env: this.env }, this.t, n);
}
cdf(n: number): resultFloat {
return Constructors_cdf({ env: this.env }, this.t, n);
}
inv(n: number): resultFloat {
return Constructors_inv({ env: this.env }, this.t, n);
}
normalize() {
return this.mapResultDist(
Constructors_normalize({ env: this.env }, this.t)
);
}
toPointSet() {
return this.mapResultDist(
Constructors_toPointSet({ env: this.env }, this.t)
);
}
toSampleSet(n: number) {
return this.mapResultDist(
Constructors_toSampleSet({ env: this.env }, this.t, n)
);
}
truncate(left: number, right: number) {
return this.mapResultDist(
Constructors_truncate({ env: this.env }, this.t, left, right)
);
}
inspect() {
return this.mapResultDist(Constructors_inspect({ env: this.env }, this.t));
}
toString(): resultString {
return Constructors_toString({ env: this.env }, this.t);
}
toSparkline(n: number): resultString {
return Constructors_toSparkline({ env: this.env }, this.t, n);
}
algebraicAdd(d2: GenericDist) {
return this.mapResultDist(
Constructors_algebraicAdd({ env: this.env }, this.t, d2.t)
);
}
algebraicMultiply(d2: GenericDist) {
return this.mapResultDist(
Constructors_algebraicMultiply({ env: this.env }, this.t, d2.t)
);
}
algebraicDivide(d2: GenericDist) {
return this.mapResultDist(
Constructors_algebraicDivide({ env: this.env }, this.t, d2.t)
);
}
algebraicSubtract(d2: GenericDist) {
return this.mapResultDist(
Constructors_algebraicSubtract({ env: this.env }, this.t, d2.t)
);
}
algebraicLogarithm(d2: GenericDist) {
return this.mapResultDist(
Constructors_algebraicLogarithm({ env: this.env }, this.t, d2.t)
);
}
algebraicPower(d2: GenericDist) {
return this.mapResultDist(
Constructors_algebraicPower({ env: this.env }, this.t, d2.t)
);
}
pointwiseAdd(d2: GenericDist) {
return this.mapResultDist(
Constructors_pointwiseAdd({ env: this.env }, this.t, d2.t)
);
}
pointwiseMultiply(d2: GenericDist) {
return this.mapResultDist(
Constructors_pointwiseMultiply({ env: this.env }, this.t, d2.t)
);
}
pointwiseDivide(d2: GenericDist) {
return this.mapResultDist(
Constructors_pointwiseDivide({ env: this.env }, this.t, d2.t)
);
}
pointwiseSubtract(d2: GenericDist) {
return this.mapResultDist(
Constructors_pointwiseSubtract({ env: this.env }, this.t, d2.t)
);
}
pointwiseLogarithm(d2: GenericDist) {
return this.mapResultDist(
Constructors_pointwiseLogarithm({ env: this.env }, this.t, d2.t)
);
}
pointwisePower(d2: GenericDist) {
return this.mapResultDist(
Constructors_pointwisePower({ env: this.env }, this.t, d2.t)
);
}
} }

View File

@ -10,10 +10,10 @@ type env = {
} }
type outputType = type outputType =
| Dist(GenericDist_Types.genericDist) | Dist(genericDist)
| Float(float) | Float(float)
| String(string) | String(string)
| GenDistError(GenericDist_Types.error) | GenDistError(error)
/* /*
We're going to add another function to this module later, so first define a We're going to add another function to this module later, so first define a
@ -113,7 +113,11 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
GenericDist.toFloatOperation(dist, ~toPointSetFn, ~distToFloatOperation) GenericDist.toFloatOperation(dist, ~toPointSetFn, ~distToFloatOperation)
->E.R2.fmap(r => Float(r)) ->E.R2.fmap(r => Float(r))
->OutputLocal.fromResult ->OutputLocal.fromResult
| ToString => dist->GenericDist.toString->String | ToString(ToString) => dist->GenericDist.toString->String
| ToString(ToSparkline(bucketCount)) =>
GenericDist.toSparkline(dist, ~sampleCount, ~bucketCount, ())
->E.R2.fmap(r => String(r))
->OutputLocal.fromResult
| ToDist(Inspect) => { | ToDist(Inspect) => {
Js.log2("Console log requested: ", dist) Js.log2("Console log requested: ", dist)
Dist(dist) Dist(dist)
@ -124,10 +128,13 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
->E.R2.fmap(r => Dist(r)) ->E.R2.fmap(r => Dist(r))
->OutputLocal.fromResult ->OutputLocal.fromResult
| ToDist(ToSampleSet(n)) => | ToDist(ToSampleSet(n)) =>
dist->GenericDist.sampleN(n)->E.R2.fmap(r => Dist(SampleSet(r)))->OutputLocal.fromResult dist
->GenericDist.toSampleSetDist(n)
->E.R2.fmap(r => Dist(SampleSet(r)))
->OutputLocal.fromResult
| ToDist(ToPointSet) => | ToDist(ToPointSet) =>
dist dist
->GenericDist.toPointSet(~xyPointLength, ~sampleCount) ->GenericDist.toPointSet(~xyPointLength, ~sampleCount, ())
->E.R2.fmap(r => Dist(PointSet(r))) ->E.R2.fmap(r => Dist(PointSet(r)))
->OutputLocal.fromResult ->OutputLocal.fromResult
| ToDistCombination(Algebraic, _, #Float(_)) => GenDistError(NotYetImplemented) | ToDistCombination(Algebraic, _, #Float(_)) => GenDistError(NotYetImplemented)
@ -181,3 +188,43 @@ module Output = {
newFnCall->E.R2.fmap(run(~env))->OutputLocal.fromResult newFnCall->E.R2.fmap(run(~env))->OutputLocal.fromResult
} }
} }
// See comment above GenericDist_Types.Constructors to explain the purpose of this module.
// I tried having another internal module called UsingDists, similar to how its done in
// GenericDist_Types.Constructors. However, this broke GenType for me, so beware.
module Constructors = {
module C = GenericDist_Types.Constructors.UsingDists
open OutputLocal
let mean = (~env, dist) => C.mean(dist)->run(~env)->toFloatR
let sample = (~env, dist) => C.sample(dist)->run(~env)->toFloatR
let cdf = (~env, dist, f) => C.cdf(dist, f)->run(~env)->toFloatR
let inv = (~env, dist, f) => C.inv(dist, f)->run(~env)->toFloatR
let pdf = (~env, dist, f) => C.pdf(dist, f)->run(~env)->toFloatR
let normalize = (~env, dist) => C.normalize(dist)->run(~env)->toDistR
let toPointSet = (~env, dist) => C.toPointSet(dist)->run(~env)->toDistR
let toSampleSet = (~env, dist, n) => C.toSampleSet(dist, n)->run(~env)->toDistR
let truncate = (~env, dist, leftCutoff, rightCutoff) =>
C.truncate(dist, leftCutoff, rightCutoff)->run(~env)->toDistR
let inspect = (~env, dist) => C.inspect(dist)->run(~env)->toDistR
let toString = (~env, dist) => C.toString(dist)->run(~env)->toStringR
let toSparkline = (~env, dist, bucketCount) =>
C.toSparkline(dist, bucketCount)->run(~env)->toStringR
let algebraicAdd = (~env, dist1, dist2) => C.algebraicAdd(dist1, dist2)->run(~env)->toDistR
let algebraicMultiply = (~env, dist1, dist2) =>
C.algebraicMultiply(dist1, dist2)->run(~env)->toDistR
let algebraicDivide = (~env, dist1, dist2) => C.algebraicDivide(dist1, dist2)->run(~env)->toDistR
let algebraicSubtract = (~env, dist1, dist2) =>
C.algebraicSubtract(dist1, dist2)->run(~env)->toDistR
let algebraicLogarithm = (~env, dist1, dist2) =>
C.algebraicLogarithm(dist1, dist2)->run(~env)->toDistR
let algebraicPower = (~env, dist1, dist2) => C.algebraicPower(dist1, dist2)->run(~env)->toDistR
let pointwiseAdd = (~env, dist1, dist2) => C.pointwiseAdd(dist1, dist2)->run(~env)->toDistR
let pointwiseMultiply = (~env, dist1, dist2) =>
C.pointwiseMultiply(dist1, dist2)->run(~env)->toDistR
let pointwiseDivide = (~env, dist1, dist2) => C.pointwiseDivide(dist1, dist2)->run(~env)->toDistR
let pointwiseSubtract = (~env, dist1, dist2) =>
C.pointwiseSubtract(dist1, dist2)->run(~env)->toDistR
let pointwiseLogarithm = (~env, dist1, dist2) =>
C.pointwiseLogarithm(dist1, dist2)->run(~env)->toDistR
let pointwisePower = (~env, dist1, dist2) => C.pointwisePower(dist1, dist2)->run(~env)->toDistR
}

View File

@ -1,19 +1,24 @@
@genType
type env = { type env = {
sampleCount: int, sampleCount: int,
xyPointLength: int, xyPointLength: int,
} }
open GenericDist_Types
@genType
type outputType = type outputType =
| Dist(GenericDist_Types.genericDist) | Dist(genericDist)
| Float(float) | Float(float)
| String(string) | String(string)
| GenDistError(GenericDist_Types.error) | GenDistError(error)
@genType
let run: (~env: env, GenericDist_Types.Operation.genericFunctionCallInfo) => outputType let run: (~env: env, GenericDist_Types.Operation.genericFunctionCallInfo) => outputType
let runFromDist: ( let runFromDist: (
~env: env, ~env: env,
~functionCallInfo: GenericDist_Types.Operation.fromDist, ~functionCallInfo: GenericDist_Types.Operation.fromDist,
GenericDist_Types.genericDist, genericDist,
) => outputType ) => outputType
let runFromFloat: ( let runFromFloat: (
~env: env, ~env: env,
@ -23,12 +28,68 @@ let runFromFloat: (
module Output: { module Output: {
type t = outputType type t = outputType
let toDist: t => option<GenericDist_Types.genericDist> let toDist: t => option<genericDist>
let toDistR: t => result<GenericDist_Types.genericDist, GenericDist_Types.error> let toDistR: t => result<genericDist, error>
let toFloat: t => option<float> let toFloat: t => option<float>
let toFloatR: t => result<float, GenericDist_Types.error> let toFloatR: t => result<float, error>
let toString: t => option<string> let toString: t => option<string>
let toStringR: t => result<string, GenericDist_Types.error> let toStringR: t => result<string, error>
let toError: t => option<GenericDist_Types.error> let toError: t => option<error>
let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t
} }
module Constructors: {
@genType
let mean: (~env: env, genericDist) => result<float, error>
@genType
let sample: (~env: env, genericDist) => result<float, error>
@genType
let cdf: (~env: env, genericDist, float) => result<float, error>
@genType
let inv: (~env: env, genericDist, float) => result<float, error>
@genType
let pdf: (~env: env, genericDist, float) => result<float, error>
@genType
let normalize: (~env: env, genericDist) => result<genericDist, error>
@genType
let toPointSet: (~env: env, genericDist) => result<genericDist, error>
@genType
let toSampleSet: (~env: env, genericDist, int) => result<genericDist, error>
@genType
let truncate: (
~env: env,
genericDist,
option<float>,
option<float>,
) => result<genericDist, error>
@genType
let inspect: (~env: env, genericDist) => result<genericDist, error>
@genType
let toString: (~env: env, genericDist) => result<string, error>
@genType
let toSparkline: (~env: env, genericDist, int) => result<string, error>
@genType
let algebraicAdd: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let algebraicMultiply: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let algebraicDivide: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let algebraicSubtract: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let algebraicLogarithm: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let algebraicPower: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let pointwiseAdd: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let pointwiseMultiply: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let pointwiseDivide: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let pointwiseSubtract: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let pointwiseLogarithm: (~env: env, genericDist, genericDist) => result<genericDist, error>
@genType
let pointwisePower: (~env: env, genericDist, genericDist) => result<genericDist, error>
}

View File

@ -19,7 +19,7 @@ module Operation = {
| #Multiply | #Multiply
| #Subtract | #Subtract
| #Divide | #Divide
| #Exponentiate | #Power
| #Logarithm | #Logarithm
] ]
@ -28,7 +28,7 @@ module Operation = {
| #Add => \"+." | #Add => \"+."
| #Multiply => \"*." | #Multiply => \"*."
| #Subtract => \"-." | #Subtract => \"-."
| #Exponentiate => \"**" | #Power => \"**"
| #Divide => \"/." | #Divide => \"/."
| #Logarithm => (a, b) => log(a) /. log(b) | #Logarithm => (a, b) => log(a) /. log(b)
} }

View File

@ -2,17 +2,20 @@
type t = GenericDist_Types.genericDist type t = GenericDist_Types.genericDist
type error = GenericDist_Types.error type error = GenericDist_Types.error
type toPointSetFn = t => result<PointSetTypes.pointSetDist, error> type toPointSetFn = t => result<PointSetTypes.pointSetDist, error>
type toSampleSetFn = t => result<array<float>, error> type toSampleSetFn = t => result<SampleSetDist.t, error>
type scaleMultiplyFn = (t, float) => result<t, error> type scaleMultiplyFn = (t, float) => result<t, error>
type pointwiseAddFn = (t, t) => result<t, error> type pointwiseAddFn = (t, t) => result<t, error>
let sampleN = (t: t, n) => let sampleN = (t: t, n) =>
switch t { switch t {
| PointSet(r) => Ok(PointSetDist.sampleNRendered(n, r)) | PointSet(r) => PointSetDist.sampleNRendered(n, r)
| Symbolic(r) => Ok(SymbolicDist.T.sampleN(n, r)) | Symbolic(r) => SymbolicDist.T.sampleN(n, r)
| SampleSet(_) => Error(GenericDist_Types.NotYetImplemented) | SampleSet(r) => SampleSetDist.sampleN(r, n)
} }
let toSampleSetDist = (t: t, n) =>
SampleSetDist.make(sampleN(t, n))->GenericDist_Types.Error.resultStringToResultError
let fromFloat = (f: float): t => Symbolic(SymbolicDist.Float.make(f)) let fromFloat = (f: float): t => Symbolic(SymbolicDist.Float.make(f))
let toString = (t: t) => let toString = (t: t) =>
@ -49,15 +52,21 @@ let toFloatOperation = (
} }
} }
//Todo: If it's a pointSet, but the xyPointLenght is different from what it has, it should change. //Todo: If it's a pointSet, but the xyPointLength is different from what it has, it should change.
// This is tricky because the case of discrete distributions. // This is tricky because the case of discrete distributions.
// Also, change the outputXYPoints/pointSetDistLength details // Also, change the outputXYPoints/pointSetDistLength details
let toPointSet = (~xyPointLength, ~sampleCount, t): result<PointSetTypes.pointSetDist, error> => { let toPointSet = (
t,
~xyPointLength,
~sampleCount,
~xSelection: GenericDist_Types.Operation.pointsetXSelection=#ByWeight,
unit,
): result<PointSetTypes.pointSetDist, error> => {
switch (t: t) { switch (t: t) {
| PointSet(pointSet) => Ok(pointSet) | PointSet(pointSet) => Ok(pointSet)
| Symbolic(r) => Ok(SymbolicDist.T.toPointSetDist(xyPointLength, r)) | Symbolic(r) => Ok(SymbolicDist.T.toPointSetDist(~xSelection, xyPointLength, r))
| SampleSet(r) => { | SampleSet(r) =>
let response = SampleSet.toPointSetDist( SampleSetDist.toPointSetDist(
~samples=r, ~samples=r,
~samplingInputs={ ~samplingInputs={
sampleCount: sampleCount, sampleCount: sampleCount,
@ -65,16 +74,23 @@ let toPointSet = (~xyPointLength, ~sampleCount, t): result<PointSetTypes.pointSe
pointSetDistLength: xyPointLength, pointSetDistLength: xyPointLength,
kernelWidth: None, kernelWidth: None,
}, },
(), )->GenericDist_Types.Error.resultStringToResultError
).pointSetDist
switch response {
| Some(r) => Ok(r)
| None => Error(Other("Converting sampleSet to pointSet failed"))
}
}
} }
} }
/*
PointSetDist.toSparkline calls "downsampleEquallyOverX", which downsamples it to n=bucketCount.
It first needs a pointSetDist, so we convert to a pointSetDist. In this process we want the
xyPointLength to be a bit longer than the eventual toSparkline downsampling. I chose 3
fairly arbitrarily.
*/
let toSparkline = (t: t, ~sampleCount: int, ~bucketCount: int=20, unit): result<string, error> =>
t
->toPointSet(~xSelection=#Linear, ~xyPointLength=bucketCount * 3, ~sampleCount, ())
->E.R.bind(r =>
r->PointSetDist.toSparkline(bucketCount)->GenericDist_Types.Error.resultStringToResultError
)
module Truncate = { module Truncate = {
let trySymbolicSimplification = (leftCutoff, rightCutoff, t: t): option<t> => let trySymbolicSimplification = (leftCutoff, rightCutoff, t: t): option<t> =>
switch (leftCutoff, rightCutoff, t) { switch (leftCutoff, rightCutoff, t) {
@ -147,10 +163,12 @@ module AlgebraicCombination = {
t1: t, t1: t,
t2: t, t2: t,
) => { ) => {
let arithmeticOperation = Operation.Algebraic.toFn(arithmeticOperation) let fn = Operation.Algebraic.toFn(arithmeticOperation)
E.R.merge(toSampleSet(t1), toSampleSet(t2))->E.R2.fmap(((a, b)) => { E.R.merge(toSampleSet(t1), toSampleSet(t2))
Belt.Array.zip(a, b)->E.A2.fmap(((a, b)) => arithmeticOperation(a, b)) ->E.R.bind(((t1, t2)) => {
SampleSetDist.map2(~fn, ~t1, ~t2)->GenericDist_Types.Error.resultStringToResultError
}) })
->E.R2.fmap(r => GenericDist_Types.SampleSet(r))
} }
//I'm (Ozzie) really just guessing here, very little idea what's best //I'm (Ozzie) really just guessing here, very little idea what's best
@ -181,13 +199,7 @@ module AlgebraicCombination = {
| Some(Error(e)) => Error(Other(e)) | Some(Error(e)) => Error(Other(e))
| None => | None =>
switch chooseConvolutionOrMonteCarlo(t1, t2) { switch chooseConvolutionOrMonteCarlo(t1, t2) {
| #CalculateWithMonteCarlo => | #CalculateWithMonteCarlo => runMonteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
runMonteCarlo(
toSampleSetFn,
arithmeticOperation,
t1,
t2,
)->E.R2.fmap(r => GenericDist_Types.SampleSet(r))
| #CalculateWithConvolution => | #CalculateWithConvolution =>
runConvolution( runConvolution(
toPointSetFn, toPointSetFn,
@ -228,7 +240,7 @@ let pointwiseCombinationFloat = (
): result<t, error> => { ): result<t, error> => {
let m = switch arithmeticOperation { let m = switch arithmeticOperation {
| #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid) | #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid)
| (#Multiply | #Divide | #Exponentiate | #Logarithm) as arithmeticOperation => | (#Multiply | #Divide | #Power | #Logarithm) as arithmeticOperation =>
toPointSetFn(t)->E.R2.fmap(t => { toPointSetFn(t)->E.R2.fmap(t => {
//TODO: Move to PointSet codebase //TODO: Move to PointSet codebase
let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary) let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary)
@ -253,7 +265,7 @@ let mixture = (
~pointwiseAddFn: pointwiseAddFn, ~pointwiseAddFn: pointwiseAddFn,
) => { ) => {
if E.A.length(values) == 0 { if E.A.length(values) == 0 {
Error(GenericDist_Types.Other("mixture must have at least 1 element")) Error(GenericDist_Types.Other("Mixture error: mixture must have at least 1 element"))
} else { } else {
let totalWeight = values->E.A2.fmap(E.Tuple2.second)->E.A.Floats.sum let totalWeight = values->E.A2.fmap(E.Tuple2.second)->E.A.Floats.sum
let properlyWeightedValues = let properlyWeightedValues =

View File

@ -1,11 +1,13 @@
type t = GenericDist_Types.genericDist type t = GenericDist_Types.genericDist
type error = GenericDist_Types.error type error = GenericDist_Types.error
type toPointSetFn = t => result<PointSetTypes.pointSetDist, error> type toPointSetFn = t => result<PointSetTypes.pointSetDist, error>
type toSampleSetFn = t => result<array<float>, error> type toSampleSetFn = t => result<SampleSetDist.t, error>
type scaleMultiplyFn = (t, float) => result<t, error> type scaleMultiplyFn = (t, float) => result<t, error>
type pointwiseAddFn = (t, t) => result<t, error> type pointwiseAddFn = (t, t) => result<t, error>
let sampleN: (t, int) => result<array<float>, error> let sampleN: (t, int) => array<float>
let toSampleSetDist: (t, int) => Belt.Result.t<QuriSquiggleLang.SampleSetDist.t, error>
let fromFloat: float => t let fromFloat: float => t
@ -20,17 +22,20 @@ let toFloatOperation: (
) => result<float, error> ) => result<float, error>
let toPointSet: ( let toPointSet: (
t,
~xyPointLength: int, ~xyPointLength: int,
~sampleCount: int, ~sampleCount: int,
t, ~xSelection: GenericDist_Types.Operation.pointsetXSelection=?,
unit,
) => result<PointSetTypes.pointSetDist, error> ) => result<PointSetTypes.pointSetDist, error>
let toSparkline: (t, ~sampleCount: int, ~bucketCount: int=?, unit) => result<string, error>
let truncate: ( let truncate: (
t, t,
~toPointSetFn: toPointSetFn, ~toPointSetFn: toPointSetFn,
~leftCutoff: option<float>=?, ~leftCutoff: option<float>=?,
~rightCutoff: option<float>=?, ~rightCutoff: option<float>=?,
unit unit,
) => result<t, error> ) => result<t, error>
let algebraicCombination: ( let algebraicCombination: (

View File

@ -1,14 +1,24 @@
type genericDist = type genericDist =
| PointSet(PointSetTypes.pointSetDist) | PointSet(PointSetTypes.pointSetDist)
| SampleSet(array<float>) | SampleSet(SampleSetDist.t)
| Symbolic(SymbolicDistTypes.symbolicDist) | Symbolic(SymbolicDistTypes.symbolicDist)
@genType
type error = type error =
| NotYetImplemented | NotYetImplemented
| Unreachable | Unreachable
| DistributionVerticalShiftIsInvalid | DistributionVerticalShiftIsInvalid
| Other(string) | Other(string)
module Error = {
type t = error
let fromString = (s: string): t => Other(s)
let resultStringToResultError: result<'a, string> => result<'a, error> = n =>
n->E.R2.errMap(r => r->fromString->Error)
}
module Operation = { module Operation = {
type direction = type direction =
| Algebraic | Algebraic
@ -19,7 +29,7 @@ module Operation = {
| #Multiply | #Multiply
| #Subtract | #Subtract
| #Divide | #Divide
| #Exponentiate | #Power
| #Logarithm | #Logarithm
] ]
@ -28,7 +38,7 @@ module Operation = {
| #Add => \"+." | #Add => \"+."
| #Multiply => \"*." | #Multiply => \"*."
| #Subtract => \"-." | #Subtract => \"-."
| #Exponentiate => \"**" | #Power => \"**"
| #Divide => \"/." | #Divide => \"/."
| #Logarithm => (a, b) => log(a) /. log(b) | #Logarithm => (a, b) => log(a) /. log(b)
} }
@ -41,6 +51,8 @@ module Operation = {
| #Sample | #Sample
] ]
type pointsetXSelection = [#Linear | #ByWeight]
type toDist = type toDist =
| Normalize | Normalize
| ToPointSet | ToPointSet
@ -50,16 +62,21 @@ module Operation = {
type toFloatArray = Sample(int) type toFloatArray = Sample(int)
type toString =
| ToString
| ToSparkline(int)
type fromDist = type fromDist =
| ToFloat(toFloat) | ToFloat(toFloat)
| ToDist(toDist) | ToDist(toDist)
| ToDistCombination(direction, arithmeticOperation, [#Dist(genericDist) | #Float(float)]) | ToDistCombination(direction, arithmeticOperation, [#Dist(genericDist) | #Float(float)])
| ToString | ToString(toString)
type singleParamaterFunction = type singleParamaterFunction =
| FromDist(fromDist) | FromDist(fromDist)
| FromFloat(fromDist) | FromFloat(fromDist)
@genType
type genericFunctionCallInfo = type genericFunctionCallInfo =
| FromDist(fromDist, genericDist) | FromDist(fromDist, genericDist)
| FromFloat(fromDist, float) | FromFloat(fromDist, float)
@ -77,7 +94,8 @@ module Operation = {
| ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})` | ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})`
| ToDist(Truncate(_, _)) => `truncate` | ToDist(Truncate(_, _)) => `truncate`
| ToDist(Inspect) => `inspect` | ToDist(Inspect) => `inspect`
| ToString => `toString` | ToString(ToString) => `toString`
| ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})`
| ToDistCombination(Algebraic, _, _) => `algebraic` | ToDistCombination(Algebraic, _, _) => `algebraic`
| ToDistCombination(Pointwise, _, _) => `pointwise` | ToDistCombination(Pointwise, _, _) => `pointwise`
} }
@ -88,3 +106,79 @@ module Operation = {
| Mixture(_) => `mixture` | Mixture(_) => `mixture`
} }
} }
/*
It can be a pain to write out the genericFunctionCallInfo. The constructors help with this.
This code only covers some of genericFunctionCallInfo: many arguments could be called with either a
float or a distribution. The "UsingDists" module assumes that everything is a distribution.
This is a tradeoff of some generality in order to get a bit more simplicity.
I could see having a longer interface in the future, but it could be messy.
Like, algebraicAddDistFloat vs. algebraicAddDistDist
*/
module Constructors = {
type t = Operation.genericFunctionCallInfo
module UsingDists = {
@genType
let mean = (dist): t => FromDist(ToFloat(#Mean), dist)
let sample = (dist): t => FromDist(ToFloat(#Sample), dist)
let cdf = (dist, x): t => FromDist(ToFloat(#Cdf(x)), dist)
let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist)
let pdf = (dist, x): t => FromDist(ToFloat(#Pdf(x)), dist)
let normalize = (dist): t => FromDist(ToDist(Normalize), dist)
let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist)
let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist)
let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist)
let inspect = (dist): t => FromDist(ToDist(Inspect), dist)
let toString = (dist): t => FromDist(ToString(ToString), dist)
let toSparkline = (dist, n): t => FromDist(ToString(ToSparkline(n)), dist)
let algebraicAdd = (dist1, dist2: genericDist): t => FromDist(
ToDistCombination(Algebraic, #Add, #Dist(dist2)),
dist1,
)
let algebraicMultiply = (dist1, dist2): t => FromDist(
ToDistCombination(Algebraic, #Multiply, #Dist(dist2)),
dist1,
)
let algebraicDivide = (dist1, dist2): t => FromDist(
ToDistCombination(Algebraic, #Divide, #Dist(dist2)),
dist1,
)
let algebraicSubtract = (dist1, dist2): t => FromDist(
ToDistCombination(Algebraic, #Subtract, #Dist(dist2)),
dist1,
)
let algebraicLogarithm = (dist1, dist2): t => FromDist(
ToDistCombination(Algebraic, #Logarithm, #Dist(dist2)),
dist1,
)
let algebraicPower = (dist1, dist2): t => FromDist(
ToDistCombination(Algebraic, #Power, #Dist(dist2)),
dist1,
)
let pointwiseAdd = (dist1, dist2): t => FromDist(
ToDistCombination(Pointwise, #Add, #Dist(dist2)),
dist1,
)
let pointwiseMultiply = (dist1, dist2): t => FromDist(
ToDistCombination(Pointwise, #Multiply, #Dist(dist2)),
dist1,
)
let pointwiseDivide = (dist1, dist2): t => FromDist(
ToDistCombination(Pointwise, #Divide, #Dist(dist2)),
dist1,
)
let pointwiseSubtract = (dist1, dist2): t => FromDist(
ToDistCombination(Pointwise, #Subtract, #Dist(dist2)),
dist1,
)
let pointwiseLogarithm = (dist1, dist2): t => FromDist(
ToDistCombination(Pointwise, #Logarithm, #Dist(dist2)),
dist1,
)
let pointwisePower = (dist1, dist2): t => FromDist(
ToDistCombination(Pointwise, #Power, #Dist(dist2)),
dist1,
)
}
}

View File

@ -114,7 +114,7 @@ let combineShapesContinuousContinuous = (
| #Subtract => (m1, m2) => m1 -. m2 | #Subtract => (m1, m2) => m1 -. m2
| #Multiply => (m1, m2) => m1 *. m2 | #Multiply => (m1, m2) => m1 *. m2
| #Divide => (m1, mInv2) => m1 *. mInv2 | #Divide => (m1, mInv2) => m1 *. mInv2
| #Exponentiate => (m1, mInv2) => m1 ** mInv2 | #Power => (m1, mInv2) => m1 ** mInv2
| #Logarithm => (m1, m2) => log(m1) /. log(m2) | #Logarithm => (m1, m2) => log(m1) /. log(m2)
} // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2) } // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
@ -124,7 +124,7 @@ let combineShapesContinuousContinuous = (
| #Add => (v1, v2, _, _) => v1 +. v2 | #Add => (v1, v2, _, _) => v1 +. v2
| #Subtract => (v1, v2, _, _) => v1 +. v2 | #Subtract => (v1, v2, _, _) => v1 +. v2
| #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2. | #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
| #Exponentiate => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2. | #Power => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
| #Logarithm => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2. | #Logarithm => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
| #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2. | #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
} }
@ -233,7 +233,7 @@ let combineShapesContinuousDiscrete = (
() ()
} }
| #Multiply | #Multiply
| #Exponentiate | #Power
| #Logarithm | #Logarithm
| #Divide => | #Divide =>
for j in 0 to t2n - 1 { for j in 0 to t2n - 1 {

View File

@ -249,6 +249,9 @@ module T = Dist({
) )
}) })
let downsampleEquallyOverX = (length, t): t =>
t |> shapeMap(XYShape.XsConversion.proportionEquallyOverX(length))
/* This simply creates multiple copies of the continuous distribution, scaled and shifted according to /* This simply creates multiple copies of the continuous distribution, scaled and shifted according to
each discrete data point, and then adds them all together. */ each discrete data point, and then adds them all together. */
let combineAlgebraicallyWithDiscrete = ( let combineAlgebraicallyWithDiscrete = (

View File

@ -191,7 +191,6 @@ let isFloat = (t: t) =>
let sampleNRendered = (n, dist) => { let sampleNRendered = (n, dist) => {
let integralCache = T.Integral.get(dist) let integralCache = T.Integral.get(dist)
let distWithUpdatedIntegralCache = T.updateIntegralCache(Some(integralCache), dist) let distWithUpdatedIntegralCache = T.updateIntegralCache(Some(integralCache), dist)
doN(n, () => sample(distWithUpdatedIntegralCache)) doN(n, () => sample(distWithUpdatedIntegralCache))
} }
@ -203,3 +202,9 @@ let operate = (distToFloatOp: Operation.distToFloatOperation, s): float =>
| #Sample => sample(s) | #Sample => sample(s)
| #Mean => T.mean(s) | #Mean => T.mean(s)
} }
let toSparkline = (t: t, bucketCount) =>
T.toContinuous(t)
->E.O2.fmap(Continuous.downsampleEquallyOverX(bucketCount))
->E.O2.toResult("toContinous Error: Could not convert into continuous distribution")
->E.R2.fmap(r => Continuous.getShape(r).ys->Sparklines.create())

View File

@ -0,0 +1,68 @@
/*
This is used as a smart constructor. The only way to create a SampleSetDist.t is to call
this constructor.
https://stackoverflow.com/questions/66909578/how-to-make-a-type-constructor-private-in-rescript-except-in-current-module
*/
module T: {
//This really should be hidden (remove the array<float>). The reason it isn't is to act as an escape hatch in JS__Test.ts.
//When we get a good functional library in TS, we could refactor that out.
@genType
type t = array<float>
let make: array<float> => result<t, string>
let get: t => array<float>
} = {
type t = array<float>
let make = (a: array<float>) =>
if E.A.length(a) > 5 {
Ok(a)
} else {
Error("too small")
}
let get = (a: t) => a
}
include T
let length = (t: t) => get(t)->E.A.length
/*
TODO: Refactor to get a more precise estimate. Also, this code is just fairly messy, could use
some refactoring.
*/
let toPointSetDist = (~samples: t, ~samplingInputs: SamplingInputs.samplingInputs): result<
PointSetTypes.pointSetDist,
string,
> =>
SampleSetDist_ToPointSet.toPointSetDist(
~samples=get(samples),
~samplingInputs,
(),
).pointSetDist->E.O2.toResult("Failed to convert to PointSetDist")
//Randomly get one sample from the distribution
let sample = (t: t): float => {
let i = E.Int.random(~min=0, ~max=E.A.length(get(t)) - 1)
E.A.unsafe_get(get(t), i)
}
/*
If asked for a length of samples shorter or equal the length of the distribution,
return this first n samples of this distribution.
Else, return n random samples of the distribution.
The former helps in cases where multiple distributions are correlated.
However, if n > length(t), then there's no clear right answer, so we just randomly
sample everything.
*/
let sampleN = (t: t, n) => {
if n <= E.A.length(get(t)) {
E.A.slice(get(t), ~offset=0, ~len=n)
} else {
Belt.Array.makeBy(n, _ => sample(t))
}
}
//TODO: Figure out what to do if distributions are different lengths. ``zip`` is kind of inelegant for this.
let map2 = (~fn: (float, float) => float, ~t1: t, ~t2: t) => {
let samples = Belt.Array.zip(get(t1), get(t2))->E.A2.fmap(((a, b)) => fn(a, b))
make(samples)
}

View File

@ -1,4 +1,4 @@
//The math here was taken from https://github.com/jasondavies/science.js/blob/master/src/stats/bandwidth.js //The math here was taken from https://github.com/jasondavies/science.js/blob/master/src/stats/SampleSetDist_Bandwidth.js
let len = x => E.A.length(x) |> float_of_int let len = x => E.A.length(x) |> float_of_int

View File

@ -1,5 +1,3 @@
// TODO: Refactor to raise correct error when not enough samples
module Internals = { module Internals = {
module Types = { module Types = {
type samplingStats = { type samplingStats = {
@ -57,6 +55,7 @@ module Internals = {
: { : {
let _ = Js.Array.push(element, continuous) let _ = Js.Array.push(element, continuous)
} }
() ()
}) })
(continuous, discrete) (continuous, discrete)
@ -71,7 +70,7 @@ module Internals = {
let formatUnitWidth = w => Jstat.max([w, 1.0]) |> int_of_float let formatUnitWidth = w => Jstat.max([w, 1.0]) |> int_of_float
let suggestedUnitWidth = (samples, outputXYPoints) => { let suggestedUnitWidth = (samples, outputXYPoints) => {
let suggestedXWidth = Bandwidth.nrd0(samples) let suggestedXWidth = SampleSetDist_Bandwidth.nrd0(samples)
xWidthToUnitWidth(samples, outputXYPoints, suggestedXWidth) xWidthToUnitWidth(samples, outputXYPoints, suggestedXWidth)
} }
@ -98,7 +97,7 @@ let toPointSetDist = (
let pdf = let pdf =
continuousPart |> E.A.length > 5 continuousPart |> E.A.length > 5
? { ? {
let _suggestedXWidth = Bandwidth.nrd0(continuousPart) let _suggestedXWidth = SampleSetDist_Bandwidth.nrd0(continuousPart)
// todo: This does some recalculating from the last step. // todo: This does some recalculating from the last step.
let _suggestedUnitWidth = Internals.T.suggestedUnitWidth( let _suggestedUnitWidth = Internals.T.suggestedUnitWidth(
continuousPart, continuousPart,

View File

@ -55,7 +55,7 @@ module Exponential = {
rate: rate, rate: rate,
}), }),
) )
: Error("Exponential distributions mean must be larger than 0") : Error("Exponential distributions rate must be larger than 0.")
let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate) let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate)
let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate) let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate)
let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate) let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate)
@ -71,7 +71,7 @@ module Cauchy = {
let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale) let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale)
let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale) let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale)
let sample = (t: t) => Jstat.Cauchy.sample(t.local, t.scale) let sample = (t: t) => Jstat.Cauchy.sample(t.local, t.scale)
let mean = (_: t) => Error("Cauchy distributions have no mean value.") let mean = (_: t) => Error("Cauchy distributions may have no mean value.")
let toString = ({local, scale}: t) => j`Cauchy($local, $scale)` let toString = ({local, scale}: t) => j`Cauchy($local, $scale)`
} }
@ -80,8 +80,8 @@ module Triangular = {
let make = (low, medium, high): result<symbolicDist, string> => let make = (low, medium, high): result<symbolicDist, string> =>
low < medium && medium < high low < medium && medium < high
? Ok(#Triangular({low: low, medium: medium, high: high})) ? Ok(#Triangular({low: low, medium: medium, high: high}))
: Error("Triangular values must be increasing order") : Error("Triangular values must be increasing order.")
let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) // not obvious in jstat docs that high comes before medium?
let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium) let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium)
let inv = (p, t: t) => Jstat.Triangular.inv(p, t.low, t.high, t.medium) let inv = (p, t: t) => Jstat.Triangular.inv(p, t.low, t.high, t.medium)
let sample = (t: t) => Jstat.Triangular.sample(t.low, t.high, t.medium) let sample = (t: t) => Jstat.Triangular.sample(t.low, t.high, t.medium)
@ -346,11 +346,11 @@ module T = {
| _ => #NoSolution | _ => #NoSolution
} }
let toPointSetDist = (sampleCount, d: symbolicDist): PointSetTypes.pointSetDist => let toPointSetDist = (~xSelection=#ByWeight, sampleCount, d: symbolicDist): PointSetTypes.pointSetDist =>
switch d { switch d {
| #Float(v) => Discrete(Discrete.make(~integralSumCache=Some(1.0), {xs: [v], ys: [1.0]})) | #Float(v) => Discrete(Discrete.make(~integralSumCache=Some(1.0), {xs: [v], ys: [1.0]}))
| _ => | _ =>
let xs = interpolateXs(~xSelection=#ByWeight, d, sampleCount) let xs = interpolateXs(~xSelection, d, sampleCount)
let ys = xs |> E.A.fmap(x => pdf(x, d)) let ys = xs |> E.A.fmap(x => pdf(x, d))
Continuous(Continuous.make(~integralSumCache=Some(1.0), {xs: xs, ys: ys})) Continuous(Continuous.make(~integralSumCache=Some(1.0), {xs: xs, ys: ys}))
} }

View File

@ -118,7 +118,7 @@ module PointwiseCombination = {
switch pointwiseOp { switch pointwiseOp {
| #Add => pointwiseAdd(evaluationParams, t1, t2) | #Add => pointwiseAdd(evaluationParams, t1, t2)
| #Multiply => pointwiseCombine(\"*.", evaluationParams, t1, t2) | #Multiply => pointwiseCombine(\"*.", evaluationParams, t1, t2)
| #Exponentiate => pointwiseCombine(\"**", evaluationParams, t1, t2) | #Power => pointwiseCombine(\"**", evaluationParams, t1, t2)
} }
} }

View File

@ -218,15 +218,14 @@ module SamplingDistribution = {
algebraicOp, algebraicOp,
a, a,
b, b,
) ) |> E.O.toResult("Could not get samples")
let sampleSetDist = samples -> E.R.bind(SampleSetDist.make)
let pointSetDist = let pointSetDist =
samples sampleSetDist
|> E.O.fmap(r => -> E.R.bind(r =>
SampleSet.toPointSetDist(~samplingInputs=evaluationParams.samplingInputs, ~samples=r, ()) SampleSetDist.toPointSetDist(~samplingInputs=evaluationParams.samplingInputs, ~samples=r));
)
|> E.O.bind(_, r => r.pointSetDist)
|> E.O.toResult("No response")
pointSetDist |> E.R.fmap(r => #Normalize(#RenderedDist(r))) pointSetDist |> E.R.fmap(r => #Normalize(#RenderedDist(r)))
}) })
} }

View File

@ -227,7 +227,7 @@ let all = [
}, },
(), (),
), ),
makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Exponentiate, dist, float)), makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Power, dist, float)),
makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)), makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)),
makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Logarithm, dist, float)), makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Logarithm, dist, float)),
Multimodal._function, Multimodal._function,

View File

@ -144,11 +144,11 @@ module MathAdtToDistDst = {
| ("subtract", _) => Error("Subtraction needs two operands") | ("subtract", _) => Error("Subtraction needs two operands")
| ("multiply", [l, r]) => toOkAlgebraic((#Multiply, l, r)) | ("multiply", [l, r]) => toOkAlgebraic((#Multiply, l, r))
| ("multiply", _) => Error("Multiplication needs two operands") | ("multiply", _) => Error("Multiplication needs two operands")
| ("pow", [l, r]) => toOkAlgebraic((#Exponentiate, l, r)) | ("pow", [l, r]) => toOkAlgebraic((#Power, l, r))
| ("pow", _) => Error("Exponentiation needs two operands") | ("pow", _) => Error("Exponentiation needs two operands")
| ("dotMultiply", [l, r]) => toOkPointwise((#Multiply, l, r)) | ("dotMultiply", [l, r]) => toOkPointwise((#Multiply, l, r))
| ("dotMultiply", _) => Error("Dotwise multiplication needs two operands") | ("dotMultiply", _) => Error("Dotwise multiplication needs two operands")
| ("dotPow", [l, r]) => toOkPointwise((#Exponentiate, l, r)) | ("dotPow", [l, r]) => toOkPointwise((#Power, l, r))
| ("dotPow", _) => Error("Dotwise exponentiation needs two operands") | ("dotPow", _) => Error("Dotwise exponentiation needs two operands")
| ("rightLogShift", [l, r]) => toOkPointwise((#Add, l, r)) | ("rightLogShift", [l, r]) => toOkPointwise((#Add, l, r))
| ("rightLogShift", _) => Error("Dotwise addition needs two operands") | ("rightLogShift", _) => Error("Dotwise addition needs two operands")

View File

@ -18,8 +18,8 @@ module Helpers = {
| "divide" => #Divide | "divide" => #Divide
| "log" => #Logarithm | "log" => #Logarithm
| "dotDivide" => #Divide | "dotDivide" => #Divide
| "pow" => #Exponentiate | "pow" => #Power
| "dotPow" => #Exponentiate | "dotPow" => #Power
| "multiply" => #Multiply | "multiply" => #Multiply
| "dotMultiply" => #Multiply | "dotMultiply" => #Multiply
| "dotLog" => #Logarithm | "dotLog" => #Logarithm
@ -45,6 +45,13 @@ module Helpers = {
FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some
} }
let toStringFn = (
fnCall: GenericDist_Types.Operation.toString,
dist: GenericDist_Types.genericDist,
) => {
FromDist(GenericDist_Types.Operation.ToString(fnCall), dist)->runGenericOperation->Some
}
let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => { let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => {
FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some
} }
@ -119,6 +126,9 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
->SymbolicConstructors.symbolicResultToOutput ->SymbolicConstructors.symbolicResultToOutput
| ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist) | ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist)
| ("mean", [EvDistribution(dist)]) => Helpers.toFloatFn(#Mean, dist) | ("mean", [EvDistribution(dist)]) => Helpers.toFloatFn(#Mean, dist)
| ("toString", [EvDistribution(dist)]) => Helpers.toStringFn(ToString, dist)
| ("toSparkline", [EvDistribution(dist)]) => Helpers.toStringFn(ToSparkline(20), dist)
| ("toSparkline", [EvDistribution(dist), EvNumber(n)]) => Helpers.toStringFn(ToSparkline(Belt.Float.toInt(n)), dist)
| ("exp", [EvDistribution(a)]) => | ("exp", [EvDistribution(a)]) =>
// https://mathjs.org/docs/reference/functions/exp.html // https://mathjs.org/docs/reference/functions/exp.html
Helpers.twoDiststoDistFn(Algebraic, "pow", GenericDist.fromFloat(Math.e), a)->Some Helpers.twoDiststoDistFn(Algebraic, "pow", GenericDist.fromFloat(Math.e), a)->Some

View File

@ -0,0 +1,27 @@
/*
This is meant as a file to contain @genType declarations as needed for Typescript.
I would ultimately want to have all @genType declarations here, vs. other files, but
@genType doesn't play as nicely with renaming Modules and functions as
would be preferable.
The below few seem to work fine. In the future there's definitely more work to do here.
*/
@genType
type env = DistributionOperation.env
@genType
type genericDist = GenericDist_Types.genericDist
@genType
type error = GenericDist_Types.error
@genType
type resultDist = result<genericDist, error>
@genType
type resultFloat = result<float, error>
@genType
type resultString = result<string, error>
@genType
let makeSampleSetDist = SampleSetDist.make

View File

@ -24,6 +24,7 @@ module FloatFloatMap = {
module Int = { module Int = {
let max = (i1: int, i2: int) => i1 > i2 ? i1 : i2 let max = (i1: int, i2: int) => i1 > i2 ? i1 : i2
let random = (~min, ~max) => Js.Math.random_int(min, max)
} }
/* Utils */ /* Utils */
module U = { module U = {
@ -101,6 +102,7 @@ module O2 = {
let default = (a, b) => O.default(b, a) let default = (a, b) => O.default(b, a)
let toExn = (a, b) => O.toExn(b, a) let toExn = (a, b) => O.toExn(b, a)
let fmap = (a, b) => O.fmap(b, a) let fmap = (a, b) => O.fmap(b, a)
let toResult = (a, b) => O.toResult(b, a)
} }
/* Functions */ /* Functions */
@ -178,6 +180,13 @@ module R = {
module R2 = { module R2 = {
let fmap = (a,b) => R.fmap(b,a) let fmap = (a,b) => R.fmap(b,a)
let bind = (a, b) => R.bind(b, a)
//Converts result type to change error type only
let errMap = (a, map) => switch(a){
| Ok(r) => Ok(r)
| Error(e) => map(e)
}
} }
let safe_fn_of_string = (fn, s: string): option<'a> => let safe_fn_of_string = (fn, s: string): option<'a> =>
@ -269,6 +278,7 @@ module A = {
let fold_right = Array.fold_right let fold_right = Array.fold_right
let concatMany = Belt.Array.concatMany let concatMany = Belt.Array.concatMany
let keepMap = Belt.Array.keepMap let keepMap = Belt.Array.keepMap
let slice = Belt.Array.slice
let init = Array.init let init = Array.init
let reduce = Belt.Array.reduce let reduce = Belt.Array.reduce
let reducei = Belt.Array.reduceWithIndex let reducei = Belt.Array.reduceWithIndex
@ -289,8 +299,7 @@ module A = {
)) ))
|> Rationale.Result.return |> Rationale.Result.return
} }
let rangeFloat = (~step=1, start, stop) =>
Belt.Array.rangeBy(start, stop, ~step) |> fmap(Belt.Int.toFloat)
// This zips while taking the longest elements of each array. // This zips while taking the longest elements of each array.
let zipMaxLength = (array1, array2) => { let zipMaxLength = (array1, array2) => {
@ -442,6 +451,12 @@ module A = {
let mean = a => sum(a) /. (Array.length(a) |> float_of_int) let mean = a => sum(a) /. (Array.length(a) |> float_of_int)
let random = Js.Math.random_int let random = Js.Math.random_int
// Gives an array with all the differences between values
// diff([1,5,3,7]) = [4,-2,4]
let diff = (arr: array<float>): array<float> =>
Belt.Array.zipBy(arr, Belt.Array.sliceToEnd(arr, 1), (left, right) => right -. left)
exception RangeError(string) exception RangeError(string)
let range = (min: float, max: float, n: int): array<float> => let range = (min: float, max: float, n: int): array<float> =>
switch n { switch n {

View File

@ -6,12 +6,12 @@ type algebraicOperation = [
| #Multiply | #Multiply
| #Subtract | #Subtract
| #Divide | #Divide
| #Exponentiate | #Power
| #Logarithm | #Logarithm
] ]
@genType @genType
type pointwiseOperation = [#Add | #Multiply | #Exponentiate] type pointwiseOperation = [#Add | #Multiply | #Power]
type scaleOperation = [#Multiply | #Exponentiate | #Logarithm | #Divide] type scaleOperation = [#Multiply | #Power | #Logarithm | #Divide]
type distToFloatOperation = [ type distToFloatOperation = [
| #Pdf(float) | #Pdf(float)
| #Cdf(float) | #Cdf(float)
@ -27,7 +27,7 @@ module Algebraic = {
| #Add => \"+." | #Add => \"+."
| #Subtract => \"-." | #Subtract => \"-."
| #Multiply => \"*." | #Multiply => \"*."
| #Exponentiate => \"**" | #Power => \"**"
| #Divide => \"/." | #Divide => \"/."
| #Logarithm => (a, b) => log(a) /. log(b) | #Logarithm => (a, b) => log(a) /. log(b)
} }
@ -43,7 +43,7 @@ module Algebraic = {
| #Add => "+" | #Add => "+"
| #Subtract => "-" | #Subtract => "-"
| #Multiply => "*" | #Multiply => "*"
| #Exponentiate => "**" | #Power => "**"
| #Divide => "/" | #Divide => "/"
| #Logarithm => "log" | #Logarithm => "log"
} }
@ -56,7 +56,7 @@ module Pointwise = {
let toString = x => let toString = x =>
switch x { switch x {
| #Add => "+" | #Add => "+"
| #Exponentiate => "^" | #Power => "**"
| #Multiply => "*" | #Multiply => "*"
} }
@ -83,7 +83,7 @@ module Scale = {
switch x { switch x {
| #Multiply => \"*." | #Multiply => \"*."
| #Divide => \"/." | #Divide => \"/."
| #Exponentiate => \"**" | #Power => \"**"
| #Logarithm => (a, b) => log(a) /. log(b) | #Logarithm => (a, b) => log(a) /. log(b)
} }
@ -91,7 +91,7 @@ module Scale = {
switch operation { switch operation {
| #Multiply => j`verticalMultiply($value, $scaleBy) ` | #Multiply => j`verticalMultiply($value, $scaleBy) `
| #Divide => j`verticalDivide($value, $scaleBy) ` | #Divide => j`verticalDivide($value, $scaleBy) `
| #Exponentiate => j`verticalExponentiate($value, $scaleBy) ` | #Power => j`verticalPower($value, $scaleBy) `
| #Logarithm => j`verticalLog($value, $scaleBy) ` | #Logarithm => j`verticalLog($value, $scaleBy) `
} }
@ -99,7 +99,7 @@ module Scale = {
switch x { switch x {
| #Multiply => (a, b) => Some(a *. b) | #Multiply => (a, b) => Some(a *. b)
| #Divide => (a, b) => Some(a /. b) | #Divide => (a, b) => Some(a /. b)
| #Exponentiate => (_, _) => None | #Power => (_, _) => None
| #Logarithm => (_, _) => None | #Logarithm => (_, _) => None
} }
@ -107,7 +107,7 @@ module Scale = {
switch x { switch x {
| #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy) | #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy)
| #Divide => (_, _) => None | #Divide => (_, _) => None
| #Exponentiate => (_, _) => None | #Power => (_, _) => None
| #Logarithm => (_, _) => None | #Logarithm => (_, _) => None
} }
} }