diff --git a/packages/ai/files/squiggleDocs.md b/packages/ai/files/squiggleDocs.md index 49dd9e75f3..e7fd6b5d7a 100644 --- a/packages/ai/files/squiggleDocs.md +++ b/packages/ai/files/squiggleDocs.md @@ -1,8 +1,8 @@ -# Squiggle Documentation +Write Squiggle code, using the attached documentation for how it works. Squiggle is a very simple language. Don't try using language primitives/constructs you don't see below, or that aren't in our documentation. They are likely to fail. -When writing Squiggle code, it's important to avoid certain common mistakes: +When writing Squiggle code, it's important to avoid certain common mistakes. ### Syntax and Structure @@ -12,6 +12,7 @@ When writing Squiggle code, it's important to avoid certain common mistakes: 4. The last value in a block/function is returned (no "return" keyword). 5. Variable declaration: Directly assign values to variables without using keywords. For example, use `foo = 3` instead of `let foo = 3`. 6. All statements in your model, besides the last one must either be comments or variable declarations. You can't do, `4 \n 5 \n 6` Similarly, you can't do, `Calculator() ... Table()` - instead, you need to set everything but the last item to a variable. +7. There's no mod operator (%). Use `Number.mod()` instead. ### Function Definitions and Use @@ -30,7 +31,6 @@ When writing Squiggle code, it's important to avoid certain common mistakes: 1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic. 2. There aren't for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile. -3. Remember to use `Number.sum` and `Number.product`, instead of using Reduce in those cases. ### List and Dictionary Operations @@ -40,24 +40,18 @@ When writing Squiggle code, it's important to avoid certain common mistakes: ### Randomness and Distribution Handling 1. There's no random() function. Use alternatives like sample(uniform(0,1)). -2. When representing percentages, use "5%" instead of "0.05" for readability. -3. The `to` syntax only works for >0 values. "4 to 10", not "0 to 10". +2. The `to` syntax only works for >0 values. "4 to 10", not "0 to 10". ### Units and Scales 1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01). -2. If you make a table that contains a column of similar distributions, use a scale to ensure consistent min and max. -3. Scale.symlog() has support for negative values, Scale.log() doesn't. Scale.symlog() is often a better choice for this reason, though Scale.log() is better when you are sure values are above 0. -4. Do use Scale.symlog() and Scale.log() on dists/plots that might need it. Many do! ### Documentation and Comments 1. Tags like @name and @doc apply to the following variable, not the full file. 2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022. ---- - -This format provides a clear and organized view of the guidelines for writing Squiggle code. +## Examples Here's are some simple example Squiggle programs: @@ -264,9 +258,6 @@ analysis = { probabilityOfPositiveNetBenefit: results.probPositiveNetBenefit, } } - -analysis - ``` ```squiggle @@ -306,23 +297,23 @@ f(t: [Date(2020), Date(2040)]) = { ```squiggle import "hub:ozziegooen/sTest" as sTest -@name("💰 Expected Cost") -@format("($.2s") +@name("💰 Expected Cost ($)") +@format("$.2s") flightCost = normal({ mean: 600, stdev: 100 }) -@name("🥇 Expected Benefit") -@format("($.2s") +@name("🥇 Expected Benefit ($)") +@format("$.2s") benefitEstimate = normal({ mean: 1500, stdev: 300 }) -@name("📊 Net Benefit") -@format("($.2s") +@name("📊 Net Benefit ($)") +@format("$.2s") netBenefit = benefitEstimate - flightCost @name("🚦 Test Suite") @doc( "Test suite to validate various aspects of the flight cost and benefits model using sTest." ) -testSuite = sTest.describe( +tests = sTest.describe( "Flight to London Test Suite", [ // Test for reasonable flight costs @@ -336,7 +327,426 @@ testSuite = sTest.describe( ), ] ) +``` + +# Squiggle Style Guide + +## Data and Calculations + +### Estimations + +- When using the "to" format, like "3 to 10", remember that this represents the 5th and 95th percentile. This is a very large range. Be paranoid about being overconfident and too narrow in your estimates. +- One good technique, when you think there's a chance that you might be very wrong about a variable, is to use a mixture that contains a very wide distribution. For example, `mx([300 to 400, 50 to 5000], [0.9, 0.1])`, or `mx([50k to 60k, 1k to 1M], [0.95, 0.05])`. This way if you are caught by surprise, the wide distribution will still give you a reasonable outcome. +- Be wary of using the uniform or the triangular distributions. These are mainly good for physical simulations, not to represent uncertainty over many real life variables. +- If the outcome of a model is an extreme probability (<0.01 or >0.99), be suspicious of the result. It should be very rare for an intervention to have an extreme effect or have an extreme impact on the probability of an event. +- Be paranoid about the uncertainty ranges of your variables. If you are dealing with a highly speculative variable, the answer might have 2-8 orders of magnitude of uncertainty, like `100 to 100K`. If you are dealing with a variable that's fairly certain, the answer might have 2-4 sig figs of uncertainty. Be focused on being accurate and not overconfident, not on impressing people. +- Be careful with sigmoid functions. Sigmoid curves with distributions can have very little uncertainty in the middle, and very high uncertainty at the tails. If you are unsure about these values, consider using a mixture distribution. For example, this curve has very high certainty in the middle, and very high uncertainty at the tails: `adoption_rate(t) = 1 / (1 + exp(-normal(0.1, 0.08) * (t - 30)))` +- Make sure to flag any variables that are highly speculative. Use @doc() to explain that the variable is speculative and to give a sense of the uncertainty. Explain your reasoning, but also warn the reader that the variable is speculative. + +### Percentages / Probabilities + +- Use a `@format()` tag, like `.0%` to format percentages. +- If using a distribution, remember that it shouldn't go outside of 0% and 100%. You can use beta distributions or truncate() to keep values in the correct range. +- If you do use a beta distribution, keep in mind that there's no `({p5, p95})` format. You can use `beta(alpha:number, beta:number)` or `beta({mean: number, stdev: number})` to create a beta distribution. +- Write percentages as `5%` instead of `0.05`. It's more readable. + +### Domains + +- Prefer using domains to throwing errors, when trying to restrict a variable. For example, don't write, `if year < 2023 then throw("Year must be 2023 or later")`. Instead, write `f(t: [2023, 2050])`. +- Err on the side of using domains in cases where you are unsure about the bounds of a function, instead of using if/throw or other error handling methods. +- If you only want to set a min or max value, use a domain with `Number.maxValue` or `-Number.maxValue` as the other bound. +- Do not use a domain with a complete range, like `[-Number.maxValue, Number.maxValue]`. This is redundant. Instead, just leave out the domain, like `f(t)`. + +```squiggle +// Do not use this +f(t: [-Number.maxValue, Number.maxValue]) + 1 + +// Do this +f(t) = t + 1 +``` + +## Structure and Naming Conventions + +### Structure + +- Don't have more than 10 variables in scope at any one time. Feel free to use many dictionaries and blocks in order to keep things organized. For example, + +```squiggle +@name("Key Inputs") +inputs = { + @name("Age (years)") + age = 34 + + @name("Hourly Wage ($/hr)") + hourlyWage = 100 + + @name("Coffee Price ($/cup)") + coffeePrice = 1 + {age, hourlyWage, coffeePrice} +} +``` + +Note: You cannot use tags within dicts like the following: + +```squiggle +// This is not valid. Do not do this. +inputs = { + @name("Age (years)") + age: 34, + + @name("Hourly Wage ($/hr)") + hourlyWage: 100, +} +``` + +- At the end of the file, don't return anything. The last line of the file should be the @notebook tag. +- You cannot start a line with a mathematical operator. For example, you cannot start a line with a + or - sign. However, you can start a line with a pipe character, `->`. +- Prettier will be run on the file. This will change the spacing and formatting. Therefore, be conservative with formatting (long lines, no risks), and allow this to do the heavy lifting later. +- If the file is over 50 lines, break it up with large styled blocks comments with headers. For example: + +```squiggle +// ===== Inputs ===== + +// ... + +// ===== Calculations ===== +``` + +### Naming Conventions + +- Use camelCase for variable names. +- All variable names must start with a lowercase letter. +- In functions, input parameters that aren't obvious should have semantic names. For example, instead of `nb` use `net_benefit`. + +### Dictionaries + +- In dictionaries, if a key name is the same as a variable name, use the variable name directly. For example, instead of {value: value}, just use {value}. If there's only one key, you can type it with a comma, like this: {value,}. + +### Unit Annotation + +- You can add unit descriptions to `@name()`, `@doc()` tags, and add them to comments. +- In addition to regular units (like "population"), add other key variables; like the date or the type of variable. For example, use "Number of Humans (Population, 2023)" instead of just "Number of Humans". It's important to be precise and detailed when annotating variables. +- Squiggle does support units directly, using the syntax `foo :: unit`. However, this is not recommended to use, because this is still a beta feature. +- Show units in parentheses after the variable name, when the variable name is not obvious. For example, use "Age (years)" instead of just "Age". In comments, use the "(units)" format. + Examples: + +```squiggle +@name("Number of Humans (2023)") +numberOfHumans = 7.8B + +@name("Net Benefit ($)") +netBenefit = 100M + +@name("Temperature (°C)") +temperature = 22 +@name("Piano Tuners in New York City (2023)") +tuners = { + pianosPerTuner = 100 to 1k // (pianos per tuner) + pianosInNYC = 1k to 50k // (pianos) + pianosInNYC / pianosPerTuner +} +``` + +- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. + +```squiggle +@name("Distance to Mars (km)") +distanceMars = 225e6 + +@name("Distance to Venus (km)") +distanceVenus = 170e6 +``` + +### Numbers + +- Use abbreviations, when simple, for numbers outside the range of 10^4 to 10^3. For example, use "10k" instead of "10000". +- For numbers outside the range of 10^10 or so, use scientific notation. For example, "1e10". +- Don't use small numbers to represent large numbers. For example, don't use '5' to represent 5 million. + +Don't use the code: + +```squiggle +@name("US Population (millions)") +usPopulation = 331.9 +``` + +Instead, use: + +```squiggle +@name("US Population") +usPopulation = 331.9M +``` + +More examples: + +```squiggle +// Correct representations +worldPopulation = 7.8B +annualBudget = 1.2T +distanceToSun = 149.6e6 // 149.6 million kilometers + +// Incorrect representations (avoid these) +worldPopulation = 7800 // Unclear if it's 7800 or 7.8 billion +annualBudget = 1200 // Unclear if it's 1200 or 1.2 trillion +``` + +- There's no need to use @format on regular numbers. The default formatting is fairly sophistated. +- Remember to use `Number.sum` and `Number.product`, instead of using Reduce in those cases. + +### Lists of Structured Data + +- When you want to store complex data as code, use lists of dictionaries, instead of using lists of lists. This makes things clearer. For example, use: + +```squiggle +[ + {year: 2023, value: 1}, + {year: 2024, value: 2}, +] +instead of: +[ + [2023, 1], + [2024, 2], +] +``` + +You can use lists instead when you have a very long list of items (20+), very few keys, and/or are generating data using functions. + +- Tables are a great way to display structured data. +- You can use the '@showAs' tag to display a table if the table can show all the data. If this takes a lot of formatting work, you can move that to a helper function. Note that helper functions must be placed before the '@showAs' tag. The `ozziegooen/helpers` library has a `dictsToTable` function that can help convert lists of dictionaries into tables. + +For example: + +```squiggle +@hide +strategiesTable(data) = Table.make( + data, + { + columns: [ + { name: "name", fn: {|f| f.n} }, + { name: "costs", fn: {|f| f.c} }, + { name: "benefits", fn: {|f| f.b} }, + ], + } +) + +@name("AI Safety Strategies") +@doc("List of 10 AI safety strategies with their costs and benefits") +@showAs(strategiesTable) +strategies = [ + { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M }, + { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M }, + { n: "Governance", c: 500k to 3M, b: 2M to 15M }, + ... +] +``` + +## Tags and Annotations + +### @name, @doc, @hide, @showAs + +- Use `@name` for simple descriptions and shortened units. Use `@doc` for further details (especially for detailing types, units, and key assumptions), when necessary. It's fine to use both @name and @doc on the same variable - but if so, don't repeat the name in the doc; instead use the doc() for additional information only. +- In `@name`, add units wherever it might be confusing, like "@name("Ball Speed (m/s)"). If the units are complex or still not obvious, add more detail in the @doc(). +- For complex and important functions, use `@name` to name the function, and `@doc` to describe the arguments and return values. @doc should represent a docstring for the function. For example: + +``` +@doc("Adds a number and a distribution. +\`\`\`squiggle +add(number, distribution) -> distribution +\`\`\`") +``` + +- Variables that are small function helpers, and that won't be interesting or useful to view the output of, should get a `@hide` tag. Key inputs and outputs should not have this tag. +- Use `@showAs` to format large lists, as tables and to show plots for dists and functions where appropriate. + +### `@format()` + +- Use `@format()` for numbers, distributions, and dates that could use obvious formatting. +- The `@format()` tag is not usable with dictionaries, functions, or lists. It is usable with variable assignments. Examples: + +```squiggle +netBenefit(costs, benefits) = benefits - costs // not valid for @format() +netBenefit = benefits - costs // valid for @format() +``` + +- This mainly makes sense for dollar amounts, percentages, and dates. ".0%" is a decent format for percentages, and "$,.0f" can be used for dollars. +- Choose the number of decimal places based on the stdev of the distribution or size of the number. +- Do not use "()" instead of "-" for negative numbers. So, do not use "($,.0f" for negative numbers, use "$,.0f" instead. + +## Limitations + +- There is no bignum type. There are floating point errors at high numbers (1e50 and above) and very small numbers (1e-10 and below). If you need to work with these, use logarithms if possible. + +## Comments + +- Add a short 1-2 line comment on the top of the file, summarizing the model. +- Add comments throughout the code that explain your reasoning and describe your uncertainties. Give special attention to probabilities and probability distributions that are particularly important and/or uncertain. Flag your uncertainties. +- Use comments next to variables to explain what units the variable is in, if this is not incredibly obvious. The units should be wrapped in parentheses. +- There shouldn't be any comments about specific changes made during editing. +- Do not use comments to explain things that are already obvious from the code. + +## Visualizations + +### Tables + +- Tables are a good way of displaying structured data. They can take a bit of formatting work. +- Tables are best when there are fewer than 30 rows and/or fewer than 4 columns. +- The table visualization is fairly simple. It doesn't support sorting, filtering, or other complex interactions. You might want to sort or filter the data before putting it in a table. + +### Notebooks + +- Use the @notebook tag for long descriptions intersperced with variables. This must be a list with strings and variables alternating. +- If you want to display variables within paragraphs, generally render dictionaries as items within the notebook list. For example: + +```squiggle +@notebook +@startOpen +summary = [ +"This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", +{ + optimalCups, + result.netBenefit, +}, +] +``` + +This format will use the variable tags to display the variables, and it's simple to use without making errors. If you want to display a variable that's already a dictionary, you don't need to do anything special. + +- String concatenation (+) is allowed, but be hesitant to do this with non-string variables. Most non-string variables don't display well in the default string representation. If you want to display a variable, consider using a custom function or formatter to convert it to a string first. Note that tags are shown in the default string representation, so you should remove them (`Tag.clear(variable)`) before displaying. +- Separate items in the list will be displayed with blank lines between them. This will break many kinds of formatting, like lists. Only do this in order to display full variables that you want to show. +- Use markdown formatting for headers, lists, and other structural elements. +- Use bold text to highlight key outputs. Like, "The optimal number of coffee cups per day is **" + Tag.clear(optimal_cups) + "**". + +Example: (For a model with 300 lines) + +```squiggle +@notebook +@startOpen +summary = [ + "## Summary + This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", + {inputs, final_answer}, + "## Major Assumptions & Uncertainties + - The model places a very high value on productivity. If you think that productivity is undervalued, coffee consumption may be underrated. + - The model only includes 3 main factors: productivity, cost, and health. It does not take into account other factors, like addiction, which is a major factor in coffee consumption. + - The model does not take into account the quality of sleep, which is critical. + " + "## Outputs + The optimal number of coffee cups per day: **" + Tag.clear(optimal_cups) + "** + The net benefit at optimal consumption: **" + result.net_benefit + "**", + "## Key Findings + - Moderate amounts of coffee consumption seem surprisingly beneficial. + - Productivity boost from coffee shows steeply diminishing returns as consumption increases, as would be expected. + - The financial cost of coffee is the critical factor in determining optimal consumption. + ## Detailed Analysis + The model incorporates several key factors: + 1. Productivity boost: Modeled with diminishing returns as coffee consumption increases. + 2. Health impact: Considers both potential benefits and risks of coffee consumption. + 3. Financial cost: Accounts for the direct cost of purchasing coffee. + 4. Monetary values: Includes estimates for the value of time (hourly wage) and health (QALY value). + + The optimal consumption level is determined by maximizing the net benefit, which is the sum of monetized productivity and health benefits minus the financial cost. + + It's important to note that this model is based on general estimates and may not apply to all individuals. Factors such as personal health conditions, caffeine sensitivity, and lifestyle choices could significantly alter the optimal consumption for a specific person. + " +] +``` + +## Plots + +- Plots are a good way of displaying the output of a model. +- Use Scale.symlog() and Scale.log() whenever you think the data is highly skewed. This is very common with distributions. +- Use Scale.symlog() instead of Scale.log() when you are unsure if the data is above or below 0. Scale.log() fails on negative values. +- Function plots use plots equally spaced on the x-axis. This means they can fail if only integers are accepted. In these cases, it can be safer just not to use the plot, or to use a scatter plot. +- When plotting 2-8 distributions over the same x-axis, it's a good idea to use Plot.dists(). For example, if you want to compare 5 different costs of a treatment, or 3 different adoption rates of a technology, this can be a good way to display the data. +- When plotting distributions in tables or if you want to display multiple distributions under each other, and you don't want to use Plot.dists, it's a good idea to have them all use the same x-axis scale, with custom min and max values. This is a good way to make sure that the x-axis scale is consistent across all distributions. + +Here's an example of how to display multiple distributions over the same x-axis, with a custom x-axis range: + +```squiggle +strategies = [ + { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M }, + { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M }, + ... +] + +rangeOfDists(dists) = { + min: Number.min(List.map(dists, {|d| Dist.quantile(d, 0.05)})), + max: Number.max(List.map(dists, {|d| Dist.quantile(d, 0.95)})), +} + +plotOfResults(fn) = { + |r| + range = List.map(strategies, fn) -> rangeOfDists + Plot.dist(fn(r), { xScale: Scale.linear(range) }) +} + +table = Table.make( + strategies, + { + columns: [ + { name: "Strategy", fn: {|r| r.name} }, + { name: "Cost", fn: plotOfResults({|r| r.c}) }, + { name: "Benefit", fn: plotOfResults({|r| r.b}) }, + ], + } +) +``` + +## Tests + +- Use `sTest` to test squiggle code. +- Test all functions that you are unsure about. Be paranoid. +- Use one describe block, with the variable name 'tests'. This should have several tests with in it, each with one expect statement. +- Use @startClosed tags on variables that are test results. Do not use @hide tags. +- Do not test if function domains return errors when called with invalid inputs. The domains should be trusted. +- If you set variables to sTest values, @hide them. They are not useful in the final output. +- Do not test obvious things, like the number of items in a list that's hardcoded. +- Feel free to use helper functions to avoid repeating code. +- The expect.toThrowAnyError() test is useful for easily sanity-checking that a function is working with different inputs. + +Example: + +```squiggle +@hide +describe = sTest.describe + +@hide +test = sTest.test + +tests = describe( + "Coffee Consumption Model Tests", + [ + // ...tests + ] +) +``` + +## Summary Notebook + +- For models over 5 lines long, you might want to include a summary notebook at the end of the file using the @notebook tag. +- Aim for a summary length of approximately (N^0.6) \* 1.2 lines, where N is the number of lines in the model. +- Use the following structure: + 1. Model description + 2. Major assumptions & uncertainties (if over 100 lines long) + 3. Outputs (including relevant Squiggle variables) + 4. Key findings (flag if anything surprised you, or if the results are counterintuitive) + 5. Detailed analysis (if over 300 lines long) + 6. Important notes or caveats (if over 100 lines long) +- The summary notebook should be the last thing in the file. It should be a variable called `summary`. +- Draw attention to anything that surprised you, or that you think is important. Also, flag major assumptions and uncertainties. + +Example: (For a model with 300 lines) + +```squiggle +@notebook +@startOpen +summary = [ + "## Summary + This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", + {inputs, finalAnswer}, + ... + ] ``` # Language Features @@ -390,6 +800,48 @@ argPlusX(5) 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. +## Unit Type Annotations + +Variable declarations may optionally be annotated with _unit types_, such as `kilograms` or `dollars`. Unit types are declared with `::`, for example: + +```squiggle +distance :: meters = 100 +``` + +A unit type can be any identifier, and you don't have to define a unit type before you use it. + +You can also create composite unit types using `*` (multiplication), `/` (division), and '^' (exponentiation). For example: + +```squiggle +raceDistance :: m = 100 +usainBoltTime :: s = 9.58 +usainBoltSpeed :: m/s = raceDistance / usainBoltTime +``` + +You can use any number of `*` and `/` operators in a unit type, but you cannot use parentheses. Unit type operators follow standard order of operations: `*` and `/` are left-associative and have the same precedence, and `^` has higher precedence. + +The following unit types are all equivalent: `kg*m/s^2`, `kg*m/s/s`, `m/s*kg/s`, `m/s^2*kg`, `kg*m^2/m/s/s`. + +For unitless types, you may use a number in the unit type annotation (by convention you should use the number `1`): + +```squiggle +springConstant :: 1 = 10 +inverseTime :: 1/s = 20 +``` + +If you use unit type annotations, Squiggle will enforce that variables must have consistent unit types. + +If a variable does not have a unit type annotation, Squiggle will attempt to infer its unit type. If the unit type can't be inferred, the variable is treated as any type. + +Inline unit type annotations are not currently supported (for example, `x = (y :: meters)`). + +Operators and functions obey the following semantics: + +- Multiplying or dividing two unit-typed variables multiplies or divides their unit types (respectively). +- Raising a unit-typed variable to a power produces a result of any unit type (i.e., the result is not type-checked). +- Most binary operators, including `+`, `-`, and comparison operators (`==`, `>=`, etc.), require that both arguments have the same unit type. +- Built-in functions can take any unit type and return any unit type. + ## Blocks Blocks are special expressions in Squiggle that can contain any number of local definitions and end with an expression. @@ -492,7 +944,7 @@ symbolicDist = Sym.normal(5, 2) # Functions -## Basic syntax +## Basic Syntax ```squiggle myMultiply(t) = normal(t^2, t^1.2+.01) @@ -541,6 +993,37 @@ You can manually set the range in the following ways: - Through the chart's settings in the UI (look for a gear icon next to the variable name) - With parameter annotations (explained below) +## Unit Types + +Like with [variables](/docs/Guides/LanguageFeatures#unit-type-annotations), you can declare unit types for function parameters: + +```squiggle +f(x :: unit) = x +``` + +You can also declare the unit type of the function's return value: + +```squiggle +convertMass(x :: lbs) :: kg = x * 2.2 +``` + +If you pass a unit-typed variable to a function with no unit-type annotations, Squiggle will attempt to infer the unit type of the return value: + +```squiggle +id(x) = x +a :: m/s = 10 +b = id(a) // Squiggle infers that b has type m/s +``` + +Unit type checking only works for statically defined functions. In the example code below, `h` cannot be unit-type checked. + +```squiggle +f(x) = x +g(x) = x +condition = (1 == 2) +h = (condition ? f : g) +``` + ## Parameter Annotations Function parameters can be annotated with _domains_ to specify the range of valid input values. @@ -571,6 +1054,327 @@ myMultiply.parameters[0] Domains and parameter names can be accessed by the `fn.parameters` property. +# 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. + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 + dist2 +``` + +### 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. + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 - dist2 +``` + +### 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. + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 * dist2 +``` + +We also provide concatenation of two distributions as a syntax sugar for `*` + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 / dist2 +``` + +### 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. + +```squiggle +(0.1 to 1) ^ beta(2, 3) +``` + +### The base `e` exponential + +```squiggle +dist = triangular(1,2,3) +exp(dist) +``` + +### Logarithms + +A projection over a stretched x-axis. + +```squiggle +dist = triangular(1,2,3) +log(dist) +``` + +```squiggle +log10(5 to 10) +``` + +Base `x` + +```squiggle +log(5 to 10, 2) +``` + +## 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**. + +```squiggle +Sym.lognormal({p5: 1, p95: 3}) .+ Sym.triangular(5,6,7) +``` + +### Pointwise multiplication + +```squiggle +Sym.lognormal({p5: 1, p95: 5}) .* Sym.uniform(1,8) +``` + +## 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 + + + +# Distribution Creation + +## Normal + +```squiggle +normal(mean: number, stdev: number) +normal({mean: number, stdev: 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. + + + + +```squiggle +normalMean = 10 +normalStdDev = 2 +logOfLognormal = log(lognormal(normalMean, normalStdDev)) +[logOfLognormal, normal(normalMean, normalStdDev)] + +``` + + + +## 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. + + + + +```squiggle +hours_the_project_will_take = 5 to 20 +chance_of_doing_anything = 0.8 +mx(hours_the_project_will_take, 0, [chance_of_doing_anything, 1 - chance_of_doing_anything]) + +``` + + + +
+ 🔒 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. +```squiggle +forecast = 3 to 30 +chance_completely_wrong = 0.05 +forecast_if_completely_wrong = normal({p5:-100, p95:200}) +mx(forecast, forecast_if_completely_wrong, [1-chance_completely_wrong, chance_completely_wrong]) +```` + +
+## 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. + +```squiggle +PointSet.makeContinuous([ + { x: 0, y: 0.1 }, + { x: 1, y: 0.2 }, + { x: 2, y: 0.15 }, + { x: 3, y: 0.1 } +]) +``` + + + **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. + +```squiggle +PointSet.makeDiscrete([ + { x: 0, y: 0.2 }, + { x: 1, y: 0.3 }, + { x: 2, y: 0.4 }, + { x: 3, y: 0.1 } +]) +``` + +### Arguments + +- `points`: An array of at least 1 coordinate. + # 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. @@ -704,11 +1508,50 @@ xs = List.upTo(0,10) ys = List.map(xs, {|x| f(x)}) ``` +# 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. + +```squiggle +dist1 = {value: normal(1,1), weight: 1} +dist2 = {value: normal(100000000000,1), weight: 1} +totalWeight = dist1.weight + dist2.weight +distMixture = mixture(dist1.value, dist2.value, [dist1.weight, dist2.weight]) +mixtureMean = mean(distMixture) +separateMeansCombined = (mean(dist1.value) * (dist1.weight) + mean(dist2.value) * (dist2.weight))/totalWeight +[mixtureMean, separateMeansCombined, distMixture] +``` + +## Means of Sample Set Distributions + +The means of sample set distributions can vary dramatically, especially as the numbers get high. + +```squiggle +symbolicDist = 5 to 50333333 +sampleSetDist = SampleSet.fromDist(symbolicDist) +[mean(symbolicDist), mean(sampleSetDist), symbolicDist, sampleSetDist] +``` + # 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. +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. @@ -745,7 +1588,7 @@ f = false ## Strings -Strings can be created with either single or double quotes. +Strings can be created with either single or double quotes. [String API](/docs/Api/String) ```squiggle @@ -777,7 +1620,7 @@ There are [3 internal representation formats for distributions](docs/Discussions 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. +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) ```squiggle @@ -788,7 +1631,7 @@ bar = myList[3][1] // "bar" ## Dictionaries -Squiggle dictionaries work similarly to Python dictionaries or Javascript objects. Like lists, they can contain values of any type. Keys must be strings. +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) ```squiggle @@ -829,7 +1672,7 @@ myFn = typeOf({|e| e}) Common.inspect: ('A, message?: String) => 'A Runs Console.log() in the [Javascript developer console](https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-developer-console) and returns the value passed in. -Common.throw: (message: String?) => any +Common.throw: (message?: String) => any Throws an error. You can use `try` to recover from this error. Common.try: (fn: () => 'A, fallbackFn: () => 'B) => 'A|'B @@ -959,7 +1802,7 @@ Dict.map({a: 1, b: 2}, {|x| x + 1}) Dict.mapKeys: (Dict('A), fn: (String) => String) => Dict('A) Dict.mapKeys({a: 1, b: 2, c: 5}, {|x| concat(x, "-foobar")}) -Dict.omit: (Dict('A), List(String)) => keys: Dict('A) +Dict.omit: (Dict('A), keys: List(String)) => Dict('A) Creates a new dictionary that excludes the omitted keys. data = { a: 1, b: 2, c: 3, d: 4 } Dict.omit(data, ["b", "d"]) // {a: 1, c: 3} @@ -1561,6 +2404,12 @@ List.findIndex: (List('A), fn: ('A) => Bool) => Number Returns `-1` if there is no value found List.findIndex([1,4,5], {|el| el>3 }) +List.sample: (List('A)) => 'A +List.sample([1,4,5]) + +List.sampleN: (List('A), n: Number) => List('A) +List.sampleN([1,4,5], 2) + ## Functional Transformations List.map: (List('A), ('A, index?: Number) => 'B) => List('B) @@ -1743,6 +2592,15 @@ import { SquiggleEditor } from "../../../components/SquiggleEditor"; Squiggle numbers are Javascript floats. +## Constants + +| Variable Name | Number Name | Value | +| ----------------- | --------------------------------------------------------------- | ----------------------- | +| `Number.minValue` | The smallest positive numeric value representable in JavaScript | 5e-324 | +| `Number.maxValue` | The largest positive numeric value representable in JavaScript | 1.7976931348623157e+308 | + +## Functions + ## Comparison Number.smaller <: (Number, Number) => Bool @@ -1765,6 +2623,12 @@ Number.divide /: (Number, Number) => Number Number.pow ^: (Number, Number) => Number +Number.mod: (Number, Number) => Number +modulo. This is the same as the '%' operator in Python. Note that there is no '%' operator in Squiggle for this operation. +mod(10, 3) +mod(-10, 3) +mod(10, -3) + ## Functions (Number) Number.unaryMinus -: (Number) => Number @@ -1899,7 +2763,7 @@ import { SquiggleEditor } from "../../../components/SquiggleEditor"; Function specifications (Specs) are an experimental feature in Squiggle. They are used to specify the structure of functions and verify that they match that structure. They are used primarily as a tag for functions. -Spec.make: ({name: String, documentation: String, validate: Function}) => Specification +Spec.make: ({name: String, documentation: String, validate: Lambda}) => Specification Create a specification. @startClosed validate(fn) = { @@ -2075,11 +2939,11 @@ Adds text documentation to a value. This is useful for documenting what a value Tag.getDoc: (any) => String -Tag.showAs: (Dist, Plot|(Dist) => Plot) => Dist, (List(any), Table|(List(any)) => Table) => List(any), ((Number) => Dist|Number, Plot|Calculator|((Number) => Dist|Number) => Plot|Calculator) => (Number) => Dist|Number, ((Date) => Dist|Number, Plot|Calculator|((Date) => Dist|Number) => Plot|Calculator) => (Date) => Dist|Number, ((Duration) => Dist|Number, Plot|Calculator|((Duration) => Dist|Number) => Plot|Calculator) => (Duration) => Dist|Number, (Function, Calculator|(Function) => Calculator) => Function +Tag.showAs: (Dist, Plot|(Dist) => Plot) => Dist, (List(any), Table|(List(any)) => Table) => List(any), ((Number) => Dist|Number, Plot|Calculator|((Number) => Dist|Number) => Plot|Calculator) => (Number) => Dist|Number, ((Date) => Dist|Number, Plot|Calculator|((Date) => Dist|Number) => Plot|Calculator) => (Date) => Dist|Number, ((Duration) => Dist|Number, Plot|Calculator|((Duration) => Dist|Number) => Plot|Calculator) => (Duration) => Dist|Number, (Lambda, Calculator|(Lambda) => Calculator) => Lambda Overrides the default visualization for a value. `showAs()` can take either a visualization, or a function that calls the value and returns a visualization. -Different types of values can be displayed in different ways. The following table shows the potential visualization types for each input type. In this table, `Number` can be used with Dates and Durations as well. +Different types of values can be displayed in different ways. The following table shows the potential visualization types for each input type. In this table, `Number` can be used with Dates and Durations as well. | **Input Type** | **Visualization Types** | | ----------------------------------- | ------------------------------------- | | **Distribution** | `Plot.dist` | @@ -2176,7 +3040,7 @@ The Calculator module allows you to make custom calculators for functions. This Calculators can be useful for debugging functions or to present functions to end users. -Calculator.make: ({fn: Function, title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator, (Function, params?: {title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator +Calculator.make: ({fn: Lambda, title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator, (Lambda, params?: {title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator `Calculator.make` takes in a function, a description, and a list of inputs. The function should take in the same number of arguments as the number of inputs, and the arguments should be of the same type as the default value of the input. @@ -2227,7 +3091,7 @@ Input.checkbox: ({name: String, description?: String, default?: Bool}) => Input Creates a checkbox input. Used for Squiggle booleans. Input.checkbox({ name: "IsTrue?", default: true }) -Input.select: ({name: String, description?: String, options: List(String), default?: String}) => Input +Input.select: ({name: String, options: List(String), description?: String, default?: String}) => Input Creates a dropdown input. Used for Squiggle strings. Input.select({ name: "Name", default: "Sue", options: ["John", "Mary", "Sue"] }) @@ -2242,7 +3106,7 @@ import { SquiggleEditor } from "../../../components/SquiggleEditor"; _Warning: Relative value functions are particularly experimental and subject to change._ -RelativeValues.gridPlot: ({ids: List(String), fn: (String, String) => List(Number)}) => Plot +RelativeValues.gridPlot: ({ids: List(String), fn: (String, String) => [Dist, Dist]}) => Plot RelativeValues.gridPlot({ ids: ["foo", "bar"], fn: {|id1, id2| [SampleSet.fromDist(2 to 5), SampleSet.fromDist(3 to 6)]}, @@ -2356,7 +3220,7 @@ Danger.poissonDist(10) ## Integration -Danger.integrateFunctionBetweenWithNumIntegrationPoints: (f: Function, min: Number, max: Number, numIntegrationPoints: Number) => Number +Danger.integrateFunctionBetweenWithNumIntegrationPoints: (f: Lambda, min: Number, max: Number, numIntegrationPoints: Number) => Number 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 distributions, use: @@ -2369,7 +3233,7 @@ Danger.integrateFunctionBetweenWithNumIntegrationPoints(auxiliaryF, min, max, nu Danger.integrateFunctionBetweenWithNumIntegrationPoints({|x| x+1}, 1, 10, 10) -Danger.integrateFunctionBetweenWithEpsilon: (f: Function, min: Number, max: Number, epsilon: Number) => Number +Danger.integrateFunctionBetweenWithEpsilon: (f: Lambda, min: Number, max: Number, epsilon: Number) => Number 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. @@ -2377,7 +3241,7 @@ Danger.integrateFunctionBetweenWithEpsilon({|x| x+1}, 1, 10, 0.1) ## Optimization -Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions: (fs: List(Function), funds: Number, approximateIncrement: Number) => any +Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions: (fs: List(Lambda), funds: Number, approximateIncrement: Number) => any 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)`. Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions( [ @@ -2387,11 +3251,3 @@ Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions( 100, 0.01 ) - -``` - -``` - -``` - -``` diff --git a/packages/ai/squiggleLibraries/ozziegooen/helpers.squiggle b/packages/ai/squiggleLibraries/ozziegooen/helpers.squiggle index b815206d3e..d572a48f91 100644 --- a/packages/ai/squiggleLibraries/ozziegooen/helpers.squiggle +++ b/packages/ai/squiggleLibraries/ozziegooen/helpers.squiggle @@ -6,17 +6,22 @@ expect = sTest.expect @hide describe = sTest.describe +@hide +styleSquiggleCode(code) = " +```squiggle +" + code + " +```" + @doc( " - round(num, n) - - Rounds the number `num` to `n` decimal places. + round(num, n) -> string - Example: - round(3.14159, 2) -> \"3.14\" -" + Rounds the number `num` to `n` decimal places." + + styleSquiggleCode("round(3.14159, 2) -> \"3.14\"") ) -export round(num, n) = { +export round(num, n) = if typeOf(num) != "Number" then throw( + "Was given a " + typeOf(num) + ", must be given a Number" +) else { asString = String.make(num) splitString = String.split(asString, "") if List.findIndex(splitString, {|r| r == "e"}) != -1 then { @@ -63,19 +68,20 @@ roundTests = describe( "rounds a negative number", {|| expect(round(-2.7182, 2)).toBe("-2.71")} ), + test( + "throws when given distribution", + {|| expect({|| round(1 to 10, 2)}).toThrowAnyError()} + ), ] ) @doc( " - formatTime(hours) + formatTime(hours) -> string Converts a number of hours to a formatted string indicating time in - seconds, minutes, hours, days, months, or years. - - Example: - formatTime(1) -> \"**1** hours\" - " + seconds, minutes, hours, days, months, or years. Bolds the result." + + styleSquiggleCode("formatTime(1) -> \"**1** hours\"") ) export formatTime(hours) = { secondsInMinute = 60 @@ -129,16 +135,16 @@ formatTimeTests = describe( ) @doc( - "## Linear or Quadratic Interpolation -```squiggle -@import('hub:ozziegooen/helpers' as h) + "## Linear or Quadratic Interpolation" + + styleSquiggleCode( + "@import('hub:ozziegooen/helpers' as h) -h.interpolate([{x: 0, y:10}, {x:10, y:20}], 'linear')(4) -> 15 -h.interpolate([{x: 0, y:10}, {x:10, y:20}], 'quadratic')(4) -> 11.6 +h.interpolate([{x: 0, y:10}, {x:10, y:20}], 'linear')(4) // 15 +h.interpolate([{x: 0, y:10}, {x:10, y:20}], 'quadratic')(4) // 11.6 //makes a graph -foo(t:[0,30]) = h.interpolate([{x: 0, y:10}, {x:10, y:20}, {x:20, y:10}], 'quadratic')(t) -" +foo(t:[0,30]) = h.interpolate([{x: 0, y:10}, {x:10, y:20}, {x:20, y:10}], 'quadratic')(t)" + ) ) export interpolate(points, type) = { sortedPoints = List.sortBy(points, {|f| f.x}) //TODO: Sort, somehow @@ -163,7 +169,6 @@ export interpolate(points, type) = { c = leftPoint.y + a * leftPoint.x ^ 2 a * x ^ 2 + b * x + c } else { foo: "Invalid interpolate type" } - } else if x > sortedPoints[i - 1].x then sortedPoints[List.length( sortedPoints ) - @@ -230,7 +235,7 @@ interpolationTests = describe( ) //myShape = [{ x: 4, y: 10 }, { x: 20, y: 40 }, { x: 30, y: 20 }] - +@hide plot(fn, xPoints) = Plot.numericFn( fn, { @@ -287,6 +292,20 @@ You have to enter data in the format of x and y values, as shown below, then get } ) +@doc( + " + distributionListRange(dists, lowQuantile, highQuantile) + + Returns a range that is the minimum and maximum of the low and high quantiles of the distributions in the list." + + styleSquiggleCode( + "distributionListRange([dist1, dist2, dist3], 0.05, 0.95) -> {min: 1, max: 10}" + ) +) +export distributionListRange(dists, lowQuantile, highQuantile) = { + min: Number.min(List.map(dists, {|d| Dist.quantile(d, lowQuantile)})), + max: Number.max(List.map(dists, {|d| Dist.quantile(d, highQuantile)})), +} + @startOpen @notebook readme = [ @@ -303,24 +322,10 @@ To use the functions from this library in your projects, import it as follows: ``` ## Functions Overview -### round -Rounds a given number to a specified number of decimal places. - -Example: - -```squiggle -h.round(3.423, 2) // Returns: \"3.42\" -```", +### round", Tag.getDoc(round), "---", - "### formatTime -Converts a given number of hours into a human-readable time format, such as seconds, minutes, hours, days, months, or years. - -Example: - -```squiggle -h.formatTime(4.23) // Enter the number of hours and format the result -```", + "### formatTime", Tag.getDoc(formatTime), "---", "### interpolate @@ -341,4 +346,7 @@ h.interpolate([{x: 0, y: 10}, {x: 10, y: 20}], 'quadratic')(4) // Returns: 11.6 ### Interpolation Calculator This tool helps visualize and compare the results of linear and quadratic interpolations for a given set of data points. Below is an example use case integrated with the library.", interpolationCalculator, + "---", + "### distributionListRange", + Tag.getDoc(distributionListRange), ] diff --git a/packages/ai/squiggleLibraries/ozziegooen/sTest.squiggle b/packages/ai/squiggleLibraries/ozziegooen/sTest.squiggle index 3b59b4dba1..5ce3002dc4 100644 --- a/packages/ai/squiggleLibraries/ozziegooen/sTest.squiggle +++ b/packages/ai/squiggleLibraries/ozziegooen/sTest.squiggle @@ -297,7 +297,7 @@ comparisonTests = describe( ] ) -errorTests = describe( +errorTests = System.version == "0.9.6-0" ? describe( "Error Throwing Tests", [ test( @@ -314,4 +314,4 @@ errorTests = describe( {|| expect({|| throw("UnexpectedError")}).toNotThrow()} ), ] -) +) : false diff --git a/packages/ai/src/squiggle/README.ts b/packages/ai/src/squiggle/README.ts index a0b4b3d845..69e726ebf8 100644 --- a/packages/ai/src/squiggle/README.ts +++ b/packages/ai/src/squiggle/README.ts @@ -1,3 +1,3 @@ // This file is auto-generated. Do not edit manually. export const README = - '# Squiggle Documentation\n\nSquiggle is a very simple language. Don\'t try using language primitives/constructs you don\'t see below, or that aren\'t in our documentation. They are likely to fail.\n\nWhen writing Squiggle code, it\'s important to avoid certain common mistakes:\n\n### Syntax and Structure\n\n1. Variable Expansion: Not supported. Don\'t use syntax like |v...| or |...v|.\n2. All pipes are "->", not "|>".\n3. Dict keys and variable names must be lowercase.\n4. The last value in a block/function is returned (no "return" keyword).\n5. Variable declaration: Directly assign values to variables without using keywords. For example, use `foo = 3` instead of `let foo = 3`.\n6. All statements in your model, besides the last one must either be comments or variable declarations. You can\'t do, `4 \\n 5 \\n 6` Similarly, you can\'t do, `Calculator() ... Table()` - instead, you need to set everything but the last item to a variable.\n\n### Function Definitions and Use\n\n1. Anonymous Functions: Use {|e| e} syntax for anonymous functions.\n2. Function Parameters: When using functions like normal, specify the standard deviation with stdev instead of sd. For example, use normal({mean: 0.3, stdev: 0.1}) instead of normal({mean: 0.3, sd: 0.1}).\n3. There\'s no recursion.\n4. You can\'t call functions that accept ranges, with distributions. No, `({|foo: [1,20]| foo}) (4 to 5)`.\n\n### Data Types and Input Handling\n\n1. Input Types: Use Input.text for numeric inputs instead of Input.number or Input.slider.\n2. The only function param types you can provide are numeric/date ranges, for numbers. f(n:[1,10]). Nothing else is valid. You cannot provide regular input type declarations.\n3. Only use Inputs directly inside calculators. They won\'t return numbers, just input types.\n\n### Looping, Conditionals, and Data Operations\n\n1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic.\n2. There aren\'t for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile.\n3. Remember to use `Number.sum` and `Number.product`, instead of using Reduce in those cases.\n\n### List and Dictionary Operations\n\n1. You can\'t do "(0..years)". Use List.make or List.upTo.\n2. There\'s no "List.sort", but there is "List.sortBy", "Number.sort".\n\n### Randomness and Distribution Handling\n\n1. There\'s no random() function. Use alternatives like sample(uniform(0,1)).\n2. When representing percentages, use "5%" instead of "0.05" for readability.\n3. The `to` syntax only works for >0 values. "4 to 10", not "0 to 10".\n\n### Units and Scales\n\n1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01).\n2. If you make a table that contains a column of similar distributions, use a scale to ensure consistent min and max.\n3. Scale.symlog() has support for negative values, Scale.log() doesn\'t. Scale.symlog() is often a better choice for this reason, though Scale.log() is better when you are sure values are above 0.\n4. Do use Scale.symlog() and Scale.log() on dists/plots that might need it. Many do!\n\n### Documentation and Comments\n\n1. Tags like @name and @doc apply to the following variable, not the full file.\n2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022.\n\n---\n\nThis format provides a clear and organized view of the guidelines for writing Squiggle code.\n\nHere\'s are some simple example Squiggle programs:\n\n```squiggle\n//Model for Piano Tuners in New York Over Time\n\n@name("🌆 Population of New York in 2022")\n@doc("I\'m really not sure here, this is a quick guess.")\npopulationOfNewYork2022 = 8.1M to 8.4M\n\n@name("🎹 Percentage of Population with Pianos")\n@format(".1%")\nproportionOfPopulationWithPianos = 0.2% to 1%\n\n@name("🔧 Number of Piano Tuners per Piano")\npianoTunersPerPiano = {\n pianosPerPianoTuner = 2k to 50k\n 1 / pianosPerPianoTuner\n}\n\n//We only mean to make an estimate for the next 10 years.\n@hide\ndomain = [Date(2024), Date(2034)]\n\n@name("Population at Time")\npopulationAtTime(t: domain) = {\n dateDiff = Duration.toYears(t - Date(2024))\n averageYearlyPercentageChange = normal({ p5: -1%, p95: 5% }) // We\'re expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year\n populationOfNewYork2022 * (averageYearlyPercentageChange + 1) ^ dateDiff\n}\n\n@name("Total Tuners, at Time")\ntotalTunersAtTime(t: domain) = populationAtTime(t) *\n proportionOfPopulationWithPianos *\n pianoTunersPerPiano\n\nmeanTunersAtTime(t: domain) = mean(totalTunersAtTime(t))\n```\n\n```squiggle\ncalculator = Calculator(\n {|a, b, c, d| [a, b, c, d]},\n {\n title: "Concat()",\n description: "This function takes in 4 arguments, then displays them",\n sampleCount: 10000,\n inputs: [\n Input.text(\n {\n name: "First Param",\n default: "10 to 13",\n description: "Must be a number or distribution",\n }\n ),\n Input.textArea(\n {\n name: "Second Param",\n default: "[4,5,2,3,4,5,3,3,2,2,2,3,3,4,45,5,5,2,1]",\n }\n ),\n Input.select(\n {\n name: "Third Param",\n default: "Option 1",\n options: ["Option 1", "Option 2", "Option 3"],\n }\n ),\n Input.checkbox({ name: "Fourth Param", default: false }),\n ],\n }\n)\n\n```\n\n```squiggle\n// Cost-benefit analysis for a housing addition in berkeley\n\n// Input section\n@name("Model Inputs")\n@doc("Key parameters for the housing development project")\ninputs = {\n landCost: 1M to 2M,\n constructionCost: 500k to 800k,\n permitFees: 50k to 100k,\n numberOfHomes: 10,\n monthlyRentalIncome: 3k to 5k,\n annualPropertyAppreciation: 2% to 5%,\n annualSocialBenefit: 10k to 30k,\n yearsToConsider: 30,\n}\n\n// Calculation section\n@name("Calculations")\n@doc("Core calculations for the cost-benefit analysis")\ncalculations(i) = {\n totalCostPerHome = i.landCost + i.constructionCost + i.permitFees\n annualRentalIncome = i.numberOfHomes * i.monthlyRentalIncome * 12\n totalCost = i.numberOfHomes * totalCostPerHome\n\n annualAppreciation(year) = i.numberOfHomes * totalCostPerHome *\n ((1 + i.annualPropertyAppreciation) ^ year -\n (1 + i.annualPropertyAppreciation) ^ (year - 1))\n\n annualBenefit(year) = annualRentalIncome + annualAppreciation(year) +\n i.numberOfHomes * i.annualSocialBenefit\n\n totalBenefit = List.upTo(1, i.yearsToConsider) -> List.map(annualBenefit)\n -> List.reduce(\n 0,\n {|acc, val| acc + val}\n )\n\n netBenefit = totalBenefit - totalCost\n probPositiveNetBenefit = 1 - cdf(netBenefit, 0)\n\n {\n totalCostPerHome: totalCostPerHome,\n annualRentalIncome: annualRentalIncome,\n totalCost: totalCost,\n totalBenefit: totalBenefit,\n netBenefit: netBenefit,\n probPositiveNetBenefit: probPositiveNetBenefit,\n }\n}\n\n// Apply calculations to inputs\n@name("Results")\n@doc("Output of calculations based on input parameters")\nresults = calculations(inputs)\n\n// Analysis section\n@name("Cost-Benefit Analysis")\n@doc("Detailed analysis of the housing development project")\nanalysis = {\n costsTable = Table.make(\n [\n { name: "Land Cost per Home", value: inputs.landCost },\n { name: "Construction Cost per Home", value: inputs.constructionCost },\n { name: "Permit Fees per Home", value: inputs.permitFees },\n { name: "Total Cost per Home", value: results.totalCostPerHome },\n { name: "Total Cost for 10 Homes", value: results.totalCost },\n ],\n {\n columns: [\n { name: "Item", fn: {|r| r.name} },\n {\n name: "Cost",\n fn: {\n |r|\n Plot.dist(\n r.value,\n {\n xScale: Scale.log({ tickFormat: "($.1s", min: 20k, max: 200M }),\n }\n )\n },\n },\n ],\n }\n )\n\n benefitTable = Table.make(\n [\n {\n name: "Monthly Rental Income per Home",\n value: inputs.monthlyRentalIncome,\n },\n {\n name: "Annual Social Benefit per Home",\n value: inputs.annualSocialBenefit,\n },\n { name: "Total Benefit over 30 years", value: results.totalBenefit },\n ],\n {\n columns: [\n { name: "Item", fn: {|r| r.name} },\n {\n name: "Value",\n fn: {\n |r|\n Plot.dist(\n r.value,\n { xScale: Scale.linear({ tickFormat: "($.1s" }) }\n )\n },\n },\n ],\n }\n )\n\n netBenefitPlot = Plot.dist(\n results.netBenefit,\n {\n title: "Distribution of Net Benefit",\n xScale: Scale.log({ tickFormat: "($.1s", min: 10M, max: 200M }),\n }\n )\n\n {\n title: "Cost-Benefit Analysis: Adding 10 Homes to Berkeley, CA",\n costs: costsTable,\n benefits: benefitTable,\n netBenefit: netBenefitPlot,\n probabilityOfPositiveNetBenefit: results.probPositiveNetBenefit,\n }\n}\n\nanalysis\n\n```\n\n```squiggle\nx = 10\nresult = if x == 1 then {\n {y: 2, z: 0}\n} else {\n {y: 0, z: 4}\n}\ny = result.y\nz = result.z\n```\n\n```squiggle\n@showAs({|f| Plot.numericFn(f, { xScale: Scale.log({ min: 1, max: 100 }) })})\nfn(t) = t ^ 2\n```\n\n```squiggle\nplot = {|t| normal(t, 2) * normal(5, 3)}\n -> Plot.distFn(\n {\n title: "A Function of Value over Time",\n xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }),\n yScale: Scale.linear({ title: "Value" }),\n distXScale: Scale.linear({ tickFormat: "#x" }),\n }\n )\n```\n\n```squiggle\nf(t: [Date(2020), Date(2040)]) = {\n yearsPassed = toYears(t - Date(2020))\n normal({mean: yearsPassed ^ 2, stdev: yearsPassed^1.3+1})\n}\n```\n\n```squiggle\nimport "hub:ozziegooen/sTest" as sTest\n@name("💰 Expected Cost")\n@format("($.2s")\nflightCost = normal({ mean: 600, stdev: 100 })\n\n@name("🥇 Expected Benefit")\n@format("($.2s")\nbenefitEstimate = normal({ mean: 1500, stdev: 300 })\n\n@name("📊 Net Benefit")\n@format("($.2s")\nnetBenefit = benefitEstimate - flightCost\n\n@name("🚦 Test Suite")\n@doc(\n "Test suite to validate various aspects of the flight cost and benefits model using sTest."\n)\ntestSuite = sTest.describe(\n "Flight to London Test Suite",\n [\n // Test for reasonable flight costs\n sTest.test(\n "Flight cost should be reasonable",\n {\n ||\n meanValue = mean(flightCost)\n sTest.expect(meanValue).toBeBetween(300, 10k)\n }\n ),\n ]\n)\n\n```\n\n# Language Features\n\n## Program Structure\n\nA Squiggle program consists of a series of definitions (for example, `x = 5`, `f(x) = x * x`). This can optionally conclude with an _end expression_.\n\nIf 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.\n\n```squiggle\nx = 5\ny = 10\nx + y\n```\n\n```squiggle\nx = 5\ny = 10\n```\n\n## Immutability\n\nAll variables in Squiggle are immutable, similar to other functional programming languages like OCaml or Haskell.\n\nIn 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).\n\nIn case of basic types such as numbers or strings, the impact of immutability is more subtle.\n\nConsider this code:\n\n```squiggle\nx = 5\nx = x + 5\n```\n\nWhile 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.\n\nIn most cases, shadowing behaves identically to what you\'d expect in languages like JavaScript or Python.\n\nOne case where shadowing matters is closures:\n\n```squiggle\nx = 5\nargPlusX(y) = x + y\n\nx = x + 5\n\nargPlusX(5)\n```\n\nIn 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.\n\n## Blocks\n\nBlocks are special expressions in Squiggle that can contain any number of local definitions and end with an expression.\n\n```squiggle\nx = { 5 } // same as "x = 5"\ny = {\n t = 10 // local variable, won\'t be available outside of the block body\n 5 * t // end expression\n}\n```\n\n## Conditionals\n\nIf/then/else statements in Squiggle are values too.\n\n```squiggle\nx = 5\nif x<8 then 10 else 3\n```\n\nSee [Control flow](/docs/Guides/ControlFlow) for more details and examples.\n\n## Comments\n\n```squiggle\n// This is a single-line comment\\n\n/*\nThis is a multiple\n-line comment.\n*/\nfoo = 5\n```\n\n## Pipes\n\nSquiggle 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.\n\n```squiggle\nnormal(5,2) -> truncateLeft(3) -> SampleSet.fromDist -> SampleSet.map({|r| r + 10})\n```\n\n## Standard Library\n\nSquiggle features a simple [standard libary](/docs/Api/Dist).\n\nMost functions are namespaced under their respective types to keep functionality distinct. Certain popular functions are usable without their namespaces.\n\nFor example,\n\n```squiggle\na = List.upTo(0, 5000) -> SampleSet.fromList // namespaces required\nb = normal(5,2) // namespace not required\nc = 5 to 10 // namespace not required\n```\n\n## Simple Error Handling\n\nSquiggle supports the functions [throw](/docs/Api/Common#throw) and [try](/docs/Api/Common#try) for simple error handling. It does not yet have proper error types.\n\n# Gotchas\n\n## Point Set Distributions Conversions\n\nPoint 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.\n\nIn 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.\n\n```squiggle\ndist = SampleSet.fromDist(5 to 100000000)\n{\n sampleSetMedian: quantile(dist, .5),\n pointSetMedian: quantile(PointSet.fromDist(dist), .5),\n dist: dist\n}\n```\n\n---\n\nThis 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.\n\nWe plan to later support more configuration of kernel density estimation, and for visualiations of Sample Set distributions to instead use histograms.\n\n## Sample Set Correlations\n\nCorrelations 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.\n\nIn this example, we subtract all three types of distributions by themselves. Notice that the Sample Set distribution returns 0. 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.\n\n```squiggle\nsampleSetDist = normal(5, 2)\npointSetDist = sampleSetDist -> PointSet.fromDist\nsymbolicDist = Sym.normal(5, 2)\n[\n sampleSetDist - sampleSetDist,\n pointSetDist - pointSetDist,\n symbolicDist - symbolicDist,\n]\n```\n\n# Functions\n\n## Basic syntax\n\n```squiggle\nmyMultiply(t) = normal(t^2, t^1.2+.01)\nmyMultiply\n```\n\nIn Squiggle, function definitions are treated as values. There\'s no explicit `return` statement; the result of the last expression in the function body is returned.\nIf you need to define local variables in functions, you can use blocks. The last expression in the block is the value of the block:\n\n```squiggle\nmultiplyBySix(x) = {\n doubleX = x * 2\n doubleX * 3\n }\n```\n\n## Anonymous Functions\n\nIn Squiggle, you can define anonymous functions using the `{|...| ...}` syntax. For example, `myMultiply(x, y) = x * y` and `myMultiply = {|x, y| x * y}` are equivalent.\n\nSquiggle functions are first-class values, meaning you can assign them to variables, pass them as arguments to other functions, and return them from other functions.\n\n```squiggle\n{|t| normal(t^2, t^1.2+.01)}\n```\n\n## Function Visualization\n\nThe Squiggle viewer can automatically visualize functions that take a single number as input and return either a number or a distribution, without the need for manual plots:\n\n1. `(number) => number`\n2. `(number) => distribution`\n\n```squiggle\nnumberToNumber(x) = x * x\nnumberToDistribution(x) = normal(x + 1, 3)\nplaceholderFunction(x, y) = x + y\n```\n\nWhen Squiggle visualizes a function, it automatically selects a range of input values to use.\nThe default range of input values is 0 to 10.\n\nYou can manually set the range in the following ways:\n\n- With `Plot.numericFn` or `Plot.distFn` plots, using the `xScale` parameter\n- Through the chart\'s settings in the UI (look for a gear icon next to the variable name)\n- With parameter annotations (explained below)\n\n## Parameter Annotations\n\nFunction parameters can be annotated with _domains_ to specify the range of valid input values.\n\nExamples:\n\n- `x: Number.rangeDomain(5, 10)`\n- `x: [5, 10]` — shortcut for `Number.rangeDomain(...)`\n\nAnnotations help to document possible values that can be passed as a parameter\'s value.\n\nAnnotations 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).\n\nDomains are checked on function calls; `f(x: [1,2]) = x; f(3)` will fail.\n\nWe plan to support other kinds of domains in the future; for now, only numeric ranges are supported.\n\n```squiggle\nyearToValue(year: [2020, 2100]) = 1.04 ^ (year - 2020)\n```\n\n### Annotation Reflection\n\n```squiggle\nmyMultiply(x: [1, 20]) = x * x\nmyMultiply.parameters[0]\n```\n\nDomains and parameter names can be accessed by the `fn.parameters` property.\n\n# Control Flow\n\nThis 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.\n\n## Conditionals\n\n### If-else\n\n```squiggle\nif condition then result else alternative\n```\n\n```squiggle\nx = 10\nif x == 1 then 1 else 2\n```\n\n### If-else as a ternary operator\n\n```squiggle\ntest ? result : alternative;\n```\n\n```squiggle\nx = 10\nx == 0 ? 1 : 2\n```\n\n### Tips and tricks\n\n#### Use brackets and parenthesis to organize control flow\n\n```squiggle\nx = 10\nif x == 1 then {\n 1\n} else {\n 2\n}\n```\n\nor\n\n```squiggle\nx = 10\ny = 20\nif x == 1 then {\n (\n if y == 0 then {\n 1\n } else {\n 2\n }\n )\n} else {\n 3\n}\n```\n\nThis is overkill for simple examples becomes useful when the control conditions are more complex.\n\n#### Save the result to a variable\n\nAssigning a value inside an if/else flow isn\'t possible:\n\n```squiggle\nx = 10\ny = 20\nif x == 1 then {\n y = 1\n} else {\n y = 2 * x\n}\n```\n\nInstead, you can do this:\n\n```squiggle\nx = 10\ny = 20\ny = if x == 1 then {\n 1\n} else {\n 2 * x\n}\n```\n\nLikewise, for assigning more than one value, you can\'t do this:\n\n```squiggle\ny = 0\nz = 0\nif x == 1 then {\n y = 2\n} else {\n z = 4\n}\n```\n\nInstead, do:\n\n```squiggle\nx = 10\nresult = if x == 1 then {\n {y: 2, z: 0}\n} else {\n {y: 0, z: 4}\n}\ny = result.y\nz = result.z\n```\n\n## For loops\n\nFor loops aren\'t supported in Squiggle. Instead, use a [map](/docs/Api/List#map) or a [reduce](/docs/Api/List#reduce) function.\n\nInstead of:\n\n```js\nxs = [];\nfor (i = 0; i < 10; i++) {\n xs[i] = f(x);\n}\n```\n\ndo:\n\n```squiggle\nf(x) = 2*x\nxs = List.upTo(0,10)\nys = List.map(xs, {|x| f(x)})\n```\n\n# Basic Types\n\n## Numbers\n\nSquiggle 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.\n[Number API](/docs/Api/Number)\n\nNumbers support a few scientific notation suffixes.\n\n| Suffix | Multiplier |\n| ------ | ---------- |\n| n | 10^-9 |\n| m | 10^-3 |\n| % | 10^-2 |\n| k | 10^3 |\n| M | 10^6 |\n| B,G | 10^9 |\n| T | 10^12 |\n| P | 10^15 |\n\nThere\'s no difference between floats and integers in Squiggle.\n\n```squiggle\nn = 4.32\nkilo = 4.32k\nmicro = 4.32m\nveryLarge = 1e50\nverySmall = 1e-50\n```\n\n## Booleans\n\nBooleans can be `true` or `false`.\n\n```squiggle\nt = true\nf = false\n```\n\n## Strings\n\nStrings can be created with either single or double quotes.\n[String API](/docs/Api/String)\n\n```squiggle\ns = "Double-quoted"\ns2 = \'Single-quoted\'\n```\n\n## Distributions\n\nDistributions are first-class citizens. Use the syntax `a to b` to create a quick lognormal distribution, or write out the whole distribution name.\n\n```squiggle\na = 10 to 20\nb = normal(4, 2)\nc = lognormal({ mean: 50, stdev: 10 })\nd = mixture(a, b, c, [.3, .3, .4])\nd\n```\n\nSee these pages for more information on distributions:\n\n- [Distribution Creation](/docs/Guides/DistributionCreation)\n- [Distribution Functions Guide](/docs/Guides/Functions)\n- [Distribution API](/docs/Api/Dist)\n\nThere 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)`.\n\n## Lists\n\nSquiggle lists can contain items of any type, similar to lists in Python. You can access individual list elements with `[number]` notation, starting from `0`.\n\nSquiggle 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.\n[List API](/docs/Api/List)\n\n```squiggle\nmyList = [1, "hello", 3 to 5, ["foo", "bar"]]\nfirst = myList[0] // 1\nbar = myList[3][1] // "bar"\n```\n\n## Dictionaries\n\nSquiggle dictionaries work similarly to Python dictionaries or Javascript objects. Like lists, they can contain values of any type. Keys must be strings.\n[Dictionary API](/docs/Api/Dictionary)\n\n```squiggle\nd = {dist: triangular(0, 1, 2), weight: 0.25, innerDict: {foo: "bar"}}\n```\n\n## Other types\n\nOther Squiggle types include:\n\n- [Functions](/docs/Guides/Functions)\n- [Plots](/docs/Api/Plot)\n- [Scales](/docs/Api/Plot#scales)\n- [Domains](#parameter-annotations)---\n description:\n\n---\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Common\n\nFunctions that work on many different types of values. Also see the experimental [JSON functions](/docs/Api/Danger#json).\n\nCommon.equal ==: (any, any) => Bool\nReturns true if the two values passed in are equal, false otherwise. Does not work for Squiggle functions, but works for most other types.\n\nCommon.unequal !=: (any, any) => Bool\n\nCommon.typeOf: (any) => String\nReturns the type of the value passed in as a string. This is useful when you want to treat a value differently depending on its type.\nmyString = typeOf("foo")\nmyBool = typeOf(true)\nmyDist = typeOf(5 to 10)\nmyFn = typeOf({|e| e})\n\nCommon.inspect: (\'A, message?: String) => \'A\nRuns Console.log() in the [Javascript developer console](https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-developer-console) and returns the value passed in.\n\nCommon.throw: (message: String?) => any\nThrows an error. You can use `try` to recover from this error.\n\nCommon.try: (fn: () => \'A, fallbackFn: () => \'B) => \'A|\'B\nTry to run a function and return its result. If the function throws an error, return the result of the fallback function instead.\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Boolean\n\nBoolean.or ||: (Bool, Bool) => Bool\n\nBoolean.and &&: (Bool, Bool) => Bool\n\nBoolean.not !: (Bool) => Bool\n\n---\n\n## description: Dates are a simple date time type.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Date\n\nA simple date type. Dates are stored as milliseconds since the epoch. They are immutable, and all functions that modify dates return a new date. Used with [Duration](./Duration) values.\n\n Dates can be useful for modeling values that change over time. Below is a simple example of a function that returns a normal distribution that changes over time, based on the number of years passed since 2020.\n\n\n\n## Constructors\n\nDate.make: (String) => Date, (year: Number, month: Number, day: Number) => Date, (year: Number) => Date\nd1 = Date.make("2020-05-12")\nd2 = Date.make(2020, 5, 10)\nd3 = Date.make(2020.5)\n\n## Conversions\n\nDate.fromUnixTime: (Number) => Date\nDate.fromUnixTime(1589222400)\n\nDate.toUnixTime: (Date) => Number\nDate.toUnixTime(Date.make(2020, 5, 12))\n\n## Algebra\n\nDate.subtract -: (Date, Date) => Duration\nDate.make(2020, 5, 12) - Date.make(2000, 1, 1)\n\nDate.subtract -: (Date, Date) => Duration\nDate.make(2020, 5, 12) - Date.make(2000, 1, 1)\n\nDate.add +: (Date, Duration) => Date, (Duration, Date) => Date\nDate.make(2020, 5, 12) + 20years\n20years + Date.make(2020, 5, 12)\n\n## Comparison\n\nDate.smaller <: (Date, Date) => Bool\n\nDate.larger >: (Date, Date) => Bool\n\nDate.smallerEq <=: (Date, Date) => Bool\n\nDate.largerEq >=: (Date, Date) => Bool\n\n## Other\n\nDate.rangeDomain: (min: Date, min: Date) => Domain\nDate.rangeDomain(Date(2000), Date(2010))\n\n---\n\n## description: Squiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Dict\n\nSquiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript.\n\n## Conversions\n\nDict.toList: (Dict(\'A)) => List([String, \'A])\nDict.toList({a: 1, b: 2})\n\nDict.fromList: (List([String, \'A])) => Dict(\'A)\nDict.fromList([\n["foo", 3],\n["bar", 20],\n]) // {foo: 3, bar: 20}\n\n## Transformations\n\nDict.set: (Dict(\'A), key: String, value: \'A) => Dict(\'A)\nCreates a new dictionary that includes the added element, while leaving the original dictionary unaltered.\nDict.set({a: 1, b: 2}, "c", 3)\n\nDict.delete: (Dict(\'A), key: String) => Dict(\'A)\nCreates a new dictionary that excludes the deleted element.\nDict.delete({a: 1, b: 2}, "a")\n\nDict.merge: (Dict(any), Dict(any)) => Dict(any)\nfirst = { a: 1, b: 2 }\nsnd = { b: 3, c: 5 }\nDict.merge(first, snd)\n\nDict.mergeMany: (List(Dict(any))) => Dict(any)\nfirst = { a: 1, b: 2 }\nsnd = { b: 3, c: 5 }\nDict.mergeMany([first, snd]) // {a: 1, b: 3, c: 5}\n\nDict.map: (Dict(\'A), fn: (\'A) => \'B) => Dict(\'B)\nDict.map({a: 1, b: 2}, {|x| x + 1})\n\nDict.mapKeys: (Dict(\'A), fn: (String) => String) => Dict(\'A)\nDict.mapKeys({a: 1, b: 2, c: 5}, {|x| concat(x, "-foobar")})\n\nDict.omit: (Dict(\'A), List(String)) => keys: Dict(\'A)\nCreates a new dictionary that excludes the omitted keys.\ndata = { a: 1, b: 2, c: 3, d: 4 }\nDict.omit(data, ["b", "d"]) // {a: 1, c: 3}\n\n## Queries\n\nDict.has: (Dict(any), key: String) => Bool\nDict.has({a: 1, b: 2}, "c")\n\nDict.size: (Dict(any)) => Number\nDict.size({a: 1, b: 2})\n\nDict.keys: (Dict(any)) => List(String)\nDict.keys({a: 1, b: 2})\n\nDict.values: (Dict(\'A)) => List(\'A)\nDict.values({ foo: 3, bar: 20 }) // [3, 20]\n\nDict.pick: (Dict(\'A), keys: List(String)) => Dict(\'A)\nCreates a new dictionary that only includes the picked keys.\ndata = { a: 1, b: 2, c: 3, d: 4 }\nDict.pick(data, ["a", "c"]) // {a: 1, c: 3}\n\n---\n\n## 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.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Dist\n\nDistributions are the flagship data type in Squiggle. The distribution type is a generic data type that contains one of three different formats of distributions.\n\nThese subtypes are [point set](/docs/api/DistPointSet), [sample set](/docs/api/DistSampleSet), and [symbolic](/docs/api/Sym). 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).\n\nSeveral 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.\n\nDistributions are created as [sample sets](/DistSampleSet) by default. To create a symbolic distribution, use `Sym.` namespace: `Sym.normal`, `Sym.beta` and so on.\n\n## Distributions\n\nThese are functions for creating primitive 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.\n\nSee a longer tutorial on creating distributions [here](/docs/Guides/DistributionCreation).\n\nDist.make: (Dist) => Dist, (Number) => SymbolicDist\nDist.make(5)\nDist.make(normal({p5: 4, p95: 10}))\n\nDist.mixture: (List(Dist|Number), weights?: List(Number)) => Dist, (Dist|Number) => Dist, (Dist|Number, Dist|Number, weights?: [Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number, Number]) => Dist\nThe `mixture` function takes a list of distributions and a list of weights, and returns a new distribution that is a mixture of the distributions in the list. The weights should be positive numbers that sum to 1. If no weights are provided, the function will assume that all distributions have equal weight.\n\nNote: If you want to pass in over 5 distributions, you must use the list syntax.\nmixture(1,normal(5,2))\nmixture(normal(5,2), normal(10,2), normal(15,2), [0.3, 0.5, 0.2])\nmixture([normal(5,2), normal(10,2), normal(15,2), normal(20,1)], [0.3, 0.5, 0.1, 0.1])\n\nDist.mx: (List(Dist|Number), weights?: List(Number)) => Dist, (Dist|Number) => Dist, (Dist|Number, Dist|Number, weights?: [Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number, Number]) => Dist\nAlias for mixture()\nmx(1,normal(5,2))\n\nDist.normal: (mean: Dist|Number, stdev: Dist|Number) => SampleSetDist, ({p5: Number, p95: Number}) => SampleSetDist, ({p10: Number, p90: Number}) => SampleSetDist, ({p25: Number, p75: Number}) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist\nnormal(5,1)\nnormal({p5: 4, p95: 10})\nnormal({p10: 4, p90: 10})\nnormal({p25: 4, p75: 10})\nnormal({mean: 5, stdev: 2})\n\nDist.lognormal: (mu: Dist|Number, sigma: Dist|Number) => SampleSetDist, ({p5: Number, p95: Number}) => SampleSetDist, ({p10: Number, p90: Number}) => SampleSetDist, ({p25: Number, p75: Number}) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist\nlognormal(0.5, 0.8)\nlognormal({p5: 4, p95: 10})\nlognormal({p10: 4, p90: 10})\nlognormal({p25: 4, p75: 10})\nlognormal({mean: 5, stdev: 2})\n\nDist.uniform: (low: Dist|Number, high: Dist|Number) => SampleSetDist\nuniform(10, 12)\n\nDist.beta: (alpha: Dist|Number, beta: Dist|Number) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist\nbeta(20, 25)\nbeta({mean: 0.39, stdev: 0.1})\n\nDist.cauchy: (location: Dist|Number, scale: Dist|Number) => SampleSetDist\ncauchy(5, 1)\n\nDist.gamma: (shape: Dist|Number, scale: Dist|Number) => SampleSetDist\ngamma(5, 1)\n\nDist.logistic: (location: Dist|Number, scale: Dist|Number) => SampleSetDist\nlogistic(5, 1)\n\nDist.to to: (p5: Dist|Number, p95: Dist|Number) => SampleSetDist\nThe "to" function is a shorthand for lognormal({p5:min, p95:max}). It does not accept values of 0 or less, as those are not valid for lognormal distributions.\n5 to 10\nto(5,10)\n\nDist.exponential: (rate: Dist|Number) => SampleSetDist\nexponential(2)\n\nDist.bernoulli: (p: Dist|Number) => SampleSetDist\nbernoulli(0.5)\n\nDist.triangular: (min: Number, mode: Number, max: Number) => SampleSetDist\ntriangular(3, 5, 10)\n\n## Basic Functions\n\nDist.mean: (Dist) => Number\n\nDist.median: (Dist) => Number\n\nDist.stdev: (Dist) => Number\n\nDist.variance: (Dist) => Number\n\nDist.min: (Dist) => Number\n\nDist.max: (Dist) => Number\n\nDist.mode: (Dist) => Number\n\nDist.sample: (Dist) => Number\n\nDist.sampleN: (Dist, n: Number) => List(Number)\n\nDist.exp: (Dist) => Dist\n\nDist.cdf: (Dist, Number) => Number\n\nDist.pdf: (Dist, Number) => Number\n\nDist.inv: (Dist, Number) => Number\n\nDist.quantile: (Dist, Number) => Number\n\nDist.truncate: (Dist, left: Number, right: Number) => Dist\nTruncates both the left side and the right side of a distribution.\n\nSample 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.\n\nDist.truncateLeft: (Dist, Number) => Dist\n\nDist.truncateRight: (Dist, Number) => Dist\n\n## Algebra (Dist)\n\nDist.add +: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.multiply \\*: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.subtract -: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.divide /: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.pow ^: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.log: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.log: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.log10: (Dist) => Dist\n\nDist.unaryMinus -: (Dist) => Dist\n\n## Algebra (List)\n\nDist.sum: (List(Dist|Number)) => Dist\n\nDist.product: (List(Dist|Number)) => Dist\n\nDist.cumsum: (List(Dist|Number)) => List(Dist)\n\nDist.cumprod: (List(Dist|Number)) => List(Dist)\n\nDist.diff: (List(Dist|Number)) => List(Dist)\n\n## Pointwise Algebra\n\nPointwise 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.\n\nThe infixes `.+`,`.-`, `.*`, `./`, `.^` are supported for their respective operations. `Mixture` works using pointwise addition.\n\nPointwise operations work on Point Set distributions, so will convert other distributions to Point Set ones first. 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.\n\nDist.dotAdd: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotMultiply: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotSubtract: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotDivide: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotPow: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\n## Normalization\n\nThere 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.\n\nThe 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.\n\nDist.normalize: (Dist) => Dist\nNormalize 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.\n\nDist.isNormalized: (Dist) => Bool\nCheck if a distribution is normalized. This only impacts Point Set distributions, because those are the only ones that can be non-normlized. Most distributions are typically normalized, but there are some commands that could produce non-normalized distributions.\n\nDist.integralSum: (Dist) => Number\nGet 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.\n\n## Utility\n\nDist.sparkline: (Dist, Number?) => String\n\nProduce a sparkline of length `n`. For example, `▁▁▁▁▁▂▄▆▇██▇▆▄▂▁▁▁▁▁`. These can be useful for testing or quick visualizations that can be copied and pasted into text.\n\n## Scoring\n\nDist.klDivergence: (Dist, Dist) => Number\n[Kullback–Leibler divergence](https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence) between two distributions.\n\nNote 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.\nDist.klDivergence(Sym.normal(5,2), Sym.normal(5,1.5))\n\nDist.logScore: ({estimate: Dist, answer: Dist|Number, prior?: Dist}) => Number\nA 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.\n\n Note that it is fairly slow.\n\nDist.logScore({estimate: Sym.normal(5,2), answer: Sym.normal(5.2,1), prior: Sym.normal(5.5,3)})\nDist.logScore({estimate: Sym.normal(5,2), answer: Sym.normal(5.2,1)})\nDist.logScore({estimate: Sym.normal(5,2), answer: 4.5})\n\n---\n\n## description: Sample set distributions are one of the three distribution formats. Internally, they are stored as a list of numbers.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# SampleSet\n\nSample 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.\n\nMonte Carlo calculations typically result in sample set distributions.\n\nAll regular distribution function work on sample set distributions. In addition, there are several functions that only work on sample set distributions.\n\n## Constructors\n\nSampleSet.make: (Dist) => SampleSetDist, (Number) => SampleSetDist, (List(Number)) => SampleSetDist, ((index?: Number) => Number) => SampleSetDist\nCalls the correct conversion constructor, based on the corresponding input type, to create a sample set distribution\nSampleSet(5)\nSampleSet.make([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])\nSampleSet.make({|i| sample(normal(5,2))})\n\n## Conversions\n\nSampleSet.fromDist: (Dist) => SampleSetDist\nConverts any distribution type into a sample set distribution.\nSampleSet.fromDist(Sym.normal(5,2))\n\nSampleSet.fromNumber: (Number) => SampleSetDist\nConvert a number into a sample set distribution that contains `n` copies of that number. `n` refers to the model sample count.\nSampleSet.fromNumber(3)\n\nSampleSet.fromList: (List(Number)) => SampleSetDist\nConvert a list of numbers into a sample set distribution.\nSampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])\n\nSampleSet.toList: (SampleSetDist) => List(Number)\nGets the internal samples of a sampleSet distribution. This is separate from the `sampleN()` function, which would shuffle the samples. `toList()` maintains order and length.\nSampleSet.toList(SampleSet.fromDist(normal(5,2)))\n\nSampleSet.fromFn: ((index?: Number) => Number) => SampleSetDist\nConvert a function into a sample set distribution by calling it `n` times.\nSampleSet.fromFn({|i| sample(normal(5,2))})\n\n## Transformations\n\nSampleSet.map: (SampleSetDist, fn: (Number) => Number) => SampleSetDist\nTransforms a sample set distribution by applying a function to each sample. Returns a new sample set distribution.\nSampleSet.map(SampleSet.fromDist(normal(5,2)), {|x| x + 1})\n\nSampleSet.map2: (SampleSetDist, SampleSetDist, fn: (Number, Number) => Number) => SampleSetDist\nTransforms two sample set distributions by applying a function to each pair of samples. Returns a new sample set distribution.\nSampleSet.map2(\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\n{|x, y| x + y}\n)\n\nSampleSet.map3: (SampleSetDist, SampleSetDist, SampleSetDist, fn: (Number, Number, Number) => Number) => SampleSetDist\nSampleSet.map3(\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\n{|x, y, z| max([x,y,z])}\n)\n\nSampleSet.mapN: (List(SampleSetDist), fn: (List(Number)) => Number) => SampleSetDist\nSampleSet.mapN(\n[\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2))\n],\nmax\n)\n\n---\n\n## description: The Sym module provides functions to create some common symbolic distributions.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Sym\n\nSymbolic Distributions. All these functions match the functions for creating sample set distributions, but produce symbolic distributions instead. Symbolic distributions won\'t capture correlations, but are more performant than sample distributions.\n\nSym.normal: (Number, Number) => SymbolicDist, ({p5: Number, p95: Number}) => SymbolicDist, ({p10: Number, p90: Number}) => SymbolicDist, ({p25: Number, p75: Number}) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist\nSym.normal(5, 1)\nSym.normal({ p5: 4, p95: 10 })\nSym.normal({ p10: 4, p90: 10 })\nSym.normal({ p25: 4, p75: 10 })\nSym.normal({ mean: 5, stdev: 2 })\n\nSym.lognormal: (Number, Number) => SymbolicDist, ({p5: Number, p95: Number}) => SymbolicDist, ({p10: Number, p90: Number}) => SymbolicDist, ({p25: Number, p75: Number}) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist\nSym.lognormal(0.5, 0.8)\nSym.lognormal({ p5: 4, p95: 10 })\nSym.lognormal({ p10: 4, p90: 10 })\nSym.lognormal({ p25: 4, p75: 10 })\nSym.lognormal({ mean: 5, stdev: 2 })\n\nSym.uniform: (Number, Number) => SymbolicDist\nSym.uniform(10, 12)\n\nSym.beta: (Number, Number) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist\nSym.beta(20, 25)\nSym.beta({ mean: 0.39, stdev: 0.1 })\n\nSym.cauchy: (Number, Number) => SymbolicDist\nSym.cauchy(5, 1)\n\nSym.gamma: (Number, Number) => SymbolicDist\nSym.gamma(5, 1)\n\nSym.logistic: (Number, Number) => SymbolicDist\nSym.logistic(5, 1)\n\nSym.exponential: (Number) => SymbolicDist\nSym.exponential(2)\n\nSym.bernoulli: (Number) => SymbolicDist\nSym.bernoulli(0.5)\n\nSym.pointMass: (Number) => SymbolicDist\nPoint mass distributions are already symbolic, so you can use the regular `pointMass` function.\npointMass(0.5)\n\nSym.triangular: (Number, Number, Number) => SymbolicDist\nSym.triangular(3, 5, 10)\n\n---\n\n## 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.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# PointSet\n\nPoint 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.\n\nOne 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.\n\n## Constructors\n\nPointSet.make: (Dist) => PointSetDist, (Number) => PointSetDist\nPointSet.make(normal(5,10))\nPointSet(3)\n\nPointSet.makeContinuous: (List({x: Number, y: Number})) => PointSetDist\nPointSet.makeContinuous([\n{x: 0, y: 0.2},\n{x: 1, y: 0.7},\n{x: 2, y: 0.8},\n{x: 3, y: 0.2}\n])\n\nPointSet.makeDiscrete: (List({x: Number, y: Number})) => PointSetDist\nPointSet.makeDiscrete([\n{x: 0, y: 0.2},\n{x: 1, y: 0.7},\n{x: 2, y: 0.8},\n{x: 3, y: 0.2}\n])\n\n## Conversions\n\nPointSet.fromDist: (Dist) => PointSetDist\nConverts 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.\nPointSet.fromDist(normal(5,2))\n\nPointSet.fromNumber: (Number) => PointSetDist\nPointSet.fromNumber(3)\n\nPointSet.downsample: (PointSetDist, newLength: Number) => PointSetDist\nPointSet.downsample(PointSet.fromDist(normal(5,2)), 50)\n\nPointSet.support: (PointSetDist) => {points: List(Number), segments: List([Number, Number])}\nPointSet.support(PointSet.fromDist(normal(5,2)))\n\n## Transformations\n\nPointSet.mapY: (PointSetDist, fn: (Number) => Number) => PointSetDist\nPointSet.mapY(mx(Sym.normal(5,2)), {|x| x + 1})\n\n---\n\n## description: Durations are a simple time type, representing a length of time. They are internally stored as milliseconds, but often shown and written using seconds, minutes, hours, days, etc.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Duration\n\nDurations are a simple time type, representing a length of time. They are internally stored as milliseconds, but often shown and written using seconds, minutes, hours, days, etc. Durations are typically used with [Date](./Date) values.\n\n| **Unit Name** | **Example** | **Convert Number to Duration** | **Convert Duration to Number** |\n| ------------- | ----------- | ------------------------------ | ------------------------------ |\n| Minute | `5minutes` | `fromMinutes(number)` | `toMinutes(duration)` |\n| Hour | `5hour` | `fromHours(number)` | `toHours(duration)` |\n| Day | `5days` | `fromDays(number)` | `toDays(duration)` |\n| Year | `5years` | `fromYears(number)` | `toYears(duration)` |\n\n## Constructors\n\nDuration.fromMinutes: (Number) => Duration\nDuration.fromMinutes(5)\n\nDuration.fromHours: (Number) => Duration\nDuration.fromHours(5)\n\nDuration.fromDays: (Number) => Duration\nDuration.fromDays(5)\n\nDuration.fromYears: (Number) => Duration\nDuration.fromYears(5)\n\n## Conversions\n\nDuration.toMinutes: (Duration) => Number\nDuration.toMinutes(5minutes)\n\nDuration.toHours: (Duration) => Number\nDuration.toHours(5minutes)\n\nDuration.toDays: (Duration) => Number\nDuration.toDays(5minutes)\n\nDuration.toYears: (Duration) => Number\nDuration.toYears(5minutes)\n\n## Algebra\n\nDuration.unaryMinus -: (Duration) => Duration\n-5minutes\n\nDuration.add +: (Duration, Duration) => Duration\n5minutes + 10minutes\n\nDuration.subtract -: (Duration, Duration) => Duration\n5minutes - 10minutes\n\nDuration.multiply _: (Duration, Number) => Duration, (Number, Duration) => Duration\n5minutes _ 10\n10 \\* 5minutes\n\nDuration.divide /: (Duration, Duration) => Number\n5minutes / 2minutes\n\nDuration.divide /: (Duration, Duration) => Number\n5minutes / 2minutes\n\n## Comparison\n\nDuration.smaller <: (Duration, Duration) => Bool\n\nDuration.larger >: (Duration, Duration) => Bool\n\nDuration.smallerEq <=: (Duration, Duration) => Bool\n\nDuration.largerEq >=: (Duration, Duration) => Bool\n\n---\n\n## description: Lists are a simple data structure that can hold any type of value. They are similar to arrays in Javascript or lists in Python.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# List\n\nLists are a simple data structure that can hold any type of value. They are similar to arrays in Javascript or lists in Python.\n\n```squiggle\nmyList = [1, 2, 3, normal(5,2), "hello"]\n```\n\nLists are immutable, meaning that they cannot be modified. Instead, all list functions return a new list.\n\n## Constructors\n\nList.make: (count: Number, fn: (index?: Number) => \'A) => List(\'A), (count: Number, value: \'A) => List(\'A), (SampleSetDist) => List(Number)\nCreates an array of length `count`, with each element being `value`. If `value` is a function, it will be called `count` times, with the index as the argument.\nList.make(2, 3)\nList.make(2, {|| 3})\nList.make(2, {|index| index+1})\n\nList.upTo: (low: Number, high: Number) => List(Number)\nList.upTo(1,4)\n\n## Modifications\n\nList.reverse: (List(\'A)) => List(\'A)\nList.reverse([1,4,5]) // [5,4,1]\n\nList.concat: (List(\'A), List(\'A)) => List(\'A)\nList.concat([1,2,3], [4, 5, 6])\n\nList.sortBy: (List(\'A), fn: (\'A) => Number) => List(\'A)\nList.sortBy([{a:3}, {a:1}], {|f| f.a})\n\nList.append: (List(\'A), \'A) => List(\'A)\nList.append([1,4],5)\n\nList.join: (List(String), separator?: String) => String, (List(String)) => String\nList.join(["a", "b", "c"], ",") // "a,b,c"\n\nList.flatten: (List(any)) => List(any)\nList.flatten([[1,2], [3,4]])\n\nList.shuffle: (List(\'A)) => List(\'A)\nList.shuffle([1,3,4,20])\n\nList.zip: (List(\'A), List(\'B)) => List([\'A, \'B])\nList.zip([1,3,4,20], [2,4,5,6])\n\nList.unzip: (List([\'A, \'B])) => [List(\'A), List(\'B)]\nList.unzip([[1,2], [2,3], [4,5]])\n\n## Filtering\n\nList.slice: (List(\'A), startIndex: Number, endIndex?: Number) => List(\'A)\nReturns a copy of the list, between the selected `start` and `end`, end not included. Directly uses the [Javascript implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) underneath.\nList.slice([1,2,5,10],1,3)\n\nList.uniq: (List(\'A)) => List(\'A)\nFilters the list for unique elements. Works on select Squiggle types.\nList.uniq([1,2,3,"hi",false,"hi"])\n\nList.uniqBy: (List(\'A), (\'A) => \'B) => List(\'A)\nFilters the list for unique elements. Works on select Squiggle types.\nList.uniqBy([[1,5], [3,5], [5,7]], {|x| x[1]})\n\nList.filter: (List(\'A), fn: (\'A) => Bool) => List(\'A)\nList.filter([1,4,5], {|x| x>3})\n\n## Queries\n\nList.length: (List(any)) => Number\nList.length([1,4,5])\n\nList.first: (List(\'A)) => \'A\nList.first([1,4,5])\n\nList.last: (List(\'A)) => \'A\nList.last([1,4,5])\n\nList.minBy: (List(\'A), fn: (\'A) => Number) => \'A\nList.minBy([{a:3}, {a:1}], {|f| f.a})\n\nList.maxBy: (List(\'A), fn: (\'A) => Number) => \'A\nList.maxBy([{a:3}, {a:1}], {|f| f.a})\n\nList.every: (List(\'A), fn: (\'A) => Bool) => Bool\nList.every([1,4,5], {|el| el>3 })\n\nList.some: (List(\'A), fn: (\'A) => Bool) => Bool\nList.some([1,4,5], {|el| el>3 })\n\nList.find: (List(\'A), fn: (\'A) => Bool) => \'A\nReturns an error if there is no value found\nList.find([1,4,5], {|el| el>3 })\n\nList.findIndex: (List(\'A), fn: (\'A) => Bool) => Number\nReturns `-1` if there is no value found\nList.findIndex([1,4,5], {|el| el>3 })\n\n## Functional Transformations\n\nList.map: (List(\'A), (\'A, index?: Number) => \'B) => List(\'B)\nList.map([1,4,5], {|x| x+1})\nList.map([1,4,5], {|x,i| x+i+1})\n\nList.reduce: (List(\'B), initialValue: \'A, callbackFn: (accumulator: \'A, currentValue: \'B, currentIndex?: Number) => \'A) => \'A\nApplies `f` to each element of `arr`. The function `f` has two main paramaters, an accumulator and the next value from the array. It can also accept an optional third `index` parameter.\nList.reduce([1,4,5], 2, {|acc, el| acc+el})\n\nList.reduceReverse: (List(\'B), initialValue: \'A, callbackFn: (accumulator: \'A, currentValue: \'B) => \'A) => \'A\nWorks like `reduce`, but the function is applied to each item from the last back to the first.\nList.reduceReverse([1,4,5], 2, {|acc, el| acc-el})\n\nList.reduceWhile: (List(\'B), initialValue: \'A, callbackFn: (accumulator: \'A, currentValue: \'B) => \'A, conditionFn: (\'A) => Bool) => \'A\nWorks like `reduce`, but stops when the condition is no longer met. This is useful, in part, for simulating processes that need to stop based on the process state.\n\n// Adds first two elements, returns `11`.\nList.reduceWhile([5, 6, 7], 0, {|acc, curr| acc + curr}, {|acc| acc < 15})\n\n// Adds first two elements, returns `{ x: 11 }`.\nList.reduceWhile(\n[5, 6, 7],\n{ x: 0 },\n{|acc, curr| { x: acc.x + curr }},\n{|acc| acc.x < 15}\n)\n\n---\n\n## description: Simple constants and functions for math in Squiggle.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Math\n\n## Constants\n\n| Variable Name | Number Name | Value |\n| -------------- | --------------------------------------------------------------------------------- | -------------------- |\n| `Math.e` | Euler\'s number | ≈ 2.718281828459045 |\n| `Math.ln2` | Natural logarithm of 2 | ≈ 0.6931471805599453 |\n| `Math.ln10` | Natural logarithm of 10 | ≈ 2.302585092994046 |\n| `Math.log2e` | Base 2 logarithm of E | ≈ 1.4426950408889634 |\n| `Math.log10e` | Base 10 logarithm of E | ≈ 0.4342944819032518 |\n| `Math.pi` | Pi - ratio of the circumference to the diameter of a circle | ≈ 3.141592653589793 |\n| `Math.sqrt1_2` | Square root of 1/2 | ≈ 0.7071067811865476 |\n| `Math.sqrt2` | Square root of 2 | ≈ 1.4142135623730951 |\n| `Math.phi` | Phi is the golden ratio. | 1.618033988749895 |\n| `Math.tau` | Tau is the ratio constant of a circle\'s circumference to radius, equal to 2 \\* pi | 6.283185307179586 |\n\n## Functions\n\nMath.sqrt: (Number) => Number\n\nMath.sin: (Number) => Number\n\nMath.cos: (Number) => Number\n\nMath.tan: (Number) => Number\n\nMath.asin: (Number) => Number\n\nMath.acos: (Number) => Number\n\nMath.atan: (Number) => Number\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# MixedSet\n\nThe MixedSet module offers functionality for creating mixed sets, which are sets that can contain both discrete and continuous values. Discrete values are represented as points, while continuous values are represented as ranges. Mixed sets are particularly useful for describing the support of mixed probability distributions.\n\nThe majority of set functions in the MixedSet module are designed to mirror the [upcomming set functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) in Javascript.\n\nThe primary purpose of mixed sets in Squiggle is to facilitate scoring. For instance, by utilizing mixed sets, you can easily determine if one distribution covers the support of another distribution. If it doesn\'t, it may be prone to receiving a score of negative infinity.\n\nCurrently, there is no dedicated MixedSet object type. Instead, mixed sets are implemented as dictionaries, where discrete values are stored as points and continuous values are stored as segments.\n\nMixedSet.difference: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])}\n\nMixedSet.intersection: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])}\n\nMixedSet.union: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])}\n\nMixedSet.isSubsetOf: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.isSupersetOf: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.isEqual: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.isEmpty: ({points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.min: ({points: List(Number), segments: List([Number, Number])}) => Number\nReturns the minimum value in the set\n\nMixedSet.max: ({points: List(Number), segments: List([Number, Number])}) => Number\nReturns the maximum value in the set\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Plot\n\nThe Plot module provides functions to create plots of distributions and functions.\n\nRaw 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.\n\nPlot.dist: (dist: Dist, params?: {xScale?: Scale, yScale?: Scale, showSummary?: Bool}) => Plot\nPlot.dist(\nnormal(5, 2),\n{\nxScale: Scale.linear({ min: -2, max: 6, title: "X Axis Title" }),\nshowSummary: true,\n}\n)\n\nPlot.dists: (dists: List(Dist|Number)|List({name?: String, value: Dist|Number}), {xScale?: Scale, yScale?: Scale, showSummary?: Bool}?) => Plot\nPlot.dists(\n{\ndists: [\n{ name: "First Dist", value: normal(0, 1) },\n{ name: "Second Dist", value: uniform(2, 4) },\n],\nxScale: Scale.symlog({ min: -2, max: 5 }),\n}\n)\n\nPlot.numericFn: (fn: (Number) => Number, params?: {xScale?: Scale, yScale?: Scale, xPoints?: List(Number)}) => Plot\nPlot.numericFn(\n{|t|t ^ 2},\n{ xScale: Scale.log({ min: 1, max: 100 }), points: 10 }\n)\n\nPlot.distFn: (fn: (Number) => Dist, params?: {xScale?: Scale, yScale?: Scale, distXScale?: Scale, xPoints?: List(Number)}) => Plot\nPlot.distFn(\n{|t|normal(t, 2) \\* normal(5, 3)},\n{\nxScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }),\nyScale: Scale.linear({ title: "Value" }),\ndistXScale: Scale.linear({ tickFormat: "#x" }),\n}\n)\n\nPlot.scatter: ({xDist: SampleSetDist, yDist: SampleSetDist, xScale?: Scale, yScale?: Scale}) => Plot\nxDist = SampleSet.fromDist(2 to 5)\nyDist = normal({p5:-3, p95:3}) _ 5 - xDist ^ 2\nPlot.scatter({\nxDist: xDist,\nyDist: yDist,\nxScale: Scale.log({min: 1.5}),\n})\nxDist = SampleSet.fromDist(normal({p5:-2, p95:5}))\nyDist = normal({p5:-3, p95:3}) _ 5 - xDist\nPlot.scatter({\nxDist: xDist,\nyDist: yDist,\nxScale: Scale.symlog({title: "X Axis Title"}),\nyScale: Scale.symlog({title: "Y Axis Title"}),\n})\n\n---\n\n## description: Squiggle numbers are Javascript floats.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Number\n\nSquiggle numbers are Javascript floats.\n\n## Comparison\n\nNumber.smaller <: (Number, Number) => Bool\n\nNumber.larger >: (Number, Number) => Bool\n\nNumber.smallerEq <=: (Number, Number) => Bool\n\nNumber.largerEq >=: (Number, Number) => Bool\n\n## Algebra (Number)\n\nNumber.add +: (Number, Number) => Number\n\nNumber.subtract -: (Number, Number) => Number\n\nNumber.multiply \\*: (Number, Number) => Number\n\nNumber.divide /: (Number, Number) => Number\n\nNumber.pow ^: (Number, Number) => Number\n\n## Functions (Number)\n\nNumber.unaryMinus -: (Number) => Number\nexp(3.5)\n\nNumber.exp: (Number) => Number\nexponent\nexp(3.5)\n\nNumber.log: (Number) => Number\nlog(3.5)\n\nNumber.log10: (Number) => Number\nlog10(3.5)\n\nNumber.log2: (Number) => Number\nlog2(3.5)\n\nNumber.floor: (Number) => Number\nfloor(3.5)\n\nNumber.ceil: (Number) => Number\nceil(3.5)\n\nNumber.abs: (Number) => Number\nabsolute value\nabs(3.5)\n\nNumber.round: (Number) => Number\nround(3.5)\n\n## Algebra (List)\n\nNumber.sum: (List(Number)) => Number\nsum([3,5,2])\n\nNumber.product: (List(Number)) => Number\nproduct([3,5,2])\n\nNumber.cumprod: (List(Number)) => List(Number)\ncumulative product\ncumprod([3,5,2,3,5])\n\nNumber.diff: (List(Number)) => List(Number)\ndiff([3,5,2,3,5])\n\n## Functions (List)\n\nNumber.min: (List(Number)) => Number, (Number, Number) => Number\nmin([3,5,2])\n\nNumber.max: (List(Number)) => Number, (Number, Number) => Number\nmax([3,5,2])\n\nNumber.mean: (List(Number)) => Number\nmean([3,5,2])\n\nNumber.quantile: (List(Number), Number) => Number\nquantile([1,5,10,40,2,4], 0.3)\n\nNumber.median: (List(Number)) => Number\nmedian([1,5,10,40,2,4])\n\nNumber.geomean: (List(Number)) => Number\ngeometric mean\ngeomean([3,5,2])\n\nNumber.stdev: (List(Number)) => Number\nstandard deviation\nstdev([3,5,2,3,5])\n\nNumber.variance: (List(Number)) => Number\nvariance([3,5,2,3,5])\n\nNumber.sort: (List(Number)) => List(Number)\nsort([3,5,2,3,5])\n\n## Utils\n\nNumber.rangeDomain: (min: Number, max: Number) => Domain\nNumber.rangeDomain(5, 10)\n\n---\n\n## description: Scales for plots.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Scale\n\nChart axes in [plots](./Plot.mdx) can be scaled using the following functions. Each scale function accepts optional min and max value. Power scale accepts an extra exponent parameter.\n\nSquiggle uses D3 for the tick formats. You can read about d3 tick formats [here](https://github.com/d3/d3-format).\n\n## Numeric Scales\n\nScale.linear: ({min?: Number, max?: Number, tickFormat?: String, title?: String}) => Scale, () => Scale\nScale.linear({ min: 3, max: 10 })\n\nScale.log: ({min?: Number, max?: Number, tickFormat?: String, title?: String}) => Scale, () => Scale\nScale.log({ min: 1, max: 100 })\n\nScale.symlog: ({min?: Number, max?: Number, tickFormat?: String, title?: String, constant?: Number}) => Scale, () => Scale\nSymmetric log scale. Useful for plotting data that includes zero or negative values.\n\nThe 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).\n\nThe default value for `constant` is `0.0001`.\nScale.symlog({ min: -10, max: 10 })\n\nScale.power: ({min?: Number, max?: Number, tickFormat?: String, title?: String, exponent?: Number}) => Scale, () => Scale\nPower scale. Accepts an extra `exponent` parameter, like, `Scale.power({exponent: 2, min: 0, max: 100})`.\n\nThe default value for `exponent` is `0.1`.\nScale.power({ min: 1, max: 100, exponent: 0.1 })\n\n## Date Scales\n\nScale.date: ({min?: Date, max?: Date, tickFormat?: String, title?: String}) => Scale, () => Scale\nOnly works on Date values. Is a linear scale under the hood.\nScale.date({ min: Date(2022), max: Date(2025) })\n\n---\n\n## description: Function Specifications\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Spec\n\nFunction specifications (Specs) are an experimental feature in Squiggle. They are used to specify the structure of functions and verify that they match that structure. They are used primarily as a tag for functions.\n\nSpec.make: ({name: String, documentation: String, validate: Function}) => Specification\nCreate a specification.\n@startClosed\nvalidate(fn) = {\nhasErrors = List.upTo(2020, 2030)\n-> List.some(\n{|e| typeOf(fn(Date(e))) != "Distribution"}\n)\nhasErrors ? "Some results aren\'t distributions" : ""\n}\n\nspec = Spec.make(\n{\nname: "Stock market over time",\ndocumentation: "A distribution of stock market values over time.",\nvalidate: validate,\n}\n)\n\n@spec(spec)\nmyEstimate(t: [Date(2020), Date(2030)]) = normal(10, 3)\n\n---\n\n## description: Functions for working with strings in Squiggle\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# String\n\nStrings support all JSON escape sequences, with addition of escaped single-quotes (for single-quoted strings)\n\n```squiggle\na = "\'\\" NUL:\\u0000"\nb = \'\\\'" NUL:\\u0000\'\n```\n\nString.make: (any) => String\nConverts any value to a string. Some information is often lost.\n\nString.concat: (String, String) => String, (String, any) => String\n\nString.add +: (String, String) => String, (String, any) => String\n\nString.split: (String, separator: String) => List(String)\n\n---\n\n## description: Tables are a simple date time type.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Table\n\nThe Table module allows you to make simple tables for displaying data.\n\nTable.make: (data: List(\'A), params: {columns: List({fn: (\'A) => any, name?: String})}) => Table\nTable.make(\n[\n{ name: "First Dist", value: normal(0, 1) },\n{ name: "Second Dist", value: uniform(2, 4) },\n{ name: "Third Dist", value: uniform(5, 6) },\n],\n{\ncolumns: [\n{ name: "Name", fn: {|d|d.name} },\n{ name: "Mean", fn: {|d|mean(d.value)} },\n{ name: "Std Dev", fn: {|d|variance(d.value)} },\n{ name: "Dist", fn: {|d|d.value} },\n],\n}\n)\nTable.make(\n[\n{ name: "First Dist", value: Sym.lognormal({ p5: 1, p95: 10 }) },\n{ name: "Second Dist", value: Sym.lognormal({ p5: 5, p95: 30 }) },\n{ name: "Third Dist", value: Sym.lognormal({ p5: 50, p95: 90 }) },\n],\n{\ncolumns: [\n{ name: "Name", fn: {|d|d.name} },\n{\nname: "Plot",\nfn: {\n|d|\nPlot.dist(\n{\ndist: d.value,\nxScale: Scale.log({ min: 0.5, max: 100 }),\nshowSummary: false,\n}\n)\n},\n},\n],\n}\n)\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# System\n\n## Constants\n\n### System.version\n\nReturns the current version of Squiggle.\n\n## Functions\n\nSystem.sampleCount: () => Number\nThe number of samples set in the current environment. This variable can be modified in the Squiggle playground settings.\n\n---\n\n## description: The Tag module handles tags, which allow the additions of metadata to Squiggle variables.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Tag\n\nTags are metadata that can be added to Squiggle variables. They are used to add additional information to variables, such as names, descriptions, and visualization options. While tags can be accessed at runtime, they are primarily meant for use with the Squiggle Playground and other visualizations.\nTags can be added to variables either by using their name `Tag.get[Name]` or by using decorators.\n\n## List of Tags\n\n| Tag Name | Description |\n| ------------- | ------------------------------------------------------------------------------------------ |\n| `name` | Change the default display name for the variable, in the playground. |\n| `doc` | Adds documentation to the variable in the playground. |\n| `showAs` | Change the default view for the value when displayed. |\n| `format` | Format a number, date, or duration when displayed. |\n| `notebook` | Formats lists as notebooks. |\n| `hide` | Don\'t show the variable in the playground |\n| `startOpen` | Start the variable open in the playground |\n| `startClosed` | Start the variable closed in the playground |\n| `location` | Store the proper location. Helps when you want to locate code corresponding to a variable. |\n| `exportData` | Metadata about exported variables. Cannot be added manually. |\n\n## Example\n\n\n\n## Tags\n\nTag.name: (\'A, String) => \'A\nAdds a user-facing name to a value. This is useful for documenting what a value represents, or how it was calculated.\n\n_Note: While names are shown in the sidebar, you still need to call variables by their regular variable names in code._\n\nTag.getName: (any) => String\n\nTag.doc: (\'A, String) => \'A\nAdds text documentation to a value. This is useful for documenting what a value represents or how it was calculated.\n\nTag.getDoc: (any) => String\n\nTag.showAs: (Dist, Plot|(Dist) => Plot) => Dist, (List(any), Table|(List(any)) => Table) => List(any), ((Number) => Dist|Number, Plot|Calculator|((Number) => Dist|Number) => Plot|Calculator) => (Number) => Dist|Number, ((Date) => Dist|Number, Plot|Calculator|((Date) => Dist|Number) => Plot|Calculator) => (Date) => Dist|Number, ((Duration) => Dist|Number, Plot|Calculator|((Duration) => Dist|Number) => Plot|Calculator) => (Duration) => Dist|Number, (Function, Calculator|(Function) => Calculator) => Function\nOverrides the default visualization for a value.\n`showAs()` can take either a visualization, or a function that calls the value and returns a visualization.\n\nDifferent types of values can be displayed in different ways. The following table shows the potential visualization types for each input type. In this table, `Number` can be used with Dates and Durations as well.\n| **Input Type** | **Visualization Types** |\n| ----------------------------------- | ------------------------------------- |\n| **Distribution** | `Plot.dist` |\n| **List** | `Table` |\n| **`(Number -> Number)` Function** | `Plot.numericFn`, `Calculator` |\n| **`(Number -> Dist)` Function** | `Plot.distFn`, `Calculator` |\n| **Function** | `Calculator` |\n\nexample1 = ({|x| x + 1}) -> Tag.showAs(Calculator)\n@showAs({|f| Plot.numericFn(f, { xScale: Scale.symlog() })})\nexample2 = {|x| x + 1}\n\nTag.getShowAs: (any) => any\n\nTag.getExportData: (any) => any\n\nTag.spec: (\'A, Specification) => \'A\nAdds a specification to a value. This is useful for documenting how a value was calculated, or what it represents.\n\nTag.getSpec: (any) => any\n\nTag.format: (Dist|Number, numberFormat: String) => Dist|Number, (Duration, numberFormat: String) => Duration, (Date, timeFormat: String) => Date\nSet the display format for a number, distribution, duration, or date. Uses the [d3-format](https://d3js.org/d3-format) syntax on numbers and distributions, and the [d3-time-format](https://d3js.org/d3-time-format) syntax for dates.\n\nTag.getFormat: (Dist|Number) => String, (Duration) => String, (Date) => String\n\nTag.hide: (\'A, Bool) => \'A, (\'A) => \'A\nHides a value when displayed under Variables. This is useful for hiding intermediate values or helper functions that are used in calculations, but are not directly relevant to the user. Only hides top-level variables.\n\nTag.getHide: (any) => Bool\n\nTag.startOpen: (\'A) => \'A\nWhen the value is first displayed, it will begin open in the viewer. Refresh the page to reset.\n\nTag.startClosed: (\'A) => \'A\nWhen the value is first displayed, it will begin collapsed in the viewer. Refresh the page to reset.\n\nTag.getStartOpenState: (any) => String\nReturns the startOpenState of a value, which can be "open", "closed", or "" if no startOpenState is set. Set using `Tag.startOpen` and `Tag.startClosed`.\n\nTag.notebook: (List(\'A), Bool) => List(\'A), (List(\'A)) => List(\'A)\nDisplays the list of values as a notebook. This means that element indices are hidden, and the values are displayed in a vertical list. Useful for displaying combinations of text and values.\nCalculator.make(\n{|f, contents| f ? Tag.notebook(contents) : contents},\n{\ndescription: "Shows the contents as a notebook if the checkbox is checked.",\ninputs: [\nInput.checkbox({ name: "Show as Notebook", default: true }),\nInput.textArea(\n{\nname: "Contents to show",\ndefault: "[\n\\"## Distribution 1\\",\nnormal(5, 2),\n\\"## Distribution 1\\",\nnormal(20, 1),\n\\"This is an opening section. Here is more text.\n\\",\n]",\n}\n),\n],\n}\n)\n\nTag.getNotebook: (any) => Bool\n\nTag.location: (\'A) => \'A\nSaves the location of a value. Note that this must be called at the point where the location is to be saved. If you use it in a helper function, it will save the location of the helper function, not the location where the helper function is called.\n\nTag.getLocation: (any) => any\n\n## Functions\n\nTag.getAll: (any) => Dict(any)\nReturns a dictionary of all tags on a value.\n\nTag.omit: (\'A, List(String)) => \'A\nReturns a copy of the value with the specified tags removed.\n\nTag.clear: (\'A) => \'A\nReturns a copy of the value with all tags removed.\n\n---\n\n## description: The Calculator module helps you create custom calculators\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Calculator\n\nThe Calculator module allows you to make custom calculators for functions. This is a form that\'s tied to a specific Squiggle function, where the inputs to the form are passed to that function, and the output of the function gets shown on the bottom.\n\nCalculators can be useful for debugging functions or to present functions to end users.\n\nCalculator.make: ({fn: Function, title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator, (Function, params?: {title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator\n\n`Calculator.make` takes in a function, a description, and a list of inputs. The function should take in the same number of arguments as the number of inputs, and the arguments should be of the same type as the default value of the input.\n\nInputs are created using the `Input` module. The Input module has a few different functions for creating different types of inputs.\n\nFor calculators that take a long time to run, we recommend setting `autorun` to `false`. This will create a button that the user can click to run the calculator.\n\nCalculator.make(\n{|text, textArea, select, checkbox| text + textArea},\n{\ntitle: "My example calculator",\ninputs: [\nInput.text({ name: "text", default: "20" }),\nInput.textArea({ name: "textArea", default: "50 to 80" }),\nInput.select({ name: "select", default: "second", options: ["first", "second", "third"] }),\nInput.checkbox({ name: "checkbox", default: true }),\n],\nsampleCount: 10k,\n})\n// When a calculator is created with only a function, it will guess the inputs based on the function\'s parameters. It won\'t provide default values if it\'s a user-written function.\n\n({|x| x \\* 5}) -> Calculator\n\n---\n\n## description: Inputs are now only used for describing forms for calculators.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Input\n\nInputs are now only used for describing forms for [calculators](./Calculator.mdx).\n\nInput.text: ({name: String, description?: String, default?: Number|String}) => Input\nCreates a single-line input. This input can be used for all Squiggle types.\nInput.text({ name: "First", default: "John" })\nInput.text({ name: "Number of X in Y", default: \'20 to 300\' })\n\nInput.textArea: ({name: String, description?: String, default?: Number|String}) => Input\nCreates a multi-line input, sized with the provided input. This input can be used for all Squiggle types.\nInput.textArea({ name: "people", default: \'{\n"John": 20 to 50,\n"Mary": 30 to 90,\n}\' })\n\nInput.checkbox: ({name: String, description?: String, default?: Bool}) => Input\nCreates a checkbox input. Used for Squiggle booleans.\nInput.checkbox({ name: "IsTrue?", default: true })\n\nInput.select: ({name: String, description?: String, options: List(String), default?: String}) => Input\nCreates a dropdown input. Used for Squiggle strings.\nInput.select({ name: "Name", default: "Sue", options: ["John", "Mary", "Sue"] })\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# RelativeValues\n\n_Warning: Relative value functions are particularly experimental and subject to change._\n\nRelativeValues.gridPlot: ({ids: List(String), fn: (String, String) => List(Number)}) => Plot\nRelativeValues.gridPlot({\nids: ["foo", "bar"],\nfn: {|id1, id2| [SampleSet.fromDist(2 to 5), SampleSet.fromDist(3 to 6)]},\n})\n\n---\n\n## description: Newer experimental functions which are less stable than Squiggle as a whole\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Danger\n\nThe Danger library contains newer experimental functions which are less stable than Squiggle as a whole. They are not recommended for production use, but are useful for testing out new ideas.,\n\n## JSON\n\nThe JSON module provides JSON-like objects in Squiggle. `Danger.json` is mainly useful for debugging, and `Danger.jsonString` is useful for sending data to other systems. A simple example is shown below.\n\nWe have custom serializers for different Squiggle objects. Note that this API is unstable and might change over time.\n\n\n\nDanger.json: (any) => any\nConverts a value to a simpler form, similar to JSON. This is useful for debugging. Keeps functions and dates, but converts objects like distributions, calculators, and plots to combinations of dictionaries and lists.\nDanger.json({a: 1, b: 2})\nDanger.json([2 to 5, Sym.normal(5, 2), Calculator({|x| x + 1})])\n\nDanger.jsonString: (any) => String\nConverts a value to a stringified JSON, similar to JSON.stringify() in Javasript. Replaces functions with dict summaries.\nDanger.jsonString({a: 1, b: 2})\nDanger.jsonString([2 to 5, Sym.normal(5, 2), Calculator({|x| x + 1})])\n\n## Javascript\n\nNear 1-1 matches of Javascript functions.\n\nDanger.parseFloat: (String) => Number|String\nConverts a string to a number. If the string can\'t be converted, returns `Parse Failed`. Calls Javascript `parseFloat` under the hood.\nDanger.parseFloat(\'10.3\')\n\nDanger.now: () => Date\nReturns the current date. Internally calls `Date.now()` in JavaScript.\n\n_Caution: This function, which returns the current date, produces varying outputs with each call. As a result, accurately estimating the value of functions that incorporate `Danger.now()` at past time points is challenging. In the future, we intend to implement a feature allowing the input of a simulated time via an environment variable to address this issue._\nDanger.now()\n\n## Math\n\nDanger.laplace: (Number, Number) => Number\nCalculates the probability implied by [Laplace\'s rule of succession](https://en.wikipedia.org/wiki/Rule_of_succession)\ntrials = 10\nsuccesses = 1\nDanger.laplace(successes, trials) // (successes + 1) / (trials + 2) = 2 / 12 = 0.1666\n\nDanger.yTransform: (PointSetDist) => PointSetDist\nDanger.yTransform(PointSet(Sym.normal(5,2)))\n\n## Combinatorics\n\nDanger.factorial: (Number) => Number\nDanger.factorial(20)\n\nDanger.choose: (Number, Number) => Number\n`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).\nDanger.choose(1, 20)\n\nDanger.binomial: (Number, Number, Number) => Number\n`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.\nDanger.binomial(1, 20, 0.5)\n\nDanger.combinations: (List(\'A), Number) => List(List(\'A))\nReturns all combinations of the input list taken r elements at a time.\nDanger.combinations([1, 2, 3], 2) // [[1, 2], [1, 3], [2, 3]]\n\nDanger.allCombinations: (List(\'A)) => List(List(\'A))\nReturns all possible combinations of the elements in the input list.\nDanger.allCombinations([1, 2, 3]) // [[1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]\n\n## Distributions\n\nDanger.binomialDist: (numberOfTrials: Dist|Number, probabilityOfSuccess: Dist|Number) => SampleSetDist\nA binomial distribution.\n\n`n` must be above 0, and `p` must be between 0 and 1.\n\nNote: The binomial distribution is a discrete distribution. When representing this, the Squiggle distribution component might show it as partially or fully continuous. This is a visual mistake; if you inspect the underlying data, it should be discrete.\nDanger.binomialDist(8, 0.5)\n\nDanger.poissonDist: (rate: Dist|Number) => SampleSetDist\nA Poisson distribution.\n\nNote: The Poisson distribution is a discrete distribution. When representing this, the Squiggle distribution component might show it as partially or fully continuous. This is a visual mistake; if you inspect the underlying data, it should be discrete.\nDanger.poissonDist(10)\n\n## Integration\n\nDanger.integrateFunctionBetweenWithNumIntegrationPoints: (f: Function, min: Number, max: Number, numIntegrationPoints: Number) => Number\nIntegrates the function `f` between `min` and `max`, and computes `numIntegrationPoints` in between to do so.\n\nNote that the function `f` has to take in and return numbers. To integrate a function which returns distributions, use:\n\n```squiggle\nauxiliaryF(x) = mean(f(x))\n\nDanger.integrateFunctionBetweenWithNumIntegrationPoints(auxiliaryF, min, max, numIntegrationPoints)\n```\n\nDanger.integrateFunctionBetweenWithNumIntegrationPoints({|x| x+1}, 1, 10, 10)\n\nDanger.integrateFunctionBetweenWithEpsilon: (f: Function, min: Number, max: Number, epsilon: Number) => Number\nIntegrates 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`.\n\nSame caveats as `integrateFunctionBetweenWithNumIntegrationPoints` apply.\nDanger.integrateFunctionBetweenWithEpsilon({|x| x+1}, 1, 10, 0.1)\n\n## Optimization\n\nDanger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions: (fs: List(Function), funds: Number, approximateIncrement: Number) => any\nComputes 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)`.\nDanger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions(\n[\n{|x| x+1},\n{|y| 10}\n],\n100,\n0.01\n)\n\n```\n\n```\n\n```\n\n```\n'; + '# Squiggle Language Guidelines\n\nWhen writing Squiggle code, it\'s important to avoid certain common mistakes.\n\n### Syntax and Structure\n\n1. Variable Expansion: Not supported. Don\'t use syntax like |v...| or |...v|.\n2. All pipes are "->", not "|>".\n3. Dict keys and variable names must be lowercase.\n4. The last value in a block/function is returned (no "return" keyword).\n5. Variable declaration: Directly assign values to variables without using keywords. For example, use `foo = 3` instead of `let foo = 3`.\n6. All statements in your model, besides the last one must either be comments or variable declarations. You can\'t do, `4 \\n 5 \\n 6` Similarly, you can\'t do, `Calculator() ... Table()` - instead, you need to set everything but the last item to a variable.\n7. There\'s no mod operator (%). Use `Number.mod()` instead.\n\n### Function Definitions and Use\n\n1. Anonymous Functions: Use {|e| e} syntax for anonymous functions.\n2. Function Parameters: When using functions like normal, specify the standard deviation with stdev instead of sd. For example, use normal({mean: 0.3, stdev: 0.1}) instead of normal({mean: 0.3, sd: 0.1}).\n3. There\'s no recursion.\n4. You can\'t call functions that accept ranges, with distributions. No, `({|foo: [1,20]| foo}) (4 to 5)`.\n\n### Data Types and Input Handling\n\n1. Input Types: Use Input.text for numeric inputs instead of Input.number or Input.slider.\n2. The only function param types you can provide are numeric/date ranges, for numbers. f(n:[1,10]). Nothing else is valid. You cannot provide regular input type declarations.\n3. Only use Inputs directly inside calculators. They won\'t return numbers, just input types.\n\n### Looping, Conditionals, and Data Operations\n\n1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic.\n2. There aren\'t for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile.\n3. Remember to use `Number.sum` and `Number.product`, instead of using Reduce in those cases.\n\n### List and Dictionary Operations\n\n1. You can\'t do "(0..years)". Use List.make or List.upTo.\n2. There\'s no "List.sort", but there is "List.sortBy", "Number.sort".\n\n### Randomness and Distribution Handling\n\n1. There\'s no random() function. Use alternatives like sample(uniform(0,1)).\n2. When representing percentages, use "5%" instead of "0.05" for readability.\n3. The `to` syntax only works for >0 values. "4 to 10", not "0 to 10".\n\n### Units and Scales\n\n1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01).\n2. If you make a table that contains a column of similar distributions, use a scale to ensure consistent min and max.\n3. Scale.symlog() has support for negative values, Scale.log() doesn\'t. Scale.symlog() is often a better choice for this reason, though Scale.log() is better when you are sure values are above 0.\n4. Do use Scale.symlog() and Scale.log() on dists/plots that might need it. Many do!\n\n### Documentation and Comments\n\n1. Tags like @name and @doc apply to the following variable, not the full file.\n2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022.\n\n---\n\nThis format provides a clear and organized view of the guidelines for writing Squiggle code.\n\nHere\'s are some simple example Squiggle programs:\n\n```squiggle\n//Model for Piano Tuners in New York Over Time\n\n@name("🌆 Population of New York in 2022")\n@doc("I\'m really not sure here, this is a quick guess.")\npopulationOfNewYork2022 = 8.1M to 8.4M\n\n@name("🎹 Percentage of Population with Pianos")\n@format(".1%")\nproportionOfPopulationWithPianos = 0.2% to 1%\n\n@name("🔧 Number of Piano Tuners per Piano")\npianoTunersPerPiano = {\n pianosPerPianoTuner = 2k to 50k\n 1 / pianosPerPianoTuner\n}\n\n//We only mean to make an estimate for the next 10 years.\n@hide\ndomain = [Date(2024), Date(2034)]\n\n@name("Population at Time")\npopulationAtTime(t: domain) = {\n dateDiff = Duration.toYears(t - Date(2024))\n averageYearlyPercentageChange = normal({ p5: -1%, p95: 5% }) // We\'re expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year\n populationOfNewYork2022 * (averageYearlyPercentageChange + 1) ^ dateDiff\n}\n\n@name("Total Tuners, at Time")\ntotalTunersAtTime(t: domain) = populationAtTime(t) *\n proportionOfPopulationWithPianos *\n pianoTunersPerPiano\n\nmeanTunersAtTime(t: domain) = mean(totalTunersAtTime(t))\n```\n\n```squiggle\ncalculator = Calculator(\n {|a, b, c, d| [a, b, c, d]},\n {\n title: "Concat()",\n description: "This function takes in 4 arguments, then displays them",\n sampleCount: 10000,\n inputs: [\n Input.text(\n {\n name: "First Param",\n default: "10 to 13",\n description: "Must be a number or distribution",\n }\n ),\n Input.textArea(\n {\n name: "Second Param",\n default: "[4,5,2,3,4,5,3,3,2,2,2,3,3,4,45,5,5,2,1]",\n }\n ),\n Input.select(\n {\n name: "Third Param",\n default: "Option 1",\n options: ["Option 1", "Option 2", "Option 3"],\n }\n ),\n Input.checkbox({ name: "Fourth Param", default: false }),\n ],\n }\n)\n\n```\n\n```squiggle\n// Cost-benefit analysis for a housing addition in berkeley\n\n// Input section\n@name("Model Inputs")\n@doc("Key parameters for the housing development project")\ninputs = {\n landCost: 1M to 2M,\n constructionCost: 500k to 800k,\n permitFees: 50k to 100k,\n numberOfHomes: 10,\n monthlyRentalIncome: 3k to 5k,\n annualPropertyAppreciation: 2% to 5%,\n annualSocialBenefit: 10k to 30k,\n yearsToConsider: 30,\n}\n\n// Calculation section\n@name("Calculations")\n@doc("Core calculations for the cost-benefit analysis")\ncalculations(i) = {\n totalCostPerHome = i.landCost + i.constructionCost + i.permitFees\n annualRentalIncome = i.numberOfHomes * i.monthlyRentalIncome * 12\n totalCost = i.numberOfHomes * totalCostPerHome\n\n annualAppreciation(year) = i.numberOfHomes * totalCostPerHome *\n ((1 + i.annualPropertyAppreciation) ^ year -\n (1 + i.annualPropertyAppreciation) ^ (year - 1))\n\n annualBenefit(year) = annualRentalIncome + annualAppreciation(year) +\n i.numberOfHomes * i.annualSocialBenefit\n\n totalBenefit = List.upTo(1, i.yearsToConsider) -> List.map(annualBenefit)\n -> List.reduce(\n 0,\n {|acc, val| acc + val}\n )\n\n netBenefit = totalBenefit - totalCost\n probPositiveNetBenefit = 1 - cdf(netBenefit, 0)\n\n {\n totalCostPerHome: totalCostPerHome,\n annualRentalIncome: annualRentalIncome,\n totalCost: totalCost,\n totalBenefit: totalBenefit,\n netBenefit: netBenefit,\n probPositiveNetBenefit: probPositiveNetBenefit,\n }\n}\n\n// Apply calculations to inputs\n@name("Results")\n@doc("Output of calculations based on input parameters")\nresults = calculations(inputs)\n\n// Analysis section\n@name("Cost-Benefit Analysis")\n@doc("Detailed analysis of the housing development project")\nanalysis = {\n costsTable = Table.make(\n [\n { name: "Land Cost per Home", value: inputs.landCost },\n { name: "Construction Cost per Home", value: inputs.constructionCost },\n { name: "Permit Fees per Home", value: inputs.permitFees },\n { name: "Total Cost per Home", value: results.totalCostPerHome },\n { name: "Total Cost for 10 Homes", value: results.totalCost },\n ],\n {\n columns: [\n { name: "Item", fn: {|r| r.name} },\n {\n name: "Cost",\n fn: {\n |r|\n Plot.dist(\n r.value,\n {\n xScale: Scale.log({ tickFormat: "($.1s", min: 20k, max: 200M }),\n }\n )\n },\n },\n ],\n }\n )\n\n benefitTable = Table.make(\n [\n {\n name: "Monthly Rental Income per Home",\n value: inputs.monthlyRentalIncome,\n },\n {\n name: "Annual Social Benefit per Home",\n value: inputs.annualSocialBenefit,\n },\n { name: "Total Benefit over 30 years", value: results.totalBenefit },\n ],\n {\n columns: [\n { name: "Item", fn: {|r| r.name} },\n {\n name: "Value",\n fn: {\n |r|\n Plot.dist(\n r.value,\n { xScale: Scale.linear({ tickFormat: "($.1s" }) }\n )\n },\n },\n ],\n }\n )\n\n netBenefitPlot = Plot.dist(\n results.netBenefit,\n {\n title: "Distribution of Net Benefit",\n xScale: Scale.log({ tickFormat: "($.1s", min: 10M, max: 200M }),\n }\n )\n\n {\n title: "Cost-Benefit Analysis: Adding 10 Homes to Berkeley, CA",\n costs: costsTable,\n benefits: benefitTable,\n netBenefit: netBenefitPlot,\n probabilityOfPositiveNetBenefit: results.probPositiveNetBenefit,\n }\n}\n```\n\n```squiggle\nx = 10\nresult = if x == 1 then {\n {y: 2, z: 0}\n} else {\n {y: 0, z: 4}\n}\ny = result.y\nz = result.z\n```\n\n```squiggle\n@showAs({|f| Plot.numericFn(f, { xScale: Scale.log({ min: 1, max: 100 }) })})\nfn(t) = t ^ 2\n```\n\n```squiggle\nplot = {|t| normal(t, 2) * normal(5, 3)}\n -> Plot.distFn(\n {\n title: "A Function of Value over Time",\n xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }),\n yScale: Scale.linear({ title: "Value" }),\n distXScale: Scale.linear({ tickFormat: "#x" }),\n }\n )\n```\n\n```squiggle\nf(t: [Date(2020), Date(2040)]) = {\n yearsPassed = toYears(t - Date(2020))\n normal({mean: yearsPassed ^ 2, stdev: yearsPassed^1.3+1})\n}\n```\n\n````squiggle\nimport "hub:ozziegooen/sTest" as sTest\n@name("💰 Expected Cost ($)")\n@format("$.2s")\nflightCost = normal({ mean: 600, stdev: 100 })\n\n@name("🥇 Expected Benefit ($)")\n@format("$.2s")\nbenefitEstimate = normal({ mean: 1500, stdev: 300 })\n\n@name("📊 Net Benefit ($)")\n@format("$.2s")\nnetBenefit = benefitEstimate - flightCost\n\n@name("🚦 Test Suite")\n@doc(\n "Test suite to validate various aspects of the flight cost and benefits model using sTest."\n)\ntests = sTest.describe(\n "Flight to London Test Suite",\n [\n // Test for reasonable flight costs\n sTest.test(\n "Flight cost should be reasonable",\n {\n ||\n meanValue = mean(flightCost)\n sTest.expect(meanValue).toBeBetween(300, 10k)\n }\n ),\n ]\n)\n\n# Squiggle Style Guide\n\n## Limitations\n\n- There are floating point errors at high numbers (1e50 and above) and very small numbers (1e-10 and below). If you need to work with these, use logarithms if possible.\n\n## Data and Calculations\n\n### Estimations\n\n- When using the "to" format, like "3 to 10", remember that this represents the 5th and 95th percentile. This is a very large range. Be paranoid about being overconfident and too narrow in your estimates.\n- One good technique, when you think there\'s a chance that you might be very wrong about a variable, is to use a mixture that contains a very wide distribution. For example, `mx([300 to 400, 50 to 5000], [0.9, 0.1])`, or `mx([50k to 60k, 1k to 1M], [0.95, 0.05])`. This way if you are caught by surprise, the wide distribution will still give you a reasonable outcome.\n- Be wary of using the uniform or the PERT distributions. The uniform distribution is mainly good for physical simulations.\n- If the outcome of a model is an extreme probability (<0.01 or >0.99), be suspicious of the result. It should be very rare for an intervention to have an extreme effect or have an extreme impact on the probability of an event.\n- Be paranoid about the uncertainty ranges of your variables. If you are dealing with a highly speculative variable, the answer might have 2-8 orders of magnitude of uncertainty, like "100 to 100K". If you are dealing with a variable that\'s fairly certain, the answer might have 2-4 sig figs of uncertainty. Be focused on being accurate and not overconfident, not on impressing people.\n- Be careful with sigmoid functions. Sigmoid curves with distributions can have very little uncertainty in the middle, and very high uncertainty at the tails. If you are unsure about these values, consider using a mixture distribution. For example, this curve has very high certainty in the middle, and very high uncertainty at the tails: `adoption_rate(t: inputs.t) = 1 / (1 + exp(-normal(0.1, 0.08) * (t - 30)))`\n- Make sure to flag any variables that are highly speculative. Use @doc() to explain that the variable is speculative and to give a sense of the uncertainty. Explain your reasoning, but also warn the reader that the variable is speculative.\n\n### Percentages / Probabilities\n\n- Use a @format() tag, like ".0%" to format percentages.\n- If using a distribution, remember that it shouldn\'t go outside of 0% and 100%. You can use beta distributions or truncate() to keep values in the correct range.\n- If you do use a beta distribution, keep in mind that there\'s no ({p5, p95}) format. You can use beta(alpha:number, beta:number) or beta({mean: number, stdev: number}) to create a beta distribution.\n- Write percentages as "5%" instead of "0.05". It\'s more readable.\n\n### Domains\n\n- Prefer using domains to throwing errors, when trying to restrict a variable. For example, don\'t write, "if year < 2023 then throw("Year must be 2023 or later")". Instead, write f(t: [2023, 2050]).\n- Err on the side of using domains in cases where you are unsure about the bounds of a function, instead of using if/throw or other error handling methods.\n- If you only want to set a min or max value, use a domain with Number.maxValue or -Number.maxValue as the other bound.\n- Do not use a domain with a complete range, like [-Number.maxValue, Number.maxValue]. This is redundant. Instead, just leave out the domain, like "foo(f)".\n\n```squiggle\n// Do not use this\nf(t: [-Number.maxValue, Number.maxValue]) + 1\n\n// Do this\nf(t) = t + 1\n````\n\n## Structure and Naming Conventions\n\n### Structure\n\n- Don\'t have more than 10 variables in scope at any one time. Feel free to use many dictionaries and blocks in order to keep things organized. For example,\n\n```squiggle\n@name("Key Inputs")\ninputs = {\n @name("Age (years)")\n age = 34\n\n @name("Hourly Wage ($/hr)")\n hourly_wage = 100\n\n @name("Coffee Price ($/cup)")\n coffee_price = 1\n {age, hourly_wage, health_value, coffee_price}\n}\n```\n\nNote: You cannot use tags within dicts like the following:\n\n```squiggle\n// This is not valid. Do not do this.\ninputs = {\n @name("Age (years)")\n age = 34,\n\n @name("Hourly Wage ($/hr)")\n hourly_wage: 100,\n}\n```\n\n- At the end of the file, don\'t return anything. The last line of the file should be the @notebook tag.\n- You cannot start a line with a mathematical operator. For example, you cannot start a line with a + or - sign. However, you can start a line with a pipe character, `->`.\n- Prettier will be run on the file. This will change the spacing and formatting. Therefore, be conservative with formatting (long lines, no risks), and allow this to do the heavy lifting later.\n- If the file is over 50 lines, break it up with large styled blocks comments with headers. For example:\n\n```squiggle\n// ===== Inputs =====\n\n// ...\n\n// ===== Calculations =====\n```\n\n### Naming Conventions\n\n- Use snake_case for variable names.\n- All variable names must start with a lowercase letter.\n- In functions, input parameters that aren\'t obvious should have semantic names. For example, instead of "nb" use "net_benefit".\n\n### Dictionaries\n\n- In dictionaries, if a key name is the same as a variable name, use the variable name directly. For example, instead of {value: value}, just use {value}. If there\'s only one key, you can type it with a comma, like this: {value,}.\n\n### Unit Annotation\n\n- Squiggle does not support units directly, but you can add them to \'@name()\', \'@doc()\' tags, and add them to comments.\n- In addition to regular units (like "population"), add other key variables; like the date or the type of variable. For example, use "Number of Humans (Population, 2023)" instead of just "Number of Humans". It\'s important to be precise and detailed when annotating variables.\n- Show units in parentheses after the variable name, when the variable name is not obvious. For example, use "Age (years)" instead of just "Age". In comments, use the "(units)" format.\n Examples:\n\n```squiggle\n@name("Number of Humans (2023)")\nnumber_of_humans = 7.8B\n\n@name("Net Benefit ($)")\nnet_benefit = 100M\n\n@name("Temperature (°C)")\ntemperature = 22\n\n@name("Piano Tuners in New York City (2023)")\ntuners = {\n pianos_per_piano_tuners = 100 to 1k // (pianos per tuner)\n pianos_in_nyc = 1k to 50k // (pianos)\n pianos_in_nyc / pianos_per_piano_tuners\n}\n```\n\n- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations.\n\n```squiggle\n@name("Distance to Mars (km)")\ndistance_mars = 225e6\n\n@name("Distance to Venus (km)")\ndistance_venus = 170e6\n```\n\n### Numbers\n\n- Use abbreviations, when simple, for numbers outside the range of 10^4 to 10^3. For example, use "10k" instead of "10000".\n- For numbers outside the range of 10^10 or so, use scientific notation. For example, "1e10".\n- Don\'t use small numbers to represent large numbers. For example, don\'t use \'5\' to represent 5 million.\n\nDon\'t use the code:\n\n```squiggle\n@name("US Population (millions)")\nus_population = 331.9\n```\n\nInstead, use:\n\n```squiggle\n@name("US Population")\nus_population = 331.9M\n```\n\nMore examples:\n\n```squiggle\n// Correct representations\nworld_population = 7.8B\nannual_budget = 1.2T\ndistance_to_sun = 149.6e6 // 149.6 million kilometers\n\n// Incorrect representations (avoid these)\nworld_population = 7800 // Unclear if it\'s 7800 or 7.8 billion\nannual_budget = 1200 // Unclear if it\'s 1200 or 1.2 trillion\n```\n\n- There\'s no need to use @format on regular numbers. The default formatting is fairly sophistated.\n\n### Lists of Structured Data\n\n- When you want to store complex data as code, use lists of dictionaries, instead of using lists of lists. This makes things clearer. For example, use:\n\n```squiggle\n[\n {year: 2023, value: 1},\n {year: 2024, value: 2},\n]\ninstead of:\n[\n [2023, 1],\n [2024, 2],\n]\n```\n\nYou can use lists instead when you have a very long list of items (20+), very few keys, and/or are generating data using functions.\n\n- Tables are a great way to display structured data.\n- You can use the \'@showAs\' tag to display a table if the table can show all the data. If this takes a lot of formatting work, you can move that to a helper function. Note that helper functions must be placed before the \'@showAs\' tag.\n\nFor example:\n\n```squiggle\n@hide\nstrategiesTable(data) = Table.make(\n data,\n {\n columns: [\n { name: "name", fn: {|f| f.n} },\n { name: "costs", fn: {|f| f.c} },\n { name: "benefits", fn: {|f| f.b} },\n ],\n }\n)\n\n@name("AI Safety Strategies")\n@doc("List of 10 AI safety strategies with their costs and benefits")\n@showAs(strategiesTable)\nstrategies = [\n { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M },\n { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M },\n { n: "Governance", c: 500k to 3M, b: 2M to 15M },\n ...\n]\n```\n\n## Tags and Annotations\n\n### @name, @doc, @hide, @showAs\n\n- Use `@name` for simple descriptions and shortened units. Use `@doc` for further details (especially for detailing types, units, and key assumptions), when necessary. It\'s fine to use both @name and @doc on the same variable - but if so, don\'t repeat the name in the doc; instead use the doc() for additional information only.\n- In `@name`, add units wherever it might be confusing, like "@name("Ball Speed (m/s)"). If the units are complex or still not obvious, add more detail in the @doc().\n- For complex and important functions, use `@name` to name the function, and `@doc` to describe the arguments and return values. @doc should represent a docstring for the function. For example:\n\n```\n@doc("Adds a number and a distribution.\n\\`\\`\\`squiggle\nadd(number, distribution) -> distribution\n\\`\\`\\`")\n```\n\n- Variables that are small function helpers, and that won\'t be interesting or useful to view the output of, should get a `@hide` tag. Key inputs and outputs should not have this tag.\n- Use `@showAs` to format large lists, as tables and to show plots for dists and functions where appropriate.\n\n### `@format()`\n\n- Use `@format()` for numbers, distributions, and dates that could use obvious formatting.\n- The `@format()` tag is not usable with dictionaries, functions, or lists. It is usable with variable assignments. Examples:\n\n```squiggle\nnet_benefit(costs, benefits) = benefits - costs // not valid for @format()\nnet_benefit = benefits - costs // valid for @format()\n```\n\n- This mainly makes sense for dollar amounts, percentages, and dates. ".0%" is a decent format for percentages, and "$,.0f" can be used for dollars.\n- Choose the number of decimal places based on the stdev of the distribution or size of the number.\n- Do not use "()" instead of "-" for negative numbers. So, do not use "($,.0f" for negative numbers, use "$,.0f" instead.\n\n## Comments\n\n- Add a short 1-2 line comment on the top of the file, summarizing the model.\n- Add comments throughout the code that explain your reasoning and describe your uncertainties. Give special attention to probabilities and probability distributions that are particularly important and/or uncertain. Flag your uncertainties.\n- Use comments next to variables to explain what units the variable is in, if this is not incredibly obvious. The units should be wrapped in parentheses.\n- There shouldn\'t be any comments about specific changes made during editing.\n- Do not use comments to explain things that are already obvious from the code.\n\n## Visualizations\n\n### Tables\n\n- Tables are a good way of displaying structured data. They can take a bit of formatting work.\n- Tables are best when there are fewer than 30 rows and/or fewer than 4 columns.\n- The table visualization is fairly simple. It doesn\'t support sorting, filtering, or other complex interactions. You might want to sort or filter the data before putting it in a table.\n\n### Notebooks\n\n- Use the @notebook tag for long descriptions intersperced with variables. This must be a list with strings and variables alternating.\n- If you want to display variables within paragraphs, generally render dictionaries as items within the notebook list. For example:\n\n```squiggle\n@notebook\n@startOpen\nsummary = [\n"This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.",\n{\n optimal_cups,\n result.net_benefit,\n},\n]\n```\n\nThis format will use the variable tags to display the variables, and it\'s simple to use without making errors. If you want to display a variable that\'s already a dictionary, you don\'t need to do anything special.\n\n- String concatenation (+) is allowed, but be hesitant to do this with non-string variables. Most non-string variables don\'t display well in the default string representation. If you want to display a variable, consider using a custom function or formatter to convert it to a string first. Note that tags are shown in the default string representation, so you should remove them (`Tag.clear(variable)`) before displaying.\n- Separate items in the list will be displayed with blank lines between them. This will break many kinds of formatting, like lists. Only do this in order to display full variables that you want to show.\n- Use markdown formatting for headers, lists, and other structural elements.\n- Use bold text to highlight key outputs. Like, "The optimal number of coffee cups per day is **" + Tag.clear(optimal_cups) + "**".\n\nExample: (For a model with 300 lines)\n\n```squiggle\n@notebook\n@startOpen\nsummary = [\n "## Summary\n This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.",\n {inputs, final_answer},\n "## Major Assumptions & Uncertainties\n - The model places a very high value on productivity. If you think that productivity is undervalued, coffee consumption may be underrated.\n - The model only includes 3 main factors: productivity, cost, and health. It does not take into account other factors, like addiction, which is a major factor in coffee consumption.\n - The model does not take into account the quality of sleep, which is critical.\n "\n "## Outputs\n The optimal number of coffee cups per day: **" + Tag.clear(optimal_cups) + "**\n The net benefit at optimal consumption: **" + result.net_benefit + "**",\n "## Key Findings\n - Moderate amounts of coffee consumption seem surprisingly beneficial.\n - Productivity boost from coffee shows steeply diminishing returns as consumption increases, as would be expected.\n - The financial cost of coffee is the critical factor in determining optimal consumption.\n ## Detailed Analysis\n The model incorporates several key factors:\n 1. Productivity boost: Modeled with diminishing returns as coffee consumption increases.\n 2. Health impact: Considers both potential benefits and risks of coffee consumption.\n 3. Financial cost: Accounts for the direct cost of purchasing coffee.\n 4. Monetary values: Includes estimates for the value of time (hourly wage) and health (QALY value).\n\n The optimal consumption level is determined by maximizing the net benefit, which is the sum of monetized productivity and health benefits minus the financial cost.\n\n It\'s important to note that this model is based on general estimates and may not apply to all individuals. Factors such as personal health conditions, caffeine sensitivity, and lifestyle choices could significantly alter the optimal consumption for a specific person.\n "\n]\n```\n\n## Plots\n\n- Plots are a good way of displaying the output of a model.\n- Use Scale.symlog() and Scale.log() whenever you think the data is highly skewed. This is very common with distributions.\n- Use Scale.symlog() instead of Scale.log() when you are unsure if the data is above or below 0. Scale.log() fails on negative values.\n- Function plots use plots equally spaced on the x-axis. This means they can fail if only integers are accepted. In these cases, it can be safer just not to use the plot, or to use a scatter plot.\n- When plotting 2-8 distributions over the same x-axis, it\'s a good idea to use Plot.dists(). For example, if you want to compare 5 different costs of a treatment, or 3 different adoption rates of a technology, this can be a good way to display the data.\n- When plotting distributions in tables or if you want to display multiple distributions under each other, and you don\'t want to use Plot.dists, it\'s a good idea to have them all use the same x-axis scale, with custom min and max values. This is a good way to make sure that the x-axis scale is consistent across all distributions.\n\nHere\'s an example of how to display multiple distributions over the same x-axis, with a custom x-axis range:\n\n```squiggle\nstrategies = [\n { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M },\n { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M },\n ...\n]\n\nrangeOfDists(dists) = {\n min: Number.min(List.map(dists, {|d| Dist.quantile(d, 0.05)})),\n max: Number.max(List.map(dists, {|d| Dist.quantile(d, 0.95)})),\n}\n\nplotOfResults(fn) = {\n |r|\n range = List.map(strategies, fn) -> rangeOfDists\n Plot.dist(fn(r), { xScale: Scale.linear(range) })\n}\n\ntable = Table.make(\n strategies,\n {\n columns: [\n { name: "Strategy", fn: {|r| r.name} },\n { name: "Cost", fn: plotOfResults({|r| r.c}) },\n { name: "Benefit", fn: plotOfResults({|r| r.b}) },\n ],\n }\n)\n```\n\n## Tests\n\n- Use `sTest` to test squiggle code.\n- Test all functions that you are unsure about. Be paranoid.\n- Use one describe block, with the variable name \'tests\'. This should have several tests with in it, each with one expect statement.\n- Use @startClosed tags on variables that are test results. Do not use @hide tags.\n- Do not test if function domains return errors when called with invalid inputs. The domains should be trusted.\n- If you set variables to sTest values, @hide them. They are not useful in the final output.\n- Do not test obvious things, like the number of items in a list that\'s hardcoded.\n- Feel free to use helper functions to avoid repeating code.\n- The expect.toThrowAnyError() test is useful for easily sanity-checking that a function is working with different inputs.\n\nExample:\n\n```squiggle\n@hide\ndescribe = sTest.describe\n\n@hide\ntest = sTest.test\n\ntests = describe(\n "Coffee Consumption Model Tests",\n [\n // ...tests\n ]\n)\n```\n\n## Summary Notebook\n\n- For models over 5 lines long, you might want to include a summary notebook at the end of the file using the @notebook tag.\n- Aim for a summary length of approximately (N^0.6) \\* 1.2 lines, where N is the number of lines in the model.\n- Use the following structure:\n 1. Model description\n 2. Major assumptions & uncertainties (if over 100 lines long)\n 3. Outputs (including relevant Squiggle variables)\n 4. Key findings (flag if anything surprised you, or if the results are counterintuitive)\n 5. Detailed analysis (if over 300 lines long)\n 6. Important notes or caveats (if over 100 lines long)\n- The summary notebook should be the last thing in the file. It should be a variable called `summary`.\n- Draw attention to anything that surprised you, or that you think is important. Also, flag major assumptions and uncertainties.\n\nExample: (For a model with 300 lines)\n\n```squiggle\n@notebook\n@startOpen\nsummary = [\n "## Summary\n This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.",\n {inputs, final_answer},\n ...\n ]\n```\n\n# Roadmap\n\nSquiggle 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.\n\nIf you have preferences or suggestions for our roadmap, please say so! Post your thoughts in the Github discussion or in the Discord.\n\nNote that our short-term roadmap changes frequently, and is not captured here.\n\n## Programming Language Features\n\n- A simple type system\n- Optional and default paramaters for functions\n- Much better code editor integration\n\n## Distribution Features\n\nThere are many important distribution types that Squiggle doesn\'t yet support. Some key functions we\'d like include:\n\n[Metalog Distribution](https://en.wikipedia.org/wiki/Metalog_distribution) \nAdd 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.\n\n`Distribution.smoothen(p)` \nTakes a distribution and smoothens it. For example, [Elicit Forecast](https://forecast.elicit.org/) does something like this, with uniform distributions.\n\n## Major Future Additions\n\n**An interface to interpret & score Squiggle files** \nSquiggle 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.\n\nMy 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.\n\nOf course, we\'d also need good math for how the scoring should work, exactly.\n\nThis 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.\n\n**Importance & quality scores** \nWorkflows/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.\n\n**Static / sensitivity analysis** \nGuesstimate has Sensitivity analysis that\'s pretty useful. This could be quite feasible to add, though it will likely require some thinking.\n\n**Randomness seeds** \nRight 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.\n\n**Caching/memoization** \nThere are many performance improvements that Squiggle could have. We\'ll get to some of them eventually.\n\n# Language Features\n\n## Program Structure\n\nA Squiggle program consists of a series of definitions (for example, `x = 5`, `f(x) = x * x`). This can optionally conclude with an _end expression_.\n\nIf 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.\n\n```squiggle\nx = 5\ny = 10\nx + y\n```\n\n```squiggle\nx = 5\ny = 10\n```\n\n## Immutability\n\nAll variables in Squiggle are immutable, similar to other functional programming languages like OCaml or Haskell.\n\nIn 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).\n\nIn case of basic types such as numbers or strings, the impact of immutability is more subtle.\n\nConsider this code:\n\n```squiggle\nx = 5\nx = x + 5\n```\n\nWhile 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.\n\nIn most cases, shadowing behaves identically to what you\'d expect in languages like JavaScript or Python.\n\nOne case where shadowing matters is closures:\n\n```squiggle\nx = 5\nargPlusX(y) = x + y\n\nx = x + 5\n\nargPlusX(5)\n```\n\nIn 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.\n\n## Unit Type Annotations\n\nVariable declarations may optionally be annotated with _unit types_, such as `kilograms` or `dollars`. Unit types are declared with `::`, for example:\n\n```squiggle\ndistance :: meters = 100\n```\n\nA unit type can be any identifier, and you don\'t have to define a unit type before you use it.\n\nYou can also create composite unit types using `*` (multiplication), `/` (division), and \'^\' (exponentiation). For example:\n\n```squiggle\nraceDistance :: m = 100\nusainBoltTime :: s = 9.58\nusainBoltSpeed :: m/s = raceDistance / usainBoltTime\n```\n\nYou can use any number of `*` and `/` operators in a unit type, but you cannot use parentheses. Unit type operators follow standard order of operations: `*` and `/` are left-associative and have the same precedence, and `^` has higher precedence.\n\nThe following unit types are all equivalent: `kg*m/s^2`, `kg*m/s/s`, `m/s*kg/s`, `m/s^2*kg`, `kg*m^2/m/s/s`.\n\nFor unitless types, you may use a number in the unit type annotation (by convention you should use the number `1`):\n\n```squiggle\nspringConstant :: 1 = 10\ninverseTime :: 1/s = 20\n```\n\nIf you use unit type annotations, Squiggle will enforce that variables must have consistent unit types.\n\nIf a variable does not have a unit type annotation, Squiggle will attempt to infer its unit type. If the unit type can\'t be inferred, the variable is treated as any type.\n\nInline unit type annotations are not currently supported (for example, `x = (y :: meters)`).\n\nOperators and functions obey the following semantics:\n\n- Multiplying or dividing two unit-typed variables multiplies or divides their unit types (respectively).\n- Raising a unit-typed variable to a power produces a result of any unit type (i.e., the result is not type-checked).\n- Most binary operators, including `+`, `-`, and comparison operators (`==`, `>=`, etc.), require that both arguments have the same unit type.\n- Built-in functions can take any unit type and return any unit type.\n\n## Blocks\n\nBlocks are special expressions in Squiggle that can contain any number of local definitions and end with an expression.\n\n```squiggle\nx = { 5 } // same as "x = 5"\ny = {\n t = 10 // local variable, won\'t be available outside of the block body\n 5 * t // end expression\n}\n```\n\n## Conditionals\n\nIf/then/else statements in Squiggle are values too.\n\n```squiggle\nx = 5\nif x<8 then 10 else 3\n```\n\nSee [Control flow](/docs/Guides/ControlFlow) for more details and examples.\n\n## Comments\n\n```squiggle\n// This is a single-line comment\\n\n/*\nThis is a multiple\n-line comment.\n*/\nfoo = 5\n```\n\n## Pipes\n\nSquiggle 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.\n\n```squiggle\nnormal(5,2) -> truncateLeft(3) -> SampleSet.fromDist -> SampleSet.map({|r| r + 10})\n```\n\n## Standard Library\n\nSquiggle features a simple [standard libary](/docs/Api/Dist).\n\nMost functions are namespaced under their respective types to keep functionality distinct. Certain popular functions are usable without their namespaces.\n\nFor example,\n\n```squiggle\na = List.upTo(0, 5000) -> SampleSet.fromList // namespaces required\nb = normal(5,2) // namespace not required\nc = 5 to 10 // namespace not required\n```\n\n## Simple Error Handling\n\nSquiggle supports the functions [throw](/docs/Api/Common#throw) and [try](/docs/Api/Common#try) for simple error handling. It does not yet have proper error types.\n\n# Gotchas\n\n## Point Set Distributions Conversions\n\nPoint 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.\n\nIn 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.\n\n```squiggle\ndist = SampleSet.fromDist(5 to 100000000)\n{\n sampleSetMedian: quantile(dist, .5),\n pointSetMedian: quantile(PointSet.fromDist(dist), .5),\n dist: dist\n}\n```\n\n---\n\nThis 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.\n\nWe plan to later support more configuration of kernel density estimation, and for visualiations of Sample Set distributions to instead use histograms.\n\n## Sample Set Correlations\n\nCorrelations 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.\n\nIn this example, we subtract all three types of distributions by themselves. Notice that the Sample Set distribution returns 0. 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.\n\n```squiggle\nsampleSetDist = normal(5, 2)\npointSetDist = sampleSetDist -> PointSet.fromDist\nsymbolicDist = Sym.normal(5, 2)\n[\n sampleSetDist - sampleSetDist,\n pointSetDist - pointSetDist,\n symbolicDist - symbolicDist,\n]\n```\n\n# Functions\n\n## Basic Syntax\n\n```squiggle\nmyMultiply(t) = normal(t^2, t^1.2+.01)\nmyMultiply\n```\n\nIn Squiggle, function definitions are treated as values. There\'s no explicit `return` statement; the result of the last expression in the function body is returned.\nIf you need to define local variables in functions, you can use blocks. The last expression in the block is the value of the block:\n\n```squiggle\nmultiplyBySix(x) = {\n doubleX = x * 2\n doubleX * 3\n }\n```\n\n## Anonymous Functions\n\nIn Squiggle, you can define anonymous functions using the `{|...| ...}` syntax. For example, `myMultiply(x, y) = x * y` and `myMultiply = {|x, y| x * y}` are equivalent.\n\nSquiggle functions are first-class values, meaning you can assign them to variables, pass them as arguments to other functions, and return them from other functions.\n\n```squiggle\n{|t| normal(t^2, t^1.2+.01)}\n```\n\n## Function Visualization\n\nThe Squiggle viewer can automatically visualize functions that take a single number as input and return either a number or a distribution, without the need for manual plots:\n\n1. `(number) => number`\n2. `(number) => distribution`\n\n```squiggle\nnumberToNumber(x) = x * x\nnumberToDistribution(x) = normal(x + 1, 3)\nplaceholderFunction(x, y) = x + y\n```\n\nWhen Squiggle visualizes a function, it automatically selects a range of input values to use.\nThe default range of input values is 0 to 10.\n\nYou can manually set the range in the following ways:\n\n- With `Plot.numericFn` or `Plot.distFn` plots, using the `xScale` parameter\n- Through the chart\'s settings in the UI (look for a gear icon next to the variable name)\n- With parameter annotations (explained below)\n\n## Unit Types\n\nLike with [variables](/docs/Guides/LanguageFeatures#unit-type-annotations), you can declare unit types for function parameters:\n\n```squiggle\nf(x :: unit) = x\n```\n\nYou can also declare the unit type of the function\'s return value:\n\n```squiggle\nconvertMass(x :: lbs) :: kg = x * 2.2\n```\n\nIf you pass a unit-typed variable to a function with no unit-type annotations, Squiggle will attempt to infer the unit type of the return value:\n\n```squiggle\nid(x) = x\na :: m/s = 10\nb = id(a) // Squiggle infers that b has type m/s\n```\n\nUnit type checking only works for statically defined functions. In the example code below, `h` cannot be unit-type checked.\n\n```squiggle\nf(x) = x\ng(x) = x\ncondition = (1 == 2)\nh = (condition ? f : g)\n```\n\n## Parameter Annotations\n\nFunction parameters can be annotated with _domains_ to specify the range of valid input values.\n\nExamples:\n\n- `x: Number.rangeDomain(5, 10)`\n- `x: [5, 10]` — shortcut for `Number.rangeDomain(...)`\n\nAnnotations help to document possible values that can be passed as a parameter\'s value.\n\nAnnotations 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).\n\nDomains are checked on function calls; `f(x: [1,2]) = x; f(3)` will fail.\n\nWe plan to support other kinds of domains in the future; for now, only numeric ranges are supported.\n\n```squiggle\nyearToValue(year: [2020, 2100]) = 1.04 ^ (year - 2020)\n```\n\n### Annotation Reflection\n\n```squiggle\nmyMultiply(x: [1, 20]) = x * x\nmyMultiply.parameters[0]\n```\n\nDomains and parameter names can be accessed by the `fn.parameters` property.\n\n# Distribution Functions\n\n## Standard Operations\n\nHere are the ways we combine distributions.\n\n### Addition\n\nA horizontal right shift. The addition operation represents the distribution of the sum of\nthe value of one random sample chosen from the first distribution and the value one random sample\nchosen from the second distribution.\n\n```squiggle\ndist1 = 1 to 10\ndist2 = triangular(1,2,3)\ndist1 + dist2\n```\n\n### Subtraction\n\nA horizontal left shift. The subtraction operation represents the distribution of the value of\none random sample chosen from the first distribution minus the value of one random sample chosen\nfrom the second distribution.\n\n```squiggle\ndist1 = 1 to 10\ndist2 = triangular(1,2,3)\ndist1 - dist2\n```\n\n### Multiplication\n\nA proportional scaling. The multiplication operation represents the distribution of the multiplication of\nthe value of one random sample chosen from the first distribution times the value one random sample\nchosen from the second distribution.\n\n```squiggle\ndist1 = 1 to 10\ndist2 = triangular(1,2,3)\ndist1 * dist2\n```\n\nWe also provide concatenation of two distributions as a syntax sugar for `*`\n\n```squiggle\ndist1 = 1 to 10\ndist2 = triangular(1,2,3)\ndist1 / dist2\n```\n\n### Exponentiation\n\nA projection over a contracted x-axis. The exponentiation operation represents the distribution of\nthe exponentiation of the value of one random sample chosen from the first distribution to the power of\nthe value one random sample chosen from the second distribution.\n\n```squiggle\n(0.1 to 1) ^ beta(2, 3)\n```\n\n### The base `e` exponential\n\n```squiggle\ndist = triangular(1,2,3)\nexp(dist)\n```\n\n### Logarithms\n\nA projection over a stretched x-axis.\n\n```squiggle\ndist = triangular(1,2,3)\nlog(dist)\n```\n\n```squiggle\nlog10(5 to 10)\n```\n\nBase `x`\n\n```squiggle\nlog(5 to 10, 2)\n```\n\n## Pointwise Operations\n\n### Pointwise addition\n\nFor every point on the x-axis, operate the corresponding points in the y axis of the pdf.\n\n**Pointwise operations are done with `PointSetDist` internals rather than `SampleSetDist` internals**.\n\n```squiggle\nSym.lognormal({p5: 1, p95: 3}) .+ Sym.triangular(5,6,7)\n```\n\n### Pointwise multiplication\n\n```squiggle\nSym.lognormal({p5: 1, p95: 5}) .* Sym.uniform(1,8)\n```\n\n## Standard Functions\n\n### Probability density function\n\nThe `pdf(dist, x)` function returns the density of a distribution at the\ngiven point x.\n\n\n\n#### Validity\n\n- `x` must be a scalar\n- `dist` must be a distribution\n\n### Cumulative density function\n\nThe `cdf(dist, x)` gives the cumulative probability of the distribution\nor all values lower than x. It is the inverse of `quantile`.\n\n\n\n#### Validity\n\n- `x` must be a scalar\n- `dist` must be a distribution\n\n### Quantile\n\nThe `quantile(dist, prob)` gives the value x for which the sum of the probability for all values\nlower than x is equal to prob. It is the inverse of `cdf`. In the literature, it\nis also known as the quantiles function. In the optional `summary statistics` panel which appears\nbeneath distributions, the numbers beneath 5%, 10%, 25% etc are the quantiles of that distribution\nfor those precentage values.\n\n\n\n#### Validity\n\n- `prob` must be a scalar (please only put it in `(0,1)`)\n- `dist` must be a distribution\n\n### Mean\n\nThe `mean(distribution)` function gives the mean (expected value) of a distribution.\n\n\n\n### Sampling a distribution\n\nThe `sample(distribution)` samples a given distribution.\n\n\n\n## Converting between distribution formats\n\nWe can convert any distribution into the `SampleSet` format\n\n\n\nOr the `PointSet` format\n\n\n\n#### Validity\n\n- Second argument to `SampleSet.fromDist` must be a number.\n\n## Normalization\n\nSome distribution operations (like horizontal shift) return an unnormalized distriibution.\n\nWe provide a `normalize` function\n\n\n\n#### Validity - Input to `normalize` must be a dist\n\nWe provide a predicate `isNormalized`, for when we have simple control flow\n\n\n\n#### Validity\n\n- Input to `isNormalized` must be a dist\n\n## `inspect`\n\nYou 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.\n\n\n\nSave for a logging side effect, `inspect` does nothing to input and returns it.\n\n## Truncate\n\nYou can cut off from the left\n\n\n\nYou can cut off from the right\n\n\n\nYou can cut off from both sides\n\n\n\n# Distribution Creation\n\n## Normal\n\n```squiggle\nnormal(mean: number, stdev: number)\nnormal({mean: number, stdev: number})\nnormal({p5: number, p95: number})\nnormal({p10: number, p90: number})\nnormal({p25: number, p75: number})\n```\n\nCreates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) with the given mean and standard deviation.\n\n\n\n\n```squiggle\nnormalMean = 10\nnormalStdDev = 2\nlogOfLognormal = log(lognormal(normalMean, normalStdDev))\n[logOfLognormal, normal(normalMean, normalStdDev)]\n\n```\n\n\n\n## To\n\n```squiggle\n(5thPercentile: number) to (95thPercentile: number)\nto(5thPercentile: number, 95thPercentile: number)\n```\n\nThe `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.\n\n\n\n\n```squiggle\nhours_the_project_will_take = 5 to 20\nchance_of_doing_anything = 0.8\nmx(hours_the_project_will_take, 0, [chance_of_doing_anything, 1 - chance_of_doing_anything])\n\n```\n\n\n\n
\n 🔒 Model Uncertainty Safeguarding\n One technique several Foretold.io users used is to combine their main guess, with a\n "just-in-case distribution". This latter distribution would have very low weight, but would be\n very wide, just in case they were dramatically off for some weird reason.\n```squiggle\nforecast = 3 to 30\nchance_completely_wrong = 0.05\nforecast_if_completely_wrong = normal({p5:-100, p95:200})\nmx(forecast, forecast_if_completely_wrong, [1-chance_completely_wrong, chance_completely_wrong])\n````\n\n
\n## SampleSet.fromList\n\n```squiggle\nSampleSet.fromList(samples:number[])\n```\n\nCreates a sample set distribution using an array of samples.\n\nSamples 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.\n\n```squiggle\nPointSet.makeContinuous([\n { x: 0, y: 0.1 },\n { x: 1, y: 0.2 },\n { x: 2, y: 0.15 },\n { x: 3, y: 0.1 }\n])\n```\n\n\n **Caution!** \n Distributions made with ``makeContinuous`` are not automatically normalized. We suggest normalizing them manually using the ``normalize`` function.\n\n\n### Arguments\n\n- `points`: An array of at least 3 coordinates.\n\n## PointSet.makeDiscrete\n\n```squiggle\nPointSet.makeDiscrete(points:{x: number, y: number})\n```\n\nCreates a discrete point set distribution using a list of points.\n\n```squiggle\nPointSet.makeDiscrete([\n { x: 0, y: 0.2 },\n { x: 1, y: 0.3 },\n { x: 2, y: 0.4 },\n { x: 3, y: 0.1 }\n])\n```\n\n### Arguments\n\n- `points`: An array of at least 1 coordinate.\n\n# Debugging\n\nInteractive visualizations are a primary tool for understanding Squiggle code, but there are some additional techniques that can improve the debugging process. Here are some tips and tricks:\n\n## Basic Console Logging\n\n- **Built-in Inspection:** Utilize the [`inspect()`](/docs/Api/BuiltIn#inspect) function to log any variable to the console. This function provides a detailed view of the variable\'s current state and is useful for tracking values throughout your code.\n- **Variable Settings Toggle:** Click on the variable menu in the Squiggle interface and select "Log to JS Console".\n\n## `Window.squiggleOutput`\n\nSquiggle pushes its output to `window.squiggleOutput`. Like with the outputs of `inspect`, you can see this in the [JS developer console](https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-developer-console).\n\n## `Danger.json`\n\nYou can call [`Danger.json()`](/docs/Api/Danger#json) see variables in a format similar to JSON. This is useful for seeing all aspects of complex types like distributions.\n\n```squiggle\nsampleSet = 30 to 50\npointSet = Sym.normal(5, 2)\nplot = Plot.dists([sampleSet, pointSet])\nfn(e) = e\n{\n json: Danger.json([sampleSet, pointSet, plot, fn]),\n jsonString: Danger.jsonString([pointSet, fn]),\n}\n```\n\n## Profiling\n\nIn the playground configuration panel, you can enable the "Performance Profiler" checkbox. This will highlight the code in the editor according to how much time was spend on each expression.\n\nCaveats:\n\n- The code will execute slightly slower in profiler mode\n- Imports won\'t be profiled correctly (but slow calls of imported functions will be highlighted)\n- If the code is fast, you\'ll randomly get highlighted and unhighlighted results, because time measurement is imprecise\n\nIf you\'re using Squiggle components in React, you can enable the profiler for any component that supports the `environment` prop with `environment={profile: true}`:\n\n```squiggle\ndist = normal(0, 1)\nlist = List.upTo(1, 100000) -> List.length\n```\n\n# Control Flow\n\nThis 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.\n\n## Conditionals\n\n### If-else\n\n```squiggle\nif condition then result else alternative\n```\n\n```squiggle\nx = 10\nif x == 1 then 1 else 2\n```\n\n### If-else as a ternary operator\n\n```squiggle\ntest ? result : alternative;\n```\n\n```squiggle\nx = 10\nx == 0 ? 1 : 2\n```\n\n### Tips and tricks\n\n#### Use brackets and parenthesis to organize control flow\n\n```squiggle\nx = 10\nif x == 1 then {\n 1\n} else {\n 2\n}\n```\n\nor\n\n```squiggle\nx = 10\ny = 20\nif x == 1 then {\n (\n if y == 0 then {\n 1\n } else {\n 2\n }\n )\n} else {\n 3\n}\n```\n\nThis is overkill for simple examples becomes useful when the control conditions are more complex.\n\n#### Save the result to a variable\n\nAssigning a value inside an if/else flow isn\'t possible:\n\n```squiggle\nx = 10\ny = 20\nif x == 1 then {\n y = 1\n} else {\n y = 2 * x\n}\n```\n\nInstead, you can do this:\n\n```squiggle\nx = 10\ny = 20\ny = if x == 1 then {\n 1\n} else {\n 2 * x\n}\n```\n\nLikewise, for assigning more than one value, you can\'t do this:\n\n```squiggle\ny = 0\nz = 0\nif x == 1 then {\n y = 2\n} else {\n z = 4\n}\n```\n\nInstead, do:\n\n```squiggle\nx = 10\nresult = if x == 1 then {\n {y: 2, z: 0}\n} else {\n {y: 0, z: 4}\n}\ny = result.y\nz = result.z\n```\n\n## For loops\n\nFor loops aren\'t supported in Squiggle. Instead, use a [map](/docs/Api/List#map) or a [reduce](/docs/Api/List#reduce) function.\n\nInstead of:\n\n```js\nxs = [];\nfor (i = 0; i < 10; i++) {\n xs[i] = f(x);\n}\n```\n\ndo:\n\n```squiggle\nf(x) = 2*x\nxs = List.upTo(0,10)\nys = List.map(xs, {|x| f(x)})\n```\n\n# Known Bugs\n\nMuch of the Squiggle math is imprecise. This can cause significant errors, so watch out.\n\nBelow are a few specific examples to watch for. We\'ll work on improving these over time and adding much better warnings and error management.\n\nYou 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).\n\n## Operations on very small or large numbers, silently round to 0 and 1\n\nSquiggle is poor at dealing with very small or large numbers, given fundamental limitations of floating point precision.\nSee [this Github Issue](https://github.com/quantified-uncertainty/squiggle/issues/834).\n\n## Mixtures of distributions with very different means\n\nIf you take the pointwise mixture of two distributions with very different means, then the value of that gets fairly warped.\n\nIn 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.\n\n```squiggle\ndist1 = {value: normal(1,1), weight: 1}\ndist2 = {value: normal(100000000000,1), weight: 1}\ntotalWeight = dist1.weight + dist2.weight\ndistMixture = mixture(dist1.value, dist2.value, [dist1.weight, dist2.weight])\nmixtureMean = mean(distMixture)\nseparateMeansCombined = (mean(dist1.value) * (dist1.weight) + mean(dist2.value) * (dist2.weight))/totalWeight\n[mixtureMean, separateMeansCombined, distMixture]\n```\n\n## Means of Sample Set Distributions\n\nThe means of sample set distributions can vary dramatically, especially as the numbers get high.\n\n```squiggle\nsymbolicDist = 5 to 50333333\nsampleSetDist = SampleSet.fromDist(symbolicDist)\n[mean(symbolicDist), mean(sampleSetDist), symbolicDist, sampleSetDist]\n```\n\n# Basic Types\n\n## Numbers\n\nSquiggle 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. \n[Number API](/docs/Api/Number)\n\nNumbers support a few scientific notation suffixes.\n\n| Suffix | Multiplier |\n| ------ | ---------- |\n| n | 10^-9 |\n| m | 10^-3 |\n| % | 10^-2 |\n| k | 10^3 |\n| M | 10^6 |\n| B,G | 10^9 |\n| T | 10^12 |\n| P | 10^15 |\n\nThere\'s no difference between floats and integers in Squiggle.\n\n```squiggle\nn = 4.32\nkilo = 4.32k\nmicro = 4.32m\nveryLarge = 1e50\nverySmall = 1e-50\n```\n\n## Booleans\n\nBooleans can be `true` or `false`.\n\n```squiggle\nt = true\nf = false\n```\n\n## Strings\n\nStrings can be created with either single or double quotes. \n[String API](/docs/Api/String)\n\n```squiggle\ns = "Double-quoted"\ns2 = \'Single-quoted\'\n```\n\n## Distributions\n\nDistributions are first-class citizens. Use the syntax `a to b` to create a quick lognormal distribution, or write out the whole distribution name.\n\n```squiggle\na = 10 to 20\nb = normal(4, 2)\nc = lognormal({ mean: 50, stdev: 10 })\nd = mixture(a, b, c, [.3, .3, .4])\nd\n```\n\nSee these pages for more information on distributions:\n\n- [Distribution Creation](/docs/Guides/DistributionCreation)\n- [Distribution Functions Guide](/docs/Guides/Functions)\n- [Distribution API](/docs/Api/Dist)\n\nThere 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)`.\n\n## Lists\n\nSquiggle lists can contain items of any type, similar to lists in Python. You can access individual list elements with `[number]` notation, starting from `0`.\n\nSquiggle 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. \n[List API](/docs/Api/List)\n\n```squiggle\nmyList = [1, "hello", 3 to 5, ["foo", "bar"]]\nfirst = myList[0] // 1\nbar = myList[3][1] // "bar"\n```\n\n## Dictionaries\n\nSquiggle dictionaries work similarly to Python dictionaries or Javascript objects. Like lists, they can contain values of any type. Keys must be strings. \n[Dictionary API](/docs/Api/Dictionary)\n\n```squiggle\nd = {dist: triangular(0, 1, 2), weight: 0.25, innerDict: {foo: "bar"}}\n```\n\n## Other types\n\nOther Squiggle types include:\n\n- [Functions](/docs/Guides/Functions)\n- [Plots](/docs/Api/Plot)\n- [Scales](/docs/Api/Plot#scales)\n- [Domains](#parameter-annotations)---\n description:\n\n---\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Common\n\nFunctions that work on many different types of values. Also see the experimental [JSON functions](/docs/Api/Danger#json).\n\nCommon.equal ==: (any, any) => Bool\nReturns true if the two values passed in are equal, false otherwise. Does not work for Squiggle functions, but works for most other types.\n\nCommon.unequal !=: (any, any) => Bool\n\nCommon.typeOf: (any) => String\nReturns the type of the value passed in as a string. This is useful when you want to treat a value differently depending on its type.\nmyString = typeOf("foo")\nmyBool = typeOf(true)\nmyDist = typeOf(5 to 10)\nmyFn = typeOf({|e| e})\n\nCommon.inspect: (\'A, message?: String) => \'A\nRuns Console.log() in the [Javascript developer console](https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-developer-console) and returns the value passed in.\n\nCommon.throw: (message?: String) => any\nThrows an error. You can use `try` to recover from this error.\n\nCommon.try: (fn: () => \'A, fallbackFn: () => \'B) => \'A|\'B\nTry to run a function and return its result. If the function throws an error, return the result of the fallback function instead.\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Boolean\n\nBoolean.or ||: (Bool, Bool) => Bool\n\nBoolean.and &&: (Bool, Bool) => Bool\n\nBoolean.not !: (Bool) => Bool\n\n---\n\n## description: Dates are a simple date time type.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Date\n\nA simple date type. Dates are stored as milliseconds since the epoch. They are immutable, and all functions that modify dates return a new date. Used with [Duration](./Duration) values.\n\n Dates can be useful for modeling values that change over time. Below is a simple example of a function that returns a normal distribution that changes over time, based on the number of years passed since 2020.\n\n\n\n## Constructors\n\nDate.make: (String) => Date, (year: Number, month: Number, day: Number) => Date, (year: Number) => Date\nd1 = Date.make("2020-05-12")\nd2 = Date.make(2020, 5, 10)\nd3 = Date.make(2020.5)\n\n## Conversions\n\nDate.fromUnixTime: (Number) => Date\nDate.fromUnixTime(1589222400)\n\nDate.toUnixTime: (Date) => Number\nDate.toUnixTime(Date.make(2020, 5, 12))\n\n## Algebra\n\nDate.subtract -: (Date, Date) => Duration\nDate.make(2020, 5, 12) - Date.make(2000, 1, 1)\n\nDate.subtract -: (Date, Date) => Duration\nDate.make(2020, 5, 12) - Date.make(2000, 1, 1)\n\nDate.add +: (Date, Duration) => Date, (Duration, Date) => Date\nDate.make(2020, 5, 12) + 20years\n20years + Date.make(2020, 5, 12)\n\n## Comparison\n\nDate.smaller <: (Date, Date) => Bool\n\nDate.larger >: (Date, Date) => Bool\n\nDate.smallerEq <=: (Date, Date) => Bool\n\nDate.largerEq >=: (Date, Date) => Bool\n\n## Other\n\nDate.rangeDomain: (min: Date, min: Date) => Domain\nDate.rangeDomain(Date(2000), Date(2010))\n\n---\n\n## description: Squiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Dict\n\nSquiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript.\n\n## Conversions\n\nDict.toList: (Dict(\'A)) => List([String, \'A])\nDict.toList({a: 1, b: 2})\n\nDict.fromList: (List([String, \'A])) => Dict(\'A)\nDict.fromList([\n["foo", 3],\n["bar", 20],\n]) // {foo: 3, bar: 20}\n\n## Transformations\n\nDict.set: (Dict(\'A), key: String, value: \'A) => Dict(\'A)\nCreates a new dictionary that includes the added element, while leaving the original dictionary unaltered.\nDict.set({a: 1, b: 2}, "c", 3)\n\nDict.delete: (Dict(\'A), key: String) => Dict(\'A)\nCreates a new dictionary that excludes the deleted element.\nDict.delete({a: 1, b: 2}, "a")\n\nDict.merge: (Dict(any), Dict(any)) => Dict(any)\nfirst = { a: 1, b: 2 }\nsnd = { b: 3, c: 5 }\nDict.merge(first, snd)\n\nDict.mergeMany: (List(Dict(any))) => Dict(any)\nfirst = { a: 1, b: 2 }\nsnd = { b: 3, c: 5 }\nDict.mergeMany([first, snd]) // {a: 1, b: 3, c: 5}\n\nDict.map: (Dict(\'A), fn: (\'A) => \'B) => Dict(\'B)\nDict.map({a: 1, b: 2}, {|x| x + 1})\n\nDict.mapKeys: (Dict(\'A), fn: (String) => String) => Dict(\'A)\nDict.mapKeys({a: 1, b: 2, c: 5}, {|x| concat(x, "-foobar")})\n\nDict.omit: (Dict(\'A), keys: List(String)) => Dict(\'A)\nCreates a new dictionary that excludes the omitted keys.\ndata = { a: 1, b: 2, c: 3, d: 4 }\nDict.omit(data, ["b", "d"]) // {a: 1, c: 3}\n\n## Queries\n\nDict.has: (Dict(any), key: String) => Bool\nDict.has({a: 1, b: 2}, "c")\n\nDict.size: (Dict(any)) => Number\nDict.size({a: 1, b: 2})\n\nDict.keys: (Dict(any)) => List(String)\nDict.keys({a: 1, b: 2})\n\nDict.values: (Dict(\'A)) => List(\'A)\nDict.values({ foo: 3, bar: 20 }) // [3, 20]\n\nDict.pick: (Dict(\'A), keys: List(String)) => Dict(\'A)\nCreates a new dictionary that only includes the picked keys.\ndata = { a: 1, b: 2, c: 3, d: 4 }\nDict.pick(data, ["a", "c"]) // {a: 1, c: 3}\n\n---\n\n## 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.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Dist\n\nDistributions are the flagship data type in Squiggle. The distribution type is a generic data type that contains one of three different formats of distributions.\n\nThese subtypes are [point set](/docs/api/DistPointSet), [sample set](/docs/api/DistSampleSet), and [symbolic](/docs/api/Sym). 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).\n\nSeveral 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.\n\nDistributions are created as [sample sets](/DistSampleSet) by default. To create a symbolic distribution, use `Sym.` namespace: `Sym.normal`, `Sym.beta` and so on.\n\n## Distributions\n\nThese are functions for creating primitive 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.\n\nSee a longer tutorial on creating distributions [here](/docs/Guides/DistributionCreation).\n\nDist.make: (Dist) => Dist, (Number) => SymbolicDist\nDist.make(5)\nDist.make(normal({p5: 4, p95: 10}))\n\nDist.mixture: (List(Dist|Number), weights?: List(Number)) => Dist, (Dist|Number) => Dist, (Dist|Number, Dist|Number, weights?: [Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number, Number]) => Dist\nThe `mixture` function takes a list of distributions and a list of weights, and returns a new distribution that is a mixture of the distributions in the list. The weights should be positive numbers that sum to 1. If no weights are provided, the function will assume that all distributions have equal weight.\n\nNote: If you want to pass in over 5 distributions, you must use the list syntax.\nmixture(1,normal(5,2))\nmixture(normal(5,2), normal(10,2), normal(15,2), [0.3, 0.5, 0.2])\nmixture([normal(5,2), normal(10,2), normal(15,2), normal(20,1)], [0.3, 0.5, 0.1, 0.1])\n\nDist.mx: (List(Dist|Number), weights?: List(Number)) => Dist, (Dist|Number) => Dist, (Dist|Number, Dist|Number, weights?: [Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number, Number]) => Dist\nAlias for mixture()\nmx(1,normal(5,2))\n\nDist.normal: (mean: Dist|Number, stdev: Dist|Number) => SampleSetDist, ({p5: Number, p95: Number}) => SampleSetDist, ({p10: Number, p90: Number}) => SampleSetDist, ({p25: Number, p75: Number}) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist\nnormal(5,1)\nnormal({p5: 4, p95: 10})\nnormal({p10: 4, p90: 10})\nnormal({p25: 4, p75: 10})\nnormal({mean: 5, stdev: 2})\n\nDist.lognormal: (mu: Dist|Number, sigma: Dist|Number) => SampleSetDist, ({p5: Number, p95: Number}) => SampleSetDist, ({p10: Number, p90: Number}) => SampleSetDist, ({p25: Number, p75: Number}) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist\nlognormal(0.5, 0.8)\nlognormal({p5: 4, p95: 10})\nlognormal({p10: 4, p90: 10})\nlognormal({p25: 4, p75: 10})\nlognormal({mean: 5, stdev: 2})\n\nDist.uniform: (low: Dist|Number, high: Dist|Number) => SampleSetDist\nuniform(10, 12)\n\nDist.beta: (alpha: Dist|Number, beta: Dist|Number) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist\nbeta(20, 25)\nbeta({mean: 0.39, stdev: 0.1})\n\nDist.cauchy: (location: Dist|Number, scale: Dist|Number) => SampleSetDist\ncauchy(5, 1)\n\nDist.gamma: (shape: Dist|Number, scale: Dist|Number) => SampleSetDist\ngamma(5, 1)\n\nDist.logistic: (location: Dist|Number, scale: Dist|Number) => SampleSetDist\nlogistic(5, 1)\n\nDist.to to: (p5: Dist|Number, p95: Dist|Number) => SampleSetDist\nThe "to" function is a shorthand for lognormal({p5:min, p95:max}). It does not accept values of 0 or less, as those are not valid for lognormal distributions.\n5 to 10\nto(5,10)\n\nDist.exponential: (rate: Dist|Number) => SampleSetDist\nexponential(2)\n\nDist.bernoulli: (p: Dist|Number) => SampleSetDist\nbernoulli(0.5)\n\nDist.triangular: (min: Number, mode: Number, max: Number) => SampleSetDist\ntriangular(3, 5, 10)\n\n## Basic Functions\n\nDist.mean: (Dist) => Number\n\nDist.median: (Dist) => Number\n\nDist.stdev: (Dist) => Number\n\nDist.variance: (Dist) => Number\n\nDist.min: (Dist) => Number\n\nDist.max: (Dist) => Number\n\nDist.mode: (Dist) => Number\n\nDist.sample: (Dist) => Number\n\nDist.sampleN: (Dist, n: Number) => List(Number)\n\nDist.exp: (Dist) => Dist\n\nDist.cdf: (Dist, Number) => Number\n\nDist.pdf: (Dist, Number) => Number\n\nDist.inv: (Dist, Number) => Number\n\nDist.quantile: (Dist, Number) => Number\n\nDist.truncate: (Dist, left: Number, right: Number) => Dist\nTruncates both the left side and the right side of a distribution.\n\nSample 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.\n\nDist.truncateLeft: (Dist, Number) => Dist\n\nDist.truncateRight: (Dist, Number) => Dist\n\n## Algebra (Dist)\n\nDist.add +: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.multiply \\*: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.subtract -: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.divide /: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.pow ^: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.log: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.log: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.log10: (Dist) => Dist\n\nDist.unaryMinus -: (Dist) => Dist\n\n## Algebra (List)\n\nDist.sum: (List(Dist|Number)) => Dist\n\nDist.product: (List(Dist|Number)) => Dist\n\nDist.cumsum: (List(Dist|Number)) => List(Dist)\n\nDist.cumprod: (List(Dist|Number)) => List(Dist)\n\nDist.diff: (List(Dist|Number)) => List(Dist)\n\n## Pointwise Algebra\n\nPointwise 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.\n\nThe infixes `.+`,`.-`, `.*`, `./`, `.^` are supported for their respective operations. `Mixture` works using pointwise addition.\n\nPointwise operations work on Point Set distributions, so will convert other distributions to Point Set ones first. 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.\n\nDist.dotAdd: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotMultiply: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotSubtract: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotDivide: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\nDist.dotPow: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist\n\n## Normalization\n\nThere 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.\n\nThe 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.\n\nDist.normalize: (Dist) => Dist\nNormalize 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.\n\nDist.isNormalized: (Dist) => Bool\nCheck if a distribution is normalized. This only impacts Point Set distributions, because those are the only ones that can be non-normlized. Most distributions are typically normalized, but there are some commands that could produce non-normalized distributions.\n\nDist.integralSum: (Dist) => Number\nGet 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.\n\n## Utility\n\nDist.sparkline: (Dist, Number?) => String\n\nProduce a sparkline of length `n`. For example, `▁▁▁▁▁▂▄▆▇██▇▆▄▂▁▁▁▁▁`. These can be useful for testing or quick visualizations that can be copied and pasted into text.\n\n## Scoring\n\nDist.klDivergence: (Dist, Dist) => Number\n[Kullback–Leibler divergence](https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence) between two distributions.\n\nNote 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.\nDist.klDivergence(Sym.normal(5,2), Sym.normal(5,1.5))\n\nDist.logScore: ({estimate: Dist, answer: Dist|Number, prior?: Dist}) => Number\nA 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.\n\n Note that it is fairly slow.\n\nDist.logScore({estimate: Sym.normal(5,2), answer: Sym.normal(5.2,1), prior: Sym.normal(5.5,3)})\nDist.logScore({estimate: Sym.normal(5,2), answer: Sym.normal(5.2,1)})\nDist.logScore({estimate: Sym.normal(5,2), answer: 4.5})\n\n---\n\n## description: Sample set distributions are one of the three distribution formats. Internally, they are stored as a list of numbers.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# SampleSet\n\nSample 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.\n\nMonte Carlo calculations typically result in sample set distributions.\n\nAll regular distribution function work on sample set distributions. In addition, there are several functions that only work on sample set distributions.\n\n## Constructors\n\nSampleSet.make: (Dist) => SampleSetDist, (Number) => SampleSetDist, (List(Number)) => SampleSetDist, ((index?: Number) => Number) => SampleSetDist\nCalls the correct conversion constructor, based on the corresponding input type, to create a sample set distribution\nSampleSet(5)\nSampleSet.make([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])\nSampleSet.make({|i| sample(normal(5,2))})\n\n## Conversions\n\nSampleSet.fromDist: (Dist) => SampleSetDist\nConverts any distribution type into a sample set distribution.\nSampleSet.fromDist(Sym.normal(5,2))\n\nSampleSet.fromNumber: (Number) => SampleSetDist\nConvert a number into a sample set distribution that contains `n` copies of that number. `n` refers to the model sample count.\nSampleSet.fromNumber(3)\n\nSampleSet.fromList: (List(Number)) => SampleSetDist\nConvert a list of numbers into a sample set distribution.\nSampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])\n\nSampleSet.toList: (SampleSetDist) => List(Number)\nGets the internal samples of a sampleSet distribution. This is separate from the `sampleN()` function, which would shuffle the samples. `toList()` maintains order and length.\nSampleSet.toList(SampleSet.fromDist(normal(5,2)))\n\nSampleSet.fromFn: ((index?: Number) => Number) => SampleSetDist\nConvert a function into a sample set distribution by calling it `n` times.\nSampleSet.fromFn({|i| sample(normal(5,2))})\n\n## Transformations\n\nSampleSet.map: (SampleSetDist, fn: (Number) => Number) => SampleSetDist\nTransforms a sample set distribution by applying a function to each sample. Returns a new sample set distribution.\nSampleSet.map(SampleSet.fromDist(normal(5,2)), {|x| x + 1})\n\nSampleSet.map2: (SampleSetDist, SampleSetDist, fn: (Number, Number) => Number) => SampleSetDist\nTransforms two sample set distributions by applying a function to each pair of samples. Returns a new sample set distribution.\nSampleSet.map2(\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\n{|x, y| x + y}\n)\n\nSampleSet.map3: (SampleSetDist, SampleSetDist, SampleSetDist, fn: (Number, Number, Number) => Number) => SampleSetDist\nSampleSet.map3(\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\n{|x, y, z| max([x,y,z])}\n)\n\nSampleSet.mapN: (List(SampleSetDist), fn: (List(Number)) => Number) => SampleSetDist\nSampleSet.mapN(\n[\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2)),\nSampleSet.fromDist(normal(5,2))\n],\nmax\n)\n\n---\n\n## description: The Sym module provides functions to create some common symbolic distributions.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Sym\n\nSymbolic Distributions. All these functions match the functions for creating sample set distributions, but produce symbolic distributions instead. Symbolic distributions won\'t capture correlations, but are more performant than sample distributions.\n\nSym.normal: (Number, Number) => SymbolicDist, ({p5: Number, p95: Number}) => SymbolicDist, ({p10: Number, p90: Number}) => SymbolicDist, ({p25: Number, p75: Number}) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist\nSym.normal(5, 1)\nSym.normal({ p5: 4, p95: 10 })\nSym.normal({ p10: 4, p90: 10 })\nSym.normal({ p25: 4, p75: 10 })\nSym.normal({ mean: 5, stdev: 2 })\n\nSym.lognormal: (Number, Number) => SymbolicDist, ({p5: Number, p95: Number}) => SymbolicDist, ({p10: Number, p90: Number}) => SymbolicDist, ({p25: Number, p75: Number}) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist\nSym.lognormal(0.5, 0.8)\nSym.lognormal({ p5: 4, p95: 10 })\nSym.lognormal({ p10: 4, p90: 10 })\nSym.lognormal({ p25: 4, p75: 10 })\nSym.lognormal({ mean: 5, stdev: 2 })\n\nSym.uniform: (Number, Number) => SymbolicDist\nSym.uniform(10, 12)\n\nSym.beta: (Number, Number) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist\nSym.beta(20, 25)\nSym.beta({ mean: 0.39, stdev: 0.1 })\n\nSym.cauchy: (Number, Number) => SymbolicDist\nSym.cauchy(5, 1)\n\nSym.gamma: (Number, Number) => SymbolicDist\nSym.gamma(5, 1)\n\nSym.logistic: (Number, Number) => SymbolicDist\nSym.logistic(5, 1)\n\nSym.exponential: (Number) => SymbolicDist\nSym.exponential(2)\n\nSym.bernoulli: (Number) => SymbolicDist\nSym.bernoulli(0.5)\n\nSym.pointMass: (Number) => SymbolicDist\nPoint mass distributions are already symbolic, so you can use the regular `pointMass` function.\npointMass(0.5)\n\nSym.triangular: (Number, Number, Number) => SymbolicDist\nSym.triangular(3, 5, 10)\n\n---\n\n## 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.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# PointSet\n\nPoint 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.\n\nOne 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.\n\n## Constructors\n\nPointSet.make: (Dist) => PointSetDist, (Number) => PointSetDist\nPointSet.make(normal(5,10))\nPointSet(3)\n\nPointSet.makeContinuous: (List({x: Number, y: Number})) => PointSetDist\nPointSet.makeContinuous([\n{x: 0, y: 0.2},\n{x: 1, y: 0.7},\n{x: 2, y: 0.8},\n{x: 3, y: 0.2}\n])\n\nPointSet.makeDiscrete: (List({x: Number, y: Number})) => PointSetDist\nPointSet.makeDiscrete([\n{x: 0, y: 0.2},\n{x: 1, y: 0.7},\n{x: 2, y: 0.8},\n{x: 3, y: 0.2}\n])\n\n## Conversions\n\nPointSet.fromDist: (Dist) => PointSetDist\nConverts 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.\nPointSet.fromDist(normal(5,2))\n\nPointSet.fromNumber: (Number) => PointSetDist\nPointSet.fromNumber(3)\n\nPointSet.downsample: (PointSetDist, newLength: Number) => PointSetDist\nPointSet.downsample(PointSet.fromDist(normal(5,2)), 50)\n\nPointSet.support: (PointSetDist) => {points: List(Number), segments: List([Number, Number])}\nPointSet.support(PointSet.fromDist(normal(5,2)))\n\n## Transformations\n\nPointSet.mapY: (PointSetDist, fn: (Number) => Number) => PointSetDist\nPointSet.mapY(mx(Sym.normal(5,2)), {|x| x + 1})\n\n---\n\n## description: Durations are a simple time type, representing a length of time. They are internally stored as milliseconds, but often shown and written using seconds, minutes, hours, days, etc.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Duration\n\nDurations are a simple time type, representing a length of time. They are internally stored as milliseconds, but often shown and written using seconds, minutes, hours, days, etc. Durations are typically used with [Date](./Date) values.\n\n| **Unit Name** | **Example** | **Convert Number to Duration** | **Convert Duration to Number** |\n| ------------- | ----------- | ------------------------------ | ------------------------------ |\n| Minute | `5minutes` | `fromMinutes(number)` | `toMinutes(duration)` |\n| Hour | `5hour` | `fromHours(number)` | `toHours(duration)` |\n| Day | `5days` | `fromDays(number)` | `toDays(duration)` |\n| Year | `5years` | `fromYears(number)` | `toYears(duration)` |\n\n## Constructors\n\nDuration.fromMinutes: (Number) => Duration\nDuration.fromMinutes(5)\n\nDuration.fromHours: (Number) => Duration\nDuration.fromHours(5)\n\nDuration.fromDays: (Number) => Duration\nDuration.fromDays(5)\n\nDuration.fromYears: (Number) => Duration\nDuration.fromYears(5)\n\n## Conversions\n\nDuration.toMinutes: (Duration) => Number\nDuration.toMinutes(5minutes)\n\nDuration.toHours: (Duration) => Number\nDuration.toHours(5minutes)\n\nDuration.toDays: (Duration) => Number\nDuration.toDays(5minutes)\n\nDuration.toYears: (Duration) => Number\nDuration.toYears(5minutes)\n\n## Algebra\n\nDuration.unaryMinus -: (Duration) => Duration\n-5minutes\n\nDuration.add +: (Duration, Duration) => Duration\n5minutes + 10minutes\n\nDuration.subtract -: (Duration, Duration) => Duration\n5minutes - 10minutes\n\nDuration.multiply _: (Duration, Number) => Duration, (Number, Duration) => Duration\n5minutes _ 10\n10 \\* 5minutes\n\nDuration.divide /: (Duration, Duration) => Number\n5minutes / 2minutes\n\nDuration.divide /: (Duration, Duration) => Number\n5minutes / 2minutes\n\n## Comparison\n\nDuration.smaller <: (Duration, Duration) => Bool\n\nDuration.larger >: (Duration, Duration) => Bool\n\nDuration.smallerEq <=: (Duration, Duration) => Bool\n\nDuration.largerEq >=: (Duration, Duration) => Bool\n\n---\n\n## description: Lists are a simple data structure that can hold any type of value. They are similar to arrays in Javascript or lists in Python.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# List\n\nLists are a simple data structure that can hold any type of value. They are similar to arrays in Javascript or lists in Python.\n\n```squiggle\nmyList = [1, 2, 3, normal(5,2), "hello"]\n```\n\nLists are immutable, meaning that they cannot be modified. Instead, all list functions return a new list.\n\n## Constructors\n\nList.make: (count: Number, fn: (index?: Number) => \'A) => List(\'A), (count: Number, value: \'A) => List(\'A), (SampleSetDist) => List(Number)\nCreates an array of length `count`, with each element being `value`. If `value` is a function, it will be called `count` times, with the index as the argument.\nList.make(2, 3)\nList.make(2, {|| 3})\nList.make(2, {|index| index+1})\n\nList.upTo: (low: Number, high: Number) => List(Number)\nList.upTo(1,4)\n\n## Modifications\n\nList.reverse: (List(\'A)) => List(\'A)\nList.reverse([1,4,5]) // [5,4,1]\n\nList.concat: (List(\'A), List(\'A)) => List(\'A)\nList.concat([1,2,3], [4, 5, 6])\n\nList.sortBy: (List(\'A), fn: (\'A) => Number) => List(\'A)\nList.sortBy([{a:3}, {a:1}], {|f| f.a})\n\nList.append: (List(\'A), \'A) => List(\'A)\nList.append([1,4],5)\n\nList.join: (List(String), separator?: String) => String, (List(String)) => String\nList.join(["a", "b", "c"], ",") // "a,b,c"\n\nList.flatten: (List(any)) => List(any)\nList.flatten([[1,2], [3,4]])\n\nList.shuffle: (List(\'A)) => List(\'A)\nList.shuffle([1,3,4,20])\n\nList.zip: (List(\'A), List(\'B)) => List([\'A, \'B])\nList.zip([1,3,4,20], [2,4,5,6])\n\nList.unzip: (List([\'A, \'B])) => [List(\'A), List(\'B)]\nList.unzip([[1,2], [2,3], [4,5]])\n\n## Filtering\n\nList.slice: (List(\'A), startIndex: Number, endIndex?: Number) => List(\'A)\nReturns a copy of the list, between the selected `start` and `end`, end not included. Directly uses the [Javascript implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) underneath.\nList.slice([1,2,5,10],1,3)\n\nList.uniq: (List(\'A)) => List(\'A)\nFilters the list for unique elements. Works on select Squiggle types.\nList.uniq([1,2,3,"hi",false,"hi"])\n\nList.uniqBy: (List(\'A), (\'A) => \'B) => List(\'A)\nFilters the list for unique elements. Works on select Squiggle types.\nList.uniqBy([[1,5], [3,5], [5,7]], {|x| x[1]})\n\nList.filter: (List(\'A), fn: (\'A) => Bool) => List(\'A)\nList.filter([1,4,5], {|x| x>3})\n\n## Queries\n\nList.length: (List(any)) => Number\nList.length([1,4,5])\n\nList.first: (List(\'A)) => \'A\nList.first([1,4,5])\n\nList.last: (List(\'A)) => \'A\nList.last([1,4,5])\n\nList.minBy: (List(\'A), fn: (\'A) => Number) => \'A\nList.minBy([{a:3}, {a:1}], {|f| f.a})\n\nList.maxBy: (List(\'A), fn: (\'A) => Number) => \'A\nList.maxBy([{a:3}, {a:1}], {|f| f.a})\n\nList.every: (List(\'A), fn: (\'A) => Bool) => Bool\nList.every([1,4,5], {|el| el>3 })\n\nList.some: (List(\'A), fn: (\'A) => Bool) => Bool\nList.some([1,4,5], {|el| el>3 })\n\nList.find: (List(\'A), fn: (\'A) => Bool) => \'A\nReturns an error if there is no value found\nList.find([1,4,5], {|el| el>3 })\n\nList.findIndex: (List(\'A), fn: (\'A) => Bool) => Number\nReturns `-1` if there is no value found\nList.findIndex([1,4,5], {|el| el>3 })\n\nList.sample: (List(\'A)) => \'A\nList.sample([1,4,5])\n\nList.sampleN: (List(\'A), n: Number) => List(\'A)\nList.sampleN([1,4,5], 2)\n\n## Functional Transformations\n\nList.map: (List(\'A), (\'A, index?: Number) => \'B) => List(\'B)\nList.map([1,4,5], {|x| x+1})\nList.map([1,4,5], {|x,i| x+i+1})\n\nList.reduce: (List(\'B), initialValue: \'A, callbackFn: (accumulator: \'A, currentValue: \'B, currentIndex?: Number) => \'A) => \'A\nApplies `f` to each element of `arr`. The function `f` has two main paramaters, an accumulator and the next value from the array. It can also accept an optional third `index` parameter.\nList.reduce([1,4,5], 2, {|acc, el| acc+el})\n\nList.reduceReverse: (List(\'B), initialValue: \'A, callbackFn: (accumulator: \'A, currentValue: \'B) => \'A) => \'A\nWorks like `reduce`, but the function is applied to each item from the last back to the first.\nList.reduceReverse([1,4,5], 2, {|acc, el| acc-el})\n\nList.reduceWhile: (List(\'B), initialValue: \'A, callbackFn: (accumulator: \'A, currentValue: \'B) => \'A, conditionFn: (\'A) => Bool) => \'A\nWorks like `reduce`, but stops when the condition is no longer met. This is useful, in part, for simulating processes that need to stop based on the process state.\n\n// Adds first two elements, returns `11`.\nList.reduceWhile([5, 6, 7], 0, {|acc, curr| acc + curr}, {|acc| acc < 15})\n\n// Adds first two elements, returns `{ x: 11 }`.\nList.reduceWhile(\n[5, 6, 7],\n{ x: 0 },\n{|acc, curr| { x: acc.x + curr }},\n{|acc| acc.x < 15}\n)\n\n---\n\n## description: Simple constants and functions for math in Squiggle.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Math\n\n## Constants\n\n| Variable Name | Number Name | Value |\n| -------------- | --------------------------------------------------------------------------------- | -------------------- |\n| `Math.e` | Euler\'s number | ≈ 2.718281828459045 |\n| `Math.ln2` | Natural logarithm of 2 | ≈ 0.6931471805599453 |\n| `Math.ln10` | Natural logarithm of 10 | ≈ 2.302585092994046 |\n| `Math.log2e` | Base 2 logarithm of E | ≈ 1.4426950408889634 |\n| `Math.log10e` | Base 10 logarithm of E | ≈ 0.4342944819032518 |\n| `Math.pi` | Pi - ratio of the circumference to the diameter of a circle | ≈ 3.141592653589793 |\n| `Math.sqrt1_2` | Square root of 1/2 | ≈ 0.7071067811865476 |\n| `Math.sqrt2` | Square root of 2 | ≈ 1.4142135623730951 |\n| `Math.phi` | Phi is the golden ratio. | 1.618033988749895 |\n| `Math.tau` | Tau is the ratio constant of a circle\'s circumference to radius, equal to 2 \\* pi | 6.283185307179586 |\n\n## Functions\n\nMath.sqrt: (Number) => Number\n\nMath.sin: (Number) => Number\n\nMath.cos: (Number) => Number\n\nMath.tan: (Number) => Number\n\nMath.asin: (Number) => Number\n\nMath.acos: (Number) => Number\n\nMath.atan: (Number) => Number\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# MixedSet\n\nThe MixedSet module offers functionality for creating mixed sets, which are sets that can contain both discrete and continuous values. Discrete values are represented as points, while continuous values are represented as ranges. Mixed sets are particularly useful for describing the support of mixed probability distributions.\n\nThe majority of set functions in the MixedSet module are designed to mirror the [upcomming set functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) in Javascript.\n\nThe primary purpose of mixed sets in Squiggle is to facilitate scoring. For instance, by utilizing mixed sets, you can easily determine if one distribution covers the support of another distribution. If it doesn\'t, it may be prone to receiving a score of negative infinity.\n\nCurrently, there is no dedicated MixedSet object type. Instead, mixed sets are implemented as dictionaries, where discrete values are stored as points and continuous values are stored as segments.\n\nMixedSet.difference: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])}\n\nMixedSet.intersection: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])}\n\nMixedSet.union: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])}\n\nMixedSet.isSubsetOf: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.isSupersetOf: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.isEqual: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.isEmpty: ({points: List(Number), segments: List([Number, Number])}) => Bool\n\nMixedSet.min: ({points: List(Number), segments: List([Number, Number])}) => Number\nReturns the minimum value in the set\n\nMixedSet.max: ({points: List(Number), segments: List([Number, Number])}) => Number\nReturns the maximum value in the set\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Plot\n\nThe Plot module provides functions to create plots of distributions and functions.\n\nRaw 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.\n\nPlot.dist: (dist: Dist, params?: {xScale?: Scale, yScale?: Scale, showSummary?: Bool}) => Plot\nPlot.dist(\nnormal(5, 2),\n{\nxScale: Scale.linear({ min: -2, max: 6, title: "X Axis Title" }),\nshowSummary: true,\n}\n)\n\nPlot.dists: (dists: List(Dist|Number)|List({name?: String, value: Dist|Number}), {xScale?: Scale, yScale?: Scale, showSummary?: Bool}?) => Plot\nPlot.dists(\n{\ndists: [\n{ name: "First Dist", value: normal(0, 1) },\n{ name: "Second Dist", value: uniform(2, 4) },\n],\nxScale: Scale.symlog({ min: -2, max: 5 }),\n}\n)\n\nPlot.numericFn: (fn: (Number) => Number, params?: {xScale?: Scale, yScale?: Scale, xPoints?: List(Number)}) => Plot\nPlot.numericFn(\n{|t|t ^ 2},\n{ xScale: Scale.log({ min: 1, max: 100 }), points: 10 }\n)\n\nPlot.distFn: (fn: (Number) => Dist, params?: {xScale?: Scale, yScale?: Scale, distXScale?: Scale, xPoints?: List(Number)}) => Plot\nPlot.distFn(\n{|t|normal(t, 2) \\* normal(5, 3)},\n{\nxScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }),\nyScale: Scale.linear({ title: "Value" }),\ndistXScale: Scale.linear({ tickFormat: "#x" }),\n}\n)\n\nPlot.scatter: ({xDist: SampleSetDist, yDist: SampleSetDist, xScale?: Scale, yScale?: Scale}) => Plot\nxDist = SampleSet.fromDist(2 to 5)\nyDist = normal({p5:-3, p95:3}) _ 5 - xDist ^ 2\nPlot.scatter({\nxDist: xDist,\nyDist: yDist,\nxScale: Scale.log({min: 1.5}),\n})\nxDist = SampleSet.fromDist(normal({p5:-2, p95:5}))\nyDist = normal({p5:-3, p95:3}) _ 5 - xDist\nPlot.scatter({\nxDist: xDist,\nyDist: yDist,\nxScale: Scale.symlog({title: "X Axis Title"}),\nyScale: Scale.symlog({title: "Y Axis Title"}),\n})\n\n---\n\n## description: Squiggle numbers are Javascript floats.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Number\n\nSquiggle numbers are Javascript floats.\n\n## Constants\n\n| Variable Name | Number Name | Value |\n| ----------------- | --------------------------------------------------------------- | ----------------------- |\n| `Number.minValue` | The smallest positive numeric value representable in JavaScript | 5e-324 |\n| `Number.maxValue` | The largest positive numeric value representable in JavaScript | 1.7976931348623157e+308 |\n\n## Functions\n\n## Comparison\n\nNumber.smaller <: (Number, Number) => Bool\n\nNumber.larger >: (Number, Number) => Bool\n\nNumber.smallerEq <=: (Number, Number) => Bool\n\nNumber.largerEq >=: (Number, Number) => Bool\n\n## Algebra (Number)\n\nNumber.add +: (Number, Number) => Number\n\nNumber.subtract -: (Number, Number) => Number\n\nNumber.multiply \\*: (Number, Number) => Number\n\nNumber.divide /: (Number, Number) => Number\n\nNumber.pow ^: (Number, Number) => Number\n\nNumber.mod: (Number, Number) => Number\nmodulo. This is the same as the \'%\' operator in Python. Note that there is no \'%\' operator in Squiggle for this operation.\nmod(10, 3)\nmod(-10, 3)\nmod(10, -3)\n\n## Functions (Number)\n\nNumber.unaryMinus -: (Number) => Number\nexp(3.5)\n\nNumber.exp: (Number) => Number\nexponent\nexp(3.5)\n\nNumber.log: (Number) => Number\nlog(3.5)\n\nNumber.log10: (Number) => Number\nlog10(3.5)\n\nNumber.log2: (Number) => Number\nlog2(3.5)\n\nNumber.floor: (Number) => Number\nfloor(3.5)\n\nNumber.ceil: (Number) => Number\nceil(3.5)\n\nNumber.abs: (Number) => Number\nabsolute value\nabs(3.5)\n\nNumber.round: (Number) => Number\nround(3.5)\n\n## Algebra (List)\n\nNumber.sum: (List(Number)) => Number\nsum([3,5,2])\n\nNumber.product: (List(Number)) => Number\nproduct([3,5,2])\n\nNumber.cumprod: (List(Number)) => List(Number)\ncumulative product\ncumprod([3,5,2,3,5])\n\nNumber.diff: (List(Number)) => List(Number)\ndiff([3,5,2,3,5])\n\n## Functions (List)\n\nNumber.min: (List(Number)) => Number, (Number, Number) => Number\nmin([3,5,2])\n\nNumber.max: (List(Number)) => Number, (Number, Number) => Number\nmax([3,5,2])\n\nNumber.mean: (List(Number)) => Number\nmean([3,5,2])\n\nNumber.quantile: (List(Number), Number) => Number\nquantile([1,5,10,40,2,4], 0.3)\n\nNumber.median: (List(Number)) => Number\nmedian([1,5,10,40,2,4])\n\nNumber.geomean: (List(Number)) => Number\ngeometric mean\ngeomean([3,5,2])\n\nNumber.stdev: (List(Number)) => Number\nstandard deviation\nstdev([3,5,2,3,5])\n\nNumber.variance: (List(Number)) => Number\nvariance([3,5,2,3,5])\n\nNumber.sort: (List(Number)) => List(Number)\nsort([3,5,2,3,5])\n\n## Utils\n\nNumber.rangeDomain: (min: Number, max: Number) => Domain\nNumber.rangeDomain(5, 10)\n\n---\n\n## description: Scales for plots.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Scale\n\nChart axes in [plots](./Plot.mdx) can be scaled using the following functions. Each scale function accepts optional min and max value. Power scale accepts an extra exponent parameter.\n\nSquiggle uses D3 for the tick formats. You can read about d3 tick formats [here](https://github.com/d3/d3-format).\n\n## Numeric Scales\n\nScale.linear: ({min?: Number, max?: Number, tickFormat?: String, title?: String}) => Scale, () => Scale\nScale.linear({ min: 3, max: 10 })\n\nScale.log: ({min?: Number, max?: Number, tickFormat?: String, title?: String}) => Scale, () => Scale\nScale.log({ min: 1, max: 100 })\n\nScale.symlog: ({min?: Number, max?: Number, tickFormat?: String, title?: String, constant?: Number}) => Scale, () => Scale\nSymmetric log scale. Useful for plotting data that includes zero or negative values.\n\nThe 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).\n\nThe default value for `constant` is `0.0001`.\nScale.symlog({ min: -10, max: 10 })\n\nScale.power: ({min?: Number, max?: Number, tickFormat?: String, title?: String, exponent?: Number}) => Scale, () => Scale\nPower scale. Accepts an extra `exponent` parameter, like, `Scale.power({exponent: 2, min: 0, max: 100})`.\n\nThe default value for `exponent` is `0.1`.\nScale.power({ min: 1, max: 100, exponent: 0.1 })\n\n## Date Scales\n\nScale.date: ({min?: Date, max?: Date, tickFormat?: String, title?: String}) => Scale, () => Scale\nOnly works on Date values. Is a linear scale under the hood.\nScale.date({ min: Date(2022), max: Date(2025) })\n\n---\n\n## description: Function Specifications\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Spec\n\nFunction specifications (Specs) are an experimental feature in Squiggle. They are used to specify the structure of functions and verify that they match that structure. They are used primarily as a tag for functions.\n\nSpec.make: ({name: String, documentation: String, validate: Lambda}) => Specification\nCreate a specification.\n@startClosed\nvalidate(fn) = {\nhasErrors = List.upTo(2020, 2030)\n-> List.some(\n{|e| typeOf(fn(Date(e))) != "Distribution"}\n)\nhasErrors ? "Some results aren\'t distributions" : ""\n}\n\nspec = Spec.make(\n{\nname: "Stock market over time",\ndocumentation: "A distribution of stock market values over time.",\nvalidate: validate,\n}\n)\n\n@spec(spec)\nmyEstimate(t: [Date(2020), Date(2030)]) = normal(10, 3)\n\n---\n\n## description: Functions for working with strings in Squiggle\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# String\n\nStrings support all JSON escape sequences, with addition of escaped single-quotes (for single-quoted strings)\n\n```squiggle\na = "\'\\" NUL:\\u0000"\nb = \'\\\'" NUL:\\u0000\'\n```\n\nString.make: (any) => String\nConverts any value to a string. Some information is often lost.\n\nString.concat: (String, String) => String, (String, any) => String\n\nString.add +: (String, String) => String, (String, any) => String\n\nString.split: (String, separator: String) => List(String)\n\n---\n\n## description: Tables are a simple date time type.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Table\n\nThe Table module allows you to make simple tables for displaying data.\n\nTable.make: (data: List(\'A), params: {columns: List({fn: (\'A) => any, name?: String})}) => Table\nTable.make(\n[\n{ name: "First Dist", value: normal(0, 1) },\n{ name: "Second Dist", value: uniform(2, 4) },\n{ name: "Third Dist", value: uniform(5, 6) },\n],\n{\ncolumns: [\n{ name: "Name", fn: {|d|d.name} },\n{ name: "Mean", fn: {|d|mean(d.value)} },\n{ name: "Std Dev", fn: {|d|variance(d.value)} },\n{ name: "Dist", fn: {|d|d.value} },\n],\n}\n)\nTable.make(\n[\n{ name: "First Dist", value: Sym.lognormal({ p5: 1, p95: 10 }) },\n{ name: "Second Dist", value: Sym.lognormal({ p5: 5, p95: 30 }) },\n{ name: "Third Dist", value: Sym.lognormal({ p5: 50, p95: 90 }) },\n],\n{\ncolumns: [\n{ name: "Name", fn: {|d|d.name} },\n{\nname: "Plot",\nfn: {\n|d|\nPlot.dist(\n{\ndist: d.value,\nxScale: Scale.log({ min: 0.5, max: 100 }),\nshowSummary: false,\n}\n)\n},\n},\n],\n}\n)\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# System\n\n## Constants\n\n### System.version\n\nReturns the current version of Squiggle.\n\n## Functions\n\nSystem.sampleCount: () => Number\nThe number of samples set in the current environment. This variable can be modified in the Squiggle playground settings.\n\n---\n\n## description: The Tag module handles tags, which allow the additions of metadata to Squiggle variables.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Tag\n\nTags are metadata that can be added to Squiggle variables. They are used to add additional information to variables, such as names, descriptions, and visualization options. While tags can be accessed at runtime, they are primarily meant for use with the Squiggle Playground and other visualizations.\nTags can be added to variables either by using their name `Tag.get[Name]` or by using decorators.\n\n## List of Tags\n\n| Tag Name | Description |\n| ------------- | ------------------------------------------------------------------------------------------ |\n| `name` | Change the default display name for the variable, in the playground. |\n| `doc` | Adds documentation to the variable in the playground. |\n| `showAs` | Change the default view for the value when displayed. |\n| `format` | Format a number, date, or duration when displayed. |\n| `notebook` | Formats lists as notebooks. |\n| `hide` | Don\'t show the variable in the playground |\n| `startOpen` | Start the variable open in the playground |\n| `startClosed` | Start the variable closed in the playground |\n| `location` | Store the proper location. Helps when you want to locate code corresponding to a variable. |\n| `exportData` | Metadata about exported variables. Cannot be added manually. |\n\n## Example\n\n\n\n## Tags\n\nTag.name: (\'A, String) => \'A\nAdds a user-facing name to a value. This is useful for documenting what a value represents, or how it was calculated.\n\n_Note: While names are shown in the sidebar, you still need to call variables by their regular variable names in code._\n\nTag.getName: (any) => String\n\nTag.doc: (\'A, String) => \'A\nAdds text documentation to a value. This is useful for documenting what a value represents or how it was calculated.\n\nTag.getDoc: (any) => String\n\nTag.showAs: (Dist, Plot|(Dist) => Plot) => Dist, (List(any), Table|(List(any)) => Table) => List(any), ((Number) => Dist|Number, Plot|Calculator|((Number) => Dist|Number) => Plot|Calculator) => (Number) => Dist|Number, ((Date) => Dist|Number, Plot|Calculator|((Date) => Dist|Number) => Plot|Calculator) => (Date) => Dist|Number, ((Duration) => Dist|Number, Plot|Calculator|((Duration) => Dist|Number) => Plot|Calculator) => (Duration) => Dist|Number, (Lambda, Calculator|(Lambda) => Calculator) => Lambda\nOverrides the default visualization for a value.\n`showAs()` can take either a visualization, or a function that calls the value and returns a visualization.\n\nDifferent types of values can be displayed in different ways. The following table shows the potential visualization types for each input type. In this table, `Number` can be used with Dates and Durations as well. \n| **Input Type** | **Visualization Types** |\n| ----------------------------------- | ------------------------------------- |\n| **Distribution** | `Plot.dist` |\n| **List** | `Table` |\n| **`(Number -> Number)` Function** | `Plot.numericFn`, `Calculator` |\n| **`(Number -> Dist)` Function** | `Plot.distFn`, `Calculator` |\n| **Function** | `Calculator` |\n\nexample1 = ({|x| x + 1}) -> Tag.showAs(Calculator)\n@showAs({|f| Plot.numericFn(f, { xScale: Scale.symlog() })})\nexample2 = {|x| x + 1}\n\nTag.getShowAs: (any) => any\n\nTag.getExportData: (any) => any\n\nTag.spec: (\'A, Specification) => \'A\nAdds a specification to a value. This is useful for documenting how a value was calculated, or what it represents.\n\nTag.getSpec: (any) => any\n\nTag.format: (Dist|Number, numberFormat: String) => Dist|Number, (Duration, numberFormat: String) => Duration, (Date, timeFormat: String) => Date\nSet the display format for a number, distribution, duration, or date. Uses the [d3-format](https://d3js.org/d3-format) syntax on numbers and distributions, and the [d3-time-format](https://d3js.org/d3-time-format) syntax for dates.\n\nTag.getFormat: (Dist|Number) => String, (Duration) => String, (Date) => String\n\nTag.hide: (\'A, Bool) => \'A, (\'A) => \'A\nHides a value when displayed under Variables. This is useful for hiding intermediate values or helper functions that are used in calculations, but are not directly relevant to the user. Only hides top-level variables.\n\nTag.getHide: (any) => Bool\n\nTag.startOpen: (\'A) => \'A\nWhen the value is first displayed, it will begin open in the viewer. Refresh the page to reset.\n\nTag.startClosed: (\'A) => \'A\nWhen the value is first displayed, it will begin collapsed in the viewer. Refresh the page to reset.\n\nTag.getStartOpenState: (any) => String\nReturns the startOpenState of a value, which can be "open", "closed", or "" if no startOpenState is set. Set using `Tag.startOpen` and `Tag.startClosed`.\n\nTag.notebook: (List(\'A), Bool) => List(\'A), (List(\'A)) => List(\'A)\nDisplays the list of values as a notebook. This means that element indices are hidden, and the values are displayed in a vertical list. Useful for displaying combinations of text and values.\nCalculator.make(\n{|f, contents| f ? Tag.notebook(contents) : contents},\n{\ndescription: "Shows the contents as a notebook if the checkbox is checked.",\ninputs: [\nInput.checkbox({ name: "Show as Notebook", default: true }),\nInput.textArea(\n{\nname: "Contents to show",\ndefault: "[\n\\"## Distribution 1\\",\nnormal(5, 2),\n\\"## Distribution 1\\",\nnormal(20, 1),\n\\"This is an opening section. Here is more text.\n\\",\n]",\n}\n),\n],\n}\n)\n\nTag.getNotebook: (any) => Bool\n\nTag.location: (\'A) => \'A\nSaves the location of a value. Note that this must be called at the point where the location is to be saved. If you use it in a helper function, it will save the location of the helper function, not the location where the helper function is called.\n\nTag.getLocation: (any) => any\n\n## Functions\n\nTag.getAll: (any) => Dict(any)\nReturns a dictionary of all tags on a value.\n\nTag.omit: (\'A, List(String)) => \'A\nReturns a copy of the value with the specified tags removed.\n\nTag.clear: (\'A) => \'A\nReturns a copy of the value with all tags removed.\n\n---\n\n## description: The Calculator module helps you create custom calculators\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Calculator\n\nThe Calculator module allows you to make custom calculators for functions. This is a form that\'s tied to a specific Squiggle function, where the inputs to the form are passed to that function, and the output of the function gets shown on the bottom.\n\nCalculators can be useful for debugging functions or to present functions to end users.\n\nCalculator.make: ({fn: Lambda, title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator, (Lambda, params?: {title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator\n\n`Calculator.make` takes in a function, a description, and a list of inputs. The function should take in the same number of arguments as the number of inputs, and the arguments should be of the same type as the default value of the input.\n\nInputs are created using the `Input` module. The Input module has a few different functions for creating different types of inputs.\n\nFor calculators that take a long time to run, we recommend setting `autorun` to `false`. This will create a button that the user can click to run the calculator.\n\nCalculator.make(\n{|text, textArea, select, checkbox| text + textArea},\n{\ntitle: "My example calculator",\ninputs: [\nInput.text({ name: "text", default: "20" }),\nInput.textArea({ name: "textArea", default: "50 to 80" }),\nInput.select({ name: "select", default: "second", options: ["first", "second", "third"] }),\nInput.checkbox({ name: "checkbox", default: true }),\n],\nsampleCount: 10k,\n})\n// When a calculator is created with only a function, it will guess the inputs based on the function\'s parameters. It won\'t provide default values if it\'s a user-written function.\n\n({|x| x \\* 5}) -> Calculator\n\n---\n\n## description: Inputs are now only used for describing forms for calculators.\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Input\n\nInputs are now only used for describing forms for [calculators](./Calculator.mdx).\n\nInput.text: ({name: String, description?: String, default?: Number|String}) => Input\nCreates a single-line input. This input can be used for all Squiggle types.\nInput.text({ name: "First", default: "John" })\nInput.text({ name: "Number of X in Y", default: \'20 to 300\' })\n\nInput.textArea: ({name: String, description?: String, default?: Number|String}) => Input\nCreates a multi-line input, sized with the provided input. This input can be used for all Squiggle types.\nInput.textArea({ name: "people", default: \'{\n"John": 20 to 50,\n"Mary": 30 to 90,\n}\' })\n\nInput.checkbox: ({name: String, description?: String, default?: Bool}) => Input\nCreates a checkbox input. Used for Squiggle booleans.\nInput.checkbox({ name: "IsTrue?", default: true })\n\nInput.select: ({name: String, options: List(String), description?: String, default?: String}) => Input\nCreates a dropdown input. Used for Squiggle strings.\nInput.select({ name: "Name", default: "Sue", options: ["John", "Mary", "Sue"] })\n\n---\n\n## description:\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# RelativeValues\n\n_Warning: Relative value functions are particularly experimental and subject to change._\n\nRelativeValues.gridPlot: ({ids: List(String), fn: (String, String) => [Dist, Dist]}) => Plot\nRelativeValues.gridPlot({\nids: ["foo", "bar"],\nfn: {|id1, id2| [SampleSet.fromDist(2 to 5), SampleSet.fromDist(3 to 6)]},\n})\n\n---\n\n## description: Newer experimental functions which are less stable than Squiggle as a whole\n\nimport { FnDocumentationFromName } from "@quri/squiggle-components";\nimport { SquiggleEditor } from "../../../components/SquiggleEditor";\n\n# Danger\n\nThe Danger library contains newer experimental functions which are less stable than Squiggle as a whole. They are not recommended for production use, but are useful for testing out new ideas.,\n\n## JSON\n\nThe JSON module provides JSON-like objects in Squiggle. `Danger.json` is mainly useful for debugging, and `Danger.jsonString` is useful for sending data to other systems. A simple example is shown below.\n\nWe have custom serializers for different Squiggle objects. Note that this API is unstable and might change over time.\n\n\n\nDanger.json: (any) => any\nConverts a value to a simpler form, similar to JSON. This is useful for debugging. Keeps functions and dates, but converts objects like distributions, calculators, and plots to combinations of dictionaries and lists.\nDanger.json({a: 1, b: 2})\nDanger.json([2 to 5, Sym.normal(5, 2), Calculator({|x| x + 1})])\n\nDanger.jsonString: (any) => String\nConverts a value to a stringified JSON, similar to JSON.stringify() in Javasript. Replaces functions with dict summaries.\nDanger.jsonString({a: 1, b: 2})\nDanger.jsonString([2 to 5, Sym.normal(5, 2), Calculator({|x| x + 1})])\n\n## Javascript\n\nNear 1-1 matches of Javascript functions.\n\nDanger.parseFloat: (String) => Number|String\nConverts a string to a number. If the string can\'t be converted, returns `Parse Failed`. Calls Javascript `parseFloat` under the hood.\nDanger.parseFloat(\'10.3\')\n\nDanger.now: () => Date\nReturns the current date. Internally calls `Date.now()` in JavaScript.\n\n_Caution: This function, which returns the current date, produces varying outputs with each call. As a result, accurately estimating the value of functions that incorporate `Danger.now()` at past time points is challenging. In the future, we intend to implement a feature allowing the input of a simulated time via an environment variable to address this issue._\nDanger.now()\n\n## Math\n\nDanger.laplace: (Number, Number) => Number\nCalculates the probability implied by [Laplace\'s rule of succession](https://en.wikipedia.org/wiki/Rule_of_succession)\ntrials = 10\nsuccesses = 1\nDanger.laplace(successes, trials) // (successes + 1) / (trials + 2) = 2 / 12 = 0.1666\n\nDanger.yTransform: (PointSetDist) => PointSetDist\nDanger.yTransform(PointSet(Sym.normal(5,2)))\n\n## Combinatorics\n\nDanger.factorial: (Number) => Number\nDanger.factorial(20)\n\nDanger.choose: (Number, Number) => Number\n`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).\nDanger.choose(1, 20)\n\nDanger.binomial: (Number, Number, Number) => Number\n`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.\nDanger.binomial(1, 20, 0.5)\n\nDanger.combinations: (List(\'A), Number) => List(List(\'A))\nReturns all combinations of the input list taken r elements at a time.\nDanger.combinations([1, 2, 3], 2) // [[1, 2], [1, 3], [2, 3]]\n\nDanger.allCombinations: (List(\'A)) => List(List(\'A))\nReturns all possible combinations of the elements in the input list.\nDanger.allCombinations([1, 2, 3]) // [[1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]\n\n## Distributions\n\nDanger.binomialDist: (numberOfTrials: Dist|Number, probabilityOfSuccess: Dist|Number) => SampleSetDist\nA binomial distribution.\n\n`n` must be above 0, and `p` must be between 0 and 1.\n\nNote: The binomial distribution is a discrete distribution. When representing this, the Squiggle distribution component might show it as partially or fully continuous. This is a visual mistake; if you inspect the underlying data, it should be discrete.\nDanger.binomialDist(8, 0.5)\n\nDanger.poissonDist: (rate: Dist|Number) => SampleSetDist\nA Poisson distribution.\n\nNote: The Poisson distribution is a discrete distribution. When representing this, the Squiggle distribution component might show it as partially or fully continuous. This is a visual mistake; if you inspect the underlying data, it should be discrete.\nDanger.poissonDist(10)\n\n## Integration\n\nDanger.integrateFunctionBetweenWithNumIntegrationPoints: (f: Lambda, min: Number, max: Number, numIntegrationPoints: Number) => Number\nIntegrates the function `f` between `min` and `max`, and computes `numIntegrationPoints` in between to do so.\n\nNote that the function `f` has to take in and return numbers. To integrate a function which returns distributions, use:\n\n```squiggle\nauxiliaryF(x) = mean(f(x))\n\nDanger.integrateFunctionBetweenWithNumIntegrationPoints(auxiliaryF, min, max, numIntegrationPoints)\n```\n\nDanger.integrateFunctionBetweenWithNumIntegrationPoints({|x| x+1}, 1, 10, 10)\n\nDanger.integrateFunctionBetweenWithEpsilon: (f: Lambda, min: Number, max: Number, epsilon: Number) => Number\nIntegrates 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`.\n\nSame caveats as `integrateFunctionBetweenWithNumIntegrationPoints` apply.\nDanger.integrateFunctionBetweenWithEpsilon({|x| x+1}, 1, 10, 0.1)\n\n## Optimization\n\nDanger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions: (fs: List(Lambda), funds: Number, approximateIncrement: Number) => any\nComputes 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)`.\nDanger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions(\n[\n{|x| x+1},\n{|y| 10}\n],\n100,\n0.01\n)\n'; diff --git a/packages/ai/src/squiggle/squiggleLibraryContents.ts b/packages/ai/src/squiggle/squiggleLibraryContents.ts index 21aeaa1f8b..273580d4d2 100644 --- a/packages/ai/src/squiggle/squiggleLibraryContents.ts +++ b/packages/ai/src/squiggle/squiggleLibraryContents.ts @@ -2,10 +2,10 @@ export const LIBRARY_CONTENTS = new Map([ [ "hub:ozziegooen/sTest", - '@startOpen\n@name("Documentation")\ndocumentation = "\n# SquiggleJest Testing Library\n\nSquiggleJest is a simple testing library for Squiggle, inspired by Jest for JavaScript. It provides a way to write and run tests for your Squiggle models and functions.\n\n## How to Use\n\n1. Import the library (assuming it\'s in a file named \'squiggleJest.squiggle\'):\n ```squiggle\n import \'squiggleJest.squiggle\' as SJ\n ```\n\n2. Create your tests using the `test` function:\n ```squiggle\n test = SJ.test\n expect = SJ.expect\n\n myTest = test(\\"My test description\\", {|| \n expect(2 + 2).toBe(4)\n })\n ```\n\n3. Group related tests using the `describe` function:\n ```squiggle\n describe = SJ.describe\n\n myTestSuite = describe(\\"My Test Suite\\", [\n test(\\"First test\\", {|| expect(true).toBeTrue()}),\n test(\\"Second test\\", {|| expect(5).toBeGreaterThan(3)})\n ])\n ```\n\n4. Run your test suite and view the results.\n\n## Available Matchers\n\n- `toBe(expected)`: Checks for exact equality\n- `toBeGreaterThan(expected)`: Checks if the actual value is greater than the expected\n- `toBeGreaterThanOrEqual(expected)`: Checks if the actual value is greater or equal than the expected\n- `toBeLessThan(expected)`: Checks if the actual value is less than the expected\n- `toBeLessThanOrEqual(expected)`: Checks if the actual value is less than or equal than the expected\n- `toBeTrue()`: Checks if the value is true\n- `toBeFalse()`: Checks if the value is false\n- `toBeCloseTo(expected, epsilon)`: Checks if the actual value is close to the expected value within a given epsilon.\n- `toBeBetween(low, high)`: Checks if the actual value is between the given low and high values (inclusive).\n- `toThrowAnyError(() => any)`: Checks to see if any error was thrown.\n- `toNotThrow(() => any)`: Checks that no error was thrown.\n\n## Examples\n\n### Testing a Simple Function\n\n```squiggle\nadd(a, b) = a + b\n\ndescribe(\\"Add function\\", [\n test(\\"adds two positive numbers\\", {|| \n expect(add(2, 3)).toBe(5)\n }),\n test(\\"adds a positive and a negative number\\", {|| \n expect(add(5, -3)).toBe(2)\n })\n])\n```\n\n### Testing a Distribution\n\n```squiggle\nmyDist = normal(10, 2)\n\ndescribe(\\"My Distribution\\", [\n test(\\"has correct mean\\", {|| \n expect(mean(myDist)).toBe(10)\n }),\n test(\\"has correct standard deviation\\", {|| \n expect(stdev(myDist)).toBe(2)\n }),\n test(\\"90% of values are within 2 standard deviations\\", {||\n lower = 10 - 2 * 2\n upper = 10 + 2 * 2\n expect(cdf(myDist, upper) - cdf(myDist, lower)).toBeGreaterThan(0.9)\n })\n])\n```\n\nThese examples demonstrate how to use SquiggleJest to test various aspects of your Squiggle models, from simple functions to complex models with distributions.\n"\n\nkeyword = "____FAILED____"\n\nfnThrewError(fn) = try(fn, {|| "____FAILED____"}) == keyword\n\n@startClosed\ncreateExpectation(actual) = {\n toBe: {\n |expected|\n if actual != expected then "Expected " + expected + " but got " +\n actual else true\n },\n toBeGreaterThan: {\n |expected|\n if actual > expected then true else "Expected " + actual +\n " to be greater than " +\n expected\n },\n toBeGreaterThanOrEqual: {\n |expected|\n if actual >= expected then true else "Expected " + actual +\n " to be less than or equal" +\n expected\n },\n toBeLessThan: {\n |expected|\n if actual < expected then true else "Expected " + actual +\n " to be less than " +\n expected\n },\n toBeLessThanOrEqual: {\n |expected|\n if actual <= expected then true else "Expected " + actual +\n " to be less than or equal" +\n expected\n },\n toBeBetween: {\n |low, high|\n if actual < low || actual > high then "Expected " + actual +\n " to be between " +\n low +\n " and " +\n high else true\n },\n toBeCloseTo: {\n |expected, epsilon|\n if abs(actual - expected) > epsilon then "Expected " + actual +\n " to be close to " +\n expected +\n " (within " +\n epsilon +\n ")" else true\n },\n toBeTrue: {|| if !actual then "Expected true but got " + actual else true},\n toBeFalse: {|| if actual then "Expected false but got " + actual else true},\n toThrowAnyError: {\n ||\n if fnThrewError(\n actual\n ) then true else "Expected to throw an error, but no error was thrown"\n },\n toNotThrow: {\n ||\n if !fnThrewError(\n actual\n ) then true else "Expected not to throw an error, but an error was thrown"\n },\n}\n\nrunTest(test) = {\n fnResult = test.fn()\n {\n name: test.name,\n passed: fnResult == true,\n error: if fnResult != true then fnResult else "",\n }\n}\n\n@startClosed\ngenerateTestReport(name, testResults) = {\n passedTests = List.filter(testResults, {|t| t.passed})\n failedTests = List.filter(testResults, {|t| !t.passed})\n\n [\n "## Test Suite: " + name + " ",\n "**Total tests**: " + List.length(testResults) + " ",\n "**Passed**: " + List.length(passedTests) + " ",\n "**Failed**: " + List.length(failedTests),\n "",\n "**Results:** ",\n ]\n}\n\n@startClosed\nformatTestResult(testResult) = (if testResult.passed then "✅" else "❌") +\n " " +\n testResult.name +\n (if testResult.error != "" then "\n --- Error: *" + testResult.error +\n "*" else "") +\n " "\n\n// Main squiggleJest framework\n@startClosed\nsquiggleJest = {\n expect: createExpectation,\n test: {|name, fn| { name: name, fn: fn }},\n describe: {\n |name, tests|\n testResults = List.map(tests, runTest)\n report = generateTestReport(name, testResults)\n testDetails = List.map(testResults, formatTestResult)\n List.concat(report, testDetails) -> List.join("\n")\n },\n}\n\nexport test = squiggleJest.test\nexport describe = squiggleJest.describe\nexport expect = squiggleJest.expect\n\n/// Testing ---\n@name("Example Model")\nmodel = { items: [1, 2, 3] }\n\ntestResults = describe(\n "Model Tests",\n [\n test(\n "has items with length 3",\n {|| expect(List.length(model.items)).toBe(3)}\n ),\n test("first item is 1", {|| expect(model.items[0]).toBe(1)}),\n test(\n "last item is greater than 2",\n {|| expect(model.items[2]).toBeGreaterThan(1)}\n ),\n test(\n "second item is less than 3",\n {|| expect(model.items[1]).toBeLessThan(8)}\n ),\n test(\n "second item is between 1 and 5",\n {|| expect(model.items[1]).toBeBetween(1, 3)}\n ),\n test(\n "contains truthy value",\n {|| expect(List.some(model.items, {|i| i > 0})).toBeTrue()}\n ),\n test(\n "doesn\'t contain 4",\n {|| expect(List.some(model.items, {|i| i == 4})).toBeFalse()}\n ),\n test("this test should fail", {|| expect(1).toBe(2)}),\n ]\n)\n\ncomparisonTests = describe(\n "Number Comparisons",\n [\n test("5 is greater than 3", {|| expect(5).toBeGreaterThan(3)}),\n test(\n "5 is greater than or equal to 5",\n {|| expect(5).toBeGreaterThanOrEqual(5)}\n ),\n test("3 is less than 5", {|| expect(3).toBeLessThan(5)}),\n test("5 is less than or equal to 5", {|| expect(5).toBeLessThanOrEqual(5)}),\n test("7 is between 5 and 10", {|| expect(7).toBeBetween(5, 10)}),\n test(\n "5 is close to 5.0001 within 0.001",\n {|| expect(5).toBeCloseTo(5.0001, 0.001)}\n ),\n test("0 is not greater than 0", {|| expect(0).toBeLessThanOrEqual(0)}),\n test("-1 is less than 0", {|| expect(-1).toBeLessThan(0)}),\n test(\n "1000000 is greater than 999999",\n {|| expect(1000000).toBeGreaterThan(999999)}\n ),\n test(\n "0.1 + 0.2 is close to 0.3",\n {|| expect(0.1 + 0.2).toBeCloseTo(0.3, 0.0000001)}\n ),\n test(\n "PI is approximately 3.14159",\n {|| expect(3.14159).toBeCloseTo(Math.pi, 0.00001)}\n ),\n test(\n "e is approximately 2.71828",\n {|| expect(2.71828).toBeCloseTo(Math.e, 0.00001)}\n ),\n test(\n "10 is between 9.99999 and 10.00001",\n {|| expect(10).toBeBetween(9.99999, 10.00001)}\n ),\n test(\n "5 is not between 5.00001 and 10",\n {|| expect(5).toBeLessThan(5.00001)}\n ),\n test("1e-10 is greater than 0", {|| expect(1e-10).toBeGreaterThan(0)}),\n test(\n "The absolute difference between 1/3 and 0.333333 is less than 1e-5",\n {|| expect(abs(1 / 3 - 0.333333)).toBeLessThan(1e-5)}\n ),\n test(\n "2^53 - 1 is the largest integer precisely representable in IEEE 754",\n {|| expect(2 ^ 53 - 1).toBe(9007199254740991)}\n ),\n ]\n)\n\nerrorTests = describe(\n "Error Throwing Tests",\n [\n test(\n "throws any error",\n {|| expect({|| throw("SomeError")}).toThrowAnyError()}\n ),\n test("doesn\'t throw any error", {|| expect({|| 2 + 2}).toNotThrow()}),\n test(\n "fails when expecting an error but none is thrown",\n {|| expect({|| 2 + 2}).toThrowAnyError()}\n ),\n test(\n "fails when expecting no error but one is thrown",\n {|| expect({|| throw("UnexpectedError")}).toNotThrow()}\n ),\n ]\n)\n', + '@startOpen\n@name("Documentation")\ndocumentation = "\n# SquiggleJest Testing Library\n\nSquiggleJest is a simple testing library for Squiggle, inspired by Jest for JavaScript. It provides a way to write and run tests for your Squiggle models and functions.\n\n## How to Use\n\n1. Import the library (assuming it\'s in a file named \'squiggleJest.squiggle\'):\n ```squiggle\n import \'squiggleJest.squiggle\' as SJ\n ```\n\n2. Create your tests using the `test` function:\n ```squiggle\n test = SJ.test\n expect = SJ.expect\n\n myTest = test(\\"My test description\\", {|| \n expect(2 + 2).toBe(4)\n })\n ```\n\n3. Group related tests using the `describe` function:\n ```squiggle\n describe = SJ.describe\n\n myTestSuite = describe(\\"My Test Suite\\", [\n test(\\"First test\\", {|| expect(true).toBeTrue()}),\n test(\\"Second test\\", {|| expect(5).toBeGreaterThan(3)})\n ])\n ```\n\n4. Run your test suite and view the results.\n\n## Available Matchers\n\n- `toBe(expected)`: Checks for exact equality\n- `toBeGreaterThan(expected)`: Checks if the actual value is greater than the expected\n- `toBeGreaterThanOrEqual(expected)`: Checks if the actual value is greater or equal than the expected\n- `toBeLessThan(expected)`: Checks if the actual value is less than the expected\n- `toBeLessThanOrEqual(expected)`: Checks if the actual value is less than or equal than the expected\n- `toBeTrue()`: Checks if the value is true\n- `toBeFalse()`: Checks if the value is false\n- `toBeCloseTo(expected, epsilon)`: Checks if the actual value is close to the expected value within a given epsilon.\n- `toBeBetween(low, high)`: Checks if the actual value is between the given low and high values (inclusive).\n- `toThrowAnyError(() => any)`: Checks to see if any error was thrown.\n- `toNotThrow(() => any)`: Checks that no error was thrown.\n\n## Examples\n\n### Testing a Simple Function\n\n```squiggle\nadd(a, b) = a + b\n\ndescribe(\\"Add function\\", [\n test(\\"adds two positive numbers\\", {|| \n expect(add(2, 3)).toBe(5)\n }),\n test(\\"adds a positive and a negative number\\", {|| \n expect(add(5, -3)).toBe(2)\n })\n])\n```\n\n### Testing a Distribution\n\n```squiggle\nmyDist = normal(10, 2)\n\ndescribe(\\"My Distribution\\", [\n test(\\"has correct mean\\", {|| \n expect(mean(myDist)).toBe(10)\n }),\n test(\\"has correct standard deviation\\", {|| \n expect(stdev(myDist)).toBe(2)\n }),\n test(\\"90% of values are within 2 standard deviations\\", {||\n lower = 10 - 2 * 2\n upper = 10 + 2 * 2\n expect(cdf(myDist, upper) - cdf(myDist, lower)).toBeGreaterThan(0.9)\n })\n])\n```\n\nThese examples demonstrate how to use SquiggleJest to test various aspects of your Squiggle models, from simple functions to complex models with distributions.\n"\n\nkeyword = "____FAILED____"\n\nfnThrewError(fn) = try(fn, {|| "____FAILED____"}) == keyword\n\n@startClosed\ncreateExpectation(actual) = {\n toBe: {\n |expected|\n if actual != expected then "Expected " + expected + " but got " +\n actual else true\n },\n toBeGreaterThan: {\n |expected|\n if actual > expected then true else "Expected " + actual +\n " to be greater than " +\n expected\n },\n toBeGreaterThanOrEqual: {\n |expected|\n if actual >= expected then true else "Expected " + actual +\n " to be less than or equal" +\n expected\n },\n toBeLessThan: {\n |expected|\n if actual < expected then true else "Expected " + actual +\n " to be less than " +\n expected\n },\n toBeLessThanOrEqual: {\n |expected|\n if actual <= expected then true else "Expected " + actual +\n " to be less than or equal" +\n expected\n },\n toBeBetween: {\n |low, high|\n if actual < low || actual > high then "Expected " + actual +\n " to be between " +\n low +\n " and " +\n high else true\n },\n toBeCloseTo: {\n |expected, epsilon|\n if abs(actual - expected) > epsilon then "Expected " + actual +\n " to be close to " +\n expected +\n " (within " +\n epsilon +\n ")" else true\n },\n toBeTrue: {|| if !actual then "Expected true but got " + actual else true},\n toBeFalse: {|| if actual then "Expected false but got " + actual else true},\n toThrowAnyError: {\n ||\n if fnThrewError(\n actual\n ) then true else "Expected to throw an error, but no error was thrown"\n },\n toNotThrow: {\n ||\n if !fnThrewError(\n actual\n ) then true else "Expected not to throw an error, but an error was thrown"\n },\n}\n\nrunTest(test) = {\n fnResult = test.fn()\n {\n name: test.name,\n passed: fnResult == true,\n error: if fnResult != true then fnResult else "",\n }\n}\n\n@startClosed\ngenerateTestReport(name, testResults) = {\n passedTests = List.filter(testResults, {|t| t.passed})\n failedTests = List.filter(testResults, {|t| !t.passed})\n\n [\n "## Test Suite: " + name + " ",\n "**Total tests**: " + List.length(testResults) + " ",\n "**Passed**: " + List.length(passedTests) + " ",\n "**Failed**: " + List.length(failedTests),\n "",\n "**Results:** ",\n ]\n}\n\n@startClosed\nformatTestResult(testResult) = (if testResult.passed then "✅" else "❌") +\n " " +\n testResult.name +\n (if testResult.error != "" then "\n --- Error: *" + testResult.error +\n "*" else "") +\n " "\n\n// Main squiggleJest framework\n@startClosed\nsquiggleJest = {\n expect: createExpectation,\n test: {|name, fn| { name: name, fn: fn }},\n describe: {\n |name, tests|\n testResults = List.map(tests, runTest)\n report = generateTestReport(name, testResults)\n testDetails = List.map(testResults, formatTestResult)\n List.concat(report, testDetails) -> List.join("\n")\n },\n}\n\nexport test = squiggleJest.test\nexport describe = squiggleJest.describe\nexport expect = squiggleJest.expect\n\n/// Testing ---\n@name("Example Model")\nmodel = { items: [1, 2, 3] }\n\ntestResults = describe(\n "Model Tests",\n [\n test(\n "has items with length 3",\n {|| expect(List.length(model.items)).toBe(3)}\n ),\n test("first item is 1", {|| expect(model.items[0]).toBe(1)}),\n test(\n "last item is greater than 2",\n {|| expect(model.items[2]).toBeGreaterThan(1)}\n ),\n test(\n "second item is less than 3",\n {|| expect(model.items[1]).toBeLessThan(8)}\n ),\n test(\n "second item is between 1 and 5",\n {|| expect(model.items[1]).toBeBetween(1, 3)}\n ),\n test(\n "contains truthy value",\n {|| expect(List.some(model.items, {|i| i > 0})).toBeTrue()}\n ),\n test(\n "doesn\'t contain 4",\n {|| expect(List.some(model.items, {|i| i == 4})).toBeFalse()}\n ),\n test("this test should fail", {|| expect(1).toBe(2)}),\n ]\n)\n\ncomparisonTests = describe(\n "Number Comparisons",\n [\n test("5 is greater than 3", {|| expect(5).toBeGreaterThan(3)}),\n test(\n "5 is greater than or equal to 5",\n {|| expect(5).toBeGreaterThanOrEqual(5)}\n ),\n test("3 is less than 5", {|| expect(3).toBeLessThan(5)}),\n test("5 is less than or equal to 5", {|| expect(5).toBeLessThanOrEqual(5)}),\n test("7 is between 5 and 10", {|| expect(7).toBeBetween(5, 10)}),\n test(\n "5 is close to 5.0001 within 0.001",\n {|| expect(5).toBeCloseTo(5.0001, 0.001)}\n ),\n test("0 is not greater than 0", {|| expect(0).toBeLessThanOrEqual(0)}),\n test("-1 is less than 0", {|| expect(-1).toBeLessThan(0)}),\n test(\n "1000000 is greater than 999999",\n {|| expect(1000000).toBeGreaterThan(999999)}\n ),\n test(\n "0.1 + 0.2 is close to 0.3",\n {|| expect(0.1 + 0.2).toBeCloseTo(0.3, 0.0000001)}\n ),\n test(\n "PI is approximately 3.14159",\n {|| expect(3.14159).toBeCloseTo(Math.pi, 0.00001)}\n ),\n test(\n "e is approximately 2.71828",\n {|| expect(2.71828).toBeCloseTo(Math.e, 0.00001)}\n ),\n test(\n "10 is between 9.99999 and 10.00001",\n {|| expect(10).toBeBetween(9.99999, 10.00001)}\n ),\n test(\n "5 is not between 5.00001 and 10",\n {|| expect(5).toBeLessThan(5.00001)}\n ),\n test("1e-10 is greater than 0", {|| expect(1e-10).toBeGreaterThan(0)}),\n test(\n "The absolute difference between 1/3 and 0.333333 is less than 1e-5",\n {|| expect(abs(1 / 3 - 0.333333)).toBeLessThan(1e-5)}\n ),\n test(\n "2^53 - 1 is the largest integer precisely representable in IEEE 754",\n {|| expect(2 ^ 53 - 1).toBe(9007199254740991)}\n ),\n ]\n)\n\nerrorTests = System.version == "0.9.6-0" ? describe(\n "Error Throwing Tests",\n [\n test(\n "throws any error",\n {|| expect({|| throw("SomeError")}).toThrowAnyError()}\n ),\n test("doesn\'t throw any error", {|| expect({|| 2 + 2}).toNotThrow()}),\n test(\n "fails when expecting an error but none is thrown",\n {|| expect({|| 2 + 2}).toThrowAnyError()}\n ),\n test(\n "fails when expecting no error but one is thrown",\n {|| expect({|| throw("UnexpectedError")}).toNotThrow()}\n ),\n ]\n) : false\n', ], [ "hub:ozziegooen/helpers", - 'import "hub:ozziegooen/sTest" as sTest\n@hide\ntest = sTest.test\n@hide\nexpect = sTest.expect\n@hide\ndescribe = sTest.describe\n\n@doc(\n "\n round(num, n)\n \n Rounds the number `num` to `n` decimal places.\n \n Example:\n round(3.14159, 2) -> \\"3.14\\"\n"\n)\nexport round(num, n) = {\n asString = String.make(num)\n splitString = String.split(asString, "")\n if List.findIndex(splitString, {|r| r == "e"}) != -1 then {\n // Handle scientific notation\n parts = String.split(asString, "e")\n decimalPart = parts[0]\n exponentPart = parts[1]\n roundedDecimalPart = if List.findIndex(\n String.split(decimalPart, ""),\n {|r| r == "."}\n ) !=\n -1 then {\n decimalIndex = List.findIndex(\n String.split(decimalPart, ""),\n {|r| r == "."}\n )\n endIndex = min(\n [decimalIndex + n + 1, List.length(String.split(decimalPart, ""))]\n )\n String.split(decimalPart, "") -> List.slice(0, endIndex) -> List.join("")\n } else decimalPart\n roundedDecimalPart + "e" + exponentPart\n } else {\n // Handle non-scientific notation numbers\n decimalIndex = List.findIndex(splitString, {|r| r == "."})\n if decimalIndex == -1 then asString else {\n endIndex = min([decimalIndex + n + 1, List.length(splitString)])\n splitString -> List.slice(0, endIndex) -> List.join("")\n }\n }\n}\n\n@name("round tests")\nroundTests = describe(\n "Round Function Tests",\n [\n test("rounds a simple number", {|| expect(round(3.14159, 2)).toBe("3.14")}),\n test("rounds a whole number", {|| expect(round(10, 2)).toBe("10")}),\n test(\n "rounds a number in scientific notation",\n {|| expect(round(1.23e4, 2)).toBe("12300")}\n ),\n test(\n "rounds a negative number",\n {|| expect(round(-2.7182, 2)).toBe("-2.71")}\n ),\n ]\n)\n\n@doc(\n "\n formatTime(hours)\n \n Converts a number of hours to a formatted string indicating time in \n seconds, minutes, hours, days, months, or years.\n \n Example:\n formatTime(1) -> \\"**1** hours\\"\n "\n)\nexport formatTime(hours) = {\n secondsInMinute = 60\n minutesInHour = 60\n hoursInDay = 24\n daysInMonth = 30\n monthsInYear = 12\n\n totalSeconds = hours * minutesInHour * secondsInMinute\n totalMinutes = hours * minutesInHour\n totalHours = hours\n totalDays = hours / hoursInDay\n totalMonths = totalDays / daysInMonth\n totalYears = totalMonths / monthsInYear\n round(n) = round(n, 2) -> {|r| "**" + r + "**"}\n\n if totalYears >= 1 then round(totalYears) + " years" else if totalMonths >=\n 1 then round(totalMonths) + " months" else if totalDays >= 1 then round(\n totalDays\n ) +\n " days" else if totalHours >= 1 then round(totalHours) +\n " hours" else if totalMinutes >= 1 then round(totalMinutes) +\n " minutes" else round(totalSeconds) + " seconds"\n}\n\n@name("formatTime tests")\nformatTimeTests = describe(\n "FormatTime Function Tests",\n [\n test(\n "formats time less than a minute",\n {|| expect(formatTime(0.01)).toBe("**36** seconds")}\n ),\n test(\n "formats time in hours",\n {|| expect(formatTime(1)).toBe("**1** hours")}\n ),\n test(\n "formats time in days",\n {|| expect(formatTime(24)).toBe("**1** days")}\n ),\n test(\n "formats time in months",\n {|| expect(formatTime(720)).toBe("**1** months")}\n ),\n test(\n "formats time in years",\n {|| expect(formatTime(8760)).toBe("**1.01** years")}\n ),\n ]\n)\n\n@doc(\n "## Linear or Quadratic Interpolation\n```squiggle\n@import(\'hub:ozziegooen/helpers\' as h)\n\nh.interpolate([{x: 0, y:10}, {x:10, y:20}], \'linear\')(4) -> 15\nh.interpolate([{x: 0, y:10}, {x:10, y:20}], \'quadratic\')(4) -> 11.6\n\n//makes a graph\nfoo(t:[0,30]) = h.interpolate([{x: 0, y:10}, {x:10, y:20}, {x:20, y:10}], \'quadratic\')(t) \n"\n)\nexport interpolate(points, type) = {\n sortedPoints = List.sortBy(points, {|f| f.x}) //TODO: Sort, somehow\n {\n |x|\n result = List.reduce(\n sortedPoints,\n sortedPoints[0].y,\n {\n |acc, point, i|\n if i == 0 then acc else if sortedPoints[i - 1].x <= x &&\n x <= point.x then {\n leftPoint = sortedPoints[i - 1]\n rightPoint = point\n\n if type == "linear" then {\n slope = (rightPoint.y - leftPoint.y) / (rightPoint.x - leftPoint.x)\n leftPoint.y + slope * (x - leftPoint.x)\n } else if type == "quadratic" then {\n a = (rightPoint.y - leftPoint.y) / (rightPoint.x - leftPoint.x) ^ 2\n b = -2 * a * leftPoint.x\n c = leftPoint.y + a * leftPoint.x ^ 2\n a * x ^ 2 + b * x + c\n } else { foo: "Invalid interpolate type" }\n\n } else if x > sortedPoints[i - 1].x then sortedPoints[List.length(\n sortedPoints\n ) -\n 1].y else acc\n }\n )\n result\n }\n}\n\ninterpolationTests = describe(\n "Interpolation Function Tests",\n [\n test(\n "linear interpolation within range",\n {\n ||\n expect(\n interpolate([{ x: 0, y: 10 }, { x: 10, y: 20 }], "linear")(4)\n ).toBe(\n 14\n )\n }\n ),\n test(\n "quadratic interpolation within range",\n {\n ||\n expect(\n interpolate([{ x: 0, y: 10 }, { x: 10, y: 20 }], "quadratic")(4)\n ).toBe(\n 11.6\n )\n }\n ),\n test(\n "linear interpolation at boundary",\n {\n ||\n expect(\n interpolate([{ x: 0, y: 10 }, { x: 10, y: 20 }], "linear")(0)\n ).toBe(\n 10\n )\n }\n ),\n test(\n "quadratic interpolation, additional points",\n {\n ||\n expect(\n interpolate(\n [{ x: 0, y: 10 }, { x: 10, y: 20 }, { x: 20, y: 10 }],\n "quadratic"\n )(\n 15\n )\n ).toBe(\n 17.5\n )\n }\n ),\n ]\n)\n\n//myShape = [{ x: 4, y: 10 }, { x: 20, y: 40 }, { x: 30, y: 20 }]\n\nplot(fn, xPoints) = Plot.numericFn(\n fn,\n {\n xScale: Scale.linear({ min: 0, max: 50 }),\n xPoints: xPoints -> List.concat(List.upTo(0, 50)),\n }\n)\n\n@hide\ncalculator_fn(shape, select) = {\n xPoints = shape -> map({|r| r.x})\n if select == "linear" then plot(\n interpolate(shape, "linear"),\n xPoints\n ) else if select == "quadratic" then plot(\n interpolate(shape, "quadratic"),\n xPoints\n ) else {\n linear: plot(interpolate(shape, "linear"), xPoints),\n quadratic: plot(interpolate(shape, "quadratic"), xPoints),\n }\n}\n\n@name("Interpolation Calculator (for debugging)")\ninterpolationCalculator = Calculator(\n calculator_fn,\n {\n title: "Interpolate: function demonstration",\n description: "``interpolate(data, type=\'linear\'|\'quadratic\')``. \n \nYou have to enter data in the format of x and y values, as shown below, then get a function that can be called with any X to get any Y value.\n\n*Note: One important restriction is that these don\'t yet do a good job outside the data bounds. It\'s unclear what\'s best. I assume we should later give users options.*",\n inputs: [\n Input.textArea(\n {\n name: "Example input",\n default: "[\n { x: 4, y: 10 },\n { x: 20, y: 30 },\n { x: 30, y: 50 },\n { x: 40, y: 30 },,\n]",\n }\n ),\n Input.select(\n {\n name: "interpolate Type",\n options: ["linear", "quadratic", "show both (for demonstration)"],\n default: "show both (for demonstration)",\n }\n ),\n ],\n }\n)\n\n@startOpen\n@notebook\nreadme = [\n "# Helpers Library\n\nA small library of various helper functions for numerical operations and formatting. Import this library into your Squiggle projects to utilize these utilities.\n\n## Import Usage\n\nTo use the functions from this library in your projects, import it as follows:\n\n```squiggle\n@import(\'hub:ozziegooen/helpers\') as h\n```\n## Functions Overview\n\n### round\nRounds a given number to a specified number of decimal places.\n\nExample:\n\n```squiggle\nh.round(3.423, 2) // Returns: \\"3.42\\"\n```",\n Tag.getDoc(round),\n "---",\n "### formatTime\nConverts a given number of hours into a human-readable time format, such as seconds, minutes, hours, days, months, or years.\n\nExample:\n\n```squiggle\nh.formatTime(4.23) // Enter the number of hours and format the result\n```",\n Tag.getDoc(formatTime),\n "---",\n "### interpolate\nProvides linear or quadratic interpolation for a set of points. Returns a function that can interpolate the y-value for any x-value.\n\nExample for Linear Interpolation:\n\n```squiggle\nh.interpolate([{x: 0, y: 10}, {x: 10, y: 20}], \'linear\')(4) // Returns: 15\n```\n\nExample for Quadratic Interpolation:\n\n```squiggle\nh.interpolate([{x: 0, y: 10}, {x: 10, y: 20}], \'quadratic\')(4) // Returns: 11.6\n```\n\n### Interpolation Calculator\nThis tool helps visualize and compare the results of linear and quadratic interpolations for a given set of data points. Below is an example use case integrated with the library.",\n interpolationCalculator,\n]\n', + 'import "hub:ozziegooen/sTest" as sTest\n@hide\ntest = sTest.test\n@hide\nexpect = sTest.expect\n@hide\ndescribe = sTest.describe\n\n@hide\nstyleSquiggleCode(code) = "\n```squiggle\n" + code + "\n```"\n\n@doc(\n "\n round(num, n) -> string\n \n Rounds the number `num` to `n` decimal places." +\n styleSquiggleCode("round(3.14159, 2) -> \\"3.14\\"")\n)\nexport round(num, n) = if typeOf(num) != "Number" then throw(\n "Was given a " + typeOf(num) + ", must be given a Number"\n) else {\n asString = String.make(num)\n splitString = String.split(asString, "")\n if List.findIndex(splitString, {|r| r == "e"}) != -1 then {\n // Handle scientific notation\n parts = String.split(asString, "e")\n decimalPart = parts[0]\n exponentPart = parts[1]\n roundedDecimalPart = if List.findIndex(\n String.split(decimalPart, ""),\n {|r| r == "."}\n ) !=\n -1 then {\n decimalIndex = List.findIndex(\n String.split(decimalPart, ""),\n {|r| r == "."}\n )\n endIndex = min(\n [decimalIndex + n + 1, List.length(String.split(decimalPart, ""))]\n )\n String.split(decimalPart, "") -> List.slice(0, endIndex) -> List.join("")\n } else decimalPart\n roundedDecimalPart + "e" + exponentPart\n } else {\n // Handle non-scientific notation numbers\n decimalIndex = List.findIndex(splitString, {|r| r == "."})\n if decimalIndex == -1 then asString else {\n endIndex = min([decimalIndex + n + 1, List.length(splitString)])\n splitString -> List.slice(0, endIndex) -> List.join("")\n }\n }\n}\n\n@name("round tests")\nroundTests = describe(\n "Round Function Tests",\n [\n test("rounds a simple number", {|| expect(round(3.14159, 2)).toBe("3.14")}),\n test("rounds a whole number", {|| expect(round(10, 2)).toBe("10")}),\n test(\n "rounds a number in scientific notation",\n {|| expect(round(1.23e4, 2)).toBe("12300")}\n ),\n test(\n "rounds a negative number",\n {|| expect(round(-2.7182, 2)).toBe("-2.71")}\n ),\n test(\n "throws when given distribution",\n {|| expect({|| round(1 to 10, 2)}).toThrowAnyError()}\n ),\n ]\n)\n\n@doc(\n "\n formatTime(hours) -> string\n \n Converts a number of hours to a formatted string indicating time in \n seconds, minutes, hours, days, months, or years. Bolds the result." +\n styleSquiggleCode("formatTime(1) -> \\"**1** hours\\"")\n)\nexport formatTime(hours) = {\n secondsInMinute = 60\n minutesInHour = 60\n hoursInDay = 24\n daysInMonth = 30\n monthsInYear = 12\n\n totalSeconds = hours * minutesInHour * secondsInMinute\n totalMinutes = hours * minutesInHour\n totalHours = hours\n totalDays = hours / hoursInDay\n totalMonths = totalDays / daysInMonth\n totalYears = totalMonths / monthsInYear\n round(n) = round(n, 2) -> {|r| "**" + r + "**"}\n\n if totalYears >= 1 then round(totalYears) + " years" else if totalMonths >=\n 1 then round(totalMonths) + " months" else if totalDays >= 1 then round(\n totalDays\n ) +\n " days" else if totalHours >= 1 then round(totalHours) +\n " hours" else if totalMinutes >= 1 then round(totalMinutes) +\n " minutes" else round(totalSeconds) + " seconds"\n}\n\n@name("formatTime tests")\nformatTimeTests = describe(\n "FormatTime Function Tests",\n [\n test(\n "formats time less than a minute",\n {|| expect(formatTime(0.01)).toBe("**36** seconds")}\n ),\n test(\n "formats time in hours",\n {|| expect(formatTime(1)).toBe("**1** hours")}\n ),\n test(\n "formats time in days",\n {|| expect(formatTime(24)).toBe("**1** days")}\n ),\n test(\n "formats time in months",\n {|| expect(formatTime(720)).toBe("**1** months")}\n ),\n test(\n "formats time in years",\n {|| expect(formatTime(8760)).toBe("**1.01** years")}\n ),\n ]\n)\n\n@doc(\n "## Linear or Quadratic Interpolation" +\n styleSquiggleCode(\n "@import(\'hub:ozziegooen/helpers\' as h)\n\nh.interpolate([{x: 0, y:10}, {x:10, y:20}], \'linear\')(4) // 15\nh.interpolate([{x: 0, y:10}, {x:10, y:20}], \'quadratic\')(4) // 11.6\n\n//makes a graph\nfoo(t:[0,30]) = h.interpolate([{x: 0, y:10}, {x:10, y:20}, {x:20, y:10}], \'quadratic\')(t)"\n )\n)\nexport interpolate(points, type) = {\n sortedPoints = List.sortBy(points, {|f| f.x}) //TODO: Sort, somehow\n {\n |x|\n result = List.reduce(\n sortedPoints,\n sortedPoints[0].y,\n {\n |acc, point, i|\n if i == 0 then acc else if sortedPoints[i - 1].x <= x &&\n x <= point.x then {\n leftPoint = sortedPoints[i - 1]\n rightPoint = point\n\n if type == "linear" then {\n slope = (rightPoint.y - leftPoint.y) / (rightPoint.x - leftPoint.x)\n leftPoint.y + slope * (x - leftPoint.x)\n } else if type == "quadratic" then {\n a = (rightPoint.y - leftPoint.y) / (rightPoint.x - leftPoint.x) ^ 2\n b = -2 * a * leftPoint.x\n c = leftPoint.y + a * leftPoint.x ^ 2\n a * x ^ 2 + b * x + c\n } else { foo: "Invalid interpolate type" }\n } else if x > sortedPoints[i - 1].x then sortedPoints[List.length(\n sortedPoints\n ) -\n 1].y else acc\n }\n )\n result\n }\n}\n\ninterpolationTests = describe(\n "Interpolation Function Tests",\n [\n test(\n "linear interpolation within range",\n {\n ||\n expect(\n interpolate([{ x: 0, y: 10 }, { x: 10, y: 20 }], "linear")(4)\n ).toBe(\n 14\n )\n }\n ),\n test(\n "quadratic interpolation within range",\n {\n ||\n expect(\n interpolate([{ x: 0, y: 10 }, { x: 10, y: 20 }], "quadratic")(4)\n ).toBe(\n 11.6\n )\n }\n ),\n test(\n "linear interpolation at boundary",\n {\n ||\n expect(\n interpolate([{ x: 0, y: 10 }, { x: 10, y: 20 }], "linear")(0)\n ).toBe(\n 10\n )\n }\n ),\n test(\n "quadratic interpolation, additional points",\n {\n ||\n expect(\n interpolate(\n [{ x: 0, y: 10 }, { x: 10, y: 20 }, { x: 20, y: 10 }],\n "quadratic"\n )(\n 15\n )\n ).toBe(\n 17.5\n )\n }\n ),\n ]\n)\n\n//myShape = [{ x: 4, y: 10 }, { x: 20, y: 40 }, { x: 30, y: 20 }]\n@hide\nplot(fn, xPoints) = Plot.numericFn(\n fn,\n {\n xScale: Scale.linear({ min: 0, max: 50 }),\n xPoints: xPoints -> List.concat(List.upTo(0, 50)),\n }\n)\n\n@hide\ncalculator_fn(shape, select) = {\n xPoints = shape -> map({|r| r.x})\n if select == "linear" then plot(\n interpolate(shape, "linear"),\n xPoints\n ) else if select == "quadratic" then plot(\n interpolate(shape, "quadratic"),\n xPoints\n ) else {\n linear: plot(interpolate(shape, "linear"), xPoints),\n quadratic: plot(interpolate(shape, "quadratic"), xPoints),\n }\n}\n\n@name("Interpolation Calculator (for debugging)")\ninterpolationCalculator = Calculator(\n calculator_fn,\n {\n title: "Interpolate: function demonstration",\n description: "``interpolate(data, type=\'linear\'|\'quadratic\')``. \n \nYou have to enter data in the format of x and y values, as shown below, then get a function that can be called with any X to get any Y value.\n\n*Note: One important restriction is that these don\'t yet do a good job outside the data bounds. It\'s unclear what\'s best. I assume we should later give users options.*",\n inputs: [\n Input.textArea(\n {\n name: "Example input",\n default: "[\n { x: 4, y: 10 },\n { x: 20, y: 30 },\n { x: 30, y: 50 },\n { x: 40, y: 30 },,\n]",\n }\n ),\n Input.select(\n {\n name: "interpolate Type",\n options: ["linear", "quadratic", "show both (for demonstration)"],\n default: "show both (for demonstration)",\n }\n ),\n ],\n }\n)\n\n@doc(\n "\n distributionListRange(dists, lowQuantile, highQuantile)\n \n Returns a range that is the minimum and maximum of the low and high quantiles of the distributions in the list." +\n styleSquiggleCode(\n "distributionListRange([dist1, dist2, dist3], 0.05, 0.95) -> {min: 1, max: 10}"\n )\n)\nexport distributionListRange(dists, lowQuantile, highQuantile) = {\n min: Number.min(List.map(dists, {|d| Dist.quantile(d, lowQuantile)})),\n max: Number.max(List.map(dists, {|d| Dist.quantile(d, highQuantile)})),\n}\n\n@startOpen\n@notebook\nreadme = [\n "# Helpers Library\n\nA small library of various helper functions for numerical operations and formatting. Import this library into your Squiggle projects to utilize these utilities.\n\n## Import Usage\n\nTo use the functions from this library in your projects, import it as follows:\n\n```squiggle\n@import(\'hub:ozziegooen/helpers\') as h\n```\n## Functions Overview\n\n### round",\n Tag.getDoc(round),\n "---",\n "### formatTime",\n Tag.getDoc(formatTime),\n "---",\n "### interpolate\nProvides linear or quadratic interpolation for a set of points. Returns a function that can interpolate the y-value for any x-value.\n\nExample for Linear Interpolation:\n\n```squiggle\nh.interpolate([{x: 0, y: 10}, {x: 10, y: 20}], \'linear\')(4) // Returns: 15\n```\n\nExample for Quadratic Interpolation:\n\n```squiggle\nh.interpolate([{x: 0, y: 10}, {x: 10, y: 20}], \'quadratic\')(4) // Returns: 11.6\n```\n\n### Interpolation Calculator\nThis tool helps visualize and compare the results of linear and quadratic interpolations for a given set of data points. Below is an example use case integrated with the library.",\n interpolationCalculator,\n "---",\n "### distributionListRange",\n Tag.getDoc(distributionListRange),\n]\n', ], ]); diff --git a/packages/ai/src/steps/adjustToFeedbackStep.ts b/packages/ai/src/steps/adjustToFeedbackStep.ts index a916eec14c..7b10e2b77e 100644 --- a/packages/ai/src/steps/adjustToFeedbackStep.ts +++ b/packages/ai/src/steps/adjustToFeedbackStep.ts @@ -34,6 +34,7 @@ Please review the code and output according to the following criteria: 7. **Edge Cases**: Ensure that edge cases are handled and add simple error handling where appropriate. 8. **Remove Redundant Comments**: Delete any comments that no longer serve a purpose (e.g., comments explaining previous changes). 9. **Sensitivity Analysis**: Do not add sensitivity analysis functions. +10. **Style Guide**: Follow the included Squiggle Style Guide. If no adjustments are required (this should be the usual outcome), respond with: diff --git a/packages/ai/src/steps/fixCodeUntilItRunsStep.ts b/packages/ai/src/steps/fixCodeUntilItRunsStep.ts index 3637eb459c..7b2fe329a0 100644 --- a/packages/ai/src/steps/fixCodeUntilItRunsStep.ts +++ b/packages/ai/src/steps/fixCodeUntilItRunsStep.ts @@ -29,12 +29,24 @@ function editExistingSquiggleCodePrompt(code: Code): PromptPair { const error = codeErrorString(code); const advice = getSquiggleAdvice(error, code.source); const fullPrompt = `You are an expert Squiggle code debugger. Your task is solely to fix an error in the given Squiggle code. + Follow these steps: -1. Analyze the Code and Error Message: Carefully review the original code, the error message, and any provided advice. -2. Maintain Code Integrity: Fix the error while preserving the original code structure and functionality. Avoid unrelated changes. -3. Choose the Most Efficient Fix: If multiple solutions exist, implement the most straightforward and effective one. -4. Avoid Repetition of Past Mistakes: Review previous attempts (if any) and do not repeat the same errors. +1. **Comprehensive Analysis**: Carefully analyze the code and error message to identify both the immediate cause of the error and any related issues that could stem from the same root cause. Consider errors related to the logic, structure, or syntax that may not be reflected directly in the error message. + +2. **Find and Fix All Instances**: If the error is found in multiple locations or is part of a larger pattern, apply the fix to **all occurrences**. Ensure that no instances of the same issue remain in the code. Make sure to read through the rest of the code after your initial fix idea, to find potential additional fixes. + +3. **Reasoning and Fix Documentation**: Provide detailed reasoning for your chosen fix, including an explanation of why it is the most appropriate solution. For each fix, explain how it resolves the error and ensure that the corrected code maintains the intended functionality. + +4. **Return Valid Code**: Ensure that the corrected code can execute successfully without further errors. If the fix involves structural changes, ensure the code still returns the necessary values or outputs where applicable. + +5. **Reflection and Code Review**: After making changes, reflect on the solution to ensure that all parts of the code are consistent with the fix. If the fix needs to be applied elsewhere, ensure that similar corrections are made globally in the code. + +### Additional Requirements: +- Use Squiggle documentation to support your solution where necessary. +- If the error message points to a location distant from the actual issue (due to limitations in error handling), carefully evaluate the context and resolve the issue where appropriate. The real issue is often a few lines away from the place the bug is thought to be. +- Often, once you make one fix, another obvious issue emerges. Think through if this will happen - if so, try to fix this now as well. +- Use SEARCH/REPLACE blocks for each fix, ensuring that they capture the necessary scope for each correction. ${addLineNumbers(code.source)} diff --git a/packages/website/public/llms/basicPrompt.markdown b/packages/website/public/llms/basicPrompt.markdown new file mode 100644 index 0000000000..e0857f54dd --- /dev/null +++ b/packages/website/public/llms/basicPrompt.markdown @@ -0,0 +1,330 @@ +Write Squiggle code, using the attached documentation for how it works. + +Squiggle is a very simple language. Don't try using language primitives/constructs you don't see below, or that aren't in our documentation. They are likely to fail. + +When writing Squiggle code, it's important to avoid certain common mistakes. + +### Syntax and Structure + +1. Variable Expansion: Not supported. Don't use syntax like |v...| or |...v|. +2. All pipes are "->", not "|>". +3. Dict keys and variable names must be lowercase. +4. The last value in a block/function is returned (no "return" keyword). +5. Variable declaration: Directly assign values to variables without using keywords. For example, use `foo = 3` instead of `let foo = 3`. +6. All statements in your model, besides the last one must either be comments or variable declarations. You can't do, `4 \n 5 \n 6` Similarly, you can't do, `Calculator() ... Table()` - instead, you need to set everything but the last item to a variable. +7. There's no mod operator (%). Use `Number.mod()` instead. + +### Function Definitions and Use + +1. Anonymous Functions: Use {|e| e} syntax for anonymous functions. +2. Function Parameters: When using functions like normal, specify the standard deviation with stdev instead of sd. For example, use normal({mean: 0.3, stdev: 0.1}) instead of normal({mean: 0.3, sd: 0.1}). +3. There's no recursion. +4. You can't call functions that accept ranges, with distributions. No, `({|foo: [1,20]| foo}) (4 to 5)`. + +### Data Types and Input Handling + +1. Input Types: Use Input.text for numeric inputs instead of Input.number or Input.slider. +2. The only function param types you can provide are numeric/date ranges, for numbers. f(n:[1,10]). Nothing else is valid. You cannot provide regular input type declarations. +3. Only use Inputs directly inside calculators. They won't return numbers, just input types. + +### Looping, Conditionals, and Data Operations + +1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic. +2. There aren't for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile. + +### List and Dictionary Operations + +1. You can't do "(0..years)". Use List.make or List.upTo. +2. There's no "List.sort", but there is "List.sortBy", "Number.sort". + +### Randomness and Distribution Handling + +1. There's no random() function. Use alternatives like sample(uniform(0,1)). +2. The `to` syntax only works for >0 values. "4 to 10", not "0 to 10". + +### Units and Scales + +1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01). + +### Documentation and Comments + +1. Tags like @name and @doc apply to the following variable, not the full file. +2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022. + +## Examples + +Here's are some simple example Squiggle programs: + +```squiggle +//Model for Piano Tuners in New York Over Time + +@name("🌆 Population of New York in 2022") +@doc("I'm really not sure here, this is a quick guess.") +populationOfNewYork2022 = 8.1M to 8.4M + +@name("🎹 Percentage of Population with Pianos") +@format(".1%") +proportionOfPopulationWithPianos = 0.2% to 1% + +@name("🔧 Number of Piano Tuners per Piano") +pianoTunersPerPiano = { + pianosPerPianoTuner = 2k to 50k + 1 / pianosPerPianoTuner +} + +//We only mean to make an estimate for the next 10 years. +@hide +domain = [Date(2024), Date(2034)] + +@name("Population at Time") +populationAtTime(t: domain) = { + dateDiff = Duration.toYears(t - Date(2024)) + averageYearlyPercentageChange = normal({ p5: -1%, p95: 5% }) // We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year + populationOfNewYork2022 * (averageYearlyPercentageChange + 1) ^ dateDiff +} + +@name("Total Tuners, at Time") +totalTunersAtTime(t: domain) = populationAtTime(t) * + proportionOfPopulationWithPianos * + pianoTunersPerPiano + +meanTunersAtTime(t: domain) = mean(totalTunersAtTime(t)) +``` + +```squiggle +calculator = Calculator( + {|a, b, c, d| [a, b, c, d]}, + { + title: "Concat()", + description: "This function takes in 4 arguments, then displays them", + sampleCount: 10000, + inputs: [ + Input.text( + { + name: "First Param", + default: "10 to 13", + description: "Must be a number or distribution", + } + ), + Input.textArea( + { + name: "Second Param", + default: "[4,5,2,3,4,5,3,3,2,2,2,3,3,4,45,5,5,2,1]", + } + ), + Input.select( + { + name: "Third Param", + default: "Option 1", + options: ["Option 1", "Option 2", "Option 3"], + } + ), + Input.checkbox({ name: "Fourth Param", default: false }), + ], + } +) + +``` + +```squiggle +// Cost-benefit analysis for a housing addition in berkeley + +// Input section +@name("Model Inputs") +@doc("Key parameters for the housing development project") +inputs = { + landCost: 1M to 2M, + constructionCost: 500k to 800k, + permitFees: 50k to 100k, + numberOfHomes: 10, + monthlyRentalIncome: 3k to 5k, + annualPropertyAppreciation: 2% to 5%, + annualSocialBenefit: 10k to 30k, + yearsToConsider: 30, +} + +// Calculation section +@name("Calculations") +@doc("Core calculations for the cost-benefit analysis") +calculations(i) = { + totalCostPerHome = i.landCost + i.constructionCost + i.permitFees + annualRentalIncome = i.numberOfHomes * i.monthlyRentalIncome * 12 + totalCost = i.numberOfHomes * totalCostPerHome + + annualAppreciation(year) = i.numberOfHomes * totalCostPerHome * + ((1 + i.annualPropertyAppreciation) ^ year - + (1 + i.annualPropertyAppreciation) ^ (year - 1)) + + annualBenefit(year) = annualRentalIncome + annualAppreciation(year) + + i.numberOfHomes * i.annualSocialBenefit + + totalBenefit = List.upTo(1, i.yearsToConsider) -> List.map(annualBenefit) + -> List.reduce( + 0, + {|acc, val| acc + val} + ) + + netBenefit = totalBenefit - totalCost + probPositiveNetBenefit = 1 - cdf(netBenefit, 0) + + { + totalCostPerHome: totalCostPerHome, + annualRentalIncome: annualRentalIncome, + totalCost: totalCost, + totalBenefit: totalBenefit, + netBenefit: netBenefit, + probPositiveNetBenefit: probPositiveNetBenefit, + } +} + +// Apply calculations to inputs +@name("Results") +@doc("Output of calculations based on input parameters") +results = calculations(inputs) + +// Analysis section +@name("Cost-Benefit Analysis") +@doc("Detailed analysis of the housing development project") +analysis = { + costsTable = Table.make( + [ + { name: "Land Cost per Home", value: inputs.landCost }, + { name: "Construction Cost per Home", value: inputs.constructionCost }, + { name: "Permit Fees per Home", value: inputs.permitFees }, + { name: "Total Cost per Home", value: results.totalCostPerHome }, + { name: "Total Cost for 10 Homes", value: results.totalCost }, + ], + { + columns: [ + { name: "Item", fn: {|r| r.name} }, + { + name: "Cost", + fn: { + |r| + Plot.dist( + r.value, + { + xScale: Scale.log({ tickFormat: "($.1s", min: 20k, max: 200M }), + } + ) + }, + }, + ], + } + ) + + benefitTable = Table.make( + [ + { + name: "Monthly Rental Income per Home", + value: inputs.monthlyRentalIncome, + }, + { + name: "Annual Social Benefit per Home", + value: inputs.annualSocialBenefit, + }, + { name: "Total Benefit over 30 years", value: results.totalBenefit }, + ], + { + columns: [ + { name: "Item", fn: {|r| r.name} }, + { + name: "Value", + fn: { + |r| + Plot.dist( + r.value, + { xScale: Scale.linear({ tickFormat: "($.1s" }) } + ) + }, + }, + ], + } + ) + + netBenefitPlot = Plot.dist( + results.netBenefit, + { + title: "Distribution of Net Benefit", + xScale: Scale.log({ tickFormat: "($.1s", min: 10M, max: 200M }), + } + ) + + { + title: "Cost-Benefit Analysis: Adding 10 Homes to Berkeley, CA", + costs: costsTable, + benefits: benefitTable, + netBenefit: netBenefitPlot, + probabilityOfPositiveNetBenefit: results.probPositiveNetBenefit, + } +} +``` + +```squiggle +x = 10 +result = if x == 1 then { + {y: 2, z: 0} +} else { + {y: 0, z: 4} +} +y = result.y +z = result.z +``` + +```squiggle +@showAs({|f| Plot.numericFn(f, { xScale: Scale.log({ min: 1, max: 100 }) })}) +fn(t) = t ^ 2 +``` + +```squiggle +plot = {|t| normal(t, 2) * normal(5, 3)} + -> Plot.distFn( + { + title: "A Function of Value over Time", + xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }), + yScale: Scale.linear({ title: "Value" }), + distXScale: Scale.linear({ tickFormat: "#x" }), + } + ) +``` + +```squiggle +f(t: [Date(2020), Date(2040)]) = { + yearsPassed = toYears(t - Date(2020)) + normal({mean: yearsPassed ^ 2, stdev: yearsPassed^1.3+1}) +} +``` + +```squiggle +import "hub:ozziegooen/sTest" as sTest +@name("💰 Expected Cost ($)") +@format("$.2s") +flightCost = normal({ mean: 600, stdev: 100 }) + +@name("🥇 Expected Benefit ($)") +@format("$.2s") +benefitEstimate = normal({ mean: 1500, stdev: 300 }) + +@name("📊 Net Benefit ($)") +@format("$.2s") +netBenefit = benefitEstimate - flightCost + +@name("🚦 Test Suite") +@doc( + "Test suite to validate various aspects of the flight cost and benefits model using sTest." +) +tests = sTest.describe( + "Flight to London Test Suite", + [ + // Test for reasonable flight costs + sTest.test( + "Flight cost should be reasonable", + { + || + meanValue = mean(flightCost) + sTest.expect(meanValue).toBeBetween(300, 10k) + } + ), + ] +) +``` diff --git a/packages/website/public/llms/documentationBundle.markdown b/packages/website/public/llms/documentationBundle.markdown new file mode 100644 index 0000000000..e7fd6b5d7a --- /dev/null +++ b/packages/website/public/llms/documentationBundle.markdown @@ -0,0 +1,3253 @@ +Write Squiggle code, using the attached documentation for how it works. + +Squiggle is a very simple language. Don't try using language primitives/constructs you don't see below, or that aren't in our documentation. They are likely to fail. + +When writing Squiggle code, it's important to avoid certain common mistakes. + +### Syntax and Structure + +1. Variable Expansion: Not supported. Don't use syntax like |v...| or |...v|. +2. All pipes are "->", not "|>". +3. Dict keys and variable names must be lowercase. +4. The last value in a block/function is returned (no "return" keyword). +5. Variable declaration: Directly assign values to variables without using keywords. For example, use `foo = 3` instead of `let foo = 3`. +6. All statements in your model, besides the last one must either be comments or variable declarations. You can't do, `4 \n 5 \n 6` Similarly, you can't do, `Calculator() ... Table()` - instead, you need to set everything but the last item to a variable. +7. There's no mod operator (%). Use `Number.mod()` instead. + +### Function Definitions and Use + +1. Anonymous Functions: Use {|e| e} syntax for anonymous functions. +2. Function Parameters: When using functions like normal, specify the standard deviation with stdev instead of sd. For example, use normal({mean: 0.3, stdev: 0.1}) instead of normal({mean: 0.3, sd: 0.1}). +3. There's no recursion. +4. You can't call functions that accept ranges, with distributions. No, `({|foo: [1,20]| foo}) (4 to 5)`. + +### Data Types and Input Handling + +1. Input Types: Use Input.text for numeric inputs instead of Input.number or Input.slider. +2. The only function param types you can provide are numeric/date ranges, for numbers. f(n:[1,10]). Nothing else is valid. You cannot provide regular input type declarations. +3. Only use Inputs directly inside calculators. They won't return numbers, just input types. + +### Looping, Conditionals, and Data Operations + +1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic. +2. There aren't for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile. + +### List and Dictionary Operations + +1. You can't do "(0..years)". Use List.make or List.upTo. +2. There's no "List.sort", but there is "List.sortBy", "Number.sort". + +### Randomness and Distribution Handling + +1. There's no random() function. Use alternatives like sample(uniform(0,1)). +2. The `to` syntax only works for >0 values. "4 to 10", not "0 to 10". + +### Units and Scales + +1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01). + +### Documentation and Comments + +1. Tags like @name and @doc apply to the following variable, not the full file. +2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022. + +## Examples + +Here's are some simple example Squiggle programs: + +```squiggle +//Model for Piano Tuners in New York Over Time + +@name("🌆 Population of New York in 2022") +@doc("I'm really not sure here, this is a quick guess.") +populationOfNewYork2022 = 8.1M to 8.4M + +@name("🎹 Percentage of Population with Pianos") +@format(".1%") +proportionOfPopulationWithPianos = 0.2% to 1% + +@name("🔧 Number of Piano Tuners per Piano") +pianoTunersPerPiano = { + pianosPerPianoTuner = 2k to 50k + 1 / pianosPerPianoTuner +} + +//We only mean to make an estimate for the next 10 years. +@hide +domain = [Date(2024), Date(2034)] + +@name("Population at Time") +populationAtTime(t: domain) = { + dateDiff = Duration.toYears(t - Date(2024)) + averageYearlyPercentageChange = normal({ p5: -1%, p95: 5% }) // We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year + populationOfNewYork2022 * (averageYearlyPercentageChange + 1) ^ dateDiff +} + +@name("Total Tuners, at Time") +totalTunersAtTime(t: domain) = populationAtTime(t) * + proportionOfPopulationWithPianos * + pianoTunersPerPiano + +meanTunersAtTime(t: domain) = mean(totalTunersAtTime(t)) +``` + +```squiggle +calculator = Calculator( + {|a, b, c, d| [a, b, c, d]}, + { + title: "Concat()", + description: "This function takes in 4 arguments, then displays them", + sampleCount: 10000, + inputs: [ + Input.text( + { + name: "First Param", + default: "10 to 13", + description: "Must be a number or distribution", + } + ), + Input.textArea( + { + name: "Second Param", + default: "[4,5,2,3,4,5,3,3,2,2,2,3,3,4,45,5,5,2,1]", + } + ), + Input.select( + { + name: "Third Param", + default: "Option 1", + options: ["Option 1", "Option 2", "Option 3"], + } + ), + Input.checkbox({ name: "Fourth Param", default: false }), + ], + } +) + +``` + +```squiggle +// Cost-benefit analysis for a housing addition in berkeley + +// Input section +@name("Model Inputs") +@doc("Key parameters for the housing development project") +inputs = { + landCost: 1M to 2M, + constructionCost: 500k to 800k, + permitFees: 50k to 100k, + numberOfHomes: 10, + monthlyRentalIncome: 3k to 5k, + annualPropertyAppreciation: 2% to 5%, + annualSocialBenefit: 10k to 30k, + yearsToConsider: 30, +} + +// Calculation section +@name("Calculations") +@doc("Core calculations for the cost-benefit analysis") +calculations(i) = { + totalCostPerHome = i.landCost + i.constructionCost + i.permitFees + annualRentalIncome = i.numberOfHomes * i.monthlyRentalIncome * 12 + totalCost = i.numberOfHomes * totalCostPerHome + + annualAppreciation(year) = i.numberOfHomes * totalCostPerHome * + ((1 + i.annualPropertyAppreciation) ^ year - + (1 + i.annualPropertyAppreciation) ^ (year - 1)) + + annualBenefit(year) = annualRentalIncome + annualAppreciation(year) + + i.numberOfHomes * i.annualSocialBenefit + + totalBenefit = List.upTo(1, i.yearsToConsider) -> List.map(annualBenefit) + -> List.reduce( + 0, + {|acc, val| acc + val} + ) + + netBenefit = totalBenefit - totalCost + probPositiveNetBenefit = 1 - cdf(netBenefit, 0) + + { + totalCostPerHome: totalCostPerHome, + annualRentalIncome: annualRentalIncome, + totalCost: totalCost, + totalBenefit: totalBenefit, + netBenefit: netBenefit, + probPositiveNetBenefit: probPositiveNetBenefit, + } +} + +// Apply calculations to inputs +@name("Results") +@doc("Output of calculations based on input parameters") +results = calculations(inputs) + +// Analysis section +@name("Cost-Benefit Analysis") +@doc("Detailed analysis of the housing development project") +analysis = { + costsTable = Table.make( + [ + { name: "Land Cost per Home", value: inputs.landCost }, + { name: "Construction Cost per Home", value: inputs.constructionCost }, + { name: "Permit Fees per Home", value: inputs.permitFees }, + { name: "Total Cost per Home", value: results.totalCostPerHome }, + { name: "Total Cost for 10 Homes", value: results.totalCost }, + ], + { + columns: [ + { name: "Item", fn: {|r| r.name} }, + { + name: "Cost", + fn: { + |r| + Plot.dist( + r.value, + { + xScale: Scale.log({ tickFormat: "($.1s", min: 20k, max: 200M }), + } + ) + }, + }, + ], + } + ) + + benefitTable = Table.make( + [ + { + name: "Monthly Rental Income per Home", + value: inputs.monthlyRentalIncome, + }, + { + name: "Annual Social Benefit per Home", + value: inputs.annualSocialBenefit, + }, + { name: "Total Benefit over 30 years", value: results.totalBenefit }, + ], + { + columns: [ + { name: "Item", fn: {|r| r.name} }, + { + name: "Value", + fn: { + |r| + Plot.dist( + r.value, + { xScale: Scale.linear({ tickFormat: "($.1s" }) } + ) + }, + }, + ], + } + ) + + netBenefitPlot = Plot.dist( + results.netBenefit, + { + title: "Distribution of Net Benefit", + xScale: Scale.log({ tickFormat: "($.1s", min: 10M, max: 200M }), + } + ) + + { + title: "Cost-Benefit Analysis: Adding 10 Homes to Berkeley, CA", + costs: costsTable, + benefits: benefitTable, + netBenefit: netBenefitPlot, + probabilityOfPositiveNetBenefit: results.probPositiveNetBenefit, + } +} +``` + +```squiggle +x = 10 +result = if x == 1 then { + {y: 2, z: 0} +} else { + {y: 0, z: 4} +} +y = result.y +z = result.z +``` + +```squiggle +@showAs({|f| Plot.numericFn(f, { xScale: Scale.log({ min: 1, max: 100 }) })}) +fn(t) = t ^ 2 +``` + +```squiggle +plot = {|t| normal(t, 2) * normal(5, 3)} + -> Plot.distFn( + { + title: "A Function of Value over Time", + xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }), + yScale: Scale.linear({ title: "Value" }), + distXScale: Scale.linear({ tickFormat: "#x" }), + } + ) +``` + +```squiggle +f(t: [Date(2020), Date(2040)]) = { + yearsPassed = toYears(t - Date(2020)) + normal({mean: yearsPassed ^ 2, stdev: yearsPassed^1.3+1}) +} +``` + +```squiggle +import "hub:ozziegooen/sTest" as sTest +@name("💰 Expected Cost ($)") +@format("$.2s") +flightCost = normal({ mean: 600, stdev: 100 }) + +@name("🥇 Expected Benefit ($)") +@format("$.2s") +benefitEstimate = normal({ mean: 1500, stdev: 300 }) + +@name("📊 Net Benefit ($)") +@format("$.2s") +netBenefit = benefitEstimate - flightCost + +@name("🚦 Test Suite") +@doc( + "Test suite to validate various aspects of the flight cost and benefits model using sTest." +) +tests = sTest.describe( + "Flight to London Test Suite", + [ + // Test for reasonable flight costs + sTest.test( + "Flight cost should be reasonable", + { + || + meanValue = mean(flightCost) + sTest.expect(meanValue).toBeBetween(300, 10k) + } + ), + ] +) +``` + +# Squiggle Style Guide + +## Data and Calculations + +### Estimations + +- When using the "to" format, like "3 to 10", remember that this represents the 5th and 95th percentile. This is a very large range. Be paranoid about being overconfident and too narrow in your estimates. +- One good technique, when you think there's a chance that you might be very wrong about a variable, is to use a mixture that contains a very wide distribution. For example, `mx([300 to 400, 50 to 5000], [0.9, 0.1])`, or `mx([50k to 60k, 1k to 1M], [0.95, 0.05])`. This way if you are caught by surprise, the wide distribution will still give you a reasonable outcome. +- Be wary of using the uniform or the triangular distributions. These are mainly good for physical simulations, not to represent uncertainty over many real life variables. +- If the outcome of a model is an extreme probability (<0.01 or >0.99), be suspicious of the result. It should be very rare for an intervention to have an extreme effect or have an extreme impact on the probability of an event. +- Be paranoid about the uncertainty ranges of your variables. If you are dealing with a highly speculative variable, the answer might have 2-8 orders of magnitude of uncertainty, like `100 to 100K`. If you are dealing with a variable that's fairly certain, the answer might have 2-4 sig figs of uncertainty. Be focused on being accurate and not overconfident, not on impressing people. +- Be careful with sigmoid functions. Sigmoid curves with distributions can have very little uncertainty in the middle, and very high uncertainty at the tails. If you are unsure about these values, consider using a mixture distribution. For example, this curve has very high certainty in the middle, and very high uncertainty at the tails: `adoption_rate(t) = 1 / (1 + exp(-normal(0.1, 0.08) * (t - 30)))` +- Make sure to flag any variables that are highly speculative. Use @doc() to explain that the variable is speculative and to give a sense of the uncertainty. Explain your reasoning, but also warn the reader that the variable is speculative. + +### Percentages / Probabilities + +- Use a `@format()` tag, like `.0%` to format percentages. +- If using a distribution, remember that it shouldn't go outside of 0% and 100%. You can use beta distributions or truncate() to keep values in the correct range. +- If you do use a beta distribution, keep in mind that there's no `({p5, p95})` format. You can use `beta(alpha:number, beta:number)` or `beta({mean: number, stdev: number})` to create a beta distribution. +- Write percentages as `5%` instead of `0.05`. It's more readable. + +### Domains + +- Prefer using domains to throwing errors, when trying to restrict a variable. For example, don't write, `if year < 2023 then throw("Year must be 2023 or later")`. Instead, write `f(t: [2023, 2050])`. +- Err on the side of using domains in cases where you are unsure about the bounds of a function, instead of using if/throw or other error handling methods. +- If you only want to set a min or max value, use a domain with `Number.maxValue` or `-Number.maxValue` as the other bound. +- Do not use a domain with a complete range, like `[-Number.maxValue, Number.maxValue]`. This is redundant. Instead, just leave out the domain, like `f(t)`. + +```squiggle +// Do not use this +f(t: [-Number.maxValue, Number.maxValue]) + 1 + +// Do this +f(t) = t + 1 +``` + +## Structure and Naming Conventions + +### Structure + +- Don't have more than 10 variables in scope at any one time. Feel free to use many dictionaries and blocks in order to keep things organized. For example, + +```squiggle +@name("Key Inputs") +inputs = { + @name("Age (years)") + age = 34 + + @name("Hourly Wage ($/hr)") + hourlyWage = 100 + + @name("Coffee Price ($/cup)") + coffeePrice = 1 + {age, hourlyWage, coffeePrice} +} +``` + +Note: You cannot use tags within dicts like the following: + +```squiggle +// This is not valid. Do not do this. +inputs = { + @name("Age (years)") + age: 34, + + @name("Hourly Wage ($/hr)") + hourlyWage: 100, +} +``` + +- At the end of the file, don't return anything. The last line of the file should be the @notebook tag. +- You cannot start a line with a mathematical operator. For example, you cannot start a line with a + or - sign. However, you can start a line with a pipe character, `->`. +- Prettier will be run on the file. This will change the spacing and formatting. Therefore, be conservative with formatting (long lines, no risks), and allow this to do the heavy lifting later. +- If the file is over 50 lines, break it up with large styled blocks comments with headers. For example: + +```squiggle +// ===== Inputs ===== + +// ... + +// ===== Calculations ===== +``` + +### Naming Conventions + +- Use camelCase for variable names. +- All variable names must start with a lowercase letter. +- In functions, input parameters that aren't obvious should have semantic names. For example, instead of `nb` use `net_benefit`. + +### Dictionaries + +- In dictionaries, if a key name is the same as a variable name, use the variable name directly. For example, instead of {value: value}, just use {value}. If there's only one key, you can type it with a comma, like this: {value,}. + +### Unit Annotation + +- You can add unit descriptions to `@name()`, `@doc()` tags, and add them to comments. +- In addition to regular units (like "population"), add other key variables; like the date or the type of variable. For example, use "Number of Humans (Population, 2023)" instead of just "Number of Humans". It's important to be precise and detailed when annotating variables. +- Squiggle does support units directly, using the syntax `foo :: unit`. However, this is not recommended to use, because this is still a beta feature. +- Show units in parentheses after the variable name, when the variable name is not obvious. For example, use "Age (years)" instead of just "Age". In comments, use the "(units)" format. + Examples: + +```squiggle +@name("Number of Humans (2023)") +numberOfHumans = 7.8B + +@name("Net Benefit ($)") +netBenefit = 100M + +@name("Temperature (°C)") +temperature = 22 + +@name("Piano Tuners in New York City (2023)") +tuners = { + pianosPerTuner = 100 to 1k // (pianos per tuner) + pianosInNYC = 1k to 50k // (pianos) + pianosInNYC / pianosPerTuner +} +``` + +- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. + +```squiggle +@name("Distance to Mars (km)") +distanceMars = 225e6 + +@name("Distance to Venus (km)") +distanceVenus = 170e6 +``` + +### Numbers + +- Use abbreviations, when simple, for numbers outside the range of 10^4 to 10^3. For example, use "10k" instead of "10000". +- For numbers outside the range of 10^10 or so, use scientific notation. For example, "1e10". +- Don't use small numbers to represent large numbers. For example, don't use '5' to represent 5 million. + +Don't use the code: + +```squiggle +@name("US Population (millions)") +usPopulation = 331.9 +``` + +Instead, use: + +```squiggle +@name("US Population") +usPopulation = 331.9M +``` + +More examples: + +```squiggle +// Correct representations +worldPopulation = 7.8B +annualBudget = 1.2T +distanceToSun = 149.6e6 // 149.6 million kilometers + +// Incorrect representations (avoid these) +worldPopulation = 7800 // Unclear if it's 7800 or 7.8 billion +annualBudget = 1200 // Unclear if it's 1200 or 1.2 trillion +``` + +- There's no need to use @format on regular numbers. The default formatting is fairly sophistated. +- Remember to use `Number.sum` and `Number.product`, instead of using Reduce in those cases. + +### Lists of Structured Data + +- When you want to store complex data as code, use lists of dictionaries, instead of using lists of lists. This makes things clearer. For example, use: + +```squiggle +[ + {year: 2023, value: 1}, + {year: 2024, value: 2}, +] +instead of: +[ + [2023, 1], + [2024, 2], +] +``` + +You can use lists instead when you have a very long list of items (20+), very few keys, and/or are generating data using functions. + +- Tables are a great way to display structured data. +- You can use the '@showAs' tag to display a table if the table can show all the data. If this takes a lot of formatting work, you can move that to a helper function. Note that helper functions must be placed before the '@showAs' tag. The `ozziegooen/helpers` library has a `dictsToTable` function that can help convert lists of dictionaries into tables. + +For example: + +```squiggle +@hide +strategiesTable(data) = Table.make( + data, + { + columns: [ + { name: "name", fn: {|f| f.n} }, + { name: "costs", fn: {|f| f.c} }, + { name: "benefits", fn: {|f| f.b} }, + ], + } +) + +@name("AI Safety Strategies") +@doc("List of 10 AI safety strategies with their costs and benefits") +@showAs(strategiesTable) +strategies = [ + { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M }, + { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M }, + { n: "Governance", c: 500k to 3M, b: 2M to 15M }, + ... +] +``` + +## Tags and Annotations + +### @name, @doc, @hide, @showAs + +- Use `@name` for simple descriptions and shortened units. Use `@doc` for further details (especially for detailing types, units, and key assumptions), when necessary. It's fine to use both @name and @doc on the same variable - but if so, don't repeat the name in the doc; instead use the doc() for additional information only. +- In `@name`, add units wherever it might be confusing, like "@name("Ball Speed (m/s)"). If the units are complex or still not obvious, add more detail in the @doc(). +- For complex and important functions, use `@name` to name the function, and `@doc` to describe the arguments and return values. @doc should represent a docstring for the function. For example: + +``` +@doc("Adds a number and a distribution. +\`\`\`squiggle +add(number, distribution) -> distribution +\`\`\`") +``` + +- Variables that are small function helpers, and that won't be interesting or useful to view the output of, should get a `@hide` tag. Key inputs and outputs should not have this tag. +- Use `@showAs` to format large lists, as tables and to show plots for dists and functions where appropriate. + +### `@format()` + +- Use `@format()` for numbers, distributions, and dates that could use obvious formatting. +- The `@format()` tag is not usable with dictionaries, functions, or lists. It is usable with variable assignments. Examples: + +```squiggle +netBenefit(costs, benefits) = benefits - costs // not valid for @format() +netBenefit = benefits - costs // valid for @format() +``` + +- This mainly makes sense for dollar amounts, percentages, and dates. ".0%" is a decent format for percentages, and "$,.0f" can be used for dollars. +- Choose the number of decimal places based on the stdev of the distribution or size of the number. +- Do not use "()" instead of "-" for negative numbers. So, do not use "($,.0f" for negative numbers, use "$,.0f" instead. + +## Limitations + +- There is no bignum type. There are floating point errors at high numbers (1e50 and above) and very small numbers (1e-10 and below). If you need to work with these, use logarithms if possible. + +## Comments + +- Add a short 1-2 line comment on the top of the file, summarizing the model. +- Add comments throughout the code that explain your reasoning and describe your uncertainties. Give special attention to probabilities and probability distributions that are particularly important and/or uncertain. Flag your uncertainties. +- Use comments next to variables to explain what units the variable is in, if this is not incredibly obvious. The units should be wrapped in parentheses. +- There shouldn't be any comments about specific changes made during editing. +- Do not use comments to explain things that are already obvious from the code. + +## Visualizations + +### Tables + +- Tables are a good way of displaying structured data. They can take a bit of formatting work. +- Tables are best when there are fewer than 30 rows and/or fewer than 4 columns. +- The table visualization is fairly simple. It doesn't support sorting, filtering, or other complex interactions. You might want to sort or filter the data before putting it in a table. + +### Notebooks + +- Use the @notebook tag for long descriptions intersperced with variables. This must be a list with strings and variables alternating. +- If you want to display variables within paragraphs, generally render dictionaries as items within the notebook list. For example: + +```squiggle +@notebook +@startOpen +summary = [ +"This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", +{ + optimalCups, + result.netBenefit, +}, +] +``` + +This format will use the variable tags to display the variables, and it's simple to use without making errors. If you want to display a variable that's already a dictionary, you don't need to do anything special. + +- String concatenation (+) is allowed, but be hesitant to do this with non-string variables. Most non-string variables don't display well in the default string representation. If you want to display a variable, consider using a custom function or formatter to convert it to a string first. Note that tags are shown in the default string representation, so you should remove them (`Tag.clear(variable)`) before displaying. +- Separate items in the list will be displayed with blank lines between them. This will break many kinds of formatting, like lists. Only do this in order to display full variables that you want to show. +- Use markdown formatting for headers, lists, and other structural elements. +- Use bold text to highlight key outputs. Like, "The optimal number of coffee cups per day is **" + Tag.clear(optimal_cups) + "**". + +Example: (For a model with 300 lines) + +```squiggle +@notebook +@startOpen +summary = [ + "## Summary + This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", + {inputs, final_answer}, + "## Major Assumptions & Uncertainties + - The model places a very high value on productivity. If you think that productivity is undervalued, coffee consumption may be underrated. + - The model only includes 3 main factors: productivity, cost, and health. It does not take into account other factors, like addiction, which is a major factor in coffee consumption. + - The model does not take into account the quality of sleep, which is critical. + " + "## Outputs + The optimal number of coffee cups per day: **" + Tag.clear(optimal_cups) + "** + The net benefit at optimal consumption: **" + result.net_benefit + "**", + "## Key Findings + - Moderate amounts of coffee consumption seem surprisingly beneficial. + - Productivity boost from coffee shows steeply diminishing returns as consumption increases, as would be expected. + - The financial cost of coffee is the critical factor in determining optimal consumption. + ## Detailed Analysis + The model incorporates several key factors: + 1. Productivity boost: Modeled with diminishing returns as coffee consumption increases. + 2. Health impact: Considers both potential benefits and risks of coffee consumption. + 3. Financial cost: Accounts for the direct cost of purchasing coffee. + 4. Monetary values: Includes estimates for the value of time (hourly wage) and health (QALY value). + + The optimal consumption level is determined by maximizing the net benefit, which is the sum of monetized productivity and health benefits minus the financial cost. + + It's important to note that this model is based on general estimates and may not apply to all individuals. Factors such as personal health conditions, caffeine sensitivity, and lifestyle choices could significantly alter the optimal consumption for a specific person. + " +] +``` + +## Plots + +- Plots are a good way of displaying the output of a model. +- Use Scale.symlog() and Scale.log() whenever you think the data is highly skewed. This is very common with distributions. +- Use Scale.symlog() instead of Scale.log() when you are unsure if the data is above or below 0. Scale.log() fails on negative values. +- Function plots use plots equally spaced on the x-axis. This means they can fail if only integers are accepted. In these cases, it can be safer just not to use the plot, or to use a scatter plot. +- When plotting 2-8 distributions over the same x-axis, it's a good idea to use Plot.dists(). For example, if you want to compare 5 different costs of a treatment, or 3 different adoption rates of a technology, this can be a good way to display the data. +- When plotting distributions in tables or if you want to display multiple distributions under each other, and you don't want to use Plot.dists, it's a good idea to have them all use the same x-axis scale, with custom min and max values. This is a good way to make sure that the x-axis scale is consistent across all distributions. + +Here's an example of how to display multiple distributions over the same x-axis, with a custom x-axis range: + +```squiggle +strategies = [ + { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M }, + { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M }, + ... +] + +rangeOfDists(dists) = { + min: Number.min(List.map(dists, {|d| Dist.quantile(d, 0.05)})), + max: Number.max(List.map(dists, {|d| Dist.quantile(d, 0.95)})), +} + +plotOfResults(fn) = { + |r| + range = List.map(strategies, fn) -> rangeOfDists + Plot.dist(fn(r), { xScale: Scale.linear(range) }) +} + +table = Table.make( + strategies, + { + columns: [ + { name: "Strategy", fn: {|r| r.name} }, + { name: "Cost", fn: plotOfResults({|r| r.c}) }, + { name: "Benefit", fn: plotOfResults({|r| r.b}) }, + ], + } +) +``` + +## Tests + +- Use `sTest` to test squiggle code. +- Test all functions that you are unsure about. Be paranoid. +- Use one describe block, with the variable name 'tests'. This should have several tests with in it, each with one expect statement. +- Use @startClosed tags on variables that are test results. Do not use @hide tags. +- Do not test if function domains return errors when called with invalid inputs. The domains should be trusted. +- If you set variables to sTest values, @hide them. They are not useful in the final output. +- Do not test obvious things, like the number of items in a list that's hardcoded. +- Feel free to use helper functions to avoid repeating code. +- The expect.toThrowAnyError() test is useful for easily sanity-checking that a function is working with different inputs. + +Example: + +```squiggle +@hide +describe = sTest.describe + +@hide +test = sTest.test + +tests = describe( + "Coffee Consumption Model Tests", + [ + // ...tests + ] +) +``` + +## Summary Notebook + +- For models over 5 lines long, you might want to include a summary notebook at the end of the file using the @notebook tag. +- Aim for a summary length of approximately (N^0.6) \* 1.2 lines, where N is the number of lines in the model. +- Use the following structure: + 1. Model description + 2. Major assumptions & uncertainties (if over 100 lines long) + 3. Outputs (including relevant Squiggle variables) + 4. Key findings (flag if anything surprised you, or if the results are counterintuitive) + 5. Detailed analysis (if over 300 lines long) + 6. Important notes or caveats (if over 100 lines long) +- The summary notebook should be the last thing in the file. It should be a variable called `summary`. +- Draw attention to anything that surprised you, or that you think is important. Also, flag major assumptions and uncertainties. + +Example: (For a model with 300 lines) + +```squiggle +@notebook +@startOpen +summary = [ + "## Summary + This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", + {inputs, finalAnswer}, + ... + ] +``` + +# 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. + +```squiggle +x = 5 +y = 10 +x + y +``` + +```squiggle +x = 5 +y = 10 +``` + +## 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 cases, shadowing behaves identically to what you'd expect in languages like JavaScript or Python. + +One case where shadowing matters is closures: + +```squiggle +x = 5 +argPlusX(y) = x + y + +x = x + 5 + +argPlusX(5) +``` + +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. + +## Unit Type Annotations + +Variable declarations may optionally be annotated with _unit types_, such as `kilograms` or `dollars`. Unit types are declared with `::`, for example: + +```squiggle +distance :: meters = 100 +``` + +A unit type can be any identifier, and you don't have to define a unit type before you use it. + +You can also create composite unit types using `*` (multiplication), `/` (division), and '^' (exponentiation). For example: + +```squiggle +raceDistance :: m = 100 +usainBoltTime :: s = 9.58 +usainBoltSpeed :: m/s = raceDistance / usainBoltTime +``` + +You can use any number of `*` and `/` operators in a unit type, but you cannot use parentheses. Unit type operators follow standard order of operations: `*` and `/` are left-associative and have the same precedence, and `^` has higher precedence. + +The following unit types are all equivalent: `kg*m/s^2`, `kg*m/s/s`, `m/s*kg/s`, `m/s^2*kg`, `kg*m^2/m/s/s`. + +For unitless types, you may use a number in the unit type annotation (by convention you should use the number `1`): + +```squiggle +springConstant :: 1 = 10 +inverseTime :: 1/s = 20 +``` + +If you use unit type annotations, Squiggle will enforce that variables must have consistent unit types. + +If a variable does not have a unit type annotation, Squiggle will attempt to infer its unit type. If the unit type can't be inferred, the variable is treated as any type. + +Inline unit type annotations are not currently supported (for example, `x = (y :: meters)`). + +Operators and functions obey the following semantics: + +- Multiplying or dividing two unit-typed variables multiplies or divides their unit types (respectively). +- Raising a unit-typed variable to a power produces a result of any unit type (i.e., the result is not type-checked). +- Most binary operators, including `+`, `-`, and comparison operators (`==`, `>=`, etc.), require that both arguments have the same unit type. +- Built-in functions can take any unit type and return any unit type. + +## Blocks + +Blocks are special expressions in Squiggle that can contain any number of local definitions and end with an expression. + +```squiggle +x = { 5 } // same as "x = 5" +y = { + t = 10 // local variable, won't be available outside of the block body + 5 * t // end expression +} +``` + +## Conditionals + +If/then/else statements in Squiggle are values too. + +```squiggle +x = 5 +if x<8 then 10 else 3 +``` + +See [Control flow](/docs/Guides/ControlFlow) for more details and examples. + +## Comments + +```squiggle +// This is a single-line comment\n +/* +This is a multiple +-line comment. +*/ +foo = 5 +``` + +## 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. + +```squiggle +normal(5,2) -> truncateLeft(3) -> SampleSet.fromDist -> SampleSet.map({|r| r + 10}) +``` + +## Standard Library + +Squiggle features a simple [standard libary](/docs/Api/Dist). + +Most functions are namespaced under their respective types to keep functionality distinct. Certain popular functions are usable without their namespaces. + +For example, + +```squiggle +a = List.upTo(0, 5000) -> SampleSet.fromList // namespaces required +b = normal(5,2) // namespace not required +c = 5 to 10 // namespace not required +``` + +## Simple Error Handling + +Squiggle supports the functions [throw](/docs/Api/Common#throw) and [try](/docs/Api/Common#try) for simple error handling. It does not yet have proper error types. + +# 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. + +```squiggle +dist = SampleSet.fromDist(5 to 100000000) +{ + sampleSetMedian: quantile(dist, .5), + pointSetMedian: quantile(PointSet.fromDist(dist), .5), + dist: dist +} +``` + +--- + +This can be particularly confusing for visualizations. Visualizations automatically convert distributions into Point Set formats. Therefore, they might often show negative values, even if the underlying distribution is fully positive. + +We plan to later support more configuration of kernel density estimation, and for visualiations of Sample Set distributions to instead use histograms. + +## Sample Set Correlations + +Correlations with Sample Set distributions are a bit complicated. Monte Carlo generations with Squiggle are ordered. The first sample in one Sample Set distribution will correspond to the first sample in a distribution that comes from a resulting Monte Carlo generation. Therefore, Sample Set distributions in a chain of Monte Carlo generations are likely to all be correlated with each other. This connection breaks if any node changes to the Point Set or Symbolic format. + +In this example, we subtract all three types of distributions by themselves. Notice that the Sample Set distribution returns 0. 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. + +```squiggle +sampleSetDist = normal(5, 2) +pointSetDist = sampleSetDist -> PointSet.fromDist +symbolicDist = Sym.normal(5, 2) +[ + sampleSetDist - sampleSetDist, + pointSetDist - pointSetDist, + symbolicDist - symbolicDist, +] +``` + +# Functions + +## Basic Syntax + +```squiggle +myMultiply(t) = normal(t^2, t^1.2+.01) +myMultiply +``` + +In Squiggle, function definitions are treated as values. There's no explicit `return` statement; the result of the last expression in the function body is returned. +If you need to define local variables in functions, you can use blocks. The last expression in the block is the value of the block: + +```squiggle +multiplyBySix(x) = { + doubleX = x * 2 + doubleX * 3 + } +``` + +## Anonymous Functions + +In Squiggle, you can define anonymous functions using the `{|...| ...}` syntax. For example, `myMultiply(x, y) = x * y` and `myMultiply = {|x, y| x * y}` are equivalent. + +Squiggle functions are first-class values, meaning you can assign them to variables, pass them as arguments to other functions, and return them from other functions. + +```squiggle +{|t| normal(t^2, t^1.2+.01)} +``` + +## Function Visualization + +The Squiggle viewer can automatically visualize functions that take a single number as input and return either a number or a distribution, without the need for manual plots: + +1. `(number) => number` +2. `(number) => distribution` + +```squiggle +numberToNumber(x) = x * x +numberToDistribution(x) = normal(x + 1, 3) +placeholderFunction(x, y) = x + y +``` + +When Squiggle visualizes a function, it automatically selects a range of input values to use. +The default range of input values is 0 to 10. + +You can manually set 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) + +## Unit Types + +Like with [variables](/docs/Guides/LanguageFeatures#unit-type-annotations), you can declare unit types for function parameters: + +```squiggle +f(x :: unit) = x +``` + +You can also declare the unit type of the function's return value: + +```squiggle +convertMass(x :: lbs) :: kg = x * 2.2 +``` + +If you pass a unit-typed variable to a function with no unit-type annotations, Squiggle will attempt to infer the unit type of the return value: + +```squiggle +id(x) = x +a :: m/s = 10 +b = id(a) // Squiggle infers that b has type m/s +``` + +Unit type checking only works for statically defined functions. In the example code below, `h` cannot be unit-type checked. + +```squiggle +f(x) = x +g(x) = x +condition = (1 == 2) +h = (condition ? f : g) +``` + +## Parameter Annotations + +Function parameters can be annotated with _domains_ to specify the range of valid input values. + +Examples: + +- `x: Number.rangeDomain(5, 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. + +```squiggle +yearToValue(year: [2020, 2100]) = 1.04 ^ (year - 2020) +``` + +### Annotation Reflection + +```squiggle +myMultiply(x: [1, 20]) = x * x +myMultiply.parameters[0] +``` + +Domains and parameter names can be accessed by the `fn.parameters` property. + +# 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. + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 + dist2 +``` + +### 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. + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 - dist2 +``` + +### 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. + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 * dist2 +``` + +We also provide concatenation of two distributions as a syntax sugar for `*` + +```squiggle +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 / dist2 +``` + +### 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. + +```squiggle +(0.1 to 1) ^ beta(2, 3) +``` + +### The base `e` exponential + +```squiggle +dist = triangular(1,2,3) +exp(dist) +``` + +### Logarithms + +A projection over a stretched x-axis. + +```squiggle +dist = triangular(1,2,3) +log(dist) +``` + +```squiggle +log10(5 to 10) +``` + +Base `x` + +```squiggle +log(5 to 10, 2) +``` + +## 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**. + +```squiggle +Sym.lognormal({p5: 1, p95: 3}) .+ Sym.triangular(5,6,7) +``` + +### Pointwise multiplication + +```squiggle +Sym.lognormal({p5: 1, p95: 5}) .* Sym.uniform(1,8) +``` + +## 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 + + + +# Distribution Creation + +## Normal + +```squiggle +normal(mean: number, stdev: number) +normal({mean: number, stdev: 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. + + + + +```squiggle +normalMean = 10 +normalStdDev = 2 +logOfLognormal = log(lognormal(normalMean, normalStdDev)) +[logOfLognormal, normal(normalMean, normalStdDev)] + +``` + + + +## 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. + + + + +```squiggle +hours_the_project_will_take = 5 to 20 +chance_of_doing_anything = 0.8 +mx(hours_the_project_will_take, 0, [chance_of_doing_anything, 1 - chance_of_doing_anything]) + +``` + + + +
+ 🔒 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. +```squiggle +forecast = 3 to 30 +chance_completely_wrong = 0.05 +forecast_if_completely_wrong = normal({p5:-100, p95:200}) +mx(forecast, forecast_if_completely_wrong, [1-chance_completely_wrong, chance_completely_wrong]) +```` + +
+## 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. + +```squiggle +PointSet.makeContinuous([ + { x: 0, y: 0.1 }, + { x: 1, y: 0.2 }, + { x: 2, y: 0.15 }, + { x: 3, y: 0.1 } +]) +``` + + + **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. + +```squiggle +PointSet.makeDiscrete([ + { x: 0, y: 0.2 }, + { x: 1, y: 0.3 }, + { x: 2, y: 0.4 }, + { x: 3, y: 0.1 } +]) +``` + +### Arguments + +- `points`: An array of at least 1 coordinate. + +# 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 +``` + +```squiggle +x = 10 +if x == 1 then 1 else 2 +``` + +### If-else as a ternary operator + +```squiggle +test ? result : alternative; +``` + +```squiggle +x = 10 +x == 0 ? 1 : 2 +``` + +### Tips and tricks + +#### Use brackets and parenthesis to organize control flow + +```squiggle +x = 10 +if x == 1 then { + 1 +} else { + 2 +} +``` + +or + +```squiggle +x = 10 +y = 20 +if x == 1 then { + ( + if y == 0 then { + 1 + } else { + 2 + } + ) +} else { + 3 +} +``` + +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: + +```squiggle +x = 10 +y = 20 +y = if x == 1 then { + 1 +} else { + 2 * x +} +``` + +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: + +```squiggle +x = 10 +result = if x == 1 then { + {y: 2, z: 0} +} else { + {y: 0, z: 4} +} +y = result.y +z = result.z +``` + +## For loops + +For loops aren't supported in Squiggle. Instead, use a [map](/docs/Api/List#map) or a [reduce](/docs/Api/List#reduce) function. + +Instead of: + +```js +xs = []; +for (i = 0; i < 10; i++) { + xs[i] = f(x); +} +``` + +do: + +```squiggle +f(x) = 2*x +xs = List.upTo(0,10) +ys = List.map(xs, {|x| f(x)}) +``` + +# 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. + +```squiggle +dist1 = {value: normal(1,1), weight: 1} +dist2 = {value: normal(100000000000,1), weight: 1} +totalWeight = dist1.weight + dist2.weight +distMixture = mixture(dist1.value, dist2.value, [dist1.weight, dist2.weight]) +mixtureMean = mean(distMixture) +separateMeansCombined = (mean(dist1.value) * (dist1.weight) + mean(dist2.value) * (dist2.weight))/totalWeight +[mixtureMean, separateMeansCombined, distMixture] +``` + +## Means of Sample Set Distributions + +The means of sample set distributions can vary dramatically, especially as the numbers get high. + +```squiggle +symbolicDist = 5 to 50333333 +sampleSetDist = SampleSet.fromDist(symbolicDist) +[mean(symbolicDist), mean(sampleSetDist), symbolicDist, sampleSetDist] +``` + +# 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 | +| % | 10^-2 | +| 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. + +```squiggle +n = 4.32 +kilo = 4.32k +micro = 4.32m +veryLarge = 1e50 +verySmall = 1e-50 +``` + +## Booleans + +Booleans can be `true` or `false`. + +```squiggle +t = true +f = false +``` + +## Strings + +Strings can be created with either single or double quotes. +[String API](/docs/Api/String) + +```squiggle +s = "Double-quoted" +s2 = 'Single-quoted' +``` + +## 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. + +```squiggle +a = 10 to 20 +b = normal(4, 2) +c = lognormal({ mean: 50, stdev: 10 }) +d = mixture(a, b, c, [.3, .3, .4]) +d +``` + +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) + +```squiggle +myList = [1, "hello", 3 to 5, ["foo", "bar"]] +first = myList[0] // 1 +bar = myList[3][1] // "bar" +``` + +## 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) + +```squiggle +d = {dist: triangular(0, 1, 2), weight: 0.25, innerDict: {foo: "bar"}} +``` + +## Other types + +Other Squiggle types include: + +- [Functions](/docs/Guides/Functions) +- [Plots](/docs/Api/Plot) +- [Scales](/docs/Api/Plot#scales) +- [Domains](#parameter-annotations)--- + description: + +--- + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Common + +Functions that work on many different types of values. Also see the experimental [JSON functions](/docs/Api/Danger#json). + +Common.equal ==: (any, any) => Bool +Returns true if the two values passed in are equal, false otherwise. Does not work for Squiggle functions, but works for most other types. + +Common.unequal !=: (any, any) => Bool + +Common.typeOf: (any) => String +Returns the type of the value passed in as a string. This is useful when you want to treat a value differently depending on its type. +myString = typeOf("foo") +myBool = typeOf(true) +myDist = typeOf(5 to 10) +myFn = typeOf({|e| e}) + +Common.inspect: ('A, message?: String) => 'A +Runs Console.log() in the [Javascript developer console](https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-developer-console) and returns the value passed in. + +Common.throw: (message?: String) => any +Throws an error. You can use `try` to recover from this error. + +Common.try: (fn: () => 'A, fallbackFn: () => 'B) => 'A|'B +Try to run a function and return its result. If the function throws an error, return the result of the fallback function instead. + +--- + +## description: + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Boolean + +Boolean.or ||: (Bool, Bool) => Bool + +Boolean.and &&: (Bool, Bool) => Bool + +Boolean.not !: (Bool) => Bool + +--- + +## description: Dates are a simple date time type. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Date + +A simple date type. Dates are stored as milliseconds since the epoch. They are immutable, and all functions that modify dates return a new date. Used with [Duration](./Duration) values. + + Dates can be useful for modeling values that change over time. Below is a simple example of a function that returns a normal distribution that changes over time, based on the number of years passed since 2020. + + + +## Constructors + +Date.make: (String) => Date, (year: Number, month: Number, day: Number) => Date, (year: Number) => Date +d1 = Date.make("2020-05-12") +d2 = Date.make(2020, 5, 10) +d3 = Date.make(2020.5) + +## Conversions + +Date.fromUnixTime: (Number) => Date +Date.fromUnixTime(1589222400) + +Date.toUnixTime: (Date) => Number +Date.toUnixTime(Date.make(2020, 5, 12)) + +## Algebra + +Date.subtract -: (Date, Date) => Duration +Date.make(2020, 5, 12) - Date.make(2000, 1, 1) + +Date.subtract -: (Date, Date) => Duration +Date.make(2020, 5, 12) - Date.make(2000, 1, 1) + +Date.add +: (Date, Duration) => Date, (Duration, Date) => Date +Date.make(2020, 5, 12) + 20years +20years + Date.make(2020, 5, 12) + +## Comparison + +Date.smaller <: (Date, Date) => Bool + +Date.larger >: (Date, Date) => Bool + +Date.smallerEq <=: (Date, Date) => Bool + +Date.largerEq >=: (Date, Date) => Bool + +## Other + +Date.rangeDomain: (min: Date, min: Date) => Domain +Date.rangeDomain(Date(2000), Date(2010)) + +--- + +## description: Squiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Dict + +Squiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript. + +## Conversions + +Dict.toList: (Dict('A)) => List([String, 'A]) +Dict.toList({a: 1, b: 2}) + +Dict.fromList: (List([String, 'A])) => Dict('A) +Dict.fromList([ +["foo", 3], +["bar", 20], +]) // {foo: 3, bar: 20} + +## Transformations + +Dict.set: (Dict('A), key: String, value: 'A) => Dict('A) +Creates a new dictionary that includes the added element, while leaving the original dictionary unaltered. +Dict.set({a: 1, b: 2}, "c", 3) + +Dict.delete: (Dict('A), key: String) => Dict('A) +Creates a new dictionary that excludes the deleted element. +Dict.delete({a: 1, b: 2}, "a") + +Dict.merge: (Dict(any), Dict(any)) => Dict(any) +first = { a: 1, b: 2 } +snd = { b: 3, c: 5 } +Dict.merge(first, snd) + +Dict.mergeMany: (List(Dict(any))) => Dict(any) +first = { a: 1, b: 2 } +snd = { b: 3, c: 5 } +Dict.mergeMany([first, snd]) // {a: 1, b: 3, c: 5} + +Dict.map: (Dict('A), fn: ('A) => 'B) => Dict('B) +Dict.map({a: 1, b: 2}, {|x| x + 1}) + +Dict.mapKeys: (Dict('A), fn: (String) => String) => Dict('A) +Dict.mapKeys({a: 1, b: 2, c: 5}, {|x| concat(x, "-foobar")}) + +Dict.omit: (Dict('A), keys: List(String)) => Dict('A) +Creates a new dictionary that excludes the omitted keys. +data = { a: 1, b: 2, c: 3, d: 4 } +Dict.omit(data, ["b", "d"]) // {a: 1, c: 3} + +## Queries + +Dict.has: (Dict(any), key: String) => Bool +Dict.has({a: 1, b: 2}, "c") + +Dict.size: (Dict(any)) => Number +Dict.size({a: 1, b: 2}) + +Dict.keys: (Dict(any)) => List(String) +Dict.keys({a: 1, b: 2}) + +Dict.values: (Dict('A)) => List('A) +Dict.values({ foo: 3, bar: 20 }) // [3, 20] + +Dict.pick: (Dict('A), keys: List(String)) => Dict('A) +Creates a new dictionary that only includes the picked keys. +data = { a: 1, b: 2, c: 3, d: 4 } +Dict.pick(data, ["a", "c"]) // {a: 1, c: 3} + +--- + +## 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 { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Dist + +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](/docs/api/Sym). 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](/DistSampleSet) by default. To create a symbolic distribution, use `Sym.` namespace: `Sym.normal`, `Sym.beta` and so on. + +## Distributions + +These are functions for creating primitive 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). + +Dist.make: (Dist) => Dist, (Number) => SymbolicDist +Dist.make(5) +Dist.make(normal({p5: 4, p95: 10})) + +Dist.mixture: (List(Dist|Number), weights?: List(Number)) => Dist, (Dist|Number) => Dist, (Dist|Number, Dist|Number, weights?: [Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number, Number]) => Dist +The `mixture` function takes a list of distributions and a list of weights, and returns a new distribution that is a mixture of the distributions in the list. The weights should be positive numbers that sum to 1. If no weights are provided, the function will assume that all distributions have equal weight. + +Note: If you want to pass in over 5 distributions, you must use the list syntax. +mixture(1,normal(5,2)) +mixture(normal(5,2), normal(10,2), normal(15,2), [0.3, 0.5, 0.2]) +mixture([normal(5,2), normal(10,2), normal(15,2), normal(20,1)], [0.3, 0.5, 0.1, 0.1]) + +Dist.mx: (List(Dist|Number), weights?: List(Number)) => Dist, (Dist|Number) => Dist, (Dist|Number, Dist|Number, weights?: [Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number]) => Dist, (Dist|Number, Dist|Number, Dist|Number, Dist|Number, Dist|Number, weights?: [Number, Number, Number, Number, Number]) => Dist +Alias for mixture() +mx(1,normal(5,2)) + +Dist.normal: (mean: Dist|Number, stdev: Dist|Number) => SampleSetDist, ({p5: Number, p95: Number}) => SampleSetDist, ({p10: Number, p90: Number}) => SampleSetDist, ({p25: Number, p75: Number}) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist +normal(5,1) +normal({p5: 4, p95: 10}) +normal({p10: 4, p90: 10}) +normal({p25: 4, p75: 10}) +normal({mean: 5, stdev: 2}) + +Dist.lognormal: (mu: Dist|Number, sigma: Dist|Number) => SampleSetDist, ({p5: Number, p95: Number}) => SampleSetDist, ({p10: Number, p90: Number}) => SampleSetDist, ({p25: Number, p75: Number}) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist +lognormal(0.5, 0.8) +lognormal({p5: 4, p95: 10}) +lognormal({p10: 4, p90: 10}) +lognormal({p25: 4, p75: 10}) +lognormal({mean: 5, stdev: 2}) + +Dist.uniform: (low: Dist|Number, high: Dist|Number) => SampleSetDist +uniform(10, 12) + +Dist.beta: (alpha: Dist|Number, beta: Dist|Number) => SampleSetDist, ({mean: Number, stdev: Number}) => SampleSetDist +beta(20, 25) +beta({mean: 0.39, stdev: 0.1}) + +Dist.cauchy: (location: Dist|Number, scale: Dist|Number) => SampleSetDist +cauchy(5, 1) + +Dist.gamma: (shape: Dist|Number, scale: Dist|Number) => SampleSetDist +gamma(5, 1) + +Dist.logistic: (location: Dist|Number, scale: Dist|Number) => SampleSetDist +logistic(5, 1) + +Dist.to to: (p5: Dist|Number, p95: Dist|Number) => SampleSetDist +The "to" function is a shorthand for lognormal({p5:min, p95:max}). It does not accept values of 0 or less, as those are not valid for lognormal distributions. +5 to 10 +to(5,10) + +Dist.exponential: (rate: Dist|Number) => SampleSetDist +exponential(2) + +Dist.bernoulli: (p: Dist|Number) => SampleSetDist +bernoulli(0.5) + +Dist.triangular: (min: Number, mode: Number, max: Number) => SampleSetDist +triangular(3, 5, 10) + +## Basic Functions + +Dist.mean: (Dist) => Number + +Dist.median: (Dist) => Number + +Dist.stdev: (Dist) => Number + +Dist.variance: (Dist) => Number + +Dist.min: (Dist) => Number + +Dist.max: (Dist) => Number + +Dist.mode: (Dist) => Number + +Dist.sample: (Dist) => Number + +Dist.sampleN: (Dist, n: Number) => List(Number) + +Dist.exp: (Dist) => Dist + +Dist.cdf: (Dist, Number) => Number + +Dist.pdf: (Dist, Number) => Number + +Dist.inv: (Dist, Number) => Number + +Dist.quantile: (Dist, Number) => Number + +Dist.truncate: (Dist, left: Number, right: Number) => Dist +Truncates both the left side and the right side of a distribution. + +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. + +Dist.truncateLeft: (Dist, Number) => Dist + +Dist.truncateRight: (Dist, Number) => Dist + +## Algebra (Dist) + +Dist.add +: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.multiply \*: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.subtract -: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.divide /: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.pow ^: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.log: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.log: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.log10: (Dist) => Dist + +Dist.unaryMinus -: (Dist) => Dist + +## Algebra (List) + +Dist.sum: (List(Dist|Number)) => Dist + +Dist.product: (List(Dist|Number)) => Dist + +Dist.cumsum: (List(Dist|Number)) => List(Dist) + +Dist.cumprod: (List(Dist|Number)) => List(Dist) + +Dist.diff: (List(Dist|Number)) => List(Dist) + +## Pointwise Algebra + +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. `Mixture` works using pointwise addition. + +Pointwise operations work on Point Set distributions, so will convert other distributions to Point Set ones first. 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. + +Dist.dotAdd: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.dotMultiply: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.dotSubtract: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.dotDivide: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +Dist.dotPow: (Dist, Number) => Dist, (Number, Dist) => Dist, (Dist, Dist) => Dist + +## 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. + +Dist.normalize: (Dist) => Dist +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. + +Dist.isNormalized: (Dist) => Bool +Check if a distribution is normalized. This only impacts Point Set distributions, because those are the only ones that can be non-normlized. Most distributions are typically normalized, but there are some commands that could produce non-normalized distributions. + +Dist.integralSum: (Dist) => Number +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. + +## Utility + +Dist.sparkline: (Dist, Number?) => String + +Produce a sparkline of length `n`. For example, `▁▁▁▁▁▂▄▆▇██▇▆▄▂▁▁▁▁▁`. These can be useful for testing or quick visualizations that can be copied and pasted into text. + +## Scoring + +Dist.klDivergence: (Dist, Dist) => Number +[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. +Dist.klDivergence(Sym.normal(5,2), Sym.normal(5,1.5)) + +Dist.logScore: ({estimate: Dist, answer: Dist|Number, prior?: Dist}) => Number +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: Sym.normal(5,2), answer: Sym.normal(5.2,1), prior: Sym.normal(5.5,3)}) +Dist.logScore({estimate: Sym.normal(5,2), answer: Sym.normal(5.2,1)}) +Dist.logScore({estimate: Sym.normal(5,2), answer: 4.5}) + +--- + +## description: Sample set distributions are one of the three distribution formats. Internally, they are stored as a list of numbers. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# SampleSet + +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. + +## Constructors + +SampleSet.make: (Dist) => SampleSetDist, (Number) => SampleSetDist, (List(Number)) => SampleSetDist, ((index?: Number) => Number) => SampleSetDist +Calls the correct conversion constructor, based on the corresponding input type, to create a sample set distribution +SampleSet(5) +SampleSet.make([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3]) +SampleSet.make({|i| sample(normal(5,2))}) + +## Conversions + +SampleSet.fromDist: (Dist) => SampleSetDist +Converts any distribution type into a sample set distribution. +SampleSet.fromDist(Sym.normal(5,2)) + +SampleSet.fromNumber: (Number) => SampleSetDist +Convert a number into a sample set distribution that contains `n` copies of that number. `n` refers to the model sample count. +SampleSet.fromNumber(3) + +SampleSet.fromList: (List(Number)) => SampleSetDist +Convert a list of numbers into a sample set distribution. +SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3]) + +SampleSet.toList: (SampleSetDist) => List(Number) +Gets the internal samples of a sampleSet distribution. This is separate from the `sampleN()` function, which would shuffle the samples. `toList()` maintains order and length. +SampleSet.toList(SampleSet.fromDist(normal(5,2))) + +SampleSet.fromFn: ((index?: Number) => Number) => SampleSetDist +Convert a function into a sample set distribution by calling it `n` times. +SampleSet.fromFn({|i| sample(normal(5,2))}) + +## Transformations + +SampleSet.map: (SampleSetDist, fn: (Number) => Number) => SampleSetDist +Transforms a sample set distribution by applying a function to each sample. Returns a new sample set distribution. +SampleSet.map(SampleSet.fromDist(normal(5,2)), {|x| x + 1}) + +SampleSet.map2: (SampleSetDist, SampleSetDist, fn: (Number, Number) => Number) => SampleSetDist +Transforms two sample set distributions by applying a function to each pair of samples. Returns a new sample set distribution. +SampleSet.map2( +SampleSet.fromDist(normal(5,2)), +SampleSet.fromDist(normal(5,2)), +{|x, y| x + y} +) + +SampleSet.map3: (SampleSetDist, SampleSetDist, SampleSetDist, fn: (Number, Number, Number) => Number) => SampleSetDist +SampleSet.map3( +SampleSet.fromDist(normal(5,2)), +SampleSet.fromDist(normal(5,2)), +SampleSet.fromDist(normal(5,2)), +{|x, y, z| max([x,y,z])} +) + +SampleSet.mapN: (List(SampleSetDist), fn: (List(Number)) => Number) => SampleSetDist +SampleSet.mapN( +[ +SampleSet.fromDist(normal(5,2)), +SampleSet.fromDist(normal(5,2)), +SampleSet.fromDist(normal(5,2)) +], +max +) + +--- + +## description: The Sym module provides functions to create some common symbolic distributions. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Sym + +Symbolic Distributions. All these functions match the functions for creating sample set distributions, but produce symbolic distributions instead. Symbolic distributions won't capture correlations, but are more performant than sample distributions. + +Sym.normal: (Number, Number) => SymbolicDist, ({p5: Number, p95: Number}) => SymbolicDist, ({p10: Number, p90: Number}) => SymbolicDist, ({p25: Number, p75: Number}) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist +Sym.normal(5, 1) +Sym.normal({ p5: 4, p95: 10 }) +Sym.normal({ p10: 4, p90: 10 }) +Sym.normal({ p25: 4, p75: 10 }) +Sym.normal({ mean: 5, stdev: 2 }) + +Sym.lognormal: (Number, Number) => SymbolicDist, ({p5: Number, p95: Number}) => SymbolicDist, ({p10: Number, p90: Number}) => SymbolicDist, ({p25: Number, p75: Number}) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist +Sym.lognormal(0.5, 0.8) +Sym.lognormal({ p5: 4, p95: 10 }) +Sym.lognormal({ p10: 4, p90: 10 }) +Sym.lognormal({ p25: 4, p75: 10 }) +Sym.lognormal({ mean: 5, stdev: 2 }) + +Sym.uniform: (Number, Number) => SymbolicDist +Sym.uniform(10, 12) + +Sym.beta: (Number, Number) => SymbolicDist, ({mean: Number, stdev: Number}) => SymbolicDist +Sym.beta(20, 25) +Sym.beta({ mean: 0.39, stdev: 0.1 }) + +Sym.cauchy: (Number, Number) => SymbolicDist +Sym.cauchy(5, 1) + +Sym.gamma: (Number, Number) => SymbolicDist +Sym.gamma(5, 1) + +Sym.logistic: (Number, Number) => SymbolicDist +Sym.logistic(5, 1) + +Sym.exponential: (Number) => SymbolicDist +Sym.exponential(2) + +Sym.bernoulli: (Number) => SymbolicDist +Sym.bernoulli(0.5) + +Sym.pointMass: (Number) => SymbolicDist +Point mass distributions are already symbolic, so you can use the regular `pointMass` function. +pointMass(0.5) + +Sym.triangular: (Number, Number, Number) => SymbolicDist +Sym.triangular(3, 5, 10) + +--- + +## 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. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# PointSet + +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. + +## Constructors + +PointSet.make: (Dist) => PointSetDist, (Number) => PointSetDist +PointSet.make(normal(5,10)) +PointSet(3) + +PointSet.makeContinuous: (List({x: Number, y: Number})) => PointSetDist +PointSet.makeContinuous([ +{x: 0, y: 0.2}, +{x: 1, y: 0.7}, +{x: 2, y: 0.8}, +{x: 3, y: 0.2} +]) + +PointSet.makeDiscrete: (List({x: Number, y: Number})) => PointSetDist +PointSet.makeDiscrete([ +{x: 0, y: 0.2}, +{x: 1, y: 0.7}, +{x: 2, y: 0.8}, +{x: 3, y: 0.2} +]) + +## Conversions + +PointSet.fromDist: (Dist) => PointSetDist +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(normal(5,2)) + +PointSet.fromNumber: (Number) => PointSetDist +PointSet.fromNumber(3) + +PointSet.downsample: (PointSetDist, newLength: Number) => PointSetDist +PointSet.downsample(PointSet.fromDist(normal(5,2)), 50) + +PointSet.support: (PointSetDist) => {points: List(Number), segments: List([Number, Number])} +PointSet.support(PointSet.fromDist(normal(5,2))) + +## Transformations + +PointSet.mapY: (PointSetDist, fn: (Number) => Number) => PointSetDist +PointSet.mapY(mx(Sym.normal(5,2)), {|x| x + 1}) + +--- + +## description: Durations are a simple time type, representing a length of time. They are internally stored as milliseconds, but often shown and written using seconds, minutes, hours, days, etc. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Duration + +Durations are a simple time type, representing a length of time. They are internally stored as milliseconds, but often shown and written using seconds, minutes, hours, days, etc. Durations are typically used with [Date](./Date) values. + +| **Unit Name** | **Example** | **Convert Number to Duration** | **Convert Duration to Number** | +| ------------- | ----------- | ------------------------------ | ------------------------------ | +| Minute | `5minutes` | `fromMinutes(number)` | `toMinutes(duration)` | +| Hour | `5hour` | `fromHours(number)` | `toHours(duration)` | +| Day | `5days` | `fromDays(number)` | `toDays(duration)` | +| Year | `5years` | `fromYears(number)` | `toYears(duration)` | + +## Constructors + +Duration.fromMinutes: (Number) => Duration +Duration.fromMinutes(5) + +Duration.fromHours: (Number) => Duration +Duration.fromHours(5) + +Duration.fromDays: (Number) => Duration +Duration.fromDays(5) + +Duration.fromYears: (Number) => Duration +Duration.fromYears(5) + +## Conversions + +Duration.toMinutes: (Duration) => Number +Duration.toMinutes(5minutes) + +Duration.toHours: (Duration) => Number +Duration.toHours(5minutes) + +Duration.toDays: (Duration) => Number +Duration.toDays(5minutes) + +Duration.toYears: (Duration) => Number +Duration.toYears(5minutes) + +## Algebra + +Duration.unaryMinus -: (Duration) => Duration +-5minutes + +Duration.add +: (Duration, Duration) => Duration +5minutes + 10minutes + +Duration.subtract -: (Duration, Duration) => Duration +5minutes - 10minutes + +Duration.multiply _: (Duration, Number) => Duration, (Number, Duration) => Duration +5minutes _ 10 +10 \* 5minutes + +Duration.divide /: (Duration, Duration) => Number +5minutes / 2minutes + +Duration.divide /: (Duration, Duration) => Number +5minutes / 2minutes + +## Comparison + +Duration.smaller <: (Duration, Duration) => Bool + +Duration.larger >: (Duration, Duration) => Bool + +Duration.smallerEq <=: (Duration, Duration) => Bool + +Duration.largerEq >=: (Duration, Duration) => Bool + +--- + +## description: Lists are a simple data structure that can hold any type of value. They are similar to arrays in Javascript or lists in Python. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# List + +Lists are a simple data structure that can hold any type of value. They are similar to arrays in Javascript or lists in Python. + +```squiggle +myList = [1, 2, 3, normal(5,2), "hello"] +``` + +Lists are immutable, meaning that they cannot be modified. Instead, all list functions return a new list. + +## Constructors + +List.make: (count: Number, fn: (index?: Number) => 'A) => List('A), (count: Number, value: 'A) => List('A), (SampleSetDist) => List(Number) +Creates an array of length `count`, with each element being `value`. If `value` is a function, it will be called `count` times, with the index as the argument. +List.make(2, 3) +List.make(2, {|| 3}) +List.make(2, {|index| index+1}) + +List.upTo: (low: Number, high: Number) => List(Number) +List.upTo(1,4) + +## Modifications + +List.reverse: (List('A)) => List('A) +List.reverse([1,4,5]) // [5,4,1] + +List.concat: (List('A), List('A)) => List('A) +List.concat([1,2,3], [4, 5, 6]) + +List.sortBy: (List('A), fn: ('A) => Number) => List('A) +List.sortBy([{a:3}, {a:1}], {|f| f.a}) + +List.append: (List('A), 'A) => List('A) +List.append([1,4],5) + +List.join: (List(String), separator?: String) => String, (List(String)) => String +List.join(["a", "b", "c"], ",") // "a,b,c" + +List.flatten: (List(any)) => List(any) +List.flatten([[1,2], [3,4]]) + +List.shuffle: (List('A)) => List('A) +List.shuffle([1,3,4,20]) + +List.zip: (List('A), List('B)) => List(['A, 'B]) +List.zip([1,3,4,20], [2,4,5,6]) + +List.unzip: (List(['A, 'B])) => [List('A), List('B)] +List.unzip([[1,2], [2,3], [4,5]]) + +## Filtering + +List.slice: (List('A), startIndex: Number, endIndex?: Number) => List('A) +Returns a copy of the list, between the selected `start` and `end`, end not included. Directly uses the [Javascript implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) underneath. +List.slice([1,2,5,10],1,3) + +List.uniq: (List('A)) => List('A) +Filters the list for unique elements. Works on select Squiggle types. +List.uniq([1,2,3,"hi",false,"hi"]) + +List.uniqBy: (List('A), ('A) => 'B) => List('A) +Filters the list for unique elements. Works on select Squiggle types. +List.uniqBy([[1,5], [3,5], [5,7]], {|x| x[1]}) + +List.filter: (List('A), fn: ('A) => Bool) => List('A) +List.filter([1,4,5], {|x| x>3}) + +## Queries + +List.length: (List(any)) => Number +List.length([1,4,5]) + +List.first: (List('A)) => 'A +List.first([1,4,5]) + +List.last: (List('A)) => 'A +List.last([1,4,5]) + +List.minBy: (List('A), fn: ('A) => Number) => 'A +List.minBy([{a:3}, {a:1}], {|f| f.a}) + +List.maxBy: (List('A), fn: ('A) => Number) => 'A +List.maxBy([{a:3}, {a:1}], {|f| f.a}) + +List.every: (List('A), fn: ('A) => Bool) => Bool +List.every([1,4,5], {|el| el>3 }) + +List.some: (List('A), fn: ('A) => Bool) => Bool +List.some([1,4,5], {|el| el>3 }) + +List.find: (List('A), fn: ('A) => Bool) => 'A +Returns an error if there is no value found +List.find([1,4,5], {|el| el>3 }) + +List.findIndex: (List('A), fn: ('A) => Bool) => Number +Returns `-1` if there is no value found +List.findIndex([1,4,5], {|el| el>3 }) + +List.sample: (List('A)) => 'A +List.sample([1,4,5]) + +List.sampleN: (List('A), n: Number) => List('A) +List.sampleN([1,4,5], 2) + +## Functional Transformations + +List.map: (List('A), ('A, index?: Number) => 'B) => List('B) +List.map([1,4,5], {|x| x+1}) +List.map([1,4,5], {|x,i| x+i+1}) + +List.reduce: (List('B), initialValue: 'A, callbackFn: (accumulator: 'A, currentValue: 'B, currentIndex?: Number) => 'A) => 'A +Applies `f` to each element of `arr`. The function `f` has two main paramaters, an accumulator and the next value from the array. It can also accept an optional third `index` parameter. +List.reduce([1,4,5], 2, {|acc, el| acc+el}) + +List.reduceReverse: (List('B), initialValue: 'A, callbackFn: (accumulator: 'A, currentValue: 'B) => 'A) => 'A +Works like `reduce`, but the function is applied to each item from the last back to the first. +List.reduceReverse([1,4,5], 2, {|acc, el| acc-el}) + +List.reduceWhile: (List('B), initialValue: 'A, callbackFn: (accumulator: 'A, currentValue: 'B) => 'A, conditionFn: ('A) => Bool) => 'A +Works like `reduce`, but stops when the condition is no longer met. This is useful, in part, for simulating processes that need to stop based on the process state. + +// 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} +) + +--- + +## description: Simple constants and functions for math in Squiggle. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Math + +## Constants + +| Variable Name | Number Name | Value | +| -------------- | --------------------------------------------------------------------------------- | -------------------- | +| `Math.e` | Euler's number | ≈ 2.718281828459045 | +| `Math.ln2` | Natural logarithm of 2 | ≈ 0.6931471805599453 | +| `Math.ln10` | Natural logarithm of 10 | ≈ 2.302585092994046 | +| `Math.log2e` | Base 2 logarithm of E | ≈ 1.4426950408889634 | +| `Math.log10e` | Base 10 logarithm of E | ≈ 0.4342944819032518 | +| `Math.pi` | Pi - ratio of the circumference to the diameter of a circle | ≈ 3.141592653589793 | +| `Math.sqrt1_2` | Square root of 1/2 | ≈ 0.7071067811865476 | +| `Math.sqrt2` | Square root of 2 | ≈ 1.4142135623730951 | +| `Math.phi` | Phi is the golden ratio. | 1.618033988749895 | +| `Math.tau` | Tau is the ratio constant of a circle's circumference to radius, equal to 2 \* pi | 6.283185307179586 | + +## Functions + +Math.sqrt: (Number) => Number + +Math.sin: (Number) => Number + +Math.cos: (Number) => Number + +Math.tan: (Number) => Number + +Math.asin: (Number) => Number + +Math.acos: (Number) => Number + +Math.atan: (Number) => Number + +--- + +## description: + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# MixedSet + +The MixedSet module offers functionality for creating mixed sets, which are sets that can contain both discrete and continuous values. Discrete values are represented as points, while continuous values are represented as ranges. Mixed sets are particularly useful for describing the support of mixed probability distributions. + +The majority of set functions in the MixedSet module are designed to mirror the [upcomming set functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) in Javascript. + +The primary purpose of mixed sets in Squiggle is to facilitate scoring. For instance, by utilizing mixed sets, you can easily determine if one distribution covers the support of another distribution. If it doesn't, it may be prone to receiving a score of negative infinity. + +Currently, there is no dedicated MixedSet object type. Instead, mixed sets are implemented as dictionaries, where discrete values are stored as points and continuous values are stored as segments. + +MixedSet.difference: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])} + +MixedSet.intersection: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])} + +MixedSet.union: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => {points: List(Number), segments: List([Number, Number])} + +MixedSet.isSubsetOf: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool + +MixedSet.isSupersetOf: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool + +MixedSet.isEqual: ({points: List(Number), segments: List([Number, Number])}, {points: List(Number), segments: List([Number, Number])}) => Bool + +MixedSet.isEmpty: ({points: List(Number), segments: List([Number, Number])}) => Bool + +MixedSet.min: ({points: List(Number), segments: List([Number, Number])}) => Number +Returns the minimum value in the set + +MixedSet.max: ({points: List(Number), segments: List([Number, Number])}) => Number +Returns the maximum value in the set + +--- + +## description: + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# 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.dist: (dist: Dist, params?: {xScale?: Scale, yScale?: Scale, showSummary?: Bool}) => Plot +Plot.dist( +normal(5, 2), +{ +xScale: Scale.linear({ min: -2, max: 6, title: "X Axis Title" }), +showSummary: true, +} +) + +Plot.dists: (dists: List(Dist|Number)|List({name?: String, value: Dist|Number}), {xScale?: Scale, yScale?: Scale, showSummary?: Bool}?) => Plot +Plot.dists( +{ +dists: [ +{ name: "First Dist", value: normal(0, 1) }, +{ name: "Second Dist", value: uniform(2, 4) }, +], +xScale: Scale.symlog({ min: -2, max: 5 }), +} +) + +Plot.numericFn: (fn: (Number) => Number, params?: {xScale?: Scale, yScale?: Scale, xPoints?: List(Number)}) => Plot +Plot.numericFn( +{|t|t ^ 2}, +{ xScale: Scale.log({ min: 1, max: 100 }), points: 10 } +) + +Plot.distFn: (fn: (Number) => Dist, params?: {xScale?: Scale, yScale?: Scale, distXScale?: Scale, xPoints?: List(Number)}) => Plot +Plot.distFn( +{|t|normal(t, 2) \* normal(5, 3)}, +{ +xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }), +yScale: Scale.linear({ title: "Value" }), +distXScale: Scale.linear({ tickFormat: "#x" }), +} +) + +Plot.scatter: ({xDist: SampleSetDist, yDist: SampleSetDist, xScale?: Scale, yScale?: Scale}) => Plot +xDist = SampleSet.fromDist(2 to 5) +yDist = normal({p5:-3, p95:3}) _ 5 - xDist ^ 2 +Plot.scatter({ +xDist: xDist, +yDist: yDist, +xScale: Scale.log({min: 1.5}), +}) +xDist = SampleSet.fromDist(normal({p5:-2, p95:5})) +yDist = normal({p5:-3, p95:3}) _ 5 - xDist +Plot.scatter({ +xDist: xDist, +yDist: yDist, +xScale: Scale.symlog({title: "X Axis Title"}), +yScale: Scale.symlog({title: "Y Axis Title"}), +}) + +--- + +## description: Squiggle numbers are Javascript floats. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Number + +Squiggle numbers are Javascript floats. + +## Constants + +| Variable Name | Number Name | Value | +| ----------------- | --------------------------------------------------------------- | ----------------------- | +| `Number.minValue` | The smallest positive numeric value representable in JavaScript | 5e-324 | +| `Number.maxValue` | The largest positive numeric value representable in JavaScript | 1.7976931348623157e+308 | + +## Functions + +## Comparison + +Number.smaller <: (Number, Number) => Bool + +Number.larger >: (Number, Number) => Bool + +Number.smallerEq <=: (Number, Number) => Bool + +Number.largerEq >=: (Number, Number) => Bool + +## Algebra (Number) + +Number.add +: (Number, Number) => Number + +Number.subtract -: (Number, Number) => Number + +Number.multiply \*: (Number, Number) => Number + +Number.divide /: (Number, Number) => Number + +Number.pow ^: (Number, Number) => Number + +Number.mod: (Number, Number) => Number +modulo. This is the same as the '%' operator in Python. Note that there is no '%' operator in Squiggle for this operation. +mod(10, 3) +mod(-10, 3) +mod(10, -3) + +## Functions (Number) + +Number.unaryMinus -: (Number) => Number +exp(3.5) + +Number.exp: (Number) => Number +exponent +exp(3.5) + +Number.log: (Number) => Number +log(3.5) + +Number.log10: (Number) => Number +log10(3.5) + +Number.log2: (Number) => Number +log2(3.5) + +Number.floor: (Number) => Number +floor(3.5) + +Number.ceil: (Number) => Number +ceil(3.5) + +Number.abs: (Number) => Number +absolute value +abs(3.5) + +Number.round: (Number) => Number +round(3.5) + +## Algebra (List) + +Number.sum: (List(Number)) => Number +sum([3,5,2]) + +Number.product: (List(Number)) => Number +product([3,5,2]) + +Number.cumprod: (List(Number)) => List(Number) +cumulative product +cumprod([3,5,2,3,5]) + +Number.diff: (List(Number)) => List(Number) +diff([3,5,2,3,5]) + +## Functions (List) + +Number.min: (List(Number)) => Number, (Number, Number) => Number +min([3,5,2]) + +Number.max: (List(Number)) => Number, (Number, Number) => Number +max([3,5,2]) + +Number.mean: (List(Number)) => Number +mean([3,5,2]) + +Number.quantile: (List(Number), Number) => Number +quantile([1,5,10,40,2,4], 0.3) + +Number.median: (List(Number)) => Number +median([1,5,10,40,2,4]) + +Number.geomean: (List(Number)) => Number +geometric mean +geomean([3,5,2]) + +Number.stdev: (List(Number)) => Number +standard deviation +stdev([3,5,2,3,5]) + +Number.variance: (List(Number)) => Number +variance([3,5,2,3,5]) + +Number.sort: (List(Number)) => List(Number) +sort([3,5,2,3,5]) + +## Utils + +Number.rangeDomain: (min: Number, max: Number) => Domain +Number.rangeDomain(5, 10) + +--- + +## description: Scales for plots. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Scale + +Chart axes in [plots](./Plot.mdx) can be scaled using the following functions. Each scale function accepts optional min and max value. Power scale accepts an extra exponent parameter. + +Squiggle uses D3 for the tick formats. You can read about d3 tick formats [here](https://github.com/d3/d3-format). + +## Numeric Scales + +Scale.linear: ({min?: Number, max?: Number, tickFormat?: String, title?: String}) => Scale, () => Scale +Scale.linear({ min: 3, max: 10 }) + +Scale.log: ({min?: Number, max?: Number, tickFormat?: String, title?: String}) => Scale, () => Scale +Scale.log({ min: 1, max: 100 }) + +Scale.symlog: ({min?: Number, max?: Number, tickFormat?: String, title?: String, constant?: Number}) => Scale, () => Scale +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 `0.0001`. +Scale.symlog({ min: -10, max: 10 }) + +Scale.power: ({min?: Number, max?: Number, tickFormat?: String, title?: String, exponent?: Number}) => Scale, () => Scale +Power scale. Accepts an extra `exponent` parameter, like, `Scale.power({exponent: 2, min: 0, max: 100})`. + +The default value for `exponent` is `0.1`. +Scale.power({ min: 1, max: 100, exponent: 0.1 }) + +## Date Scales + +Scale.date: ({min?: Date, max?: Date, tickFormat?: String, title?: String}) => Scale, () => Scale +Only works on Date values. Is a linear scale under the hood. +Scale.date({ min: Date(2022), max: Date(2025) }) + +--- + +## description: Function Specifications + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Spec + +Function specifications (Specs) are an experimental feature in Squiggle. They are used to specify the structure of functions and verify that they match that structure. They are used primarily as a tag for functions. + +Spec.make: ({name: String, documentation: String, validate: Lambda}) => Specification +Create a specification. +@startClosed +validate(fn) = { +hasErrors = List.upTo(2020, 2030) +-> List.some( +{|e| typeOf(fn(Date(e))) != "Distribution"} +) +hasErrors ? "Some results aren't distributions" : "" +} + +spec = Spec.make( +{ +name: "Stock market over time", +documentation: "A distribution of stock market values over time.", +validate: validate, +} +) + +@spec(spec) +myEstimate(t: [Date(2020), Date(2030)]) = normal(10, 3) + +--- + +## description: Functions for working with strings in Squiggle + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# String + +Strings support all JSON escape sequences, with addition of escaped single-quotes (for single-quoted strings) + +```squiggle +a = "'\" NUL:\u0000" +b = '\'" NUL:\u0000' +``` + +String.make: (any) => String +Converts any value to a string. Some information is often lost. + +String.concat: (String, String) => String, (String, any) => String + +String.add +: (String, String) => String, (String, any) => String + +String.split: (String, separator: String) => List(String) + +--- + +## description: Tables are a simple date time type. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Table + +The Table module allows you to make simple tables for displaying data. + +Table.make: (data: List('A), params: {columns: List({fn: ('A) => any, name?: String})}) => Table +Table.make( +[ +{ name: "First Dist", value: normal(0, 1) }, +{ name: "Second Dist", value: uniform(2, 4) }, +{ name: "Third Dist", value: uniform(5, 6) }, +], +{ +columns: [ +{ name: "Name", fn: {|d|d.name} }, +{ name: "Mean", fn: {|d|mean(d.value)} }, +{ name: "Std Dev", fn: {|d|variance(d.value)} }, +{ name: "Dist", fn: {|d|d.value} }, +], +} +) +Table.make( +[ +{ name: "First Dist", value: Sym.lognormal({ p5: 1, p95: 10 }) }, +{ name: "Second Dist", value: Sym.lognormal({ p5: 5, p95: 30 }) }, +{ name: "Third Dist", value: Sym.lognormal({ p5: 50, p95: 90 }) }, +], +{ +columns: [ +{ name: "Name", fn: {|d|d.name} }, +{ +name: "Plot", +fn: { +|d| +Plot.dist( +{ +dist: d.value, +xScale: Scale.log({ min: 0.5, max: 100 }), +showSummary: false, +} +) +}, +}, +], +} +) + +--- + +## description: + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# System + +## Constants + +### System.version + +Returns the current version of Squiggle. + +## Functions + +System.sampleCount: () => Number +The number of samples set in the current environment. This variable can be modified in the Squiggle playground settings. + +--- + +## description: The Tag module handles tags, which allow the additions of metadata to Squiggle variables. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Tag + +Tags are metadata that can be added to Squiggle variables. They are used to add additional information to variables, such as names, descriptions, and visualization options. While tags can be accessed at runtime, they are primarily meant for use with the Squiggle Playground and other visualizations. +Tags can be added to variables either by using their name `Tag.get[Name]` or by using decorators. + +## List of Tags + +| Tag Name | Description | +| ------------- | ------------------------------------------------------------------------------------------ | +| `name` | Change the default display name for the variable, in the playground. | +| `doc` | Adds documentation to the variable in the playground. | +| `showAs` | Change the default view for the value when displayed. | +| `format` | Format a number, date, or duration when displayed. | +| `notebook` | Formats lists as notebooks. | +| `hide` | Don't show the variable in the playground | +| `startOpen` | Start the variable open in the playground | +| `startClosed` | Start the variable closed in the playground | +| `location` | Store the proper location. Helps when you want to locate code corresponding to a variable. | +| `exportData` | Metadata about exported variables. Cannot be added manually. | + +## Example + + + +## Tags + +Tag.name: ('A, String) => 'A +Adds a user-facing name to a value. This is useful for documenting what a value represents, or how it was calculated. + +_Note: While names are shown in the sidebar, you still need to call variables by their regular variable names in code._ + +Tag.getName: (any) => String + +Tag.doc: ('A, String) => 'A +Adds text documentation to a value. This is useful for documenting what a value represents or how it was calculated. + +Tag.getDoc: (any) => String + +Tag.showAs: (Dist, Plot|(Dist) => Plot) => Dist, (List(any), Table|(List(any)) => Table) => List(any), ((Number) => Dist|Number, Plot|Calculator|((Number) => Dist|Number) => Plot|Calculator) => (Number) => Dist|Number, ((Date) => Dist|Number, Plot|Calculator|((Date) => Dist|Number) => Plot|Calculator) => (Date) => Dist|Number, ((Duration) => Dist|Number, Plot|Calculator|((Duration) => Dist|Number) => Plot|Calculator) => (Duration) => Dist|Number, (Lambda, Calculator|(Lambda) => Calculator) => Lambda +Overrides the default visualization for a value. +`showAs()` can take either a visualization, or a function that calls the value and returns a visualization. + +Different types of values can be displayed in different ways. The following table shows the potential visualization types for each input type. In this table, `Number` can be used with Dates and Durations as well. +| **Input Type** | **Visualization Types** | +| ----------------------------------- | ------------------------------------- | +| **Distribution** | `Plot.dist` | +| **List** | `Table` | +| **`(Number -> Number)` Function** | `Plot.numericFn`, `Calculator` | +| **`(Number -> Dist)` Function** | `Plot.distFn`, `Calculator` | +| **Function** | `Calculator` | + +example1 = ({|x| x + 1}) -> Tag.showAs(Calculator) +@showAs({|f| Plot.numericFn(f, { xScale: Scale.symlog() })}) +example2 = {|x| x + 1} + +Tag.getShowAs: (any) => any + +Tag.getExportData: (any) => any + +Tag.spec: ('A, Specification) => 'A +Adds a specification to a value. This is useful for documenting how a value was calculated, or what it represents. + +Tag.getSpec: (any) => any + +Tag.format: (Dist|Number, numberFormat: String) => Dist|Number, (Duration, numberFormat: String) => Duration, (Date, timeFormat: String) => Date +Set the display format for a number, distribution, duration, or date. Uses the [d3-format](https://d3js.org/d3-format) syntax on numbers and distributions, and the [d3-time-format](https://d3js.org/d3-time-format) syntax for dates. + +Tag.getFormat: (Dist|Number) => String, (Duration) => String, (Date) => String + +Tag.hide: ('A, Bool) => 'A, ('A) => 'A +Hides a value when displayed under Variables. This is useful for hiding intermediate values or helper functions that are used in calculations, but are not directly relevant to the user. Only hides top-level variables. + +Tag.getHide: (any) => Bool + +Tag.startOpen: ('A) => 'A +When the value is first displayed, it will begin open in the viewer. Refresh the page to reset. + +Tag.startClosed: ('A) => 'A +When the value is first displayed, it will begin collapsed in the viewer. Refresh the page to reset. + +Tag.getStartOpenState: (any) => String +Returns the startOpenState of a value, which can be "open", "closed", or "" if no startOpenState is set. Set using `Tag.startOpen` and `Tag.startClosed`. + +Tag.notebook: (List('A), Bool) => List('A), (List('A)) => List('A) +Displays the list of values as a notebook. This means that element indices are hidden, and the values are displayed in a vertical list. Useful for displaying combinations of text and values. +Calculator.make( +{|f, contents| f ? Tag.notebook(contents) : contents}, +{ +description: "Shows the contents as a notebook if the checkbox is checked.", +inputs: [ +Input.checkbox({ name: "Show as Notebook", default: true }), +Input.textArea( +{ +name: "Contents to show", +default: "[ +\"## Distribution 1\", +normal(5, 2), +\"## Distribution 1\", +normal(20, 1), +\"This is an opening section. Here is more text. +\", +]", +} +), +], +} +) + +Tag.getNotebook: (any) => Bool + +Tag.location: ('A) => 'A +Saves the location of a value. Note that this must be called at the point where the location is to be saved. If you use it in a helper function, it will save the location of the helper function, not the location where the helper function is called. + +Tag.getLocation: (any) => any + +## Functions + +Tag.getAll: (any) => Dict(any) +Returns a dictionary of all tags on a value. + +Tag.omit: ('A, List(String)) => 'A +Returns a copy of the value with the specified tags removed. + +Tag.clear: ('A) => 'A +Returns a copy of the value with all tags removed. + +--- + +## description: The Calculator module helps you create custom calculators + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Calculator + +The Calculator module allows you to make custom calculators for functions. This is a form that's tied to a specific Squiggle function, where the inputs to the form are passed to that function, and the output of the function gets shown on the bottom. + +Calculators can be useful for debugging functions or to present functions to end users. + +Calculator.make: ({fn: Lambda, title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator, (Lambda, params?: {title?: String, description?: String, inputs?: List(Input), autorun?: Bool, sampleCount?: Number}) => Calculator + +`Calculator.make` takes in a function, a description, and a list of inputs. The function should take in the same number of arguments as the number of inputs, and the arguments should be of the same type as the default value of the input. + +Inputs are created using the `Input` module. The Input module has a few different functions for creating different types of inputs. + +For calculators that take a long time to run, we recommend setting `autorun` to `false`. This will create a button that the user can click to run the calculator. + +Calculator.make( +{|text, textArea, select, checkbox| text + textArea}, +{ +title: "My example calculator", +inputs: [ +Input.text({ name: "text", default: "20" }), +Input.textArea({ name: "textArea", default: "50 to 80" }), +Input.select({ name: "select", default: "second", options: ["first", "second", "third"] }), +Input.checkbox({ name: "checkbox", default: true }), +], +sampleCount: 10k, +}) +// When a calculator is created with only a function, it will guess the inputs based on the function's parameters. It won't provide default values if it's a user-written function. + +({|x| x \* 5}) -> Calculator + +--- + +## description: Inputs are now only used for describing forms for calculators. + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Input + +Inputs are now only used for describing forms for [calculators](./Calculator.mdx). + +Input.text: ({name: String, description?: String, default?: Number|String}) => Input +Creates a single-line input. This input can be used for all Squiggle types. +Input.text({ name: "First", default: "John" }) +Input.text({ name: "Number of X in Y", default: '20 to 300' }) + +Input.textArea: ({name: String, description?: String, default?: Number|String}) => Input +Creates a multi-line input, sized with the provided input. This input can be used for all Squiggle types. +Input.textArea({ name: "people", default: '{ +"John": 20 to 50, +"Mary": 30 to 90, +}' }) + +Input.checkbox: ({name: String, description?: String, default?: Bool}) => Input +Creates a checkbox input. Used for Squiggle booleans. +Input.checkbox({ name: "IsTrue?", default: true }) + +Input.select: ({name: String, options: List(String), description?: String, default?: String}) => Input +Creates a dropdown input. Used for Squiggle strings. +Input.select({ name: "Name", default: "Sue", options: ["John", "Mary", "Sue"] }) + +--- + +## description: + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# RelativeValues + +_Warning: Relative value functions are particularly experimental and subject to change._ + +RelativeValues.gridPlot: ({ids: List(String), fn: (String, String) => [Dist, Dist]}) => Plot +RelativeValues.gridPlot({ +ids: ["foo", "bar"], +fn: {|id1, id2| [SampleSet.fromDist(2 to 5), SampleSet.fromDist(3 to 6)]}, +}) + +--- + +## description: Newer experimental functions which are less stable than Squiggle as a whole + +import { FnDocumentationFromName } from "@quri/squiggle-components"; +import { SquiggleEditor } from "../../../components/SquiggleEditor"; + +# Danger + +The Danger library contains newer experimental functions which are less stable than Squiggle as a whole. They are not recommended for production use, but are useful for testing out new ideas., + +## JSON + +The JSON module provides JSON-like objects in Squiggle. `Danger.json` is mainly useful for debugging, and `Danger.jsonString` is useful for sending data to other systems. A simple example is shown below. + +We have custom serializers for different Squiggle objects. Note that this API is unstable and might change over time. + + + +Danger.json: (any) => any +Converts a value to a simpler form, similar to JSON. This is useful for debugging. Keeps functions and dates, but converts objects like distributions, calculators, and plots to combinations of dictionaries and lists. +Danger.json({a: 1, b: 2}) +Danger.json([2 to 5, Sym.normal(5, 2), Calculator({|x| x + 1})]) + +Danger.jsonString: (any) => String +Converts a value to a stringified JSON, similar to JSON.stringify() in Javasript. Replaces functions with dict summaries. +Danger.jsonString({a: 1, b: 2}) +Danger.jsonString([2 to 5, Sym.normal(5, 2), Calculator({|x| x + 1})]) + +## Javascript + +Near 1-1 matches of Javascript functions. + +Danger.parseFloat: (String) => Number|String +Converts a string to a number. If the string can't be converted, returns `Parse Failed`. Calls Javascript `parseFloat` under the hood. +Danger.parseFloat('10.3') + +Danger.now: () => Date +Returns the current date. Internally calls `Date.now()` in JavaScript. + +_Caution: This function, which returns the current date, produces varying outputs with each call. As a result, accurately estimating the value of functions that incorporate `Danger.now()` at past time points is challenging. In the future, we intend to implement a feature allowing the input of a simulated time via an environment variable to address this issue._ +Danger.now() + +## Math + +Danger.laplace: (Number, Number) => Number +Calculates the probability implied by [Laplace's rule of succession](https://en.wikipedia.org/wiki/Rule_of_succession) +trials = 10 +successes = 1 +Danger.laplace(successes, trials) // (successes + 1) / (trials + 2) = 2 / 12 = 0.1666 + +Danger.yTransform: (PointSetDist) => PointSetDist +Danger.yTransform(PointSet(Sym.normal(5,2))) + +## Combinatorics + +Danger.factorial: (Number) => Number +Danger.factorial(20) + +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). +Danger.choose(1, 20) + +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. +Danger.binomial(1, 20, 0.5) + +Danger.combinations: (List('A), Number) => List(List('A)) +Returns all combinations of the input list taken r elements at a time. +Danger.combinations([1, 2, 3], 2) // [[1, 2], [1, 3], [2, 3]] + +Danger.allCombinations: (List('A)) => List(List('A)) +Returns all possible combinations of the elements in the input list. +Danger.allCombinations([1, 2, 3]) // [[1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]] + +## Distributions + +Danger.binomialDist: (numberOfTrials: Dist|Number, probabilityOfSuccess: Dist|Number) => SampleSetDist +A binomial distribution. + +`n` must be above 0, and `p` must be between 0 and 1. + +Note: The binomial distribution is a discrete distribution. When representing this, the Squiggle distribution component might show it as partially or fully continuous. This is a visual mistake; if you inspect the underlying data, it should be discrete. +Danger.binomialDist(8, 0.5) + +Danger.poissonDist: (rate: Dist|Number) => SampleSetDist +A Poisson distribution. + +Note: The Poisson distribution is a discrete distribution. When representing this, the Squiggle distribution component might show it as partially or fully continuous. This is a visual mistake; if you inspect the underlying data, it should be discrete. +Danger.poissonDist(10) + +## Integration + +Danger.integrateFunctionBetweenWithNumIntegrationPoints: (f: Lambda, min: Number, max: Number, numIntegrationPoints: Number) => Number +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 distributions, use: + +```squiggle +auxiliaryF(x) = mean(f(x)) + +Danger.integrateFunctionBetweenWithNumIntegrationPoints(auxiliaryF, min, max, numIntegrationPoints) +``` + +Danger.integrateFunctionBetweenWithNumIntegrationPoints({|x| x+1}, 1, 10, 10) + +Danger.integrateFunctionBetweenWithEpsilon: (f: Lambda, min: Number, max: Number, epsilon: Number) => Number +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. +Danger.integrateFunctionBetweenWithEpsilon({|x| x+1}, 1, 10, 0.1) + +## Optimization + +Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions: (fs: List(Lambda), funds: Number, approximateIncrement: Number) => any +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)`. +Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions( +[ +{|x| x+1}, +{|y| 10} +], +100, +0.01 +) diff --git a/packages/website/public/llms/prompt.txt b/packages/website/public/llms/prompt.txt deleted file mode 100644 index 18ba46b041..0000000000 --- a/packages/website/public/llms/prompt.txt +++ /dev/null @@ -1,174 +0,0 @@ -Write Squiggle code, using the attached documentation for how it works. - -Key instructions: -Write the entire code, don't truncate it. So don't ever use "...", just write out the entire code. The code output you produce should be directly runnable in Squiggle, it shouldn't need any changes from users. - -About Squiggle. -Squiggle is a very simple language, that's much simpler than JS. Don't try using language primitives/constructs you don't see below, or that aren't in our documentation. They are likely to fail. - -When writing Squiggle code, it's important to avoid certain common mistakes: - -### Syntax and Structure -1. Variable Expansion: Not supported. Don't use syntax like |v...| or |...v|. -2. All pipes are "->", not "|>". -3. Dict keys and variable names must be lowercase. -4. The last value in a block/function is returned (no "return" keyword). -5. Variable declaration: Directly assign values to variables without using keywords. For example, use ``foo = 3`` instead of ``let foo = 3``. -6. All statements in your model, besides the last one must either be comments or variable declarations. You can't do, ```4 \n 5 \n 6``` Similarly, you can't do, ```Calculator() ... Table()``` - instead, you need to set everything but the last item to a variable. - -### Function Definitions and Use -1. Anonymous Functions: Use {|e| e} syntax for anonymous functions. -2. Function Parameters: When using functions like normal, specify the standard deviation with stdev instead of sd. For example, use normal({mean: 0.3, stdev: 0.1}) instead of normal({mean: 0.3, sd: 0.1}). -3. There's no recursion. -4. You can't call functions that accept ranges, with distributions. No, ``({|foo: [1,20]| foo}) (4 to 5)``. - -### Data Types and Input Handling -1. Input Types: Use Input.text for numeric inputs instead of Input.number or Input.slider. -2. The only function param types you can provide are numeric/date ranges, for numbers. f(n:[1,10]). Nothing else is valid. You cannot provide regular input type declarations. -3. Only use Inputs directly inside calculators. They won't return numbers, just input types. - -### Looping, Conditionals, and Data Operations -1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic. -2. There aren't for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile. -3. Remember to use ``Number.sum`` and ``Number.product``, instead of using Reduce in those cases. - -### List and Dictionary Operations -1. You can't do "(0..years)". Use List.make or List.upTo. -2. There's no "List.sort", but there is "List.sortBy", "Number.sort". - -### Randomness and Distribution Handling -1. There's no random() function. Use alternatives like sample(uniform(0,1)). -2. When representing percentages, use "5%" instead of "0.05" for readability. -3. The ``to`` syntax only works for >0 values. "4 to 10", not "0 to 10". - -### Units and Scales -1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01). -2. If you make a table that contains a column of similar distributions, use a scale to ensure consistent min and max. -3. Scale.symlog() has support for negative values, Scale.log() doesn't. Scale.symlog() is often a better choice for this reason, though Scale.log() is better when you are sure values are above 0. -4. Do use Scale.symlog() and Scale.log() on dists/plots that might need it. Many do! - -### Documentation and Comments -1. Tags like @name and @doc apply to the following variable, not the full file. -2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022. - ---- - -This format provides a clear and organized view of the guidelines for writing Squiggle code. - - -Here's are some simple example Squiggle programs: -```squiggle -//Model for Piano Tuners in New York Over Time - -@name("Population of New York in 2022") -@doc("I'm really not sure here, this is a quick guess.") -populationOfNewYork2022 = 8.1M to 8.4M - -@name("Percentage of Population with Pianos") -proportionOfPopulationWithPianos = 0.2% to 1% - -@name("Number of Piano Tuners per Piano") -pianoTunersPerPiano = { - pianosPerPianoTuner = 2k to 50k - 1 / pianosPerPianoTuner -} - -//We only mean to make an estimate for the next 10 years. -@hide -domain = [Date(2024), Date(2034)] - -@name("Time in years after 2024") -populationAtTime(t: domain) = { - dateDiff = Duration.toYears(t - Date(2024)) - averageYearlyPercentageChange = normal({ p5: -1%, p95: 5% }) // We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year - populationOfNewYork2022 * (averageYearlyPercentageChange + 1) ^ dateDiff -} -totalTunersAtTime(t: domain) = populationAtTime(t) * - proportionOfPopulationWithPianos * - pianoTunersPerPiano - -{ - populationAtTime, - totalTunersAtTimeMedian: {|t: domain| median(totalTunersAtTime(t))}, -} -``` -```squiggle -Calculator( - {|a, b,c,d| [a,b,c,d]}, - { - title: "Concat()", - description: "This function takes in 4 arguments, then displays them", - autorun: true, - sampleCount: 10000, - inputs: [ - Input.text({ - name: "First Param", - default: "10 to 13", - description: "Must be a number or distribution", - }), - Input.textArea({ name: "Second Param", default: "[4,5,2,3,4,5,3,3,2,2,2,3,3,4,45,5,5,2,1]" }), - Input.select({ name: "Third Param", default: "Option 1", options: ["Option 1", "Option 2", "Option 3"] }), - Input.checkbox({ name: "Fourth Param", default: false}) - ] - } -) -``` -```squiggle -Table.make( - [ - { name: "First Dist", value: Sym.lognormal({ p5: 1, p95: 10 }) }, - { name: "Second Dist", value: Sym.lognormal({ p5: 5, p95: 30 }) }, - { name: "Third Dist", value: Sym.lognormal({ p5: 50, p95: 90 }) }, - ], - { - columns: [ - { name: "Name", fn: {|d|d.name} }, - { - name: "Plot", - fn: { - |d| - Plot.dist( - { - dist: d.value, - xScale: Scale.log({ min: 0.5, max: 100 }), - showSummary: false, - } - ) - }, - }, - ], - } -) -``` -```squiggle -x = 10 -result = if x == 1 then { - {y: 2, z: 0} -} else { - {y: 0, z: 4} -} -y = result.y -z = result.z -``` -```squiggle -@showAs({|f| Plot.numericFn(f, { xScale: Scale.log({ min: 1, max: 100 }) })}) -fn(t) = t ^ 2 -``` -```squiggle -{|t| normal(t, 2) * normal(5, 3)} - -> Plot.distFn( - { - title: "A Function of Value over Time", - xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }), - yScale: Scale.linear({ title: "Value" }), - distXScale: Scale.linear({ tickFormat: "#x" }), - } - ) -``` - -``` -f(t: [Date(2020), Date(2040)]) = { - yearsPassed = toYears(t - Date(2020)) - normal({mean: yearsPassed ^ 2, stdev: yearsPassed^1.3+1}) -} -``` \ No newline at end of file diff --git a/packages/website/public/llms/styleGuide.markdown b/packages/website/public/llms/styleGuide.markdown new file mode 100644 index 0000000000..6cb9e5a3e6 --- /dev/null +++ b/packages/website/public/llms/styleGuide.markdown @@ -0,0 +1,419 @@ +# Squiggle Style Guide + +## Data and Calculations + +### Estimations + +- When using the "to" format, like "3 to 10", remember that this represents the 5th and 95th percentile. This is a very large range. Be paranoid about being overconfident and too narrow in your estimates. +- One good technique, when you think there's a chance that you might be very wrong about a variable, is to use a mixture that contains a very wide distribution. For example, `mx([300 to 400, 50 to 5000], [0.9, 0.1])`, or `mx([50k to 60k, 1k to 1M], [0.95, 0.05])`. This way if you are caught by surprise, the wide distribution will still give you a reasonable outcome. +- Be wary of using the uniform or the triangular distributions. These are mainly good for physical simulations, not to represent uncertainty over many real life variables. +- If the outcome of a model is an extreme probability (<0.01 or >0.99), be suspicious of the result. It should be very rare for an intervention to have an extreme effect or have an extreme impact on the probability of an event. +- Be paranoid about the uncertainty ranges of your variables. If you are dealing with a highly speculative variable, the answer might have 2-8 orders of magnitude of uncertainty, like `100 to 100K`. If you are dealing with a variable that's fairly certain, the answer might have 2-4 sig figs of uncertainty. Be focused on being accurate and not overconfident, not on impressing people. +- Be careful with sigmoid functions. Sigmoid curves with distributions can have very little uncertainty in the middle, and very high uncertainty at the tails. If you are unsure about these values, consider using a mixture distribution. For example, this curve has very high certainty in the middle, and very high uncertainty at the tails: `adoption_rate(t) = 1 / (1 + exp(-normal(0.1, 0.08) * (t - 30)))` +- Make sure to flag any variables that are highly speculative. Use @doc() to explain that the variable is speculative and to give a sense of the uncertainty. Explain your reasoning, but also warn the reader that the variable is speculative. + +### Percentages / Probabilities + +- Use a `@format()` tag, like `.0%` to format percentages. +- If using a distribution, remember that it shouldn't go outside of 0% and 100%. You can use beta distributions or truncate() to keep values in the correct range. +- If you do use a beta distribution, keep in mind that there's no `({p5, p95})` format. You can use `beta(alpha:number, beta:number)` or `beta({mean: number, stdev: number})` to create a beta distribution. +- Write percentages as `5%` instead of `0.05`. It's more readable. + +### Domains + +- Prefer using domains to throwing errors, when trying to restrict a variable. For example, don't write, `if year < 2023 then throw("Year must be 2023 or later")`. Instead, write `f(t: [2023, 2050])`. +- Err on the side of using domains in cases where you are unsure about the bounds of a function, instead of using if/throw or other error handling methods. +- If you only want to set a min or max value, use a domain with `Number.maxValue` or `-Number.maxValue` as the other bound. +- Do not use a domain with a complete range, like `[-Number.maxValue, Number.maxValue]`. This is redundant. Instead, just leave out the domain, like `f(t)`. + +```squiggle +// Do not use this +f(t: [-Number.maxValue, Number.maxValue]) + 1 + +// Do this +f(t) = t + 1 +``` + +## Structure and Naming Conventions + +### Structure + +- Don't have more than 10 variables in scope at any one time. Feel free to use many dictionaries and blocks in order to keep things organized. For example, + +```squiggle +@name("Key Inputs") +inputs = { + @name("Age (years)") + age = 34 + + @name("Hourly Wage ($/hr)") + hourlyWage = 100 + + @name("Coffee Price ($/cup)") + coffeePrice = 1 + {age, hourlyWage, coffeePrice} +} +``` + +Note: You cannot use tags within dicts like the following: + +```squiggle +// This is not valid. Do not do this. +inputs = { + @name("Age (years)") + age: 34, + + @name("Hourly Wage ($/hr)") + hourlyWage: 100, +} +``` + +- At the end of the file, don't return anything. The last line of the file should be the @notebook tag. +- You cannot start a line with a mathematical operator. For example, you cannot start a line with a + or - sign. However, you can start a line with a pipe character, `->`. +- Prettier will be run on the file. This will change the spacing and formatting. Therefore, be conservative with formatting (long lines, no risks), and allow this to do the heavy lifting later. +- If the file is over 50 lines, break it up with large styled blocks comments with headers. For example: + +```squiggle +// ===== Inputs ===== + +// ... + +// ===== Calculations ===== +``` + +### Naming Conventions + +- Use camelCase for variable names. +- All variable names must start with a lowercase letter. +- In functions, input parameters that aren't obvious should have semantic names. For example, instead of `nb` use `net_benefit`. + +### Dictionaries + +- In dictionaries, if a key name is the same as a variable name, use the variable name directly. For example, instead of {value: value}, just use {value}. If there's only one key, you can type it with a comma, like this: {value,}. + +### Unit Annotation + +- You can add unit descriptions to `@name()`, `@doc()` tags, and add them to comments. +- In addition to regular units (like "population"), add other key variables; like the date or the type of variable. For example, use "Number of Humans (Population, 2023)" instead of just "Number of Humans". It's important to be precise and detailed when annotating variables. +- Squiggle does support units directly, using the syntax `foo :: unit`. However, this is not recommended to use, because this is still a beta feature. +- Show units in parentheses after the variable name, when the variable name is not obvious. For example, use "Age (years)" instead of just "Age". In comments, use the "(units)" format. + Examples: + +```squiggle +@name("Number of Humans (2023)") +numberOfHumans = 7.8B + +@name("Net Benefit ($)") +netBenefit = 100M + +@name("Temperature (°C)") +temperature = 22 + +@name("Piano Tuners in New York City (2023)") +tuners = { + pianosPerTuner = 100 to 1k // (pianos per tuner) + pianosInNYC = 1k to 50k // (pianos) + pianosInNYC / pianosPerTuner +} +``` + +- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. + +```squiggle +@name("Distance to Mars (km)") +distanceMars = 225e6 + +@name("Distance to Venus (km)") +distanceVenus = 170e6 +``` + +### Numbers + +- Use abbreviations, when simple, for numbers outside the range of 10^4 to 10^3. For example, use "10k" instead of "10000". +- For numbers outside the range of 10^10 or so, use scientific notation. For example, "1e10". +- Don't use small numbers to represent large numbers. For example, don't use '5' to represent 5 million. + +Don't use the code: + +```squiggle +@name("US Population (millions)") +usPopulation = 331.9 +``` + +Instead, use: + +```squiggle +@name("US Population") +usPopulation = 331.9M +``` + +More examples: + +```squiggle +// Correct representations +worldPopulation = 7.8B +annualBudget = 1.2T +distanceToSun = 149.6e6 // 149.6 million kilometers + +// Incorrect representations (avoid these) +worldPopulation = 7800 // Unclear if it's 7800 or 7.8 billion +annualBudget = 1200 // Unclear if it's 1200 or 1.2 trillion +``` + +- There's no need to use @format on regular numbers. The default formatting is fairly sophistated. +- Remember to use `Number.sum` and `Number.product`, instead of using Reduce in those cases. + +### Lists of Structured Data + +- When you want to store complex data as code, use lists of dictionaries, instead of using lists of lists. This makes things clearer. For example, use: + +```squiggle +[ + {year: 2023, value: 1}, + {year: 2024, value: 2}, +] +instead of: +[ + [2023, 1], + [2024, 2], +] +``` + +You can use lists instead when you have a very long list of items (20+), very few keys, and/or are generating data using functions. + +- Tables are a great way to display structured data. +- You can use the '@showAs' tag to display a table if the table can show all the data. If this takes a lot of formatting work, you can move that to a helper function. Note that helper functions must be placed before the '@showAs' tag. The `ozziegooen/helpers` library has a `dictsToTable` function that can help convert lists of dictionaries into tables. + +For example: + +```squiggle +@hide +strategiesTable(data) = Table.make( + data, + { + columns: [ + { name: "name", fn: {|f| f.n} }, + { name: "costs", fn: {|f| f.c} }, + { name: "benefits", fn: {|f| f.b} }, + ], + } +) + +@name("AI Safety Strategies") +@doc("List of 10 AI safety strategies with their costs and benefits") +@showAs(strategiesTable) +strategies = [ + { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M }, + { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M }, + { n: "Governance", c: 500k to 3M, b: 2M to 15M }, + ... +] +``` + +## Tags and Annotations + +### @name, @doc, @hide, @showAs + +- Use `@name` for simple descriptions and shortened units. Use `@doc` for further details (especially for detailing types, units, and key assumptions), when necessary. It's fine to use both @name and @doc on the same variable - but if so, don't repeat the name in the doc; instead use the doc() for additional information only. +- In `@name`, add units wherever it might be confusing, like "@name("Ball Speed (m/s)"). If the units are complex or still not obvious, add more detail in the @doc(). +- For complex and important functions, use `@name` to name the function, and `@doc` to describe the arguments and return values. @doc should represent a docstring for the function. For example: + +``` +@doc("Adds a number and a distribution. +\`\`\`squiggle +add(number, distribution) -> distribution +\`\`\`") +``` + +- Variables that are small function helpers, and that won't be interesting or useful to view the output of, should get a `@hide` tag. Key inputs and outputs should not have this tag. +- Use `@showAs` to format large lists, as tables and to show plots for dists and functions where appropriate. + +### `@format()` + +- Use `@format()` for numbers, distributions, and dates that could use obvious formatting. +- The `@format()` tag is not usable with dictionaries, functions, or lists. It is usable with variable assignments. Examples: + +```squiggle +netBenefit(costs, benefits) = benefits - costs // not valid for @format() +netBenefit = benefits - costs // valid for @format() +``` + +- This mainly makes sense for dollar amounts, percentages, and dates. ".0%" is a decent format for percentages, and "$,.0f" can be used for dollars. +- Choose the number of decimal places based on the stdev of the distribution or size of the number. +- Do not use "()" instead of "-" for negative numbers. So, do not use "($,.0f" for negative numbers, use "$,.0f" instead. + +## Limitations + +- There is no bignum type. There are floating point errors at high numbers (1e50 and above) and very small numbers (1e-10 and below). If you need to work with these, use logarithms if possible. + +## Comments + +- Add a short 1-2 line comment on the top of the file, summarizing the model. +- Add comments throughout the code that explain your reasoning and describe your uncertainties. Give special attention to probabilities and probability distributions that are particularly important and/or uncertain. Flag your uncertainties. +- Use comments next to variables to explain what units the variable is in, if this is not incredibly obvious. The units should be wrapped in parentheses. +- There shouldn't be any comments about specific changes made during editing. +- Do not use comments to explain things that are already obvious from the code. + +## Visualizations + +### Tables + +- Tables are a good way of displaying structured data. They can take a bit of formatting work. +- Tables are best when there are fewer than 30 rows and/or fewer than 4 columns. +- The table visualization is fairly simple. It doesn't support sorting, filtering, or other complex interactions. You might want to sort or filter the data before putting it in a table. + +### Notebooks + +- Use the @notebook tag for long descriptions intersperced with variables. This must be a list with strings and variables alternating. +- If you want to display variables within paragraphs, generally render dictionaries as items within the notebook list. For example: + +```squiggle +@notebook +@startOpen +summary = [ +"This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", +{ + optimalCups, + result.netBenefit, +}, +] +``` + +This format will use the variable tags to display the variables, and it's simple to use without making errors. If you want to display a variable that's already a dictionary, you don't need to do anything special. + +- String concatenation (+) is allowed, but be hesitant to do this with non-string variables. Most non-string variables don't display well in the default string representation. If you want to display a variable, consider using a custom function or formatter to convert it to a string first. Note that tags are shown in the default string representation, so you should remove them (`Tag.clear(variable)`) before displaying. +- Separate items in the list will be displayed with blank lines between them. This will break many kinds of formatting, like lists. Only do this in order to display full variables that you want to show. +- Use markdown formatting for headers, lists, and other structural elements. +- Use bold text to highlight key outputs. Like, "The optimal number of coffee cups per day is **" + Tag.clear(optimal_cups) + "**". + +Example: (For a model with 300 lines) + +```squiggle +@notebook +@startOpen +summary = [ + "## Summary + This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", + {inputs, final_answer}, + "## Major Assumptions & Uncertainties + - The model places a very high value on productivity. If you think that productivity is undervalued, coffee consumption may be underrated. + - The model only includes 3 main factors: productivity, cost, and health. It does not take into account other factors, like addiction, which is a major factor in coffee consumption. + - The model does not take into account the quality of sleep, which is critical. + " + "## Outputs + The optimal number of coffee cups per day: **" + Tag.clear(optimal_cups) + "** + The net benefit at optimal consumption: **" + result.net_benefit + "**", + "## Key Findings + - Moderate amounts of coffee consumption seem surprisingly beneficial. + - Productivity boost from coffee shows steeply diminishing returns as consumption increases, as would be expected. + - The financial cost of coffee is the critical factor in determining optimal consumption. + ## Detailed Analysis + The model incorporates several key factors: + 1. Productivity boost: Modeled with diminishing returns as coffee consumption increases. + 2. Health impact: Considers both potential benefits and risks of coffee consumption. + 3. Financial cost: Accounts for the direct cost of purchasing coffee. + 4. Monetary values: Includes estimates for the value of time (hourly wage) and health (QALY value). + + The optimal consumption level is determined by maximizing the net benefit, which is the sum of monetized productivity and health benefits minus the financial cost. + + It's important to note that this model is based on general estimates and may not apply to all individuals. Factors such as personal health conditions, caffeine sensitivity, and lifestyle choices could significantly alter the optimal consumption for a specific person. + " +] +``` + +## Plots + +- Plots are a good way of displaying the output of a model. +- Use Scale.symlog() and Scale.log() whenever you think the data is highly skewed. This is very common with distributions. +- Use Scale.symlog() instead of Scale.log() when you are unsure if the data is above or below 0. Scale.log() fails on negative values. +- Function plots use plots equally spaced on the x-axis. This means they can fail if only integers are accepted. In these cases, it can be safer just not to use the plot, or to use a scatter plot. +- When plotting 2-8 distributions over the same x-axis, it's a good idea to use Plot.dists(). For example, if you want to compare 5 different costs of a treatment, or 3 different adoption rates of a technology, this can be a good way to display the data. +- When plotting distributions in tables or if you want to display multiple distributions under each other, and you don't want to use Plot.dists, it's a good idea to have them all use the same x-axis scale, with custom min and max values. This is a good way to make sure that the x-axis scale is consistent across all distributions. + +Here's an example of how to display multiple distributions over the same x-axis, with a custom x-axis range: + +```squiggle +strategies = [ + { n: "AI Ethics", c: 1M to 5M, b: 5M to 20M }, + { n: "Alignment Research", c: 2M to 10M, b: 10M to 50M }, + ... +] + +rangeOfDists(dists) = { + min: Number.min(List.map(dists, {|d| Dist.quantile(d, 0.05)})), + max: Number.max(List.map(dists, {|d| Dist.quantile(d, 0.95)})), +} + +plotOfResults(fn) = { + |r| + range = List.map(strategies, fn) -> rangeOfDists + Plot.dist(fn(r), { xScale: Scale.linear(range) }) +} + +table = Table.make( + strategies, + { + columns: [ + { name: "Strategy", fn: {|r| r.name} }, + { name: "Cost", fn: plotOfResults({|r| r.c}) }, + { name: "Benefit", fn: plotOfResults({|r| r.b}) }, + ], + } +) +``` + +## Tests + +- Use `sTest` to test squiggle code. +- Test all functions that you are unsure about. Be paranoid. +- Use one describe block, with the variable name 'tests'. This should have several tests with in it, each with one expect statement. +- Use @startClosed tags on variables that are test results. Do not use @hide tags. +- Do not test if function domains return errors when called with invalid inputs. The domains should be trusted. +- If you set variables to sTest values, @hide them. They are not useful in the final output. +- Do not test obvious things, like the number of items in a list that's hardcoded. +- Feel free to use helper functions to avoid repeating code. +- The expect.toThrowAnyError() test is useful for easily sanity-checking that a function is working with different inputs. + +Example: + +```squiggle +@hide +describe = sTest.describe + +@hide +test = sTest.test + +tests = describe( + "Coffee Consumption Model Tests", + [ + // ...tests + ] +) +``` + +## Summary Notebook + +- For models over 5 lines long, you might want to include a summary notebook at the end of the file using the @notebook tag. +- Aim for a summary length of approximately (N^0.6) \* 1.2 lines, where N is the number of lines in the model. +- Use the following structure: + 1. Model description + 2. Major assumptions & uncertainties (if over 100 lines long) + 3. Outputs (including relevant Squiggle variables) + 4. Key findings (flag if anything surprised you, or if the results are counterintuitive) + 5. Detailed analysis (if over 300 lines long) + 6. Important notes or caveats (if over 100 lines long) +- The summary notebook should be the last thing in the file. It should be a variable called `summary`. +- Draw attention to anything that surprised you, or that you think is important. Also, flag major assumptions and uncertainties. + +Example: (For a model with 300 lines) + +```squiggle +@notebook +@startOpen +summary = [ + "## Summary + This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", + {inputs, finalAnswer}, + ... + ] +``` diff --git a/packages/website/scripts/compileDocsForLLM.mts b/packages/website/scripts/compileDocsForLLM.mts index cceb4e78df..436f0360be 100755 --- a/packages/website/scripts/compileDocsForLLM.mts +++ b/packages/website/scripts/compileDocsForLLM.mts @@ -96,15 +96,12 @@ const allDocumentationItems = () => { .join("\n\n\n"); }; -const promptPageRaw = readFile("./public/llms/prompt.txt"); +const basicPrompt = readFile("./public/llms/basicPrompt.markdown"); +const styleGuideRaw = readFile("./public/llms/styleGuide.markdown"); const documentationBundlePage = async () => { - const targetFilename = "./public/llms/documentationBundle.txt"; - - const header = `# 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. ---- \n\n -`; + const targetFilename = "./public/llms/documentationBundle.markdown"; + // We're not using this anymore, but leaving it here in case we want it again. const getGrammarContent = async () => { const grammarFiles = await glob("../squiggle-lang/src/**/*.peggy"); return readFile(grammarFiles[0]); @@ -113,24 +110,31 @@ This file is auto-generated from the documentation files in the Squiggle reposit const getGuideContent = async () => { const documentationFiles = await glob("./src/pages/docs/Guides/*.{md,mdx}"); return Promise.all( - documentationFiles.map(async (filePath) => { - const content = readFile(filePath); - const withoutHeaders = removeHeaderLines(content); - const convertedContent = convertSquiggleEditorTags(withoutHeaders); - return convertedContent; - }) + documentationFiles + .filter( + (filePath) => + !filePath.endsWith("Roadmap.md") && + !filePath.endsWith("Debugging.mdx") + ) + .map(async (filePath) => { + const content = readFile(filePath); + const withoutHeaders = removeHeaderLines(content); + const convertedContent = convertSquiggleEditorTags(withoutHeaders); + return convertedContent; + }) ).then((contents) => contents.join("\n\n\n")); }; console.log("Compiling documentation bundle page..."); - const grammarContent = await getGrammarContent(); + // const grammarContent = await getGrammarContent(); const guideContent = await getGuideContent(); const apiContent = allDocumentationItems(); - // const content = guideContent; const content = - header + - promptPageRaw + - `## Peggy Grammar \n\n ${grammarContent} \n\n --- \n\n ` + + basicPrompt + + "\n\n" + + styleGuideRaw + + "\n\n" + + // `## Peggy Grammar \n\n ${grammarContent} \n\n --- \n\n ` + convertSquiggleEditorTags(guideContent) + apiContent; fs.writeFile(targetFilename, content, (err) => { @@ -143,25 +147,25 @@ This file is auto-generated from the documentation files in the Squiggle reposit }; const promptPage = async () => { - console.log("Compiling prompt page..."); + console.log("Compiling basic prompt page..."); const introduction = `--- description: LLM Prompt Example notes: "This Doc is generated using a script, do not edit directly!" --- -# LLM Prompt Example +# LLM Basic Prompt Example -The following is a prompt that we use to help LLMs, like GPT and Claude, write Squiggle code. This would ideally be provided with the full documentation, for example with [this document](/llms/documentationBundle.txt). +The following is a prompt that we use to help LLMs, like GPT and Claude, write Squiggle code. This would ideally be provided with the full documentation, for example with [this document](/llms/documentationBundle.markdown). -You can read this document in plaintext [here](/llms/prompt.txt). +You can read this document in plaintext [here](/llms/BasicPrompt.markdown). --- `; - const target = "./src/pages/docs/Ecosystem/LLMPrompt.md"; + const target = "./src/pages/docs/Ecosystem/BasicPrompt.md"; fs.writeFile( target, - introduction + promptPageRaw.replace(/\`squiggle/g, "`js"), + introduction + basicPrompt.replace(/\`squiggle/g, "`js"), (err) => { if (err) { console.error(err); diff --git a/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md b/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md new file mode 100644 index 0000000000..58139e584b --- /dev/null +++ b/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md @@ -0,0 +1,343 @@ +--- +description: LLM Prompt Example +notes: "This Doc is generated using a script, do not edit directly!" +--- + +# LLM Basic Prompt Example + +The following is a prompt that we use to help LLMs, like GPT and Claude, write Squiggle code. This would ideally be provided with the full documentation, for example with [this document](/llms/documentationBundle.markdown). + +You can read this document in plaintext [here](/llms/BasicPrompt.markdown). + +--- + +Write Squiggle code, using the attached documentation for how it works. + +Squiggle is a very simple language. Don't try using language primitives/constructs you don't see below, or that aren't in our documentation. They are likely to fail. + +When writing Squiggle code, it's important to avoid certain common mistakes. + +### Syntax and Structure + +1. Variable Expansion: Not supported. Don't use syntax like |v...| or |...v|. +2. All pipes are "->", not "|>". +3. Dict keys and variable names must be lowercase. +4. The last value in a block/function is returned (no "return" keyword). +5. Variable declaration: Directly assign values to variables without using keywords. For example, use `foo = 3` instead of `let foo = 3`. +6. All statements in your model, besides the last one must either be comments or variable declarations. You can't do, `4 \n 5 \n 6` Similarly, you can't do, `Calculator() ... Table()` - instead, you need to set everything but the last item to a variable. +7. There's no mod operator (%). Use `Number.mod()` instead. + +### Function Definitions and Use + +1. Anonymous Functions: Use {|e| e} syntax for anonymous functions. +2. Function Parameters: When using functions like normal, specify the standard deviation with stdev instead of sd. For example, use normal({mean: 0.3, stdev: 0.1}) instead of normal({mean: 0.3, sd: 0.1}). +3. There's no recursion. +4. You can't call functions that accept ranges, with distributions. No, `({|foo: [1,20]| foo}) (4 to 5)`. + +### Data Types and Input Handling + +1. Input Types: Use Input.text for numeric inputs instead of Input.number or Input.slider. +2. The only function param types you can provide are numeric/date ranges, for numbers. f(n:[1,10]). Nothing else is valid. You cannot provide regular input type declarations. +3. Only use Inputs directly inside calculators. They won't return numbers, just input types. + +### Looping, Conditionals, and Data Operations + +1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic. +2. There aren't for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile. + +### List and Dictionary Operations + +1. You can't do "(0..years)". Use List.make or List.upTo. +2. There's no "List.sort", but there is "List.sortBy", "Number.sort". + +### Randomness and Distribution Handling + +1. There's no random() function. Use alternatives like sample(uniform(0,1)). +2. The `to` syntax only works for >0 values. "4 to 10", not "0 to 10". + +### Units and Scales + +1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01). + +### Documentation and Comments + +1. Tags like @name and @doc apply to the following variable, not the full file. +2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022. + +## Examples + +Here's are some simple example Squiggle programs: + +```js +//Model for Piano Tuners in New York Over Time + +@name("🌆 Population of New York in 2022") +@doc("I'm really not sure here, this is a quick guess.") +populationOfNewYork2022 = 8.1M to 8.4M + +@name("🎹 Percentage of Population with Pianos") +@format(".1%") +proportionOfPopulationWithPianos = 0.2% to 1% + +@name("🔧 Number of Piano Tuners per Piano") +pianoTunersPerPiano = { + pianosPerPianoTuner = 2k to 50k + 1 / pianosPerPianoTuner +} + +//We only mean to make an estimate for the next 10 years. +@hide +domain = [Date(2024), Date(2034)] + +@name("Population at Time") +populationAtTime(t: domain) = { + dateDiff = Duration.toYears(t - Date(2024)) + averageYearlyPercentageChange = normal({ p5: -1%, p95: 5% }) // We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year + populationOfNewYork2022 * (averageYearlyPercentageChange + 1) ^ dateDiff +} + +@name("Total Tuners, at Time") +totalTunersAtTime(t: domain) = populationAtTime(t) * + proportionOfPopulationWithPianos * + pianoTunersPerPiano + +meanTunersAtTime(t: domain) = mean(totalTunersAtTime(t)) +``` + +```js +calculator = Calculator( + {|a, b, c, d| [a, b, c, d]}, + { + title: "Concat()", + description: "This function takes in 4 arguments, then displays them", + sampleCount: 10000, + inputs: [ + Input.text( + { + name: "First Param", + default: "10 to 13", + description: "Must be a number or distribution", + } + ), + Input.textArea( + { + name: "Second Param", + default: "[4,5,2,3,4,5,3,3,2,2,2,3,3,4,45,5,5,2,1]", + } + ), + Input.select( + { + name: "Third Param", + default: "Option 1", + options: ["Option 1", "Option 2", "Option 3"], + } + ), + Input.checkbox({ name: "Fourth Param", default: false }), + ], + } +) + +``` + +```js +// Cost-benefit analysis for a housing addition in berkeley + +// Input section +@name("Model Inputs") +@doc("Key parameters for the housing development project") +inputs = { + landCost: 1M to 2M, + constructionCost: 500k to 800k, + permitFees: 50k to 100k, + numberOfHomes: 10, + monthlyRentalIncome: 3k to 5k, + annualPropertyAppreciation: 2% to 5%, + annualSocialBenefit: 10k to 30k, + yearsToConsider: 30, +} + +// Calculation section +@name("Calculations") +@doc("Core calculations for the cost-benefit analysis") +calculations(i) = { + totalCostPerHome = i.landCost + i.constructionCost + i.permitFees + annualRentalIncome = i.numberOfHomes * i.monthlyRentalIncome * 12 + totalCost = i.numberOfHomes * totalCostPerHome + + annualAppreciation(year) = i.numberOfHomes * totalCostPerHome * + ((1 + i.annualPropertyAppreciation) ^ year - + (1 + i.annualPropertyAppreciation) ^ (year - 1)) + + annualBenefit(year) = annualRentalIncome + annualAppreciation(year) + + i.numberOfHomes * i.annualSocialBenefit + + totalBenefit = List.upTo(1, i.yearsToConsider) -> List.map(annualBenefit) + -> List.reduce( + 0, + {|acc, val| acc + val} + ) + + netBenefit = totalBenefit - totalCost + probPositiveNetBenefit = 1 - cdf(netBenefit, 0) + + { + totalCostPerHome: totalCostPerHome, + annualRentalIncome: annualRentalIncome, + totalCost: totalCost, + totalBenefit: totalBenefit, + netBenefit: netBenefit, + probPositiveNetBenefit: probPositiveNetBenefit, + } +} + +// Apply calculations to inputs +@name("Results") +@doc("Output of calculations based on input parameters") +results = calculations(inputs) + +// Analysis section +@name("Cost-Benefit Analysis") +@doc("Detailed analysis of the housing development project") +analysis = { + costsTable = Table.make( + [ + { name: "Land Cost per Home", value: inputs.landCost }, + { name: "Construction Cost per Home", value: inputs.constructionCost }, + { name: "Permit Fees per Home", value: inputs.permitFees }, + { name: "Total Cost per Home", value: results.totalCostPerHome }, + { name: "Total Cost for 10 Homes", value: results.totalCost }, + ], + { + columns: [ + { name: "Item", fn: {|r| r.name} }, + { + name: "Cost", + fn: { + |r| + Plot.dist( + r.value, + { + xScale: Scale.log({ tickFormat: "($.1s", min: 20k, max: 200M }), + } + ) + }, + }, + ], + } + ) + + benefitTable = Table.make( + [ + { + name: "Monthly Rental Income per Home", + value: inputs.monthlyRentalIncome, + }, + { + name: "Annual Social Benefit per Home", + value: inputs.annualSocialBenefit, + }, + { name: "Total Benefit over 30 years", value: results.totalBenefit }, + ], + { + columns: [ + { name: "Item", fn: {|r| r.name} }, + { + name: "Value", + fn: { + |r| + Plot.dist( + r.value, + { xScale: Scale.linear({ tickFormat: "($.1s" }) } + ) + }, + }, + ], + } + ) + + netBenefitPlot = Plot.dist( + results.netBenefit, + { + title: "Distribution of Net Benefit", + xScale: Scale.log({ tickFormat: "($.1s", min: 10M, max: 200M }), + } + ) + + { + title: "Cost-Benefit Analysis: Adding 10 Homes to Berkeley, CA", + costs: costsTable, + benefits: benefitTable, + netBenefit: netBenefitPlot, + probabilityOfPositiveNetBenefit: results.probPositiveNetBenefit, + } +} +``` + +```js +x = 10 +result = if x == 1 then { + {y: 2, z: 0} +} else { + {y: 0, z: 4} +} +y = result.y +z = result.z +``` + +```js +@showAs({|f| Plot.numericFn(f, { xScale: Scale.log({ min: 1, max: 100 }) })}) +fn(t) = t ^ 2 +``` + +```js +plot = {|t| normal(t, 2) * normal(5, 3)} + -> Plot.distFn( + { + title: "A Function of Value over Time", + xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }), + yScale: Scale.linear({ title: "Value" }), + distXScale: Scale.linear({ tickFormat: "#x" }), + } + ) +``` + +```js +f(t: [Date(2020), Date(2040)]) = { + yearsPassed = toYears(t - Date(2020)) + normal({mean: yearsPassed ^ 2, stdev: yearsPassed^1.3+1}) +} +``` + +```js +import "hub:ozziegooen/sTest" as sTest +@name("💰 Expected Cost ($)") +@format("$.2s") +flightCost = normal({ mean: 600, stdev: 100 }) + +@name("🥇 Expected Benefit ($)") +@format("$.2s") +benefitEstimate = normal({ mean: 1500, stdev: 300 }) + +@name("📊 Net Benefit ($)") +@format("$.2s") +netBenefit = benefitEstimate - flightCost + +@name("🚦 Test Suite") +@doc( + "Test suite to validate various aspects of the flight cost and benefits model using sTest." +) +tests = sTest.describe( + "Flight to London Test Suite", + [ + // Test for reasonable flight costs + sTest.test( + "Flight cost should be reasonable", + { + || + meanValue = mean(flightCost) + sTest.expect(meanValue).toBeBetween(300, 10k) + } + ), + ] +) +``` diff --git a/packages/website/src/pages/docs/Ecosystem/LanguageModels.md b/packages/website/src/pages/docs/Ecosystem/LanguageModels.md index f4520c889c..3fc80f4e7f 100644 --- a/packages/website/src/pages/docs/Ecosystem/LanguageModels.md +++ b/packages/website/src/pages/docs/Ecosystem/LanguageModels.md @@ -17,7 +17,9 @@ This bot often produces poor results, or fails to properly call the server, so i ## Document with all documentation -[All Squiggle Documentation, in One Document](/llms/documentationBundle.txt) +[All Squiggle Documentation, in One Document](/llms/documentationBundle.markdown) + +If you want a shorter prompt, you can use [this shorter prompt](/llms/BasicPrompt.markdown). ## Instructions for use with Claude diff --git a/packages/website/src/pages/docs/Ecosystem/_meta.json b/packages/website/src/pages/docs/Ecosystem/_meta.json index 1b7a3d8a43..b04b1c33f1 100644 --- a/packages/website/src/pages/docs/Ecosystem/_meta.json +++ b/packages/website/src/pages/docs/Ecosystem/_meta.json @@ -3,5 +3,5 @@ "Tooling": "Tooling", "Gallery": "Gallery", "LanguageModels": "Language Models", - "LLMPrompt": "LLM Prompt" + "BasicPrompt": "Basic LLM Prompt" } diff --git a/packages/website/turbo.json b/packages/website/turbo.json index 3a9343c730..a15f55979e 100644 --- a/packages/website/turbo.json +++ b/packages/website/turbo.json @@ -7,10 +7,11 @@ "*.tsbuildinfo", ".next", "!.next/cache", - "public/llms/documentationBundle.txt", - "src/pages/docs/Ecosystem/LLMPrompt.md", - "src/pages/docs/Api/*", - "src/pages/docs/Ecosystem/LLMPrompt.md" + "public/llms/documentationBundle.markdown", + "public/llms/styleGuide.markdown", + "public/llms/BasicPrompt.markdown", + "src/pages/docs/Ecosystem/BasicPrompt.md", + "src/pages/docs/Api/*" ] }, "lint": {