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