Merge branch 'staging' into reducer-dev

This commit is contained in:
Umur Ozkul 2022-04-08 11:09:46 +02:00
commit 1d550353c9
98 changed files with 19976 additions and 34253 deletions

View File

@ -16,17 +16,17 @@ jobs:
name: Check if the changes are about squiggle-lang src files name: Check if the changes are about squiggle-lang src files
uses: fkirc/skip-duplicate-actions@master uses: fkirc/skip-duplicate-actions@master
with: with:
paths: '["packages/squiggle-lang/*"]' paths: '["packages/squiggle-lang/**"]'
- id: skip_components_check - id: skip_components_check
name: Check if the changes are about components src files name: Check if the changes are about components src files
uses: fkirc/skip-duplicate-actions@master uses: fkirc/skip-duplicate-actions@master
with: with:
paths: '["packages/components/*"]' paths: '["packages/components/**"]'
- id: skip_website_check - id: skip_website_check
name: Check if the changes are about website src files name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@master uses: fkirc/skip-duplicate-actions@master
with: with:
paths: '["packages/website/*"]' paths: '["packages/website/**"]'
lang-build-test: lang-build-test:
name: Language build and test name: Language build and test

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ yarn-error.log
.parcel-cache .parcel-cache
.DS_Store .DS_Store
**/.sync.ffs_db **/.sync.ffs_db
.direnv

View File

@ -8,7 +8,7 @@
"packages/*" "packages/*"
], ],
"resolutions": { "resolutions": {
"@types/react": "17.0.39" "@types/react": "^17.0.43"
}, },
"packageManager": "yarn@1.22.17" "packageManager": "yarn@1.22.17"
} }

View File

@ -4,7 +4,7 @@ const custom = require('../webpack.config.js');
module.exports = { module.exports = {
webpackFinal: async (config) => { webpackFinal: async (config) => {
config.resolve.alias = custom.resolve.alias; config.resolve.alias = custom.resolve.alias;
return { ...config, module: { ...config.module, rules: config.module.rules.concat(custom.module.rules) } }; return { ...config, module: { ...config.module, rules: config.module.rules.concat(custom.module.rules.filter(x => x.loader === "ts-loader")) } };
}, },
"stories": [ "stories": [
"../src/**/*.stories.mdx", "../src/**/*.stories.mdx",

View File

@ -6,4 +6,4 @@ export const parameters = {
date: /Date$/, date: /Date$/,
}, },
}, },
} }

View File

@ -1,25 +1,29 @@
# 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 all the components for squiggle. These can be used either as a library or hosted as a [storybook](https://storybook.js.org/).
# Build for development # Build for development
We assume that you had run `yarn` at monorepo level, installing dependencies.
You need to _prepare_ by building and bundling `squiggle-lang` We assume that you had run `yarn` at monorepo level, installing dependencies.
``` sh
You need to _prepare_ by building and bundling `squiggle-lang`
```sh
cd ../squiggle-lang cd ../squiggle-lang
yarn build yarn build
``` ```
If you've otherwise done this recently you can skip those. If you've otherwise done this recently you can skip those.
Run a development server Run a development server
``` sh ```sh
yarn start yarn start
``` ```
And build artefacts for production, And build artefacts for production,
``` sh ```sh
yarn bundle # builds components library yarn bundle # builds components library
yarn build # builds storybook app yarn build # builds storybook app
``` ```

View File

@ -1,6 +1,6 @@
{ {
"name": "@quri/squiggle-components", "name": "@quri/squiggle-components",
"version": "0.1.6", "version": "0.1.8",
"dependencies": { "dependencies": {
"@quri/squiggle-lang": "0.2.2", "@quri/squiggle-lang": "0.2.2",
"@testing-library/jest-dom": "^5.16.3", "@testing-library/jest-dom": "^5.16.3",
@ -9,14 +9,16 @@
"@types/jest": "^27.4.0", "@types/jest": "^27.4.0",
"@types/lodash": "^4.14.178", "@types/lodash": "^4.14.178",
"@types/node": "^17.0.16", "@types/node": "^17.0.16",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14", "@types/react-dom": "^17.0.14",
"antd": "^4.19.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^17.0.2", "react": "^17.0.2",
"react-ace": "^9.5.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"react-vega": "^7.4.4", "react-vega": "^7.4.4",
"styled-components": "^5.3.5",
"tsconfig-paths-webpack-plugin": "^3.5.2", "tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.3", "typescript": "^4.6.3",
"vega": "^5.21.0", "vega": "^5.21.0",
@ -27,7 +29,7 @@
}, },
"scripts": { "scripts": {
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public", "start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
"build": "build-storybook -s public", "build": "tsc -b && build-storybook -s public",
"bundle": "webpack", "bundle": "webpack",
"all": "yarn bundle && yarn build" "all": "yarn bundle && yarn build"
}, },
@ -68,9 +70,10 @@
"@storybook/node-logger": "^6.4.18", "@storybook/node-logger": "^6.4.18",
"@storybook/preset-create-react-app": "^4.0.0", "@storybook/preset-create-react-app": "^4.0.0",
"@storybook/react": "^6.4.18", "@storybook/react": "^6.4.18",
"@types/webpack": "^5.28.0", "@types/styled-components": "^5.1.24",
"css-loader": "^6.7.1",
"prettier": "^2.6.0", "prettier": "^2.6.0",
"react-codejar": "^1.1.2", "style-loader": "^3.3.1",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"webpack": "^5.70.0", "webpack": "^5.70.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",

View File

@ -0,0 +1,45 @@
import _ from "lodash";
import React, { FC } from "react";
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-golang";
import "ace-builds/src-noconflict/theme-github";
interface CodeEditorProps {
value: string;
onChange: (value: string) => void;
oneLine?: boolean;
width?: number;
}
export let CodeEditor: FC<CodeEditorProps> = ({
value,
onChange,
oneLine = false,
}: CodeEditorProps) => {
let lineCount = value.split("\n").length;
let id = _.uniqueId();
return (
<AceEditor
value={value}
mode="golang"
theme="github"
width={"100%"}
minLines={oneLine ? lineCount : 15}
maxLines={oneLine ? lineCount : 15}
showGutter={false}
highlightActiveLine={false}
showPrintMargin={false}
onChange={onChange}
name={id}
editorProps={{
$blockScrolling: true,
}}
setOptions={{
enableBasicAutocompletion: false,
enableLiveAutocompletion: false,
}}
/>
);
};
export default CodeEditor;

View File

@ -0,0 +1,98 @@
import * as React from "react";
import _ from "lodash";
const orderOfMagnitudeNum = (n: number) => {
return Math.pow(10, n);
};
// 105 -> 3
const orderOfMagnitude = (n: number) => {
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
};
function withXSigFigs(number: number, sigFigs: number) {
const withPrecision = number.toPrecision(sigFigs);
const formatted = Number(withPrecision);
return `${formatted}`;
}
class NumberShowerBuilder {
number: number;
precision: number;
constructor(number: number, precision = 2) {
this.number = number;
this.precision = precision;
}
convert() {
const number = Math.abs(this.number);
const response = this.evaluate(number);
if (this.number < 0) {
response.value = "-" + response.value;
}
return response;
}
metricSystem(number: number, order: number) {
const newNumber = number / orderOfMagnitudeNum(order);
const precision = this.precision;
return `${withXSigFigs(newNumber, precision)}`;
}
evaluate(number: number) {
if (number === 0) {
return { value: this.metricSystem(0, 0) };
}
const order = orderOfMagnitude(number);
if (order < -2) {
return { value: this.metricSystem(number, order), power: order };
} else if (order < 4) {
return { value: this.metricSystem(number, 0) };
} else if (order < 6) {
return { value: this.metricSystem(number, 3), symbol: "K" };
} else if (order < 9) {
return { value: this.metricSystem(number, 6), symbol: "M" };
} else if (order < 12) {
return { value: this.metricSystem(number, 9), symbol: "B" };
} else if (order < 15) {
return { value: this.metricSystem(number, 12), symbol: "T" };
} else {
return { value: this.metricSystem(number, order), power: order };
}
}
}
export function numberShow(number: number, precision = 2) {
const ns = new NumberShowerBuilder(number, precision);
return ns.convert();
}
export interface NumberShowerProps {
number: number;
precision?: number
}
export let NumberShower: React.FC<NumberShowerProps> = ({
number,
precision = 2
}: NumberShowerProps) => {
let numberWithPresentation = numberShow(number, precision);
return (
<span>
{numberWithPresentation.value}
{numberWithPresentation.symbol}
{numberWithPresentation.power ? (
<span>
{"\u00b710"}
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
{numberWithPresentation.power}
</span>
</span>
) : (
<></>
)}
</span>
);
}

View File

@ -11,18 +11,20 @@ import type {
import { createClassFromSpec } from "react-vega"; import { createClassFromSpec } from "react-vega";
import * as chartSpecification from "./spec-distributions.json"; import * as chartSpecification from "./spec-distributions.json";
import * as percentilesSpec from "./spec-percentiles.json"; import * as percentilesSpec from "./spec-percentiles.json";
import { NumberShower } from "./NumberShower";
import styled from "styled-components";
let SquiggleVegaChart = createClassFromSpec({ let SquiggleVegaChart = createClassFromSpec({
spec: chartSpecification as Spec spec: chartSpecification as Spec,
}); });
let SquigglePercentilesChart = createClassFromSpec({ let SquigglePercentilesChart = createClassFromSpec({
spec: percentilesSpec as Spec spec: percentilesSpec as Spec,
}); });
export interface SquiggleChartProps { export interface SquiggleChartProps {
/** The input string for squiggle */ /** The input string for squiggle */
squiggleString: string; squiggleString?: string;
/** If the output requires monte carlo sampling, the amount of samples */ /** If the output requires monte carlo sampling, the amount of samples */
sampleCount?: number; sampleCount?: number;
@ -40,24 +42,58 @@ export interface SquiggleChartProps {
environment?: exportEnv; environment?: exportEnv;
/** When the environment changes */ /** When the environment changes */
onEnvChange?(env: exportEnv): void; onEnvChange?(env: exportEnv): void;
/** CSS width of the element */
width?: number;
height?: number;
} }
export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => { const Error = styled.div`
border: 1px solid #792e2e;
background: #eee2e2;
padding: 0.4em 0.8em;
`;
const ShowError: React.FC<{ heading: string; children: React.ReactNode }> = ({
heading = "Error",
children,
}) => {
return (
<Error>
<h3>{heading}</h3>
{children}
</Error>
);
};
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
squiggleString = "",
sampleCount = 1000,
outputXYPoints = 1000,
kernelWidth,
pointDistLength = 1000,
diagramStart = 0,
diagramStop = 10,
diagramCount = 20,
environment = [],
onEnvChange = () => {},
width = 500,
height = 60,
}: SquiggleChartProps) => {
let samplingInputs: SamplingInputs = { let samplingInputs: SamplingInputs = {
sampleCount: props.sampleCount, sampleCount: sampleCount,
outputXYPoints: props.outputXYPoints, outputXYPoints: outputXYPoints,
kernelWidth: props.kernelWidth, kernelWidth: kernelWidth,
pointDistLength: props.pointDistLength, pointDistLength: pointDistLength,
}; };
let result = run(props.squiggleString, samplingInputs, props.environment); let result = run(squiggleString, samplingInputs, environment);
if (result.tag === "Ok") { if (result.tag === "Ok") {
let environment = result.value.environment; let environment = result.value.environment;
let exports = result.value.exports; let exports = result.value.exports;
if (props.onEnvChange) props.onEnvChange(environment); onEnvChange(environment);
let chartResults = exports.map((chartResult: exportDistribution) => { let chartResults = exports.map((chartResult: exportDistribution) => {
if (chartResult["NAME"] === "Float") { if (chartResult["NAME"] === "Float") {
return <MakeNumberShower precision={3} number={chartResult["VAL"]} />; return <NumberShower precision={3} number={chartResult["VAL"]} />;
} else if (chartResult["NAME"] === "DistPlus") { } else if (chartResult["NAME"] === "DistPlus") {
let shape = chartResult.VAL.pointSetDist; let shape = chartResult.VAL.pointSetDist;
if (shape.tag === "Continuous") { if (shape.tag === "Continuous") {
@ -74,7 +110,14 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
y: y, y: y,
})); }));
return <SquiggleVegaChart data={{ con: values }} actions={false}/>; return (
<SquiggleVegaChart
width={width}
height={height}
data={{ con: values }}
actions={false}
/>
);
} else if (shape.tag === "Discrete") { } else if (shape.tag === "Discrete") {
let xyShape = shape.value.xyShape; let xyShape = shape.value.xyShape;
let totalY = xyShape.ys.reduce((a, b) => a + b); let totalY = xyShape.ys.reduce((a, b) => a + b);
@ -89,7 +132,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
y: y, y: y,
})); }));
return <SquiggleVegaChart data={{ dis: values }} actions={false}/>; return <SquiggleVegaChart data={{ dis: values }} actions={false} />;
} else if (shape.tag === "Mixed") { } else if (shape.tag === "Mixed") {
let discreteShape = shape.value.discrete.xyShape; let discreteShape = shape.value.discrete.xyShape;
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b); let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
@ -123,10 +166,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
let total = 0; let total = 0;
let cdf = sortedPoints.map((point: labeledPoint) => { let cdf = sortedPoints.map((point: labeledPoint) => {
if (point.type == "discrete") { if (point.type === "discrete") {
total += point.y; total += point.y;
return total; return total;
} else if (point.type == "continuous") { } else if (point.type === "continuous") {
total += (point.y / totalY) * totalContinuous; total += (point.y / totalY) * totalContinuous;
return total; return total;
} }
@ -147,10 +190,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
}) })
); );
let continuousValues = cdfLabeledPoint.filter( let continuousValues = cdfLabeledPoint.filter(
(x) => x.type == "continuous" (x) => x.type === "continuous"
); );
let discreteValues = cdfLabeledPoint.filter( let discreteValues = cdfLabeledPoint.filter(
(x) => x.type == "discrete" (x) => x.type === "discrete"
); );
return ( return (
@ -162,14 +205,14 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
} }
} else if (chartResult.NAME === "Function") { } else if (chartResult.NAME === "Function") {
// We are looking at a function. In this case, we draw a Percentiles chart // We are looking at a function. In this case, we draw a Percentiles chart
let start = props.diagramStart ? props.diagramStart : 0; let start = diagramStart;
let stop = props.diagramStop ? props.diagramStop : 10; let stop = diagramStop;
let count = props.diagramCount ? props.diagramCount : 100; let count = diagramCount;
let step = (stop - start) / count; let step = (stop - start) / count;
let data = _.range(start, stop, step).map((x) => { let data = _.range(start, stop, step).map((x) => {
if (chartResult.NAME == "Function") { if (chartResult.NAME === "Function") {
let result = chartResult.VAL(x); let result = chartResult.VAL(x);
if (result.tag == "Ok") { if (result.tag === "Ok") {
let percentileArray = [ let percentileArray = [
0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95,
0.99, 0.99,
@ -196,22 +239,28 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
return null; return null;
} }
}); });
return <SquigglePercentilesChart return (
data={{ facet: data.filter(x => x !== null) }} <SquigglePercentilesChart
data={{ facet: data.filter((x) => x !== null) }}
actions={false} actions={false}
/>; />
);
} }
}); });
return <>{chartResults}</>; return <>{chartResults}</>;
} else if (result.tag == "Error") { } else if (result.tag === "Error") {
// At this point, we came across an error. What was our error? // At this point, we came across an error. What was our error?
return <p>{"Error parsing Squiggle: " + result.value}</p>; return (
<ShowError heading={"Parse Error"}>
{result.value}
</ShowError>
);
} }
return <p>{"Invalid Response"}</p>; return <p>{"Invalid Response"}</p>;
}; };
function getPercentiles(percentiles: number[], t: DistPlus) { function getPercentiles(percentiles: number[], t: DistPlus) {
if (t.pointSetDist.tag == "Discrete") { if (t.pointSetDist.tag === "Discrete") {
let total = 0; let total = 0;
let maxX = _.max(t.pointSetDist.value.xyShape.xs); let maxX = _.max(t.pointSetDist.value.xyShape.xs);
let bounds = percentiles.map((_) => maxX); let bounds = percentiles.map((_) => maxX);
@ -221,14 +270,14 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
(x, y) => { (x, y) => {
total += y; total += y;
percentiles.forEach((v, i) => { percentiles.forEach((v, i) => {
if (total > v && bounds[i] == maxX) { if (total > v && bounds[i] === maxX) {
bounds[i] = x; bounds[i] = x;
} }
}); });
} }
); );
return bounds; return bounds;
} else if (t.pointSetDist.tag == "Continuous") { } else if (t.pointSetDist.tag === "Continuous") {
let total = 0; let total = 0;
let maxX = _.max(t.pointSetDist.value.xyShape.xs); let maxX = _.max(t.pointSetDist.value.xyShape.xs);
let totalY = _.sum(t.pointSetDist.value.xyShape.ys); let totalY = _.sum(t.pointSetDist.value.xyShape.ys);
@ -239,14 +288,14 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
(x, y) => { (x, y) => {
total += y / totalY; total += y / totalY;
percentiles.forEach((v, i) => { percentiles.forEach((v, i) => {
if (total > v && bounds[i] == maxX) { if (total > v && bounds[i] === maxX) {
bounds[i] = x; bounds[i] = x;
} }
}); });
} }
); );
return bounds; return bounds;
} else if (t.pointSetDist.tag == "Mixed") { } else if (t.pointSetDist.tag === "Mixed") {
let discreteShape = t.pointSetDist.value.discrete.xyShape; let discreteShape = t.pointSetDist.value.discrete.xyShape;
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b); let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
@ -280,13 +329,13 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
let maxX = _.max(sortedPoints.map((x) => x.x)); let maxX = _.max(sortedPoints.map((x) => x.x));
let bounds = percentiles.map((_) => maxX); let bounds = percentiles.map((_) => maxX);
sortedPoints.map((point: labeledPoint) => { sortedPoints.map((point: labeledPoint) => {
if (point.type == "discrete") { if (point.type === "discrete") {
total += point.y; total += point.y;
} else if (point.type == "continuous") { } else if (point.type === "continuous") {
total += (point.y / totalY) * totalContinuous; total += (point.y / totalY) * totalContinuous;
} }
percentiles.forEach((v, i) => { percentiles.forEach((v, i) => {
if (total > v && bounds[i] == maxX) { if (total > v && bounds[i] === maxX) {
bounds[i] = total; bounds[i] = total;
} }
}); });
@ -295,91 +344,3 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
return bounds; return bounds;
} }
} }
function MakeNumberShower(props: { number: number; precision: number }) {
let numberWithPresentation = numberShow(props.number, props.precision);
return (
<span>
{numberWithPresentation.value}
{numberWithPresentation.symbol}
{numberWithPresentation.power ? (
<span>
{"\u00b710"}
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
{numberWithPresentation.power}
</span>
</span>
) : (
<></>
)}
</span>
);
}
const orderOfMagnitudeNum = (n: number) => {
return Math.pow(10, n);
};
// 105 -> 3
const orderOfMagnitude = (n: number) => {
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
};
function withXSigFigs(number: number, sigFigs: number) {
const withPrecision = number.toPrecision(sigFigs);
const formatted = Number(withPrecision);
return `${formatted}`;
}
class NumberShower {
number: number;
precision: number;
constructor(number: number, precision = 2) {
this.number = number;
this.precision = precision;
}
convert() {
const number = Math.abs(this.number);
const response = this.evaluate(number);
if (this.number < 0) {
response.value = "-" + response.value;
}
return response;
}
metricSystem(number: number, order: number) {
const newNumber = number / orderOfMagnitudeNum(order);
const precision = this.precision;
return `${withXSigFigs(newNumber, precision)}`;
}
evaluate(number: number) {
if (number === 0) {
return { value: this.metricSystem(0, 0) };
}
const order = orderOfMagnitude(number);
if (order < -2) {
return { value: this.metricSystem(number, order), power: order };
} else if (order < 4) {
return { value: this.metricSystem(number, 0) };
} else if (order < 6) {
return { value: this.metricSystem(number, 3), symbol: "K" };
} else if (order < 9) {
return { value: this.metricSystem(number, 6), symbol: "M" };
} else if (order < 12) {
return { value: this.metricSystem(number, 9), symbol: "B" };
} else if (order < 15) {
return { value: this.metricSystem(number, 12), symbol: "T" };
} else {
return { value: this.metricSystem(number, order), power: order };
}
}
}
export function numberShow(number: number, precision = 2) {
const ns = new NumberShower(number, precision);
return ns.convert();
}

