Merge branch 'develop' into multiple-charts
This commit is contained in:
commit
ffb9e85d75
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
|
@ -11,3 +11,10 @@ updates:
|
|||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "⬆️"
|
||||
open-pull-requests-limit: 100
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "⬆️"
|
||||
|
|
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
|
@ -24,27 +24,27 @@ jobs:
|
|||
steps:
|
||||
- id: skip_lang_check
|
||||
name: Check if the changes are about squiggle-lang src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/squiggle-lang/**"]'
|
||||
- id: skip_components_check
|
||||
name: Check if the changes are about components src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/components/**"]'
|
||||
- id: skip_website_check
|
||||
name: Check if the changes are about website src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/website/**"]'
|
||||
- id: skip_vscodeext_check
|
||||
name: Check if the changes are about vscode extension src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/vscode-ext/**"]'
|
||||
- id: skip_cli_check
|
||||
name: Check if the changes are about cli src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/cli/**"]'
|
||||
|
||||
|
@ -58,7 +58,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/squiggle-lang
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Dependencies
|
||||
run: cd ../../ && yarn
|
||||
- name: Check rescript lint
|
||||
|
@ -79,7 +79,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/squiggle-lang
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Install dependencies from monorepo level
|
||||
|
@ -107,7 +107,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/components
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check javascript, typescript, and markdown lint
|
||||
uses: creyD/prettier_action@v4.2
|
||||
with:
|
||||
|
@ -124,7 +124,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/components
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Build rescript codebase in squiggle-lang
|
||||
|
@ -144,7 +144,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/website
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check javascript, typescript, and markdown lint
|
||||
uses: creyD/prettier_action@v4.2
|
||||
with:
|
||||
|
@ -161,7 +161,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/website
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Build rescript in squiggle-lang
|
||||
|
@ -181,7 +181,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/vscode-ext
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Lint the VSCode Extension source code
|
||||
|
@ -197,7 +197,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/vscode-ext
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Build
|
||||
|
@ -213,7 +213,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/cli
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check javascript, typescript, and markdown lint
|
||||
uses: creyD/prettier_action@v4.2
|
||||
with:
|
||||
|
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
@ -33,11 +33,11 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -48,7 +48,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Build rescript
|
||||
|
@ -65,4 +65,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
140
.github/workflows/release-please.yml
vendored
Normal file
140
.github/workflows/release-please.yml
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
name: Run Release Please
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
pre_check:
|
||||
name: Precheck for skipping redundant jobs
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
|
||||
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
|
||||
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
|
||||
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
|
||||
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_lang_check
|
||||
name: Check if the changes are about squiggle-lang src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/squiggle-lang/**"]'
|
||||
- id: skip_components_check
|
||||
name: Check if the changes are about components src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/components/**"]'
|
||||
- id: skip_website_check
|
||||
name: Check if the changes are about website src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/website/**"]'
|
||||
- id: skip_vscodeext_check
|
||||
name: Check if the changes are about vscode extension src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/vscode-ext/**"]'
|
||||
- id: skip_cli_check
|
||||
name: Check if the changes are about cli src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/cli/**"]'
|
||||
|
||||
relplz-lang:
|
||||
name: for squiggle-lang
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
||||
steps:
|
||||
- name: Release please (squiggle-lang)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
id: release
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/squiggle-lang
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
# - name: Publish: Checkout source
|
||||
# uses: actions/checkout@v2
|
||||
# # these if statements ensure that a publication only occurs when
|
||||
# # a new release is created:
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish: Install dependencies
|
||||
# run: yarn
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish
|
||||
# run: cd packages/squiggle-lang && yarn publish
|
||||
# env:
|
||||
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
relplz-components:
|
||||
name: for components
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
|
||||
steps:
|
||||
- name: Release please (components)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/components
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
# - name: Publish: Checkout source
|
||||
# uses: actions/checkout@v2
|
||||
# # these if statements ensure that a publication only occurs when
|
||||
# # a new release is created:
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish: Install dependencies
|
||||
# run: yarn
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish
|
||||
# run: cd packages/components && yarn publish
|
||||
# env:
|
||||
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
relplz-website:
|
||||
name: for website
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
|
||||
steps:
|
||||
- name: Release please (website)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/website
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
relplz-vscodeext:
|
||||
name: for vscode-ext
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
|
||||
steps:
|
||||
- name: Release please (vscode-ext)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/vscode-ext
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
relplz-cl:
|
||||
name: for cli
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
|
||||
steps:
|
||||
- name: Release please (cli)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/cli
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
7
.release-please-manifest.json
Normal file
7
.release-please-manifest.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"packages/cli": "0.0.3",
|
||||
"packages/components": "0.3.1",
|
||||
"packages/squiggle-lang": "0.3.0",
|
||||
"packages/vscode-ext": "0.3.1",
|
||||
"packages/website": "0.3.0"
|
||||
}
|
|
@ -12,8 +12,8 @@ _An estimation language_.
|
|||
|
||||
- [Gallery](https://www.squiggle-language.com/docs/Discussions/Gallery)
|
||||
- [Squiggle playground](https://squiggle-language.com/playground)
|
||||
- [Language basics](https://www.squiggle-language.com/docs/Features/Language)
|
||||
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Features/Functions)
|
||||
- [Language basics](https://www.squiggle-language.com/docs/Guides/Language)
|
||||
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Guides/Functions)
|
||||
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
|
||||
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
||||
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
|
||||
|
|
|
@ -54,7 +54,6 @@ export function DynamicSquiggleChart({ squiggleString }) {
|
|||
width={445}
|
||||
height={200}
|
||||
showSummary={true}
|
||||
showTypes={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,43 +1,45 @@
|
|||
{
|
||||
"name": "@quri/squiggle-components",
|
||||
"version": "0.2.23",
|
||||
"version": "0.3.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^1.0.0",
|
||||
"@floating-ui/react-dom-interactions": "^0.9.1",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/resolvers": "^2.9.6",
|
||||
"@quri/squiggle-lang": "^0.2.8",
|
||||
"@hookform/resolvers": "^2.9.7",
|
||||
"@quri/squiggle-lang": "^0.3.0",
|
||||
"@react-hook/size": "^2.1.2",
|
||||
"clsx": "^1.2.1",
|
||||
"framer-motion": "^6.5.1",
|
||||
"framer-motion": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.1.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-hook-form": "^7.33.1",
|
||||
"react-hook-form": "^7.34.0",
|
||||
"react-use": "^17.4.0",
|
||||
"react-vega": "^7.6.0",
|
||||
"vega": "^5.22.1",
|
||||
"vega-embed": "^6.21.0",
|
||||
"vega-lite": "^5.3.0",
|
||||
"vega-lite": "^5.4.0",
|
||||
"vscode-uri": "^3.0.3",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
||||
"@storybook/addon-actions": "^6.5.9",
|
||||
"@storybook/addon-essentials": "^6.5.9",
|
||||
"@storybook/addon-links": "^6.5.9",
|
||||
"@storybook/builder-webpack5": "^6.5.9",
|
||||
"@storybook/manager-webpack5": "^6.5.9",
|
||||
"@storybook/addon-essentials": "^6.5.10",
|
||||
"@storybook/addon-links": "^6.5.10",
|
||||
"@storybook/builder-webpack5": "^6.5.10",
|
||||
"@storybook/manager-webpack5": "^6.5.10",
|
||||
"@storybook/node-logger": "^6.5.9",
|
||||
"@storybook/preset-create-react-app": "^4.1.2",
|
||||
"@storybook/react": "^6.5.9",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@storybook/react": "^6.5.10",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.2.6",
|
||||
"@testing-library/user-event": "^14.4.2",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^18.0.6",
|
||||
"@types/node": "^18.6.4",
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/styled-components": "^5.1.24",
|
||||
"@types/webpack": "^5.28.0",
|
||||
|
@ -49,12 +51,12 @@
|
|||
"react": "^18.1.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"ts-loader": "^9.3.0",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^4.7.4",
|
||||
"web-vitals": "^2.1.4",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.3"
|
||||
},
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
import { Vega } from "react-vega";
|
||||
import { ErrorAlert } from "./Alert";
|
||||
import { useSize } from "react-use";
|
||||
import clsx from "clsx";
|
||||
|
||||
import {
|
||||
buildVegaSpec,
|
||||
|
@ -19,19 +18,18 @@ import {
|
|||
import { NumberShower } from "./NumberShower";
|
||||
import { Plot, parsePlot } from "../lib/plotParser";
|
||||
import { flattenResult, all } from "../lib/utility";
|
||||
import { hasMassBelowZero } from "../lib/distributionUtils";
|
||||
|
||||
export type DistributionPlottingSettings = {
|
||||
/** Whether to show a summary of means, stdev, percentiles etc */
|
||||
showSummary: boolean;
|
||||
/** Whether to show the user graph controls (scale etc) */
|
||||
showControls: boolean;
|
||||
actions?: boolean;
|
||||
} & DistributionChartSpecOptions;
|
||||
|
||||
export type DistributionChartProps = {
|
||||
plot: Plot;
|
||||
width?: number;
|
||||
height: number;
|
||||
actions?: boolean;
|
||||
} & DistributionPlottingSettings;
|
||||
|
||||
export function defaultPlot(distribution: Distribution): Plot {
|
||||
|
@ -53,17 +51,10 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
height,
|
||||
showSummary,
|
||||
width,
|
||||
showControls,
|
||||
logX,
|
||||
expY,
|
||||
actions = false,
|
||||
} = props;
|
||||
const [isLogX, setLogX] = React.useState(logX);
|
||||
const [isExpY, setExpY] = React.useState(expY);
|
||||
|
||||
React.useEffect(() => setLogX(logX), [logX]);
|
||||
React.useEffect(() => setExpY(expY), [expY]);
|
||||
|
||||
const shape = distribution.pointSet();
|
||||
const [sized] = useSize((size) => {
|
||||
let shapes = flattenResult(
|
||||
plot.distributions.map((x) =>
|
||||
|
@ -82,13 +73,6 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
);
|
||||
}
|
||||
|
||||
const massBelow0 = all(
|
||||
shapes.value.map(
|
||||
(shape) =>
|
||||
shape.continuous.some((x) => x.x <= 0) ||
|
||||
shape.discrete.some((x) => x.x <= 0)
|
||||
)
|
||||
);
|
||||
const spec = buildVegaSpec(props);
|
||||
|
||||
let widthProp = width ? width : size.width;
|
||||
|
@ -104,7 +88,11 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
|
||||
return (
|
||||
<div style={{ width: widthProp }}>
|
||||
{!(isLogX && massBelow0) ? (
|
||||
{logX && hasMassBelowZero(shape.value) ? (
|
||||
<ErrorAlert heading="Log Domain Error">
|
||||
Cannot graph distribution with negative values on logarithmic scale.
|
||||
</ErrorAlert>
|
||||
) : (
|
||||
<Vega
|
||||
spec={spec}
|
||||
data={{ data: shapes.value, domain }}
|
||||
|
@ -112,69 +100,18 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
height={height}
|
||||
actions={actions}
|
||||
/>
|
||||
) : (
|
||||
<ErrorAlert heading="Log Domain Error">
|
||||
Cannot graph distribution with negative values on logarithmic scale.
|
||||
</ErrorAlert>
|
||||
)}
|
||||
<div className="flex justify-center">
|
||||
{showSummary && plot.distributions.length == 1 && (
|
||||
<SummaryTable distribution={plot.distributions[0].distribution} />
|
||||
)}
|
||||
</div>
|
||||
{showControls && (
|
||||
<div>
|
||||
<CheckBox
|
||||
label="Log X scale"
|
||||
value={isLogX}
|
||||
onChange={setLogX}
|
||||
// Check whether we should disable the checkbox
|
||||
{...(massBelow0
|
||||
? {
|
||||
disabled: true,
|
||||
tooltip:
|
||||
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values.",
|
||||
}
|
||||
: {})}
|
||||
/>
|
||||
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return sized;
|
||||
};
|
||||
|
||||
interface CheckBoxProps {
|
||||
label: string;
|
||||
onChange: (x: boolean) => void;
|
||||
value: boolean;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const CheckBox: React.FC<CheckBoxProps> = ({
|
||||
label,
|
||||
onChange,
|
||||
value,
|
||||
disabled = false,
|
||||
tooltip,
|
||||
}) => {
|
||||
return (
|
||||
<span title={tooltip}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={() => onChange(!value)}
|
||||
disabled={disabled}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<label className={clsx(disabled && "text-slate-400")}> {label}</label>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => (
|
||||
|
|
|
@ -39,13 +39,10 @@ interface FunctionChart1NumberProps {
|
|||
type point = { x: number; value: result<number, string> };
|
||||
|
||||
let getFunctionImage = ({ chartSettings, fn, environment }) => {
|
||||
//We adjust the count, because the count is made for distributions, which are much more expensive to estimate
|
||||
let adjustedCount = chartSettings.count * 20;
|
||||
|
||||
let chartPointsToRender = _rangeByCount(
|
||||
chartSettings.start,
|
||||
chartSettings.stop,
|
||||
adjustedCount
|
||||
chartSettings.count
|
||||
);
|
||||
|
||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||
|
|
|
@ -9,12 +9,13 @@ import {
|
|||
defaultEnvironment,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { useSquiggle } from "../lib/hooks";
|
||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
||||
import { SquiggleItem } from "./SquiggleItem";
|
||||
import { SquiggleViewer } from "./SquiggleViewer";
|
||||
|
||||
export interface SquiggleChartProps {
|
||||
/** The input string for squiggle */
|
||||
code?: string;
|
||||
/** Allows to re-run the code if code hasn't changed */
|
||||
executionId?: number;
|
||||
/** If the output requires monte carlo sampling, the amount of samples */
|
||||
sampleCount?: number;
|
||||
/** The amount of points returned to draw the distribution */
|
||||
|
@ -36,10 +37,6 @@ export interface SquiggleChartProps {
|
|||
jsImports?: jsImports;
|
||||
/** Whether to show a summary of the distribution */
|
||||
showSummary?: boolean;
|
||||
/** Whether to show type information about returns, default false */
|
||||
showTypes?: boolean;
|
||||
/** Whether to show graph controls (scale etc)*/
|
||||
showControls?: boolean;
|
||||
/** Set the x scale to be logarithmic by deault */
|
||||
logX?: boolean;
|
||||
/** Set the y scale to be exponential by deault */
|
||||
|
@ -56,6 +53,7 @@ export interface SquiggleChartProps {
|
|||
maxX?: number;
|
||||
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||
distributionChartActions?: boolean;
|
||||
enableLocalSettings?: boolean;
|
||||
}
|
||||
|
||||
const defaultOnChange = () => {};
|
||||
|
@ -63,6 +61,7 @@ const defaultOnChange = () => {};
|
|||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||
({
|
||||
code = "",
|
||||
executionId = 0,
|
||||
environment,
|
||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||
height = 200,
|
||||
|
@ -70,19 +69,18 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
jsImports = defaultImports,
|
||||
showSummary = false,
|
||||
width,
|
||||
showTypes = false,
|
||||
showControls = false,
|
||||
logX = false,
|
||||
expY = false,
|
||||
diagramStart = 0,
|
||||
diagramStop = 10,
|
||||
diagramCount = 100,
|
||||
diagramCount = 20,
|
||||
tickFormat,
|
||||
minX,
|
||||
maxX,
|
||||
color,
|
||||
title,
|
||||
distributionChartActions,
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
const result = useSquiggle({
|
||||
code,
|
||||
|
@ -90,14 +88,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
environment,
|
||||
jsImports,
|
||||
onChange,
|
||||
executionId,
|
||||
});
|
||||
|
||||
if (result.tag !== "Ok") {
|
||||
return <SquiggleErrorAlert error={result.value} />;
|
||||
}
|
||||
|
||||
let distributionPlotSettings = {
|
||||
showControls,
|
||||
const distributionPlotSettings = {
|
||||
showSummary,
|
||||
logX,
|
||||
expY,
|
||||
|
@ -109,21 +103,21 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
actions: distributionChartActions,
|
||||
};
|
||||
|
||||
let chartSettings = {
|
||||
const chartSettings = {
|
||||
start: diagramStart,
|
||||
stop: diagramStop,
|
||||
count: diagramCount,
|
||||
};
|
||||
|
||||
return (
|
||||
<SquiggleItem
|
||||
expression={result.value}
|
||||
<SquiggleViewer
|
||||
result={result}
|
||||
width={width}
|
||||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
showTypes={showTypes}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment ?? defaultEnvironment}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ const SquiggleContext = React.createContext<SquiggleContextShape>({
|
|||
|
||||
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
||||
const context = useContext(SquiggleContext);
|
||||
|
||||
if (context.containerized) {
|
||||
return <>{children}</>;
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react";
|
||||
import { SquiggleEditor } from "./SquiggleEditor";
|
||||
import type { SquiggleEditorProps } from "./SquiggleEditor";
|
||||
import { runPartial, defaultBindings } from "@quri/squiggle-lang";
|
||||
import type {
|
||||
result,
|
||||
errorValue,
|
||||
bindings as bindingsType,
|
||||
} from "@quri/squiggle-lang";
|
||||
|
||||
function resultDefault(x: result<bindingsType, errorValue>): bindingsType {
|
||||
switch (x.tag) {
|
||||
case "Ok":
|
||||
return x.value;
|
||||
case "Error":
|
||||
return defaultBindings;
|
||||
}
|
||||
}
|
||||
|
||||
export type SquiggleEditorWithImportedBindingsProps = SquiggleEditorProps & {
|
||||
bindingsImportUrl: string;
|
||||
};
|
||||
|
||||
export const SquiggleEditorWithImportedBindings: React.FC<
|
||||
SquiggleEditorWithImportedBindingsProps
|
||||
> = (props) => {
|
||||
const { bindingsImportUrl, ...editorProps } = props;
|
||||
const [bindingsResult, setBindingsResult] = React.useState({
|
||||
tag: "Ok",
|
||||
value: defaultBindings,
|
||||
} as result<bindingsType, errorValue>);
|
||||
React.useEffect(() => {
|
||||
async function retrieveBindings(fileName: string) {
|
||||
let contents = await fetch(fileName).then((response) => {
|
||||
return response.text();
|
||||
});
|
||||
setBindingsResult(
|
||||
runPartial(
|
||||
contents,
|
||||
editorProps.bindings,
|
||||
editorProps.environment,
|
||||
editorProps.jsImports
|
||||
)
|
||||
);
|
||||
}
|
||||
retrieveBindings(bindingsImportUrl);
|
||||
}, [bindingsImportUrl]);
|
||||
const deliveredBindings = resultDefault(bindingsResult);
|
||||
return (
|
||||
<SquiggleEditor {...{ ...editorProps, bindings: deliveredBindings }} />
|
||||
);
|
||||
};
|
|
@ -1,294 +0,0 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
squiggleExpression,
|
||||
environment,
|
||||
declaration,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { NumberShower } from "./NumberShower";
|
||||
import {
|
||||
DistributionChart,
|
||||
DistributionPlottingSettings,
|
||||
makePlot,
|
||||
defaultPlot,
|
||||
} from "./DistributionChart";
|
||||
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
|
||||
|
||||
function getRange<a>(x: declaration<a>) {
|
||||
const first = x.args[0];
|
||||
switch (first.tag) {
|
||||
case "Float": {
|
||||
return { floats: { min: first.value.min, max: first.value.max } };
|
||||
}
|
||||
case "Date": {
|
||||
return { time: { min: first.value.min, max: first.value.max } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
||||
const range = getRange(x);
|
||||
const min = range.floats ? range.floats.min : 0;
|
||||
const max = range.floats ? range.floats.max : 10;
|
||||
return {
|
||||
start: min,
|
||||
stop: max,
|
||||
count: 20,
|
||||
};
|
||||
}
|
||||
|
||||
interface VariableBoxProps {
|
||||
heading: string;
|
||||
children: React.ReactNode;
|
||||
showTypes: boolean;
|
||||
}
|
||||
|
||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||
heading = "Error",
|
||||
children,
|
||||
showTypes = false,
|
||||
}) => {
|
||||
if (showTypes) {
|
||||
return (
|
||||
<div className="bg-white border border-grey-200 m-2">
|
||||
<div className="border-b border-grey-200 p-3">
|
||||
<header className="font-mono">{heading}</header>
|
||||
</div>
|
||||
<div className="p-3">{children}</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
export interface SquiggleItemProps {
|
||||
/** The input string for squiggle */
|
||||
expression: squiggleExpression;
|
||||
width?: number;
|
||||
height: number;
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
/** Whether to show type information */
|
||||
showTypes: boolean;
|
||||
/** Settings for displaying functions */
|
||||
chartSettings: FunctionChartSettings;
|
||||
/** Environment for further function executions */
|
||||
environment: environment;
|
||||
}
|
||||
|
||||
export const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
||||
expression,
|
||||
width,
|
||||
height,
|
||||
distributionPlotSettings,
|
||||
showTypes = false,
|
||||
chartSettings,
|
||||
environment,
|
||||
}) => {
|
||||
switch (expression.tag) {
|
||||
case "number":
|
||||
return (
|
||||
<VariableBox heading="Number" showTypes={showTypes}>
|
||||
<div className="font-semibold text-slate-600">
|
||||
<NumberShower precision={3} number={expression.value} />
|
||||
</div>
|
||||
</VariableBox>
|
||||
);
|
||||
case "distribution": {
|
||||
const distType = expression.value.type();
|
||||
return (
|
||||
<VariableBox
|
||||
heading={`Distribution (${distType})`}
|
||||
showTypes={showTypes}
|
||||
>
|
||||
{distType === "Symbolic" && showTypes ? (
|
||||
<div>{expression.value.toString()}</div>
|
||||
) : null}
|
||||
<DistributionChart
|
||||
plot={defaultPlot(expression.value)}
|
||||
{...distributionPlotSettings}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "string":
|
||||
return (
|
||||
<VariableBox heading="String" showTypes={showTypes}>
|
||||
<span className="text-slate-400">"</span>
|
||||
<span className="text-slate-600 font-semibold">
|
||||
{expression.value}
|
||||
</span>
|
||||
<span className="text-slate-400">"</span>
|
||||
</VariableBox>
|
||||
);
|
||||
case "boolean":
|
||||
return (
|
||||
<VariableBox heading="Boolean" showTypes={showTypes}>
|
||||
{expression.value.toString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case "symbol":
|
||||
return (
|
||||
<VariableBox heading="Symbol" showTypes={showTypes}>
|
||||
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
|
||||
<span className="text-slate-600">{expression.value}</span>
|
||||
</VariableBox>
|
||||
);
|
||||
case "call":
|
||||
return (
|
||||
<VariableBox heading="Call" showTypes={showTypes}>
|
||||
{expression.value}
|
||||
</VariableBox>
|
||||
);
|
||||
case "array":
|
||||
return (
|
||||
<VariableBox heading="Array" showTypes={showTypes}>
|
||||
{expression.value.map((r, i) => (
|
||||
<div key={i} className="flex pt-1">
|
||||
<div className="flex-none bg-slate-100 rounded-sm px-1">
|
||||
<header className="text-slate-400 font-mono">{i}</header>
|
||||
</div>
|
||||
<div className="px-2 mb-2 grow">
|
||||
<SquiggleItem
|
||||
key={i}
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
height={50}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
showTypes={showTypes}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</VariableBox>
|
||||
);
|
||||
case "record":
|
||||
let plot = makePlot(expression.value);
|
||||
if (plot) {
|
||||
return (
|
||||
<DistributionChart
|
||||
plot={plot}
|
||||
{...distributionPlotSettings}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<VariableBox heading="Record" showTypes={showTypes}>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(expression.value).map(([key, r]) => (
|
||||
<div key={key} className="flex space-x-2">
|
||||
<div className="flex-none">
|
||||
<header className="text-slate-500 font-mono">{key}:</header>
|
||||
</div>
|
||||
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
|
||||
<SquiggleItem
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
height={height / 3}
|
||||
showTypes={showTypes}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</VariableBox>
|
||||
);
|
||||
case "arraystring":
|
||||
return (
|
||||
<VariableBox heading="Array String" showTypes={showTypes}>
|
||||
{expression.value.map((r) => `"${r}"`).join(", ")}
|
||||
</VariableBox>
|
||||
);
|
||||
case "date":
|
||||
return (
|
||||
<VariableBox heading="Date" showTypes={showTypes}>
|
||||
{expression.value.toDateString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case "timeDuration": {
|
||||
return (
|
||||
<VariableBox heading="Time Duration" showTypes={showTypes}>
|
||||
<NumberShower precision={3} number={expression.value} />
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "lambda":
|
||||
return (
|
||||
<VariableBox heading="Function" showTypes={showTypes}>
|
||||
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join(
|
||||
","
|
||||
)})`}</div>
|
||||
<FunctionChart
|
||||
fn={expression.value}
|
||||
chartSettings={chartSettings}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
height={height}
|
||||
environment={{
|
||||
sampleCount: environment.sampleCount / 10,
|
||||
xyPointLength: environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
</VariableBox>
|
||||
);
|
||||
case "lambdaDeclaration": {
|
||||
return (
|
||||
<VariableBox heading="Function Declaration" showTypes={showTypes}>
|
||||
<FunctionChart
|
||||
fn={expression.value.fn}
|
||||
chartSettings={getChartSettings(expression.value)}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
height={height}
|
||||
environment={{
|
||||
sampleCount: environment.sampleCount / 10,
|
||||
xyPointLength: environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "module": {
|
||||
return (
|
||||
<VariableBox heading="Module" showTypes={showTypes}>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(expression.value)
|
||||
.filter(([key, _]) => key !== "Math")
|
||||
.map(([key, r]) => (
|
||||
<div key={key} className="flex space-x-2">
|
||||
<div className="flex-none">
|
||||
<header className="text-slate-500 font-mono">{key}:</header>
|
||||
</div>
|
||||
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
|
||||
<SquiggleItem
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
height={height / 3}
|
||||
showTypes={showTypes}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<div>
|
||||
<span>No display for type: </span>{" "}
|
||||
<span className="font-semibold text-slate-600">{expression.tag}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,11 +1,19 @@
|
|||
import React, { FC, useState, useEffect, useMemo } from "react";
|
||||
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||
import React, {
|
||||
FC,
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||
import * as yup from "yup";
|
||||
import { useMaybeControlledValue } from "../lib/hooks";
|
||||
import { useMaybeControlledValue, useRunnerState } from "../lib/hooks";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
ChartSquareBarIcon,
|
||||
CheckCircleIcon,
|
||||
ClipboardCopyIcon,
|
||||
CodeIcon,
|
||||
CogIcon,
|
||||
CurrencyDollarIcon,
|
||||
|
@ -24,21 +32,32 @@ import { JsonEditor } from "./JsonEditor";
|
|||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||
import { SquiggleContainer } from "./SquiggleContainer";
|
||||
import { Toggle } from "./ui/Toggle";
|
||||
import { Checkbox } from "./ui/Checkbox";
|
||||
import { StyledTab } from "./ui/StyledTab";
|
||||
import { InputItem } from "./ui/InputItem";
|
||||
import { Text } from "./ui/Text";
|
||||
import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
||||
import { HeadedSection } from "./ui/HeadedSection";
|
||||
import {
|
||||
defaultColor,
|
||||
defaultTickFormat,
|
||||
} from "../lib/distributionSpecBuilder";
|
||||
import { Button } from "./ui/Button";
|
||||
|
||||
type PlaygroundProps = SquiggleChartProps & {
|
||||
/** The initial squiggle string to put in the playground */
|
||||
defaultCode?: string;
|
||||
/** How many pixels high is the playground */
|
||||
onCodeChange?(expr: string): void;
|
||||
/* When settings change */
|
||||
onSettingsChange?(settings: any): void;
|
||||
/** Should we show the editor? */
|
||||
showEditor?: boolean;
|
||||
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
|
||||
showShareButton?: boolean;
|
||||
};
|
||||
|
||||
const schema = yup.object({}).shape({
|
||||
const schema = yup
|
||||
.object({})
|
||||
.shape({
|
||||
sampleCount: yup
|
||||
.number()
|
||||
.required()
|
||||
|
@ -55,74 +74,11 @@ const schema = yup.object({}).shape({
|
|||
.default(1000)
|
||||
.min(10)
|
||||
.max(10000),
|
||||
chartHeight: yup.number().required().positive().integer().default(350),
|
||||
leftSizePercent: yup
|
||||
.number()
|
||||
.required()
|
||||
.positive()
|
||||
.integer()
|
||||
.min(10)
|
||||
.max(100)
|
||||
.default(50),
|
||||
showTypes: yup.boolean().required(),
|
||||
showControls: yup.boolean().required(),
|
||||
showSummary: yup.boolean().required(),
|
||||
showEditor: yup.boolean().required(),
|
||||
logX: yup.boolean().required(),
|
||||
expY: yup.boolean().required(),
|
||||
tickFormat: yup.string().default(".9~s"),
|
||||
title: yup.string(),
|
||||
color: yup.string().default("#739ECC").required(),
|
||||
minX: yup.number(),
|
||||
maxX: yup.number(),
|
||||
distributionChartActions: yup.boolean(),
|
||||
showSettingsPage: yup.boolean().default(false),
|
||||
diagramStart: yup.number().required().positive().integer().default(0).min(0),
|
||||
diagramStop: yup.number().required().positive().integer().default(10).min(0),
|
||||
diagramCount: yup.number().required().positive().integer().default(20).min(2),
|
||||
});
|
||||
})
|
||||
.concat(viewSettingsSchema);
|
||||
|
||||
type FormFields = yup.InferType<typeof schema>;
|
||||
|
||||
const HeadedSection: FC<{ title: string; children: React.ReactNode }> = ({
|
||||
title,
|
||||
children,
|
||||
}) => (
|
||||
<div>
|
||||
<header className="text-lg leading-6 font-medium text-gray-900">
|
||||
{title}
|
||||
</header>
|
||||
<div className="mt-4">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Text: FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<p className="text-sm text-gray-500">{children}</p>
|
||||
);
|
||||
|
||||
function InputItem<T>({
|
||||
name,
|
||||
label,
|
||||
type,
|
||||
register,
|
||||
}: {
|
||||
name: Path<T>;
|
||||
label: string;
|
||||
type: "number" | "text" | "color";
|
||||
register: UseFormRegister<T>;
|
||||
}) {
|
||||
return (
|
||||
<label className="block">
|
||||
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
||||
<input
|
||||
type={type}
|
||||
{...register(name, { valueAsNumber: type === "number" })}
|
||||
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||
register,
|
||||
}) => (
|
||||
|
@ -158,128 +114,6 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
|||
</div>
|
||||
);
|
||||
|
||||
const ViewSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||
register,
|
||||
}) => (
|
||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
||||
<HeadedSection title="General Display Settings">
|
||||
<div className="space-y-4">
|
||||
<Checkbox
|
||||
name="showEditor"
|
||||
register={register}
|
||||
label="Show code editor on left"
|
||||
/>
|
||||
<InputItem
|
||||
name="chartHeight"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Chart Height (in pixels)"
|
||||
/>
|
||||
<Checkbox
|
||||
name="showTypes"
|
||||
register={register}
|
||||
label="Show information about displayed types"
|
||||
/>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
|
||||
<div className="pt-8">
|
||||
<HeadedSection title="Distribution Display Settings">
|
||||
<div className="space-y-2">
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="logX"
|
||||
label="Show x scale logarithmically"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="expY"
|
||||
label="Show y scale exponentially"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="distributionChartActions"
|
||||
label="Show vega chart controls"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="showControls"
|
||||
label="Show toggles to adjust scale of x and y axes"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="showSummary"
|
||||
label="Show summary statistics"
|
||||
/>
|
||||
<InputItem
|
||||
name="minX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Min X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="maxX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Max X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="title"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Title"
|
||||
/>
|
||||
<InputItem
|
||||
name="tickFormat"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Tick Format"
|
||||
/>
|
||||
<InputItem
|
||||
name="color"
|
||||
type="color"
|
||||
register={register}
|
||||
label="Color"
|
||||
/>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
|
||||
<div className="pt-8">
|
||||
<HeadedSection title="Function Display Settings">
|
||||
<div className="space-y-6">
|
||||
<Text>
|
||||
When displaying functions of single variables that return numbers or
|
||||
distributions, we need to use defaults for the x-axis. We need to
|
||||
select a minimum and maximum value of x to sample, and a number n of
|
||||
the number of points to sample.
|
||||
</Text>
|
||||
<div className="space-y-4">
|
||||
<InputItem
|
||||
type="number"
|
||||
name="diagramStart"
|
||||
register={register}
|
||||
label="Min X Value"
|
||||
/>
|
||||
<InputItem
|
||||
type="number"
|
||||
name="diagramStop"
|
||||
register={register}
|
||||
label="Max X Value"
|
||||
/>
|
||||
<InputItem
|
||||
type="number"
|
||||
name="diagramCount"
|
||||
register={register}
|
||||
label="Points between X min and X max to sample"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const InputVariablesSettings: React.FC<{
|
||||
initialImports: any; // TODO - any json type
|
||||
setImports: (imports: any) => void;
|
||||
|
@ -361,69 +195,59 @@ const RunControls: React.FC<{
|
|||
icons={[CheckCircleIcon, PauseIcon]}
|
||||
status={autorunMode}
|
||||
onChange={onAutorunModeChange}
|
||||
spinIcon={autorunMode && isRunning}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useRunnerState = (code: string) => {
|
||||
const [autorunMode, setAutorunMode] = useState(true);
|
||||
const [renderedCode, setRenderedCode] = useState(code); // used in manual run mode only
|
||||
const [isRunning, setIsRunning] = useState(false); // used in manual run mode only
|
||||
|
||||
// This part is tricky and fragile; we need to re-render first to make sure that the icon is spinning,
|
||||
// and only then evaluate the squiggle code (which freezes the UI).
|
||||
// Also note that `useEffect` execution order matters here.
|
||||
// Hopefully it'll all go away after we make squiggle code evaluation async.
|
||||
useEffect(() => {
|
||||
if (renderedCode === code && isRunning) {
|
||||
// It's not possible to put this after `setRenderedCode(code)` below because React would apply
|
||||
// `setIsRunning` and `setRenderedCode` together and spinning icon will disappear immediately.
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [renderedCode, code, isRunning]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autorunMode && isRunning) {
|
||||
setRenderedCode(code); // TODO - force run even if code hasn't changed
|
||||
}
|
||||
}, [autorunMode, code, isRunning]);
|
||||
|
||||
const run = () => {
|
||||
// The rest will be handled by useEffects above, but we need to update the spinner first.
|
||||
setIsRunning(true);
|
||||
const ShareButton: React.FC = () => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText((window.top || window).location.href);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 1000);
|
||||
};
|
||||
return (
|
||||
<div className="w-36">
|
||||
<Button onClick={copy} wide>
|
||||
{isCopied ? (
|
||||
"Copied to clipboard!"
|
||||
) : (
|
||||
<div className="flex items-center space-x-1">
|
||||
<ClipboardCopyIcon className="w-4 h-4" />
|
||||
<span>Copy share link</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
run,
|
||||
renderedCode: autorunMode ? code : renderedCode,
|
||||
isRunning,
|
||||
autorunMode,
|
||||
setAutorunMode: (newValue: boolean) => {
|
||||
if (!newValue) setRenderedCode(code);
|
||||
setAutorunMode(newValue);
|
||||
},
|
||||
};
|
||||
type PlaygroundContextShape = {
|
||||
getLeftPanelElement: () => HTMLDivElement | undefined;
|
||||
};
|
||||
export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
||||
getLeftPanelElement: () => undefined,
|
||||
});
|
||||
|
||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||
defaultCode = "",
|
||||
height = 500,
|
||||
showTypes = false,
|
||||
showControls = false,
|
||||
showSummary = false,
|
||||
logX = false,
|
||||
expY = false,
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
color = "#739ECC",
|
||||
tickFormat = ".9~s",
|
||||
color = defaultColor,
|
||||
tickFormat = defaultTickFormat,
|
||||
distributionChartActions,
|
||||
code: controlledCode,
|
||||
onCodeChange,
|
||||
onSettingsChange,
|
||||
showEditor = true,
|
||||
showShareButton = false,
|
||||
}) => {
|
||||
const [code, setCode] = useMaybeControlledValue({
|
||||
value: controlledCode,
|
||||
|
@ -439,8 +263,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
sampleCount: 1000,
|
||||
xyPointLength: 1000,
|
||||
chartHeight: 150,
|
||||
showTypes,
|
||||
showControls,
|
||||
logX,
|
||||
expY,
|
||||
title,
|
||||
|
@ -451,8 +273,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
distributionChartActions,
|
||||
showSummary,
|
||||
showEditor,
|
||||
leftSizePercent: 50,
|
||||
showSettingsPage: false,
|
||||
diagramStart: 0,
|
||||
diagramStop: 10,
|
||||
diagramCount: 20,
|
||||
|
@ -474,17 +294,31 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
[vars.sampleCount, vars.xyPointLength]
|
||||
);
|
||||
|
||||
const { run, autorunMode, setAutorunMode, isRunning, renderedCode } =
|
||||
useRunnerState(code);
|
||||
const {
|
||||
run,
|
||||
autorunMode,
|
||||
setAutorunMode,
|
||||
isRunning,
|
||||
renderedCode,
|
||||
executionId,
|
||||
} = useRunnerState(code);
|
||||
|
||||
const squiggleChart = (
|
||||
const squiggleChart =
|
||||
renderedCode === "" ? null : (
|
||||
<div className="relative">
|
||||
{isRunning ? (
|
||||
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
||||
) : null}
|
||||
<SquiggleChart
|
||||
code={renderedCode}
|
||||
executionId={executionId}
|
||||
environment={env}
|
||||
{...vars}
|
||||
bindings={defaultBindings}
|
||||
jsImports={imports}
|
||||
enableLocalSettings={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const firstTab = vars.showEditor ? (
|
||||
|
@ -509,7 +343,15 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
<SamplingSettings register={register} />
|
||||
</StyledTab.Panel>
|
||||
<StyledTab.Panel>
|
||||
<ViewSettings register={register} />
|
||||
<ViewSettings
|
||||
register={
|
||||
// This is dangerous, but doesn't cause any problems.
|
||||
// I tried to make `ViewSettings` generic (to allow it to accept any extension of a settings schema), but it didn't work.
|
||||
register as unknown as UseFormRegister<
|
||||
yup.InferType<typeof viewSettingsSchema>
|
||||
>
|
||||
}
|
||||
/>
|
||||
</StyledTab.Panel>
|
||||
<StyledTab.Panel>
|
||||
<InputVariablesSettings
|
||||
|
@ -520,20 +362,33 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
</StyledTab.Panels>
|
||||
);
|
||||
|
||||
const leftPanelRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const withEditor = (
|
||||
<div className="flex mt-2">
|
||||
<div className="w-1/2">{tabs}</div>
|
||||
<div
|
||||
className="w-1/2 relative"
|
||||
style={{ minHeight: height }}
|
||||
ref={leftPanelRef}
|
||||
>
|
||||
{tabs}
|
||||
</div>
|
||||
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
||||
|
||||
const getLeftPanelElement = useCallback(() => {
|
||||
return leftPanelRef.current ?? undefined;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SquiggleContainer>
|
||||
<PlaygroundContext.Provider value={{ getLeftPanelElement }}>
|
||||
<StyledTab.Group>
|
||||
<div className="pb-4">
|
||||
<div className="flex justify-between items-center mt-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<StyledTab.List>
|
||||
<StyledTab
|
||||
name={vars.showEditor ? "Code" : "Display"}
|
||||
|
@ -543,6 +398,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
||||
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
||||
</StyledTab.List>
|
||||
<div className="flex space-x-2 items-center">
|
||||
<RunControls
|
||||
autorunMode={autorunMode}
|
||||
isStale={renderedCode !== code}
|
||||
|
@ -550,10 +406,13 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
isRunning={isRunning}
|
||||
onAutorunModeChange={setAutorunMode}
|
||||
/>
|
||||
{showShareButton && <ShareButton />}
|
||||
</div>
|
||||
</div>
|
||||
{vars.showEditor ? withEditor : withoutEditor}
|
||||
</div>
|
||||
</StyledTab.Group>
|
||||
</PlaygroundContext.Provider>
|
||||
</SquiggleContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
import React from "react";
|
||||
import { squiggleExpression, declaration } from "@quri/squiggle-lang";
|
||||
import { NumberShower } from "../NumberShower";
|
||||
import { DistributionChart } from "../DistributionChart";
|
||||
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
||||
import clsx from "clsx";
|
||||
import { VariableBox } from "./VariableBox";
|
||||
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
||||
import { hasMassBelowZero } from "../../lib/distributionUtils";
|
||||
import { MergedItemSettings } from "./utils";
|
||||
|
||||
function getRange<a>(x: declaration<a>) {
|
||||
const first = x.args[0];
|
||||
switch (first.tag) {
|
||||
case "Float": {
|
||||
return { floats: { min: first.value.min, max: first.value.max } };
|
||||
}
|
||||
case "Date": {
|
||||
return { time: { min: first.value.min, max: first.value.max } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
||||
const range = getRange(x);
|
||||
const min = range.floats ? range.floats.min : 0;
|
||||
const max = range.floats ? range.floats.max : 10;
|
||||
return {
|
||||
start: min,
|
||||
stop: max,
|
||||
count: 20,
|
||||
};
|
||||
}
|
||||
|
||||
const VariableList: React.FC<{
|
||||
path: string[];
|
||||
heading: string;
|
||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||
}> = ({ path, heading, children }) => (
|
||||
<VariableBox path={path} heading={heading}>
|
||||
{(settings) => (
|
||||
<div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}>
|
||||
{children(settings)}
|
||||
</div>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
|
||||
export interface Props {
|
||||
/** The output of squiggle's run */
|
||||
expression: squiggleExpression;
|
||||
/** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */
|
||||
path: string[];
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export const ExpressionViewer: React.FC<Props> = ({
|
||||
path,
|
||||
expression,
|
||||
width,
|
||||
}) => {
|
||||
if (typeof expression !== "object") {
|
||||
return (
|
||||
<VariableList path={path} heading="Error">
|
||||
{() => `Unknown expression: ${expression}`}
|
||||
</VariableList>
|
||||
);
|
||||
}
|
||||
switch (expression.tag) {
|
||||
case "number":
|
||||
return (
|
||||
<VariableBox path={path} heading="Number">
|
||||
{() => (
|
||||
<div className="font-semibold text-slate-600">
|
||||
<NumberShower precision={3} number={expression.value} />
|
||||
</div>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "distribution": {
|
||||
const distType = expression.value.type();
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
heading={`Distribution (${distType})\n${
|
||||
distType === "Symbolic" ? expression.value.toString() : ""
|
||||
}`}
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
const shape = expression.value.pointSet();
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
path={path}
|
||||
onChange={onChange}
|
||||
disableLogX={
|
||||
shape.tag === "Ok" && hasMassBelowZero(shape.value)
|
||||
}
|
||||
withFunctionSettings={false}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{(settings) => {
|
||||
return (
|
||||
<DistributionChart
|
||||
distribution={expression.value}
|
||||
{...settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "string":
|
||||
return (
|
||||
<VariableBox path={path} heading="String">
|
||||
{() => (
|
||||
<>
|
||||
<span className="text-slate-400">"</span>
|
||||
<span className="text-slate-600 font-semibold font-mono">
|
||||
{expression.value}
|
||||
</span>
|
||||
<span className="text-slate-400">"</span>
|
||||
</>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "boolean":
|
||||
return (
|
||||
<VariableBox path={path} heading="Boolean">
|
||||
{() => expression.value.toString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case "symbol":
|
||||
return (
|
||||
<VariableBox path={path} heading="Symbol">
|
||||
{() => (
|
||||
<>
|
||||
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
|
||||
<span className="text-slate-600">{expression.value}</span>
|
||||
</>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "call":
|
||||
return (
|
||||
<VariableBox path={path} heading="Call">
|
||||
{() => expression.value}
|
||||
</VariableBox>
|
||||
);
|
||||
case "arraystring":
|
||||
return (
|
||||
<VariableBox path={path} heading="Array String">
|
||||
{() => expression.value.map((r) => `"${r}"`).join(", ")}
|
||||
</VariableBox>
|
||||
);
|
||||
case "date":
|
||||
return (
|
||||
<VariableBox path={path} heading="Date">
|
||||
{() => expression.value.toDateString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case "void":
|
||||
return (
|
||||
<VariableBox path={path} heading="Void">
|
||||
{() => "Void"}
|
||||
</VariableBox>
|
||||
);
|
||||
case "timeDuration": {
|
||||
return (
|
||||
<VariableBox path={path} heading="Time Duration">
|
||||
{() => <NumberShower precision={3} number={expression.value} />}
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "lambda":
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
heading="Function"
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
path={path}
|
||||
onChange={onChange}
|
||||
withFunctionSettings={true}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{(settings) => (
|
||||
<>
|
||||
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join(
|
||||
","
|
||||
)})`}</div>
|
||||
<FunctionChart
|
||||
fn={expression.value}
|
||||
chartSettings={settings.chartSettings}
|
||||
distributionPlotSettings={settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
environment={{
|
||||
sampleCount: settings.environment.sampleCount / 10,
|
||||
xyPointLength: settings.environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "lambdaDeclaration": {
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
heading="Function Declaration"
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
onChange={onChange}
|
||||
path={path}
|
||||
withFunctionSettings={true}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{(settings) => (
|
||||
<FunctionChart
|
||||
fn={expression.value.fn}
|
||||
chartSettings={getChartSettings(expression.value)}
|
||||
distributionPlotSettings={settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
environment={{
|
||||
sampleCount: settings.environment.sampleCount / 10,
|
||||
xyPointLength: settings.environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "module": {
|
||||
return (
|
||||
<VariableList path={path} heading="Module">
|
||||
{(settings) =>
|
||||
Object.entries(expression.value)
|
||||
.filter(([key, r]) => !key.match(/^(Math|System)\./))
|
||||
.map(([key, r]) => (
|
||||
<ExpressionViewer
|
||||
key={key}
|
||||
path={[...path, key]}
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</VariableList>
|
||||
);
|
||||
}
|
||||
case "record":
|
||||
return (
|
||||
<VariableList path={path} heading="Record">
|
||||
{(settings) =>
|
||||
Object.entries(expression.value).map(([key, r]) => (
|
||||
<ExpressionViewer
|
||||
key={key}
|
||||
path={[...path, key]}
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</VariableList>
|
||||
);
|
||||
case "array":
|
||||
return (
|
||||
<VariableList path={path} heading="Array">
|
||||
{(settings) =>
|
||||
expression.value.map((r, i) => (
|
||||
<ExpressionViewer
|
||||
key={i}
|
||||
path={[...path, String(i)]}
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</VariableList>
|
||||
);
|
||||
default: {
|
||||
return (
|
||||
<VariableList path={path} heading="Error">
|
||||
{() => (
|
||||
<div>
|
||||
<span>No display for type: </span>{" "}
|
||||
<span className="font-semibold text-slate-600">
|
||||
{expression.tag}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</VariableList>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,168 @@
|
|||
import { CogIcon } from "@heroicons/react/solid";
|
||||
import React, { useContext, useRef, useState, useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Modal } from "../ui/Modal";
|
||||
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
||||
import { Path, pathAsString } from "./utils";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
import {
|
||||
defaultColor,
|
||||
defaultTickFormat,
|
||||
} from "../../lib/distributionSpecBuilder";
|
||||
import { PlaygroundContext } from "../SquigglePlayground";
|
||||
|
||||
type Props = {
|
||||
path: Path;
|
||||
onChange: () => void;
|
||||
disableLogX?: boolean;
|
||||
withFunctionSettings: boolean;
|
||||
};
|
||||
|
||||
const ItemSettingsModal: React.FC<
|
||||
Props & { close: () => void; resetScroll: () => void }
|
||||
> = ({
|
||||
path,
|
||||
onChange,
|
||||
disableLogX,
|
||||
withFunctionSettings,
|
||||
close,
|
||||
resetScroll,
|
||||
}) => {
|
||||
const { setSettings, getSettings, getMergedSettings } =
|
||||
useContext(ViewerContext);
|
||||
|
||||
const mergedSettings = getMergedSettings(path);
|
||||
|
||||
const { register, watch } = useForm({
|
||||
resolver: yupResolver(viewSettingsSchema),
|
||||
defaultValues: {
|
||||
// this is a mess and should be fixed
|
||||
showEditor: true, // doesn't matter
|
||||
chartHeight: mergedSettings.height,
|
||||
showSummary: mergedSettings.distributionPlotSettings.showSummary,
|
||||
logX: mergedSettings.distributionPlotSettings.logX,
|
||||
expY: mergedSettings.distributionPlotSettings.expY,
|
||||
tickFormat:
|
||||
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
|
||||
title: mergedSettings.distributionPlotSettings.title,
|
||||
color: mergedSettings.distributionPlotSettings.color || defaultColor,
|
||||
minX: mergedSettings.distributionPlotSettings.minX,
|
||||
maxX: mergedSettings.distributionPlotSettings.maxX,
|
||||
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
||||
diagramStart: mergedSettings.chartSettings.start,
|
||||
diagramStop: mergedSettings.chartSettings.stop,
|
||||
diagramCount: mergedSettings.chartSettings.count,
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
const subscription = watch((vars) => {
|
||||
const settings = getSettings(path); // get the latest version
|
||||
setSettings(path, {
|
||||
...settings,
|
||||
distributionPlotSettings: {
|
||||
showSummary: vars.showSummary,
|
||||
logX: vars.logX,
|
||||
expY: vars.expY,
|
||||
format: vars.tickFormat,
|
||||
title: vars.title,
|
||||
color: vars.color,
|
||||
minX: vars.minX,
|
||||
maxX: vars.maxX,
|
||||
actions: vars.distributionChartActions,
|
||||
},
|
||||
chartSettings: {
|
||||
start: vars.diagramStart,
|
||||
stop: vars.diagramStop,
|
||||
count: vars.diagramCount,
|
||||
},
|
||||
});
|
||||
onChange();
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, [getSettings, setSettings, onChange, path, watch]);
|
||||
|
||||
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
||||
|
||||
return (
|
||||
<Modal container={getLeftPanelElement()} close={close}>
|
||||
<Modal.Header>
|
||||
Chart settings
|
||||
{path.length ? (
|
||||
<>
|
||||
{" for "}
|
||||
<span
|
||||
title="Scroll to item"
|
||||
className="cursor-pointer"
|
||||
onClick={resetScroll}
|
||||
>
|
||||
{pathAsString(path)}
|
||||
</span>{" "}
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<ViewSettings
|
||||
register={register}
|
||||
withShowEditorSetting={false}
|
||||
withFunctionSettings={withFunctionSettings}
|
||||
disableLogXSetting={disableLogX}
|
||||
/>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { enableLocalSettings, setSettings, getSettings } =
|
||||
useContext(ViewerContext);
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
if (!enableLocalSettings) {
|
||||
return null;
|
||||
}
|
||||
const settings = getSettings(props.path);
|
||||
|
||||
const resetScroll = () => {
|
||||
if (!ref.current) return;
|
||||
window.scroll({
|
||||
top: ref.current.getBoundingClientRect().y + window.scrollY,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-2" ref={ref}>
|
||||
<CogIcon
|
||||
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
/>
|
||||
{settings.distributionPlotSettings || settings.chartSettings ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSettings(props.path, {
|
||||
...settings,
|
||||
distributionPlotSettings: undefined,
|
||||
chartSettings: undefined,
|
||||
});
|
||||
props.onChange();
|
||||
}}
|
||||
className="text-xs px-1 py-0.5 rounded bg-slate-300"
|
||||
>
|
||||
Reset settings
|
||||
</button>
|
||||
) : null}
|
||||
{isOpen ? (
|
||||
<ItemSettingsModal
|
||||
{...props}
|
||||
close={() => setIsOpen(false)}
|
||||
resetScroll={resetScroll}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
import React, { useContext, useReducer } from "react";
|
||||
import { Tooltip } from "../ui/Tooltip";
|
||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
|
||||
type SettingsMenuParams = {
|
||||
onChange: () => void; // used to notify VariableBox that settings have changed, so that VariableBox could re-render itself
|
||||
};
|
||||
|
||||
type VariableBoxProps = {
|
||||
path: string[];
|
||||
heading: string;
|
||||
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||
path,
|
||||
heading = "Error",
|
||||
renderSettingsMenu,
|
||||
children,
|
||||
}) => {
|
||||
const { setSettings, getSettings, getMergedSettings } =
|
||||
useContext(ViewerContext);
|
||||
|
||||
// Since ViewerContext doesn't keep the actual settings, VariableBox won't rerender when setSettings is called.
|
||||
// So we use `forceUpdate` to force rerendering.
|
||||
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const settings = getSettings(path);
|
||||
|
||||
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
||||
setSettings(path, newSettings);
|
||||
forceUpdate();
|
||||
};
|
||||
|
||||
const toggleCollapsed = () => {
|
||||
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
||||
};
|
||||
|
||||
const isTopLevel = path.length === 0;
|
||||
const name = isTopLevel ? "Result" : path[path.length - 1];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header className="inline-flex space-x-1">
|
||||
<Tooltip text={heading}>
|
||||
<span
|
||||
className="text-slate-500 font-mono text-sm cursor-pointer"
|
||||
onClick={toggleCollapsed}
|
||||
>
|
||||
{name}:
|
||||
</span>
|
||||
</Tooltip>
|
||||
{settings.collapsed ? (
|
||||
<span
|
||||
className="rounded p-0.5 bg-slate-200 text-slate-500 font-mono text-xs cursor-pointer"
|
||||
onClick={toggleCollapsed}
|
||||
>
|
||||
...
|
||||
</span>
|
||||
) : renderSettingsMenu ? (
|
||||
renderSettingsMenu({ onChange: forceUpdate })
|
||||
) : null}
|
||||
</header>
|
||||
{settings.collapsed ? null : (
|
||||
<div className="flex w-full">
|
||||
{path.length ? (
|
||||
<div
|
||||
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
|
||||
onClick={toggleCollapsed}
|
||||
></div>
|
||||
) : null}
|
||||
<div className="grow">{children(getMergedSettings(path))}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
import { defaultEnvironment } from "@quri/squiggle-lang";
|
||||
import React from "react";
|
||||
import { LocalItemSettings, MergedItemSettings, Path } from "./utils";
|
||||
|
||||
type ViewerContextShape = {
|
||||
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
||||
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
|
||||
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
|
||||
getSettings(path: Path): LocalItemSettings;
|
||||
getMergedSettings(path: Path): MergedItemSettings;
|
||||
setSettings(path: Path, value: LocalItemSettings): void;
|
||||
enableLocalSettings: boolean; // show local settings icon in the UI
|
||||
};
|
||||
|
||||
export const ViewerContext = React.createContext<ViewerContextShape>({
|
||||
getSettings: () => ({ collapsed: false }),
|
||||
getMergedSettings: () => ({
|
||||
collapsed: false,
|
||||
// copy-pasted from SquiggleChart
|
||||
chartSettings: {
|
||||
start: 0,
|
||||
stop: 10,
|
||||
count: 100,
|
||||
},
|
||||
distributionPlotSettings: {
|
||||
showSummary: false,
|
||||
logX: false,
|
||||
expY: false,
|
||||
},
|
||||
environment: defaultEnvironment,
|
||||
height: 150,
|
||||
}),
|
||||
setSettings() {},
|
||||
enableLocalSettings: false,
|
||||
});
|
100
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
100
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import React, { useCallback, useRef } from "react";
|
||||
import { environment } from "@quri/squiggle-lang";
|
||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { ExpressionViewer } from "./ExpressionViewer";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
import {
|
||||
LocalItemSettings,
|
||||
MergedItemSettings,
|
||||
Path,
|
||||
pathAsString,
|
||||
} from "./utils";
|
||||
import { useSquiggle } from "../../lib/hooks";
|
||||
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
||||
|
||||
type Props = {
|
||||
/** The output of squiggle's run */
|
||||
result: ReturnType<typeof useSquiggle>;
|
||||
width?: number;
|
||||
height: number;
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
/** Settings for displaying functions */
|
||||
chartSettings: FunctionChartSettings;
|
||||
/** Environment for further function executions */
|
||||
environment: environment;
|
||||
enableLocalSettings?: boolean;
|
||||
};
|
||||
|
||||
type Settings = {
|
||||
[k: string]: LocalItemSettings;
|
||||
};
|
||||
|
||||
const defaultSettings: LocalItemSettings = { collapsed: false };
|
||||
|
||||
export const SquiggleViewer: React.FC<Props> = ({
|
||||
result,
|
||||
width,
|
||||
height,
|
||||
distributionPlotSettings,
|
||||
chartSettings,
|
||||
environment,
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
||||
const settingsRef = useRef<Settings>({});
|
||||
|
||||
const getSettings = useCallback(
|
||||
(path: Path) => {
|
||||
return settingsRef.current[pathAsString(path)] || defaultSettings;
|
||||
},
|
||||
[settingsRef]
|
||||
);
|
||||
|
||||
const setSettings = useCallback(
|
||||
(path: Path, value: LocalItemSettings) => {
|
||||
settingsRef.current[pathAsString(path)] = value;
|
||||
},
|
||||
[settingsRef]
|
||||
);
|
||||
|
||||
const getMergedSettings = useCallback(
|
||||
(path: Path) => {
|
||||
const localSettings = getSettings(path);
|
||||
const result: MergedItemSettings = {
|
||||
distributionPlotSettings: {
|
||||
...distributionPlotSettings,
|
||||
...(localSettings.distributionPlotSettings || {}),
|
||||
},
|
||||
chartSettings: {
|
||||
...chartSettings,
|
||||
...(localSettings.chartSettings || {}),
|
||||
},
|
||||
environment: {
|
||||
...environment,
|
||||
...(localSettings.environment || {}),
|
||||
},
|
||||
height: localSettings.height || height,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
[distributionPlotSettings, chartSettings, environment, height, getSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewerContext.Provider
|
||||
value={{
|
||||
getSettings,
|
||||
setSettings,
|
||||
getMergedSettings,
|
||||
enableLocalSettings,
|
||||
}}
|
||||
>
|
||||
{result.tag === "Ok" ? (
|
||||
<ExpressionViewer path={[]} expression={result.value} width={width} />
|
||||
) : (
|
||||
<SquiggleErrorAlert error={result.value} />
|
||||
)}
|
||||
</ViewerContext.Provider>
|
||||
);
|
||||
};
|
22
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
22
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { environment } from "@quri/squiggle-lang";
|
||||
|
||||
export type LocalItemSettings = {
|
||||
collapsed: boolean;
|
||||
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
||||
chartSettings?: Partial<FunctionChartSettings>;
|
||||
height?: number;
|
||||
environment?: Partial<environment>;
|
||||
};
|
||||
|
||||
export type MergedItemSettings = {
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
chartSettings: FunctionChartSettings;
|
||||
height: number;
|
||||
environment: environment;
|
||||
};
|
||||
|
||||
export type Path = string[];
|
||||
|
||||
export const pathAsString = (path: Path) => path.join(".");
|
163
packages/components/src/components/ViewSettings.tsx
Normal file
163
packages/components/src/components/ViewSettings.tsx
Normal file
|
@ -0,0 +1,163 @@
|
|||
import React from "react";
|
||||
import * as yup from "yup";
|
||||
import { UseFormRegister } from "react-hook-form";
|
||||
import { InputItem } from "./ui/InputItem";
|
||||
import { Checkbox } from "./ui/Checkbox";
|
||||
import { HeadedSection } from "./ui/HeadedSection";
|
||||
import { Text } from "./ui/Text";
|
||||
import {
|
||||
defaultColor,
|
||||
defaultTickFormat,
|
||||
} from "../lib/distributionSpecBuilder";
|
||||
|
||||
export const viewSettingsSchema = yup.object({}).shape({
|
||||
chartHeight: yup.number().required().positive().integer().default(350),
|
||||
showSummary: yup.boolean().required(),
|
||||
showEditor: yup.boolean().required(),
|
||||
logX: yup.boolean().required(),
|
||||
expY: yup.boolean().required(),
|
||||
tickFormat: yup.string().default(defaultTickFormat),
|
||||
title: yup.string(),
|
||||
color: yup.string().default(defaultColor).required(),
|
||||
minX: yup.number(),
|
||||
maxX: yup.number(),
|
||||
distributionChartActions: yup.boolean(),
|
||||
diagramStart: yup.number().required().positive().integer().default(0).min(0),
|
||||
diagramStop: yup.number().required().positive().integer().default(10).min(0),
|
||||
diagramCount: yup.number().required().positive().integer().default(20).min(2),
|
||||
});
|
||||
|
||||
type FormFields = yup.InferType<typeof viewSettingsSchema>;
|
||||
|
||||
// This component is used in two places: for global settings in SquigglePlayground, and for item-specific settings in modal dialogs.
|
||||
export const ViewSettings: React.FC<{
|
||||
withShowEditorSetting?: boolean;
|
||||
withFunctionSettings?: boolean;
|
||||
disableLogXSetting?: boolean;
|
||||
register: UseFormRegister<FormFields>;
|
||||
}> = ({
|
||||
withShowEditorSetting = true,
|
||||
withFunctionSettings = true,
|
||||
disableLogXSetting,
|
||||
register,
|
||||
}) => {
|
||||
return (
|
||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
||||
<HeadedSection title="General Display Settings">
|
||||
<div className="space-y-4">
|
||||
{withShowEditorSetting ? (
|
||||
<Checkbox
|
||||
name="showEditor"
|
||||
register={register}
|
||||
label="Show code editor on left"
|
||||
/>
|
||||
) : null}
|
||||
<InputItem
|
||||
name="chartHeight"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Chart Height (in pixels)"
|
||||
/>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
|
||||
<div className="pt-8">
|
||||
<HeadedSection title="Distribution Display Settings">
|
||||
<div className="space-y-2">
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="logX"
|
||||
label="Show x scale logarithmically"
|
||||
disabled={disableLogXSetting}
|
||||
tooltip={
|
||||
disableLogXSetting
|
||||
? "Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="expY"
|
||||
label="Show y scale exponentially"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="distributionChartActions"
|
||||
label="Show vega chart controls"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="showSummary"
|
||||
label="Show summary statistics"
|
||||
/>
|
||||
<InputItem
|
||||
name="minX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Min X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="maxX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Max X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="title"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Title"
|
||||
/>
|
||||
<InputItem
|
||||
name="tickFormat"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Tick Format"
|
||||
/>
|
||||
<InputItem
|
||||
name="color"
|
||||
type="color"
|
||||
register={register}
|
||||
label="Color"
|
||||
/>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
|
||||
{withFunctionSettings ? (
|
||||
<div className="pt-8">
|
||||
<HeadedSection title="Function Display Settings">
|
||||
<div className="space-y-6">
|
||||
<Text>
|
||||
When displaying functions of single variables that return
|
||||
numbers or distributions, we need to use defaults for the
|
||||
x-axis. We need to select a minimum and maximum value of x to
|
||||
sample, and a number n of the number of points to sample.
|
||||
</Text>
|
||||
<div className="space-y-4">
|
||||
<InputItem
|
||||
type="number"
|
||||
name="diagramStart"
|
||||
register={register}
|
||||
label="Min X Value"
|
||||
/>
|
||||
<InputItem
|
||||
type="number"
|
||||
name="diagramStop"
|
||||
register={register}
|
||||
label="Max X Value"
|
||||
/>
|
||||
<InputItem
|
||||
type="number"
|
||||
name="diagramCount"
|
||||
register={register}
|
||||
label="Points between X min and X max to sample"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
22
packages/components/src/components/ui/Button.tsx
Normal file
22
packages/components/src/components/ui/Button.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
wide?: boolean; // stretch the button horizontally
|
||||
};
|
||||
|
||||
export const Button: React.FC<Props> = ({ onClick, wide, children }) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
"rounded-md py-1.5 px-2 bg-slate-500 text-white text-xs font-semibold flex items-center justify-center space-x-1",
|
||||
wide && "w-full"
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { Path, UseFormRegister } from "react-hook-form";
|
||||
|
||||
|
@ -5,20 +6,32 @@ export function Checkbox<T>({
|
|||
name,
|
||||
label,
|
||||
register,
|
||||
disabled,
|
||||
tooltip,
|
||||
}: {
|
||||
name: Path<T>;
|
||||
label: string;
|
||||
register: UseFormRegister<T>;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
}) {
|
||||
return (
|
||||
<label className="flex items-center">
|
||||
<label className="flex items-center" title={tooltip}>
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={disabled}
|
||||
{...register(name)}
|
||||
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||
/>
|
||||
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
|
||||
<div className="ml-3 text-sm font-medium text-gray-700">{label}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"ml-3 text-sm font-medium",
|
||||
disabled ? "text-gray-400" : "text-gray-700"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
|
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import React from "react";
|
||||
|
||||
export const HeadedSection: React.FC<{
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ title, children }) => (
|
||||
<div>
|
||||
<header className="text-lg leading-6 font-medium text-gray-900">
|
||||
{title}
|
||||
</header>
|
||||
<div className="mt-4">{children}</div>
|
||||
</div>
|
||||
);
|
25
packages/components/src/components/ui/InputItem.tsx
Normal file
25
packages/components/src/components/ui/InputItem.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from "react";
|
||||
import { Path, UseFormRegister } from "react-hook-form";
|
||||
|
||||
export function InputItem<T>({
|
||||
name,
|
||||
label,
|
||||
type,
|
||||
register,
|
||||
}: {
|
||||
name: Path<T>;
|
||||
label: string;
|
||||
type: "number" | "text" | "color";
|
||||
register: UseFormRegister<T>;
|
||||
}) {
|
||||
return (
|
||||
<label className="block">
|
||||
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
||||
<input
|
||||
type={type}
|
||||
{...register(name, { valueAsNumber: type === "number" })}
|
||||
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
184
packages/components/src/components/ui/Modal.tsx
Normal file
184
packages/components/src/components/ui/Modal.tsx
Normal file
|
@ -0,0 +1,184 @@
|
|||
import { motion } from "framer-motion";
|
||||
import React, { useContext } from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import clsx from "clsx";
|
||||
import { useWindowScroll, useWindowSize } from "react-use";
|
||||
|
||||
type ModalContextShape = {
|
||||
close: () => void;
|
||||
};
|
||||
const ModalContext = React.createContext<ModalContextShape>({
|
||||
close: () => undefined,
|
||||
});
|
||||
|
||||
const Overlay: React.FC = () => {
|
||||
const { close } = useContext(ModalContext);
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute inset-0 -z-10 bg-black"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.1 }}
|
||||
onClick={close}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalHeader: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
const { close } = useContext(ModalContext);
|
||||
return (
|
||||
<header className="px-5 py-3 border-b border-gray-200 font-bold flex items-center justify-between">
|
||||
<div>{children}</div>
|
||||
<button
|
||||
className="px-1 bg-transparent cursor-pointer text-gray-700 hover:text-accent-500"
|
||||
type="button"
|
||||
onClick={close}
|
||||
>
|
||||
<XIcon className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500" />
|
||||
</button>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO - get rid of forwardRef, support `focus` and `{...hotkeys}` via smart props
|
||||
const ModalBody = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
JSX.IntrinsicElements["div"]
|
||||
>(function ModalBody(props, ref) {
|
||||
return <div ref={ref} className="px-5 py-3 overflow-auto" {...props} />;
|
||||
});
|
||||
|
||||
const ModalFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<div className="px-5 py-3 border-t border-gray-200">{children}</div>
|
||||
);
|
||||
|
||||
const ModalWindow: React.FC<{
|
||||
children: React.ReactNode;
|
||||
container?: HTMLElement;
|
||||
}> = ({ children, container }) => {
|
||||
// This component works in two possible modes:
|
||||
// 1. container mode - the modal is rendered inside a container element
|
||||
// 2. centered mode - the modal is rendered in the middle of the screen
|
||||
// The mode is determined by the presence of the `container` prop and by whether the available space is large enough to fit the modal.
|
||||
|
||||
// Necessary for container mode - need to reposition the modal on scroll and resize events.
|
||||
useWindowSize();
|
||||
useWindowScroll();
|
||||
|
||||
let position:
|
||||
| {
|
||||
left: number;
|
||||
top: number;
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
transform: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
// If available space in `visibleRect` is smaller than these, fallback to positioning in the middle of the screen.
|
||||
const minWidth = 384;
|
||||
const minHeight = 300;
|
||||
const offset = 8;
|
||||
const naturalWidth = 576; // maximum possible width; modal tries to take this much space, but can be smaller
|
||||
|
||||
if (container) {
|
||||
const { clientWidth: screenWidth, clientHeight: screenHeight } =
|
||||
document.documentElement;
|
||||
const rect = container?.getBoundingClientRect();
|
||||
|
||||
const visibleRect = {
|
||||
left: Math.max(rect.left, 0),
|
||||
right: Math.min(rect.right, screenWidth),
|
||||
top: Math.max(rect.top, 0),
|
||||
bottom: Math.min(rect.bottom, screenHeight),
|
||||
};
|
||||
const maxWidth = visibleRect.right - visibleRect.left - 2 * offset;
|
||||
const maxHeight = visibleRect.bottom - visibleRect.top - 2 * offset;
|
||||
|
||||
const center = {
|
||||
left: visibleRect.left + (visibleRect.right - visibleRect.left) / 2,
|
||||
top: visibleRect.top + (visibleRect.bottom - visibleRect.top) / 2,
|
||||
};
|
||||
position = {
|
||||
left: center.left,
|
||||
top: center.top,
|
||||
transform: "translate(-50%, -50%)",
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
};
|
||||
if (maxWidth < minWidth || maxHeight < minHeight) {
|
||||
position = undefined; // modal is hard to fit in the container, fallback to positioning it in the middle of the screen
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-white rounded-md shadow-toast flex flex-col overflow-auto border",
|
||||
position ? "fixed" : null
|
||||
)}
|
||||
style={{
|
||||
width: naturalWidth,
|
||||
...(position ?? {
|
||||
maxHeight: "calc(100% - 20px)",
|
||||
maxWidth: "calc(100% - 20px)",
|
||||
width: naturalWidth,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type ModalType = React.FC<{
|
||||
children: React.ReactNode;
|
||||
container?: HTMLElement; // if specified, modal will be positioned over the visible part of the container, if it's not too small
|
||||
close: () => void;
|
||||
}> & {
|
||||
Body: typeof ModalBody;
|
||||
Footer: typeof ModalFooter;
|
||||
Header: typeof ModalHeader;
|
||||
};
|
||||
|
||||
export const Modal: ModalType = ({ children, container, close }) => {
|
||||
const [el] = React.useState(() => document.createElement("div"));
|
||||
|
||||
React.useEffect(() => {
|
||||
document.body.appendChild(el);
|
||||
return () => {
|
||||
document.body.removeChild(el);
|
||||
};
|
||||
}, [el]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
close();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, [close]);
|
||||
|
||||
const modal = (
|
||||
<ModalContext.Provider value={{ close }}>
|
||||
<div className="squiggle">
|
||||
<div className="fixed inset-0 z-40 flex justify-center items-center">
|
||||
<Overlay />
|
||||
<ModalWindow container={container}>{children}</ModalWindow>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
|
||||
return ReactDOM.createPortal(modal, container || el);
|
||||
};
|
||||
|
||||
Modal.Body = ModalBody;
|
||||
Modal.Footer = ModalFooter;
|
||||
Modal.Header = ModalHeader;
|
5
packages/components/src/components/ui/Text.tsx
Normal file
5
packages/components/src/components/ui/Text.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
|
||||
export const Text: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<p className="text-sm text-gray-500">{children}</p>
|
||||
);
|
|
@ -1,5 +1,5 @@
|
|||
import { RefreshIcon } from "@heroicons/react/solid";
|
||||
import clsx from "clsx";
|
||||
import { motion } from "framer-motion";
|
||||
import React from "react";
|
||||
|
||||
type IconType = (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||
|
@ -9,19 +9,19 @@ type Props = {
|
|||
onChange: (status: boolean) => void;
|
||||
texts: [string, string];
|
||||
icons: [IconType, IconType];
|
||||
spinIcon?: boolean;
|
||||
};
|
||||
|
||||
export const Toggle: React.FC<Props> = ({
|
||||
texts: [onText, offText],
|
||||
icons: [OnIcon, OffIcon],
|
||||
status,
|
||||
onChange,
|
||||
texts: [onText, offText],
|
||||
icons: [OnIcon, OffIcon],
|
||||
spinIcon,
|
||||
}) => {
|
||||
const CurrentIcon = status ? OnIcon : OffIcon;
|
||||
return (
|
||||
<motion.button
|
||||
layout
|
||||
transition={{ duration: 0.2 }}
|
||||
<button
|
||||
className={clsx(
|
||||
"rounded-md py-0.5 bg-slate-500 text-white text-xs font-semibold flex items-center space-x-1",
|
||||
status ? "bg-slate-500" : "bg-gray-400",
|
||||
|
@ -30,12 +30,18 @@ export const Toggle: React.FC<Props> = ({
|
|||
)}
|
||||
onClick={() => onChange(!status)}
|
||||
>
|
||||
<motion.div layout transition={{ duration: 0.2 }}>
|
||||
<CurrentIcon className="w-6 h-6" />
|
||||
</motion.div>
|
||||
<motion.span layout transition={{ duration: 0.2 }}>
|
||||
{status ? onText : offText}
|
||||
</motion.span>
|
||||
</motion.button>
|
||||
<div className="relative w-6 h-6" key={String(spinIcon)}>
|
||||
<CurrentIcon
|
||||
className={clsx(
|
||||
"w-6 h-6 absolute opacity-100",
|
||||
spinIcon && "animate-hide"
|
||||
)}
|
||||
/>
|
||||
{spinIcon && (
|
||||
<RefreshIcon className="w-6 h-6 absolute opacity-0 animate-appear-and-spin" />
|
||||
)}
|
||||
</div>
|
||||
<span>{status ? onText : offText}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import React, { cloneElement, useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
flip,
|
||||
shift,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useHover,
|
||||
useInteractions,
|
||||
useRole,
|
||||
} from "@floating-ui/react-dom-interactions";
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export const Tooltip: React.FC<Props> = ({ text, children }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { x, y, reference, floating, strategy, context } = useFloating({
|
||||
placement: "top",
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
middleware: [shift(), flip()],
|
||||
});
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||
useHover(context),
|
||||
useRole(context, { role: "tooltip" }),
|
||||
useDismiss(context),
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{cloneElement(
|
||||
children,
|
||||
getReferenceProps({ ref: reference, ...children.props })
|
||||
)}
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
{...getFloatingProps({
|
||||
ref: floating,
|
||||
className:
|
||||
"text-xs p-2 border border-gray-300 rounded bg-white z-10",
|
||||
style: {
|
||||
position: strategy,
|
||||
top: y ?? 0,
|
||||
left: x ?? 0,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div className="font-mono whitespace-pre">{text}</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -2,5 +2,6 @@ export { SquiggleChart } from "./components/SquiggleChart";
|
|||
export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor";
|
||||
export { SquigglePlayground } from "./components/SquigglePlayground";
|
||||
export { SquiggleContainer } from "./components/SquiggleContainer";
|
||||
export { SquiggleEditorWithImportedBindings } from "./components/SquiggleEditorWithImportedBindings";
|
||||
|
||||
export { mergeBindings } from "@quri/squiggle-lang";
|
||||
|
|
|
@ -56,12 +56,15 @@ export let expYScale: PowScale = {
|
|||
domain: { data: "domain", field: "y" },
|
||||
};
|
||||
|
||||
export const defaultTickFormat = ".9~s";
|
||||
export const defaultColor = "#739ECC";
|
||||
|
||||
export function buildVegaSpec(
|
||||
specOptions: DistributionChartSpecOptions
|
||||
): VisualizationSpec {
|
||||
const {
|
||||
format = ".9~s",
|
||||
color = "#739ECC",
|
||||
format = defaultTickFormat,
|
||||
color = defaultColor,
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
|
|
5
packages/components/src/lib/distributionUtils.ts
Normal file
5
packages/components/src/lib/distributionUtils.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { shape } from "@quri/squiggle-lang";
|
||||
|
||||
export const hasMassBelowZero = (shape: shape) =>
|
||||
shape.continuous.some((x) => x.x <= 0) ||
|
||||
shape.discrete.some((x) => x.x <= 0);
|
3
packages/components/src/lib/hooks/index.ts
Normal file
3
packages/components/src/lib/hooks/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { useMaybeControlledValue } from "./useMaybeControlledValue";
|
||||
export { useSquiggle, useSquigglePartial } from "./useSquiggle";
|
||||
export { useRunnerState } from "./useRunnerState";
|
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { useState } from "react";
|
||||
|
||||
type ControlledValueArgs<T> = {
|
||||
value?: T;
|
||||
defaultValue: T;
|
||||
onChange?: (x: T) => void;
|
||||
};
|
||||
|
||||
export function useMaybeControlledValue<T>(
|
||||
args: ControlledValueArgs<T>
|
||||
): [T, (x: T) => void] {
|
||||
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
||||
let value = args.value ?? uncontrolledValue;
|
||||
let onChange = (newValue: T) => {
|
||||
if (args.value === undefined) {
|
||||
// uncontrolled mode
|
||||
setUncontrolledValue(newValue);
|
||||
}
|
||||
args.onChange?.(newValue);
|
||||
};
|
||||
return [value, onChange];
|
||||
}
|
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import { useLayoutEffect, useReducer } from "react";
|
||||
|
||||
type State = {
|
||||
autorunMode: boolean;
|
||||
renderedCode: string;
|
||||
// "prepared" is for rendering a spinner; "run" for executing squiggle code; then it gets back to "none" on the next render
|
||||
runningState: "none" | "prepared" | "run";
|
||||
executionId: number;
|
||||
};
|
||||
|
||||
const buildInitialState = (code: string): State => ({
|
||||
autorunMode: true,
|
||||
renderedCode: "",
|
||||
runningState: "none",
|
||||
executionId: 1,
|
||||
});
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: "SET_AUTORUN_MODE";
|
||||
value: boolean;
|
||||
code: string;
|
||||
}
|
||||
| {
|
||||
type: "PREPARE_RUN";
|
||||
}
|
||||
| {
|
||||
type: "RUN";
|
||||
code: string;
|
||||
}
|
||||
| {
|
||||
type: "STOP_RUN";
|
||||
};
|
||||
|
||||
const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "SET_AUTORUN_MODE":
|
||||
return {
|
||||
...state,
|
||||
autorunMode: action.value,
|
||||
};
|
||||
case "PREPARE_RUN":
|
||||
return {
|
||||
...state,
|
||||
runningState: "prepared",
|
||||
};
|
||||
case "RUN":
|
||||
return {
|
||||
...state,
|
||||
runningState: "run",
|
||||
renderedCode: action.code,
|
||||
executionId: state.executionId + 1,
|
||||
};
|
||||
case "STOP_RUN":
|
||||
return {
|
||||
...state,
|
||||
runningState: "none",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const useRunnerState = (code: string) => {
|
||||
const [state, dispatch] = useReducer(reducer, buildInitialState(code));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (state.runningState === "prepared") {
|
||||
// this is necessary for async playground loading - otherwise it executes the code synchronously on the initial load
|
||||
// (it's surprising that this is necessary, but empirically it _is_ necessary, both with `useEffect` and `useLayoutEffect`)
|
||||
setTimeout(() => {
|
||||
dispatch({ type: "RUN", code });
|
||||
}, 0);
|
||||
} else if (state.runningState === "run") {
|
||||
dispatch({ type: "STOP_RUN" });
|
||||
}
|
||||
}, [state.runningState, code]);
|
||||
|
||||
const run = () => {
|
||||
// The rest will be handled by dispatches above on following renders, but we need to update the spinner first.
|
||||
dispatch({ type: "PREPARE_RUN" });
|
||||
};
|
||||
|
||||
if (
|
||||
state.autorunMode &&
|
||||
state.renderedCode !== code &&
|
||||
state.runningState === "none"
|
||||
) {
|
||||
run();
|
||||
}
|
||||
|
||||
return {
|
||||
run,
|
||||
autorunMode: state.autorunMode,
|
||||
renderedCode: state.renderedCode,
|
||||
isRunning: state.runningState !== "none",
|
||||
executionId: state.executionId,
|
||||
setAutorunMode: (newValue: boolean) => {
|
||||
dispatch({ type: "SET_AUTORUN_MODE", value: newValue, code });
|
||||
},
|
||||
};
|
||||
};
|
|
@ -5,10 +5,11 @@ import {
|
|||
run,
|
||||
runPartial,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
|
||||
code: string;
|
||||
executionId?: number;
|
||||
bindings?: bindings;
|
||||
jsImports?: jsImports;
|
||||
environment?: environment;
|
||||
|
@ -21,7 +22,15 @@ const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>(
|
|||
) => {
|
||||
const result: T = useMemo<T>(
|
||||
() => f(args.code, args.bindings, args.environment, args.jsImports),
|
||||
[f, args.code, args.bindings, args.environment, args.jsImports]
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
f,
|
||||
args.code,
|
||||
args.bindings,
|
||||
args.environment,
|
||||
args.jsImports,
|
||||
args.executionId,
|
||||
]
|
||||
);
|
||||
|
||||
const { onChange } = args;
|
||||
|
@ -42,23 +51,3 @@ export const useSquigglePartial = (
|
|||
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
|
||||
return useSquiggleAny(args, run);
|
||||
};
|
||||
|
||||
type ControlledValueArgs<T> = {
|
||||
value?: T;
|
||||
defaultValue: T;
|
||||
onChange?: (x: T) => void;
|
||||
};
|
||||
export function useMaybeControlledValue<T>(
|
||||
args: ControlledValueArgs<T>
|
||||
): [T, (x: T) => void] {
|
||||
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
||||
let value = args.value ?? uncontrolledValue;
|
||||
let onChange = (newValue: T) => {
|
||||
if (args.value === undefined) {
|
||||
// uncontrolled mode
|
||||
setUncontrolledValue(newValue);
|
||||
}
|
||||
args.onChange?.(newValue);
|
||||
};
|
||||
return [value, onChange];
|
||||
}
|
|
@ -43,7 +43,7 @@ could be continuous, discrete or mixed.
|
|||
<Story
|
||||
name="Continuous Pointset"
|
||||
args={{
|
||||
code: "toPointSet(normal(5,2))",
|
||||
code: "PointSet.fromDist(normal(5,2))",
|
||||
width,
|
||||
}}
|
||||
>
|
||||
|
@ -57,7 +57,7 @@ could be continuous, discrete or mixed.
|
|||
<Story
|
||||
name="Continuous SampleSet"
|
||||
args={{
|
||||
code: "toSampleSet(normal(5,2), 1000)",
|
||||
code: "SampleSet.fromDist(normal(5,2))",
|
||||
width,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -21,3 +21,16 @@ including sampling settings, in squiggle.
|
|||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="With share button"
|
||||
args={{
|
||||
defaultCode: "normal(5,2)",
|
||||
height: 800,
|
||||
showShareButton: true,
|
||||
}}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
|
|
@ -5,6 +5,27 @@ module.exports = {
|
|||
},
|
||||
important: ".squiggle",
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
animation: {
|
||||
"appear-and-spin":
|
||||
"spin 1s linear infinite, squiggle-appear 0.2s forwards",
|
||||
"semi-appear": "squiggle-semi-appear 0.2s forwards",
|
||||
hide: "squiggle-hide 0.2s forwards",
|
||||
},
|
||||
keyframes: {
|
||||
"squiggle-appear": {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 1 },
|
||||
},
|
||||
"squiggle-semi-appear": {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 0.5 },
|
||||
},
|
||||
"squiggle-hide": {
|
||||
from: { opacity: 1 },
|
||||
to: { opacity: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ environment created from the squiggle code.
|
|||
```js
|
||||
import { run } from "@quri/squiggle-lang";
|
||||
run(
|
||||
"normal(0, 1) * fromSamples([-3,-2,-1,1,2,3,3,3,4,9]"
|
||||
"normal(0, 1) * SampleSet.fromList([-3, 2,-1,1,2,3,3,3,4,9])"
|
||||
).value.value.toSparkline().value;
|
||||
```
|
||||
|
||||
|
|
|
@ -17,14 +17,6 @@ describe("builtin", () => {
|
|||
testEval("1-1", "Ok(0)")
|
||||
testEval("2>1", "Ok(true)")
|
||||
testEval("concat('a','b')", "Ok('ab')")
|
||||
testEval(
|
||||
"addOne(t)=t+1; toList(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))",
|
||||
"Ok([2,3,4,5,6,7])",
|
||||
)
|
||||
testEval(
|
||||
"toList(mapSamplesN([fromSamples([1,2,3,4,5,6]), fromSamples([6, 5, 4, 3, 2, 1])], {|x| x[0] > x[1] ? x[0] : x[1]}))",
|
||||
"Ok([6,5,4,4,5,6])",
|
||||
)
|
||||
})
|
||||
|
||||
describe("builtin exception", () => {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
open Jest
|
||||
open Reducer_Peggy_TestHelpers
|
||||
|
||||
describe("Peggy void", () => {
|
||||
//literal
|
||||
testToExpression("()", "{()}", ~v="()", ())
|
||||
testToExpression(
|
||||
"fn()=1",
|
||||
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1}))}",
|
||||
~v="@{fn: lambda(_=>internal code)}",
|
||||
(),
|
||||
)
|
||||
testToExpression("fn()=1; fn()", "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:fn ())}", ~v="1", ())
|
||||
testToExpression(
|
||||
"fn(a)=(); call fn(1)",
|
||||
"{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)})}",
|
||||
~v="@{_: (),fn: lambda(a=>internal code)}",
|
||||
(),
|
||||
)
|
||||
})
|
|
@ -1,15 +1,6 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
describe("map reduce", () => {
|
||||
testEvalToBe("double(x)=2*x; arr=[1,2,3]; map(arr, double)", "Ok([2,4,6])")
|
||||
testEvalToBe("myadd(acc,x)=acc+x; arr=[1,2,3]; reduce(arr, 0, myadd)", "Ok(6)")
|
||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; reduce(arr, 0, change)", "Ok(15)")
|
||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; reduceReverse(arr, 0, change)", "Ok(9)")
|
||||
testEvalToBe("arr=[1,2,3]; reverse(arr)", "Ok([3,2,1])")
|
||||
testEvalToBe("check(x)=(x==2);arr=[1,2,3]; filter(arr,check)", "Ok([2])")
|
||||
})
|
||||
|
||||
Skip.describe("map reduce (sam)", () => {
|
||||
testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???")
|
||||
testEvalToBe("addone(x)=x+1; map(2, {x: addone})", "Error???")
|
||||
|
|
|
@ -27,6 +27,15 @@ describe("eval", () => {
|
|||
test("index", () => expectEvalToBe("r = {a: 1}; r.a", "Ok(1)"))
|
||||
test("index", () => expectEvalToBe("r = {a: 1}; r.b", "Error(Record property not found: b)"))
|
||||
testEvalError("{a: 1}.b") // invalid syntax
|
||||
test("always the same property ending", () =>
|
||||
expectEvalToBe(
|
||||
`{
|
||||
a: 1,
|
||||
b: 2,
|
||||
}`,
|
||||
"Ok({a: 1,b: 2})",
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
describe("multi-line", () => {
|
||||
|
|
|
@ -41,12 +41,6 @@ describe("eval on distribution functions", () => {
|
|||
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)")
|
||||
|
|
|
@ -16,6 +16,13 @@ describe("FunctionRegistry Library", () => {
|
|||
testEvalToBe("List.first([3,5,8])", "Ok(3)")
|
||||
testEvalToBe("List.last([3,5,8])", "Ok(8)")
|
||||
testEvalToBe("List.reverse([3,5,8])", "Ok([8,5,3])")
|
||||
testEvalToBe("double(x)=2*x; arr=[1,2,3]; List.map(arr, double)", "Ok([2,4,6])")
|
||||
testEvalToBe("double(x)=2*x; arr=[1,2,3]; map(arr, double)", "Ok([2,4,6])")
|
||||
testEvalToBe("myadd(acc,x)=acc+x; arr=[1,2,3]; List.reduce(arr, 0, myadd)", "Ok(6)")
|
||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; List.reduce(arr, 0, change)", "Ok(15)")
|
||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; List.reduceReverse(arr, 0, change)", "Ok(9)")
|
||||
testEvalToBe("check(x)=(x==2);arr=[1,2,3]; List.filter(arr,check)", "Ok([2])")
|
||||
testEvalToBe("arr=[1,2,3]; List.reverse(arr)", "Ok([3,2,1])")
|
||||
testEvalToBe("Dist.normal(5,2)", "Ok(Normal(5,2))")
|
||||
testEvalToBe("normal(5,2)", "Ok(Normal(5,2))")
|
||||
testEvalToBe("normal({mean:5,stdev:2})", "Ok(Normal(5,2))")
|
||||
|
@ -53,6 +60,17 @@ describe("FunctionRegistry Library", () => {
|
|||
)
|
||||
testEvalToBe("Dist.logScore({estimate: normal(5,2), answer: 4.5})", "Ok(1.6433360626394853)")
|
||||
testEvalToBe("Dist.klDivergence(normal(5,2), normal(5,1.5))", "Ok(0.06874342818671068)")
|
||||
testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)")
|
||||
testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)")
|
||||
testEvalToBe("SampleSet.fromFn({|| sample(normal(5,2))})", "Ok(Sample Set Distribution)")
|
||||
testEvalToBe(
|
||||
"addOne(t)=t+1; SampleSet.toList(SampleSet.map(SampleSet.fromList([1,2,3,4,5,6]), addOne))",
|
||||
"Ok([2,3,4,5,6,7])",
|
||||
)
|
||||
testEvalToBe(
|
||||
"SampleSet.toList(SampleSet.mapN([SampleSet.fromList([1,2,3,4,5,6]), SampleSet.fromList([6, 5, 4, 3, 2, 1])], {|x| x[0] > x[1] ? x[0] : x[1]}))",
|
||||
"Ok([6,5,4,4,5,6])",
|
||||
)
|
||||
})
|
||||
|
||||
describe("Fn auto-testing", () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@quri/squiggle-lang",
|
||||
"version": "0.2.11",
|
||||
"version": "0.3.0",
|
||||
"homepage": "https://squiggle-language.com",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
@ -20,6 +20,7 @@
|
|||
"test:ts": "jest __tests__/TS/",
|
||||
"test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*",
|
||||
"test:watch": "jest --watchAll",
|
||||
"test:fnRegistry": "jest __tests__/SquiggleLibrary/SquiggleLibrary_FunctionRegistryLibrary_test.bs.js",
|
||||
"coverage:rescript": "rm -f *.coverage && yarn clean && BISECT_ENABLE=yes yarn build && yarn test:rescript && bisect-ppx-report html",
|
||||
"coverage:ts": "yarn clean && yarn build && nyc --reporter=lcov yarn test:ts",
|
||||
"coverage:rescript:ci": "yarn clean && BISECT_ENABLE=yes yarn build:rescript && yarn test:rescript && bisect-ppx-report send-to Codecov",
|
||||
|
@ -31,6 +32,7 @@
|
|||
"format:prettier": "prettier --write .",
|
||||
"format": "yarn format:rescript && yarn format:prettier",
|
||||
"prepack": "yarn build && yarn test && yarn bundle",
|
||||
"all:rescript": "yarn build:rescript && yarn test:rescript && yarn format:rescript",
|
||||
"all": "yarn build && yarn bundle && yarn test"
|
||||
},
|
||||
"keywords": [
|
||||
|
@ -42,7 +44,7 @@
|
|||
"@stdlib/stats": "^0.0.13",
|
||||
"jstat": "^1.9.5",
|
||||
"lodash": "^4.17.21",
|
||||
"mathjs": "^10.6.4",
|
||||
"mathjs": "^11.0.1",
|
||||
"pdfast": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -53,7 +55,7 @@
|
|||
"bisect_ppx": "^2.7.1",
|
||||
"chalk": "^5.0.1",
|
||||
"codecov": "^3.8.3",
|
||||
"fast-check": "^3.0.1",
|
||||
"fast-check": "^3.1.1",
|
||||
"gentype": "^4.5.0",
|
||||
"jest": "^27.5.1",
|
||||
"moduleserve": "^0.9.1",
|
||||
|
@ -67,7 +69,7 @@
|
|||
"ts-loader": "^9.3.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.7.4",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"source": "./src/js/index.ts",
|
||||
|
|
|
@ -120,6 +120,10 @@ function createTsExport(
|
|||
x: expressionValue,
|
||||
environment: environment
|
||||
): squiggleExpression {
|
||||
switch (x) {
|
||||
case "EvVoid":
|
||||
return tag("void", "");
|
||||
default: {
|
||||
switch (x.tag) {
|
||||
case "EvArray":
|
||||
// genType doesn't convert anything more than 2 layers down into {tag: x, value: x}
|
||||
|
@ -153,7 +157,8 @@ function createTsExport(
|
|||
return tag("number", x.value);
|
||||
case "EvRecord":
|
||||
// genType doesn't support records, so we have to do the raw conversion ourself
|
||||
let result: tagged<"record", { [key: string]: squiggleExpression }> = tag(
|
||||
let result: tagged<"record", { [key: string]: squiggleExpression }> =
|
||||
tag(
|
||||
"record",
|
||||
_.mapValues(x.value, (x: unknown) =>
|
||||
convertRawToTypescript(x as rescriptExport, environment)
|
||||
|
@ -173,8 +178,10 @@ function createTsExport(
|
|||
case "EvTypeIdentifier":
|
||||
return tag("typeIdentifier", x.value);
|
||||
case "EvType":
|
||||
let typeResult: tagged<"type", { [key: string]: squiggleExpression }> =
|
||||
tag(
|
||||
let typeResult: tagged<
|
||||
"type",
|
||||
{ [key: string]: squiggleExpression }
|
||||
> = tag(
|
||||
"type",
|
||||
_.mapValues(x.value, (x: unknown) =>
|
||||
convertRawToTypescript(x as rescriptExport, environment)
|
||||
|
@ -194,3 +201,5 @@ function createTsExport(
|
|||
return moduleResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import { tagged, tag } from "./types";
|
|||
|
||||
// Raw rescript types.
|
||||
export type rescriptExport =
|
||||
| 0 // EvVoid
|
||||
| {
|
||||
TAG: 0; // EvArray
|
||||
_0: rescriptExport[];
|
||||
|
@ -131,7 +132,8 @@ export type squiggleExpression =
|
|||
| tagged<"record", { [key: string]: squiggleExpression }>
|
||||
| tagged<"type", { [key: string]: squiggleExpression }>
|
||||
| tagged<"typeIdentifier", string>
|
||||
| tagged<"module", { [key: string]: squiggleExpression }>;
|
||||
| tagged<"module", { [key: string]: squiggleExpression }>
|
||||
| tagged<"void", string>;
|
||||
|
||||
export { lambdaValue };
|
||||
|
||||
|
@ -139,6 +141,10 @@ export function convertRawToTypescript(
|
|||
result: rescriptExport,
|
||||
environment: environment
|
||||
): squiggleExpression {
|
||||
if (typeof result === "number") {
|
||||
// EvVoid
|
||||
return tag("void", "");
|
||||
}
|
||||
switch (result.TAG) {
|
||||
case 0: // EvArray
|
||||
return tag(
|
||||
|
|
|
@ -250,7 +250,7 @@ module T = Dist({
|
|||
|
||||
let downsample = (length, t): t =>
|
||||
t |> shapeMap(XYShape.XsConversion.proportionByProbabilityMass(length, integral(t).xyShape))
|
||||
let integralEndY = (t: t) => t.integralSumCache |> E.O.default(t |> integral |> lastY)
|
||||
let integralEndY = (t: t) => t.integralSumCache |> E.O.defaultFn(() => t |> integral |> lastY)
|
||||
let integralXtoY = (f, t: t) => t |> integral |> shapeFn(XYShape.XtoY.linear(f))
|
||||
let integralYtoX = (f, t: t) => t |> integral |> shapeFn(XYShape.YtoX.linear(f))
|
||||
let toContinuous = t => Some(t)
|
||||
|
|
|
@ -158,7 +158,8 @@ module T = Dist({
|
|||
Continuous.make(~interpolation=#Stepwise, integralShape)
|
||||
}
|
||||
|
||||
let integralEndY = (t: t) => t.integralSumCache |> E.O.default(t |> integral |> Continuous.lastY)
|
||||
let integralEndY = (t: t) =>
|
||||
t.integralSumCache |> E.O.defaultFn(() => t |> integral |> Continuous.lastY)
|
||||
let minX = shapeFn(XYShape.T.minX)
|
||||
let maxX = shapeFn(XYShape.T.maxX)
|
||||
let toDiscreteProbabilityMassFraction = _ => 1.0
|
||||
|
|
|
@ -13,9 +13,11 @@ let buildSimple = (
|
|||
~discrete: option<PointSetTypes.discreteShape>,
|
||||
): option<PointSetTypes.pointSetDist> => {
|
||||
let continuous =
|
||||
continuous |> E.O.default(Continuous.make(~integralSumCache=Some(0.0), {xs: [], ys: []}))
|
||||
continuous |> E.O.defaultFn(() =>
|
||||
Continuous.make(~integralSumCache=Some(0.0), {xs: [], ys: []})
|
||||
)
|
||||
let discrete =
|
||||
discrete |> E.O.default(Discrete.make(~integralSumCache=Some(0.0), {xs: [], ys: []}))
|
||||
discrete |> E.O.defaultFn(() => Discrete.make(~integralSumCache=Some(0.0), {xs: [], ys: []}))
|
||||
let cLength = continuous |> Continuous.getShape |> XYShape.T.xs |> E.A.length
|
||||
let dLength = discrete |> Discrete.getShape |> XYShape.T.xs |> E.A.length
|
||||
switch (cLength, dLength) {
|
||||
|
|
|
@ -47,6 +47,7 @@ type fnDefinition = {
|
|||
array<internalExpressionValue>,
|
||||
array<frValue>,
|
||||
GenericDist.env,
|
||||
Reducer_Expression_T.reducerFn,
|
||||
) => result<internalExpressionValue, string>,
|
||||
}
|
||||
|
||||
|
@ -184,6 +185,9 @@ module FRType = {
|
|||
This module, Matcher, is fairly lengthy. However, only two functions from it
|
||||
are meant to be used outside of it. These are findMatches and matchToDef in Matches.Registry.
|
||||
The rest of it is just called from those two functions.
|
||||
|
||||
Update: This really should be completely re-done sometime, and tested. It works, but it's pretty messy. I'm sure
|
||||
there are internal bugs, but the end functionality works, so I'm not too worried.
|
||||
*/
|
||||
module Matcher = {
|
||||
module MatchSimple = {
|
||||
|
@ -239,7 +243,15 @@ module Matcher = {
|
|||
type definitionId = int
|
||||
type match = Match.t<array<definitionId>, definitionId>
|
||||
|
||||
let match = (f: function, fnName: string, args: array<internalExpressionValue>): match => {
|
||||
let match = (
|
||||
f: function,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
): match => {
|
||||
switch nameSpace {
|
||||
| Some(ns) if ns !== f.nameSpace => Match.DifferentName
|
||||
| _ => {
|
||||
let matchedDefinition = () =>
|
||||
E.A.getIndexBy(f.definitions, r =>
|
||||
MatchSimple.isFullMatch(FnDefinition.match(r, fnName, args))
|
||||
|
@ -248,7 +260,9 @@ module Matcher = {
|
|||
let nameMatchIndexes =
|
||||
f.definitions
|
||||
->E.A2.fmapi((index, r) =>
|
||||
MatchSimple.isNameMatchOnly(FnDefinition.match(r, fnName, args)) ? Some(index) : None
|
||||
MatchSimple.isNameMatchOnly(FnDefinition.match(r, fnName, args))
|
||||
? Some(index)
|
||||
: None
|
||||
)
|
||||
->E.A.O.concatSomes
|
||||
switch nameMatchIndexes {
|
||||
|
@ -263,29 +277,48 @@ module Matcher = {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module RegistryMatch = {
|
||||
type match = {
|
||||
nameSpace: string,
|
||||
fnName: string,
|
||||
inputIndex: int,
|
||||
}
|
||||
let makeMatch = (fnName: string, inputIndex: int) => {fnName: fnName, inputIndex: inputIndex}
|
||||
let makeMatch = (nameSpace: string, fnName: string, inputIndex: int) => {
|
||||
nameSpace: nameSpace,
|
||||
fnName: fnName,
|
||||
inputIndex: inputIndex,
|
||||
}
|
||||
}
|
||||
|
||||
module Registry = {
|
||||
let _findExactMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
|
||||
let functionMatchPairs = r.functions->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
|
||||
let _findExactMatches = (
|
||||
r: registry,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
) => {
|
||||
let functionMatchPairs =
|
||||
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
|
||||
let fullMatch = functionMatchPairs->E.A.getBy(((_, match)) => Match.isFullMatch(match))
|
||||
fullMatch->E.O.bind(((fn, match)) =>
|
||||
switch match {
|
||||
| FullMatch(index) => Some(RegistryMatch.makeMatch(fn.name, index))
|
||||
| FullMatch(index) => Some(RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let _findNameMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
|
||||
let functionMatchPairs = r.functions->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
|
||||
let _findNameMatches = (
|
||||
r: registry,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
) => {
|
||||
let functionMatchPairs =
|
||||
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
|
||||
let getNameMatches =
|
||||
functionMatchPairs
|
||||
->E.A2.fmap(((fn, match)) => Match.isNameMatchOnly(match) ? Some((fn, match)) : None)
|
||||
|
@ -295,7 +328,7 @@ module Matcher = {
|
|||
->E.A2.fmap(((fn, match)) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments(indexes) =>
|
||||
indexes->E.A2.fmap(index => RegistryMatch.makeMatch(fn.name, index))
|
||||
indexes->E.A2.fmap(index => RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
|
||||
| _ => []
|
||||
}
|
||||
)
|
||||
|
@ -306,22 +339,26 @@ module Matcher = {
|
|||
let findMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
|
||||
let fnNameInParts = Js.String.split(".", fnName)
|
||||
let fnToSearch = E.A.get(fnNameInParts, 1) |> E.O.default(fnNameInParts[0])
|
||||
let nameSpace = E.A.length(fnNameInParts) > 1 ? Some(fnNameInParts[0]) : None
|
||||
|
||||
switch _findExactMatches(r, fnToSearch, args) {
|
||||
switch _findExactMatches(r, nameSpace, fnToSearch, args) {
|
||||
| Some(r) => Match.FullMatch(r)
|
||||
| None =>
|
||||
switch _findNameMatches(r, fnToSearch, args) {
|
||||
switch _findNameMatches(r, nameSpace, fnToSearch, args) {
|
||||
| Some(r) => Match.SameNameDifferentArguments(r)
|
||||
| None => Match.DifferentName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let matchToDef = (registry: registry, {fnName, inputIndex}: RegistryMatch.match): option<
|
||||
fnDefinition,
|
||||
> =>
|
||||
let matchToDef = (
|
||||
registry: registry,
|
||||
{nameSpace, fnName, inputIndex}: RegistryMatch.match,
|
||||
): option<fnDefinition> =>
|
||||
registry.functions
|
||||
->E.A.getBy(fn => fn.name === fnName)
|
||||
->E.A.getBy(fn => {
|
||||
nameSpace === fn.nameSpace && fnName === fn.name
|
||||
})
|
||||
->E.O.bind(fn => E.A.get(fn.definitions, inputIndex))
|
||||
}
|
||||
}
|
||||
|
@ -342,10 +379,15 @@ module FnDefinition = {
|
|||
}
|
||||
}
|
||||
|
||||
let run = (t: t, args: array<internalExpressionValue>, env: GenericDist.env) => {
|
||||
let run = (
|
||||
t: t,
|
||||
args: array<internalExpressionValue>,
|
||||
env: GenericDist.env,
|
||||
reducer: Reducer_Expression_T.reducerFn,
|
||||
) => {
|
||||
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args)
|
||||
switch argValues {
|
||||
| Some(values) => t.run(args, values, env)
|
||||
| Some(values) => t.run(args, values, env, reducer)
|
||||
| None => Error("Incorrect Types")
|
||||
}
|
||||
}
|
||||
|
@ -452,6 +494,7 @@ module Registry = {
|
|||
~fnName: string,
|
||||
~args: array<internalExpressionValue>,
|
||||
~env: GenericDist.env,
|
||||
~reducer: Reducer_Expression_T.reducerFn,
|
||||
) => {
|
||||
let relevantFunctions = Js.Dict.get(registry.fnNameDict, fnName) |> E.O.default([])
|
||||
let modified = {functions: relevantFunctions, fnNameDict: registry.fnNameDict}
|
||||
|
@ -468,7 +511,8 @@ module Registry = {
|
|||
}
|
||||
|
||||
switch Matcher.Registry.findMatches(modified, fnName, args) {
|
||||
| Matcher.Match.FullMatch(match) => match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env))
|
||||
| Matcher.Match.FullMatch(match) =>
|
||||
match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env, reducer))
|
||||
| SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m)))
|
||||
| _ => None
|
||||
}
|
||||
|
@ -478,8 +522,9 @@ module Registry = {
|
|||
registry,
|
||||
(fnName, args): ReducerInterface_InternalExpressionValue.functionCall,
|
||||
env,
|
||||
reducer: Reducer_Expression_T.reducerFn,
|
||||
) => {
|
||||
_matchAndRun(~registry, ~fnName, ~args, ~env)->E.O2.fmap(
|
||||
_matchAndRun(~registry, ~fnName, ~args, ~env, ~reducer)->E.O2.fmap(
|
||||
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ let impossibleError = "Wrong inputs / Logically impossible"
|
|||
|
||||
module Wrappers = {
|
||||
let symbolic = r => DistributionTypes.Symbolic(r)
|
||||
let pointSet = r => DistributionTypes.PointSet(r)
|
||||
let sampleSet = r => DistributionTypes.SampleSet(r)
|
||||
let evDistribution = r => ReducerInterface_InternalExpressionValue.IEvDistribution(r)
|
||||
let evNumber = r => ReducerInterface_InternalExpressionValue.IEvNumber(r)
|
||||
let evArray = r => ReducerInterface_InternalExpressionValue.IEvArray(r)
|
||||
|
|
|
@ -2,6 +2,7 @@ let fnList = Belt.Array.concatMany([
|
|||
FR_Dict.library,
|
||||
FR_Dist.library,
|
||||
FR_Fn.library,
|
||||
FR_Sampleset.library,
|
||||
FR_List.library,
|
||||
FR_Number.library,
|
||||
FR_Pointset.library,
|
||||
|
|
|
@ -52,7 +52,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="merge",
|
||||
~inputs=[FRTypeDict(FRTypeAny), FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _) => {
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvRecord(d1), IEvRecord(d2)] => Internals.merge(d1, d2)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -74,7 +74,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="mergeMany",
|
||||
~inputs=[FRTypeArray(FRTypeDict(FRTypeAny))],
|
||||
~run=(_, inputs, _) =>
|
||||
~run=(_, inputs, _, _) =>
|
||||
inputs
|
||||
->Prepare.ToTypedArray.dicts
|
||||
->E.R2.fmap(E.Dict.concatMany)
|
||||
|
@ -96,7 +96,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="keys",
|
||||
~inputs=[FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _) =>
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvRecord(d1)] => Internals.keys(d1)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -116,7 +116,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="values",
|
||||
~inputs=[FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _) =>
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvRecord(d1)] => Internals.values(d1)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -136,7 +136,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="toList",
|
||||
~inputs=[FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _) =>
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvRecord(dict)] => dict->Internals.toList->Ok
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -156,10 +156,11 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="fromList",
|
||||
~inputs=[FRTypeArray(FRTypeArray(FRTypeAny))],
|
||||
~run=(inputs, _, _) =>
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvArray(items)] => Internals.fromList(items)
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
|
|
|
@ -21,7 +21,8 @@ module DistributionCreation = {
|
|||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
|
||||
~run=(_, inputs, env) => inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env),
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
@ -30,7 +31,7 @@ module DistributionCreation = {
|
|||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
|
||||
~run=(_, inputs, env) =>
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
|
@ -40,7 +41,7 @@ module DistributionCreation = {
|
|||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
|
||||
~run=(_, inputs, env) =>
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
|
@ -57,7 +58,8 @@ module DistributionCreation = {
|
|||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeDistOrNumber],
|
||||
~run=(_, inputs, env) => inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env),
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="declare",
|
||||
~inputs=[Declaration.frType],
|
||||
~run=(_, inputs, _) => {
|
||||
~run=(_, inputs, _, _) => {
|
||||
inputs->getOrError(0)->E.R.bind(Declaration.fromExpressionValue)
|
||||
},
|
||||
(),
|
||||
|
|
|
@ -23,13 +23,67 @@ module Internals = {
|
|||
let reverse = (array: array<internalExpressionValue>): internalExpressionValue => IEvArray(
|
||||
Belt.Array.reverse(array),
|
||||
)
|
||||
|
||||
let map = (array: array<internalExpressionValue>, environment, eLambdaValue, reducer): result<
|
||||
ReducerInterface_InternalExpressionValue.t,
|
||||
Reducer_ErrorValue.errorValue,
|
||||
> => {
|
||||
let rMappedList = array->E.A.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->E.R.bind(_, acc => {
|
||||
let rNewElem = Reducer_Expression_Lambda.doLambdaCall(
|
||||
eLambdaValue,
|
||||
list{elem},
|
||||
environment,
|
||||
reducer,
|
||||
)
|
||||
rNewElem->E.R2.fmap(newElem => list{newElem, ...acc})
|
||||
})
|
||||
)
|
||||
rMappedList->E.R2.fmap(mappedList => mappedList->Belt.List.toArray->Wrappers.evArray)
|
||||
}
|
||||
|
||||
let reduce = (aValueArray, initialValue, aLambdaValue, environment, reducer) => {
|
||||
aValueArray->E.A.reduce(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->E.R.bind(_, acc =>
|
||||
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let reduceReverse = (aValueArray, initialValue, aLambdaValue, environment, reducer) => {
|
||||
aValueArray->Belt.Array.reduceReverse(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->Belt.Result.flatMap(acc =>
|
||||
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let filter = (aValueArray, aLambdaValue, environment, reducer) => {
|
||||
let rMappedList = aValueArray->Belt.Array.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->E.R.bind(_, acc => {
|
||||
let rNewElem = Reducer_Expression_Lambda.doLambdaCall(
|
||||
aLambdaValue,
|
||||
list{elem},
|
||||
environment,
|
||||
reducer,
|
||||
)
|
||||
rNewElem->E.R2.fmap(newElem => {
|
||||
switch newElem {
|
||||
| IEvBool(true) => list{elem, ...acc}
|
||||
| _ => acc
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
rMappedList->E.R2.fmap(mappedList => mappedList->Belt.List.toArray->Wrappers.evArray)
|
||||
}
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="make",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtArray,
|
||||
~examples=[`List.make(2, "testValue")`],
|
||||
~definitions=[
|
||||
|
@ -37,7 +91,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="make",
|
||||
~inputs=[FRTypeNumber, FRTypeAny],
|
||||
~run=(inputs, _, _) => {
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvNumber(number), value] => Internals.makeFromNumber(number, value)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -51,14 +105,14 @@ let library = [
|
|||
Function.make(
|
||||
~name="upTo",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtArray,
|
||||
~examples=[`List.upTo(1,4)`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="upTo",
|
||||
~inputs=[FRTypeNumber, FRTypeNumber],
|
||||
~run=(_, inputs, _) =>
|
||||
~run=(_, inputs, _, _) =>
|
||||
inputs
|
||||
->Prepare.ToValueTuple.twoNumbers
|
||||
->E.R2.fmap(((low, high)) => Internals.upTo(low, high)),
|
||||
|
@ -70,13 +124,13 @@ let library = [
|
|||
Function.make(
|
||||
~name="first",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`List.first([1,4,5])`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="first",
|
||||
~inputs=[FRTypeArray(FRTypeAny)],
|
||||
~run=(inputs, _, _) =>
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array)] => Internals.first(array)
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -89,13 +143,13 @@ let library = [
|
|||
Function.make(
|
||||
~name="last",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`List.last([1,4,5])`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="last",
|
||||
~inputs=[FRTypeArray(FRTypeAny)],
|
||||
~run=(inputs, _, _) =>
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array)] => Internals.last(array)
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -115,7 +169,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="reverse",
|
||||
~inputs=[FRTypeArray(FRTypeAny)],
|
||||
~run=(inputs, _, _) =>
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array)] => Internals.reverse(array)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
|
@ -125,4 +179,87 @@ let library = [
|
|||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map",
|
||||
~nameSpace,
|
||||
~output=EvtArray,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.map([1,4,5], {|x| x+1})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), IEvLambda(lambda)] =>
|
||||
Internals.map(array, env, lambda, reducer)->E.R2.errMap(_ => "Error!")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="reduce",
|
||||
~nameSpace,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.reduce([1,4,5], 2, {|acc, el| acc+el})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="reduce",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), initialValue, IEvLambda(lambda)] =>
|
||||
Internals.reduce(array, initialValue, lambda, env, reducer)->E.R2.errMap(_ => "Error!")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="reduceReverse",
|
||||
~nameSpace,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.reduceReverse([1,4,5], 2, {|acc, el| acc-el})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="reduceReverse",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), initialValue, IEvLambda(lambda)] =>
|
||||
Internals.reduceReverse(array, initialValue, lambda, env, reducer)->E.R2.errMap(_ =>
|
||||
"Error!"
|
||||
)
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="filter",
|
||||
~nameSpace,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.filter([1,4,5], {|x| x>3})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="filter",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), IEvLambda(lambda)] =>
|
||||
Internals.filter(array, lambda, env, reducer)->E.R2.errMap(_ => "Error!")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ module NumberToNumber = {
|
|||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeNumber],
|
||||
~run=(_, inputs, _) => {
|
||||
~run=(_, inputs, _, _) => {
|
||||
inputs
|
||||
->getOrError(0)
|
||||
->E.R.bind(Prepare.oneNumber)
|
||||
|
@ -25,7 +25,7 @@ module ArrayNumberDist = {
|
|||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeArray(FRTypeNumber)],
|
||||
~run=(_, inputs, _) =>
|
||||
~run=(_, inputs, _, _) =>
|
||||
Prepare.ToTypedArray.numbers(inputs)
|
||||
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
|
||||
->E.R.bind(fn),
|
||||
|
@ -36,7 +36,7 @@ module ArrayNumberDist = {
|
|||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeArray(FRTypeAny)],
|
||||
~run=(_, inputs, _) =>
|
||||
~run=(_, inputs, _, _) =>
|
||||
Prepare.ToTypedArray.numbers(inputs)
|
||||
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
|
||||
->E.R.bind(fn),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let nameSpace = "Pointset"
|
||||
let nameSpace = "PointSet"
|
||||
let requiresNamespace = true
|
||||
|
||||
let inputsTodist = (inputs: array<FunctionRegistry_Core.frValue>, makeDist) => {
|
||||
|
@ -24,12 +24,41 @@ let inputsTodist = (inputs: array<FunctionRegistry_Core.frValue>, makeDist) => {
|
|||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="fromDist",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`PointSet.fromDist(normal(5,2))`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromDist",
|
||||
~inputs=[FRTypeDist],
|
||||
~run=(_, inputs, env, _) =>
|
||||
switch inputs {
|
||||
| [FRValueDist(dist)] =>
|
||||
GenericDist.toPointSet(
|
||||
dist,
|
||||
~xyPointLength=env.xyPointLength,
|
||||
~sampleCount=env.sampleCount,
|
||||
(),
|
||||
)
|
||||
->E.R2.fmap(Wrappers.pointSet)
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="makeContinuous",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`Pointset.makeContinuous([
|
||||
`PointSet.makeContinuous([
|
||||
{x: 0, y: 0.2},
|
||||
{x: 1, y: 0.7},
|
||||
{x: 2, y: 0.8},
|
||||
|
@ -41,7 +70,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="makeContinuous",
|
||||
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||
~run=(_, inputs, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
|
||||
~run=(_, inputs, _, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
|
||||
(),
|
||||
),
|
||||
],
|
||||
|
@ -52,7 +81,7 @@ let library = [
|
|||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`Pointset.makeDiscrete([
|
||||
`PointSet.makeDiscrete([
|
||||
{x: 0, y: 0.2},
|
||||
{x: 1, y: 0.7},
|
||||
{x: 2, y: 0.8},
|
||||
|
@ -64,7 +93,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="makeDiscrete",
|
||||
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||
~run=(_, inputs, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
|
||||
~run=(_, inputs, _, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
|
||||
(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let nameSpace = "SampleSet"
|
||||
let requiresNamespace = true
|
||||
|
||||
module Internal = {
|
||||
type t = SampleSetDist.t
|
||||
let doLambdaCall = (aLambdaValue, list, environment, reducer) =>
|
||||
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, environment, reducer) {
|
||||
| Ok(IEvNumber(f)) => Ok(f)
|
||||
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
|
||||
}
|
||||
|
||||
let toType = (r): result<
|
||||
ReducerInterface_InternalExpressionValue.t,
|
||||
Reducer_ErrorValue.errorValue,
|
||||
> =>
|
||||
switch r {
|
||||
| Ok(r) => Ok(Wrappers.evDistribution(SampleSet(r)))
|
||||
| Error(r) => Error(REDistributionError(SampleSetError(r)))
|
||||
}
|
||||
|
||||
//TODO: I don't know why this seems to need at least one input
|
||||
let fromFn = (
|
||||
aLambdaValue,
|
||||
env: ReducerInterface_InternalExpressionValue.environment,
|
||||
reducer,
|
||||
) => {
|
||||
let sampleCount = env.sampleCount
|
||||
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)}, env, reducer)
|
||||
Belt_Array.makeBy(sampleCount, r => fn(r->Js.Int.toFloat))->E.A.R.firstErrorOrOpen
|
||||
}
|
||||
|
||||
let map1 = (sampleSetDist: t, aLambdaValue, env, reducer) => {
|
||||
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)}, env, reducer)
|
||||
SampleSetDist.samplesMap(~fn, sampleSetDist)->toType
|
||||
}
|
||||
|
||||
let map2 = (t1: t, t2: t, aLambdaValue, env, reducer) => {
|
||||
let fn = (a, b) => doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b)}, env, reducer)
|
||||
SampleSetDist.map2(~fn, ~t1, ~t2)->toType
|
||||
}
|
||||
|
||||
let map3 = (t1: t, t2: t, t3: t, aLambdaValue, env, reducer) => {
|
||||
let fn = (a, b, c) =>
|
||||
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)}, env, reducer)
|
||||
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
|
||||
}
|
||||
|
||||
let parseSampleSetArray = (arr: array<internalExpressionValue>): option<
|
||||
array<SampleSetDist.t>,
|
||||
> => {
|
||||
let parseSampleSet = (value: internalExpressionValue): option<SampleSetDist.t> =>
|
||||
switch value {
|
||||
| IEvDistribution(SampleSet(dist)) => Some(dist)
|
||||
| _ => None
|
||||
}
|
||||
E.A.O.openIfAllSome(E.A.fmap(parseSampleSet, arr))
|
||||
}
|
||||
|
||||
let mapN = (aValueArray: array<internalExpressionValue>, aLambdaValue, env, reducer) => {
|
||||
switch parseSampleSetArray(aValueArray) {
|
||||
| Some(t1) =>
|
||||
let fn = a =>
|
||||
doLambdaCall(
|
||||
aLambdaValue,
|
||||
list{IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))},
|
||||
env,
|
||||
reducer,
|
||||
)
|
||||
SampleSetDist.mapN(~fn, ~t1)->toType
|
||||
| None => Error(REFunctionNotFound(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="fromDist",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.fromDist(normal(5,2))`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromDist",
|
||||
~inputs=[FRTypeDist],
|
||||
~run=(_, inputs, env, _) =>
|
||||
switch inputs {
|
||||
| [FRValueDist(dist)] =>
|
||||
GenericDist.toSampleSetDist(dist, env.sampleCount)
|
||||
->E.R2.fmap(Wrappers.sampleSet)
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="fromList",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromList",
|
||||
~inputs=[FRTypeArray(FRTypeNumber)],
|
||||
~run=(_, inputs, _, _) => {
|
||||
let sampleSet =
|
||||
Prepare.ToTypedArray.numbers(inputs) |> E.R2.bind(r =>
|
||||
SampleSetDist.make(r)->E.R2.errMap(_ => "AM I HERE? WHYERE AMI??")
|
||||
)
|
||||
sampleSet->E.R2.fmap(Wrappers.sampleSet)->E.R2.fmap(Wrappers.evDistribution)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="toList",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.toList(SampleSet.fromDist(normal(5,2)))`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtArray,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="toList",
|
||||
~inputs=[FRTypeDist],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvDistribution(SampleSet(dist))] =>
|
||||
dist->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="fromFn",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.fromFn({|| sample(normal(5,2))})`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromFn",
|
||||
~inputs=[FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvLambda(lambda)] =>
|
||||
switch Internal.fromFn(lambda, env, reducer) {
|
||||
| Ok(r) => Ok(r->Wrappers.sampleSet->Wrappers.evDistribution)
|
||||
| Error(_) => Error("issue")
|
||||
}
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[`SampleSet.map(SampleSet.fromDist(normal(5,2)), {|x| x + 1})`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map",
|
||||
~inputs=[FRTypeDist, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvDistribution(SampleSet(dist)), IEvLambda(lambda)] =>
|
||||
Internal.map1(dist, lambda, env, reducer)->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map2",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`SampleSet.map2(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y| x + y})`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map2",
|
||||
~inputs=[FRTypeDist, FRTypeDist, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) => {
|
||||
switch inputs {
|
||||
| [
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvLambda(lambda),
|
||||
] =>
|
||||
Internal.map2(dist1, dist2, lambda, env, reducer)->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map3",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`SampleSet.map3(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y, z| max([x,y,z])})`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map3",
|
||||
~inputs=[FRTypeDist, FRTypeDist, FRTypeDist, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvDistribution(SampleSet(dist3)),
|
||||
IEvLambda(lambda),
|
||||
] =>
|
||||
Internal.map3(dist1, dist2, dist3, lambda, env, reducer)->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="mapN",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`SampleSet.mapN([SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2))], {|x| max(x)})`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="mapN",
|
||||
~inputs=[FRTypeArray(FRTypeDist), FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(dists), IEvLambda(lambda)] =>
|
||||
Internal.mapN(dists, lambda, env, reducer)->E.R2.errMap(_e => {
|
||||
"AHHH doesn't work"
|
||||
})
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -30,7 +30,7 @@ let library = [
|
|||
("prior", FRTypeDist),
|
||||
]),
|
||||
],
|
||||
~run=(_, inputs, env) => {
|
||||
~run=(_, inputs, env, _) => {
|
||||
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(inputs) {
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d)), FRValueDist(prior)]) =>
|
||||
runScoring(estimate, Score_Dist(d), Some(prior), env)
|
||||
|
@ -49,7 +49,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="logScore",
|
||||
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
|
||||
~run=(_, inputs, env) => {
|
||||
~run=(_, inputs, env, _) => {
|
||||
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(inputs) {
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d))]) =>
|
||||
runScoring(estimate, Score_Dist(d), None, env)
|
||||
|
@ -74,7 +74,7 @@ let library = [
|
|||
FnDefinition.make(
|
||||
~name="klDivergence",
|
||||
~inputs=[FRTypeDist, FRTypeDist],
|
||||
~run=(_, inputs, env) => {
|
||||
~run=(_, inputs, env, _) => {
|
||||
switch inputs {
|
||||
| [FRValueDist(estimate), FRValueDist(d)] =>
|
||||
runScoring(estimate, Score_Dist(d), None, env)
|
||||
|
|
|
@ -95,33 +95,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
|
||||
let doExportBindings = (bindings: nameSpace) => bindings->Bindings.toExpressionValue->Ok
|
||||
|
||||
let doKeepArray = (aValueArray, aLambdaValue) => {
|
||||
let rMappedList = aValueArray->Belt.Array.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc => {
|
||||
let rNewElem = Lambda.doLambdaCall(aLambdaValue, list{elem}, environment, reducer)
|
||||
rNewElem->Result.map(newElem =>
|
||||
switch newElem {
|
||||
| IEvBool(true) => list{elem, ...acc}
|
||||
| _ => acc
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
rMappedList->Result.map(mappedList => mappedList->Belt.List.toArray->IEvArray)
|
||||
}
|
||||
|
||||
let doMapArray = (aValueArray, aLambdaValue) => {
|
||||
let rMappedList = aValueArray->Belt.Array.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc => {
|
||||
let rNewElem = Lambda.doLambdaCall(aLambdaValue, list{elem}, environment, reducer)
|
||||
rNewElem->Result.map(newElem => list{newElem, ...acc})
|
||||
})
|
||||
)
|
||||
rMappedList->Result.map(mappedList => mappedList->Belt.List.toArray->IEvArray)
|
||||
}
|
||||
|
||||
module SampleMap = {
|
||||
type t = SampleSetDist.t
|
||||
let doLambdaCall = (aLambdaValue, list) =>
|
||||
switch Lambda.doLambdaCall(aLambdaValue, list, environment, reducer) {
|
||||
| Ok(IEvNumber(f)) => Ok(f)
|
||||
|
@ -134,22 +108,6 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
| Error(r) => Error(REDistributionError(SampleSetError(r)))
|
||||
}
|
||||
|
||||
let map1 = (sampleSetDist: t, aLambdaValue) => {
|
||||
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)})
|
||||
toType(SampleSetDist.samplesMap(~fn, sampleSetDist))
|
||||
}
|
||||
|
||||
let map2 = (t1: t, t2: t, aLambdaValue) => {
|
||||
let fn = (a, b) => doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b)})
|
||||
SampleSetDist.map2(~fn, ~t1, ~t2)->toType
|
||||
}
|
||||
|
||||
let map3 = (t1: t, t2: t, t3: t, aLambdaValue) => {
|
||||
let fn = (a, b, c) =>
|
||||
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)})
|
||||
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
|
||||
}
|
||||
|
||||
let parseSampleSetArray = (arr: array<internalExpressionValue>): option<
|
||||
array<SampleSetDist.t>,
|
||||
> => {
|
||||
|
@ -161,7 +119,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
E.A.O.openIfAllSome(E.A.fmap(parseSampleSet, arr))
|
||||
}
|
||||
|
||||
let mapN = (aValueArray: array<internalExpressionValue>, aLambdaValue) => {
|
||||
let _mapN = (aValueArray: array<internalExpressionValue>, aLambdaValue) => {
|
||||
switch parseSampleSetArray(aValueArray) {
|
||||
| Some(t1) =>
|
||||
let fn = a => doLambdaCall(aLambdaValue, list{IEvArray(E.A.fmap(x => IEvNumber(x), a))})
|
||||
|
@ -172,22 +130,6 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
}
|
||||
}
|
||||
|
||||
let doReduceArray = (aValueArray, initialValue, aLambdaValue) => {
|
||||
aValueArray->Belt.Array.reduce(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc =>
|
||||
Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let doReduceReverseArray = (aValueArray, initialValue, aLambdaValue) => {
|
||||
aValueArray->Belt.Array.reduceReverse(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc =>
|
||||
Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
switch call {
|
||||
| ("$_atIndex_$", [IEvArray(aValueArray), IEvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
|
||||
| ("$_atIndex_$", [IEvBindings(dict), IEvString(sIndex)]) => moduleAtIndex(dict, sIndex)
|
||||
|
@ -226,38 +168,6 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
doAddString(aValueString, bValueString)
|
||||
| ("inspect", [value, IEvString(label)]) => inspectLabel(value, label)
|
||||
| ("inspect", [value]) => inspect(value)
|
||||
| ("filter", [IEvArray(aValueArray), IEvLambda(aLambdaValue)]) =>
|
||||
doKeepArray(aValueArray, aLambdaValue)
|
||||
| ("map", [IEvArray(aValueArray), IEvLambda(aLambdaValue)]) =>
|
||||
doMapArray(aValueArray, aLambdaValue)
|
||||
| ("mapSamples", [IEvDistribution(SampleSet(dist)), IEvLambda(aLambdaValue)]) =>
|
||||
SampleMap.map1(dist, aLambdaValue)
|
||||
| (
|
||||
"mapSamples2",
|
||||
[
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvLambda(aLambdaValue),
|
||||
],
|
||||
) =>
|
||||
SampleMap.map2(dist1, dist2, aLambdaValue)
|
||||
| (
|
||||
"mapSamples3",
|
||||
[
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvDistribution(SampleSet(dist3)),
|
||||
IEvLambda(aLambdaValue),
|
||||
],
|
||||
) =>
|
||||
SampleMap.map3(dist1, dist2, dist3, aLambdaValue)
|
||||
| ("mapSamplesN", [IEvArray(aValueArray), IEvLambda(aLambdaValue)]) =>
|
||||
SampleMap.mapN(aValueArray, aLambdaValue)
|
||||
| ("reduce", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) =>
|
||||
doReduceArray(aValueArray, initialValue, aLambdaValue)
|
||||
| ("reduceReverse", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) =>
|
||||
doReduceReverseArray(aValueArray, initialValue, aLambdaValue)
|
||||
| ("reverse", [IEvArray(aValueArray)]) => aValueArray->Belt.Array.reverse->IEvArray->Ok
|
||||
| (_, [IEvBool(_)])
|
||||
| (_, [IEvNumber(_)])
|
||||
| (_, [IEvString(_)])
|
||||
|
|
|
@ -82,3 +82,5 @@ let eIdentifier = (name: string): expression =>
|
|||
|
||||
let eTypeIdentifier = (name: string): expression =>
|
||||
name->BInternalExpressionValue.IEvTypeIdentifier->BExpressionT.EValue
|
||||
|
||||
let eVoid: expression = BInternalExpressionValue.IEvVoid->BExpressionT.EValue
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
}}
|
||||
|
||||
start
|
||||
// = _nl start:typeExpression _nl finalComment? {return start}
|
||||
= _nl start:outerBlock _nl finalComment? {return start}
|
||||
|
||||
zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda
|
||||
|
@ -39,13 +38,19 @@ statement
|
|||
= letStatement
|
||||
/ defunStatement
|
||||
/ typeStatement
|
||||
/ voidStatement
|
||||
|
||||
voidStatement
|
||||
= "call" _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||
{ var variable = h.nodeIdentifier("_", location());
|
||||
return h.nodeLetStatement(variable, value); }
|
||||
|
||||
letStatement
|
||||
= variable:identifier _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||
= variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||
{ return h.nodeLetStatement(variable, value) }
|
||||
|
||||
defunStatement
|
||||
= variable:identifier '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
|
||||
= variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
|
||||
{ var value = h.nodeLambda(args, body)
|
||||
return h.nodeLetStatement(variable, value) }
|
||||
|
||||
|
@ -54,6 +59,8 @@ defunStatement
|
|||
array_parameters
|
||||
= head:dollarIdentifier tail:(_ ',' _nl @dollarIdentifier)*
|
||||
{ return [head, ...tail]; }
|
||||
/ ""
|
||||
{ return [h.nodeIdentifier("_", location())]; }
|
||||
|
||||
expression = ifthenelse / ternary / logicalAdditive
|
||||
|
||||
|
@ -137,11 +144,11 @@ chainFunctionCall
|
|||
}, head)}
|
||||
|
||||
chainedFunction
|
||||
= fn:dollarIdentifier '(' _nl args:array_functionArguments _nl ')'
|
||||
= fn:variable '(' _nl args:array_functionArguments _nl ')'
|
||||
{ return {fnName: fn.value, args: args}}
|
||||
/ fn:dollarIdentifier '(' _nl ')'
|
||||
/ fn:variable '(' _nl ')'
|
||||
{ return {fnName: fn.value, args: []}}
|
||||
/ fn:dollarIdentifier
|
||||
/ fn:variable
|
||||
{ return {fnName: fn.value, args: []}}
|
||||
|
||||
// end of binary operators
|
||||
|
@ -172,6 +179,8 @@ collectionElement
|
|||
array_functionArguments
|
||||
= head:expression tail:(_ ',' _nl @expression)*
|
||||
{ return [head, ...tail]; }
|
||||
/ ""
|
||||
{return [h.nodeVoid()];}
|
||||
|
||||
atom
|
||||
= '(' _nl expression:expression _nl ')' {return expression}
|
||||
|
@ -183,8 +192,13 @@ basicLiteral
|
|||
= string
|
||||
/ number
|
||||
/ boolean
|
||||
/ dollarIdentifierWithModule
|
||||
/ dollarIdentifier
|
||||
/ variable
|
||||
/ voidLiteral
|
||||
|
||||
voidLiteral 'void'
|
||||
= "()" {return h.nodeVoid();}
|
||||
|
||||
variable = dollarIdentifierWithModule / dollarIdentifier
|
||||
|
||||
dollarIdentifierWithModule 'identifier'
|
||||
= head:$moduleIdentifier
|
||||
|
@ -195,7 +209,7 @@ dollarIdentifierWithModule 'identifier'
|
|||
modifiers.unshift(head)
|
||||
modifiers.push(final)
|
||||
let modifiedIdentifier = modifiers.join('.')
|
||||
return h.nodeIdentifier(modifiedIdentifier)
|
||||
return h.nodeIdentifier(modifiedIdentifier, location())
|
||||
}
|
||||
|
||||
identifier 'identifier'
|
||||
|
@ -263,9 +277,13 @@ arrayConstructor 'array'
|
|||
{ return [head, ...tail]; }
|
||||
|
||||
recordConstructor 'record'
|
||||
= '{' _nl args:array_recordArguments _nl '}'
|
||||
= '{' _nl args:array_recordArguments _nl end_of_record
|
||||
{ return h.constructRecord(args); }
|
||||
|
||||
end_of_record
|
||||
= '}'
|
||||
/ ',' _nl '}'
|
||||
|
||||
array_recordArguments
|
||||
= head:keyValuePair tail:(_ ',' _nl @keyValuePair)*
|
||||
{ return [head, ...tail]; }
|
||||
|
|
|
@ -34,6 +34,7 @@ type nodeModuleIdentifier = {...node, "value": string}
|
|||
type nodeString = {...node, "value": string}
|
||||
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
|
||||
type nodeTypeIdentifier = {...node, "value": string}
|
||||
type nodeVoid = node
|
||||
|
||||
type peggyNode =
|
||||
| PgNodeBlock(nodeBlock)
|
||||
|
@ -50,6 +51,7 @@ type peggyNode =
|
|||
| PgNodeString(nodeString)
|
||||
| PgNodeTernary(nodeTernary)
|
||||
| PgNodeTypeIdentifier(nodeTypeIdentifier)
|
||||
| PgNodeVoid(nodeVoid)
|
||||
|
||||
external castNodeBlock: node => nodeBlock = "%identity"
|
||||
external castNodeBoolean: node => nodeBoolean = "%identity"
|
||||
|
@ -65,6 +67,7 @@ external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity"
|
|||
external castNodeString: node => nodeString = "%identity"
|
||||
external castNodeTernary: node => nodeTernary = "%identity"
|
||||
external castNodeTypeIdentifier: node => nodeTypeIdentifier = "%identity"
|
||||
external castNodeVoid: node => nodeVoid = "%identity"
|
||||
|
||||
exception UnsupportedPeggyNodeType(string) // This should never happen; programming error
|
||||
let castNodeType = (node: node) =>
|
||||
|
@ -83,6 +86,7 @@ let castNodeType = (node: node) =>
|
|||
| "String" => node->castNodeString->PgNodeString
|
||||
| "Ternary" => node->castNodeTernary->PgNodeTernary
|
||||
| "TypeIdentifier" => node->castNodeTypeIdentifier->PgNodeTypeIdentifier
|
||||
| "Void" => node->castNodeVoid->PgNodeVoid
|
||||
| _ => raise(UnsupportedPeggyNodeType(node["type"]))
|
||||
}
|
||||
|
||||
|
@ -116,6 +120,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
|
|||
" " ++
|
||||
toString(node["falseExpression"]) ++ ")"
|
||||
| PgNodeTypeIdentifier(node) => `#${node["value"]}`
|
||||
| PgNodeVoid(_node) => "()"
|
||||
}
|
||||
}
|
||||
and toString = (node: node): string => node->castNodeType->pgToString
|
||||
|
|
|
@ -48,5 +48,6 @@ let rec fromNode = (node: Parse.node): expression => {
|
|||
)
|
||||
| PgNodeTypeIdentifier(nodeTypeIdentifier) =>
|
||||
ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"])
|
||||
| PgNodeVoid(_) => ExpressionBuilder.eVoid
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,3 +213,7 @@ export function nodeTernary(
|
|||
export function nodeTypeIdentifier(typeValue: string) {
|
||||
return { type: "TypeIdentifier", value: typeValue };
|
||||
}
|
||||
|
||||
export function nodeVoid() {
|
||||
return { type: "Void" };
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ module ErrorValue = Reducer_ErrorValue
|
|||
@genType.opaque
|
||||
type internalCode = Object
|
||||
|
||||
@genType.opaque
|
||||
type hiddenNameSpace = Object
|
||||
|
||||
@genType
|
||||
type rec externalExpressionValue =
|
||||
| EvArray(array<externalExpressionValue>)
|
||||
|
@ -25,15 +28,18 @@ type rec externalExpressionValue =
|
|||
| EvTypeIdentifier(string)
|
||||
| EvModule(record)
|
||||
| EvType(record)
|
||||
| EvVoid
|
||||
and record = Js.Dict.t<externalExpressionValue>
|
||||
and externalBindings = record
|
||||
and lambdaValue = {
|
||||
parameters: array<string>,
|
||||
context: externalBindings,
|
||||
context: hiddenNameSpace,
|
||||
body: internalCode,
|
||||
}
|
||||
and lambdaDeclaration = Declaration.declaration<lambdaValue>
|
||||
|
||||
@genType
|
||||
type externalBindings = record
|
||||
|
||||
@genType
|
||||
type t = externalExpressionValue
|
||||
|
||||
|
@ -63,6 +69,7 @@ let rec toString = aValue =>
|
|||
| EvTimeDuration(t) => DateTime.Duration.toString(t)
|
||||
| EvType(t) => `type${t->toStringRecord}`
|
||||
| EvTypeIdentifier(id) => `#${id}`
|
||||
| EvVoid => `()`
|
||||
}
|
||||
and toStringRecord = aRecord => {
|
||||
let pairs =
|
||||
|
|
|
@ -4,17 +4,19 @@ type internalExpressionValue = InternalExpressionValue.t
|
|||
/*
|
||||
Map external calls of Reducer
|
||||
*/
|
||||
let dispatch = (call: InternalExpressionValue.functionCall, environment, reducer, chain): result<
|
||||
internalExpressionValue,
|
||||
'e,
|
||||
> => {
|
||||
let dispatch = (
|
||||
call: InternalExpressionValue.functionCall,
|
||||
environment,
|
||||
reducer: Reducer_Expression_T.reducerFn,
|
||||
chain,
|
||||
): result<internalExpressionValue, 'e> => {
|
||||
E.A.O.firstSomeFn([
|
||||
() => ReducerInterface_GenericDistribution.dispatch(call, environment),
|
||||
() => ReducerInterface_Date.dispatch(call, environment),
|
||||
() => ReducerInterface_Duration.dispatch(call, environment),
|
||||
() => ReducerInterface_Number.dispatch(call, environment),
|
||||
() => FunctionRegistry_Library.dispatch(call, environment),
|
||||
])->E.O2.default(chain(call, environment, reducer))
|
||||
() => FunctionRegistry_Library.dispatch(call, environment, reducer),
|
||||
])->E.O2.defaultFn(() => chain(call, environment, reducer))
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -233,19 +233,6 @@ let dispatchToGenericOutput = (call: IEV.functionCall, env: GenericDist.env): op
|
|||
| ("inv", [IEvDistribution(dist), IEvNumber(float)]) => Helpers.toFloatFn(#Inv(float), dist, ~env)
|
||||
| ("quantile", [IEvDistribution(dist), IEvNumber(float)]) =>
|
||||
Helpers.toFloatFn(#Inv(float), dist, ~env)
|
||||
| ("toSampleSet", [IEvDistribution(dist), IEvNumber(float)]) =>
|
||||
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist, ~env)
|
||||
| ("toSampleSet", [IEvDistribution(dist)]) =>
|
||||
Helpers.toDistFn(ToSampleSet(env.sampleCount), dist, ~env)
|
||||
| ("toList", [IEvDistribution(SampleSet(dist))]) => Some(FloatArray(SampleSetDist.T.get(dist)))
|
||||
| ("fromSamples", [IEvArray(inputArray)]) => {
|
||||
let _wrapInputErrors = x => SampleSetDist.NonNumericInput(x)
|
||||
let parsedArray = Helpers.parseNumberArray(inputArray)->E.R2.errMap(_wrapInputErrors)
|
||||
switch parsedArray {
|
||||
| Ok(array) => DistributionOperation.run(FromSamples(array), ~env)
|
||||
| Error(e) => GenDistError(SampleSetError(e))
|
||||
}->Some
|
||||
}
|
||||
| ("inspect", [IEvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist, ~env)
|
||||
| ("truncateLeft", [IEvDistribution(dist), IEvNumber(float)]) =>
|
||||
Helpers.toDistFn(Truncate(Some(float), None), dist, ~env)
|
||||
|
|
|
@ -23,6 +23,7 @@ type rec t =
|
|||
| IEvTimeDuration(float)
|
||||
| IEvType(map)
|
||||
| IEvTypeIdentifier(string)
|
||||
| IEvVoid
|
||||
and map = Belt.Map.String.t<t>
|
||||
and nameSpace = NameSpace(Belt.Map.String.t<t>)
|
||||
and lambdaValue = {
|
||||
|
@ -36,6 +37,29 @@ type internalExpressionValue = t
|
|||
|
||||
type functionCall = (string, array<t>)
|
||||
|
||||
module Internal = {
|
||||
module NameSpace = {
|
||||
external castNameSpaceToHidden: nameSpace => ExternalExpressionValue.hiddenNameSpace =
|
||||
"%identity"
|
||||
external castHiddenToNameSpace: ExternalExpressionValue.hiddenNameSpace => nameSpace =
|
||||
"%identity"
|
||||
}
|
||||
module Lambda = {
|
||||
let toInternal = (v: ExternalExpressionValue.lambdaValue): lambdaValue => {
|
||||
let p = v.parameters
|
||||
let c = v.context->NameSpace.castHiddenToNameSpace
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
and toExternal = (v: lambdaValue): ExternalExpressionValue.lambdaValue => {
|
||||
let p = v.parameters
|
||||
let c = v.context->NameSpace.castNameSpaceToHidden
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rec toString = aValue =>
|
||||
switch aValue {
|
||||
| IEvArray(anArray) => {
|
||||
|
@ -60,6 +84,7 @@ let rec toString = aValue =>
|
|||
| IEvType(aMap) => aMap->toStringMap
|
||||
| IEvTimeDuration(t) => DateTime.Duration.toString(t)
|
||||
| IEvTypeIdentifier(id) => `#${id}`
|
||||
| IEvVoid => `()`
|
||||
}
|
||||
and toStringMap = aMap => {
|
||||
let pairs =
|
||||
|
@ -92,6 +117,7 @@ let toStringWithType = aValue =>
|
|||
| IEvTimeDuration(_) => `Date::${toString(aValue)}`
|
||||
| IEvType(_) => `Type::${toString(aValue)}`
|
||||
| IEvTypeIdentifier(_) => `TypeIdentifier::${toString(aValue)}`
|
||||
| IEvVoid => `Void`
|
||||
}
|
||||
|
||||
let argsToString = (args: array<t>): string => {
|
||||
|
@ -135,6 +161,7 @@ type internalExpressionValueType =
|
|||
| EvtTimeDuration
|
||||
| EvtType
|
||||
| EvtTypeIdentifier
|
||||
| EvtVoid
|
||||
|
||||
type functionCallSignature = CallSignature(string, array<internalExpressionValueType>)
|
||||
type functionDefinitionSignature =
|
||||
|
@ -158,6 +185,7 @@ let valueToValueType = value =>
|
|||
| IEvTimeDuration(_) => EvtTimeDuration
|
||||
| IEvType(_) => EvtType
|
||||
| IEvTypeIdentifier(_) => EvtTypeIdentifier
|
||||
| IEvVoid => EvtVoid
|
||||
}
|
||||
|
||||
let externalValueToValueType = (value: ExternalExpressionValue.t) =>
|
||||
|
@ -178,6 +206,7 @@ let externalValueToValueType = (value: ExternalExpressionValue.t) =>
|
|||
| EvTimeDuration(_) => EvtTimeDuration
|
||||
| EvType(_) => EvtType
|
||||
| EvTypeIdentifier(_) => EvtTypeIdentifier
|
||||
| EvVoid => EvtVoid
|
||||
}
|
||||
|
||||
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
|
||||
|
@ -203,6 +232,7 @@ let valueTypeToString = (valueType: internalExpressionValueType): string =>
|
|||
| EvtTimeDuration => `Duration`
|
||||
| EvtType => `Type`
|
||||
| EvtTypeIdentifier => `TypeIdentifier`
|
||||
| EvtVoid => `Void`
|
||||
}
|
||||
|
||||
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
|
||||
|
@ -232,16 +262,12 @@ let rec toExternal = (iev: t): ExternalExpressionValue.t => {
|
|||
| IEvType(v) => v->mapToExternal->EvType
|
||||
| IEvTypeIdentifier(v) => EvTypeIdentifier(v)
|
||||
| IEvBindings(v) => v->nameSpaceToTypeScriptBindings->EvModule
|
||||
| IEvVoid => EvVoid
|
||||
}
|
||||
}
|
||||
and mapToExternal = v =>
|
||||
v->Belt.Map.String.map(e => toExternal(e))->Belt.Map.String.toArray->Js.Dict.fromArray
|
||||
and lambdaValueToExternal = v => {
|
||||
let p = v.parameters
|
||||
let c = v.context->nameSpaceToTypeScriptBindings
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
and lambdaValueToExternal = Internal.Lambda.toExternal
|
||||
and nameSpaceToTypeScriptBindings = (
|
||||
nameSpace: nameSpace,
|
||||
): ReducerInterface_ExternalExpressionValue.externalBindings => {
|
||||
|
@ -271,16 +297,12 @@ let rec toInternal = (ev: ExternalExpressionValue.t): t => {
|
|||
| EvTimeDuration(v) => IEvTimeDuration(v)
|
||||
| EvType(v) => v->recordToInternal->IEvType
|
||||
| EvTypeIdentifier(v) => IEvTypeIdentifier(v)
|
||||
| EvVoid => IEvVoid
|
||||
}
|
||||
}
|
||||
and recordToInternal = v =>
|
||||
v->Js.Dict.entries->Belt.Map.String.fromArray->Belt.Map.String.map(e => toInternal(e))
|
||||
and lambdaValueToInternal = v => {
|
||||
let p = v.parameters
|
||||
let c = v.context->nameSpaceFromTypeScriptBindings
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
and lambdaValueToInternal = Internal.Lambda.toInternal
|
||||
and nameSpaceFromTypeScriptBindings = (
|
||||
r: ReducerInterface_ExternalExpressionValue.externalBindings,
|
||||
): nameSpace =>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module Bindings = Reducer_Bindings
|
||||
|
||||
let internalStdLib = Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings
|
||||
let internalStdLib =
|
||||
Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings->SquiggleLibrary_Versions.makeBindings
|
||||
|
||||
@genType
|
||||
let externalStdLib = internalStdLib->Bindings.toTypeScriptBindings
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
module Bindings = Reducer_Bindings
|
||||
|
||||
let bindings: Bindings.t =
|
||||
[
|
||||
("System.version", ReducerInterface_InternalExpressionValue.IEvString("0.3.0")),
|
||||
]->Bindings.fromArray
|
||||
|
||||
let makeBindings = (previousBindings: Bindings.t): Bindings.t =>
|
||||
previousBindings->Bindings.merge(bindings)
|
|
@ -82,6 +82,11 @@ module O = {
|
|||
| None => d
|
||||
| Some(a) => a
|
||||
}
|
||||
let defaultFn = (d, o) =>
|
||||
switch o {
|
||||
| None => d()
|
||||
| Some(a) => a
|
||||
}
|
||||
let isSome = o =>
|
||||
switch o {
|
||||
| Some(_) => true
|
||||
|
@ -158,6 +163,7 @@ module O = {
|
|||
|
||||
module O2 = {
|
||||
let default = (a, b) => O.default(b, a)
|
||||
let defaultFn = (a, b) => O.defaultFn(b, a)
|
||||
let toExn = (a, b) => O.toExn(b, a)
|
||||
let fmap = (a, b) => O.fmap(b, a)
|
||||
let toResult = (a, b) => O.toResult(b, a)
|
||||
|
@ -546,6 +552,7 @@ module A = {
|
|||
let slice = Belt.Array.slice
|
||||
let init = Array.init
|
||||
let reduce = Belt.Array.reduce
|
||||
let reduceReverse = Belt.Array.reduceReverse
|
||||
let reducei = Belt.Array.reduceWithIndex
|
||||
let some = Belt.Array.some
|
||||
let isEmpty = r => length(r) < 1
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
React.createElement(squiggle_components.SquigglePlayground, {
|
||||
code: text,
|
||||
showEditor: false,
|
||||
showTypes: Boolean(showSettings.showTypes),
|
||||
showControls: Boolean(showSettings.showControls),
|
||||
showSummary: Boolean(showSettings.showSummary),
|
||||
})
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"displayName": "Squiggle",
|
||||
"description": "Squiggle language support",
|
||||
"license": "MIT",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.1",
|
||||
"publisher": "QURI",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"icon": "media/vendor/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.68.0"
|
||||
"vscode": "^1.69.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages",
|
||||
|
@ -105,16 +105,6 @@
|
|||
"configuration": {
|
||||
"title": "Squiggle",
|
||||
"properties": {
|
||||
"squiggle.playground.showTypes": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to show the types of outputs in the playground"
|
||||
},
|
||||
"squiggle.playground.showControls": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to show the log scale controls in the playground"
|
||||
},
|
||||
"squiggle.playground.showSummary": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
@ -127,7 +117,7 @@
|
|||
"vscode:prepublish": "yarn run compile",
|
||||
"compile:tsc": "tsc -b",
|
||||
"compile:grammar": "js-yaml syntaxes/squiggle.tmLanguage.yaml >syntaxes/squiggle.tmLanguage.json",
|
||||
"compile:vendor": "(cd ../squiggle-lang && yarn run build) && (cd ../components && yarn run bundle && yarn run build:css) && mkdir -p media/vendor && cp ../components/dist/bundle.js media/vendor/components.js && cp ../components/dist/main.css media/vendor/components.css && cp ../../node_modules/react/umd/react.production.min.js media/vendor/react.js && cp ../../node_modules/react-dom/umd/react-dom.production.min.js media/vendor/react-dom.js && cp ../website/static/img/quri-logo.png media/vendor/icon.png",
|
||||
"compile:vendor": "(cd ../squiggle-lang && yarn run build) && (cd ../components && yarn run bundle && yarn run build:css) && mkdir -p media/vendor && cp ../components/dist/bundle.js media/vendor/components.js && cp ../components/dist/main.css media/vendor/components.css && cp ../../node_modules/react/umd/react.production.min.js media/vendor/react.js && cp ../../node_modules/react-dom/umd/react-dom.production.min.js media/vendor/react-dom.js && cp ../website/static/img/squiggle-logo.png media/vendor/icon.png",
|
||||
"compile": "yarn run compile:vendor && yarn run compile:grammar && yarn run compile:tsc",
|
||||
"watch": "tsc -b -watch",
|
||||
"pretest": "yarn run compile && yarn run lint",
|
||||
|
@ -138,10 +128,10 @@
|
|||
"devDependencies": {
|
||||
"@types/glob": "^7.2.0",
|
||||
"@types/node": "18.x",
|
||||
"@types/vscode": "^1.69.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"eslint": "^8.20.0",
|
||||
"@types/vscode": "^1.70.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.32.0",
|
||||
"@typescript-eslint/parser": "^5.32.0",
|
||||
"eslint": "^8.21.0",
|
||||
"glob": "^8.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"typescript": "^4.7.4",
|
||||
|
|
8
packages/website/CHANGELOG.md
Normal file
8
packages/website/CHANGELOG.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
### [0.2.1](https://www.github.com/quantified-uncertainty/squiggle/compare/squiggle-website-v0.2.0...squiggle-website-v0.2.1) (2022-08-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- added NextJS starter to `Integrations.md` ([7ea9522](https://www.github.com/quantified-uncertainty/squiggle/commit/7ea95225b2fa3bd638b75a23bfd2d55ea1b7d595))
|
||||
- so the PR it opens is proper this time ([fd411c4](https://www.github.com/quantified-uncertainty/squiggle/commit/fd411c49b9013ba215ed305ae6ed66850592433b))
|
|
@ -5,16 +5,14 @@ title: Date
|
|||
|
||||
Squiggle date types are a very simple implementation on [Javascript's Date type](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date). It's mainly here for early experimentation. There are more relevant functions for the [Duration](/docs/Api/Duration) type.
|
||||
|
||||
### makeFromYear
|
||||
|
||||
(Now `makeDateFromYear`)
|
||||
### fromYear
|
||||
|
||||
```
|
||||
Date.makeFromYear: (number) => date
|
||||
Date.fromYear: (number) => date
|
||||
```
|
||||
|
||||
```js
|
||||
makeFromYear(2022.32);
|
||||
Date.fromYear(2022.32);
|
||||
```
|
||||
|
||||
### toString
|
||||
|
@ -30,7 +28,7 @@ add: (date, duration) => date
|
|||
```
|
||||
|
||||
```js
|
||||
makeFromYear(2022.32) + years(5);
|
||||
Date.fromYear(2022.32) + years(5);
|
||||
```
|
||||
|
||||
### subtract
|
||||
|
@ -41,6 +39,6 @@ subtract: (date, duration) => date
|
|||
```
|
||||
|
||||
```js
|
||||
makeFromYear(2040) - makeFromYear(2020); // 20 years
|
||||
makeFromYear(2040) - years(20); // 2020
|
||||
Date.fromYear(2040) - Date.fromYear(2020); // 20 years
|
||||
Date.fromYear(2040) - years(20); // 2020
|
||||
```
|
||||
|
|
|
@ -290,38 +290,6 @@ quantile: (distribution, number) => number
|
|||
quantile(normal(5, 2), 0.5);
|
||||
```
|
||||
|
||||
### toPointSet
|
||||
|
||||
**TODO: Will soon be called "PointSet.make"**
|
||||
|
||||
Converts a distribution to the pointSet format.
|
||||
|
||||
```
|
||||
toPointSet: (distribution) => pointSetDistribution
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```javascript
|
||||
toPointSet(normal(5, 2));
|
||||
```
|
||||
|
||||
### toSampleSet
|
||||
|
||||
**TODO: Will soon be called "SampleSet.make"**
|
||||
|
||||
Converts a distribution to the sampleSet format, with n samples.
|
||||
|
||||
```
|
||||
toSampleSet: (distribution, number) => sampleSetDistribution
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```javascript
|
||||
toSampleSet(normal(5, 2), 1000);
|
||||
```
|
||||
|
||||
### truncateLeft
|
||||
|
||||
Truncates the left side of a distribution. Returns either a pointSet distribution or a symbolic distribution.
|
||||
|
@ -364,6 +332,26 @@ klDivergence: (distribution, distribution) => number
|
|||
klDivergence(normal(5, 2), normal(5, 4)); // returns 0.57
|
||||
```
|
||||
|
||||
### logScore
|
||||
|
||||
A log loss score. Often that often acts as a [scoring rule](https://en.wikipedia.org/wiki/Scoring_rule). Useful when evaluating the accuracy of a forecast.
|
||||
|
||||
Note that it is fairly slow.
|
||||
|
||||
```
|
||||
Dist.logScore: ({estimate: distribution, ?prior: distribution, answer: distribution|number}) => number
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```javascript
|
||||
Dist.logScore({
|
||||
estimate: normal(5, 2),
|
||||
answer: normal(4.5, 1.2),
|
||||
prior: normal(6, 4),
|
||||
}); // returns -0.597.57
|
||||
```
|
||||
|
||||
## Display
|
||||
|
||||
### toString
|
||||
|
@ -392,20 +380,6 @@ sparkline: (distribution, n = 20) => string
|
|||
toSparkline(truncateLeft(normal(5, 2), 3), 20); // produces ▁▇█████▇▅▄▃▂▂▁▁▁▁▁▁▁
|
||||
```
|
||||
|
||||
### inspect
|
||||
|
||||
Prints the value of the distribution to the Javascript console, then returns the distribution. Useful for debugging.
|
||||
|
||||
```
|
||||
inspect: (distribution) => distribution
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```javascript
|
||||
inspect(normal(5, 2)); // logs "normal(5, 2)" to the javascript console and returns the distribution.
|
||||
```
|
||||
|
||||
## Normalization
|
||||
|
||||
There are some situations where computation will return unnormalized distributions. This means that their cumulative sums are not equal to 1.0. Unnormalized distributions are not valid for many relevant functions; for example, klDivergence and scoring.
|
||||
|
@ -414,7 +388,7 @@ The only functions that do not return normalized distributions are the pointwise
|
|||
|
||||
### normalize
|
||||
|
||||
Normalize a distribution. This means scaling it appropriately so that it's cumulative sum is equal to 1.
|
||||
Normalize a distribution. This means scaling it appropriately so that it's cumulative sum is equal to 1. This only impacts Pointset distributions, because those are the only ones that can be non-normlized.
|
||||
|
||||
```
|
||||
normalize: (distribution) => distribution
|
||||
|
@ -606,75 +580,3 @@ dotPow: (distributionLike, distributionLike) => distribution
|
|||
```
|
||||
dotExp: (distributionLike, distributionLike) => distribution
|
||||
```
|
||||
|
||||
## Scale Arithmetic Operations
|
||||
|
||||
<Admonition type="caution" title="Likely to change">
|
||||
<p>
|
||||
We're planning on removing scale operations in favor of more general
|
||||
functions soon.
|
||||
</p>
|
||||
</Admonition>
|
||||
|
||||
Scale operations are similar to pointwise operations, but operate on a constant y-value instead of y-values coming from a distribution. You can think about this as scaling a distribution vertically by a constant.
|
||||
|
||||
The following items would be equivalent.
|
||||
|
||||
```js
|
||||
scalePow(normal(5,2), 2)
|
||||
mapY(normal(5,2), {|y| y ^ 2}) // Not yet available
|
||||
```
|
||||
|
||||
### scaleMultiply
|
||||
|
||||
```
|
||||
scaleMultiply: (distributionLike, number) => distribution
|
||||
```
|
||||
|
||||
### scalePow
|
||||
|
||||
```
|
||||
scalePow: (distributionLike, number) => distribution
|
||||
```
|
||||
|
||||
### scaleExp
|
||||
|
||||
```
|
||||
scaleExp: (distributionLike, number) => distribution
|
||||
```
|
||||
|
||||
### scaleLog
|
||||
|
||||
```
|
||||
scaleLog: (distributionLike, number) => distribution
|
||||
```
|
||||
|
||||
### scaleLog10
|
||||
|
||||
```
|
||||
scaleLog10: (distributionLike, number) => distribution
|
||||
```
|
||||
|
||||
## Special
|
||||
|
||||
### Declaration (Continuous Functions)
|
||||
|
||||
Adds metadata to a function of the input ranges. Works now for numeric and date inputs. This is useful when making formal predictions. It allows you to limit the domain that your prediction will be used and scored within.
|
||||
|
||||
Declarations are currently experimental and will likely be removed or changed in the future.
|
||||
|
||||
```
|
||||
declareFn: (dict<{fn: lambda, inputs: array<dict<{min: number, max: number}>>}>) => declaration
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```javascript
|
||||
declareFn({
|
||||
fn: {|a,b| a },
|
||||
inputs: [
|
||||
{min: 0, max: 100},
|
||||
{min: 30, max: 50}
|
||||
]
|
||||
})
|
||||
```
|
|
@ -3,26 +3,20 @@ sidebar_position: 4
|
|||
title: Point Set Distribution
|
||||
---
|
||||
|
||||
:::danger
|
||||
These functions aren't yet implemented with these specific names. This should be changed soon
|
||||
:::
|
||||
|
||||
Point set distributions are one of the three distribution formats. They are stored as a list of x-y coordinates representing both discrete and continuous distributions.
|
||||
|
||||
One complication is that it's possible to represent invalid probability distributions in the point set format. For example, you can represent shapes with negative values, or shapes that are not normalized.
|
||||
|
||||
### make
|
||||
### fromDist
|
||||
|
||||
Converts the distribution in question into a point set distribution. If the distribution is symbolic, then it does this by taking the quantiles. If the distribution is a sample set, then it uses a version of kernel density estimation to approximate the point set format. One complication of this latter process is that if there is a high proportion of overlapping samples (samples that are exactly the same as each other), it will convert these samples into discrete point masses. Eventually we'd like to add further methods to help adjust this process.
|
||||
|
||||
```
|
||||
PointSet.make: (distribution) => pointSetDist
|
||||
PointSet.fromDist: (distribution) => pointSetDist
|
||||
```
|
||||
|
||||
### makeContinuous
|
||||
|
||||
**TODO: Now called "toContinuousPointSet"**
|
||||
|
||||
Converts a set of x-y coordinates directly into a continuous distribution.
|
||||
|
||||
```
|
||||
|
@ -40,10 +34,6 @@ PointSet.makeContinuous([
|
|||
|
||||
### makeDiscrete
|
||||
|
||||
**TODO: Now called "toDiscretePointSet"**
|
||||
|
||||
Converts a set of x-y coordinates directly into a discrete distribution.
|
||||
|
||||
```
|
||||
PointSet.makeDiscrete: (list<{x: number, y: number}>) => pointSetDist
|
||||
```
|
||||
|
|
|
@ -3,22 +3,42 @@ sidebar_position: 5
|
|||
title: Sample Set Distribution
|
||||
---
|
||||
|
||||
:::danger
|
||||
These functions aren't yet implemented with these specific names. This should be added soon.
|
||||
:::
|
||||
|
||||
Sample set distributions are one of the three distribution formats. Internally, they are stored as a list of numbers. It's useful to distinguish point set distributions from arbitrary lists of numbers to make it clear which functions are applicable.
|
||||
|
||||
Monte Carlo calculations typically result in sample set distributions.
|
||||
|
||||
All regular distribution function work on sample set distributions. In addition, there are several functions that only work on sample set distributions.
|
||||
|
||||
### make
|
||||
### fromDist
|
||||
|
||||
```
|
||||
SampleSet.make: (distribution) => sampleSet
|
||||
SampleSet.make: (list<number>) => sampleSet
|
||||
SampleSet.make: (() => number) => sampleSet // not yet implemented
|
||||
SampleSet.fromDist: (list<number>) => sampleSet
|
||||
```
|
||||
|
||||
### fromList
|
||||
|
||||
```
|
||||
SampleSet.fromList: (list<number>) => sampleSet
|
||||
```
|
||||
|
||||
### fromFn
|
||||
|
||||
```
|
||||
SampleSet.fromFn: ((float) => number) => sampleSet
|
||||
```
|
||||
|
||||
### toList
|
||||
|
||||
```
|
||||
SampleSet.toList: (sampleSet) => list<number>
|
||||
```
|
||||
|
||||
Gets the internal samples of a sampleSet distribution. This is separate from the sampleN() function, which would shuffle the samples. toList() maintains order and length.
|
||||
|
||||
**Examples**
|
||||
|
||||
```
|
||||
toList(SampleSet.fromDist(normal(5,2)))
|
||||
```
|
||||
|
||||
### map
|
||||
|
@ -39,16 +59,8 @@ SampleSet.map2: (sampleSet, sampleSet, ((number, number) => number)) => sampleSe
|
|||
SampleSet.map3: (sampleSet, sampleSet, sampleSet, ((number, number, number) => number)) => sampleSet
|
||||
```
|
||||
|
||||
### toList
|
||||
### mapN
|
||||
|
||||
```
|
||||
SampleSet.toList: (sampleSet) => list<number>
|
||||
```
|
||||
|
||||
Gets the internal samples of a sampleSet distribution. This is separate from the sampleN() function, which would shuffle the samples. toList() maintains order and length.
|
||||
|
||||
**Examples**
|
||||
|
||||
```
|
||||
toList(toSampleSet(normal(5,2)))
|
||||
SampleSet.mapN: (list<sampleSet>, (list<sampleSet> => number)) => sampleSet
|
||||
```
|
27
packages/website/docs/Api/Function.md
Normal file
27
packages/website/docs/Api/Function.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
sidebar_position: 6
|
||||
title: Function
|
||||
---
|
||||
|
||||
## declare (experimental)
|
||||
|
||||
Adds metadata to a function of the input ranges. Works now for numeric and date inputs. This is useful when making formal predictions. It allows you to limit the domain that your prediction will be used and scored within.
|
||||
|
||||
The one function that declarations currently have is that they impact plotting. If you `declare` a single-variable function within a specific range, this specific range will be plotted.
|
||||
|
||||
Declarations are currently experimental and will likely be removed or changed in the future.
|
||||
|
||||
```
|
||||
Function.declare: (dict<{fn: lambda, inputs: array<dict<{min: number, max: number}>>}>) => declaration
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```javascript
|
||||
Function.declare({
|
||||
fn: {|a| a+10 },
|
||||
inputs: [
|
||||
{min: 30, max: 100}
|
||||
]
|
||||
})
|
||||
```
|
|
@ -11,8 +11,6 @@ myList = [3, normal(5, 2), "random"];
|
|||
|
||||
### make
|
||||
|
||||
**Note: currently just called `makeList`, without the preix**
|
||||
|
||||
```
|
||||
List.make: (number, 'a) => list<'a>
|
||||
```
|
||||
|
@ -37,9 +35,7 @@ toString: (list<'a>) => string
|
|||
length: (list<'a>) => number
|
||||
```
|
||||
|
||||
### up to
|
||||
|
||||
**Note: currently just called `upTo`, without the preix**
|
||||
### upTo
|
||||
|
||||
```
|
||||
List.upTo: (low:number, high:number) => list<number>
|
||||
|
@ -77,6 +73,14 @@ map: (list<'a>, a => b) => list<'b>
|
|||
|
||||
See [Rescript implementation](https://rescript-lang.org/docs/manual/latest/api/belt/array#map).
|
||||
|
||||
### filter
|
||||
|
||||
```
|
||||
filter: (list<'a>, 'a => bool) => list<'a>
|
||||
```
|
||||
|
||||
See [Rescript implementation of keep](https://rescript-lang.org/docs/manual/latest/api/belt/array#keep), which is functionally equivalent.
|
||||
|
||||
### reduce
|
||||
|
||||
```
|
||||
|
|
|
@ -55,6 +55,12 @@ min: (list<number>) => number
|
|||
mean: (list<number>) => number
|
||||
```
|
||||
|
||||
### geometric mean
|
||||
|
||||
```
|
||||
geomean: (list<number>) => number
|
||||
```
|
||||
|
||||
### stdev
|
||||
|
||||
```
|
||||
|
@ -117,6 +123,12 @@ product: (list<number>) => number
|
|||
cumprod: (list<number>) => list<number>
|
||||
```
|
||||
|
||||
### diff
|
||||
|
||||
```
|
||||
diff: (list<number>) => list<number>
|
||||
```
|
||||
|
||||
### subtract
|
||||
|
||||
```
|
||||
|
|
|
@ -7,7 +7,12 @@ import { SquiggleEditor } from "../../src/components/SquiggleEditor";
|
|||
|
||||
Much of the Squiggle math is imprecise. This can cause significant errors, so watch out.
|
||||
|
||||
Below are some specific examples to watch for. We'll work on improving these over time and adding much better warnings and error management.
|
||||
Below are a few specific examples to watch for. We'll work on improving these over time and adding much better warnings and error management.
|
||||
|
||||
## Operations on very small or large numbers, silently round to 0 and 1
|
||||
|
||||
Squiggle is poor at dealing with very small or large numbers, given fundamental limitations of floating point precision.
|
||||
See [this Github Issue](https://github.com/quantified-uncertainty/squiggle/issues/834).
|
||||
|
||||
## Mixtures of distributions with very different means
|
||||
|
||||
|
@ -31,6 +36,6 @@ The means of sample set distributions can vary dramatically, especially as the n
|
|||
|
||||
<SquiggleEditor
|
||||
defaultCode={`symbolicDist = 5 to 50333333
|
||||
sampleSetDist = toSampleSet(symbolicDist)
|
||||
sampleSetDist = SampleSet.fromDist(symbolicDist)
|
||||
[mean(symbolicDist), mean(sampleSetDist), symbolicDist, sampleSetDist]`}
|
||||
/>
|
||||
|
|
|
@ -3,31 +3,20 @@ title: Future Features
|
|||
sidebar_position: 3
|
||||
---
|
||||
|
||||
Squiggle is still very early. The main first goal is to become stable. This means having a clean codebase, having decent test coverage, and having a syntax we are reasonably confident in. Later on, there are many other features that will be interesting to explore.
|
||||
Squiggle is still very early. The main first goal is to become stable (to reach version 1.0). Right now we think it is useable to use for small projects, but do note that there are very likely some math bugs and performance problems.
|
||||
|
||||
## Programming Language Features
|
||||
|
||||
- Equality (a == b)
|
||||
- If/else statements
|
||||
- Arrays
|
||||
- Tables / Matrices
|
||||
- Simple objects
|
||||
- A simple type system
|
||||
- Simple module system (`Dist.Normal` instead of `normal`)
|
||||
- A simple time library & notation
|
||||
- Optional and default paramaters for functions
|
||||
- Anonymous Functions (This is particularly convenient in cases where tiny functions are submitted in forecasting competitions)
|
||||
- A notation to limit the domain of functions. For example, maybe a function only applies for t=[2 to 20]
|
||||
- Custom parser (Right now we're using Math.js's parser, which doesn't give us much flexibility)
|
||||
- "Partial-domain" distributions. For example, maybe someone has a distribution for when AGI will happen, but doesn't want to make any estimates past 2200.
|
||||
- Some story for tests
|
||||
- Much better code editor integration
|
||||
|
||||
## Distribution Features
|
||||
|
||||
`Distribution.fromSamples([])`
|
||||
Converts a list of samples, for example, from Guesstimate, into a distribution shape. Maybe takes a list of optional parameters.
|
||||
|
||||
`Distribution.fromCoordinates({xs, ys})`
|
||||
Convert XY coordinates into a distribution. Figure out a good way to do this for continuous, discrete, and mixed distributions.
|
||||
There are many important distribution types that Squiggle doesn't yet support. Some key functions we'd like include:
|
||||
|
||||
[Metalog Distribution](https://en.wikipedia.org/wiki/Metalog_distribution)
|
||||
Add the Metalog distribution, and some convenient methods for generating these distributions. This might be a bit tricky because we might need or build a library to fit data. There's no Metalog javascript library yet, this would be pretty useful. There's already a Metalog library in Python, so that one could be used for inspiration.
|
||||
|
@ -40,15 +29,6 @@ Takes a distribution and smoothens it. For example, [Elicit Forecast](https://fo
|
|||
**Probabilities**
|
||||
Right now Squiggle mostly works with probability distributions only, but it should also work smoothly with probabilities.
|
||||
|
||||
**Scoring**
|
||||
Have functions to score probabilities, probability distributions, and functions that return probability distributions.
|
||||
|
||||
**Full javascript library**
|
||||
A full Javascript library that accesses most of the probabilistic functionality of Squiggle, but can be used directly in javascript functions.
|
||||
|
||||
**Importance & quality scores**
|
||||
Workflows/functionality to declare the importance and coveredness of each part of the paramater space. For example, some subsets of the paramater space of a function might be much more important to get right than others. Similarly, the analyst might be much more certain about some parts than others. Ideally. they could decline sections.
|
||||
|
||||
**An interface to interpret & score Squiggle files**
|
||||
Squiggle functions need to be aggregated and scored. This should be done outside one Squiggle file. Maybe this should also be done in Squiggle, or maybe it should be done using Javascript.
|
||||
|
||||
|
@ -58,55 +38,17 @@ Of course, we'd also need good math for how the scoring should work, exactly.
|
|||
|
||||
This interface should also be able to handle changing Squiggle values. This is because people would be likely to want to update their functions over time, and that should be taken into account for scoring.
|
||||
|
||||
**Easily call other functions**
|
||||
It would be great to be able to call other people's Squiggle functions, from other Squiggle functions. This could raise a whole bunch of challenging issues. Additionally, it would be neat to call other data, both from knowledge graphs, and from regular APIs. Note that this could obviously complicate scoring a lot; I imagine that either easy scoring, or simple data fetching, would have to accept sacrifices.
|
||||
**Importance & quality scores**
|
||||
Workflows/functionality to declare the importance and coveredness of each part of the paramater space. For example, some subsets of the paramater space of a function might be much more important to get right than others. Similarly, the analyst might be much more certain about some parts than others. Ideally. they could decline sections.
|
||||
|
||||
**Correlated uncertainties**
|
||||
Right now there's no functionality to declare that two different distributions are correlated.
|
||||
|
||||
**Static / Sensitivity Analysis**
|
||||
**Static / sensitivity analysis**
|
||||
Guesstimate has Sensitivity analysis that's pretty useful. This could be quite feasible to add, though it will likely require some thinking.
|
||||
|
||||
**Annotation**
|
||||
It might be useful to allow people to annotate functions and variables with longer descriptions, maybe Markdown. This could very much help interpretation/analysis of these items.
|
||||
|
||||
**Randomness Seeds**
|
||||
**Randomness seeds**
|
||||
Right now, Monte Carlo simulations are totally random. It would be nicer to be able to enter a seed somehow in order to control the randomness. Or, with the same seed, the function should always return the same values. This would make debugging and similar easier.
|
||||
|
||||
## Major Standard Language Features
|
||||
|
||||
- Some testing story.
|
||||
- A custom code highlighting format.
|
||||
- Possibly a decent web GUI (a much more advanced playground).
|
||||
- A VS Code extention and similar.
|
||||
|
||||
## Bugs
|
||||
|
||||
- Discrete distributions are particularly buggy. Try `mm(1,2,3,4,5,6,7,8,9,10) .* (5 to 8)`
|
||||
|
||||
## New Functions
|
||||
|
||||
### Distributions
|
||||
|
||||
```js
|
||||
cauchy();
|
||||
pareto();
|
||||
metalog();
|
||||
```
|
||||
|
||||
Possibly change mm to mix, or mx(). Also, change input format, maybe to mx([a,b,c], [a,b,c]).
|
||||
|
||||
### Functions
|
||||
|
||||
```js
|
||||
samples(distribution, n);
|
||||
toPdf(distribution);
|
||||
toCdf(distribution);
|
||||
toHash(distribution);
|
||||
trunctate(distribution, leftValue, rightValue);
|
||||
leftTrunctate(distribution, leftValue);
|
||||
rightTrunctate(distribution, rightValue);
|
||||
distributionFromSamples(array, params);
|
||||
distributionFromPoints();
|
||||
distributionFromHash();
|
||||
```
|
||||
**Caching/memoization**
|
||||
There are many performance improvements that Squiggle could have. We'll get to some of them eventually.
|
||||
|
|
|
@ -3,6 +3,8 @@ sidebar_position: 2
|
|||
title: Gallery
|
||||
---
|
||||
|
||||
- [Adjusting probabilities for the passage of time](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3/p/j8o6sgRerE3tqNWdj) by Nuño Sempere
|
||||
- [GiveWell's GiveDirectly cost effectiveness analysis](https://observablehq.com/@hazelfire/givewells-givedirectly-cost-effectiveness-analysis) by Sam Nolan
|
||||
- [Astronomical Waste](https://observablehq.com/@quinn-dougherty/waste)
|
||||
- [A Critical Review of Open Philanthropy’s Bet On Criminal Justice Reform](https://forum.effectivealtruism.org/posts/h2N9qEbvQ6RHABcae/a-critical-review-of-open-philanthropy-s-bet-on-criminal) by Nuño Sempere
|
||||
- [Samotsvety Nuclear Risk Forecasts — March 2022](https://forum.effectivealtruism.org/posts/KRFXjCqqfGQAYirm5/samotsvety-nuclear-risk-forecasts-march-2022) by Nuño Sempere, Misha Yagudin, Eli Lifland
|
||||
- [Adjusting probabilities for the passage of time](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3/p/j8o6sgRerE3tqNWdj) by Nuño Sempere
|
||||
- [List of QURI Squiggle Models](https://github.com/quantified-uncertainty/squiggle-models) by Nuño Sempere, Sam Nolan, and Ozzie Gooen
|
||||
|
|
|
@ -5,17 +5,17 @@ author: Ozzie Gooen
|
|||
date: 02-19-2022
|
||||
---
|
||||
|
||||
Probability distributions have several subtle possible formats. Three important ones that we deal with in Squiggle are symbolic, sample set, and graph formats.
|
||||
Probability distributions have several subtle possible formats. Three important ones that we deal with in Squiggle are symbolic, sample set, and point set formats.
|
||||
|
||||
_Symbolic_ formats are just the math equations. `normal(5,3)` is the symbolic representation of a normal distribution.
|
||||
|
||||
When you sample distributions (usually starting with symbolic formats), you get lists of samples. Monte Carlo techniques return lists of samples. Let’s call this the “_Sample Set_” format.
|
||||
|
||||
Lastly is what I’ll refer to as the _Graph_ format. It describes the coordinates, or the shape, of the distribution. You can save these formats in JSON, for instance, like, `{xs: [1, 2, 3, 4, …], ys: [.0001, .0003, .002, …]}`.
|
||||
Lastly is what I’ll refer to as the _Point Set_ format. It describes the coordinates, or the shape, of the distribution. You can save these formats in JSON, for instance, like, `{xs: [1, 2, 3, 4, …], ys: [.0001, .0003, .002, …]}`.
|
||||
|
||||
Symbolic, Sample Set, and Graph formats all have very different advantages and disadvantages.
|
||||
Symbolic, Sample Set, and Point Set formats all have very different advantages and disadvantages.
|
||||
|
||||
Note that the name "Symbolic" is fairly standard, but I haven't found common names for what I'm referring to as "Sample Set" and "Graph" formats. The formats aren't often specifically referred to for these purposes, from what I can tell.
|
||||
Note that the name "Symbolic" is fairly standard, but I haven't found common names for what I'm referring to as "Sample Set" and "Point Set" formats. The formats aren't often specifically referred to for these purposes, from what I can tell.
|
||||
|
||||
## Symbolic Formats
|
||||
|
||||
|
@ -40,7 +40,7 @@ To perform calculations of symbolic systems, you need to find analytical solutio
|
|||
- It’s often either impossible or computationally infeasible to find analytical solutions to most symbolic equations.
|
||||
- Solving symbolic equations requires very specialized tooling that’s very rare. There are a few small symbolic solver libraries out there, but not many. Wolfram Research is the main group that seems very strong here, and their work is mostly closed source + expensive.
|
||||
|
||||
**Converting to Graph Formats**
|
||||
**Converting to Point Set Formats**
|
||||
|
||||
- Very easy. Choose X points such that you capture most of the distribution (you can set a threshold, like 99.9%). For each X point, calculate the pdf, and save as the Y points.
|
||||
|
||||
|
@ -49,23 +49,23 @@ To perform calculations of symbolic systems, you need to find analytical solutio
|
|||
- Very easy. Just sample a bunch of times. The regular way is to randomly sample (This is trivial to do for all distributions with inverse-cdf functions.) If you want to get more fancy, you could provide extra samples from the tails, that would be weighted lower. Or, you could take samples in equal distances (of probability mass) along the entire distribution, then optionally shuffle it. (In the latter case, these would not be random samples, but sometimes that’s fine.)
|
||||
|
||||
**How to Visualize**
|
||||
Convert to graph, then display that. (Optionally, you can also convert to samples, then display those using a histogram, but this is often worse you have both options.)
|
||||
Convert to point set, then display that. (Optionally, you can also convert to samples, then display those using a histogram, but this is often worse you have both options.)
|
||||
|
||||
**Bonus: The Metalog Distribution**
|
||||
|
||||
The Metalog distribution seems like it can represent almost any reasonable distribution. It’s symbolic. This is great for storage, but it’s not clear if it helps with calculation. My impression is that we don’t have symbolic ways of doing most functions (addition, multiplication, etc) on metalog distributions. Also, note that it can take a fair bit of computation to fit a shape to the Metalog distribution.
|
||||
|
||||
## Graph Formats
|
||||
## Point Set Formats
|
||||
|
||||
**TL;DR**
|
||||
Lists of the x-y coordinates of the shape of a distribution. (Usually the pdf, which is more compressed than the cdf). Some key functions (like pdf, cdf) and manipulations can work on almost any graphically-described distribution.
|
||||
Lists of the x-y coordinates of the shape of a distribution. (Usually the pdf, which is more compressed than the cdf). Some key functions (like pdf, cdf) and manipulations can work on almost any point set distribution.
|
||||
|
||||
**Alternative Names:**
|
||||
Grid, Mesh, Graph, Vector, Pdf, PdfCoords/PdfPoints, Discretised, Bezier, Curve
|
||||
See [this facebook thread](https://www.facebook.com/ozzie.gooen/posts/10165936265785363?notif_id=1644937423623638¬if_t=feedback_reaction_generic&ref=notif).
|
||||
|
||||
**How to Do Computation**
|
||||
Use graph techniques. These can be fairly computationally-intensive (particularly finding integrals, which take a whole lot of adding). In the case that you want to multiply independent distributions, you can try convolution, but it’s pretty expensive.
|
||||
Use point set techniques. These can be fairly computationally-intensive (particularly finding integrals, which take a whole lot of adding). In the case that you want to multiply independent distributions, you can try convolution, but it’s pretty expensive.
|
||||
|
||||
**Examples**
|
||||
`{xs: [1, 2, 3, 4…], ys: [.0001, .0003, .002, .04, ...]} `
|
||||
|
@ -74,18 +74,18 @@ Use graph techniques. These can be fairly computationally-intensive (particularl
|
|||
**Advantages**
|
||||
|
||||
- Much more compressed than Sample List formats, but much less compressed than Symbolic formats.
|
||||
- Many functions (pdf, cdf, percentiles, mean, integration, etc) and manipulations (truncation, scaling horizontally or vertically), are possible on essentially all graph distributions.
|
||||
- Many functions (pdf, cdf, percentiles, mean, integration, etc) and manipulations (truncation, scaling horizontally or vertically), are possible on essentially all point set distributions.
|
||||
|
||||
**Disadvantages**
|
||||
|
||||
- Most calculations are infeasible/impossible to perform graphically. In these cases, you need to use sampling.
|
||||
- Most calculations are infeasible/impossible to perform using point sets formats. In these cases, you need to use sampling.
|
||||
- Not as accurate or fast as symbolic methods, where the symbolic methods are applicable.
|
||||
- The tails get cut off, which is subideal. It’s assumed that the value of the pdf outside of the bounded range is exactly 0, which is not correct. (Note: If you have ideas on how to store graph formats that don’t cut off tails, let me know)
|
||||
- The tails get cut off, which is subideal. It’s assumed that the value of the pdf outside of the bounded range is exactly 0, which is not correct. (Note: If you have ideas on how to store point set formats that don’t cut off tails, let me know)
|
||||
|
||||
**Converting to Symbolic Formats**
|
||||
|
||||
- Okay, if you are okay with a Metalog approximation or similar. Metaculus uses an additive combination of up to [Logistic distributions](https://www.metaculus.com/help/faq/); you could also fit this. Fitting takes a little time (it requires several attempts and some optimization), can be arbitrarily accurate.
|
||||
- If you want to be very fancy, you could try to fit graph distributions into normal / lognormal / etc. but this seems like a lot of work for little gain.
|
||||
- If you want to be very fancy, you could try to fit point set distributions into normal / lognormal / etc. but this seems like a lot of work for little gain.
|
||||
|
||||
**Converting to Sample List Formats**
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: "Distribution Creation"
|
||||
title: "Distributions: Creation"
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
|
@ -17,7 +17,7 @@ The `to` function is an easy way to generate simple distributions using predicte
|
|||
|
||||
If both values are above zero, a `lognormal` distribution is used. If not, a `normal` distribution is used.
|
||||
|
||||
<Tabs>
|
||||
<Tabs lazy>
|
||||
<TabItem value="ex1" label="5 to 10" default>
|
||||
When <code>5 to 10</code> is entered, both numbers are positive, so it
|
||||
generates a lognormal distribution with 5th and 95th percentiles at 5 and
|
||||
|
@ -35,8 +35,9 @@ If both values are above zero, a `lognormal` distribution is used. If not, a `no
|
|||
<SquiggleEditor defaultCode="-5 to -3" />
|
||||
</TabItem>
|
||||
<TabItem value="ex4" label="1 to 10000">
|
||||
It's very easy to generate distributions with very long tails. If this
|
||||
happens, you can click the "log x scale" box to view this using a log scale.
|
||||
It's very easy to generate distributions with very long tails. These can be
|
||||
impossible to see without changing view settings. (These settings are
|
||||
available in the Playground, but not this smaller editor component)
|
||||
<SquiggleEditor defaultCode="1 to 10000" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
@ -74,7 +75,7 @@ If both values are above zero, a `lognormal` distribution is used. If not, a `no
|
|||
|
||||
The `mixture` mixes combines multiple distributions to create a mixture. You can optionally pass in a list of proportional weights.
|
||||
|
||||
<Tabs>
|
||||
<Tabs lazy>
|
||||
<TabItem value="ex1" label="Simple" default>
|
||||
<SquiggleEditor defaultCode="mixture(1 to 2, 5 to 8, 9 to 10)" />
|
||||
</TabItem>
|
||||
|
@ -110,6 +111,11 @@ The `mixture` mixes combines multiple distributions to create a mixture. You can
|
|||
<p>
|
||||
In this case, I have a 20% chance of spending 0 time with it. I might estimate my hours with,
|
||||
</p>
|
||||
<Admonition type="caution" title="Caution">
|
||||
<p>
|
||||
There's a temporary bug where the below render is compressed. If you toggle the code it will fix render correctly.
|
||||
</p>
|
||||
</Admonition>
|
||||
<SquiggleEditor
|
||||
defaultCode={`hours_the_project_will_take = 5 to 20
|
||||
chance_of_doing_anything = 0.8
|
||||
|
@ -139,12 +145,12 @@ mx(forecast, forecast_if_completely_wrong, [1-chance_completely_wrong, chance_co
|
|||
|
||||
Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) with the given mean and standard deviation.
|
||||
|
||||
<Tabs>
|
||||
<Tabs lazy>
|
||||
<TabItem value="ex1" label="normal(5,1)" default>
|
||||
<SquiggleEditor defaultCode="normal(5, 1)" />
|
||||
</TabItem>
|
||||
<TabItem value="ex2" label="normal(100000000000, 100000000000)">
|
||||
<SquiggleEditor defaultCode="normal(100000000000, 100000000000)" />
|
||||
<TabItem value="ex2" label="normal(1G, 1G)">
|
||||
<SquiggleEditor defaultCode="normal(1G, 1G)" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
@ -234,7 +240,7 @@ with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),poi
|
|||
|
||||
`pointMass()` distributions are currently the only discrete distributions accessible in Squiggle.
|
||||
|
||||
<Tabs>
|
||||
<Tabs lazy>
|
||||
<TabItem value="ex1" label="pointMass(3)" default>
|
||||
<SquiggleEditor defaultCode="pointMass(3)" />
|
||||
</TabItem>
|
||||
|
@ -263,7 +269,7 @@ with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),poi
|
|||
|
||||
Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) with the given `alpha` and `beta` values. For a good summary of the beta distribution, see [this explanation](https://stats.stackexchange.com/a/47782) on Stack Overflow.
|
||||
|
||||
<Tabs>
|
||||
<Tabs lazy>
|
||||
<TabItem value="ex1" label="beta(10, 20)" default>
|
||||
<SquiggleEditor defaultCode="beta(10,20)" />
|
||||
</TabItem>
|
||||
|
@ -279,11 +285,8 @@ Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) w
|
|||
<TabItem value="ex5" label="beta(0.8, 0.8)">
|
||||
<SquiggleEditor defaultCode="beta(0.8, 0.8)" />
|
||||
</TabItem>
|
||||
<TabItem
|
||||
value="from mean and standard deviation"
|
||||
label="beta({mean: 0.39, stdev: 0.1})"
|
||||
>
|
||||
<SquiggleEditor initialSquiggleString="beta({mean: 0.39, stdev: 0.1})" />
|
||||
<TabItem value="ex6" label="beta({mean: 0.39, stdev: 0.1})">
|
||||
<SquiggleEditor defaultCode="beta({mean: 0.39, stdev: 0.1})" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
@ -300,7 +303,7 @@ Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) w
|
|||
</p>
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
<Tabs>
|
||||
<Tabs lazy>
|
||||
<TabItem value="ex1" label="beta(0.3, 0.3)" default>
|
||||
<SquiggleEditor defaultCode="beta(0.3, 0.3)" />
|
||||
</TabItem>
|
||||
|
@ -343,13 +346,13 @@ Creates a [triangular distribution](https://en.wikipedia.org/wiki/Triangular_dis
|
|||
|
||||
<SquiggleEditor defaultCode="triangular(1, 2, 4)" />
|
||||
|
||||
## FromSamples
|
||||
## SampleSet.fromList
|
||||
|
||||
`fromSamples(samples:number[])`
|
||||
`SampleSet.fromList(samples:number[])`
|
||||
|
||||
Creates a sample set distribution using an array of samples.
|
||||
|
||||
<SquiggleEditor defaultCode="fromSamples([1,2,3,4,6,5,5,5])" />
|
||||
<SquiggleEditor defaultCode="SampleSet.fromList([1,2,3,4,6,5,5,5])" />
|
||||
|
||||
### Arguments
|
||||
|
||||
|
@ -367,3 +370,41 @@ Creates a sample set distribution using an array of samples.
|
|||
specificity.
|
||||
</p>
|
||||
</Admonition>
|
||||
|
||||
## PointSet.makeContinuous
|
||||
|
||||
`PointSet.makeContinuous(points:{x: number, y: number})`
|
||||
|
||||
Creates a continuous point set distribution using a list of points.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`PointSet.makeContinuous([
|
||||
{ x: 0, y: 0.1 },
|
||||
{ x: 1, y: 0.2 },
|
||||
{ x: 2, y: 0.15 },
|
||||
{ x: 3, y: 0.1 }
|
||||
])`}
|
||||
/>
|
||||
|
||||
### Arguments
|
||||
|
||||
- `points`: An array of at least 3 coordinates.
|
||||
|
||||
## PointSet.makeDiscrete
|
||||
|
||||
`PointSet.makeDiscrete(points:{x: number, y: number})`
|
||||
|
||||
Creates a discrete point set distribution using a list of points.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`PointSet.makeDiscrete([
|
||||
{ x: 0, y: 0.1 },
|
||||
{ x: 1, y: 0.2 },
|
||||
{ x: 2, y: 0.15 },
|
||||
{ x: 3, y: 0.1 }
|
||||
])`}
|
||||
/>
|
||||
|
||||
### Arguments
|
||||
|
||||
- `points`: An array of at least 1 coordinate.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: "Distribution Functions"
|
||||
title: "Distributions: Key Functions"
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
|
@ -47,7 +47,7 @@ dist1 * dist2`}
|
|||
|
||||
We also provide concatenation of two distributions as a syntax sugar for `*`
|
||||
|
||||
<SquiggleEditor defaultCode="(0.1 to 1) triangular(1,2,3)" />
|
||||
<SquiggleEditor defaultCode="(0.1 to 1) * triangular(1,2,3)" />
|
||||
|
||||
### Division
|
||||
|
||||
|
@ -87,18 +87,11 @@ A projection over a stretched x-axis.
|
|||
log(dist)`}
|
||||
/>
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`dist = beta(1,2)
|
||||
log10(dist)`}
|
||||
/>
|
||||
<SquiggleEditor defaultCode={`log10(5 to 10)`} />
|
||||
|
||||
Base `x`
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`x = 2
|
||||
dist = beta(2,3)
|
||||
log(dist, x)`}
|
||||
/>
|
||||
<SquiggleEditor defaultCode={`log(5 to 10, 2)`} />
|
||||
|
||||
#### Validity
|
||||
|
||||
|
@ -113,45 +106,25 @@ For every point on the x-axis, operate the corresponding points in the y axis of
|
|||
|
||||
TODO: this isn't in the new interpreter/parser yet.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .+ dist2`}
|
||||
/>
|
||||
<SquiggleEditor defaultCode={`(1 to 10) .+ triangular(1,2,3)`} />
|
||||
|
||||
### Pointwise subtraction
|
||||
|
||||
TODO: this isn't in the new interpreter/parser yet.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .- dist2`}
|
||||
/>
|
||||
<SquiggleEditor defaultCode={`(1 to 10) .- triangular(1,2,3)`} />
|
||||
|
||||
### Pointwise multiplication
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .* dist2`}
|
||||
/>
|
||||
<SquiggleEditor defaultCode={`(1 to 10) .* triangular(1,2,3)`} />
|
||||
|
||||
### Pointwise division
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`dist1 = uniform(0,20)
|
||||
dist2 = normal(10,8)
|
||||
dist1 ./ dist2`}
|
||||
/>
|
||||
<SquiggleEditor defaultCode={`(uniform(0,10)) ./ normal(10,4)`} />
|
||||
|
||||
### Pointwise exponentiation
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .^ dist2`}
|
||||
/>
|
||||
<SquiggleEditor defaultCode={`(1 to 10) .^ triangular(1,2,3)`} />
|
||||
|
||||
## Standard functions on distributions
|
||||
|
||||
|
@ -208,21 +181,15 @@ The `sample(distribution)` samples a given distribution.
|
|||
|
||||
Recall the [three formats of distributions](https://develop--squiggle-documentation.netlify.app/docs/Discussions/Three-Types-Of-Distributions). We can force any distribution into `SampleSet` format
|
||||
|
||||
<SquiggleEditor defaultCode="toSampleSet(normal(5, 10))" />
|
||||
<SquiggleEditor defaultCode="SampleSet.fromDist(normal(5, 10))" />
|
||||
|
||||
Or `PointSet` format
|
||||
|
||||
<SquiggleEditor defaultCode="toPointSet(normal(5, 10))" />
|
||||
|
||||
### `toSampleSet` has two signatures
|
||||
|
||||
Above, we saw the unary `toSampleSet`, which uses an internal hardcoded number of samples. If you'd like to provide the number of samples, it has a binary signature as well (floored)
|
||||
|
||||
<SquiggleEditor defaultCode="[toSampleSet(0.1 to 1, 100.1), toSampleSet(0.1 to 1, 5000), toSampleSet(0.1 to 1, 20000)]" />
|
||||
<SquiggleEditor defaultCode="PointSet.fromDist(normal(5, 10))" />
|
||||
|
||||
#### Validity
|
||||
|
||||
- Second argument to `toSampleSet` must be a number.
|
||||
- Second argument to `SampleSet.fromDist` must be a number.
|
||||
|
||||
## Normalization
|
||||
|
||||
|
@ -246,7 +213,7 @@ We provide a predicate `isNormalized`, for when we have simple control flow
|
|||
|
||||
You may like to debug by right clicking your browser and using the _inspect_ functionality on the webpage, and viewing the _console_ tab. Then, wrap your squiggle output with `inspect` to log an internal representation.
|
||||
|
||||
<SquiggleEditor defaultCode="inspect(toSampleSet(0.1 to 1, 100))" />
|
||||
<SquiggleEditor defaultCode="inspect(SampleSet.fromDist(0.1 to 1))" />
|
||||
|
||||
Save for a logging side effect, `inspect` does nothing to input and returns it.
|
||||
|
||||
|
|
41
packages/website/docs/Guides/Gotchas.mdx
Normal file
41
packages/website/docs/Guides/Gotchas.mdx
Normal file
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: Gotchas
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
import { SquiggleEditor } from "../../src/components/SquiggleEditor";
|
||||
import Admonition from "@theme/Admonition";
|
||||
|
||||
## Point Set Distributions Conversions
|
||||
|
||||
Point Set conversions are done with [kernel density estimation](https://en.wikipedia.org/wiki/Kernel_density_estimation), which is lossy. This might be particularly noticeable in cases where distributions should be entirely above zero.
|
||||
|
||||
In this example, we see that the median of this (highly skewed) distribution is positive when it's in a Sample Set format, but negative when it's converted to a Point Set format.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`dist = SampleSet.fromDist(5 to 100000000)
|
||||
{
|
||||
sampleSetMedian: quantile(dist, .5),
|
||||
pointSetMedian: quantile(PointSet.fromDist(dist), .5),
|
||||
dist: dist
|
||||
}`}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
This can be particularly confusing for visualizations. Visualizations automatically convert distributions into Point Set formats. Therefore, they might often show negative values, even if the underlying distribution is fully positive.
|
||||
|
||||
We plan to later support more configuration of kernel density estimation, and for visualiations of Sample Set distributions to instead use histograms.
|
||||
|
||||
## Sample Set Correlations
|
||||
|
||||
Correlations with Sample Set distributions are a bit complicated. Monte Carlo generations with Squiggle are ordered. The first sample in one Sample Set distribution will correspond to the first sample in a distribution that comes from a resulting Monte Carlo generation. Therefore, Sample Set distributions in a chain of Monte Carlo generations are likely to all be correlated with each other. This connection breaks if any node changes to the Point Set or Symbolic format.
|
||||
|
||||
In this example, we subtract all three types of distributions by themselves. Notice that the Sample Set distribution returns 1. The other two return the result of subtracting one normal distribution from a separate uncorrelated distribution. These results are clearly very different to each other.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`sampleSetDist = normal(5,2) |> SampleSet.fromDist
|
||||
sampleSetDistToPointSet = sampleSetDist |> PointSet.fromDist
|
||||
symbolicDist = normal(5,2)
|
||||
[sampleSetDist-sampleSetDist, sampleSetDistToPointSet-sampleSetDistToPointSet, symbolicDist-symbolicDist]`}
|
||||
/>
|
|
@ -5,50 +5,103 @@ title: Language Basics
|
|||
|
||||
import { SquiggleEditor } from "../../src/components/SquiggleEditor";
|
||||
|
||||
## Expressions
|
||||
Squiggle supports some simple types and language features.
|
||||
|
||||
### Distributions
|
||||
|
||||
<SquiggleEditor defaultCode={`mixture(1 to 2, 3, [0.3, 0.7])`} />
|
||||
|
||||
### Numbers
|
||||
## Numbers
|
||||
|
||||
<SquiggleEditor defaultCode="4.32" />
|
||||
|
||||
### Arrays
|
||||
## Distributions
|
||||
|
||||
There are several ways of easily entering distributions. See the [documentation](/docs/Api/Dist/) on distributions for a complete API.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`[beta(1,10), 4, isNormalized(toSampleSet(1 to 2))]`}
|
||||
defaultCode={`a = normal(4,2)
|
||||
b = 30 to 50
|
||||
c = lognormal({mean:90, stdev: 7})
|
||||
d = mixture(a,b,c, [.3, .3, .4])
|
||||
d`}
|
||||
/>
|
||||
|
||||
### Records
|
||||
## Lists
|
||||
|
||||
Squiggle lists can accept items of any type, similar to those in Python. [API](/docs/Api/List).
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`[beta(1,10), 4, isNormalized(SampleSet.fromDist(1 to 2))]`}
|
||||
/>
|
||||
|
||||
## Dictionaries
|
||||
|
||||
Squiggle dictionaries work similarly to Python dictionaries. [API](/docs/Api/Dictionary).
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`d = {dist: triangular(0, 1, 2), weight: 0.25}
|
||||
d.dist`}
|
||||
/>
|
||||
|
||||
## Statements
|
||||
|
||||
A statement assigns expressions to names. It looks like `<symbol> = <expression>`
|
||||
## Functions
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`value_of_work = 10 to 70
|
||||
5 + value_of_work / 75`}
|
||||
defaultCode={`f(t) = normal(t^2, t^1.2+.01)
|
||||
f`}
|
||||
/>
|
||||
|
||||
### Functions
|
||||
## Anonymous Functions
|
||||
|
||||
We can define functions
|
||||
<SquiggleEditor defaultCode={`{|t| normal(t^2, t^1.2+.01)}`} />
|
||||
|
||||
## Comments
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`ozzie_estimate(t) = lognormal(t^(1.1), 0.5)
|
||||
nuno_estimate(t, m) = mixture(normal(-5, 1), lognormal(m, t / 1.25))
|
||||
ozzie_estimate(1) * nuno_estimate(1, 1)`}
|
||||
defaultCode={`// This is a single-line comment\n
|
||||
/*
|
||||
This is a multiple
|
||||
-line comment.
|
||||
*/
|
||||
""
|
||||
`}
|
||||
/>
|
||||
|
||||
## See more
|
||||
## Pipes
|
||||
|
||||
- [Distribution creation](./DistributionCreation)
|
||||
- [Functions reference](./Functions)
|
||||
- [Gallery](../Discussions/Gallery)
|
||||
Squiggle features [data-first](https://www.javierchavarri.com/data-first-and-data-last-a-comparison/) pipes. Functions in the standard library are organized to make this convenient.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`normal(5,2) |> truncateLeft(3) |> SampleSet.fromDist |> SampleSet.map({|r| r + 10})`}
|
||||
/>
|
||||
|
||||
## Standard Library
|
||||
|
||||
Squiggle features a simple [standard libary](/docs/Api/Dist).
|
||||
|
||||
Most functions are namespaced under their respective types to keep functionality distinct. Certain popular functions are usable without their namespaces.
|
||||
|
||||
For example,
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`a = List.upTo(0, 5000) |> SampleSet.fromList // namespaces required
|
||||
b = normal(5,2) // namespace not required
|
||||
c = 5 to 10 // namespace not required
|
||||
""`}
|
||||
/>
|
||||
|
||||
## Number Prefixes
|
||||
|
||||
Numbers support a few scientific notation prefixes.
|
||||
|
||||
| prefix | multiplier |
|
||||
| ------ | ---------- |
|
||||
| n | 10^-9 |
|
||||
| m | 10^-3 |
|
||||
| k | 10^3 |
|
||||
| M | 10^6 |
|
||||
| B,G | 10^9 |
|
||||
| T | 10^12 |
|
||||
| P | 10^15 |
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`simpleNumber = 4.32k
|
||||
distribution = 40M to 50M
|
||||
distribution`}
|
||||
/>
|
||||
|
|
38
packages/website/docs/Integrations.md
Normal file
38
packages/website/docs/Integrations.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
title: "Integrations"
|
||||
---
|
||||
|
||||
## Node Packages
|
||||
|
||||
There are two JavaScript packages currently available for Squiggle:
|
||||
|
||||
- [`@quri/squiggle-lang`](https://www.npmjs.com/package/@quri/squiggle-lang)
|
||||
- [`@quri/squiggle-components`](https://www.npmjs.com/package/@quri/squiggle-components)
|
||||
|
||||
Types are available for both packages.
|
||||
|
||||
## [Squiggle Language](https://www.npmjs.com/package/@quri/squiggle-lang) ![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-lang.svg)
|
||||
|
||||
[_See `README.md` in Github_](https://github.com/quantified-uncertainty/squiggle/tree/develop/packages/squiggle-lang#use-the-npm-package)
|
||||
|
||||
## [Squiggle Components](https://www.npmjs.com/package/@quri/squiggle-components) ![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-components.svg)
|
||||
|
||||
[_See `README.md` in Github_](https://github.com/quantified-uncertainty/squiggle/tree/develop/packages/components#usage-in-a-react-project)
|
||||
|
||||
This documentation uses `@quri/squiggle-components` frequently.
|
||||
|
||||
We host [a storybook](https://squiggle-components.netlify.app/) with details
|
||||
and usage of each of the components made available.
|
||||
|
||||
## [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle) ![npm version](https://vsmarketplacebadge.apphb.com/version/QURI.vscode-squiggle.svg)
|
||||
|
||||
This extention allows you to run and visualize Squiggle code.
|
||||
|
||||
## [Observable Library](https://observablehq.com/@hazelfire/squiggle)
|
||||
|
||||
An exportable [Observable Notebook](https://observablehq.com/@hazelfire/squiggle) of the key components that you can directly import and use in Observable notebooks.
|
||||
|
||||
## [NextJS starter](https://github.com/quantified-uncertainty/next-app-with-squiggle)
|
||||
|
||||
A template for presenting estimates on a nextjs site.
|
39
packages/website/docs/Internal/ImportIntoMdx.mdx
Normal file
39
packages/website/docs/Internal/ImportIntoMdx.mdx
Normal file
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: How to import squiggle files into `.mdx` documents
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
import { SquiggleEditorWithImportedBindings } from "../../src/components/SquiggleEditor";
|
||||
|
||||
_Proof of concept_
|
||||
|
||||
## Consider the following squiggle file
|
||||
|
||||
In our docusaurus repo, we have a static asset called `demo.squiggle`. It looks like this
|
||||
|
||||
```js
|
||||
x = 1 to 2
|
||||
y = {a: x, b: 1e1}
|
||||
f(t) = normal(t, 1.1)
|
||||
z = y.b * y.a
|
||||
```
|
||||
|
||||
We can call `f(z)` upon the assignments in `demo.squiggle` like so:
|
||||
|
||||
```jsx
|
||||
import { SquiggleEditorWithImportedBindings } from "../../src/components/SquiggleEditor";
|
||||
|
||||
<SquiggleEditorWithImportedBindings
|
||||
defaultCode={"f(z)"}
|
||||
bindingsImportFile={"/estimates/demo.squiggle"}
|
||||
/>;
|
||||
```
|
||||
|
||||
Notice, you need to wrap the export of `@quri/squiggle-components` in custom code for dynamicism, please view `packages/website/src/components/` in github for details.
|
||||
|
||||
Which would then look exactly like
|
||||
|
||||
<SquiggleEditorWithImportedBindings
|
||||
defaultCode={"f(z)"}
|
||||
bindingsImportUrl={"/estimates/demo.squiggle"}
|
||||
/>
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
title: Introduction
|
||||
---
|
||||
|
||||
Squiggle is an _estimation language_, and a syntax for _calculating and expressing beliefs_ involving uncertainty. It has use cases in forecasting and writing evaluations.
|
||||
|
||||
## Get started
|
||||
|
||||
- [Gallery](./Discussions/Gallery)
|
||||
- [Squiggle playground](/playground)
|
||||
- [Language basics](./Guides/Language)
|
||||
- [Squiggle functions source of truth](./Guides/Functions)
|
||||
- [Known bugs](./Discussions/Bugs)
|
||||
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
||||
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
title: Node Packages
|
||||
---
|
||||
|
||||
There are two JavaScript packages currently available for Squiggle:
|
||||
|
||||
- [`@quri/squiggle-lang`](https://www.npmjs.com/package/@quri/squiggle-lang) ![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-lang.svg)
|
||||
- [`@quri/squiggle-components`](https://www.npmjs.com/package/@quri/squiggle-components) ![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-components.svg)
|
||||
|
||||
Types are available for both packages.
|
||||
|
||||
## Squiggle Language
|
||||
|
||||
[_See `README.md` in Github_](https://github.com/quantified-uncertainty/squiggle/tree/develop/packages/squiggle-lang#use-the-npm-package)
|
||||
|
||||
## Squiggle Components
|
||||
|
||||
[_See `README.md` in Github_](https://github.com/quantified-uncertainty/squiggle/tree/develop/packages/components#usage-in-a-react-project)
|
||||
|
||||
This documentation uses `@quri/squiggle-components` frequently.
|
||||
|
||||
We host [a storybook](https://squiggle-components.netlify.app/) with details
|
||||
and usage of each of the components made available.
|
119
packages/website/docs/Overview.mdx
Normal file
119
packages/website/docs/Overview.mdx
Normal file
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
title: Overview
|
||||
---
|
||||
|
||||
import { SquiggleEditor } from "../src/components/SquiggleEditor";
|
||||
|
||||
Squiggle is a minimalist programming language for probabilistic estimation. It's meant for intuitively-driven quantitative estimation instead of data analysis or data-driven statistical techniques.
|
||||
|
||||
The basics of Squiggle are fairly straightforward. This can be enough for many models. The more advanced functionality can take some time to learn.
|
||||
|
||||
## Simple example
|
||||
|
||||
Say you're trying to estimate the number of piano tuners in New York City. You can build a simple model of this, like so.
|
||||
(Tip: This is interactive! Feel free to modify the code directly.)
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`
|
||||
// Piano tuners in NYC over the next 5 years
|
||||
populationOfNewYork2022 = 8.1M to 8.4M // This means that you're 90% confident the value is between 8.1 and 8.4 Million.\n
|
||||
proportionOfPopulationWithPianos = {
|
||||
percentage = (.2 to 1)
|
||||
percentage * 0.01
|
||||
} // We assume there are almost no people with multiple pianos\n
|
||||
pianoTunersPerPiano = {
|
||||
pianosPerPianoTuner = 2k to 50k // This is artificially narrow, to help graphics later
|
||||
1 / pianosPerPianoTuner
|
||||
} \n
|
||||
totalTunersIn2022 = populationOfNewYork2022 * proportionOfPopulationWithPianos * pianoTunersPerPiano
|
||||
totalTunersIn2022
|
||||
`}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
Now let's take this a bit further. Let's imagine that you think that NYC will grow over time, and you'd like to estimate the number of piano tuners for every point in time for the next few years.
|
||||
|
||||
<SquiggleEditor
|
||||
defaultCode={`// Piano tuners in NYC over the next 5 years
|
||||
populationOfNewYork2022 = 8.1M to 8.4M\n
|
||||
proportionOfPopulationWithPianos = {
|
||||
percentage = (.2 to 1)
|
||||
percentage * 0.01
|
||||
} // We assume there are almost no people with multiple pianos\n
|
||||
pianoTunersPerPiano = {
|
||||
pianosPerPianoTuner = 2k to 50k // This is artificially narrow, to help graphics later
|
||||
1 / pianosPerPianoTuner
|
||||
} \n
|
||||
//Time in years after 2022
|
||||
populationAtTime(t) = {
|
||||
averageYearlyPercentageChange = -0.01 to 0.05 // We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year
|
||||
populationOfNewYork2022 * ((averageYearlyPercentageChange + 1) ^ t)
|
||||
}\n
|
||||
median(v) = quantile(v, .5)
|
||||
totalTunersAtTime(t) = populationAtTime(t) * proportionOfPopulationWithPianos * pianoTunersPerPiano\n
|
||||
{
|
||||
populationAtTime: populationAtTime,
|
||||
totalTunersAtTimeMedian: {|t| median(totalTunersAtTime(t))}
|
||||
}`}
|
||||
/>
|
||||
|
||||
If you haven't noticed yet, you can hover over the `populationAtTime` graph to see the distribution of population at different points in time.
|
||||
|
||||
## Using Squiggle
|
||||
|
||||
You can currently interact with Squiggle in a few ways:
|
||||
|
||||
**[Playground](/playground)**
|
||||
The [Squiggle Playground](/playground) is a nice tool for working with small models and making prototypes. You can make simple shareable links, but you can't save models that change over time.
|
||||
|
||||
**[Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle)**
|
||||
There's a simple [VS Code extension](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle) for running and visualizing Squiggle code. We find that VS Code is a useful editor for managing larger Squiggle setups.
|
||||
|
||||
**[Typescript Library](https://www.npmjs.com/package/@quri/squiggle-lang)**
|
||||
Squiggle is built using [Rescript](https://rescript-lang.org/), and is accessible via a simple Typescript library. You can use this library to either run Squiggle code in full, or to call select specific functions within Squiggle (though this latter functionality is very minimal).
|
||||
|
||||
**[React Components Library](https://www.npmjs.com/package/@quri/squiggle-components)**
|
||||
All of the components used in the playground and documentation are available in a separate component NPM repo. You can see the full Storybook of components [here](https://squiggle-components.netlify.app).
|
||||
|
||||
**[Observable](https://observablehq.com/@hazelfire/squiggle)**
|
||||
You can use Squiggle Components in Observable notebooks. Sam Nolan put together an exportable [Observable Notebook](https://observablehq.com/@hazelfire/squiggle) of the key components that you can directly import and use in your Observable notebooks.
|
||||
|
||||
## Squiggle Vs. Other Tools
|
||||
|
||||
### What Squiggle Is
|
||||
|
||||
- A simple programming language for doing math with probability distributions.
|
||||
- An embeddable language that can be used in Javascript applications.
|
||||
- A tool to encode functions as forecasts that can be embedded in other applications.
|
||||
|
||||
### What Squiggle Is Not
|
||||
|
||||
- A complete replacement for enterprise Risk Analysis tools. (See [Crystal Ball](https://www.oracle.com/applications/crystalball/), [@Risk](https://www.palisade.com/risk/), [Lumina Analytica](https://lumina.com/))
|
||||
- A [probabilistic programming language](https://en.wikipedia.org/wiki/Probabilistic_programming). Squiggle does not support Bayesian inference.
|
||||
- A tool for substantial data analysis. (See programming languages like [Python](https://www.python.org/) or [Julia](https://julialang.org/))
|
||||
- A programming language for anything other than estimation.
|
||||
- A visually-driven tool. (See [Guesstimate](https://www.getguesstimate.com/) and [Causal](https://causal.app/))
|
||||
|
||||
### Strengths
|
||||
|
||||
- Simple and readable syntax, especially for dealing with probabilistic math.
|
||||
- Fast for relatively small models. Strong for rapid prototyping.
|
||||
- Optimized for using some numeric and symbolic approaches, not just Monte Carlo.
|
||||
- Embeddable in Javascript.
|
||||
- Free and open-source.
|
||||
|
||||
### Weaknesses
|
||||
|
||||
- Limited scientific capabilities.
|
||||
- Much slower than serious probabilistic programming languages on sizeable models.
|
||||
- Can't do Bayesian backwards inference.
|
||||
- Essentially no support for libraries or modules (yet).
|
||||
- Still very new, so a tiny ecosystem.
|
||||
- Still very new, so there are likely math bugs.
|
||||
- Generally not as easy to use as Guesstimate or Causal, especially for non programmers.
|
||||
|
||||
## Organization
|
||||
|
||||
Squiggle is one of the main projects of [The Quantified Uncertainty Research Institute](https://quantifieduncertainty.org/). QURI is a nonprofit funded primarily by [Effective Altruist](https://www.effectivealtruism.org/) donors.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user