Merge branch 'develop' into reducer-dev

This commit is contained in:
Umur Ozkul 2022-04-17 20:38:20 +02:00
commit da37536b4b
37 changed files with 1088 additions and 30864 deletions

1
.github/CODEOWNERS vendored
View File

@ -24,6 +24,7 @@
*.json @quinn-dougherty @Hazelfire @OAGr
*.y*ml @quinn-dougherty @OAGr
*.config.js @Hazelfire @OAGr
netlify.toml @quinn-dougherty @OAGr @Hazelfire
# Documentation
*.md @quinn-dougherty @OAGr @Hazelfire

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Ideas and feature requests - Squiggle Discussions on GitHub
url: https://github.com/quantified-uncertainty/squiggle/discussions
about: Please propose and discuss new features here. Remember to search for your idea before posting a new topic! Where would you like to see Squiggle go over the next few months, several months, or few years?

View File

@ -1,6 +0,0 @@
---
name: Idea or feature request
about: Where would you like to see Squiggle go over the next few months, several months, or few years?
---
# Description

View File

@ -6,7 +6,8 @@ labels: "ops & testing"
# Description:
<!-- delete this section if testing task or otherwise not applicable -->
# The OS and version, yarn version, etc. in which this came up
<!-- delete this section if testing task or otherwise not applicable -->
# Desired behavior

View File

@ -1,6 +1,6 @@
---
name: Bug reports for Squiggle users
about: Rendering oddly, trouble with the playground, something like this?
about: Rendering oddly? Is there a mathematical correctness problem?
labels: "bug"
---

View File

@ -4,14 +4,10 @@ on:
push:
branches:
- master
- production
- staging
- develop
pull_request:
branches:
- master
- production
- staging
- develop
jobs:

View File

@ -42,17 +42,6 @@ yarn
See `packages/*/README.md` to work with whatever project you're interested in.
## `codium` for `rescript`
If you have `nix` installed with `flakes` enabled, you can build a `codium` in this repo for `rescript` development, if you don't want to pollute your machine's global editor with another mode/extension.
```sh
nix develop
codium
```
The `nix develop` shell also provides `yarn`.
# Contributing
See `CONTRIBUTING.md`.

View File

@ -1,9 +0,0 @@
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
inherit (lock.nodes.flake-compat.locked) owner repo rev narHash;
flake-compat = builtins.fetchTarball {
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
sha256 = narHash;
};
in
import flake-compat { src = ./.; }

View File

@ -1,44 +0,0 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1641205782,
"narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1647893727,
"narHash": "sha256-pOi7VdCb+s5Cwh5CS7YEZVRgH9uCmE87J5W7iXv29Ck=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "1ec61dd4167f04be8d05c45780818826132eea0d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,94 +0,0 @@
{
description = "Building codium for rescript development";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
};
outputs =
{ self
, nixpkgs
, flake-compat
}:
let
# Generate a user-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate;
# System types to support.
supportedSystems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" ];
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Nixpkgs instantiated for supported system types.
nixpkgsFor = forAllSystems (system:
import nixpkgs {
inherit system;
overlays = [ self.overlay ];
});
in
{
overlay = final: prev: { };
# the default devShell used when running `nix develop`
devShell = forAllSystems (system: self.devShells.${system}.defaultShell);
devShells = forAllSystems (system:
let
pkgs = nixpkgsFor."${system}";
in
{
# In case we don't want to provide an editor, this defaultShell will provide only coq packages we need.
defaultShell = pkgs.mkShell {
buildInputs = with pkgs; [
yarn
yarn2nix
nodePackages.npm
nodejs
patchelf
(pkgs.vscode-with-extensions.override {
vscode = pkgs.vscodium;
vscodeExtensions = pkgs.vscode-utils.extensionsFromVscodeMarketplace [
{
name = "rescript-vscode";
publisher = "chenglou92";
version = "1.2.1";
sha256 = "sha256-7/YakKtJ4WhgAR4rZltrq8g4TtM5QZ2spbrEUrNoXVg=";
}
{
name = "vim";
publisher = "vscodevim";
version = "1.22.2";
sha256 = "sha256-dtIlgODzRdoMKnG9050ZcCX3w15A/R3FaMc+ZylvBbU=";
}
{
name = "vscode-typescript-next";
publisher = "ms-vscode";
version = "4.7.20220323";
sha256 = "sha256-mjiBCyg5As/XAU9I5k6jEZWGJA3P6P5o1roe2bS7aUI=";
}
{
name = "nix-ide";
publisher = "jnoortheen";
version = "0.1.20";
sha256 = "sha256-Q6X41I68m0jaCXaQGEFOoAbSUrr/wFhfCH5KrduOtZo=";
}
{
name = "json";
publisher = "ZainChen";
version = "2.0.2";
sha256 = "sha256-nC3Q8KuCtn/jg1j/NaAxWGvnKe/ykrPm2PUjfsJz8aI=";
}
{
name = "prettier-vscode";
publisher = "esbenp";
version = "9.3.0";
sha256 = "sha256-hJgPjWf7a8+ltjmXTK8U/MwqgIZqBjmcCfHsAk2G3PA=";
}
];
})
];
};
}
);
};
}

