Merged with develop
This commit is contained in:
commit
8b0615eec0
|
@ -11,3 +11,4 @@ packages/squiggle-lang/.nyc_output/
|
|||
packages/squiggle-lang/coverage/
|
||||
packages/squiggle-lang/.cache/
|
||||
packages/website/build/
|
||||
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.17.12",
|
||||
"@storybook/addon-actions": "^6.5.3",
|
||||
"@storybook/addon-essentials": "^6.5.3",
|
||||
"@storybook/addon-links": "^6.5.3",
|
||||
"@storybook/builder-webpack5": "^6.5.3",
|
||||
"@storybook/manager-webpack5": "^6.5.3",
|
||||
"@storybook/node-logger": "^6.4.22",
|
||||
"@storybook/preset-create-react-app": "^4.1.0",
|
||||
"@storybook/react": "^6.5.3",
|
||||
"@storybook/addon-essentials": "^6.5.4",
|
||||
"@storybook/addon-links": "^6.5.4",
|
||||
"@storybook/builder-webpack5": "^6.5.4",
|
||||
"@storybook/manager-webpack5": "^6.5.4",
|
||||
"@storybook/node-logger": "^6.5.4",
|
||||
"@storybook/preset-create-react-app": "^4.1.1",
|
||||
"@storybook/react": "^6.5.4",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.2.0",
|
||||
"@testing-library/user-event": "^14.2.0",
|
||||
|
|
1
packages/squiggle-lang/.gitignore
vendored
1
packages/squiggle-lang/.gitignore
vendored
|
@ -21,3 +21,4 @@ dist
|
|||
_coverage
|
||||
coverage
|
||||
.nyc_output/
|
||||
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||
|
|
|
@ -4,4 +4,5 @@ lib
|
|||
*.gen.tsx
|
||||
.nyc_output/
|
||||
coverage/
|
||||
.cache/
|
||||
.cache/
|
||||
Reducer_Peggy_GeneratedParser.js
|
||||
|
|
|
@ -23,7 +23,7 @@ describe("eval on distribution functions", () => {
|
|||
testEval("-normal(5,2)", "Ok(Normal(-5,2))")
|
||||
})
|
||||
describe("to", () => {
|
||||
testEval("5 to 2", "Error(Distribution Math Error: Low value must be less than high value.)")
|
||||
testEval("5 to 2", "Error(TODO: Low value must be less than high value.)")
|
||||
testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))")
|
||||
testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))")
|
||||
})
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
"homepage": "https://squiggle-language.com",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"peggy": "peggy --cache ",
|
||||
"build:peggy": "find . -type f -name *.peggy -exec yarn run peggy {} \\;",
|
||||
"build": "yarn build:rescript && yarn build:typescript",
|
||||
"peggy": "peggy --cache",
|
||||
"build": "yarn build:peggy && yarn build:rescript && yarn build:typescript",
|
||||
"build:peggy": "find . -type f -name *.peggy -exec yarn peggy {} \\;",
|
||||
"build:rescript": "rescript build -with-deps",
|
||||
"build:typescript": "tsc",
|
||||
"bundle": "webpack",
|
||||
|
@ -61,7 +61,7 @@
|
|||
"rescript-fast-check": "^1.1.1",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-loader": "^9.3.0",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-node": "^10.8.0",
|
||||
"typescript": "^4.6.3",
|
||||
"webpack": "^5.72.1",
|
||||
"webpack-cli": "^4.9.2"
|
||||
|
|
|
@ -31,6 +31,8 @@ let sampleN = (t: t, n) =>
|
|||
| SampleSet(r) => SampleSetDist.sampleN(r, n)
|
||||
}
|
||||
|
||||
let sample = (t: t) => sampleN(t, 1)->E.A.first |> E.O.toExn("Should not have happened")
|
||||
|
||||
let toSampleSetDist = (t: t, n) =>
|
||||
SampleSetDist.make(sampleN(t, n))->E.R2.errMap(DistributionTypes.Error.sampleErrorToDistErr)
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ type scaleMultiplyFn = (t, float) => result<t, error>
|
|||
type pointwiseAddFn = (t, t) => result<t, error>
|
||||
|
||||
let sampleN: (t, int) => array<float>
|
||||
let sample: t => float
|
||||
|
||||
let toSampleSetDist: (t, int) => Belt.Result.t<QuriSquiggleLang.SampleSetDist.t, error>
|
||||
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
|
||||
/*
|
||||
Function Registry "Type". A type, without any other information.
|
||||
Like, #Float
|
||||
*/
|
||||
type rec frType =
|
||||
| FRTypeNumber
|
||||
| FRTypeNumeric
|
||||
| FRTypeDistOrNumber
|
||||
| FRTypeRecord(frTypeRecord)
|
||||
| FRTypeArray(array<frType>)
|
||||
| FRTypeOption(frType)
|
||||
and frTypeRecord = array<frTypeRecordParam>
|
||||
and frTypeRecordParam = (string, frType)
|
||||
|
||||
/*
|
||||
Function Registry "Value". A type, with the information of that type.
|
||||
Like, #Float(40.0)
|
||||
*/
|
||||
type rec frValue =
|
||||
| FRValueNumber(float)
|
||||
| FRValueDist(DistributionTypes.genericDist)
|
||||
| FRValueOption(option<frValue>)
|
||||
| FRValueDistOrNumber(frValueDistOrNumber)
|
||||
| FRValueRecord(frValueRecord)
|
||||
and frValueRecord = array<frValueRecordParam>
|
||||
and frValueRecordParam = (string, frValue)
|
||||
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
|
||||
|
||||
type fnDefinition = {
|
||||
name: string,
|
||||
inputs: array<frType>,
|
||||
run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
|
||||
}
|
||||
|
||||
type function = {
|
||||
name: string,
|
||||
definitions: array<fnDefinition>,
|
||||
}
|
||||
|
||||
type registry = array<function>
|
||||
|
||||
module FRType = {
|
||||
type t = frType
|
||||
let rec toString = (t: t) =>
|
||||
switch t {
|
||||
| FRTypeNumber => "number"
|
||||
| FRTypeNumeric => "numeric"
|
||||
| FRTypeDistOrNumber => "frValueDistOrNumber"
|
||||
| FRTypeRecord(r) => {
|
||||
let input = ((name, frType): frTypeRecordParam) => `${name}: ${toString(frType)}`
|
||||
`record({${r->E.A2.fmap(input)->E.A2.joinWith(", ")}})`
|
||||
}
|
||||
| FRTypeArray(r) => `record(${r->E.A2.fmap(toString)->E.A2.joinWith(", ")})`
|
||||
| FRTypeOption(v) => `option(${toString(v)})`
|
||||
}
|
||||
|
||||
let rec matchWithExpressionValue = (t: t, r: expressionValue): option<frValue> =>
|
||||
switch (t, r) {
|
||||
| (FRTypeNumber, EvNumber(f)) => Some(FRValueNumber(f))
|
||||
| (FRTypeDistOrNumber, EvNumber(f)) => Some(FRValueDistOrNumber(FRValueNumber(f)))
|
||||
| (FRTypeDistOrNumber, EvDistribution(Symbolic(#Float(f)))) =>
|
||||
Some(FRValueDistOrNumber(FRValueNumber(f)))
|
||||
| (FRTypeDistOrNumber, EvDistribution(f)) => Some(FRValueDistOrNumber(FRValueDist(f)))
|
||||
| (FRTypeNumeric, EvNumber(f)) => Some(FRValueNumber(f))
|
||||
| (FRTypeNumeric, EvDistribution(Symbolic(#Float(f)))) => Some(FRValueNumber(f))
|
||||
| (FRTypeOption(v), _) => Some(FRValueOption(matchWithExpressionValue(v, r)))
|
||||
| (FRTypeRecord(recordParams), EvRecord(record)) => {
|
||||
let getAndMatch = (name, input) =>
|
||||
E.Dict.get(record, name)->E.O.bind(matchWithExpressionValue(input))
|
||||
//All names in the type must be present. If any are missing, the corresponding
|
||||
//value will be None, and this function would return None.
|
||||
let namesAndValues: array<option<(Js.Dict.key, frValue)>> =
|
||||
recordParams->E.A2.fmap(((name, input)) =>
|
||||
getAndMatch(name, input)->E.O2.fmap(match => (name, match))
|
||||
)
|
||||
namesAndValues->E.A.O.openIfAllSome->E.O2.fmap(r => FRValueRecord(r))
|
||||
}
|
||||
| _ => None
|
||||
}
|
||||
|
||||
let matchWithExpressionValueArray = (inputs: array<t>, args: array<expressionValue>): option<
|
||||
array<frValue>,
|
||||
> => {
|
||||
let isSameLength = E.A.length(inputs) == E.A.length(args)
|
||||
if !isSameLength {
|
||||
None
|
||||
} else {
|
||||
E.A.zip(inputs, args)
|
||||
->E.A2.fmap(((input, arg)) => matchWithExpressionValue(input, arg))
|
||||
->E.A.O.openIfAllSome
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This module, Matcher, is fairly lengthy. However, only two functions from it
|
||||
are meant to be used outside of it. These are findMatches and matchToDef in Matches.Registry.
|
||||
The rest of it is just called from those two functions.
|
||||
*/
|
||||
module Matcher = {
|
||||
module MatchSimple = {
|
||||
type t = DifferentName | SameNameDifferentArguments | FullMatch
|
||||
|
||||
let isFullMatch = (match: t) =>
|
||||
switch match {
|
||||
| FullMatch => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let isNameMatchOnly = (match: t) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments => true
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
module Match = {
|
||||
type t<'a, 'b> = DifferentName | SameNameDifferentArguments('a) | FullMatch('b)
|
||||
|
||||
let isFullMatch = (match: t<'a, 'b>): bool =>
|
||||
switch match {
|
||||
| FullMatch(_) => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let isNameMatchOnly = (match: t<'a, 'b>) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments(_) => true
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
module FnDefinition = {
|
||||
let matchAssumingSameName = (f: fnDefinition, args: array<expressionValue>) => {
|
||||
switch FRType.matchWithExpressionValueArray(f.inputs, args) {
|
||||
| Some(_) => MatchSimple.FullMatch
|
||||
| None => MatchSimple.SameNameDifferentArguments
|
||||
}
|
||||
}
|
||||
|
||||
let match = (f: fnDefinition, fnName: string, args: array<expressionValue>) => {
|
||||
if f.name !== fnName {
|
||||
MatchSimple.DifferentName
|
||||
} else {
|
||||
matchAssumingSameName(f, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module Function = {
|
||||
type definitionId = int
|
||||
type match = Match.t<array<definitionId>, definitionId>
|
||||
|
||||
let match = (f: function, fnName: string, args: array<expressionValue>): match => {
|
||||
let matchedDefinition = () =>
|
||||
E.A.getIndexBy(f.definitions, r =>
|
||||
MatchSimple.isFullMatch(FnDefinition.match(r, fnName, args))
|
||||
) |> E.O.fmap(r => Match.FullMatch(r))
|
||||
let getMatchedNameOnlyDefinition = () => {
|
||||
let nameMatchIndexes =
|
||||
f.definitions
|
||||
->E.A2.fmapi((index, r) =>
|
||||
MatchSimple.isNameMatchOnly(FnDefinition.match(r, fnName, args)) ? Some(index) : None
|
||||
)
|
||||
->E.A.O.concatSomes
|
||||
switch nameMatchIndexes {
|
||||
| [] => None
|
||||
| elements => Some(Match.SameNameDifferentArguments(elements))
|
||||
}
|
||||
}
|
||||
|
||||
E.A.O.firstSomeFnWithDefault(
|
||||
[matchedDefinition, getMatchedNameOnlyDefinition],
|
||||
Match.DifferentName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module RegistryMatch = {
|
||||
type match = {
|
||||
fnName: string,
|
||||
inputIndex: int,
|
||||
}
|
||||
let makeMatch = (fnName: string, inputIndex: int) => {fnName: fnName, inputIndex: inputIndex}
|
||||
}
|
||||
|
||||
module Registry = {
|
||||
let _findExactMatches = (r: registry, fnName: string, args: array<expressionValue>) => {
|
||||
let functionMatchPairs = r->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
|
||||
let fullMatch = functionMatchPairs->E.A.getBy(((_, match)) => Match.isFullMatch(match))
|
||||
fullMatch->E.O.bind(((fn, match)) =>
|
||||
switch match {
|
||||
| FullMatch(index) => Some(RegistryMatch.makeMatch(fn.name, index))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let _findNameMatches = (r: registry, fnName: string, args: array<expressionValue>) => {
|
||||
let functionMatchPairs = r->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
|
||||
let getNameMatches =
|
||||
functionMatchPairs
|
||||
->E.A2.fmap(((fn, match)) => Match.isNameMatchOnly(match) ? Some((fn, match)) : None)
|
||||
->E.A.O.concatSomes
|
||||
let matches =
|
||||
getNameMatches
|
||||
->E.A2.fmap(((fn, match)) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments(indexes) =>
|
||||
indexes->E.A2.fmap(index => RegistryMatch.makeMatch(fn.name, index))
|
||||
| _ => []
|
||||
}
|
||||
)
|
||||
->Belt.Array.concatMany
|
||||
E.A.toNoneIfEmpty(matches)
|
||||
}
|
||||
|
||||
let findMatches = (r: registry, fnName: string, args: array<expressionValue>) => {
|
||||
switch _findExactMatches(r, fnName, args) {
|
||||
| Some(r) => Match.FullMatch(r)
|
||||
| None =>
|
||||
switch _findNameMatches(r, fnName, args) {
|
||||
| Some(r) => Match.SameNameDifferentArguments(r)
|
||||
| None => Match.DifferentName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let matchToDef = (registry: registry, {fnName, inputIndex}: RegistryMatch.match): option<
|
||||
fnDefinition,
|
||||
> =>
|
||||
registry
|
||||
->E.A.getBy(fn => fn.name === fnName)
|
||||
->E.O.bind(fn => E.A.get(fn.definitions, inputIndex))
|
||||
}
|
||||
}
|
||||
|
||||
module FnDefinition = {
|
||||
type t = fnDefinition
|
||||
|
||||
let toString = (t: t) => {
|
||||
let inputs = t.inputs->E.A2.fmap(FRType.toString)->E.A2.joinWith(", ")
|
||||
t.name ++ `(${inputs})`
|
||||
}
|
||||
|
||||
let run = (t: t, args: array<expressionValue>, env: DistributionOperation.env) => {
|
||||
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args)
|
||||
switch argValues {
|
||||
| Some(values) => t.run(values, env)
|
||||
| None => Error("Incorrect Types")
|
||||
}
|
||||
}
|
||||
|
||||
let make = (~name, ~inputs, ~run): t => {
|
||||
name: name,
|
||||
inputs: inputs,
|
||||
run: run,
|
||||
}
|
||||
}
|
||||
|
||||
module Function = {
|
||||
type t = function
|
||||
|
||||
let make = (~name, ~definitions): t => {
|
||||
name: name,
|
||||
definitions: definitions,
|
||||
}
|
||||
}
|
||||
|
||||
module Registry = {
|
||||
/*
|
||||
There's a (potential+minor) bug here: If a function definition is called outside of the calls
|
||||
to the registry, then it's possible that there could be a match after the registry is
|
||||
called. However, for now, we could just call the registry last.
|
||||
*/
|
||||
let matchAndRun = (
|
||||
~registry: registry,
|
||||
~fnName: string,
|
||||
~args: array<expressionValue>,
|
||||
~env: DistributionOperation.env,
|
||||
) => {
|
||||
let matchToDef = m => Matcher.Registry.matchToDef(registry, m)
|
||||
let showNameMatchDefinitions = matches => {
|
||||
let defs =
|
||||
matches
|
||||
->E.A2.fmap(matchToDef)
|
||||
->E.A.O.concatSomes
|
||||
->E.A2.fmap(FnDefinition.toString)
|
||||
->E.A2.fmap(r => `[${r}]`)
|
||||
->E.A2.joinWith("; ")
|
||||
`There are function matches for ${fnName}(), but with different arguments: ${defs}`
|
||||
}
|
||||
switch Matcher.Registry.findMatches(registry, fnName, args) {
|
||||
| Matcher.Match.FullMatch(match) => match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env))
|
||||
| SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m)))
|
||||
| _ => None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
|
||||
type rec frType =
|
||||
| FRTypeNumber
|
||||
| FRTypeNumeric
|
||||
| FRTypeDistOrNumber
|
||||
| FRTypeRecord(frTypeRecord)
|
||||
| FRTypeArray(array<frType>)
|
||||
| FRTypeOption(frType)
|
||||
and frTypeRecord = array<frTypeRecordParam>
|
||||
and frTypeRecordParam = (string, frType)
|
||||
|
||||
type rec frValue =
|
||||
| FRValueNumber(float)
|
||||
| FRValueDist(DistributionTypes.genericDist)
|
||||
| FRValueOption(option<frValue>)
|
||||
| FRValueDistOrNumber(frValueDistOrNumber)
|
||||
| FRValueRecord(frValueRecord)
|
||||
and frValueRecord = array<frValueRecordParam>
|
||||
and frValueRecordParam = (string, frValue)
|
||||
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
|
||||
|
||||
type fnDefinition = {
|
||||
name: string,
|
||||
inputs: array<frType>,
|
||||
run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
|
||||
}
|
||||
|
||||
type function = {
|
||||
name: string,
|
||||
definitions: array<fnDefinition>,
|
||||
}
|
||||
|
||||
type registry = array<function>
|
||||
|
||||
// Note: The function "name" is just used for documentation purposes
|
||||
module Function: {
|
||||
type t = function
|
||||
let make: (~name: string, ~definitions: array<fnDefinition>) => t
|
||||
}
|
||||
|
||||
module FnDefinition: {
|
||||
type t = fnDefinition
|
||||
let make: (
|
||||
~name: string,
|
||||
~inputs: array<frType>,
|
||||
~run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
|
||||
) => t
|
||||
}
|
||||
|
||||
module Registry: {
|
||||
let matchAndRun: (
|
||||
~registry: registry,
|
||||
~fnName: string,
|
||||
~args: array<expressionValue>,
|
||||
~env: QuriSquiggleLang.DistributionOperation.env,
|
||||
) => option<result<expressionValue, string>>
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
open FunctionRegistry_Core
|
||||
|
||||
let impossibleError = "Wrong inputs / Logically impossible"
|
||||
|
||||
module Wrappers = {
|
||||
let symbolic = r => DistributionTypes.Symbolic(r)
|
||||
let evDistribution = r => ReducerInterface_ExpressionValue.EvDistribution(r)
|
||||
let symbolicEvDistribution = r => r->DistributionTypes.Symbolic->evDistribution
|
||||
}
|
||||
|
||||
module Prepare = {
|
||||
type ts = array<frValue>
|
||||
type err = string
|
||||
|
||||
module ToValueArray = {
|
||||
module Record = {
|
||||
let twoArgs = (inputs: ts): result<ts, err> =>
|
||||
switch inputs {
|
||||
| [FRValueRecord([(_, n1), (_, n2)])] => Ok([n1, n2])
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module ToValueTuple = {
|
||||
let twoDistOrNumber = (values: ts): result<(frValueDistOrNumber, frValueDistOrNumber), err> => {
|
||||
switch values {
|
||||
| [FRValueDistOrNumber(a1), FRValueDistOrNumber(a2)] => Ok(a1, a2)
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
}
|
||||
|
||||
let oneDistOrNumber = (values: ts): result<frValueDistOrNumber, err> => {
|
||||
switch values {
|
||||
| [FRValueDistOrNumber(a1)] => Ok(a1)
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
}
|
||||
|
||||
module Record = {
|
||||
let twoDistOrNumber = (values: ts): result<(frValueDistOrNumber, frValueDistOrNumber), err> =>
|
||||
values->ToValueArray.Record.twoArgs->E.R.bind(twoDistOrNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module Process = {
|
||||
module DistOrNumberToDist = {
|
||||
module Helpers = {
|
||||
let toSampleSet = (r, env: DistributionOperation.env) =>
|
||||
GenericDist.toSampleSetDist(r, env.sampleCount)
|
||||
|
||||
let mapFnResult = r =>
|
||||
switch r {
|
||||
| Ok(r) => Ok(GenericDist.sample(r))
|
||||
| Error(r) => Error(Operation.Other(r))
|
||||
}
|
||||
|
||||
let wrapSymbolic = (fn, r) => r->fn->E.R2.fmap(Wrappers.symbolic)
|
||||
|
||||
let singleVarSample = (dist, fn, env) => {
|
||||
switch toSampleSet(dist, env) {
|
||||
| Ok(dist) =>
|
||||
switch SampleSetDist.samplesMap(~fn=f => fn(f)->mapFnResult, dist) {
|
||||
| Ok(r) => Ok(DistributionTypes.SampleSet(r))
|
||||
| Error(r) => Error(DistributionTypes.Error.toString(DistributionTypes.SampleSetError(r)))
|
||||
}
|
||||
| Error(r) => Error(DistributionTypes.Error.toString(r))
|
||||
}
|
||||
}
|
||||
|
||||
let twoVarSample = (dist1, dist2, fn, env) => {
|
||||
let altFn = (a, b) => fn((a, b))->mapFnResult
|
||||
switch E.R.merge(toSampleSet(dist1, env), toSampleSet(dist2, env)) {
|
||||
| Ok((t1, t2)) =>
|
||||
switch SampleSetDist.map2(~fn=altFn, ~t1, ~t2) {
|
||||
| Ok(r) => Ok(DistributionTypes.SampleSet(r))
|
||||
| Error(r) => Error(Operation.Error.toString(r))
|
||||
}
|
||||
| Error(r) => Error(DistributionTypes.Error.toString(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let oneValue = (
|
||||
~fn: float => result<DistributionTypes.genericDist, string>,
|
||||
~value: frValueDistOrNumber,
|
||||
~env: DistributionOperation.env,
|
||||
): result<DistributionTypes.genericDist, string> => {
|
||||
switch value {
|
||||
| FRValueNumber(a1) => fn(a1)
|
||||
| FRValueDist(a1) => Helpers.singleVarSample(a1, r => fn(r), env)
|
||||
}
|
||||
}
|
||||
|
||||
let oneValueUsingSymbolicDist = (~fn, ~value) => oneValue(~fn=Helpers.wrapSymbolic(fn), ~value)
|
||||
|
||||
let twoValues = (
|
||||
~fn: ((float, float)) => result<DistributionTypes.genericDist, string>,
|
||||
~values: (frValueDistOrNumber, frValueDistOrNumber),
|
||||
~env: DistributionOperation.env,
|
||||
): result<DistributionTypes.genericDist, string> => {
|
||||
switch values {
|
||||
| (FRValueNumber(a1), FRValueNumber(a2)) => fn((a1, a2))
|
||||
| (FRValueDist(a1), FRValueNumber(a2)) => Helpers.singleVarSample(a1, r => fn((r, a2)), env)
|
||||
| (FRValueNumber(a1), FRValueDist(a2)) => Helpers.singleVarSample(a2, r => fn((a1, r)), env)
|
||||
| (FRValueDist(a1), FRValueDist(a2)) => Helpers.twoVarSample(a1, a2, fn, env)
|
||||
}
|
||||
}
|
||||
|
||||
let twoValuesUsingSymbolicDist = (~fn, ~values) =>
|
||||
twoValues(~fn=Helpers.wrapSymbolic(fn), ~values)
|
||||
}
|
||||
}
|
||||
|
||||
module TwoArgDist = {
|
||||
let process = (~fn, ~env, r) =>
|
||||
r
|
||||
->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env))
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
|
||||
let make = (name, fn) => {
|
||||
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~run=(inputs, env) =>
|
||||
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env)
|
||||
)
|
||||
}
|
||||
|
||||
let makeRecordP5P95 = (name, fn) => {
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
|
||||
~run=(inputs, env) => inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
)
|
||||
}
|
||||
|
||||
let makeRecordMeanStdev = (name, fn) => {
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
|
||||
~run=(inputs, env) => inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module OneArgDist = {
|
||||
let process = (~fn, ~env, r) =>
|
||||
r
|
||||
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
|
||||
let make = (name, fn) => {
|
||||
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber], ~run=(inputs, env) =>
|
||||
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let twoArgs = E.Tuple2.toFnCall
|
||||
|
||||
let registry = [
|
||||
Function.make(
|
||||
~name="Normal",
|
||||
~definitions=[
|
||||
TwoArgDist.make("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||
TwoArgDist.makeRecordP5P95("normal", r =>
|
||||
twoArgs(SymbolicDist.Normal.from90PercentCI, r)->Ok
|
||||
),
|
||||
TwoArgDist.makeRecordMeanStdev("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||
],
|
||||
),
|
||||
Function.make(
|
||||
~name="Lognormal",
|
||||
~definitions=[
|
||||
TwoArgDist.make("lognormal", twoArgs(SymbolicDist.Lognormal.make)),
|
||||
TwoArgDist.makeRecordP5P95("lognormal", r =>
|
||||
twoArgs(SymbolicDist.Lognormal.from90PercentCI, r)->Ok
|
||||
),
|
||||
TwoArgDist.makeRecordMeanStdev("lognormal", twoArgs(SymbolicDist.Lognormal.fromMeanAndStdev)),
|
||||
],
|
||||
),
|
||||
Function.make(
|
||||
~name="Uniform",
|
||||
~definitions=[TwoArgDist.make("uniform", twoArgs(SymbolicDist.Uniform.make))],
|
||||
),
|
||||
Function.make(
|
||||
~name="Beta",
|
||||
~definitions=[TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make))],
|
||||
),
|
||||
Function.make(
|
||||
~name="Cauchy",
|
||||
~definitions=[TwoArgDist.make("cauchy", twoArgs(SymbolicDist.Cauchy.make))],
|
||||
),
|
||||
Function.make(
|
||||
~name="Gamma",
|
||||
~definitions=[TwoArgDist.make("gamma", twoArgs(SymbolicDist.Gamma.make))],
|
||||
),
|
||||
Function.make(
|
||||
~name="Logistic",
|
||||
~definitions=[TwoArgDist.make("logistic", twoArgs(SymbolicDist.Logistic.make))],
|
||||
),
|
||||
Function.make(
|
||||
~name="To",
|
||||
~definitions=[
|
||||
TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
|
||||
TwoArgDist.make(
|
||||
"credibleIntervalToDistribution",
|
||||
twoArgs(SymbolicDist.From90thPercentile.make),
|
||||
),
|
||||
],
|
||||
),
|
||||
Function.make(
|
||||
~name="Exponential",
|
||||
~definitions=[OneArgDist.make("exponential", SymbolicDist.Exponential.make)],
|
||||
),
|
||||
Function.make(
|
||||
~name="Bernoulli",
|
||||
~definitions=[OneArgDist.make("bernoulli", SymbolicDist.Bernoulli.make)],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,46 @@
|
|||
# Function Registry
|
||||
|
||||
The function registry is a library for organizing function definitions.
|
||||
|
||||
The main interface is fairly constrained. Basically, write functions like the following, and add them to a big array.
|
||||
|
||||
```rescript
|
||||
Function.make(
|
||||
~name="Normal",
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="Normal",
|
||||
~definitions=[
|
||||
FnDefinition.make(~name="normal", ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~run=(
|
||||
inputs,
|
||||
env,
|
||||
) =>
|
||||
inputs
|
||||
->Prepare.ToValueTuple.twoDistOrNumber
|
||||
->E.R.bind(
|
||||
Process.twoDistsOrNumbersToDistUsingSymbolicDist(
|
||||
~fn=E.Tuple2.toFnCall(SymbolicDist.Normal.make),
|
||||
~env,
|
||||
~values=_,
|
||||
),
|
||||
)
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
The Function name is just there for future documentation. The function defintions
|
||||
|
||||
## Key Files
|
||||
|
||||
**FunctionRegistry_Core**
|
||||
Key types, internal functionality, and a `Registry` module with a `matchAndRun` function to call function definitions.
|
||||
|
||||
**FunctionRegistry_Library**
|
||||
A list of all the Functions defined in the Function Registry.
|
||||
|
||||
**FunctionRegistry_Helpers**
|
||||
A list of helper functions for the FunctionRegistry_Library.
|
File diff suppressed because it is too large
Load Diff
|
@ -179,27 +179,6 @@ module Helpers = {
|
|||
}
|
||||
|
||||
module SymbolicConstructors = {
|
||||
let oneFloat = name =>
|
||||
switch name {
|
||||
| "exponential" => Ok(SymbolicDist.Exponential.make)
|
||||
| "bernoulli" => Ok(SymbolicDist.Bernoulli.make)
|
||||
| _ => Error("Unreachable state")
|
||||
}
|
||||
|
||||
let twoFloat = name =>
|
||||
switch name {
|
||||
| "beta" => Ok(SymbolicDist.Beta.make)
|
||||
| "cauchy" => Ok(SymbolicDist.Cauchy.make)
|
||||
| "credibleIntervalToDistribution" => Ok(SymbolicDist.From90thPercentile.make)
|
||||
| "gamma" => Ok(SymbolicDist.Gamma.make)
|
||||
| "logistic" => Ok(SymbolicDist.Logistic.make)
|
||||
| "lognormal" => Ok(SymbolicDist.Lognormal.make)
|
||||
| "normal" => Ok(SymbolicDist.Normal.make)
|
||||
| "to" => Ok(SymbolicDist.From90thPercentile.make) // as credibleIntervalToDistribution is defined "to" might be redundant
|
||||
| "uniform" => Ok(SymbolicDist.Uniform.make)
|
||||
| _ => Error("Unreachable state")
|
||||
}
|
||||
|
||||
let threeFloat = name =>
|
||||
switch name {
|
||||
| "triangular" => Ok(SymbolicDist.Triangular.make)
|
||||
|
@ -221,27 +200,8 @@ let dispatchToGenericOutput = (
|
|||
): option<DistributionOperation.outputType> => {
|
||||
let (fnName, args) = call
|
||||
switch (fnName, args) {
|
||||
| (("exponential" | "bernoulli") as fnName, [EvNumber(f)]) =>
|
||||
SymbolicConstructors.oneFloat(fnName)
|
||||
->E.R.bind(r => r(f))
|
||||
->SymbolicConstructors.symbolicResultToOutput
|
||||
| ("delta", [EvNumber(f)]) =>
|
||||
SymbolicDist.Float.makeSafe(f)->SymbolicConstructors.symbolicResultToOutput
|
||||
| (
|
||||
("normal"
|
||||
| "uniform"
|
||||
| "beta"
|
||||
| "lognormal"
|
||||
| "cauchy"
|
||||
| "gamma"
|
||||
| "credibleIntervalToDistribution"
|
||||
| "to"
|
||||
| "logistic") as fnName,
|
||||
[EvNumber(f1), EvNumber(f2)],
|
||||
) =>
|
||||
SymbolicConstructors.twoFloat(fnName)
|
||||
->E.R.bind(r => r(f1, f2))
|
||||
->SymbolicConstructors.symbolicResultToOutput
|
||||
| ("triangular" as fnName, [EvNumber(f1), EvNumber(f2), EvNumber(f3)]) =>
|
||||
SymbolicConstructors.threeFloat(fnName)
|
||||
->E.R.bind(r => r(f1, f2, f3))
|
||||
|
@ -390,6 +350,20 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
|
|||
| GenDistError(err) => Error(REDistributionError(err))
|
||||
}
|
||||
|
||||
let dispatch = (call, environment) => {
|
||||
dispatchToGenericOutput(call, environment)->E.O2.fmap(genericOutputToReducerValue)
|
||||
// I expect that it's important to build this first, so it doesn't get recalculated for each tryRegistry() call.
|
||||
let registry = FunctionRegistry_Library.registry
|
||||
|
||||
let tryRegistry = ((fnName, args): ExpressionValue.functionCall, env) => {
|
||||
FunctionRegistry_Core.Registry.matchAndRun(~registry, ~fnName, ~args, ~env)->E.O2.fmap(
|
||||
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
|
||||
)
|
||||
}
|
||||
|
||||
let dispatch = (call: ExpressionValue.functionCall, environment) => {
|
||||
let regularDispatch =
|
||||
dispatchToGenericOutput(call, environment)->E.O2.fmap(genericOutputToReducerValue)
|
||||
switch regularDispatch {
|
||||
| Some(x) => Some(x)
|
||||
| None => tryRegistry(call, environment)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
Some functions from modules `L`, `O`, and `R` below were copied directly from
|
||||
running `rescript convert -all` on Rationale https://github.com/jonlaing/rationale
|
||||
*/
|
||||
|
||||
let equals = (a, b) => a === b
|
||||
|
||||
module FloatFloatMap = {
|
||||
module Id = Belt.Id.MakeComparable({
|
||||
type t = float
|
||||
|
@ -49,6 +52,7 @@ module Tuple2 = {
|
|||
let (_, b) = v
|
||||
b
|
||||
}
|
||||
let toFnCall = (fn, (a1, a2)) => fn(a1, a2)
|
||||
}
|
||||
|
||||
module O = {
|
||||
|
@ -525,6 +529,7 @@ module A = {
|
|||
let unsafe_get = Array.unsafe_get
|
||||
let get = Belt.Array.get
|
||||
let getBy = Belt.Array.getBy
|
||||
let getIndexBy = Belt.Array.getIndexBy
|
||||
let last = a => get(a, length(a) - 1)
|
||||
let first = get(_, 0)
|
||||
let hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome
|
||||
|
@ -538,6 +543,7 @@ module A = {
|
|||
let reducei = Belt.Array.reduceWithIndex
|
||||
let isEmpty = r => length(r) < 1
|
||||
let stableSortBy = Belt.SortArray.stableSortBy
|
||||
let toNoneIfEmpty = r => isEmpty(r) ? None : Some(r)
|
||||
let toRanges = (a: array<'a>) =>
|
||||
switch a |> Belt.Array.length {
|
||||
| 0
|
||||
|
@ -552,6 +558,12 @@ module A = {
|
|||
|> (x => Ok(x))
|
||||
}
|
||||
|
||||
let getByOpen = (a, op, bin) =>
|
||||
switch getBy(a, r => bin(op(r))) {
|
||||
| Some(r) => Some(op(r))
|
||||
| None => None
|
||||
}
|
||||
|
||||
let tail = Belt.Array.sliceToEnd(_, 1)
|
||||
|
||||
let zip = Belt.Array.zip
|
||||
|
@ -636,6 +648,19 @@ module A = {
|
|||
}
|
||||
}
|
||||
let firstSome = x => Belt.Array.getBy(x, O.isSome)
|
||||
|
||||
let firstSomeFn = (r: array<unit => option<'a>>): option<'a> =>
|
||||
O.flatten(getByOpen(r, l => l(), O.isSome))
|
||||
|
||||
let firstSomeFnWithDefault = (r, default) => firstSomeFn(r)->O2.default(default)
|
||||
|
||||
let openIfAllSome = (optionals: array<option<'a>>): option<array<'a>> => {
|
||||
if all(O.isSome, optionals) {
|
||||
Some(optionals |> fmap(O.toExn("Warning: This should not have happened")))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module R = {
|
||||
|
@ -822,6 +847,7 @@ module A = {
|
|||
|
||||
module A2 = {
|
||||
let fmap = (a, b) => A.fmap(b, a)
|
||||
let fmapi = (a, b) => A.fmapi(b, a)
|
||||
let joinWith = (a, b) => A.joinWith(b, a)
|
||||
let filter = (a, b) => A.filter(b, a)
|
||||
}
|
||||
|
@ -832,4 +858,10 @@ module JsArray = {
|
|||
|> Js.Array.filter(O.isSome)
|
||||
|> Js.Array.map(O.toExn("Warning: This should not have happened"))
|
||||
let filter = Js.Array.filter
|
||||
}
|
||||
}
|
||||
|
||||
module Dict = {
|
||||
type t<'a> = Js.Dict.t<'a>
|
||||
let get = Js.Dict.get
|
||||
let keys = Js.Dict.keys
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ type operationError =
|
|||
| SampleMapNeedsNtoNFunction
|
||||
| PdfInvalidError
|
||||
| NotYetImplemented // should be removed when `klDivergence` for mixed and discrete is implemented.
|
||||
| Other(string)
|
||||
|
||||
@genType
|
||||
module Error = {
|
||||
|
@ -73,6 +74,7 @@ module Error = {
|
|||
| SampleMapNeedsNtoNFunction => "SampleMap needs a function that converts a number to a number"
|
||||
| PdfInvalidError => "This Pdf is invalid"
|
||||
| NotYetImplemented => "This pathway is not yet implemented"
|
||||
| Other(t) => t
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user