# Squiggle Documentation, One Page This file is auto-generated from the documentation files in the Squiggle repository. It includes our Peggy Grammar. It is meant to be given to an LLM. It is not meant to be read by humans. --- ## Peggy Grammar // Documentation: https://peggyjs.org/documentation.html {{ import * as h from './peggyHelpers.js'; }} start = _nl start:outerBlock _nl finalComment? {return start} zeroOMoreArgumentsBlockOrExpression = lambda / innerBlockOrExpression outerBlock = imports:importStatementsList statements:statementsList finalExpression:(statementSeparator @expression)? { if (finalExpression) { statements.push(finalExpression); } return h.nodeProgram(imports, statements, location()); } / imports:importStatementsList finalExpression:expression { return h.nodeProgram(imports, [finalExpression], location()); } importStatementsList = (@importStatement __nl)* importStatement = _nl 'import' __ file:string variable:(__ 'as' __ @identifier) { return [file, variable]; } innerBlockOrExpression = quotedInnerBlock / finalExpression:expression { return h.nodeBlock([finalExpression], location()); } quotedInnerBlock = '{' _nl statements:statementsList finalExpression:(statementSeparator @expression) _nl '}' { if (finalExpression) { statements.push(finalExpression); } return h.nodeBlock(statements, location()); } / '{' _nl finalExpression:expression _nl '}' { return h.nodeBlock([finalExpression], location()); } statementsList = statement|1.., statementSeparator| statement = letStatement / defunStatement / voidStatement voidStatement = "call" _nl value:zeroOMoreArgumentsBlockOrExpression { const variable = h.nodeIdentifier("_", location()); return h.nodeLetStatement(variable, value, location()); } letStatement = variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression { return h.nodeLetStatement(variable, value, location()); } defunStatement = variable:variable '(' _nl args:functionParameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression { const value = h.nodeLambda(args, body, location(), variable); return h.nodeDefunStatement(variable, value, location()); } assignmentOp "assignment" = '=' functionParameters = functionParameter|.., commaSeparator| functionParameter = id:dollarIdentifier annotation:(_ ':' _nl @expression)? { return annotation ? h.nodeIdentifierWithAnnotation(id.value, annotation, location()) : id; } /* Rules for expressions start here. Peggy doesn't have built-in support for operator precedence, so we have to recurse into expressions, starting from the lowest precedence rules (ternary operators), up to highest precedence rules (function calls and field lookups), until we bottom down at atoms and start all over again. */ expression = ifthenelse / ternary / logicalOr // Ternaries ifthenelse = 'if' __nl condition:logicalOr __nl 'then' __nl trueExpression:innerBlockOrExpression __nl 'else' __nl falseExpression:(ifthenelse/innerBlockOrExpression) { return h.nodeTernary(condition, trueExpression, falseExpression, 'IfThenElse', location()); } ternary = condition:logicalOr _ '?' _nl trueExpression:logicalOr _ ':' _nl falseExpression:(ternary / logicalOr) { return h.nodeTernary(condition, trueExpression, falseExpression, 'C', location()); } // Binary operators logicalOr = head:logicalAnd tail:(_ @logicalOrOp _nl @logicalAnd)* { return h.makeInfixChain(head, tail, location()); } logicalOrOp "operator" = '||' logicalAnd = head:equality tail:(_ @logicalAndOp _nl @equality)* { return h.makeInfixChain(head, tail, location()); } logicalAndOp "operator" = '&&' equality = left:relational _ operator:equalityOp _nl right:relational { return h.nodeInfixCall(operator, left, right, location()); } / relational equalityOp "operator" = '==' / '!=' relational = left:credibleInterval _ operator:relationalOp _nl right:credibleInterval { return h.nodeInfixCall(operator, left, right, location()); } / credibleInterval relationalOp "operator" = '<=' / '<' / '>=' / '>' credibleInterval = head:additive tail:(__ @credibleIntervalOp __nl @additive)* { return h.makeInfixChain(head, tail, location()); } credibleIntervalOp "operator" = 'to' additive = head:multiplicative tail:(_ @additiveOp _nl @multiplicative)* { return h.makeInfixChain(head, tail, location()); } additiveOp "operator" = '+' / '-' / '.+' / '.-' multiplicative = head:power tail:(_ @multiplicativeOp _nl @power)* { return h.makeInfixChain(head, tail, location()); } multiplicativeOp "operator" = '*' / '/' / '.*' / './' power = left:chainFunctionCall _ operator:powerOp _nl right:power { return h.nodeInfixCall(operator, left, right, location()); } / chainFunctionCall powerOp "operator" = '^' / '.^' chainFunctionCall = head:unary tail:(_ ('->') _nl @chainedFunction)* { return tail.reduce(function(result, element) { return h.nodePipe(result, element.callable, element.args, location()); }, head); } chainedFunction = fn:callableBasicValue '(' _nl args:functionArguments _nl ')' { return { callable: fn, args }; } / fn:callableBasicValue { return { callable: fn, args: [] }; } callableBasicValue = staticCollectionElement / valueConstructor / variable // Unary operator unary = unaryOperator:unaryOperator _nl right:(unary/postOperator) { return h.nodeUnaryCall(unaryOperator, right, location())} / postOperator unaryOperator "unary operator" = '-' / '.-' / '!' // Function calls and field lookups postOperator = collectionElement / atom staticCollectionElement = head:atom &('['/'.') // should we allow whitespace before the first []? tail:( _ '[' _nl arg:expression _nl ']' {return {mode: 'bracket', arg}} / '.' arg:$dollarIdentifier {return {mode: 'dot', arg}} // TODO: should we allow whitespace before "."? )* { return tail.reduce(function(result, element) { switch (element.mode) { case 'dot': return h.nodeDotLookup(result, element.arg, location()); case 'bracket': return h.nodeBracketLookup(result, element.arg, location()); default: throw new Error("Parser implementation error"); } }, head); } collectionElement = head:atom &('['/'('/'.') tail:( _ '(' _nl args:functionArguments _nl ')' { return {mode: 'call', args}; } / _ '[' _nl arg:expression _nl ']' { return {mode: 'bracket', arg}; } / '.' arg:$dollarIdentifier { return {mode: 'dot', arg}; } )* { return tail.reduce(function(result, element) { switch (element.mode) { case 'call': return h.nodeCall(result, element.args, location()); case 'dot': return h.nodeDotLookup(result, element.arg, location()); case 'bracket': return h.nodeBracketLookup(result, element.arg, location()); default: throw new Error("Parser implementation error"); } }, head); } functionArguments = expression|.., commaSeparator| atom = '(' _nl @expression _nl ')' / basicValue basicValue = valueConstructor / basicLiteral // Basic literals such as numbers, strings and booleans basicLiteral = string / number / boolean / variable / voidLiteral voidLiteral 'void' = "()" {return h.nodeVoid(location());} variable = dollarIdentifierWithModule / dollarIdentifier dollarIdentifierWithModule 'identifier' = head:$moduleIdentifier tail:('.' _nl @$moduleIdentifier)* '.' _nl final:$dollarIdentifier { const modifiers = [head, ...tail, final]; const modifiedIdentifier = modifiers.join('.'); return h.nodeIdentifier(modifiedIdentifier, location()); } moduleIdentifier 'identifier' = ([A-Z]+[_a-z0-9]i*) identifier 'identifier' = ([_a-z]+[_a-z0-9]i*) { return h.nodeIdentifier(text(), location()); } unitIdentifier 'identifier' = 'minutes' / 'hours' / 'days' / 'years' / 'n' / 'm' / 'k' / 'M' / 'B' / 'G' / 'T' / 'P' dollarIdentifier 'identifier' = ([\$_a-z]+[\$_a-z0-9]i*) { return h.nodeIdentifier(text(), location()); } escapeSeq 'escape sequence' = "\\" @( esc:[^u] { return h.parseEscapeSequence([esc], location(), error) } / esc:("u" . . . .) { return h.parseEscapeSequence(esc, location(), error) } ) string 'string' = "'" characters:(!("'" / "\\") @. / escapeSeq)* "'" { return h.nodeString(characters.join(""), location()); } / '"' characters:(!('"' / "\\") @. / escapeSeq)* '"' { return h.nodeString(characters.join(""), location()); } number = number:float unit:unitIdentifier? { if (unit === null) { return number; } else { return h.nodeUnitValue(number, unit, location()); } } float 'number' = significand:floatSignificand exponent:floatExponent? { return h.nodeFloat({ integer: significand.integer, fractional: significand.fractional, exponent, }, location()); } floatSignificand = integer:intLiteral "." fractional:($d+)? { return { integer, fractional, }; } / integer:intLiteral { return { integer, fractional: null, }; } / "." fractional:$(d+) { return { integer: 0, fractional, }; } floatExponent = [e]i @value:signedIntLiteral intLiteral = d+ { return parseInt(text(), 10); } signedIntLiteral = ('-'/'+')? d+ { return parseInt(text(), 10); } d = [0-9] boolean 'boolean' = ('true' / 'false') ! [a-z]i ! [_$] { return h.nodeBoolean(text() === 'true', location()); } // Contructors - delimited blocks such as {} and [] valueConstructor = arrayConstructor / lambda / quotedInnerBlock / dictConstructor lambda = '{' _nl '|' _nl args:functionParameters _nl '|' _nl statements:statementsList finalExpression: (statementSeparator @expression) _nl '}' { statements.push(finalExpression); return h.nodeLambda(args, h.nodeBlock(statements, location()), location(), undefined); } / '{' _nl '|' _nl args:functionParameters _nl '|' _nl finalExpression: expression _nl '}' { return h.nodeLambda(args, finalExpression, location(), undefined); } arrayConstructor 'array' = '[' _nl ']' { return h.nodeArray([], location()); } / '[' _nl args:array_elements _nl ']' { return h.nodeArray(args, location()); } array_elements = @expression|1.., commaSeparator| commaSeparator? dictConstructor 'dict' = '{' _nl '}' { return h.nodeDict([], location()); } / '{' _nl args:array_dictEntries _nl '}' { return h.nodeDict(args, location()); } array_dictEntries = @(keyValuePair / keyValueInherit)| 1.., commaSeparator|commaSeparator? keyValueInherit = key:identifier { return h.nodeKeyValue(key, key, location()); } keyValuePair = key:expression _ ':' _nl value:expression { return h.nodeKeyValue(key, value, location()); } // Separators _ 'whitespace' = whiteSpaceCharactersOrComment* _nl 'whitespace' = (whiteSpaceCharactersOrComment / commentOrNewLine)* __ 'whitespace' = whiteSpaceCharactersOrComment+ __nl 'whitespace' = (whiteSpaceCharactersOrComment / commentOrNewLine)+ statementSeparator ';' = _ (';' / commentOrNewLine)+ _nl commaSeparator ',' = _ ',' _nl commentOrNewLine = finalComment? newLine newLine "newline" = [\n\r] finalComment "line comment" = _ ('//') comment:($([^\r\n]*)) { options.comments.push(h.lineComment(comment, location())); } whiteSpaceCharactersOrComment = whiteSpaceCharacters / delimitedComment delimitedComment "comment" = '/*' comment:($( ([^*] / [*][^/])* )) '*/' { options.comments.push(h.blockComment(comment, location())); } // delimitedComment "comment" // = '/*' comment:($([^*])*) '*/' { options.comments.push(h.blockComment(comment, location())); } whiteSpaceCharacters = [ \t] --- --- description: Squiggle is a minimalist programming language for probabilistic estimation. It's meant for intuitively-driven quantitative estimation instead of data analysis or data-driven statistical techniques. --- import { SquiggleEditor } from "@quri/squiggle-components"; import { Callout, Tabs, Tab } from "nextra/components"; # Introduction Squiggle is a minimalist programming language for probabilistic estimation. It's meant for intuitively-driven quantitative estimation instead of data analysis or data-driven statistical techniques. The basics of Squiggle are fairly straightforward. This can be enough for many models. The more advanced functionality can take some time to learn. ## A Simple Example Say you're trying to estimate the number of piano tuners in New York City. You can build a simple model of this, like so. **Tip**