View File

@ -1,8 +1,9 @@
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import { SquiggleChart } from "./SquiggleChart"; import { SquiggleChart } from "./SquiggleChart";
import { ReactCodeJar } from "react-codejar"; import { CodeEditor } from "./CodeEditor";
import type { exportEnv } from "@quri/squiggle-lang"; import type { exportEnv } from "@quri/squiggle-lang";
import styled from 'styled-components'
export interface SquiggleEditorProps { export interface SquiggleEditorProps {
/** The input string for squiggle */ /** The input string for squiggle */
@ -23,67 +24,57 @@ export interface SquiggleEditorProps {
environment?: exportEnv; environment?: exportEnv;
/** when the environment changes. Used again for notebook magic*/ /** when the environment changes. Used again for notebook magic*/
onEnvChange?(env: exportEnv): void; onEnvChange?(env: exportEnv): void;
/** The width of the element */
width: number;
} }
const highlight = (_: HTMLInputElement) => {}; const Input = styled.div`
border: 1px solid #ddd;
padding: 0.3em 0.3em;
margin-bottom: 1em;
`;
interface SquiggleEditorState { export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
expression: string; initialSquiggleString = "",
env: exportEnv; width = 500,
} sampleCount,
outputXYPoints,
export class SquiggleEditor extends React.Component< kernelWidth,
SquiggleEditorProps, pointDistLength,
SquiggleEditorState diagramStart,
> { diagramStop,
constructor(props: SquiggleEditorProps) { diagramCount,
super(props); onEnvChange,
let code = props.initialSquiggleString ? props.initialSquiggleString : ""; environment,
this.state = { expression: code, env: props.environment }; }: SquiggleEditorProps) => {
} let [expression, setExpression] = React.useState(initialSquiggleString);
render() { return (
let { expression, env } = this.state; <div>
let props = this.props; <Input>
return ( <CodeEditor
<div> value={expression}
<ReactCodeJar onChange={setExpression}
code={expression} oneLine={true}
onUpdate={(e) => {
this.setState({ expression: e });
}}
style={{
borderRadius: "6px",
width: "530px",
border: "1px solid grey",
fontFamily: "'Source Code Pro', monospace",
fontSize: "14px",
fontWeight: "400",
letterSpacing: "normal",
lineHeight: "20px",
padding: "10px",
tabSize: "4",
}}
highlight={highlight}
lineNumbers={false}
/> />
<SquiggleChart </Input>
squiggleString={expression} <SquiggleChart
sampleCount={props.sampleCount} width={width}
outputXYPoints={props.outputXYPoints} squiggleString={expression}
kernelWidth={props.kernelWidth} sampleCount={sampleCount}
pointDistLength={props.pointDistLength} outputXYPoints={outputXYPoints}
diagramStart={props.diagramStart} kernelWidth={kernelWidth}
diagramStop={props.diagramStop} pointDistLength={pointDistLength}
diagramCount={props.diagramCount} diagramStart={diagramStart}
environment={env} diagramStop={diagramStop}
onEnvChange={props.onEnvChange} diagramCount={diagramCount}
/> environment={environment}
</div> onEnvChange={onEnvChange}
); />
} </div>
} );
};
export function renderSquiggleEditor(props: SquiggleEditorProps) { export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
let parent = document.createElement("div"); let parent = document.createElement("div");
ReactDOM.render( ReactDOM.render(
<SquiggleEditor <SquiggleEditor

View File

@ -0,0 +1,131 @@
import _ from "lodash";
import React, { FC, useState } from "react";
import ReactDOM from "react-dom";
import { SquiggleChart } from "./SquiggleChart";
import CodeEditor from "./CodeEditor";
import { Form, Input, Card, Row, Col } from "antd";
import "antd/dist/antd.css";
interface FieldFloatProps {
label: string;
className?: string;
value: number;
onChange: (value: number) => void;
}
function FieldFloat(Props: FieldFloatProps) {
let [contents, setContents] = useState(Props.value + "");
return (
<Form.Item label={Props.label}>
<Input
value={contents}
className={Props.className ? Props.className : ""}
onChange={(e) => {
setContents(e.target.value);
let result = parseFloat(contents);
if (_.isFinite(result)) {
Props.onChange(result);
}
}}
/>
</Form.Item>
);
}
interface Props {
initialSquiggleString: string;
}
let SquigglePlayground: FC<Props> = (props) => {
let [squiggleString, setSquiggleString] = useState(
props.initialSquiggleString
);
let [sampleCount, setSampleCount] = useState(1000);
let [outputXYPoints, setOutputXYPoints] = useState(1000);
let [pointDistLength, setPointDistLength] = useState(1000);
let [diagramStart, setDiagramStart] = useState(0);
let [diagramStop, setDiagramStop] = useState(10);
let [diagramCount, setDiagramCount] = useState(20);
var demoDist = (
<SquiggleChart
squiggleString={squiggleString}
sampleCount={sampleCount}
outputXYPoints={outputXYPoints}
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
pointDistLength={pointDistLength}
height={150}
/>
);
return (
<Row>
<Col span={12}>
<Card title="Distribution Form">
<Form>
<Row gutter={16}>
<Col span={24}>
<CodeEditor
value={squiggleString}
onChange={setSquiggleString}
oneLine={false}
/>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<FieldFloat
value={sampleCount}
label="Sample Count"
onChange={setSampleCount}
/>
</Col>
<Col span={12}>
<FieldFloat
value={outputXYPoints}
onChange={setOutputXYPoints}
label="Output XY-points"
/>
</Col>
<Col span={12}>
<FieldFloat
value={pointDistLength}
onChange={setPointDistLength}
label="Downsample To"
/>
</Col>
<Col span={12}>
<FieldFloat
value={diagramStart}
onChange={setDiagramStart}
label="Diagram Start"
/>
</Col>
<Col span={12}>
<FieldFloat
value={diagramStop}
onChange={setDiagramStop}
label="Diagram Stop"
/>
</Col>
<Col span={12}>
<FieldFloat
value={diagramCount}
onChange={setDiagramCount}
label="Diagram Count"
/>
</Col>
</Row>
</Form>
</Card>
</Col>
<Col span={12}>{demoDist}</Col>
</Row>
);
};
export default SquigglePlayground;
export function renderSquigglePlaygroundToDom(props: Props) {
let parent = document.createElement("div");
ReactDOM.render(<SquigglePlayground {...props} />, parent);
return parent;
}

View File

@ -1,2 +1,6 @@
export { SquiggleChart } from "./SquiggleChart"; export { SquiggleChart } from "./SquiggleChart";
export { SquiggleEditor, renderSquiggleEditor } from "./SquiggleEditor"; export { SquiggleEditor, renderSquiggleEditorToDom } from "./SquiggleEditor";
import SquigglePlayground, {
renderSquigglePlaygroundToDom,
} from "./SquigglePlayground";
export { SquigglePlayground, renderSquigglePlaygroundToDom };

View File

@ -1,123 +1,181 @@
{ {
"$schema": "https://vega.github.io/schema/vega/v5.json", "$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A basic area chart example.", "description": "A basic area chart example",
"width": 500, "width": 500,
"height": 200, "height": 100,
"padding": 5, "padding": 5,
"data": [{ "name": "con" }, { "name": "dis" }], "data": [
"signals": [
{ {
"name": "mousex", "name": "con"
"description": "x position of mouse",
"update": "0",
"on": [{ "events": "mousemove", "update": "1-x()/width" }]
}, },
{
"name": "dis"
}
],
"signals": [
{ {
"name": "xscale", "name": "xscale",
"description": "The transform of the x scale", "description": "The transform of the x scale",
"value": 1.0, "value": false,
"bind": { "bind": {
"input": "range", "input": "checkbox",
"min": 0.1, "name": "log x scale"
"max": 1
} }
}, },
{ {
"name": "yscale", "name": "yscale",
"description": "The transform of the y scale", "description": "The transform of the y scale",
"value": 1.0, "value": false,
"bind": { "bind": {
"input": "range", "input": "checkbox",
"min": 0.1, "name": "log y scale"
"max": 1
} }
} }
], ],
"scales": [ "scales": [
{ {
"name": "xscale", "name": "xscale",
"type": "pow", "type": "pow",
"exponent": { "signal": "xscale" }, "exponent": {
"signal": "xscale ? 0.1 : 1"
},
"range": "width", "range": "width",
"zero": false, "zero": false,
"nice": false, "nice": false,
"domain": { "domain": {
"fields": [ "fields": [
{ "data": "con", "field": "x" }, {
{ "data": "dis", "field": "x" } "data": "con",
"field": "x"
},
{
"data": "dis",
"field": "x"
}
] ]
} }
}, },
{ {
"name": "yscale", "name": "yscale",
"type": "pow", "type": "pow",
"exponent": { "signal": "yscale" }, "exponent": {
"signal": "yscale ? 0.1 : 1"
},
"range": "height", "range": "height",
"nice": true, "nice": true,
"zero": true, "zero": true,
"domain": { "domain": {
"fields": [ "fields": [
{ "data": "con", "field": "y" }, {
{ "data": "dis", "field": "y" } "data": "con",
"field": "y"
},
{
"data": "dis",
"field": "y"
}
] ]
} }
} }
], ],
"axes": [ "axes": [
{ "orient": "bottom", "scale": "xscale", "tickCount": 20 }, {
{ "orient": "left", "scale": "yscale" } "orient": "bottom",
"scale": "xscale",
"labelColor": "#666",
"tickColor": "#ddd",
"format": "~s",
"tickCount": 20
}
], ],
"marks": [ "marks": [
{ {
"type": "area", "type": "area",
"from": { "data": "con" }, "from": {
"data": "con"
},
"encode": { "encode": {
"enter": {
"tooltip": { "signal": "datum.cdf" }
},
"update": { "update": {
"x": { "scale": "xscale", "field": "x" }, "x": {
"y": { "scale": "yscale", "field": "y" }, "scale": "xscale",
"y2": { "scale": "yscale", "value": 0 }, "field": "x"
"fill": {
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#1b6fac'}, {offset: 1.0, color: '#1b6fac'} ] }",
"color": "#000"
}, },
"interpolate": { "value": "monotone" }, "y": {
"fillOpacity": { "value": 1 } "scale": "yscale",
"field": "y"
},
"y2": {
"scale": "yscale",
"value": 0
},
"fill": {
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: '#4C78A8'}] }"
},
"interpolate": {
"value": "monotone"
},
"fillOpacity": {
"value": 1
}
} }
} }
}, },
{ {
"type": "rect", "type": "rect",
"from": { "data": "dis" }, "from": {
"data": "dis"
},
"encode": { "encode": {
"enter": { "enter": {
"y2": { "scale": "yscale", "value": 0 }, "y2": {
"width": { "value": 1 } "scale": "yscale",
"value": 0
},
"width": {
"value": 1
}
}, },
"update": { "update": {
"x": { "scale": "xscale", "field": "x" }, "x": {
"y": { "scale": "yscale", "field": "y" } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "y"
}
} }
} }
}, },
{ {
"type": "symbol", "type": "symbol",
"from": { "data": "dis" }, "from": {
"data": "dis"
},
"encode": { "encode": {
"enter": { "enter": {
"shape": { "value": "circle" }, "shape": {
"width": { "value": 5 }, "value": "circle"
"tooltip": { "signal": "datum.y" } },
"width": {
"value": 5
},
"tooltip": {
"signal": "datum.y"
}
}, },
"update": { "update": {
"x": { "scale": "xscale", "field": "x" }, "x": {
"y": { "scale": "yscale", "field": "y" } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "y"
},
"fill": {
"value": "#1e4577"
}
} }
} }
} }

View File

@ -7,7 +7,12 @@
{ {
"name": "facet", "name": "facet",
"values": [], "values": [],
"format": { "type": "json", "parse": { "timestamp": "date" } } "format": {
"type": "json",
"parse": {
"timestamp": "date"
}
}
}, },
{ {
"name": "table", "name": "table",
@ -70,7 +75,10 @@
"name": "xscale", "name": "xscale",
"type": "linear", "type": "linear",
"nice": true, "nice": true,
"domain": { "data": "facet", "field": "x" }, "domain": {
"data": "facet",
"field": "x"
},
"range": "width" "range": "width"
}, },
{ {
@ -79,7 +87,10 @@
"range": "height", "range": "height",
"nice": true, "nice": true,
"zero": true, "zero": true,
"domain": { "data": "facet", "field": "p99" } "domain": {
"data": "facet",
"field": "p99"
}
} }
], ],
"axes": [ "axes": [
@ -89,8 +100,20 @@
"grid": false, "grid": false,
"tickSize": 2, "tickSize": 2,
"encode": { "encode": {
"grid": { "enter": { "stroke": { "value": "#ccc" } } }, "grid": {
"ticks": { "enter": { "stroke": { "value": "#ccc" } } } "enter": {
"stroke": {
"value": "#ccc"
}
}
},
"ticks": {
"enter": {
"stroke": {
"value": "#ccc"
}
}
}
} }
}, },
{ {
@ -100,107 +123,249 @@
"domain": false, "domain": false,
"tickSize": 2, "tickSize": 2,
"encode": { "encode": {
"grid": { "enter": { "stroke": { "value": "#ccc" } } }, "grid": {
"ticks": { "enter": { "stroke": { "value": "#ccc" } } } "enter": {
"stroke": {
"value": "#ccc"
}
}
},
"ticks": {
"enter": {
"stroke": {
"value": "#ccc"
}
}
}
} }
} }
], ],
"marks": [ "marks": [
{ {
"type": "area", "type": "area",
"from": { "data": "table" }, "from": {
"data": "table"
},
"encode": { "encode": {
"enter": { "fill": { "value": "#4C78A8" } }, "enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": { "update": {
"interpolate": { "value": "monotone" }, "interpolate": {
"x": { "scale": "xscale", "field": "x" }, "value": "monotone"
"y": { "scale": "yscale", "field": "p1" }, },
"y2": { "scale": "yscale", "field": "p99" }, "x": {
"opacity": { "value": 0.05 } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p1"
},
"y2": {
"scale": "yscale",
"field": "p99"
},
"opacity": {
"value": 0.05
}
} }
} }
}, },
{ {
"type": "area", "type": "area",
"from": { "data": "table" }, "from": {
"data": "table"
},
"encode": { "encode": {
"enter": { "fill": { "value": "#4C78A8" } }, "enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": { "update": {
"interpolate": { "value": "monotone" }, "interpolate": {
"x": { "scale": "xscale", "field": "x" }, "value": "monotone"
"y": { "scale": "yscale", "field": "p5" }, },
"y2": { "scale": "yscale", "field": "p95" }, "x": {
"opacity": { "value": 0.1 } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p5"
},
"y2": {
"scale": "yscale",
"field": "p95"
},
"opacity": {
"value": 0.1
}
} }
} }
}, },
{ {
"type": "area", "type": "area",
"from": { "data": "table" }, "from": {
"data": "table"
},
"encode": { "encode": {
"enter": { "fill": { "value": "#4C78A8" } }, "enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": { "update": {
"interpolate": { "value": "monotone" }, "interpolate": {
"x": { "scale": "xscale", "field": "x" }, "value": "monotone"
"y": { "scale": "yscale", "field": "p10" }, },
"y2": { "scale": "yscale", "field": "p90" }, "x": {
"opacity": { "value": 0.15 } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p10"
},
"y2": {
"scale": "yscale",
"field": "p90"
},
"opacity": {
"value": 0.15
}
} }
} }
}, },
{ {
"type": "area", "type": "area",
"from": { "data": "table" }, "from": {
"data": "table"
},
"encode": { "encode": {
"enter": { "fill": { "value": "#4C78A8" } }, "enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": { "update": {
"interpolate": { "value": "monotone" }, "interpolate": {
"x": { "scale": "xscale", "field": "x" }, "value": "monotone"
"y": { "scale": "yscale", "field": "p20" }, },
"y2": { "scale": "yscale", "field": "p80" }, "x": {
"opacity": { "value": 0.2 } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p20"
},
"y2": {
"scale": "yscale",
"field": "p80"
},
"opacity": {
"value": 0.2
}
} }
} }
}, },
{ {
"type": "area", "type": "area",
"from": { "data": "table" }, "from": {
"data": "table"
},
"encode": { "encode": {
"enter": { "fill": { "value": "#4C78A8" } }, "enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": { "update": {
"interpolate": { "value": "monotone" }, "interpolate": {
"x": { "scale": "xscale", "field": "x" }, "value": "monotone"
"y": { "scale": "yscale", "field": "p30" }, },
"y2": { "scale": "yscale", "field": "p70" }, "x": {
"opacity": { "value": 0.2 } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p30"
},
"y2": {
"scale": "yscale",
"field": "p70"
},
"opacity": {
"value": 0.2
}
} }
} }
}, },
{ {
"type": "area", "type": "area",
"from": { "data": "table" }, "from": {
"data": "table"
},
"encode": { "encode": {
"enter": { "fill": { "value": "#4C78A8" } }, "enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": { "update": {
"interpolate": { "value": "monotone" }, "interpolate": {
"x": { "scale": "xscale", "field": "x" }, "value": "monotone"
"y": { "scale": "yscale", "field": "p40" }, },
"y2": { "scale": "yscale", "field": "p60" }, "x": {
"opacity": { "value": 0.2 } "scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p40"
},
"y2": {
"scale": "yscale",
"field": "p60"
},
"opacity": {
"value": 0.2
}
} }
} }
}, },
{ {
"type": "line", "type": "line",
"from": { "data": "table" }, "from": {
"data": "table"
},
"encode": { "encode": {
"update": { "update": {
"interpolate": { "value": "monotone" }, "interpolate": {
"stroke": { "value": "#4C78A8" }, "value": "monotone"
"strokeWidth": { "value": 2 }, },
"opacity": { "value": 0.8 }, "stroke": {
"x": { "scale": "xscale", "field": "x" }, "value": "#4C78A8"
"y": { "scale": "yscale", "field": "p50" } },
"strokeWidth": {
"value": 2
},
"opacity": {
"value": 0.8
},
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p50"
}
} }
} }
} }

View File

@ -2,8 +2,5 @@ import { Meta } from "@storybook/addon-docs";
<Meta title="Squiggle/Introduction" /> <Meta title="Squiggle/Introduction" />
This is the component library for Squiggle. All of these components are react This is the component library for Squiggle. These are React
components, and can be used in any application that you see fit. components, and can be used in any application that you see fit.
Currently, the only component that is provided is the SquiggleChart component.
This component allows you to render the result of a squiggle expression.

View File

@ -0,0 +1,60 @@
import { NumberShower } from "../NumberShower";
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
<Meta title="Squiggle/NumberShower" component={NumberShower} />
# Number Shower
The number shower is a simple component to display a number.
It uses the symbols "K", "M", "B", and "T", to represent thousands, millions, billions, and trillions. Outside of that range, it uses scientific notation.
<Canvas>
<Story
name="Ten Thousand"
args={{
number: 10000,
precision: 2
}}
>
{args => <NumberShower {...args}/>}
</Story>
</Canvas>
<Canvas>
<Story
name="Ten Billion"
args={{
number: 10000000000,
precision: 2
}}
>
{args => <NumberShower {...args}/>}
</Story>
</Canvas>
<Canvas>
<Story
name="1.2*10^15"
args={{
number: 1200000000000000,
precision: 2
}}
>
{args => <NumberShower {...args}/>}
</Story>
</Canvas>
<Canvas>
<Story
name="1.35*10^-13"
args={{
number: 0.000000000000135,
precision: 2
}}
>
{args => <NumberShower {...args}/>}
</Story>
</Canvas>
<Props of={NumberShower} />

View File

@ -18,7 +18,7 @@ could be continuous, discrete or mixed.
## Distributions ## Distributions
An example of a normal distribution is: ### Continuous Distributions
<Canvas> <Canvas>
<Story <Story
@ -31,26 +31,26 @@ An example of a normal distribution is:
</Story> </Story>
</Canvas> </Canvas>
An example of a Discrete distribution is: ### Discrete Distributions
<Canvas> <Canvas>
<Story <Story
name="Discrete" name="Discrete"
args={{ args={{
squiggleString: "mm(0, 1, [0.5, 0.5])", squiggleString: "mm(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
}} }}
> >
{Template.bind({})} {Template.bind({})}
</Story> </Story>
</Canvas> </Canvas>
An example of a Mixed distribution is: ## Mixed distributions
<Canvas> <Canvas>
<Story <Story
name="Mixed" name="Mixed"
args={{ args={{
squiggleString: "mm(0, 5 to 10, [0.5, 0.5])", squiggleString: "mm(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
}} }}
> >
{Template.bind({})} {Template.bind({})}
@ -66,7 +66,7 @@ to allow large and small numbers being printed cleanly.
<Story <Story
name="Constant" name="Constant"
args={{ args={{
squiggleString: "500000 * 5000000", squiggleString: "500000000",
}} }}
> >
{Template.bind({})} {Template.bind({})}
@ -75,14 +75,28 @@ to allow large and small numbers being printed cleanly.
## Functions ## Functions
Finally, a function can be returned, and this shows how the distribution changes Full functions can be returned. These plot out the results of distributions between a set of x-coordinates.
over the axis between x = 0 and 10.
The default is show 10 points between 0 and 10.
<Canvas> <Canvas>
<Story <Story
name="Function" name="Function"
args={{ args={{
squiggleString: "f(x) = normal(x,x)\nf", squiggleString: "f(x) = normal(x^2,x^1.8)\nf",
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Errors
<Canvas>
<Story
name="Error"
args={{
squiggleString: "f(x) = normal(",
}} }}
> >
{Template.bind({})} {Template.bind({})}

View File

@ -20,3 +20,16 @@ the distribution.
{Template.bind({})} {Template.bind({})}
</Story> </Story>
</Canvas> </Canvas>
You can also name variables like so:
<Canvas>
<Story
name="Variables"
args={{
initialSquiggleString: "x = 2\nnormal(x,2)",
}}
>
{Template.bind({})}
</Story>
</Canvas>

View File

@ -0,0 +1,22 @@
import SquigglePlayground from "../SquigglePlayground";
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
export const Template = (props) => <SquigglePlayground {...props} />;
# Squiggle Playground
A Squiggle playground is an environment where you can play around with all settings,
including sampling settings, in squiggle.
<Canvas>
<Story
name="Normal"
args={{
initialSquiggleString: "normal(5,2)",
}}
>
{Template.bind({})}
</Story>
</Canvas>

View File

@ -16,10 +16,10 @@
"declaration": true, "declaration": true,
"sourceMap": true "sourceMap": true
}, },
"files": ["src/spec-distributions.json","src/spec-percentiles.json"], "files": ["src/spec-distributions.json", "src/spec-percentiles.json"],
"target": "ES6", "target": "ES6",
"include": ["src/**/*", "src/*"], "include": ["src/**/*", "src/*"],
"exclude": ["node_modules", "**/*.spec.ts"], "exclude": ["node_modules", "**/*.spec.ts", "webpack.config.js"],
"references": [ "references": [
{ {
"path": "../squiggle-lang" "path": "../squiggle-lang"

View File

@ -12,12 +12,16 @@ module.exports = {
options: { projectReferences: true }, options: { projectReferences: true },
exclude: /node_modules/, exclude: /node_modules/,
}, },
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
], ],
}, },
resolve: { resolve: {
extensions: [".js", ".tsx", ".ts"], extensions: [".js", ".tsx", ".ts"],
alias: { alias: {
"@quri/squiggle-lang": path.resolve(__dirname, '../squiggle-lang/src/js') "@quri/squiggle-lang": path.resolve(__dirname, "../squiggle-lang/src/js"),
}, },
}, },
output: { output: {

View File

@ -1,16 +0,0 @@
.DS_Store
.merlin
.bsb.lock
npm-debug.log
/node_modules/
.cache
.cache/*
dist
lib/*
*.cache
build
yarn-error.log
*.bs.js
# Local Netlify folder
.netlify
.idea

View File

@ -1,21 +0,0 @@
# TODO: REVIVE PLAYGROUND.
# Squiggle Playground
This repository contains the squiggle playground, a small web interface
for playing around with squiggle concepts.
It depends on `@quri/squiggle-components` and `@quri/squiggle-lang` so both of them will
need to be packaged for this to work. This can be done from the root directory
with
```
yarn build:lang
yarn build:components
```
Then, starting the playground can be done with:
```
yarn parcel
```

View File

@ -1,4 +0,0 @@
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

View File

@ -1,54 +0,0 @@
{
"name": "@quri/squiggle-playground",
"version": "0.1.0",
"homepage": "https://foretold-app.github.io/estiband/",
"scripts": {
"parcel": "parcel ./src/index.html",
"parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall --no-scope-hoist",
"deploy": "gh-pages -d dist",
"ci": "yarn parcel-build"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@emotion/react": "^11.8.1",
"@quri/squiggle-lang": "^0.2.2",
"ace-builds": "^1.4.12",
"antd": "^4.18.5",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"binary-search-tree": "0.2.6",
"css-loader": "^6.7.1",
"gh-pages": "3.2.3",
"jstat": "1.9.5",
"lenses-ppx": "6.1.10",
"less": "4.1.2",
"lodash": "4.17.21",
"mathjs": "10.4.1",
"moduleserve": "0.9.1",
"moment": "2.29.1",
"pdfast": "^0.2.0",
"rationale": "0.2.0",
"react": "17.0.2",
"react-ace": "^9.2.0",
"react-dom": "^17.0.2",
"react-use": "^17.3.2",
"react-vega": "^7.4.4",
"vega": "*",
"vega-embed": "6.20.8",
"vega-lite": "*"
},
"devDependencies": {
"@emotion/babel-plugin": "^11.7.2",
"@parcel/core": "^2.4.0",
"@types/react": "^17.0.43",
"autoprefixer": "^10.4.2",
"docsify": "^4.12.2",
"jest": "^27.5.1",
"parcel": "^2.4.0",
"postcss": "^8.4.7",
"postcss-cli": "^9.1.0",
"tailwindcss": "^3.0.23",
"typescript": "^4.6.3"
}
}

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,9 +0,0 @@
import React from 'react'
import { render } from "react-dom"
import DistBuilder from "./components/DistBuilder"
var root = document.querySelector("#app")
if (!(root == null)) {
render(<DistBuilder />, root)
}

View File

@ -1,34 +0,0 @@
import React, {FC} from "react";
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-golang";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/src-noconflict/keybinding-vim";
interface CodeEditorProps {
value : string,
onChange : (value: string) => void
}
export let CodeEditor : FC<CodeEditorProps> = (props) =>
<AceEditor
value={props.value}
mode="golang"
height="400px"
width="100%"
theme="github"
showGutter={false}
highlightActiveLine={false}
showPrintMargin={false}
onChange={props.onChange}
name="UNIQUE_ID_OF_DIV"
editorProps={{
$blockScrolling: true,
}}
setOptions={{
enableBasicAutocompletion: false,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
/>

View File

@ -1,171 +0,0 @@
import { FC, useState } from "react"
import { SquiggleChart } from "@quri/squiggle-components"
import { CodeEditor } from "./CodeEditor"
import { Form, Input, Card, Row, Col } from "antd"
import { css } from '@emotion/react'
interface FieldFloatProps {
label : string,
className? : string,
value : number,
onChange : (value: number) => void,
}
function FieldFloat(Props: FieldFloatProps) {
let [contents, setContents] = useState(Props.value + "");
return <Form.Item label={Props.label}>
<Input
value={contents}
className={Props.className ? Props.className : ""}
onChange={(e) => setContents(e.target.value)}
onBlur={(_) => {
let result = parseFloat(contents);
if(result != NaN) {
Props.onChange(result)
}
}}
/>
</Form.Item>
}
let rows = css`
>.antCol:firstChild {
paddingLeft: 0.25em;
paddingRight: 0.125em;
}
>.antCol:lastChild {
paddingLeft: 0.125em;
paddingRight: 0.25em;
}
>.antCol:not(:lastChild):not(:lastChild) {
paddingLeft: 0.125em;
paddingRight: 0.125em;
}
`
let parent = css`
.antImportNumber {
width: 100%;
}
.anticon {
verticalAlign: "zero";
}
`
var form = css`
backgroundColor: #eee;
padding: 1em;
`
var dist = css`
padding: 1em;
`
var spacer = css`
marginTop: 1em;
`
var groupA = css`
.antInputNumberInputs {
backgroundColor: #fff7db;
}
`
var groupB = css`
.antInputNumberInput {
backgroundColor: #eaf4ff;
}
`
var Styles = {
rows: rows,
parent: parent,
form: form,
dist: dist,
spacer: spacer,
groupA: groupA,
groupB: groupB
};
let DistBuilder : FC<{}> = (_: {}) => {
let [squiggleString, setSquiggleString] = useState("mm(normal(5,2), normal(10,2))")
let [sampleCount, setSampleCount] = useState(1000)
let [outputXYPoints, setOutputXYPoints] = useState(1000)
let [pointDistLength, setPointDistLength] = useState(undefined)
let [kernelWidth, setKernelWidth] = useState(undefined)
let [diagramStart, setDiagramStart] = useState(0)
let [diagramStop, setDiagramStop] = useState(10)
let [diagramCount, setDiagramCount] = useState(20)
var demoDist =
<SquiggleChart
squiggleString={squiggleString}
sampleCount={sampleCount}
outputXYPoints={outputXYPoints}
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
pointDistLength={pointDistLength}
/>
return (
<div className="grid grid-cols-2 gap-4">
<div>
<Card
title="Distribution Form">
<Form>
<Row css={Styles.rows}>
<Col span={24}>
<CodeEditor value={squiggleString} onChange={setSquiggleString} /> </Col>
</Row>
<Row css={Styles.rows}>
<Col span={12}>
<FieldFloat
value={sampleCount}
label="Sample Count"
onChange={setSampleCount}
/> </Col>
<Col span={12}>
<FieldFloat
value={outputXYPoints}
onChange={setOutputXYPoints}
label="Output XY-points" />
</Col>
<Col span={12}>
<FieldFloat
value={pointDistLength}
onChange={setPointDistLength}
label="Downsample To"
/>
</Col>
<Col span={12}>
<FieldFloat
value={kernelWidth}
onChange={setKernelWidth}
label="Kernel Width"
/> </Col>
<Col span={12}>
<FieldFloat
value={diagramStart}
onChange={setDiagramStart}
label="Diagram Start"
/>
</Col>
<Col span={12}>
<FieldFloat
value={diagramStop}
onChange={setDiagramStop}
label="Diagram Stop"
/> </Col>
<Col span={12}>
<FieldFloat
value={diagramCount}
onChange={setDiagramCount}
label="Diagram Count"
/>
</Col>
</Row>
</Form>
</Card>
</div>
{demoDist}
</div>
)
}
export default DistBuilder

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Squiggle Language</title>
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
<link href="./styles/antd.css" rel="stylesheet">
<link href="./styles/index.css" rel="stylesheet">
<script type="module" src="./Index.tsx" defer></script>
</head>
<body>
<div id="app" style="height: 100%"></div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,9 +0,0 @@
module.exports = {
content: [
"./src/components/*.tsx"
],
theme: {
extend: {},
},
plugins: [],
}

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react",
"noImplicitAny": false,
"removeComments": true,
"preserveConstEnums": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "./dist",
"declarationDir": "./dist",
"declaration": true,
"sourceMap": true
},
"target": "ES6",
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}

File diff suppressed because it is too large Load Diff

View File

@ -17,3 +17,5 @@ yarn-error.log
*.gen.tsx *.gen.tsx
*.gen.js *.gen.js
dist dist
*.coverage
_coverage

View File

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

View File

@ -1,89 +0,0 @@
open Jest
open Expect
let makeTest = (~only=false, str, item1, item2) =>
only
? Only.test(str, () => expect(item1) -> toEqual(item2))
: test(str, () => expect(item1) -> toEqual(item2))
describe("PointSetTypes", () =>
describe("Domain", () => {
let makeComplete = (yPoint, expectation) =>
makeTest(
"With input: " ++ Js.Float.toString(yPoint),
PointSetTypes.Domain.yPointToSubYPoint(Complete, yPoint),
expectation,
)
let makeSingle = (direction: [#left | #right], excludingProbabilityMass, yPoint, expectation) =>
makeTest(
"Excluding: " ++
(Js.Float.toString(excludingProbabilityMass) ++
(" and yPoint: " ++ Js.Float.toString(yPoint))),
PointSetTypes.Domain.yPointToSubYPoint(
direction == #left
? LeftLimited({xPoint: 3.0, excludingProbabilityMass: excludingProbabilityMass})
: RightLimited({xPoint: 3.0, excludingProbabilityMass: excludingProbabilityMass}),
yPoint,
),
expectation,
)
let makeDouble = (domain, yPoint, expectation) =>
makeTest("Excluding: limits", PointSetTypes.Domain.yPointToSubYPoint(domain, yPoint), expectation)
describe("With Complete Domain", () => {
makeComplete(0.0, Some(0.0))
makeComplete(0.6, Some(0.6))
makeComplete(1.0, Some(1.0))
})
describe("With Left Limit", () => {
makeSingle(#left, 0.5, 1.0, Some(1.0))
makeSingle(#left, 0.5, 0.75, Some(0.5))
makeSingle(#left, 0.8, 0.9, Some(0.5))
makeSingle(#left, 0.5, 0.4, None)
makeSingle(#left, 0.5, 0.5, Some(0.0))
})
describe("With Right Limit", () => {
makeSingle(#right, 0.5, 1.0, None)
makeSingle(#right, 0.5, 0.25, Some(0.5))
makeSingle(#right, 0.8, 0.5, None)
makeSingle(#right, 0.2, 0.2, Some(0.25))
makeSingle(#right, 0.5, 0.5, Some(1.0))
makeSingle(#right, 0.5, 0.0, Some(0.0))
makeSingle(#right, 0.5, 0.5, Some(1.0))
})
describe("With Left and Right Limit", () => {
makeDouble(
LeftAndRightLimited(
{excludingProbabilityMass: 0.25, xPoint: 3.0},
{excludingProbabilityMass: 0.25, xPoint: 10.0},
),
0.5,
Some(0.5),
)
makeDouble(
LeftAndRightLimited(
{excludingProbabilityMass: 0.1, xPoint: 3.0},
{excludingProbabilityMass: 0.1, xPoint: 10.0},
),
0.2,
Some(0.125),
)
makeDouble(
LeftAndRightLimited(
{excludingProbabilityMass: 0.1, xPoint: 3.0},
{excludingProbabilityMass: 0.1, xPoint: 10.0},
),
0.1,
Some(0.0),
)
makeDouble(
LeftAndRightLimited(
{excludingProbabilityMass: 0.1, xPoint: 3.0},
{excludingProbabilityMass: 0.1, xPoint: 10.0},
),
0.05,
None,
)
})
})
)

View File

@ -0,0 +1,50 @@
open Jest
open Expect
let env: DistributionOperation.env = {
sampleCount: 100,
xyPointLength: 100,
}
let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev}))
let normalDist5: GenericDist_Types.genericDist = mkNormal(5.0, 2.0)
let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0}))
let {toFloat, toDist, toString, toError} = module(DistributionOperation.Output)
let {run} = module(DistributionOperation)
let {fmap} = module(DistributionOperation.Output)
let run = run(~env)
let outputMap = fmap(~env)
let toExt: option<'a> => 'a = E.O.toExt(
"Should be impossible to reach (This error is in test file)",
)
describe("toPointSet", () => {
test("on symbolic normal distribution", () => {
let result =
run(FromDist(ToDist(ToPointSet), normalDist5))
->outputMap(FromDist(ToFloat(#Mean)))
->toFloat
->toExt
expect(result)->toBeSoCloseTo(5.0, ~digits=0)
})
test("on sample set distribution with under 4 points", () => {
let result =
run(FromDist(ToDist(ToPointSet), SampleSet([0.0, 1.0, 2.0, 3.0])))->outputMap(
FromDist(ToFloat(#Mean)),
)
expect(result)->toEqual(GenDistError(Other("Converting sampleSet to pointSet failed")))
})
test("on sample set", () => {
let result =
run(FromDist(ToDist(ToPointSet), normalDist5))
->outputMap(FromDist(ToDist(ToSampleSet(1000))))
->outputMap(FromDist(ToDist(ToPointSet)))
->outputMap(FromDist(ToFloat(#Mean)))
->toFloat
->toExt
expect(result)->toBeSoCloseTo(5.0, ~digits=-1)
})
})

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,125 @@
open Jest
let testSkip: (bool, string, unit => assertion) => unit = (skip: bool) =>
if skip {
Skip.test
} else {
test
}
let testEval = (~skip=false, str, result) =>
testSkip(skip)(str, () => Reducer_TestHelpers.expectEvalToBe(str, result))
let testParse = (~skip=false, str, result) =>
testSkip(skip)(str, () => Reducer_TestHelpers.expectParseToBe(str, result))
describe("eval on distribution functions", () => {
describe("normal distribution", () => {
testEval("normal(5,2)", "Ok(Normal(5,2))")
})
describe("lognormal distribution", () => {
testEval("lognormal(5,2)", "Ok(Lognormal(5,2))")
})
describe("unaryMinus", () => {
testEval("mean(-normal(5,2))", "Ok(-5.002887370380851)")
})
describe("to", () => {
testEval("5 to 2", "Error(TODO: Low value must be less than high value.)")
testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.278507821238345))")
testEval("to(-2,2)", "Ok(Normal(0,1.215913388057542))")
})
describe("mean", () => {
testEval("mean(normal(5,2))", "Ok(5)")
testEval("mean(lognormal(1,2))", "Ok(20.085536923187668)")
})
describe("normalize", () => {
testEval("normalize(normal(5,2))", "Ok(Normal(5,2))")
})
describe("toPointSet", () => {
testEval("toPointSet(normal(5,2))", "Ok(Point Set Distribution)")
})
describe("toSampleSet", () => {
testEval("toSampleSet(normal(5,2), 100)", "Ok(Sample Set Distribution)")
})
describe("add", () => {
testEval("add(normal(5,2), normal(10,2))", "Ok(Normal(15,2.8284271247461903))")
testEval("add(normal(5,2), lognormal(10,2))", "Ok(Sample Set Distribution)")
testEval("add(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("add(3, normal(5,2))", "Ok(Point Set Distribution)")
testEval("3+normal(5,2)", "Ok(Point Set Distribution)")
testEval("normal(5,2)+3", "Ok(Point Set Distribution)")
})
describe("truncate", () => {
testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("truncateRight(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("truncate(normal(5,2), 3, 8)", "Ok(Point Set Distribution)")
})
describe("exp", () => {
testEval("exp(normal(5,2))", "Ok(Point Set Distribution)")
})
describe("pow", () => {
testEval("pow(3, uniform(5,8))", "Ok(Point Set Distribution)")
testEval("pow(uniform(5,8), 3)", "Ok(Point Set Distribution)")
testEval("pow(uniform(5,8), uniform(9, 10))", "Ok(Sample Set Distribution)")
})
describe("log", () => {
testEval("log(2, uniform(5,8))", "Ok(Point Set Distribution)")
testEval("log(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("log(normal(5,2), normal(10,1))", "Ok(Sample Set Distribution)")
testEval("log(uniform(5,8))", "Ok(Point Set Distribution)")
testEval("log10(uniform(5,8))", "Ok(Point Set Distribution)")
})
describe("dotLog", () => {
testEval("dotLog(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("dotLog(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("dotLog(normal(5,2), normal(10,1))", "Ok(Point Set Distribution)")
})
describe("dotAdd", () => {
testEval("dotAdd(normal(5,2), lognormal(10,2))", "Ok(Point Set Distribution)")
testEval("dotAdd(normal(5,2), 3)", "Ok(Point Set Distribution)")
})
describe("equality", () => {
testEval(~skip=true, "normal(5,2) == normal(5,2)", "Ok(true)")
})
describe("mixture", () => {
testEval(
~skip=true,
"mx(normal(5,2), normal(10,1), normal(15, 1))",
"Ok(Point Set Distribution)",
)
testEval(
~skip=true,
"mixture(normal(5,2), normal(10,1), [.2,, .4])",
"Ok(Point Set Distribution)",
)
})
})
describe("parse on distribution functions", () => {
describe("power", () => {
testParse("normal(5,2) ^ normal(5,1)", "Ok((:pow (:normal 5 2) (:normal 5 1)))")
testParse("3 ^ normal(5,1)", "Ok((:pow 3 (:normal 5 1)))")
testParse("normal(5,2) ^ 3", "Ok((:pow (:normal 5 2) 3))")
})
describe("pointwise arithmetic expressions", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
testParse(~skip=true, "normal(5,2) .- normal(5,1)", "Ok((:dotSubtract (:normal 5 2) (:normal 5 1)))")
testParse("normal(5,2) .* normal(5,1)", "Ok((:dotMultiply (:normal 5 2) (:normal 5 1)))")
testParse("normal(5,2) ./ normal(5,1)", "Ok((:dotDivide (:normal 5 2) (:normal 5 1)))")
testParse("normal(5,2) .^ normal(5,1)", "Ok((:dotPow (:normal 5 2) (:normal 5 1)))")
})
describe("equality", () => {
testParse("5 == normal(5,2)", "Ok((:equal 5 (:normal 5 2)))")
})
describe("pointwise adding two normals", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
})
describe("exponential of one distribution", () => {
testParse(~skip=true, "exp(normal(5,2)", "Ok((:pow (:normal 5 2) 3))")
})
})

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
{ {
"name": "@quri/squiggle-lang", "name": "@quri/squiggle-lang",
"reason": {},
"sources": [ "sources": [
{ {
"dir": "src/rescript", "dir": "src/rescript",
@ -28,7 +27,8 @@
"bs-dependencies": [ "bs-dependencies": [
"@glennsl/rescript-jest", "@glennsl/rescript-jest",
"@glennsl/bs-json", "@glennsl/bs-json",
"rationale" "rationale",
"bisect_ppx"
], ],
"gentypeconfig": { "gentypeconfig": {
"language": "typescript", "language": "typescript",
@ -41,7 +41,13 @@
}, },
"refmt": 3, "refmt": 3,
"warnings": { "warnings": {
"number": "+A-42-48-9-30-4-102" "number": "+A-42-48-9-30-4-102-20-27-41"
}, },
"ppx-flags": [] "ppx-flags": [
[
"../../node_modules/bisect_ppx/ppx",
"--exclude-files",
".*_test\\.res$$"
]
]
} }

View File

@ -2,4 +2,10 @@
module.exports = { module.exports = {
preset: 'ts-jest', preset: 'ts-jest',
testEnvironment: 'node', testEnvironment: 'node',
setupFilesAfterEnv: [
"<rootdir>/../../node_modules/bisect_ppx/src/runtime/js/jest.bs.js"
],
testPathIgnorePatterns: [
"__tests__/TestHelpers.bs.js"
],
}; };

View File

@ -10,8 +10,9 @@
"test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'", "test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'",
"test": "jest", "test": "jest",
"test:watch": "jest --watchAll", "test:watch": "jest --watchAll",
"all": "yarn build && yarn bundle && yarn test", "coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report html",
"rescript:format": "find . -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;" "rescript:format": "find . -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;",
"all": "yarn build && yarn bundle && yarn test"
}, },
"keywords": [ "keywords": [
"Rescript" "Rescript"
@ -25,12 +26,12 @@
"mathjs": "10.4.1", "mathjs": "10.4.1",
"pdfast": "^0.2.0", "pdfast": "^0.2.0",
"rationale": "0.2.0", "rationale": "0.2.0",
"rescript": "^9.1.4" "rescript": "^9.1.4",
"bisect_ppx": "^2.7.1"
}, },
"devDependencies": { "devDependencies": {
"@glennsl/rescript-jest": "^0.9.0", "@glennsl/rescript-jest": "^0.9.0",
"@types/jest": "^27.4.0", "@types/jest": "^27.4.0",
"@types/webpack": "^5.28.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"docsify": "^4.12.2", "docsify": "^4.12.2",
"gentype": "^4.3.0", "gentype": "^4.3.0",

View File

@ -1,7 +1,7 @@
import {runAll} from '../rescript/ProgramEvaluator.gen'; import {runAll} from '../rescript/ProgramEvaluator.gen';
import type { Inputs_SamplingInputs_t as SamplingInputs, exportEnv, exportType, exportDistribution} from '../rescript/ProgramEvaluator.gen'; import type { Inputs_SamplingInputs_t as SamplingInputs, exportEnv, exportType, exportDistribution} from '../rescript/ProgramEvaluator.gen';
export type { SamplingInputs, exportEnv, exportDistribution } export type { SamplingInputs, exportEnv, exportDistribution }
export type {t as DistPlus} from '../rescript/pointSetDist/DistPlus.gen'; export type {t as DistPlus} from '../rescript/OldInterpreter/DistPlus.gen';
export let defaultSamplingInputs : SamplingInputs = { export let defaultSamplingInputs : SamplingInputs = {
sampleCount : 10000, sampleCount : 10000,

View File

@ -10,10 +10,10 @@ type env = {
} }
type outputType = type outputType =
| Dist(GenericDist_Types.genericDist) | Dist(genericDist)
| Float(float) | Float(float)
| String(string) | String(string)
| GenDistError(GenericDist_Types.error) | GenDistError(error)
/* /*
We're going to add another function to this module later, so first define a We're going to add another function to this module later, so first define a
@ -48,12 +48,24 @@ module OutputLocal = {
| _ => None | _ => None
} }
let toFloatR = (t: t): result<float, error> =>
switch t {
| Float(r) => Ok(r)
| e => Error(toErrorOrUnreachable(e))
}
let toString = (t: t) => let toString = (t: t) =>
switch t { switch t {
| String(d) => Some(d) | String(d) => Some(d)
| _ => None | _ => None
} }
let toStringR = (t: t): result<string, error> =>
switch t {
| String(r) => Ok(r)
| e => Error(toErrorOrUnreachable(e))
}
//This is used to catch errors in other switch statements. //This is used to catch errors in other switch statements.
let fromResult = (r: result<t, error>): outputType => let fromResult = (r: result<t, error>): outputType =>
switch r { switch r {

View File

@ -26,7 +26,9 @@ module Output: {
let toDist: t => option<GenericDist_Types.genericDist> let toDist: t => option<GenericDist_Types.genericDist>
let toDistR: t => result<GenericDist_Types.genericDist, GenericDist_Types.error> let toDistR: t => result<GenericDist_Types.genericDist, GenericDist_Types.error>
let toFloat: t => option<float> let toFloat: t => option<float>
let toFloatR: t => result<float, GenericDist_Types.error>
let toString: t => option<string> let toString: t => option<string>
let toStringR: t => result<string, GenericDist_Types.error>
let toError: t => option<GenericDist_Types.error> let toError: t => option<GenericDist_Types.error>
let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t
} }

View File

@ -0,0 +1,92 @@
type genericDist =
| PointSet(PointSetTypes.pointSetDist)
| SampleSet(array<float>)
| Symbolic(SymbolicDistTypes.symbolicDist)
type error =
| NotYetImplemented
| Unreachable
| DistributionVerticalShiftIsInvalid
| Other(string)
module Operation = {
type direction =
| Algebraic
| Pointwise
type arithmeticOperation = [
| #Add
| #Multiply
| #Subtract
| #Divide
| #Exponentiate
| #Logarithm
]
let arithmeticToFn = (arithmetic: arithmeticOperation) =>
switch arithmetic {
| #Add => \"+."
| #Multiply => \"*."
| #Subtract => \"-."
| #Exponentiate => \"**"
| #Divide => \"/."
| #Logarithm => (a, b) => log(a) /. log(b)
}
type toFloat = [
| #Cdf(float)
| #Inv(float)
| #Pdf(float)
| #Mean
| #Sample
]
}
module DistributionOperation = {
type toDist =
| Normalize
| ToPointSet
| ToSampleSet(int)
| Truncate(option<float>, option<float>)
| Inspect
type toFloatArray = Sample(int)
type fromDist =
| ToFloat(Operation.toFloat)
| ToDist(toDist)
| ToDistCombination(Operation.direction, Operation.arithmeticOperation, [#Dist(genericDist) | #Float(float)])
| ToString
type singleParamaterFunction =
| FromDist(fromDist)
| FromFloat(fromDist)
type genericFunctionCallInfo =
| FromDist(fromDist, genericDist)
| FromFloat(fromDist, float)
| Mixture(array<(genericDist, float)>)
let distCallToString = (distFunction: fromDist): string =>
switch distFunction {
| ToFloat(#Cdf(r)) => `cdf(${E.Float.toFixed(r)})`
| ToFloat(#Inv(r)) => `inv(${E.Float.toFixed(r)})`
| ToFloat(#Mean) => `mean`
| ToFloat(#Pdf(r)) => `pdf(${E.Float.toFixed(r)})`
| ToFloat(#Sample) => `sample`
| ToDist(Normalize) => `normalize`
| ToDist(ToPointSet) => `toPointSet`
| ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})`
| ToDist(Truncate(_, _)) => `truncate`
| ToDist(Inspect) => `inspect`
| ToString => `toString`
| ToDistCombination(Algebraic, _, _) => `algebraic`
| ToDistCombination(Pointwise, _, _) => `pointwise`
}
let toString = (d: genericFunctionCallInfo): string =>
switch d {
| FromDist(f, _) | FromFloat(f, _) => distCallToString(f)
| Mixture(_) => `mixture`
}
}

View File

@ -228,7 +228,7 @@ let pointwiseCombinationFloat = (
): result<t, error> => { ): result<t, error> => {
let m = switch arithmeticOperation { let m = switch arithmeticOperation {
| #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid) | #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid)
| (#Multiply | #Divide | #Exponentiate | #Log) as arithmeticOperation => | (#Multiply | #Divide | #Exponentiate | #Logarithm) as arithmeticOperation =>
toPointSetFn(t)->E.R2.fmap(t => { toPointSetFn(t)->E.R2.fmap(t => {
//TODO: Move to PointSet codebase //TODO: Move to PointSet codebase
let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary) let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary)

View File

@ -30,7 +30,7 @@ let truncate: (
~toPointSetFn: toPointSetFn, ~toPointSetFn: toPointSetFn,
~leftCutoff: option<float>=?, ~leftCutoff: option<float>=?,
~rightCutoff: option<float>=?, ~rightCutoff: option<float>=?,
unit, unit
) => result<t, error> ) => result<t, error>
let algebraicCombination: ( let algebraicCombination: (

View File

@ -1,6 +1,6 @@
type genericDist = type genericDist =
| PointSet(PointSetTypes.pointSetDist) | PointSet(PointSetTypes.pointSetDist)
| SampleSet(array<float>) | SampleSet(SampleSet.t)
| Symbolic(SymbolicDistTypes.symbolicDist) | Symbolic(SymbolicDistTypes.symbolicDist)
type error = type error =
@ -20,7 +20,7 @@ module Operation = {
| #Subtract | #Subtract
| #Divide | #Divide
| #Exponentiate | #Exponentiate
| #Log | #Logarithm
] ]
let arithmeticToFn = (arithmetic: arithmeticOperation) => let arithmeticToFn = (arithmetic: arithmeticOperation) =>
@ -30,7 +30,7 @@ module Operation = {
| #Subtract => \"-." | #Subtract => \"-."
| #Exponentiate => \"**" | #Exponentiate => \"**"
| #Divide => \"/." | #Divide => \"/."
| #Log => (a, b) => log(a) /. log(b) | #Logarithm => (a, b) => log(a) /. log(b)
} }
type toFloat = [ type toFloat = [

View File

@ -115,16 +115,17 @@ let combineShapesContinuousContinuous = (
| #Multiply => (m1, m2) => m1 *. m2 | #Multiply => (m1, m2) => m1 *. m2
| #Divide => (m1, mInv2) => m1 *. mInv2 | #Divide => (m1, mInv2) => m1 *. mInv2
| #Exponentiate => (m1, mInv2) => m1 ** mInv2 | #Exponentiate => (m1, mInv2) => m1 ** mInv2
| #Log => (m1, m2) => log(m1) /. log(m2) | #Logarithm => (m1, m2) => log(m1) /. log(m2)
} // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2) } // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
// TODO: I don't know what the variances are for exponentatiation // TODO: Variances are for exponentatiation or logarithms are almost totally made up and very likely very wrong.
// converts the variances and means of the two inputs into the variance of the output // converts the variances and means of the two inputs into the variance of the output
let combineVariancesFn = switch op { let combineVariancesFn = switch op {
| #Add => (v1, v2, _, _) => v1 +. v2 | #Add => (v1, v2, _, _) => v1 +. v2
| #Subtract => (v1, v2, _, _) => v1 +. v2 | #Subtract => (v1, v2, _, _) => v1 +. v2
| #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2. | #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
| #Exponentiate => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2. | #Exponentiate => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
| #Logarithm => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
| #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2. | #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
} }
@ -233,7 +234,7 @@ let combineShapesContinuousDiscrete = (
} }
| #Multiply | #Multiply
| #Exponentiate | #Exponentiate
| #Log | #Logarithm
| #Divide => | #Divide =>
for j in 0 to t2n - 1 { for j in 0 to t2n - 1 {
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes. // creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.

View File

@ -1,6 +1,47 @@
open Distributions open Distributions
type t = PointSetTypes.continuousShape type t = PointSetTypes.continuousShape
module Analysis = {
let integrate = (
~indefiniteIntegralStepwise=(p, h1) => h1 *. p,
~indefiniteIntegralLinear=(p, a, b) => a *. p +. b *. p ** 2.0 /. 2.0,
t: t,
): float => {
let xs = t.xyShape.xs
let ys = t.xyShape.ys
E.A.reducei(xs, 0.0, (acc, _x, i) => {
let areaUnderIntegral = // TODO Take this switch statement out of the loop body
switch (t.interpolation, i) {
| (_, 0) => 0.0
| (#Stepwise, _) =>
indefiniteIntegralStepwise(xs[i], ys[i - 1]) -.
indefiniteIntegralStepwise(xs[i - 1], ys[i - 1])
| (#Linear, _) =>
let x1 = xs[i - 1]
let x2 = xs[i]
if x1 == x2 {
0.0
} else {
let h1 = ys[i - 1]
let h2 = ys[i]
let b = (h1 -. h2) /. (x1 -. x2)
let a = h1 -. b *. x1
indefiniteIntegralLinear(x2, a, b) -. indefiniteIntegralLinear(x1, a, b)
}
}
acc +. areaUnderIntegral
})
}
let getMeanOfSquares = (t: t) => {
let indefiniteIntegralLinear = (p, a, b) => a *. p ** 3.0 /. 3.0 +. b *. p ** 4.0 /. 4.0
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 3.0 /. 3.0
integrate(~indefiniteIntegralStepwise, ~indefiniteIntegralLinear, t)
}
}
let getShape = (t: t) => t.xyShape let getShape = (t: t) => t.xyShape
let interpolation = (t: t) => t.interpolation let interpolation = (t: t) => t.interpolation
let make = (~interpolation=#Linear, ~integralSumCache=None, ~integralCache=None, xyShape): t => { let make = (~interpolation=#Linear, ~integralSumCache=None, ~integralCache=None, xyShape): t => {
@ -194,7 +235,7 @@ module T = Dist({
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 2.0 /. 2.0 let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 2.0 /. 2.0
let indefiniteIntegralLinear = (p, a, b) => a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0 let indefiniteIntegralLinear = (p, a, b) => a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0
XYShape.Analysis.integrateContinuousShape( Analysis.integrate(
~indefiniteIntegralStepwise, ~indefiniteIntegralStepwise,
~indefiniteIntegralLinear, ~indefiniteIntegralLinear,
t, t,
@ -204,7 +245,7 @@ module T = Dist({
XYShape.Analysis.getVarianceDangerously( XYShape.Analysis.getVarianceDangerously(
t, t,
mean, mean,
XYShape.Analysis.getMeanOfSquaresContinuousShape, Analysis.getMeanOfSquares,
) )
}) })

View File

@ -209,8 +209,9 @@ module T = Dist({
let s = getShape(t) let s = getShape(t)
E.A.reducei(s.xs, 0.0, (acc, x, i) => acc +. x *. s.ys[i]) E.A.reducei(s.xs, 0.0, (acc, x, i) => acc +. x *. s.ys[i])
} }
let variance = (t: t): float => { let variance = (t: t): float => {
let getMeanOfSquares = t => t |> shapeMap(XYShape.Analysis.squareXYShape) |> mean let getMeanOfSquares = t => t |> shapeMap(XYShape.T.square) |> mean
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares) XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
} }
}) })

View File

@ -213,8 +213,8 @@ module T = Dist({
let getMeanOfSquares = ({discrete, continuous}: t) => { let getMeanOfSquares = ({discrete, continuous}: t) => {
let discreteMean = let discreteMean =
discrete |> Discrete.shapeMap(XYShape.Analysis.squareXYShape) |> Discrete.T.mean discrete |> Discrete.shapeMap(XYShape.T.square) |> Discrete.T.mean
let continuousMean = continuous |> XYShape.Analysis.getMeanOfSquaresContinuousShape let continuousMean = continuous |> Continuous.Analysis.getMeanOfSquares
(discreteMean *. discreteIntegralSum +. continuousMean *. continuousIntegralSum) /. (discreteMean *. discreteIntegralSum +. continuousMean *. continuousIntegralSum) /.
totalIntegralSum totalIntegralSum
} }

View File

@ -0,0 +1,93 @@
type domainLimit = {
xPoint: float,
excludingProbabilityMass: float,
}
type domain =
| Complete
| LeftLimited(domainLimit)
| RightLimited(domainLimit)
| LeftAndRightLimited(domainLimit, domainLimit)
type distributionType = [
| #PDF
| #CDF
]
type xyShape = XYShape.xyShape;
type interpolationStrategy = XYShape.interpolationStrategy;
type extrapolationStrategy = XYShape.extrapolationStrategy;
type interpolator = XYShape.extrapolationStrategy;
type rec continuousShape = {
xyShape: xyShape,
interpolation: interpolationStrategy,
integralSumCache: option<float>,
integralCache: option<continuousShape>,
}
type discreteShape = {
xyShape: xyShape,
integralSumCache: option<float>,
integralCache: option<continuousShape>,
}
type mixedShape = {
continuous: continuousShape,
discrete: discreteShape,
integralSumCache: option<float>,
integralCache: option<continuousShape>,
}
type pointSetDistMonad<'a, 'b, 'c> =
| Mixed('a)
| Discrete('b)
| Continuous('c)
@genType
type pointSetDist = pointSetDistMonad<mixedShape, discreteShape, continuousShape>
module ShapeMonad = {
let fmap = (t: pointSetDistMonad<'a, 'b, 'c>, (fn1, fn2, fn3)): pointSetDistMonad<'d, 'e, 'f> =>
switch t {
| Mixed(m) => Mixed(fn1(m))
| Discrete(m) => Discrete(fn2(m))
| Continuous(m) => Continuous(fn3(m))
}
}
type generationSource =
| SquiggleString(string)
| Shape(pointSetDist)
@genType
type distPlus = {
pointSetDist: pointSetDist,
integralCache: continuousShape,
squiggleString: option<string>,
}
type mixedPoint = {
continuous: float,
discrete: float,
}
module MixedPoint = {
type t = mixedPoint
let toContinuousValue = (t: t) => t.continuous
let toDiscreteValue = (t: t) => t.discrete
let makeContinuous = (continuous: float): t => {continuous: continuous, discrete: 0.0}
let makeDiscrete = (discrete: float): t => {continuous: 0.0, discrete: discrete}
let fmap = (fn: float => float, t: t) => {
continuous: fn(t.continuous),
discrete: fn(t.discrete),
}
let combine2 = (fn, c: t, d: t): t => {
continuous: fn(c.continuous, d.continuous),
discrete: fn(c.discrete, d.discrete),
}
let add = combine2((a, b) => a +. b)
}

View File

@ -1,3 +1,5 @@
type t = array<float>
// TODO: Refactor to raise correct error when not enough samples // TODO: Refactor to raise correct error when not enough samples
module Internals = { module Internals = {

View File

@ -2,7 +2,7 @@ open SymbolicDistTypes
module Normal = { module Normal = {
type t = normal type t = normal
let make = (mean: float, stdev: float): result<symbolicDist,string> => let make = (mean: float, stdev: float): result<symbolicDist, string> =>
stdev > 0.0 stdev > 0.0
? Ok(#Normal({mean: mean, stdev: stdev})) ? Ok(#Normal({mean: mean, stdev: stdev}))
: Error("Standard deviation of normal distribution must be larger than 0") : Error("Standard deviation of normal distribution must be larger than 0")
@ -48,12 +48,14 @@ module Normal = {
module Exponential = { module Exponential = {
type t = exponential type t = exponential
let make = (rate: float): result<symbolicDist,string> => let make = (rate: float): result<symbolicDist, string> =>
rate > 0.0 rate > 0.0
? Ok(#Exponential({ ? Ok(
rate: rate, #Exponential({
})) rate: rate,
: Error("Exponential distributions mean must be larger than 0") }),
)
: Error("Exponential distributions rate must be larger than 0.")
let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate) let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate)
let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate) let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate)
let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate) let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate)
@ -69,7 +71,7 @@ module Cauchy = {
let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale) let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale)
let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale) let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale)
let sample = (t: t) => Jstat.Cauchy.sample(t.local, t.scale) let sample = (t: t) => Jstat.Cauchy.sample(t.local, t.scale)
let mean = (_: t) => Error("Cauchy distributions have no mean value.") let mean = (_: t) => Error("Cauchy distributions may have no mean value.")
let toString = ({local, scale}: t) => j`Cauchy($local, $scale)` let toString = ({local, scale}: t) => j`Cauchy($local, $scale)`
} }
@ -78,8 +80,8 @@ module Triangular = {
let make = (low, medium, high): result<symbolicDist, string> => let make = (low, medium, high): result<symbolicDist, string> =>
low < medium && medium < high low < medium && medium < high
? Ok(#Triangular({low: low, medium: medium, high: high})) ? Ok(#Triangular({low: low, medium: medium, high: high}))
: Error("Triangular values must be increasing order") : Error("Triangular values must be increasing order.")
let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) // not obvious in jstat docs that high comes before medium?
let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium) let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium)
let inv = (p, t: t) => Jstat.Triangular.inv(p, t.low, t.high, t.medium) let inv = (p, t: t) => Jstat.Triangular.inv(p, t.low, t.high, t.medium)
let sample = (t: t) => Jstat.Triangular.sample(t.low, t.high, t.medium) let sample = (t: t) => Jstat.Triangular.sample(t.low, t.high, t.medium)
@ -89,7 +91,7 @@ module Triangular = {
module Beta = { module Beta = {
type t = beta type t = beta
let make = (alpha, beta) => let make = (alpha, beta) =>
alpha > 0.0 && beta > 0.0 alpha > 0.0 && beta > 0.0
? Ok(#Beta({alpha: alpha, beta: beta})) ? Ok(#Beta({alpha: alpha, beta: beta}))
: Error("Beta distribution parameters must be positive") : Error("Beta distribution parameters must be positive")
@ -103,10 +105,10 @@ module Beta = {
module Lognormal = { module Lognormal = {
type t = lognormal type t = lognormal
let make = (mu, sigma) => let make = (mu, sigma) =>
sigma > 0.0 sigma > 0.0
? Ok(#Lognormal({mu: mu, sigma: sigma})) ? Ok(#Lognormal({mu: mu, sigma: sigma}))
: Error("Lognormal standard deviation must be larger than 0") : Error("Lognormal standard deviation must be larger than 0")
let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma) let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma)
let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma) let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma)
let inv = (p, t: t) => Jstat.Lognormal.inv(p, t.mu, t.sigma) let inv = (p, t: t) => Jstat.Lognormal.inv(p, t.mu, t.sigma)
@ -127,8 +129,7 @@ module Lognormal = {
let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0) let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0)
let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5) let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5)
Ok(#Lognormal({mu: mu, sigma: sigma})) Ok(#Lognormal({mu: mu, sigma: sigma}))
} } else {
else {
Error("Lognormal standard deviation must be larger than 0") Error("Lognormal standard deviation must be larger than 0")
} }
} }
@ -154,9 +155,7 @@ module Lognormal = {
module Uniform = { module Uniform = {
type t = uniform type t = uniform
let make = (low, high) => let make = (low, high) =>
high > low high > low ? Ok(#Uniform({low: low, high: high})) : Error("High must be larger than low")
? Ok(#Uniform({low: low, high: high}))
: Error("High must be larger than low")
let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high) let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high)
let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high) let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high)
@ -165,7 +164,7 @@ module Uniform = {
let mean = (t: t) => Ok(Jstat.Uniform.mean(t.low, t.high)) let mean = (t: t) => Ok(Jstat.Uniform.mean(t.low, t.high))
let toString = ({low, high}: t) => j`Uniform($low,$high)` let toString = ({low, high}: t) => j`Uniform($low,$high)`
let truncate = (low, high, t: t): t => { let truncate = (low, high, t: t): t => {
//todo: add check //todo: add check
let newLow = max(E.O.default(neg_infinity, low), t.low) let newLow = max(E.O.default(neg_infinity, low), t.low)
let newHigh = min(E.O.default(infinity, high), t.high) let newHigh = min(E.O.default(infinity, high), t.high)
{low: newLow, high: newHigh} {low: newLow, high: newHigh}
@ -183,6 +182,15 @@ module Float = {
let toString = Js.Float.toString let toString = Js.Float.toString
} }
module From90thPercentile = {
let make = (low, high) =>
switch (low, high) {
| (low, high) if low <= 0.0 && low < high => Ok(Normal.from90PercentCI(low, high))
| (low, high) if low < high => Ok(Lognormal.from90PercentCI(low, high))
| (_, _) => Error("Low value must be less than high value.")
}
}
module T = { module T = {
let minCdfValue = 0.0001 let minCdfValue = 0.0001
let maxCdfValue = 0.9999 let maxCdfValue = 0.9999

View File

@ -8,28 +8,22 @@ let make =
( (
~pointSetDist, ~pointSetDist,
~squiggleString, ~squiggleString,
~domain=Complete,
~unit=UnspecifiedDistribution,
(), (),
) )
: t => { : t => {
let integral = pointSetDistIntegral(pointSetDist); let integral = pointSetDistIntegral(pointSetDist);
{pointSetDist, domain, integralCache: integral, unit, squiggleString}; {pointSetDist, integralCache: integral, squiggleString};
}; };
let update = let update =
( (
~pointSetDist=?, ~pointSetDist=?,
~integralCache=?, ~integralCache=?,
~domain=?,
~unit=?,
~squiggleString=?, ~squiggleString=?,
t: t, t: t,
) => { ) => {
pointSetDist: E.O.default(t.pointSetDist, pointSetDist), pointSetDist: E.O.default(t.pointSetDist, pointSetDist),
integralCache: E.O.default(t.integralCache, integralCache), integralCache: E.O.default(t.integralCache, integralCache),
domain: E.O.default(t.domain, domain),
unit: E.O.default(t.unit, unit),
squiggleString: E.O.default(t.squiggleString, squiggleString), squiggleString: E.O.default(t.squiggleString, squiggleString),
}; };
@ -38,12 +32,6 @@ let updateShape = (pointSetDist, t) => {
update(~pointSetDist, ~integralCache, t); update(~pointSetDist, ~integralCache, t);
}; };
let domainIncludedProbabilityMass = (t: t) =>
Domain.includedProbabilityMass(t.domain);
let domainIncludedProbabilityMassAdjustment = (t: t, f) =>
f *. Domain.includedProbabilityMass(t.domain);
let toPointSetDist = ({pointSetDist, _}: t) => pointSetDist; let toPointSetDist = ({pointSetDist, _}: t) => pointSetDist;
let pointSetDistFn = (fn, {pointSetDist}: t) => fn(pointSetDist); let pointSetDistFn = (fn, {pointSetDist}: t) => fn(pointSetDist);
@ -73,8 +61,7 @@ module T =
let xToY = (f, t: t) => let xToY = (f, t: t) =>
t t
|> toPointSetDist |> toPointSetDist
|> PointSetDist.T.xToY(f) |> PointSetDist.T.xToY(f);
|> MixedPoint.fmap(domainIncludedProbabilityMassAdjustment(t));
let minX = pointSetDistFn(PointSetDist.T.minX); let minX = pointSetDistFn(PointSetDist.T.minX);
let maxX = pointSetDistFn(PointSetDist.T.maxX); let maxX = pointSetDistFn(PointSetDist.T.maxX);
@ -115,7 +102,6 @@ module T =
f, f,
toPointSetDist(t), toPointSetDist(t),
) )
|> domainIncludedProbabilityMassAdjustment(t);
}; };
// TODO: This part is broken when there is a limit, if this is supposed to be taken into account. // TODO: This part is broken when there is a limit, if this is supposed to be taken into account.

View File

@ -229,6 +229,6 @@ let all = [
), ),
makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Exponentiate, dist, float)), makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Exponentiate, dist, float)),
makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)), makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)),
makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Log, dist, float)), makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Logarithm, dist, float)),
Multimodal._function, Multimodal._function,
] ]

View File

@ -12,6 +12,7 @@ type rec expressionValue =
| EvSymbol(string) | EvSymbol(string)
| EvArray(array<expressionValue>) | EvArray(array<expressionValue>)
| EvRecord(Js.Dict.t<expressionValue>) | EvRecord(Js.Dict.t<expressionValue>)
| EvDistribution(GenericDist_Types.genericDist)
type functionCall = (string, array<expressionValue>) type functionCall = (string, array<expressionValue>)
@ -38,6 +39,7 @@ let rec toString = aValue =>
->Js.String.concatMany("") ->Js.String.concatMany("")
`{${pairs}}` `{${pairs}}`
} }
| EvDistribution(dist) => `${GenericDist.toString(dist)}`
} }
let toStringWithType = aValue => let toStringWithType = aValue =>
@ -48,6 +50,7 @@ let toStringWithType = aValue =>
| EvSymbol(_) => `Symbol::${toString(aValue)}` | EvSymbol(_) => `Symbol::${toString(aValue)}`
| EvArray(_) => `Array::${toString(aValue)}` | EvArray(_) => `Array::${toString(aValue)}`
| EvRecord(_) => `Record::${toString(aValue)}` | EvRecord(_) => `Record::${toString(aValue)}`
| EvDistribution(_) => `Distribution::${toString(aValue)}`
} }
let argsToString = (args: array<expressionValue>): string => { let argsToString = (args: array<expressionValue>): string => {

View File

@ -13,13 +13,10 @@ module Sample = {
/* /*
Map external calls of Reducer Map external calls of Reducer
*/ */
let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> => let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> =>
switch call { ReducerInterface_GenericDistribution.dispatch(call) |> E.O.default(chain(call))
| ("add", [EvNumber(a), EvNumber(b)]) => Sample.customAdd(a, b)->EvNumber->Ok /*
| call => chain(call)
/*
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally. If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
The final chain(call) invokes the builtin default functions of the interpreter. The final chain(call) invokes the builtin default functions of the interpreter.
@ -35,4 +32,3 @@ Remember from the users point of view, there are no different modules:
// "doSth( constructorType2 )" // "doSth( constructorType2 )"
doSth gets dispatched to the correct module because of the type signature. You get function and operator abstraction for free. You don't need to combine different implementations into one type. That would be duplicating the repsonsibility of the dispatcher. doSth gets dispatched to the correct module because of the type signature. You get function and operator abstraction for free. You don't need to combine different implementations into one type. That would be duplicating the repsonsibility of the dispatcher.
*/ */
}

View File

@ -0,0 +1,185 @@
module ExpressionValue = ReducerInterface_ExpressionValue
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
let runGenericOperation = DistributionOperation.run(
~env={
sampleCount: 1000,
xyPointLength: 1000,
},
)
module Helpers = {
let arithmeticMap = r =>
switch r {
| "add" => #Add
| "dotAdd" => #Add
| "subtract" => #Subtract
| "dotSubtract" => #Subtract
| "divide" => #Divide
| "log" => #Logarithm
| "dotDivide" => #Divide
| "pow" => #Exponentiate
| "dotPow" => #Exponentiate
| "multiply" => #Multiply
| "dotMultiply" => #Multiply
| "dotLog" => #Logarithm
| _ => #Multiply
}
let catchAndConvertTwoArgsToDists = (args: array<expressionValue>): option<(
GenericDist_Types.genericDist,
GenericDist_Types.genericDist,
)> => {
switch args {
| [EvDistribution(a), EvDistribution(b)] => Some((a, b))
| [EvNumber(a), EvDistribution(b)] => Some((GenericDist.fromFloat(a), b))
| [EvDistribution(a), EvNumber(b)] => Some((a, GenericDist.fromFloat(b)))
| _ => None
}
}
let toFloatFn = (
fnCall: GenericDist_Types.Operation.toFloat,
dist: GenericDist_Types.genericDist,
) => {
FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some
}
let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => {
FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some
}
let twoDiststoDistFn = (direction, arithmetic, dist1, dist2) => {
FromDist(
GenericDist_Types.Operation.ToDistCombination(
direction,
arithmeticMap(arithmetic),
#Dist(dist2),
),
dist1,
)->runGenericOperation
}
}
module SymbolicConstructors = {
let oneFloat = name =>
switch name {
| "exponential" => Ok(SymbolicDist.Exponential.make)
| _ => Error("Unreachable state")
}
let twoFloat = name =>
switch name {
| "normal" => Ok(SymbolicDist.Normal.make)
| "uniform" => Ok(SymbolicDist.Uniform.make)
| "beta" => Ok(SymbolicDist.Beta.make)
| "lognormal" => Ok(SymbolicDist.Lognormal.make)
| "to" => Ok(SymbolicDist.From90thPercentile.make)
| _ => Error("Unreachable state")
}
let threeFloat = name =>
switch name {
| "triangular" => Ok(SymbolicDist.Triangular.make)
| _ => Error("Unreachable state")
}
let symbolicResultToOutput = (
symbolicResult: result<SymbolicDistTypes.symbolicDist, string>,
): option<DistributionOperation.outputType> =>
switch symbolicResult {
| Ok(r) => Some(Dist(Symbolic(r)))
| Error(r) => Some(GenDistError(Other(r)))
}
}
module Math = {
let e = 2.718281828459
}
let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
DistributionOperation.outputType,
> => {
let (fnName, args) = call
switch (fnName, args) {
| ("exponential" as fnName, [EvNumber(f1)]) =>
SymbolicConstructors.oneFloat(fnName)
->E.R.bind(r => r(f1))
->SymbolicConstructors.symbolicResultToOutput
| (
("normal" | "uniform" | "beta" | "lognormal" | "to") 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))
->SymbolicConstructors.symbolicResultToOutput
| ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist)
| ("mean", [EvDistribution(dist)]) => Helpers.toFloatFn(#Mean, dist)
| ("exp", [EvDistribution(a)]) =>
// https://mathjs.org/docs/reference/functions/exp.html
Helpers.twoDiststoDistFn(Algebraic, "pow", GenericDist.fromFloat(Math.e), a)->Some
| ("normalize", [EvDistribution(dist)]) => Helpers.toDistFn(Normalize, dist)
| ("toPointSet", [EvDistribution(dist)]) => Helpers.toDistFn(ToPointSet, 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)
| ("toSampleSet", [EvDistribution(dist), EvNumber(float)]) =>
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist)
| ("truncateLeft", [EvDistribution(dist), EvNumber(float)]) =>
Helpers.toDistFn(Truncate(Some(float), None), dist)
| ("truncateRight", [EvDistribution(dist), EvNumber(float)]) =>
Helpers.toDistFn(Truncate(None, Some(float)), dist)
| ("truncate", [EvDistribution(dist), EvNumber(float1), EvNumber(float2)]) =>
Helpers.toDistFn(Truncate(Some(float1), Some(float2)), dist)
| ("log", [EvDistribution(a)]) =>
Helpers.twoDiststoDistFn(Algebraic, "log", a, GenericDist.fromFloat(Math.e))->Some
| ("log10", [EvDistribution(a)]) =>
Helpers.twoDiststoDistFn(Algebraic, "log", a, GenericDist.fromFloat(10.0))->Some
| ("unaryMinus", [EvDistribution(a)]) =>
Helpers.twoDiststoDistFn(Algebraic, "multiply", a, GenericDist.fromFloat(-1.0))->Some
| (("add" | "multiply" | "subtract" | "divide" | "pow" | "log") as arithmetic, [a, b] as args) =>
Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) =>
Helpers.twoDiststoDistFn(Algebraic, arithmetic, fst, snd)
)
| (
("dotAdd"
| "dotMultiply"
| "dotSubtract"
| "dotDivide"
| "dotPow"
| "dotLog") as arithmetic,
[a, b] as args,
) =>
Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) =>
Helpers.twoDiststoDistFn(Pointwise, arithmetic, fst, snd)
)
| ("dotLog", [EvDistribution(a)]) =>
Helpers.twoDiststoDistFn(Pointwise, "dotLog", a, GenericDist.fromFloat(Math.e))->Some
| ("dotExp", [EvDistribution(a)]) =>
Helpers.twoDiststoDistFn(Pointwise, "dotPow", GenericDist.fromFloat(Math.e), a)->Some
| _ => None
}
}
let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
expressionValue,
Reducer_ErrorValue.errorValue,
> =>
switch o {
| Dist(d) => Ok(ReducerInterface_ExpressionValue.EvDistribution(d))
| Float(d) => Ok(EvNumber(d))
| String(d) => Ok(EvString(d))
| GenDistError(NotYetImplemented) => Error(RETodo("Function not yet implemented"))
| GenDistError(Unreachable) => Error(RETodo("Unreachable"))
| GenDistError(DistributionVerticalShiftIsInvalid) =>
Error(RETodo("Distribution Vertical Shift is Invalid"))
| GenDistError(Other(s)) => Error(RETodo(s))
}
let dispatch = call => {
dispatchToGenericOutput(call)->E.O2.fmap(genericOutputToReducerValue)
}

