Merge branch 'staging' into reducer-dev
This commit is contained in:
commit
1d550353c9
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
|
@ -16,17 +16,17 @@ jobs:
|
||||||
name: Check if the changes are about squiggle-lang src files
|
name: Check if the changes are about squiggle-lang src files
|
||||||
uses: fkirc/skip-duplicate-actions@master
|
uses: fkirc/skip-duplicate-actions@master
|
||||||
with:
|
with:
|
||||||
paths: '["packages/squiggle-lang/*"]'
|
paths: '["packages/squiggle-lang/**"]'
|
||||||
- id: skip_components_check
|
- id: skip_components_check
|
||||||
name: Check if the changes are about components src files
|
name: Check if the changes are about components src files
|
||||||
uses: fkirc/skip-duplicate-actions@master
|
uses: fkirc/skip-duplicate-actions@master
|
||||||
with:
|
with:
|
||||||
paths: '["packages/components/*"]'
|
paths: '["packages/components/**"]'
|
||||||
- id: skip_website_check
|
- id: skip_website_check
|
||||||
name: Check if the changes are about website src files
|
name: Check if the changes are about website src files
|
||||||
uses: fkirc/skip-duplicate-actions@master
|
uses: fkirc/skip-duplicate-actions@master
|
||||||
with:
|
with:
|
||||||
paths: '["packages/website/*"]'
|
paths: '["packages/website/**"]'
|
||||||
|
|
||||||
lang-build-test:
|
lang-build-test:
|
||||||
name: Language build and test
|
name: Language build and test
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ yarn-error.log
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
.DS_Store
|
.DS_Store
|
||||||
**/.sync.ffs_db
|
**/.sync.ffs_db
|
||||||
|
.direnv
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "17.0.39"
|
"@types/react": "^17.0.43"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.17"
|
"packageManager": "yarn@1.22.17"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ const custom = require('../webpack.config.js');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
webpackFinal: async (config) => {
|
webpackFinal: async (config) => {
|
||||||
config.resolve.alias = custom.resolve.alias;
|
config.resolve.alias = custom.resolve.alias;
|
||||||
return { ...config, module: { ...config.module, rules: config.module.rules.concat(custom.module.rules) } };
|
return { ...config, module: { ...config.module, rules: config.module.rules.concat(custom.module.rules.filter(x => x.loader === "ts-loader")) } };
|
||||||
},
|
},
|
||||||
"stories": [
|
"stories": [
|
||||||
"../src/**/*.stories.mdx",
|
"../src/**/*.stories.mdx",
|
||||||
|
|
|
@ -6,4 +6,4 @@ export const parameters = {
|
||||||
date: /Date$/,
|
date: /Date$/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
# Squiggle Components
|
# Squiggle Components
|
||||||
|
|
||||||
This package contains all the components for squiggle. These can be used either as a library or hosted as a [storybook](https://storybook.js.org/).
|
This package contains all the components for squiggle. These can be used either as a library or hosted as a [storybook](https://storybook.js.org/).
|
||||||
|
|
||||||
# Build for development
|
# Build for development
|
||||||
We assume that you had run `yarn` at monorepo level, installing dependencies.
|
|
||||||
|
|
||||||
You need to _prepare_ by building and bundling `squiggle-lang`
|
We assume that you had run `yarn` at monorepo level, installing dependencies.
|
||||||
``` sh
|
|
||||||
|
You need to _prepare_ by building and bundling `squiggle-lang`
|
||||||
|
|
||||||
|
```sh
|
||||||
cd ../squiggle-lang
|
cd ../squiggle-lang
|
||||||
yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
If you've otherwise done this recently you can skip those.
|
If you've otherwise done this recently you can skip those.
|
||||||
|
|
||||||
Run a development server
|
Run a development server
|
||||||
|
|
||||||
``` sh
|
```sh
|
||||||
yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
And build artefacts for production,
|
And build artefacts for production,
|
||||||
|
|
||||||
``` sh
|
```sh
|
||||||
yarn bundle # builds components library
|
yarn bundle # builds components library
|
||||||
yarn build # builds storybook app
|
yarn build # builds storybook app
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-components",
|
"name": "@quri/squiggle-components",
|
||||||
"version": "0.1.6",
|
"version": "0.1.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quri/squiggle-lang": "0.2.2",
|
"@quri/squiggle-lang": "0.2.2",
|
||||||
"@testing-library/jest-dom": "^5.16.3",
|
"@testing-library/jest-dom": "^5.16.3",
|
||||||
|
@ -9,14 +9,16 @@
|
||||||
"@types/jest": "^27.4.0",
|
"@types/jest": "^27.4.0",
|
||||||
"@types/lodash": "^4.14.178",
|
"@types/lodash": "^4.14.178",
|
||||||
"@types/node": "^17.0.16",
|
"@types/node": "^17.0.16",
|
||||||
"@types/react": "^17.0.43",
|
|
||||||
"@types/react-dom": "^17.0.14",
|
"@types/react-dom": "^17.0.14",
|
||||||
|
"antd": "^4.19.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
"react-ace": "^9.5.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
"react-vega": "^7.4.4",
|
"react-vega": "^7.4.4",
|
||||||
|
"styled-components": "^5.3.5",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.6.3",
|
||||||
"vega": "^5.21.0",
|
"vega": "^5.21.0",
|
||||||
|
@ -27,7 +29,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
||||||
"build": "build-storybook -s public",
|
"build": "tsc -b && build-storybook -s public",
|
||||||
"bundle": "webpack",
|
"bundle": "webpack",
|
||||||
"all": "yarn bundle && yarn build"
|
"all": "yarn bundle && yarn build"
|
||||||
},
|
},
|
||||||
|
@ -68,9 +70,10 @@
|
||||||
"@storybook/node-logger": "^6.4.18",
|
"@storybook/node-logger": "^6.4.18",
|
||||||
"@storybook/preset-create-react-app": "^4.0.0",
|
"@storybook/preset-create-react-app": "^4.0.0",
|
||||||
"@storybook/react": "^6.4.18",
|
"@storybook/react": "^6.4.18",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/styled-components": "^5.1.24",
|
||||||
|
"css-loader": "^6.7.1",
|
||||||
"prettier": "^2.6.0",
|
"prettier": "^2.6.0",
|
||||||
"react-codejar": "^1.1.2",
|
"style-loader": "^3.3.1",
|
||||||
"ts-loader": "^9.2.8",
|
"ts-loader": "^9.2.8",
|
||||||
"webpack": "^5.70.0",
|
"webpack": "^5.70.0",
|
||||||
"webpack-cli": "^4.9.2",
|
"webpack-cli": "^4.9.2",
|
||||||
|
|
45
packages/components/src/CodeEditor.tsx
Normal file
45
packages/components/src/CodeEditor.tsx
Normal 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;
|
98
packages/components/src/NumberShower.tsx
Normal file
98
packages/components/src/NumberShower.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -11,18 +11,20 @@ import type {
|
||||||
import { createClassFromSpec } from "react-vega";
|
import { createClassFromSpec } from "react-vega";
|
||||||
import * as chartSpecification from "./spec-distributions.json";
|
import * as chartSpecification from "./spec-distributions.json";
|
||||||
import * as percentilesSpec from "./spec-percentiles.json";
|
import * as percentilesSpec from "./spec-percentiles.json";
|
||||||
|
import { NumberShower } from "./NumberShower";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
let SquiggleVegaChart = createClassFromSpec({
|
let SquiggleVegaChart = createClassFromSpec({
|
||||||
spec: chartSpecification as Spec
|
spec: chartSpecification as Spec,
|
||||||
});
|
});
|
||||||
|
|
||||||
let SquigglePercentilesChart = createClassFromSpec({
|
let SquigglePercentilesChart = createClassFromSpec({
|
||||||
spec: percentilesSpec as Spec
|
spec: percentilesSpec as Spec,
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export interface SquiggleChartProps {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
squiggleString: string;
|
squiggleString?: string;
|
||||||
|
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
sampleCount?: number;
|
sampleCount?: number;
|
||||||
|
@ -40,24 +42,58 @@ export interface SquiggleChartProps {
|
||||||
environment?: exportEnv;
|
environment?: exportEnv;
|
||||||
/** When the environment changes */
|
/** When the environment changes */
|
||||||
onEnvChange?(env: exportEnv): void;
|
onEnvChange?(env: exportEnv): void;
|
||||||
|
/** CSS width of the element */
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
const Error = styled.div`
|
||||||
|
border: 1px solid #792e2e;
|
||||||
|
background: #eee2e2;
|
||||||
|
padding: 0.4em 0.8em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ShowError: React.FC<{ heading: string; children: React.ReactNode }> = ({
|
||||||
|
heading = "Error",
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Error>
|
||||||
|
<h3>{heading}</h3>
|
||||||
|
{children}
|
||||||
|
</Error>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
||||||
|
squiggleString = "",
|
||||||
|
sampleCount = 1000,
|
||||||
|
outputXYPoints = 1000,
|
||||||
|
kernelWidth,
|
||||||
|
pointDistLength = 1000,
|
||||||
|
diagramStart = 0,
|
||||||
|
diagramStop = 10,
|
||||||
|
diagramCount = 20,
|
||||||
|
environment = [],
|
||||||
|
onEnvChange = () => {},
|
||||||
|
width = 500,
|
||||||
|
height = 60,
|
||||||
|
}: SquiggleChartProps) => {
|
||||||
let samplingInputs: SamplingInputs = {
|
let samplingInputs: SamplingInputs = {
|
||||||
sampleCount: props.sampleCount,
|
sampleCount: sampleCount,
|
||||||
outputXYPoints: props.outputXYPoints,
|
outputXYPoints: outputXYPoints,
|
||||||
kernelWidth: props.kernelWidth,
|
kernelWidth: kernelWidth,
|
||||||
pointDistLength: props.pointDistLength,
|
pointDistLength: pointDistLength,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = run(props.squiggleString, samplingInputs, props.environment);
|
let result = run(squiggleString, samplingInputs, environment);
|
||||||
if (result.tag === "Ok") {
|
if (result.tag === "Ok") {
|
||||||
let environment = result.value.environment;
|
let environment = result.value.environment;
|
||||||
let exports = result.value.exports;
|
let exports = result.value.exports;
|
||||||
if (props.onEnvChange) props.onEnvChange(environment);
|
onEnvChange(environment);
|
||||||
let chartResults = exports.map((chartResult: exportDistribution) => {
|
let chartResults = exports.map((chartResult: exportDistribution) => {
|
||||||
if (chartResult["NAME"] === "Float") {
|
if (chartResult["NAME"] === "Float") {
|
||||||
return <MakeNumberShower precision={3} number={chartResult["VAL"]} />;
|
return <NumberShower precision={3} number={chartResult["VAL"]} />;
|
||||||
} else if (chartResult["NAME"] === "DistPlus") {
|
} else if (chartResult["NAME"] === "DistPlus") {
|
||||||
let shape = chartResult.VAL.pointSetDist;
|
let shape = chartResult.VAL.pointSetDist;
|
||||||
if (shape.tag === "Continuous") {
|
if (shape.tag === "Continuous") {
|
||||||
|
@ -74,7 +110,14 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
y: y,
|
y: y,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return <SquiggleVegaChart data={{ con: values }} actions={false}/>;
|
return (
|
||||||
|
<SquiggleVegaChart
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
data={{ con: values }}
|
||||||
|
actions={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (shape.tag === "Discrete") {
|
} else if (shape.tag === "Discrete") {
|
||||||
let xyShape = shape.value.xyShape;
|
let xyShape = shape.value.xyShape;
|
||||||
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
||||||
|
@ -89,7 +132,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
y: y,
|
y: y,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return <SquiggleVegaChart data={{ dis: values }} actions={false}/>;
|
return <SquiggleVegaChart data={{ dis: values }} actions={false} />;
|
||||||
} else if (shape.tag === "Mixed") {
|
} else if (shape.tag === "Mixed") {
|
||||||
let discreteShape = shape.value.discrete.xyShape;
|
let discreteShape = shape.value.discrete.xyShape;
|
||||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||||
|
@ -123,10 +166,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let cdf = sortedPoints.map((point: labeledPoint) => {
|
let cdf = sortedPoints.map((point: labeledPoint) => {
|
||||||
if (point.type == "discrete") {
|
if (point.type === "discrete") {
|
||||||
total += point.y;
|
total += point.y;
|
||||||
return total;
|
return total;
|
||||||
} else if (point.type == "continuous") {
|
} else if (point.type === "continuous") {
|
||||||
total += (point.y / totalY) * totalContinuous;
|
total += (point.y / totalY) * totalContinuous;
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
@ -147,10 +190,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
let continuousValues = cdfLabeledPoint.filter(
|
let continuousValues = cdfLabeledPoint.filter(
|
||||||
(x) => x.type == "continuous"
|
(x) => x.type === "continuous"
|
||||||
);
|
);
|
||||||
let discreteValues = cdfLabeledPoint.filter(
|
let discreteValues = cdfLabeledPoint.filter(
|
||||||
(x) => x.type == "discrete"
|
(x) => x.type === "discrete"
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -162,14 +205,14 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
}
|
}
|
||||||
} else if (chartResult.NAME === "Function") {
|
} else if (chartResult.NAME === "Function") {
|
||||||
// We are looking at a function. In this case, we draw a Percentiles chart
|
// We are looking at a function. In this case, we draw a Percentiles chart
|
||||||
let start = props.diagramStart ? props.diagramStart : 0;
|
let start = diagramStart;
|
||||||
let stop = props.diagramStop ? props.diagramStop : 10;
|
let stop = diagramStop;
|
||||||
let count = props.diagramCount ? props.diagramCount : 100;
|
let count = diagramCount;
|
||||||
let step = (stop - start) / count;
|
let step = (stop - start) / count;
|
||||||
let data = _.range(start, stop, step).map((x) => {
|
let data = _.range(start, stop, step).map((x) => {
|
||||||
if (chartResult.NAME == "Function") {
|
if (chartResult.NAME === "Function") {
|
||||||
let result = chartResult.VAL(x);
|
let result = chartResult.VAL(x);
|
||||||
if (result.tag == "Ok") {
|
if (result.tag === "Ok") {
|
||||||
let percentileArray = [
|
let percentileArray = [
|
||||||
0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95,
|
0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95,
|
||||||
0.99,
|
0.99,
|
||||||
|
@ -196,22 +239,28 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return <SquigglePercentilesChart
|
return (
|
||||||
data={{ facet: data.filter(x => x !== null) }}
|
<SquigglePercentilesChart
|
||||||
|
data={{ facet: data.filter((x) => x !== null) }}
|
||||||
actions={false}
|
actions={false}
|
||||||
/>;
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return <>{chartResults}</>;
|
return <>{chartResults}</>;
|
||||||
} else if (result.tag == "Error") {
|
} else if (result.tag === "Error") {
|
||||||
// At this point, we came across an error. What was our error?
|
// At this point, we came across an error. What was our error?
|
||||||
return <p>{"Error parsing Squiggle: " + result.value}</p>;
|
return (
|
||||||
|
<ShowError heading={"Parse Error"}>
|
||||||
|
{result.value}
|
||||||
|
</ShowError>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return <p>{"Invalid Response"}</p>;
|
return <p>{"Invalid Response"}</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPercentiles(percentiles: number[], t: DistPlus) {
|
function getPercentiles(percentiles: number[], t: DistPlus) {
|
||||||
if (t.pointSetDist.tag == "Discrete") {
|
if (t.pointSetDist.tag === "Discrete") {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
|
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
|
||||||
let bounds = percentiles.map((_) => maxX);
|
let bounds = percentiles.map((_) => maxX);
|
||||||
|
@ -221,14 +270,14 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
|
||||||
(x, y) => {
|
(x, y) => {
|
||||||
total += y;
|
total += y;
|
||||||
percentiles.forEach((v, i) => {
|
percentiles.forEach((v, i) => {
|
||||||
if (total > v && bounds[i] == maxX) {
|
if (total > v && bounds[i] === maxX) {
|
||||||
bounds[i] = x;
|
bounds[i] = x;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return bounds;
|
return bounds;
|
||||||
} else if (t.pointSetDist.tag == "Continuous") {
|
} else if (t.pointSetDist.tag === "Continuous") {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
|
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
|
||||||
let totalY = _.sum(t.pointSetDist.value.xyShape.ys);
|
let totalY = _.sum(t.pointSetDist.value.xyShape.ys);
|
||||||
|
@ -239,14 +288,14 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
|
||||||
(x, y) => {
|
(x, y) => {
|
||||||
total += y / totalY;
|
total += y / totalY;
|
||||||
percentiles.forEach((v, i) => {
|
percentiles.forEach((v, i) => {
|
||||||
if (total > v && bounds[i] == maxX) {
|
if (total > v && bounds[i] === maxX) {
|
||||||
bounds[i] = x;
|
bounds[i] = x;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return bounds;
|
return bounds;
|
||||||
} else if (t.pointSetDist.tag == "Mixed") {
|
} else if (t.pointSetDist.tag === "Mixed") {
|
||||||
let discreteShape = t.pointSetDist.value.discrete.xyShape;
|
let discreteShape = t.pointSetDist.value.discrete.xyShape;
|
||||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
@ -280,13 +329,13 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
|
||||||
let maxX = _.max(sortedPoints.map((x) => x.x));
|
let maxX = _.max(sortedPoints.map((x) => x.x));
|
||||||
let bounds = percentiles.map((_) => maxX);
|
let bounds = percentiles.map((_) => maxX);
|
||||||
sortedPoints.map((point: labeledPoint) => {
|
sortedPoints.map((point: labeledPoint) => {
|
||||||
if (point.type == "discrete") {
|
if (point.type === "discrete") {
|
||||||
total += point.y;
|
total += point.y;
|
||||||
} else if (point.type == "continuous") {
|
} else if (point.type === "continuous") {
|
||||||
total += (point.y / totalY) * totalContinuous;
|
total += (point.y / totalY) * totalContinuous;
|
||||||
}
|
}
|
||||||
percentiles.forEach((v, i) => {
|
percentiles.forEach((v, i) => {
|
||||||
if (total > v && bounds[i] == maxX) {
|
if (total > v && bounds[i] === maxX) {
|
||||||
bounds[i] = total;
|
bounds[i] = total;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -295,91 +344,3 @@ function getPercentiles(percentiles: number[], t: DistPlus) {
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function MakeNumberShower(props: { number: number; precision: number }) {
|
|
||||||
let numberWithPresentation = numberShow(props.number, props.precision);
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{numberWithPresentation.value}
|
|
||||||
{numberWithPresentation.symbol}
|
|
||||||
{numberWithPresentation.power ? (
|
|
||||||
<span>
|
|
||||||
{"\u00b710"}
|
|
||||||
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
|
||||||
{numberWithPresentation.power}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderOfMagnitudeNum = (n: number) => {
|
|
||||||
return Math.pow(10, n);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 105 -> 3
|
|
||||||
const orderOfMagnitude = (n: number) => {
|
|
||||||
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
|
|
||||||
};
|
|
||||||
|
|
||||||
function withXSigFigs(number: number, sigFigs: number) {
|
|
||||||
const withPrecision = number.toPrecision(sigFigs);
|
|
||||||
const formatted = Number(withPrecision);
|
|
||||||
return `${formatted}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NumberShower {
|
|
||||||
number: number;
|
|
||||||
precision: number;
|
|
||||||
|
|
||||||
constructor(number: number, precision = 2) {
|
|
||||||
this.number = number;
|
|
||||||
this.precision = precision;
|
|
||||||
}
|
|
||||||
|
|
||||||
convert() {
|
|
||||||
const number = Math.abs(this.number);
|
|
||||||
const response = this.evaluate(number);
|
|
||||||
if (this.number < 0) {
|
|
||||||
response.value = "-" + response.value;
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
metricSystem(number: number, order: number) {
|
|
||||||
const newNumber = number / orderOfMagnitudeNum(order);
|
|
||||||
const precision = this.precision;
|
|
||||||
return `${withXSigFigs(newNumber, precision)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate(number: number) {
|
|
||||||
if (number === 0) {
|
|
||||||
return { value: this.metricSystem(0, 0) };
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = orderOfMagnitude(number);
|
|
||||||
if (order < -2) {
|
|
||||||
return { value: this.metricSystem(number, order), power: order };
|
|
||||||
} else if (order < 4) {
|
|
||||||
return { value: this.metricSystem(number, 0) };
|
|
||||||
} else if (order < 6) {
|
|
||||||
return { value: this.metricSystem(number, 3), symbol: "K" };
|
|
||||||
} else if (order < 9) {
|
|
||||||
return { value: this.metricSystem(number, 6), symbol: "M" };
|
|
||||||
} else if (order < 12) {
|
|
||||||
return { value: this.metricSystem(number, 9), symbol: "B" };
|
|
||||||
} else if (order < 15) {
|
|
||||||
return { value: this.metricSystem(number, 12), symbol: "T" };
|
|
||||||
} else {
|
|
||||||
return { value: this.metricSystem(number, order), power: order };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberShow(number: number, precision = 2) {
|
|
||||||
const ns = new NumberShower(number, precision);
|
|
||||||
return ns.convert();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { SquiggleChart } from "./SquiggleChart";
|
import { SquiggleChart } from "./SquiggleChart";
|
||||||
import { ReactCodeJar } from "react-codejar";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import type { exportEnv } from "@quri/squiggle-lang";
|
import type { exportEnv } from "@quri/squiggle-lang";
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
export interface SquiggleEditorProps {
|
export interface SquiggleEditorProps {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
|
@ -23,67 +24,57 @@ export interface SquiggleEditorProps {
|
||||||
environment?: exportEnv;
|
environment?: exportEnv;
|
||||||
/** when the environment changes. Used again for notebook magic*/
|
/** when the environment changes. Used again for notebook magic*/
|
||||||
onEnvChange?(env: exportEnv): void;
|
onEnvChange?(env: exportEnv): void;
|
||||||
|
/** The width of the element */
|
||||||
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlight = (_: HTMLInputElement) => {};
|
const Input = styled.div`
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.3em 0.3em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
`;
|
||||||
|
|
||||||
interface SquiggleEditorState {
|
export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
||||||
expression: string;
|
initialSquiggleString = "",
|
||||||
env: exportEnv;
|
width = 500,
|
||||||
}
|
sampleCount,
|
||||||
|
outputXYPoints,
|
||||||
export class SquiggleEditor extends React.Component<
|
kernelWidth,
|
||||||
SquiggleEditorProps,
|
pointDistLength,
|
||||||
SquiggleEditorState
|
diagramStart,
|
||||||
> {
|
diagramStop,
|
||||||
constructor(props: SquiggleEditorProps) {
|
diagramCount,
|
||||||
super(props);
|
onEnvChange,
|
||||||
let code = props.initialSquiggleString ? props.initialSquiggleString : "";
|
environment,
|
||||||
this.state = { expression: code, env: props.environment };
|
}: SquiggleEditorProps) => {
|
||||||
}
|
let [expression, setExpression] = React.useState(initialSquiggleString);
|
||||||
render() {
|
return (
|
||||||
let { expression, env } = this.state;
|
<div>
|
||||||
let props = this.props;
|
<Input>
|
||||||
return (
|
<CodeEditor
|
||||||
<div>
|
value={expression}
|
||||||
<ReactCodeJar
|
onChange={setExpression}
|
||||||
code={expression}
|
oneLine={true}
|
||||||
onUpdate={(e) => {
|
|
||||||
this.setState({ expression: e });
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: "6px",
|
|
||||||
width: "530px",
|
|
||||||
border: "1px solid grey",
|
|
||||||
fontFamily: "'Source Code Pro', monospace",
|
|
||||||
fontSize: "14px",
|
|
||||||
fontWeight: "400",
|
|
||||||
letterSpacing: "normal",
|
|
||||||
lineHeight: "20px",
|
|
||||||
padding: "10px",
|
|
||||||
tabSize: "4",
|
|
||||||
}}
|
|
||||||
highlight={highlight}
|
|
||||||
lineNumbers={false}
|
|
||||||
/>
|
/>
|
||||||
<SquiggleChart
|
</Input>
|
||||||
squiggleString={expression}
|
<SquiggleChart
|
||||||
sampleCount={props.sampleCount}
|
width={width}
|
||||||
outputXYPoints={props.outputXYPoints}
|
squiggleString={expression}
|
||||||
kernelWidth={props.kernelWidth}
|
sampleCount={sampleCount}
|
||||||
pointDistLength={props.pointDistLength}
|
outputXYPoints={outputXYPoints}
|
||||||
diagramStart={props.diagramStart}
|
kernelWidth={kernelWidth}
|
||||||
diagramStop={props.diagramStop}
|
pointDistLength={pointDistLength}
|
||||||
diagramCount={props.diagramCount}
|
diagramStart={diagramStart}
|
||||||
environment={env}
|
diagramStop={diagramStop}
|
||||||
onEnvChange={props.onEnvChange}
|
diagramCount={diagramCount}
|
||||||
/>
|
environment={environment}
|
||||||
</div>
|
onEnvChange={onEnvChange}
|
||||||
);
|
/>
|
||||||
}
|
</div>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export function renderSquiggleEditor(props: SquiggleEditorProps) {
|
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
|
||||||
let parent = document.createElement("div");
|
let parent = document.createElement("div");
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<SquiggleEditor
|
<SquiggleEditor
|
||||||
|
|
131
packages/components/src/SquigglePlayground.tsx
Normal file
131
packages/components/src/SquigglePlayground.tsx
Normal 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;
|
||||||
|
}
|
|
@ -1,2 +1,6 @@
|
||||||
export { SquiggleChart } from "./SquiggleChart";
|
export { SquiggleChart } from "./SquiggleChart";
|
||||||
export { SquiggleEditor, renderSquiggleEditor } from "./SquiggleEditor";
|
export { SquiggleEditor, renderSquiggleEditorToDom } from "./SquiggleEditor";
|
||||||
|
import SquigglePlayground, {
|
||||||
|
renderSquigglePlaygroundToDom,
|
||||||
|
} from "./SquigglePlayground";
|
||||||
|
export { SquigglePlayground, renderSquigglePlaygroundToDom };
|
||||||
|
|
|
@ -1,123 +1,181 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||||
"description": "A basic area chart example.",
|
"description": "A basic area chart example",
|
||||||
"width": 500,
|
"width": 500,
|
||||||
"height": 200,
|
"height": 100,
|
||||||
"padding": 5,
|
"padding": 5,
|
||||||
"data": [{ "name": "con" }, { "name": "dis" }],
|
"data": [
|
||||||
|
|
||||||
"signals": [
|
|
||||||
{
|
{
|
||||||
"name": "mousex",
|
"name": "con"
|
||||||
"description": "x position of mouse",
|
|
||||||
"update": "0",
|
|
||||||
"on": [{ "events": "mousemove", "update": "1-x()/width" }]
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "dis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"signals": [
|
||||||
{
|
{
|
||||||
"name": "xscale",
|
"name": "xscale",
|
||||||
"description": "The transform of the x scale",
|
"description": "The transform of the x scale",
|
||||||
"value": 1.0,
|
"value": false,
|
||||||
"bind": {
|
"bind": {
|
||||||
"input": "range",
|
"input": "checkbox",
|
||||||
"min": 0.1,
|
"name": "log x scale"
|
||||||
"max": 1
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "yscale",
|
"name": "yscale",
|
||||||
"description": "The transform of the y scale",
|
"description": "The transform of the y scale",
|
||||||
"value": 1.0,
|
"value": false,
|
||||||
"bind": {
|
"bind": {
|
||||||
"input": "range",
|
"input": "checkbox",
|
||||||
"min": 0.1,
|
"name": "log y scale"
|
||||||
"max": 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"scales": [
|
"scales": [
|
||||||
{
|
{
|
||||||
"name": "xscale",
|
"name": "xscale",
|
||||||
"type": "pow",
|
"type": "pow",
|
||||||
"exponent": { "signal": "xscale" },
|
"exponent": {
|
||||||
|
"signal": "xscale ? 0.1 : 1"
|
||||||
|
},
|
||||||
"range": "width",
|
"range": "width",
|
||||||
"zero": false,
|
"zero": false,
|
||||||
"nice": false,
|
"nice": false,
|
||||||
"domain": {
|
"domain": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{ "data": "con", "field": "x" },
|
{
|
||||||
{ "data": "dis", "field": "x" }
|
"data": "con",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "dis",
|
||||||
|
"field": "x"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "yscale",
|
"name": "yscale",
|
||||||
"type": "pow",
|
"type": "pow",
|
||||||
"exponent": { "signal": "yscale" },
|
"exponent": {
|
||||||
|
"signal": "yscale ? 0.1 : 1"
|
||||||
|
},
|
||||||
"range": "height",
|
"range": "height",
|
||||||
"nice": true,
|
"nice": true,
|
||||||
"zero": true,
|
"zero": true,
|
||||||
"domain": {
|
"domain": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{ "data": "con", "field": "y" },
|
{
|
||||||
{ "data": "dis", "field": "y" }
|
"data": "con",
|
||||||
|
"field": "y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "dis",
|
||||||
|
"field": "y"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"axes": [
|
"axes": [
|
||||||
{ "orient": "bottom", "scale": "xscale", "tickCount": 20 },
|
{
|
||||||
{ "orient": "left", "scale": "yscale" }
|
"orient": "bottom",
|
||||||
|
"scale": "xscale",
|
||||||
|
"labelColor": "#666",
|
||||||
|
"tickColor": "#ddd",
|
||||||
|
"format": "~s",
|
||||||
|
"tickCount": 20
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"marks": [
|
"marks": [
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": { "data": "con" },
|
"from": {
|
||||||
|
"data": "con"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": {
|
|
||||||
"tooltip": { "signal": "datum.cdf" }
|
|
||||||
},
|
|
||||||
"update": {
|
"update": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"x": {
|
||||||
"y": { "scale": "yscale", "field": "y" },
|
"scale": "xscale",
|
||||||
"y2": { "scale": "yscale", "value": 0 },
|
"field": "x"
|
||||||
"fill": {
|
|
||||||
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#1b6fac'}, {offset: 1.0, color: '#1b6fac'} ] }",
|
|
||||||
"color": "#000"
|
|
||||||
},
|
},
|
||||||
"interpolate": { "value": "monotone" },
|
"y": {
|
||||||
"fillOpacity": { "value": 1 }
|
"scale": "yscale",
|
||||||
|
"field": "y"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"fill": {
|
||||||
|
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: '#4C78A8'}] }"
|
||||||
|
},
|
||||||
|
"interpolate": {
|
||||||
|
"value": "monotone"
|
||||||
|
},
|
||||||
|
"fillOpacity": {
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rect",
|
"type": "rect",
|
||||||
"from": { "data": "dis" },
|
"from": {
|
||||||
|
"data": "dis"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": {
|
"enter": {
|
||||||
"y2": { "scale": "yscale", "value": 0 },
|
"y2": {
|
||||||
"width": { "value": 1 }
|
"scale": "yscale",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"x": {
|
||||||
"y": { "scale": "yscale", "field": "y" }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "y"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "symbol",
|
"type": "symbol",
|
||||||
"from": { "data": "dis" },
|
"from": {
|
||||||
|
"data": "dis"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": {
|
"enter": {
|
||||||
"shape": { "value": "circle" },
|
"shape": {
|
||||||
"width": { "value": 5 },
|
"value": "circle"
|
||||||
"tooltip": { "signal": "datum.y" }
|
},
|
||||||
|
"width": {
|
||||||
|
"value": 5
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"signal": "datum.y"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"x": {
|
||||||
"y": { "scale": "yscale", "field": "y" }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "y"
|
||||||
|
},
|
||||||
|
"fill": {
|
||||||
|
"value": "#1e4577"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,12 @@
|
||||||
{
|
{
|
||||||
"name": "facet",
|
"name": "facet",
|
||||||
"values": [],
|
"values": [],
|
||||||
"format": { "type": "json", "parse": { "timestamp": "date" } }
|
"format": {
|
||||||
|
"type": "json",
|
||||||
|
"parse": {
|
||||||
|
"timestamp": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "table",
|
"name": "table",
|
||||||
|
@ -70,7 +75,10 @@
|
||||||
"name": "xscale",
|
"name": "xscale",
|
||||||
"type": "linear",
|
"type": "linear",
|
||||||
"nice": true,
|
"nice": true,
|
||||||
"domain": { "data": "facet", "field": "x" },
|
"domain": {
|
||||||
|
"data": "facet",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
"range": "width"
|
"range": "width"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -79,7 +87,10 @@
|
||||||
"range": "height",
|
"range": "height",
|
||||||
"nice": true,
|
"nice": true,
|
||||||
"zero": true,
|
"zero": true,
|
||||||
"domain": { "data": "facet", "field": "p99" }
|
"domain": {
|
||||||
|
"data": "facet",
|
||||||
|
"field": "p99"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"axes": [
|
"axes": [
|
||||||
|
@ -89,8 +100,20 @@
|
||||||
"grid": false,
|
"grid": false,
|
||||||
"tickSize": 2,
|
"tickSize": 2,
|
||||||
"encode": {
|
"encode": {
|
||||||
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
"grid": {
|
||||||
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
"enter": {
|
||||||
|
"stroke": {
|
||||||
|
"value": "#ccc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ticks": {
|
||||||
|
"enter": {
|
||||||
|
"stroke": {
|
||||||
|
"value": "#ccc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -100,107 +123,249 @@
|
||||||
"domain": false,
|
"domain": false,
|
||||||
"tickSize": 2,
|
"tickSize": 2,
|
||||||
"encode": {
|
"encode": {
|
||||||
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
"grid": {
|
||||||
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
"enter": {
|
||||||
|
"stroke": {
|
||||||
|
"value": "#ccc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ticks": {
|
||||||
|
"enter": {
|
||||||
|
"stroke": {
|
||||||
|
"value": "#ccc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"marks": [
|
"marks": [
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": { "data": "table" },
|
"from": {
|
||||||
|
"data": "table"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": { "fill": { "value": "#4C78A8" } },
|
"enter": {
|
||||||
|
"fill": {
|
||||||
|
"value": "#4C78A8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"value": "monotone"
|
||||||
"y": { "scale": "yscale", "field": "p1" },
|
},
|
||||||
"y2": { "scale": "yscale", "field": "p99" },
|
"x": {
|
||||||
"opacity": { "value": 0.05 }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p1"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p99"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.05
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": { "data": "table" },
|
"from": {
|
||||||
|
"data": "table"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": { "fill": { "value": "#4C78A8" } },
|
"enter": {
|
||||||
|
"fill": {
|
||||||
|
"value": "#4C78A8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"value": "monotone"
|
||||||
"y": { "scale": "yscale", "field": "p5" },
|
},
|
||||||
"y2": { "scale": "yscale", "field": "p95" },
|
"x": {
|
||||||
"opacity": { "value": 0.1 }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p5"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p95"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": { "data": "table" },
|
"from": {
|
||||||
|
"data": "table"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": { "fill": { "value": "#4C78A8" } },
|
"enter": {
|
||||||
|
"fill": {
|
||||||
|
"value": "#4C78A8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"value": "monotone"
|
||||||
"y": { "scale": "yscale", "field": "p10" },
|
},
|
||||||
"y2": { "scale": "yscale", "field": "p90" },
|
"x": {
|
||||||
"opacity": { "value": 0.15 }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p10"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p90"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.15
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": { "data": "table" },
|
"from": {
|
||||||
|
"data": "table"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": { "fill": { "value": "#4C78A8" } },
|
"enter": {
|
||||||
|
"fill": {
|
||||||
|
"value": "#4C78A8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"value": "monotone"
|
||||||
"y": { "scale": "yscale", "field": "p20" },
|
},
|
||||||
"y2": { "scale": "yscale", "field": "p80" },
|
"x": {
|
||||||
"opacity": { "value": 0.2 }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p20"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p80"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": { "data": "table" },
|
"from": {
|
||||||
|
"data": "table"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": { "fill": { "value": "#4C78A8" } },
|
"enter": {
|
||||||
|
"fill": {
|
||||||
|
"value": "#4C78A8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"value": "monotone"
|
||||||
"y": { "scale": "yscale", "field": "p30" },
|
},
|
||||||
"y2": { "scale": "yscale", "field": "p70" },
|
"x": {
|
||||||
"opacity": { "value": 0.2 }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p30"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p70"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": { "data": "table" },
|
"from": {
|
||||||
|
"data": "table"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": { "fill": { "value": "#4C78A8" } },
|
"enter": {
|
||||||
|
"fill": {
|
||||||
|
"value": "#4C78A8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"value": "monotone"
|
||||||
"y": { "scale": "yscale", "field": "p40" },
|
},
|
||||||
"y2": { "scale": "yscale", "field": "p60" },
|
"x": {
|
||||||
"opacity": { "value": 0.2 }
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p40"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p60"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "line",
|
"type": "line",
|
||||||
"from": { "data": "table" },
|
"from": {
|
||||||
|
"data": "table"
|
||||||
|
},
|
||||||
"encode": {
|
"encode": {
|
||||||
"update": {
|
"update": {
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": {
|
||||||
"stroke": { "value": "#4C78A8" },
|
"value": "monotone"
|
||||||
"strokeWidth": { "value": 2 },
|
},
|
||||||
"opacity": { "value": 0.8 },
|
"stroke": {
|
||||||
"x": { "scale": "xscale", "field": "x" },
|
"value": "#4C78A8"
|
||||||
"y": { "scale": "yscale", "field": "p50" }
|
},
|
||||||
|
"strokeWidth": {
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.8
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"scale": "xscale",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"scale": "yscale",
|
||||||
|
"field": "p50"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,5 @@ import { Meta } from "@storybook/addon-docs";
|
||||||
|
|
||||||
<Meta title="Squiggle/Introduction" />
|
<Meta title="Squiggle/Introduction" />
|
||||||
|
|
||||||
This is the component library for Squiggle. All of these components are react
|
This is the component library for Squiggle. These are React
|
||||||
components, and can be used in any application that you see fit.
|
components, and can be used in any application that you see fit.
|
||||||
|
|
||||||
Currently, the only component that is provided is the SquiggleChart component.
|
|
||||||
This component allows you to render the result of a squiggle expression.
|
|
60
packages/components/src/stories/NumberShower.stories.mdx
Normal file
60
packages/components/src/stories/NumberShower.stories.mdx
Normal 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} />
|
|
@ -18,7 +18,7 @@ could be continuous, discrete or mixed.
|
||||||
|
|
||||||
## Distributions
|
## Distributions
|
||||||
|
|
||||||
An example of a normal distribution is:
|
### Continuous Distributions
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
|
@ -31,26 +31,26 @@ An example of a normal distribution is:
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
An example of a Discrete distribution is:
|
### Discrete Distributions
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Discrete"
|
name="Discrete"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "mm(0, 1, [0.5, 0.5])",
|
squiggleString: "mm(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
An example of a Mixed distribution is:
|
## Mixed distributions
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Mixed"
|
name="Mixed"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "mm(0, 5 to 10, [0.5, 0.5])",
|
squiggleString: "mm(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
@ -66,7 +66,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Constant"
|
name="Constant"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "500000 * 5000000",
|
squiggleString: "500000000",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
@ -75,14 +75,28 @@ to allow large and small numbers being printed cleanly.
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
Finally, a function can be returned, and this shows how the distribution changes
|
Full functions can be returned. These plot out the results of distributions between a set of x-coordinates.
|
||||||
over the axis between x = 0 and 10.
|
|
||||||
|
The default is show 10 points between 0 and 10.
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Function"
|
name="Function"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "f(x) = normal(x,x)\nf",
|
squiggleString: "f(x) = normal(x^2,x^1.8)\nf",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Error"
|
||||||
|
args={{
|
||||||
|
squiggleString: "f(x) = normal(",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
|
|
@ -20,3 +20,16 @@ the distribution.
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
You can also name variables like so:
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Variables"
|
||||||
|
args={{
|
||||||
|
initialSquiggleString: "x = 2\nnormal(x,2)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
|
@ -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>
|
|
@ -16,10 +16,10 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"files": ["src/spec-distributions.json","src/spec-percentiles.json"],
|
"files": ["src/spec-distributions.json", "src/spec-percentiles.json"],
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"include": ["src/**/*", "src/*"],
|
"include": ["src/**/*", "src/*"],
|
||||||
"exclude": ["node_modules", "**/*.spec.ts"],
|
"exclude": ["node_modules", "**/*.spec.ts", "webpack.config.js"],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../squiggle-lang"
|
"path": "../squiggle-lang"
|
||||||
|
|
|
@ -12,12 +12,16 @@ module.exports = {
|
||||||
options: { projectReferences: true },
|
options: { projectReferences: true },
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ["style-loader", "css-loader"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".js", ".tsx", ".ts"],
|
extensions: [".js", ".tsx", ".ts"],
|
||||||
alias: {
|
alias: {
|
||||||
"@quri/squiggle-lang": path.resolve(__dirname, '../squiggle-lang/src/js')
|
"@quri/squiggle-lang": path.resolve(__dirname, "../squiggle-lang/src/js"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
|
16
packages/playground/.gitignore
vendored
16
packages/playground/.gitignore
vendored
|
@ -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
|
|
|
@ -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
|
|
||||||
```
|
|
|
@ -1,4 +0,0 @@
|
||||||
[[redirects]]
|
|
||||||
from = "/*"
|
|
||||||
to = "/index.html"
|
|
||||||
status = 200
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}}
|
|
||||||
/>
|
|
|
@ -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
|
|
|
@ -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
|
@ -1,3 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./src/components/*.tsx"
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
|
@ -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
2
packages/squiggle-lang/.gitignore
vendored
2
packages/squiggle-lang/.gitignore
vendored
|
@ -17,3 +17,5 @@ yarn-error.log
|
||||||
*.gen.tsx
|
*.gen.tsx
|
||||||
*.gen.js
|
*.gen.js
|
||||||
dist
|
dist
|
||||||
|
*.coverage
|
||||||
|
_coverage
|
||||||
|
|
|
@ -13,6 +13,9 @@ Other:
|
||||||
yarn start # listens to files and recompiles at every mutation
|
yarn start # listens to files and recompiles at every mutation
|
||||||
yarn test
|
yarn test
|
||||||
yarn test:watch # keeps an active session and runs all tests at every mutation
|
yarn test:watch # keeps an active session and runs all tests at every mutation
|
||||||
|
|
||||||
|
# where o := open in osx and o := xdg-open in linux,
|
||||||
|
yarn coverage; o _coverage/index.html # produces coverage report and opens it in browser
|
||||||
```
|
```
|
||||||
|
|
||||||
# TODO: clean up this README.md
|
# TODO: clean up this README.md
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
|
161
packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res
Normal file
161
packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res
Normal 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(`▁▁▁▁▁▁▁▁▂▃▅▆▇████████`)
|
||||||
|
})
|
||||||
|
})
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -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))")
|
||||||
|
})
|
||||||
|
})
|
|
@ -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)
|
|
||||||
})
|
|
||||||
)
|
|
|
@ -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, ()), `▁▁▁▁▁▁▁▁▁▁▂▃▅▇███▇▅▃▂`)
|
|
||||||
})
|
|
26
packages/squiggle-lang/__tests__/TestHelpers.res
Normal file
26
packages/squiggle-lang/__tests__/TestHelpers.res
Normal 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
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-lang",
|
"name": "@quri/squiggle-lang",
|
||||||
"reason": {},
|
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"dir": "src/rescript",
|
"dir": "src/rescript",
|
||||||
|
@ -28,7 +27,8 @@
|
||||||
"bs-dependencies": [
|
"bs-dependencies": [
|
||||||
"@glennsl/rescript-jest",
|
"@glennsl/rescript-jest",
|
||||||
"@glennsl/bs-json",
|
"@glennsl/bs-json",
|
||||||
"rationale"
|
"rationale",
|
||||||
|
"bisect_ppx"
|
||||||
],
|
],
|
||||||
"gentypeconfig": {
|
"gentypeconfig": {
|
||||||
"language": "typescript",
|
"language": "typescript",
|
||||||
|
@ -41,7 +41,13 @@
|
||||||
},
|
},
|
||||||
"refmt": 3,
|
"refmt": 3,
|
||||||
"warnings": {
|
"warnings": {
|
||||||
"number": "+A-42-48-9-30-4-102"
|
"number": "+A-42-48-9-30-4-102-20-27-41"
|
||||||
},
|
},
|
||||||
"ppx-flags": []
|
"ppx-flags": [
|
||||||
|
[
|
||||||
|
"../../node_modules/bisect_ppx/ppx",
|
||||||
|
"--exclude-files",
|
||||||
|
".*_test\\.res$$"
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,10 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
preset: 'ts-jest',
|
preset: 'ts-jest',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
setupFilesAfterEnv: [
|
||||||
|
"<rootdir>/../../node_modules/bisect_ppx/src/runtime/js/jest.bs.js"
|
||||||
|
],
|
||||||
|
testPathIgnorePatterns: [
|
||||||
|
"__tests__/TestHelpers.bs.js"
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
"test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'",
|
"test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watchAll",
|
"test:watch": "jest --watchAll",
|
||||||
"all": "yarn build && yarn bundle && yarn test",
|
"coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report html",
|
||||||
"rescript:format": "find . -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;"
|
"rescript:format": "find . -type f \\( -name '*.res' -o -name '*.resi' \\) -exec sh -c 'bsc -format {} | sponge {}' \\;",
|
||||||
|
"all": "yarn build && yarn bundle && yarn test"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Rescript"
|
"Rescript"
|
||||||
|
@ -25,12 +26,12 @@
|
||||||
"mathjs": "10.4.1",
|
"mathjs": "10.4.1",
|
||||||
"pdfast": "^0.2.0",
|
"pdfast": "^0.2.0",
|
||||||
"rationale": "0.2.0",
|
"rationale": "0.2.0",
|
||||||
"rescript": "^9.1.4"
|
"rescript": "^9.1.4",
|
||||||
|
"bisect_ppx": "^2.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@glennsl/rescript-jest": "^0.9.0",
|
"@glennsl/rescript-jest": "^0.9.0",
|
||||||
"@types/jest": "^27.4.0",
|
"@types/jest": "^27.4.0",
|
||||||
"@types/webpack": "^5.28.0",
|
|
||||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||||
"docsify": "^4.12.2",
|
"docsify": "^4.12.2",
|
||||||
"gentype": "^4.3.0",
|
"gentype": "^4.3.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {runAll} from '../rescript/ProgramEvaluator.gen';
|
import {runAll} from '../rescript/ProgramEvaluator.gen';
|
||||||
import type { Inputs_SamplingInputs_t as SamplingInputs, exportEnv, exportType, exportDistribution} from '../rescript/ProgramEvaluator.gen';
|
import type { Inputs_SamplingInputs_t as SamplingInputs, exportEnv, exportType, exportDistribution} from '../rescript/ProgramEvaluator.gen';
|
||||||
export type { SamplingInputs, exportEnv, exportDistribution }
|
export type { SamplingInputs, exportEnv, exportDistribution }
|
||||||
export type {t as DistPlus} from '../rescript/pointSetDist/DistPlus.gen';
|
export type {t as DistPlus} from '../rescript/OldInterpreter/DistPlus.gen';
|
||||||
|
|
||||||
export let defaultSamplingInputs : SamplingInputs = {
|
export let defaultSamplingInputs : SamplingInputs = {
|
||||||
sampleCount : 10000,
|
sampleCount : 10000,
|
||||||
|
|
|
@ -10,10 +10,10 @@ type env = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type outputType =
|
type outputType =
|
||||||
| Dist(GenericDist_Types.genericDist)
|
| Dist(genericDist)
|
||||||
| Float(float)
|
| Float(float)
|
||||||
| String(string)
|
| String(string)
|
||||||
| GenDistError(GenericDist_Types.error)
|
| GenDistError(error)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We're going to add another function to this module later, so first define a
|
We're going to add another function to this module later, so first define a
|
||||||
|
@ -48,12 +48,24 @@ module OutputLocal = {
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toFloatR = (t: t): result<float, error> =>
|
||||||
|
switch t {
|
||||||
|
| Float(r) => Ok(r)
|
||||||
|
| e => Error(toErrorOrUnreachable(e))
|
||||||
|
}
|
||||||
|
|
||||||
let toString = (t: t) =>
|
let toString = (t: t) =>
|
||||||
switch t {
|
switch t {
|
||||||
| String(d) => Some(d)
|
| String(d) => Some(d)
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toStringR = (t: t): result<string, error> =>
|
||||||
|
switch t {
|
||||||
|
| String(r) => Ok(r)
|
||||||
|
| e => Error(toErrorOrUnreachable(e))
|
||||||
|
}
|
||||||
|
|
||||||
//This is used to catch errors in other switch statements.
|
//This is used to catch errors in other switch statements.
|
||||||
let fromResult = (r: result<t, error>): outputType =>
|
let fromResult = (r: result<t, error>): outputType =>
|
||||||
switch r {
|
switch r {
|
|
@ -26,7 +26,9 @@ module Output: {
|
||||||
let toDist: t => option<GenericDist_Types.genericDist>
|
let toDist: t => option<GenericDist_Types.genericDist>
|
||||||
let toDistR: t => result<GenericDist_Types.genericDist, GenericDist_Types.error>
|
let toDistR: t => result<GenericDist_Types.genericDist, GenericDist_Types.error>
|
||||||
let toFloat: t => option<float>
|
let toFloat: t => option<float>
|
||||||
|
let toFloatR: t => result<float, GenericDist_Types.error>
|
||||||
let toString: t => option<string>
|
let toString: t => option<string>
|
||||||
|
let toStringR: t => result<string, GenericDist_Types.error>
|
||||||
let toError: t => option<GenericDist_Types.error>
|
let toError: t => option<GenericDist_Types.error>
|
||||||
let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t
|
let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t
|
||||||
}
|
}
|
|
@ -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`
|
||||||
|
}
|
||||||
|
}
|
|
@ -228,7 +228,7 @@ let pointwiseCombinationFloat = (
|
||||||
): result<t, error> => {
|
): result<t, error> => {
|
||||||
let m = switch arithmeticOperation {
|
let m = switch arithmeticOperation {
|
||||||
| #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid)
|
| #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid)
|
||||||
| (#Multiply | #Divide | #Exponentiate | #Log) as arithmeticOperation =>
|
| (#Multiply | #Divide | #Exponentiate | #Logarithm) as arithmeticOperation =>
|
||||||
toPointSetFn(t)->E.R2.fmap(t => {
|
toPointSetFn(t)->E.R2.fmap(t => {
|
||||||
//TODO: Move to PointSet codebase
|
//TODO: Move to PointSet codebase
|
||||||
let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary)
|
let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary)
|
|
@ -30,7 +30,7 @@ let truncate: (
|
||||||
~toPointSetFn: toPointSetFn,
|
~toPointSetFn: toPointSetFn,
|
||||||
~leftCutoff: option<float>=?,
|
~leftCutoff: option<float>=?,
|
||||||
~rightCutoff: option<float>=?,
|
~rightCutoff: option<float>=?,
|
||||||
unit,
|
unit
|
||||||
) => result<t, error>
|
) => result<t, error>
|
||||||
|
|
||||||
let algebraicCombination: (
|
let algebraicCombination: (
|
|
@ -1,6 +1,6 @@
|
||||||
type genericDist =
|
type genericDist =
|
||||||
| PointSet(PointSetTypes.pointSetDist)
|
| PointSet(PointSetTypes.pointSetDist)
|
||||||
| SampleSet(array<float>)
|
| SampleSet(SampleSet.t)
|
||||||
| Symbolic(SymbolicDistTypes.symbolicDist)
|
| Symbolic(SymbolicDistTypes.symbolicDist)
|
||||||
|
|
||||||
type error =
|
type error =
|
||||||
|
@ -20,7 +20,7 @@ module Operation = {
|
||||||
| #Subtract
|
| #Subtract
|
||||||
| #Divide
|
| #Divide
|
||||||
| #Exponentiate
|
| #Exponentiate
|
||||||
| #Log
|
| #Logarithm
|
||||||
]
|
]
|
||||||
|
|
||||||
let arithmeticToFn = (arithmetic: arithmeticOperation) =>
|
let arithmeticToFn = (arithmetic: arithmeticOperation) =>
|
||||||
|
@ -30,7 +30,7 @@ module Operation = {
|
||||||
| #Subtract => \"-."
|
| #Subtract => \"-."
|
||||||
| #Exponentiate => \"**"
|
| #Exponentiate => \"**"
|
||||||
| #Divide => \"/."
|
| #Divide => \"/."
|
||||||
| #Log => (a, b) => log(a) /. log(b)
|
| #Logarithm => (a, b) => log(a) /. log(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
type toFloat = [
|
type toFloat = [
|
|
@ -115,16 +115,17 @@ let combineShapesContinuousContinuous = (
|
||||||
| #Multiply => (m1, m2) => m1 *. m2
|
| #Multiply => (m1, m2) => m1 *. m2
|
||||||
| #Divide => (m1, mInv2) => m1 *. mInv2
|
| #Divide => (m1, mInv2) => m1 *. mInv2
|
||||||
| #Exponentiate => (m1, mInv2) => m1 ** mInv2
|
| #Exponentiate => (m1, mInv2) => m1 ** mInv2
|
||||||
| #Log => (m1, m2) => log(m1) /. log(m2)
|
| #Logarithm => (m1, m2) => log(m1) /. log(m2)
|
||||||
} // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
|
} // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
|
||||||
|
|
||||||
// TODO: I don't know what the variances are for exponentatiation
|
// TODO: Variances are for exponentatiation or logarithms are almost totally made up and very likely very wrong.
|
||||||
// converts the variances and means of the two inputs into the variance of the output
|
// converts the variances and means of the two inputs into the variance of the output
|
||||||
let combineVariancesFn = switch op {
|
let combineVariancesFn = switch op {
|
||||||
| #Add => (v1, v2, _, _) => v1 +. v2
|
| #Add => (v1, v2, _, _) => v1 +. v2
|
||||||
| #Subtract => (v1, v2, _, _) => v1 +. v2
|
| #Subtract => (v1, v2, _, _) => v1 +. v2
|
||||||
| #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
| #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||||
| #Exponentiate => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
| #Exponentiate => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||||
|
| #Logarithm => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||||
| #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
|
| #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +234,7 @@ let combineShapesContinuousDiscrete = (
|
||||||
}
|
}
|
||||||
| #Multiply
|
| #Multiply
|
||||||
| #Exponentiate
|
| #Exponentiate
|
||||||
| #Log
|
| #Logarithm
|
||||||
| #Divide =>
|
| #Divide =>
|
||||||
for j in 0 to t2n - 1 {
|
for j in 0 to t2n - 1 {
|
||||||
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
|
@ -1,6 +1,47 @@
|
||||||
open Distributions
|
open Distributions
|
||||||
|
|
||||||
type t = PointSetTypes.continuousShape
|
type t = PointSetTypes.continuousShape
|
||||||
|
|
||||||
|
module Analysis = {
|
||||||
|
let integrate = (
|
||||||
|
~indefiniteIntegralStepwise=(p, h1) => h1 *. p,
|
||||||
|
~indefiniteIntegralLinear=(p, a, b) => a *. p +. b *. p ** 2.0 /. 2.0,
|
||||||
|
t: t,
|
||||||
|
): float => {
|
||||||
|
let xs = t.xyShape.xs
|
||||||
|
let ys = t.xyShape.ys
|
||||||
|
|
||||||
|
E.A.reducei(xs, 0.0, (acc, _x, i) => {
|
||||||
|
let areaUnderIntegral = // TODO Take this switch statement out of the loop body
|
||||||
|
switch (t.interpolation, i) {
|
||||||
|
| (_, 0) => 0.0
|
||||||
|
| (#Stepwise, _) =>
|
||||||
|
indefiniteIntegralStepwise(xs[i], ys[i - 1]) -.
|
||||||
|
indefiniteIntegralStepwise(xs[i - 1], ys[i - 1])
|
||||||
|
| (#Linear, _) =>
|
||||||
|
let x1 = xs[i - 1]
|
||||||
|
let x2 = xs[i]
|
||||||
|
if x1 == x2 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let h1 = ys[i - 1]
|
||||||
|
let h2 = ys[i]
|
||||||
|
let b = (h1 -. h2) /. (x1 -. x2)
|
||||||
|
let a = h1 -. b *. x1
|
||||||
|
indefiniteIntegralLinear(x2, a, b) -. indefiniteIntegralLinear(x1, a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc +. areaUnderIntegral
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let getMeanOfSquares = (t: t) => {
|
||||||
|
let indefiniteIntegralLinear = (p, a, b) => a *. p ** 3.0 /. 3.0 +. b *. p ** 4.0 /. 4.0
|
||||||
|
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 3.0 /. 3.0
|
||||||
|
integrate(~indefiniteIntegralStepwise, ~indefiniteIntegralLinear, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let getShape = (t: t) => t.xyShape
|
let getShape = (t: t) => t.xyShape
|
||||||
let interpolation = (t: t) => t.interpolation
|
let interpolation = (t: t) => t.interpolation
|
||||||
let make = (~interpolation=#Linear, ~integralSumCache=None, ~integralCache=None, xyShape): t => {
|
let make = (~interpolation=#Linear, ~integralSumCache=None, ~integralCache=None, xyShape): t => {
|
||||||
|
@ -194,7 +235,7 @@ module T = Dist({
|
||||||
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 2.0 /. 2.0
|
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 2.0 /. 2.0
|
||||||
let indefiniteIntegralLinear = (p, a, b) => a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0
|
let indefiniteIntegralLinear = (p, a, b) => a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0
|
||||||
|
|
||||||
XYShape.Analysis.integrateContinuousShape(
|
Analysis.integrate(
|
||||||
~indefiniteIntegralStepwise,
|
~indefiniteIntegralStepwise,
|
||||||
~indefiniteIntegralLinear,
|
~indefiniteIntegralLinear,
|
||||||
t,
|
t,
|
||||||
|
@ -204,7 +245,7 @@ module T = Dist({
|
||||||
XYShape.Analysis.getVarianceDangerously(
|
XYShape.Analysis.getVarianceDangerously(
|
||||||
t,
|
t,
|
||||||
mean,
|
mean,
|
||||||
XYShape.Analysis.getMeanOfSquaresContinuousShape,
|
Analysis.getMeanOfSquares,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -209,8 +209,9 @@ module T = Dist({
|
||||||
let s = getShape(t)
|
let s = getShape(t)
|
||||||
E.A.reducei(s.xs, 0.0, (acc, x, i) => acc +. x *. s.ys[i])
|
E.A.reducei(s.xs, 0.0, (acc, x, i) => acc +. x *. s.ys[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
let variance = (t: t): float => {
|
let variance = (t: t): float => {
|
||||||
let getMeanOfSquares = t => t |> shapeMap(XYShape.Analysis.squareXYShape) |> mean
|
let getMeanOfSquares = t => t |> shapeMap(XYShape.T.square) |> mean
|
||||||
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
|
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -213,8 +213,8 @@ module T = Dist({
|
||||||
|
|
||||||
let getMeanOfSquares = ({discrete, continuous}: t) => {
|
let getMeanOfSquares = ({discrete, continuous}: t) => {
|
||||||
let discreteMean =
|
let discreteMean =
|
||||||
discrete |> Discrete.shapeMap(XYShape.Analysis.squareXYShape) |> Discrete.T.mean
|
discrete |> Discrete.shapeMap(XYShape.T.square) |> Discrete.T.mean
|
||||||
let continuousMean = continuous |> XYShape.Analysis.getMeanOfSquaresContinuousShape
|
let continuousMean = continuous |> Continuous.Analysis.getMeanOfSquares
|
||||||
(discreteMean *. discreteIntegralSum +. continuousMean *. continuousIntegralSum) /.
|
(discreteMean *. discreteIntegralSum +. continuousMean *. continuousIntegralSum) /.
|
||||||
totalIntegralSum
|
totalIntegralSum
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
type t = array<float>
|
||||||
|
|
||||||
// TODO: Refactor to raise correct error when not enough samples
|
// TODO: Refactor to raise correct error when not enough samples
|
||||||
|
|
||||||
module Internals = {
|
module Internals = {
|
|
@ -2,7 +2,7 @@ open SymbolicDistTypes
|
||||||
|
|
||||||
module Normal = {
|
module Normal = {
|
||||||
type t = normal
|
type t = normal
|
||||||
let make = (mean: float, stdev: float): result<symbolicDist,string> =>
|
let make = (mean: float, stdev: float): result<symbolicDist, string> =>
|
||||||
stdev > 0.0
|
stdev > 0.0
|
||||||
? Ok(#Normal({mean: mean, stdev: stdev}))
|
? Ok(#Normal({mean: mean, stdev: stdev}))
|
||||||
: Error("Standard deviation of normal distribution must be larger than 0")
|
: Error("Standard deviation of normal distribution must be larger than 0")
|
||||||
|
@ -48,12 +48,14 @@ module Normal = {
|
||||||
|
|
||||||
module Exponential = {
|
module Exponential = {
|
||||||
type t = exponential
|
type t = exponential
|
||||||
let make = (rate: float): result<symbolicDist,string> =>
|
let make = (rate: float): result<symbolicDist, string> =>
|
||||||
rate > 0.0
|
rate > 0.0
|
||||||
? Ok(#Exponential({
|
? Ok(
|
||||||
rate: rate,
|
#Exponential({
|
||||||
}))
|
rate: rate,
|
||||||
: Error("Exponential distributions mean must be larger than 0")
|
}),
|
||||||
|
)
|
||||||
|
: Error("Exponential distributions rate must be larger than 0.")
|
||||||
let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate)
|
let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate)
|
||||||
let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate)
|
let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate)
|
||||||
let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate)
|
let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate)
|
||||||
|
@ -69,7 +71,7 @@ module Cauchy = {
|
||||||
let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale)
|
let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale)
|
||||||
let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale)
|
let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale)
|
||||||
let sample = (t: t) => Jstat.Cauchy.sample(t.local, t.scale)
|
let sample = (t: t) => Jstat.Cauchy.sample(t.local, t.scale)
|
||||||
let mean = (_: t) => Error("Cauchy distributions have no mean value.")
|
let mean = (_: t) => Error("Cauchy distributions may have no mean value.")
|
||||||
let toString = ({local, scale}: t) => j`Cauchy($local, $scale)`
|
let toString = ({local, scale}: t) => j`Cauchy($local, $scale)`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +80,8 @@ module Triangular = {
|
||||||
let make = (low, medium, high): result<symbolicDist, string> =>
|
let make = (low, medium, high): result<symbolicDist, string> =>
|
||||||
low < medium && medium < high
|
low < medium && medium < high
|
||||||
? Ok(#Triangular({low: low, medium: medium, high: high}))
|
? Ok(#Triangular({low: low, medium: medium, high: high}))
|
||||||
: Error("Triangular values must be increasing order")
|
: Error("Triangular values must be increasing order.")
|
||||||
let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium)
|
let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) // not obvious in jstat docs that high comes before medium?
|
||||||
let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium)
|
let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium)
|
||||||
let inv = (p, t: t) => Jstat.Triangular.inv(p, t.low, t.high, t.medium)
|
let inv = (p, t: t) => Jstat.Triangular.inv(p, t.low, t.high, t.medium)
|
||||||
let sample = (t: t) => Jstat.Triangular.sample(t.low, t.high, t.medium)
|
let sample = (t: t) => Jstat.Triangular.sample(t.low, t.high, t.medium)
|
||||||
|
@ -89,7 +91,7 @@ module Triangular = {
|
||||||
|
|
||||||
module Beta = {
|
module Beta = {
|
||||||
type t = beta
|
type t = beta
|
||||||
let make = (alpha, beta) =>
|
let make = (alpha, beta) =>
|
||||||
alpha > 0.0 && beta > 0.0
|
alpha > 0.0 && beta > 0.0
|
||||||
? Ok(#Beta({alpha: alpha, beta: beta}))
|
? Ok(#Beta({alpha: alpha, beta: beta}))
|
||||||
: Error("Beta distribution parameters must be positive")
|
: Error("Beta distribution parameters must be positive")
|
||||||
|
@ -103,10 +105,10 @@ module Beta = {
|
||||||
|
|
||||||
module Lognormal = {
|
module Lognormal = {
|
||||||
type t = lognormal
|
type t = lognormal
|
||||||
let make = (mu, sigma) =>
|
let make = (mu, sigma) =>
|
||||||
sigma > 0.0
|
sigma > 0.0
|
||||||
? Ok(#Lognormal({mu: mu, sigma: sigma}))
|
? Ok(#Lognormal({mu: mu, sigma: sigma}))
|
||||||
: Error("Lognormal standard deviation must be larger than 0")
|
: Error("Lognormal standard deviation must be larger than 0")
|
||||||
let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma)
|
let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma)
|
||||||
let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma)
|
let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma)
|
||||||
let inv = (p, t: t) => Jstat.Lognormal.inv(p, t.mu, t.sigma)
|
let inv = (p, t: t) => Jstat.Lognormal.inv(p, t.mu, t.sigma)
|
||||||
|
@ -127,8 +129,7 @@ module Lognormal = {
|
||||||
let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0)
|
let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0)
|
||||||
let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5)
|
let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5)
|
||||||
Ok(#Lognormal({mu: mu, sigma: sigma}))
|
Ok(#Lognormal({mu: mu, sigma: sigma}))
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Error("Lognormal standard deviation must be larger than 0")
|
Error("Lognormal standard deviation must be larger than 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,9 +155,7 @@ module Lognormal = {
|
||||||
module Uniform = {
|
module Uniform = {
|
||||||
type t = uniform
|
type t = uniform
|
||||||
let make = (low, high) =>
|
let make = (low, high) =>
|
||||||
high > low
|
high > low ? Ok(#Uniform({low: low, high: high})) : Error("High must be larger than low")
|
||||||
? Ok(#Uniform({low: low, high: high}))
|
|
||||||
: Error("High must be larger than low")
|
|
||||||
|
|
||||||
let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high)
|
let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high)
|
||||||
let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high)
|
let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high)
|
||||||
|
@ -165,7 +164,7 @@ module Uniform = {
|
||||||
let mean = (t: t) => Ok(Jstat.Uniform.mean(t.low, t.high))
|
let mean = (t: t) => Ok(Jstat.Uniform.mean(t.low, t.high))
|
||||||
let toString = ({low, high}: t) => j`Uniform($low,$high)`
|
let toString = ({low, high}: t) => j`Uniform($low,$high)`
|
||||||
let truncate = (low, high, t: t): t => {
|
let truncate = (low, high, t: t): t => {
|
||||||
//todo: add check
|
//todo: add check
|
||||||
let newLow = max(E.O.default(neg_infinity, low), t.low)
|
let newLow = max(E.O.default(neg_infinity, low), t.low)
|
||||||
let newHigh = min(E.O.default(infinity, high), t.high)
|
let newHigh = min(E.O.default(infinity, high), t.high)
|
||||||
{low: newLow, high: newHigh}
|
{low: newLow, high: newHigh}
|
||||||
|
@ -183,6 +182,15 @@ module Float = {
|
||||||
let toString = Js.Float.toString
|
let toString = Js.Float.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module From90thPercentile = {
|
||||||
|
let make = (low, high) =>
|
||||||
|
switch (low, high) {
|
||||||
|
| (low, high) if low <= 0.0 && low < high => Ok(Normal.from90PercentCI(low, high))
|
||||||
|
| (low, high) if low < high => Ok(Lognormal.from90PercentCI(low, high))
|
||||||
|
| (_, _) => Error("Low value must be less than high value.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module T = {
|
module T = {
|
||||||
let minCdfValue = 0.0001
|
let minCdfValue = 0.0001
|
||||||
let maxCdfValue = 0.9999
|
let maxCdfValue = 0.9999
|
|
@ -8,28 +8,22 @@ let make =
|
||||||
(
|
(
|
||||||
~pointSetDist,
|
~pointSetDist,
|
||||||
~squiggleString,
|
~squiggleString,
|
||||||
~domain=Complete,
|
|
||||||
~unit=UnspecifiedDistribution,
|
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
: t => {
|
: t => {
|
||||||
let integral = pointSetDistIntegral(pointSetDist);
|
let integral = pointSetDistIntegral(pointSetDist);
|
||||||
{pointSetDist, domain, integralCache: integral, unit, squiggleString};
|
{pointSetDist, integralCache: integral, squiggleString};
|
||||||
};
|
};
|
||||||
|
|
||||||
let update =
|
let update =
|
||||||
(
|
(
|
||||||
~pointSetDist=?,
|
~pointSetDist=?,
|
||||||
~integralCache=?,
|
~integralCache=?,
|
||||||
~domain=?,
|
|
||||||
~unit=?,
|
|
||||||
~squiggleString=?,
|
~squiggleString=?,
|
||||||
t: t,
|
t: t,
|
||||||
) => {
|
) => {
|
||||||
pointSetDist: E.O.default(t.pointSetDist, pointSetDist),
|
pointSetDist: E.O.default(t.pointSetDist, pointSetDist),
|
||||||
integralCache: E.O.default(t.integralCache, integralCache),
|
integralCache: E.O.default(t.integralCache, integralCache),
|
||||||
domain: E.O.default(t.domain, domain),
|
|
||||||
unit: E.O.default(t.unit, unit),
|
|
||||||
squiggleString: E.O.default(t.squiggleString, squiggleString),
|
squiggleString: E.O.default(t.squiggleString, squiggleString),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,12 +32,6 @@ let updateShape = (pointSetDist, t) => {
|
||||||
update(~pointSetDist, ~integralCache, t);
|
update(~pointSetDist, ~integralCache, t);
|
||||||
};
|
};
|
||||||
|
|
||||||
let domainIncludedProbabilityMass = (t: t) =>
|
|
||||||
Domain.includedProbabilityMass(t.domain);
|
|
||||||
|
|
||||||
let domainIncludedProbabilityMassAdjustment = (t: t, f) =>
|
|
||||||
f *. Domain.includedProbabilityMass(t.domain);
|
|
||||||
|
|
||||||
let toPointSetDist = ({pointSetDist, _}: t) => pointSetDist;
|
let toPointSetDist = ({pointSetDist, _}: t) => pointSetDist;
|
||||||
|
|
||||||
let pointSetDistFn = (fn, {pointSetDist}: t) => fn(pointSetDist);
|
let pointSetDistFn = (fn, {pointSetDist}: t) => fn(pointSetDist);
|
||||||
|
@ -73,8 +61,7 @@ module T =
|
||||||
let xToY = (f, t: t) =>
|
let xToY = (f, t: t) =>
|
||||||
t
|
t
|
||||||
|> toPointSetDist
|
|> toPointSetDist
|
||||||
|> PointSetDist.T.xToY(f)
|
|> PointSetDist.T.xToY(f);
|
||||||
|> MixedPoint.fmap(domainIncludedProbabilityMassAdjustment(t));
|
|
||||||
|
|
||||||
let minX = pointSetDistFn(PointSetDist.T.minX);
|
let minX = pointSetDistFn(PointSetDist.T.minX);
|
||||||
let maxX = pointSetDistFn(PointSetDist.T.maxX);
|
let maxX = pointSetDistFn(PointSetDist.T.maxX);
|
||||||
|
@ -115,7 +102,6 @@ module T =
|
||||||
f,
|
f,
|
||||||
toPointSetDist(t),
|
toPointSetDist(t),
|
||||||
)
|
)
|
||||||
|> domainIncludedProbabilityMassAdjustment(t);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: This part is broken when there is a limit, if this is supposed to be taken into account.
|
// TODO: This part is broken when there is a limit, if this is supposed to be taken into account.
|
|
@ -229,6 +229,6 @@ let all = [
|
||||||
),
|
),
|
||||||
makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Exponentiate, dist, float)),
|
makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Exponentiate, dist, float)),
|
||||||
makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)),
|
makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)),
|
||||||
makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Log, dist, float)),
|
makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Logarithm, dist, float)),
|
||||||
Multimodal._function,
|
Multimodal._function,
|
||||||
]
|
]
|
|
@ -12,6 +12,7 @@ type rec expressionValue =
|
||||||
| EvSymbol(string)
|
| EvSymbol(string)
|
||||||
| EvArray(array<expressionValue>)
|
| EvArray(array<expressionValue>)
|
||||||
| EvRecord(Js.Dict.t<expressionValue>)
|
| EvRecord(Js.Dict.t<expressionValue>)
|
||||||
|
| EvDistribution(GenericDist_Types.genericDist)
|
||||||
|
|
||||||
type functionCall = (string, array<expressionValue>)
|
type functionCall = (string, array<expressionValue>)
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ let rec toString = aValue =>
|
||||||
->Js.String.concatMany("")
|
->Js.String.concatMany("")
|
||||||
`{${pairs}}`
|
`{${pairs}}`
|
||||||
}
|
}
|
||||||
|
| EvDistribution(dist) => `${GenericDist.toString(dist)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
let toStringWithType = aValue =>
|
let toStringWithType = aValue =>
|
||||||
|
@ -48,6 +50,7 @@ let toStringWithType = aValue =>
|
||||||
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
||||||
| EvArray(_) => `Array::${toString(aValue)}`
|
| EvArray(_) => `Array::${toString(aValue)}`
|
||||||
| EvRecord(_) => `Record::${toString(aValue)}`
|
| EvRecord(_) => `Record::${toString(aValue)}`
|
||||||
|
| EvDistribution(_) => `Distribution::${toString(aValue)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
let argsToString = (args: array<expressionValue>): string => {
|
let argsToString = (args: array<expressionValue>): string => {
|
||||||
|
|
|
@ -13,13 +13,10 @@ module Sample = {
|
||||||
/*
|
/*
|
||||||
Map external calls of Reducer
|
Map external calls of Reducer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> =>
|
let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> =>
|
||||||
switch call {
|
ReducerInterface_GenericDistribution.dispatch(call) |> E.O.default(chain(call))
|
||||||
| ("add", [EvNumber(a), EvNumber(b)]) => Sample.customAdd(a, b)->EvNumber->Ok
|
/*
|
||||||
|
|
||||||
| call => chain(call)
|
|
||||||
|
|
||||||
/*
|
|
||||||
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
||||||
|
|
||||||
The final chain(call) invokes the builtin default functions of the interpreter.
|
The final chain(call) invokes the builtin default functions of the interpreter.
|
||||||
|
@ -35,4 +32,3 @@ Remember from the users point of view, there are no different modules:
|
||||||
// "doSth( constructorType2 )"
|
// "doSth( constructorType2 )"
|
||||||
doSth gets dispatched to the correct module because of the type signature. You get function and operator abstraction for free. You don't need to combine different implementations into one type. That would be duplicating the repsonsibility of the dispatcher.
|
doSth gets dispatched to the correct module because of the type signature. You get function and operator abstraction for free. You don't need to combine different implementations into one type. That would be duplicating the repsonsibility of the dispatcher.
|
||||||
*/
|
*/
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
let dispatch: ReducerInterface_ExpressionValue.functionCall => option<
|
||||||
|
result<ReducerInterface_ExpressionValue.expressionValue, Reducer_ErrorValue.errorValue>,
|
||||||
|
>
|
|
@ -100,6 +100,7 @@ module O = {
|
||||||
module O2 = {
|
module O2 = {
|
||||||
let default = (a, b) => O.default(b, a)
|
let default = (a, b) => O.default(b, a)
|
||||||
let toExn = (a, b) => O.toExn(b, a)
|
let toExn = (a, b) => O.toExn(b, a)
|
||||||
|
let fmap = (a, b) => O.fmap(b, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
|
@ -7,11 +7,11 @@ type algebraicOperation = [
|
||||||
| #Subtract
|
| #Subtract
|
||||||
| #Divide
|
| #Divide
|
||||||
| #Exponentiate
|
| #Exponentiate
|
||||||
| #Log
|
| #Logarithm
|
||||||
]
|
]
|
||||||
@genType
|
@genType
|
||||||
type pointwiseOperation = [#Add | #Multiply | #Exponentiate]
|
type pointwiseOperation = [#Add | #Multiply | #Exponentiate]
|
||||||
type scaleOperation = [#Multiply | #Exponentiate | #Log | #Divide]
|
type scaleOperation = [#Multiply | #Exponentiate | #Logarithm | #Divide]
|
||||||
type distToFloatOperation = [
|
type distToFloatOperation = [
|
||||||
| #Pdf(float)
|
| #Pdf(float)
|
||||||
| #Cdf(float)
|
| #Cdf(float)
|
||||||
|
@ -29,7 +29,7 @@ module Algebraic = {
|
||||||
| #Multiply => \"*."
|
| #Multiply => \"*."
|
||||||
| #Exponentiate => \"**"
|
| #Exponentiate => \"**"
|
||||||
| #Divide => \"/."
|
| #Divide => \"/."
|
||||||
| #Log => (a, b) => log(a) /. log(b)
|
| #Logarithm => (a, b) => log(a) /. log(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
let applyFn = (t, f1, f2) =>
|
let applyFn = (t, f1, f2) =>
|
||||||
|
@ -45,7 +45,7 @@ module Algebraic = {
|
||||||
| #Multiply => "*"
|
| #Multiply => "*"
|
||||||
| #Exponentiate => "**"
|
| #Exponentiate => "**"
|
||||||
| #Divide => "/"
|
| #Divide => "/"
|
||||||
| #Log => "log"
|
| #Logarithm => "log"
|
||||||
}
|
}
|
||||||
|
|
||||||
let format = (a, b, c) => b ++ (" " ++ (toString(a) ++ (" " ++ c)))
|
let format = (a, b, c) => b ++ (" " ++ (toString(a) ++ (" " ++ c)))
|
||||||
|
@ -84,7 +84,7 @@ module Scale = {
|
||||||
| #Multiply => \"*."
|
| #Multiply => \"*."
|
||||||
| #Divide => \"/."
|
| #Divide => \"/."
|
||||||
| #Exponentiate => \"**"
|
| #Exponentiate => \"**"
|
||||||
| #Log => (a, b) => log(a) /. log(b)
|
| #Logarithm => (a, b) => log(a) /. log(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
let format = (operation: t, value, scaleBy) =>
|
let format = (operation: t, value, scaleBy) =>
|
||||||
|
@ -92,7 +92,7 @@ module Scale = {
|
||||||
| #Multiply => j`verticalMultiply($value, $scaleBy) `
|
| #Multiply => j`verticalMultiply($value, $scaleBy) `
|
||||||
| #Divide => j`verticalDivide($value, $scaleBy) `
|
| #Divide => j`verticalDivide($value, $scaleBy) `
|
||||||
| #Exponentiate => j`verticalExponentiate($value, $scaleBy) `
|
| #Exponentiate => j`verticalExponentiate($value, $scaleBy) `
|
||||||
| #Log => j`verticalLog($value, $scaleBy) `
|
| #Logarithm => j`verticalLog($value, $scaleBy) `
|
||||||
}
|
}
|
||||||
|
|
||||||
let toIntegralSumCacheFn = x =>
|
let toIntegralSumCacheFn = x =>
|
||||||
|
@ -100,7 +100,7 @@ module Scale = {
|
||||||
| #Multiply => (a, b) => Some(a *. b)
|
| #Multiply => (a, b) => Some(a *. b)
|
||||||
| #Divide => (a, b) => Some(a /. b)
|
| #Divide => (a, b) => Some(a /. b)
|
||||||
| #Exponentiate => (_, _) => None
|
| #Exponentiate => (_, _) => None
|
||||||
| #Log => (_, _) => None
|
| #Logarithm => (_, _) => None
|
||||||
}
|
}
|
||||||
|
|
||||||
let toIntegralCacheFn = x =>
|
let toIntegralCacheFn = x =>
|
||||||
|
@ -108,7 +108,7 @@ module Scale = {
|
||||||
| #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy)
|
| #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy)
|
||||||
| #Divide => (_, _) => None
|
| #Divide => (_, _) => None
|
||||||
| #Exponentiate => (_, _) => None
|
| #Exponentiate => (_, _) => None
|
||||||
| #Log => (_, _) => None
|
| #Logarithm => (_, _) => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
open PointSetTypes
|
@genType
|
||||||
|
type xyShape = {
|
||||||
|
xs: array<float>,
|
||||||
|
ys: array<float>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@genType
|
||||||
|
type interpolationStrategy = [
|
||||||
|
| #Stepwise
|
||||||
|
| #Linear
|
||||||
|
]
|
||||||
|
|
||||||
|
@genType
|
||||||
|
type extrapolationStrategy = [
|
||||||
|
| #UseZero
|
||||||
|
| #UseOutermostPoints
|
||||||
|
]
|
||||||
|
|
||||||
|
type interpolator = (xyShape, int, float) => float
|
||||||
|
|
||||||
let interpolate = (xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float): float => {
|
let interpolate = (xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float): float => {
|
||||||
let minProportion = (xMax -. xIntended) /. (xMax -. xMin)
|
let minProportion = (xMax -. xIntended) /. (xMax -. xMin)
|
||||||
|
@ -25,6 +43,7 @@ module T = {
|
||||||
let xTotalRange = (t: t) => maxX(t) -. minX(t)
|
let xTotalRange = (t: t) => maxX(t) -. minX(t)
|
||||||
let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys}
|
let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys}
|
||||||
let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)}
|
let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)}
|
||||||
|
let square = mapX(x => x ** 2.0)
|
||||||
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys)
|
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys)
|
||||||
let fromArray = ((xs, ys)): t => {xs: xs, ys: ys}
|
let fromArray = ((xs, ys)): t => {xs: xs, ys: ys}
|
||||||
let fromArrays = (xs, ys): t => {xs: xs, ys: ys}
|
let fromArrays = (xs, ys): t => {xs: xs, ys: ys}
|
||||||
|
@ -126,8 +145,8 @@ module XtoY = {
|
||||||
/* Returns a between-points-interpolating function that can be used with PointwiseCombination.combine.
|
/* Returns a between-points-interpolating function that can be used with PointwiseCombination.combine.
|
||||||
Interpolation can either be stepwise (using the value on the left) or linear. Extrapolation can be `UseZero or `UseOutermostPoints. */
|
Interpolation can either be stepwise (using the value on the left) or linear. Extrapolation can be `UseZero or `UseOutermostPoints. */
|
||||||
let continuousInterpolator = (
|
let continuousInterpolator = (
|
||||||
interpolation: PointSetTypes.interpolationStrategy,
|
interpolation: interpolationStrategy,
|
||||||
extrapolation: PointSetTypes.extrapolationStrategy,
|
extrapolation: extrapolationStrategy,
|
||||||
): interpolator =>
|
): interpolator =>
|
||||||
switch (interpolation, extrapolation) {
|
switch (interpolation, extrapolation) {
|
||||||
| (#Linear, #UseZero) =>
|
| (#Linear, #UseZero) =>
|
||||||
|
@ -392,49 +411,9 @@ let logScorePoint = (sampleCount, t1, t2) =>
|
||||||
|> E.O.fmap(Pairs.y)
|
|> E.O.fmap(Pairs.y)
|
||||||
|
|
||||||
module Analysis = {
|
module Analysis = {
|
||||||
let integrateContinuousShape = (
|
|
||||||
~indefiniteIntegralStepwise=(p, h1) => h1 *. p,
|
|
||||||
~indefiniteIntegralLinear=(p, a, b) => a *. p +. b *. p ** 2.0 /. 2.0,
|
|
||||||
t: PointSetTypes.continuousShape,
|
|
||||||
): float => {
|
|
||||||
let xs = t.xyShape.xs
|
|
||||||
let ys = t.xyShape.ys
|
|
||||||
|
|
||||||
E.A.reducei(xs, 0.0, (acc, _x, i) => {
|
|
||||||
let areaUnderIntegral = // TODO Take this switch statement out of the loop body
|
|
||||||
switch (t.interpolation, i) {
|
|
||||||
| (_, 0) => 0.0
|
|
||||||
| (#Stepwise, _) =>
|
|
||||||
indefiniteIntegralStepwise(xs[i], ys[i - 1]) -.
|
|
||||||
indefiniteIntegralStepwise(xs[i - 1], ys[i - 1])
|
|
||||||
| (#Linear, _) =>
|
|
||||||
let x1 = xs[i - 1]
|
|
||||||
let x2 = xs[i]
|
|
||||||
if x1 == x2 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
let h1 = ys[i - 1]
|
|
||||||
let h2 = ys[i]
|
|
||||||
let b = (h1 -. h2) /. (x1 -. x2)
|
|
||||||
let a = h1 -. b *. x1
|
|
||||||
indefiniteIntegralLinear(x2, a, b) -. indefiniteIntegralLinear(x1, a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acc +. areaUnderIntegral
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let getMeanOfSquaresContinuousShape = (t: PointSetTypes.continuousShape) => {
|
|
||||||
let indefiniteIntegralLinear = (p, a, b) => a *. p ** 3.0 /. 3.0 +. b *. p ** 4.0 /. 4.0
|
|
||||||
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 3.0 /. 3.0
|
|
||||||
integrateContinuousShape(~indefiniteIntegralStepwise, ~indefiniteIntegralLinear, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
let getVarianceDangerously = (t: 't, mean: 't => float, getMeanOfSquares: 't => float): float => {
|
let getVarianceDangerously = (t: 't, mean: 't => float, getMeanOfSquares: 't => float): float => {
|
||||||
let meanSquared = mean(t) ** 2.0
|
let meanSquared = mean(t) ** 2.0
|
||||||
let meanOfSquares = getMeanOfSquares(t)
|
let meanOfSquares = getMeanOfSquares(t)
|
||||||
meanOfSquares -. meanSquared
|
meanOfSquares -. meanSquared
|
||||||
}
|
}
|
||||||
|
|
||||||
let squareXYShape = T.mapX(x => x ** 2.0)
|
|
||||||
}
|
}
|
|
@ -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)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user