Merge branch 'Umur-reducer-dev' into reducer-dev

This commit is contained in:
Umur Ozkul 2022-05-02 12:35:58 +02:00
commit 806ff93983
30 changed files with 338 additions and 128 deletions

View File

@ -1,9 +1,9 @@
[![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-components.svg)](https://www.npmjs.com/package/@quri/squiggle-components)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/quantified-uncertainty/squiggle/blob/develop/LICENSE)
# Squiggle Components
# Squiggle components
This package contains all the components for squiggle. These can be used either as a library or hosted as a [storybook](https://storybook.js.org/).
This package contains the react components for squiggle. These can be used either as a library or hosted as a [storybook](https://storybook.js.org/).
# Usage in a `react` project
@ -17,7 +17,10 @@ Add to `App.js`:
```jsx
import { SquiggleEditor } from "@quri/squiggle-components";
<SquiggleEditor initialSquiggleString="x = beta(3, 10); x + 20" />;
<SquiggleEditor
initialSquiggleString="x = beta($alpha, 10); x + $shift"
jsImports={{ alpha: 3, shift: 20 }}
/>;
```
# Build storybook for development
@ -38,9 +41,3 @@ Run a development server
```sh
yarn start
```
And build artefacts for production,
```sh
yarn build # builds storybook app
```

View File

@ -1,13 +1,13 @@
{
"name": "@quri/squiggle-components",
"version": "0.2.15",
"version": "0.2.17",
"license": "MIT",
"dependencies": {
"@quri/squiggle-lang": "^0.2.7",
"@quri/squiggle-lang": "^0.2.8",
"@react-hook/size": "^2.1.2",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-ace": "10.1.0",
"react-ace": "^10.1.0",
"react-dom": "^18.1.0",
"react-vega": "^7.5.0",
"styled-components": "^5.3.5",
@ -36,7 +36,7 @@
"@types/styled-components": "^5.1.24",
"@types/webpack": "^5.28.0",
"cross-env": "^7.0.3",
"react-scripts": "5.0.1",
"react-scripts": "^5.0.1",
"style-loader": "^3.3.1",
"ts-loader": "^9.2.9",
"tsconfig-paths-webpack-plugin": "^3.5.2",
@ -49,11 +49,11 @@
"scripts": {
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
"build": "tsc -b && build-storybook -s public",
"build:package": "tsc -b",
"bundle": "webpack",
"all": "yarn bundle && yarn build",
"lint": "prettier --check .",
"format": "prettier --write ."
"format": "prettier --write .",
"prepack": "yarn bundle && tsc -b"
},
"eslintConfig": {
"extends": [
@ -87,7 +87,6 @@
"@types/react": "17.0.43"
},
"source": "./src/index.ts",
"browser": "dist/bundle.js",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts"
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts"
}

View File

@ -144,8 +144,6 @@ export interface SquiggleChartProps {
diagramStop?: number;
/** If the result is a function, how many points along the function it samples */
diagramCount?: number;
/** variables declared before this expression */
environment?: unknown;
/** When the environment changes */
onChange?(expr: squiggleExpression): void;
/** CSS width of the element */

View File

@ -32,16 +32,14 @@ export interface SquiggleEditorProps {
diagramStop?: number;
/** If the result is a function, how many points along the function it samples */
diagramCount?: number;
/** The environment, other variables that were already declared */
environment?: unknown;
/** when the environment changes. Used again for notebook magic*/
onChange?(expr: squiggleExpression): void;
/** The width of the element */
width: number;
width?: number;
/** Previous variable declarations */
bindings: bindings;
bindings?: bindings;
/** JS Imports */
jsImports: jsImports;
jsImports?: jsImports;
}
const Input = styled.div`
@ -61,7 +59,6 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
diagramStop,
diagramCount,
onChange,
environment,
bindings = defaultBindings,
jsImports = defaultImports,
}: SquiggleEditorProps) => {
@ -87,7 +84,6 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
environment={environment}
onChange={onChange}
bindings={bindings}
jsImports={jsImports}
@ -145,8 +141,6 @@ export interface SquigglePartialProps {
diagramCount?: number;
/** when the environment changes. Used again for notebook magic*/
onChange?(expr: bindings): void;
/** The width of the element */
width: number;
/** Previously declared variables */
bindings?: bindings;
/** Variables imported from js */

View File

@ -3,7 +3,26 @@
# Squiggle language
## Build for development
_An estimation language_
# Use the `npm` package
For instance, in a javascript project, you can
```sh
yarn add @quri/squiggle-lang
```
```js
import { run } from "@quri/squiggle-lang";
run(
"normal(0, 1) * fromSamples([-3,-2,-1,1,2,3,3,3,4,9]"
).value.value.toSparkline().value;
```
**However, for most use cases you'll prefer to use our [library of react components](https://www.npmjs.com/package/@quri/squiggle-components)**, and let your app transitively depend on `@quri/squiggle-lang`.
# Build for development
We assume that you ran `yarn` at the monorepo level.
@ -23,7 +42,7 @@ yarn test
yarn coverage:rescript; o _coverage/index.html # produces coverage report and opens it in browser
```
## Distributing this package or using this package from other monorepo packages
# Distributing this package or using this package from other monorepo packages
As it says in the other `packages/*/README.md`s, building this package is an essential step of building other packages.

View File

@ -7,18 +7,18 @@ module ExpressionT = Reducer_Expression_T
let exampleExpression = eNumber(1.)
let exampleExpressionY = eSymbol("y")
let exampleStatement = eLetStatement("y", eNumber(1.))
let exampleStatementY = eLetStatement("y", eNumber(1.))
let exampleStatementX = eLetStatement("y", eSymbol("x"))
let exampleStatementZ = eLetStatement("z", eSymbol("y"))
// If it is not a mactro then it is not expanded
// If it is not a macro then it is not expanded
testMacro([], exampleExpression, "Ok(1)")
describe("bindStatement", () => {
// A statement is bound by the bindings created by the previous statement
testMacro([], eBindStatement(eBindings([]), exampleStatement), "Ok((:$setBindings {} :y 1))")
testMacro([], eBindStatement(eBindings([]), exampleStatementY), "Ok((:$setBindings {} :y 1))")
// Then it answers the bindings for the next statement when reduced
testMacroEval([], eBindStatement(eBindings([]), exampleStatement), "Ok({y: 1})")
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})")
// Now let's feed a binding to see what happens
testMacro(
[],
@ -30,7 +30,7 @@ describe("bindStatement", () => {
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block
testMacro(
[("z", EvNumber(99.))],
eBindStatementDefault(exampleStatement),
eBindStatementDefault(exampleStatementY),
"Ok((:$setBindings {z: 99} :y 1))",
)
})
@ -41,19 +41,19 @@ describe("bindExpression", () => {
// When an let statement is the end expression then bindings are returned
testMacro(
[],
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatement),
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY),
"Ok((:$exportBindings (:$setBindings {x: 2} :y 1)))",
)
// Now let's reduce that expression
testMacroEval(
[],
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatement),
eBindExpression(eBindings([("x", EvNumber(2.))]), exampleStatementY),
"Ok({x: 2,y: 1})",
)
// When bindings are missing the context is injected. This must be the first and last statement of a block
testMacroEval(
[("z", EvNumber(99.))],
eBindExpressionDefault(exampleStatement),
eBindExpressionDefault(exampleStatementY),
"Ok({y: 1,z: 99})",
)
})
@ -63,22 +63,22 @@ describe("block", () => {
testMacro([], eBlock(list{exampleExpression}), "Ok((:$$bindExpression 1))")
testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)")
// Block with a single statement
testMacro([], eBlock(list{exampleStatement}), "Ok((:$$bindExpression (:$let :y 1)))")
testMacroEval([], eBlock(list{exampleStatement}), "Ok({y: 1})")
testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$bindExpression (:$let :y 1)))")
testMacroEval([], eBlock(list{exampleStatementY}), "Ok({y: 1})")
// Block with a statement and an expression
testMacro(
[],
eBlock(list{exampleStatement, exampleExpressionY}),
eBlock(list{exampleStatementY, exampleExpressionY}),
"Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) :y))",
)
testMacroEval([], eBlock(list{exampleStatement, exampleExpressionY}), "Ok(1)")
testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)")
// Block with a statement and another statement
testMacro(
[],
eBlock(list{exampleStatement, exampleStatementZ}),
eBlock(list{exampleStatementY, exampleStatementZ}),
"Ok((:$$bindExpression (:$$bindStatement (:$let :y 1)) (:$let :z :y)))",
)
testMacroEval([], eBlock(list{exampleStatement, exampleStatementZ}), "Ok({y: 1,z: 1})")
testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok({y: 1,z: 1})")
// Block inside a block
testMacro(
[],

View File

@ -0,0 +1,17 @@
open Jest
open Reducer_TestHelpers
Skip.describe("function trics", () => {
testEvalToBe("f(x,y)=x(y); f(f)", "????")
testEvalToBe("f(x)=x(y); f(f)", "????")
testEvalToBe("f(x)=x; f(f)", "????")
testEvalToBe("f(x,y)=x(y); f(z)", "????")
testEvalToBe("f(x,y)=x(y); f(2)", "????") //prevent js error
testEvalToBe("f(x)=f(y)=2; f(2)", "????") //prevent multiple assignment
testEvalToBe("f(x)=x+1; g(x)=f(x)+1;g(2)", "????") //TODO: f is not found
testEvalToBe("y=2;g(x)=y+1;g(2)", "????") //TODO : y is not found
testEvalToBe("y=2;g(x)=inspect(y)+1", "????") //TODO : 666
testEvalToBe("f(x,y)=x+y; f(1,2,3,4)", "????") //TODO : arity))
testEvalToBe("f(x,y)=x+y; f(1)", "????") //TODO : arity))
})

View File

@ -132,7 +132,8 @@ describe("parse on distribution functions", () => {
testParse(
~skip=true,
"normal(5,2) .- normal(5,1)",
"Ok((:$$block (:dotPow (:normal 5 2) (:normal 5 1))))",
"Ok((:$$block (:dotSubtract (:normal 5 2) (:normal 5 1))))",
// TODO: !!! returns "Ok((:$$block (:dotPow (:normal 5 2) (:normal 5 1))))"
)
testParse(
"normal(5,2) .* normal(5,1)",

View File

@ -1,5 +1,5 @@
import { Distribution } from "../../src/js/index";
import { expectErrorToBeBounded, failDefault } from "./TestHelpers";
import { expectErrorToBeBounded, failDefault, testRun } from "./TestHelpers";
import * as fc from "fast-check";
// Beware: float64Array makes it appear in an infinite loop.
@ -212,3 +212,18 @@ describe("mean is mean", () => {
);
});
});
describe("fromSamples function", () => {
test.skip("gives a mean near the mean of the input", () => {
fc.assert(
fc.property(arrayGen(), (xs_) => {
let xs = Array.from(xs_);
let xsString = xs.toString();
let squiggleString = `x = fromSamples([${xsString}]); mean(x)`;
let squiggleResult = testRun(squiggleString);
let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length;
expect(squiggleResult.value).toBeCloseTo(mean, 4);
})
);
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@quri/squiggle-lang",
"version": "0.2.7",
"version": "0.2.8",
"homepage": "https://squiggle-language.com",
"license": "MIT",
"scripts": {
@ -24,6 +24,7 @@
"format:rescript": "rescript format -all",
"format:prettier": "prettier --write .",
"format": "yarn format:rescript && yarn format:prettier",
"prepack": "yarn build && yarn test && yarn bundle",
"all": "yarn build && yarn bundle && yarn test"
},
"keywords": [
@ -35,22 +36,22 @@
"rescript": "^9.1.4",
"jstat": "^1.9.5",
"pdfast": "^0.2.0",
"mathjs": "10.5.0"
"mathjs": "^10.5.0"
},
"devDependencies": {
"bisect_ppx": "^2.7.1",
"lodash": "4.17.21",
"lodash": "^4.17.21",
"rescript-fast-check": "^1.1.1",
"@glennsl/rescript-jest": "^0.9.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/jest": "^27.4.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"chalk": "^5.0.1",
"codecov": "3.8.3",
"fast-check": "2.25.0",
"codecov": "^3.8.3",
"fast-check": "^2.25.0",
"gentype": "^4.3.0",
"jest": "^27.5.1",
"moduleserve": "0.9.1",
"moduleserve": "^0.9.1",
"nyc": "^15.1.0",
"reanalyze": "^2.19.0",
"ts-jest": "^27.1.4",

View File

@ -8,6 +8,15 @@ import {
externalBindings,
expressionValue,
errorValue,
distributionError,
toPointSet,
continuousShape,
discreteShape,
distributionErrorToString,
internalCode,
mixedShape,
sampleSetDist,
symbolicDist,
} from "../rescript/TypescriptInterface.gen";
export {
makeSampleSetDist,
@ -32,6 +41,53 @@ import { Distribution } from "./distribution";
export { Distribution, squiggleExpression, result, resultMap };
export let defaultSamplingInputs: samplingParams = {
sampleCount: 10000,
xyPointLength: 10000,
};
export type result<a, b> =
| {
tag: "Ok";
value: a;
}
| {
tag: "Error";
value: b;
};
export function resultMap<a, b, c>(
r: result<a, c>,
mapFn: (x: a) => b
): result<b, c> {
if (r.tag === "Ok") {
return { tag: "Ok", value: mapFn(r.value) };
} else {
return r;
}
}
function Ok<a, b>(x: a): result<a, b> {
return { tag: "Ok", value: x };
}
type tagged<a, b> = { tag: a; value: b };
function tag<a, b>(x: a, y: b): tagged<a, b> {
return { tag: x, value: y };
}
export type squiggleExpression =
| tagged<"symbol", string>
| tagged<"string", string>
| tagged<"call", string>
| tagged<"lambda", [string[], internalCode]>
| tagged<"array", squiggleExpression[]>
| tagged<"boolean", boolean>
| tagged<"distribution", Distribution>
| tagged<"number", number>
| tagged<"record", { [key: string]: squiggleExpression }>;
export function run(
squiggleString: string,
bindings?: externalBindings,

View File

@ -159,6 +159,16 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
->GenericDist.toPointSet(~xyPointLength, ~sampleCount, ())
->E.R2.fmap(r => Dist(PointSet(r)))
->OutputLocal.fromResult
| ToDist(Scale(#Logarithm, f)) =>
dist
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~algebraicCombination=#Logarithm, ~f)
->E.R2.fmap(r => Dist(r))
->OutputLocal.fromResult
| ToDist(Scale(#Power, f)) =>
dist
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~algebraicCombination=#Power, ~f)
->E.R2.fmap(r => Dist(r))
->OutputLocal.fromResult
| ToDistCombination(Algebraic(_), _, #Float(_)) => GenDistError(NotYetImplemented)
| ToDistCombination(Algebraic(strategy), arithmeticOperation, #Dist(t2)) =>
dist
@ -194,6 +204,12 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
->GenericDist.mixture(~scaleMultiplyFn=scaleMultiply, ~pointwiseAddFn=pointwiseAdd)
->E.R2.fmap(r => Dist(r))
->OutputLocal.fromResult
| FromSamples(xs) =>
xs
->SampleSetDist.make
->E.R2.errMap(x => DistributionTypes.SampleSetError(x))
->E.R2.fmap(x => x->DistributionTypes.SampleSet->Dist)
->OutputLocal.fromResult
}
}
@ -234,6 +250,7 @@ module Constructors = {
let isNormalized = (~env, dist) => C.isNormalized(dist)->run(~env)->toBoolR
let toPointSet = (~env, dist) => C.toPointSet(dist)->run(~env)->toDistR
let toSampleSet = (~env, dist, n) => C.toSampleSet(dist, n)->run(~env)->toDistR
let fromSamples = (~env, xs) => C.fromSamples(xs)->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

View File

@ -64,6 +64,8 @@ module Constructors: {
@genType
let toSampleSet: (~env: env, genericDist, int) => result<genericDist, error>
@genType
let fromSamples: (~env: env, SampleSetDist.t) => 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>

View File

@ -11,7 +11,7 @@ type error =
| NotYetImplemented
| Unreachable
| DistributionVerticalShiftIsInvalid
| TooFewSamples
| SampleSetError(SampleSetDist.sampleSetError)
| ArgumentError(string)
| OperationError(Operation.Error.t)
| PointSetConversionError(SampleSetDist.pointsetConversionError)
@ -35,7 +35,8 @@ module Error = {
| DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift is Invalid"
| ArgumentError(s) => `Argument Error ${s}`
| LogarithmOfDistributionError(s) => `Logarithm of input error: ${s}`
| TooFewSamples => "Too Few Samples"
| SampleSetError(TooFewSamples) => "Too Few Samples"
| SampleSetError(NonNumericInput(err)) => `Found a non-number in input: ${err}`
| OperationError(err) => Operation.Error.toString(err)
| PointSetConversionError(err) => SampleSetDist.pointsetConversionErrorToString(err)
| SparklineError(err) => PointSetTypes.sparklineErrorToString(err)
@ -47,10 +48,7 @@ module Error = {
let resultStringToResultError: result<'a, string> => result<'a, error> = n =>
n->E.R2.errMap(r => r->fromString)
let sampleErrorToDistErr = (err: SampleSetDist.sampleSetError): error =>
switch err {
| TooFewSamples => TooFewSamples
}
let sampleErrorToDistErr = (err: SampleSetDist.sampleSetError): error => SampleSetError(err)
}
@genType
@ -68,12 +66,19 @@ module DistributionOperation = {
| #Pdf(float)
| #Mean
| #Sample
| #IntegralSum
]
type toScaleFn = [
| #Power
| #Logarithm
]
type toDist =
| Normalize
| ToPointSet
| ToSampleSet(int)
| Scale(toScaleFn, float)
| Truncate(option<float>, option<float>)
| Inspect
@ -99,6 +104,7 @@ module DistributionOperation = {
type genericFunctionCallInfo =
| FromDist(fromDist, genericDist)
| FromFloat(fromDist, float)
| FromSamples(array<float>)
| Mixture(array<(genericDist, float)>)
let distCallToString = (distFunction: fromDist): string =>
@ -113,6 +119,8 @@ module DistributionOperation = {
| ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})`
| ToDist(Truncate(_, _)) => `truncate`
| ToDist(Inspect) => `inspect`
| ToDist(Scale(#Power, r)) => `scalePower(${E.Float.toFixed(r)})`
| ToDist(Scale(#Logarithm, r)) => `scaleLog(${E.Float.toFixed(r)})`
| ToString(ToString) => `toString`
| ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})`
| ToBool(IsNormalized) => `isNormalized`
@ -124,6 +132,7 @@ module DistributionOperation = {
switch d {
| FromDist(f, _) | FromFloat(f, _) => distCallToString(f)
| Mixture(_) => `mixture`
| FromSamples(_) => `fromSamples`
}
}
module Constructors = {
@ -140,8 +149,11 @@ module Constructors = {
let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist)
let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist)
let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist)
let fromSamples = (xs): t => FromSamples(xs)
let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist)
let inspect = (dist): t => FromDist(ToDist(Inspect), dist)
let scalePower = (dist, n): t => FromDist(ToDist(Scale(#Power, n)), dist)
let scaleLogarithm = (dist, n): t => FromDist(ToDist(Scale(#Logarithm, n)), 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(

View File

@ -62,10 +62,13 @@ let isNormalized = (t: t): bool => Js.Math.abs_float(integralEndY(t) -. 1.0) < 1
let toFloatOperation = (
t,
~toPointSetFn: toPointSetFn,
~distToFloatOperation: Operation.distToFloatOperation,
~distToFloatOperation: DistributionTypes.DistributionOperation.toFloat,
) => {
switch distToFloatOperation {
| #IntegralSum => Ok(integralEndY(t))
| (#Pdf(_) | #Cdf(_) | #Inv(_) | #Mean | #Sample) as op => {
let trySymbolicSolution = switch (t: t) {
| Symbolic(r) => SymbolicDist.T.operate(distToFloatOperation, r)->E.R.toOption
| Symbolic(r) => SymbolicDist.T.operate(op, r)->E.R.toOption
| _ => None
}
@ -81,7 +84,9 @@ let toFloatOperation = (
| None =>
switch trySampleSetSolution {
| Some(r) => Ok(r)
| None => toPointSetFn(t)->E.R2.fmap(PointSetDist.operate(distToFloatOperation))
| None => toPointSetFn(t)->E.R2.fmap(PointSetDist.operate(op))
}
}
}
}
}

View File

@ -20,7 +20,7 @@ let isNormalized: t => bool
let toFloatOperation: (
t,
~toPointSetFn: toPointSetFn,
~distToFloatOperation: Operation.distToFloatOperation,
~distToFloatOperation: DistributionTypes.DistributionOperation.toFloat,
) => result<float, error>
@genType

View File

@ -156,8 +156,10 @@ let reduce = (
~integralSumCachesFn: (float, float) => option<float>=(_, _) => None,
fn: (float, float) => result<float, 'e>,
continuousShapes,
): result<t, 'e> =>
continuousShapes |> E.A.R.foldM(combinePointwise(~integralSumCachesFn, fn), empty)
): result<t, 'e> => {
let merge = combinePointwise(~integralSumCachesFn, fn)
continuousShapes |> E.A.R.foldM(merge, empty)
}
let mapYResult = (
~integralSumCacheFn=_ => None,

View File

@ -34,9 +34,10 @@ let lastY = (t: t) => t |> getShape |> XYShape.T.lastY
let combinePointwise = (
~integralSumCachesFn=(_, _) => None,
fn,
t1: PointSetTypes.discreteShape,
t2: PointSetTypes.discreteShape,
): PointSetTypes.discreteShape => {
): result<PointSetTypes.discreteShape, 'e> => {
let combinedIntegralSum = Common.combineIntegralSums(
integralSumCachesFn,
t1.integralSumCache,
@ -49,16 +50,22 @@ let combinePointwise = (
make(
~integralSumCache=combinedIntegralSum,
XYShape.PointwiseCombination.combine(
(a, b) => Ok(a +. b),
fn,
XYShape.XtoY.discreteInterpolator,
t1.xyShape,
t2.xyShape,
)->E.R.toExn("Addition operation should never fail", _),
)
)->Ok
}
let reduce = (~integralSumCachesFn=(_, _) => None, discreteShapes): PointSetTypes.discreteShape =>
discreteShapes |> E.A.fold_left(combinePointwise(~integralSumCachesFn), empty)
let reduce = (
~integralSumCachesFn=(_, _) => None,
fn: (float, float) => result<float, 'e>,
discreteShapes: array<PointSetTypes.discreteShape>,
): result<t, 'e> => {
let merge = combinePointwise(~integralSumCachesFn, fn)
discreteShapes |> E.A.R.foldM(merge, empty)
}
let updateIntegralSumCache = (integralSumCache, t: t): t => {
...t,

View File

@ -316,7 +316,10 @@ let combinePointwise = (
t2: t,
): result<t, 'e> => {
let reducedDiscrete =
[t1, t2] |> E.A.fmap(toDiscrete) |> E.A.O.concatSomes |> Discrete.reduce(~integralSumCachesFn)
[t1, t2]
|> E.A.fmap(toDiscrete)
|> E.A.O.concatSomes
|> Discrete.reduce(~integralSumCachesFn, fn)
let reducedContinuous =
[t1, t2]
@ -335,11 +338,11 @@ let combinePointwise = (
t1.integralCache,
t2.integralCache,
)
reducedContinuous->E.R2.fmap(continuous =>
E.R.merge(reducedContinuous, reducedDiscrete)->E.R2.fmap(((continuous, discrete)) =>
make(
~integralSumCache=combinedIntegralSum,
~integralCache=combinedIntegral,
~discrete=reducedDiscrete,
~discrete,
~continuous,
)
)

View File

@ -84,7 +84,12 @@ let combinePointwise = (
m2,
)->E.R2.fmap(x => PointSetTypes.Continuous(x))
| (Discrete(m1), Discrete(m2)) =>
Ok(PointSetTypes.Discrete(Discrete.combinePointwise(~integralSumCachesFn, m1, m2)))
Discrete.combinePointwise(
~integralSumCachesFn,
fn,
m1,
m2,
)->E.R2.fmap(x => PointSetTypes.Discrete(x))
| (m1, m2) =>
Mixed.combinePointwise(
~integralSumCachesFn,

View File

@ -1,11 +1,12 @@
@genType
module Error = {
@genType
type sampleSetError = TooFewSamples
type sampleSetError = TooFewSamples | NonNumericInput(string)
let sampleSetErrorToString = (err: sampleSetError): string =>
switch err {
| TooFewSamples => "Too few samples when constructing sample set"
| NonNumericInput(err) => `Found a non-number in input: ${err}`
}
@genType

View File

@ -1,27 +1,30 @@
//The math here was taken from https://github.com/jasondavies/science.js/blob/master/src/stats/SampleSetDist_Bandwidth.js
let {iqr_percentile, nrd0_lo_denominator, one, nrd0_coef, nrd_coef, nrd_fractionalPower} = module(
MagicNumbers.SampleSetBandwidth
)
let len = x => E.A.length(x) |> float_of_int
let iqr = x => Jstat.percentile(x, 0.75, true) -. Jstat.percentile(x, 0.25, true)
let iqr = x =>
Jstat.percentile(x, iqr_percentile, true) -. Jstat.percentile(x, 1.0 -. iqr_percentile, true)
// Silverman, B. W. (1986) Density Estimation. London: Chapman and Hall.
let nrd0 = x => {
let hi = Js_math.sqrt(Jstat.variance(x))
let lo = Js_math.minMany_float([hi, iqr(x) /. 1.34])
let lo = Js_math.minMany_float([hi, iqr(x) /. nrd0_lo_denominator])
let e = Js_math.abs_float(x[1])
let lo' = switch (lo, hi, e) {
| (lo, _, _) if !Js.Float.isNaN(lo) => lo
| (_, hi, _) if !Js.Float.isNaN(hi) => hi
| (_, _, e) if !Js.Float.isNaN(e) => e
| _ => 1.0
| _ => one
}
0.9 *. lo' *. Js.Math.pow_float(~base=len(x), ~exp=-0.2)
nrd0_coef *. lo' *. Js.Math.pow_float(~base=len(x), ~exp=nrd_fractionalPower)
}
// Scott, D. W. (1992) Multivariate Density Estimation: Theory, Practice, and Visualization. Wiley.
let nrd = x => {
let h = iqr(x) /. 1.34
1.06 *.
let h = iqr(x) /. nrd0_lo_denominator
nrd_coef *.
Js.Math.min_float(Js.Math.sqrt(Jstat.variance(x)), h) *.
Js.Math.pow_float(~base=len(x), ~exp=-1.0 /. 5.0)
Js.Math.pow_float(~base=len(x), ~exp=nrd_fractionalPower)
}

View File

@ -35,3 +35,16 @@ module ToPointSet = {
*/
let minDiscreteToKeep = samples => max(20, E.A.length(samples) / 50)
}
module SampleSetBandwidth = {
// Silverman, B. W. (1986) Density Estimation. London: Chapman and Hall.
// Scott, D. W. (1992) Multivariate Density Estimation: Theory, Practice, and Visualization. Wiley.
let iqr_percentile = 0.75
let iqr_percentile_complement = 1.0 -. iqr_percentile
let nrd0_lo_denominator = 1.34
let one = 1.0
let nrd0_coef = 0.9
let nrd_coef = 1.06
let nrd_fractionalPower = -0.2
}

View File

@ -26,10 +26,10 @@ let eFunction = (fName: string, lispArgs: list<expression>): expression => {
}
let eLambda = (parameters: array<string>, context, expr) =>
BExpressionValue.EvLambda(
parameters,
context,
expr->castExpressionToInternalCode,
BExpressionValue.EvLambda({
parameters: parameters,
context: context,
body: expr->castExpressionToInternalCode}
)->BExpressionT.EValue
let eNumber = aNumber => aNumber->BExpressionValue.EvNumber->BExpressionT.EValue

View File

@ -30,6 +30,6 @@ let applyParametersToLambda = (
reducer(newExpression, bindings, environment)
}
let doLambdaCall = ((parameters, context, internal), args, environment, reducer) => {
applyParametersToLambda(internal, parameters, args, context, environment, reducer)
let doLambdaCall = (lambdaValue: ExpressionValue.lambdaValue, args, environment, reducer) => {
applyParametersToLambda(lambdaValue.body, lambdaValue.parameters, args, lambdaValue.context, environment, reducer)
}

View File

@ -15,15 +15,19 @@ type rec expressionValue =
| EvBool(bool)
| EvCall(string) // External function call
| EvDistribution(DistributionTypes.genericDist)
| EvLambda((array<string>, record, internalCode))
| EvLambda(lambdaValue)
| EvNumber(float)
| EvRecord(record)
| EvString(string)
| EvSymbol(string)
and record = Js.Dict.t<expressionValue>
and externalBindings = record
and lambdaValue = {
parameters: array<string>,
context: externalBindings,
body: internalCode,
}
@genType
type externalBindings = record
@genType
let defaultExternalBindings: externalBindings = Js.Dict.empty()
@ -41,8 +45,8 @@ let rec toString = aValue =>
}
| EvBool(aBool) => Js.String.make(aBool)
| EvCall(fName) => `:${fName}`
| EvLambda((parameters, _context, _internalCode)) =>
`lambda(${Js.Array2.toString(parameters)}=>internal)`
| EvLambda(lambdaValue) =>
`lambda(${Js.Array2.toString(lambdaValue.parameters)}=>internal code)`
| EvNumber(aNumber) => Js.String.make(aNumber)
| EvString(aString) => `'${aString}'`
| EvSymbol(aString) => `:${aString}`
@ -65,7 +69,7 @@ let toStringWithType = aValue =>
| EvBool(_) => `Bool::${toString(aValue)}`
| EvCall(_) => `Call::${toString(aValue)}`
| EvDistribution(_) => `Distribution::${toString(aValue)}`
| EvLambda((_parameters, _context, _internalCode)) => `Lambda::${toString(aValue)}`
| EvLambda(_) => `Lambda::${toString(aValue)}`
| EvNumber(_) => `Number::${toString(aValue)}`
| EvRecord(_) => `Record::${toString(aValue)}`
| EvString(_) => `String::${toString(aValue)}`

View File

@ -130,6 +130,7 @@ module Helpers = {
| Error(err) => GenDistError(ArgumentError(err))
}
}
| Some(EvNumber(_))
| Some(EvDistribution(_)) =>
switch parseDistributionArray(args) {
| Ok(distributions) => mixtureWithDefaultWeights(distributions)
@ -194,6 +195,7 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall, _environment)
->SymbolicConstructors.symbolicResultToOutput
| ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist)
| ("mean", [EvDistribution(dist)]) => Helpers.toFloatFn(#Mean, dist)
| ("integralSum", [EvDistribution(dist)]) => Helpers.toFloatFn(#IntegralSum, dist)
| ("toString", [EvDistribution(dist)]) => Helpers.toStringFn(ToString, dist)
| ("toSparkline", [EvDistribution(dist)]) => Helpers.toStringFn(ToSparkline(20), dist)
| ("toSparkline", [EvDistribution(dist), EvNumber(n)]) =>
@ -209,6 +211,15 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall, _environment)
| ("normalize", [EvDistribution(dist)]) => Helpers.toDistFn(Normalize, dist)
| ("isNormalized", [EvDistribution(dist)]) => Helpers.toBoolFn(IsNormalized, dist)
| ("toPointSet", [EvDistribution(dist)]) => Helpers.toDistFn(ToPointSet, dist)
| ("scaleLog", [EvDistribution(dist)]) =>
Helpers.toDistFn(Scale(#Logarithm, MagicNumbers.Math.e), dist)
| ("scaleLog10", [EvDistribution(dist)]) => Helpers.toDistFn(Scale(#Logarithm, 10.0), dist)
| ("scaleLog", [EvDistribution(dist), EvNumber(float)]) =>
Helpers.toDistFn(Scale(#Logarithm, float), dist)
| ("scalePow", [EvDistribution(dist), EvNumber(float)]) =>
Helpers.toDistFn(Scale(#Power, float), dist)
| ("scaleExp", [EvDistribution(dist)]) =>
Helpers.toDistFn(Scale(#Power, MagicNumbers.Math.e), dist)
| ("cdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Cdf(float), dist)
| ("pdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Pdf(float), dist)
| ("inv", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Inv(float), dist)
@ -216,6 +227,14 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall, _environment)
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist)
| ("toSampleSet", [EvDistribution(dist)]) =>
Helpers.toDistFn(ToSampleSet(MagicNumbers.Environment.defaultSampleCount), dist)
| ("fromSamples", [EvArray(inputArray)]) => {
let _wrapInputErrors = x => SampleSetDist.NonNumericInput(x)
let parsedArray = Helpers.parseNumberArray(inputArray)->E.R2.errMap(_wrapInputErrors)
switch parsedArray {
| Ok(array) => runGenericOperation(FromSamples(array))
| Error(e) => GenDistError(SampleSetError(e))
}->Some
}
| ("inspect", [EvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist)
| ("truncateLeft", [EvDistribution(dist), EvNumber(float)]) =>
Helpers.toDistFn(Truncate(Some(float), None), dist)

View File

@ -289,6 +289,13 @@ module R = {
| Ok(r) => r->Ok
| Error(x) => x->f->Error
}
//I'm not sure what to call this.
let unify = (a: result<'a, 'b>, c: 'b => 'a): 'a =>
switch a {
| Ok(x) => x
| Error(x) => c(x)
}
}
module R2 = {
@ -307,6 +314,8 @@ module R2 = {
| Ok(x) => x->Ok
| Error(x) => x->f->Error
}
let toExn = (a, b) => R.toExn(b, a)
}
let safe_fn_of_string = (fn, s: string): option<'a> =>

View File

@ -98,6 +98,19 @@ bound `a`, mode `b` and upper bound `c`.
Squiggle, when the context is right, automatically casts a float to a constant distribution.
## `fromSamples`
The last distribution constructor takes an array of samples and constructs a sample set distribution.
<SquiggleEditor initialSquiggleString="fromSamples([1,2,3,4,6,5,5,5])" />
#### Validity
For `fromSamples(xs)`,
- `xs.length > 5`
- Strictly every element of `xs` must be a number.
## Operating on distributions
Here are the ways we combine distributions.
@ -315,6 +328,16 @@ Or `PointSet` format
<SquiggleEditor initialSquiggleString="toPointSet(normal(5, 10))" />
### `toSampleSet` has two signatures
Above, we saw the unary `toSampleSet`, which uses an internal hardcoded number of samples. If you'd like to provide the number of samples, it has a binary signature as well (floored)
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1, 100.1)" />
#### Validity
- Second argument to `toSampleSet` must be a number.
## Normalization
Some distribution operations (like horizontal shift) return an unnormalized distriibution.
@ -333,18 +356,6 @@ We provide a predicate `isNormalized`, for when we have simple control flow
- Input to `isNormalized` must be a dist
## Convert any distribution to a sample set distribution
`toSampleSet` has two signatures
It is unary when you use an internal hardcoded number of samples
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1)" />
And binary when you provide a number of samples (floored)
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1, 100)" />
## `inspect`
You may like to debug by right clicking your browser and using the _inspect_ functionality on the webpage, and viewing the _console_ tab. Then, wrap your squiggle output with `inspect` to log an internal representation.

View File

@ -6498,7 +6498,7 @@ coa@^2.0.2:
chalk "^2.4.1"
q "^1.1.2"
codecov@3.8.3:
codecov@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.8.3.tgz#9c3e364b8a700c597346ae98418d09880a3fdbe7"
integrity sha512-Y8Hw+V3HgR7V71xWH2vQ9lyS358CbGCldWlJFR0JirqoGtOoas3R3/OclRTvgUYFK29mmJICDPauVKmpqbwhOA==
@ -8607,7 +8607,7 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
fast-check@2.25.0, fast-check@^2.17.0:
fast-check@^2.17.0, fast-check@^2.25.0:
version "2.25.0"
resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.25.0.tgz#5146601851bf3be0953bd17eb2b7d547936c6561"
integrity sha512-wRUT2KD2lAmT75WNIJIHECawoUUMHM0I5jrlLXGtGeqmPL8jl/EldUDjY1VCp6fDY8yflyfUeIOsOBrIbIiArg==
@ -11627,7 +11627,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -11751,7 +11751,7 @@ markdown-to-jsx@^7.1.3:
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz#a5f22102fb12241c8cea1ca6a4050bb76b23a25d"
integrity sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==
mathjs@10.5.0:
mathjs@^10.5.0:
version "10.5.0"
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-10.5.0.tgz#f81d0518fe7b4b2a0b85e1125b8ecfc364fb0292"
integrity sha512-gRnSY9psN9zgiB2QV9F4XbuX5hwjxY5Ou7qoTFWDbn2vZ3UEs+sjfK/SRg2WP30TNfZWpwlGdp8H1knFJnpFdA==
@ -12120,7 +12120,7 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moduleserve@0.9.1:
moduleserve@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/moduleserve/-/moduleserve-0.9.1.tgz#11bad4337ea248d7eaf10d2c7f8649a8c3b9c1f8"
integrity sha512-WF2BeGnM2Ko7bdICgJO+Ibu+ZD33ExJHzOzTDsCUzfZnvnFfheEIYBTWyIqSRU0tXh4UTQ1krDOCglFTJPBMow==
@ -14439,7 +14439,7 @@ react-ace@10.0.0:
lodash.isequal "^4.5.0"
prop-types "^15.7.2"
react-ace@10.1.0:
react-ace@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-10.1.0.tgz#d348eac2b16475231779070b6cd16768deed565f"
integrity sha512-VkvUjZNhdYTuKOKQpMIZi7uzZZVgzCjM7cLYu6F64V0mejY8a2XTyPUIMszC6A4trbeMIHbK5fYFcT/wkP/8VA==
@ -14693,7 +14693,7 @@ react-router@6.3.0, react-router@^6.0.0:
dependencies:
history "^5.2.0"
react-scripts@5.0.1:
react-scripts@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"
integrity sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==