View File

@ -0,0 +1,3 @@
let dispatch: ReducerInterface_ExpressionValue.functionCall => option<
result<ReducerInterface_ExpressionValue.expressionValue, Reducer_ErrorValue.errorValue>,
>

View File

@ -100,6 +100,7 @@ module O = {
module O2 = { module O2 = {
let default = (a, b) => O.default(b, a) let default = (a, b) => O.default(b, a)
let toExn = (a, b) => O.toExn(b, a) let toExn = (a, b) => O.toExn(b, a)
let fmap = (a, b) => O.fmap(b, a)
} }
/* Functions */ /* Functions */

View File

@ -7,11 +7,11 @@ type algebraicOperation = [
| #Subtract | #Subtract
| #Divide | #Divide
| #Exponentiate | #Exponentiate
| #Log | #Logarithm
] ]
@genType @genType
type pointwiseOperation = [#Add | #Multiply | #Exponentiate] type pointwiseOperation = [#Add | #Multiply | #Exponentiate]
type scaleOperation = [#Multiply | #Exponentiate | #Log | #Divide] type scaleOperation = [#Multiply | #Exponentiate | #Logarithm | #Divide]
type distToFloatOperation = [ type distToFloatOperation = [
| #Pdf(float) | #Pdf(float)
| #Cdf(float) | #Cdf(float)
@ -29,7 +29,7 @@ module Algebraic = {
| #Multiply => \"*." | #Multiply => \"*."
| #Exponentiate => \"**" | #Exponentiate => \"**"
| #Divide => \"/." | #Divide => \"/."
| #Log => (a, b) => log(a) /. log(b) | #Logarithm => (a, b) => log(a) /. log(b)
} }
let applyFn = (t, f1, f2) => let applyFn = (t, f1, f2) =>
@ -45,7 +45,7 @@ module Algebraic = {
| #Multiply => "*" | #Multiply => "*"
| #Exponentiate => "**" | #Exponentiate => "**"
| #Divide => "/" | #Divide => "/"
| #Log => "log" | #Logarithm => "log"
} }
let format = (a, b, c) => b ++ (" " ++ (toString(a) ++ (" " ++ c))) let format = (a, b, c) => b ++ (" " ++ (toString(a) ++ (" " ++ c)))
@ -84,7 +84,7 @@ module Scale = {
| #Multiply => \"*." | #Multiply => \"*."
| #Divide => \"/." | #Divide => \"/."
| #Exponentiate => \"**" | #Exponentiate => \"**"
| #Log => (a, b) => log(a) /. log(b) | #Logarithm => (a, b) => log(a) /. log(b)
} }
let format = (operation: t, value, scaleBy) => let format = (operation: t, value, scaleBy) =>
@ -92,7 +92,7 @@ module Scale = {
| #Multiply => j`verticalMultiply($value, $scaleBy) ` | #Multiply => j`verticalMultiply($value, $scaleBy) `
| #Divide => j`verticalDivide($value, $scaleBy) ` | #Divide => j`verticalDivide($value, $scaleBy) `
| #Exponentiate => j`verticalExponentiate($value, $scaleBy) ` | #Exponentiate => j`verticalExponentiate($value, $scaleBy) `
| #Log => j`verticalLog($value, $scaleBy) ` | #Logarithm => j`verticalLog($value, $scaleBy) `
} }
let toIntegralSumCacheFn = x => let toIntegralSumCacheFn = x =>
@ -100,7 +100,7 @@ module Scale = {
| #Multiply => (a, b) => Some(a *. b) | #Multiply => (a, b) => Some(a *. b)
| #Divide => (a, b) => Some(a /. b) | #Divide => (a, b) => Some(a /. b)
| #Exponentiate => (_, _) => None | #Exponentiate => (_, _) => None
| #Log => (_, _) => None | #Logarithm => (_, _) => None
} }
let toIntegralCacheFn = x => let toIntegralCacheFn = x =>
@ -108,7 +108,7 @@ module Scale = {
| #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy) | #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy)
| #Divide => (_, _) => None | #Divide => (_, _) => None
| #Exponentiate => (_, _) => None | #Exponentiate => (_, _) => None
| #Log => (_, _) => None | #Logarithm => (_, _) => None
} }
} }

