Merge pull request #419 from quantified-uncertainty/develop
`master` <- `develop` sync apr28
This commit is contained in:
commit
1806ba80fb
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
|
@ -9,3 +9,5 @@ updates:
|
|||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: "⬆️"
|
||||
|
|
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
|
@ -9,6 +9,7 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- develop
|
||||
- reducer-dev
|
||||
|
||||
jobs:
|
||||
pre_check:
|
||||
|
@ -71,12 +72,16 @@ jobs:
|
|||
run: cd ../../ && yarn
|
||||
- name: Build rescript codebase
|
||||
run: yarn build
|
||||
- name: Run tests
|
||||
run: yarn test
|
||||
- name: Run rescript tests
|
||||
run: yarn test:rescript
|
||||
- name: Run typescript tests
|
||||
run: yarn test:ts
|
||||
- name: Run webpack
|
||||
run: yarn bundle
|
||||
- name: Upload coverage report
|
||||
run: yarn coverage:ci
|
||||
- name: Upload rescript coverage report
|
||||
run: yarn coverage:rescript:ci
|
||||
- name: Upload typescript coverage report
|
||||
run: yarn coverage:ts:ci
|
||||
|
||||
components-lint:
|
||||
name: Components lint
|
||||
|
|
7
.github/workflows/codeql-analysis.yml
vendored
7
.github/workflows/codeql-analysis.yml
vendored
|
@ -18,13 +18,6 @@ on:
|
|||
- production
|
||||
- staging
|
||||
- develop
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches:
|
||||
- master
|
||||
- production
|
||||
- staging
|
||||
- develop
|
||||
schedule:
|
||||
- cron: "42 19 * * 0"
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,3 +6,4 @@ yarn-error.log
|
|||
.DS_Store
|
||||
**/.sync.ffs_db
|
||||
.direnv
|
||||
.log
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"extends": "@parcel/config-default",
|
||||
"transformers": {
|
||||
"*.res": ["@parcel/transformer-raw"]
|
||||
}
|
||||
}
|
|
@ -7,3 +7,7 @@ node_modules
|
|||
packages/*/node_modules
|
||||
packages/website/.docusaurus
|
||||
packages/squiggle-lang/lib
|
||||
packages/squiggle-lang/.nyc_output/
|
||||
packages/squiggle-lang/coverage/
|
||||
packages/squiggle-lang/.cache/
|
||||
packages/website/build/
|
||||
|
|
|
@ -8,7 +8,7 @@ Squiggle is currently pre-alpha.
|
|||
|
||||
# Quick links
|
||||
|
||||
- [Roadmap to the alpha](https://github.com/QURIresearch/squiggle/projects/2)
|
||||
- [Roadmap to the alpha](https://github.com/orgs/quantified-uncertainty/projects/1)
|
||||
- The team presently communicates via the **EA Forecasting and Epistemics** slack (channels `#squiggle` and `#squiggle-ops`), you can track down an invite by reaching out to Ozzie Gooen
|
||||
- [Squiggle documentation](https://www.squiggle-language.com/docs/Language)
|
||||
- [Rescript documentation](https://rescript-lang.org/docs/manual/latest/introduction)
|
||||
|
@ -20,10 +20,9 @@ Anyone (with a github account) can file an issue at any time. Please allow Quinn
|
|||
|
||||
# Project structure
|
||||
|
||||
Squiggle is a **monorepo** with four **packages**.
|
||||
Squiggle is a **monorepo** with three **packages**.
|
||||
|
||||
- **components** is where we improve reactive interfacing with Squiggle
|
||||
- **playground** is the site `playground.squiggle-language.com`
|
||||
- **squiggle-lang** is where the magic happens: probability distributions, the interpreter, etc.
|
||||
- **website** is the site `squiggle-language.com`
|
||||
|
||||
|
@ -41,13 +40,17 @@ We aspire for `ci.yaml` and `README.md`s to be in one-to-one correspondence.
|
|||
|
||||
## If you're on NixOS
|
||||
|
||||
You'll need to run a command like this in order to get `yarn build` to run, especially in `packages/squiggle-lang`.
|
||||
You can't run `yarn` outside of a FHS shell. Additionally, you need to `patchelf` some things. A script does everything for you.
|
||||
|
||||
```sh
|
||||
patchelf --set-interpreter $(patchelf --print-interpreter $(which mkdir)) ./node_modules/gentype/gentype.exe
|
||||
./nixos.sh
|
||||
```
|
||||
|
||||
See [here](https://github.com/NixOS/nixpkgs/issues/107375)
|
||||
Reasons for this are comments in the script. Then, you should be able to do all the package-level `yarn run` commands/scripts.
|
||||
|
||||
# Try not to push directly to develop
|
||||
|
||||
If you absolutely must, please prefix your commit message with `hotfix: `.
|
||||
|
||||
# Pull request protocol
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ The playground depends on the components library which then depends on the langu
|
|||
|
||||
# Develop
|
||||
|
||||
For any project in the repo, begin by running `yarn` in the top level (TODO: is this true?)
|
||||
For any project in the repo, begin by running `yarn` in the top level
|
||||
|
||||
```sh
|
||||
yarn
|
||||
|
|
20
examples/decay.squiggle
Normal file
20
examples/decay.squiggle
Normal file
|
@ -0,0 +1,20 @@
|
|||
# The following code was provided by Nuño Sempere, it comes directly from the post https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3/p/j8o6sgRerE3tqNWdj
|
||||
## Initial setup
|
||||
yearly_probability_max = 0.95
|
||||
yearly_probability_min = 0.66
|
||||
period_probability_function(epsilon, yearly_probability) = 1 - (1 - yearly_probability) ^ (1 / epsilon)
|
||||
probability_decayed(t, time_periods, period_probability) = 1 - (1 - period_probability) ^ (time_periods - t)
|
||||
|
||||
## Monthly decomposition
|
||||
months_in_a_year=12
|
||||
|
||||
monthly_probability_min = period_probability_function(months_in_a_year, yearly_probability_min)
|
||||
monthly_probability_max = period_probability_function(months_in_a_year, yearly_probability_max)
|
||||
|
||||
probability_decayed_monthly_min(t) = probability_decayed(t, months_in_a_year, monthly_probability_min)
|
||||
probability_decayed_monthly_max(t) = probability_decayed(t, months_in_a_year, monthly_probability_max)
|
||||
probability_decayed_monthly(t) = probability_decayed_monthly_min(t) to probability_decayed_monthly_max(t)
|
||||
|
||||
probability_decayed_monthly
|
||||
## probability_decayed_monthly(6)
|
||||
## mean(probability_decayed_monthly(6))
|
38
examples/givedirectly.squiggle
Normal file
38
examples/givedirectly.squiggle
Normal file
|
@ -0,0 +1,38 @@
|
|||
# This is a cost effectiveness analysis of givedirectly, originally done by givewell, and translated into Squiggle by Sam Nolan
|
||||
donation_size = 10000
|
||||
proportion_of_funding_available = beta(10, 2)
|
||||
total_funding_available = donation_size * proportion_of_funding_available
|
||||
household_size = 3.7 to 5.7
|
||||
size_of_transfer = 800 to 1200
|
||||
size_of_transfer_per_person = size_of_transfer / household_size
|
||||
|
||||
portion_invested = 0.3 to 0.5
|
||||
amount_invested = portion_invested * size_of_transfer_per_person
|
||||
amount_consumed = (1 - portion_invested) * size_of_transfer_per_person
|
||||
return_on_investment = 0.08 to 0.12
|
||||
increase_in_consumption_from_investments = return_on_investment * amount_invested
|
||||
baseline_consumption = 200 to 350
|
||||
log_increase_in_consumption = log(amount_consumed + baseline_consumption) + log(baseline_consumption)
|
||||
log_increase_in_consumption_from_investment = log(increase_in_consumption_from_investments + baseline_consumption) + log(baseline_consumption)
|
||||
investment_duration = 8 to 12
|
||||
discount_rate = beta(1.004, 20)
|
||||
|
||||
present_value_excluding_last_year = log_increase_in_consumption_from_investment * (1 - (1 + discount_rate) ^ (-investment_duration)) / (log(1 + discount_rate))
|
||||
|
||||
percent_of_investment_returned = 0.15 to 0.25
|
||||
|
||||
pv_consumption_last_year = (log(baseline_consumption + amount_invested * (return_on_investment + percent_of_investment_returned)) - log(baseline_consumption)) / (1 + discount_rate)^investment_duration
|
||||
|
||||
total_pv_of_cash_transfer = pv_consumption_last_year + present_value_excluding_last_year + log_increase_in_consumption
|
||||
|
||||
discount_negative_spoiler = 0.03 to 0.07
|
||||
|
||||
value_discounting_spoiler = discount_negative_spoiler * total_pv_of_cash_transfer
|
||||
|
||||
consumption_increase_per_household = value_discounting_spoiler * household_size
|
||||
|
||||
amount_of_transfers_made = total_funding_available / size_of_transfer
|
||||
|
||||
total_increase_in_ln_consumption = amount_of_transfers_made * consumption_increase_per_household
|
||||
|
||||
total_increase_in_ln_consumption
|
3
examples/wholenumberassignmentevaluation.squiggle
Normal file
3
examples/wholenumberassignmentevaluation.squiggle
Normal file
|
@ -0,0 +1,3 @@
|
|||
xY1 = 99
|
||||
aBa3 = xY1 * 2 + 1
|
||||
aBa3 * xY1 + aBa3
|
18
nixos.sh
Executable file
18
nixos.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
# This script is only relevant if you're rolling nixos.
|
||||
|
||||
# Esy (a bisect_ppx dependency/build tool) is borked on nixos without using an FHS shell. https://github.com/esy/esy/issues/858
|
||||
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
|
||||
set -x
|
||||
|
||||
fhsShellName="squiggle-development"
|
||||
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn]; runScript = \"yarn\"; }).env"
|
||||
nix-shell - <<<"$fhsShellDotNix"
|
||||
|
||||
theLd=$(patchelf --print-interpreter $(which mkdir))
|
||||
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
|
||||
patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe
|
||||
patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/ppx
|
||||
patchelf --set-interpreter $theLd ./node_moduels/bisect_ppx/bisect-ppx-report
|
||||
theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | grep $fhsShellName | head -n 1)
|
||||
patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe
|
2
packages/components/.prettierignore
Normal file
2
packages/components/.prettierignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
dist/
|
||||
storybook-static
|
|
@ -1,26 +1,45 @@
|
|||
{
|
||||
"name": "@quri/squiggle-components",
|
||||
"version": "0.1.8",
|
||||
"version": "0.2.9",
|
||||
"licence": "MIT",
|
||||
"dependencies": {
|
||||
"@quri/squiggle-lang": "0.2.2",
|
||||
"antd": "^4.20.1",
|
||||
"react-ace": "10.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"@react-hook/size": "^2.1.2",
|
||||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.16.7",
|
||||
"@storybook/addon-actions": "^6.4.22",
|
||||
"@storybook/addon-essentials": "^6.4.22",
|
||||
"@storybook/addon-links": "^6.4.22",
|
||||
"@storybook/builder-webpack5": "^6.4.22",
|
||||
"@storybook/manager-webpack5": "^6.4.22",
|
||||
"@storybook/node-logger": "^6.4.22",
|
||||
"@storybook/preset-create-react-app": "^4.1.0",
|
||||
"@storybook/react": "^6.4.22",
|
||||
"@types/styled-components": "^5.1.24",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^9.2.9",
|
||||
"webpack": "^5.72.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.8.1",
|
||||
"@quri/squiggle-lang": "0.2.5",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.0.1",
|
||||
"@testing-library/user-event": "^14.0.4",
|
||||
"@testing-library/react": "^13.1.1",
|
||||
"@testing-library/user-event": "^14.1.1",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/node": "^17.0.24",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^17.0.29",
|
||||
"@types/react": "^18.0.3",
|
||||
"@types/react-dom": "^18.0.1",
|
||||
"antd": "^4.19.3",
|
||||
"@types/react-dom": "^18.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.0.0",
|
||||
"react-ace": "10.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react": "^18.1.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-vega": "^7.5.0",
|
||||
"styled-components": "^5.3.5",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"typescript": "^4.6.3",
|
||||
"vega": "^5.22.1",
|
||||
|
@ -65,25 +84,6 @@
|
|||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.16.7",
|
||||
"@storybook/addon-actions": "^6.4.22",
|
||||
"@storybook/addon-essentials": "^6.4.22",
|
||||
"@storybook/addon-links": "^6.4.22",
|
||||
"@storybook/builder-webpack5": "^6.4.22",
|
||||
"@storybook/manager-webpack5": "^6.4.22",
|
||||
"@storybook/node-logger": "^6.4.22",
|
||||
"@storybook/preset-create-react-app": "^4.1.0",
|
||||
"@storybook/react": "^6.4.22",
|
||||
"@types/styled-components": "^5.1.24",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"react-codejar": "^1.1.2",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^9.2.8",
|
||||
"webpack": "^5.72.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.8.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.43"
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
errorValueToString,
|
||||
squiggleExpression,
|
||||
} from "@quri/squiggle-lang";
|
||||
import type { samplingParams, exportEnv } from "@quri/squiggle-lang";
|
||||
import type { samplingParams } from "@quri/squiggle-lang";
|
||||
import { NumberShower } from "./NumberShower";
|
||||
import { DistributionChart } from "./DistributionChart";
|
||||
import { ErrorBox } from "./ErrorBox";
|
||||
|
@ -129,9 +129,9 @@ export interface SquiggleChartProps {
|
|||
/** If the result is a function, how many points along the function it samples */
|
||||
diagramCount?: number;
|
||||
/** variables declared before this expression */
|
||||
environment?: exportEnv;
|
||||
environment?: unknown;
|
||||
/** When the environment changes */
|
||||
onEnvChange?(env: exportEnv): void;
|
||||
onChange?(expr: squiggleExpression): void;
|
||||
/** CSS width of the element */
|
||||
width?: number;
|
||||
height?: number;
|
||||
|
@ -141,8 +141,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
|||
squiggleString = "",
|
||||
sampleCount = 1000,
|
||||
outputXYPoints = 1000,
|
||||
environment = [],
|
||||
onEnvChange = () => {},
|
||||
onChange = () => {},
|
||||
height = 60,
|
||||
width = NaN,
|
||||
}: SquiggleChartProps) => {
|
||||
|
@ -155,11 +154,11 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
|||
sampleCount: sampleCount,
|
||||
xyPointLength: outputXYPoints,
|
||||
};
|
||||
let expressionResult = run(squiggleString, samplingInputs, environment);
|
||||
let expressionResult = run(squiggleString, samplingInputs);
|
||||
let internal: JSX.Element;
|
||||
if (expressionResult.tag === "Ok") {
|
||||
onEnvChange(environment);
|
||||
let expression = expressionResult.value;
|
||||
onChange(expression);
|
||||
internal = (
|
||||
<SquiggleItem expression={expression} width={_width} height={height} />
|
||||
);
|
||||
|
|
|
@ -2,8 +2,8 @@ import * as React from "react";
|
|||
import * as ReactDOM from "react-dom";
|
||||
import { SquiggleChart } from "./SquiggleChart";
|
||||
import { CodeEditor } from "./CodeEditor";
|
||||
import type { exportEnv } from "@quri/squiggle-lang";
|
||||
import styled from "styled-components";
|
||||
import type { squiggleExpression } from "@quri/squiggle-lang";
|
||||
|
||||
export interface SquiggleEditorProps {
|
||||
/** The input string for squiggle */
|
||||
|
@ -21,9 +21,9 @@ export interface SquiggleEditorProps {
|
|||
/** If the result is a function, how many points along the function it samples */
|
||||
diagramCount?: number;
|
||||
/** The environment, other variables that were already declared */
|
||||
environment?: exportEnv;
|
||||
environment?: unknown;
|
||||
/** when the environment changes. Used again for notebook magic*/
|
||||
onEnvChange?(env: exportEnv): void;
|
||||
onChange?(expr: squiggleExpression): void;
|
||||
/** The width of the element */
|
||||
width: number;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
|||
diagramStart,
|
||||
diagramStop,
|
||||
diagramCount,
|
||||
onEnvChange,
|
||||
onChange,
|
||||
environment,
|
||||
}: SquiggleEditorProps) => {
|
||||
let [expression, setExpression] = React.useState(initialSquiggleString);
|
||||
|
@ -70,7 +70,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
|||
diagramStop={diagramStop}
|
||||
diagramCount={diagramCount}
|
||||
environment={environment}
|
||||
onEnvChange={onEnvChange}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -81,7 +81,7 @@ export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
|
|||
ReactDOM.render(
|
||||
<SquiggleEditor
|
||||
{...props}
|
||||
onEnvChange={(env) => {
|
||||
onChange={(expr) => {
|
||||
// Typescript complains on two levels here.
|
||||
// - Div elements don't have a value property
|
||||
// - Even if it did (like it was an input element), it would have to
|
||||
|
@ -97,10 +97,10 @@ export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
|
|||
// viewof env = cell('normal(0,1)')
|
||||
// to work
|
||||
// @ts-ignore
|
||||
parent.value = env;
|
||||
parent.value = expr;
|
||||
|
||||
parent.dispatchEvent(new CustomEvent("input"));
|
||||
if (props.onEnvChange) props.onEnvChange(env);
|
||||
if (props.onChange) props.onChange(expr);
|
||||
}}
|
||||
/>,
|
||||
parent
|
||||
|
|
2
packages/squiggle-lang/.gitignore
vendored
2
packages/squiggle-lang/.gitignore
vendored
|
@ -19,3 +19,5 @@ yarn-error.log
|
|||
dist
|
||||
*.coverage
|
||||
_coverage
|
||||
coverage
|
||||
.nyc_output/
|
||||
|
|
|
@ -9,3 +9,7 @@ examples
|
|||
yarn.nix
|
||||
bsconfig.json
|
||||
tsconfig.json
|
||||
.nyc_outputs
|
||||
*.coverage
|
||||
_coverage
|
||||
coverage
|
||||
|
|
|
@ -2,3 +2,6 @@ dist
|
|||
lib
|
||||
*.bs.js
|
||||
*.gen.tsx
|
||||
.nyc_output/
|
||||
coverage/
|
||||
.cache/
|
|
@ -0,0 +1,17 @@
|
|||
open Jest
|
||||
open TestHelpers
|
||||
|
||||
describe("Combining Continuous and Discrete Distributions", () => {
|
||||
makeTest(
|
||||
"keep order of xs when multiplying by negative number",
|
||||
AlgebraicShapeCombination.isOrdered(
|
||||
AlgebraicShapeCombination.combineShapesContinuousDiscrete(
|
||||
#Multiply,
|
||||
{xs: [0., 1.], ys: [1., 1.]},
|
||||
{xs: [-1.], ys: [1.]},
|
||||
~discretePosition=Second,
|
||||
),
|
||||
), // Multiply distribution by -1
|
||||
true,
|
||||
)
|
||||
})
|
|
@ -30,7 +30,7 @@ let toExt: option<'a> => 'a = E.O.toExt(
|
|||
describe("sparkline", () => {
|
||||
let runTest = (
|
||||
name: string,
|
||||
dist: GenericDist_Types.genericDist,
|
||||
dist: DistributionTypes.genericDist,
|
||||
expected: DistributionOperation.outputType,
|
||||
) => {
|
||||
test(name, () => {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
let normalDist5: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0}))
|
||||
let normalDist10: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 10.0, stdev: 2.0}))
|
||||
let normalDist20: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 20.0, stdev: 2.0}))
|
||||
let normalDist: GenericDist_Types.genericDist = normalDist5
|
||||
let normalDist5: DistributionTypes.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0}))
|
||||
let normalDist10: DistributionTypes.genericDist = Symbolic(#Normal({mean: 10.0, stdev: 2.0}))
|
||||
let normalDist20: DistributionTypes.genericDist = Symbolic(#Normal({mean: 20.0, stdev: 2.0}))
|
||||
let normalDist: DistributionTypes.genericDist = normalDist5
|
||||
|
||||
let betaDist: GenericDist_Types.genericDist = Symbolic(#Beta({alpha: 2.0, beta: 5.0}))
|
||||
let lognormalDist: GenericDist_Types.genericDist = Symbolic(#Lognormal({mu: 0.0, sigma: 1.0}))
|
||||
let cauchyDist: GenericDist_Types.genericDist = Symbolic(#Cauchy({local: 1.0, scale: 1.0}))
|
||||
let triangularDist: GenericDist_Types.genericDist = Symbolic(
|
||||
let betaDist: DistributionTypes.genericDist = Symbolic(#Beta({alpha: 2.0, beta: 5.0}))
|
||||
let lognormalDist: DistributionTypes.genericDist = Symbolic(#Lognormal({mu: 0.0, sigma: 1.0}))
|
||||
let cauchyDist: DistributionTypes.genericDist = Symbolic(#Cauchy({local: 1.0, scale: 1.0}))
|
||||
let triangularDist: DistributionTypes.genericDist = Symbolic(
|
||||
#Triangular({low: 1.0, medium: 2.0, high: 3.0}),
|
||||
)
|
||||
let exponentialDist: GenericDist_Types.genericDist = Symbolic(#Exponential({rate: 2.0}))
|
||||
let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0}))
|
||||
let floatDist: GenericDist_Types.genericDist = Symbolic(#Float(1e1))
|
||||
let exponentialDist: DistributionTypes.genericDist = Symbolic(#Exponential({rate: 2.0}))
|
||||
let uniformDist: DistributionTypes.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0}))
|
||||
let floatDist: DistributionTypes.genericDist = Symbolic(#Float(1e1))
|
||||
|
|
|
@ -43,10 +43,10 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
test("normal(mean=5) + normal(mean=20)", () => {
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist20)
|
||||
->E.R2.fmap(GenericDist_Types.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
->expect
|
||||
->toBe(Some(2.5e1))
|
||||
})
|
||||
|
@ -57,15 +57,15 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(GenericDist_Types.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.01927225696028752, ~digits=1) // (uniformMean +. betaMean)
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.786831807237022, ~digits=1) // (uniformMean +. betaMean)
|
||||
}
|
||||
})
|
||||
test("beta(alpha=2, beta=5) + uniform(low=9, high=10)", () => {
|
||||
|
@ -74,15 +74,15 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(GenericDist_Types.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.019275414920485248, ~digits=1) // (uniformMean +. betaMean)
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.784290207736126, ~digits=1) // (uniformMean +. betaMean)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -95,7 +95,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
normalDist10 // this should be normal(10, sqrt(8))
|
||||
->Ok
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, x))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -103,7 +103,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let calculated =
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist5)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, x))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -126,7 +126,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -134,7 +134,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -155,30 +155,30 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=4.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.001978994877226945, ~digits=3)
|
||||
// This value was calculated by a python script
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
||||
}
|
||||
})
|
||||
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)", () => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=4.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.001978994877226945, ~digits=3)
|
||||
// This is nondeterministic.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -187,7 +187,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
normalDist10
|
||||
->Ok
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -195,7 +195,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let calculated =
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist5)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -217,7 +217,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -225,7 +225,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -246,30 +246,30 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=4.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.0013961779932477507, ~digits=3)
|
||||
// The value was calculated externally using a python script
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
||||
}
|
||||
})
|
||||
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)", () => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=4.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.001388898111625753, ~digits=3)
|
||||
// The value was calculated externally using a python script
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -279,7 +279,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
normalDist10
|
||||
->Ok
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -287,7 +287,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let calculated =
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist5)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -309,7 +309,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -317,7 +317,7 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
|
@ -338,30 +338,30 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(10.927078217530806, ~digits=0)
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.179319623146968, ~digits=0)
|
||||
}
|
||||
})
|
||||
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)", () => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(10.915396627014363, ~digits=0)
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.190872365862756, ~digits=0)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -15,7 +15,7 @@ open TestHelpers
|
|||
module Internals = {
|
||||
let epsilon = 5e1
|
||||
|
||||
let mean = GenericDist_Types.Constructors.UsingDists.mean
|
||||
let mean = DistributionTypes.Constructors.UsingDists.mean
|
||||
|
||||
let expectImpossiblePath: string => assertion = algebraicOp =>
|
||||
`${algebraicOp} has`->expect->toEqual("failed")
|
||||
|
@ -50,7 +50,11 @@ module Internals = {
|
|||
let dist1 = dist1'->DistributionTypes.Symbolic
|
||||
let dist2 = dist2'->DistributionTypes.Symbolic
|
||||
let received =
|
||||
distOp(dist1, dist2)->E.R2.fmap(mean)->E.R2.fmap(run)->E.R2.fmap(toFloat)->E.R.toExn
|
||||
distOp(dist1, dist2)
|
||||
->E.R2.fmap(mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
let expected = floatOp(runMean(dist1), runMean(dist2))
|
||||
switch received {
|
||||
| None => expectImpossiblePath(description)
|
||||
|
@ -80,14 +84,16 @@ let {testOperationMean, distributions, pairsOfDifferentDistributions, epsilon} =
|
|||
describe("Means are invariant", () => {
|
||||
describe("for addition", () => {
|
||||
let testAdditionMean = testOperationMean(algebraicAdd, "algebraicAdd", \"+.", ~epsilon)
|
||||
let testAddInvariant = (t1, t2) =>
|
||||
E.R.liftM2(testAdditionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
||||
|
||||
testAll("with two of the same distribution", distributions, dist => {
|
||||
E.R.liftM2(testAdditionMean, dist, dist)->E.R.toExn
|
||||
testAddInvariant(dist, dist)
|
||||
})
|
||||
|
||||
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
|
||||
let (dist1, dist2) = dists
|
||||
E.R.liftM2(testAdditionMean, dist1, dist2)->E.R.toExn
|
||||
testAddInvariant(dist1, dist2)
|
||||
})
|
||||
|
||||
testAll(
|
||||
|
@ -95,7 +101,7 @@ describe("Means are invariant", () => {
|
|||
pairsOfDifferentDistributions,
|
||||
dists => {
|
||||
let (dist1, dist2) = dists
|
||||
E.R.liftM2(testAdditionMean, dist2, dist1)->E.R.toExn
|
||||
testAddInvariant(dist1, dist2)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -107,14 +113,16 @@ describe("Means are invariant", () => {
|
|||
\"-.",
|
||||
~epsilon,
|
||||
)
|
||||
let testSubtractInvariant = (t1, t2) =>
|
||||
E.R.liftM2(testSubtractionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
||||
|
||||
testAll("with two of the same distribution", distributions, dist => {
|
||||
E.R.liftM2(testSubtractionMean, dist, dist)->E.R.toExn
|
||||
testSubtractInvariant(dist, dist)
|
||||
})
|
||||
|
||||
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
|
||||
let (dist1, dist2) = dists
|
||||
E.R.liftM2(testSubtractionMean, dist1, dist2)->E.R.toExn
|
||||
testSubtractInvariant(dist1, dist2)
|
||||
})
|
||||
|
||||
testAll(
|
||||
|
@ -122,7 +130,7 @@ describe("Means are invariant", () => {
|
|||
pairsOfDifferentDistributions,
|
||||
dists => {
|
||||
let (dist1, dist2) = dists
|
||||
E.R.liftM2(testSubtractionMean, dist2, dist1)->E.R.toExn
|
||||
testSubtractInvariant(dist1, dist2)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -134,14 +142,16 @@ describe("Means are invariant", () => {
|
|||
\"*.",
|
||||
~epsilon,
|
||||
)
|
||||
let testMultiplicationInvariant = (t1, t2) =>
|
||||
E.R.liftM2(testMultiplicationMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
||||
|
||||
testAll("with two of the same distribution", distributions, dist => {
|
||||
E.R.liftM2(testMultiplicationMean, dist, dist)->E.R.toExn
|
||||
testMultiplicationInvariant(dist, dist)
|
||||
})
|
||||
|
||||
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
|
||||
let (dist1, dist2) = dists
|
||||
E.R.liftM2(testMultiplicationMean, dist1, dist2)->E.R.toExn
|
||||
testMultiplicationInvariant(dist1, dist2)
|
||||
})
|
||||
|
||||
testAll(
|
||||
|
@ -149,7 +159,7 @@ describe("Means are invariant", () => {
|
|||
pairsOfDifferentDistributions,
|
||||
dists => {
|
||||
let (dist1, dist2) = dists
|
||||
E.R.liftM2(testMultiplicationMean, dist2, dist1)->E.R.toExn
|
||||
testMultiplicationInvariant(dist1, dist2)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
open Jest
|
||||
open Expect
|
||||
|
||||
describe("Converting from a sample set distribution", () => {
|
||||
test("Should be normalized", () => {
|
||||
let outputXYShape = SampleSetDist_ToPointSet.Internals.KDE.normalSampling(
|
||||
[1., 2., 3., 3., 4., 5., 5., 5., 6., 8., 9., 9.],
|
||||
50,
|
||||
2,
|
||||
)
|
||||
let c: PointSetTypes.continuousShape = {
|
||||
xyShape: outputXYShape,
|
||||
interpolation: #Linear,
|
||||
integralSumCache: None,
|
||||
integralCache: None,
|
||||
}
|
||||
|
||||
expect(Continuous.isNormalized(c))->toBe(true)
|
||||
})
|
||||
})
|
|
@ -1,40 +0,0 @@
|
|||
open Jest
|
||||
open TestHelpers
|
||||
|
||||
describe("Continuous and discrete splits", () => {
|
||||
makeTest(
|
||||
"splits (1)",
|
||||
SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete([1.432, 1.33455, 2.0]),
|
||||
([1.432, 1.33455, 2.0], E.FloatFloatMap.empty()),
|
||||
)
|
||||
makeTest(
|
||||
"splits (2)",
|
||||
SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete([
|
||||
1.432,
|
||||
1.33455,
|
||||
2.0,
|
||||
2.0,
|
||||
2.0,
|
||||
2.0,
|
||||
]) |> (((c, disc)) => (c, disc |> E.FloatFloatMap.toArray)),
|
||||
([1.432, 1.33455], [(2.0, 4.0)]),
|
||||
)
|
||||
|
||||
let makeDuplicatedArray = count => {
|
||||
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
|
||||
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
|
||||
E.A.concatMany([sorted, sorted, sorted, sorted]) |> Belt.SortArray.stableSortBy(_, compare)
|
||||
}
|
||||
|
||||
let (_, discrete1) = SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete(
|
||||
makeDuplicatedArray(10),
|
||||
)
|
||||
let toArr1 = discrete1 |> E.FloatFloatMap.toArray
|
||||
makeTest("splitMedium at count=10", toArr1 |> Belt.Array.length, 10)
|
||||
|
||||
let (_c, discrete2) = SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete(
|
||||
makeDuplicatedArray(500),
|
||||
)
|
||||
let toArr2 = discrete2 |> E.FloatFloatMap.toArray
|
||||
makeTest("splitMedium at count=500", toArr2 |> Belt.Array.length, 500)
|
||||
})
|
|
@ -0,0 +1,48 @@
|
|||
open Jest
|
||||
open TestHelpers
|
||||
|
||||
let prepareInputs = (ar, minWeight) =>
|
||||
E.A.Sorted.Floats.splitContinuousAndDiscreteForMinWeight(ar, ~minDiscreteWeight=minWeight) |> (
|
||||
((c, disc)) => (c, disc |> E.FloatFloatMap.toArray)
|
||||
)
|
||||
|
||||
describe("Continuous and discrete splits", () => {
|
||||
makeTest(
|
||||
"is empty, with no common elements",
|
||||
prepareInputs([1.432, 1.33455, 2.0], 2),
|
||||
([1.33455, 1.432, 2.0], []),
|
||||
)
|
||||
|
||||
makeTest(
|
||||
"only stores 3.5 as discrete when minWeight is 3",
|
||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
|
||||
([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
|
||||
)
|
||||
|
||||
makeTest(
|
||||
"doesn't store 3.5 as discrete when minWeight is 5",
|
||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
|
||||
([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []),
|
||||
)
|
||||
|
||||
let makeDuplicatedArray = count => {
|
||||
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
|
||||
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
|
||||
E.A.concatMany([sorted, sorted, sorted, sorted]) |> Belt.SortArray.stableSortBy(_, compare)
|
||||
}
|
||||
|
||||
let (_, discrete1) = E.A.Sorted.Floats.splitContinuousAndDiscreteForMinWeight(
|
||||
makeDuplicatedArray(10),
|
||||
~minDiscreteWeight=2,
|
||||
)
|
||||
let toArr1 = discrete1 |> E.FloatFloatMap.toArray
|
||||
makeTest("splitMedium at count=10", toArr1 |> Belt.Array.length, 10)
|
||||
|
||||
let (_c, discrete2) = E.A.Sorted.Floats.splitContinuousAndDiscreteForMinWeight(
|
||||
makeDuplicatedArray(500),
|
||||
~minDiscreteWeight=2,
|
||||
)
|
||||
let toArr2 = discrete2 |> E.FloatFloatMap.toArray
|
||||
makeTest("splitMedium at count=500", toArr2 |> Belt.Array.length, 500)
|
||||
// makeTest("foo", [] |> Belt.Array.length, 500)
|
||||
})
|
|
@ -7,5 +7,69 @@ open Expect
|
|||
let expectParseToBe = (expr: string, answer: string) =>
|
||||
Reducer.parse(expr)->Expression.toStringResult->expect->toBe(answer)
|
||||
|
||||
let expectParseOuterToBe = (expr: string, answer: string) =>
|
||||
Reducer.parseOuter(expr)->Expression.toStringResult->expect->toBe(answer)
|
||||
|
||||
let expectParsePartialToBe = (expr: string, answer: string) =>
|
||||
Reducer.parsePartial(expr)->Expression.toStringResult->expect->toBe(answer)
|
||||
|
||||
let expectEvalToBe = (expr: string, answer: string) =>
|
||||
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
||||
|
||||
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
||||
Reducer.evaluateUsingExternalBindings(expr, bindings)
|
||||
->ExpressionValue.toStringResult
|
||||
->expect
|
||||
->toBe(answer)
|
||||
|
||||
let expectEvalPartialBindingsToBe = (
|
||||
expr: string,
|
||||
bindings: Reducer.externalBindings,
|
||||
answer: string,
|
||||
) =>
|
||||
Reducer.evaluatePartialUsingExternalBindings(expr, bindings)
|
||||
->ExpressionValue.toStringResultRecord
|
||||
->expect
|
||||
->toBe(answer)
|
||||
|
||||
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||
let testParseOuterToBe = (expr, answer) => test(expr, () => expectParseOuterToBe(expr, answer))
|
||||
let testParsePartialToBe = (expr, answer) => test(expr, () => expectParsePartialToBe(expr, answer))
|
||||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||
test(desc, () => expectParseToBe(expr, answer))
|
||||
|
||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||
test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
let testEvalPartialBindingsToBe = (expr, bindingsList, answer) =>
|
||||
test(expr, () => expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
|
||||
module MySkip = {
|
||||
let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||
let testParseOuterToBe = (expr, answer) =>
|
||||
Skip.test(expr, () => expectParseOuterToBe(expr, answer))
|
||||
let testParsePartialToBe = (expr, answer) =>
|
||||
Skip.test(expr, () => expectParsePartialToBe(expr, answer))
|
||||
let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer))
|
||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Skip.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
let testEvalPartialBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Skip.test(expr, () =>
|
||||
expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)
|
||||
)
|
||||
}
|
||||
module MyOnly = {
|
||||
let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
||||
let testParseOuterToBe = (expr, answer) =>
|
||||
Only.test(expr, () => expectParseOuterToBe(expr, answer))
|
||||
let testParsePartialToBe = (expr, answer) =>
|
||||
Only.test(expr, () => expectParsePartialToBe(expr, answer))
|
||||
let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer))
|
||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Only.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
||||
let testEvalPartialBindingsToBe = (expr, bindingsList, answer) =>
|
||||
Only.test(expr, () =>
|
||||
expectEvalPartialBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
describe("Parse for Bindings", () => {
|
||||
testParseOuterToBe("x", "Ok((:$$bindExpression (:$$bindings) :x))")
|
||||
testParseOuterToBe("x+1", "Ok((:$$bindExpression (:$$bindings) (:add :x 1)))")
|
||||
testParseOuterToBe(
|
||||
"y = x+1; y",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) :y))",
|
||||
)
|
||||
})
|
||||
|
||||
describe("Parse Partial", () => {
|
||||
testParsePartialToBe(
|
||||
"x",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) :x) (:$exportVariablesExpression)))",
|
||||
)
|
||||
testParsePartialToBe(
|
||||
"y=x",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y :x)) (:$exportVariablesExpression)))",
|
||||
)
|
||||
testParsePartialToBe(
|
||||
"y=x+1",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$exportVariablesExpression)))",
|
||||
)
|
||||
testParsePartialToBe(
|
||||
"y = x+1; z = y",
|
||||
"Ok((:$$bindExpression (:$$bindStatement (:$$bindStatement (:$$bindings) (:$let :y (:add :x 1))) (:$let :z :y)) (:$exportVariablesExpression)))",
|
||||
)
|
||||
})
|
||||
|
||||
describe("Eval with Bindings", () => {
|
||||
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
|
||||
testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||
})
|
||||
|
||||
/*
|
||||
Partial code is a partial code fragment that is cut out from a larger code.
|
||||
Therefore it does not end with an expression.
|
||||
*/
|
||||
describe("Eval Partial", () => {
|
||||
testEvalPartialBindingsToBe(
|
||||
// A partial cannot end with an expression
|
||||
"x",
|
||||
list{("x", ExpressionValue.EvNumber(1.))},
|
||||
"Error(Assignment expected)",
|
||||
)
|
||||
testEvalPartialBindingsToBe("y=x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1, y: 1})")
|
||||
testEvalPartialBindingsToBe(
|
||||
"y=x+1",
|
||||
list{("x", ExpressionValue.EvNumber(1.))},
|
||||
"Ok({x: 1, y: 2})",
|
||||
)
|
||||
testEvalPartialBindingsToBe(
|
||||
"y = x+1; z = y",
|
||||
list{("x", ExpressionValue.EvNumber(1.))},
|
||||
"Ok({x: 1, y: 2, z: 2})",
|
||||
)
|
||||
})
|
|
@ -0,0 +1,12 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
Skip.describe("Parse ternary operator", () => {
|
||||
testParseToBe("true ? 'YES' : 'NO'", "Ok('YES')")
|
||||
testParseToBe("false ? 'YES' : 'NO'", "Ok('NO')")
|
||||
})
|
||||
|
||||
Skip.describe("Evaluate ternary operator", () => {
|
||||
testEvalToBe("true ? 'YES' : 'NO'", "Ok('YES')")
|
||||
testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')")
|
||||
})
|
|
@ -1,15 +1,6 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||
|
||||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||
test(desc, () => expectParseToBe(expr, answer))
|
||||
|
||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||
|
||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||
|
||||
describe("reducer using mathjs parse", () => {
|
||||
// Test the MathJs parser compatibility
|
||||
// Those tests toString that there is a semantic mapping from MathJs to Expression
|
||||
|
|
|
@ -20,9 +20,10 @@ describe("eval on distribution functions", () => {
|
|||
})
|
||||
describe("unaryMinus", () => {
|
||||
testEval("mean(-normal(5,2))", "Ok(-5)")
|
||||
testEval("-normal(5,2)", "Ok(Normal(-5,2))")
|
||||
})
|
||||
describe("to", () => {
|
||||
testEval("5 to 2", "Error(TODO: Low value must be less than high value.)")
|
||||
testEval("5 to 2", "Error(Distribution Math Error: Low value must be less than high value.)")
|
||||
testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))")
|
||||
testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))")
|
||||
})
|
||||
|
@ -53,6 +54,7 @@ describe("eval on distribution functions", () => {
|
|||
describe("subtract", () => {
|
||||
testEval("10 - normal(5, 1)", "Ok(Normal(5,1))")
|
||||
testEval("normal(5, 1) - 10", "Ok(Normal(-5,1))")
|
||||
testEval("mean(1 - toPointSet(normal(5, 2)))", "Ok(-4.002309896304692)")
|
||||
})
|
||||
describe("multiply", () => {
|
||||
testEval("normal(10, 2) * 2", "Ok(Normal(20,4))")
|
||||
|
@ -67,7 +69,7 @@ describe("eval on distribution functions", () => {
|
|||
testEval("lognormal(10,2) / lognormal(5,2)", "Ok(Lognormal(5,2.8284271247461903))")
|
||||
testEval("lognormal(5, 2) / 2", "Ok(Lognormal(4.306852819440055,2))")
|
||||
testEval("2 / lognormal(5, 2)", "Ok(Lognormal(-4.306852819440055,2))")
|
||||
testEval("2 / normal(10, 2)", "Ok(Point Set Distribution)")
|
||||
testEval("2 / normal(10, 2)", "Ok(Sample Set Distribution)")
|
||||
testEval("normal(10, 2) / 2", "Ok(Normal(5,1))")
|
||||
})
|
||||
describe("truncate", () => {
|
||||
|
@ -77,27 +79,27 @@ describe("eval on distribution functions", () => {
|
|||
})
|
||||
|
||||
describe("exp", () => {
|
||||
testEval("exp(normal(5,2))", "Ok(Point Set Distribution)")
|
||||
testEval("exp(normal(5,2))", "Ok(Sample Set Distribution)")
|
||||
})
|
||||
|
||||
describe("pow", () => {
|
||||
testEval("pow(3, uniform(5,8))", "Ok(Point Set Distribution)")
|
||||
testEval("pow(uniform(5,8), 3)", "Ok(Point Set Distribution)")
|
||||
testEval("pow(3, uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||
testEval("pow(uniform(5,8), 3)", "Ok(Sample Set Distribution)")
|
||||
testEval("pow(uniform(5,8), uniform(9, 10))", "Ok(Sample Set Distribution)")
|
||||
})
|
||||
|
||||
describe("log", () => {
|
||||
testEval("log(2, uniform(5,8))", "Ok(Point Set Distribution)")
|
||||
testEval("log(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
||||
testEval("log(normal(5,2), normal(10,1))", "Ok(Sample Set Distribution)")
|
||||
testEval("log(uniform(5,8))", "Ok(Point Set Distribution)")
|
||||
testEval("log10(uniform(5,8))", "Ok(Point Set Distribution)")
|
||||
})
|
||||
|
||||
describe("dotLog", () => {
|
||||
testEval("dotLog(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
||||
testEval("dotLog(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
||||
testEval("dotLog(normal(5,2), normal(10,1))", "Ok(Point Set Distribution)")
|
||||
testEval("log(2, uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||
testEval(
|
||||
"log(normal(5,2), 3)",
|
||||
"Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)",
|
||||
)
|
||||
testEval(
|
||||
"log(normal(5,2), normal(10,1))",
|
||||
"Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)",
|
||||
)
|
||||
testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||
testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||
})
|
||||
|
||||
describe("dotAdd", () => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
resultMap,
|
||||
squiggleExpression,
|
||||
errorValueToString,
|
||||
} from "../src/js/index";
|
||||
} from "../../src/js/index";
|
||||
|
||||
let testRun = (x: string): squiggleExpression => {
|
||||
let result = run(x, { sampleCount: 100, xyPointLength: 100 });
|
||||
|
@ -46,6 +46,8 @@ describe("Distribution", () => {
|
|||
//It's important that sampleCount is less than 9. If it's more, than that will create randomness
|
||||
//Also, note, the value should be created using makeSampleSetDist() later on.
|
||||
let env = { sampleCount: 8, xyPointLength: 100 };
|
||||
let dist1Samples = [3, 4, 5, 6, 6, 7, 10, 15, 30];
|
||||
let dist1SampleCount = dist1Samples.length;
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: [3, 4, 5, 6, 6, 7, 10, 15, 30] },
|
||||
env
|
||||
|
@ -56,16 +58,19 @@ describe("Distribution", () => {
|
|||
);
|
||||
|
||||
test("mean", () => {
|
||||
expect(dist.mean().value).toBeCloseTo(3.737);
|
||||
expect(dist.mean().value).toBeCloseTo(9.5555555);
|
||||
});
|
||||
test("pdf", () => {
|
||||
expect(dist.pdf(5.0).value).toBeCloseTo(0.0431);
|
||||
expect(dist.pdf(5.0).value).toBeCloseTo(0.10499097598222966, 1);
|
||||
});
|
||||
test("cdf", () => {
|
||||
expect(dist.cdf(5.0).value).toBeCloseTo(0.155);
|
||||
expect(dist.cdf(5.0).value).toBeCloseTo(
|
||||
dist1Samples.filter((x) => x <= 5).length / dist1SampleCount,
|
||||
1
|
||||
);
|
||||
});
|
||||
test("inv", () => {
|
||||
expect(dist.inv(0.5).value).toBeCloseTo(9.458);
|
||||
expect(dist.inv(0.5).value).toBeCloseTo(6);
|
||||
});
|
||||
test("toPointSet", () => {
|
||||
expect(
|
||||
|
@ -73,7 +78,7 @@ describe("Distribution", () => {
|
|||
).toEqual(Ok("Point Set Distribution"));
|
||||
});
|
||||
test("toSparkline", () => {
|
||||
expect(dist.toSparkline(20).value).toEqual("▁▁▃▅███▆▄▃▂▁▁▂▂▃▂▁▁▁");
|
||||
expect(dist.toSparkline(20).value).toEqual("▁▁▃▇█▇▄▂▂▂▁▁▁▁▁▂▂▁▁▁");
|
||||
});
|
||||
test("algebraicAdd", () => {
|
||||
expect(
|
||||
|
@ -87,6 +92,6 @@ describe("Distribution", () => {
|
|||
resultMap(dist.pointwiseAdd(dist2), (r: Distribution) =>
|
||||
r.toSparkline(20)
|
||||
).value
|
||||
).toEqual(Ok("▁▂▅██▅▅▅▆▇█▆▅▃▃▂▂▁▁▁"));
|
||||
).toEqual(Ok("▁▂██▃▃▃▃▄▅▄▃▃▂▂▂▁▁▁▁"));
|
||||
});
|
||||
});
|
27
packages/squiggle-lang/__tests__/TS/Jstat_test.ts
Normal file
27
packages/squiggle-lang/__tests__/TS/Jstat_test.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
// import { errorValueToString } from "../../src/js/index";
|
||||
import * as fc from "fast-check";
|
||||
import { testRun } from "./TestHelpers";
|
||||
|
||||
describe("cumulative density function of a normal distribution", () => {
|
||||
test("at 3 stdevs to the right of the mean is near 1", () => {
|
||||
fc.assert(
|
||||
fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => {
|
||||
let threeStdevsAboveMean = mean + 3 * stdev;
|
||||
let squiggleString = `cdf(normal(${mean}, ${stdev}), ${threeStdevsAboveMean})`;
|
||||
let squiggleResult = testRun(squiggleString);
|
||||
expect(squiggleResult.value).toBeCloseTo(1);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("at 3 stdevs to the left of the mean is near 0", () => {
|
||||
fc.assert(
|
||||
fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => {
|
||||
let threeStdevsBelowMean = mean - 3 * stdev;
|
||||
let squiggleString = `cdf(normal(${mean}, ${stdev}), ${threeStdevsBelowMean})`;
|
||||
let squiggleResult = testRun(squiggleString);
|
||||
expect(squiggleResult.value).toBeCloseTo(0);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
52
packages/squiggle-lang/__tests__/TS/Parser_test.ts
Normal file
52
packages/squiggle-lang/__tests__/TS/Parser_test.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import {
|
||||
run,
|
||||
squiggleExpression,
|
||||
errorValue,
|
||||
result,
|
||||
} from "../../src/js/index";
|
||||
import { testRun } from "./TestHelpers";
|
||||
import * as fc from "fast-check";
|
||||
|
||||
describe("Squiggle's parser is whitespace insensitive", () => {
|
||||
test("when assigning a distribution to a name and calling that name", () => {
|
||||
// intersperse varying amounts of whitespace in a squiggle string
|
||||
let squiggleString = (
|
||||
a: string,
|
||||
b: string,
|
||||
c: string,
|
||||
d: string,
|
||||
e: string,
|
||||
f: string,
|
||||
g: string,
|
||||
h: string
|
||||
): string => {
|
||||
return `theDist${a}=${b}beta(${c}4${d},${e}5e1)${f};${g}theDist${h}`;
|
||||
};
|
||||
let squiggleOutput = testRun(
|
||||
squiggleString("", "", "", "", "", "", "", "")
|
||||
);
|
||||
|
||||
// Add "\n" to this when multiline is introduced.
|
||||
let whitespaceGen = () => {
|
||||
return fc.constantFrom("", " ", "\t", " ", " ", " ", " ");
|
||||
};
|
||||
|
||||
fc.assert(
|
||||
fc.property(
|
||||
whitespaceGen(),
|
||||
whitespaceGen(),
|
||||
whitespaceGen(),
|
||||
whitespaceGen(),
|
||||
whitespaceGen(),
|
||||
whitespaceGen(),
|
||||
whitespaceGen(),
|
||||
whitespaceGen(),
|
||||
(a, b, c, d, e, f, g, h) => {
|
||||
expect(testRun(squiggleString(a, b, c, d, e, f, g, h))).toEqual(
|
||||
squiggleOutput
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
37
packages/squiggle-lang/__tests__/TS/PointSet_test.ts
Normal file
37
packages/squiggle-lang/__tests__/TS/PointSet_test.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
// import { errorValueToString } from "../../src/js/index";
|
||||
import { testRun, expectErrorToBeBounded } from "./TestHelpers";
|
||||
import * as fc from "fast-check";
|
||||
|
||||
describe("Mean of mixture is weighted average of means", () => {
|
||||
test.skip("mx(beta(a,b), lognormal(m,s), [x,y])", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.float({ min: 1e-1 }), // alpha
|
||||
fc.float({ min: 1 }), // beta
|
||||
fc.float(), // mu
|
||||
fc.float({ min: 1e-1 }), // sigma
|
||||
fc.float({ min: 1e-7 }),
|
||||
fc.float({ min: 1e-7 }),
|
||||
(a, b, m, s, x, y) => {
|
||||
let squiggleString = `mean(mixture(beta(${a},${b}), lognormal(${m},${s}), [${x}, ${y}]))`;
|
||||
let res = testRun(squiggleString);
|
||||
let weightDenom = x + y;
|
||||
let betaWeight = x / weightDenom;
|
||||
let lognormalWeight = y / weightDenom;
|
||||
let betaMean = 1 / (1 + b / a);
|
||||
let lognormalMean = m + s ** 2 / 2;
|
||||
if (res.tag == "number") {
|
||||
expectErrorToBeBounded(
|
||||
res.value,
|
||||
betaWeight * betaMean + lognormalWeight * lognormalMean,
|
||||
1,
|
||||
2
|
||||
);
|
||||
} else {
|
||||
expect(res.value).toEqual("some error message");
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
214
packages/squiggle-lang/__tests__/TS/SampleSet_test.ts
Normal file
214
packages/squiggle-lang/__tests__/TS/SampleSet_test.ts
Normal file
|
@ -0,0 +1,214 @@
|
|||
import { Distribution } from "../../src/js/index";
|
||||
import { expectErrorToBeBounded, failDefault } from "./TestHelpers";
|
||||
import * as fc from "fast-check";
|
||||
|
||||
// Beware: float64Array makes it appear in an infinite loop.
|
||||
let arrayGen = () =>
|
||||
fc.float32Array({
|
||||
minLength: 10,
|
||||
maxLength: 10000,
|
||||
noDefaultInfinity: true,
|
||||
noNaN: true,
|
||||
});
|
||||
|
||||
describe("cumulative density function", () => {
|
||||
let n = 10000;
|
||||
|
||||
// We should fix this.
|
||||
test.skip("'s codomain is bounded above", () => {
|
||||
fc.assert(
|
||||
fc.property(arrayGen(), fc.float(), (xs_, x) => {
|
||||
let xs = Array.from(xs_);
|
||||
// Should compute with squiggle strings once interpreter has `sample`
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: n, xyPointLength: 100 }
|
||||
);
|
||||
let cdfValue = dist.cdf(x).value;
|
||||
let epsilon = 5e-7;
|
||||
expect(cdfValue).toBeLessThanOrEqual(1 + epsilon);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("'s codomain is bounded below", () => {
|
||||
fc.assert(
|
||||
fc.property(arrayGen(), fc.float(), (xs_, x) => {
|
||||
let xs = Array.from(xs_);
|
||||
// Should compute with squiggle strings once interpreter has `sample`
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: n, xyPointLength: 100 }
|
||||
);
|
||||
let cdfValue = dist.cdf(x).value;
|
||||
expect(cdfValue).toBeGreaterThanOrEqual(0);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// This may not be true due to KDE estimating there to be mass above the
|
||||
// highest value. These tests fail
|
||||
test.skip("at the highest number in the sample is close to 1", () => {
|
||||
fc.assert(
|
||||
fc.property(arrayGen(), (xs_) => {
|
||||
let xs = Array.from(xs_);
|
||||
let max = Math.max(...xs);
|
||||
// Should compute with squiggle strings once interpreter has `sample`
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: n, xyPointLength: 100 }
|
||||
);
|
||||
let cdfValue = dist.cdf(max).value;
|
||||
expect(cdfValue).toBeCloseTo(1.0, 2);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// I may simply be mistaken about the math here.
|
||||
test.skip("at the lowest number in the distribution is within epsilon of 0", () => {
|
||||
fc.assert(
|
||||
fc.property(arrayGen(), (xs_) => {
|
||||
let xs = Array.from(xs_);
|
||||
let min = Math.min(...xs);
|
||||
// Should compute with squiggle strings once interpreter has `sample`
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: n, xyPointLength: 100 }
|
||||
);
|
||||
let cdfValue = dist.cdf(min).value;
|
||||
let max = Math.max(...xs);
|
||||
let epsilon = 5e-3;
|
||||
if (max - min < epsilon) {
|
||||
expect(cdfValue).toBeGreaterThan(4 * epsilon);
|
||||
} else {
|
||||
expect(cdfValue).toBeLessThan(4 * epsilon);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// I believe this is true, but due to bugs can't get the test to pass.
|
||||
test.skip("is <= 1 everywhere with equality when x is higher than the max", () => {
|
||||
fc.assert(
|
||||
fc.property(arrayGen(), fc.float(), (xs_, x) => {
|
||||
let xs = Array.from(xs_);
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: n, xyPointLength: 100 }
|
||||
);
|
||||
let cdfValue = dist.cdf(x).value;
|
||||
let max = Math.max(...xs);
|
||||
if (x > max) {
|
||||
let epsilon = (x - max) / x;
|
||||
expect(cdfValue).toBeGreaterThan(1 * (1 - epsilon));
|
||||
} else if (typeof cdfValue == "number") {
|
||||
expect(Math.round(1e5 * cdfValue) / 1e5).toBeLessThanOrEqual(1);
|
||||
} else {
|
||||
failDefault();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("is non-negative everywhere with zero when x is lower than the min", () => {
|
||||
fc.assert(
|
||||
fc.property(arrayGen(), fc.float(), (xs_, x) => {
|
||||
let xs = Array.from(xs_);
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: n, xyPointLength: 100 }
|
||||
);
|
||||
let cdfValue = dist.cdf(x).value;
|
||||
if (x < Math.min(...xs)) {
|
||||
expect(cdfValue).toEqual(0);
|
||||
} else {
|
||||
expect(cdfValue).toBeGreaterThan(0);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// I no longer believe this is true.
|
||||
describe("probability density function", () => {
|
||||
let n = 1000;
|
||||
|
||||
test.skip("assigns to the max at most the weight of the mean", () => {
|
||||
fc.assert(
|
||||
fc.property(arrayGen(), (xs_) => {
|
||||
let xs = Array.from(xs_);
|
||||
let max = Math.max(...xs);
|
||||
let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length;
|
||||
// Should be from squiggleString once interpreter exposes sampleset
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: n, xyPointLength: 100 }
|
||||
);
|
||||
let pdfValueMean = dist.pdf(mean).value;
|
||||
let pdfValueMax = dist.pdf(max).value;
|
||||
if (typeof pdfValueMean == "number" && typeof pdfValueMax == "number") {
|
||||
expect(pdfValueMax).toBeLessThanOrEqual(pdfValueMean);
|
||||
} else {
|
||||
expect(pdfValueMax).toEqual(pdfValueMean);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// // This should be true, but I can't get it to work.
|
||||
describe("mean is mean", () => {
|
||||
test.skip("when sampling twice as widely as the input", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.float64Array({ minLength: 10, maxLength: 100000 }),
|
||||
(xs_) => {
|
||||
let xs = Array.from(xs_);
|
||||
let n = xs.length;
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: 2 * n, xyPointLength: 4 * n }
|
||||
);
|
||||
let mean = dist.mean();
|
||||
if (typeof mean.value == "number") {
|
||||
expectErrorToBeBounded(
|
||||
mean.value,
|
||||
xs.reduce((a, b) => a + b, 0.0) / n,
|
||||
5e-1,
|
||||
1
|
||||
);
|
||||
} else {
|
||||
failDefault();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test.skip("when sampling half as widely as the input", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.float64Array({ minLength: 10, maxLength: 100000 }),
|
||||
(xs_) => {
|
||||
let xs = Array.from(xs_);
|
||||
let n = xs.length;
|
||||
let dist = new Distribution(
|
||||
{ tag: "SampleSet", value: xs },
|
||||
{ sampleCount: Math.floor(n / 2), xyPointLength: 4 * n }
|
||||
);
|
||||
let mean = dist.mean();
|
||||
if (typeof mean.value == "number") {
|
||||
expectErrorToBeBounded(
|
||||
mean.value,
|
||||
xs.reduce((a, b) => a + b, 0.0) / n,
|
||||
5e-1,
|
||||
1
|
||||
);
|
||||
} else {
|
||||
failDefault();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
33
packages/squiggle-lang/__tests__/TS/Scalars_test.ts
Normal file
33
packages/squiggle-lang/__tests__/TS/Scalars_test.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
// import { errorValueToString } from "../../src/js/index";
|
||||
import { testRun } from "./TestHelpers";
|
||||
import * as fc from "fast-check";
|
||||
|
||||
describe("Scalar manipulation is well-modeled by javascript math", () => {
|
||||
test("in the case of natural logarithms", () => {
|
||||
fc.assert(
|
||||
fc.property(fc.float(), (x) => {
|
||||
let squiggleString = `log(${x})`;
|
||||
let squiggleResult = testRun(squiggleString);
|
||||
if (x == 0) {
|
||||
expect(squiggleResult.value).toEqual(-Infinity);
|
||||
} else if (x < 0) {
|
||||
expect(squiggleResult.value).toEqual(
|
||||
"somemessage (confused why a test case hasn't pointed out to me that this message is bogus)"
|
||||
);
|
||||
} else {
|
||||
expect(squiggleResult.value).toEqual(Math.log(x));
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("in the case of addition (with assignment)", () => {
|
||||
fc.assert(
|
||||
fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => {
|
||||
let squiggleString = `x = ${x}; y = ${y}; z = ${z}; x + y + z`;
|
||||
let squiggleResult = testRun(squiggleString);
|
||||
expect(squiggleResult.value).toBeCloseTo(x + y + z);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
22
packages/squiggle-lang/__tests__/TS/Symbolic_test.ts
Normal file
22
packages/squiggle-lang/__tests__/TS/Symbolic_test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { errorValueToString } from "../../src/js/index";
|
||||
import { testRun } from "./TestHelpers";
|
||||
import * as fc from "fast-check";
|
||||
|
||||
describe("Symbolic mean", () => {
|
||||
test("mean(triangular(x,y,z))", () => {
|
||||
fc.assert(
|
||||
fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => {
|
||||
if (!(x < y && y < z)) {
|
||||
try {
|
||||
let squiggleResult = testRun(`mean(triangular(${x},${y},${z}))`);
|
||||
expect(squiggleResult.value).toBeCloseTo((x + y + z) / 3);
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toEqual(
|
||||
"Expected squiggle expression to evaluate but got error: Distribution Math Error: Triangular values must be increasing order."
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
45
packages/squiggle-lang/__tests__/TS/TestHelpers.ts
Normal file
45
packages/squiggle-lang/__tests__/TS/TestHelpers.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import {
|
||||
run,
|
||||
// Distribution,
|
||||
squiggleExpression,
|
||||
errorValueToString,
|
||||
// errorValue,
|
||||
// result,
|
||||
} from "../../src/js/index";
|
||||
|
||||
export function testRun(x: string): squiggleExpression {
|
||||
let squiggleResult = run(x, { sampleCount: 1000, xyPointLength: 100 });
|
||||
// return squiggleResult.value
|
||||
if (squiggleResult.tag === "Ok") {
|
||||
return squiggleResult.value;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Expected squiggle expression to evaluate but got error: ${errorValueToString(
|
||||
squiggleResult.value
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function failDefault() {
|
||||
expect("be reached").toBe("codepath should never");
|
||||
}
|
||||
|
||||
/**
|
||||
* This appears also in `TestHelpers.res`. According to https://www.math.net/percent-error, it computes
|
||||
* absolute error when numerical stability concerns make me not want to compute relative error.
|
||||
* */
|
||||
export function expectErrorToBeBounded(
|
||||
received: number,
|
||||
expected: number,
|
||||
epsilon: number,
|
||||
digits: number
|
||||
) {
|
||||
let distance = Math.abs(received - expected);
|
||||
let expectedAbs = Math.abs(expected);
|
||||
let normalizingDenom = Math.max(expectedAbs, 1);
|
||||
let error = distance / normalizingDenom;
|
||||
expect(Math.round(10 ** digits * error) / 10 ** digits).toBeLessThanOrEqual(
|
||||
epsilon
|
||||
);
|
||||
}
|
|
@ -30,8 +30,8 @@ let {toFloat, toDist, toString, toError, fmap} = module(DistributionOperation.Ou
|
|||
let fnImage = (theFn, inps) => Js.Array.map(theFn, inps)
|
||||
|
||||
let env: DistributionOperation.env = {
|
||||
sampleCount: 10000,
|
||||
xyPointLength: 1000,
|
||||
sampleCount: MagicNumbers.Environment.defaultSampleCount,
|
||||
xyPointLength: MagicNumbers.Environment.defaultXYPointLength,
|
||||
}
|
||||
|
||||
let run = DistributionOperation.run(~env)
|
||||
|
|
46
packages/squiggle-lang/benchmark/conversion_tests.ts
Normal file
46
packages/squiggle-lang/benchmark/conversion_tests.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { distributions, generateInt, generateFloatRange } from "./generators";
|
||||
import { test, expectEqual } from "./lib";
|
||||
|
||||
let checkDistributionSame = (
|
||||
distribution: string,
|
||||
operation: (arg: string) => string
|
||||
): void => {
|
||||
expectEqual(
|
||||
operation(distribution),
|
||||
operation(`toPointSet(${distribution})`)
|
||||
);
|
||||
expectEqual(
|
||||
operation(distribution),
|
||||
operation(`toSampleSet(${distribution})`)
|
||||
);
|
||||
};
|
||||
|
||||
Object.entries(distributions).map(([key, generator]) => {
|
||||
let distribution = generator();
|
||||
test(`mean is the same for ${key} distribution under all distribution types`, () =>
|
||||
checkDistributionSame(distribution, (d: string) => `mean(${d})`));
|
||||
|
||||
test(`cdf is the same for ${key} distribution under all distribution types`, () => {
|
||||
let cdf_value = generateInt();
|
||||
checkDistributionSame(
|
||||
distribution,
|
||||
(d: string) => `cdf(${d}, ${cdf_value})`
|
||||
);
|
||||
});
|
||||
|
||||
test(`pdf is the same for ${key} distribution under all distribution types`, () => {
|
||||
let pdf_value = generateInt();
|
||||
checkDistributionSame(
|
||||
distribution,
|
||||
(d: string) => `pdf(${d}, ${pdf_value})`
|
||||
);
|
||||
});
|
||||
|
||||
test(`inv is the same for ${key} distribution under all distribution types`, () => {
|
||||
let inv_value = generateFloatRange(0, 1);
|
||||
checkDistributionSame(
|
||||
distribution,
|
||||
(d: string) => `inv(${d}, ${inv_value})`
|
||||
);
|
||||
});
|
||||
});
|
48
packages/squiggle-lang/benchmark/generators.ts
Normal file
48
packages/squiggle-lang/benchmark/generators.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
export let generateFloatRange = (min: number, max: number): number =>
|
||||
Math.random() * (max - min) + min;
|
||||
export let generateIntRange = (min: number, max: number): number =>
|
||||
Math.floor(generateFloatRange(min, max));
|
||||
|
||||
export let generateIntMin = (min: number): number => generateIntRange(min, 100);
|
||||
|
||||
export let generateInt = (): number => generateIntMin(-100);
|
||||
|
||||
let generatePositiveInt = (): number => generateIntMin(1);
|
||||
|
||||
export let generateNormal = (): string =>
|
||||
`normal(${generateInt()}, ${generatePositiveInt()})`;
|
||||
|
||||
export let generateBeta = (): string =>
|
||||
`beta(${generatePositiveInt()}, ${generatePositiveInt()})`;
|
||||
|
||||
export let generateLognormal = (): string =>
|
||||
`lognormal(${generateInt()}, ${generatePositiveInt()})`;
|
||||
|
||||
export let generateExponential = (): string =>
|
||||
`exponential(${generatePositiveInt()})`;
|
||||
|
||||
export let generateUniform = (): string => {
|
||||
let a = generateInt();
|
||||
let b = generateIntMin(a + 1);
|
||||
return `uniform(${a}, ${b})`;
|
||||
};
|
||||
export let generateCauchy = (): string => {
|
||||
return `cauchy(${generateInt()}, ${generatePositiveInt()})`;
|
||||
};
|
||||
|
||||
export let generateTriangular = (): string => {
|
||||
let a = generateInt();
|
||||
let b = generateIntMin(a + 1);
|
||||
let c = generateIntMin(b + 1);
|
||||
return `triangular(${a}, ${b}, ${c})`;
|
||||
};
|
||||
|
||||
export let distributions: { [key: string]: () => string } = {
|
||||
normal: generateNormal,
|
||||
beta: generateBeta,
|
||||
lognormal: generateLognormal,
|
||||
exponential: generateExponential,
|
||||
triangular: generateTriangular,
|
||||
cauchy: generateCauchy,
|
||||
uniform: generateUniform,
|
||||
};
|
42
packages/squiggle-lang/benchmark/lib.ts
Normal file
42
packages/squiggle-lang/benchmark/lib.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { run, squiggleExpression, errorValueToString } from "../src/js/index";
|
||||
import * as chalk from "chalk";
|
||||
|
||||
let testRun = (x: string): squiggleExpression => {
|
||||
let result = run(x, { sampleCount: 100, xyPointLength: 100 });
|
||||
if (result.tag === "Ok") {
|
||||
return result.value;
|
||||
} else {
|
||||
throw Error(
|
||||
"Expected squiggle expression to evaluate but got error: " +
|
||||
errorValueToString(result.value)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export function test(name: string, fn: () => void) {
|
||||
console.log(chalk.cyan.bold(name));
|
||||
fn();
|
||||
}
|
||||
|
||||
export function expectEqual(expression1: string, expression2: string) {
|
||||
let result1 = testRun(expression1);
|
||||
let result2 = testRun(expression2);
|
||||
if (result1.tag === "number" && result2.tag === "number") {
|
||||
let logloss = Math.log(Math.abs(result1.value - result2.value));
|
||||
let isBadLogless = logloss > 1;
|
||||
console.log(chalk.blue(`${expression1} = ${expression2}`));
|
||||
console.log(`${result1.value} = ${result2.value}`);
|
||||
console.log(
|
||||
`logloss = ${
|
||||
isBadLogless
|
||||
? chalk.red(logloss.toFixed(2))
|
||||
: chalk.green(logloss.toFixed(2))
|
||||
}`
|
||||
);
|
||||
console.log();
|
||||
} else {
|
||||
throw Error(
|
||||
`Expected both to be number, but got ${result1.tag} and ${result2.tag}`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -20,12 +20,7 @@
|
|||
],
|
||||
"suffix": ".bs.js",
|
||||
"namespace": true,
|
||||
"bs-dependencies": [
|
||||
"@glennsl/rescript-jest",
|
||||
"@glennsl/bs-json",
|
||||
"rationale",
|
||||
"bisect_ppx"
|
||||
],
|
||||
"bs-dependencies": ["@glennsl/rescript-jest", "bisect_ppx"],
|
||||
"gentypeconfig": {
|
||||
"language": "typescript",
|
||||
"module": "commonjs",
|
||||
|
@ -37,7 +32,7 @@
|
|||
},
|
||||
"refmt": 3,
|
||||
"warnings": {
|
||||
"number": "+A-42-48-9-30-4-102-20-27-41"
|
||||
"number": "+A-42-48-9-30-4"
|
||||
},
|
||||
"ppx-flags": [
|
||||
["../../node_modules/bisect_ppx/ppx", "--exclude-files", ".*_test\\.res$$"]
|
||||
|
|
|
@ -9,5 +9,6 @@ module.exports = {
|
|||
".*Fixtures.bs.js",
|
||||
"/node_modules/",
|
||||
".*Helpers.bs.js",
|
||||
".*Helpers.ts",
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Hat tip to @dfalling
|
||||
# https://forum.rescript-lang.org/t/rescript-9-1-how-can-we-format-to-standard-out/1590/2?u=quinn-dougherty
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
{
|
||||
"name": "@quri/squiggle-lang",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.5",
|
||||
"homepage": "https://squiggle-language.com",
|
||||
"licence": "MIT",
|
||||
"scripts": {
|
||||
"build": "rescript build -with-deps",
|
||||
"bundle": "webpack",
|
||||
"start": "rescript build -w -with-deps",
|
||||
"clean": "rescript clean",
|
||||
"test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'",
|
||||
"test:reducer": "jest __tests__/Reducer*/",
|
||||
"benchmark": "ts-node benchmark/conversion_tests.ts",
|
||||
"test": "jest",
|
||||
"test:ts": "jest __tests__/TS/",
|
||||
"test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*",
|
||||
"test:watch": "jest --watchAll",
|
||||
"test:quick": "jest --modulePathIgnorePatterns=__tests__/Distributions/Invariants/*",
|
||||
"coverage": "rm -f *.coverage; yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report html",
|
||||
"coverage:ci": "yarn clean; BISECT_ENABLE=yes yarn build; yarn test; bisect-ppx-report send-to Codecov",
|
||||
"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; yarn test:rescript; bisect-ppx-report send-to Codecov",
|
||||
"coverage:ts:ci": "yarn coverage:ts && codecov",
|
||||
"lint:rescript": "./lint.sh",
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint": "yarn lint:rescript && yarn lint:prettier",
|
||||
"format": "rescript format -all && prettier --write .",
|
||||
"format:rescript": "rescript format -all",
|
||||
"format:prettier": "prettier --write .",
|
||||
"format": "yarn format:rescript && yarn format:prettier",
|
||||
"all": "yarn build && yarn bundle && yarn test"
|
||||
},
|
||||
"keywords": [
|
||||
|
@ -24,26 +31,29 @@
|
|||
],
|
||||
"author": "Quantified Uncertainty Research Institute",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@glennsl/bs-json": "^5.0.2",
|
||||
"devDependencies": {
|
||||
"bisect_ppx": "^2.7.1",
|
||||
"jstat": "^1.9.5",
|
||||
"lodash": "4.17.21",
|
||||
"mathjs": "10.4.3",
|
||||
"pdfast": "^0.2.0",
|
||||
"rationale": "0.2.0",
|
||||
"rescript": "^9.1.4",
|
||||
"bisect_ppx": "^2.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rescript-fast-check": "^1.1.1",
|
||||
"@glennsl/rescript-jest": "^0.9.0",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||
"@types/jest": "^27.4.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||
"docsify": "^4.12.2",
|
||||
"chalk": "^5.0.1",
|
||||
"codecov": "3.8.3",
|
||||
"fast-check": "2.25.0",
|
||||
"gentype": "^4.3.0",
|
||||
"jest": "^27.5.1",
|
||||
"mathjs": "10.5.0",
|
||||
"moduleserve": "0.9.1",
|
||||
"nyc": "^15.1.0",
|
||||
"pdfast": "^0.2.0",
|
||||
"reanalyze": "^2.19.0",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-loader": "^9.2.8",
|
||||
"ts-node": "^10.7.0",
|
||||
"typescript": "^4.6.3",
|
||||
"webpack": "^5.72.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import * as _ from "lodash";
|
||||
import type {
|
||||
exportEnv,
|
||||
exportDistribution,
|
||||
} from "../rescript/ProgramEvaluator.gen";
|
||||
export type { exportEnv, exportDistribution };
|
||||
import {
|
||||
genericDist,
|
||||
samplingParams,
|
||||
|
@ -48,7 +43,6 @@ import {
|
|||
Constructors_pointwiseLogarithm,
|
||||
Constructors_pointwisePower,
|
||||
} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen";
|
||||
import { pointSetDistFn } from "../rescript/OldInterpreter/DistPlus.bs";
|
||||
export type { samplingParams, errorValue };
|
||||
|
||||
export let defaultSamplingInputs: samplingParams = {
|
||||
|
@ -98,8 +92,7 @@ export type squiggleExpression =
|
|||
| tagged<"record", { [key: string]: squiggleExpression }>;
|
||||
export function run(
|
||||
squiggleString: string,
|
||||
samplingInputs?: samplingParams,
|
||||
_environment?: exportEnv
|
||||
samplingInputs?: samplingParams
|
||||
): result<squiggleExpression, errorValue> {
|
||||
let si: samplingParams = samplingInputs
|
||||
? samplingInputs
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
type functionCallInfo = GenericDist_Types.Operation.genericFunctionCallInfo
|
||||
type functionCallInfo = DistributionTypes.DistributionOperation.genericFunctionCallInfo
|
||||
type genericDist = DistributionTypes.genericDist
|
||||
type error = DistributionTypes.error
|
||||
|
||||
|
@ -120,7 +120,10 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
|
|||
(),
|
||||
)->OutputLocal.toDistR
|
||||
|
||||
let fromDistFn = (subFnName: GenericDist_Types.Operation.fromDist, dist: genericDist) => {
|
||||
let fromDistFn = (
|
||||
subFnName: DistributionTypes.DistributionOperation.fromDist,
|
||||
dist: genericDist,
|
||||
) => {
|
||||
let response = switch subFnName {
|
||||
| ToFloat(distToFloatOperation) =>
|
||||
GenericDist.toFloatOperation(dist, ~toPointSetFn, ~distToFloatOperation)
|
||||
|
@ -151,20 +154,26 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
|
|||
->GenericDist.toPointSet(~xyPointLength, ~sampleCount, ())
|
||||
->E.R2.fmap(r => Dist(PointSet(r)))
|
||||
->OutputLocal.fromResult
|
||||
| ToDistCombination(Algebraic, _, #Float(_)) => GenDistError(NotYetImplemented)
|
||||
| ToDistCombination(Algebraic, arithmeticOperation, #Dist(t2)) =>
|
||||
| ToDistCombination(Algebraic(_), _, #Float(_)) => GenDistError(NotYetImplemented)
|
||||
| ToDistCombination(Algebraic(strategy), arithmeticOperation, #Dist(t2)) =>
|
||||
dist
|
||||
->GenericDist.algebraicCombination(~toPointSetFn, ~toSampleSetFn, ~arithmeticOperation, ~t2)
|
||||
->GenericDist.algebraicCombination(
|
||||
~strategy,
|
||||
~toPointSetFn,
|
||||
~toSampleSetFn,
|
||||
~arithmeticOperation,
|
||||
~t2,
|
||||
)
|
||||
->E.R2.fmap(r => Dist(r))
|
||||
->OutputLocal.fromResult
|
||||
| ToDistCombination(Pointwise, arithmeticOperation, #Dist(t2)) =>
|
||||
| ToDistCombination(Pointwise, algebraicCombination, #Dist(t2)) =>
|
||||
dist
|
||||
->GenericDist.pointwiseCombination(~toPointSetFn, ~arithmeticOperation, ~t2)
|
||||
->GenericDist.pointwiseCombination(~toPointSetFn, ~algebraicCombination, ~t2)
|
||||
->E.R2.fmap(r => Dist(r))
|
||||
->OutputLocal.fromResult
|
||||
| ToDistCombination(Pointwise, arithmeticOperation, #Float(float)) =>
|
||||
| ToDistCombination(Pointwise, algebraicCombination, #Float(f)) =>
|
||||
dist
|
||||
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~arithmeticOperation, ~float)
|
||||
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~algebraicCombination, ~f)
|
||||
->E.R2.fmap(r => Dist(r))
|
||||
->OutputLocal.fromResult
|
||||
}
|
||||
|
@ -192,24 +201,24 @@ module Output = {
|
|||
let fmap = (
|
||||
~env,
|
||||
input: outputType,
|
||||
functionCallInfo: GenericDist_Types.Operation.singleParamaterFunction,
|
||||
functionCallInfo: DistributionTypes.DistributionOperation.singleParamaterFunction,
|
||||
): outputType => {
|
||||
let newFnCall: result<functionCallInfo, error> = switch (functionCallInfo, input) {
|
||||
| (FromDist(fromDist), Dist(o)) => Ok(FromDist(fromDist, o))
|
||||
| (FromFloat(fromDist), Float(o)) => Ok(FromFloat(fromDist, o))
|
||||
| (_, GenDistError(r)) => Error(r)
|
||||
| (FromDist(_), _) => Error(Other("Expected dist, got something else"))
|
||||
| (FromFloat(_), _) => Error(Other("Expected float, got something else"))
|
||||
| (FromDist(_), _) => Error(OtherError("Expected dist, got something else"))
|
||||
| (FromFloat(_), _) => Error(OtherError("Expected float, got something else"))
|
||||
}
|
||||
newFnCall->E.R2.fmap(run(~env))->OutputLocal.fromResult
|
||||
}
|
||||
}
|
||||
|
||||
// See comment above GenericDist_Types.Constructors to explain the purpose of this module.
|
||||
// See comment above DistributionTypes.Constructors to explain the purpose of this module.
|
||||
// I tried having another internal module called UsingDists, similar to how its done in
|
||||
// GenericDist_Types.Constructors. However, this broke GenType for me, so beware.
|
||||
// DistributionTypes.Constructors. However, this broke GenType for me, so beware.
|
||||
module Constructors = {
|
||||
module C = GenericDist_Types.Constructors.UsingDists
|
||||
module C = DistributionTypes.Constructors.UsingDists
|
||||
open OutputLocal
|
||||
let mean = (~env, dist) => C.mean(dist)->run(~env)->toFloatR
|
||||
let sample = (~env, dist) => C.sample(dist)->run(~env)->toFloatR
|
||||
|
|
|
@ -4,7 +4,7 @@ type env = {
|
|||
xyPointLength: int,
|
||||
}
|
||||
|
||||
open GenericDist_Types
|
||||
open DistributionTypes
|
||||
|
||||
@genType
|
||||
type outputType =
|
||||
|
@ -15,15 +15,15 @@ type outputType =
|
|||
| GenDistError(error)
|
||||
|
||||
@genType
|
||||
let run: (~env: env, GenericDist_Types.Operation.genericFunctionCallInfo) => outputType
|
||||
let run: (~env: env, DistributionTypes.DistributionOperation.genericFunctionCallInfo) => outputType
|
||||
let runFromDist: (
|
||||
~env: env,
|
||||
~functionCallInfo: GenericDist_Types.Operation.fromDist,
|
||||
~functionCallInfo: DistributionTypes.DistributionOperation.fromDist,
|
||||
genericDist,
|
||||
) => outputType
|
||||
let runFromFloat: (
|
||||
~env: env,
|
||||
~functionCallInfo: GenericDist_Types.Operation.fromDist,
|
||||
~functionCallInfo: DistributionTypes.DistributionOperation.fromDist,
|
||||
float,
|
||||
) => outputType
|
||||
|
||||
|
@ -38,7 +38,7 @@ module Output: {
|
|||
let toBool: t => option<bool>
|
||||
let toBoolR: t => result<bool, error>
|
||||
let toError: t => option<error>
|
||||
let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t
|
||||
let fmap: (~env: env, t, DistributionTypes.DistributionOperation.singleParamaterFunction) => t
|
||||
}
|
||||
|
||||
module Constructors: {
|
||||
|
|
|
@ -4,38 +4,62 @@ type genericDist =
|
|||
| SampleSet(SampleSetDist.t)
|
||||
| Symbolic(SymbolicDistTypes.symbolicDist)
|
||||
|
||||
type asAlgebraicCombinationStrategy = AsDefault | AsSymbolic | AsMonteCarlo | AsConvolution
|
||||
|
||||
@genType
|
||||
type error =
|
||||
| NotYetImplemented
|
||||
| Unreachable
|
||||
| DistributionVerticalShiftIsInvalid
|
||||
| TooFewSamples
|
||||
| ArgumentError(string)
|
||||
| Other(string)
|
||||
| OperationError(Operation.Error.t)
|
||||
| PointSetConversionError(SampleSetDist.pointsetConversionError)
|
||||
| SparklineError(PointSetTypes.sparklineError) // This type of error is for when we find a sparkline of a discrete distribution. This should probably at some point be actually implemented
|
||||
| RequestedStrategyInvalidError(string)
|
||||
| LogarithmOfDistributionError(string)
|
||||
| OtherError(string)
|
||||
|
||||
module Operation = {
|
||||
type direction =
|
||||
| Algebraic
|
||||
| Pointwise
|
||||
@genType
|
||||
module Error = {
|
||||
type t = error
|
||||
|
||||
type arithmeticOperation = [
|
||||
| #Add
|
||||
| #Multiply
|
||||
| #Subtract
|
||||
| #Divide
|
||||
| #Power
|
||||
| #Logarithm
|
||||
]
|
||||
let fromString = (s: string): t => OtherError(s)
|
||||
|
||||
let arithmeticToFn = (arithmetic: arithmeticOperation) =>
|
||||
switch arithmetic {
|
||||
| #Add => \"+."
|
||||
| #Multiply => \"*."
|
||||
| #Subtract => \"-."
|
||||
| #Power => \"**"
|
||||
| #Divide => \"/."
|
||||
| #Logarithm => (a, b) => log(a) /. log(b)
|
||||
@genType
|
||||
let toString = (err: error): string =>
|
||||
switch err {
|
||||
| NotYetImplemented => "Function Not Yet Implemented"
|
||||
| Unreachable => "Unreachable"
|
||||
| DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift is Invalid"
|
||||
| ArgumentError(s) => `Argument Error ${s}`
|
||||
| LogarithmOfDistributionError(s) => `Logarithm of input error: ${s}`
|
||||
| TooFewSamples => "Too Few Samples"
|
||||
| OperationError(err) => Operation.Error.toString(err)
|
||||
| PointSetConversionError(err) => SampleSetDist.pointsetConversionErrorToString(err)
|
||||
| SparklineError(err) => PointSetTypes.sparklineErrorToString(err)
|
||||
| RequestedStrategyInvalidError(err) => `Requested strategy invalid: ${err}`
|
||||
| OtherError(s) => s
|
||||
}
|
||||
|
||||
let resultStringToResultError: result<'a, string> => result<'a, error> = n =>
|
||||
n->E.R2.errMap(r => r->fromString)
|
||||
|
||||
let sampleErrorToDistErr = (err: SampleSetDist.sampleSetError): error =>
|
||||
switch err {
|
||||
| TooFewSamples => TooFewSamples
|
||||
}
|
||||
}
|
||||
|
||||
@genType
|
||||
module DistributionOperation = {
|
||||
@genType
|
||||
type pointsetXSelection = [#Linear | #ByWeight]
|
||||
|
||||
type direction =
|
||||
| Algebraic(asAlgebraicCombinationStrategy)
|
||||
| Pointwise
|
||||
|
||||
type toFloat = [
|
||||
| #Cdf(float)
|
||||
| #Inv(float)
|
||||
|
@ -43,9 +67,7 @@ module Operation = {
|
|||
| #Mean
|
||||
| #Sample
|
||||
]
|
||||
}
|
||||
|
||||
module DistributionOperation = {
|
||||
type toDist =
|
||||
| Normalize
|
||||
| ToPointSet
|
||||
|
@ -55,15 +77,18 @@ module DistributionOperation = {
|
|||
|
||||
type toFloatArray = Sample(int)
|
||||
|
||||
type fromDist =
|
||||
| ToFloat(Operation.toFloat)
|
||||
| ToDist(toDist)
|
||||
| ToDistCombination(
|
||||
Operation.direction,
|
||||
Operation.arithmeticOperation,
|
||||
[#Dist(genericDist) | #Float(float)],
|
||||
)
|
||||
type toBool = IsNormalized
|
||||
|
||||
type toString =
|
||||
| ToString
|
||||
| ToSparkline(int)
|
||||
|
||||
type fromDist =
|
||||
| ToFloat(toFloat)
|
||||
| ToDist(toDist)
|
||||
| ToDistCombination(direction, Operation.Algebraic.t, [#Dist(genericDist) | #Float(float)])
|
||||
| ToString(toString)
|
||||
| ToBool(toBool)
|
||||
|
||||
type singleParamaterFunction =
|
||||
| FromDist(fromDist)
|
||||
|
@ -86,8 +111,10 @@ module DistributionOperation = {
|
|||
| ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})`
|
||||
| ToDist(Truncate(_, _)) => `truncate`
|
||||
| ToDist(Inspect) => `inspect`
|
||||
| ToString => `toString`
|
||||
| ToDistCombination(Algebraic, _, _) => `algebraic`
|
||||
| ToString(ToString) => `toString`
|
||||
| ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})`
|
||||
| ToBool(IsNormalized) => `isNormalized`
|
||||
| ToDistCombination(Algebraic(_), _, _) => `algebraic`
|
||||
| ToDistCombination(Pointwise, _, _) => `pointwise`
|
||||
}
|
||||
|
||||
|
@ -97,3 +124,71 @@ module DistributionOperation = {
|
|||
| Mixture(_) => `mixture`
|
||||
}
|
||||
}
|
||||
module Constructors = {
|
||||
type t = DistributionOperation.genericFunctionCallInfo
|
||||
|
||||
module UsingDists = {
|
||||
@genType
|
||||
let mean = (dist): t => FromDist(ToFloat(#Mean), dist)
|
||||
let sample = (dist): t => FromDist(ToFloat(#Sample), dist)
|
||||
let cdf = (dist, x): t => FromDist(ToFloat(#Cdf(x)), dist)
|
||||
let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist)
|
||||
let pdf = (dist, x): t => FromDist(ToFloat(#Pdf(x)), dist)
|
||||
let normalize = (dist): t => FromDist(ToDist(Normalize), dist)
|
||||
let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist)
|
||||
let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist)
|
||||
let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist)
|
||||
let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist)
|
||||
let inspect = (dist): t => FromDist(ToDist(Inspect), dist)
|
||||
let toString = (dist): t => FromDist(ToString(ToString), dist)
|
||||
let toSparkline = (dist, n): t => FromDist(ToString(ToSparkline(n)), dist)
|
||||
let algebraicAdd = (dist1, dist2: genericDist): t => FromDist(
|
||||
ToDistCombination(Algebraic(AsDefault), #Add, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicMultiply = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic(AsDefault), #Multiply, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicDivide = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic(AsDefault), #Divide, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicSubtract = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic(AsDefault), #Subtract, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicLogarithm = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic(AsDefault), #Logarithm, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicPower = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic(AsDefault), #Power, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseAdd = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Add, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseMultiply = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Multiply, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseDivide = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Divide, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseSubtract = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Subtract, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseLogarithm = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Logarithm, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwisePower = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Power, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,24 @@ type toSampleSetFn = t => result<SampleSetDist.t, error>
|
|||
type scaleMultiplyFn = (t, float) => result<t, error>
|
||||
type pointwiseAddFn = (t, t) => result<t, error>
|
||||
|
||||
let isPointSet = (t: t) =>
|
||||
switch t {
|
||||
| PointSet(_) => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let isSampleSetSet = (t: t) =>
|
||||
switch t {
|
||||
| SampleSet(_) => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let isSymbolic = (t: t) =>
|
||||
switch t {
|
||||
| Symbolic(_) => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let sampleN = (t: t, n) =>
|
||||
switch t {
|
||||
| PointSet(r) => PointSetDist.sampleNRendered(n, r)
|
||||
|
@ -14,7 +32,7 @@ let sampleN = (t: t, n) =>
|
|||
}
|
||||
|
||||
let toSampleSetDist = (t: t, n) =>
|
||||
SampleSetDist.make(sampleN(t, n))->GenericDist_Types.Error.resultStringToResultError
|
||||
SampleSetDist.make(sampleN(t, n))->E.R2.errMap(DistributionTypes.Error.sampleErrorToDistErr)
|
||||
|
||||
let fromFloat = (f: float): t => Symbolic(SymbolicDist.Float.make(f))
|
||||
|
||||
|
@ -46,20 +64,27 @@ let toFloatOperation = (
|
|||
~toPointSetFn: toPointSetFn,
|
||||
~distToFloatOperation: Operation.distToFloatOperation,
|
||||
) => {
|
||||
let symbolicSolution = switch (t: t) {
|
||||
| Symbolic(r) =>
|
||||
switch SymbolicDist.T.operate(distToFloatOperation, r) {
|
||||
| Ok(f) => Some(f)
|
||||
| _ => None
|
||||
}
|
||||
let trySymbolicSolution = switch (t: t) {
|
||||
| Symbolic(r) => SymbolicDist.T.operate(distToFloatOperation, r)->E.R.toOption
|
||||
| _ => None
|
||||
}
|
||||
|
||||
switch symbolicSolution {
|
||||
let trySampleSetSolution = switch ((t: t), distToFloatOperation) {
|
||||
| (SampleSet(sampleSet), #Mean) => SampleSetDist.mean(sampleSet)->Some
|
||||
| (SampleSet(sampleSet), #Sample) => SampleSetDist.sample(sampleSet)->Some
|
||||
| (SampleSet(sampleSet), #Inv(r)) => SampleSetDist.percentile(sampleSet, r)->Some
|
||||
| _ => None
|
||||
}
|
||||
|
||||
switch trySymbolicSolution {
|
||||
| Some(r) => Ok(r)
|
||||
| None =>
|
||||
switch trySampleSetSolution {
|
||||
| Some(r) => Ok(r)
|
||||
| None => toPointSetFn(t)->E.R2.fmap(PointSetDist.operate(distToFloatOperation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Todo: If it's a pointSet, but the xyPointLength is different from what it has, it should change.
|
||||
// This is tricky because the case of discrete distributions.
|
||||
|
@ -68,8 +93,8 @@ let toPointSet = (
|
|||
t,
|
||||
~xyPointLength,
|
||||
~sampleCount,
|
||||
~xSelection: GenericDist_Types.Operation.pointsetXSelection=#ByWeight,
|
||||
unit,
|
||||
~xSelection: DistributionTypes.DistributionOperation.pointsetXSelection=#ByWeight,
|
||||
(),
|
||||
): result<PointSetTypes.pointSetDist, error> => {
|
||||
switch (t: t) {
|
||||
| PointSet(pointSet) => Ok(pointSet)
|
||||
|
@ -83,7 +108,7 @@ let toPointSet = (
|
|||
pointSetDistLength: xyPointLength,
|
||||
kernelWidth: None,
|
||||
},
|
||||
)->GenericDist_Types.Error.resultStringToResultError
|
||||
)->E.R2.errMap(x => DistributionTypes.PointSetConversionError(x))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,18 +118,24 @@ let toPointSet = (
|
|||
xyPointLength to be a bit longer than the eventual toSparkline downsampling. I chose 3
|
||||
fairly arbitrarily.
|
||||
*/
|
||||
let toSparkline = (t: t, ~sampleCount: int, ~bucketCount: int=20, unit): result<string, error> =>
|
||||
let toSparkline = (t: t, ~sampleCount: int, ~bucketCount: int=20, ()): result<string, error> =>
|
||||
t
|
||||
->toPointSet(~xSelection=#Linear, ~xyPointLength=bucketCount * 3, ~sampleCount, ())
|
||||
->E.R.bind(r =>
|
||||
r->PointSetDist.toSparkline(bucketCount)->GenericDist_Types.Error.resultStringToResultError
|
||||
r->PointSetDist.toSparkline(bucketCount)->E.R2.errMap(x => DistributionTypes.SparklineError(x))
|
||||
)
|
||||
|
||||
module Truncate = {
|
||||
let trySymbolicSimplification = (leftCutoff, rightCutoff, t: t): option<t> =>
|
||||
let trySymbolicSimplification = (
|
||||
leftCutoff: option<float>,
|
||||
rightCutoff: option<float>,
|
||||
t: t,
|
||||
): option<t> =>
|
||||
switch (leftCutoff, rightCutoff, t) {
|
||||
| (None, None, _) => None
|
||||
| (lc, rc, Symbolic(#Uniform(u))) if lc < rc =>
|
||||
| (Some(lc), Some(rc), Symbolic(#Uniform(u))) if lc < rc =>
|
||||
Some(Symbolic(#Uniform(SymbolicDist.Uniform.truncate(Some(lc), Some(rc), u))))
|
||||
| (lc, rc, Symbolic(#Uniform(u))) =>
|
||||
Some(Symbolic(#Uniform(SymbolicDist.Uniform.truncate(lc, rc, u))))
|
||||
| _ => None
|
||||
}
|
||||
|
@ -137,85 +168,190 @@ let truncate = Truncate.run
|
|||
of a new variable that is the result of the operation on A and B.
|
||||
For instance, normal(0, 1) + normal(1, 1) -> normal(1, 2).
|
||||
In general, this is implemented via convolution.
|
||||
|
||||
TODO: It would be useful to be able to pass in a paramater to get this to run either with convolution or monte carlo.
|
||||
*/
|
||||
module AlgebraicCombination = {
|
||||
let tryAnalyticalSimplification = (
|
||||
arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
||||
t1: t,
|
||||
t2: t,
|
||||
): option<result<SymbolicDistTypes.symbolicDist, string>> =>
|
||||
switch (arithmeticOperation, t1, t2) {
|
||||
| (arithmeticOperation, Symbolic(d1), Symbolic(d2)) =>
|
||||
switch SymbolicDist.T.tryAnalyticalSimplification(d1, d2, arithmeticOperation) {
|
||||
| #AnalyticalSolution(symbolicDist) => Some(Ok(symbolicDist))
|
||||
| #Error(er) => Some(Error(er))
|
||||
| #NoSolution => None
|
||||
module InputValidator = {
|
||||
/*
|
||||
It would be good to also do a check to make sure that probability mass for the second
|
||||
operand, at value 1.0, is 0 (or approximately 0). However, we'd ideally want to check
|
||||
that both the probability mass and the probability density are greater than zero.
|
||||
Right now we don't yet have a way of getting probability mass, so I'll leave this for later.
|
||||
*/
|
||||
let getLogarithmInputError = (t1: t, t2: t, ~toPointSetFn: toPointSetFn): option<error> => {
|
||||
let firstOperandIsGreaterThanZero =
|
||||
toFloatOperation(
|
||||
t1,
|
||||
~toPointSetFn,
|
||||
~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
|
||||
) |> E.R.fmap(r => r > 0.)
|
||||
let secondOperandIsGreaterThanZero =
|
||||
toFloatOperation(
|
||||
t2,
|
||||
~toPointSetFn,
|
||||
~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
|
||||
) |> E.R.fmap(r => r > 0.)
|
||||
let items = E.A.R.firstErrorOrOpen([
|
||||
firstOperandIsGreaterThanZero,
|
||||
secondOperandIsGreaterThanZero,
|
||||
])
|
||||
switch items {
|
||||
| Error(r) => Some(r)
|
||||
| Ok([true, _]) =>
|
||||
Some(LogarithmOfDistributionError("First input must be completely greater than 0"))
|
||||
| Ok([false, true]) =>
|
||||
Some(LogarithmOfDistributionError("Second input must be completely greater than 0"))
|
||||
| Ok([false, false]) => None
|
||||
| Ok(_) => Some(Unreachable)
|
||||
}
|
||||
| _ => None
|
||||
}
|
||||
|
||||
let runConvolution = (
|
||||
let run = (t1: t, t2: t, ~toPointSetFn: toPointSetFn, ~arithmeticOperation): option<error> => {
|
||||
if arithmeticOperation == #Logarithm {
|
||||
getLogarithmInputError(t1, t2, ~toPointSetFn)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module StrategyCallOnValidatedInputs = {
|
||||
let convolution = (
|
||||
toPointSet: toPointSetFn,
|
||||
arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
||||
arithmeticOperation: Operation.convolutionOperation,
|
||||
t1: t,
|
||||
t2: t,
|
||||
) =>
|
||||
E.R.merge(toPointSet(t1), toPointSet(t2))->E.R2.fmap(((a, b)) =>
|
||||
PointSetDist.combineAlgebraically(arithmeticOperation, a, b)
|
||||
)
|
||||
): result<t, error> =>
|
||||
E.R.merge(toPointSet(t1), toPointSet(t2))
|
||||
->E.R2.fmap(((a, b)) => PointSetDist.combineAlgebraically(arithmeticOperation, a, b))
|
||||
->E.R2.fmap(r => DistributionTypes.PointSet(r))
|
||||
|
||||
let runMonteCarlo = (
|
||||
let monteCarlo = (
|
||||
toSampleSet: toSampleSetFn,
|
||||
arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
||||
arithmeticOperation: Operation.algebraicOperation,
|
||||
t1: t,
|
||||
t2: t,
|
||||
) => {
|
||||
): result<t, error> => {
|
||||
let fn = Operation.Algebraic.toFn(arithmeticOperation)
|
||||
E.R.merge(toSampleSet(t1), toSampleSet(t2))
|
||||
->E.R.bind(((t1, t2)) => {
|
||||
SampleSetDist.map2(~fn, ~t1, ~t2)->GenericDist_Types.Error.resultStringToResultError
|
||||
SampleSetDist.map2(~fn, ~t1, ~t2)->E.R2.errMap(x => DistributionTypes.OperationError(x))
|
||||
})
|
||||
->E.R2.fmap(r => DistributionTypes.SampleSet(r))
|
||||
}
|
||||
|
||||
let symbolic = (
|
||||
arithmeticOperation: Operation.algebraicOperation,
|
||||
t1: t,
|
||||
t2: t,
|
||||
): SymbolicDistTypes.analyticalSimplificationResult => {
|
||||
switch (t1, t2) {
|
||||
| (DistributionTypes.Symbolic(d1), DistributionTypes.Symbolic(d2)) =>
|
||||
SymbolicDist.T.tryAnalyticalSimplification(d1, d2, arithmeticOperation)
|
||||
| _ => #NoSolution
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module StrategyChooser = {
|
||||
type specificStrategy = [#AsSymbolic | #AsMonteCarlo | #AsConvolution]
|
||||
|
||||
//I'm (Ozzie) really just guessing here, very little idea what's best
|
||||
let expectedConvolutionCost: t => int = x =>
|
||||
switch x {
|
||||
| Symbolic(#Float(_)) => 1
|
||||
| Symbolic(_) => 1000
|
||||
| Symbolic(#Float(_)) => MagicNumbers.OpCost.floatCost
|
||||
| Symbolic(_) => MagicNumbers.OpCost.symbolicCost
|
||||
| PointSet(Discrete(m)) => m.xyShape->XYShape.T.length
|
||||
| PointSet(Mixed(_)) => 1000
|
||||
| PointSet(Continuous(_)) => 1000
|
||||
| _ => 1000
|
||||
| PointSet(Mixed(_)) => MagicNumbers.OpCost.mixedCost
|
||||
| PointSet(Continuous(_)) => MagicNumbers.OpCost.continuousCost
|
||||
| _ => MagicNumbers.OpCost.wildcardCost
|
||||
}
|
||||
|
||||
let chooseConvolutionOrMonteCarlo = (t2: t, t1: t) =>
|
||||
expectedConvolutionCost(t1) * expectedConvolutionCost(t2) > 10000
|
||||
? #CalculateWithMonteCarlo
|
||||
: #CalculateWithConvolution
|
||||
let hasSampleSetDist = (t1: t, t2: t): bool => isSampleSetSet(t1) || isSampleSetSet(t2)
|
||||
|
||||
let convolutionIsFasterThanMonteCarlo = (t1: t, t2: t): bool =>
|
||||
expectedConvolutionCost(t1) * expectedConvolutionCost(t2) < MagicNumbers.OpCost.monteCarloCost
|
||||
|
||||
let preferConvolutionToMonteCarlo = (t1, t2, arithmeticOperation) => {
|
||||
!hasSampleSetDist(t1, t2) &&
|
||||
Operation.Convolution.canDoAlgebraicOperation(arithmeticOperation) &&
|
||||
convolutionIsFasterThanMonteCarlo(t1, t2)
|
||||
}
|
||||
|
||||
let run = (~t1: t, ~t2: t, ~arithmeticOperation): specificStrategy => {
|
||||
switch StrategyCallOnValidatedInputs.symbolic(arithmeticOperation, t1, t2) {
|
||||
| #AnalyticalSolution(_)
|
||||
| #Error(_) =>
|
||||
#AsSymbolic
|
||||
| #NoSolution =>
|
||||
preferConvolutionToMonteCarlo(t1, t2, arithmeticOperation) ? #AsConvolution : #AsMonteCarlo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let runStrategyOnValidatedInputs = (
|
||||
~t1: t,
|
||||
~t2: t,
|
||||
~arithmeticOperation,
|
||||
~strategy: StrategyChooser.specificStrategy,
|
||||
~toPointSetFn: toPointSetFn,
|
||||
~toSampleSetFn: toSampleSetFn,
|
||||
): result<t, error> => {
|
||||
switch strategy {
|
||||
| #AsMonteCarlo =>
|
||||
StrategyCallOnValidatedInputs.monteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
|
||||
| #AsSymbolic =>
|
||||
switch StrategyCallOnValidatedInputs.symbolic(arithmeticOperation, t1, t2) {
|
||||
| #AnalyticalSolution(symbolicDist) => Ok(Symbolic(symbolicDist))
|
||||
| #Error(e) => Error(OperationError(e))
|
||||
| #NoSolution => Error(Unreachable)
|
||||
}
|
||||
| #AsConvolution =>
|
||||
switch Operation.Convolution.fromAlgebraicOperation(arithmeticOperation) {
|
||||
| Some(convOp) => StrategyCallOnValidatedInputs.convolution(toPointSetFn, convOp, t1, t2)
|
||||
| None => Error(Unreachable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let run = (
|
||||
~strategy: DistributionTypes.asAlgebraicCombinationStrategy,
|
||||
t1: t,
|
||||
~toPointSetFn: toPointSetFn,
|
||||
~toSampleSetFn: toSampleSetFn,
|
||||
~arithmeticOperation,
|
||||
~arithmeticOperation: Operation.algebraicOperation,
|
||||
~t2: t,
|
||||
): result<t, error> => {
|
||||
switch tryAnalyticalSimplification(arithmeticOperation, t1, t2) {
|
||||
| Some(Ok(symbolicDist)) => Ok(Symbolic(symbolicDist))
|
||||
| Some(Error(e)) => Error(Other(e))
|
||||
| None =>
|
||||
switch chooseConvolutionOrMonteCarlo(t1, t2) {
|
||||
| #CalculateWithMonteCarlo => runMonteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
|
||||
| #CalculateWithConvolution =>
|
||||
runConvolution(
|
||||
toPointSetFn,
|
||||
let invalidOperationError = InputValidator.run(t1, t2, ~arithmeticOperation, ~toPointSetFn)
|
||||
switch (invalidOperationError, strategy) {
|
||||
| (Some(e), _) => Error(e)
|
||||
| (None, AsDefault) => {
|
||||
let chooseStrategy = StrategyChooser.run(~arithmeticOperation, ~t1, ~t2)
|
||||
runStrategyOnValidatedInputs(
|
||||
~t1,
|
||||
~t2,
|
||||
~strategy=chooseStrategy,
|
||||
~arithmeticOperation,
|
||||
~toPointSetFn,
|
||||
~toSampleSetFn,
|
||||
)
|
||||
}
|
||||
| (None, AsMonteCarlo) =>
|
||||
StrategyCallOnValidatedInputs.monteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
|
||||
| (None, AsSymbolic) =>
|
||||
switch StrategyCallOnValidatedInputs.symbolic(arithmeticOperation, t1, t2) {
|
||||
| #AnalyticalSolution(symbolicDist) => Ok(Symbolic(symbolicDist))
|
||||
| #NoSolution => Error(RequestedStrategyInvalidError(`No analytic solution for inputs`))
|
||||
| #Error(err) => Error(OperationError(err))
|
||||
}
|
||||
| (None, AsConvolution) =>
|
||||
switch Operation.Convolution.fromAlgebraicOperation(arithmeticOperation) {
|
||||
| None => {
|
||||
let errString = `Convolution not supported for ${Operation.Algebraic.toString(
|
||||
arithmeticOperation,
|
||||
t1,
|
||||
t2,
|
||||
)->E.R2.fmap(r => DistributionTypes.PointSet(r))
|
||||
)}`
|
||||
Error(RequestedStrategyInvalidError(errString))
|
||||
}
|
||||
| Some(convOp) => StrategyCallOnValidatedInputs.convolution(toPointSetFn, convOp, t1, t2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,40 +363,36 @@ let algebraicCombination = AlgebraicCombination.run
|
|||
let pointwiseCombination = (
|
||||
t1: t,
|
||||
~toPointSetFn: toPointSetFn,
|
||||
~arithmeticOperation,
|
||||
~algebraicCombination: Operation.algebraicOperation,
|
||||
~t2: t,
|
||||
): result<t, error> => {
|
||||
E.R.merge(toPointSetFn(t1), toPointSetFn(t2))
|
||||
->E.R2.fmap(((t1, t2)) =>
|
||||
PointSetDist.combinePointwise(
|
||||
GenericDist_Types.Operation.arithmeticToFn(arithmeticOperation),
|
||||
t1,
|
||||
t2,
|
||||
)
|
||||
)
|
||||
E.R.merge(toPointSetFn(t1), toPointSetFn(t2))->E.R.bind(((t1, t2)) =>
|
||||
PointSetDist.combinePointwise(Operation.Algebraic.toFn(algebraicCombination), t1, t2)
|
||||
->E.R2.fmap(r => DistributionTypes.PointSet(r))
|
||||
->E.R2.errMap(err => DistributionTypes.OperationError(err))
|
||||
)
|
||||
}
|
||||
|
||||
let pointwiseCombinationFloat = (
|
||||
t: t,
|
||||
~toPointSetFn: toPointSetFn,
|
||||
~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
||||
~float: float,
|
||||
~algebraicCombination: Operation.algebraicOperation,
|
||||
~f: float,
|
||||
): result<t, error> => {
|
||||
let m = switch arithmeticOperation {
|
||||
let m = switch algebraicCombination {
|
||||
| #Add | #Subtract => Error(DistributionTypes.DistributionVerticalShiftIsInvalid)
|
||||
| (#Multiply | #Divide | #Power | #Logarithm) as arithmeticOperation =>
|
||||
toPointSetFn(t)->E.R2.fmap(t => {
|
||||
toPointSetFn(t)->E.R.bind(t => {
|
||||
//TODO: Move to PointSet codebase
|
||||
let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary)
|
||||
let integralSumCacheFn = Operation.Scale.toIntegralSumCacheFn(arithmeticOperation)
|
||||
let integralCacheFn = Operation.Scale.toIntegralCacheFn(arithmeticOperation)
|
||||
PointSetDist.T.mapY(
|
||||
~integralSumCacheFn=integralSumCacheFn(float),
|
||||
~integralCacheFn=integralCacheFn(float),
|
||||
~fn=fn(float),
|
||||
PointSetDist.T.mapYResult(
|
||||
~integralSumCacheFn=integralSumCacheFn(f),
|
||||
~integralCacheFn=integralCacheFn(f),
|
||||
~fn=fn(f),
|
||||
t,
|
||||
)
|
||||
)->E.R2.errMap(x => DistributionTypes.OperationError(x))
|
||||
})
|
||||
}
|
||||
m->E.R2.fmap(r => DistributionTypes.PointSet(r))
|
||||
|
@ -274,7 +406,7 @@ let mixture = (
|
|||
~pointwiseAddFn: pointwiseAddFn,
|
||||
) => {
|
||||
if E.A.length(values) == 0 {
|
||||
Error(DistributionTypes.Other("Mixture error: mixture must have at least 1 element"))
|
||||
Error(DistributionTypes.OtherError("Mixture error: mixture must have at least 1 element"))
|
||||
} else {
|
||||
let totalWeight = values->E.A2.fmap(E.Tuple2.second)->E.A.Floats.sum
|
||||
let properlyWeightedValues =
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
type t = GenericDist_Types.genericDist
|
||||
type error = GenericDist_Types.error
|
||||
type t = DistributionTypes.genericDist
|
||||
type error = DistributionTypes.error
|
||||
type toPointSetFn = t => result<PointSetTypes.pointSetDist, error>
|
||||
type toSampleSetFn = t => result<SampleSetDist.t, error>
|
||||
type scaleMultiplyFn = (t, float) => result<t, error>
|
||||
|
@ -28,7 +28,7 @@ let toPointSet: (
|
|||
t,
|
||||
~xyPointLength: int,
|
||||
~sampleCount: int,
|
||||
~xSelection: GenericDist_Types.Operation.pointsetXSelection=?,
|
||||
~xSelection: DistributionTypes.DistributionOperation.pointsetXSelection=?,
|
||||
unit,
|
||||
) => result<PointSetTypes.pointSetDist, error>
|
||||
let toSparkline: (t, ~sampleCount: int, ~bucketCount: int=?, unit) => result<string, error>
|
||||
|
@ -42,25 +42,26 @@ let truncate: (
|
|||
) => result<t, error>
|
||||
|
||||
let algebraicCombination: (
|
||||
~strategy: DistributionTypes.asAlgebraicCombinationStrategy,
|
||||
t,
|
||||
~toPointSetFn: toPointSetFn,
|
||||
~toSampleSetFn: toSampleSetFn,
|
||||
~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
||||
~arithmeticOperation: Operation.algebraicOperation,
|
||||
~t2: t,
|
||||
) => result<t, error>
|
||||
|
||||
let pointwiseCombination: (
|
||||
t,
|
||||
~toPointSetFn: toPointSetFn,
|
||||
~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
||||
~algebraicCombination: Operation.algebraicOperation,
|
||||
~t2: t,
|
||||
) => result<t, error>
|
||||
|
||||
let pointwiseCombinationFloat: (
|
||||
t,
|
||||
~toPointSetFn: toPointSetFn,
|
||||
~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
||||
~float: float,
|
||||
~algebraicCombination: Operation.algebraicOperation,
|
||||
~f: float,
|
||||
) => result<t, error>
|
||||
|
||||
let mixture: (
|
||||
|
@ -68,3 +69,6 @@ let mixture: (
|
|||
~scaleMultiplyFn: scaleMultiplyFn,
|
||||
~pointwiseAddFn: pointwiseAddFn,
|
||||
) => result<t, error>
|
||||
|
||||
let isSymbolic: t => bool
|
||||
let isPointSet: t => bool
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
type genericDist = DistributionTypes.genericDist
|
||||
@genType
|
||||
type error = DistributionTypes.error
|
||||
|
||||
@genType
|
||||
module Error = {
|
||||
type t = error
|
||||
|
||||
let fromString = (s: string): t => Other(s)
|
||||
|
||||
@genType
|
||||
let toString = (x: t) => {
|
||||
switch x {
|
||||
| NotYetImplemented => "Not Yet Implemented"
|
||||
| Unreachable => "Unreachable"
|
||||
| DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift Is Invalid"
|
||||
| ArgumentError(x) => `Argument Error: ${x}`
|
||||
| Other(s) => s
|
||||
}
|
||||
}
|
||||
|
||||
let resultStringToResultError: result<'a, string> => result<'a, error> = n =>
|
||||
n->E.R2.errMap(r => r->fromString->Error)
|
||||
}
|
||||
|
||||
module Operation = {
|
||||
type direction =
|
||||
| Algebraic
|
||||
| Pointwise
|
||||
|
||||
type arithmeticOperation = [
|
||||
| #Add
|
||||
| #Multiply
|
||||
| #Subtract
|
||||
| #Divide
|
||||
| #Power
|
||||
| #Logarithm
|
||||
]
|
||||
|
||||
let arithmeticToFn = (arithmetic: arithmeticOperation) =>
|
||||
switch arithmetic {
|
||||
| #Add => \"+."
|
||||
| #Multiply => \"*."
|
||||
| #Subtract => \"-."
|
||||
| #Power => \"**"
|
||||
| #Divide => \"/."
|
||||
| #Logarithm => (a, b) => log(a) /. log(b)
|
||||
}
|
||||
|
||||
type toFloat = [
|
||||
| #Cdf(float)
|
||||
| #Inv(float)
|
||||
| #Mean
|
||||
| #Pdf(float)
|
||||
| #Sample
|
||||
]
|
||||
|
||||
@genType
|
||||
type pointsetXSelection = [#Linear | #ByWeight]
|
||||
|
||||
type toDist =
|
||||
| Normalize
|
||||
| ToPointSet
|
||||
| ToSampleSet(int)
|
||||
| Truncate(option<float>, option<float>)
|
||||
| Inspect
|
||||
|
||||
type toFloatArray = Sample(int)
|
||||
|
||||
type toString =
|
||||
| ToString
|
||||
| ToSparkline(int)
|
||||
|
||||
type toBool = IsNormalized
|
||||
|
||||
type fromDist =
|
||||
| ToFloat(toFloat)
|
||||
| ToDist(toDist)
|
||||
| ToDistCombination(direction, arithmeticOperation, [#Dist(genericDist) | #Float(float)])
|
||||
| ToString(toString)
|
||||
| ToBool(toBool)
|
||||
|
||||
type singleParamaterFunction =
|
||||
| FromDist(fromDist)
|
||||
| FromFloat(fromDist)
|
||||
|
||||
@genType
|
||||
type genericFunctionCallInfo =
|
||||
| FromDist(fromDist, genericDist)
|
||||
| FromFloat(fromDist, float)
|
||||
| Mixture(array<(genericDist, float)>)
|
||||
|
||||
let distCallToString = (distFunction: fromDist): string =>
|
||||
switch distFunction {
|
||||
| ToFloat(#Cdf(r)) => `cdf(${E.Float.toFixed(r)})`
|
||||
| ToFloat(#Inv(r)) => `inv(${E.Float.toFixed(r)})`
|
||||
| ToFloat(#Mean) => `mean`
|
||||
| ToFloat(#Pdf(r)) => `pdf(${E.Float.toFixed(r)})`
|
||||
| ToFloat(#Sample) => `sample`
|
||||
| ToDist(Normalize) => `normalize`
|
||||
| ToDist(ToPointSet) => `toPointSet`
|
||||
| ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})`
|
||||
| ToDist(Truncate(_, _)) => `truncate`
|
||||
| ToDist(Inspect) => `inspect`
|
||||
| ToString(ToString) => `toString`
|
||||
| ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})`
|
||||
| ToBool(IsNormalized) => `isNormalized`
|
||||
| ToDistCombination(Algebraic, _, _) => `algebraic`
|
||||
| ToDistCombination(Pointwise, _, _) => `pointwise`
|
||||
}
|
||||
|
||||
let toString = (d: genericFunctionCallInfo): string =>
|
||||
switch d {
|
||||
| FromDist(f, _) | FromFloat(f, _) => distCallToString(f)
|
||||
| Mixture(_) => `mixture`
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
It can be a pain to write out the genericFunctionCallInfo. The constructors help with this.
|
||||
This code only covers some of genericFunctionCallInfo: many arguments could be called with either a
|
||||
float or a distribution. The "UsingDists" module assumes that everything is a distribution.
|
||||
This is a tradeoff of some generality in order to get a bit more simplicity.
|
||||
I could see having a longer interface in the future, but it could be messy.
|
||||
Like, algebraicAddDistFloat vs. algebraicAddDistDist
|
||||
*/
|
||||
module Constructors = {
|
||||
type t = Operation.genericFunctionCallInfo
|
||||
|
||||
module UsingDists = {
|
||||
@genType
|
||||
let mean = (dist): t => FromDist(ToFloat(#Mean), dist)
|
||||
let sample = (dist): t => FromDist(ToFloat(#Sample), dist)
|
||||
let cdf = (dist, x): t => FromDist(ToFloat(#Cdf(x)), dist)
|
||||
let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist)
|
||||
let pdf = (dist, x): t => FromDist(ToFloat(#Pdf(x)), dist)
|
||||
let normalize = (dist): t => FromDist(ToDist(Normalize), dist)
|
||||
let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist)
|
||||
let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist)
|
||||
let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist)
|
||||
let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist)
|
||||
let inspect = (dist): t => FromDist(ToDist(Inspect), dist)
|
||||
let toString = (dist): t => FromDist(ToString(ToString), dist)
|
||||
let toSparkline = (dist, n): t => FromDist(ToString(ToSparkline(n)), dist)
|
||||
let algebraicAdd = (dist1, dist2: genericDist): t => FromDist(
|
||||
ToDistCombination(Algebraic, #Add, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicMultiply = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic, #Multiply, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicDivide = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic, #Divide, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicSubtract = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic, #Subtract, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicLogarithm = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic, #Logarithm, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let algebraicPower = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Algebraic, #Power, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseAdd = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Add, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseMultiply = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Multiply, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseDivide = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Divide, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseSubtract = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Subtract, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwiseLogarithm = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Logarithm, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
let pointwisePower = (dist1, dist2): t => FromDist(
|
||||
ToDistCombination(Pointwise, #Power, #Dist(dist2)),
|
||||
dist1,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -96,36 +96,25 @@ let toDiscretePointMassesFromTriangulars = (
|
|||
}
|
||||
|
||||
let combineShapesContinuousContinuous = (
|
||||
op: Operation.algebraicOperation,
|
||||
op: Operation.convolutionOperation,
|
||||
s1: PointSetTypes.xyShape,
|
||||
s2: PointSetTypes.xyShape,
|
||||
): PointSetTypes.xyShape => {
|
||||
// if we add the two distributions, we should probably use normal filters.
|
||||
// if we multiply the two distributions, we should probably use lognormal filters.
|
||||
let t1m = toDiscretePointMassesFromTriangulars(s1)
|
||||
let t2m = switch op {
|
||||
| #Divide => toDiscretePointMassesFromTriangulars(~inverse=true, s2)
|
||||
| _ => toDiscretePointMassesFromTriangulars(~inverse=false, s2)
|
||||
}
|
||||
let t2m = toDiscretePointMassesFromTriangulars(~inverse=false, s2)
|
||||
|
||||
let combineMeansFn = switch op {
|
||||
| #Add => (m1, m2) => m1 +. m2
|
||||
| #Subtract => (m1, m2) => m1 -. m2
|
||||
| #Multiply => (m1, m2) => m1 *. m2
|
||||
| #Divide => (m1, mInv2) => m1 *. mInv2
|
||||
| #Power => (m1, mInv2) => m1 ** mInv2
|
||||
| #Logarithm => (m1, m2) => log(m1) /. log(m2)
|
||||
} // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
|
||||
|
||||
// TODO: Variances are for exponentatiation or logarithms are almost totally made up and very likely very wrong.
|
||||
// converts the variances and means of the two inputs into the variance of the output
|
||||
let combineVariancesFn = switch op {
|
||||
| #Add => (v1, v2, _, _) => v1 +. v2
|
||||
| #Subtract => (v1, v2, _, _) => v1 +. v2
|
||||
| #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||
| #Power => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||
| #Logarithm => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||
| #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
|
||||
}
|
||||
|
||||
// TODO: If operating on two positive-domain distributions, we should take that into account
|
||||
|
@ -198,16 +187,20 @@ let toDiscretePointMassesFromDiscrete = (s: PointSetTypes.xyShape): pointMassesW
|
|||
{n: n, masses: masses, means: means, variances: variances}
|
||||
}
|
||||
|
||||
type argumentPosition = First | Second
|
||||
|
||||
let combineShapesContinuousDiscrete = (
|
||||
op: Operation.algebraicOperation,
|
||||
op: Operation.convolutionOperation,
|
||||
continuousShape: PointSetTypes.xyShape,
|
||||
discreteShape: PointSetTypes.xyShape,
|
||||
~discretePosition: argumentPosition,
|
||||
): PointSetTypes.xyShape => {
|
||||
let t1n = continuousShape |> XYShape.T.length
|
||||
let t2n = discreteShape |> XYShape.T.length
|
||||
|
||||
// each x pair is added/subtracted
|
||||
let fn = Operation.Algebraic.toFn(op)
|
||||
let opFunc = Operation.Convolution.toFn(op)
|
||||
let fn = discretePosition == First ? (a, b) => opFunc(b, a) : opFunc
|
||||
|
||||
let outXYShapes: array<array<(float, float)>> = Belt.Array.makeUninitializedUnsafe(t2n)
|
||||
|
||||
|
@ -218,49 +211,56 @@ let combineShapesContinuousDiscrete = (
|
|||
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
||||
let dxyShape: array<(float, float)> = Belt.Array.makeUninitializedUnsafe(t1n)
|
||||
for i in 0 to t1n - 1 {
|
||||
// When this operation is flipped (like 1 - normal(5, 2)) then the
|
||||
// x axis coordinates would all come out the wrong order. So we need
|
||||
// to fill them out in the opposite direction
|
||||
let index = discretePosition == First ? t1n - 1 - i : i
|
||||
Belt.Array.set(
|
||||
dxyShape,
|
||||
i,
|
||||
index,
|
||||
(
|
||||
fn(continuousShape.xs[i], discreteShape.xs[j]),
|
||||
continuousShape.ys[i] *. discreteShape.ys[j],
|
||||
),
|
||||
) |> ignore
|
||||
()
|
||||
}
|
||||
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore
|
||||
()
|
||||
}
|
||||
| #Multiply
|
||||
| #Power
|
||||
| #Logarithm
|
||||
| #Divide =>
|
||||
| #Multiply =>
|
||||
for j in 0 to t2n - 1 {
|
||||
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
||||
let dxyShape: array<(float, float)> = Belt.Array.makeUninitializedUnsafe(t1n)
|
||||
for i in 0 to t1n - 1 {
|
||||
// If this operation would flip the x axis (such as -1 * normal(5, 2)),
|
||||
// then we want to fill the shape in backwards to ensure all the points
|
||||
// are still in the right order
|
||||
let index = discreteShape.xs[j] > 0.0 ? i : t1n - 1 - i
|
||||
Belt.Array.set(
|
||||
dxyShape,
|
||||
i,
|
||||
index,
|
||||
(
|
||||
fn(continuousShape.xs[i], discreteShape.xs[j]),
|
||||
continuousShape.ys[i] *. discreteShape.ys[j] /. discreteShape.xs[j],
|
||||
continuousShape.ys[i] *. discreteShape.ys[j] /. Js.Math.abs_float(discreteShape.xs[j]),
|
||||
),
|
||||
) |> ignore
|
||||
()
|
||||
}
|
||||
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
outXYShapes
|
||||
|> E.A.fmap(XYShape.T.fromZippedArray)
|
||||
|> E.A.fold_left(
|
||||
XYShape.PointwiseCombination.combine(
|
||||
\"+.",
|
||||
(acc, x) =>
|
||||
XYShape.PointwiseCombination.addCombine(
|
||||
XYShape.XtoY.continuousInterpolator(#Linear, #UseZero),
|
||||
acc,
|
||||
x,
|
||||
),
|
||||
XYShape.T.empty,
|
||||
)
|
||||
}
|
||||
|
||||
let isOrdered = (a: XYShape.T.t): bool => E.A.Sorted.Floats.isSorted(a.xs)
|
||||
|
|
|
@ -87,12 +87,11 @@ let stepwiseToLinear = (t: t): t =>
|
|||
// Note: This results in a distribution with as many points as the sum of those in t1 and t2.
|
||||
let combinePointwise = (
|
||||
~integralSumCachesFn=(_, _) => None,
|
||||
~integralCachesFn: (t, t) => option<t>=(_, _) => None,
|
||||
~distributionType: PointSetTypes.distributionType=#PDF,
|
||||
fn: (float, float) => float,
|
||||
fn: (float, float) => result<float, Operation.Error.t>,
|
||||
t1: PointSetTypes.continuousShape,
|
||||
t2: PointSetTypes.continuousShape,
|
||||
): PointSetTypes.continuousShape => {
|
||||
): result<PointSetTypes.continuousShape, 'e> => {
|
||||
// If we're adding the distributions, and we know the total of each, then we
|
||||
// can just sum them up. Otherwise, all bets are off.
|
||||
let combinedIntegralSum = Common.combineIntegralSums(
|
||||
|
@ -120,9 +119,8 @@ let combinePointwise = (
|
|||
|
||||
let interpolator = XYShape.XtoY.continuousInterpolator(t1.interpolation, extrapolation)
|
||||
|
||||
make(
|
||||
~integralSumCache=combinedIntegralSum,
|
||||
XYShape.PointwiseCombination.combine(fn, interpolator, t1.xyShape, t2.xyShape),
|
||||
XYShape.PointwiseCombination.combine(fn, interpolator, t1.xyShape, t2.xyShape)->E.R2.fmap(x =>
|
||||
make(~integralSumCache=combinedIntegralSum, x)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -141,18 +139,47 @@ let updateIntegralSumCache = (integralSumCache, t: t): t => {
|
|||
|
||||
let updateIntegralCache = (integralCache, t: t): t => {...t, integralCache: integralCache}
|
||||
|
||||
let reduce = (
|
||||
let sum = (
|
||||
~integralSumCachesFn: (float, float) => option<float>=(_, _) => None,
|
||||
~integralCachesFn: (t, t) => option<t>=(_, _) => None,
|
||||
fn,
|
||||
continuousShapes,
|
||||
) =>
|
||||
): t =>
|
||||
continuousShapes |> E.A.fold_left(
|
||||
combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn),
|
||||
(x, y) =>
|
||||
combinePointwise(~integralSumCachesFn, (a, b) => Ok(a +. b), x, y)->E.R.toExn(
|
||||
"Addition should never fail",
|
||||
_,
|
||||
),
|
||||
empty,
|
||||
)
|
||||
|
||||
let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn, t: t) =>
|
||||
let reduce = (
|
||||
~integralSumCachesFn: (float, float) => option<float>=(_, _) => None,
|
||||
fn: (float, float) => result<float, 'e>,
|
||||
continuousShapes,
|
||||
): result<t, 'e> =>
|
||||
continuousShapes |> E.A.R.foldM(combinePointwise(~integralSumCachesFn, fn), empty)
|
||||
|
||||
let mapYResult = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
~fn: float => result<float, 'e>,
|
||||
t: t,
|
||||
): result<t, 'e> =>
|
||||
XYShape.T.mapYResult(fn, getShape(t))->E.R2.fmap(x =>
|
||||
make(
|
||||
~interpolation=t.interpolation,
|
||||
~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn),
|
||||
~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn),
|
||||
x,
|
||||
)
|
||||
)
|
||||
|
||||
let mapY = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
~fn: float => float,
|
||||
t: t,
|
||||
): t =>
|
||||
make(
|
||||
~interpolation=t.interpolation,
|
||||
~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn),
|
||||
|
@ -176,6 +203,7 @@ module T = Dist({
|
|||
let minX = shapeFn(XYShape.T.minX)
|
||||
let maxX = shapeFn(XYShape.T.maxX)
|
||||
let mapY = mapY
|
||||
let mapYResult = mapYResult
|
||||
let updateIntegralCache = updateIntegralCache
|
||||
let toDiscreteProbabilityMassFraction = _ => 0.0
|
||||
let toPointSetDist = (t: t): PointSetTypes.pointSetDist => Continuous(t)
|
||||
|
@ -241,15 +269,21 @@ module T = Dist({
|
|||
XYShape.Analysis.getVarianceDangerously(t, mean, Analysis.getMeanOfSquares)
|
||||
})
|
||||
|
||||
let isNormalized = (t: t): bool => {
|
||||
let areaUnderIntegral = t |> updateIntegralCache(Some(T.integral(t))) |> T.integralEndY
|
||||
areaUnderIntegral < 1. +. 1e-7 && areaUnderIntegral > 1. -. 1e-7
|
||||
}
|
||||
|
||||
let downsampleEquallyOverX = (length, t): t =>
|
||||
t |> shapeMap(XYShape.XsConversion.proportionEquallyOverX(length))
|
||||
|
||||
/* This simply creates multiple copies of the continuous distribution, scaled and shifted according to
|
||||
each discrete data point, and then adds them all together. */
|
||||
let combineAlgebraicallyWithDiscrete = (
|
||||
op: Operation.algebraicOperation,
|
||||
op: Operation.convolutionOperation,
|
||||
t1: t,
|
||||
t2: PointSetTypes.discreteShape,
|
||||
~discretePosition: AlgebraicShapeCombination.argumentPosition,
|
||||
) => {
|
||||
let t1s = t1 |> getShape
|
||||
let t2s = t2.xyShape // TODO would like to use Discrete.getShape here, but current file structure doesn't allow for that
|
||||
|
@ -266,11 +300,11 @@ let combineAlgebraicallyWithDiscrete = (
|
|||
op,
|
||||
continuousAsLinear |> getShape,
|
||||
t2s,
|
||||
~discretePosition,
|
||||
)
|
||||
|
||||
let combinedIntegralSum = switch op {
|
||||
| #Multiply
|
||||
| #Divide =>
|
||||
| #Multiply =>
|
||||
Common.combineIntegralSums((a, b) => Some(a *. b), t1.integralSumCache, t2.integralSumCache)
|
||||
| _ => None
|
||||
}
|
||||
|
@ -280,7 +314,7 @@ let combineAlgebraicallyWithDiscrete = (
|
|||
}
|
||||
}
|
||||
|
||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t) => {
|
||||
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t) => {
|
||||
let s1 = t1 |> getShape
|
||||
let s2 = t2 |> getShape
|
||||
let t1n = s1 |> XYShape.T.length
|
||||
|
|
|
@ -34,11 +34,6 @@ let lastY = (t: t) => t |> getShape |> XYShape.T.lastY
|
|||
|
||||
let combinePointwise = (
|
||||
~integralSumCachesFn=(_, _) => None,
|
||||
~integralCachesFn: (
|
||||
PointSetTypes.continuousShape,
|
||||
PointSetTypes.continuousShape,
|
||||
) => option<PointSetTypes.continuousShape>=(_, _) => None,
|
||||
fn,
|
||||
t1: PointSetTypes.discreteShape,
|
||||
t2: PointSetTypes.discreteShape,
|
||||
): PointSetTypes.discreteShape => {
|
||||
|
@ -54,24 +49,16 @@ let combinePointwise = (
|
|||
make(
|
||||
~integralSumCache=combinedIntegralSum,
|
||||
XYShape.PointwiseCombination.combine(
|
||||
\"+.",
|
||||
(a, b) => Ok(a +. b),
|
||||
XYShape.XtoY.discreteInterpolator,
|
||||
t1.xyShape,
|
||||
t2.xyShape,
|
||||
),
|
||||
)->E.R.toExn("Addition operation should never fail", _),
|
||||
)
|
||||
}
|
||||
|
||||
let reduce = (
|
||||
~integralSumCachesFn=(_, _) => None,
|
||||
~integralCachesFn=(_, _) => None,
|
||||
fn,
|
||||
discreteShapes,
|
||||
): PointSetTypes.discreteShape =>
|
||||
discreteShapes |> E.A.fold_left(
|
||||
combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn),
|
||||
empty,
|
||||
)
|
||||
let reduce = (~integralSumCachesFn=(_, _) => None, discreteShapes): PointSetTypes.discreteShape =>
|
||||
discreteShapes |> E.A.fold_left(combinePointwise(~integralSumCachesFn), empty)
|
||||
|
||||
let updateIntegralSumCache = (integralSumCache, t: t): t => {
|
||||
...t,
|
||||
|
@ -85,7 +72,7 @@ let updateIntegralCache = (integralCache, t: t): t => {
|
|||
|
||||
/* This multiples all of the data points together and creates a new discrete distribution from the results.
|
||||
Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */
|
||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t => {
|
||||
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t): t => {
|
||||
let t1s = t1 |> getShape
|
||||
let t2s = t2 |> getShape
|
||||
let t1n = t1s |> XYShape.T.length
|
||||
|
@ -97,7 +84,7 @@ let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =
|
|||
t2.integralSumCache,
|
||||
)
|
||||
|
||||
let fn = Operation.Algebraic.toFn(op)
|
||||
let fn = Operation.Convolution.toFn(op)
|
||||
let xToYMap = E.FloatFloatMap.empty()
|
||||
|
||||
for i in 0 to t1n - 1 {
|
||||
|
@ -116,7 +103,26 @@ let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =
|
|||
make(~integralSumCache=combinedIntegralSum, combinedShape)
|
||||
}
|
||||
|
||||
let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn, t: t) =>
|
||||
let mapYResult = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
~fn: float => result<float, 'e>,
|
||||
t: t,
|
||||
): result<t, 'e> =>
|
||||
XYShape.T.mapYResult(fn, getShape(t))->E.R2.fmap(x =>
|
||||
make(
|
||||
~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn),
|
||||
~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn),
|
||||
x,
|
||||
)
|
||||
)
|
||||
|
||||
let mapY = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
~fn: float => float,
|
||||
t: t,
|
||||
): t =>
|
||||
make(
|
||||
~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn),
|
||||
~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn),
|
||||
|
@ -156,6 +162,7 @@ module T = Dist({
|
|||
let maxX = shapeFn(XYShape.T.maxX)
|
||||
let toDiscreteProbabilityMassFraction = _ => 1.0
|
||||
let mapY = mapY
|
||||
let mapYResult = mapYResult
|
||||
let updateIntegralCache = updateIntegralCache
|
||||
let toPointSetDist = (t: t): PointSetTypes.pointSetDist => Discrete(t)
|
||||
let toContinuous = _ => None
|
||||
|
|
|
@ -9,6 +9,12 @@ module type dist = {
|
|||
~fn: float => float,
|
||||
t,
|
||||
) => t
|
||||
let mapYResult: (
|
||||
~integralSumCacheFn: float => option<float>=?,
|
||||
~integralCacheFn: PointSetTypes.continuousShape => option<PointSetTypes.continuousShape>=?,
|
||||
~fn: float => result<float, 'e>,
|
||||
t,
|
||||
) => result<t, 'e>
|
||||
let xToY: (float, t) => PointSetTypes.mixedPoint
|
||||
let toPointSetDist: t => PointSetTypes.pointSetDist
|
||||
let toContinuous: t => option<PointSetTypes.continuousShape>
|
||||
|
@ -37,6 +43,7 @@ module Dist = (T: dist) => {
|
|||
let integral = T.integral
|
||||
let xTotalRange = (t: t) => maxX(t) -. minX(t)
|
||||
let mapY = T.mapY
|
||||
let mapYResult = T.mapYResult
|
||||
let xToY = T.xToY
|
||||
let downsample = T.downsample
|
||||
let toPointSetDist = T.toPointSetDist
|
||||
|
|
|
@ -146,8 +146,7 @@ module T = Dist({
|
|||
let discreteIntegral = Continuous.stepwiseToLinear(Discrete.T.Integral.get(t.discrete))
|
||||
|
||||
Continuous.make(
|
||||
XYShape.PointwiseCombination.combine(
|
||||
\"+.",
|
||||
XYShape.PointwiseCombination.addCombine(
|
||||
XYShape.XtoY.continuousInterpolator(#Linear, #UseOutermostPoints),
|
||||
Continuous.getShape(continuousIntegral),
|
||||
Continuous.getShape(discreteIntegral),
|
||||
|
@ -161,24 +160,20 @@ module T = Dist({
|
|||
|
||||
let integralYtoX = (f, t) => t |> integral |> Continuous.getShape |> XYShape.YtoX.linear(f)
|
||||
|
||||
// This pipes all ys (continuous and discrete) through fn.
|
||||
// If mapY is a linear operation, we might be able to update the integralSumCaches as well;
|
||||
// if not, they'll be set to None.
|
||||
let mapY = (
|
||||
~integralSumCacheFn=previousIntegralSum => None,
|
||||
~integralCacheFn=previousIntegral => None,
|
||||
~fn,
|
||||
let createMixedFromContinuousDiscrete = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
t: t,
|
||||
discrete: PointSetTypes.discreteShape,
|
||||
continuous: PointSetTypes.continuousShape,
|
||||
): t => {
|
||||
let yMappedDiscrete: PointSetTypes.discreteShape =
|
||||
t.discrete
|
||||
|> Discrete.T.mapY(~fn)
|
||||
discrete
|
||||
|> Discrete.updateIntegralSumCache(E.O.bind(t.discrete.integralSumCache, integralSumCacheFn))
|
||||
|> Discrete.updateIntegralCache(E.O.bind(t.discrete.integralCache, integralCacheFn))
|
||||
|
||||
let yMappedContinuous: PointSetTypes.continuousShape =
|
||||
t.continuous
|
||||
|> Continuous.T.mapY(~fn)
|
||||
continuous
|
||||
|> Continuous.updateIntegralSumCache(
|
||||
E.O.bind(t.continuous.integralSumCache, integralSumCacheFn),
|
||||
)
|
||||
|
@ -192,6 +187,46 @@ module T = Dist({
|
|||
}
|
||||
}
|
||||
|
||||
// This pipes all ys (continuous and discrete) through fn.
|
||||
// If mapY is a linear operation, we might be able to update the integralSumCaches as well;
|
||||
// if not, they'll be set to None.
|
||||
let mapY = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
~fn: float => float,
|
||||
t: t,
|
||||
): t => {
|
||||
let discrete = t.discrete |> Discrete.T.mapY(~fn)
|
||||
let continuous = t.continuous |> Continuous.T.mapY(~fn)
|
||||
createMixedFromContinuousDiscrete(
|
||||
~integralCacheFn,
|
||||
~integralSumCacheFn,
|
||||
t,
|
||||
discrete,
|
||||
continuous,
|
||||
)
|
||||
}
|
||||
|
||||
let mapYResult = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
~fn: float => result<float, 'e>,
|
||||
t: t,
|
||||
): result<t, 'e> => {
|
||||
E.R.merge(
|
||||
Discrete.T.mapYResult(~fn, t.discrete),
|
||||
Continuous.T.mapYResult(~fn, t.continuous),
|
||||
)->E.R2.fmap(((discreteMapped, continuousMapped)) => {
|
||||
createMixedFromContinuousDiscrete(
|
||||
~integralCacheFn,
|
||||
~integralSumCacheFn,
|
||||
t,
|
||||
discreteMapped,
|
||||
continuousMapped,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let mean = ({discrete, continuous}: t): float => {
|
||||
let discreteMean = Discrete.T.mean(discrete)
|
||||
let continuousMean = Continuous.T.mean(continuous)
|
||||
|
@ -226,7 +261,7 @@ module T = Dist({
|
|||
}
|
||||
})
|
||||
|
||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t => {
|
||||
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t): t => {
|
||||
// Discrete convolution can cause a huge increase in the number of samples,
|
||||
// so we'll first downsample.
|
||||
|
||||
|
@ -242,9 +277,19 @@ let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =
|
|||
// continuous (*) continuous => continuous, but also
|
||||
// discrete (*) continuous => continuous (and vice versa). We have to take care of all combos and then combine them:
|
||||
let ccConvResult = Continuous.combineAlgebraically(op, t1.continuous, t2.continuous)
|
||||
let dcConvResult = Continuous.combineAlgebraicallyWithDiscrete(op, t2.continuous, t1.discrete)
|
||||
let cdConvResult = Continuous.combineAlgebraicallyWithDiscrete(op, t1.continuous, t2.discrete)
|
||||
let continuousConvResult = Continuous.reduce(\"+.", [ccConvResult, dcConvResult, cdConvResult])
|
||||
let dcConvResult = Continuous.combineAlgebraicallyWithDiscrete(
|
||||
op,
|
||||
t2.continuous,
|
||||
t1.discrete,
|
||||
~discretePosition=First,
|
||||
)
|
||||
let cdConvResult = Continuous.combineAlgebraicallyWithDiscrete(
|
||||
op,
|
||||
t1.continuous,
|
||||
t2.discrete,
|
||||
~discretePosition=Second,
|
||||
)
|
||||
let continuousConvResult = Continuous.sum([ccConvResult, dcConvResult, cdConvResult])
|
||||
|
||||
// ... finally, discrete (*) discrete => discrete, obviously:
|
||||
let discreteConvResult = Discrete.combineAlgebraically(op, t1.discrete, t2.discrete)
|
||||
|
@ -266,21 +311,18 @@ let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =
|
|||
let combinePointwise = (
|
||||
~integralSumCachesFn=(_, _) => None,
|
||||
~integralCachesFn=(_, _) => None,
|
||||
fn,
|
||||
fn: (float, float) => result<float, 'e>,
|
||||
t1: t,
|
||||
t2: t,
|
||||
): t => {
|
||||
): result<t, 'e> => {
|
||||
let reducedDiscrete =
|
||||
[t1, t2]
|
||||
|> E.A.fmap(toDiscrete)
|
||||
|> E.A.O.concatSomes
|
||||
|> Discrete.reduce(~integralSumCachesFn, ~integralCachesFn, fn)
|
||||
[t1, t2] |> E.A.fmap(toDiscrete) |> E.A.O.concatSomes |> Discrete.reduce(~integralSumCachesFn)
|
||||
|
||||
let reducedContinuous =
|
||||
[t1, t2]
|
||||
|> E.A.fmap(toContinuous)
|
||||
|> E.A.O.concatSomes
|
||||
|> Continuous.reduce(~integralSumCachesFn, ~integralCachesFn, fn)
|
||||
|> Continuous.reduce(~integralSumCachesFn, fn)
|
||||
|
||||
let combinedIntegralSum = Common.combineIntegralSums(
|
||||
integralSumCachesFn,
|
||||
|
@ -293,11 +335,12 @@ let combinePointwise = (
|
|||
t1.integralCache,
|
||||
t2.integralCache,
|
||||
)
|
||||
|
||||
reducedContinuous->E.R2.fmap(continuous =>
|
||||
make(
|
||||
~integralSumCache=combinedIntegralSum,
|
||||
~integralCache=combinedIntegral,
|
||||
~discrete=reducedDiscrete,
|
||||
~continuous=reducedContinuous,
|
||||
~continuous,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,13 @@ let fmap = ((fn1, fn2, fn3), t: t): t =>
|
|||
| Continuous(m) => Continuous(fn3(m))
|
||||
}
|
||||
|
||||
let fmapResult = ((fn1, fn2, fn3), t: t): result<t, 'e> =>
|
||||
switch t {
|
||||
| Mixed(m) => fn1(m)->E.R2.fmap(x => PointSetTypes.Mixed(x))
|
||||
| Discrete(m) => fn2(m)->E.R2.fmap(x => PointSetTypes.Discrete(x))
|
||||
| Continuous(m) => fn3(m)->E.R2.fmap(x => PointSetTypes.Continuous(x))
|
||||
}
|
||||
|
||||
let toMixed = mapToAll((
|
||||
m => m,
|
||||
d =>
|
||||
|
@ -35,13 +42,24 @@ let toMixed = mapToAll((
|
|||
))
|
||||
|
||||
//TODO WARNING: The combineAlgebraicallyWithDiscrete will break for subtraction and division, like, discrete - continous
|
||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =>
|
||||
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t): t =>
|
||||
switch (t1, t2) {
|
||||
| (Continuous(m1), Continuous(m2)) =>
|
||||
Continuous.combineAlgebraically(op, m1, m2) |> Continuous.T.toPointSetDist
|
||||
| (Continuous(m1), Discrete(m2))
|
||||
| (Discrete(m2), Continuous(m1)) =>
|
||||
Continuous.combineAlgebraicallyWithDiscrete(op, m1, m2) |> Continuous.T.toPointSetDist
|
||||
| (Discrete(m1), Continuous(m2)) =>
|
||||
Continuous.combineAlgebraicallyWithDiscrete(
|
||||
op,
|
||||
m2,
|
||||
m1,
|
||||
~discretePosition=First,
|
||||
) |> Continuous.T.toPointSetDist
|
||||
| (Continuous(m1), Discrete(m2)) =>
|
||||
Continuous.combineAlgebraicallyWithDiscrete(
|
||||
op,
|
||||
m1,
|
||||
m2,
|
||||
~discretePosition=Second,
|
||||
) |> Continuous.T.toPointSetDist
|
||||
| (Discrete(m1), Discrete(m2)) =>
|
||||
Discrete.combineAlgebraically(op, m1, m2) |> Discrete.T.toPointSetDist
|
||||
| (m1, m2) => Mixed.combineAlgebraically(op, toMixed(m1), toMixed(m2)) |> Mixed.T.toPointSetDist
|
||||
|
@ -53,23 +71,28 @@ let combinePointwise = (
|
|||
PointSetTypes.continuousShape,
|
||||
PointSetTypes.continuousShape,
|
||||
) => option<PointSetTypes.continuousShape>=(_, _) => None,
|
||||
fn,
|
||||
fn: (float, float) => result<float, Operation.Error.t>,
|
||||
t1: t,
|
||||
t2: t,
|
||||
) =>
|
||||
): result<PointSetTypes.pointSetDist, Operation.Error.t> =>
|
||||
switch (t1, t2) {
|
||||
| (Continuous(m1), Continuous(m2)) =>
|
||||
PointSetTypes.Continuous(
|
||||
Continuous.combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn, m1, m2),
|
||||
)
|
||||
Continuous.combinePointwise(
|
||||
~integralSumCachesFn,
|
||||
fn,
|
||||
m1,
|
||||
m2,
|
||||
)->E.R2.fmap(x => PointSetTypes.Continuous(x))
|
||||
| (Discrete(m1), Discrete(m2)) =>
|
||||
PointSetTypes.Discrete(
|
||||
Discrete.combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn, m1, m2),
|
||||
)
|
||||
Ok(PointSetTypes.Discrete(Discrete.combinePointwise(~integralSumCachesFn, m1, m2)))
|
||||
| (m1, m2) =>
|
||||
PointSetTypes.Mixed(
|
||||
Mixed.combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn, toMixed(m1), toMixed(m2)),
|
||||
)
|
||||
Mixed.combinePointwise(
|
||||
~integralSumCachesFn,
|
||||
~integralCachesFn,
|
||||
fn,
|
||||
toMixed(m1),
|
||||
toMixed(m2),
|
||||
)->E.R2.fmap(x => PointSetTypes.Mixed(x))
|
||||
}
|
||||
|
||||
module T = Dist({
|
||||
|
@ -134,10 +157,8 @@ module T = Dist({
|
|||
let integralYtoX = f =>
|
||||
mapToAll((Mixed.T.Integral.yToX(f), Discrete.T.Integral.yToX(f), Continuous.T.Integral.yToX(f)))
|
||||
let maxX = mapToAll((Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX))
|
||||
let mapY = (
|
||||
~integralSumCacheFn=previousIntegralSum => None,
|
||||
~integralCacheFn=previousIntegral => None,
|
||||
~fn,
|
||||
let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn: float => float): (
|
||||
t => t
|
||||
) =>
|
||||
fmap((
|
||||
Mixed.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn),
|
||||
|
@ -145,6 +166,17 @@ module T = Dist({
|
|||
Continuous.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn),
|
||||
))
|
||||
|
||||
let mapYResult = (
|
||||
~integralSumCacheFn=_ => None,
|
||||
~integralCacheFn=_ => None,
|
||||
~fn: float => result<float, 'e>,
|
||||
): (t => result<t, 'e>) =>
|
||||
fmapResult((
|
||||
Mixed.T.mapYResult(~integralSumCacheFn, ~integralCacheFn, ~fn),
|
||||
Discrete.T.mapYResult(~integralSumCacheFn, ~integralCacheFn, ~fn),
|
||||
Continuous.T.mapYResult(~integralSumCacheFn, ~integralCacheFn, ~fn),
|
||||
))
|
||||
|
||||
let mean = (t: t): float =>
|
||||
switch t {
|
||||
| Mixed(m) => Mixed.T.mean(m)
|
||||
|
@ -203,8 +235,8 @@ let operate = (distToFloatOp: Operation.distToFloatOperation, s): float =>
|
|||
| #Mean => T.mean(s)
|
||||
}
|
||||
|
||||
let toSparkline = (t: t, bucketCount) =>
|
||||
let toSparkline = (t: t, bucketCount): result<string, PointSetTypes.sparklineError> =>
|
||||
T.toContinuous(t)
|
||||
->E.O2.fmap(Continuous.downsampleEquallyOverX(bucketCount))
|
||||
->E.O2.toResult("toContinous Error: Could not convert into continuous distribution")
|
||||
->E.O2.toResult(PointSetTypes.CannotSparklineDiscrete)
|
||||
->E.R2.fmap(r => Continuous.getShape(r).ys->Sparklines.create())
|
||||
|
|
|
@ -94,3 +94,11 @@ module MixedPoint = {
|
|||
|
||||
let add = combine2((a, b) => a +. b)
|
||||
}
|
||||
|
||||
@genType
|
||||
type sparklineError = CannotSparklineDiscrete
|
||||
|
||||
let sparklineErrorToString = (err: sparklineError): string =>
|
||||
switch err {
|
||||
| CannotSparklineDiscrete => "Cannot find the sparkline of a discrete distribution"
|
||||
}
|
||||
|
|
|
@ -15,8 +15,18 @@ const samplesToContinuousPdf = (
|
|||
if (_.isFinite(max)) {
|
||||
_samples = _.filter(_samples, (r) => r < max);
|
||||
}
|
||||
|
||||
// The pdf that's created from this function is not a pdf but a pmf. y values
|
||||
// being probability mass and not density.
|
||||
// This is awkward, because our code assumes later that y is a density
|
||||
let pdf = pdfast.create(_samples, { size, width });
|
||||
return { xs: pdf.map((r) => r.x), ys: pdf.map((r) => r.y) };
|
||||
|
||||
// To convert this to a density, we need to find the step size. This is kept
|
||||
// constant for all y values
|
||||
let stepSize = pdf[1].x - pdf[0].x;
|
||||
|
||||
// We then adjust the y values to density
|
||||
return { xs: pdf.map((r) => r.x), ys: pdf.map((r) => r.y / stepSize) };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,3 +1,24 @@
|
|||
@genType
|
||||
module Error = {
|
||||
@genType
|
||||
type sampleSetError = TooFewSamples
|
||||
|
||||
let sampleSetErrorToString = (err: sampleSetError): string =>
|
||||
switch err {
|
||||
| TooFewSamples => "Too few samples when constructing sample set"
|
||||
}
|
||||
|
||||
@genType
|
||||
type pointsetConversionError = TooFewSamplesForConversionToPointSet
|
||||
|
||||
let pointsetConversionErrorToString = (err: pointsetConversionError) =>
|
||||
switch err {
|
||||
| TooFewSamplesForConversionToPointSet => "Too Few Samples to convert to point set"
|
||||
}
|
||||
}
|
||||
|
||||
include Error
|
||||
|
||||
/*
|
||||
This is used as a smart constructor. The only way to create a SampleSetDist.t is to call
|
||||
this constructor.
|
||||
|
@ -8,7 +29,7 @@ module T: {
|
|||
//When we get a good functional library in TS, we could refactor that out.
|
||||
@genType
|
||||
type t = array<float>
|
||||
let make: array<float> => result<t, string>
|
||||
let make: array<float> => result<t, sampleSetError>
|
||||
let get: t => array<float>
|
||||
} = {
|
||||
type t = array<float>
|
||||
|
@ -16,7 +37,7 @@ module T: {
|
|||
if E.A.length(a) > 5 {
|
||||
Ok(a)
|
||||
} else {
|
||||
Error("too small")
|
||||
Error(TooFewSamples)
|
||||
}
|
||||
let get = (a: t) => a
|
||||
}
|
||||
|
@ -31,13 +52,13 @@ some refactoring.
|
|||
*/
|
||||
let toPointSetDist = (~samples: t, ~samplingInputs: SamplingInputs.samplingInputs): result<
|
||||
PointSetTypes.pointSetDist,
|
||||
string,
|
||||
pointsetConversionError,
|
||||
> =>
|
||||
SampleSetDist_ToPointSet.toPointSetDist(
|
||||
~samples=get(samples),
|
||||
~samplingInputs,
|
||||
(),
|
||||
).pointSetDist->E.O2.toResult("Failed to convert to PointSetDist")
|
||||
).pointSetDist->E.O2.toResult(TooFewSamplesForConversionToPointSet)
|
||||
|
||||
//Randomly get one sample from the distribution
|
||||
let sample = (t: t): float => {
|
||||
|
@ -62,7 +83,28 @@ let sampleN = (t: t, n) => {
|
|||
}
|
||||
|
||||
//TODO: Figure out what to do if distributions are different lengths. ``zip`` is kind of inelegant for this.
|
||||
let map2 = (~fn: (float, float) => float, ~t1: t, ~t2: t) => {
|
||||
let map2 = (~fn: (float, float) => result<float, Operation.Error.t>, ~t1: t, ~t2: t): result<
|
||||
t,
|
||||
Operation.Error.t,
|
||||
> => {
|
||||
let samples = Belt.Array.zip(get(t1), get(t2))->E.A2.fmap(((a, b)) => fn(a, b))
|
||||
make(samples)
|
||||
|
||||
// This assertion should never be reached. In order for it to be reached, one
|
||||
// of the input parameters would need to be a sample set distribution with less
|
||||
// than 6 samples. Which should be impossible due to the smart constructor.
|
||||
// I could prove this to the type system (say, creating a {first: float, second: float, ..., fifth: float, rest: array<float>}
|
||||
// But doing so would take too much time, so I'll leave it as an assertion
|
||||
E.A.R.firstErrorOrOpen(samples)->E.R2.fmap(x =>
|
||||
E.R.toExn("Input of samples should be larger than 5", make(x))
|
||||
)
|
||||
}
|
||||
|
||||
let mean = t => T.get(t)->E.A.Floats.mean
|
||||
let geomean = t => T.get(t)->E.A.Floats.geomean
|
||||
let mode = t => T.get(t)->E.A.Floats.mode
|
||||
let sum = t => T.get(t)->E.A.Floats.sum
|
||||
let min = t => T.get(t)->E.A.Floats.min
|
||||
let max = t => T.get(t)->E.A.Floats.max
|
||||
let stdev = t => T.get(t)->E.A.Floats.stdev
|
||||
let variance = t => T.get(t)->E.A.Floats.variance
|
||||
let percentile = (t, f) => T.get(t)->E.A.Floats.percentile(f)
|
||||
|
|
|
@ -39,28 +39,6 @@ module Internals = {
|
|||
module T = {
|
||||
type t = array<float>
|
||||
|
||||
let splitContinuousAndDiscrete = (sortedArray: t) => {
|
||||
let continuous = []
|
||||
let discrete = E.FloatFloatMap.empty()
|
||||
Belt.Array.forEachWithIndex(sortedArray, (index, element) => {
|
||||
let maxIndex = (sortedArray |> Array.length) - 1
|
||||
let possiblySimilarElements = switch index {
|
||||
| 0 => [index + 1]
|
||||
| n if n == maxIndex => [index - 1]
|
||||
| _ => [index - 1, index + 1]
|
||||
} |> Belt.Array.map(_, r => sortedArray[r])
|
||||
let hasSimilarElement = Belt.Array.some(possiblySimilarElements, r => r == element)
|
||||
hasSimilarElement
|
||||
? E.FloatFloatMap.increment(element, discrete)
|
||||
: {
|
||||
let _ = Js.Array.push(element, continuous)
|
||||
}
|
||||
|
||||
()
|
||||
})
|
||||
(continuous, discrete)
|
||||
}
|
||||
|
||||
let xWidthToUnitWidth = (samples, outputXYPoints, xWidth) => {
|
||||
let xyPointRange = E.A.Sorted.range(samples) |> E.O.default(0.0)
|
||||
let xyPointWidth = xyPointRange /. float_of_int(outputXYPoints)
|
||||
|
@ -83,9 +61,13 @@ let toPointSetDist = (
|
|||
~samples: Internals.T.t,
|
||||
~samplingInputs: SamplingInputs.samplingInputs,
|
||||
(),
|
||||
) => {
|
||||
): Internals.Types.outputs => {
|
||||
Array.fast_sort(compare, samples)
|
||||
let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples)
|
||||
let minDiscreteToKeep = MagicNumbers.ToPointSet.minDiscreteToKeep(samples)
|
||||
let (continuousPart, discretePart) = E.A.Sorted.Floats.splitContinuousAndDiscreteForMinWeight(
|
||||
samples,
|
||||
~minDiscreteWeight=minDiscreteToKeep,
|
||||
)
|
||||
let length = samples |> E.A.length |> float_of_int
|
||||
let discrete: PointSetTypes.discreteShape =
|
||||
discretePart
|
||||
|
@ -133,9 +115,17 @@ let toPointSetDist = (
|
|||
~discrete=Some(discrete),
|
||||
)
|
||||
|
||||
/*
|
||||
I'm surprised that this doesn't come out normalized. My guess is that the KDE library
|
||||
we're using is standardizing on something else. If we ever change that library, we should
|
||||
check to see if we still need to do this.
|
||||
*/
|
||||
|
||||
let normalizedPointSet = pointSetDist->E.O2.fmap(PointSetDist.T.normalize)
|
||||
|
||||
let samplesParse: Internals.Types.outputs = {
|
||||
continuousParseParams: pdf |> E.O.fmap(snd),
|
||||
pointSetDist: pointSetDist,
|
||||
pointSetDist: normalizedPointSet,
|
||||
}
|
||||
|
||||
samplesParse
|
||||
|
|
|
@ -52,7 +52,7 @@ module Normal = {
|
|||
switch operation {
|
||||
| #Add => Some(#Normal({mean: n1 +. n2.mean, stdev: n2.stdev}))
|
||||
| #Subtract => Some(#Normal({mean: n1 -. n2.mean, stdev: n2.stdev}))
|
||||
| #Multiply => Some(#Normal({mean: n1 *. n2.mean, stdev: n1 *. n2.stdev}))
|
||||
| #Multiply => Some(#Normal({mean: n1 *. n2.mean, stdev: Js.Math.abs_float(n1) *. n2.stdev}))
|
||||
| _ => None
|
||||
}
|
||||
|
||||
|
@ -60,8 +60,8 @@ module Normal = {
|
|||
switch operation {
|
||||
| #Add => Some(#Normal({mean: n1.mean +. n2, stdev: n1.stdev}))
|
||||
| #Subtract => Some(#Normal({mean: n1.mean -. n2, stdev: n1.stdev}))
|
||||
| #Multiply => Some(#Normal({mean: n1.mean *. n2, stdev: n1.stdev *. n2}))
|
||||
| #Divide => Some(#Normal({mean: n1.mean /. n2, stdev: n1.stdev /. n2}))
|
||||
| #Multiply => Some(#Normal({mean: n1.mean *. n2, stdev: n1.stdev *. Js.Math.abs_float(n2)}))
|
||||
| #Divide => Some(#Normal({mean: n1.mean /. n2, stdev: n1.stdev /. Js.Math.abs_float(n2)}))
|
||||
| _ => None
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,10 @@ module Exponential = {
|
|||
|
||||
module Cauchy = {
|
||||
type t = cauchy
|
||||
let make = (local, scale): symbolicDist => #Cauchy({local: local, scale: scale})
|
||||
let make = (local, scale): result<symbolicDist, string> =>
|
||||
scale > 0.0
|
||||
? Ok(#Cauchy({local: local, scale: scale}))
|
||||
: Error("Cauchy distribution scale parameter must larger than 0.")
|
||||
let pdf = (x, t: t) => Jstat.Cauchy.pdf(x, t.local, t.scale)
|
||||
let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale)
|
||||
let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale)
|
||||
|
@ -377,7 +380,7 @@ module T = {
|
|||
): analyticalSimplificationResult =>
|
||||
switch (d1, d2) {
|
||||
| (#Float(v1), #Float(v2)) =>
|
||||
switch Operation.Algebraic.applyFn(op, v1, v2) {
|
||||
switch Operation.Algebraic.toFn(op, v1, v2) {
|
||||
| Ok(r) => #AnalyticalSolution(#Float(r))
|
||||
| Error(n) => #Error(n)
|
||||
}
|
||||
|
|
|
@ -45,6 +45,6 @@ type symbolicDist = [
|
|||
|
||||
type analyticalSimplificationResult = [
|
||||
| #AnalyticalSolution(symbolicDist)
|
||||
| #Error(string)
|
||||
| #Error(Operation.Error.t)
|
||||
| #NoSolution
|
||||
]
|
||||
|
|
37
packages/squiggle-lang/src/rescript/MagicNumbers.res
Normal file
37
packages/squiggle-lang/src/rescript/MagicNumbers.res
Normal file
|
@ -0,0 +1,37 @@
|
|||
module Math = {
|
||||
let e = Js.Math._E
|
||||
let pi = Js.Math._PI
|
||||
}
|
||||
|
||||
module Epsilon = {
|
||||
let ten = 1e-10
|
||||
let seven = 1e-7
|
||||
}
|
||||
|
||||
module Environment = {
|
||||
let defaultXYPointLength = 1000
|
||||
let defaultSampleCount = 10000
|
||||
}
|
||||
|
||||
module OpCost = {
|
||||
let floatCost = 1
|
||||
let symbolicCost = 1000
|
||||
// Discrete cost is the length of the xyShape
|
||||
let mixedCost = 1000
|
||||
let continuousCost = 1000
|
||||
let wildcardCost = 1000
|
||||
let monteCarloCost = Environment.defaultSampleCount
|
||||
}
|
||||
|
||||
module ToPointSet = {
|
||||
/*
|
||||
This function chooses the minimum amount of duplicate samples that need
|
||||
to exist in order for this to be considered discrete. The tricky thing
|
||||
is that there are some operations that create duplicate continuous samples,
|
||||
so we can't guarantee that these only will occur because the fundamental
|
||||
structure is meant to be discrete. I chose this heuristic because I think
|
||||
it would strike a reasonable trade-off, but I’m really unsure what’s
|
||||
best right now.
|
||||
*/
|
||||
let minDiscreteToKeep = samples => max(20, E.A.length(samples) / 50)
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
open ASTTypes
|
||||
|
||||
let toString = ASTTypes.Node.toString
|
||||
|
||||
let envs = (samplingInputs, environment) => {
|
||||
samplingInputs: samplingInputs,
|
||||
environment: environment,
|
||||
evaluateNode: ASTEvaluator.toLeaf,
|
||||
}
|
||||
|
||||
let toLeaf = (samplingInputs, environment, node: node) =>
|
||||
ASTEvaluator.toLeaf(envs(samplingInputs, environment), node)
|
||||
|
||||
let toPointSetDist = (samplingInputs, environment, node: node) =>
|
||||
switch toLeaf(samplingInputs, environment, node) {
|
||||
| Ok(#RenderedDist(pointSetDist)) => Ok(pointSetDist)
|
||||
| Ok(_) => Error("Rendering failed.")
|
||||
| Error(e) => Error(e)
|
||||
}
|
||||
|
||||
let runFunction = (samplingInputs, environment, inputs, fn: ASTTypes.Function.t) => {
|
||||
let params = envs(samplingInputs, environment)
|
||||
ASTTypes.Function.run(params, inputs, fn)
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
open ASTTypes
|
||||
|
||||
type tResult = node => result<node, string>
|
||||
|
||||
/* Given two random variables A and B, this returns the distribution
|
||||
of a new variable that is the result of the operation on A and B.
|
||||
For instance, normal(0, 1) + normal(1, 1) -> normal(1, 2).
|
||||
In general, this is implemented via convolution. */
|
||||
module AlgebraicCombination = {
|
||||
let tryAnalyticalSimplification = (operation, t1: node, t2: node) =>
|
||||
switch (operation, t1, t2) {
|
||||
| (operation, #SymbolicDist(d1), #SymbolicDist(d2)) =>
|
||||
switch SymbolicDist.T.tryAnalyticalSimplification(d1, d2, operation) {
|
||||
| #AnalyticalSolution(symbolicDist) => Ok(#SymbolicDist(symbolicDist))
|
||||
| #Error(er) => Error(er)
|
||||
| #NoSolution => Ok(#AlgebraicCombination(operation, t1, t2))
|
||||
}
|
||||
| _ => Ok(#AlgebraicCombination(operation, t1, t2))
|
||||
}
|
||||
|
||||
let combinationByRendering = (evaluationParams, algebraicOp, t1: node, t2: node): result<
|
||||
node,
|
||||
string,
|
||||
> =>
|
||||
E.R.merge(
|
||||
Node.ensureIsRenderedAndGetShape(evaluationParams, t1),
|
||||
Node.ensureIsRenderedAndGetShape(evaluationParams, t2),
|
||||
) |> E.R.fmap(((a, b)) => #RenderedDist(PointSetDist.combineAlgebraically(algebraicOp, a, b)))
|
||||
|
||||
let nodeScore: node => int = x =>
|
||||
switch x {
|
||||
| #SymbolicDist(#Float(_)) => 1
|
||||
| #SymbolicDist(_) => 1000
|
||||
| #RenderedDist(Discrete(m)) => m.xyShape |> XYShape.T.length
|
||||
| #RenderedDist(Mixed(_)) => 1000
|
||||
| #RenderedDist(Continuous(_)) => 1000
|
||||
| _ => 1000
|
||||
}
|
||||
|
||||
let choose = (t1: node, t2: node) =>
|
||||
nodeScore(t1) * nodeScore(t2) > 10000 ? #Sampling : #Analytical
|
||||
|
||||
let combine = (evaluationParams, algebraicOp, t1: node, t2: node): result<node, string> =>
|
||||
E.R.merge(
|
||||
ASTTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(evaluationParams, t1),
|
||||
ASTTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(evaluationParams, t2),
|
||||
) |> E.R.bind(_, ((a, b)) =>
|
||||
switch choose(a, b) {
|
||||
| #Sampling =>
|
||||
ASTTypes.SamplingDistribution.combineShapesUsingSampling(
|
||||
evaluationParams,
|
||||
algebraicOp,
|
||||
a,
|
||||
b,
|
||||
)
|
||||
| #Analytical => combinationByRendering(evaluationParams, algebraicOp, a, b)
|
||||
}
|
||||
)
|
||||
|
||||
let operationToLeaf = (
|
||||
evaluationParams: evaluationParams,
|
||||
algebraicOp: Operation.algebraicOperation,
|
||||
t1: node,
|
||||
t2: node,
|
||||
): result<node, string> =>
|
||||
algebraicOp
|
||||
|> tryAnalyticalSimplification(_, t1, t2)
|
||||
|> E.R.bind(_, x =>
|
||||
switch x {
|
||||
| #SymbolicDist(_) as t => Ok(t)
|
||||
| _ => combine(evaluationParams, algebraicOp, t1, t2)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module PointwiseCombination = {
|
||||
//TODO: This is crude and slow. It forces everything to be pointSetDist, even though much
|
||||
//of the process could happen on symbolic distributions without a conversion to be a pointSetDist.
|
||||
let pointwiseAdd = (evaluationParams: evaluationParams, t1: node, t2: node) =>
|
||||
switch (Node.render(evaluationParams, t1), Node.render(evaluationParams, t2)) {
|
||||
| (Ok(#RenderedDist(rs1)), Ok(#RenderedDist(rs2))) =>
|
||||
Ok(
|
||||
#RenderedDist(
|
||||
PointSetDist.combinePointwise(
|
||||
~integralSumCachesFn=(a, b) => Some(a +. b),
|
||||
~integralCachesFn=(a, b) => Some(
|
||||
Continuous.combinePointwise(~distributionType=#CDF, \"+.", a, b),
|
||||
),
|
||||
\"+.",
|
||||
rs1,
|
||||
rs2,
|
||||
),
|
||||
),
|
||||
)
|
||||
| (Error(e1), _) => Error(e1)
|
||||
| (_, Error(e2)) => Error(e2)
|
||||
| _ => Error("Pointwise combination: rendering failed.")
|
||||
}
|
||||
|
||||
let pointwiseCombine = (fn, evaluationParams: evaluationParams, t1: node, t2: node) =>
|
||||
switch // TODO: construct a function that we can easily sample from, to construct
|
||||
// a RenderedDist. Use the xMin and xMax of the rendered pointSetDists to tell the sampling function where to look.
|
||||
// TODO: This should work for symbolic distributions too!
|
||||
(Node.render(evaluationParams, t1), Node.render(evaluationParams, t2)) {
|
||||
| (Ok(#RenderedDist(rs1)), Ok(#RenderedDist(rs2))) =>
|
||||
Ok(#RenderedDist(PointSetDist.combinePointwise(fn, rs1, rs2)))
|
||||
| (Error(e1), _) => Error(e1)
|
||||
| (_, Error(e2)) => Error(e2)
|
||||
| _ => Error("Pointwise combination: rendering failed.")
|
||||
}
|
||||
|
||||
let operationToLeaf = (
|
||||
evaluationParams: evaluationParams,
|
||||
pointwiseOp: Operation.pointwiseOperation,
|
||||
t1: node,
|
||||
t2: node,
|
||||
) =>
|
||||
switch pointwiseOp {
|
||||
| #Add => pointwiseAdd(evaluationParams, t1, t2)
|
||||
| #Multiply => pointwiseCombine(\"*.", evaluationParams, t1, t2)
|
||||
| #Power => pointwiseCombine(\"**", evaluationParams, t1, t2)
|
||||
}
|
||||
}
|
||||
|
||||
module Truncate = {
|
||||
type simplificationResult = [
|
||||
| #Solution(ASTTypes.node)
|
||||
| #Error(string)
|
||||
| #NoSolution
|
||||
]
|
||||
|
||||
let trySimplification = (leftCutoff, rightCutoff, t): simplificationResult =>
|
||||
switch (leftCutoff, rightCutoff, t) {
|
||||
| (None, None, t) => #Solution(t)
|
||||
| (Some(lc), Some(rc), _) if lc > rc =>
|
||||
#Error("Left truncation bound must be smaller than right truncation bound.")
|
||||
| (lc, rc, #SymbolicDist(#Uniform(u))) =>
|
||||
#Solution(#SymbolicDist(#Uniform(SymbolicDist.Uniform.truncate(lc, rc, u))))
|
||||
| _ => #NoSolution
|
||||
}
|
||||
|
||||
let truncateAsShape = (evaluationParams: evaluationParams, leftCutoff, rightCutoff, t) =>
|
||||
switch // TODO: use named args for xMin/xMax in renderToShape; if we're lucky we can at least get the tail
|
||||
// of a distribution we otherwise wouldn't get at all
|
||||
Node.ensureIsRendered(evaluationParams, t) {
|
||||
| Ok(#RenderedDist(rs)) =>
|
||||
Ok(#RenderedDist(PointSetDist.T.truncate(leftCutoff, rightCutoff, rs)))
|
||||
| Error(e) => Error(e)
|
||||
| _ => Error("Could not truncate distribution.")
|
||||
}
|
||||
|
||||
let operationToLeaf = (
|
||||
evaluationParams,
|
||||
leftCutoff: option<float>,
|
||||
rightCutoff: option<float>,
|
||||
t: node,
|
||||
): result<node, string> =>
|
||||
t
|
||||
|> trySimplification(leftCutoff, rightCutoff)
|
||||
|> (
|
||||
x =>
|
||||
switch x {
|
||||
| #Solution(t) => Ok(t)
|
||||
| #Error(e) => Error(e)
|
||||
| #NoSolution => truncateAsShape(evaluationParams, leftCutoff, rightCutoff, t)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module Normalize = {
|
||||
let rec operationToLeaf = (evaluationParams, t: node): result<node, string> =>
|
||||
switch t {
|
||||
| #RenderedDist(s) => Ok(#RenderedDist(PointSetDist.T.normalize(s)))
|
||||
| #SymbolicDist(_) => Ok(t)
|
||||
| _ => ASTTypes.Node.evaluateAndRetry(evaluationParams, operationToLeaf, t)
|
||||
}
|
||||
}
|
||||
|
||||
module FunctionCall = {
|
||||
let _runHardcodedFunction = (name, evaluationParams, args) =>
|
||||
TypeSystem.Function.Ts.findByNameAndRun(HardcodedFunctions.all, name, evaluationParams, args)
|
||||
|
||||
let _runLocalFunction = (name, evaluationParams: evaluationParams, args) =>
|
||||
Environment.getFunction(evaluationParams.environment, name) |> E.R.bind(_, ((argNames, fn)) =>
|
||||
ASTTypes.Function.run(evaluationParams, args, (argNames, fn))
|
||||
)
|
||||
|
||||
let _runWithEvaluatedInputs = (
|
||||
evaluationParams: ASTTypes.evaluationParams,
|
||||
name,
|
||||
args: array<ASTTypes.node>,
|
||||
) =>
|
||||
_runHardcodedFunction(name, evaluationParams, args) |> E.O.default(
|
||||
_runLocalFunction(name, evaluationParams, args),
|
||||
)
|
||||
|
||||
// TODO: This forces things to be floats
|
||||
let run = (evaluationParams, name, args) =>
|
||||
args
|
||||
|> E.A.fmap(a => evaluationParams.evaluateNode(evaluationParams, a))
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
|> E.R.bind(_, _runWithEvaluatedInputs(evaluationParams, name))
|
||||
}
|
||||
|
||||
module Render = {
|
||||
let rec operationToLeaf = (evaluationParams: evaluationParams, t: node): result<node, string> =>
|
||||
switch t {
|
||||
| #Function(_) => Error("Cannot render a function")
|
||||
| #SymbolicDist(d) =>
|
||||
Ok(
|
||||
#RenderedDist(
|
||||
SymbolicDist.T.toPointSetDist(evaluationParams.samplingInputs.pointSetDistLength, d),
|
||||
),
|
||||
)
|
||||
| #RenderedDist(_) as t => Ok(t) // already a rendered pointSetDist, we're done here
|
||||
| _ => ASTTypes.Node.evaluateAndRetry(evaluationParams, operationToLeaf, t)
|
||||
}
|
||||
}
|
||||
|
||||
/* This function recursively goes through the nodes of the parse tree,
|
||||
replacing each Operation node and its subtree with a Data node.
|
||||
Whenever possible, the replacement produces a new Symbolic Data node,
|
||||
but most often it will produce a RenderedDist.
|
||||
This function is used mainly to turn a parse tree into a single RenderedDist
|
||||
that can then be displayed to the user. */
|
||||
let rec toLeaf = (evaluationParams: ASTTypes.evaluationParams, node: node): result<node, string> =>
|
||||
switch node {
|
||||
// Leaf nodes just stay leaf nodes
|
||||
| #SymbolicDist(_)
|
||||
| #Function(_)
|
||||
| #RenderedDist(_) =>
|
||||
Ok(node)
|
||||
| #Array(args) =>
|
||||
args |> E.A.fmap(toLeaf(evaluationParams)) |> E.A.R.firstErrorOrOpen |> E.R.fmap(r => #Array(r))
|
||||
// Operations nevaluationParamsd to be turned into leaves
|
||||
| #AlgebraicCombination(algebraicOp, t1, t2) =>
|
||||
AlgebraicCombination.operationToLeaf(evaluationParams, algebraicOp, t1, t2)
|
||||
| #PointwiseCombination(pointwiseOp, t1, t2) =>
|
||||
PointwiseCombination.operationToLeaf(evaluationParams, pointwiseOp, t1, t2)
|
||||
| #Truncate(leftCutoff, rightCutoff, t) =>
|
||||
Truncate.operationToLeaf(evaluationParams, leftCutoff, rightCutoff, t)
|
||||
| #Normalize(t) => Normalize.operationToLeaf(evaluationParams, t)
|
||||
| #Render(t) => Render.operationToLeaf(evaluationParams, t)
|
||||
| #Hash(t) =>
|
||||
t
|
||||
|> E.A.fmap(((name: string, node: node)) =>
|
||||
toLeaf(evaluationParams, node) |> E.R.fmap(r => (name, r))
|
||||
)
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
|> E.R.fmap(r => #Hash(r))
|
||||
| #Symbol(r) =>
|
||||
ASTTypes.Environment.get(evaluationParams.environment, r)
|
||||
|> E.O.toResult("Undeclared variable " ++ r)
|
||||
|> E.R.bind(_, toLeaf(evaluationParams))
|
||||
| #FunctionCall(name, args) =>
|
||||
FunctionCall.run(evaluationParams, name, args) |> E.R.bind(_, toLeaf(evaluationParams))
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
@genType
|
||||
type rec hash = array<(string, node)>
|
||||
and node = [
|
||||
| #SymbolicDist(SymbolicDistTypes.symbolicDist)
|
||||
| #RenderedDist(PointSetTypes.pointSetDist)
|
||||
| #Symbol(string)
|
||||
| #Hash(hash)
|
||||
| #Array(array<node>)
|
||||
| #Function(array<string>, node)
|
||||
| #AlgebraicCombination(Operation.algebraicOperation, node, node)
|
||||
| #PointwiseCombination(Operation.pointwiseOperation, node, node)
|
||||
| #Normalize(node)
|
||||
| #Render(node)
|
||||
| #Truncate(option<float>, option<float>, node)
|
||||
| #FunctionCall(string, array<node>)
|
||||
]
|
||||
|
||||
type statement = [
|
||||
| #Assignment(string, node)
|
||||
| #Expression(node)
|
||||
]
|
||||
type program = array<statement>
|
||||
|
||||
type environment = Belt.Map.String.t<node>
|
||||
|
||||
type rec evaluationParams = {
|
||||
samplingInputs: SamplingInputs.samplingInputs,
|
||||
environment: environment,
|
||||
evaluateNode: (evaluationParams, node) => Belt.Result.t<node, string>,
|
||||
}
|
||||
|
||||
module Environment = {
|
||||
type t = environment
|
||||
module MS = Belt.Map.String
|
||||
let fromArray = MS.fromArray
|
||||
let empty: t = []->fromArray
|
||||
let mergeKeepSecond = (a: t, b: t) =>
|
||||
MS.merge(a, b, (_, a, b) =>
|
||||
switch (a, b) {
|
||||
| (_, Some(b)) => Some(b)
|
||||
| (Some(a), _) => Some(a)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
let update = (t, str, fn) => MS.update(t, str, fn)
|
||||
let get = (t: t, str) => MS.get(t, str)
|
||||
let getFunction = (t: t, str) =>
|
||||
switch get(t, str) {
|
||||
| Some(#Function(argNames, fn)) => Ok((argNames, fn))
|
||||
| _ => Error("Function " ++ (str ++ " not found"))
|
||||
}
|
||||
}
|
||||
|
||||
module Node = {
|
||||
let getFloat = (node: node) =>
|
||||
node |> (
|
||||
x =>
|
||||
switch x {
|
||||
| #RenderedDist(Discrete({xyShape: {xs: [x], ys: [1.0]}})) => Some(x)
|
||||
| #SymbolicDist(#Float(x)) => Some(x)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
|
||||
let evaluate = (evaluationParams: evaluationParams) =>
|
||||
evaluationParams.evaluateNode(evaluationParams)
|
||||
|
||||
let evaluateAndRetry = (evaluationParams, fn, node) =>
|
||||
node |> evaluationParams.evaluateNode(evaluationParams) |> E.R.bind(_, fn(evaluationParams))
|
||||
|
||||
let rec toString: node => string = x =>
|
||||
switch x {
|
||||
| #SymbolicDist(d) => SymbolicDist.T.toString(d)
|
||||
| #RenderedDist(_) => "[renderedShape]"
|
||||
| #AlgebraicCombination(op, t1, t2) =>
|
||||
Operation.Algebraic.format(op, toString(t1), toString(t2))
|
||||
| #PointwiseCombination(op, t1, t2) =>
|
||||
Operation.Pointwise.format(op, toString(t1), toString(t2))
|
||||
| #Normalize(t) => "normalize(k" ++ (toString(t) ++ ")")
|
||||
| #Truncate(lc, rc, t) => Operation.Truncate.toString(lc, rc, toString(t))
|
||||
| #Render(t) => toString(t)
|
||||
| #Symbol(t) => "Symbol: " ++ t
|
||||
| #FunctionCall(name, args) =>
|
||||
"[Function call: (" ++
|
||||
(name ++
|
||||
((args |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ ")]"))
|
||||
| #Function(args, internal) =>
|
||||
"[Function: (" ++ ((args |> Js.String.concatMany(_, ",")) ++ (toString(internal) ++ ")]"))
|
||||
| #Array(a) => "[" ++ ((a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]")
|
||||
| #Hash(h) =>
|
||||
"{" ++
|
||||
((h
|
||||
|> E.A.fmap(((name, value)) => name ++ (":" ++ toString(value)))
|
||||
|> Js.String.concatMany(_, ",")) ++
|
||||
"}")
|
||||
}
|
||||
|
||||
let render = (evaluationParams: evaluationParams, r) => #Render(r) |> evaluate(evaluationParams)
|
||||
|
||||
let ensureIsRendered = (params, t) =>
|
||||
switch t {
|
||||
| #RenderedDist(_) => Ok(t)
|
||||
| _ =>
|
||||
switch render(params, t) {
|
||||
| Ok(#RenderedDist(r)) => Ok(#RenderedDist(r))
|
||||
| Ok(_) => Error("Did not render as requested")
|
||||
| Error(e) => Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
let ensureIsRenderedAndGetShape = (params, t) =>
|
||||
switch ensureIsRendered(params, t) {
|
||||
| Ok(#RenderedDist(r)) => Ok(r)
|
||||
| Ok(_) => Error("Did not render as requested")
|
||||
| Error(e) => Error(e)
|
||||
}
|
||||
|
||||
let toPointSetDist = (item: node) =>
|
||||
switch item {
|
||||
| #RenderedDist(r) => Some(r)
|
||||
| _ => None
|
||||
}
|
||||
|
||||
let _toFloat = (t: PointSetTypes.pointSetDist) =>
|
||||
switch t {
|
||||
| Discrete({xyShape: {xs: [x], ys: [1.0]}}) => Some(#SymbolicDist(#Float(x)))
|
||||
| _ => None
|
||||
}
|
||||
|
||||
let toFloat = (item: node): result<node, string> =>
|
||||
item |> toPointSetDist |> E.O.bind(_, _toFloat) |> E.O.toResult("Not valid shape")
|
||||
}
|
||||
|
||||
module Function = {
|
||||
type t = (array<string>, node)
|
||||
let fromNode: node => option<t> = node =>
|
||||
switch node {
|
||||
| #Function(r) => Some(r)
|
||||
| _ => None
|
||||
}
|
||||
let argumentNames = ((a, _): t) => a
|
||||
let internals = ((_, b): t) => b
|
||||
let run = (evaluationParams: evaluationParams, args: array<node>, t: t) =>
|
||||
if E.A.length(args) == E.A.length(argumentNames(t)) {
|
||||
let newEnvironment = Belt.Array.zip(argumentNames(t), args) |> Environment.fromArray
|
||||
let newEvaluationParams: evaluationParams = {
|
||||
samplingInputs: evaluationParams.samplingInputs,
|
||||
environment: Environment.mergeKeepSecond(evaluationParams.environment, newEnvironment),
|
||||
evaluateNode: evaluationParams.evaluateNode,
|
||||
}
|
||||
evaluationParams.evaluateNode(newEvaluationParams, internals(t))
|
||||
} else {
|
||||
Error("Wrong number of variables")
|
||||
}
|
||||
}
|
||||
|
||||
module SamplingDistribution = {
|
||||
type t = [
|
||||
| #SymbolicDist(SymbolicDistTypes.symbolicDist)
|
||||
| #RenderedDist(PointSetTypes.pointSetDist)
|
||||
]
|
||||
|
||||
let isSamplingDistribution: node => bool = x =>
|
||||
switch x {
|
||||
| #SymbolicDist(_) => true
|
||||
| #RenderedDist(_) => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let fromNode: node => result<t, string> = x =>
|
||||
switch x {
|
||||
| #SymbolicDist(n) => Ok(#SymbolicDist(n))
|
||||
| #RenderedDist(n) => Ok(#RenderedDist(n))
|
||||
| _ => Error("Not valid type")
|
||||
}
|
||||
|
||||
let renderIfIsNotSamplingDistribution = (params, t): result<node, string> =>
|
||||
!isSamplingDistribution(t)
|
||||
? switch Node.render(params, t) {
|
||||
| Ok(r) => Ok(r)
|
||||
| Error(e) => Error(e)
|
||||
}
|
||||
: Ok(t)
|
||||
|
||||
let map = (~renderedDistFn, ~symbolicDistFn, node: node) =>
|
||||
node |> (
|
||||
x =>
|
||||
switch x {
|
||||
| #RenderedDist(r) => Some(renderedDistFn(r))
|
||||
| #SymbolicDist(s) => Some(symbolicDistFn(s))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
|
||||
let sampleN = n =>
|
||||
map(~renderedDistFn=PointSetDist.sampleNRendered(n), ~symbolicDistFn=SymbolicDist.T.sampleN(n))
|
||||
|
||||
let getCombinationSamples = (n, algebraicOp, t1: node, t2: node) =>
|
||||
switch (sampleN(n, t1), sampleN(n, t2)) {
|
||||
| (Some(a), Some(b)) =>
|
||||
Some(
|
||||
Belt.Array.zip(a, b) |> E.A.fmap(((a, b)) => Operation.Algebraic.toFn(algebraicOp, a, b)),
|
||||
)
|
||||
| _ => None
|
||||
}
|
||||
|
||||
let combineShapesUsingSampling = (
|
||||
evaluationParams: evaluationParams,
|
||||
algebraicOp,
|
||||
t1: node,
|
||||
t2: node,
|
||||
) => {
|
||||
let i1 = renderIfIsNotSamplingDistribution(evaluationParams, t1)
|
||||
let i2 = renderIfIsNotSamplingDistribution(evaluationParams, t2)
|
||||
E.R.merge(i1, i2) |> E.R.bind(_, ((a, b)) => {
|
||||
let samples =
|
||||
getCombinationSamples(
|
||||
evaluationParams.samplingInputs.sampleCount,
|
||||
algebraicOp,
|
||||
a,
|
||||
b,
|
||||
) |> E.O.toResult("Could not get samples")
|
||||
|
||||
let sampleSetDist = samples->E.R.bind(SampleSetDist.make)
|
||||
|
||||
let pointSetDist =
|
||||
sampleSetDist->E.R.bind(r =>
|
||||
SampleSetDist.toPointSetDist(~samplingInputs=evaluationParams.samplingInputs, ~samples=r)
|
||||
)
|
||||
pointSetDist |> E.R.fmap(r => #Normalize(#RenderedDist(r)))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
open PointSetTypes
|
||||
|
||||
@genType
|
||||
type t = PointSetTypes.distPlus
|
||||
|
||||
let pointSetDistIntegral = pointSetDist => PointSetDist.T.Integral.get(pointSetDist)
|
||||
let make = (~pointSetDist, ~squiggleString, ()): t => {
|
||||
let integral = pointSetDistIntegral(pointSetDist)
|
||||
{pointSetDist: pointSetDist, integralCache: integral, squiggleString: squiggleString}
|
||||
}
|
||||
|
||||
let update = (~pointSetDist=?, ~integralCache=?, ~squiggleString=?, t: t) => {
|
||||
pointSetDist: E.O.default(t.pointSetDist, pointSetDist),
|
||||
integralCache: E.O.default(t.integralCache, integralCache),
|
||||
squiggleString: E.O.default(t.squiggleString, squiggleString),
|
||||
}
|
||||
|
||||
let updateShape = (pointSetDist, t) => {
|
||||
let integralCache = pointSetDistIntegral(pointSetDist)
|
||||
update(~pointSetDist, ~integralCache, t)
|
||||
}
|
||||
|
||||
let toPointSetDist = ({pointSetDist, _}: t) => pointSetDist
|
||||
|
||||
let pointSetDistFn = (fn, {pointSetDist}: t) => fn(pointSetDist)
|
||||
|
||||
module T = Distributions.Dist({
|
||||
type t = PointSetTypes.distPlus
|
||||
type integral = PointSetTypes.distPlus
|
||||
let toPointSetDist = toPointSetDist
|
||||
let toContinuous = pointSetDistFn(PointSetDist.T.toContinuous)
|
||||
let toDiscrete = pointSetDistFn(PointSetDist.T.toDiscrete)
|
||||
|
||||
let normalize = (t: t): t => {
|
||||
let normalizedShape = t |> toPointSetDist |> PointSetDist.T.normalize
|
||||
t |> updateShape(normalizedShape)
|
||||
}
|
||||
|
||||
let truncate = (leftCutoff, rightCutoff, t: t): t => {
|
||||
let truncatedShape = t |> toPointSetDist |> PointSetDist.T.truncate(leftCutoff, rightCutoff)
|
||||
|
||||
t |> updateShape(truncatedShape)
|
||||
}
|
||||
|
||||
let xToY = (f, t: t) => t |> toPointSetDist |> PointSetDist.T.xToY(f)
|
||||
|
||||
let minX = pointSetDistFn(PointSetDist.T.minX)
|
||||
let maxX = pointSetDistFn(PointSetDist.T.maxX)
|
||||
let toDiscreteProbabilityMassFraction = pointSetDistFn(
|
||||
PointSetDist.T.toDiscreteProbabilityMassFraction,
|
||||
)
|
||||
|
||||
// This bit is kind of awkward, could probably use rethinking.
|
||||
let integral = (t: t) => updateShape(Continuous(t.integralCache), t)
|
||||
|
||||
let updateIntegralCache = (integralCache: option<PointSetTypes.continuousShape>, t) =>
|
||||
update(~integralCache=E.O.default(t.integralCache, integralCache), t)
|
||||
|
||||
let downsample = (i, t): t => updateShape(t |> toPointSetDist |> PointSetDist.T.downsample(i), t)
|
||||
// todo: adjust for limit, maybe?
|
||||
let mapY = (
|
||||
~integralSumCacheFn=previousIntegralSum => None,
|
||||
~integralCacheFn=previousIntegralCache => None,
|
||||
~fn,
|
||||
{pointSetDist, _} as t: t,
|
||||
): t => PointSetDist.T.mapY(~integralSumCacheFn, ~fn, pointSetDist) |> updateShape(_, t)
|
||||
|
||||
// get the total of everything
|
||||
let integralEndY = (t: t) => {
|
||||
PointSetDist.T.Integral.sum(toPointSetDist(t))
|
||||
}
|
||||
|
||||
// TODO: Fix this below, obviously. Adjust for limits
|
||||
let integralXtoY = (f, t: t) => {
|
||||
PointSetDist.T.Integral.xToY(f, toPointSetDist(t))
|
||||
}
|
||||
|
||||
// TODO: This part is broken when there is a limit, if this is supposed to be taken into account.
|
||||
let integralYtoX = (f, t: t) => {
|
||||
PointSetDist.T.Integral.yToX(f, toPointSetDist(t))
|
||||
}
|
||||
|
||||
let mean = (t: t) => {
|
||||
PointSetDist.T.mean(t.pointSetDist)
|
||||
}
|
||||
let variance = (t: t) => PointSetDist.T.variance(t.pointSetDist)
|
||||
})
|
|
@ -1,240 +0,0 @@
|
|||
open TypeSystem
|
||||
|
||||
let wrongInputsError = (r: array<typedValue>) => {
|
||||
let inputs = r |> E.A.fmap(TypedValue.toString) |> Js.String.concatMany(_, ",")
|
||||
Js.log3("Inputs were", inputs, r)
|
||||
Error("Wrong inputs. The inputs were:" ++ inputs)
|
||||
}
|
||||
|
||||
let to_: (float, float) => result<node, string> = (low, high) =>
|
||||
switch (low, high) {
|
||||
| (low, high) if low <= 0.0 && low < high =>
|
||||
Ok(#SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)))
|
||||
| (low, high) if low < high =>
|
||||
Ok(#SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high)))
|
||||
| (_, _) => Error("Low value must be less than high value.")
|
||||
}
|
||||
|
||||
let makeSymbolicFromTwoFloats = (name, fn) =>
|
||||
Function.T.make(
|
||||
~name,
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#Float, #Float],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#Float(a), #Float(b)] => fn(a, b) |> E.R.fmap(r => #SymbolicDist(r))
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
)
|
||||
|
||||
let makeSymbolicFromOneFloat = (name, fn) =>
|
||||
Function.T.make(
|
||||
~name,
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#Float],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#Float(a)] => fn(a) |> E.R.fmap(r => #SymbolicDist(r))
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
)
|
||||
|
||||
let makeDistFloat = (name, fn) =>
|
||||
Function.T.make(
|
||||
~name,
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#SamplingDistribution, #Float],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#SamplingDist(a), #Float(b)] => fn(a, b)
|
||||
| [#RenderedDist(a), #Float(b)] => fn(#RenderedDist(a), b)
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
)
|
||||
|
||||
let makeRenderedDistFloat = (name, fn) =>
|
||||
Function.T.make(
|
||||
~name,
|
||||
~outputType=#RenderedDistribution,
|
||||
~inputTypes=[#RenderedDistribution, #Float],
|
||||
~shouldCoerceTypes=true,
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#RenderedDist(a), #Float(b)] => fn(a, b)
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
)
|
||||
|
||||
let makeDist = (name, fn) =>
|
||||
Function.T.make(
|
||||
~name,
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#SamplingDistribution],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#SamplingDist(a)] => fn(a)
|
||||
| [#RenderedDist(a)] => fn(#RenderedDist(a))
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
)
|
||||
|
||||
let floatFromDist = (
|
||||
distToFloatOp: Operation.distToFloatOperation,
|
||||
t: TypeSystem.samplingDist,
|
||||
): result<node, string> =>
|
||||
switch t {
|
||||
| #SymbolicDist(s) =>
|
||||
SymbolicDist.T.operate(distToFloatOp, s) |> E.R.bind(_, v => Ok(#SymbolicDist(#Float(v))))
|
||||
| #RenderedDist(rs) =>
|
||||
PointSetDist.operate(distToFloatOp, rs) |> (v => Ok(#SymbolicDist(#Float(v))))
|
||||
}
|
||||
|
||||
let verticalScaling = (scaleOp, rs, scaleBy) => {
|
||||
// scaleBy has to be a single float, otherwise we'll return an error.
|
||||
let fn = (secondary, main) => Operation.Scale.toFn(scaleOp, main, secondary)
|
||||
let integralSumCacheFn = Operation.Scale.toIntegralSumCacheFn(scaleOp)
|
||||
let integralCacheFn = Operation.Scale.toIntegralCacheFn(scaleOp)
|
||||
Ok(
|
||||
#RenderedDist(
|
||||
PointSetDist.T.mapY(
|
||||
~integralSumCacheFn=integralSumCacheFn(scaleBy),
|
||||
~integralCacheFn=integralCacheFn(scaleBy),
|
||||
~fn=fn(scaleBy),
|
||||
rs,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
module Multimodal = {
|
||||
let getByNameResult = Hash.getByNameResult
|
||||
|
||||
let _paramsToDistsAndWeights = (r: array<typedValue>) =>
|
||||
switch r {
|
||||
| [#Hash(r)] =>
|
||||
let dists =
|
||||
getByNameResult(r, "dists")
|
||||
->E.R.bind(TypeSystem.TypedValue.toArray)
|
||||
->E.R.bind(r => r |> E.A.fmap(TypeSystem.TypedValue.toDist) |> E.A.R.firstErrorOrOpen)
|
||||
let weights =
|
||||
getByNameResult(r, "weights")
|
||||
->E.R.bind(TypeSystem.TypedValue.toArray)
|
||||
->E.R.bind(r => r |> E.A.fmap(TypeSystem.TypedValue.toFloat) |> E.A.R.firstErrorOrOpen)
|
||||
|
||||
E.R.merge(dists, weights)->E.R.bind(((a, b)) =>
|
||||
E.A.length(b) > E.A.length(a)
|
||||
? Error("Too many weights provided")
|
||||
: Ok(
|
||||
E.A.zipMaxLength(a, b) |> E.A.fmap(((a, b)) => (
|
||||
a |> E.O.toExn(""),
|
||||
b |> E.O.default(1.0),
|
||||
)),
|
||||
)
|
||||
)
|
||||
| _ => Error("Needs items")
|
||||
}
|
||||
let _runner: array<typedValue> => result<node, string> = r => {
|
||||
let paramsToDistsAndWeights =
|
||||
_paramsToDistsAndWeights(r) |> E.R.fmap(
|
||||
E.A.fmap(((dist, weight)) =>
|
||||
#FunctionCall("scaleMultiply", [dist, #SymbolicDist(#Float(weight))])
|
||||
),
|
||||
)
|
||||
let pointwiseSum: result<node, string> =
|
||||
paramsToDistsAndWeights->E.R.bind(E.R.errorIfCondition(E.A.isEmpty, "Needs one input"))
|
||||
|> E.R.fmap(r =>
|
||||
r
|
||||
|> Js.Array.sliceFrom(1)
|
||||
|> E.A.fold_left((acc, x) => #PointwiseCombination(#Add, acc, x), E.A.unsafe_get(r, 0))
|
||||
)
|
||||
pointwiseSum
|
||||
}
|
||||
|
||||
let _function = Function.T.make(
|
||||
~name="multimodal",
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#Hash([("dists", #Array(#SamplingDistribution)), ("weights", #Array(#Float))])],
|
||||
~run=_runner,
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
||||
let all = [
|
||||
makeSymbolicFromTwoFloats("normal", SymbolicDist.Normal.make),
|
||||
makeSymbolicFromTwoFloats("uniform", SymbolicDist.Uniform.make),
|
||||
makeSymbolicFromTwoFloats("beta", SymbolicDist.Beta.make),
|
||||
makeSymbolicFromTwoFloats("lognormal", SymbolicDist.Lognormal.make),
|
||||
makeSymbolicFromTwoFloats("lognormalFromMeanAndStdDev", SymbolicDist.Lognormal.fromMeanAndStdev),
|
||||
makeSymbolicFromOneFloat("exponential", SymbolicDist.Exponential.make),
|
||||
Function.T.make(
|
||||
~name="to",
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#Float, #Float],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#Float(a), #Float(b)] => to_(a, b)
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
),
|
||||
Function.T.make(
|
||||
~name="triangular",
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#Float, #Float, #Float],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#Float(a), #Float(b), #Float(c)] =>
|
||||
SymbolicDist.Triangular.make(a, b, c) |> E.R.fmap(r => #SymbolicDist(r))
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
),
|
||||
Function.T.make(
|
||||
~name="log",
|
||||
~outputType=#Float,
|
||||
~inputTypes=[#Float],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#Float(a)] => Ok(#SymbolicDist(#Float(Js.Math.log(a))))
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
),
|
||||
makeDistFloat("pdf", (dist, float) => floatFromDist(#Pdf(float), dist)),
|
||||
makeDistFloat("inv", (dist, float) => floatFromDist(#Inv(float), dist)),
|
||||
makeDistFloat("cdf", (dist, float) => floatFromDist(#Cdf(float), dist)),
|
||||
makeDist("mean", dist => floatFromDist(#Mean, dist)),
|
||||
makeDist("sample", dist => floatFromDist(#Sample, dist)),
|
||||
Function.T.make(
|
||||
~name="render",
|
||||
~outputType=#RenderedDistribution,
|
||||
~inputTypes=[#RenderedDistribution],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#RenderedDist(c)] => Ok(#RenderedDist(c))
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
),
|
||||
Function.T.make(
|
||||
~name="normalize",
|
||||
~outputType=#SamplingDistribution,
|
||||
~inputTypes=[#SamplingDistribution],
|
||||
~run=x =>
|
||||
switch x {
|
||||
| [#SamplingDist(#SymbolicDist(c))] => Ok(#SymbolicDist(c))
|
||||
| [#SamplingDist(#RenderedDist(c))] => Ok(#RenderedDist(PointSetDist.T.normalize(c)))
|
||||
| e => wrongInputsError(e)
|
||||
},
|
||||
(),
|
||||
),
|
||||
makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Power, dist, float)),
|
||||
makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)),
|
||||
makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Logarithm, dist, float)),
|
||||
Multimodal._function,
|
||||
]
|
|
@ -1,196 +0,0 @@
|
|||
type node = ASTTypes.node
|
||||
let getFloat = ASTTypes.Node.getFloat
|
||||
|
||||
type samplingDist = [
|
||||
| #SymbolicDist(SymbolicDistTypes.symbolicDist)
|
||||
| #RenderedDist(PointSetTypes.pointSetDist)
|
||||
]
|
||||
|
||||
type rec hashType = array<(string, _type)>
|
||||
and _type = [
|
||||
| #Float
|
||||
| #SamplingDistribution
|
||||
| #RenderedDistribution
|
||||
| #Array(_type)
|
||||
| #Hash(hashType)
|
||||
]
|
||||
|
||||
type rec hashTypedValue = array<(string, typedValue)>
|
||||
and typedValue = [
|
||||
| #Float(float)
|
||||
| #RenderedDist(PointSetTypes.pointSetDist)
|
||||
| #SamplingDist(samplingDist)
|
||||
| #Array(array<typedValue>)
|
||||
| #Hash(hashTypedValue)
|
||||
]
|
||||
|
||||
type _function = {
|
||||
name: string,
|
||||
inputTypes: array<_type>,
|
||||
outputType: _type,
|
||||
run: array<typedValue> => result<node, string>,
|
||||
shouldCoerceTypes: bool,
|
||||
}
|
||||
|
||||
type functions = array<_function>
|
||||
type inputNodes = array<node>
|
||||
|
||||
module TypedValue = {
|
||||
let rec toString: typedValue => string = x =>
|
||||
switch x {
|
||||
| #SamplingDist(_) => "[sampling dist]"
|
||||
| #RenderedDist(_) => "[rendered PointSetDist]"
|
||||
| #Float(f) => "Float: " ++ Js.Float.toString(f)
|
||||
| #Array(a) => "[" ++ ((a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]")
|
||||
| #Hash(v) =>
|
||||
"{" ++
|
||||
((v
|
||||
|> E.A.fmap(((name, value)) => name ++ (":" ++ toString(value)))
|
||||
|> Js.String.concatMany(_, ",")) ++
|
||||
"}")
|
||||
}
|
||||
|
||||
let rec fromNode = (node: node): result<typedValue, string> =>
|
||||
switch node {
|
||||
| #SymbolicDist(#Float(r)) => Ok(#Float(r))
|
||||
| #SymbolicDist(s) => Ok(#SamplingDist(#SymbolicDist(s)))
|
||||
| #RenderedDist(s) => Ok(#RenderedDist(s))
|
||||
| #Array(r) => r |> E.A.fmap(fromNode) |> E.A.R.firstErrorOrOpen |> E.R.fmap(r => #Array(r))
|
||||
| #Hash(hash) =>
|
||||
hash
|
||||
|> E.A.fmap(((name, t)) => fromNode(t) |> E.R.fmap(r => (name, r)))
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
|> E.R.fmap(r => #Hash(r))
|
||||
| e => Error("Wrong type: " ++ ASTTypes.Node.toString(e))
|
||||
}
|
||||
|
||||
// todo: Arrays and hashes
|
||||
let rec fromNodeWithTypeCoercion = (evaluationParams, _type: _type, node) =>
|
||||
switch (_type, node) {
|
||||
| (#Float, _) =>
|
||||
switch getFloat(node) {
|
||||
| Some(a) => Ok(#Float(a))
|
||||
| _ => Error("Type Error: Expected float.")
|
||||
}
|
||||
| (#SamplingDistribution, _) =>
|
||||
ASTTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(
|
||||
evaluationParams,
|
||||
node,
|
||||
) |> E.R.bind(_, fromNode)
|
||||
| (#RenderedDistribution, _) =>
|
||||
ASTTypes.Node.render(evaluationParams, node) |> E.R.bind(_, fromNode)
|
||||
| (#Array(_type), #Array(b)) =>
|
||||
b
|
||||
|> E.A.fmap(fromNodeWithTypeCoercion(evaluationParams, _type))
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
|> E.R.fmap(r => #Array(r))
|
||||
| (#Hash(named), #Hash(r)) =>
|
||||
let keyValues =
|
||||
named |> E.A.fmap(((name, intendedType)) => (name, intendedType, Hash.getByName(r, name)))
|
||||
let typedHash =
|
||||
keyValues
|
||||
|> E.A.fmap(((name, intendedType, optionNode)) =>
|
||||
switch optionNode {
|
||||
| Some(node) =>
|
||||
fromNodeWithTypeCoercion(evaluationParams, intendedType, node) |> E.R.fmap(node => (
|
||||
name,
|
||||
node,
|
||||
))
|
||||
| None => Error("Hash parameter not present in hash.")
|
||||
}
|
||||
)
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
|> E.R.fmap(r => #Hash(r))
|
||||
typedHash
|
||||
| _ => Error("fromNodeWithTypeCoercion error, sorry.")
|
||||
}
|
||||
|
||||
let toFloat: typedValue => result<float, string> = x =>
|
||||
switch x {
|
||||
| #Float(x) => Ok(x)
|
||||
| _ => Error("Not a float")
|
||||
}
|
||||
|
||||
let toArray: typedValue => result<array<'a>, string> = x =>
|
||||
switch x {
|
||||
| #Array(x) => Ok(x)
|
||||
| _ => Error("Not an array")
|
||||
}
|
||||
|
||||
let toNamed: typedValue => result<hashTypedValue, string> = x =>
|
||||
switch x {
|
||||
| #Hash(x) => Ok(x)
|
||||
| _ => Error("Not a named item")
|
||||
}
|
||||
|
||||
let toDist: typedValue => result<node, string> = x =>
|
||||
switch x {
|
||||
| #SamplingDist(#SymbolicDist(c)) => Ok(#SymbolicDist(c))
|
||||
| #SamplingDist(#RenderedDist(c)) => Ok(#RenderedDist(c))
|
||||
| #RenderedDist(c) => Ok(#RenderedDist(c))
|
||||
| #Float(x) => Ok(#SymbolicDist(#Float(x)))
|
||||
| x => Error("Cannot be converted into a distribution: " ++ toString(x))
|
||||
}
|
||||
}
|
||||
|
||||
module Function = {
|
||||
type t = _function
|
||||
type ts = functions
|
||||
|
||||
module T = {
|
||||
let make = (~name, ~inputTypes, ~outputType, ~run, ~shouldCoerceTypes=true, _): t => {
|
||||
name: name,
|
||||
inputTypes: inputTypes,
|
||||
outputType: outputType,
|
||||
run: run,
|
||||
shouldCoerceTypes: shouldCoerceTypes,
|
||||
}
|
||||
|
||||
let _inputLengthCheck = (inputNodes: inputNodes, t: t) => {
|
||||
let expectedLength = E.A.length(t.inputTypes)
|
||||
let actualLength = E.A.length(inputNodes)
|
||||
expectedLength == actualLength
|
||||
? Ok(inputNodes)
|
||||
: Error(
|
||||
"Wrong number of inputs. Expected" ++
|
||||
((expectedLength |> E.I.toString) ++
|
||||
(". Got:" ++ (actualLength |> E.I.toString))),
|
||||
)
|
||||
}
|
||||
|
||||
let _coerceInputNodes = (evaluationParams, inputTypes, shouldCoerce, inputNodes) =>
|
||||
Belt.Array.zip(inputTypes, inputNodes)
|
||||
|> E.A.fmap(((def, input)) =>
|
||||
shouldCoerce
|
||||
? TypedValue.fromNodeWithTypeCoercion(evaluationParams, def, input)
|
||||
: TypedValue.fromNode(input)
|
||||
)
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
|
||||
let inputsToTypedValues = (
|
||||
evaluationParams: ASTTypes.evaluationParams,
|
||||
inputNodes: inputNodes,
|
||||
t: t,
|
||||
) =>
|
||||
_inputLengthCheck(inputNodes, t)->E.R.bind(
|
||||
_coerceInputNodes(evaluationParams, t.inputTypes, t.shouldCoerceTypes),
|
||||
)
|
||||
|
||||
let run = (evaluationParams: ASTTypes.evaluationParams, inputNodes: inputNodes, t: t) =>
|
||||
inputsToTypedValues(evaluationParams, inputNodes, t)->E.R.bind(t.run)
|
||||
|> (
|
||||
x =>
|
||||
switch x {
|
||||
| Ok(i) => Ok(i)
|
||||
| Error(r) => Error("Function " ++ (t.name ++ (" error: " ++ r)))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module Ts = {
|
||||
let findByName = (ts: ts, n: string) => ts |> Belt.Array.getBy(_, ({name}) => name == n)
|
||||
|
||||
let findByNameAndRun = (ts: ts, n: string, evaluationParams, inputTypes) =>
|
||||
findByName(ts, n) |> E.O.fmap(T.run(evaluationParams, inputTypes))
|
||||
}
|
||||
}
|
|
@ -1,290 +0,0 @@
|
|||
module MathJsonToMathJsAdt = {
|
||||
type rec arg =
|
||||
| Symbol(string)
|
||||
| Value(float)
|
||||
| Fn(fn)
|
||||
| Array(array<arg>)
|
||||
| Blocks(array<arg>)
|
||||
| Object(Js.Dict.t<arg>)
|
||||
| Assignment(arg, arg)
|
||||
| FunctionAssignment(fnAssignment)
|
||||
and fn = {
|
||||
name: string,
|
||||
args: array<arg>,
|
||||
}
|
||||
and fnAssignment = {
|
||||
name: string,
|
||||
args: array<string>,
|
||||
expression: arg,
|
||||
}
|
||||
|
||||
let rec run = (j: Js.Json.t) => {
|
||||
open Json.Decode
|
||||
switch field("mathjs", string, j) {
|
||||
| "FunctionNode" =>
|
||||
let args = j |> field("args", array(run))
|
||||
let name = j |> optional(field("fn", field("name", string)))
|
||||
name |> E.O.fmap(name => Fn({name: name, args: args |> E.A.O.concatSomes}))
|
||||
| "OperatorNode" =>
|
||||
let args = j |> field("args", array(run))
|
||||
Some(
|
||||
Fn({
|
||||
name: j |> field("fn", string),
|
||||
args: args |> E.A.O.concatSomes,
|
||||
}),
|
||||
)
|
||||
| "ConstantNode" => optional(field("value", Json.Decode.float), j) |> E.O.fmap(r => Value(r))
|
||||
| "ParenthesisNode" => j |> field("content", run)
|
||||
| "ObjectNode" =>
|
||||
let properties = j |> field("properties", dict(run))
|
||||
Js.Dict.entries(properties)
|
||||
|> E.A.fmap(((key, value)) => value |> E.O.fmap(v => (key, v)))
|
||||
|> E.A.O.concatSomes
|
||||
|> Js.Dict.fromArray
|
||||
|> (r => Some(Object(r)))
|
||||
| "ArrayNode" =>
|
||||
let items = field("items", array(run), j)
|
||||
Some(Array(items |> E.A.O.concatSomes))
|
||||
| "SymbolNode" => Some(Symbol(field("name", string, j)))
|
||||
| "AssignmentNode" =>
|
||||
let object_ = j |> field("object", run)
|
||||
let value_ = j |> field("value", run)
|
||||
switch (object_, value_) {
|
||||
| (Some(o), Some(v)) => Some(Assignment(o, v))
|
||||
| _ => None
|
||||
}
|
||||
| "BlockNode" =>
|
||||
let block = r => r |> field("node", run)
|
||||
let args = j |> field("blocks", array(block)) |> E.A.O.concatSomes
|
||||
Some(Blocks(args))
|
||||
| "FunctionAssignmentNode" =>
|
||||
let name = j |> field("name", string)
|
||||
let args = j |> field("params", array(field("name", string)))
|
||||
let expression = j |> field("expr", run)
|
||||
expression |> E.O.fmap(expression => FunctionAssignment({
|
||||
name: name,
|
||||
args: args,
|
||||
expression: expression,
|
||||
}))
|
||||
| n =>
|
||||
Js.log3("Couldn't parse mathjs node", j, n)
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module MathAdtToDistDst = {
|
||||
open MathJsonToMathJsAdt
|
||||
|
||||
let handleSymbol = sym => Ok(#Symbol(sym))
|
||||
|
||||
// TODO: This only works on the top level, which needs to be refactored. Also, I think functions don't need to be done like this anymore.
|
||||
module MathAdtCleaner = {
|
||||
let transformWithSymbol = (f: float, s: string) =>
|
||||
switch s {
|
||||
| "K" => Some(f *. 1000.)
|
||||
| "M" => Some(f *. 1000000.)
|
||||
| "B" => Some(f *. 1000000000.)
|
||||
| "T" => Some(f *. 1000000000000.)
|
||||
| _ => None
|
||||
}
|
||||
let rec run = x =>
|
||||
switch x {
|
||||
| Fn({name: "multiply", args: [Value(f), Symbol(s)]}) as doNothing =>
|
||||
transformWithSymbol(f, s) |> E.O.fmap(r => Value(r)) |> E.O.default(doNothing)
|
||||
| Fn({name: "unaryMinus", args: [Value(f)]}) => Value(-1.0 *. f)
|
||||
| Fn({name, args}) => Fn({name: name, args: args |> E.A.fmap(run)})
|
||||
| Array(args) => Array(args |> E.A.fmap(run))
|
||||
| Symbol(s) => Symbol(s)
|
||||
| Value(v) => Value(v)
|
||||
| Blocks(args) => Blocks(args |> E.A.fmap(run))
|
||||
| Assignment(a, b) => Assignment(a, run(b))
|
||||
| FunctionAssignment(a) => FunctionAssignment(a)
|
||||
| Object(v) =>
|
||||
Object(
|
||||
v
|
||||
|> Js.Dict.entries
|
||||
|> E.A.fmap(((key, value)) => (key, run(value)))
|
||||
|> Js.Dict.fromArray,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let lognormal = (args, parseArgs, nodeParser) =>
|
||||
switch args {
|
||||
| [Object(o)] =>
|
||||
let g = s =>
|
||||
Js.Dict.get(o, s) |> E.O.toResult("Variable was empty") |> E.R.bind(_, nodeParser)
|
||||
switch (g("mean"), g("stdev"), g("mu"), g("sigma")) {
|
||||
| (Ok(mean), Ok(stdev), _, _) =>
|
||||
Ok(#FunctionCall("lognormalFromMeanAndStdDev", [mean, stdev]))
|
||||
| (_, _, Ok(mu), Ok(sigma)) => Ok(#FunctionCall("lognormal", [mu, sigma]))
|
||||
| _ => Error("Lognormal distribution needs either mean and stdev or mu and sigma")
|
||||
}
|
||||
| _ => parseArgs() |> E.R.fmap((args: array<ASTTypes.node>) => #FunctionCall("lognormal", args))
|
||||
}
|
||||
|
||||
// Error("Dotwise exponentiation needs two operands")
|
||||
let operationParser = (name: string, args: result<array<ASTTypes.node>, string>): result<
|
||||
ASTTypes.node,
|
||||
string,
|
||||
> => {
|
||||
let toOkAlgebraic = r => Ok(#AlgebraicCombination(r))
|
||||
let toOkPointwise = r => Ok(#PointwiseCombination(r))
|
||||
let toOkTruncate = r => Ok(#Truncate(r))
|
||||
args |> E.R.bind(_, args =>
|
||||
switch (name, args) {
|
||||
| ("add", [l, r]) => toOkAlgebraic((#Add, l, r))
|
||||
| ("add", _) => Error("Addition needs two operands")
|
||||
| ("unaryMinus", [l]) => toOkAlgebraic((#Multiply, #SymbolicDist(#Float(-1.0)), l))
|
||||
| ("subtract", [l, r]) => toOkAlgebraic((#Subtract, l, r))
|
||||
| ("subtract", _) => Error("Subtraction needs two operands")
|
||||
| ("multiply", [l, r]) => toOkAlgebraic((#Multiply, l, r))
|
||||
| ("multiply", _) => Error("Multiplication needs two operands")
|
||||
| ("pow", [l, r]) => toOkAlgebraic((#Power, l, r))
|
||||
| ("pow", _) => Error("Exponentiation needs two operands")
|
||||
| ("dotMultiply", [l, r]) => toOkPointwise((#Multiply, l, r))
|
||||
| ("dotMultiply", _) => Error("Dotwise multiplication needs two operands")
|
||||
| ("dotPow", [l, r]) => toOkPointwise((#Power, l, r))
|
||||
| ("dotPow", _) => Error("Dotwise exponentiation needs two operands")
|
||||
| ("rightLogShift", [l, r]) => toOkPointwise((#Add, l, r))
|
||||
| ("rightLogShift", _) => Error("Dotwise addition needs two operands")
|
||||
| ("divide", [l, r]) => toOkAlgebraic((#Divide, l, r))
|
||||
| ("divide", _) => Error("Division needs two operands")
|
||||
| ("leftTruncate", [d, #SymbolicDist(#Float(lc))]) => toOkTruncate((Some(lc), None, d))
|
||||
| ("leftTruncate", _) =>
|
||||
Error("leftTruncate needs two arguments: the expression and the cutoff")
|
||||
| ("rightTruncate", [d, #SymbolicDist(#Float(rc))]) => toOkTruncate((None, Some(rc), d))
|
||||
| ("rightTruncate", _) =>
|
||||
Error("rightTruncate needs two arguments: the expression and the cutoff")
|
||||
| ("truncate", [d, #SymbolicDist(#Float(lc)), #SymbolicDist(#Float(rc))]) =>
|
||||
toOkTruncate((Some(lc), Some(rc), d))
|
||||
| ("truncate", _) => Error("truncate needs three arguments: the expression and both cutoffs")
|
||||
| _ => Error("This type not currently supported")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let functionParser = (
|
||||
nodeParser: MathJsonToMathJsAdt.arg => Belt.Result.t<ASTTypes.node, string>,
|
||||
name: string,
|
||||
args: array<MathJsonToMathJsAdt.arg>,
|
||||
): result<ASTTypes.node, string> => {
|
||||
let parseArray = ags => ags |> E.A.fmap(nodeParser) |> E.A.R.firstErrorOrOpen
|
||||
let parseArgs = () => parseArray(args)
|
||||
switch name {
|
||||
| "lognormal" => lognormal(args, parseArgs, nodeParser)
|
||||
| "multimodal"
|
||||
| "add"
|
||||
| "subtract"
|
||||
| "multiply"
|
||||
| "unaryMinus"
|
||||
| "dotMultiply"
|
||||
| "dotPow"
|
||||
| "rightLogShift"
|
||||
| "divide"
|
||||
| "pow"
|
||||
| "leftTruncate"
|
||||
| "rightTruncate"
|
||||
| "truncate" =>
|
||||
operationParser(name, parseArgs())
|
||||
| "mm" =>
|
||||
let weights =
|
||||
args
|
||||
|> E.A.last
|
||||
|> E.O.bind(_, x =>
|
||||
switch x {
|
||||
| Array(values) => Some(parseArray(values))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
let possibleDists = E.O.isSome(weights)
|
||||
? Belt.Array.slice(args, ~offset=0, ~len=E.A.length(args) - 1)
|
||||
: args
|
||||
let dists = parseArray(possibleDists)
|
||||
switch (weights, dists) {
|
||||
| (Some(Error(r)), _) => Error(r)
|
||||
| (_, Error(r)) => Error(r)
|
||||
| (None, Ok(dists)) =>
|
||||
let hash: ASTTypes.node = #FunctionCall(
|
||||
"multimodal",
|
||||
[#Hash([("dists", #Array(dists)), ("weights", #Array([]))])],
|
||||
)
|
||||
Ok(hash)
|
||||
| (Some(Ok(weights)), Ok(dists)) =>
|
||||
let hash: ASTTypes.node = #FunctionCall(
|
||||
"multimodal",
|
||||
[#Hash([("dists", #Array(dists)), ("weights", #Array(weights))])],
|
||||
)
|
||||
Ok(hash)
|
||||
}
|
||||
| name => parseArgs() |> E.R.fmap((args: array<ASTTypes.node>) => #FunctionCall(name, args))
|
||||
}
|
||||
}
|
||||
|
||||
let rec nodeParser: MathJsonToMathJsAdt.arg => result<ASTTypes.node, string> = x =>
|
||||
switch x {
|
||||
| Value(f) => Ok(#SymbolicDist(#Float(f)))
|
||||
| Symbol(sym) => Ok(#Symbol(sym))
|
||||
| Fn({name, args}) => functionParser(nodeParser, name, args)
|
||||
| _ => Error("This type not currently supported")
|
||||
}
|
||||
|
||||
// | FunctionAssignment({name, args, expression}) => {
|
||||
// let evaluatedExpression = run(expression);
|
||||
// `Function(_ => Ok(evaluatedExpression));
|
||||
// }
|
||||
let rec topLevel = (r): result<ASTTypes.program, string> =>
|
||||
switch r {
|
||||
| FunctionAssignment({name, args, expression}) =>
|
||||
switch nodeParser(expression) {
|
||||
| Ok(r) => Ok([#Assignment(name, #Function(args, r))])
|
||||
| Error(r) => Error(r)
|
||||
}
|
||||
| Value(_) as r => nodeParser(r) |> E.R.fmap(r => [#Expression(r)])
|
||||
| Fn(_) as r => nodeParser(r) |> E.R.fmap(r => [#Expression(r)])
|
||||
| Array(_) => Error("Array not valid as top level")
|
||||
| Symbol(s) => handleSymbol(s) |> E.R.fmap(r => [#Expression(r)])
|
||||
| Object(_) => Error("Object not valid as top level")
|
||||
| Assignment(name, value) =>
|
||||
switch name {
|
||||
| Symbol(symbol) => nodeParser(value) |> E.R.fmap(r => [#Assignment(symbol, r)])
|
||||
| _ => Error("Symbol not a string")
|
||||
}
|
||||
| Blocks(blocks) =>
|
||||
blocks |> E.A.fmap(b => topLevel(b)) |> E.A.R.firstErrorOrOpen |> E.R.fmap(E.A.concatMany)
|
||||
}
|
||||
|
||||
let run = (r): result<ASTTypes.program, string> => r |> MathAdtCleaner.run |> topLevel
|
||||
}
|
||||
|
||||
/* The MathJs parser doesn't support '.+' syntax, but we want it because it
|
||||
would make sense with '.*'. Our workaround is to change this to >>>, which is
|
||||
logShift in mathJS. We don't expect to use logShift anytime soon, so this tradeoff
|
||||
seems fine.
|
||||
*/
|
||||
let pointwiseToRightLogShift = Js.String.replaceByRe(%re("/\.\+/g"), ">>>")
|
||||
|
||||
let fromString2 = str => {
|
||||
/* We feed the user-typed string into Mathjs.parseMath,
|
||||
which returns a JSON with (hopefully) a single-element array.
|
||||
This array element is the top-level node of a nested-object tree
|
||||
representing the functions/arguments/values/etc. in the string.
|
||||
|
||||
The function MathJsonToMathJsAdt then recursively unpacks this JSON into a typed data structure we can use.
|
||||
Inside of this function, MathAdtToDistDst is called whenever a distribution function is encountered.
|
||||
*/
|
||||
let mathJsToJson = str |> pointwiseToRightLogShift |> Mathjs.parseMath
|
||||
|
||||
let mathJsParse = E.R.bind(mathJsToJson, r =>
|
||||
switch MathJsonToMathJsAdt.run(r) {
|
||||
| Some(r) => Ok(r)
|
||||
| None => Error("MathJsParse Error")
|
||||
}
|
||||
)
|
||||
|
||||
let value = E.R.bind(mathJsParse, MathAdtToDistDst.run)
|
||||
value
|
||||
}
|
||||
|
||||
let fromString = str => fromString2(str)
|
|
@ -1,185 +0,0 @@
|
|||
// TODO: This setup is more confusing than it should be, there's more work to do in cleanup here.
|
||||
module Inputs = {
|
||||
module SamplingInputs = {
|
||||
type t = {
|
||||
sampleCount: option<int>,
|
||||
outputXYPoints: option<int>,
|
||||
kernelWidth: option<float>,
|
||||
pointDistLength: option<int>,
|
||||
}
|
||||
}
|
||||
let defaultRecommendedLength = 100
|
||||
let defaultShouldDownsample = true
|
||||
|
||||
type inputs = {
|
||||
squiggleString: string,
|
||||
samplingInputs: SamplingInputs.t,
|
||||
environment: ASTTypes.environment,
|
||||
}
|
||||
|
||||
let empty: SamplingInputs.t = {
|
||||
sampleCount: None,
|
||||
outputXYPoints: None,
|
||||
kernelWidth: None,
|
||||
pointDistLength: None,
|
||||
}
|
||||
|
||||
let make = (
|
||||
~samplingInputs=empty,
|
||||
~squiggleString,
|
||||
~environment=ASTTypes.Environment.empty,
|
||||
(),
|
||||
): inputs => {
|
||||
samplingInputs: samplingInputs,
|
||||
squiggleString: squiggleString,
|
||||
environment: environment,
|
||||
}
|
||||
}
|
||||
|
||||
type exportDistribution = [
|
||||
| #DistPlus(DistPlus.t)
|
||||
| #Float(float)
|
||||
| #Function(float => Belt.Result.t<DistPlus.t, string>)
|
||||
]
|
||||
|
||||
type exportEnv = array<(string, ASTTypes.node)>
|
||||
|
||||
type exportType = {
|
||||
environment: exportEnv,
|
||||
exports: array<exportDistribution>,
|
||||
}
|
||||
|
||||
module Internals = {
|
||||
let addVariable = (
|
||||
{samplingInputs, squiggleString, environment}: Inputs.inputs,
|
||||
str,
|
||||
node,
|
||||
): Inputs.inputs => {
|
||||
samplingInputs: samplingInputs,
|
||||
squiggleString: squiggleString,
|
||||
environment: ASTTypes.Environment.update(environment, str, _ => Some(node)),
|
||||
}
|
||||
|
||||
type outputs = {
|
||||
graph: ASTTypes.node,
|
||||
pointSetDist: PointSetTypes.pointSetDist,
|
||||
}
|
||||
let makeOutputs = (graph, shape): outputs => {graph: graph, pointSetDist: shape}
|
||||
|
||||
let makeInputs = (inputs: Inputs.inputs): SamplingInputs.samplingInputs => {
|
||||
sampleCount: inputs.samplingInputs.sampleCount |> E.O.default(10000),
|
||||
outputXYPoints: inputs.samplingInputs.outputXYPoints |> E.O.default(10000),
|
||||
kernelWidth: inputs.samplingInputs.kernelWidth,
|
||||
pointSetDistLength: inputs.samplingInputs.pointDistLength |> E.O.default(10000),
|
||||
}
|
||||
|
||||
let runNode = (inputs, node) => AST.toLeaf(makeInputs(inputs), inputs.environment, node)
|
||||
|
||||
let renderIfNeeded = (inputs: Inputs.inputs, node: ASTTypes.node): result<
|
||||
ASTTypes.node,
|
||||
string,
|
||||
> =>
|
||||
node |> (
|
||||
x =>
|
||||
switch x {
|
||||
| #Normalize(_) as n
|
||||
| #SymbolicDist(_) as n =>
|
||||
#Render(n)
|
||||
|> runNode(inputs)
|
||||
|> (
|
||||
x =>
|
||||
switch x {
|
||||
| Ok(#RenderedDist(_)) as r => r
|
||||
| Error(r) => Error(r)
|
||||
| _ => Error("Didn't render, but intended to")
|
||||
}
|
||||
)
|
||||
|
||||
| n => Ok(n)
|
||||
}
|
||||
)
|
||||
|
||||
let outputToDistPlus = (inputs: Inputs.inputs, pointSetDist: PointSetTypes.pointSetDist) =>
|
||||
DistPlus.make(~pointSetDist, ~squiggleString=Some(inputs.squiggleString), ())
|
||||
|
||||
let rec returnDist = (
|
||||
functionInfo: (array<string>, ASTTypes.node),
|
||||
inputs: Inputs.inputs,
|
||||
env: ASTTypes.environment,
|
||||
) => {
|
||||
(input: float) => {
|
||||
let foo: Inputs.inputs = {...inputs, environment: env}
|
||||
evaluateFunction(foo, functionInfo, [#SymbolicDist(#Float(input))]) |> E.R.bind(_, a =>
|
||||
switch a {
|
||||
| #DistPlus(d) => Ok(DistPlus.T.normalize(d))
|
||||
| n =>
|
||||
Js.log2("Error here", n)
|
||||
Error("wrong type")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// TODO: Consider using ExpressionTypes.ExpressionTree.getFloat or similar in this function
|
||||
and coersionToExportedTypes = (inputs, env: ASTTypes.environment, ex: ASTTypes.node): result<
|
||||
exportDistribution,
|
||||
string,
|
||||
> =>
|
||||
ex
|
||||
|> renderIfNeeded(inputs)
|
||||
|> E.R.bind(_, x =>
|
||||
switch x {
|
||||
| #RenderedDist(Discrete({xyShape: {xs: [x], ys: [1.0]}})) => Ok(#Float(x))
|
||||
| #SymbolicDist(#Float(x)) => Ok(#Float(x))
|
||||
| #RenderedDist(n) => Ok(#DistPlus(outputToDistPlus(inputs, n)))
|
||||
| #Function(n) => Ok(#Function(returnDist(n, inputs, env)))
|
||||
| n => Error("Didn't output a rendered distribution. Format:" ++ AST.toString(n))
|
||||
}
|
||||
)
|
||||
|
||||
and evaluateFunction = (inputs: Inputs.inputs, fn: (array<string>, ASTTypes.node), fnInputs) => {
|
||||
let output = AST.runFunction(makeInputs(inputs), inputs.environment, fnInputs, fn)
|
||||
output |> E.R.bind(_, coersionToExportedTypes(inputs, inputs.environment))
|
||||
}
|
||||
|
||||
let runProgram = (inputs: Inputs.inputs, p: ASTTypes.program) => {
|
||||
let ins = ref(inputs)
|
||||
p
|
||||
|> E.A.fmap(x =>
|
||||
switch x {
|
||||
| #Assignment(name, node) =>
|
||||
ins := addVariable(ins.contents, name, node)
|
||||
None
|
||||
| #Expression(node) => Some(runNode(ins.contents, node))
|
||||
}
|
||||
)
|
||||
|> E.A.O.concatSomes
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
|> E.R.bind(_, d =>
|
||||
d
|
||||
|> E.A.fmap(x => coersionToExportedTypes(inputs, ins.contents.environment, x))
|
||||
|> E.A.R.firstErrorOrOpen
|
||||
)
|
||||
|> E.R.fmap(ex => {
|
||||
environment: Belt.Map.String.toArray(ins.contents.environment),
|
||||
exports: ex,
|
||||
})
|
||||
}
|
||||
|
||||
let inputsToLeaf = (inputs: Inputs.inputs) =>
|
||||
Parser.fromString(inputs.squiggleString) |> E.R.bind(_, g => runProgram(inputs, g))
|
||||
}
|
||||
|
||||
@genType
|
||||
let runAll: (string, Inputs.SamplingInputs.t, exportEnv) => result<exportType, string> = (
|
||||
squiggleString,
|
||||
samplingInputs,
|
||||
environment,
|
||||
) => {
|
||||
let inputs = Inputs.make(
|
||||
~samplingInputs,
|
||||
~squiggleString,
|
||||
~environment=Belt.Map.String.fromArray(environment),
|
||||
(),
|
||||
)
|
||||
Internals.inputsToLeaf(inputs)
|
||||
}
|
|
@ -6,5 +6,10 @@ module Js = Reducer_Js
|
|||
module MathJs = Reducer_MathJs
|
||||
|
||||
type expressionValue = Reducer_Expression.expressionValue
|
||||
type externalBindings = Expression.externalBindings
|
||||
let evaluate = Expression.eval
|
||||
let evaluateUsingExternalBindings = Expression.evalUsingExternalBindings
|
||||
let evaluatePartialUsingExternalBindings = Expression.evalPartialUsingExternalBindings
|
||||
let parse = Expression.parse
|
||||
let parseOuter = Expression.parseOuter
|
||||
let parsePartial = Expression.parsePartial
|
||||
|
|
|
@ -7,7 +7,20 @@ module MathJs = Reducer_MathJs
|
|||
|
||||
@genType
|
||||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
|
||||
@genType
|
||||
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
||||
@genType
|
||||
let evaluate: string => result<expressionValue, Reducer_ErrorValue.errorValue>
|
||||
@genType
|
||||
let evaluateUsingExternalBindings: (
|
||||
string,
|
||||
externalBindings,
|
||||
) => result<expressionValue, Reducer_ErrorValue.errorValue>
|
||||
@genType
|
||||
let evaluatePartialUsingExternalBindings: (
|
||||
string,
|
||||
externalBindings,
|
||||
) => result<externalBindings, Reducer_ErrorValue.errorValue>
|
||||
let parse: string => result<Expression.expression, ErrorValue.errorValue>
|
||||
let parseOuter: string => result<Expression.expression, ErrorValue.errorValue>
|
||||
let parsePartial: string => result<Expression.expression, ErrorValue.errorValue>
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
module Builtin = Reducer_Dispatch_BuiltIn
|
||||
module BuiltinMacros = Reducer_Dispatch_BuiltInMacros
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
Macros are like functions but instead of taking values as parameters,
|
||||
they take expressions as parameters and return a new expression.
|
||||
Macros are used to define language building blocks. They are like Lisp macros.
|
||||
*/
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||
module Result = Belt.Result
|
||||
|
||||
open Reducer_ErrorValue
|
||||
|
||||
type expression = ExpressionT.expression
|
||||
|
||||
type reducerFn = (
|
||||
expression,
|
||||
ExpressionT.bindings,
|
||||
) => result<ExpressionValue.expressionValue, errorValue>
|
||||
|
||||
let dispatchMacroCall = (
|
||||
list: list<expression>,
|
||||
bindings: ExpressionT.bindings,
|
||||
reduceExpression: reducerFn,
|
||||
): result<expression, 'e> => {
|
||||
let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings): result<
|
||||
expression,
|
||||
errorValue,
|
||||
> =>
|
||||
switch expression {
|
||||
| ExpressionT.EValue(EvSymbol(aSymbol)) =>
|
||||
switch bindings->Belt.Map.String.get(aSymbol) {
|
||||
| Some(boundExpression) => boundExpression->Ok
|
||||
| None => RESymbolNotFound(aSymbol)->Error
|
||||
}
|
||||
| ExpressionT.EValue(_) => expression->Ok
|
||||
| ExpressionT.EBindings(_) => expression->Ok
|
||||
| ExpressionT.EList(list) => {
|
||||
let racc = list->Belt.List.reduceReverse(Ok(list{}), (racc, each: expression) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
each
|
||||
->replaceSymbols(bindings)
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
})
|
||||
)
|
||||
racc->Result.map(acc => acc->ExpressionT.EList)
|
||||
}
|
||||
}
|
||||
|
||||
let doBindStatement = (statement: expression, bindings: ExpressionT.bindings) => {
|
||||
switch statement {
|
||||
| ExpressionT.EList(list{
|
||||
ExpressionT.EValue(EvCall("$let")),
|
||||
ExpressionT.EValue(EvSymbol(aSymbol)),
|
||||
expressionToReduce,
|
||||
}) => {
|
||||
let rNewExpressionToReduce = replaceSymbols(expressionToReduce, bindings)
|
||||
|
||||
let rNewValue =
|
||||
rNewExpressionToReduce->Result.flatMap(newExpressionToReduce =>
|
||||
reduceExpression(newExpressionToReduce, bindings)
|
||||
)
|
||||
|
||||
let rNewExpression = rNewValue->Result.map(newValue => ExpressionT.EValue(newValue))
|
||||
rNewExpression->Result.map(newExpression =>
|
||||
Belt.Map.String.set(bindings, aSymbol, newExpression)->ExpressionT.EBindings
|
||||
)
|
||||
}
|
||||
| _ => REAssignmentExpected->Error
|
||||
}
|
||||
}
|
||||
|
||||
let doExportVariableExpression = (bindings: ExpressionT.bindings) => {
|
||||
let emptyDictionary: Js.Dict.t<ExpressionValue.expressionValue> = Js.Dict.empty()
|
||||
let reducedBindings = bindings->Belt.Map.String.keep((_key, value) =>
|
||||
switch value {
|
||||
| ExpressionT.EValue(_) => true
|
||||
| _ => false
|
||||
}
|
||||
)
|
||||
let externalBindings = reducedBindings->Belt.Map.String.reduce(emptyDictionary, (
|
||||
acc,
|
||||
key,
|
||||
expressionValue,
|
||||
) => {
|
||||
let value = switch expressionValue {
|
||||
| EValue(aValue) => aValue
|
||||
| _ => EvSymbol("internal")
|
||||
}
|
||||
Js.Dict.set(acc, key, value)
|
||||
acc
|
||||
})
|
||||
externalBindings->ExpressionValue.EvRecord->ExpressionT.EValue->Ok
|
||||
}
|
||||
|
||||
let doBindExpression = (expression: expression, bindings: ExpressionT.bindings) =>
|
||||
switch expression {
|
||||
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$let")), ..._}) =>
|
||||
REExpressionExpected->Error
|
||||
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$exportVariablesExpression"))}) =>
|
||||
doExportVariableExpression(bindings)
|
||||
| _ => replaceSymbols(expression, bindings)
|
||||
}
|
||||
|
||||
switch list {
|
||||
| list{ExpressionT.EValue(EvCall("$$bindings"))} => bindings->ExpressionT.EBindings->Ok
|
||||
|
||||
| list{
|
||||
ExpressionT.EValue(EvCall("$$bindStatement")),
|
||||
ExpressionT.EBindings(bindings),
|
||||
statement,
|
||||
} =>
|
||||
doBindStatement(statement, bindings)
|
||||
| list{
|
||||
ExpressionT.EValue(EvCall("$$bindExpression")),
|
||||
ExpressionT.EBindings(bindings),
|
||||
expression,
|
||||
} =>
|
||||
doBindExpression(expression, bindings)
|
||||
| _ => list->ExpressionT.EList->Ok
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ type errorValue =
|
|||
| RERecordPropertyNotFound(string, string)
|
||||
| RESymbolNotFound(string)
|
||||
| RESyntaxError(string)
|
||||
| REDistributionError(DistributionTypes.error)
|
||||
| RETodo(string) // To do
|
||||
|
||||
type t = errorValue
|
||||
|
@ -20,6 +21,7 @@ let errorToString = err =>
|
|||
| REAssignmentExpected => "Assignment expected"
|
||||
| REExpressionExpected => "Expression expected"
|
||||
| REFunctionExpected(msg) => `Function expected: ${msg}`
|
||||
| REDistributionError(err) => `Distribution Math Error: ${DistributionTypes.Error.toString(err)}`
|
||||
| REJavaScriptExn(omsg, oname) => {
|
||||
let answer = "JS Exception:"
|
||||
let answer = switch oname {
|
||||
|
|
|
@ -15,7 +15,7 @@ type t = expression
|
|||
*/
|
||||
let rec toString = expression =>
|
||||
switch expression {
|
||||
| T.EBindings(bindings) => "$$bound"
|
||||
| T.EBindings(_) => "$$bound"
|
||||
| T.EList(aList) =>
|
||||
`(${Belt.List.map(aList, aValue => toString(aValue))
|
||||
->Extra.List.interperse(" ")
|
||||
|
@ -39,12 +39,26 @@ let parse_ = (expr: string, parser, converter): result<t, errorValue> =>
|
|||
let parse = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
|
||||
|
||||
let parsePartial = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromPartialNode)
|
||||
|
||||
let parseOuter = (mathJsCode: string): result<t, errorValue> =>
|
||||
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromOuterNode)
|
||||
|
||||
let defaultBindings: T.bindings = Belt.Map.String.empty
|
||||
|
||||
/*
|
||||
Recursively evaluate/reduce the expression (Lisp AST)
|
||||
*/
|
||||
let rec reduceExpression = (expression: t, bindings: T.bindings): result<expressionValue, 'e> => {
|
||||
/*
|
||||
Macros are like functions but instead of taking values as parameters,
|
||||
they take expressions as parameters and return a new expression.
|
||||
Macros are used to define language building blocks. They are like Lisp macros.
|
||||
*/
|
||||
let doMacroCall = (list: list<t>, bindings: T.bindings): result<t, 'e> =>
|
||||
Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, reduceExpression)
|
||||
|
||||
/*
|
||||
After reducing each level of expression(Lisp AST), we have a value list to evaluate
|
||||
*/
|
||||
|
@ -54,72 +68,10 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
|
|||
| _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
|
||||
}
|
||||
|
||||
/*
|
||||
Macros are like functions but instead of taking values as parameters,
|
||||
they take expressions as parameters and return a new expression.
|
||||
Macros are used to define language building blocks. They are like Lisp macros.
|
||||
*/
|
||||
let doMacroCall = (list: list<t>, bindings: T.bindings): result<t, 'e> => {
|
||||
let dispatchMacroCall = (list: list<t>, bindings: T.bindings): result<t, 'e> => {
|
||||
let rec replaceSymbols = (expression: t, bindings: T.bindings): result<t, errorValue> =>
|
||||
switch expression {
|
||||
| T.EValue(EvSymbol(aSymbol)) =>
|
||||
switch bindings->Belt.Map.String.get(aSymbol) {
|
||||
| Some(boundExpression) => boundExpression->Ok
|
||||
| None => RESymbolNotFound(aSymbol)->Error
|
||||
}
|
||||
| T.EValue(_) => expression->Ok
|
||||
| T.EBindings(_) => expression->Ok
|
||||
| T.EList(list) => {
|
||||
let racc = list->Belt.List.reduceReverse(Ok(list{}), (racc, each: expression) =>
|
||||
racc->Result.flatMap(acc => {
|
||||
each
|
||||
->replaceSymbols(bindings)
|
||||
->Result.flatMap(newNode => {
|
||||
acc->Belt.List.add(newNode)->Ok
|
||||
})
|
||||
})
|
||||
)
|
||||
racc->Result.map(acc => acc->T.EList)
|
||||
}
|
||||
}
|
||||
|
||||
let doBindStatement = (statement: t, bindings: T.bindings) => {
|
||||
switch statement {
|
||||
| T.EList(list{T.EValue(EvCall("$let")), T.EValue(EvSymbol(aSymbol)), expression}) => {
|
||||
let rNewExpression = replaceSymbols(expression, bindings)
|
||||
rNewExpression->Result.map(newExpression =>
|
||||
Belt.Map.String.set(bindings, aSymbol, newExpression)->T.EBindings
|
||||
)
|
||||
}
|
||||
| _ => REAssignmentExpected->Error
|
||||
}
|
||||
}
|
||||
|
||||
let doBindExpression = (expression: t, bindings: T.bindings) => {
|
||||
switch expression {
|
||||
| T.EList(list{T.EValue(EvCall("$let")), ..._}) => REExpressionExpected->Error
|
||||
| _ => replaceSymbols(expression, bindings)
|
||||
}
|
||||
}
|
||||
|
||||
switch list {
|
||||
| list{T.EValue(EvCall("$$bindings"))} => bindings->T.EBindings->Ok
|
||||
|
||||
| list{T.EValue(EvCall("$$bindStatement")), T.EBindings(bindings), statement} =>
|
||||
doBindStatement(statement, bindings)
|
||||
| list{T.EValue(EvCall("$$bindExpression")), T.EBindings(bindings), expression} =>
|
||||
doBindExpression(expression, bindings)
|
||||
| _ => list->T.EList->Ok
|
||||
}
|
||||
}
|
||||
|
||||
list->dispatchMacroCall(bindings)
|
||||
}
|
||||
|
||||
let rec seekMacros = (expression: t, bindings: T.bindings): result<t, 'e> =>
|
||||
switch expression {
|
||||
| T.EValue(value) => expression->Ok
|
||||
| T.EValue(_value) => expression->Ok
|
||||
| T.EBindings(_value) => expression->Ok
|
||||
| T.EList(list) => {
|
||||
let racc: result<list<t>, 'e> = list->Belt.List.reduceReverse(Ok(list{}), (
|
||||
racc,
|
||||
|
@ -155,6 +107,7 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
|
|||
)
|
||||
racc->Result.flatMap(acc => acc->reduceValueList)
|
||||
}
|
||||
| EBindings(_bindings) => RETodo("Error: Bindings cannot be reduced to values")->Error
|
||||
}
|
||||
|
||||
let rExpandedExpression: result<t, 'e> = expression->seekMacros(bindings)
|
||||
|
@ -163,17 +116,71 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result<express
|
|||
)
|
||||
}
|
||||
|
||||
let evalWBindingsExpression = (aExpression, bindings): result<expressionValue, 'e> =>
|
||||
let evalUsingExternalBindingsExpression_ = (aExpression, bindings): result<expressionValue, 'e> =>
|
||||
reduceExpression(aExpression, bindings)
|
||||
|
||||
/*
|
||||
Evaluates MathJs code via Reducer using bindings and answers the result
|
||||
Evaluates MathJs code via Reducer using bindings and answers the result.
|
||||
When bindings are used, the code is a partial code as if it is cut from a larger code.
|
||||
Therefore all statements are assignments.
|
||||
*/
|
||||
let evalWBindings = (codeText: string, bindings: T.bindings) => {
|
||||
parse(codeText)->Result.flatMap(code => code->evalWBindingsExpression(bindings))
|
||||
let evalPartialUsingExternalBindings_ = (codeText: string, bindings: T.bindings) => {
|
||||
parsePartial(codeText)->Result.flatMap(expression =>
|
||||
expression->evalUsingExternalBindingsExpression_(bindings)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Evaluates MathJs code via Reducer and answers the result
|
||||
Evaluates MathJs code via Reducer using bindings and answers the result.
|
||||
When bindings are used, the code is a partial code as if it is cut from a larger code.
|
||||
Therefore all statments are assignments.
|
||||
*/
|
||||
let eval = (code: string) => evalWBindings(code, defaultBindings)
|
||||
let evalOuterWBindings_ = (codeText: string, bindings: T.bindings) => {
|
||||
parseOuter(codeText)->Result.flatMap(expression =>
|
||||
expression->evalUsingExternalBindingsExpression_(bindings)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Evaluates MathJs code and bindings via Reducer and answers the result
|
||||
*/
|
||||
let eval = (codeText: string) => {
|
||||
parse(codeText)->Result.flatMap(expression =>
|
||||
expression->evalUsingExternalBindingsExpression_(defaultBindings)
|
||||
)
|
||||
}
|
||||
|
||||
type externalBindings = ReducerInterface.ExpressionValue.externalBindings //Js.Dict.t<expressionValue>
|
||||
|
||||
let externalBindingsToBindings = (externalBindings: externalBindings): T.bindings => {
|
||||
let keys = Js.Dict.keys(externalBindings)
|
||||
keys->Belt.Array.reduce(defaultBindings, (acc, key) => {
|
||||
let value = Js.Dict.unsafeGet(externalBindings, key)
|
||||
acc->Belt.Map.String.set(key, T.EValue(value))
|
||||
})
|
||||
}
|
||||
/*
|
||||
Evaluates code with external bindings. External bindings are a record of expression values.
|
||||
*/
|
||||
let evalUsingExternalBindings = (code: string, externalBindings: externalBindings) => {
|
||||
let bindings = externalBindings->externalBindingsToBindings
|
||||
evalOuterWBindings_(code, bindings)
|
||||
}
|
||||
|
||||
/*
|
||||
Evaluates code with external bindings. External bindings are a record of expression values.
|
||||
The code is a partial code as if it is cut from a larger code. Therefore all statments are assignments.
|
||||
*/
|
||||
let evalPartialUsingExternalBindings = (code: string, externalBindings: externalBindings): result<
|
||||
externalBindings,
|
||||
'e,
|
||||
> => {
|
||||
let bindings = externalBindings->externalBindingsToBindings
|
||||
let answer = evalPartialUsingExternalBindings_(code, bindings)
|
||||
answer->Result.flatMap(answer =>
|
||||
switch answer {
|
||||
| EvRecord(aRecord) => Ok(aRecord)
|
||||
| _ => RETodo("TODO: External bindings must be returned")->Error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ type answer = {"value": unit}
|
|||
Rescript cannot type cast on basic values passed on their own.
|
||||
This is why we call evalua inside Javascript and wrap the result in an Object
|
||||
*/
|
||||
let eval__ = %raw(`function (expr) { return {value: Mathjs.evaluate(expr)}; }`)
|
||||
let eval__: string => 'a = %raw(`function (expr) { return {value: Mathjs.evaluate(expr)}; }`)
|
||||
|
||||
/*
|
||||
Call MathJs evaluate and return as a variant
|
||||
|
|
|
@ -9,6 +9,22 @@ type expression = ExpressionT.expression
|
|||
type expressionValue = ExpressionValue.expressionValue
|
||||
type errorValue = ErrorValue.errorValue
|
||||
|
||||
let passToFunction = (fName: string, rLispArgs): result<expression, errorValue> => {
|
||||
let toEvCallValue = (name: string): expression => name->ExpressionValue.EvCall->ExpressionT.EValue
|
||||
|
||||
let fn = fName->toEvCallValue
|
||||
rLispArgs->Result.flatMap(lispArgs => list{fn, ...lispArgs}->ExpressionT.EList->Ok)
|
||||
}
|
||||
|
||||
type blockTag =
|
||||
| ImportVariablesStatement
|
||||
| ExportVariablesExpression
|
||||
type tagOrNode =
|
||||
| BlockTag(blockTag)
|
||||
| BlockNode(Parse.node)
|
||||
|
||||
let toTagOrNode = block => BlockNode(block["node"])
|
||||
|
||||
let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||
let fromNodeList = (nodeList: list<Parse.node>): result<list<expression>, 'e> =>
|
||||
|
@ -18,16 +34,9 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
)
|
||||
)
|
||||
|
||||
let toEvCallValue = (name: string): expression =>
|
||||
name->ExpressionValue.EvCall->ExpressionT.EValue
|
||||
let toEvSymbolValue = (name: string): expression =>
|
||||
name->ExpressionValue.EvSymbol->ExpressionT.EValue
|
||||
|
||||
let passToFunction = (fName: string, rLispArgs): result<expression, errorValue> => {
|
||||
let fn = fName->toEvCallValue
|
||||
rLispArgs->Result.flatMap(lispArgs => list{fn, ...lispArgs}->ExpressionT.EList->Ok)
|
||||
}
|
||||
|
||||
let caseFunctionNode = fNode => {
|
||||
let lispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList
|
||||
passToFunction(fNode->Parse.nameOfFunctionNode, lispArgs)
|
||||
|
@ -94,27 +103,6 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExpressionT.EList(list))
|
||||
}
|
||||
|
||||
let caseBlockNode = (bNode): result<expression, errorValue> => {
|
||||
let blocks = bNode["blocks"]
|
||||
let initialBindings = passToFunction("$$bindings", list{}->Ok)
|
||||
let lastIndex = Belt.Array.length(blocks) - 1
|
||||
blocks->Belt.Array.reduceWithIndex(initialBindings, (rPreviousBindings, block, i) => {
|
||||
rPreviousBindings->Result.flatMap(previousBindings => {
|
||||
let node = block["node"]
|
||||
let rStatement: result<expression, errorValue> = node->fromNode
|
||||
let bindName = if i == lastIndex {
|
||||
"$$bindExpression"
|
||||
} else {
|
||||
"$$bindStatement"
|
||||
}
|
||||
rStatement->Result.flatMap((statement: expression) => {
|
||||
let lispArgs = list{previousBindings, statement}->Ok
|
||||
passToFunction(bindName, lispArgs)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
||||
| MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"])
|
||||
| MjArrayNode(aNode) => caseArrayNode(aNode)
|
||||
|
@ -124,8 +112,7 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
let rExpr: result<expression, errorValue> = expr->Ok
|
||||
rExpr
|
||||
}
|
||||
| MjBlockNode(bNode) => caseBlockNode(bNode)
|
||||
// | MjBlockNode(bNode) => "statement"->toEvSymbolValue->Ok
|
||||
| MjBlockNode(bNode) => bNode["blocks"]->Belt.Array.map(toTagOrNode)->caseTagOrNodes
|
||||
| MjConstantNode(cNode) =>
|
||||
cNode["value"]->JavaScript.Gate.jsToEv->Result.flatMap(v => v->ExpressionT.EValue->Ok)
|
||||
| MjFunctionNode(fNode) => fNode->caseFunctionNode
|
||||
|
@ -136,3 +123,73 @@ let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
|||
}
|
||||
rFinalExpression
|
||||
})
|
||||
and caseTagOrNodes = (tagOrNodes): result<expression, errorValue> => {
|
||||
let initialBindings = passToFunction("$$bindings", list{}->Ok)
|
||||
let lastIndex = Belt.Array.length(tagOrNodes) - 1
|
||||
tagOrNodes->Belt.Array.reduceWithIndex(initialBindings, (rPreviousBindings, tagOrNode, i) => {
|
||||
rPreviousBindings->Result.flatMap(previousBindings => {
|
||||
let rStatement: result<expression, errorValue> = switch tagOrNode {
|
||||
| BlockNode(node) => fromNode(node)
|
||||
| BlockTag(tag) =>
|
||||
switch tag {
|
||||
| ImportVariablesStatement => passToFunction("$importVariablesStatement", list{}->Ok)
|
||||
| ExportVariablesExpression => passToFunction("$exportVariablesExpression", list{}->Ok)
|
||||
}
|
||||
}
|
||||
|
||||
let bindName = if i == lastIndex {
|
||||
"$$bindExpression"
|
||||
} else {
|
||||
"$$bindStatement"
|
||||
}
|
||||
|
||||
rStatement->Result.flatMap((statement: expression) => {
|
||||
let lispArgs = list{previousBindings, statement}->Ok
|
||||
passToFunction(bindName, lispArgs)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let fromPartialNode = (mathJsNode: Parse.node): result<expression, errorValue> => {
|
||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||
let casePartialBlockNode = (bNode: Parse.blockNode) => {
|
||||
let blocksOrTags = bNode["blocks"]->Belt.Array.map(toTagOrNode)
|
||||
let completed = Js.Array2.concat(blocksOrTags, [BlockTag(ExportVariablesExpression)])
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let casePartialExpression = (node: Parse.node) => {
|
||||
let completed = [BlockNode(node), BlockTag(ExportVariablesExpression)]
|
||||
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
||||
| MjBlockNode(bNode) => casePartialBlockNode(bNode)
|
||||
| _ => casePartialExpression(mathJsNode)
|
||||
}
|
||||
rFinalExpression
|
||||
})
|
||||
}
|
||||
|
||||
let fromOuterNode = (mathJsNode: Parse.node): result<expression, errorValue> => {
|
||||
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||
let casePartialBlockNode = (bNode: Parse.blockNode) => {
|
||||
let blocksOrTags = bNode["blocks"]->Belt.Array.map(toTagOrNode)
|
||||
let completed = blocksOrTags
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let casePartialExpression = (node: Parse.node) => {
|
||||
let completed = [BlockNode(node)]
|
||||
completed->caseTagOrNodes
|
||||
}
|
||||
|
||||
let rFinalExpression: result<expression, errorValue> = switch typedMathJsNode {
|
||||
| MjBlockNode(bNode) => casePartialBlockNode(bNode)
|
||||
| _ => casePartialExpression(mathJsNode)
|
||||
}
|
||||
rFinalExpression
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,12 +10,15 @@ type rec expressionValue =
|
|||
| EvArray(array<expressionValue>)
|
||||
| EvBool(bool)
|
||||
| EvCall(string) // External function call
|
||||
| EvDistribution(GenericDist_Types.genericDist)
|
||||
| EvDistribution(DistributionTypes.genericDist)
|
||||
| EvNumber(float)
|
||||
| EvRecord(Js.Dict.t<expressionValue>)
|
||||
| EvString(string)
|
||||
| EvSymbol(string)
|
||||
|
||||
@genType
|
||||
type externalBindings = Js.Dict.t<expressionValue>
|
||||
|
||||
type functionCall = (string, array<expressionValue>)
|
||||
|
||||
let rec toString = aValue =>
|
||||
|
@ -33,7 +36,10 @@ let rec toString = aValue =>
|
|||
->Js.String.concatMany("")
|
||||
`[${args}]`
|
||||
}
|
||||
| EvRecord(aRecord) => {
|
||||
| EvRecord(aRecord) => aRecord->toStringRecord
|
||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||
}
|
||||
and toStringRecord = aRecord => {
|
||||
let pairs =
|
||||
aRecord
|
||||
->Js.Dict.entries
|
||||
|
@ -42,8 +48,6 @@ let rec toString = aValue =>
|
|||
->Js.String.concatMany("")
|
||||
`{${pairs}}`
|
||||
}
|
||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||
}
|
||||
|
||||
let toStringWithType = aValue =>
|
||||
switch aValue {
|
||||
|
@ -68,3 +72,9 @@ let toStringResult = x =>
|
|||
| Ok(a) => `Ok(${toString(a)})`
|
||||
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||
}
|
||||
|
||||
let toStringResultRecord = x =>
|
||||
switch x {
|
||||
| Ok(a) => `Ok(${toStringRecord(a)})`
|
||||
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@ module ExpressionValue = ReducerInterface_ExpressionValue
|
|||
|
||||
type expressionValue = ExpressionValue.expressionValue
|
||||
|
||||
module Sample = {
|
||||
// In real life real libraries should be somewhere else
|
||||
/*
|
||||
For an example of mapping polymorphic custom functions. To be deleted after real integration
|
||||
*/
|
||||
let customAdd = (a: float, b: float): float => {a +. b}
|
||||
}
|
||||
// module Sample = {
|
||||
// // In real life real libraries should be somewhere else
|
||||
// /*
|
||||
// For an example of mapping polymorphic custom functions. To be deleted after real integration
|
||||
// */
|
||||
// let customAdd = (a: float, b: float): float => {a +. b}
|
||||
// }
|
||||
|
||||
/*
|
||||
Map external calls of Reducer
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
module ExpressionValue = ReducerInterface_ExpressionValue
|
||||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
|
||||
let defaultSampleCount = 10000
|
||||
|
||||
let runGenericOperation = DistributionOperation.run(
|
||||
~env={
|
||||
sampleCount: defaultSampleCount,
|
||||
xyPointLength: 1000,
|
||||
sampleCount: MagicNumbers.Environment.defaultSampleCount,
|
||||
xyPointLength: MagicNumbers.Environment.defaultXYPointLength,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -24,13 +22,12 @@ module Helpers = {
|
|||
| "dotPow" => #Power
|
||||
| "multiply" => #Multiply
|
||||
| "dotMultiply" => #Multiply
|
||||
| "dotLog" => #Logarithm
|
||||
| _ => #Multiply
|
||||
}
|
||||
|
||||
let catchAndConvertTwoArgsToDists = (args: array<expressionValue>): option<(
|
||||
GenericDist_Types.genericDist,
|
||||
GenericDist_Types.genericDist,
|
||||
DistributionTypes.genericDist,
|
||||
DistributionTypes.genericDist,
|
||||
)> => {
|
||||
switch args {
|
||||
| [EvDistribution(a), EvDistribution(b)] => Some((a, b))
|
||||
|
@ -41,33 +38,41 @@ module Helpers = {
|
|||
}
|
||||
|
||||
let toFloatFn = (
|
||||
fnCall: GenericDist_Types.Operation.toFloat,
|
||||
dist: GenericDist_Types.genericDist,
|
||||
fnCall: DistributionTypes.DistributionOperation.toFloat,
|
||||
dist: DistributionTypes.genericDist,
|
||||
) => {
|
||||
FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some
|
||||
FromDist(DistributionTypes.DistributionOperation.ToFloat(fnCall), dist)
|
||||
->runGenericOperation
|
||||
->Some
|
||||
}
|
||||
|
||||
let toStringFn = (
|
||||
fnCall: GenericDist_Types.Operation.toString,
|
||||
dist: GenericDist_Types.genericDist,
|
||||
fnCall: DistributionTypes.DistributionOperation.toString,
|
||||
dist: DistributionTypes.genericDist,
|
||||
) => {
|
||||
FromDist(GenericDist_Types.Operation.ToString(fnCall), dist)->runGenericOperation->Some
|
||||
FromDist(DistributionTypes.DistributionOperation.ToString(fnCall), dist)
|
||||
->runGenericOperation
|
||||
->Some
|
||||
}
|
||||
|
||||
let toBoolFn = (
|
||||
fnCall: GenericDist_Types.Operation.toBool,
|
||||
dist: GenericDist_Types.genericDist,
|
||||
fnCall: DistributionTypes.DistributionOperation.toBool,
|
||||
dist: DistributionTypes.genericDist,
|
||||
) => {
|
||||
FromDist(GenericDist_Types.Operation.ToBool(fnCall), dist)->runGenericOperation->Some
|
||||
FromDist(DistributionTypes.DistributionOperation.ToBool(fnCall), dist)
|
||||
->runGenericOperation
|
||||
->Some
|
||||
}
|
||||
|
||||
let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => {
|
||||
FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some
|
||||
let toDistFn = (fnCall: DistributionTypes.DistributionOperation.toDist, dist) => {
|
||||
FromDist(DistributionTypes.DistributionOperation.ToDist(fnCall), dist)
|
||||
->runGenericOperation
|
||||
->Some
|
||||
}
|
||||
|
||||
let twoDiststoDistFn = (direction, arithmetic, dist1, dist2) => {
|
||||
FromDist(
|
||||
GenericDist_Types.Operation.ToDistCombination(
|
||||
DistributionTypes.DistributionOperation.ToDistCombination(
|
||||
direction,
|
||||
arithmeticMap(arithmetic),
|
||||
#Dist(dist2),
|
||||
|
@ -84,7 +89,7 @@ module Helpers = {
|
|||
let parseNumberArray = (ags: array<expressionValue>): Belt.Result.t<array<float>, string> =>
|
||||
E.A.fmap(parseNumber, ags) |> E.A.R.firstErrorOrOpen
|
||||
|
||||
let parseDist = (args: expressionValue): Belt.Result.t<GenericDist_Types.genericDist, string> =>
|
||||
let parseDist = (args: expressionValue): Belt.Result.t<DistributionTypes.genericDist, string> =>
|
||||
switch args {
|
||||
| EvDistribution(x) => Ok(x)
|
||||
| EvNumber(x) => Ok(GenericDist.fromFloat(x))
|
||||
|
@ -92,12 +97,12 @@ module Helpers = {
|
|||
}
|
||||
|
||||
let parseDistributionArray = (ags: array<expressionValue>): Belt.Result.t<
|
||||
array<GenericDist_Types.genericDist>,
|
||||
array<DistributionTypes.genericDist>,
|
||||
string,
|
||||
> => E.A.fmap(parseDist, ags) |> E.A.R.firstErrorOrOpen
|
||||
|
||||
let mixtureWithGivenWeights = (
|
||||
distributions: array<GenericDist_Types.genericDist>,
|
||||
distributions: array<DistributionTypes.genericDist>,
|
||||
weights: array<float>,
|
||||
): DistributionOperation.outputType =>
|
||||
E.A.length(distributions) == E.A.length(weights)
|
||||
|
@ -107,7 +112,7 @@ module Helpers = {
|
|||
)
|
||||
|
||||
let mixtureWithDefaultWeights = (
|
||||
distributions: array<GenericDist_Types.genericDist>,
|
||||
distributions: array<DistributionTypes.genericDist>,
|
||||
): DistributionOperation.outputType => {
|
||||
let length = E.A.length(distributions)
|
||||
let weights = Belt.Array.make(length, 1.0 /. Belt.Int.toFloat(length))
|
||||
|
@ -126,7 +131,7 @@ module Helpers = {
|
|||
| Error(err) => GenDistError(ArgumentError(err))
|
||||
}
|
||||
}
|
||||
| Some(EvDistribution(b)) =>
|
||||
| Some(EvDistribution(_)) =>
|
||||
switch parseDistributionArray(args) {
|
||||
| Ok(distributions) => mixtureWithDefaultWeights(distributions)
|
||||
| Error(err) => GenDistError(ArgumentError(err))
|
||||
|
@ -149,6 +154,7 @@ module SymbolicConstructors = {
|
|||
| "uniform" => Ok(SymbolicDist.Uniform.make)
|
||||
| "beta" => Ok(SymbolicDist.Beta.make)
|
||||
| "lognormal" => Ok(SymbolicDist.Lognormal.make)
|
||||
| "cauchy" => Ok(SymbolicDist.Cauchy.make)
|
||||
| "to" => Ok(SymbolicDist.From90thPercentile.make)
|
||||
| _ => Error("Unreachable state")
|
||||
}
|
||||
|
@ -164,14 +170,10 @@ module SymbolicConstructors = {
|
|||
): option<DistributionOperation.outputType> =>
|
||||
switch symbolicResult {
|
||||
| Ok(r) => Some(Dist(Symbolic(r)))
|
||||
| Error(r) => Some(GenDistError(Other(r)))
|
||||
| Error(r) => Some(GenDistError(OtherError(r)))
|
||||
}
|
||||
}
|
||||
|
||||
module Math = {
|
||||
let e = 2.718281828459
|
||||
}
|
||||
|
||||
let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
|
||||
DistributionOperation.outputType,
|
||||
> => {
|
||||
|
@ -182,7 +184,7 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
|
|||
->E.R.bind(r => r(f1))
|
||||
->SymbolicConstructors.symbolicResultToOutput
|
||||
| (
|
||||
("normal" | "uniform" | "beta" | "lognormal" | "to") as fnName,
|
||||
("normal" | "uniform" | "beta" | "lognormal" | "cauchy" | "to") as fnName,
|
||||
[EvNumber(f1), EvNumber(f2)],
|
||||
) =>
|
||||
SymbolicConstructors.twoFloat(fnName)
|
||||
|
@ -200,7 +202,12 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
|
|||
Helpers.toStringFn(ToSparkline(Belt.Float.toInt(n)), dist)
|
||||
| ("exp", [EvDistribution(a)]) =>
|
||||
// https://mathjs.org/docs/reference/functions/exp.html
|
||||
Helpers.twoDiststoDistFn(Algebraic, "pow", GenericDist.fromFloat(Math.e), a)->Some
|
||||
Helpers.twoDiststoDistFn(
|
||||
Algebraic(AsDefault),
|
||||
"pow",
|
||||
GenericDist.fromFloat(MagicNumbers.Math.e),
|
||||
a,
|
||||
)->Some
|
||||
| ("normalize", [EvDistribution(dist)]) => Helpers.toDistFn(Normalize, dist)
|
||||
| ("isNormalized", [EvDistribution(dist)]) => Helpers.toBoolFn(IsNormalized, dist)
|
||||
| ("toPointSet", [EvDistribution(dist)]) => Helpers.toDistFn(ToPointSet, dist)
|
||||
|
@ -210,7 +217,7 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
|
|||
| ("toSampleSet", [EvDistribution(dist), EvNumber(float)]) =>
|
||||
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist)
|
||||
| ("toSampleSet", [EvDistribution(dist)]) =>
|
||||
Helpers.toDistFn(ToSampleSet(defaultSampleCount), dist)
|
||||
Helpers.toDistFn(ToSampleSet(MagicNumbers.Environment.defaultSampleCount), dist)
|
||||
| ("inspect", [EvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist)
|
||||
| ("truncateLeft", [EvDistribution(dist), EvNumber(float)]) =>
|
||||
Helpers.toDistFn(Truncate(Some(float), None), dist)
|
||||
|
@ -220,31 +227,38 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
|
|||
Helpers.toDistFn(Truncate(Some(float1), Some(float2)), dist)
|
||||
| ("mx" | "mixture", args) => Helpers.mixture(args)->Some
|
||||
| ("log", [EvDistribution(a)]) =>
|
||||
Helpers.twoDiststoDistFn(Algebraic, "log", a, GenericDist.fromFloat(Math.e))->Some
|
||||
Helpers.twoDiststoDistFn(
|
||||
Algebraic(AsDefault),
|
||||
"log",
|
||||
a,
|
||||
GenericDist.fromFloat(MagicNumbers.Math.e),
|
||||
)->Some
|
||||
| ("log10", [EvDistribution(a)]) =>
|
||||
Helpers.twoDiststoDistFn(Algebraic, "log", a, GenericDist.fromFloat(10.0))->Some
|
||||
Helpers.twoDiststoDistFn(Algebraic(AsDefault), "log", a, GenericDist.fromFloat(10.0))->Some
|
||||
| ("unaryMinus", [EvDistribution(a)]) =>
|
||||
Helpers.twoDiststoDistFn(Algebraic, "multiply", a, GenericDist.fromFloat(-1.0))->Some
|
||||
| (("add" | "multiply" | "subtract" | "divide" | "pow" | "log") as arithmetic, [a, b] as args) =>
|
||||
Helpers.twoDiststoDistFn(Algebraic(AsDefault), "multiply", a, GenericDist.fromFloat(-1.0))->Some
|
||||
| (("add" | "multiply" | "subtract" | "divide" | "pow" | "log") as arithmetic, [_, _] as args) =>
|
||||
Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) =>
|
||||
Helpers.twoDiststoDistFn(Algebraic, arithmetic, fst, snd)
|
||||
Helpers.twoDiststoDistFn(Algebraic(AsDefault), arithmetic, fst, snd)
|
||||
)
|
||||
| (
|
||||
("dotAdd"
|
||||
| "dotMultiply"
|
||||
| "dotSubtract"
|
||||
| "dotDivide"
|
||||
| "dotPow"
|
||||
| "dotLog") as arithmetic,
|
||||
[a, b] as args,
|
||||
| "dotPow") as arithmetic,
|
||||
[_, _] as args,
|
||||
) =>
|
||||
Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) =>
|
||||
Helpers.twoDiststoDistFn(Pointwise, arithmetic, fst, snd)
|
||||
)
|
||||
| ("dotLog", [EvDistribution(a)]) =>
|
||||
Helpers.twoDiststoDistFn(Pointwise, "dotLog", a, GenericDist.fromFloat(Math.e))->Some
|
||||
| ("dotExp", [EvDistribution(a)]) =>
|
||||
Helpers.twoDiststoDistFn(Pointwise, "dotPow", GenericDist.fromFloat(Math.e), a)->Some
|
||||
Helpers.twoDiststoDistFn(
|
||||
Pointwise,
|
||||
"dotPow",
|
||||
GenericDist.fromFloat(MagicNumbers.Math.e),
|
||||
a,
|
||||
)->Some
|
||||
| _ => None
|
||||
}
|
||||
}
|
||||
|
@ -258,12 +272,7 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
|
|||
| Float(d) => Ok(EvNumber(d))
|
||||
| String(d) => Ok(EvString(d))
|
||||
| Bool(d) => Ok(EvBool(d))
|
||||
| GenDistError(NotYetImplemented) => Error(RETodo("Function not yet implemented"))
|
||||
| GenDistError(Unreachable) => Error(RETodo("Unreachable"))
|
||||
| GenDistError(DistributionVerticalShiftIsInvalid) =>
|
||||
Error(RETodo("Distribution Vertical Shift Is Invalid"))
|
||||
| GenDistError(ArgumentError(err)) => Error(RETodo("Argument Error: " ++ err))
|
||||
| GenDistError(Other(s)) => Error(RETodo(s))
|
||||
| GenDistError(err) => Error(REDistributionError(err))
|
||||
}
|
||||
|
||||
let dispatch = call => {
|
||||
|
|
|
@ -31,6 +31,9 @@ let makeSampleSetDist = SampleSetDist.make
|
|||
@genType
|
||||
let evaluate = Reducer.evaluate
|
||||
|
||||
@genType
|
||||
let evaluateUsingExternalBindings = Reducer.evaluateUsingExternalBindings
|
||||
|
||||
@genType
|
||||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
|
||||
|
@ -53,4 +56,4 @@ type continuousShape = PointSetTypes.continuousShape
|
|||
let errorValueToString = Reducer_ErrorValue.errorToString
|
||||
|
||||
@genType
|
||||
let distributionErrorToString = GenericDist_Types.Error.toString
|
||||
let distributionErrorToString = DistributionTypes.Error.toString
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
open Rationale.Function.Infix
|
||||
/*
|
||||
Some functions from modules `L`, `O`, and `R` below were copied directly from
|
||||
running `rescript convert -all` on Rationale https://github.com/jonlaing/rationale
|
||||
*/
|
||||
module FloatFloatMap = {
|
||||
module Id = Belt.Id.MakeComparable({
|
||||
type t = float
|
||||
|
@ -8,7 +11,7 @@ module FloatFloatMap = {
|
|||
type t = Belt.MutableMap.t<Id.t, float, Id.identity>
|
||||
|
||||
let fromArray = (ar: array<(float, float)>) => Belt.MutableMap.fromArray(ar, ~id=module(Id))
|
||||
let toArray = (t: t) => Belt.MutableMap.toArray(t)
|
||||
let toArray = (t: t): array<(float, float)> => Belt.MutableMap.toArray(t)
|
||||
let empty = () => Belt.MutableMap.make(~id=module(Id))
|
||||
let increment = (el, t: t) =>
|
||||
Belt.MutableMap.update(t, el, x =>
|
||||
|
@ -20,6 +23,10 @@ module FloatFloatMap = {
|
|||
|
||||
let get = (el, t: t) => Belt.MutableMap.get(t, el)
|
||||
let fmap = (fn, t: t) => Belt.MutableMap.map(t, fn)
|
||||
let partition = (fn, t: t) => {
|
||||
let (match, noMatch) = Belt.Array.partition(toArray(t), fn)
|
||||
(fromArray(match), fromArray(noMatch))
|
||||
}
|
||||
}
|
||||
|
||||
module Int = {
|
||||
|
@ -28,7 +35,7 @@ module Int = {
|
|||
}
|
||||
/* Utils */
|
||||
module U = {
|
||||
let isEqual = (a, b) => a == b
|
||||
let isEqual = \"=="
|
||||
let toA = a => [a]
|
||||
let id = e => e
|
||||
}
|
||||
|
@ -51,17 +58,59 @@ module O = {
|
|||
| None => rFn()
|
||||
}
|
||||
()
|
||||
let fmap = Rationale.Option.fmap
|
||||
let bind = Rationale.Option.bind
|
||||
let default = Rationale.Option.default
|
||||
let isSome = Rationale.Option.isSome
|
||||
let isNone = Rationale.Option.isNone
|
||||
let toExn = Rationale.Option.toExn
|
||||
let some = Rationale.Option.some
|
||||
let firstSome = Rationale.Option.firstSome
|
||||
let toExt = Rationale.Option.toExn // wanna flag this-- looks like a typo but `Rationale.OptiontoExt` doesn't exist.
|
||||
let flatApply = (fn, b) => Rationale.Option.apply(fn, Some(b)) |> Rationale.Option.flatten
|
||||
let flatten = Rationale.Option.flatten
|
||||
let fmap = (f: 'a => 'b, x: option<'a>): option<'b> => {
|
||||
switch x {
|
||||
| None => None
|
||||
| Some(x') => Some(f(x'))
|
||||
}
|
||||
}
|
||||
let bind = (o, f) =>
|
||||
switch o {
|
||||
| None => None
|
||||
| Some(a) => f(a)
|
||||
}
|
||||
let default = (d, o) =>
|
||||
switch o {
|
||||
| None => d
|
||||
| Some(a) => a
|
||||
}
|
||||
let isSome = o =>
|
||||
switch o {
|
||||
| Some(_) => true
|
||||
| _ => false
|
||||
}
|
||||
let isNone = o =>
|
||||
switch o {
|
||||
| None => true
|
||||
| _ => false
|
||||
}
|
||||
let toExn = (err, o) =>
|
||||
switch o {
|
||||
| None => raise(Failure(err))
|
||||
| Some(a) => a
|
||||
}
|
||||
|
||||
let some = a => Some(a)
|
||||
let firstSome = (a, b) =>
|
||||
switch a {
|
||||
| None => b
|
||||
| _ => a
|
||||
}
|
||||
|
||||
let toExt = toExn
|
||||
|
||||
let flatten = o =>
|
||||
switch o {
|
||||
| None => None
|
||||
| Some(x) => x
|
||||
}
|
||||
|
||||
let apply = (o, a) =>
|
||||
switch o {
|
||||
| Some(f) => bind(a, b => some(f(b)))
|
||||
| _ => None
|
||||
}
|
||||
let flatApply = (fn, b) => apply(fn, Some(b)) |> flatten
|
||||
|
||||
let toBool = opt =>
|
||||
switch opt {
|
||||
|
@ -109,6 +158,11 @@ module O2 = {
|
|||
|
||||
/* Functions */
|
||||
module F = {
|
||||
let pipe = (f, g, x) => g(f(x))
|
||||
let compose = (f, g, x) => f(g(x))
|
||||
let flip = (f, a, b) => f(b, a)
|
||||
let always = (x, _y) => x
|
||||
|
||||
let apply = (a, e) => a |> e
|
||||
|
||||
let flatten2Callbacks = (fn1, fn2, fnlast) =>
|
||||
|
@ -152,13 +206,35 @@ module I = {
|
|||
let toString = Js.Int.toString
|
||||
}
|
||||
|
||||
exception Assertion(string)
|
||||
|
||||
/* R for Result */
|
||||
module R = {
|
||||
let result = Rationale.Result.result
|
||||
open Belt.Result
|
||||
let result = (okF, errF, r) =>
|
||||
switch r {
|
||||
| Ok(a) => okF(a)
|
||||
| Error(err) => errF(err)
|
||||
}
|
||||
let id = e => e |> result(U.id, U.id)
|
||||
let fmap = Rationale.Result.fmap
|
||||
let bind = Rationale.Result.bind
|
||||
let toExn = Belt.Result.getExn
|
||||
let fmap = (f: 'a => 'b, r: result<'a, 'c>): result<'b, 'c> => {
|
||||
switch r {
|
||||
| Ok(r') => Ok(f(r'))
|
||||
| Error(err) => Error(err)
|
||||
}
|
||||
}
|
||||
let bind = (r, f) =>
|
||||
switch r {
|
||||
| Ok(a) => f(a)
|
||||
| Error(err) => Error(err)
|
||||
}
|
||||
|
||||
let toExn = (msg: string, x: result<'a, 'b>): 'a =>
|
||||
switch x {
|
||||
| Ok(r) => r
|
||||
| Error(_) => raise(Assertion(msg))
|
||||
}
|
||||
|
||||
let default = (default, res: Belt.Result.t<'a, 'b>) =>
|
||||
switch res {
|
||||
| Ok(r) => r
|
||||
|
@ -179,13 +255,17 @@ module R = {
|
|||
let errorIfCondition = (errorCondition, errorMessage, r) =>
|
||||
errorCondition(r) ? Error(errorMessage) : Ok(r)
|
||||
|
||||
let ap = Rationale.Result.ap
|
||||
let ap = (r, a) =>
|
||||
switch r {
|
||||
| Ok(f) => Ok(f(a))
|
||||
| Error(err) => Error(err)
|
||||
}
|
||||
let ap' = (r, a) =>
|
||||
switch r {
|
||||
| Ok(f) => fmap(f, a)
|
||||
| Error(err) => Error(err)
|
||||
}
|
||||
// (a1 -> a2 -> r) -> m a1 -> m a2 -> m r // not in Rationale
|
||||
|
||||
let liftM2: (('a, 'b) => 'c, result<'a, 'd>, result<'b, 'd>) => result<'c, 'd> = (op, xR, yR) => {
|
||||
ap'(fmap(op, xR), yR)
|
||||
}
|
||||
|
@ -210,10 +290,10 @@ module R2 = {
|
|||
let bind = (a, b) => R.bind(b, a)
|
||||
|
||||
//Converts result type to change error type only
|
||||
let errMap = (a, map) =>
|
||||
let errMap = (a: result<'a, 'b>, map: 'b => 'c): result<'a, 'c> =>
|
||||
switch a {
|
||||
| Ok(r) => Ok(r)
|
||||
| Error(e) => map(e)
|
||||
| Error(e) => Error(map(e))
|
||||
}
|
||||
|
||||
let fmap2 = (xR, f) =>
|
||||
|
@ -235,7 +315,7 @@ module S = {
|
|||
}
|
||||
|
||||
module J = {
|
||||
let toString = \"||>"(Js.Json.decodeString, O.default(""))
|
||||
let toString = F.pipe(Js.Json.decodeString, O.default(""))
|
||||
let fromString = Js.Json.string
|
||||
let fromNumber = Js.Json.number
|
||||
|
||||
|
@ -248,7 +328,7 @@ module J = {
|
|||
|
||||
let toString = (str: option<'a>) =>
|
||||
switch str {
|
||||
| Some(str) => Some(str |> \"||>"(Js.Json.decodeString, O.default("")))
|
||||
| Some(str) => Some(str |> F.pipe(Js.Json.decodeString, O.default("")))
|
||||
| _ => None
|
||||
}
|
||||
}
|
||||
|
@ -263,34 +343,132 @@ module JsDate = {
|
|||
|
||||
/* List */
|
||||
module L = {
|
||||
module Util = {
|
||||
let eq = (a, b) => a == b
|
||||
}
|
||||
let fmap = List.map
|
||||
let get = Belt.List.get
|
||||
let toArray = Array.of_list
|
||||
let fmapi = List.mapi
|
||||
let concat = List.concat
|
||||
let drop = Rationale.RList.drop
|
||||
let remove = Rationale.RList.remove
|
||||
let concat' = (xs, ys) => List.append(ys, xs)
|
||||
|
||||
let rec drop = (i, xs) =>
|
||||
switch (i, xs) {
|
||||
| (_, list{}) => list{}
|
||||
| (i, _) if i <= 0 => xs
|
||||
| (i, list{_, ...b}) => drop(i - 1, b)
|
||||
}
|
||||
|
||||
let append = (a, xs) => List.append(xs, list{a})
|
||||
let take = {
|
||||
let rec loop = (i, xs, acc) =>
|
||||
switch (i, xs) {
|
||||
| (i, _) if i <= 0 => acc
|
||||
| (_, list{}) => acc
|
||||
| (i, list{a, ...b}) => loop(i - 1, b, append(a, acc))
|
||||
}
|
||||
(i, xs) => loop(i, xs, list{})
|
||||
}
|
||||
let takeLast = (i, xs) => List.rev(xs) |> take(i) |> List.rev
|
||||
|
||||
let splitAt = (i, xs) => (take(i, xs), takeLast(List.length(xs) - i, xs))
|
||||
let remove = (i, n, xs) => {
|
||||
let (a, b) = splitAt(i, xs)
|
||||
\"@"(a, drop(n, b))
|
||||
}
|
||||
|
||||
let find = List.find
|
||||
let filter = List.filter
|
||||
let for_all = List.for_all
|
||||
let exists = List.exists
|
||||
let sort = List.sort
|
||||
let length = List.length
|
||||
let filter_opt = Rationale.RList.filter_opt
|
||||
let uniqBy = Rationale.RList.uniqBy
|
||||
let join = Rationale.RList.join
|
||||
let head = Rationale.RList.head
|
||||
let uniq = Rationale.RList.uniq
|
||||
|
||||
let filter_opt = xs => {
|
||||
let rec loop = (l, acc) =>
|
||||
switch l {
|
||||
| list{} => acc
|
||||
| list{hd, ...tl} =>
|
||||
switch hd {
|
||||
| None => loop(tl, acc)
|
||||
| Some(x) => loop(tl, list{x, ...acc})
|
||||
}
|
||||
}
|
||||
List.rev(loop(xs, list{}))
|
||||
}
|
||||
|
||||
let containsWith = f => List.exists(f)
|
||||
|
||||
let uniqWithBy = (eq, f, xs) =>
|
||||
List.fold_left(
|
||||
((acc, tacc), v) =>
|
||||
containsWith(eq(f(v)), tacc) ? (acc, tacc) : (append(v, acc), append(f(v), tacc)),
|
||||
(list{}, list{}),
|
||||
xs,
|
||||
) |> fst
|
||||
|
||||
let uniqBy = (f, xs) => uniqWithBy(Util.eq, f, xs)
|
||||
let join = j => List.fold_left((acc, v) => String.length(acc) == 0 ? v : acc ++ (j ++ v), "")
|
||||
|
||||
let head = xs =>
|
||||
switch List.hd(xs) {
|
||||
| exception _ => None
|
||||
| a => Some(a)
|
||||
}
|
||||
|
||||
let uniq = xs => uniqBy(x => x, xs)
|
||||
let flatten = List.flatten
|
||||
let last = Rationale.RList.last
|
||||
let last = xs => xs |> List.rev |> head
|
||||
let append = List.append
|
||||
let getBy = Belt.List.getBy
|
||||
let dropLast = Rationale.RList.dropLast
|
||||
let contains = Rationale.RList.contains
|
||||
let without = Rationale.RList.without
|
||||
let update = Rationale.RList.update
|
||||
let dropLast = (i, xs) => take(List.length(xs) - i, xs)
|
||||
let containsWith = f => List.exists(f)
|
||||
let contains = x => containsWith(Util.eq(x))
|
||||
|
||||
let reject = pred => List.filter(x => !pred(x))
|
||||
let tail = xs =>
|
||||
switch List.tl(xs) {
|
||||
| exception _ => None
|
||||
| a => Some(a)
|
||||
}
|
||||
|
||||
let init = xs => {
|
||||
O.fmap(List.rev, xs |> List.rev |> tail)
|
||||
}
|
||||
|
||||
let singleton = (x: 'a): list<'a> => list{x}
|
||||
|
||||
let adjust = (f, i, xs) => {
|
||||
let (a, b) = splitAt(i + 1, xs)
|
||||
switch a {
|
||||
| _ if i < 0 => xs
|
||||
| _ if i >= List.length(xs) => xs
|
||||
| list{} => b
|
||||
| list{a} => list{f(a), ...b}
|
||||
| a =>
|
||||
O.fmap(
|
||||
concat'(b),
|
||||
O.bind(init(a), x =>
|
||||
O.fmap(F.flip(append, x), O.fmap(fmap(f), O.fmap(singleton, last(a))))
|
||||
),
|
||||
) |> O.default(xs)
|
||||
}
|
||||
}
|
||||
|
||||
let without = (exclude, xs) => reject(x => contains(x, exclude), xs)
|
||||
let update = (x, i, xs) => adjust(F.always(x), i, xs)
|
||||
let iter = List.iter
|
||||
let findIndex = Rationale.RList.findIndex
|
||||
|
||||
let findIndex = {
|
||||
let rec loop = (pred, xs, i) =>
|
||||
switch xs {
|
||||
| list{} => None
|
||||
| list{a, ...b} => pred(a) ? Some(i) : loop(pred, b, i + 1)
|
||||
}
|
||||
(pred, xs) => loop(pred, xs, 0)
|
||||
}
|
||||
|
||||
let headSafe = Belt.List.head
|
||||
let tailSafe = Belt.List.tail
|
||||
let headExn = Belt.List.headExn
|
||||
|
@ -340,8 +518,6 @@ module A = {
|
|||
let reduce = Belt.Array.reduce
|
||||
let reducei = Belt.Array.reduceWithIndex
|
||||
let isEmpty = r => length(r) < 1
|
||||
let min = a => get(a, 0) |> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i < j ? i : j))
|
||||
let max = a => get(a, 0) |> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i > j ? i : j))
|
||||
let stableSortBy = Belt.SortArray.stableSortBy
|
||||
let toRanges = (a: array<'a>) =>
|
||||
switch a |> Belt.Array.length {
|
||||
|
@ -354,9 +530,12 @@ module A = {
|
|||
Belt.Array.getUnsafe(a, index),
|
||||
Belt.Array.getUnsafe(a, index + 1),
|
||||
))
|
||||
|> Rationale.Result.return
|
||||
|> (x => Ok(x))
|
||||
}
|
||||
|
||||
let tail = Belt.Array.sliceToEnd(_, 1)
|
||||
|
||||
let zip = Belt.Array.zip
|
||||
// This zips while taking the longest elements of each array.
|
||||
let zipMaxLength = (array1, array2) => {
|
||||
let maxLength = Int.max(length(array1), length(array2))
|
||||
|
@ -415,8 +594,8 @@ module A = {
|
|||
module O = {
|
||||
let concatSomes = (optionals: array<option<'a>>): array<'a> =>
|
||||
optionals
|
||||
|> Js.Array.filter(Rationale.Option.isSome)
|
||||
|> Js.Array.map(Rationale.Option.toExn("Warning: This should not have happened"))
|
||||
|> Js.Array.filter(O.isSome)
|
||||
|> Js.Array.map(O.toExn("Warning: This should not have happened"))
|
||||
let defaultEmpty = (o: option<array<'a>>): array<'a> =>
|
||||
switch o {
|
||||
| Some(o) => o
|
||||
|
@ -438,6 +617,32 @@ module A = {
|
|||
r |> Belt.Array.map(_, r => Belt.Result.getExn(r))
|
||||
bringErrorUp |> Belt.Result.map(_, forceOpen)
|
||||
}
|
||||
let filterOk = (x: array<result<'a, 'b>>): array<'a> => fmap(R.toOption, x)->O.concatSomes
|
||||
|
||||
let forM = (x: array<'a>, fn: 'a => result<'b, 'c>): result<array<'b>, 'c> =>
|
||||
firstErrorOrOpen(fmap(fn, x))
|
||||
|
||||
let foldM = (fn: ('c, 'a) => result<'b, 'e>, init: 'c, x: array<'a>): result<'c, 'e> => {
|
||||
let acc = ref(init)
|
||||
let final = ref(Ok())
|
||||
let break = ref(false)
|
||||
let i = ref(0)
|
||||
|
||||
while break.contents != true && i.contents < length(x) {
|
||||
switch fn(acc.contents, x[i.contents]) {
|
||||
| Ok(r) => acc := r
|
||||
| Error(err) => {
|
||||
final := Error(err)
|
||||
break := true
|
||||
}
|
||||
}
|
||||
i := i.contents + 1
|
||||
}
|
||||
switch final.contents {
|
||||
| Ok(_) => Ok(acc.contents)
|
||||
| Error(err) => Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module Sorted = {
|
||||
|
@ -448,8 +653,11 @@ module A = {
|
|||
| (Some(min), Some(max)) => Some(max -. min)
|
||||
| _ => None
|
||||
}
|
||||
|
||||
let floatCompare: (float, float) => int = compare
|
||||
|
||||
let binarySearchFirstElementGreaterIndex = (ar: array<'a>, el: 'a) => {
|
||||
let el = Belt.SortArray.binarySearchBy(ar, el, compare)
|
||||
let el = Belt.SortArray.binarySearchBy(ar, el, floatCompare)
|
||||
let el = el < 0 ? el * -1 - 1 : el
|
||||
switch el {
|
||||
| e if e >= length(ar) => #overMax
|
||||
|
@ -460,25 +668,33 @@ module A = {
|
|||
|
||||
let concat = (t1: array<'a>, t2: array<'a>) => {
|
||||
let ts = Belt.Array.concat(t1, t2)
|
||||
ts |> Array.fast_sort(compare)
|
||||
ts |> Array.fast_sort(floatCompare)
|
||||
ts
|
||||
}
|
||||
|
||||
let concatMany = (t1: array<array<'a>>) => {
|
||||
let ts = Belt.Array.concatMany(t1)
|
||||
ts |> Array.fast_sort(compare)
|
||||
ts |> Array.fast_sort(floatCompare)
|
||||
ts
|
||||
}
|
||||
|
||||
module Floats = {
|
||||
let isSorted = (ar: array<float>): bool =>
|
||||
reduce(zip(ar, tail(ar)), true, (acc, (first, second)) => acc && first < second)
|
||||
|
||||
let makeIncrementalUp = (a, b) =>
|
||||
Array.make(b - a + 1, a) |> Array.mapi((i, c) => c + i) |> Belt.Array.map(_, float_of_int)
|
||||
|
||||
let makeIncrementalDown = (a, b) =>
|
||||
Array.make(a - b + 1, a) |> Array.mapi((i, c) => c - i) |> Belt.Array.map(_, float_of_int)
|
||||
|
||||
let split = (sortedArray: array<float>) => {
|
||||
let continuous = []
|
||||
/*
|
||||
This function goes through a sorted array and divides it into two different clusters:
|
||||
continuous samples and discrete samples. The discrete samples are stored in a mutable map.
|
||||
Samples are thought to be discrete if they have any duplicates.
|
||||
*/
|
||||
let _splitContinuousAndDiscreteForDuplicates = (sortedArray: array<float>) => {
|
||||
let continuous: array<float> = []
|
||||
let discrete = FloatFloatMap.empty()
|
||||
Belt.Array.forEachWithIndex(sortedArray, (index, element) => {
|
||||
let maxIndex = (sortedArray |> Array.length) - 1
|
||||
|
@ -499,14 +715,48 @@ module A = {
|
|||
|
||||
(continuous, discrete)
|
||||
}
|
||||
|
||||
/*
|
||||
This function works very similarly to splitContinuousAndDiscreteForDuplicates. The one major difference
|
||||
is that you can specify a minDiscreteWeight. If the min discreet weight is 4, that would mean that
|
||||
at least four elements needed from a specific value for that to be kept as discrete. This is important
|
||||
because in some cases, we can expect that some common elements will be generated by regular operations.
|
||||
The final continous array will be sorted.
|
||||
*/
|
||||
let splitContinuousAndDiscreteForMinWeight = (
|
||||
sortedArray: array<float>,
|
||||
~minDiscreteWeight: int,
|
||||
) => {
|
||||
let (continuous, discrete) = _splitContinuousAndDiscreteForDuplicates(sortedArray)
|
||||
let keepFn = v => Belt.Float.toInt(v) >= minDiscreteWeight
|
||||
let (discreteToKeep, discreteToIntegrate) = FloatFloatMap.partition(
|
||||
((_, v)) => keepFn(v),
|
||||
discrete,
|
||||
)
|
||||
let newContinousSamples =
|
||||
discreteToIntegrate->FloatFloatMap.toArray
|
||||
|> fmap(((k, v)) => Belt.Array.makeBy(Belt.Float.toInt(v), _ => k))
|
||||
|> Belt.Array.concatMany
|
||||
let newContinuous = concat(continuous, newContinousSamples)
|
||||
newContinuous |> Array.fast_sort(floatCompare)
|
||||
(newContinuous, discreteToKeep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module Floats = {
|
||||
let sum = Belt.Array.reduce(_, 0., (i, j) => i +. j)
|
||||
let mean = a => sum(a) /. (Array.length(a) |> float_of_int)
|
||||
let mean = Jstat.mean
|
||||
let geomean = Jstat.geomean
|
||||
let mode = Jstat.mode
|
||||
let variance = Jstat.variance
|
||||
let stdev = Jstat.stdev
|
||||
let sum = Jstat.sum
|
||||
let random = Js.Math.random_int
|
||||
|
||||
//Passing true for the exclusive parameter excludes both endpoints of the range.
|
||||
//https://jstat.github.io/all.html
|
||||
let percentile = (a, b) => Jstat.percentile(a, b, false)
|
||||
|
||||
// Gives an array with all the differences between values
|
||||
// diff([1,5,3,7]) = [4,-2,4]
|
||||
let diff = (arr: array<float>): array<float> =>
|
||||
|
@ -525,6 +775,9 @@ module A = {
|
|||
let diff = (max -. min) /. Belt.Float.fromInt(n - 1)
|
||||
Belt.Array.makeBy(n, i => min +. Belt.Float.fromInt(i) *. diff)
|
||||
}
|
||||
|
||||
let min = Js.Math.minMany_float
|
||||
let max = Js.Math.maxMany_float
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,7 +789,7 @@ module A2 = {
|
|||
module JsArray = {
|
||||
let concatSomes = (optionals: Js.Array.t<option<'a>>): Js.Array.t<'a> =>
|
||||
optionals
|
||||
|> Js.Array.filter(Rationale.Option.isSome)
|
||||
|> Js.Array.map(Rationale.Option.toExn("Warning: This should not have happened"))
|
||||
|> Js.Array.filter(O.isSome)
|
||||
|> Js.Array.map(O.toExn("Warning: This should not have happened"))
|
||||
let filter = Js.Array.filter
|
||||
}
|
||||
|
|
|
@ -9,6 +9,13 @@ type algebraicOperation = [
|
|||
| #Power
|
||||
| #Logarithm
|
||||
]
|
||||
|
||||
type convolutionOperation = [
|
||||
| #Add
|
||||
| #Multiply
|
||||
| #Subtract
|
||||
]
|
||||
|
||||
@genType
|
||||
type pointwiseOperation = [#Add | #Multiply | #Power]
|
||||
type scaleOperation = [#Multiply | #Power | #Logarithm | #Divide]
|
||||
|
@ -20,22 +27,81 @@ type distToFloatOperation = [
|
|||
| #Sample
|
||||
]
|
||||
|
||||
module Algebraic = {
|
||||
type t = algebraicOperation
|
||||
module Convolution = {
|
||||
type t = convolutionOperation
|
||||
//Only a selection of operations are supported by convolution.
|
||||
let fromAlgebraicOperation = (op: algebraicOperation): option<convolutionOperation> =>
|
||||
switch op {
|
||||
| #Add => Some(#Add)
|
||||
| #Subtract => Some(#Subtract)
|
||||
| #Multiply => Some(#Multiply)
|
||||
| #Divide | #Power | #Logarithm => None
|
||||
}
|
||||
|
||||
let canDoAlgebraicOperation = (op: algebraicOperation): bool =>
|
||||
fromAlgebraicOperation(op)->E.O.isSome
|
||||
|
||||
let toFn: (t, float, float) => float = x =>
|
||||
switch x {
|
||||
| #Add => \"+."
|
||||
| #Subtract => \"-."
|
||||
| #Multiply => \"*."
|
||||
| #Power => \"**"
|
||||
| #Divide => \"/."
|
||||
| #Logarithm => (a, b) => log(a) /. log(b)
|
||||
}
|
||||
}
|
||||
|
||||
let applyFn = (t, f1, f2) =>
|
||||
switch (t, f1, f2) {
|
||||
| (#Divide, _, 0.) => Error("Cannot divide $v1 by zero.")
|
||||
| _ => Ok(toFn(t, f1, f2))
|
||||
type operationError =
|
||||
| DivisionByZeroError
|
||||
| ComplexNumberError
|
||||
|
||||
@genType
|
||||
module Error = {
|
||||
@genType
|
||||
type t = operationError
|
||||
|
||||
let toString = (err: t): string =>
|
||||
switch err {
|
||||
| DivisionByZeroError => "Cannot divide by zero"
|
||||
| ComplexNumberError => "Operation returned complex result"
|
||||
}
|
||||
}
|
||||
|
||||
let power = (a: float, b: float): result<float, Error.t> =>
|
||||
if a >= 0.0 {
|
||||
Ok(a ** b)
|
||||
} else {
|
||||
Error(ComplexNumberError)
|
||||
}
|
||||
|
||||
let divide = (a: float, b: float): result<float, Error.t> =>
|
||||
if b != 0.0 {
|
||||
Ok(a /. b)
|
||||
} else {
|
||||
Error(DivisionByZeroError)
|
||||
}
|
||||
|
||||
let logarithm = (a: float, b: float): result<float, Error.t> =>
|
||||
if b == 1. {
|
||||
Error(DivisionByZeroError)
|
||||
} else if b == 0. {
|
||||
Ok(0.)
|
||||
} else if a > 0.0 && b > 0.0 {
|
||||
Ok(log(a) /. log(b))
|
||||
} else {
|
||||
Error(ComplexNumberError)
|
||||
}
|
||||
|
||||
@genType
|
||||
module Algebraic = {
|
||||
@genType
|
||||
type t = algebraicOperation
|
||||
let toFn: (t, float, float) => result<float, Error.t> = (x, a, b) =>
|
||||
switch x {
|
||||
| #Add => Ok(a +. b)
|
||||
| #Subtract => Ok(a -. b)
|
||||
| #Multiply => Ok(a *. b)
|
||||
| #Power => power(a, b)
|
||||
| #Divide => divide(a, b)
|
||||
| #Logarithm => logarithm(a, b)
|
||||
}
|
||||
|
||||
let toString = x =>
|
||||
|
@ -79,12 +145,12 @@ module DistToFloat = {
|
|||
// Note that different logarithms don't really do anything.
|
||||
module Scale = {
|
||||
type t = scaleOperation
|
||||
let toFn = x =>
|
||||
let toFn = (x: t, a: float, b: float): result<float, Error.t> =>
|
||||
switch x {
|
||||
| #Multiply => \"*."
|
||||
| #Divide => \"/."
|
||||
| #Power => \"**"
|
||||
| #Logarithm => (a, b) => log(a) /. log(b)
|
||||
| #Multiply => Ok(a *. b)
|
||||
| #Divide => divide(a, b)
|
||||
| #Power => power(a, b)
|
||||
| #Logarithm => logarithm(a, b)
|
||||
}
|
||||
|
||||
let format = (operation: t, value, scaleBy) =>
|
||||
|
|
|
@ -16,7 +16,7 @@ let create = (relativeHeights: array<float>, ~maximum=?, ()) => {
|
|||
if E.A.length(relativeHeights) === 0 {
|
||||
""
|
||||
} else {
|
||||
let maximum = maximum->E.O2.default(E.A.max(relativeHeights)->E.O2.toExn(""))
|
||||
let maximum = maximum->E.O2.default(E.A.Floats.max(relativeHeights))
|
||||
|
||||
relativeHeights
|
||||
->E.A2.fmap(_heightToTickIndex(maximum))
|
||||
|
|
|
@ -43,6 +43,10 @@ module T = {
|
|||
let xTotalRange = (t: t) => maxX(t) -. minX(t)
|
||||
let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys}
|
||||
let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)}
|
||||
let mapYResult = (fn: float => result<float, 'e>, t: t): result<t, 'e> => {
|
||||
let mappedYs = E.A.fmap(fn, t.ys)
|
||||
E.A.R.firstErrorOrOpen(mappedYs)->E.R2.fmap(y => {xs: t.xs, ys: y})
|
||||
}
|
||||
let square = mapX(x => x ** 2.0)
|
||||
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys)
|
||||
let fromArray = ((xs, ys)): t => {xs: xs, ys: ys}
|
||||
|
@ -60,8 +64,8 @@ module T = {
|
|||
|
||||
module Ts = {
|
||||
type t = T.ts
|
||||
let minX = (t: t) => t |> E.A.fmap(T.minX) |> E.A.min |> extImp
|
||||
let maxX = (t: t) => t |> E.A.fmap(T.maxX) |> E.A.max |> extImp
|
||||
let minX = (t: t) => t |> E.A.fmap(T.minX) |> E.A.Floats.min
|
||||
let maxX = (t: t) => t |> E.A.fmap(T.maxX) |> E.A.Floats.max
|
||||
let equallyDividedXs = (t: t, newLength) => E.A.Floats.range(minX(t), maxX(t), newLength)
|
||||
let allXs = (t: t) => t |> E.A.fmap(T.xs) |> E.A.Sorted.concatMany
|
||||
}
|
||||
|
@ -199,7 +203,7 @@ module XtoY = {
|
|||
|
||||
/* Returns a between-points-interpolating function that can be used with PointwiseCombination.combine.
|
||||
For discrete distributions, the probability density between points is zero, so we just return zero here. */
|
||||
let discreteInterpolator: interpolator = (t: T.t, leftIndex: int, x: float) => 0.0
|
||||
let discreteInterpolator: interpolator = (_: T.t, _: int, _: float) => 0.0
|
||||
}
|
||||
|
||||
module XsConversion = {
|
||||
|
@ -220,8 +224,8 @@ module XsConversion = {
|
|||
|
||||
module Zipped = {
|
||||
type zipped = array<(float, float)>
|
||||
let compareYs = ((_, y1), (_, y2)) => y1 > y2 ? 1 : 0
|
||||
let compareXs = ((x1, _), (x2, _)) => x1 > x2 ? 1 : 0
|
||||
let compareYs = ((_, y1): (float, float), (_, y2): (float, float)) => y1 > y2 ? 1 : 0
|
||||
let compareXs = ((x1, _): (float, float), (x2, _): (float, float)) => x1 > x2 ? 1 : 0
|
||||
let sortByY = (t: zipped) => t |> E.A.stableSortBy(_, compareYs)
|
||||
let sortByX = (t: zipped) => t |> E.A.stableSortBy(_, compareXs)
|
||||
let filterByX = (testFn: float => bool, t: zipped) => t |> E.A.filter(((x, _)) => testFn(x))
|
||||
|
@ -229,7 +233,12 @@ module Zipped = {
|
|||
|
||||
module PointwiseCombination = {
|
||||
// t1Interpolator and t2Interpolator are functions from XYShape.XtoY, e.g. linearBetweenPointsExtrapolateFlat.
|
||||
let combine = %raw(` // : (float => float => float, T.t, T.t, bool) => T.t
|
||||
let combine: (
|
||||
(float, float) => result<float, Operation.Error.t>,
|
||||
interpolator,
|
||||
T.t,
|
||||
T.t,
|
||||
) => result<T.t, Operation.Error.t> = %raw(`
|
||||
// This function combines two xyShapes by looping through both of them simultaneously.
|
||||
// It always moves on to the next smallest x, whether that's in the first or second input's xs,
|
||||
// and interpolates the value on the other side, thus accumulating xs and ys.
|
||||
|
@ -277,13 +286,28 @@ module PointwiseCombination = {
|
|||
}
|
||||
|
||||
outX.push(x);
|
||||
outY.push(fn(ya, yb));
|
||||
|
||||
// Here I check whether the operation was a success. If it was
|
||||
// keep going. Otherwise, stop and throw the error back to user
|
||||
let newY = fn(ya, yb);
|
||||
if(newY.TAG === 0){
|
||||
outY.push(newY._0);
|
||||
}
|
||||
else {
|
||||
return newY;
|
||||
}
|
||||
}
|
||||
|
||||
return {xs: outX, ys: outY};
|
||||
return {TAG: 0, _0: {xs: outX, ys: outY}, [Symbol.for("name")]: "Ok"};
|
||||
}
|
||||
`)
|
||||
|
||||
let addCombine = (interpolator: interpolator, t1: T.t, t2: T.t): T.t =>
|
||||
combine((a, b) => Ok(a +. b), interpolator, t1, t2)->E.R.toExn(
|
||||
"Add operation should never fail",
|
||||
_,
|
||||
)
|
||||
|
||||
let combineEvenXs = (~fn, ~xToYSelection, sampleCount, t1: T.t, t2: T.t) =>
|
||||
switch (E.A.length(t1.xs), E.A.length(t2.xs)) {
|
||||
| (0, 0) => T.empty
|
||||
|
|
2
packages/website/.prettierignore
Normal file
2
packages/website/.prettierignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
.docusaurus
|
||||
build
|
36
packages/website/docs/Discussions/Bugs.mdx
Normal file
36
packages/website/docs/Discussions/Bugs.mdx
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
title: "Known Bugs"
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
## Mixtures of distributions with very different means
|
||||
|
||||
If you take the pointwise mixture of two distributions with very different means, then the value of that gets fairly warped.
|
||||
|
||||
In the following case, the mean of the mixture should be equal to the sum of the means of the parts. These are shown as the first two displayed variables. These variables diverge as the underlying distributions change.
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = {value: normal(1,1), weight: 1}
|
||||
dist2 = {value: normal(100000000000,1), weight: 1}
|
||||
totalWeight = dist1.weight + dist2.weight
|
||||
distMixture = mixture(dist1.value, dist2.value, [dist1.weight, dist2.weight])
|
||||
mixtureMean = mean(distMixture)
|
||||
separateMeansCombined = (mean(dist1.value) * (dist1.weight) + mean(dist2.value) * (dist2.weight))/totalWeight
|
||||
[mixtureMean, separateMeansCombined, distMixture]`}
|
||||
/>
|
||||
|
||||
## Means of Sample Set Distributions
|
||||
|
||||
The means of sample set distributions can vary dramatically, especially as the numbers get high.
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`symbolicDist = 5 to 50333333
|
||||
sampleSetDist = toSampleSet(symbolicDist)
|
||||
[mean(symbolicDist), mean(sampleSetDist), symbolicDist, sampleSetDist]`}
|
||||
/>
|
7
packages/website/docs/Discussions/Gallery.md
Normal file
7
packages/website/docs/Discussions/Gallery.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
sidebar_position: 6
|
||||
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
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
sidebar_position: 5
|
||||
title: Three Formats of Distributions
|
||||
author: Ozzie Gooen
|
||||
date: 02-19-2022
|
||||
---
|
||||
|
||||
# Three Formats of Distributions
|
||||
|
||||
_Author: Ozzie Gooen_
|
||||
_Written on: Feb 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.
|
||||
|
||||
_Symbolic_ formats are just the math equations. `normal(5,3)` is the symbolic representation of a normal distribution.
|
|
@ -1,12 +1,15 @@
|
|||
---
|
||||
title: "Functions Reference"
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
import { SquiggleEditor } from "../../src/components/SquiggleEditor";
|
||||
|
||||
# Squiggle Functions Reference
|
||||
_The source of truth for this document is [this file of code](https://github.com/quantified-uncertainty/squiggle/blob/develop/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res)_
|
||||
|
||||
## Distributions
|
||||
## Inventory distributions
|
||||
|
||||
We provide starter distributions, computed symbolically.
|
||||
|
||||
### Normal distribution
|
||||
|
||||
|
@ -15,6 +18,10 @@ and standard deviation.
|
|||
|
||||
<SquiggleEditor initialSquiggleString="normal(5, 1)" />
|
||||
|
||||
#### Validity
|
||||
|
||||
- `sd > 0`
|
||||
|
||||
### Uniform distribution
|
||||
|
||||
The `uniform(low, high)` function creates a uniform distribution between the
|
||||
|
@ -22,86 +29,271 @@ two given numbers.
|
|||
|
||||
<SquiggleEditor initialSquiggleString="uniform(3, 7)" />
|
||||
|
||||
#### Validity
|
||||
|
||||
- `low < high`
|
||||
|
||||
### Lognormal distribution
|
||||
|
||||
The `lognormal(mu, sigma)` returns the log of a normal distribution with parameters
|
||||
mu and sigma. The log of lognormal(mu, sigma) is a normal distribution with parameters
|
||||
mean mu and standard deviation sigma.
|
||||
`mu` and `sigma`. The log of `lognormal(mu, sigma)` is a normal distribution with mean `mu` and standard deviation `sigma`.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="lognormal(0, 0.7)" />
|
||||
|
||||
An alternative format is also available. The "to" notation creates a lognormal
|
||||
An alternative format is also available. The `to` notation creates a lognormal
|
||||
distribution with a 90% confidence interval between the two numbers. We add
|
||||
this convinience as lognormal distributions are commonly used in practice.
|
||||
this convenience as lognormal distributions are commonly used in practice.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="2 to 10" />
|
||||
|
||||
#### Future feature:
|
||||
|
||||
Furthermore, it's also possible to create a lognormal from it's actual mean
|
||||
and standard deviation, using `lognormalFromMeanAndStdDev`.
|
||||
|
||||
TODO: interpreter/parser doesn't provide this in current `develop` branch
|
||||
|
||||
<SquiggleEditor initialSquiggleString="lognormalFromMeanAndStdDev(20, 10)" />
|
||||
|
||||
#### Validity
|
||||
|
||||
- `sigma > 0`
|
||||
- In `x to y` notation, `x < y`
|
||||
|
||||
### Beta distribution
|
||||
|
||||
The `beta(a, b)` function creates a beta distribution with parameters a and b:
|
||||
The `beta(a, b)` function creates a beta distribution with parameters `a` and `b`:
|
||||
|
||||
<SquiggleEditor initialSquiggleString="beta(20, 20)" />
|
||||
<SquiggleEditor initialSquiggleString="beta(10, 20)" />
|
||||
|
||||
#### Validity
|
||||
|
||||
- `a > 0`
|
||||
- `b > 0`
|
||||
- Empirically, we have noticed that numerical instability arises when `a < 1` or `b < 1`
|
||||
|
||||
### Exponential distribution
|
||||
|
||||
The `exponential(mean)` function creates an exponential distribution with the given
|
||||
mean.
|
||||
The `exponential(rate)` function creates an exponential distribution with the given
|
||||
rate.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="exponential(1)" />
|
||||
<SquiggleEditor initialSquiggleString="exponential(1.11)" />
|
||||
|
||||
### The Triangular distribution
|
||||
#### Validity
|
||||
|
||||
- `rate > 0`
|
||||
|
||||
### Triangular distribution
|
||||
|
||||
The `triangular(a,b,c)` function creates a triangular distribution with lower
|
||||
bound a, mode b and upper bound c.
|
||||
bound `a`, mode `b` and upper bound `c`.
|
||||
|
||||
#### Validity
|
||||
|
||||
- `a < b < c`
|
||||
|
||||
<SquiggleEditor initialSquiggleString="triangular(1, 2, 4)" />
|
||||
|
||||
### Multimodal distriutions
|
||||
### Scalar (constant dist)
|
||||
|
||||
The multimodal function combines 2 or more other distributions to create a weighted
|
||||
Squiggle, when the context is right, automatically casts a float to a constant distribution.
|
||||
|
||||
## Operating on distributions
|
||||
|
||||
Here are the ways we combine distributions.
|
||||
|
||||
### Mixture of distributions
|
||||
|
||||
The `mixture` function combines 2 or more other distributions to create a weighted
|
||||
combination of the two. The first positional arguments represent the distributions
|
||||
to be combined, and the last argument is how much to weigh every distribution in the
|
||||
combination.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="mx(uniform(0,1), normal(1,1), [0.5, 0.5])" />
|
||||
<SquiggleEditor initialSquiggleString="mixture(uniform(0,1), normal(1,1), [0.5, 0.5])" />
|
||||
|
||||
It's possible to create discrete distributions using this method.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="mx(0, 1, [0.2,0.8])" />
|
||||
<SquiggleEditor initialSquiggleString="mixture(0, 1, [0.2,0.8])" />
|
||||
|
||||
As well as mixed distributions:
|
||||
|
||||
<SquiggleEditor initialSquiggleString="mx(3, 8, 1 to 10, [0.2, 0.3, 0.5])" />
|
||||
<SquiggleEditor initialSquiggleString="mixture(3, 8, 1 to 10, [0.2, 0.3, 0.5])" />
|
||||
|
||||
## Other Functions
|
||||
An alias of `mixture` is `mx`
|
||||
|
||||
### PDF of a distribution
|
||||
#### Validity
|
||||
|
||||
The `pdf(distribution, x)` function returns the density of a distribution at the
|
||||
Using javascript's variable arguments notation, consider `mx(...dists, weights)`:
|
||||
|
||||
- `dists.length == weights.length`
|
||||
|
||||
### Addition
|
||||
|
||||
A horizontal right shift
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 + dist2`}
|
||||
/>
|
||||
|
||||
### Subtraction
|
||||
|
||||
A horizontal left shift
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 - dist2`}
|
||||
/>
|
||||
|
||||
### Multiplication
|
||||
|
||||
TODO: provide intuition pump for the semantics
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 * dist2`}
|
||||
/>
|
||||
|
||||
We also provide concatenation of two distributions as a syntax sugar for `*`
|
||||
|
||||
<SquiggleEditor initialSquiggleString="(0.1 to 1) triangular(1,2,3)" />
|
||||
|
||||
### Division
|
||||
|
||||
TODO: provide intuition pump for the semantics
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 / dist2`}
|
||||
/>
|
||||
|
||||
### Exponentiation
|
||||
|
||||
TODO: provide intuition pump for the semantics
|
||||
|
||||
<SquiggleEditor initialSquiggleString={`(0.1 to 1) ^ beta(2, 3)`} />
|
||||
|
||||
### Taking the base `e` exponential
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist = triangular(1,2,3)
|
||||
exp(dist)`}
|
||||
/>
|
||||
|
||||
### Taking logarithms
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist = triangular(1,2,3)
|
||||
log(dist)`}
|
||||
/>
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist = beta(1,2)
|
||||
log10(dist)`}
|
||||
/>
|
||||
|
||||
Base `x`
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`x = 2
|
||||
dist = beta(2,3)
|
||||
log(dist, x)`}
|
||||
/>
|
||||
|
||||
#### Validity
|
||||
|
||||
- `x` must be a scalar
|
||||
- See [the current discourse](https://github.com/quantified-uncertainty/squiggle/issues/304)
|
||||
|
||||
### Pointwise addition
|
||||
|
||||
**Pointwise operations are done with `PointSetDist` internals rather than `SampleSetDist` internals**.
|
||||
|
||||
TODO: this isn't in the new interpreter/parser yet.
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .+ dist2`}
|
||||
/>
|
||||
|
||||
### Pointwise subtraction
|
||||
|
||||
TODO: this isn't in the new interpreter/parser yet.
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .- dist2`}
|
||||
/>
|
||||
|
||||
### Pointwise multiplication
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .* dist2`}
|
||||
/>
|
||||
|
||||
### Pointwise division
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 ./ dist2`}
|
||||
/>
|
||||
|
||||
### Pointwise exponentiation
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`dist1 = 1 to 10
|
||||
dist2 = triangular(1,2,3)
|
||||
dist1 .^ dist2`}
|
||||
/>
|
||||
|
||||
## Standard functions on distributions
|
||||
|
||||
### Probability density function
|
||||
|
||||
The `pdf(dist, x)` function returns the density of a distribution at the
|
||||
given point x.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="pdf(normal(0,1),0)" />
|
||||
|
||||
### Inverse of a distribution
|
||||
#### Validity
|
||||
|
||||
The `inv(distribution, prob)` gives the value x or which the probability for all values
|
||||
lower than x is equal to prob. It is the inverse of `cdf`.
|
||||
- `x` must be a scalar
|
||||
- `dist` must be a distribution
|
||||
|
||||
<SquiggleEditor initialSquiggleString="inv(normal(0,1),0.5)" />
|
||||
### Cumulative density function
|
||||
|
||||
### CDF of a distribution
|
||||
|
||||
The `cdf(distribution,x)` gives the cumulative probability of the distribution
|
||||
The `cdf(dist, x)` gives the cumulative probability of the distribution
|
||||
or all values lower than x. It is the inverse of `inv`.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="cdf(normal(0,1),0)" />
|
||||
|
||||
### Mean of a distribution
|
||||
#### Validity
|
||||
|
||||
- `x` must be a scalar
|
||||
- `dist` must be a distribution
|
||||
|
||||
### Inverse CDF
|
||||
|
||||
The `inv(dist, prob)` gives the value x or which the probability for all values
|
||||
lower than x is equal to prob. It is the inverse of `cdf`.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="inv(normal(0,1),0.5)" />
|
||||
|
||||
#### Validity
|
||||
|
||||
- `prob` must be a scalar (please only put it in `(0,1)`)
|
||||
- `dist` must be a distribution
|
||||
|
||||
### Mean
|
||||
|
||||
The `mean(distribution)` function gives the mean (expected value) of a distribution.
|
||||
|
||||
|
@ -112,3 +304,65 @@ The `mean(distribution)` function gives the mean (expected value) of a distribut
|
|||
The `sample(distribution)` samples a given distribution.
|
||||
|
||||
<SquiggleEditor initialSquiggleString="sample(normal(0, 10))" />
|
||||
|
||||
## Converting between distribution formats
|
||||
|
||||
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 initialSquiggleString="toSampleSet(normal(5, 10))" />
|
||||
|
||||
Or `PointSet` format
|
||||
|
||||
<SquiggleEditor initialSquiggleString="toPointSet(normal(5, 10))" />
|
||||
|
||||
## Normalization
|
||||
|
||||
Some distribution operations (like horizontal shift) return an unnormalized distriibution.
|
||||
|
||||
We provide a `normalize` function
|
||||
|
||||
<SquiggleEditor initialSquiggleString="normalize((0.1 to 1) + triangular(0.1, 1, 10))" />
|
||||
|
||||
#### Validity - Input to `normalize` must be a dist
|
||||
|
||||
We provide a predicate `isNormalized`, for when we have simple control flow
|
||||
|
||||
<SquiggleEditor initialSquiggleString="isNormalized((0.1 to 1) * triangular(0.1, 1, 10))" />
|
||||
|
||||
#### Validity
|
||||
|
||||
- Input to `isNormalized` must be a dist
|
||||
|
||||
## Convert any distribution to a sample set distribution
|
||||
|
||||
`toSampleSet` has two signatures
|
||||
|
||||
It is unary when you use an internal hardcoded number of samples
|
||||
|
||||
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1)" />
|
||||
|
||||
And binary when you provide a number of samples (floored)
|
||||
|
||||
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1, 100)" />
|
||||
|
||||
## `inspect`
|
||||
|
||||
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 initialSquiggleString="inspect(toSampleSet(0.1 to 1, 100))" />
|
||||
|
||||
Save for a logging side effect, `inspect` does nothing to input and returns it.
|
||||
|
||||
## Truncate
|
||||
|
||||
You can cut off from the left
|
||||
|
||||
<SquiggleEditor initialSquiggleString="truncateLeft(0.1 to 1, 0.5)" />
|
||||
|
||||
You can cut off from the right
|
||||
|
||||
<SquiggleEditor initialSquiggleString="truncateRight(0.1 to 1, 10)" />
|
||||
|
||||
You can cut off from both sides
|
||||
|
||||
<SquiggleEditor initialSquiggleString="truncate(0.1 to 1, 0.5, 1.5)" />
|
||||
|
|
|
@ -1,39 +1,53 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
title: Language Basics
|
||||
---
|
||||
|
||||
import { SquiggleEditor } from "../../src/components/SquiggleEditor";
|
||||
|
||||
# Squiggle Language
|
||||
## Expressions
|
||||
|
||||
The squiggle language has a very simple syntax. The best way to get to understand
|
||||
it is by simply looking at examples.
|
||||
A distribution
|
||||
|
||||
## Basic Language
|
||||
<SquiggleEditor initialSquiggleString={`mixture(1 to 2, 3, [0.3, 0.7])`} />
|
||||
|
||||
As an example:
|
||||
A number
|
||||
|
||||
<SquiggleEditor initialSquiggleString="4.321e-3" />
|
||||
|
||||
Arrays
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`[beta(1,10), 4, isNormalized(toSampleSet(1 to 2))]`}
|
||||
/>
|
||||
|
||||
Records
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`d = {dist: triangular(0, 1, 2), weight: 0.25}
|
||||
d.dist`}
|
||||
/>
|
||||
|
||||
## Statements
|
||||
|
||||
A statement assigns expressions to names. It looks like `<symbol> = <expression>`
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`value_of_work = 10 to 70
|
||||
value_of_work`}
|
||||
5 + value_of_work / 75`}
|
||||
/>
|
||||
|
||||
Squiggle can declare variables (`value_of_work = 10 to 70`) and declare exports
|
||||
(the lone `value_of_work` line). Variables can be used later in a squiggle program
|
||||
and even in other notebooks!
|
||||
### Functions
|
||||
|
||||
An export is rendered to the output view so you can see your result.
|
||||
|
||||
the exports can be expressions, such as:
|
||||
|
||||
<SquiggleEditor initialSquiggleString="normal(0,1)" />
|
||||
|
||||
## Functions
|
||||
|
||||
Squiggle supports functions, including the rendering of functions:
|
||||
We can define functions
|
||||
|
||||
<SquiggleEditor
|
||||
initialSquiggleString={`ozzie_estimate(t) = lognormal({mean: 3 + (t+.1)^2.5, stdev: 8})
|
||||
ozzie_estimate
|
||||
`}
|
||||
initialSquiggleString={`ozzie_estimate(t) = lognormal(1, t ^ 1.01)
|
||||
nuño_estimate(t, m) = mixture(0.5 to 2, normal(m, t ^ 1.25))
|
||||
ozzie_estimate(5) * nuño_estimate(5.01, 1)`}
|
||||
/>
|
||||
|
||||
## See more
|
||||
|
||||
- [Functions reference](https://squiggle-language.com/docs/Features/Functions)
|
||||
- [Gallery](https://squiggle-language.com/docs/Discussions/Gallery)
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
title: Node Packages
|
||||
---
|
||||
|
||||
# Javascript Libraries
|
||||
|
||||
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)
|
||||
- [`@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.
|
||||
|
||||
|
@ -23,8 +22,8 @@ argument allows you to pass an environment previously created by another `run`
|
|||
call. Passing this environment will mean that all previously declared variables
|
||||
in the previous environment will be made available.
|
||||
|
||||
The return type of `run` is a bit complicated, and comes from auto generated js
|
||||
code that comes from rescript. I highly recommend using typescript when using
|
||||
The return type of `run` is a bit complicated, and comes from auto generated `js`
|
||||
code that comes from rescript. We highly recommend using typescript when using
|
||||
this library to help navigate the return type.
|
||||
|
||||
## Squiggle Components
|
56
packages/website/docs/Internal/Grammar.md
Normal file
56
packages/website/docs/Internal/Grammar.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: Grammar
|
||||
author:
|
||||
- Quinn Dougherty
|
||||
---
|
||||
|
||||
Formal grammar specification, reference material for parser implementation.
|
||||
|
||||
_In all likelihood the reference will have to be debugged as we see what tests pass and don't pass during implementation_.
|
||||
|
||||
## Lexical descriptions of constants and identifiers
|
||||
|
||||
```
|
||||
<number> ::= [-]? [0-9]+ (.[0-9]+)? | [-]? [0-9]+ (.[0-9]+)? [e] [-]? [0-9]+
|
||||
<symbol> ::= [a-zA-Z]+ [a-zA-Z0-9]?
|
||||
<bool> ::= true | false
|
||||
```
|
||||
|
||||
## Expressions
|
||||
|
||||
The following gives no typing information. You can obey the grammar and still write nonsensical code.
|
||||
|
||||
Think of javascript's list unpacking notation to read our variable-argument function `mixture`.
|
||||
|
||||
```
|
||||
<expr> ::= <term> + <expr> | <term> - <expr> | <expr> .+ <expr> | <expr> .- <expr> | <term>
|
||||
<term> ::= <power> * <term> | <power> / <term> | <power> .* <term> | <power ./ <term> | <power>
|
||||
<power> ::= <factor> ^ <power> | <factor> .^ <power> | <factor>
|
||||
<factor> ::= <number> | <bool> | <symbol> | ( <expr> ) | <array> | <record> | <record>.<symbol> | <symbol> => <expr> | (<symbol>, <symbol>) => <expr> | ... | <symbol>(<symbol>) | <symbol>(<symbol>, <symbol>) | ...
|
||||
```
|
||||
|
||||
## Data structures
|
||||
|
||||
```
|
||||
<array> ::= [] | [<expr>] | [<expr>, <expr>] | ...
|
||||
<record> ::= {} | {<symbol>: <expr>} | {<symbol>: <expr>, <symbol>: <expr>} | ...
|
||||
```
|
||||
|
||||
## Statements
|
||||
|
||||
```
|
||||
<statement> ::= <assign> | <assignFunction>
|
||||
<assign> ::= <symbol> = <expr>
|
||||
<assignFunction> ::= <symbol>(<symbol>) = <expr> | <symbol>(<symbol>, <symbol>) = <expr> | ...
|
||||
```
|
||||
|
||||
## A squiggle file
|
||||
|
||||
To be valid and raise no errors as of current (apr22) interpreter,
|
||||
|
||||
```
|
||||
<delim> ::= ; | \n
|
||||
<code> ::= <expr> | <statement> <delim> <expr> | <statement> <delim> <statement> <delim> <expr> | ...
|
||||
```
|
||||
|
||||
This isn't strictly speaking true; the interpreter allows expressions outside of the final line.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user