Merge pull request #20 from QURIresearch/Refactor-Feb-2022

Refactor feb 2022
This commit is contained in:
Ozzie Gooen 2022-02-17 22:26:52 -05:00 committed by GitHub
commit c0c6a45dc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 348 additions and 416 deletions

View File

@ -38,7 +38,7 @@ describe("Lodash", () =>
let toArr = discrete |> E.FloatFloatMap.toArray let toArr = discrete |> E.FloatFloatMap.toArray
makeTest("splitMedium", toArr |> Belt.Array.length, 10) makeTest("splitMedium", toArr |> Belt.Array.length, 10)
let (c, discrete) = SampleSet.Internals.T.splitContinuousAndDiscrete( let (_c, discrete) = SampleSet.Internals.T.splitContinuousAndDiscrete(
makeDuplicatedArray(500), makeDuplicatedArray(500),
) )
let toArr = discrete |> E.FloatFloatMap.toArray let toArr = discrete |> E.FloatFloatMap.toArray

View File

@ -4,9 +4,9 @@
"homepage": "https://foretold-app.github.io/estiband/", "homepage": "https://foretold-app.github.io/estiband/",
"private": false, "private": false,
"scripts": { "scripts": {
"build": "rescript build", "build": "rescript build -with-deps",
"parcel": "parcel build ./src/js/index.js --no-source-maps --no-autoinstall", "parcel": "parcel build ./src/js/index.js --no-source-maps --no-autoinstall",
"start": "rescript build -w", "start": "rescript build -w -with-deps",
"clean": "rescript clean", "clean": "rescript clean",
"test": "jest", "test": "jest",
"test:ci": "yarn jest ./__tests__/Lodash__test.re", "test:ci": "yarn jest ./__tests__/Lodash__test.re",

View File

@ -14,7 +14,7 @@ module Inputs = {
type inputs = { type inputs = {
squiggleString: string, squiggleString: string,
samplingInputs: SamplingInputs.t, samplingInputs: SamplingInputs.t,
environment: ASTTypes.AST.environment, environment: ASTTypes.environment,
} }
let empty: SamplingInputs.t = { let empty: SamplingInputs.t = {
@ -27,7 +27,7 @@ module Inputs = {
let make = ( let make = (
~samplingInputs=empty, ~samplingInputs=empty,
~squiggleString, ~squiggleString,
~environment=ASTTypes.AST.Environment.empty, ~environment=ASTTypes.Environment.empty,
(), (),
): inputs => { ): inputs => {
samplingInputs: samplingInputs, samplingInputs: samplingInputs,
@ -36,12 +36,12 @@ module Inputs = {
} }
} }
type \"export" = [ type exported = [
| #DistPlus(DistPlus.t) | #DistPlus(DistPlus.t)
| #Float(float) | #Float(float)
| #Function( | #Function(
(array<string>, ASTTypes.AST.node), (array<string>, ASTTypes.node),
ASTTypes.AST.environment, ASTTypes.environment,
) )
] ]
@ -53,18 +53,18 @@ module Internals = {
): Inputs.inputs => { ): Inputs.inputs => {
samplingInputs: samplingInputs, samplingInputs: samplingInputs,
squiggleString: squiggleString, squiggleString: squiggleString,
environment: ASTTypes.AST.Environment.update(environment, str, _ => Some( environment: ASTTypes.Environment.update(environment, str, _ => Some(
node, node,
)), )),
} }
type outputs = { type outputs = {
graph: ASTTypes.AST.node, graph: ASTTypes.node,
pointSetDist: PointSetTypes.pointSetDist, pointSetDist: PointSetTypes.pointSetDist,
} }
let makeOutputs = (graph, pointSetDist): outputs => {graph: graph, pointSetDist: pointSetDist} let makeOutputs = (graph, pointSetDist): outputs => {graph: graph, pointSetDist: pointSetDist}
let makeInputs = (inputs: Inputs.inputs): ASTTypes.AST.samplingInputs => { let makeInputs = (inputs: Inputs.inputs): SamplingInputs.samplingInputs => {
sampleCount: inputs.samplingInputs.sampleCount |> E.O.default(10000), sampleCount: inputs.samplingInputs.sampleCount |> E.O.default(10000),
outputXYPoints: inputs.samplingInputs.outputXYPoints |> E.O.default(10000), outputXYPoints: inputs.samplingInputs.outputXYPoints |> E.O.default(10000),
kernelWidth: inputs.samplingInputs.kernelWidth, kernelWidth: inputs.samplingInputs.kernelWidth,
@ -74,7 +74,7 @@ module Internals = {
let runNode = (inputs, node) => let runNode = (inputs, node) =>
AST.toLeaf(makeInputs(inputs), inputs.environment, node) AST.toLeaf(makeInputs(inputs), inputs.environment, node)
let runProgram = (inputs: Inputs.inputs, p: ASTTypes.Program.program) => { let runProgram = (inputs: Inputs.inputs, p: ASTTypes.program) => {
let ins = ref(inputs) let ins = ref(inputs)
p p
|> E.A.fmap(x => |> E.A.fmap(x =>
@ -97,8 +97,8 @@ module Internals = {
DistPlus.make(~pointSetDist, ~squiggleString=Some(inputs.squiggleString), ()) DistPlus.make(~pointSetDist, ~squiggleString=Some(inputs.squiggleString), ())
} }
let renderIfNeeded = (inputs: Inputs.inputs, node: ASTTypes.AST.node): result< let renderIfNeeded = (inputs: Inputs.inputs, node: ASTTypes.node): result<
ASTTypes.AST.node, ASTTypes.node,
string, string,
> => > =>
node |> ( node |> (
@ -121,12 +121,12 @@ let renderIfNeeded = (inputs: Inputs.inputs, node: ASTTypes.AST.node): result<
} }
) )
// TODO: Consider using ASTTypes.AST.getFloat or similar in this function // TODO: Consider using ASTTypes.getFloat or similar in this function
let coersionToExportedTypes = ( let coersionToExportedTypes = (
inputs, inputs,
env: ASTTypes.AST.environment, env: ASTTypes.environment,
node: ASTTypes.AST.node, node: ASTTypes.node,
): result<\"export", string> => ): result<exported, string> =>
node node
|> renderIfNeeded(inputs) |> renderIfNeeded(inputs)
|> E.R.bind(_, x => |> E.R.bind(_, x =>
@ -160,7 +160,7 @@ let evaluateProgram = (inputs: Inputs.inputs) =>
let evaluateFunction = ( let evaluateFunction = (
inputs: Inputs.inputs, inputs: Inputs.inputs,
fn: (array<string>, ASTTypes.AST.node), fn: (array<string>, ASTTypes.node),
fnInputs, fnInputs,
) => { ) => {
let output = AST.runFunction( let output = AST.runFunction(

View File

@ -1,6 +1,6 @@
open ASTTypes.AST open ASTTypes
let toString = ASTBasic.toString let toString = ASTTypes.Node.toString
let envs = (samplingInputs, environment) => { let envs = (samplingInputs, environment) => {
samplingInputs: samplingInputs, samplingInputs: samplingInputs,
@ -18,7 +18,7 @@ let toPointSetDist = (samplingInputs, environment, node: node) =>
| Error(e) => Error(e) | Error(e) => Error(e)
} }
let runFunction = (samplingInputs, environment, inputs, fn: PTypes.Function.t) => { let runFunction = (samplingInputs, environment, inputs, fn: ASTTypes.Function.t) => {
let params = envs(samplingInputs, environment) let params = envs(samplingInputs, environment)
PTypes.Function.run(params, inputs, fn) ASTTypes.Function.run(params, inputs, fn)
} }

View File

@ -1,27 +0,0 @@
open ASTTypes.AST
// This file exists to manage a dependency cycle. It would be good to refactor later.
let rec toString: node => string = x =>
switch x {
| #SymbolicDist(d) => SymbolicDist.T.toString(d)
| #RenderedDist(_) => "[renderedShape]"
| #AlgebraicCombination(op, t1, t2) => Operation.Algebraic.format(op, toString(t1), toString(t2))
| #PointwiseCombination(op, t1, t2) => Operation.Pointwise.format(op, toString(t1), toString(t2))
| #Normalize(t) => "normalize(k" ++ (toString(t) ++ ")")
| #Truncate(lc, rc, t) => Operation.T.truncateToString(lc, rc, toString(t))
| #Render(t) => toString(t)
| #Symbol(t) => "Symbol: " ++ t
| #FunctionCall(name, args) =>
"[Function call: (" ++
(name ++
((args |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ ")]"))
| #Function(args, internal) =>
"[Function: (" ++ ((args |> Js.String.concatMany(_, ",")) ++ (toString(internal) ++ ")]"))
| #Array(a) => "[" ++ ((a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]")
| #Hash(h) =>
"{" ++
((h
|> E.A.fmap(((name, value)) => name ++ (":" ++ toString(value)))
|> Js.String.concatMany(_, ",")) ++
"}")
}

View File

@ -1,5 +1,4 @@
open ASTTypes open ASTTypes
open ASTTypes.AST
type t = node type t = node
type tResult = node => result<node, string> type tResult = node => result<node, string>
@ -25,8 +24,8 @@ module AlgebraicCombination = {
string, string,
> => > =>
E.R.merge( E.R.merge(
Render.ensureIsRenderedAndGetShape(evaluationParams, t1), Node.ensureIsRenderedAndGetShape(evaluationParams, t1),
Render.ensureIsRenderedAndGetShape(evaluationParams, t2), Node.ensureIsRenderedAndGetShape(evaluationParams, t2),
) |> E.R.fmap(((a, b)) => #RenderedDist(PointSetDist.combineAlgebraically(algebraicOp, a, b))) ) |> E.R.fmap(((a, b)) => #RenderedDist(PointSetDist.combineAlgebraically(algebraicOp, a, b)))
let nodeScore: node => int = x => let nodeScore: node => int = x =>
@ -44,19 +43,24 @@ module AlgebraicCombination = {
let combine = (evaluationParams, algebraicOp, t1: node, t2: node): result<node, string> => let combine = (evaluationParams, algebraicOp, t1: node, t2: node): result<node, string> =>
E.R.merge( E.R.merge(
PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(evaluationParams, t1), ASTTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(evaluationParams, t1),
PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(evaluationParams, t2), ASTTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(evaluationParams, t2),
) |> E.R.bind(_, ((a, b)) => ) |> E.R.bind(_, ((a, b)) =>
switch choose(a, b) { switch choose(a, b) {
| #Sampling => | #Sampling =>
PTypes.SamplingDistribution.combineShapesUsingSampling(evaluationParams, algebraicOp, a, b) ASTTypes.SamplingDistribution.combineShapesUsingSampling(
evaluationParams,
algebraicOp,
a,
b,
)
| #Analytical => combinationByRendering(evaluationParams, algebraicOp, a, b) | #Analytical => combinationByRendering(evaluationParams, algebraicOp, a, b)
} }
) )
let operationToLeaf = ( let operationToLeaf = (
evaluationParams: evaluationParams, evaluationParams: evaluationParams,
algebraicOp: ASTTypes.algebraicOperation, algebraicOp: Operation.algebraicOperation,
t1: t, t1: t,
t2: t, t2: t,
): result<node, string> => ): result<node, string> =>
@ -71,8 +75,10 @@ module AlgebraicCombination = {
} }
module PointwiseCombination = { module PointwiseCombination = {
//TODO: This is crude and slow. It forces everything to be pointSetDist, even though much
//of the process could happen on symbolic distributions without a conversion to be a pointSetDist.
let pointwiseAdd = (evaluationParams: evaluationParams, t1: t, t2: t) => let pointwiseAdd = (evaluationParams: evaluationParams, t1: t, t2: t) =>
switch (Render.render(evaluationParams, t1), Render.render(evaluationParams, t2)) { switch (Node.render(evaluationParams, t1), Node.render(evaluationParams, t2)) {
| (Ok(#RenderedDist(rs1)), Ok(#RenderedDist(rs2))) => | (Ok(#RenderedDist(rs1)), Ok(#RenderedDist(rs2))) =>
Ok( Ok(
#RenderedDist( #RenderedDist(
@ -96,7 +102,7 @@ module PointwiseCombination = {
switch // TODO: construct a function that we can easily sample from, to construct switch // TODO: construct a function that we can easily sample from, to construct
// a RenderedDist. Use the xMin and xMax of the rendered pointSetDists to tell the sampling function where to look. // a RenderedDist. Use the xMin and xMax of the rendered pointSetDists to tell the sampling function where to look.
// TODO: This should work for symbolic distributions too! // TODO: This should work for symbolic distributions too!
(Render.render(evaluationParams, t1), Render.render(evaluationParams, t2)) { (Node.render(evaluationParams, t1), Node.render(evaluationParams, t2)) {
| (Ok(#RenderedDist(rs1)), Ok(#RenderedDist(rs2))) => | (Ok(#RenderedDist(rs1)), Ok(#RenderedDist(rs2))) =>
Ok(#RenderedDist(PointSetDist.combinePointwise(fn, rs1, rs2))) Ok(#RenderedDist(PointSetDist.combinePointwise(fn, rs1, rs2)))
| (Error(e1), _) => Error(e1) | (Error(e1), _) => Error(e1)
@ -106,7 +112,7 @@ module PointwiseCombination = {
let operationToLeaf = ( let operationToLeaf = (
evaluationParams: evaluationParams, evaluationParams: evaluationParams,
pointwiseOp: pointwiseOperation, pointwiseOp: Operation.pointwiseOperation,
t1: t, t1: t,
t2: t, t2: t,
) => ) =>
@ -118,6 +124,12 @@ module PointwiseCombination = {
} }
module Truncate = { module Truncate = {
type simplificationResult = [
| #Solution(ASTTypes.node)
| #Error(string)
| #NoSolution
]
let trySimplification = (leftCutoff, rightCutoff, t): simplificationResult => let trySimplification = (leftCutoff, rightCutoff, t): simplificationResult =>
switch (leftCutoff, rightCutoff, t) { switch (leftCutoff, rightCutoff, t) {
| (None, None, t) => #Solution(t) | (None, None, t) => #Solution(t)
@ -131,8 +143,9 @@ module Truncate = {
let truncateAsShape = (evaluationParams: evaluationParams, leftCutoff, rightCutoff, t) => let truncateAsShape = (evaluationParams: evaluationParams, leftCutoff, rightCutoff, t) =>
switch // TODO: use named args for xMin/xMax in renderToShape; if we're lucky we can at least get the tail switch // TODO: use named args for xMin/xMax in renderToShape; if we're lucky we can at least get the tail
// of a distribution we otherwise wouldn't get at all // of a distribution we otherwise wouldn't get at all
Render.ensureIsRendered(evaluationParams, t) { Node.ensureIsRendered(evaluationParams, t) {
| Ok(#RenderedDist(rs)) => Ok(#RenderedDist(PointSetDist.T.truncate(leftCutoff, rightCutoff, rs))) | Ok(#RenderedDist(rs)) =>
Ok(#RenderedDist(PointSetDist.T.truncate(leftCutoff, rightCutoff, rs)))
| Error(e) => Error(e) | Error(e) => Error(e)
| _ => Error("Could not truncate distribution.") | _ => Error("Could not truncate distribution.")
} }
@ -160,7 +173,7 @@ module Normalize = {
switch t { switch t {
| #RenderedDist(s) => Ok(#RenderedDist(PointSetDist.T.normalize(s))) | #RenderedDist(s) => Ok(#RenderedDist(PointSetDist.T.normalize(s)))
| #SymbolicDist(_) => Ok(t) | #SymbolicDist(_) => Ok(t)
| _ => evaluateAndRetry(evaluationParams, operationToLeaf, t) | _ => ASTTypes.Node.evaluateAndRetry(evaluationParams, operationToLeaf, t)
} }
} }
@ -170,13 +183,13 @@ module FunctionCall = {
let _runLocalFunction = (name, evaluationParams: evaluationParams, args) => let _runLocalFunction = (name, evaluationParams: evaluationParams, args) =>
Environment.getFunction(evaluationParams.environment, name) |> E.R.bind(_, ((argNames, fn)) => Environment.getFunction(evaluationParams.environment, name) |> E.R.bind(_, ((argNames, fn)) =>
PTypes.Function.run(evaluationParams, args, (argNames, fn)) ASTTypes.Function.run(evaluationParams, args, (argNames, fn))
) )
let _runWithEvaluatedInputs = ( let _runWithEvaluatedInputs = (
evaluationParams: ASTTypes.AST.evaluationParams, evaluationParams: ASTTypes.evaluationParams,
name, name,
args: array<ASTTypes.AST.node>, args: array<ASTTypes.node>,
) => ) =>
_runHardcodedFunction(name, evaluationParams, args) |> E.O.default( _runHardcodedFunction(name, evaluationParams, args) |> E.O.default(
_runLocalFunction(name, evaluationParams, args), _runLocalFunction(name, evaluationParams, args),
@ -195,9 +208,13 @@ module Render = {
switch t { switch t {
| #Function(_) => Error("Cannot render a function") | #Function(_) => Error("Cannot render a function")
| #SymbolicDist(d) => | #SymbolicDist(d) =>
Ok(#RenderedDist(SymbolicDist.T.toPointSetDist(evaluationParams.samplingInputs.pointSetDistLength, d))) Ok(
#RenderedDist(
SymbolicDist.T.toPointSetDist(evaluationParams.samplingInputs.pointSetDistLength, d),
),
)
| #RenderedDist(_) as t => Ok(t) // already a rendered pointSetDist, we're done here | #RenderedDist(_) as t => Ok(t) // already a rendered pointSetDist, we're done here
| _ => evaluateAndRetry(evaluationParams, operationToLeaf, t) | _ => ASTTypes.Node.evaluateAndRetry(evaluationParams, operationToLeaf, t)
} }
} }
@ -207,10 +224,7 @@ module Render = {
but most often it will produce a RenderedDist. but most often it will produce a RenderedDist.
This function is used mainly to turn a parse tree into a single RenderedDist This function is used mainly to turn a parse tree into a single RenderedDist
that can then be displayed to the user. */ that can then be displayed to the user. */
let rec toLeaf = ( let rec toLeaf = (evaluationParams: ASTTypes.evaluationParams, node: t): result<t, string> =>
evaluationParams: ASTTypes.AST.evaluationParams,
node: t,
): result<t, string> =>
switch node { switch node {
// Leaf nodes just stay leaf nodes // Leaf nodes just stay leaf nodes
| #SymbolicDist(_) | #SymbolicDist(_)
@ -236,7 +250,7 @@ let rec toLeaf = (
|> E.A.R.firstErrorOrOpen |> E.A.R.firstErrorOrOpen
|> E.R.fmap(r => #Hash(r)) |> E.R.fmap(r => #Hash(r))
| #Symbol(r) => | #Symbol(r) =>
ASTTypes.AST.Environment.get(evaluationParams.environment, r) ASTTypes.Environment.get(evaluationParams.environment, r)
|> E.O.toResult("Undeclared variable " ++ r) |> E.O.toResult("Undeclared variable " ++ r)
|> E.R.bind(_, toLeaf(evaluationParams)) |> E.R.bind(_, toLeaf(evaluationParams))
| #FunctionCall(name, args) => | #FunctionCall(name, args) =>

View File

@ -1,49 +1,56 @@
type algebraicOperation = [ type rec hash = array<(string, node)>
| #Add and node = [
| #Multiply | #SymbolicDist(SymbolicDistTypes.symbolicDist)
| #Subtract | #RenderedDist(PointSetTypes.pointSetDist)
| #Divide | #Symbol(string)
| #Exponentiate | #Hash(hash)
] | #Array(array<node>)
type pointwiseOperation = [#Add | #Multiply | #Exponentiate] | #Function(array<string>, node)
type scaleOperation = [#Multiply | #Exponentiate | #Log] | #AlgebraicCombination(Operation.algebraicOperation, node, node)
type distToFloatOperation = [ | #PointwiseCombination(Operation.pointwiseOperation, node, node)
| #Pdf(float) | #Normalize(node)
| #Cdf(float) | #Render(node)
| #Inv(float) | #Truncate(option<float>, option<float>, node)
| #Mean | #FunctionCall(string, array<node>)
| #Sample
] ]
module AST = { type statement = [
type rec hash = array<(string, node)> | #Assignment(string, node)
and node = [ | #Expression(node)
| #SymbolicDist(SymbolicDistTypes.symbolicDist) ]
| #RenderedDist(PointSetTypes.pointSetDist) type program = array<statement>
| #Symbol(string)
| #Hash(hash)
| #Array(array<node>)
| #Function(array<string>, node)
| #AlgebraicCombination(algebraicOperation, node, node)
| #PointwiseCombination(pointwiseOperation, node, node)
| #Normalize(node)
| #Render(node)
| #Truncate(option<float>, option<float>, node)
| #FunctionCall(string, array<node>)
]
module Hash = { type environment = Belt.Map.String.t<node>
type t<'a> = array<(string, 'a)>
let getByName = (t: t<'a>, name) =>
E.A.getBy(t, ((n, _)) => n == name) |> E.O.fmap(((_, r)) => r)
let getByNameResult = (t: t<'a>, name) => type rec evaluationParams = {
getByName(t, name) |> E.O.toResult(name ++ " expected and not found") samplingInputs: SamplingInputs.samplingInputs,
environment: environment,
evaluateNode: (evaluationParams, node) => Belt.Result.t<node, string>,
}
let getByNames = (hash: t<'a>, names: array<string>) => module Environment = {
names |> E.A.fmap(name => (name, getByName(hash, name))) type t = environment
} module MS = Belt.Map.String
// Have nil as option let fromArray = MS.fromArray
let empty: t = []->fromArray
let mergeKeepSecond = (a: t, b: t) =>
MS.merge(a, b, (_, a, b) =>
switch (a, b) {
| (_, Some(b)) => Some(b)
| (Some(a), _) => Some(a)
| _ => None
}
)
let update = (t, str, fn) => MS.update(t, str, fn)
let get = (t: t, str) => MS.get(t, str)
let getFunction = (t: t, str) =>
switch get(t, str) {
| Some(#Function(argNames, fn)) => Ok((argNames, fn))
| _ => Error("Function " ++ (str ++ " not found"))
}
}
module Node = {
let getFloat = (node: node) => let getFloat = (node: node) =>
node |> ( node |> (
x => x =>
@ -54,121 +61,172 @@ module AST = {
} }
) )
let toFloatIfNeeded = (node: node) => let evaluate = (evaluationParams: evaluationParams) =>
switch node |> getFloat {
| Some(float) => #SymbolicDist(#Float(float))
| None => node
}
type samplingInputs = {
sampleCount: int,
outputXYPoints: int,
kernelWidth: option<float>,
pointSetDistLength: int,
}
module SamplingInputs = {
type t = {
sampleCount: option<int>,
outputXYPoints: option<int>,
kernelWidth: option<float>,
pointSetDistLength: option<int>,
}
let withDefaults = (t: t): samplingInputs => {
sampleCount: t.sampleCount |> E.O.default(10000),
outputXYPoints: t.outputXYPoints |> E.O.default(10000),
kernelWidth: t.kernelWidth,
pointSetDistLength: t.pointSetDistLength |> E.O.default(10000),
}
}
type environment = Belt.Map.String.t<node>
module Environment = {
type t = environment
module MS = Belt.Map.String
let fromArray = MS.fromArray
let empty: t = []->fromArray
let mergeKeepSecond = (a: t, b: t) =>
MS.merge(a, b, (_, a, b) =>
switch (a, b) {
| (_, Some(b)) => Some(b)
| (Some(a), _) => Some(a)
| _ => None
}
)
let update = (t, str, fn) => MS.update(t, str, fn)
let get = (t: t, str) => MS.get(t, str)
let getFunction = (t: t, str) =>
switch get(t, str) {
| Some(#Function(argNames, fn)) => Ok((argNames, fn))
| _ => Error("Function " ++ (str ++ " not found"))
}
}
type rec evaluationParams = {
samplingInputs: samplingInputs,
environment: environment,
evaluateNode: (evaluationParams, node) => Belt.Result.t<node, string>,
}
let evaluateNode = (evaluationParams: evaluationParams) =>
evaluationParams.evaluateNode(evaluationParams) evaluationParams.evaluateNode(evaluationParams)
let evaluateAndRetry = (evaluationParams, fn, node) => let evaluateAndRetry = (evaluationParams, fn, node) =>
node |> evaluationParams.evaluateNode(evaluationParams) |> E.R.bind(_, fn(evaluationParams)) node |> evaluationParams.evaluateNode(evaluationParams) |> E.R.bind(_, fn(evaluationParams))
module Render = { let rec toString: node => string = x =>
type t = node switch x {
| #SymbolicDist(d) => SymbolicDist.T.toString(d)
| #RenderedDist(_) => "[renderedShape]"
| #AlgebraicCombination(op, t1, t2) =>
Operation.Algebraic.format(op, toString(t1), toString(t2))
| #PointwiseCombination(op, t1, t2) =>
Operation.Pointwise.format(op, toString(t1), toString(t2))
| #Normalize(t) => "normalize(k" ++ (toString(t) ++ ")")
| #Truncate(lc, rc, t) => Operation.Truncate.toString(lc, rc, toString(t))
| #Render(t) => toString(t)
| #Symbol(t) => "Symbol: " ++ t
| #FunctionCall(name, args) =>
"[Function call: (" ++
(name ++
((args |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ ")]"))
| #Function(args, internal) =>
"[Function: (" ++ ((args |> Js.String.concatMany(_, ",")) ++ (toString(internal) ++ ")]"))
| #Array(a) => "[" ++ ((a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]")
| #Hash(h) =>
"{" ++
((h
|> E.A.fmap(((name, value)) => name ++ (":" ++ toString(value)))
|> Js.String.concatMany(_, ",")) ++
"}")
}
let render = (evaluationParams: evaluationParams, r) => let render = (evaluationParams: evaluationParams, r) => #Render(r) |> evaluate(evaluationParams)
#Render(r) |> evaluateNode(evaluationParams)
let ensureIsRendered = (params, t) => let ensureIsRendered = (params, t) =>
switch t { switch t {
| #RenderedDist(_) => Ok(t) | #RenderedDist(_) => Ok(t)
| _ => | _ =>
switch render(params, t) { switch render(params, t) {
| Ok(#RenderedDist(r)) => Ok(#RenderedDist(r)) | Ok(#RenderedDist(r)) => Ok(#RenderedDist(r))
| Ok(_) => Error("Did not render as requested")
| Error(e) => Error(e)
}
}
let ensureIsRenderedAndGetShape = (params, t) =>
switch ensureIsRendered(params, t) {
| Ok(#RenderedDist(r)) => Ok(r)
| Ok(_) => Error("Did not render as requested") | Ok(_) => Error("Did not render as requested")
| Error(e) => Error(e) | Error(e) => Error(e)
} }
}
let getShape = (item: node) => let ensureIsRenderedAndGetShape = (params, t) =>
switch item { switch ensureIsRendered(params, t) {
| #RenderedDist(r) => Some(r) | Ok(#RenderedDist(r)) => Ok(r)
| _ => None | Ok(_) => Error("Did not render as requested")
} | Error(e) => Error(e)
}
let _toFloat = (t: PointSetTypes.pointSetDist) => let toPointSetDist = (item: node) =>
switch t { switch item {
| Discrete({xyShape: {xs: [x], ys: [1.0]}}) => Some(#SymbolicDist(#Float(x))) | #RenderedDist(r) => Some(r)
| _ => None | _ => None
} }
let toFloat = (item: node): result<node, string> => let _toFloat = (t: PointSetTypes.pointSetDist) =>
item |> getShape |> E.O.bind(_, _toFloat) |> E.O.toResult("Not valid shape") switch t {
} | Discrete({xyShape: {xs: [x], ys: [1.0]}}) => Some(#SymbolicDist(#Float(x)))
| _ => None
}
let toFloat = (item: node): result<node, string> =>
item |> toPointSetDist |> E.O.bind(_, _toFloat) |> E.O.toResult("Not valid shape")
} }
type simplificationResult = [ module Function = {
| #Solution(AST.node) type t = (array<string>, node)
| #Error(string) let fromNode: node => option<t> = node =>
| #NoSolution switch node {
] | #Function(r) => Some(r)
| _ => None
}
let argumentNames = ((a, _): t) => a
let internals = ((_, b): t) => b
let run = (evaluationParams: evaluationParams, args: array<node>, t: t) =>
if E.A.length(args) == E.A.length(argumentNames(t)) {
let newEnvironment = Belt.Array.zip(argumentNames(t), args) |> Environment.fromArray
let newEvaluationParams: evaluationParams = {
samplingInputs: evaluationParams.samplingInputs,
environment: Environment.mergeKeepSecond(evaluationParams.environment, newEnvironment),
evaluateNode: evaluationParams.evaluateNode,
}
evaluationParams.evaluateNode(newEvaluationParams, internals(t))
} else {
Error("Wrong number of variables")
}
}
module Program = { module SamplingDistribution = {
type statement = [ type t = [
| #Assignment(string, AST.node) | #SymbolicDist(SymbolicDistTypes.symbolicDist)
| #Expression(AST.node) | #RenderedDist(PointSetTypes.pointSetDist)
] ]
type program = array<statement>
} let isSamplingDistribution: node => bool = x =>
switch x {
| #SymbolicDist(_) => true
| #RenderedDist(_) => true
| _ => false
}
let fromNode: node => result<t, string> = x =>
switch x {
| #SymbolicDist(n) => Ok(#SymbolicDist(n))
| #RenderedDist(n) => Ok(#RenderedDist(n))
| _ => Error("Not valid type")
}
let renderIfIsNotSamplingDistribution = (params, t): result<node, string> =>
!isSamplingDistribution(t)
? switch Node.render(params, t) {
| Ok(r) => Ok(r)
| Error(e) => Error(e)
}
: Ok(t)
let map = (~renderedDistFn, ~symbolicDistFn, node: node) =>
node |> (
x =>
switch x {
| #RenderedDist(r) => Some(renderedDistFn(r))
| #SymbolicDist(s) => Some(symbolicDistFn(s))
| _ => None
}
)
let sampleN = n =>
map(~renderedDistFn=PointSetDist.sampleNRendered(n), ~symbolicDistFn=SymbolicDist.T.sampleN(n))
let getCombinationSamples = (n, algebraicOp, t1: node, t2: node) =>
switch (sampleN(n, t1), sampleN(n, t2)) {
| (Some(a), Some(b)) =>
Some(
Belt.Array.zip(a, b) |> E.A.fmap(((a, b)) => Operation.Algebraic.toFn(algebraicOp, a, b)),
)
| _ => None
}
let combineShapesUsingSampling = (
evaluationParams: evaluationParams,
algebraicOp,
t1: node,
t2: node,
) => {
let i1 = renderIfIsNotSamplingDistribution(evaluationParams, t1)
let i2 = renderIfIsNotSamplingDistribution(evaluationParams, t2)
E.R.merge(i1, i2) |> E.R.bind(_, ((a, b)) => {
let samples = getCombinationSamples(
evaluationParams.samplingInputs.sampleCount,
algebraicOp,
a,
b,
)
let pointSetDist =
samples
|> E.O.fmap(r =>
SampleSet.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)))
})
}
}

View File

@ -1,138 +0,0 @@
open ASTTypes.AST
module Function = {
type t = (array<string>, node)
let fromNode: node => option<t> = node =>
switch node {
| #Function(r) => Some(r)
| _ => None
}
let argumentNames = ((a, _): t) => a
let internals = ((_, b): t) => b
let run = (
evaluationParams: ASTTypes.AST.evaluationParams,
args: array<node>,
t: t,
) =>
if E.A.length(args) == E.A.length(argumentNames(t)) {
let newEnvironment =
Belt.Array.zip(
argumentNames(t),
args,
) |> ASTTypes.AST.Environment.fromArray
let newEvaluationParams: ASTTypes.AST.evaluationParams = {
samplingInputs: evaluationParams.samplingInputs,
environment: ASTTypes.AST.Environment.mergeKeepSecond(
evaluationParams.environment,
newEnvironment,
),
evaluateNode: evaluationParams.evaluateNode,
}
evaluationParams.evaluateNode(newEvaluationParams, internals(t))
} else {
Error("Wrong number of variables")
}
}
module Primative = {
type t = [
| #SymbolicDist(SymbolicDistTypes.symbolicDist)
| #RenderedDist(PointSetTypes.pointSetDist)
| #Function(array<string>, node)
]
let isPrimative: node => bool = x =>
switch x {
| #SymbolicDist(_)
| #RenderedDist(_)
| #Function(_) => true
| _ => false
}
let fromNode: node => option<t> = x =>
switch x {
| #SymbolicDist(_) as n
| #RenderedDist(_) as n
| #Function(_) as n =>
Some(n)
| _ => None
}
}
module SamplingDistribution = {
type t = [
| #SymbolicDist(SymbolicDistTypes.symbolicDist)
| #RenderedDist(PointSetTypes.pointSetDist)
]
let isSamplingDistribution: node => bool = x =>
switch x {
| #SymbolicDist(_) => true
| #RenderedDist(_) => true
| _ => false
}
let fromNode: node => result<t, string> = x =>
switch x {
| #SymbolicDist(n) => Ok(#SymbolicDist(n))
| #RenderedDist(n) => Ok(#RenderedDist(n))
| _ => Error("Not valid type")
}
let renderIfIsNotSamplingDistribution = (params, t): result<node, string> =>
!isSamplingDistribution(t)
? switch Render.render(params, t) {
| Ok(r) => Ok(r)
| Error(e) => Error(e)
}
: Ok(t)
let map = (~renderedDistFn, ~symbolicDistFn, node: node) =>
node |> (
x =>
switch x {
| #RenderedDist(r) => Some(renderedDistFn(r))
| #SymbolicDist(s) => Some(symbolicDistFn(s))
| _ => None
}
)
let sampleN = n =>
map(~renderedDistFn=PointSetDist.sampleNRendered(n), ~symbolicDistFn=SymbolicDist.T.sampleN(n))
let getCombinationSamples = (n, algebraicOp, t1: node, t2: node) =>
switch (sampleN(n, t1), sampleN(n, t2)) {
| (Some(a), Some(b)) =>
Some(
Belt.Array.zip(a, b) |> E.A.fmap(((a, b)) => Operation.Algebraic.toFn(algebraicOp, a, b)),
)
| _ => None
}
let combineShapesUsingSampling = (
evaluationParams: evaluationParams,
algebraicOp,
t1: node,
t2: node,
) => {
let i1 = renderIfIsNotSamplingDistribution(evaluationParams, t1)
let i2 = renderIfIsNotSamplingDistribution(evaluationParams, t2)
E.R.merge(i1, i2) |> E.R.bind(_, ((a, b)) => {
let samples = getCombinationSamples(
evaluationParams.samplingInputs.sampleCount,
algebraicOp,
a,
b,
)
// todo: This bottom part should probably be somewhere else.
// todo: REFACTOR: I'm not sure about the SampleSet line.
let pointSetDist =
samples
|> E.O.fmap(r => SampleSet.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)))
})
}
}

View File

@ -84,7 +84,7 @@ let makeDist = (name, fn) =>
) )
let floatFromDist = ( let floatFromDist = (
distToFloatOp: ASTTypes.distToFloatOperation, distToFloatOp: Operation.distToFloatOperation,
t: TypeSystem.samplingDist, t: TypeSystem.samplingDist,
): result<node, string> => ): result<node, string> =>
switch t { switch t {
@ -111,7 +111,7 @@ let verticalScaling = (scaleOp, rs, scaleBy) => {
} }
module Multimodal = { module Multimodal = {
let getByNameResult = ASTTypes.AST.Hash.getByNameResult let getByNameResult = Hash.getByNameResult
let _paramsToDistsAndWeights = (r: array<typedValue>) => let _paramsToDistsAndWeights = (r: array<typedValue>) =>
switch r { switch r {

View File

@ -1,5 +1,5 @@
type node = ASTTypes.AST.node type node = ASTTypes.node
let getFloat = ASTTypes.AST.getFloat let getFloat = ASTTypes.Node.getFloat
type samplingDist = [ type samplingDist = [
| #SymbolicDist(SymbolicDistTypes.symbolicDist) | #SymbolicDist(SymbolicDistTypes.symbolicDist)
@ -61,7 +61,7 @@ module TypedValue = {
|> E.A.fmap(((name, t)) => fromNode(t) |> E.R.fmap(r => (name, r))) |> E.A.fmap(((name, t)) => fromNode(t) |> E.R.fmap(r => (name, r)))
|> E.A.R.firstErrorOrOpen |> E.A.R.firstErrorOrOpen
|> E.R.fmap(r => #Hash(r)) |> E.R.fmap(r => #Hash(r))
| e => Error("Wrong type: " ++ ASTBasic.toString(e)) | e => Error("Wrong type: " ++ ASTTypes.Node.toString(e))
} }
// todo: Arrays and hashes // todo: Arrays and hashes
@ -73,12 +73,12 @@ module TypedValue = {
| _ => Error("Type Error: Expected float.") | _ => Error("Type Error: Expected float.")
} }
| (#SamplingDistribution, _) => | (#SamplingDistribution, _) =>
PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution( ASTTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(
evaluationParams, evaluationParams,
node, node,
) |> E.R.bind(_, fromNode) ) |> E.R.bind(_, fromNode)
| (#RenderedDistribution, _) => | (#RenderedDistribution, _) =>
ASTTypes.AST.Render.render(evaluationParams, node) |> E.R.bind(_, fromNode) ASTTypes.Node.render(evaluationParams, node) |> E.R.bind(_, fromNode)
| (#Array(_type), #Array(b)) => | (#Array(_type), #Array(b)) =>
b b
|> E.A.fmap(fromNodeWithTypeCoercion(evaluationParams, _type)) |> E.A.fmap(fromNodeWithTypeCoercion(evaluationParams, _type))
@ -89,7 +89,7 @@ module TypedValue = {
named |> E.A.fmap(((name, intendedType)) => ( named |> E.A.fmap(((name, intendedType)) => (
name, name,
intendedType, intendedType,
ASTTypes.AST.Hash.getByName(r, name), Hash.getByName(r, name),
)) ))
let typedHash = let typedHash =
keyValues keyValues
@ -172,7 +172,7 @@ module Function = {
|> E.A.R.firstErrorOrOpen |> E.A.R.firstErrorOrOpen
let inputsToTypedValues = ( let inputsToTypedValues = (
evaluationParams: ASTTypes.AST.evaluationParams, evaluationParams: ASTTypes.evaluationParams,
inputNodes: inputNodes, inputNodes: inputNodes,
t: t, t: t,
) => ) =>
@ -181,7 +181,7 @@ module Function = {
) )
let run = ( let run = (
evaluationParams: ASTTypes.AST.evaluationParams, evaluationParams: ASTTypes.evaluationParams,
inputNodes: inputNodes, inputNodes: inputNodes,
t: t, t: t,
) => ) =>

View File

@ -122,7 +122,7 @@ module MathAdtToDistDst = {
| _ => Error("Lognormal distribution needs either mean and stdev or mu and sigma") | _ => Error("Lognormal distribution needs either mean and stdev or mu and sigma")
} }
| _ => | _ =>
parseArgs() |> E.R.fmap((args: array<ASTTypes.AST.node>) => parseArgs() |> E.R.fmap((args: array<ASTTypes.node>) =>
#FunctionCall("lognormal", args) #FunctionCall("lognormal", args)
) )
} }
@ -130,8 +130,8 @@ module MathAdtToDistDst = {
// Error("Dotwise exponentiation needs two operands") // Error("Dotwise exponentiation needs two operands")
let operationParser = ( let operationParser = (
name: string, name: string,
args: result<array<ASTTypes.AST.node>, string>, args: result<array<ASTTypes.node>, string>,
): result<ASTTypes.AST.node, string> => { ): result<ASTTypes.node, string> => {
let toOkAlgebraic = r => Ok(#AlgebraicCombination(r)) let toOkAlgebraic = r => Ok(#AlgebraicCombination(r))
let toOkPointwise = r => Ok(#PointwiseCombination(r)) let toOkPointwise = r => Ok(#PointwiseCombination(r))
let toOkTruncate = r => Ok(#Truncate(r)) let toOkTruncate = r => Ok(#Truncate(r))
@ -170,12 +170,12 @@ module MathAdtToDistDst = {
let functionParser = ( let functionParser = (
nodeParser: MathJsonToMathJsAdt.arg => Belt.Result.t< nodeParser: MathJsonToMathJsAdt.arg => Belt.Result.t<
ASTTypes.AST.node, ASTTypes.node,
string, string,
>, >,
name: string, name: string,
args: array<MathJsonToMathJsAdt.arg>, args: array<MathJsonToMathJsAdt.arg>,
): result<ASTTypes.AST.node, string> => { ): result<ASTTypes.node, string> => {
let parseArray = ags => ags |> E.A.fmap(nodeParser) |> E.A.R.firstErrorOrOpen let parseArray = ags => ags |> E.A.fmap(nodeParser) |> E.A.R.firstErrorOrOpen
let parseArgs = () => parseArray(args) let parseArgs = () => parseArray(args)
switch name { switch name {
@ -212,27 +212,27 @@ module MathAdtToDistDst = {
| (Some(Error(r)), _) => Error(r) | (Some(Error(r)), _) => Error(r)
| (_, Error(r)) => Error(r) | (_, Error(r)) => Error(r)
| (None, Ok(dists)) => | (None, Ok(dists)) =>
let hash: ASTTypes.AST.node = #FunctionCall( let hash: ASTTypes.node = #FunctionCall(
"multimodal", "multimodal",
[#Hash([("dists", #Array(dists)), ("weights", #Array([]))])], [#Hash([("dists", #Array(dists)), ("weights", #Array([]))])],
) )
Ok(hash) Ok(hash)
| (Some(Ok(weights)), Ok(dists)) => | (Some(Ok(weights)), Ok(dists)) =>
let hash: ASTTypes.AST.node = #FunctionCall( let hash: ASTTypes.node = #FunctionCall(
"multimodal", "multimodal",
[#Hash([("dists", #Array(dists)), ("weights", #Array(weights))])], [#Hash([("dists", #Array(dists)), ("weights", #Array(weights))])],
) )
Ok(hash) Ok(hash)
} }
| name => | name =>
parseArgs() |> E.R.fmap((args: array<ASTTypes.AST.node>) => parseArgs() |> E.R.fmap((args: array<ASTTypes.node>) =>
#FunctionCall(name, args) #FunctionCall(name, args)
) )
} }
} }
let rec nodeParser: MathJsonToMathJsAdt.arg => result< let rec nodeParser: MathJsonToMathJsAdt.arg => result<
ASTTypes.AST.node, ASTTypes.node,
string, string,
> = x => > = x =>
switch x { switch x {
@ -246,7 +246,7 @@ module MathAdtToDistDst = {
// let evaluatedExpression = run(expression); // let evaluatedExpression = run(expression);
// `Function(_ => Ok(evaluatedExpression)); // `Function(_ => Ok(evaluatedExpression));
// } // }
let rec topLevel = (r): result<ASTTypes.Program.program, string> => let rec topLevel = (r): result<ASTTypes.program, string> =>
switch r { switch r {
| FunctionAssignment({name, args, expression}) => | FunctionAssignment({name, args, expression}) =>
switch nodeParser(expression) { switch nodeParser(expression) {
@ -267,7 +267,7 @@ module MathAdtToDistDst = {
blocks |> E.A.fmap(b => topLevel(b)) |> E.A.R.firstErrorOrOpen |> E.R.fmap(E.A.concatMany) blocks |> E.A.fmap(b => topLevel(b)) |> E.A.R.firstErrorOrOpen |> E.R.fmap(E.A.concatMany)
} }
let run = (r): result<ASTTypes.Program.program, string> => let run = (r): result<ASTTypes.program, string> =>
r |> MathAdtCleaner.run |> topLevel r |> MathAdtCleaner.run |> topLevel
} }

View File

@ -96,12 +96,10 @@ let toDiscretePointMassesFromTriangulars = (
} }
let combineShapesContinuousContinuous = ( let combineShapesContinuousContinuous = (
op: ASTTypes.algebraicOperation, op: Operation.algebraicOperation,
s1: PointSetTypes.xyShape, s1: PointSetTypes.xyShape,
s2: PointSetTypes.xyShape, s2: PointSetTypes.xyShape,
): PointSetTypes.xyShape => { ): PointSetTypes.xyShape => {
let t1n = s1 |> XYShape.T.length
let t2n = s2 |> XYShape.T.length
// if we add the two distributions, we should probably use normal filters. // if we add the two distributions, we should probably use normal filters.
// if we multiply the two distributions, we should probably use lognormal filters. // if we multiply the two distributions, we should probably use lognormal filters.
@ -194,13 +192,13 @@ let toDiscretePointMassesFromDiscrete = (s: PointSetTypes.xyShape): pointMassesW
let masses: array<float> = Belt.Array.makeBy(n, i => ys[i]) let masses: array<float> = Belt.Array.makeBy(n, i => ys[i])
let means: array<float> = Belt.Array.makeBy(n, i => xs[i]) let means: array<float> = Belt.Array.makeBy(n, i => xs[i])
let variances: array<float> = Belt.Array.makeBy(n, i => 0.0) let variances: array<float> = Belt.Array.makeBy(n, _ => 0.0)
{n: n, masses: masses, means: means, variances: variances} {n: n, masses: masses, means: means, variances: variances}
} }
let combineShapesContinuousDiscrete = ( let combineShapesContinuousDiscrete = (
op: ASTTypes.algebraicOperation, op: Operation.algebraicOperation,
continuousShape: PointSetTypes.xyShape, continuousShape: PointSetTypes.xyShape,
discreteShape: PointSetTypes.xyShape, discreteShape: PointSetTypes.xyShape,
): PointSetTypes.xyShape => { ): PointSetTypes.xyShape => {

View File

@ -211,7 +211,7 @@ module T = Dist({
/* 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 = (
op: ASTTypes.algebraicOperation, op: Operation.algebraicOperation,
t1: t, t1: t,
t2: PointSetTypes.discreteShape, t2: PointSetTypes.discreteShape,
) => { ) => {
@ -244,7 +244,7 @@ let combineAlgebraicallyWithDiscrete = (
} }
} }
let combineAlgebraically = (op: ASTTypes.algebraicOperation, t1: t, t2: t) => { let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t) => {
let s1 = t1 |> getShape let s1 = t1 |> getShape
let s2 = t2 |> getShape let s2 = t2 |> getShape
let t1n = s1 |> XYShape.T.length let t1n = s1 |> XYShape.T.length

View File

@ -85,7 +85,7 @@ let updateIntegralCache = (integralCache, t: t): t => {
/* This multiples all of the data points together and creates a new discrete distribution from the results. /* This multiples all of the data points together and creates a new discrete distribution from the results.
Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */ Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */
let combineAlgebraically = (op: ASTTypes.algebraicOperation, t1: t, t2: t): t => { let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t => {
let t1s = t1 |> getShape let t1s = t1 |> getShape
let t2s = t2 |> getShape let t2s = t2 |> getShape
let t1n = t1s |> XYShape.T.length let t1n = t1s |> XYShape.T.length

View File

@ -227,7 +227,7 @@ module T = Dist({
} }
}) })
let combineAlgebraically = (op: ASTTypes.algebraicOperation, t1: t, t2: t): t => { let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t => {
// Discrete convolution can cause a huge increase in the number of samples, // Discrete convolution can cause a huge increase in the number of samples,
// so we'll first downsample. // so we'll first downsample.
@ -240,9 +240,6 @@ let combineAlgebraically = (op: ASTTypes.algebraicOperation, t1: t, t2: t): t =>
// sqtl > 10 ? T.downsample(int_of_float(sqtl), t) : t; // sqtl > 10 ? T.downsample(int_of_float(sqtl), t) : t;
//}; //};
let t1d = t1
let t2d = t2
// continuous (*) continuous => continuous, but also // continuous (*) continuous => continuous, but also
// discrete (*) continuous => continuous (and vice versa). We have to take care of all combos and then combine them: // discrete (*) continuous => continuous (and vice versa). We have to take care of all combos and then combine them:
let ccConvResult = Continuous.combineAlgebraically(op, t1.continuous, t2.continuous) let ccConvResult = Continuous.combineAlgebraically(op, t1.continuous, t2.continuous)

View File

@ -1,6 +1,7 @@
open Distributions open Distributions
type t = PointSetTypes.pointSetDist type t = PointSetTypes.pointSetDist
let mapToAll = ((fn1, fn2, fn3), t: t) => let mapToAll = ((fn1, fn2, fn3), t: t) =>
switch t { switch t {
| Mixed(m) => fn1(m) | Mixed(m) => fn1(m)
@ -33,7 +34,7 @@ let toMixed = mapToAll((
), ),
)) ))
let combineAlgebraically = (op: ASTTypes.algebraicOperation, t1: t, t2: t): t => let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =>
switch (t1, t2) { switch (t1, t2) {
| (Continuous(m1), Continuous(m2)) => | (Continuous(m1), Continuous(m2)) =>
Continuous.combineAlgebraically(op, m1, m2) |> Continuous.T.toPointSetDist Continuous.combineAlgebraically(op, m1, m2) |> Continuous.T.toPointSetDist
@ -77,9 +78,6 @@ module T = Dist({
let toPointSetDist = (t: t) => t let toPointSetDist = (t: t) => t
let toContinuous = t => None
let toDiscrete = t => None
let downsample = (i, t) => let downsample = (i, t) =>
fmap((Mixed.T.downsample(i), Discrete.T.downsample(i), Continuous.T.downsample(i)), t) fmap((Mixed.T.downsample(i), Discrete.T.downsample(i), Continuous.T.downsample(i)), t)
@ -93,8 +91,6 @@ module T = Dist({
t, t,
) )
let toDiscreteProbabilityMassFraction = t => 0.0
let normalize = fmap((Mixed.T.normalize, Discrete.T.normalize, Continuous.T.normalize)) let normalize = fmap((Mixed.T.normalize, Discrete.T.normalize, Continuous.T.normalize))
let updateIntegralCache = (integralCache, t: t): t => let updateIntegralCache = (integralCache, t: t): t =>
@ -197,7 +193,7 @@ let sampleNRendered = (n, dist) => {
doN(n, () => sample(distWithUpdatedIntegralCache)) doN(n, () => sample(distWithUpdatedIntegralCache))
} }
let operate = (distToFloatOp: ASTTypes.distToFloatOperation, s): float => let operate = (distToFloatOp: Operation.distToFloatOperation, s): float =>
switch distToFloatOp { switch distToFloatOp {
| #Pdf(f) => pdf(f, s) | #Pdf(f) => pdf(f, s)
| #Cdf(f) => pdf(f, s) | #Cdf(f) => pdf(f, s)

View File

@ -159,7 +159,7 @@ module XtoY = {
y1 *. (1. -. fraction) +. y2 *. fraction y1 *. (1. -. fraction) +. y2 *. fraction
} }
| (#Stepwise, #UseZero) => | (#Stepwise, #UseZero) =>
(t: T.t, leftIndex: int, x: float) => (t: T.t, leftIndex: int, _x: float) =>
if leftIndex < 0 { if leftIndex < 0 {
0.0 0.0
} else if leftIndex >= T.length(t) - 1 { } else if leftIndex >= T.length(t) - 1 {
@ -168,7 +168,7 @@ module XtoY = {
t.ys[leftIndex] t.ys[leftIndex]
} }
| (#Stepwise, #UseOutermostPoints) => | (#Stepwise, #UseOutermostPoints) =>
(t: T.t, leftIndex: int, x: float) => (t: T.t, leftIndex: int, _x: float) =>
if leftIndex < 0 { if leftIndex < 0 {
t.ys[0] t.ys[0]
} else if leftIndex >= T.length(t) - 1 { } else if leftIndex >= T.length(t) - 1 {

View File

@ -80,7 +80,7 @@ module Internals = {
let toPointSetDist = ( let toPointSetDist = (
~samples: Internals.T.t, ~samples: Internals.T.t,
~samplingInputs: ASTTypes.AST.samplingInputs, ~samplingInputs: SamplingInputs.samplingInputs,
(), (),
) => { ) => {
Array.fast_sort(compare, samples) Array.fast_sort(compare, samples)

View File

@ -272,7 +272,7 @@ module T = {
| #Float(n) => Float.mean(n) | #Float(n) => Float.mean(n)
} }
let operate = (distToFloatOp: ASTTypes.distToFloatOperation, s) => let operate = (distToFloatOp: Operation.distToFloatOperation, s) =>
switch distToFloatOp { switch distToFloatOp {
| #Cdf(f) => Ok(cdf(f, s)) | #Cdf(f) => Ok(cdf(f, s))
| #Pdf(f) => Ok(pdf(f, s)) | #Pdf(f) => Ok(pdf(f, s))
@ -302,7 +302,7 @@ module T = {
let tryAnalyticalSimplification = ( let tryAnalyticalSimplification = (
d1: symbolicDist, d1: symbolicDist,
d2: symbolicDist, d2: symbolicDist,
op: ASTTypes.algebraicOperation, op: Operation.algebraicOperation,
): analyticalSimplificationResult => ): analyticalSimplificationResult =>
switch (d1, d2) { switch (d1, d2) {
| (#Float(v1), #Float(v2)) => | (#Float(v1), #Float(v2)) =>

View File

@ -0,0 +1,8 @@
type t<'a> = array<(string, 'a)>
let getByName = (t: t<'a>, name) => E.A.getBy(t, ((n, _)) => n == name) |> E.O.fmap(((_, r)) => r)
let getByNameResult = (t: t<'a>, name) =>
getByName(t, name) |> E.O.toResult(name ++ " expected and not found")
let getByNames = (hash: t<'a>, names: array<string>) =>
names |> E.A.fmap(name => (name, getByName(hash, name)))

View File

@ -1,4 +1,21 @@
open ASTTypes // This file has no dependencies. It's used outside of the interpreter, but the interpreter depends on it.
type algebraicOperation = [
| #Add
| #Multiply
| #Subtract
| #Divide
| #Exponentiate
]
type pointwiseOperation = [#Add | #Multiply | #Exponentiate]
type scaleOperation = [#Multiply | #Exponentiate | #Log]
type distToFloatOperation = [
| #Pdf(float)
| #Cdf(float)
| #Inv(float)
| #Mean
| #Sample
]
module Algebraic = { module Algebraic = {
type t = algebraicOperation type t = algebraicOperation
@ -80,28 +97,16 @@ module Scale = {
let toIntegralCacheFn = x => let toIntegralCacheFn = x =>
switch x { switch x {
| #Multiply => (a, b) => 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)
| #Exponentiate => (_, _) => None | #Exponentiate => (_, _) => None
| #Log => (_, _) => None | #Log => (_, _) => None
} }
} }
module T = { module Truncate = {
let truncateToString = (left: option<float>, right: option<float>, nodeToString) => { let toString = (left: option<float>, right: option<float>, nodeToString) => {
let left = left |> E.O.dimap(Js.Float.toString, () => "-inf") let left = left |> E.O.dimap(Js.Float.toString, () => "-inf")
let right = right |> E.O.dimap(Js.Float.toString, () => "inf") let right = right |> E.O.dimap(Js.Float.toString, () => "inf")
j`truncate($nodeToString, $left, $right)` j`truncate($nodeToString, $left, $right)`
} }
let toString = (nodeToString, x) => }
switch x {
| #AlgebraicCombination(op, t1, t2) => Algebraic.format(op, nodeToString(t1), nodeToString(t2))
| #PointwiseCombination(op, t1, t2) => Pointwise.format(op, nodeToString(t1), nodeToString(t2))
| #VerticalScaling(scaleOp, t, scaleBy) =>
Scale.format(scaleOp, nodeToString(t), nodeToString(scaleBy))
| #Normalize(t) => "normalize(k" ++ (nodeToString(t) ++ ")")
| #FloatFromDist(floatFromDistOp, t) => DistToFloat.format(floatFromDistOp, nodeToString(t))
| #Truncate(lc, rc, t) => truncateToString(lc, rc, nodeToString(t))
| #Render(t) => nodeToString(t)
| _ => ""
} // SymbolicDist and RenderedDist are handled in AST.toString.
}

View File

@ -0,0 +1,21 @@
type samplingInputs = {
sampleCount: int,
outputXYPoints: int,
kernelWidth: option<float>,
pointSetDistLength: int,
}
module SamplingInputs = {
type t = {
sampleCount: option<int>,
outputXYPoints: option<int>,
kernelWidth: option<float>,
pointSetDistLength: option<int>,
}
let withDefaults = (t: t): samplingInputs => {
sampleCount: t.sampleCount |> E.O.default(10000),
outputXYPoints: t.outputXYPoints |> E.O.default(10000),
kernelWidth: t.kernelWidth,
pointSetDistLength: t.pointSetDistLength |> E.O.default(10000),
}
}