View File

@ -1,4 +1,22 @@
open PointSetTypes @genType
type xyShape = {
xs: array<float>,
ys: array<float>,
}
@genType
type interpolationStrategy = [
| #Stepwise
| #Linear
]
@genType
type extrapolationStrategy = [
| #UseZero
| #UseOutermostPoints
]
type interpolator = (xyShape, int, float) => float
let interpolate = (xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float): float => { let interpolate = (xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float): float => {
let minProportion = (xMax -. xIntended) /. (xMax -. xMin) let minProportion = (xMax -. xIntended) /. (xMax -. xMin)
@ -25,6 +43,7 @@ module T = {
let xTotalRange = (t: t) => maxX(t) -. minX(t) let xTotalRange = (t: t) => maxX(t) -. minX(t)
let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys} let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys}
let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)} let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)}
let square = mapX(x => x ** 2.0)
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys) let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys)
let fromArray = ((xs, ys)): t => {xs: xs, ys: ys} let fromArray = ((xs, ys)): t => {xs: xs, ys: ys}
let fromArrays = (xs, ys): t => {xs: xs, ys: ys} let fromArrays = (xs, ys): t => {xs: xs, ys: ys}
@ -126,8 +145,8 @@ module XtoY = {
/* Returns a between-points-interpolating function that can be used with PointwiseCombination.combine. /* Returns a between-points-interpolating function that can be used with PointwiseCombination.combine.
Interpolation can either be stepwise (using the value on the left) or linear. Extrapolation can be `UseZero or `UseOutermostPoints. */ Interpolation can either be stepwise (using the value on the left) or linear. Extrapolation can be `UseZero or `UseOutermostPoints. */
let continuousInterpolator = ( let continuousInterpolator = (
interpolation: PointSetTypes.interpolationStrategy, interpolation: interpolationStrategy,
extrapolation: PointSetTypes.extrapolationStrategy, extrapolation: extrapolationStrategy,
): interpolator => ): interpolator =>
switch (interpolation, extrapolation) { switch (interpolation, extrapolation) {
| (#Linear, #UseZero) => | (#Linear, #UseZero) =>
@ -392,49 +411,9 @@ let logScorePoint = (sampleCount, t1, t2) =>
|> E.O.fmap(Pairs.y) |> E.O.fmap(Pairs.y)
module Analysis = { module Analysis = {
let integrateContinuousShape = (
~indefiniteIntegralStepwise=(p, h1) => h1 *. p,
~indefiniteIntegralLinear=(p, a, b) => a *. p +. b *. p ** 2.0 /. 2.0,
t: PointSetTypes.continuousShape,
): float => {
let xs = t.xyShape.xs
let ys = t.xyShape.ys
E.A.reducei(xs, 0.0, (acc, _x, i) => {
let areaUnderIntegral = // TODO Take this switch statement out of the loop body
switch (t.interpolation, i) {
| (_, 0) => 0.0
| (#Stepwise, _) =>
indefiniteIntegralStepwise(xs[i], ys[i - 1]) -.
indefiniteIntegralStepwise(xs[i - 1], ys[i - 1])
| (#Linear, _) =>
let x1 = xs[i - 1]
let x2 = xs[i]
if x1 == x2 {
0.0
} else {
let h1 = ys[i - 1]
let h2 = ys[i]
let b = (h1 -. h2) /. (x1 -. x2)
let a = h1 -. b *. x1
indefiniteIntegralLinear(x2, a, b) -. indefiniteIntegralLinear(x1, a, b)
}
}
acc +. areaUnderIntegral
})
}
let getMeanOfSquaresContinuousShape = (t: PointSetTypes.continuousShape) => {
let indefiniteIntegralLinear = (p, a, b) => a *. p ** 3.0 /. 3.0 +. b *. p ** 4.0 /. 4.0
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 3.0 /. 3.0
integrateContinuousShape(~indefiniteIntegralStepwise, ~indefiniteIntegralLinear, t)
}
let getVarianceDangerously = (t: 't, mean: 't => float, getMeanOfSquares: 't => float): float => { let getVarianceDangerously = (t: 't, mean: 't => float, getMeanOfSquares: 't => float): float => {
let meanSquared = mean(t) ** 2.0 let meanSquared = mean(t) ** 2.0
let meanOfSquares = getMeanOfSquares(t) let meanOfSquares = getMeanOfSquares(t)
meanOfSquares -. meanSquared meanOfSquares -. meanSquared
} }
let squareXYShape = T.mapX(x => x ** 2.0)
} }

View File

@ -1,154 +0,0 @@
type domainLimit = {
xPoint: float,
excludingProbabilityMass: float,
}
type domain =
| Complete
| LeftLimited(domainLimit)
| RightLimited(domainLimit)
| LeftAndRightLimited(domainLimit, domainLimit)
type distributionType = [
| #PDF
| #CDF
]
type xyShape = {
xs: array<float>,
ys: array<float>,
}
type interpolationStrategy = [
| #Stepwise
| #Linear
]
type extrapolationStrategy = [
| #UseZero
| #UseOutermostPoints
]
type interpolator = (xyShape, int, float) => float
type rec continuousShape = {
xyShape: xyShape,
interpolation: interpolationStrategy,
integralSumCache: option<float>,
integralCache: option<continuousShape>,
}
type discreteShape = {
xyShape: xyShape,
integralSumCache: option<float>,
integralCache: option<continuousShape>,
}
type mixedShape = {
continuous: continuousShape,
discrete: discreteShape,
integralSumCache: option<float>,
integralCache: option<continuousShape>,
}
type pointSetDistMonad<'a, 'b, 'c> =
| Mixed('a)
| Discrete('b)
| Continuous('c)
@genType
type pointSetDist = pointSetDistMonad<mixedShape, discreteShape, continuousShape>
module ShapeMonad = {
let fmap = (t: pointSetDistMonad<'a, 'b, 'c>, (fn1, fn2, fn3)): pointSetDistMonad<'d, 'e, 'f> =>
switch t {
| Mixed(m) => Mixed(fn1(m))
| Discrete(m) => Discrete(fn2(m))
| Continuous(m) => Continuous(fn3(m))
}
}
type generationSource =
| SquiggleString(string)
| Shape(pointSetDist)
type distributionUnit =
| UnspecifiedDistribution
@genType
type distPlus = {
pointSetDist: pointSetDist,
domain: domain,
integralCache: continuousShape,
unit: distributionUnit,
squiggleString: option<string>,
}
module DistributionUnit = {
let toJson = (distributionUnit: distributionUnit) =>
switch distributionUnit {
| _ => Js.Null.fromOption(None)
}
}
module Domain = {
let excludedProbabilityMass = (t: domain) =>
switch t {
| Complete => 0.0
| LeftLimited({excludingProbabilityMass}) => excludingProbabilityMass
| RightLimited({excludingProbabilityMass}) => excludingProbabilityMass
| LeftAndRightLimited({excludingProbabilityMass: l}, {excludingProbabilityMass: r}) => l +. r
}
let includedProbabilityMass = (t: domain) => 1.0 -. excludedProbabilityMass(t)
let initialProbabilityMass = (t: domain) =>
switch t {
| Complete
| RightLimited(_) => 0.0
| LeftLimited({excludingProbabilityMass}) => excludingProbabilityMass
| LeftAndRightLimited({excludingProbabilityMass}, _) => excludingProbabilityMass
}
let normalizeProbabilityMass = (t: domain) => 1. /. excludedProbabilityMass(t)
let yPointToSubYPoint = (t: domain, yPoint) =>
switch t {
| Complete => Some(yPoint)
| LeftLimited({excludingProbabilityMass}) if yPoint < excludingProbabilityMass => None
| LeftLimited({excludingProbabilityMass}) if yPoint >= excludingProbabilityMass =>
Some((yPoint -. excludingProbabilityMass) /. includedProbabilityMass(t))
| RightLimited({excludingProbabilityMass}) if yPoint > 1. -. excludingProbabilityMass => None
| RightLimited({excludingProbabilityMass}) if yPoint <= 1. -. excludingProbabilityMass =>
Some(yPoint /. includedProbabilityMass(t))
| LeftAndRightLimited({excludingProbabilityMass: l}, _) if yPoint < l => None
| LeftAndRightLimited(_, {excludingProbabilityMass: r}) if yPoint > 1.0 -. r => None
| LeftAndRightLimited({excludingProbabilityMass: l}, _) =>
Some((yPoint -. l) /. includedProbabilityMass(t))
| _ => None
}
}
type mixedPoint = {
continuous: float,
discrete: float,
}
module MixedPoint = {
type t = mixedPoint
let toContinuousValue = (t: t) => t.continuous
let toDiscreteValue = (t: t) => t.discrete
let makeContinuous = (continuous: float): t => {continuous: continuous, discrete: 0.0}
let makeDiscrete = (discrete: float): t => {continuous: 0.0, discrete: discrete}
let fmap = (fn: float => float, t: t) => {
continuous: fn(t.continuous),
discrete: fn(t.discrete),
}
let combine2 = (fn, c: t, d: t): t => {
continuous: fn(c.continuous, d.continuous),
discrete: fn(c.discrete, d.discrete),
}
let add = combine2((a, b) => a +. b)
}

18069
yarn.lock Normal file

File diff suppressed because it is too large Load Diff