View File

@ -0,0 +1,8 @@
[build]
base = "packages/components/"
command = "cd ../squiggle-lang && yarn build && cd ../components && yarn build"
publish = "storybook-static/"
ignore = "node -e 'process.exitCode = process.env.BRANCH.includes(\"dependabot\") ? 0 : 1' && git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../squiggle-lang"
[build.environment]
NETLIFY_USE_YARN = "true"

View File

@ -3,19 +3,20 @@
"version": "0.1.8",
"dependencies": {
"@quri/squiggle-lang": "0.2.2",
"@react-hook/size": "^2.1.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.0.1",
"@testing-library/user-event": "^14.0.4",
"@types/jest": "^27.4.0",
"@types/lodash": "^4.14.181",
"@types/node": "^17.0.23",
"@types/node": "^17.0.24",
"@types/react": "^18.0.3",
"@types/react-dom": "^18.0.0",
"@types/react-dom": "^18.0.1",
"antd": "^4.19.3",
"cross-env": "^7.0.3",
"lodash": "^4.17.21",
"react": "^18.0.0",
"react-ace": "9.5.0",
"react-ace": "10.0.0",
"react-dom": "^18.0.0",
"react-scripts": "5.0.1",
"react-vega": "^7.5.0",
@ -66,14 +67,14 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.16.7",
"@storybook/addon-actions": "^6.4.20",
"@storybook/addon-essentials": "^6.4.20",
"@storybook/addon-links": "^6.4.20",
"@storybook/builder-webpack5": "^6.4.20",
"@storybook/manager-webpack5": "^6.4.20",
"@storybook/node-logger": "^6.4.20",
"@storybook/addon-actions": "^6.4.22",
"@storybook/addon-essentials": "^6.4.22",
"@storybook/addon-links": "^6.4.22",
"@storybook/builder-webpack5": "^6.4.22",
"@storybook/manager-webpack5": "^6.4.22",
"@storybook/node-logger": "^6.4.22",
"@storybook/preset-create-react-app": "^4.1.0",
"@storybook/react": "^6.4.20",
"@storybook/react": "^6.4.22",
"@types/styled-components": "^5.1.24",
"@types/webpack": "^5.28.0",
"react-codejar": "^1.1.2",

View File

@ -10,12 +10,16 @@ interface CodeEditorProps {
onChange: (value: string) => void;
oneLine?: boolean;
width?: number;
height: number;
showGutter?: boolean;
}
export let CodeEditor: FC<CodeEditorProps> = ({
value,
onChange,
oneLine = false,
showGutter = false,
height,
}: CodeEditorProps) => {
let lineCount = value.split("\n").length;
let id = _.uniqueId();
@ -25,9 +29,10 @@ export let CodeEditor: FC<CodeEditorProps> = ({
mode="golang"
theme="github"
width={"100%"}
minLines={oneLine ? lineCount : 15}
maxLines={oneLine ? lineCount : 15}
showGutter={false}
height={String(height) + "px"}
minLines={oneLine ? lineCount : undefined}
maxLines={oneLine ? lineCount : undefined}
showGutter={showGutter}
highlightActiveLine={false}
showPrintMargin={false}
onChange={onChange}

View File

@ -27,7 +27,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
return (
<SquiggleVegaChart
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
width={width}
width={width - 20}
height={height}
actions={false}
/>

View File

@ -1,10 +1,117 @@
import * as React from "react";
import _ from "lodash";
import { run, errorValueToString } from "@quri/squiggle-lang";
import styled from "styled-components";
import {
run,
errorValueToString,
squiggleExpression,
} from "@quri/squiggle-lang";
import type { samplingParams, exportEnv } from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import { DistributionChart } from "./DistributionChart";
import { ErrorBox } from "./ErrorBox";
import useSize from "@react-hook/size";
const variableBox = {
Component: styled.div`
background: white;
border: 1px solid #eee;
border-radius: 2px;
margin-bottom: 0.4em;
`,
Heading: styled.div`
border-bottom: 1px solid #eee;
padding-left: 0.8em;
padding-right: 0.8em;
padding-top: 0.1em;
`,
Body: styled.div`
padding: 0.4em 0.8em;
`,
};
export const VariableBox: React.FC<{
heading: string;
children: React.ReactNode;
}> = ({ heading = "Error", children }) => {
return (
<variableBox.Component>
<variableBox.Heading>
<h3>{heading}</h3>
</variableBox.Heading>
<variableBox.Body>{children}</variableBox.Body>
</variableBox.Component>
);
};
export interface SquiggleItemProps {
/** The input string for squiggle */
expression: squiggleExpression;
width: number;
height: number;
}
const SquiggleItem: React.FC<SquiggleItemProps> = ({
expression,
width,
height,
}: SquiggleItemProps) => {
switch (expression.tag) {
case "number":
return (
<VariableBox heading="Number">
<NumberShower precision={3} number={expression.value} />
</VariableBox>
);
case "distribution": {
let distType = expression.value.type();
return (
<VariableBox heading={`Distribution (${distType})`}>
{distType === "Symbolic" ? (
<>
<div>{expression.value.toString()}</div>
</>
) : (
<></>
)}
<DistributionChart
distribution={expression.value}
height={height}
width={width}
/>
</VariableBox>
);
}
case "string":
return (
<VariableBox heading="String">{`"${expression.value}"`}</VariableBox>
);
case "boolean":
return (
<VariableBox heading="Boolean">
{expression.value.toString()}
</VariableBox>
);
case "symbol":
return <VariableBox heading="Symbol">{expression.value}</VariableBox>;
case "call":
return <VariableBox heading="Call">{expression.value}</VariableBox>;
case "array":
return (
<VariableBox heading="Array">
{expression.value.map((r) => (
<SquiggleItem expression={r} width={width - 20} height={50} />
))}
</VariableBox>
);
default:
return (
<ErrorBox heading="No Viewer">
{"We don't currently have a working viewer for record types."}
</ErrorBox>
);
}
};
export interface SquiggleChartProps {
/** The input string for squiggle */
@ -36,41 +143,33 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
outputXYPoints = 1000,
environment = [],
onEnvChange = () => {},
width = 500,
height = 60,
width = NaN,
}: SquiggleChartProps) => {
const target = React.useRef(null);
const [componentWidth] = useSize(target);
// I would have wanted to just use componentWidth, but this created infinite loops with SquiggleChart.stories.
//So you can manually add a width, as an escape hatch.
let _width = width || componentWidth;
let samplingInputs: samplingParams = {
sampleCount: sampleCount,
xyPointLength: outputXYPoints,
};
let expressionResult = run(squiggleString, samplingInputs, environment);
let internal: JSX.Element;
if (expressionResult.tag === "Ok") {
onEnvChange(environment);
let expression = expressionResult.value;
if (expression.tag === "number") {
return <NumberShower precision={3} number={expression.value} />;
} else if (expression.tag === "distribution") {
return (
<DistributionChart
distribution={expression.value}
height={height}
width={width}
/>
internal = (
<SquiggleItem expression={expression} width={_width} height={height} />
);
} else {
return (
<ErrorBox heading="No Viewer">
{"We don't currently have a viewer for this type: " + expression.tag}
</ErrorBox>
);
}
} else {
// At this point, we came across an error. What was our error?
return (
internal = (
<ErrorBox heading={"Parse Error"}>
{errorValueToString(expressionResult.value)}
</ErrorBox>
);
}
return <div ref={target}>{internal}</div>;
};

View File

@ -55,6 +55,8 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
value={expression}
onChange={setExpression}
oneLine={true}
showGutter={false}
height={20}
/>
</Input>
<SquiggleChart

View File

@ -3,7 +3,8 @@ 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 { Form, Input, Row, Col } from "antd";
import styled from "styled-components";
import "antd/dist/antd.css";
interface FieldFloatProps {
@ -34,9 +35,40 @@ function FieldFloat(Props: FieldFloatProps) {
interface Props {
initialSquiggleString?: string;
height?: number;
}
let SquigglePlayground: FC<Props> = ({ initialSquiggleString = "" }: Props) => {
interface Props2 {
height: number;
}
const ShowBox = styled.div<Props2>`
border: 1px solid #eee;
border-radius: 2px;
height: ${(props) => props.height};
`;
const MyComponent = styled.div`
color: ${(props) => props.theme.colors.main};
`;
interface TitleProps {
readonly maxHeight: number;
}
const Display = styled.div<TitleProps>`
background: #f6f6f6;
border-left: 1px solid #eee;
height: 100vh;
padding: 3px;
overflow-y: auto;
max-height: ${(props) => props.maxHeight}px;
`;
let SquigglePlayground: FC<Props> = ({
initialSquiggleString = "",
height = 300,
}: Props) => {
let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
let [sampleCount, setSampleCount] = useState(1000);
let [outputXYPoints, setOutputXYPoints] = useState(1000);
@ -44,7 +76,20 @@ let SquigglePlayground: FC<Props> = ({ initialSquiggleString = "" }: Props) => {
let [diagramStart, setDiagramStart] = useState(0);
let [diagramStop, setDiagramStop] = useState(10);
let [diagramCount, setDiagramCount] = useState(20);
var demoDist = (
return (
<ShowBox height={height}>
<Row>
<Col span={12}>
<CodeEditor
value={squiggleString}
onChange={setSquiggleString}
oneLine={false}
showGutter={true}
height={height - 3}
/>
</Col>
<Col span={12}>
<Display maxHeight={height - 3}>
<SquiggleChart
squiggleString={squiggleString}
sampleCount={sampleCount}
@ -55,70 +100,10 @@ let SquigglePlayground: FC<Props> = ({ initialSquiggleString = "" }: Props) => {
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}
/>
</Display>
</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>
</ShowBox>
);
};
export default SquigglePlayground;

View File

@ -4,6 +4,11 @@ import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
export const Template = SquiggleChart;
/*
We have to hardcode a width here, because otherwise some interaction with
Storybook creates an infinite loop with the internal width
*/
const width = 600;
# Squiggle Chart
@ -18,13 +23,42 @@ could be continuous, discrete or mixed.
## Distributions
### Continuous Distributions
### Continuous Distributions (Symbolic)
<Canvas>
<Story
name="Normal"
name="Continuous Symbolic"
args={{
squiggleString: "normal(5,2)",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
### Continuous Distributions (PointSet)
<Canvas>
<Story
name="Continuous Pointset"
args={{
squiggleString: "toPointSet(normal(5,2))",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
### Continuous Distributions (SampleSet)
<Canvas>
<Story
name="Continuous SampleSet"
args={{
squiggleString: "toSampleSet(normal(5,2), 1000)",
width,
}}
>
{Template.bind({})}
@ -38,6 +72,7 @@ could be continuous, discrete or mixed.
name="Discrete"
args={{
squiggleString: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
width,
}}
>
{Template.bind({})}
@ -52,6 +87,7 @@ could be continuous, discrete or mixed.
args={{
squiggleString:
"mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
width,
}}
>
{Template.bind({})}
@ -68,23 +104,21 @@ to allow large and small numbers being printed cleanly.
name="Constant"
args={{
squiggleString: "500000000",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Functions
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.
## Arrays
<Canvas>
<Story
name="Function"
name="Array"
args={{
squiggleString: "f(x) = normal(x^2,(x+.1)^1.8)\nf",
squiggleString: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
width,
}}
>
{Template.bind({})}
@ -98,6 +132,49 @@ The default is show 10 points between 0 and 10.
name="Error"
args={{
squiggleString: "f(x) = normal(",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Booleans
<Canvas>
<Story
name="Boolean"
args={{
squiggleString: "3 == 3",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Records
<Canvas>
<Story
name="Record"
args={{
squiggleString: "{foo: 35 to 50, bar: [1,2,3]}",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Strings
<Canvas>
<Story
name="String"
args={{
squiggleString: '"Lucky day!"',
width,
}}
>
{Template.bind({})}

View File

@ -1,5 +1,6 @@
import SquigglePlayground from "../components/SquigglePlayground";
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
import styled from "styled-components";
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
@ -15,6 +16,7 @@ including sampling settings, in squiggle.
name="Normal"
args={{
initialSquiggleString: "normal(5,2)",
height: 500,
}}
>
{Template.bind({})}

View File

@ -19,12 +19,12 @@ describe("eval on distribution functions", () => {
testEval("lognormal(5,2)", "Ok(Lognormal(5,2))")
})
describe("unaryMinus", () => {
testEval("mean(-normal(5,2))", "Ok(-5.002887370380851)")
testEval("mean(-normal(5,2))", "Ok(-5)")
})
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))")
testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))")
testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))")
})
describe("mean", () => {
testEval("mean(normal(5,2))", "Ok(5)")
@ -45,10 +45,30 @@ describe("eval on distribution functions", () => {
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)")
testEval("add(normal(5,2), 3)", "Ok(Normal(8,2))")
testEval("add(3, normal(5,2))", "Ok(Normal(8,2))")
testEval("3+normal(5,2)", "Ok(Normal(8,2))")
testEval("normal(5,2)+3", "Ok(Normal(8,2))")
})
describe("subtract", () => {
testEval("10 - normal(5, 1)", "Ok(Normal(5,1))")
testEval("normal(5, 1) - 10", "Ok(Normal(-5,1))")
})
describe("multiply", () => {
testEval("normal(10, 2) * 2", "Ok(Normal(20,4))")
testEval("2 * normal(10, 2)", "Ok(Normal(20,4))")
testEval("lognormal(5,2) * lognormal(10,2)", "Ok(Lognormal(15,2.8284271247461903))")
testEval("lognormal(10, 2) * lognormal(5, 2)", "Ok(Lognormal(15,2.8284271247461903))")
testEval("2 * lognormal(5, 2)", "Ok(Lognormal(5.693147180559945,2))")
testEval("lognormal(5, 2) * 2", "Ok(Lognormal(5.693147180559945,2))")
})
describe("division", () => {
testEval("lognormal(5,2) / lognormal(10,2)", "Ok(Lognormal(-5,2.8284271247461903))")
testEval("lognormal(10,2) / lognormal(5,2)", "Ok(Lognormal(5,2.8284271247461903))")
testEval("lognormal(5, 2) / 2", "Ok(Lognormal(4.306852819440055,2))")
testEval("2 / lognormal(5, 2)", "Ok(Lognormal(-4.306852819440055,2))")
testEval("2 / normal(10, 2)", "Ok(Point Set Distribution)")
testEval("normal(10, 2) / 2", "Ok(Normal(5,1))")
})
describe("truncate", () => {
testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)")
@ -101,6 +121,10 @@ describe("parse on distribution functions", () => {
testParse("3 ^ normal(5,1)", "Ok((:pow 3 (:normal 5 1)))")
testParse("normal(5,2) ^ 3", "Ok((:pow (:normal 5 2) 3))")
})
describe("subtraction", () => {
testParse("10 - normal(5,1)", "Ok((:subtract 10 (:normal 5 1)))")
testParse("normal(5,1) - 10", "Ok((:subtract (:normal 5 1) 10))")
})
describe("pointwise arithmetic expressions", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
testParse(

View File

@ -28,6 +28,7 @@ import {
Constructors_cdf,
Constructors_inv,
Constructors_normalize,
Constructors_isNormalized,
Constructors_toPointSet,
Constructors_toSampleSet,
Constructors_truncate,
@ -47,6 +48,7 @@ import {
Constructors_pointwiseLogarithm,
Constructors_pointwisePower,
} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen";
import { pointSetDistFn } from "../rescript/OldInterpreter/DistPlus.bs";
export type { samplingParams, errorValue };
export let defaultSamplingInputs: samplingParams = {
@ -189,12 +191,20 @@ export class Distribution {
return Constructors_inv({ env: this.env }, this.t, n);
}
isNormalized(): result<boolean, distributionError> {
return Constructors_isNormalized({ env: this.env }, this.t);
}
normalize(): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_normalize({ env: this.env }, this.t)
);
}
type() {
return this.t.tag;
}
pointSet(): result<shape, distributionError> {
let pointSet = toPointSet(
this.t,

View File

@ -13,6 +13,7 @@ type outputType =
| Dist(genericDist)
| Float(float)
| String(string)
| Bool(bool)
| GenDistError(error)
/*
@ -66,6 +67,18 @@ module OutputLocal = {
| e => Error(toErrorOrUnreachable(e))
}
let toBool = (t: t) =>
switch t {
| Bool(d) => Some(d)
| _ => None
}
let toBoolR = (t: t): result<bool, error> =>
switch t {
| Bool(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 {
@ -107,8 +120,8 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
(),
)->OutputLocal.toDistR
let fromDistFn = (subFnName: GenericDist_Types.Operation.fromDist, dist: genericDist) =>
switch subFnName {
let fromDistFn = (subFnName: GenericDist_Types.Operation.fromDist, dist: genericDist) => {
let response = switch subFnName {
| ToFloat(distToFloatOperation) =>
GenericDist.toFloatOperation(dist, ~toPointSetFn, ~distToFloatOperation)
->E.R2.fmap(r => Float(r))
@ -123,6 +136,7 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
Dist(dist)
}
| ToDist(Normalize) => dist->GenericDist.normalize->Dist
| ToBool(IsNormalized) => dist->GenericDist.isNormalized->Bool
| ToDist(Truncate(leftCutoff, rightCutoff)) =>
GenericDist.truncate(~toPointSetFn, ~leftCutoff, ~rightCutoff, dist, ())
->E.R2.fmap(r => Dist(r))
@ -154,6 +168,8 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
->E.R2.fmap(r => Dist(r))
->OutputLocal.fromResult
}
response
}
switch functionCallInfo {
| FromDist(subFnName, dist) => fromDistFn(subFnName, dist)
@ -201,6 +217,7 @@ module Constructors = {
let inv = (~env, dist, f) => C.inv(dist, f)->run(~env)->toFloatR
let pdf = (~env, dist, f) => C.pdf(dist, f)->run(~env)->toFloatR
let normalize = (~env, dist) => C.normalize(dist)->run(~env)->toDistR
let isNormalized = (~env, dist) => C.isNormalized(dist)->run(~env)->toBoolR
let toPointSet = (~env, dist) => C.toPointSet(dist)->run(~env)->toDistR
let toSampleSet = (~env, dist, n) => C.toSampleSet(dist, n)->run(~env)->toDistR
let truncate = (~env, dist, leftCutoff, rightCutoff) =>

View File

@ -11,6 +11,7 @@ type outputType =
| Dist(genericDist)
| Float(float)
| String(string)
| Bool(bool)
| GenDistError(error)
@genType
@ -34,6 +35,8 @@ module Output: {
let toFloatR: t => result<float, error>
let toString: t => option<string>
let toStringR: t => result<string, error>
let toBool: t => option<bool>
let toBoolR: t => result<bool, error>
let toError: t => option<error>
let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t
}
@ -52,6 +55,8 @@ module Constructors: {
@genType
let normalize: (~env: env, genericDist) => result<genericDist, error>
@genType
let isNormalized: (~env: env, genericDist) => result<bool, error>
@genType
let toPointSet: (~env: env, genericDist) => result<genericDist, error>
@genType
let toSampleSet: (~env: env, genericDist, int) => result<genericDist, error>

View File

@ -32,6 +32,15 @@ let normalize = (t: t): t =>
| SampleSet(_) => t
}
let integralEndY = (t: t): float =>
switch t {
| PointSet(r) => PointSetDist.T.integralEndY(r)
| Symbolic(_) => 1.0
| SampleSet(_) => 1.0
}
let isNormalized = (t: t): bool => Js.Math.abs_float(integralEndY(t) -. 1.0) < 1e-7
let toFloatOperation = (
t,
~toPointSetFn: toPointSetFn,

View File

@ -15,6 +15,8 @@ let toString: t => string
let normalize: t => t
let isNormalized: t => bool
let toFloatOperation: (
t,
~toPointSetFn: toPointSetFn,

View File

@ -71,11 +71,14 @@ module Operation = {
| ToString
| ToSparkline(int)
type toBool = IsNormalized
type fromDist =
| ToFloat(toFloat)
| ToDist(toDist)
| ToDistCombination(direction, arithmeticOperation, [#Dist(genericDist) | #Float(float)])
| ToString(toString)
| ToBool(toBool)
type singleParamaterFunction =
| FromDist(fromDist)
@ -101,6 +104,7 @@ module Operation = {
| ToDist(Inspect) => `inspect`
| ToString(ToString) => `toString`
| ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})`
| ToBool(IsNormalized) => `isNormalized`
| ToDistCombination(Algebraic, _, _) => `algebraic`
| ToDistCombination(Pointwise, _, _) => `pointwise`
}
@ -131,6 +135,7 @@ module Constructors = {
let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist)
let pdf = (dist, x): t => FromDist(ToFloat(#Pdf(x)), dist)
let normalize = (dist): t => FromDist(ToDist(Normalize), dist)
let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist)
let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist)
let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist)
let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist)

View File

@ -47,6 +47,7 @@ module Dist = (T: dist) => {
let truncate = T.truncate
let mean = T.mean
let variance = T.variance
let integralEndY = T.integralEndY
let updateIntegralCache = T.updateIntegralCache

View File

@ -1,5 +1,8 @@
open SymbolicDistTypes
let normal95confidencePoint = 1.6448536269514722
// explained in website/docs/internal/ProcessingConfidenceIntervals
module Normal = {
type t = normal
let make = (mean: float, stdev: float): result<symbolicDist, string> =>
@ -11,7 +14,7 @@ module Normal = {
let from90PercentCI = (low, high) => {
let mean = E.A.Floats.mean([low, high])
let stdev = (high -. low) /. (2. *. 1.644854)
let stdev = (high -. low) /. (2. *. normal95confidencePoint)
#Normal({mean: mean, stdev: stdev})
}
let inv = (p, t: t) => Jstat.Normal.inv(p, t.mean, t.stdev)
@ -21,12 +24,12 @@ module Normal = {
let add = (n1: t, n2: t) => {
let mean = n1.mean +. n2.mean
let stdev = sqrt(n1.stdev ** 2. +. n2.stdev ** 2.)
let stdev = Js.Math.sqrt(n1.stdev ** 2. +. n2.stdev ** 2.)
#Normal({mean: mean, stdev: stdev})
}
let subtract = (n1: t, n2: t) => {
let mean = n1.mean -. n2.mean
let stdev = sqrt(n1.stdev ** 2. +. n2.stdev ** 2.)
let stdev = Js.Math.sqrt(n1.stdev ** 2. +. n2.stdev ** 2.)
#Normal({mean: mean, stdev: stdev})
}
@ -44,6 +47,23 @@ module Normal = {
| #Subtract => Some(subtract(n1, n2))
| _ => None
}
let operateFloatFirst = (operation: Operation.Algebraic.t, n1: float, n2: t) =>
switch operation {
| #Add => Some(#Normal({mean: n1 +. n2.mean, stdev: n2.stdev}))
| #Subtract => Some(#Normal({mean: n1 -. n2.mean, stdev: n2.stdev}))
| #Multiply => Some(#Normal({mean: n1 *. n2.mean, stdev: n1 *. n2.stdev}))
| _ => None
}
let operateFloatSecond = (operation: Operation.Algebraic.t, n1: t, n2: float) =>
switch operation {
| #Add => Some(#Normal({mean: n1.mean +. n2, stdev: n1.stdev}))
| #Subtract => Some(#Normal({mean: n1.mean -. n2, stdev: n1.stdev}))
| #Multiply => Some(#Normal({mean: n1.mean *. n2, stdev: n1.stdev *. n2}))
| #Divide => Some(#Normal({mean: n1.mean /. n2, stdev: n1.stdev /. n2}))
| _ => None
}
}
module Exponential = {
@ -115,19 +135,22 @@ module Lognormal = {
let mean = (t: t) => Ok(Jstat.Lognormal.mean(t.mu, t.sigma))
let sample = (t: t) => Jstat.Lognormal.sample(t.mu, t.sigma)
let toString = ({mu, sigma}: t) => j`Lognormal($mu,$sigma)`
let from90PercentCI = (low, high) => {
let logLow = Js.Math.log(low)
let logHigh = Js.Math.log(high)
let mu = E.A.Floats.mean([logLow, logHigh])
let sigma = (logHigh -. logLow) /. (2.0 *. 1.645)
let sigma = (logHigh -. logLow) /. (2.0 *. normal95confidencePoint)
#Lognormal({mu: mu, sigma: sigma})
}
let fromMeanAndStdev = (mean, stdev) => {
// https://math.stackexchange.com/questions/2501783/parameters-of-a-lognormal-distribution
// https://wikiless.org/wiki/Log-normal_distribution?lang=en#Generation_and_parameters
if stdev > 0.0 {
let variance = Js.Math.pow_float(~base=stdev, ~exp=2.0)
let meanSquared = Js.Math.pow_float(~base=mean, ~exp=2.0)
let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0)
let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5)
let variance = stdev ** 2.
let meanSquared = mean ** 2.
let mu = 2. *. Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance +. meanSquared)
let sigma = Js.Math.sqrt(Js.Math.log(variance /. meanSquared +. 1.))
Ok(#Lognormal({mu: mu, sigma: sigma}))
} else {
Error("Lognormal standard deviation must be larger than 0")
@ -135,15 +158,16 @@ module Lognormal = {
}
let multiply = (l1, l2) => {
// https://wikiless.org/wiki/Log-normal_distribution?lang=en#Multiplication_and_division_of_independent,_log-normal_random_variables
let mu = l1.mu +. l2.mu
let sigma = l1.sigma +. l2.sigma
let sigma = Js.Math.sqrt(l1.sigma ** 2. +. l2.sigma ** 2.)
#Lognormal({mu: mu, sigma: sigma})
}
let divide = (l1, l2) => {
let mu = l1.mu -. l2.mu
// We believe the ratiands will have covariance zero.
// See here https://stats.stackexchange.com/questions/21735/what-are-the-mean-and-variance-of-the-ratio-of-two-lognormal-variables for details
let sigma = l1.sigma +. l2.sigma
let sigma = Js.Math.sqrt(l1.sigma ** 2. +. l2.sigma ** 2.)
#Lognormal({mu: mu, sigma: sigma})
}
let operate = (operation: Operation.Algebraic.t, n1: t, n2: t) =>
@ -152,6 +176,22 @@ module Lognormal = {
| #Divide => Some(divide(n1, n2))
| _ => None
}
let operateFloatFirst = (operation: Operation.Algebraic.t, n1: float, n2: t) =>
switch operation {
| #Multiply =>
n1 > 0.0 ? Some(#Lognormal({mu: Js.Math.log(n1) +. n2.mu, sigma: n2.sigma})) : None
| #Divide => n1 > 0.0 ? Some(#Lognormal({mu: Js.Math.log(n1) -. n2.mu, sigma: n2.sigma})) : None
| _ => None
}
let operateFloatSecond = (operation: Operation.Algebraic.t, n1: t, n2: float) =>
switch operation {
| #Multiply =>
n2 > 0.0 ? Some(#Lognormal({mu: n1.mu +. Js.Math.log(n2), sigma: n1.sigma})) : None
| #Divide => n2 > 0.0 ? Some(#Lognormal({mu: n1.mu -. Js.Math.log(n2), sigma: n1.sigma})) : None
| _ => None
}
}
module Uniform = {
@ -343,8 +383,28 @@ module T = {
}
| (#Normal(v1), #Normal(v2)) =>
Normal.operate(op, v1, v2) |> E.O.dimap(r => #AnalyticalSolution(r), () => #NoSolution)
| (#Float(v1), #Normal(v2)) =>
Normal.operateFloatFirst(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| (#Normal(v1), #Float(v2)) =>
Normal.operateFloatSecond(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| (#Lognormal(v1), #Lognormal(v2)) =>
Lognormal.operate(op, v1, v2) |> E.O.dimap(r => #AnalyticalSolution(r), () => #NoSolution)
| (#Float(v1), #Lognormal(v2)) =>
Lognormal.operateFloatFirst(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| (#Lognormal(v1), #Float(v2)) =>
Lognormal.operateFloatSecond(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| _ => #NoSolution
}

View File

@ -1,9 +1,11 @@
module ExpressionValue = ReducerInterface_ExpressionValue
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
let defaultSampleCount = 10000
let runGenericOperation = DistributionOperation.run(
~env={
sampleCount: 1000,
sampleCount: defaultSampleCount,
xyPointLength: 1000,
},
)
@ -52,6 +54,13 @@ module Helpers = {
FromDist(GenericDist_Types.Operation.ToString(fnCall), dist)->runGenericOperation->Some
}
let toBoolFn = (
fnCall: GenericDist_Types.Operation.toBool,
dist: GenericDist_Types.genericDist,
) => {
FromDist(GenericDist_Types.Operation.ToBool(fnCall), dist)->runGenericOperation->Some
}
let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => {
FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some
}
@ -193,12 +202,16 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
// 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)
| ("isNormalized", [EvDistribution(dist)]) => Helpers.toBoolFn(IsNormalized, 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)
| ("toSampleSet", [EvDistribution(dist)]) =>
Helpers.toDistFn(ToSampleSet(defaultSampleCount), dist)
| ("inspect", [EvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist)
| ("truncateLeft", [EvDistribution(dist), EvNumber(float)]) =>
Helpers.toDistFn(Truncate(Some(float), None), dist)
| ("truncateRight", [EvDistribution(dist), EvNumber(float)]) =>
@ -244,6 +257,7 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
| Dist(d) => Ok(ReducerInterface_ExpressionValue.EvDistribution(d))
| Float(d) => Ok(EvNumber(d))
| String(d) => Ok(EvString(d))
| Bool(d) => Ok(EvBool(d))
| GenDistError(NotYetImplemented) => Error(RETodo("Function not yet implemented"))
| GenDistError(Unreachable) => Error(RETodo("Unreachable"))
| GenDistError(DistributionVerticalShiftIsInvalid) =>

View File

@ -0,0 +1,32 @@
# Processing confidence intervals
This page explains what we are doing when we take a 95% confidence interval, and we get a mean and a standard deviation from it
## For normals
```js
module Normal = {
//...
let from90PercentCI = (low, high) => {
let mean = E.A.Floats.mean([low, high])
let stdev = (high -. low) /. (2. *. 1.6448536269514722)
#Normal({mean: mean, stdev: stdev})
}
//...
}
```
We know that for a normal with mean $\mu$ and standard deviation $\sigma$,
$$
a \cdot Normal(\mu, \sigma) = Normal(a\cdot \mu, |a|\cdot \sigma)
$$
We can now look at the inverse cdf of a $Normal(0,1)$. We find that the 95% point is reached at $1.6448536269514722$. ([source](https://stackoverflow.com/questions/20626994/how-to-calculate-the-inverse-of-the-normal-cumulative-distribution-function-in-p)) This means that the 90% confidence interval is $[-1.6448536269514722, 1.6448536269514722]$, which has a width of $2 \cdot 1.6448536269514722$.
So then, if we take a $Normal(0,1)$ and we multiply it by $\frac{(high -. low)}{(2. *. 1.6448536269514722)}$, it's 90% confidence interval will be multiplied by the same amount. Then we just have to shift it by the mean to get our target normal.
## For lognormals

View File

@ -0,0 +1,8 @@
[build]
base = "packages/website/"
command = "cd ../squiggle-lang && yarn build && cd ../website && yarn build"
publish = "build/"
ignore = "node -e 'process.exitCode = process.env.BRANCH.includes(\"dependabot\") ? 0 : 1' && git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ../"
[build.environment]
NETLIFY_USE_YARN = "true"

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
"react-dom": "^18.0.0",
"remark-math": "^3",
"rehype-katex": "^5",
"hast-util-is-element": "1.1.0"
"hast-util-is-element": "2.1.2"
},
"browserslist": {
"production": [

View File

@ -8,12 +8,9 @@ export default function PlaygroundPage() {
<div
style={{
maxWidth: 2000,
paddingTop: "3em",
margin: "0 auto",
}}
>
<h2> Squiggle Playground </h2>
<SquigglePlayground initialSquiggleString="normal(0,1)" />
<SquigglePlayground initialSquiggleString="normal(0,1)" height={500} />
</div>
</Layout>
);

File diff suppressed because it is too large Load Diff

1093
yarn.lock

File diff suppressed because it is too large Load Diff