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
uses: fkirc/skip-duplicate-actions@master
with:
paths: '["packages/squiggle-lang/*"]'
paths: '["packages/squiggle-lang/**"]'
- id: skip_components_check
name: Check if the changes are about components src files
uses: fkirc/skip-duplicate-actions@master
with:
paths: '["packages/components/*"]'
paths: '["packages/components/**"]'
- id: skip_website_check
name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@master
with:
paths: '["packages/website/*"]'
paths: '["packages/website/**"]'
lang-build-test:
name: Language build and test

1
.gitignore vendored
View File

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

View File

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

View File

@ -4,7 +4,7 @@ const custom = require('../webpack.config.js');
module.exports = {
webpackFinal: async (config) => {
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": [
"../src/**/*.stories.mdx",

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@quri/squiggle-components",
"version": "0.1.6",
"version": "0.1.8",
"dependencies": {
"@quri/squiggle-lang": "0.2.2",
"@testing-library/jest-dom": "^5.16.3",
@ -9,14 +9,16 @@
"@types/jest": "^27.4.0",
"@types/lodash": "^4.14.178",
"@types/node": "^17.0.16",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"antd": "^4.19.3",
"cross-env": "^7.0.3",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-ace": "^9.5.0",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"react-vega": "^7.4.4",
"styled-components": "^5.3.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.3",
"vega": "^5.21.0",
@ -27,7 +29,7 @@
},
"scripts": {
"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",
"all": "yarn bundle && yarn build"
},
@ -68,9 +70,10 @@
"@storybook/node-logger": "^6.4.18",
"@storybook/preset-create-react-app": "^4.0.0",
"@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",
"react-codejar": "^1.1.2",
"style-loader": "^3.3.1",
"ts-loader": "^9.2.8",
"webpack": "^5.70.0",
"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 * as chartSpecification from "./spec-distributions.json";
import * as percentilesSpec from "./spec-percentiles.json";
import { NumberShower } from "./NumberShower";
import styled from "styled-components";
let SquiggleVegaChart = createClassFromSpec({
spec: chartSpecification as Spec
spec: chartSpecification as Spec,
});
let SquigglePercentilesChart = createClassFromSpec({
spec: percentilesSpec as Spec
spec: percentilesSpec as Spec,
});
export interface SquiggleChartProps {
/** The input string for squiggle */
squiggleString: string;
squiggleString?: string;
/** If the output requires monte carlo sampling, the amount of samples */
sampleCount?: number;
@ -40,24 +42,58 @@ export interface SquiggleChartProps {
environment?: exportEnv;
/** When the environment changes */
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 = {
sampleCount: props.sampleCount,
outputXYPoints: props.outputXYPoints,
kernelWidth: props.kernelWidth,
pointDistLength: props.pointDistLength,
sampleCount: sampleCount,
outputXYPoints: outputXYPoints,
kernelWidth: kernelWidth,
pointDistLength: pointDistLength,
};
let result = run(props.squiggleString, samplingInputs, props.environment);
let result = run(squiggleString, samplingInputs, environment);
if (result.tag === "Ok") {
let environment = result.value.environment;
let exports = result.value.exports;
if (props.onEnvChange) props.onEnvChange(environment);
onEnvChange(environment);
let chartResults = exports.map((chartResult: exportDistribution) => {
if (chartResult["NAME"] === "Float") {
return <MakeNumberShower precision={3} number={chartResult["VAL"]} />;
return <NumberShower precision={3} number={chartResult["VAL"]} />;
} else if (chartResult["NAME"] === "DistPlus") {
let shape = chartResult.VAL.pointSetDist;
if (shape.tag === "Continuous") {
@ -74,7 +110,14 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
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") {
let xyShape = shape.value.xyShape;
let totalY = xyShape.ys.reduce((a, b) => a + b);
@ -89,7 +132,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
y: y,
}));
return <SquiggleVegaChart data={{ dis: values }} actions={false}/>;
return <SquiggleVegaChart data={{ dis: values }} actions={false} />;
} else if (shape.tag === "Mixed") {
let discreteShape = shape.value.discrete.xyShape;
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
@ -123,10 +166,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
let total = 0;
let cdf = sortedPoints.map((point: labeledPoint) => {
if (point.type == "discrete") {
if (point.type === "discrete") {
total += point.y;
return total;
} else if (point.type == "continuous") {
} else if (point.type === "continuous") {
total += (point.y / totalY) * totalContinuous;
return total;
}
@ -147,10 +190,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
})
);
let continuousValues = cdfLabeledPoint.filter(
(x) => x.type == "continuous"
(x) => x.type === "continuous"
);
let discreteValues = cdfLabeledPoint.filter(
(x) => x.type == "discrete"
(x) => x.type === "discrete"
);
return (
@ -162,14 +205,14 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
}
} else if (chartResult.NAME === "Function") {
// We are looking at a function. In this case, we draw a Percentiles chart
let start = props.diagramStart ? props.diagramStart : 0;
let stop = props.diagramStop ? props.diagramStop : 10;
let count = props.diagramCount ? props.diagramCount : 100;
let start = diagramStart;
let stop = diagramStop;
let count = diagramCount;
let step = (stop - start) / count;
let data = _.range(start, stop, step).map((x) => {
if (chartResult.NAME == "Function") {
if (chartResult.NAME === "Function") {
let result = chartResult.VAL(x);
if (result.tag == "Ok") {
if (result.tag === "Ok") {
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.99,
@ -196,22 +239,28 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
return null;
}
});
return <SquigglePercentilesChart
data={{ facet: data.filter(x => x !== null) }}
return (
<SquigglePercentilesChart
data={{ facet: data.filter((x) => x !== null) }}
actions={false}
/>;
/>
);
}
});
return <>{chartResults}</>;
} else if (result.tag == "Error") {
} else if (result.tag === "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>;
};
function getPercentiles(percentiles: number[], t: DistPlus) {
if (t.pointSetDist.tag == "Discrete") {
if (t.pointSetDist.tag === "Discrete") {
let total = 0;
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
let bounds = percentiles.map((_) => maxX);
@ -221,14 +270,14 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
(x, y) => {
total += y;
percentiles.forEach((v, i) => {
if (total > v && bounds[i] == maxX) {
if (total > v && bounds[i] === maxX) {
bounds[i] = x;
}
});
}
);
return bounds;
} else if (t.pointSetDist.tag == "Continuous") {
} else if (t.pointSetDist.tag === "Continuous") {
let total = 0;
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
let totalY = _.sum(t.pointSetDist.value.xyShape.ys);
@ -239,14 +288,14 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
(x, y) => {
total += y / totalY;
percentiles.forEach((v, i) => {
if (total > v && bounds[i] == maxX) {
if (total > v && bounds[i] === maxX) {
bounds[i] = x;
}
});
}
);
return bounds;
} else if (t.pointSetDist.tag == "Mixed") {
} else if (t.pointSetDist.tag === "Mixed") {
let discreteShape = t.pointSetDist.value.discrete.xyShape;
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 bounds = percentiles.map((_) => maxX);
sortedPoints.map((point: labeledPoint) => {
if (point.type == "discrete") {
if (point.type === "discrete") {
total += point.y;
} else if (point.type == "continuous") {
} else if (point.type === "continuous") {
total += (point.y / totalY) * totalContinuous;
}
percentiles.forEach((v, i) => {
if (total > v && bounds[i] == maxX) {
if (total > v && bounds[i] === maxX) {
bounds[i] = total;
}
});
@ -295,91 +344,3 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
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 ReactDOM from "react-dom";
import { SquiggleChart } from "./SquiggleChart";
import { ReactCodeJar } from "react-codejar";
import { CodeEditor } from "./CodeEditor";
import type { exportEnv } from "@quri/squiggle-lang";
import styled from 'styled-components'
export interface SquiggleEditorProps {
/** The input string for squiggle */
@ -23,67 +24,57 @@ export interface SquiggleEditorProps {
environment?: exportEnv;
/** when the environment changes. Used again for notebook magic*/
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 {
expression: string;
env: exportEnv;
}
export class SquiggleEditor extends React.Component<
SquiggleEditorProps,
SquiggleEditorState
> {
constructor(props: SquiggleEditorProps) {
super(props);
let code = props.initialSquiggleString ? props.initialSquiggleString : "";
this.state = { expression: code, env: props.environment };
}
render() {
let { expression, env } = this.state;
let props = this.props;
return (
<div>
<ReactCodeJar
code={expression}
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}
export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
initialSquiggleString = "",
width = 500,
sampleCount,
outputXYPoints,
kernelWidth,
pointDistLength,
diagramStart,
diagramStop,
diagramCount,
onEnvChange,
environment,
}: SquiggleEditorProps) => {
let [expression, setExpression] = React.useState(initialSquiggleString);
return (
<div>
<Input>
<CodeEditor
value={expression}
onChange={setExpression}
oneLine={true}
/>
<SquiggleChart
squiggleString={expression}
sampleCount={props.sampleCount}
outputXYPoints={props.outputXYPoints}
kernelWidth={props.kernelWidth}
pointDistLength={props.pointDistLength}
diagramStart={props.diagramStart}
diagramStop={props.diagramStop}
diagramCount={props.diagramCount}
environment={env}
onEnvChange={props.onEnvChange}
/>
</div>
);
}
}
</Input>
<SquiggleChart
width={width}
squiggleString={expression}
sampleCount={sampleCount}
outputXYPoints={outputXYPoints}
kernelWidth={kernelWidth}
pointDistLength={pointDistLength}
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
environment={environment}
onEnvChange={onEnvChange}
/>
</div>
);
};
export function renderSquiggleEditor(props: SquiggleEditorProps) {
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
let parent = document.createElement("div");
ReactDOM.render(
<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 { 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",
"description": "A basic area chart example.",
"description": "A basic area chart example",
"width": 500,
"height": 200,
"height": 100,
"padding": 5,
"data": [{ "name": "con" }, { "name": "dis" }],
"signals": [
"data": [
{
"name": "mousex",
"description": "x position of mouse",
"update": "0",
"on": [{ "events": "mousemove", "update": "1-x()/width" }]
"name": "con"
},
{
"name": "dis"
}
],
"signals": [
{
"name": "xscale",
"description": "The transform of the x scale",
"value": 1.0,
"value": false,
"bind": {
"input": "range",
"min": 0.1,
"max": 1
"input": "checkbox",
"name": "log x scale"
}
},
{
"name": "yscale",
"description": "The transform of the y scale",
"value": 1.0,
"value": false,
"bind": {
"input": "range",
"min": 0.1,
"max": 1
"input": "checkbox",
"name": "log y scale"
}
}
],
"scales": [
{
"name": "xscale",
"type": "pow",
"exponent": { "signal": "xscale" },
"exponent": {
"signal": "xscale ? 0.1 : 1"
},
"range": "width",
"zero": false,
"nice": false,
"domain": {
"fields": [
{ "data": "con", "field": "x" },
{ "data": "dis", "field": "x" }
{
"data": "con",
"field": "x"
},
{
"data": "dis",
"field": "x"
}
]
}
},
{
"name": "yscale",
"type": "pow",
"exponent": { "signal": "yscale" },
"exponent": {
"signal": "yscale ? 0.1 : 1"
},
"range": "height",
"nice": true,
"zero": true,
"domain": {
"fields": [
{ "data": "con", "field": "y" },
{ "data": "dis", "field": "y" }
{
"data": "con",
"field": "y"
},
{
"data": "dis",
"field": "y"
}
]
}
}
],
"axes": [
{ "orient": "bottom", "scale": "xscale", "tickCount": 20 },
{ "orient": "left", "scale": "yscale" }
{
"orient": "bottom",
"scale": "xscale",
"labelColor": "#666",
"tickColor": "#ddd",
"format": "~s",
"tickCount": 20
}
],
"marks": [
{
"type": "area",
"from": { "data": "con" },
"from": {
"data": "con"
},
"encode": {
"enter": {
"tooltip": { "signal": "datum.cdf" }
},
"update": {
"x": { "scale": "xscale", "field": "x" },
"y": { "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: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#1b6fac'}, {offset: 1.0, color: '#1b6fac'} ] }",
"color": "#000"
"x": {
"scale": "xscale",
"field": "x"
},
"interpolate": { "value": "monotone" },
"fillOpacity": { "value": 1 }
"y": {
"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",
"from": { "data": "dis" },
"from": {
"data": "dis"
},
"encode": {
"enter": {
"y2": { "scale": "yscale", "value": 0 },
"width": { "value": 1 }
"y2": {
"scale": "yscale",
"value": 0
},
"width": {
"value": 1
}
},
"update": {
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "y" }
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "y"
}
}
}
},
{
"type": "symbol",
"from": { "data": "dis" },
"from": {
"data": "dis"
},
"encode": {
"enter": {
"shape": { "value": "circle" },
"width": { "value": 5 },
"tooltip": { "signal": "datum.y" }
"shape": {
"value": "circle"
},
"width": {
"value": 5
},
"tooltip": {
"signal": "datum.y"
}
},
"update": {
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "y" }
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "y"
},
"fill": {
"value": "#1e4577"
}
}
}
}

View File

@ -7,7 +7,12 @@
{
"name": "facet",
"values": [],
"format": { "type": "json", "parse": { "timestamp": "date" } }
"format": {
"type": "json",
"parse": {
"timestamp": "date"
}
}
},
{
"name": "table",
@ -70,7 +75,10 @@
"name": "xscale",
"type": "linear",
"nice": true,
"domain": { "data": "facet", "field": "x" },
"domain": {
"data": "facet",
"field": "x"
},
"range": "width"
},
{
@ -79,7 +87,10 @@
"range": "height",
"nice": true,
"zero": true,
"domain": { "data": "facet", "field": "p99" }
"domain": {
"data": "facet",
"field": "p99"
}
}
],
"axes": [
@ -89,8 +100,20 @@
"grid": false,
"tickSize": 2,
"encode": {
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
"grid": {
"enter": {
"stroke": {
"value": "#ccc"
}
}
},
"ticks": {
"enter": {
"stroke": {
"value": "#ccc"
}
}
}
}
},
{
@ -100,107 +123,249 @@
"domain": false,
"tickSize": 2,
"encode": {
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
"grid": {
"enter": {
"stroke": {
"value": "#ccc"
}
}
},
"ticks": {
"enter": {
"stroke": {
"value": "#ccc"
}
}
}
}
}
],
"marks": [
{
"type": "area",
"from": { "data": "table" },
"from": {
"data": "table"
},
"encode": {
"enter": { "fill": { "value": "#4C78A8" } },
"enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": {
"interpolate": { "value": "monotone" },
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "p1" },
"y2": { "scale": "yscale", "field": "p99" },
"opacity": { "value": 0.05 }
"interpolate": {
"value": "monotone"
},
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p1"
},
"y2": {
"scale": "yscale",
"field": "p99"
},
"opacity": {
"value": 0.05
}
}
}
},
{
"type": "area",
"from": { "data": "table" },
"from": {
"data": "table"
},
"encode": {
"enter": { "fill": { "value": "#4C78A8" } },
"enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": {
"interpolate": { "value": "monotone" },
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "p5" },
"y2": { "scale": "yscale", "field": "p95" },
"opacity": { "value": 0.1 }
"interpolate": {
"value": "monotone"
},
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p5"
},
"y2": {
"scale": "yscale",
"field": "p95"
},
"opacity": {
"value": 0.1
}
}
}
},
{
"type": "area",
"from": { "data": "table" },
"from": {
"data": "table"
},
"encode": {
"enter": { "fill": { "value": "#4C78A8" } },
"enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": {
"interpolate": { "value": "monotone" },
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "p10" },
"y2": { "scale": "yscale", "field": "p90" },
"opacity": { "value": 0.15 }
"interpolate": {
"value": "monotone"
},
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p10"
},
"y2": {
"scale": "yscale",
"field": "p90"
},
"opacity": {
"value": 0.15
}
}
}
},
{
"type": "area",
"from": { "data": "table" },
"from": {
"data": "table"
},
"encode": {
"enter": { "fill": { "value": "#4C78A8" } },
"enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": {
"interpolate": { "value": "monotone" },
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "p20" },
"y2": { "scale": "yscale", "field": "p80" },
"opacity": { "value": 0.2 }
"interpolate": {
"value": "monotone"
},
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p20"
},
"y2": {
"scale": "yscale",
"field": "p80"
},
"opacity": {
"value": 0.2
}
}
}
},
{
"type": "area",
"from": { "data": "table" },
"from": {
"data": "table"
},
"encode": {
"enter": { "fill": { "value": "#4C78A8" } },
"enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": {
"interpolate": { "value": "monotone" },
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "p30" },
"y2": { "scale": "yscale", "field": "p70" },
"opacity": { "value": 0.2 }
"interpolate": {
"value": "monotone"
},
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p30"
},
"y2": {
"scale": "yscale",
"field": "p70"
},
"opacity": {
"value": 0.2
}
}
}
},
{
"type": "area",
"from": { "data": "table" },
"from": {
"data": "table"
},
"encode": {
"enter": { "fill": { "value": "#4C78A8" } },
"enter": {
"fill": {
"value": "#4C78A8"
}
},
"update": {
"interpolate": { "value": "monotone" },
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "p40" },
"y2": { "scale": "yscale", "field": "p60" },
"opacity": { "value": 0.2 }
"interpolate": {
"value": "monotone"
},
"x": {
"scale": "xscale",
"field": "x"
},
"y": {
"scale": "yscale",
"field": "p40"
},
"y2": {
"scale": "yscale",
"field": "p60"
},
"opacity": {
"value": 0.2
}
}
}
},
{
"type": "line",
"from": { "data": "table" },
"from": {
"data": "table"
},
"encode": {
"update": {
"interpolate": { "value": "monotone" },
"stroke": { "value": "#4C78A8" },
"strokeWidth": { "value": 2 },
"opacity": { "value": 0.8 },
"x": { "scale": "xscale", "field": "x" },
"y": { "scale": "yscale", "field": "p50" }
"interpolate": {
"value": "monotone"
},
"stroke": {
"value": "#4C78A8"
},
"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" />
This is the component library for Squiggle. All of these components are react
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.
This is the component library for Squiggle. These are React
components, and can be used in any application that you see fit.

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
An example of a normal distribution is:
### Continuous Distributions
<Canvas>
<Story
@ -31,26 +31,26 @@ An example of a normal distribution is:
</Story>
</Canvas>
An example of a Discrete distribution is:
### Discrete Distributions
<Canvas>
<Story
name="Discrete"
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({})}
</Story>
</Canvas>
An example of a Mixed distribution is:
## Mixed distributions
<Canvas>
<Story
name="Mixed"
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({})}
@ -66,7 +66,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Constant"
args={{
squiggleString: "500000 * 5000000",
squiggleString: "500000000",
}}
>
{Template.bind({})}
@ -75,14 +75,28 @@ to allow large and small numbers being printed cleanly.
## Functions
Finally, a function can be returned, and this shows how the distribution changes
over the axis between x = 0 and 10.
Full functions can be returned. These plot out the results of distributions between a set of x-coordinates.
The default is show 10 points between 0 and 10.
<Canvas>
<Story
name="Function"
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({})}

View File

@ -20,3 +20,16 @@ the distribution.
{Template.bind({})}
</Story>
</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,
"sourceMap": true
},
"files": ["src/spec-distributions.json","src/spec-percentiles.json"],
"files": ["src/spec-distributions.json", "src/spec-percentiles.json"],
"target": "ES6",
"include": ["src/**/*", "src/*"],
"exclude": ["node_modules", "**/*.spec.ts"],
"exclude": ["node_modules", "**/*.spec.ts", "webpack.config.js"],
"references": [
{
"path": "../squiggle-lang"

View File

@ -12,12 +12,16 @@ module.exports = {
options: { projectReferences: true },
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
resolve: {
extensions: [".js", ".tsx", ".ts"],
alias: {
"@quri/squiggle-lang": path.resolve(__dirname, '../squiggle-lang/src/js')
"@quri/squiggle-lang": path.resolve(__dirname, "../squiggle-lang/src/js"),
},
},
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.js
dist
*.coverage
_coverage

View File

@ -13,6 +13,9 @@ Other:
yarn start # listens to files and recompiles at every mutation
yarn test
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

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",
"reason": {},
"sources": [
{
"dir": "src/rescript",
@ -28,7 +27,8 @@
"bs-dependencies": [
"@glennsl/rescript-jest",
"@glennsl/bs-json",
"rationale"
"rationale",
"bisect_ppx"
],
"gentypeconfig": {
"language": "typescript",
@ -41,7 +41,13 @@
},
"refmt": 3,
"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 = {
preset: 'ts-jest',
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": "jest",
"test:watch": "jest --watchAll",
"all": "yarn build && yarn bundle && yarn test",
"rescript:format": "find . -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;"
"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 {}' \\;",
"all": "yarn build && yarn bundle && yarn test"
},
"keywords": [
"Rescript"
@ -25,12 +26,12 @@
"mathjs": "10.4.1",
"pdfast": "^0.2.0",
"rationale": "0.2.0",
"rescript": "^9.1.4"
"rescript": "^9.1.4",
"bisect_ppx": "^2.7.1"
},
"devDependencies": {
"@glennsl/rescript-jest": "^0.9.0",
"@types/jest": "^27.4.0",
"@types/webpack": "^5.28.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"docsify": "^4.12.2",
"gentype": "^4.3.0",

View File

@ -1,7 +1,7 @@
import {runAll} 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 {t as DistPlus} from '../rescript/pointSetDist/DistPlus.gen';
export type {t as DistPlus} from '../rescript/OldInterpreter/DistPlus.gen';
export let defaultSamplingInputs : SamplingInputs = {
sampleCount : 10000,

View File

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

View File

@ -26,7 +26,9 @@ module Output: {
let toDist: t => option<GenericDist_Types.genericDist>
let toDistR: t => result<GenericDist_Types.genericDist, GenericDist_Types.error>
let toFloat: t => option<float>
let toFloatR: t => result<float, GenericDist_Types.error>
let toString: t => option<string>
let toStringR: t => result<string, GenericDist_Types.error>
let toError: t => option<GenericDist_Types.error>
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> => {
let m = switch arithmeticOperation {
| #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid)
| (#Multiply | #Divide | #Exponentiate | #Log) as arithmeticOperation =>
| (#Multiply | #Divide | #Exponentiate | #Logarithm) as arithmeticOperation =>
toPointSetFn(t)->E.R2.fmap(t => {
//TODO: Move to PointSet codebase
let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary)

View File

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

View File

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

View File

@ -115,16 +115,17 @@ let combineShapesContinuousContinuous = (
| #Multiply => (m1, m2) => m1 *. m2
| #Divide => (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)
// 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
let combineVariancesFn = switch op {
| #Add => (v1, v2, _, _) => v1 +. v2
| #Subtract => (v1, v2, _, _) => v1 +. v2
| #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.
| #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.
}
@ -233,7 +234,7 @@ let combineShapesContinuousDiscrete = (
}
| #Multiply
| #Exponentiate
| #Log
| #Logarithm
| #Divide =>
for j in 0 to t2n - 1 {
// 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
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 interpolation = (t: t) => t.interpolation
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 indefiniteIntegralLinear = (p, a, b) => a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0
XYShape.Analysis.integrateContinuousShape(
Analysis.integrate(
~indefiniteIntegralStepwise,
~indefiniteIntegralLinear,
t,
@ -204,7 +245,7 @@ module T = Dist({
XYShape.Analysis.getVarianceDangerously(
t,
mean,
XYShape.Analysis.getMeanOfSquaresContinuousShape,
Analysis.getMeanOfSquares,
)
})

View File

@ -209,8 +209,9 @@ module T = Dist({
let s = getShape(t)
E.A.reducei(s.xs, 0.0, (acc, x, i) => acc +. x *. s.ys[i])
}
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)
}
})

View File

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

View File

@ -2,7 +2,7 @@ open SymbolicDistTypes
module 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
? Ok(#Normal({mean: mean, stdev: stdev}))
: Error("Standard deviation of normal distribution must be larger than 0")
@ -48,12 +48,14 @@ module Normal = {
module Exponential = {
type t = exponential
let make = (rate: float): result<symbolicDist,string> =>
let make = (rate: float): result<symbolicDist, string> =>
rate > 0.0
? Ok(#Exponential({
rate: rate,
}))
: Error("Exponential distributions mean must be larger than 0")
? Ok(
#Exponential({
rate: rate,
}),
)
: Error("Exponential distributions rate must be larger than 0.")
let pdf = (x, t: t) => Jstat.Exponential.pdf(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)
@ -69,7 +71,7 @@ module Cauchy = {
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 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)`
}
@ -78,8 +80,8 @@ module Triangular = {
let make = (low, medium, high): result<symbolicDist, string> =>
low < medium && medium < high
? Ok(#Triangular({low: low, medium: medium, high: high}))
: Error("Triangular values must be increasing order")
let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium)
: Error("Triangular values must be increasing order.")
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 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)
@ -89,7 +91,7 @@ module Triangular = {
module Beta = {
type t = beta
let make = (alpha, beta) =>
let make = (alpha, beta) =>
alpha > 0.0 && beta > 0.0
? Ok(#Beta({alpha: alpha, beta: beta}))
: Error("Beta distribution parameters must be positive")
@ -103,10 +105,10 @@ module Beta = {
module Lognormal = {
type t = lognormal
let make = (mu, sigma) =>
sigma > 0.0
? Ok(#Lognormal({mu: mu, sigma: sigma}))
: Error("Lognormal standard deviation must be larger than 0")
let make = (mu, sigma) =>
sigma > 0.0
? Ok(#Lognormal({mu: mu, sigma: sigma}))
: Error("Lognormal standard deviation must be larger than 0")
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 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 sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5)
Ok(#Lognormal({mu: mu, sigma: sigma}))
}
else {
} else {
Error("Lognormal standard deviation must be larger than 0")
}
}
@ -154,9 +155,7 @@ module Lognormal = {
module Uniform = {
type t = uniform
let make = (low, high) =>
high > low
? Ok(#Uniform({low: low, high: high}))
: Error("High must be larger than low")
high > 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 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 toString = ({low, high}: t) => j`Uniform($low,$high)`
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 newHigh = min(E.O.default(infinity, high), t.high)
{low: newLow, high: newHigh}
@ -183,6 +182,15 @@ module Float = {
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 = {
let minCdfValue = 0.0001
let maxCdfValue = 0.9999

View File

@ -8,28 +8,22 @@ let make =
(
~pointSetDist,
~squiggleString,
~domain=Complete,
~unit=UnspecifiedDistribution,
(),
)
: t => {
let integral = pointSetDistIntegral(pointSetDist);
{pointSetDist, domain, integralCache: integral, unit, squiggleString};
{pointSetDist, integralCache: integral, squiggleString};
};
let update =
(
~pointSetDist=?,
~integralCache=?,
~domain=?,
~unit=?,
~squiggleString=?,
t: t,
) => {
pointSetDist: E.O.default(t.pointSetDist, pointSetDist),
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),
};
@ -38,12 +32,6 @@ let updateShape = (pointSetDist, 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 pointSetDistFn = (fn, {pointSetDist}: t) => fn(pointSetDist);
@ -73,8 +61,7 @@ module T =
let xToY = (f, t: t) =>
t
|> toPointSetDist
|> PointSetDist.T.xToY(f)
|> MixedPoint.fmap(domainIncludedProbabilityMassAdjustment(t));
|> PointSetDist.T.xToY(f);
let minX = pointSetDistFn(PointSetDist.T.minX);
let maxX = pointSetDistFn(PointSetDist.T.maxX);
@ -115,7 +102,6 @@ module T =
f,
toPointSetDist(t),
)
|> domainIncludedProbabilityMassAdjustment(t);
};
// 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("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,
]

View File

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

View File

@ -13,13 +13,10 @@ module Sample = {
/*
Map external calls of Reducer
*/
let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> =>
switch call {
| ("add", [EvNumber(a), EvNumber(b)]) => Sample.customAdd(a, b)->EvNumber->Ok
| call => chain(call)
/*
ReducerInterface_GenericDistribution.dispatch(call) |> E.O.default(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.
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 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 = {
let default = (a, b) => O.default(b, a)
let toExn = (a, b) => O.toExn(b, a)
let fmap = (a, b) => O.fmap(b, a)
}
/* Functions */

View File

@ -7,11 +7,11 @@ type algebraicOperation = [
| #Subtract
| #Divide
| #Exponentiate
| #Log
| #Logarithm
]
@genType
type pointwiseOperation = [#Add | #Multiply | #Exponentiate]
type scaleOperation = [#Multiply | #Exponentiate | #Log | #Divide]
type scaleOperation = [#Multiply | #Exponentiate | #Logarithm | #Divide]
type distToFloatOperation = [
| #Pdf(float)
| #Cdf(float)
@ -29,7 +29,7 @@ module Algebraic = {
| #Multiply => \"*."
| #Exponentiate => \"**"
| #Divide => \"/."
| #Log => (a, b) => log(a) /. log(b)
| #Logarithm => (a, b) => log(a) /. log(b)
}
let applyFn = (t, f1, f2) =>
@ -45,7 +45,7 @@ module Algebraic = {
| #Multiply => "*"
| #Exponentiate => "**"
| #Divide => "/"
| #Log => "log"
| #Logarithm => "log"
}
let format = (a, b, c) => b ++ (" " ++ (toString(a) ++ (" " ++ c)))
@ -84,7 +84,7 @@ module Scale = {
| #Multiply => \"*."
| #Divide => \"/."
| #Exponentiate => \"**"
| #Log => (a, b) => log(a) /. log(b)
| #Logarithm => (a, b) => log(a) /. log(b)
}
let format = (operation: t, value, scaleBy) =>
@ -92,7 +92,7 @@ module Scale = {
| #Multiply => j`verticalMultiply($value, $scaleBy) `
| #Divide => j`verticalDivide($value, $scaleBy) `
| #Exponentiate => j`verticalExponentiate($value, $scaleBy) `
| #Log => j`verticalLog($value, $scaleBy) `
| #Logarithm => j`verticalLog($value, $scaleBy) `
}
let toIntegralSumCacheFn = x =>
@ -100,7 +100,7 @@ module Scale = {
| #Multiply => (a, b) => Some(a *. b)
| #Divide => (a, b) => Some(a /. b)
| #Exponentiate => (_, _) => None
| #Log => (_, _) => None
| #Logarithm => (_, _) => None
}
let toIntegralCacheFn = x =>
@ -108,7 +108,7 @@ module Scale = {
| #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy)
| #Divide => (_, _) => 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 minProportion = (xMax -. xIntended) /. (xMax -. xMin)
@ -25,6 +43,7 @@ module 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 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 fromArray = ((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.
Interpolation can either be stepwise (using the value on the left) or linear. Extrapolation can be `UseZero or `UseOutermostPoints. */
let continuousInterpolator = (
interpolation: PointSetTypes.interpolationStrategy,
extrapolation: PointSetTypes.extrapolationStrategy,
interpolation: interpolationStrategy,
extrapolation: extrapolationStrategy,
): interpolator =>
switch (interpolation, extrapolation) {
| (#Linear, #UseZero) =>
@ -392,49 +411,9 @@ let logScorePoint = (sampleCount, t1, t2) =>
|> E.O.fmap(Pairs.y)
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 meanSquared = mean(t) ** 2.0
let meanOfSquares = getMeanOfSquares(t)
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