From 267b7ba70bfa3f29f37217b4d352cf9e30bc95ae Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Mon, 7 Oct 2024 13:59:25 -0700 Subject: [PATCH 01/13] First attempt at a style guide --- packages/ai/src/steps/styleGuide.md | 237 ++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 packages/ai/src/steps/styleGuide.md diff --git a/packages/ai/src/steps/styleGuide.md b/packages/ai/src/steps/styleGuide.md new file mode 100644 index 0000000000..472a38020f --- /dev/null +++ b/packages/ai/src/steps/styleGuide.md @@ -0,0 +1,237 @@ +# Squiggle Style Guide + +## Limitations + +- Try not to use numbers higher than 10^50 or so. There are floating point errors at high numbers. If you need to work with larger numbers, try doing math with logs. + +## 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") + age = 34 + + @name("Hourly Wage") + hourly_wage = 100 + + @name("Health Value") + health_value = 100 + + @name("Coffee Price") + coffee_price = 1 + {age, hourly_wage, health_value, coffee_price} +} +``` + +- At the end of the file, don't return anything. The last line of the file should be the @notebook tag. +- If the file is over 50 lines, break it up with large styled blocks comments with headers. For example: // ===== Model Calculations ===== +- When you want to store complex data as code, use lists of dictionaries, instead of using lists of lists. This makes things much more clear. 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. + +## Unit Annotation + +- Squiggle does not support units directly, but you can add them 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. +- 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)") +number_of_humans = 7.8B + +@name("Net Benefit ($)") +net_benefit = 100M + +@name("Temperature (Β°C)") +temperature = 22 + +@name("Piano Tuners in New York City (2023)") +tuners = { + pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) + pianos_in_nyc = 1k to 50k // (pianos) + pianos_in_nyc / pianos_per_piano_tuners +} +``` + +## Tags + +- 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. + +## The `@format()` tag + +- Use `@format()` for numbers, distributions, and dates that could use obvious formatting. +- The `@format()` tag is not usable with dictionaries, functions, or lists. +- 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. + +## 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. + +## Errors & Domains + +- Prefer using domains to throwing errors. 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, where you are unsure about the bounds of a function. +- If you only want to set a min or max value, use a domain with Number.minValue or Number.maxValue as the other bound. + +## Tests + +- Use `sTest` to test squiggle code. +- Test all functions taht you are unsure about. +- 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 tests. Do not use @hide tags. +- Do not test that function domains return errors when called with invalid inputs. The domains should be trusted. + +## Naming Conventions + +- Use snake_case 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". + +## 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]). 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 PERT distributions. The uniform distribution is mainly good for physical simulations. +- 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. + +## 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. + +## 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)") +us_population = 331.9 +``` + +Instead, use: + +```squiggle +@name("US Population") +us_population = 331.9M +``` + +More examples: + +```squiggle +// Correct representations +world_population = 7.8B +annual_budget = 1.2T +distance_to_sun = 149.6e6 // 149.6 million kilometers + +// Incorrect representations (avoid these) +world_population = 7800 // Unclear if it's 7800 or 7.8 billion +annual_budget = 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. + +## Styling + +- In dicts, 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,}. + +## 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.", +{ + optimal_cups, + result.net_benefit, +}, +] +``` + +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}, + "## Outputs + The optimal number of coffee cups per day: **" + Tag.clear(optimal_cups) + "** + The net benefit at optimal consumption: **" + result.net_benefit + "**", + "## Key Findings + - The model suggests an optimal coffee consumption, balancing productivity gains, health impacts, and financial costs. + - Productivity boost from coffee shows diminishing returns as consumption increases. + - The financial cost of coffee is a significant 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." +] +``` + +## Summary + +- For models over 5 lines long, 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. Outputs (including relevant Squiggle variables) + 3. Key findings + 4. Detailed analysis (if over 300 lines long) + 5. Important notes or caveats (if over 100 lines long) From 1f671e922ef3e54ac743bc8ff6b5c791c71b2317 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Mon, 7 Oct 2024 20:50:41 -0700 Subject: [PATCH 02/13] Adding specificity --- packages/ai/src/steps/styleGuide.md | 195 +++++++++++++++++++++++----- 1 file changed, 162 insertions(+), 33 deletions(-) diff --git a/packages/ai/src/steps/styleGuide.md b/packages/ai/src/steps/styleGuide.md index 472a38020f..709f2ec4f7 100644 --- a/packages/ai/src/steps/styleGuide.md +++ b/packages/ai/src/steps/styleGuide.md @@ -26,23 +26,34 @@ inputs = { } ``` -- At the end of the file, don't return anything. The last line of the file should be the @notebook tag. -- If the file is over 50 lines, break it up with large styled blocks comments with headers. For example: // ===== Model Calculations ===== -- When you want to store complex data as code, use lists of dictionaries, instead of using lists of lists. This makes things much more clear. For example, use: +Note: You cannot use tags within dicts like the following: ```squiggle -[ - {year: 2023, value: 1}, - {year: 2024, value: 2}, -] -instead of: -[ - [2023, 1], - [2024, 2], -] +// This is not valid. Do not do this. +inputs = { + @name("Age") + age: 34, + + @name("Hourly Wage") + hourly_wage: 100, + + @name("Health Value") + health_value: 100, +} ``` -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. +- 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 ===== +``` ## Unit Annotation @@ -69,6 +80,16 @@ tuners = { } ``` +- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. + +```squiggle +@name("Distance to Mars (km)") +distance_mars = 225e6 + +@name("Distance to Venus (km)") +distance_venus = 170e6 +``` + ## Tags - 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. @@ -88,7 +109,13 @@ add(number, distribution) -> distribution ## The `@format()` tag - Use `@format()` for numbers, distributions, and dates that could use obvious formatting. -- The `@format()` tag is not usable with dictionaries, functions, or lists. +- The `@format()` tag is not usable with dictionaries, functions, or lists. It is usable with variable assignments. Examples: + +```squiggle +net_benefit(costs, benefits) = benefits - costs // not valid for @format() +net_benefit = 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. @@ -101,19 +128,42 @@ add(number, distribution) -> distribution - 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. -## Errors & Domains +## Domains -- Prefer using domains to throwing errors. 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, where you are unsure about the bounds of a function. -- If you only want to set a min or max value, use a domain with Number.minValue or Number.maxValue as the other bound. +- 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 "foo(f)". + +```squiggle +// Do not use this +f(t: [-Number.maxValue, Number.maxValue]) + 1 + +// Do this +f(t) = t + 1 +``` ## Tests - Use `sTest` to test squiggle code. -- Test all functions taht you are unsure about. +- 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 tests. Do not use @hide tags. -- Do not test that function domains return errors when called with invalid inputs. The domains should be trusted. +- 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 +``` ## Naming Conventions @@ -127,11 +177,15 @@ add(number, distribution) -> distribution - 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]). 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 PERT distributions. The uniform distribution is mainly good for physical simulations. - 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: inputs.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. +- 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. ## Numbers @@ -168,9 +222,62 @@ annual_budget = 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. -## Styling +## 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,}. + +## 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. + +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 }, + ... +] +``` + +## Tables -- In dicts, 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,}. +- 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 @@ -205,13 +312,18 @@ 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 - - The model suggests an optimal coffee consumption, balancing productivity gains, health impacts, and financial costs. - - Productivity boost from coffee shows diminishing returns as consumption increases. - - The financial cost of coffee is a significant factor in determining optimal consumption. + - 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. @@ -221,17 +333,34 @@ summary = [ 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." + 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. + " ] ``` -## Summary +## Summary Notebook - For models over 5 lines long, 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. Outputs (including relevant Squiggle variables) - 3. Key findings - 4. Detailed analysis (if over 300 lines long) - 5. Important notes or caveats (if over 100 lines long) + 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, final_answer}, + ... + ] +``` From 2742fc4c4ee028f975a5d38a646bfe8da4d1f7d6 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Wed, 9 Oct 2024 16:51:46 -0700 Subject: [PATCH 03/13] Adding new styleguide to README --- packages/ai/files/squiggleDocs.md | 426 +++++++++++++++++- .../ozziegooen/helpers.squiggle | 84 ++-- .../ozziegooen/sTest.squiggle | 4 +- packages/ai/src/squiggle/README.ts | 2 +- .../src/squiggle/squiggleLibraryContents.ts | 4 +- packages/ai/src/steps/adjustToFeedbackStep.ts | 1 + packages/ai/src/steps/styleGuide.md | 242 +++++----- .../src/pages/docs/Ecosystem/StyleGuide.md | 422 +++++++++++++++++ 8 files changed, 1021 insertions(+), 164 deletions(-) create mode 100644 packages/website/src/pages/docs/Ecosystem/StyleGuide.md diff --git a/packages/ai/files/squiggleDocs.md b/packages/ai/files/squiggleDocs.md index 49dd9e75f3..676caa72e9 100644 --- a/packages/ai/files/squiggleDocs.md +++ b/packages/ai/files/squiggleDocs.md @@ -2,7 +2,7 @@ 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. Also, pay attention to the included Style Guide. ### Syntax and Structure @@ -704,6 +704,430 @@ xs = List.upTo(0,10) ys = List.map(xs, {|x| f(x)}) ``` +--- + +## description: Squiggle Style Guide + +# Squiggle Style Guide + +Note: This document is highly opinionated and was written specifically for LLMs to read. However, humans might also find it useful. + +## Limitations + +- 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. + +## 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 PERT distributions. The uniform distribution is mainly good for physical simulations. +- 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: inputs.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 "foo(f)". + +```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)") + hourly_wage = 100 + + @name("Coffee Price ($/cup)") + coffee_price = 1 + {age, hourly_wage, health_value, coffee_price} +} +``` + +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)") + hourly_wage: 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 snake_case 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 + +- Squiggle does not support units directly, but you can add them 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. +- 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)") +number_of_humans = 7.8B + +@name("Net Benefit ($)") +net_benefit = 100M + +@name("Temperature (Β°C)") +temperature = 22 + +@name("Piano Tuners in New York City (2023)") +tuners = { + pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) + pianos_in_nyc = 1k to 50k // (pianos) + pianos_in_nyc / pianos_per_piano_tuners +} +``` + +- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. + +```squiggle +@name("Distance to Mars (km)") +distance_mars = 225e6 + +@name("Distance to Venus (km)") +distance_venus = 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)") +us_population = 331.9 +``` + +Instead, use: + +```squiggle +@name("US Population") +us_population = 331.9M +``` + +More examples: + +```squiggle +// Correct representations +world_population = 7.8B +annual_budget = 1.2T +distance_to_sun = 149.6e6 // 149.6 million kilometers + +// Incorrect representations (avoid these) +world_population = 7800 // Unclear if it's 7800 or 7.8 billion +annual_budget = 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. + +### 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. + +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 +net_benefit(costs, benefits) = benefits - costs // not valid for @format() +net_benefit = 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. + +## 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.", +{ + optimal_cups, + result.net_benefit, +}, +] +``` + +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, final_answer}, + ... + ] +``` + # Basic Types ## Numbers 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..28f881ff6a 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 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. Also, pay attention to the included Style Guide.\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---\n\n## description: Squiggle Style Guide\n\n# Squiggle Style Guide\n\nNote: This document is highly opinionated and was written specifically for LLMs to read. However, humans might also find it useful.\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# 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'; 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 67b35ba087..a6a7efb6f0 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/styleGuide.md b/packages/ai/src/steps/styleGuide.md index 709f2ec4f7..e73f2a0dd9 100644 --- a/packages/ai/src/steps/styleGuide.md +++ b/packages/ai/src/steps/styleGuide.md @@ -2,25 +2,57 @@ ## Limitations -- Try not to use numbers higher than 10^50 or so. There are floating point errors at high numbers. If you need to work with larger numbers, try doing math with logs. +- 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. -## Structure +## 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 PERT distributions. The uniform distribution is mainly good for physical simulations. +- 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: inputs.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. + +### 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 "foo(f)". + +```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") + @name("Age (years)") age = 34 - @name("Hourly Wage") + @name("Hourly Wage ($/hr)") hourly_wage = 100 - @name("Health Value") - health_value = 100 - - @name("Coffee Price") + @name("Coffee Price ($/cup)") coffee_price = 1 {age, hourly_wage, health_value, coffee_price} } @@ -31,14 +63,11 @@ Note: You cannot use tags within dicts like the following: ```squiggle // This is not valid. Do not do this. inputs = { - @name("Age") - age: 34, + @name("Age (years)") + age = 34, - @name("Hourly Wage") + @name("Hourly Wage ($/hr)") hourly_wage: 100, - - @name("Health Value") - health_value: 100, } ``` @@ -55,7 +84,17 @@ inputs = { // ===== Calculations ===== ``` -## Unit Annotation +### Naming Conventions + +- Use snake_case 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 - Squiggle does not support units directly, but you can add them 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. @@ -90,104 +129,7 @@ distance_mars = 225e6 distance_venus = 170e6 ``` -## Tags - -- 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. - -## The `@format()` tag - -- 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 -net_benefit(costs, benefits) = benefits - costs // not valid for @format() -net_benefit = 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. - -## 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. - -## 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 "foo(f)". - -```squiggle -// Do not use this -f(t: [-Number.maxValue, Number.maxValue]) + 1 - -// Do this -f(t) = t + 1 -``` - -## 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 -``` - -## Naming Conventions - -- Use snake_case 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". - -## 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]). 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 PERT distributions. The uniform distribution is mainly good for physical simulations. -- 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: inputs.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. - -## Numbers +### 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". @@ -222,11 +164,7 @@ annual_budget = 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. -## 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,}. - -## Lists of Structured Data +### 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: @@ -273,13 +211,55 @@ strategies = [ ] ``` -## Tables +## 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 +net_benefit(costs, benefits) = benefits - costs // not valid for @format() +net_benefit = 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. + +## 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 +### 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: @@ -338,6 +318,28 @@ summary = [ ] ``` +## 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 +``` + ## Summary Notebook - For models over 5 lines long, include a summary notebook at the end of the file using the @notebook tag. diff --git a/packages/website/src/pages/docs/Ecosystem/StyleGuide.md b/packages/website/src/pages/docs/Ecosystem/StyleGuide.md new file mode 100644 index 0000000000..2d5109212a --- /dev/null +++ b/packages/website/src/pages/docs/Ecosystem/StyleGuide.md @@ -0,0 +1,422 @@ +--- +description: Squiggle Style Guide +--- + +# Squiggle Style Guide +Note: This document is highly opinionated and was written specifically for LLMs to read. However, humans might also find it useful. + +## Limitations + +- 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. + +## 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 PERT distributions. The uniform distribution is mainly good for physical simulations. +- 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: inputs.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 "foo(f)". + +```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)") + hourly_wage = 100 + + @name("Coffee Price ($/cup)") + coffee_price = 1 + {age, hourly_wage, health_value, coffee_price} +} +``` + +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)") + hourly_wage: 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 snake_case 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 + +- Squiggle does not support units directly, but you can add them 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. +- 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)") +number_of_humans = 7.8B + +@name("Net Benefit ($)") +net_benefit = 100M + +@name("Temperature (Β°C)") +temperature = 22 + +@name("Piano Tuners in New York City (2023)") +tuners = { + pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) + pianos_in_nyc = 1k to 50k // (pianos) + pianos_in_nyc / pianos_per_piano_tuners +} +``` + +- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. + +```squiggle +@name("Distance to Mars (km)") +distance_mars = 225e6 + +@name("Distance to Venus (km)") +distance_venus = 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)") +us_population = 331.9 +``` + +Instead, use: + +```squiggle +@name("US Population") +us_population = 331.9M +``` + +More examples: + +```squiggle +// Correct representations +world_population = 7.8B +annual_budget = 1.2T +distance_to_sun = 149.6e6 // 149.6 million kilometers + +// Incorrect representations (avoid these) +world_population = 7800 // Unclear if it's 7800 or 7.8 billion +annual_budget = 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. + +### 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. + +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 +net_benefit(costs, benefits) = benefits - costs // not valid for @format() +net_benefit = 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. + +## 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.", +{ + optimal_cups, + result.net_benefit, +}, +] +``` + +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, final_answer}, + ... + ] +``` From cc788e7109cc6adc48702aeb1897b2908a1cafa4 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 10 Oct 2024 21:48:10 -0700 Subject: [PATCH 04/13] Updating website llmDocs generation --- packages/ai/files/squiggleDocs.md | 1624 +++++++++++------ packages/ai/src/squiggle/README.ts | 2 +- .../ai/src/steps/fixCodeUntilItRunsStep.ts | 20 +- packages/website/public/llms/prompt.md | 336 ++++ packages/website/public/llms/prompt.txt | 174 -- .../llms/styleGuide.md} | 9 +- .../website/scripts/compileDocsForLLM.mts | 13 +- 7 files changed, 1439 insertions(+), 739 deletions(-) create mode 100644 packages/website/public/llms/prompt.md delete mode 100644 packages/website/public/llms/prompt.txt rename packages/website/{src/pages/docs/Ecosystem/StyleGuide.md => public/llms/styleGuide.md} (99%) diff --git a/packages/ai/files/squiggleDocs.md b/packages/ai/files/squiggleDocs.md index 676caa72e9..0e8f72572a 100644 --- a/packages/ai/files/squiggleDocs.md +++ b/packages/ai/files/squiggleDocs.md @@ -1,8 +1,6 @@ -# Squiggle Documentation +# Squiggle Language Guidelines -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. Also, pay attention to the included Style Guide. +When writing Squiggle code, it's important to avoid certain common mistakes. ### Syntax and Structure @@ -12,6 +10,7 @@ When writing Squiggle code, it's important to avoid certain common mistakes. Als 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 @@ -264,9 +263,6 @@ analysis = { probabilityOfPositiveNetBenefit: results.probPositiveNetBenefit, } } - -analysis - ``` ```squiggle @@ -304,25 +300,25 @@ f(t: [Date(2020), Date(2040)]) = { } ``` -```squiggle +````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 @@ -337,200 +333,705 @@ testSuite = sTest.describe( ] ) -``` - -# Language Features - -## Program Structure +# Squiggle Style Guide -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_. +## Limitations -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. +- 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. -```squiggle -x = 5 -y = 10 -x + y -``` +## Data and Calculations -```squiggle -x = 5 -y = 10 -``` +### Estimations -## Immutability +- 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 PERT distributions. The uniform distribution is mainly good for physical simulations. +- 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: inputs.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. -All variables in Squiggle are immutable, similar to other functional programming languages like OCaml or Haskell. +### Percentages / Probabilities -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). +- 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. -In case of basic types such as numbers or strings, the impact of immutability is more subtle. +### Domains -Consider this code: +- 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 "foo(f)". ```squiggle -x = 5 -x = x + 5 -``` +// Do not use this +f(t: [-Number.maxValue, Number.maxValue]) + 1 -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. +// Do this +f(t) = t + 1 +```` -In most cases, shadowing behaves identically to what you'd expect in languages like JavaScript or Python. +## Structure and Naming Conventions -One case where shadowing matters is closures: +### 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 -x = 5 -argPlusX(y) = x + y +@name("Key Inputs") +inputs = { + @name("Age (years)") + age = 34 -x = x + 5 + @name("Hourly Wage ($/hr)") + hourly_wage = 100 -argPlusX(5) + @name("Coffee Price ($/cup)") + coffee_price = 1 + {age, hourly_wage, health_value, coffee_price} +} ``` -In the above example, the `argPlusX` function captures the value of `x` from line 1, not the newly shadowed `x` from line 4. As a result, `argPlusX(5)` returns 10, not 15. - -## Blocks - -Blocks are special expressions in Squiggle that can contain any number of local definitions and end with an expression. +Note: You cannot use tags within dicts like the following: ```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 +// This is not valid. Do not do this. +inputs = { + @name("Age (years)") + age = 34, + + @name("Hourly Wage ($/hr)") + hourly_wage: 100, } ``` -## Conditionals - -If/then/else statements in Squiggle are values too. +- 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 -x = 5 -if x<8 then 10 else 3 -``` - -See [Control flow](/docs/Guides/ControlFlow) for more details and examples. +// ===== Inputs ===== -## Comments +// ... -```squiggle -// This is a single-line comment\n -/* -This is a multiple --line comment. -*/ -foo = 5 +// ===== Calculations ===== ``` -## 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. +### Naming Conventions -```squiggle -normal(5,2) -> truncateLeft(3) -> SampleSet.fromDist -> SampleSet.map({|r| r + 10}) -``` +- Use snake_case 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". -## Standard Library +### Dictionaries -Squiggle features a simple [standard libary](/docs/Api/Dist). +- 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,}. -Most functions are namespaced under their respective types to keep functionality distinct. Certain popular functions are usable without their namespaces. +### Unit Annotation -For example, +- Squiggle does not support units directly, but you can add them 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. +- 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 -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 +@name("Number of Humans (2023)") +number_of_humans = 7.8B -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. +@name("Net Benefit ($)") +net_benefit = 100M -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. +@name("Temperature (Β°C)") +temperature = 22 -```squiggle -dist = SampleSet.fromDist(5 to 100000000) -{ - sampleSetMedian: quantile(dist, .5), - pointSetMedian: quantile(PointSet.fromDist(dist), .5), - dist: dist +@name("Piano Tuners in New York City (2023)") +tuners = { + pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) + pianos_in_nyc = 1k to 50k // (pianos) + pianos_in_nyc / pianos_per_piano_tuners } ``` ---- +- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. -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. +```squiggle +@name("Distance to Mars (km)") +distance_mars = 225e6 -We plan to later support more configuration of kernel density estimation, and for visualiations of Sample Set distributions to instead use histograms. +@name("Distance to Venus (km)") +distance_venus = 170e6 +``` -## Sample Set Correlations +### Numbers -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. +- 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. -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. +Don't use the code: ```squiggle -sampleSetDist = normal(5, 2) -pointSetDist = sampleSetDist -> PointSet.fromDist -symbolicDist = Sym.normal(5, 2) -[ - sampleSetDist - sampleSetDist, - pointSetDist - pointSetDist, - symbolicDist - symbolicDist, -] +@name("US Population (millions)") +us_population = 331.9 ``` -# Functions - -## Basic syntax +Instead, use: ```squiggle -myMultiply(t) = normal(t^2, t^1.2+.01) -myMultiply +@name("US Population") +us_population = 331.9M ``` -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: +More examples: ```squiggle -multiplyBySix(x) = { - doubleX = x * 2 - doubleX * 3 - } +// Correct representations +world_population = 7.8B +annual_budget = 1.2T +distance_to_sun = 149.6e6 // 149.6 million kilometers + +// Incorrect representations (avoid these) +world_population = 7800 // Unclear if it's 7800 or 7.8 billion +annual_budget = 1200 // Unclear if it's 1200 or 1.2 trillion ``` -## Anonymous Functions +- There's no need to use @format on regular numbers. The default formatting is fairly sophistated. -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. +### Lists of Structured Data -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. +- 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 -{|t| normal(t^2, t^1.2+.01)} +[ + {year: 2023, value: 1}, + {year: 2024, value: 2}, +] +instead of: +[ + [2023, 1], + [2024, 2], +] ``` -## Function Visualization +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. -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: +- 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. -1. `(number) => number` -2. `(number) => distribution` +For example: ```squiggle -numberToNumber(x) = x * x -numberToDistribution(x) = normal(x + 1, 3) -placeholderFunction(x, y) = x + y -``` +@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 +net_benefit(costs, benefits) = benefits - costs // not valid for @format() +net_benefit = 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. + +## 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.", +{ + optimal_cups, + result.net_benefit, +}, +] +``` + +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, final_answer}, + ... + ] +``` + +# Roadmap + +Squiggle is still young. The main first goal is to become stable (to reach version 1.0). Right now we think it is useable to use for small projects, but do note that there are very likely some math bugs and performance problems. + +If you have preferences or suggestions for our roadmap, please say so! Post your thoughts in the Github discussion or in the Discord. + +Note that our short-term roadmap changes frequently, and is not captured here. + +## Programming Language Features + +- A simple type system +- Optional and default paramaters for functions +- Much better code editor integration + +## Distribution Features + +There are many important distribution types that Squiggle doesn't yet support. Some key functions we'd like include: + +[Metalog Distribution](https://en.wikipedia.org/wiki/Metalog_distribution) +Add the Metalog distribution, and some convenient methods for generating these distributions. This might be a bit tricky because we might need or build a library to fit data. There's no Metalog javascript library yet, this would be pretty useful. There's already a Metalog library in Python, so that one could be used for inspiration. + +`Distribution.smoothen(p)` +Takes a distribution and smoothens it. For example, [Elicit Forecast](https://forecast.elicit.org/) does something like this, with uniform distributions. + +## Major Future Additions + +**An interface to interpret & score Squiggle files** +Squiggle functions need to be aggregated and scored. This should be done outside one Squiggle file. Maybe this should also be done in Squiggle, or maybe it should be done using Javascript. + +My guess is that there should eventually be some way for people to declare that some of their Squiggle values are meant to be formally declared, to be scored and similar by others. Then other programs can read these files, and either use the values, or score them. + +Of course, we'd also need good math for how the scoring should work, exactly. + +This interface should also be able to handle changing Squiggle values. This is because people would be likely to want to update their functions over time, and that should be taken into account for scoring. + +**Importance & quality scores** +Workflows/functionality to declare the importance and coveredness of each part of the paramater space. For example, some subsets of the paramater space of a function might be much more important to get right than others. Similarly, the analyst might be much more certain about some parts than others. Ideally. they could decline sections. + +**Static / sensitivity analysis** +Guesstimate has Sensitivity analysis that's pretty useful. This could be quite feasible to add, though it will likely require some thinking. + +**Randomness seeds** +Right now, Monte Carlo simulations are totally random. It would be nicer to be able to enter a seed somehow in order to control the randomness. Or, with the same seed, the function should always return the same values. This would make debugging and similar easier. + +**Caching/memoization** +There are many performance improvements that Squiggle could have. We'll get to some of them eventually. + +# 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. @@ -541,6 +1042,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,568 +1103,549 @@ myMultiply.parameters[0] Domains and parameter names can be accessed by the `fn.parameters` property. -# Control Flow +# Distribution Functions -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. +## Standard Operations -## Conditionals +Here are the ways we combine distributions. -### If-else +### Addition -```squiggle -if condition then result else alternative -``` +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 -x = 10 -if x == 1 then 1 else 2 +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 + dist2 ``` -### If-else as a ternary operator +### Subtraction -```squiggle -test ? result : alternative; -``` +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 -x = 10 -x == 0 ? 1 : 2 +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 - dist2 ``` -### Tips and tricks +### Multiplication -#### Use brackets and parenthesis to organize control flow +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 -x = 10 -if x == 1 then { - 1 -} else { - 2 -} +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 * dist2 ``` -or +We also provide concatenation of two distributions as a syntax sugar for `*` ```squiggle -x = 10 -y = 20 -if x == 1 then { - ( - if y == 0 then { - 1 - } else { - 2 - } - ) -} else { - 3 -} +dist1 = 1 to 10 +dist2 = triangular(1,2,3) +dist1 / dist2 ``` -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: +### Exponentiation -```squiggle -x = 10 -y = 20 -if x == 1 then { - y = 1 -} else { - y = 2 * x -} +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) ``` -Instead, you can do this: +### The base `e` exponential ```squiggle -x = 10 -y = 20 -y = if x == 1 then { - 1 -} else { - 2 * x -} +dist = triangular(1,2,3) +exp(dist) ``` -Likewise, for assigning more than one value, you can't do this: +### Logarithms + +A projection over a stretched x-axis. ```squiggle -y = 0 -z = 0 -if x == 1 then { - y = 2 -} else { - z = 4 -} +dist = triangular(1,2,3) +log(dist) ``` -Instead, do: +```squiggle +log10(5 to 10) +``` + +Base `x` ```squiggle -x = 10 -result = if x == 1 then { - {y: 2, z: 0} -} else { - {y: 0, z: 4} -} -y = result.y -z = result.z +log(5 to 10, 2) ``` -## For loops +## Pointwise Operations -For loops aren't supported in Squiggle. Instead, use a [map](/docs/Api/List#map) or a [reduce](/docs/Api/List#reduce) function. +### Pointwise addition -Instead of: +For every point on the x-axis, operate the corresponding points in the y axis of the pdf. -```js -xs = []; -for (i = 0; i < 10; i++) { - xs[i] = f(x); -} +**Pointwise operations are done with `PointSetDist` internals rather than `SampleSetDist` internals**. + +```squiggle +Sym.lognormal({p5: 1, p95: 3}) .+ Sym.triangular(5,6,7) ``` -do: +### Pointwise multiplication ```squiggle -f(x) = 2*x -xs = List.upTo(0,10) -ys = List.map(xs, {|x| f(x)}) +Sym.lognormal({p5: 1, p95: 5}) .* Sym.uniform(1,8) ``` ---- +## Standard Functions -## description: Squiggle Style Guide +### Probability density function -# Squiggle Style Guide +The `pdf(dist, x)` function returns the density of a distribution at the +given point x. -Note: This document is highly opinionated and was written specifically for LLMs to read. However, humans might also find it useful. + -## Limitations +#### Validity -- 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. +- `x` must be a scalar +- `dist` must be a distribution -## Data and Calculations +### Cumulative density function -### Estimations +The `cdf(dist, x)` gives the cumulative probability of the distribution +or all values lower than x. It is the inverse of `quantile`. -- 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 PERT distributions. The uniform distribution is mainly good for physical simulations. -- 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: inputs.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 +#### Validity -- 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. +- `x` must be a scalar +- `dist` must be a distribution -### Domains +### Quantile -- 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 "foo(f)". +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. -```squiggle -// Do not use this -f(t: [-Number.maxValue, Number.maxValue]) + 1 + -// Do this -f(t) = t + 1 -``` +#### Validity -## Structure and Naming Conventions +- `prob` must be a scalar (please only put it in `(0,1)`) +- `dist` must be a distribution -### Structure +### Mean -- 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, +The `mean(distribution)` function gives the mean (expected value) of a distribution. -```squiggle -@name("Key Inputs") -inputs = { - @name("Age (years)") - age = 34 + - @name("Hourly Wage ($/hr)") - hourly_wage = 100 +### Sampling a distribution - @name("Coffee Price ($/cup)") - coffee_price = 1 - {age, hourly_wage, health_value, coffee_price} -} -``` +The `sample(distribution)` samples a given distribution. -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, +## Converting between distribution formats - @name("Hourly Wage ($/hr)") - hourly_wage: 100, -} -``` +We can convert any distribution into the `SampleSet` format -- 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 ===== +Or the `PointSet` format -// ... + -// ===== Calculations ===== -``` +#### Validity -### Naming Conventions +- Second argument to `SampleSet.fromDist` must be a number. -- Use snake_case 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". +## Normalization -### Dictionaries +Some distribution operations (like horizontal shift) return an unnormalized distriibution. -- 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,}. +We provide a `normalize` function -### Unit Annotation + -- Squiggle does not support units directly, but you can add them 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. -- 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: +#### Validity - Input to `normalize` must be a dist -```squiggle -@name("Number of Humans (2023)") -number_of_humans = 7.8B +We provide a predicate `isNormalized`, for when we have simple control flow -@name("Net Benefit ($)") -net_benefit = 100M + -@name("Temperature (Β°C)") -temperature = 22 +#### Validity -@name("Piano Tuners in New York City (2023)") -tuners = { - pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) - pianos_in_nyc = 1k to 50k // (pianos) - pianos_in_nyc / pianos_per_piano_tuners -} +- 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}) ``` -- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. +Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) with the given mean and standard deviation. + + + ```squiggle -@name("Distance to Mars (km)") -distance_mars = 225e6 +normalMean = 10 +normalStdDev = 2 +logOfLognormal = log(lognormal(normalMean, normalStdDev)) +[logOfLognormal, normal(normalMean, normalStdDev)] -@name("Distance to Venus (km)") -distance_venus = 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: +## To ```squiggle -@name("US Population (millions)") -us_population = 331.9 +(5thPercentile: number) to (95thPercentile: number) +to(5thPercentile: number, 95thPercentile: number) ``` -Instead, use: +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 -@name("US Population") -us_population = 331.9M +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]) + ``` -More examples: + +
+ πŸ”’ 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 -// Correct representations -world_population = 7.8B -annual_budget = 1.2T -distance_to_sun = 149.6e6 // 149.6 million kilometers +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]) +```` -// Incorrect representations (avoid these) -world_population = 7800 // Unclear if it's 7800 or 7.8 billion -annual_budget = 1200 // Unclear if it's 1200 or 1.2 trillion -``` +
+## SampleSet.fromList -- There's no need to use @format on regular numbers. The default formatting is fairly sophistated. +```squiggle +SampleSet.fromList(samples:number[]) +``` -### Lists of Structured Data +Creates a sample set distribution using an array of samples. -- 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: +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 -[ - {year: 2023, value: 1}, - {year: 2024, value: 2}, -] -instead of: -[ - [2023, 1], - [2024, 2], -] +PointSet.makeContinuous([ + { x: 0, y: 0.1 }, + { x: 1, y: 0.2 }, + { x: 2, y: 0.15 }, + { x: 3, y: 0.1 } +]) ``` -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. + + **Caution!** + Distributions made with ``makeContinuous`` are not automatically normalized. We suggest normalizing them manually using the ``normalize`` function. + -- 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. +### Arguments -For example: +- `points`: An array of at least 3 coordinates. + +## PointSet.makeDiscrete ```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} }, - ], - } -) +PointSet.makeDiscrete(points:{x: number, y: number}) +``` -@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 }, - ... -] +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 } +]) ``` -## Tags and Annotations +### Arguments -### @name, @doc, @hide, @showAs +- `points`: An array of at least 1 coordinate. -- 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: +# Debugging + +Interactive 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: + +## Basic Console Logging + +- **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. +- **Variable Settings Toggle:** Click on the variable menu in the Squiggle interface and select "Log to JS Console". + +## `Window.squiggleOutput` + +Squiggle 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). +## `Danger.json` + +You 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. + +```squiggle +sampleSet = 30 to 50 +pointSet = Sym.normal(5, 2) +plot = Plot.dists([sampleSet, pointSet]) +fn(e) = e +{ + json: Danger.json([sampleSet, pointSet, plot, fn]), + jsonString: Danger.jsonString([pointSet, fn]), +} ``` -@doc("Adds a number and a distribution. -\`\`\`squiggle -add(number, distribution) -> distribution -\`\`\`") + +## Profiling + +In 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. + +Caveats: + +- The code will execute slightly slower in profiler mode +- Imports won't be profiled correctly (but slow calls of imported functions will be highlighted) +- If the code is fast, you'll randomly get highlighted and unhighlighted results, because time measurement is imprecise + +If you're using Squiggle components in React, you can enable the profiler for any component that supports the `environment` prop with `environment={profile: true}`: + +```squiggle +dist = normal(0, 1) +list = List.upTo(1, 100000) -> List.length ``` -- 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. +# 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. -### `@format()` +## Conditionals -- 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: +### If-else ```squiggle -net_benefit(costs, benefits) = benefits - costs // not valid for @format() -net_benefit = benefits - costs // valid for @format() +if condition then result else alternative ``` -- 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. +```squiggle +x = 10 +if x == 1 then 1 else 2 +``` -## Comments +### If-else as a ternary operator -- 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. +```squiggle +test ? result : alternative; +``` -## Visualizations +```squiggle +x = 10 +x == 0 ? 1 : 2 +``` -### Tables +### Tips and tricks -- 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. +#### Use brackets and parenthesis to organize control flow -### Notebooks +```squiggle +x = 10 +if x == 1 then { + 1 +} else { + 2 +} +``` -- 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: +or ```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.", -{ - optimal_cups, - result.net_benefit, -}, -] +x = 10 +y = 20 +if x == 1 then { + ( + if y == 0 then { + 1 + } else { + 2 + } + ) +} else { + 3 +} ``` -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. +This is overkill for simple examples becomes useful when the control conditions are more complex. -- 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) + "**". +#### Save the result to a variable -Example: (For a model with 300 lines) +Assigning a value inside an if/else flow isn't possible: ```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). +x = 10 +y = 20 +if x == 1 then { + y = 1 +} else { + y = 2 * x +} +``` - 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. +Instead, you can do this: - 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. - " -] +```squiggle +x = 10 +y = 20 +y = if x == 1 then { + 1 +} else { + 2 * x +} ``` -## Plots +Likewise, for assigning more than one value, you can't do this: -- 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. +```squiggle +y = 0 +z = 0 +if x == 1 then { + y = 2 +} else { + z = 4 +} +``` -Here's an example of how to display multiple distributions over the same x-axis, with a custom x-axis range: +Instead, do: ```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)})), +x = 10 +result = if x == 1 then { + {y: 2, z: 0} +} else { + {y: 0, z: 4} } +y = result.y +z = result.z +``` -plotOfResults(fn) = { - |r| - range = List.map(strategies, fn) -> rangeOfDists - Plot.dist(fn(r), { xScale: Scale.linear(range) }) +## 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); } +``` -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}) }, - ], - } -) +do: + +```squiggle +f(x) = 2*x +xs = List.upTo(0,10) +ys = List.map(xs, {|x| f(x)}) ``` -## Tests +# Known Bugs -- 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. +Much of the Squiggle math is imprecise. This can cause significant errors, so watch out. -Example: +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. -```squiggle -@hide -describe = sTest.describe +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). -@hide -test = sTest.test +## Operations on very small or large numbers, silently round to 0 and 1 -tests = describe( - "Coffee Consumption Model Tests", - [ - // ...tests - ] -) -``` +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). -## Summary Notebook +## Mixtures of distributions with very different means -- 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. +If you take the pointwise mixture of two distributions with very different means, then the value of that gets fairly warped. -Example: (For a model with 300 lines) +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 -@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}, - ... - ] +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. @@ -1169,7 +1682,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 @@ -1201,7 +1714,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 @@ -1212,7 +1725,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 @@ -1253,7 +1766,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 @@ -1383,7 +1896,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} @@ -1985,6 +2498,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) @@ -2167,6 +2686,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 @@ -2189,6 +2717,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 @@ -2323,7 +2857,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) = { @@ -2499,11 +3033,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` | @@ -2600,7 +3134,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. @@ -2651,7 +3185,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"] }) @@ -2666,7 +3200,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)]}, @@ -2780,7 +3314,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: @@ -2793,7 +3327,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. @@ -2801,7 +3335,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( [ @@ -2811,11 +3345,3 @@ Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions( 100, 0.01 ) - -``` - -``` - -``` - -``` diff --git a/packages/ai/src/squiggle/README.ts b/packages/ai/src/squiggle/README.ts index 28f881ff6a..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. Also, pay attention to the included Style Guide.\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---\n\n## description: Squiggle Style Guide\n\n# Squiggle Style Guide\n\nNote: This document is highly opinionated and was written specifically for LLMs to read. However, humans might also find it useful.\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# 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/steps/fixCodeUntilItRunsStep.ts b/packages/ai/src/steps/fixCodeUntilItRunsStep.ts index acb0e474c4..445c2dd29e 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/prompt.md b/packages/website/public/llms/prompt.md new file mode 100644 index 0000000000..cf4b46eef3 --- /dev/null +++ b/packages/website/public/llms/prompt.md @@ -0,0 +1,336 @@ +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. +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") +@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) + } + ), + ] +) \ No newline at end of file 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/src/pages/docs/Ecosystem/StyleGuide.md b/packages/website/public/llms/styleGuide.md similarity index 99% rename from packages/website/src/pages/docs/Ecosystem/StyleGuide.md rename to packages/website/public/llms/styleGuide.md index 2d5109212a..9418f104a6 100644 --- a/packages/website/src/pages/docs/Ecosystem/StyleGuide.md +++ b/packages/website/public/llms/styleGuide.md @@ -1,9 +1,4 @@ ---- -description: Squiggle Style Guide ---- - # Squiggle Style Guide -Note: This document is highly opinionated and was written specifically for LLMs to read. However, humans might also find it useful. ## Limitations @@ -329,12 +324,12 @@ summary = [ - 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. +- 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 }, diff --git a/packages/website/scripts/compileDocsForLLM.mts b/packages/website/scripts/compileDocsForLLM.mts index cceb4e78df..762b9c3a82 100755 --- a/packages/website/scripts/compileDocsForLLM.mts +++ b/packages/website/scripts/compileDocsForLLM.mts @@ -96,7 +96,8 @@ const allDocumentationItems = () => { .join("\n\n\n"); }; -const promptPageRaw = readFile("./public/llms/prompt.txt"); +const promptPageRaw = readFile("./public/llms/prompt.md"); +const styleGuideRaw = readFile("./public/llms/styleGuide.md"); const documentationBundlePage = async () => { const targetFilename = "./public/llms/documentationBundle.txt"; @@ -105,6 +106,7 @@ This file is auto-generated from the documentation files in the Squiggle reposit --- \n\n `; + // 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]); @@ -123,14 +125,17 @@ This file is auto-generated from the documentation files in the Squiggle reposit }; 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 ` + + "\n\n" + + styleGuideRaw + + "\n\n" + + // `## Peggy Grammar \n\n ${grammarContent} \n\n --- \n\n ` + convertSquiggleEditorTags(guideContent) + apiContent; fs.writeFile(targetFilename, content, (err) => { @@ -153,7 +158,7 @@ notes: "This Doc is generated using a script, do not edit directly!" 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). -You can read this document in plaintext [here](/llms/prompt.txt). +You can read this document in plaintext [here](/llms/prompt.md). --- From a9a681e05ebaa254be8fa292a08b0ba8ce6e857d Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Fri, 11 Oct 2024 23:03:37 -0700 Subject: [PATCH 05/13] Style guide updates, for cr --- packages/website/public/llms/styleGuide.md | 82 +++++++++++----------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/packages/website/public/llms/styleGuide.md b/packages/website/public/llms/styleGuide.md index 9418f104a6..a0104634dd 100644 --- a/packages/website/public/llms/styleGuide.md +++ b/packages/website/public/llms/styleGuide.md @@ -1,34 +1,30 @@ # Squiggle Style Guide -## Limitations - -- 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. - ## 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 PERT distributions. The uniform distribution is mainly good for physical simulations. +- 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: inputs.t) = 1 / (1 + exp(-normal(0.1, 0.08) * (t - 30)))` +- 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. +- 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. +- 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]). +- 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 "foo(f)". +- 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 @@ -51,11 +47,11 @@ inputs = { age = 34 @name("Hourly Wage ($/hr)") - hourly_wage = 100 + hourlyWage = 100 @name("Coffee Price ($/cup)") - coffee_price = 1 - {age, hourly_wage, health_value, coffee_price} + coffeePrice = 1 + {age, hourlyWage, coffeePrice} } ``` @@ -65,10 +61,10 @@ Note: You cannot use tags within dicts like the following: // This is not valid. Do not do this. inputs = { @name("Age (years)") - age = 34, + age: 34, @name("Hourly Wage ($/hr)") - hourly_wage: 100, + hourlyWage: 100, } ``` @@ -87,9 +83,9 @@ inputs = { ### Naming Conventions -- Use snake_case for variable names. +- 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". +- In functions, input parameters that aren't obvious should have semantic names. For example, instead of `nb` use `net_benefit`. ### Dictionaries @@ -97,26 +93,26 @@ inputs = { ### Unit Annotation -- Squiggle does not support units directly, but you can add them to '@name()', '@doc()' tags, and add them to comments. +- Squiggle does not support units directly, but you can add them 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. - 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)") -number_of_humans = 7.8B +numberOfHumans = 7.8B @name("Net Benefit ($)") -net_benefit = 100M +netBenefit = 100M @name("Temperature (Β°C)") temperature = 22 @name("Piano Tuners in New York City (2023)") tuners = { - pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) - pianos_in_nyc = 1k to 50k // (pianos) - pianos_in_nyc / pianos_per_piano_tuners + pianosPerTuner = 100 to 1k // (pianos per tuner) + pianosInNYC = 1k to 50k // (pianos) + pianosInNYC / pianosPerTuner } ``` @@ -124,10 +120,10 @@ tuners = { ```squiggle @name("Distance to Mars (km)") -distance_mars = 225e6 +distanceMars = 225e6 @name("Distance to Venus (km)") -distance_venus = 170e6 +distanceVenus = 170e6 ``` ### Numbers @@ -140,27 +136,27 @@ Don't use the code: ```squiggle @name("US Population (millions)") -us_population = 331.9 +usPopulation = 331.9 ``` Instead, use: ```squiggle @name("US Population") -us_population = 331.9M +usPopulation = 331.9M ``` More examples: ```squiggle // Correct representations -world_population = 7.8B -annual_budget = 1.2T -distance_to_sun = 149.6e6 // 149.6 million kilometers +worldPopulation = 7.8B +annualBudget = 1.2T +distanceToSun = 149.6e6 // 149.6 million kilometers // Incorrect representations (avoid these) -world_population = 7800 // Unclear if it's 7800 or 7.8 billion -annual_budget = 1200 // Unclear if it's 1200 or 1.2 trillion +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. @@ -184,7 +180,7 @@ instead of: 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. +- 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: @@ -236,14 +232,18 @@ add(number, distribution) -> distribution - The `@format()` tag is not usable with dictionaries, functions, or lists. It is usable with variable assignments. Examples: ```squiggle -net_benefit(costs, benefits) = benefits - costs // not valid for @format() -net_benefit = benefits - costs // valid for @format() +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. @@ -271,8 +271,8 @@ net_benefit = benefits - costs // valid for @format() summary = [ "This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", { - optimal_cups, - result.net_benefit, + optimalCups, + result.netBenefit, }, ] ``` @@ -411,7 +411,7 @@ Example: (For a model with 300 lines) 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}, + {inputs, finalAnswer}, ... ] ``` From e8df32358e7f9ca26d7924e0bef36aa6142d5921 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Sat, 12 Oct 2024 09:28:07 -0700 Subject: [PATCH 06/13] Formatted prompt for linter --- packages/website/public/llms/prompt.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/website/public/llms/prompt.md b/packages/website/public/llms/prompt.md index cf4b46eef3..8df79a2654 100644 --- a/packages/website/public/llms/prompt.md +++ b/packages/website/public/llms/prompt.md @@ -1,8 +1,8 @@ -Write Squiggle code, using the attached documentation for how it works. +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 @@ -333,4 +333,5 @@ tests = sTest.describe( } ), ] -) \ No newline at end of file +) +``` From 45a16ddaa2601a2ad134f55dec444a95bebe9205 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Sat, 12 Oct 2024 10:57:58 -0700 Subject: [PATCH 07/13] Minor editing of prompt vs. styleGuide --- packages/website/public/llms/prompt.md | 11 ++--------- packages/website/public/llms/styleGuide.md | 4 +++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/website/public/llms/prompt.md b/packages/website/public/llms/prompt.md index 8df79a2654..e0857f54dd 100644 --- a/packages/website/public/llms/prompt.md +++ b/packages/website/public/llms/prompt.md @@ -31,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 @@ -41,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: diff --git a/packages/website/public/llms/styleGuide.md b/packages/website/public/llms/styleGuide.md index a0104634dd..6cb9e5a3e6 100644 --- a/packages/website/public/llms/styleGuide.md +++ b/packages/website/public/llms/styleGuide.md @@ -93,8 +93,9 @@ inputs = { ### Unit Annotation -- Squiggle does not support units directly, but you can add them to `@name()`, `@doc()` tags, and add them to comments. +- 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: @@ -160,6 +161,7 @@ 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 From 2eb38dda7581f3ffa6067bf00710b467d2fd40eb Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Tue, 15 Oct 2024 10:20:23 -0700 Subject: [PATCH 08/13] Minor cleanup --- packages/ai/files/squiggleDocs.md | 212 +++-------- .../public/llms/{prompt.md => basicPrompt.md} | 0 .../website/scripts/compileDocsForLLM.mts | 33 +- .../src/pages/docs/Ecosystem/BasicPrompt.md | 343 ++++++++++++++++++ .../src/pages/docs/Ecosystem/_meta.json | 2 +- 5 files changed, 423 insertions(+), 167 deletions(-) rename packages/website/public/llms/{prompt.md => basicPrompt.md} (100%) create mode 100644 packages/website/src/pages/docs/Ecosystem/BasicPrompt.md diff --git a/packages/ai/files/squiggleDocs.md b/packages/ai/files/squiggleDocs.md index 0e8f72572a..66878798a9 100644 --- a/packages/ai/files/squiggleDocs.md +++ b/packages/ai/files/squiggleDocs.md @@ -1,4 +1,12 @@ -# Squiggle Language Guidelines +# 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. + +--- + +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. @@ -29,7 +37,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 @@ -39,24 +46,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: @@ -300,7 +301,7 @@ f(t: [Date(2020), Date(2040)]) = { } ``` -````squiggle +```squiggle import "hub:ozziegooen/sTest" as sTest @name("πŸ’° Expected Cost ($)") @format("$.2s") @@ -332,38 +333,35 @@ tests = sTest.describe( ), ] ) +``` # Squiggle Style Guide -## Limitations - -- 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. - ## 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 PERT distributions. The uniform distribution is mainly good for physical simulations. +- 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: inputs.t) = 1 / (1 + exp(-normal(0.1, 0.08) * (t - 30)))` +- 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. +- 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. +- 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]). +- 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 "foo(f)". +- 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 @@ -371,7 +369,7 @@ f(t: [-Number.maxValue, Number.maxValue]) + 1 // Do this f(t) = t + 1 -```` +``` ## Structure and Naming Conventions @@ -386,11 +384,11 @@ inputs = { age = 34 @name("Hourly Wage ($/hr)") - hourly_wage = 100 + hourlyWage = 100 @name("Coffee Price ($/cup)") - coffee_price = 1 - {age, hourly_wage, health_value, coffee_price} + coffeePrice = 1 + {age, hourlyWage, coffeePrice} } ``` @@ -400,10 +398,10 @@ Note: You cannot use tags within dicts like the following: // This is not valid. Do not do this. inputs = { @name("Age (years)") - age = 34, + age: 34, @name("Hourly Wage ($/hr)") - hourly_wage: 100, + hourlyWage: 100, } ``` @@ -422,9 +420,9 @@ inputs = { ### Naming Conventions -- Use snake_case for variable names. +- 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". +- In functions, input parameters that aren't obvious should have semantic names. For example, instead of `nb` use `net_benefit`. ### Dictionaries @@ -432,26 +430,27 @@ inputs = { ### Unit Annotation -- Squiggle does not support units directly, but you can add them to '@name()', '@doc()' tags, and add them to comments. +- 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)") -number_of_humans = 7.8B +numberOfHumans = 7.8B @name("Net Benefit ($)") -net_benefit = 100M +netBenefit = 100M @name("Temperature (Β°C)") temperature = 22 @name("Piano Tuners in New York City (2023)") tuners = { - pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) - pianos_in_nyc = 1k to 50k // (pianos) - pianos_in_nyc / pianos_per_piano_tuners + pianosPerTuner = 100 to 1k // (pianos per tuner) + pianosInNYC = 1k to 50k // (pianos) + pianosInNYC / pianosPerTuner } ``` @@ -459,10 +458,10 @@ tuners = { ```squiggle @name("Distance to Mars (km)") -distance_mars = 225e6 +distanceMars = 225e6 @name("Distance to Venus (km)") -distance_venus = 170e6 +distanceVenus = 170e6 ``` ### Numbers @@ -475,30 +474,31 @@ Don't use the code: ```squiggle @name("US Population (millions)") -us_population = 331.9 +usPopulation = 331.9 ``` Instead, use: ```squiggle @name("US Population") -us_population = 331.9M +usPopulation = 331.9M ``` More examples: ```squiggle // Correct representations -world_population = 7.8B -annual_budget = 1.2T -distance_to_sun = 149.6e6 // 149.6 million kilometers +worldPopulation = 7.8B +annualBudget = 1.2T +distanceToSun = 149.6e6 // 149.6 million kilometers // Incorrect representations (avoid these) -world_population = 7800 // Unclear if it's 7800 or 7.8 billion -annual_budget = 1200 // Unclear if it's 1200 or 1.2 trillion +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 @@ -519,7 +519,7 @@ instead of: 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. +- 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: @@ -571,14 +571,18 @@ add(number, distribution) -> distribution - The `@format()` tag is not usable with dictionaries, functions, or lists. It is usable with variable assignments. Examples: ```squiggle -net_benefit(costs, benefits) = benefits - costs // not valid for @format() -net_benefit = benefits - costs // valid for @format() +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. @@ -606,8 +610,8 @@ net_benefit = benefits - costs // valid for @format() summary = [ "This model evaluates the cost-effectiveness of coffee consumption for a 34-year-old male, considering productivity benefits, health effects, and financial costs.", { - optimal_cups, - result.net_benefit, + optimalCups, + result.netBenefit, }, ] ``` @@ -746,58 +750,11 @@ Example: (For a model with 300 lines) 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}, + {inputs, finalAnswer}, ... ] ``` -# Roadmap - -Squiggle is still young. The main first goal is to become stable (to reach version 1.0). Right now we think it is useable to use for small projects, but do note that there are very likely some math bugs and performance problems. - -If you have preferences or suggestions for our roadmap, please say so! Post your thoughts in the Github discussion or in the Discord. - -Note that our short-term roadmap changes frequently, and is not captured here. - -## Programming Language Features - -- A simple type system -- Optional and default paramaters for functions -- Much better code editor integration - -## Distribution Features - -There are many important distribution types that Squiggle doesn't yet support. Some key functions we'd like include: - -[Metalog Distribution](https://en.wikipedia.org/wiki/Metalog_distribution) -Add the Metalog distribution, and some convenient methods for generating these distributions. This might be a bit tricky because we might need or build a library to fit data. There's no Metalog javascript library yet, this would be pretty useful. There's already a Metalog library in Python, so that one could be used for inspiration. - -`Distribution.smoothen(p)` -Takes a distribution and smoothens it. For example, [Elicit Forecast](https://forecast.elicit.org/) does something like this, with uniform distributions. - -## Major Future Additions - -**An interface to interpret & score Squiggle files** -Squiggle functions need to be aggregated and scored. This should be done outside one Squiggle file. Maybe this should also be done in Squiggle, or maybe it should be done using Javascript. - -My guess is that there should eventually be some way for people to declare that some of their Squiggle values are meant to be formally declared, to be scored and similar by others. Then other programs can read these files, and either use the values, or score them. - -Of course, we'd also need good math for how the scoring should work, exactly. - -This interface should also be able to handle changing Squiggle values. This is because people would be likely to want to update their functions over time, and that should be taken into account for scoring. - -**Importance & quality scores** -Workflows/functionality to declare the importance and coveredness of each part of the paramater space. For example, some subsets of the paramater space of a function might be much more important to get right than others. Similarly, the analyst might be much more certain about some parts than others. Ideally. they could decline sections. - -**Static / sensitivity analysis** -Guesstimate has Sensitivity analysis that's pretty useful. This could be quite feasible to add, though it will likely require some thinking. - -**Randomness seeds** -Right now, Monte Carlo simulations are totally random. It would be nicer to be able to enter a seed somehow in order to control the randomness. Or, with the same seed, the function should always return the same values. This would make debugging and similar easier. - -**Caching/memoization** -There are many performance improvements that Squiggle could have. We'll get to some of them eventually. - # Language Features ## Program Structure @@ -1330,15 +1287,13 @@ Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distributio - ```squiggle normalMean = 10 normalStdDev = 2 logOfLognormal = log(lognormal(normalMean, normalStdDev)) [logOfLognormal, normal(normalMean, normalStdDev)] -``` - +```` ## To @@ -1346,20 +1301,18 @@ logOfLognormal = log(lognormal(normalMean, normalStdDev)) ```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]) -``` - +````
@@ -1424,51 +1377,6 @@ PointSet.makeDiscrete([ - `points`: An array of at least 1 coordinate. -# Debugging - -Interactive 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: - -## Basic Console Logging - -- **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. -- **Variable Settings Toggle:** Click on the variable menu in the Squiggle interface and select "Log to JS Console". - -## `Window.squiggleOutput` - -Squiggle 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). - -## `Danger.json` - -You 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. - -```squiggle -sampleSet = 30 to 50 -pointSet = Sym.normal(5, 2) -plot = Plot.dists([sampleSet, pointSet]) -fn(e) = e -{ - json: Danger.json([sampleSet, pointSet, plot, fn]), - jsonString: Danger.jsonString([pointSet, fn]), -} -``` - -## Profiling - -In 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. - -Caveats: - -- The code will execute slightly slower in profiler mode -- Imports won't be profiled correctly (but slow calls of imported functions will be highlighted) -- If the code is fast, you'll randomly get highlighted and unhighlighted results, because time measurement is imprecise - -If you're using Squiggle components in React, you can enable the profiler for any component that supports the `environment` prop with `environment={profile: true}`: - -```squiggle -dist = normal(0, 1) -list = List.upTo(1, 100000) -> List.length -``` - # 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. diff --git a/packages/website/public/llms/prompt.md b/packages/website/public/llms/basicPrompt.md similarity index 100% rename from packages/website/public/llms/prompt.md rename to packages/website/public/llms/basicPrompt.md diff --git a/packages/website/scripts/compileDocsForLLM.mts b/packages/website/scripts/compileDocsForLLM.mts index 762b9c3a82..78b59b053f 100755 --- a/packages/website/scripts/compileDocsForLLM.mts +++ b/packages/website/scripts/compileDocsForLLM.mts @@ -96,7 +96,7 @@ const allDocumentationItems = () => { .join("\n\n\n"); }; -const promptPageRaw = readFile("./public/llms/prompt.md"); +const basicPrompt = readFile("./public/llms/basicPrompt.md"); const styleGuideRaw = readFile("./public/llms/styleGuide.md"); const documentationBundlePage = async () => { const targetFilename = "./public/llms/documentationBundle.txt"; @@ -115,12 +115,18 @@ 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")); }; @@ -128,10 +134,9 @@ This file is auto-generated from the documentation files in the Squiggle reposit // const grammarContent = await getGrammarContent(); const guideContent = await getGuideContent(); const apiContent = allDocumentationItems(); - // const content = guideContent; const content = header + - promptPageRaw + + basicPrompt + "\n\n" + styleGuideRaw + "\n\n" + @@ -148,25 +153,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). -You can read this document in plaintext [here](/llms/prompt.md). +You can read this document in plaintext [here](/llms/BasicPrompt.md). --- `; - 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..50b2d79fb7 --- /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.txt). + +You can read this document in plaintext [here](/llms/BasicPrompt.md). + +--- + +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/_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" } From 349c291360ae64c64da96201f3d36250bd67bcf1 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Tue, 15 Oct 2024 11:12:00 -0700 Subject: [PATCH 09/13] Removed unused styleguide from AI --- packages/ai/src/steps/styleGuide.md | 368 ---------------------------- 1 file changed, 368 deletions(-) delete mode 100644 packages/ai/src/steps/styleGuide.md diff --git a/packages/ai/src/steps/styleGuide.md b/packages/ai/src/steps/styleGuide.md deleted file mode 100644 index e73f2a0dd9..0000000000 --- a/packages/ai/src/steps/styleGuide.md +++ /dev/null @@ -1,368 +0,0 @@ -# Squiggle Style Guide - -## Limitations - -- 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. - -## 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 PERT distributions. The uniform distribution is mainly good for physical simulations. -- 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: inputs.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. - -### 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 "foo(f)". - -```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)") - hourly_wage = 100 - - @name("Coffee Price ($/cup)") - coffee_price = 1 - {age, hourly_wage, health_value, coffee_price} -} -``` - -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)") - hourly_wage: 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 snake_case 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 - -- Squiggle does not support units directly, but you can add them 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. -- 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)") -number_of_humans = 7.8B - -@name("Net Benefit ($)") -net_benefit = 100M - -@name("Temperature (Β°C)") -temperature = 22 - -@name("Piano Tuners in New York City (2023)") -tuners = { - pianos_per_piano_tuners = 100 to 1k // (pianos per tuner) - pianos_in_nyc = 1k to 50k // (pianos) - pianos_in_nyc / pianos_per_piano_tuners -} -``` - -- Maintain Consistent Units. Ensure that related variables use the same units to prevent confusion and errors in calculations. - -```squiggle -@name("Distance to Mars (km)") -distance_mars = 225e6 - -@name("Distance to Venus (km)") -distance_venus = 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)") -us_population = 331.9 -``` - -Instead, use: - -```squiggle -@name("US Population") -us_population = 331.9M -``` - -More examples: - -```squiggle -// Correct representations -world_population = 7.8B -annual_budget = 1.2T -distance_to_sun = 149.6e6 // 149.6 million kilometers - -// Incorrect representations (avoid these) -world_population = 7800 // Unclear if it's 7800 or 7.8 billion -annual_budget = 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. - -### 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. - -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 -net_benefit(costs, benefits) = benefits - costs // not valid for @format() -net_benefit = 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. - -## 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.", -{ - optimal_cups, - result.net_benefit, -}, -] -``` - -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. - " -] -``` - -## 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 -``` - -## Summary Notebook - -- For models over 5 lines long, 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, final_answer}, - ... - ] -``` From 4a8d25af7211016a2b3978e1ac07ba83714ef57b Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Tue, 15 Oct 2024 11:17:03 -0700 Subject: [PATCH 10/13] Changed documentationBundle to markdown --- packages/ai/files/squiggleDocs.md | 16 +- .../public/llms/documentationBundle.md | 3249 +++++++++++++++++ .../website/scripts/compileDocsForLLM.mts | 10 +- .../src/pages/docs/Ecosystem/BasicPrompt.md | 2 +- .../pages/docs/Ecosystem/LanguageModels.md | 2 +- packages/website/turbo.json | 7 +- 6 files changed, 3263 insertions(+), 23 deletions(-) create mode 100644 packages/website/public/llms/documentationBundle.md diff --git a/packages/ai/files/squiggleDocs.md b/packages/ai/files/squiggleDocs.md index 66878798a9..e7fd6b5d7a 100644 --- a/packages/ai/files/squiggleDocs.md +++ b/packages/ai/files/squiggleDocs.md @@ -1,9 +1,3 @@ -# 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. - ---- - 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. @@ -1287,13 +1281,15 @@ Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distributio + ```squiggle normalMean = 10 normalStdDev = 2 logOfLognormal = log(lognormal(normalMean, normalStdDev)) [logOfLognormal, normal(normalMean, normalStdDev)] -```` +``` +
## To @@ -1301,18 +1297,20 @@ logOfLognormal = log(lognormal(normalMean, normalStdDev)) ```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]) -```` +``` +
diff --git a/packages/website/public/llms/documentationBundle.md b/packages/website/public/llms/documentationBundle.md new file mode 100644 index 0000000000..f1deba1c17 --- /dev/null +++ b/packages/website/public/llms/documentationBundle.md @@ -0,0 +1,3249 @@ +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/scripts/compileDocsForLLM.mts b/packages/website/scripts/compileDocsForLLM.mts index 78b59b053f..8077a4ec48 100755 --- a/packages/website/scripts/compileDocsForLLM.mts +++ b/packages/website/scripts/compileDocsForLLM.mts @@ -99,12 +99,7 @@ const allDocumentationItems = () => { const basicPrompt = readFile("./public/llms/basicPrompt.md"); const styleGuideRaw = readFile("./public/llms/styleGuide.md"); 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.md"; // We're not using this anymore, but leaving it here in case we want it again. const getGrammarContent = async () => { @@ -135,7 +130,6 @@ This file is auto-generated from the documentation files in the Squiggle reposit const guideContent = await getGuideContent(); const apiContent = allDocumentationItems(); const content = - header + basicPrompt + "\n\n" + styleGuideRaw + @@ -161,7 +155,7 @@ 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.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.md). You can read this document in plaintext [here](/llms/BasicPrompt.md). diff --git a/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md b/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md index 50b2d79fb7..f1fa926916 100644 --- a/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md +++ b/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md @@ -5,7 +5,7 @@ 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.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.md). You can read this document in plaintext [here](/llms/BasicPrompt.md). diff --git a/packages/website/src/pages/docs/Ecosystem/LanguageModels.md b/packages/website/src/pages/docs/Ecosystem/LanguageModels.md index f4520c889c..ffdd414e5f 100644 --- a/packages/website/src/pages/docs/Ecosystem/LanguageModels.md +++ b/packages/website/src/pages/docs/Ecosystem/LanguageModels.md @@ -17,7 +17,7 @@ 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.md) ## Instructions for use with Claude diff --git a/packages/website/turbo.json b/packages/website/turbo.json index 3a9343c730..99458eb0e8 100644 --- a/packages/website/turbo.json +++ b/packages/website/turbo.json @@ -7,10 +7,9 @@ "*.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.md", + "src/pages/docs/Ecosystem/BasicPrompt.md", + "src/pages/docs/Api/*" ] }, "lint": { From 0454db69c0d5aae8d09c88d8c552f06ba73c3219 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Tue, 15 Oct 2024 11:29:38 -0700 Subject: [PATCH 11/13] Converted .md to .markdown --- .../llms/{basicPrompt.md => basicPrompt.markdown} | 0 ...mentationBundle.md => documentationBundle.markdown} | 0 .../public/llms/{styleGuide.md => styleGuide.markdown} | 0 packages/website/scripts/compileDocsForLLM.mts | 10 +++++----- .../website/src/pages/docs/Ecosystem/BasicPrompt.md | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) rename packages/website/public/llms/{basicPrompt.md => basicPrompt.markdown} (100%) rename packages/website/public/llms/{documentationBundle.md => documentationBundle.markdown} (100%) rename packages/website/public/llms/{styleGuide.md => styleGuide.markdown} (100%) diff --git a/packages/website/public/llms/basicPrompt.md b/packages/website/public/llms/basicPrompt.markdown similarity index 100% rename from packages/website/public/llms/basicPrompt.md rename to packages/website/public/llms/basicPrompt.markdown diff --git a/packages/website/public/llms/documentationBundle.md b/packages/website/public/llms/documentationBundle.markdown similarity index 100% rename from packages/website/public/llms/documentationBundle.md rename to packages/website/public/llms/documentationBundle.markdown diff --git a/packages/website/public/llms/styleGuide.md b/packages/website/public/llms/styleGuide.markdown similarity index 100% rename from packages/website/public/llms/styleGuide.md rename to packages/website/public/llms/styleGuide.markdown diff --git a/packages/website/scripts/compileDocsForLLM.mts b/packages/website/scripts/compileDocsForLLM.mts index 8077a4ec48..436f0360be 100755 --- a/packages/website/scripts/compileDocsForLLM.mts +++ b/packages/website/scripts/compileDocsForLLM.mts @@ -96,10 +96,10 @@ const allDocumentationItems = () => { .join("\n\n\n"); }; -const basicPrompt = readFile("./public/llms/basicPrompt.md"); -const styleGuideRaw = readFile("./public/llms/styleGuide.md"); +const basicPrompt = readFile("./public/llms/basicPrompt.markdown"); +const styleGuideRaw = readFile("./public/llms/styleGuide.markdown"); const documentationBundlePage = async () => { - const targetFilename = "./public/llms/documentationBundle.md"; + 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 () => { @@ -155,9 +155,9 @@ 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.md). +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.md). +You can read this document in plaintext [here](/llms/BasicPrompt.markdown). --- diff --git a/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md b/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md index f1fa926916..58139e584b 100644 --- a/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md +++ b/packages/website/src/pages/docs/Ecosystem/BasicPrompt.md @@ -5,9 +5,9 @@ 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.md). +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.md). +You can read this document in plaintext [here](/llms/BasicPrompt.markdown). --- From 5785393eecfc004214ea4c6d56d3188bf885882a Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Tue, 15 Oct 2024 11:31:29 -0700 Subject: [PATCH 12/13] Quick fix --- packages/website/src/pages/docs/Ecosystem/LanguageModels.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/website/src/pages/docs/Ecosystem/LanguageModels.md b/packages/website/src/pages/docs/Ecosystem/LanguageModels.md index ffdd414e5f..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.md) +[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 From 8c5b6aacb5a73d8b33dda9ea2091fb84311348e5 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Tue, 15 Oct 2024 11:40:27 -0700 Subject: [PATCH 13/13] Added more markdown files to turbo.json --- .../website/public/llms/documentationBundle.markdown | 10 +++++++--- packages/website/turbo.json | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/website/public/llms/documentationBundle.markdown b/packages/website/public/llms/documentationBundle.markdown index f1deba1c17..e7fd6b5d7a 100644 --- a/packages/website/public/llms/documentationBundle.markdown +++ b/packages/website/public/llms/documentationBundle.markdown @@ -1281,13 +1281,15 @@ Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distributio + ```squiggle normalMean = 10 normalStdDev = 2 logOfLognormal = log(lognormal(normalMean, normalStdDev)) [logOfLognormal, normal(normalMean, normalStdDev)] -```` +``` + ## To @@ -1295,18 +1297,20 @@ logOfLognormal = log(lognormal(normalMean, normalStdDev)) ```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]) -```` +``` +
diff --git a/packages/website/turbo.json b/packages/website/turbo.json index 99458eb0e8..a15f55979e 100644 --- a/packages/website/turbo.json +++ b/packages/website/turbo.json @@ -7,7 +7,9 @@ "*.tsbuildinfo", ".next", "!.next/cache", - "public/llms/documentationBundle.md", + "public/llms/documentationBundle.markdown", + "public/llms/styleGuide.markdown", + "public/llms/BasicPrompt.markdown", "src/pages/docs/Ecosystem/BasicPrompt.md", "src/pages/docs/Api/*" ]