This editor is interactive! Try changing the code.

--- Now let's take this a bit further. Let's imagine that you think that NYC will grow over time, and you'd like to estimate the number of piano tuners for every point in time for the next few years. ## Using Squiggle You can currently interact with Squiggle in a few ways: **[Squiggle Hub](https://squigglehub.org/)** Squiggle Hub is a platform for the creation and sharing of code written in Squiggle. It's a great way to get started with Squiggle or to share your models with others. **[Playground](/playground)** The [Squiggle Playground](/playground) is a nice tool for working with small models and making prototypes. You can make simple shareable links, but you can't save models that change over time. **[Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle)** There's a simple [VS Code extension](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle) for running and visualizing Squiggle code. We find that VS Code is a useful editor for managing larger Squiggle setups. **[Typescript Library](https://www.npmjs.com/package/@quri/squiggle-lang)** Squiggle is built using [Typescript](https://www.typescriptlang.org/), and is accessible via a simple Typescript library. You can use this library to either run Squiggle code in full, or to call select specific functions within Squiggle. **[React Components Library](https://www.npmjs.com/package/@quri/squiggle-components)** All of the components used in the playground and documentation are available in a separate component NPM repo. You can see the full Storybook of components [here](https://components.squiggle-language.com). **[Observable](https://observablehq.com/@hazelfire/squiggle)** You can use Squiggle Components in Observable notebooks. Sam Nolan put together an exportable [Observable Notebook](https://observablehq.com/@hazelfire/squiggle) of the key components that you can directly import and use in your Observable notebooks. ## Squiggle Vs. Other Tools ### What Squiggle Is - A simple programming language for doing math with probability distributions. - An embeddable language that can be used in Javascript applications. - A tool to encode functions as forecasts that can be embedded in other applications. ### What Squiggle Is Not - A complete replacement for enterprise Risk Analysis tools. (See [Crystal Ball](https://www.oracle.com/applications/crystalball/), [@Risk](https://www.palisade.com/risk/), [Lumina Analytica](https://lumina.com/)) - A [probabilistic programming language](https://en.wikipedia.org/wiki/Probabilistic_programming). Squiggle does not support Bayesian inference. - A tool for substantial data analysis. (See programming languages like [Python](https://www.python.org/) or [Julia](https://julialang.org/)) - A programming language for anything other than estimation. - A visually-driven tool. (See [Guesstimate](https://www.getguesstimate.com/) and [Causal](https://causal.app/)) ### Strengths - Simple and readable syntax, especially for dealing with probabilistic math. - Fast for relatively small models. Strong for rapid prototyping. - Optimized for using some numeric and symbolic approaches, not just Monte Carlo. - Embeddable in Javascript. - Free and open-source. ### Weaknesses - Limited scientific capabilities. - Much slower than serious probabilistic programming languages on sizeable models. - Can't do Bayesian backwards inference. - Essentially no support for libraries or modules (yet). - Still very new, so a tiny ecosystem. - Still very new, so there are likely math bugs. - Generally not as easy to use as Guesstimate or Causal, especially for non programmers. ## Organization Squiggle is one of the main projects of [The Quantified Uncertainty Research Institute](https://quantifieduncertainty.org/). QURI is a nonprofit funded primarily by [Effective Altruist](https://www.effectivealtruism.org/) donors. # Changelog ## 0.8.0 ### [@quri/squiggle-lang](https://www.npmjs.com/package/@quri/squiggle-lang) #### Breaking changes * `SampleSet` distributions are now used as the default. * This change is important because only SampleSet distributions handle correlations, and users expecting correlations got confused when the defaults were for symbolic distributions instead. If you want to use symbolic formats, you can, like: ``Sym.normal(5,2)``. * `a to b` expressions always evaluate to lognormal distributions, limited to positive-only arguments. * Previously these changed to use normal distributions at values zero or less, and this confused users. https://github.com/quantified-uncertainty/squiggle/issues/1095. * `|>` pipes syntax is removed, in favor of the `->` syntax. * We had two syntaxes that did the same thing, and wanted to clean this up. * `#` comments support is removed, in favor of the `//` syntax. * Renamed functions such as `scaleLog`, `scalePow`, etc. to `Danger.mapYLog`, `Danger.mapYPow`, etc. * These were infrequently used and prone to errors. * Default sampleCount in squiggle-lang is now 1k, matching playground. #### New features * Parameter annotations: `f(x: [0, 10]) = ...`; see [docs](https://preview.squiggle-language.com/docs/Guides/Language#parameter-annotations) for details * `Plot.distFn` and `Plot.numericFn` (`Plot.fn` is removed) * Support for manual plot [scales](https://squiggle-language.com/docs/Api/Plot#scales) * Support for docstrings. `/** ... */` comments are displayed in the viewer * `List.reduceWhile` function * Dicts and bindings are ordered * `map` callback supports an optional second ``index`` parameter * Support for `{ foo, bar }` shorthand in dicts * Mixture correlations https://github.com/quantified-uncertainty/squiggle/pull/1732 #### Fixes * Exponentiation is right-associative * Units are whitelisted; `100blah` is a syntax error * Fixed error message when ternary condition is wrong #### Performance improvements * ~2x speedup on synthetic lambda-heavy benchmarks; similar improvements on real code, on average ### [@quri/squiggle-components](https://www.npmjs.com/package/@quri/squiggle-components) #### Breaking changes * Many `@quri/squiggle-lang` APIs have changed, so this package should be updated at the same time as `@quri/squiggle-components` * "Input variables" tab was removed; you can inject your JSON data directly into Squiggle instead #### New features * Movable divider between editor and graph view * Support for new plots: `Plot.scatter`, `Table.make` and others * New default tick format. This fixes an error that small numbers (`0.01`) were shown as `10m`; #2007. * Ability to "zoom in" on any variable in viewer; #1913 * "Find in editor" button in viewer * Other various updates to the playground UI #### Fixes * Display Infinity as "Infinity" * Fix shortcut tooltips on non-macs * Charts filter out infinity values from the domain * Improve tooltip for errors in editor ### [VS Code extension](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle) * Improvements to syntax highlighter ### [@quri/prettier-plugin-squiggle](https://www.npmjs.com/package/@quri/prettier-plugin-squiggle) **Initial release.** Install this package to format any Squiggle code with [https://prettier.io](Prettier). Requires Prettier v3. ### @quri/ui Initial release with common UI components by QURI, used in squiggle-components and Squiggle Hub. [Storybook](https://quri-ui.vercel.app/) You probably shouldn't use this package directly if you don't work at QURI, because we don't have any stability guarantees and optimize for our convenience. ### Other * [Squiggle Hub](https://squigglehub.org/) was released * New [documentation](https://www.squiggle-language.com/), now with search * We've started work on relative values functionality in Squiggle Hub. This is still fairly error-prone, we'll work on cleaning it up in the next few months. ## 0.7.0 ### @quri/squiggle-lang Language: - New functions: `List.concat`, `List.uniq`, `List.append`, `List.flatten`, `List.join`, `Dict.mapKeys`. - `stdev` and `variance` work for all kinds of distributions. - Support for `==`, `!=` and `+` on strings. - Support for trailing comma in arrays: `arr = [5,6,]` is now allowed. - `Plot.fn` allows for programmatical control of `min..max` range in charts. - Support for `{ p10: ..., p90: ... }` and `{ p25: ..., p75: ... }` in `normal()` and `lognormal()`. - Non-integer key lookups in arrays are now disallowed; `arr[1.5]` and `arr[3/0]` will result in error; previously non-integer keys got rounded to 0 or the closest integer. - Fix for zero parameter function quirks; the checks are now stricter and some existing code might break. - Fix for `normal(5,2) * 0` special case. - Fix for summary stats and `inv` function on `pointMass` distributions. - Fix for `inv` and `sample` on discrete distributions. JS API: - `sq` function allows to embed Squiggle code in JS with syntax highlighting in VS Code (when VS Code extension is installed). - `value.asJS()` method for easier conversion of results to JS values. Other: - ES6 modules build (we still provide CommonJS build as well). ### @quri/squiggle-components UI: - New code editor based on Codemirror, with the improved syntax highlighting and auto-completion for variables and stdlib functions. - Charts are now implemented in pure Canvas with D3 (instead of Vega), with some improvements in UI and rendering speed. Other: - This package is now ESM-only! We depend on D3, and D3 is ESM-only, so squiggle-components has to be ESM-only as well. If your project still uses CommonJS and you doesn't use Next.js or other smart transpiler, you might encounter problems. See [this document](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) for advice. - Much smaller bundle size, thanks to removing some heavy dependencies. - `oneLine` prop in `SquiggleEditor` removed (probably no one used it). ## 0.6.0 - Language reimplemented in Typescript instead of Rescript. - 2x performance boost on average on typical Squiggle code. Most improvements come from optimizations in distribution operations. - Color fixes and improved distribution charts in components. - `mean()` on mixed pointsets now works correctly. ## 0.5.1 - Error locations and stacktraces. - New functions: `List.length`, `Dist.set` and `Dist.map`. - Improved `to` precedence order. - Improvements to `->` operator: more stuff can be passed on the right side, e.g. anonymous functions. - Support for empty dicts (`{}`). - Various performance improvements; overall speedup on real code is around 5%. ## 0.5.0 - Performance improvements: - Interpreter is now 5x-20x faster on code written in Squiggle - SampleSet to PointSet conversions are 2x faster - cdf function on SampleSets is 30x faster - overall speedup is about 2x on average on real code written in Squiggle - 50% smaller bundle size for [@quri/squiggle-lang](https://www.npmjs.com/package/@quri/squiggle-lang); 20% smaller bundle size for [@quri/squiggle-components](https://www.npmjs.com/package/@quri/squiggle-components). ### Breaking changes Some rarely used math functions got removed or moved to the `Math` namespace. For example, `cos(x)` is now `Math.cos(x)`, and `atanh(x)` doesn't exist. If your code is now failing with ` is not defined` for anything from [this list](https://mathjs.org/docs/reference/functions.html), try adding `Math.` prefix first, and then complain on [Github issues](https://github.com/quantified-uncertainty/squiggle/issues). --- title: Processing Confidence Intervals author: Nuño Sempere description: This page explains what we are doing when we take a 90% confidence interval, and we get a mean and a standard deviation from it. --- This page explains what we are doing when we take a 90% confidence interval, and we get a mean and a standard deviation from it. ## For normals ```js module Normal = { //... let from90PercentCI = (low, high) => { let mean = E.A.Floats.mean([low, high]) let stdev = (high -. low) /. (2. *. 1.6448536269514722) #Normal({mean: mean, stdev: stdev}) } //... } ``` We know that for a normal with mean $\mu$ and standard deviation $\sigma$, $$ a \cdot Normal(\mu, \sigma) = Normal(a \cdot \mu, |a| \cdot \sigma) $$ We can now look at the quantile of a $Normal(0,1)$. We find that the 95% point is reached at $1.6448536269514722$. ([source](https://stackoverflow.com/questions/20626994/how-to-calculate-the-inverse-of-the-normal-cumulative-distribution-function-in-p)) This means that the 90% confidence interval is $[-1.6448536269514722, 1.6448536269514722]$, which has a width of $2 \cdot 1.6448536269514722$. So then, if we take a $Normal(0,1)$ and we multiply it by $\frac{(high -. low)}{(2. *. 1.6448536269514722)}$, it's 90% confidence interval will be multiplied by the same amount. Then we just have to shift it by the mean to get our target normal. --- author: - Nuño Sempere - Quinn Dougherty abstract: This document outlines some properties about algebraic combinations of distributions. It is meant to facilitate property tests for [Squiggle](https://squiggle-language.com/), an estimation language for forecasters. So far, we are focusing on the means, the standard deviation and the shape of the pdfs. description: Invariants to check with property tests. --- # Invariants of Probability Distributions Invariants to check with property tests. _This document right now is normative and aspirational, not a description of the testing that's currently done_. ## Algebraic combinations The academic keyword to search for in relation to this document is "[algebra of random variables](https://wikiless.org/wiki/Algebra_of_random_variables?lang=en)". Squiggle doesn't yet support getting the standard deviation, denoted by $\sigma$, but such support could yet be added. ### Means and standard deviations #### Sums $$ mean(f+g) = mean(f) + mean(g) $$ $$ \sigma(f+g) = \sqrt{\sigma(f)^2 + \sigma(g)^2} $$ In the case of normal distributions, $$ mean(normal(a,b) + normal(c,d)) = mean(normal(a+c, \sqrt{b^2 + d^2})) $$ #### Subtractions $$ mean(f-g) = mean(f) - mean(g) $$ $$ \sigma(f-g) = \sqrt{\sigma(f)^2 + \sigma(g)^2} $$ #### Multiplications $$ mean(f \cdot g) = mean(f) \cdot mean(g) $$ $$ \sigma(f \cdot g) = \sqrt{ (\sigma(f)^2 + mean(f)) \cdot (\sigma(g)^2 + mean(g)) - (mean(f) \cdot mean(g))^2} $$ #### Divisions Divisions are tricky, and in general we don't have good expressions to characterize properties of ratios. In particular, the ratio of two normals is a Cauchy distribution, which doesn't have to have a mean. ### Probability density functions (pdfs) Specifying the pdf of the sum/multiplication/... of distributions as a function of the pdfs of the individual arguments can still be done. But it requires integration. My sense is that this is still doable, and I (Nuño) provide some _pseudocode_ to do this. #### Sums Let $f, g$ be two independently distributed functions. Then, the pdf of their sum, evaluated at a point $z$, expressed as $(f + g)(z)$, is given by: $$ (f + g)(z)= \int_{-\infty}^{\infty} f(x)\cdot g(z-x) \,dx $$ See a proof sketch [here](https://www.milefoot.com/math/stat/rv-sums.htm) Here is some pseudocode to approximate this: ```js // pdf1 and pdf2 are pdfs, // and cdf1 and cdf2 are their corresponding cdfs let epsilonForBounds = 2 ** -16; let getBounds = (cdf) => { let cdf_min = -1; let cdf_max = 1; let n = 0; while ( (cdf(cdf_min) > epsilonForBounds || 1 - cdf(cdf_max) > epsilonForBounds) && n < 10 ) { if (cdf(cdf_min) > epsilonForBounds) { cdf_min = cdf_min * 2; } if (1 - cdf(cdf_max) > epsilonForBounds) { cdf_max = cdf_max * 2; } } return [cdf_min, cdf_max]; }; let epsilonForIntegrals = 2 ** -16; let pdfOfSum = (pdf1, pdf2, cdf1, cdf2, z) => { let bounds1 = getBounds(cdf1); let bounds2 = getBounds(cdf2); let bounds = [ Math.min(bounds1[0], bounds2[0]), Math.max(bounds1[1], bounds2[1]), ]; let result = 0; for (let x = bounds[0]; (x = x + epsilonForIntegrals); x < bounds[1]) { let delta = pdf1(x) * pdf2(z - x); result = result + delta * epsilonForIntegrals; } return result; }; ``` ## `pdf`, `cdf`, and `quantile` With $\forall dist, pdf := x \mapsto \texttt{pdf}(dist, x) \land cdf := x \mapsto \texttt{cdf}(dist, x) \land quantile := p \mapsto \texttt{quantile}(dist, p)$, ### `cdf` and `quantile` are inverses $$ \forall x \in (0,1), cdf(quantile(x)) = x \land \forall x \in \texttt{dom}(cdf), x = quantile(cdf(x)) $$ ### The codomain of `cdf` equals the open interval `(0,1)` equals the codomain of `pdf` $$ \texttt{cod}(cdf) = (0,1) = \texttt{cod}(pdf) $$ ## To do: - Write out invariants for CDFs and Inverse CDFs - Provide sources or derivations, useful as this document becomes more complicated - Provide definitions for the probability density function, exponential, inverse, log, etc. - Provide at least some tests for division - See if playing around with characteristic functions turns out anything useful --- description: Squiggle's grammar is described with Peggy --- # Grammar Squiggle grammar is described with [Peggy](https://peggyjs.org) in [this file](https://github.com/quantified-uncertainty/squiggle/blob/main/packages/squiggle-lang/src/ast/peggyParser.peggy). --- description: Squiggle is still very early. The main first goal is to become stable (to reach version 1. --- # Roadmap Squiggle is still young. The main first goal is to become stable (to reach version 1.0). Right now we think it is useable to use for small projects, but do note that there are very likely some math bugs and performance problems. If you have preferences or suggestions for our roadmap, please say so! Post your thoughts in the Github discussion or in the Discord. ## Programming Language Features - A simple type system - Optional and default paramaters for functions - Some testing - Much better code editor integration - DateTime support ## Distribution Features There are many important distribution types that Squiggle doesn't yet support. Some key functions we'd like include: [Metalog Distribution](https://en.wikipedia.org/wiki/Metalog_distribution) Add the Metalog distribution, and some convenient methods for generating these distributions. This might be a bit tricky because we might need or build a library to fit data. There's no Metalog javascript library yet, this would be pretty useful. There's already a Metalog library in Python, so that one could be used for inspiration. `Distribution.smoothen(p)` Takes a distribution and smoothens it. For example, [Elicit Forecast](https://forecast.elicit.org/) does something like this, with uniform distributions. ## Major Future Additions **An interface to interpret & score Squiggle files** Squiggle functions need to be aggregated and scored. This should be done outside one Squiggle file. Maybe this should also be done in Squiggle, or maybe it should be done using Javascript. My guess is that there should eventually be some way for people to declare that some of their Squiggle values are meant to be formally declared, to be scored and similar by others. Then other programs can read these files, and either use the values, or score them. Of course, we'd also need good math for how the scoring should work, exactly. This interface should also be able to handle changing Squiggle values. This is because people would be likely to want to update their functions over time, and that should be taken into account for scoring. **Importance & quality scores** Workflows/functionality to declare the importance and coveredness of each part of the paramater space. For example, some subsets of the paramater space of a function might be much more important to get right than others. Similarly, the analyst might be much more certain about some parts than others. Ideally. they could decline sections. **Static / sensitivity analysis** Guesstimate has Sensitivity analysis that's pretty useful. This could be quite feasible to add, though it will likely require some thinking. **Annotation** It might be useful to allow people to annotate functions and variables with longer descriptions, maybe Markdown. This could very much help interpretation/analysis of these items. **Randomness seeds** Right now, Monte Carlo simulations are totally random. It would be nicer to be able to enter a seed somehow in order to control the randomness. Or, with the same seed, the function should always return the same values. This would make debugging and similar easier. **Caching/memoization** There are many performance improvements that Squiggle could have. We'll get to some of them eventually. --- description: "Language features: an overview of syntax, operators, functions, and more" --- import { SquiggleEditor } from "@quri/squiggle-components"; # Language Features ## Program Structure A Squiggle program consists of a series of definitions (for example, `x = 5`, `f(x) = x * x`). This can optionally conclude with an *end expression*. If an end expression is provided, it becomes the evaluated output of the program, and only this result will be displayed in the viewer. Otherwise, all top-level variable definitions will be displayed. ## Immutability All variables in Squiggle are immutable, similar to other functional programming languages like OCaml or Haskell. In the case of container types (lists and dictionaries), this implies that an operation such as myList[3] = 10 is not permitted. Instead, we recommend using `List.map`, `List.reduce` or other [List functions](/docs/Api/List). In case of basic types such as numbers or strings, the impact of immutability is more subtle. Consider this code: ```squiggle x = 5 x = x + 5 ``` While it appears that the value of x has changed, what actually occurred is the creation of a new variable with the same name, which [shadowed](https://en.wikipedia.org/wiki/Variable_shadowing) the previous x variable. In most shadowing scenarios, the result would be identical to what you'd expect in languages such as Javascript or Python. One case where shadowing matters is closures: In the above example, the `argPlusX` function captures the value of `x` from line 1, not the newly shadowed `x` from line 4. As a result, `argPlusX(5)` returns 10, not 15. ## Blocks Blocks are special expressions in Squiggle that can contain any number of local definitions and end with an expression. ## Conditionals If/then/else statements in Squiggle are values too. See [Control flow](/docs/Guides/ControlFlow) for more details and examples. ## Comments ## Pipes Squiggle features [data-first](https://www.javierchavarri.com/data-first-and-data-last-a-comparison/) pipes. Functions in the standard library are organized to make this convenient. truncateLeft(3) -> SampleSet.fromDist -> SampleSet.map({|r| r + 10})`} /> ## Standard Library Squiggle features a simple [standard libary](/docs/Api/Dist). Most functions are namespaced under their respective types to keep functionality distinct. Certain popular functions are usable without their namespaces. For example, SampleSet.fromList // namespaces required b = normal(5,2) // namespace not required c = 5 to 10 // namespace not required`} /> --- description: Explore common pitfalls and misunderstandings in Squiggle's handling of distributions --- import { SquiggleEditor } from "@quri/squiggle-components"; # Gotchas ## Point Set Distributions Conversions Point Set conversions are done with [kernel density estimation](https://en.wikipedia.org/wiki/Kernel_density_estimation), which is lossy. This might be particularly noticeable in cases where distributions should be entirely above zero. In this example, we see that the median of this (highly skewed) distribution is positive when it's in a Sample Set format, but negative when it's converted to a Point Set format. --- This can be particularly confusing for visualizations. Visualizations automatically convert distributions into Point Set formats. Therefore, they might often show negative values, even if the underlying distribution is fully positive. We plan to later support more configuration of kernel density estimation, and for visualiations of Sample Set distributions to instead use histograms. ## Sample Set Correlations Correlations with Sample Set distributions are a bit complicated. Monte Carlo generations with Squiggle are ordered. The first sample in one Sample Set distribution will correspond to the first sample in a distribution that comes from a resulting Monte Carlo generation. Therefore, Sample Set distributions in a chain of Monte Carlo generations are likely to all be correlated with each other. This connection breaks if any node changes to the Point Set or Symbolic format. In this example, we subtract all three types of distributions by themselves. Notice that the Sample Set distribution returns 1. The other two return the result of subtracting one normal distribution from a separate uncorrelated distribution. These results are clearly very different to each other. PointSet.fromDist symbolicDist = Sym.normal(5, 2) [ sampleSetDist - sampleSetDist, pointSetDist - pointSetDist, symbolicDist - symbolicDist, ]`} /> --- description: "Squiggle Function Overview" --- import { SquiggleEditor } from "@quri/squiggle-components"; # Functions ## Basic syntax There's no `return` statement in Squiggle; function definitions are values. If you need to define local variables in functions, you can use blocks: ## Anonymous Functions `f(x, y) = x * y` and `f = {|x, y| x * y}` are equivalent. Squiggle functions are values, and you can save them to variables, pass them to other functions as arguments, return functions from functions, and so on. ## Function Visualization The Squiggle viewer can automatically visualize two types of functions, without manual plots: 1. `(number) => number` 2. `(number) => distribution` When Squiggle displays a single parameter function, it needs to select some range of parameter values. The default range is 0 to 10. You can manually set the the range in the following ways: - With `Plot.numericFn` or `Plot.distFn` plots, using the `xScale` parameter - Through the chart's settings in the UI (look for a gear icon next to the variable name) - With parameter annotations (explained below) ## Parameter Annotations Function parameters can be annotated with _domains_. Examples: - `x: Number.rangeDomain({ min: 5, max: 10 })` - `x: [5, 10]` — shortcut for `Number.rangeDomain(...)` Annotations help to document possible values that can be passed as a parameter's value. Annotations will affect the parameter range used in the function's chart. For more control over function charts, you can use the [Plot module API](/docs/Api/Plot). Domains are checked on function calls; `f(x: [1,2]) = x; f(3)` will fail. We plan to support other kinds of domains in the future; for now, only numeric ranges are supported. ### Annotation Reflection Domains and parameter names can be accessed by the `fn.parameters` property. For example, domains extracted with `.parameters` can be reused in annotations of other parameters: --- description: Various functions for operating on distributions --- import { SquiggleEditor } from "@quri/squiggle-components"; # Distribution Functions ## Standard Operations Here are the ways we combine distributions. ### Addition A horizontal right shift. The addition operation represents the distribution of the sum of the value of one random sample chosen from the first distribution and the value one random sample chosen from the second distribution. ### Subtraction A horizontal left shift. The subtraction operation represents the distribution of the value of one random sample chosen from the first distribution minus the value of one random sample chosen from the second distribution. ### Multiplication A proportional scaling. The multiplication operation represents the distribution of the multiplication of the value of one random sample chosen from the first distribution times the value one random sample chosen from the second distribution. We also provide concatenation of two distributions as a syntax sugar for `*` ### Division A proportional scaling (normally a shrinking if the second distribution has values higher than 1). The division operation represents the distribution of the division of the value of one random sample chosen from the first distribution over the value one random sample chosen from the second distribution. If the second distribution has some values near zero, it tends to be particularly unstable. ### Exponentiation A projection over a contracted x-axis. The exponentiation operation represents the distribution of the exponentiation of the value of one random sample chosen from the first distribution to the power of the value one random sample chosen from the second distribution. ### The base `e` exponential ### Logarithms A projection over a stretched x-axis. Base `x` ## Pointwise Operations ### Pointwise addition For every point on the x-axis, operate the corresponding points in the y axis of the pdf. **Pointwise operations are done with `PointSetDist` internals rather than `SampleSetDist` internals**. ### Pointwise multiplication ## Standard Functions ### Probability density function The `pdf(dist, x)` function returns the density of a distribution at the given point x. #### Validity - `x` must be a scalar - `dist` must be a distribution ### Cumulative density function The `cdf(dist, x)` gives the cumulative probability of the distribution or all values lower than x. It is the inverse of `quantile`. #### Validity - `x` must be a scalar - `dist` must be a distribution ### Quantile The `quantile(dist, prob)` gives the value x for which the sum of the probability for all values lower than x is equal to prob. It is the inverse of `cdf`. In the literature, it is also known as the quantiles function. In the optional `summary statistics` panel which appears beneath distributions, the numbers beneath 5%, 10%, 25% etc are the quantiles of that distribution for those precentage values. #### 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. ### Sampling a distribution The `sample(distribution)` samples a given distribution. ## Converting between distribution formats We can convert any distribution into the `SampleSet` format Or the `PointSet` format #### Validity - Second argument to `SampleSet.fromDist` must be a number. ## Normalization Some distribution operations (like horizontal shift) return an unnormalized distriibution. We provide a `normalize` function #### Validity - Input to `normalize` must be a dist We provide a predicate `isNormalized`, for when we have simple control flow #### Validity - Input to `isNormalized` must be a dist ## `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. Save for a logging side effect, `inspect` does nothing to input and returns it. ## Truncate You can cut off from the left You can cut off from the right You can cut off from both sides --- description: Various ways to create Squiggle distributions --- import { Callout, Tabs, Tab } from "nextra/components"; import { SquiggleEditor } from "@quri/squiggle-components"; # Distribution Creation ## Normal ```squiggle normal(mean: number, standardDeviation: number) normal({mean: number, standardDeviation: number}) normal({p5: number, p95: number}) normal({p10: number, p90: number}) normal({p25: number, p75: number}) ``` Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) with the given mean and standard deviation. [Wikipedia](https://en.wikipedia.org/wiki/Normal_distribution) ## Lognormal ```squiggle lognormal(mu: number, sigma: number) lognormal({mean: number, standardDeviation: number}) lognormal({p5: number, p95: number}) lognormal({p10: number, p90: number}) lognormal({p25: number, p75: number}) ``` Creates a [lognormal distribution](https://en.wikipedia.org/wiki/Log-normal_distribution) with the given mu and sigma. `Mu` and `sigma` represent the mean and standard deviation of the normal which results when you take the log of our lognormal distribution. They can be difficult to directly reason about. However, there are several alternative ways to specify a lognormal distribution which are often easier to reason about. [Wikipedia](https://en.wikipedia.org/wiki/Log-normal_distribution)
❓ Understanding mu and sigma The log of lognormal(mu, sigma) is a normal distribution with mean mu and standard deviation sigma. For example, these two distributions are identical:
## To ```squiggle (5thPercentile: number) to (95thPercentile: number) to(5thPercentile: number, 95thPercentile: number) ``` The `to` function is an easy way to generate lognormal distributions using predicted _5th_ and _95th_ percentiles. It's the same as ``lognormal({p5, p95})``, but easier to write and read. 5 to 10 does the same thing as to(5,10). It's very easy to generate distributions with very long tails. These can be impossible to see without changing view settings. (These settings are available in the Playground, but not this smaller editor component) ### Arguments - `5thPercentile`: number - `95thPercentile`: number, greater than `5thPercentile` **Tip** "To" is a great way to generate probability distributions very quickly from your intuitions. It's easy to write and easy to read. It's often a good place to begin an estimate. **Caution** If you haven't tried [calibration training](https://www.lesswrong.com/posts/LdFbx9oqtKAAwtKF3/list-of-probability-calibration-exercises), you're likely to be overconfident. We recommend doing calibration training to get a feel for what a 90 percent confident interval feels like. ## Uniform ```squiggle uniform(low:number, high:number) ``` Creates a [uniform distribution]() with the given low and high values. ### Arguments - `low`: Number - `high`: Number greater than `low` **Caution** While uniform distributions are very simple to understand, we find it rare to find uncertainties that actually look like this. Before using a uniform distribution, think hard about if you are really 100% confident that the paramater will not wind up being just outside the stated boundaries. One good example of a uniform distribution uncertainty would be clear physical limitations. You might have complete complete uncertainty on what time of day an event will occur, but can say with 100% confidence it will happen between the hours of 0:00 and 24:00. ## Point Mass ```squiggle pointMass(value:number) ``` Creates a discrete distribution with all of its probability mass at point `value`. Few Squiggle users call the function `pointMass()` directly. Numbers are often (but not always) converted into point mass distributions automatically, when it is appropriate. For example, in the function `mixture(1,2,normal(5,2))`, the first two arguments will get converted into point mass distributions with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),pointMass(2),pointMass(5,2))`. `pointMass()` distributions are currently the only discrete distributions accessible in Squiggle. ### Arguments - `value`: Number ## Beta ```squiggle beta(alpha:number, beta:number) beta({mean: number, stdev: number}) ``` Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) with the given `alpha` and `beta` values. For a good summary of the beta distribution, see [this explanation](https://stats.stackexchange.com/a/47782) on Stack Overflow. ### Arguments - `alpha`: Number greater than zero - `beta`: Number greater than zero **Caution with small numbers** Squiggle struggles to show beta distributions when either alpha or beta are below 1.0. This is because the tails at ~0.0 and ~1.0 are very high. Using a log scale for the y-axis helps here.
Examples
## Mixture ```squiggle mixture(...distributions: Distribution[], weights?: number[]) mixture(distributions: Distribution[], weights?: number[]) mx(...distributions: Distribution[], weights?: number[]) mx(distributions: Distribution[], weights?: number[]) ``` The `mixture` mixes combines multiple distributions to create a mixture. You can optionally pass in a list of proportional weights. ### Arguments - `distributions`: A set of distributions or numbers, each passed as a paramater. Numbers will be converted into point mass distributions. - `weights`: An optional array of numbers, each representing the weight of its corresponding distribution. The weights will be re-scaled to add to `1.0`. If a weights array is provided, it must be the same length as the distribution paramaters. ### Aliases - `mx` ### Special Use Cases of Mixtures
🕐 Zero or Continuous One common reason to have mixtures of continous and discrete distributions is to handle the special case of 0. Say I want to model the time I will spend on some upcoming project. I think I have an 80% chance of doing it. In this case, I have a 20% chance of spending 0 time with it. I might estimate my hours with,
🔒 Model Uncertainty Safeguarding One technique several Foretold.io users used is to combine their main guess, with a "just-in-case distribution". This latter distribution would have very low weight, but would be very wide, just in case they were dramatically off for some weird reason.
## SampleSet.fromList ```squiggle SampleSet.fromList(samples:number[]) ``` Creates a sample set distribution using an array of samples. Samples are converted into PDFs automatically using [kernel density estimation](https://en.wikipedia.org/wiki/Kernel_density_estimation) and an approximated bandwidth. This is an approximation and can be error-prone. ### Arguments - `samples`: An array of at least 5 numbers. ## PointSet.makeContinuous ```squiggle PointSet.makeContinuous(points:{x: number, y: number}) ``` Creates a continuous point set distribution using a list of points. **Caution!** Distributions made with ``makeContinuous`` are not automatically normalized. We suggest normalizing them manually using the ``normalize`` function. ### Arguments - `points`: An array of at least 3 coordinates. ## PointSet.makeDiscrete ```squiggle PointSet.makeDiscrete(points:{x: number, y: number}) ``` Creates a discrete point set distribution using a list of points. ### Arguments - `points`: An array of at least 1 coordinate. --- description: This page documents control flow. Squiggle has if/else statements, but not for loops. --- import { SquiggleEditor } from "@quri/squiggle-components"; # Control Flow This page documents control flow. Squiggle has if/else statements, but not for loops. But for for loops, you can use reduce/map constructs instead, which are also documented here. ## Conditionals ### If-else ```squiggle if condition then result else alternative ``` ### If-else as a ternary operator ```squiggle test ? result : alternative; ``` ### Tips and tricks #### Use brackets and parenthesis to organize control flow or This is overkill for simple examples becomes useful when the control conditions are more complex. #### Save the result to a variable Assigning a value inside an if/else flow isn't possible: ```squiggle x = 10 y = 20 if x == 1 then { y = 1 } else { y = 2 * x } ``` Instead, you can do this: Likewise, for assigning more than one value, you can't do this: ```squiggle y = 0 z = 0 if x == 1 then { y = 2 } else { z = 4 } ``` Instead, do: ## For loops For loops aren't supported in Squiggle. Instead, use a [map](/docs/Api/List#map) or a [reduce](/docs/Api/List#reduce) operator. Instead of: ```js xs = []; for (i = 0; i < 10; i++) { xs[i] = f(x); } ``` do: --- description: Much of the Squiggle math is imprecise. This can cause significant errors, so watch out. --- import { SquiggleEditor } from "@quri/squiggle-components"; # Known Bugs Much of the Squiggle math is imprecise. This can cause significant errors, so watch out. Below are a few specific examples to watch for. We'll work on improving these over time and adding much better warnings and error management. You can see an updated list of known language bugs [here](https://github.com/quantified-uncertainty/squiggle/issues?q=is%3Aopen+is%3Aissue+label%3ABug+label%3ALanguage). ## Operations on very small or large numbers, silently round to 0 and 1 Squiggle is poor at dealing with very small or large numbers, given fundamental limitations of floating point precision. See [this Github Issue](https://github.com/quantified-uncertainty/squiggle/issues/834). ## Mixtures of distributions with very different means 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. ## Means of Sample Set Distributions The means of sample set distributions can vary dramatically, especially as the numbers get high. --- description: "Basic Types" --- import { SquiggleEditor } from "@quri/squiggle-components"; # Basic Types ## Numbers Squiggle numbers are built directly on [Javascript numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number). They can be integers or floats, and support all the usual arithmetic operations. [Number API](/docs/Api/Number) Numbers support a few scientific notation suffixes. | Suffix | Multiplier | | ------ | ---------- | | n | 10^-9 | | m | 10^-3 | | k | 10^3 | | M | 10^6 | | B,G | 10^9 | | T | 10^12 | | P | 10^15 | There's no difference between floats and integers in Squiggle. ## Booleans Booleans can be `true` or `false`. ## Strings Strings can be created with either single or double quotes. [String API](/docs/Api/String) ## Distributions Distributions are first-class citizens. Use the syntax `a to b` to create a quick lognormal distribution, or write out the whole distribution name. See these pages for more information on distributions: - [Distribution Creation](/docs/Guides/DistributionCreation) - [Distribution Functions Guide](/docs/Guides/Functions) - [Distribution API](/docs/Api/Dist) There are [3 internal representation formats for distributions](docs/Discussions/Three-Formats-Of-Distributions): [Sample Set](/docs/API/DistSampleSet), [Point Set](/docs/API/DistPointSet), and Symbolic. By default, Squiggle will use sample set distributions, which allow for correlations between parameters. Point Set and Symbolic distributions will be more accurate and fast, but do not support correlations. If you prefer this tradeoff, you can manually use them by adding a `Sym.` before the distribution name, i.e. `Sym.normal(0, 1)`. ## Lists Squiggle lists can contain items of any type, similar to lists in Python. You can access individual list elements with `[number]` notation, starting from `0`. Squiggle is an immutable language, so you cannot modify lists in-place. Instead, you can use functions such as `List.map` or `List.reduce` to create new lists. [List API](/docs/Api/List) ## Dictionaries Squiggle dictionaries work similarly to Python dictionaries or Javascript objects. Like lists, they can contain values of any type. Keys must be strings. [Dictionary API](/docs/Api/Dictionary) ## Other types Other Squiggle types include: - [Functions](/docs/Guides/Functions) - [Plots](/docs/Api/Plot) - [Scales](/docs/Api/Plot#scales) - [Domains](#parameter-annotations) --- description: The table module helps you create tables of data. --- import { SquiggleEditor } from "@quri/squiggle-components"; # Table The Table module allows you to make tables of data. ### Table.make ``` Table.make: ({data: list<'a>, columns: list<{name?:string, fn: 'a => any }>}) => table ``` Examples: You can hardcode the scales to make the xAxis consistent between rows. --- description: Functions for working with strings in Squiggle --- # String Strings support all JSON escape sequences, with addition of escaped single-quotes (for single-quoted strings) ```js a = "'\" NUL:\u0000" b = '\'" NUL:\u0000' ``` ### concat ``` concat: (string, string) => string ``` ```squiggle s1 = concat("foo", "bar") // foobar s2 = "foo" + "bar" // foobar s3 = "foo" + 3 // foo3 ``` --- description: The Plot module provides functions to create plots of distributions and functions. --- import { SquiggleEditor } from "@quri/squiggle-components"; # Plot The Plot module provides functions to create plots of distributions and functions. Raw functions and distributions are plotted with default parameters, while plot objects created by functions from this module give you more control over chart parameters and access to more complex charts. ### Plot.dists Plots one or more labeled distributions on the same plot. Distributions can be either continuous, discrete, or a single number. ``` Plot.dists: ({dists: list<{name: string, value: distribution | number}>, xScale: scale, yScale: scale, title: string, showSummary: bool}) => plot ``` Examples: ### Plot.dist Like `Plot.dists`, but plots a single distribution. ``` Plot.dist: ({dist: dist, xScale: scale, yScale: scale, title: string, showSummary: bool}) => plot ``` Examples: ### Plot.numericFn Plots a function that outputs numeric values. This works by sampling the function at a fixed number of points. The number of points is adjustable with the `points` parameter. ``` Plot.numericFn: ({fn: (number => number), xScale: scale, yScale: scale, points: number}) => plot ``` Examples: ### Plot.distFn ``` Plot.distFn: ({fn: (number => dist), xScale: scale, yScale: scale, distXScale: scale, points: number}) => plot ``` ### Plot.scatter Plots a scatterplot. Requires two sample set distributions. ``` Plot.numericFn: ({yDist: sampleSetDist, xDist: sampleSetDist, xScale: Scale, yScale: Scale}) => plot ``` ### Scales Chart axes can be scaled using the following functions. Each scale function accepts optional min and max value. Power scale accepts an extra exponent parameter. We use D3 for the tick formats. You can read about custom tick formats [here](https://github.com/d3/d3-format). ``` Scale.log: ({min: number, max: number, tickFormat: string}) => scale Scale.linear: ({min: number, max: number, tickFormat: string}) => scale Scale.symlog: ({min: number, max: number, tickFormat: string, constant: number}) => scale Scale.power: ({min: number, max: number, tickFormat: string, exponent: number}) => scale ``` **Scale.log** **Scale.linear** **Scale.symlog** Symmetric log scale. Useful for plotting data that includes zero or negative values. The function accepts an additional `constant` parameter, used as follows: `Scale.symlog({constant: 0.1})`. This parameter allows you to allocate more pixel space to data with lower or higher absolute values. By adjusting this constant, you effectively control the scale's focus, shifting it between smaller and larger values. For more detailed information on this parameter, refer to the [D3 Documentation](https://d3js.org/d3-scale/symlog). The default value for `constant` is `1`. **Scale.power** Power scale. Accepts an extra `exponent` parameter, like, `Scale.power({exponent: 2, min: 0, max: 100})`. The default value for `exponent` is `0.1`. --- description: Squiggle numbers are Javascript floats. --- # Number Squiggle `numbers` are Javascript floats. Many of the functions below work on lists or pairs of numbers. ### ceil ``` ceil: (number) => number ``` ### floor ``` floor: (number) => number ``` ### abs ``` abs: (number) => number ``` ### round ``` round: (number) => number ``` ## Statistics ### max ``` max: (list) => number ``` ### min ``` min: (list) => number ``` ### mean ``` mean: (list) => number ``` ### geometric mean ``` geomean: (list) => number ``` ### stdev ``` stdev: (list) => number ``` ### variance ``` variance: (list) => number ``` ## Algebra ### unaryMinus ``` unaryMinus: (number) => number ``` ### equal ``` equal: (number, number) => boolean ``` ### add ``` add: (number, number) => number ``` ### sum ``` sum: (list) => number ``` ### cumulative sum ``` cumsum: (list) => list ``` ### multiply ``` multiply: (number, number) => number ``` ### product ``` product: (list) => number ``` ### cumulative product ``` cumprod: (list) => list ``` ### diff ``` diff: (list) => list ``` ### subtract ``` subtract: (number, number) => number ``` ### divide ``` divide: (number, number) => number ``` ### pow ``` pow: (number, number) => number ``` ### exp ``` exp: (number) => number ``` ### log ``` log: (number) => number ``` --- description: Math functions and constants --- # Math ### E ``` Math.e: ``` Euler's number; ≈ 2.718281828459045 ### LN2 ``` Math.ln2: ``` Natural logarithm of 2; ≈ 0.6931471805599453 ### LN10 ``` Math.ln10: ``` Natural logarithm of 10; ≈ 2.302585092994046 ### LOG2E ``` Math.log2e: ``` Base 2 logarithm of E; ≈ 1.4426950408889634Base 2 logarithm of E; ≈ 1.4426950408889634 ### LOG10E ``` Math.log10e: ``` Base 10 logarithm of E; ≈ 0.4342944819032518 ### PI ``` Math.pi: ``` Pi - ratio of the circumference to the diameter of a circle; ≈ 3.141592653589793 ### SQRT1_2 ``` Math.sqrt1_2: ``` Square root of 1/2; ≈ 0.7071067811865476 ### SQRT2 ``` Math.sqrt2: ``` Square root of 2; ≈ 1.4142135623730951 ### PHI ``` Math.phi: ``` Phi is the golden ratio. 1.618033988749895 ### TAU ``` Math.tau: ``` Tau is the ratio constant of a circle's circumference to radius, equal to 2 \* pi. 6.283185307179586 ### Math functions ``` Math.sqrt(x) // Square root Math.sin(x) // Sine Math.cos(x) // Cosine Math.tan(x) // Tangent Math.asin(x) // Arcsine (inverse sine) Math.acos(x) // Arccosine (invers cosine) Math.atan(x) // Acrtangent (inverse tangent) ``` --- description: Squiggle lists are a lot like Python lists or Ruby arrays. They accept all types. --- # List Squiggle lists are a lot like Python lists or Ruby arrays. They accept all types. ```squiggle myList = [3, normal(5, 2), "random"] ``` ### make ``` List.make: (number, 'a) => list<'a> ``` Returns an array of size `n` filled with the value. ```squiggle List.make(4, 1) // creates the list [1, 1, 1, 1] ``` See [Rescript implementation](https://rescript-lang.org/docs/manual/latest/api/belt/array#make) ### toString ``` toString: (list<'a>) => string ``` ### length ``` length: (list<'a>) => number ``` ### upTo ``` List.upTo: (low:number, high:number) => list ``` ```squiggle List.upTo(0, 5) // creates the list [0, 1, 2, 3, 4, 5] ``` Syntax taken from [Ruby](https://apidock.com/ruby/v2_5_5/Integer/upto). ### first ``` first: (list<'a>) => 'a ``` ### last ``` last: (list<'a>) => 'a ``` ### concat ``` List.concat: (list<'a>, list<'a>) => list<'a> ``` ### append ``` List.append: (list<'a>, <'a>) => list<'a> ``` ### reverse ``` reverse: (list<'a>) => list<'a> ``` ### uniq Filters the list for unique elements. Now only works on Strings, Numbers, and Booleans. ``` List.uniq: (list<'a>) => list<'a> ``` ```squiggle List.uniq(["foobar", "foobar", 1, 1, 2]) // ["foobar", 1, 2] ``` ### map ``` map: (list<'a>, a => b) => list<'b> map: (list<'a>, (a, number) => b) => list<'b> ``` ```squiggle map(["foo", "bar"], {|s| s + "!"}) map(["foo", "bar"], {|s, i| {word: s, index: i}}) ``` See [Rescript implementation](https://rescript-lang.org/docs/manual/latest/api/belt/array#map). ### filter ``` filter: (list<'a>, 'a => bool) => list<'a> ``` See [Rescript implementation of keep](https://rescript-lang.org/docs/manual/latest/api/belt/array#keep), which is functionally equivalent. ### reduce ``` reduce: (list<'b>, 'a, ('a, 'b) => 'a) => 'a ``` `reduce(arr, init, f)` Applies `f` to each element of `arr`. The function `f` has two paramaters, an accumulator and the next value from the array. ```squiggle reduce([2, 3, 4], 1, {|acc, value| acc + value}) == 10 ``` See [Rescript implementation](https://rescript-lang.org/docs/manual/latest/api/belt/array#reduce). ### reduce reverse ``` reduceReverse: (list<'b>, 'a, ('a, 'b) => 'a) => 'a ``` Works like `reduce`, but the function is applied to each item from the last back to the first. See [Rescript implementation](https://rescript-lang.org/docs/manual/latest/api/belt/array#reducereverse). ### reduceWhile ``` List.reduceWhile: (list<'b>, 'a, ('a, 'b) => 'a, ('a) => bool) => 'a ``` Works like `reduce`, but stops when the accumulator doesn't satisfy the condition, and returns the last accumulator that satisfies the condition (or the initial value if even the initial value doesn't satisfy the condition). This is useful for simulating processes that need to stop based on the process state. Example: ```js /** Adds first two elements, returns `11`. */ List.reduceWhile([5, 6, 7], 0, {|acc, curr| acc + curr}, {|acc| acc < 15}) /** Adds first two elements, returns `{ x: 11 }`. */ List.reduceWhile([5, 6, 7], { x: 0 }, {|acc, curr| { x: acc.x + curr }}, {|acc| acc.x < 15}) ``` ### join ``` List.join: (list, string) => string ``` ```squiggle List.join(["foo", "bar", "char"], "--") // "foo--bar--char" ``` ### flatten ``` flatten: (list) => list ``` ```squiggle List.flatten([ [1, 2], [3, 4], ]) // [1,2,3,4] ``` --- description: Sample set distributions are one of the three distribution formats. Internally, they are stored as a list of numbers. --- # Sample Set Distribution Sample set distributions are one of the three distribution formats. Internally, they are stored as a list of numbers. It's useful to distinguish point set distributions from arbitrary lists of numbers to make it clear which functions are applicable. Monte Carlo calculations typically result in sample set distributions. All regular distribution function work on sample set distributions. In addition, there are several functions that only work on sample set distributions. ### fromDist ``` SampleSet.fromDist: (distribution) => sampleSet ``` ### fromList ``` SampleSet.fromList: (list) => sampleSet ``` ### fromFn ``` SampleSet.fromFn: ((float) => number) => sampleSet ``` ### toList ``` SampleSet.toList: (sampleSet) => list ``` Gets the internal samples of a sampleSet distribution. This is separate from the sampleN() function, which would shuffle the samples. toList() maintains order and length. **Examples** ``` toList(SampleSet.fromDist(normal(5,2))) ``` ### map ``` SampleSet.map: (sampleSet, (number => number)) => sampleSet ``` ### map2 ``` SampleSet.map2: (sampleSet, sampleSet, ((number, number) => number)) => sampleSet ``` ### map3 ``` SampleSet.map3: (sampleSet, sampleSet, sampleSet, ((number, number, number) => number)) => sampleSet ``` ### mapN ``` SampleSet.mapN: (list, (list => number)) => sampleSet ``` --- description: Point set distributions are one of the three distribution formats. They are stored as a list of x-y coordinates representing both discrete and continuous distributions. --- # Point Set Distribution Point set distributions are one of the three distribution formats. They are stored as a list of x-y coordinates representing both discrete and continuous distributions. One complication is that it's possible to represent invalid probability distributions in the point set format. For example, you can represent shapes with negative values, or shapes that are not normalized. ### fromDist Converts the distribution in question into a point set distribution. If the distribution is symbolic, then it does this by taking the quantiles. If the distribution is a sample set, then it uses a version of kernel density estimation to approximate the point set format. One complication of this latter process is that if there is a high proportion of overlapping samples (samples that are exactly the same as each other), it will convert these samples into discrete point masses. Eventually we'd like to add further methods to help adjust this process. ``` PointSet.fromDist: (distribution) => pointSetDist ``` ### makeContinuous Converts a set of x-y coordinates directly into a continuous distribution. ``` PointSet.makeContinuous: (list<{x: number, y: number}>) => pointSetDist ``` ```squiggle PointSet.makeContinuous([ { x: 0, y: 0.1 }, { x: 1, y: 0.2 }, { x: 2, y: 0.15 }, { x: 3, y: 0.1 }, ]) ``` ### makeDiscrete ``` PointSet.makeDiscrete: (list<{x: number, y: number}>) => pointSetDist ``` ```squiggle PointSet.makeDiscrete([ { x: 0, y: 0.1 }, { x: 1, y: 0.2 }, { x: 2, y: 0.15 }, { x: 3, y: 0.1 }, ]) ``` ### mapY ``` PointSet.mapY: (pointSetDist, (number => number)) => pointSetDist ``` ```squiggle normal(5,3) -> PointSet.fromDist -> PointSet.mapY({|x| x ^ 2}) -> normalize ``` --- description: Distributions are the flagship data type in Squiggle. The distribution type is a generic data type that contains one of three different formats of distributions. --- import { Callout } from "nextra/components"; # Distribution Distributions are the flagship data type in Squiggle. The distribution type is a generic data type that contains one of three different formats of distributions. These subtypes are [point set](/docs/Api/DistPointSet), [sample set](/docs/Api/DistSampleSet), and symbolic. The first two of these have a few custom functions that only work on them. You can read more about the differences between these formats [here](/docs/Discussions/Three-Formats-Of-Distributions). Several functions below only can work on particular distribution formats. For example, scoring and pointwise math requires the point set format. When this happens, the types are automatically converted to the correct format. These conversions are lossy. Distributions are created as [sample sets](/Docs/Api/DistSampleSet) by default. To create a symbolic distribution, use `Sym.` namespace: `Sym.normal`, `Sym.beta` and so on. ## Distribution Creation These are functions for creating primative distributions. Many of these could optionally take in distributions as inputs. In these cases, Monte Carlo Sampling will be used to generate the greater distribution. This can be used for simple hierarchical models. See a longer tutorial on creating distributions [here](/docs/Guides/DistributionCreation). ### normal ``` normal: (distribution|number, distribution|number) => distribution normal: (dict<{p5: distribution|number, p95: distribution|number}>) => distribution normal: (dict<{p10: distribution|number, p90: distribution|number}>) => distribution normal: (dict<{p25: distribution|number, p75: distribution|number}>) => distribution normal: (dict<{mean: distribution|number, stdev: distribution|number}>) => distribution ``` **Examples** ```squiggle normal(5, 1) normal({ p5: 4, p95: 10 }) normal({ p10: 5, p95: 9 }) normal({ p25: 5, p75: 9 }) normal({ mean: 5, stdev: 2 }) normal(5 to 10, normal(3, 2)) normal({ mean: uniform(5, 9), stdev: 3 }) ``` ### lognormal ``` lognormal: (distribution|number, distribution|number) => distribution lognormal: (dict<{p5: distribution|number, p95: distribution|number}>) => distribution lognormal: (dict<{p10: distribution|number, p90: distribution|number}>) => distribution lognormal: (dict<{p25: distribution|number, p75: distribution|number}>) => distribution lognormal: (dict<{mean: distribution|number, stdev: distribution|number}>) => distribution ``` **Examples** ```squiggle lognormal(0.5, 0.8) lognormal({ p5: 4, p95: 10 }) lognormal({ p10: 5, p95: 9 }) lognormal({ p25: 5, p75: 9 }) lognormal({ mean: 5, stdev: 2 }) ``` ### to The `to` function is an easy way to generate simple distributions using predicted _5th_ and _95th_ percentiles. `To` is simply an alias for `lognormal({p5:low, p95:high})`. It does not accept values of 0 or less, as those are not valid for lognormal distributions. ``` to: (distribution|number, distribution|number) => distribution ``` **Examples** ```squiggle 5 to 10 to(5,10) ``` ### uniform ``` uniform: (distribution|number, distribution|number) => distribution ``` **Examples** ```squiggle uniform(10, 12) ``` ### beta ``` beta: (distribution|number, distribution|number) => distribution beta: (dict<{mean: distribution|number, stdev: distribution|number}>) => distribution ``` **Examples** ```squiggle beta(20, 25) beta({ mean: 0.39, stdev: 0.1 }) ``` ### cauchy ``` cauchy: (distribution|number, distribution|number) => distribution ``` **Examples** ```squiggle cauchy(5, 1) ``` ### gamma ``` gamma: (distribution|number, distribution|number) => distribution ``` **Examples** ```squiggle gamma(5, 1) ``` ### logistic ``` logistic: (distribution|number, distribution|number) => distribution ``` **Examples** ```squiggle gamma(5, 1) ``` ### exponential ``` exponential: (distribution|number) => distribution ``` **Examples** ```squiggle exponential(2) ``` ### bernoulli ``` bernoulli: (distribution|number) => distribution ``` **Examples** ```squiggle bernoulli(0.5) ``` ### triangular ``` triangular: (number, number, number) => distribution; ``` **Examples** ```squiggle triangular(5, 10, 20) ``` ### mixture ``` mixture: (...distributionLike, weights?:list) => distribution mixture: (list, weights?:list) => distribution ``` **Examples** ```squiggle mixture(normal(5, 1), normal(10, 1), 8) mx(normal(5, 1), normal(10, 1), [0.3, 0.7]) mx([normal(5, 1), normal(10, 1)], [0.3, 0.7]) ``` ## Functions ### sample One random sample from the distribution ``` sample: (distribution) => number ``` **Examples** ```squiggle sample(normal(5, 2)) ``` ### sampleN N random samples from the distribution ``` sampleN: (distribution, number) => list ``` **Examples** ```squiggle sampleN(normal(5, 2), 100) ``` ### mean The distribution mean ``` mean: (distribution) => number ``` **Examples** ```squiggle mean(normal(5, 2)) ``` ### stdev Standard deviation. Only works now on sample set distributions (so converts other distributions into sample set in order to calculate.) ``` stdev: (distribution) => number ``` ### variance Variance. Similar to stdev, only works now on sample set distributions. ``` variance: (distribution) => number ``` ### cdf ``` cdf: (distribution, number) => number ``` **Examples** ```squiggle cdf(normal(5, 2), 3) ``` ### pdf ``` pdf: (distribution, number) => number ``` **Examples** ```squiggle pdf(normal(5, 2), 3) ``` ### quantile ``` quantile: (distribution, number) => number ``` **Examples** ```squiggle quantile(normal(5, 2), 0.5) ``` ### truncate Truncates both the left side and the right side of a distribution. ``` truncate: (distribution, left: number, right: number) => distribution ``` **Implementation Details** Sample set distributions are truncated by filtering samples, but point set distributions are truncated using direct geometric manipulation. Uniform distributions are truncated symbolically. Symbolic but non-uniform distributions get converted to Point Set distributions. ### truncateLeft Truncates the left side of a distribution. ``` truncateLeft: (distribution, left: number) => distribution ``` **Examples** ```squiggle truncateLeft(normal(5, 2), 3) ``` ### truncateRight Truncates the right side of a distribution. ``` truncateRight: (distribution, right: number) => distribution ``` **Examples** ```squiggle truncateRight(normal(5, 2), 6) ``` ### klDivergence [Kullback–Leibler divergence](https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence) between two distributions. Note that this can be very brittle. If the second distribution has probability mass at areas where the first doesn't, then the result will be infinite. Due to numeric approximations, some probability mass in point set distributions is rounded to zero, leading to infinite results with klDivergence. ``` klDivergence: (distribution, distribution) => number ``` **Examples** ```squiggle Dist.klDivergence(normal(5, 2), normal(5, 4)) // returns 0.57 ``` ### logScore A log loss score. Often that often acts as a [scoring rule](https://en.wikipedia.org/wiki/Scoring_rule). Useful when evaluating the accuracy of a forecast. Note that it is fairly slow. ``` Dist.logScore: ({estimate: distribution, ?prior: distribution, answer: distribution|number}) => number ``` **Examples** ```squiggle Dist.logScore({ estimate: normal(5, 2), answer: normal(4.5, 1.2), prior: normal(6, 4), }) // returns -0.597.57 ``` ## Display ### toString ``` toString: (distribution) => string ``` **Examples** ```squiggle toString(normal(5, 2)) ``` ### sparkline Produce a sparkline of length n. For example, `▁▁▁▁▁▂▄▆▇██▇▆▄▂▁▁▁▁▁`. These can be useful for testing or quick text visualizations. ``` sparkline: (distribution, n = 20) => string ``` **Examples** ```squiggle sparkline(truncateLeft(normal(5, 2), 3), 20) // produces ▁▇█████▇▅▄▃▂▂▁▁▁▁▁▁▁ ``` ## Normalization There are some situations where computation will return unnormalized distributions. This means that their cumulative sums are not equal to 1.0. Unnormalized distributions are not valid for many relevant functions; for example, klDivergence and scoring. The only functions that do not return normalized distributions are the pointwise arithmetic operations and the scalewise arithmetic operations. If you use these functions, it is recommended that you consider normalizing the resulting distributions. ### normalize Normalize a distribution. This means scaling it appropriately so that it's cumulative sum is equal to 1. This only impacts Point Set distributions, because those are the only ones that can be non-normlized. ``` normalize: (distribution) => distribution ``` **Examples** ```squiggle normalize(normal(5, 2)) ``` ### isNormalized Check of a distribution is normalized. Most distributions are typically normalized, but there are some commands that could produce non-normalized distributions. ``` isNormalized: (distribution) => bool ``` **Examples** ```squiggle isNormalized(normal(5, 2)) // returns true ``` ### integralSum **Note: If you have suggestions for better names for this, please let us know.** Get the sum of the integral of a distribution. If the distribution is normalized, this will be 1.0. This is useful for understanding unnormalized distributions. ``` integralSum: (distribution) => number ``` **Examples** ```squiggle integralSum(normal(5, 2)) ``` ## Regular Arithmetic Operations Regular arithmetic operations cover the basic mathematical operations on distributions. They work much like their equivalent operations on numbers. The infixes `+`,`-`, `*`, `/`, `^` are supported for addition, subtraction, multiplication, division, power, and unaryMinus. ```squiggle pointMass(5 + 10) == pointMass(5) + pointMass(10); ``` ### add ``` add: (distributionLike, distributionLike) => distribution ``` **Examples** ```squiggle normal(0, 1) + normal(1, 3) // returns normal(1, 3.16...) add(normal(0, 1), normal(1, 3)) // returns normal(1, 3.16...) ``` ### multiply ``` multiply: (distributionLike, distributionLike) => distribution ``` ### product ``` product: (list) => distribution ``` ### subtract ``` subtract: (distributionLike, distributionLike) => distribution ``` ### divide ``` divide: (distributionLike, distributionLike) => distribution ``` ### pow ``` pow: (distributionLike, distributionLike) => distribution ``` ### exp ``` exp: (distributionLike, distributionLike) => distribution ``` ### log ``` log: (distributionLike, distributionLike) => distribution ``` ### log10 ``` log10: (distributionLike, distributionLike) => distribution ``` ### unaryMinus ``` unaryMinus: (distribution) => distribution ``` **Examples** ```squiggle -normal(5, 2) // same as normal(-5, 2) unaryMinus(normal(5, 2)) // same as normal(-5, 2) ``` ## Pointwise Arithmetic Operations **Unnormalized Results** Pointwise arithmetic operations typically return unnormalized or completely invalid distributions. For example, the operation{" "} normal(5,2) .- uniform(10,12) results in a distribution-like object with negative probability mass. Pointwise arithmetic operations cover the standard arithmetic operations, but work in a different way than the regular operations. These operate on the y-values of the distributions instead of the x-values. A pointwise addition would add the y-values of two distributions. The infixes `.+`,`.-`, `.*`, `./`, `.^` are supported for their respective operations. The `mixture` methods works with pointwise addition. ### dotAdd ``` dotAdd: (distributionLike, distributionLike) => distribution ``` ### dotMultiply ``` dotMultiply: (distributionLike, distributionLike) => distribution ``` ### dotSubtract ``` dotSubtract: (distributionLike, distributionLike) => distribution ``` ### dotDivide ``` dotDivide: (distributionLike, distributionLike) => distribution ``` ### dotPow ``` dotPow: (distributionLike, distributionLike) => distribution ``` ### dotExp ``` dotExp: (distributionLike, distributionLike) => distribution ``` --- description: Squiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript. --- # Dictionary Squiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript. Dictionaries are ordered. Duplicates are not allowed. They are immutable, like all types in Squiggle. **Example** ```squiggle table = 10 to 30 chair = 0.01 to 0.5 valueFromOfficeItems = { keyboard: 1, headphones: "ToDo", chair, table } valueFromHomeItems = { monitor: 1, bed: 0.2 to 0.6, lights: 0.02 to 0.2, coffee: 5 to 20, chair, table } homeToItemsConversion = 0.1 to 0.4 conversionFn(i) = [i[0], i[1] * homeToItemsConversion] updatedValueFromHomeItems = valueFromHomeItems -> Dict.toList -> map(conversionFn) -> Dict.fromList allItems = Dict.merge(valueFromOfficeItems, updatedValueFromHomeItems) ``` ### toList ``` Dict.toList: (dict<'a>) => list> ``` ```squiggle Dict.toList({ foo: 3, bar: 20 }) // [["foo", 3], ["bar", 20]] ``` ### fromList ``` Dict.fromList: (list>) => dict<'a> ``` ```squiggle Dict.fromList([ ["foo", 3], ["bar", 20], ]) // {foo: 3, bar: 20} ``` ### keys ``` Dict.keys: (dict<'a>) => list ``` ```squiggle Dict.keys({ foo: 3, bar: 20 }) // ["foo", "bar"] ``` ### values ``` Dict.values: (dict<'a>) => list<'a> ``` ```squiggle Dict.values({ foo: 3, bar: 20 }) // [3, 20] ``` ### merge ``` Dict.merge: (dict<'a>, dict<'b>) => dict<'a|b> ``` ```squiggle first = { a: 1, b: 2 }; snd = { b: 3, c: 5 }; Dict.merge(first, snd); // {a: 1, b: 3, c: 5} ``` ### mergeMany ``` Dict.mergeMany: (list>) => dict<'a> ``` ```squiggle first = { a: 1, b: 2 } snd = { b: 3, c: 5 } Dict.mergeMany([first, snd]) // {a: 1, b: 3, c: 5} ``` ### set ``` Dict.set: (dict<'a>, string, 'a) => dict<'a> ``` Creates a new dictionary that includes the added element, while leaving the original dictionary unaltered. ### map ``` Dict.map: (dict<'a>, (`a => `a)) => dict<'a> ``` ```squiggle Dict.map({a: 1, b: 2}, {|x| x + 1}) // { a: 2, b:3 } ``` ### mapKeys ``` Dict.map: (dict<'a>, (string => string)) => dict<'a> ``` ```squiggle Dict.mapKeys({a: 1, b: 2}, {|x| x + "hi" }) // {ahi: 1, bhi: 2} ``` --- description: The Danger library contains newer experimental functions which are less stable than Squiggle as a whole --- # Danger The Danger library contains newer experimental functions which are less stable than Squiggle as a whole. Beware: their name, behavior, namespace or existence may change at any time. ### laplace ``` Danger.laplace: (number, number) => number ``` Calculates the probability implied by [Laplace's rule of succession](https://en.wikipedia.org/wiki/Rule_of_succession) ```squiggle trials = 10 successes = 1 Danger.laplace(trials, successes); // (successes + 1) / (trials + 2) = 2 / 12 = 0.1666 ``` ### factorial ``` Danger.factorial: (number) => number ``` Returns the factorial of a number ### choose ``` Danger.choose: (number, number) => number ``` `Danger.choose(n,k)` returns `factorial(n) / (factorial(n - k) *.factorial(k))`, i.e., the number of ways you can choose k items from n choices, without repetition. This function is also known as the [binomial coefficient](https://en.wikipedia.org/wiki/Binomial_coefficient). ### binomial ``` Danger.binomial: (number, number, number) => number ``` `Danger.binomial(n, k, p)` returns `choose((n, k)) * pow(p, k) * pow(1 - p, n - k)`, i.e., the probability that an event of probability p will happen exactly k times in n draws. ### integrateFunctionBetweenWithNumIntegrationPoints ``` Danger.integrateFunctionBetweenWithNumIntegrationPoints: (number => number, number, number, number) => number ``` `Danger.integrateFunctionBetweenWithNumIntegrationPoints(f, min, max, numIntegrationPoints)` integrates the function `f` between `min` and `max`, and computes `numIntegrationPoints` in between to do so. Note that the function `f` has to take in and return numbers. To integrate a function which returns distributios, use: ```squiggle auxiliaryF(x) = mean(f(x)) Danger.integrateFunctionBetweenWithNumIntegrationPoints(auxiliaryF, min, max, numIntegrationPoints) ``` ### integrateFunctionBetweenWithEpsilon ``` Danger.integrateFunctionBetweenWithEpsilon: (number => number, number, number, number) => number ``` `Danger.integrateFunctionBetweenWithEpsilon(f, min, max, epsilon)` integrates the function `f` between `min` and `max`, and uses an interval of `epsilon` between integration points when doing so. This makes its runtime less predictable than `integrateFunctionBetweenWithNumIntegrationPoints`, because runtime will not only depend on `epsilon`, but also on `min` and `max`. Same caveats as `integrateFunctionBetweenWithNumIntegrationPoints` apply. ### optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions ``` Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions: (array number>, number, number) => number ``` `Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions([f1, f2], funds, approximateIncrement)` computes the optimal allocation of $`funds` between `f1` and `f2`. For the answer given to be correct, `f1` and `f2` will have to be decreasing, i.e., if `x > y`, then `f_i(x) < f_i(y)`. Example: ```squiggle Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions( [ {|x| 20-x}, {|y| 10} ], 100, 0.01 ) ``` Note also that the array ought to have more than one function in it. --- description: Probability distributions have several subtle possible formats. Three important ones that we deal with in Squiggle are symbolic, sample set, and point set formats. --- # Three Formats of Distributions _by Ozzie Gooen_ Probability distributions have several subtle possible formats. Three important ones that we deal with in Squiggle are symbolic, sample set, and point set formats. _Symbolic_ formats are just the math equations. `Sym.normal(5,3)` is the symbolic representation of a normal distribution. Distribution constructors that don't have `Sym.` prefix, e.g. `normal(5,3)`, are stored as lists of samples. Monte Carlo techniques also return lists of samples. Let’s call this the “_Sample Set_” format. Lastly is what I’ll refer to as the _Point Set_ format. It describes the coordinates, or the shape, of the distribution. You can save these formats in JSON, for instance, like, `{xs: [1, 2, 3, 4, …], ys: [.0001, .0003, .002, …]}`. Symbolic, Sample Set, and Point Set formats all have very different advantages and disadvantages. Note that the name "Symbolic" is fairly standard, but I haven't found common names for what I'm referring to as "Sample Set" and "Point Set" formats. The formats aren't often specifically referred to for these purposes, from what I can tell. ## Symbolic Formats **TL;DR** Mathematical representations. Require analytic solutions. These are often ideal where they can be applied, but apply to very few actual functions. Typically used sparsely, except for the starting distributions (before any computation is performed). **Examples** `Sym.normal(5,2)` `pdf(Sym.normal(2,5), 1.2) + Sym.beta(5, log(2))` **How to Do Computation** To perform calculations of symbolic systems, you need to find analytical solutions. For example, there are equations to find the pdf or cdf of most distribution shapes at any point. There are also lots of simplifications that could be done in particular situations. For example, there’s an analytical solution for combining normal distributions. **Advantages** - Maximally compressed; i.e. very easy to store. - Very readable. - When symbolic operations are feasible and easy to discover, they are trivially fast and completely accurate. **Disadvantages** - It’s often either impossible or computationally infeasible to find analytical solutions to most symbolic equations. - Solving symbolic equations requires very specialized tooling that’s very rare. There are a few small symbolic solver libraries out there, but not many. Wolfram Research is the main group that seems very strong here, and their work is mostly closed source + expensive. **Converting to Point Set Formats** - Very easy. Choose X points such that you capture most of the distribution (you can set a threshold, like 99.9%). For each X point, calculate the pdf, and save as the Y points. **Converting to Sample List Formats** - Very easy. Just sample a bunch of times. The regular way is to randomly sample (This is trivial to do for all distributions with inverse-cdf functions.) If you want to get more fancy, you could provide extra samples from the tails, that would be weighted lower. Or, you could take samples in equal distances (of probability mass) along the entire distribution, then optionally shuffle it. (In the latter case, these would not be random samples, but sometimes that’s fine.) **How to Visualize** Convert to point set, then display that. (Optionally, you can also convert to samples, then display those using a histogram, but this is often worse you have both options.) **Bonus: The Metalog Distribution** The Metalog distribution seems like it can represent almost any reasonable distribution. It’s symbolic. This is great for storage, but it’s not clear if it helps with calculation. My impression is that we don’t have symbolic ways of doing most functions (addition, multiplication, etc) on metalog distributions. Also, note that it can take a fair bit of computation to fit a shape to the Metalog distribution. ## Point Set Formats **TL;DR** Lists of the x-y coordinates of the shape of a distribution. (Usually the pdf, which is more compressed than the cdf). Some key functions (like pdf, cdf) and manipulations can work on almost any point set distribution. **Alternative Names:** Grid, Mesh, Graph, Vector, Pdf, PdfCoords/PdfPoints, Discretised, Bezier, Curve See [this facebook thread](https://www.facebook.com/ozzie.gooen/posts/10165936265785363?notif_id=1644937423623638¬if_t=feedback_reaction_generic&ref=notif). **How to Do Computation** Use point set techniques. These can be fairly computationally-intensive (particularly finding integrals, which take a whole lot of adding). In the case that you want to multiply independent distributions, you can try convolution, but it’s pretty expensive. **Examples** `{xs: [1, 2, 3, 4…], ys: [.0001, .0003, .002, .04, ...]} ` `[[1, .0001], [2, .0003], [3, .002]...] ` **Advantages** - Much more compressed than Sample List formats, but much less compressed than Symbolic formats. - Many functions (pdf, cdf, percentiles, mean, integration, etc) and manipulations (truncation, scaling horizontally or vertically), are possible on essentially all point set distributions. **Disadvantages** - Most calculations are infeasible/impossible to perform using point sets formats. In these cases, you need to use sampling. - Not as accurate or fast as symbolic methods, where the symbolic methods are applicable. - The tails get cut off, which is subideal. It’s assumed that the value of the pdf outside of the bounded range is exactly 0, which is not correct. (Note: If you have ideas on how to store point set formats that don’t cut off tails, let me know) **Converting to Symbolic Formats** - Okay, if you are okay with a Metalog approximation or similar. Metaculus uses an additive combination of up to [Logistic distributions](https://www.metaculus.com/help/faq/); you could also fit this. Fitting takes a little time (it requires several attempts and some optimization), can be arbitrarily accurate. - If you want to be very fancy, you could try to fit point set distributions into normal / lognormal / etc. but this seems like a lot of work for little gain. **Converting to Sample List Formats** - Just sample a lot. The same as converting symbolic formats into samples. **How to Visualize** - It’s already in a good format for visualization, just plot it in any library. **Handling Long Tails / Optimization** - You can choose specific points to use to save computation. For example, taking extra points at the ends. **Additional Metadata** - The format mentioned above does not suggest any specific form of interpolation, or strategy of dealing with the tails. Several interpolation methods are possible; for example, linear interpolation, or stepwise interpolation. **Potential Alternatives** - [Bézier curves](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) could, in theory, be more optimal. Bézier are used for vector image programs. They represent a more complicated format than a list of x-y coordinate pairs, but come with much more flexibility. Arguably, they sit somewhere between fitting distributions to Metalog distributions, and just taking many x-y points. ## Sample Set Formats **TL;DR** Random samples. Use Monte Carlo simulation to perform calculations. This is the predominant technique using Monte Carlo methods; in these cases, most nodes are essentially represented as sample sets. [Guesstimate](https://www.getguesstimate.com/) works this way. **How to Do Computation** Use [Monte Carlo methods](https://en.wikipedia.org/wiki/Monte_Carlo_method). You could get fancy with these with a [probabilistic programming language](https://en.wikipedia.org/wiki/Probabilistic_programming), which often have highly optimized Monte Carlo tooling. Variational inference is used for very similar problems. **Examples** `[3.23848, 4.82081, 1.382833, 9.238383…]` **Advantages** - Monte Carlo methods are effectively the only ways to calculate many/most functions. - The use of Monte Carlo methods make for very easy sensitivity analysis. - [Probabilistic inference](https://machinelearningmastery.com/markov-chain-monte-carlo-for-probability/) is only possible using Monte Carlo methods. - In some cases, Monte Carlo computation functionally represents possible worlds. There’s no very clear line between Monte Carlo methods and agent based modeling simulations. - You can handle math with distributions that are correlated with each other. (I believe, but am not sure). **Disadvantages** - Monte Carlo methods can be very slow. - Requires fairly heavy tooling to make efficient. - Sampling methods are very lossy, especially for tails. **Converting to Symbolic Formats** I don’t know of a straightforward way of doing this. Convert to Sample List first, then you can convert to Metalog or similar. **Converting to Sample List Formats** [Kernel density estimation](https://en.wikipedia.org/wiki/Kernel_density_estimation) works. However, it requires a few parameters from the user, for tuning. There are functions to estimate these parameters, but this is tricky. Two forms of density estimation are shown as code [here](https://github.com/jasondavies/science.js/blob/master/src/stats/bandwidth.js). There’s some more description in the webppl documentation [here](https://webppl.readthedocs.io/en/master/distributions.html#KDE). **Handling Long Tails / Optimization** - You can weight samples differently. This allows you to save more at the tails, for more granularity there, without biasing the results. (I’m not sure how difficult this would be.) **How to Visualize** Use a histogram. | | Symbolic | Symbolic(metalog) | Numeric | Samples/MC | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | Example | normal(5,2) | metalog([(2,3)]) | [[1,2,3,4], [3,5,9,10]] | [1.38483, 0.233, 38.8383, …] | | Techniques | Analytic | Analytic | Numeric | Monte Carlo, variational inference | | Available calculations | Pdf(), cdf(), sample, inverse Add or multiply normal distributions Add lognormal distributions Select other calculations | Pdf(), cdf(), sample(), inverseCdf() | Pointwise operations Truncate Mixture Select regular operations by constants (normal(5,2) \* 3) | Normal operations, most functions. Not pointwise functions. | | Use for computation | Lossless, Very fast, Extremely limited | | Medium speed, Minor accuracy loss, Select useful, but limited functions | Slow and lossy, but very general-purpose. | | Use for storage | Tiny, Lossless, Extremely limited | (Assuming other data is fit to metalog) High information densityt | Medium information density | Low information density | --- description: How to use Squiggle in Node.js, web, VS Code and other environments --- # Integrations ## Node Packages There are two JavaScript packages currently available for Squiggle: - [`@quri/squiggle-lang`](https://www.npmjs.com/package/@quri/squiggle-lang) - [`@quri/squiggle-components`](https://www.npmjs.com/package/@quri/squiggle-components) Types are available for both packages. ## [Squiggle Language](https://www.npmjs.com/package/@quri/squiggle-lang) ![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-lang.svg) [_See `README.md` in Github_](https://github.com/quantified-uncertainty/squiggle/tree/main/packages/squiggle-lang#use-the-npm-package) ## [Squiggle Components](https://www.npmjs.com/package/@quri/squiggle-components) ![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-components.svg) [_See `README.md` in Github_](https://github.com/quantified-uncertainty/squiggle/tree/main/packages/components#usage-in-a-react-project) This documentation uses `@quri/squiggle-components` frequently. We host [a storybook](https://components.squiggle-language.com) with details and usage of each of the components made available. ## [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle) This extention allows you to run and visualize Squiggle code. ## [Observable Library](https://observablehq.com/@hazelfire/squiggle) An exportable [Observable Notebook](https://observablehq.com/@hazelfire/squiggle) of the key components that you can directly import and use in Observable notebooks. ## [NextJS starter](https://github.com/quantified-uncertainty/next-app-with-squiggle) A template for presenting estimates on a nextjs site. --- description: Link to LLM documentation --- # Use with Language Models There are not yet any language models trained on Squiggle. However, we have compiled our documentation and grammar into one document. You can use this with Anthropic's [Claude](https://www.anthropic.com/index/introducing-claude) chat interface or other interfaces with large context windows, after which you can get Squiggle help. We've found that it's able to get Squiggle code mostly correct, but often needs some manual assistance. Manual training LLMs for writing Squiggle could make for a great project. If you might be interested in doing this, let us know! ## Document with all documentation [All Squiggle Documentation, in One Document](https://github.com/quantified-uncertainty/squiggle/blob/main/packages/website/allContent.tx) ## Instructions for use with Claude 1. Copy the Squiggle documentation file listed above. 2. Go to the [Claude Chat Interface](https://console.anthropic.com/claude). Paste the file. 3. Ask a question, like, `"Write a Squiggle model to estimate the costs of taking a plane flight from SFO to London. Include Include the environmental impact, social impact, cost of ticket, cost of time, cost of jet lag."` 4. You should get an attempt at a Squiggle Model. Try it out in the Squiggle Playground. There will likely be a few errors, so try fixing these errors. --- description: Links to various Squiggle models and writeups --- # Gallery - Many models are on the [Squiggle Hub](https://squigglehub.org/) application. - Several models are listed under the [Squiggle](https://forum.effectivealtruism.org/topics/squiggle) tag on the Effective Altruism Forum. - [GiveWell's GiveDirectly cost effectiveness analysis](https://observablehq.com/@hazelfire/givewells-givedirectly-cost-effectiveness-analysis) by Sam Nolan - [A Critical Review of Open Philanthropy’s Bet On Criminal Justice Reform](https://forum.effectivealtruism.org/posts/h2N9qEbvQ6RHABcae/a-critical-review-of-open-philanthropy-s-bet-on-criminal) by Nuño Sempere - [Samotsvety Nuclear Risk Forecasts — March 2022](https://forum.effectivealtruism.org/posts/KRFXjCqqfGQAYirm5/samotsvety-nuclear-risk-forecasts-march-2022) by Nuño Sempere, Misha Yagudin, Eli Lifland - [Adjusting probabilities for the passage of time](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3/p/j8o6sgRerE3tqNWdj) by Nuño Sempere - [List of QURI Squiggle Models](https://github.com/quantified-uncertainty/squiggle-models) by Nuño Sempere, Sam Nolan, and Ozzie Gooen