Yichus/Simulation
Builtin package (resource simulation.bosatsu).
This is the complete package source used by this build. The export
list names its public API; definitions below give the types and behavior.
New to the API? Start with Make a calculator or Build an API, then use this page to look up a definition.
package Yichus/Simulation
from Bosatsu/Predef import Int, String, Bool, List, Option, Some, None
from Yichus/Num/Float64 import Float64, int_to_Float64
from Yichus/Geometry import Line, Point
export (
Discreteness(),
ChartRange(),
XAxisMode(),
OutputFormat(),
WidgetRepr(),
InputSpec(),
BoundInput(),
BoundOutput(),
InputEntry(),
OutputEntry(),
AssumptionConfig(),
SimBuilder(),
SimConfig(),
InputHandle,
OutputHandle,
value,
int_slider,
int_number_input,
int_dropdown,
bool_checkbox,
bool_dropdown,
string_dropdown,
bind_discrete,
bind_continuous,
outputs,
currency_output,
currency_output_chartable,
number_output,
number_output_chartable,
percent_output,
percent_output_chartable,
sim,
LineCurve(),
SampledCurve(),
HLineCurve(),
IntersectionMark(),
AreaBetweenMark(),
PointMark(),
RegionMark(),
GraphXBound(),
AreaSide(),
Dataset(),
SeriesLayer(),
Graph(),
curve,
sampled_curve,
hline_at,
intersection,
x_at,
x_of,
on_line,
on_hline,
area_between_marks,
point_mark,
region,
dataset,
smoothing,
regression,
projection,
graph,
graph_output
)
exposes (Yichus/Num/Float64, Yichus/Geometry)
# ============================================================================
# Discreteness — Discrete drives bar charts, Continuous drives line charts.
# Required at every bind site; no default.
# ============================================================================
enum Discreteness:
Discrete
Continuous
# ============================================================================
# ChartRange — explicit sample count and optional override of the x-axis
# input's slider min/max. None means "use the input's slider bounds."
# ============================================================================
struct ChartRange(
samples: Int,
min_override: Option[Int],
max_override: Option[Int]
)
# ============================================================================
# Opaque InputHandle — constructor not re-exported, so user code can hold
# handles returned from bind_* helpers but cannot manufacture them.
# Stores the name (runtime key) and the typed default value.
# ============================================================================
struct InputHandle[a](name: String, default: a)
# Accessor that extracts the typed default from a handle.
# Exists so user-side `calculate(value(income), value(deductions))` works
# without exposing the handle's internal layout.
def value[a](h: InputHandle[a]) -> a:
InputHandle(_, d) = h
d
# ============================================================================
# XAxisMode — typed cross-references via InputHandle[Int].
# The Bosatsu typechecker enforces:
# - referenced handle must be in lexical scope (no typos)
# - referenced handle must be Int-valued (no Bool/String x-axes)
# These were the responsibilities of the removed Stage 1c validation pass.
# ============================================================================
enum XAxisMode:
XAxisDisallowed
XAxisPinned(input: InputHandle[Int], range: ChartRange)
XAxisDefault(
default_input: InputHandle[Int],
eligible: List[InputHandle[Int]],
range: ChartRange
)
XAxisOptional(
default_input: Option[InputHandle[Int]],
eligible: List[InputHandle[Int]],
range: ChartRange
)
# ============================================================================
# OutputFormat — the three concrete display formats. Chart/Table are gone;
# charting is handled per-output via XAxisMode.
# ============================================================================
enum OutputFormat:
Currency
Percent
Number
GraphKind
# ============================================================================
# OutputHandle — opaque, defined for symmetry with InputHandle.
# Currently constructed internally but not surfaced through output helpers
# because outputs aren't cross-referenced today.
# ============================================================================
struct OutputHandle[a](name: String)
# ============================================================================
# WidgetRepr — monomorphic visual representation. Carries only what the
# JS renderer needs; defaults and value-type info live in InputSpec/BoundInput.
# ============================================================================
enum WidgetRepr:
WSlider(min: Int, max: Int, step: Int)
WNumberInput(min: Int, max: Int, step: Int)
WCheckbox
WDropdownInt(options: List[(String, Int)])
WDropdownBool(options: List[(String, Bool)])
WDropdownString(options: List[(String, String)])
# ============================================================================
# InputSpec[a] — the typed bundle produced by smart constructors. Pairs the
# visual WidgetRepr with the typed default and the bosatsu type repr string.
# `bosatsu_type_repr` is a typed payload concatenated into pin formula
# synthesis later (Stage 3); never inspected via match/switch.
# ============================================================================
struct InputSpec[a](
bosatsu_type_repr: String,
default: a,
widget_repr: WidgetRepr
)
# ============================================================================
# Smart constructors — each returns an InputSpec[a] for a fixed `a`.
# The phantom-parameter loophole that allowed `Slider(...): Widget[Bool]`
# is closed: int_slider literally cannot return InputSpec[Bool].
# ============================================================================
def int_slider(min_v: Int, max_v: Int, step: Int, default: Int) -> InputSpec[Int]:
InputSpec("Int", default, WSlider(min_v, max_v, step))
def int_number_input(min_v: Int, max_v: Int, step: Int, default: Int) -> InputSpec[Int]:
InputSpec("Int", default, WNumberInput(min_v, max_v, step))
def int_dropdown(options: List[(String, Int)], default: Int) -> InputSpec[Int]:
InputSpec("Int", default, WDropdownInt(options))
def bool_checkbox(default: Bool) -> InputSpec[Bool]:
InputSpec("Bool", default, WCheckbox)
def bool_dropdown(options: List[(String, Bool)], default: Bool) -> InputSpec[Bool]:
InputSpec("Bool", default, WDropdownBool(options))
def string_dropdown(options: List[(String, String)], default: String) -> InputSpec[String]:
InputSpec("String", default, WDropdownString(options))
# ============================================================================
# BoundInput[a] — the typed bundle stored per input in SimBuilder.
# The `default: a` field ties the value type to the rest of the record.
# ============================================================================
struct BoundInput[a](
bosatsu_type_repr: String,
default: a,
widget_repr: WidgetRepr,
discreteness: Discreteness,
name: String,
label: String,
handle: InputHandle[a]
)
# ============================================================================
# BoundOutput[a] — similar typed bundle for outputs. Carries the typed
# default value so the existential pack matches across the output list.
# ============================================================================
struct BoundOutput[a](
label: String,
format: OutputFormat,
primary: Bool,
x_axis: XAxisMode,
default_value: a,
handle: OutputHandle[a]
)
# ============================================================================
# Existential wrappers — `exists a.` lives inside these single-field structs
# so SimBuilder's lists are heterogeneous in `a`. Same shape as
# EntityHandler.ops in dynamo-transform.bosatsu.
# ============================================================================
struct InputEntry(payload: exists a. BoundInput[a])
struct OutputEntry(payload: exists a. BoundOutput[a])
# ============================================================================
# Configuration for "What if?" assumption toggles.
# ============================================================================
struct AssumptionConfig(
name: String,
description: String,
variants: List[(String, String)]
)
# ============================================================================
# SimBuilder — accumulates typed input/output entries via continuation
# passing in bind_discrete / bind_continuous / outputs.
# ============================================================================
struct SimBuilder(
inputs: List[(String, InputEntry)],
outputs: List[(String, OutputEntry)]
)
# ============================================================================
# SimConfig — top-level metadata. `sweeps` is gone; chartable outputs
# subsume it per-output via XAxisMode.
# ============================================================================
struct SimConfig(
name: String,
description: String,
package_name: String,
function_name: String,
inputs: List[(String, InputEntry)],
outputs: List[(String, OutputEntry)],
assumptions: List[AssumptionConfig],
snippet_max_lines: Int
)
# ============================================================================
# bind_discrete — polymorphic in `a`. Builds the InputHandle, hands it to
# the continuation, then prepends the BoundInput to SimBuilder.inputs.
# ============================================================================
def bind_discrete[a](
name: String,
label: String,
spec: InputSpec[a],
next: InputHandle[a] -> SimBuilder
) -> SimBuilder:
InputSpec(type_repr, default_v, widget_repr) = spec
handle = InputHandle(name, default_v)
built = next(handle)
match built:
case SimBuilder(rest_inputs, rest_outputs):
bound = BoundInput(type_repr, default_v, widget_repr, Discrete, name, label, handle)
SimBuilder(
[(name, InputEntry(bound)), *rest_inputs],
rest_outputs
)
# ============================================================================
# bind_continuous — Int-restricted. Continuous only makes sense for numeric
# inputs; trying to mark a Bool/String input as Continuous is a type error.
# ============================================================================
def bind_continuous(
name: String,
label: String,
spec: InputSpec[Int],
next: InputHandle[Int] -> SimBuilder
) -> SimBuilder:
InputSpec(type_repr, default_v, widget_repr) = spec
handle = InputHandle(name, default_v)
built = next(handle)
match built:
case SimBuilder(rest_inputs, rest_outputs):
bound = BoundInput(type_repr, default_v, widget_repr, Continuous, name, label, handle)
SimBuilder(
[(name, InputEntry(bound)), *rest_inputs],
rest_outputs
)
# ============================================================================
# outputs — receives the calculator's result via continuation, expects a
# list of output entries built from it.
# ============================================================================
def outputs[r](
result: r,
build: r -> List[(String, OutputEntry)]
) -> SimBuilder:
SimBuilder([], build(result))
# ============================================================================
# Output helpers — each produces a (name, OutputEntry) pair. The non-
# chartable variants emit XAxisDisallowed; the *_chartable variants take an
# explicit XAxisMode. The `*_handle` variant could later expose the handle
# but today's outputs aren't cross-referenced anywhere.
# ============================================================================
def currency_output[a](
name: String,
label: String,
val: a,
primary: Bool
) -> (String, OutputEntry):
handle = OutputHandle(name)
bound = BoundOutput(label, Currency, primary, XAxisDisallowed, val, handle)
(name, OutputEntry(bound))
def currency_output_chartable[a](
name: String,
label: String,
val: a,
primary: Bool,
x_axis: XAxisMode
) -> (String, OutputEntry):
handle = OutputHandle(name)
bound = BoundOutput(label, Currency, primary, x_axis, val, handle)
(name, OutputEntry(bound))
def number_output[a](
name: String,
label: String,
val: a,
primary: Bool
) -> (String, OutputEntry):
handle = OutputHandle(name)
bound = BoundOutput(label, Number, primary, XAxisDisallowed, val, handle)
(name, OutputEntry(bound))
def number_output_chartable[a](
name: String,
label: String,
val: a,
primary: Bool,
x_axis: XAxisMode
) -> (String, OutputEntry):
handle = OutputHandle(name)
bound = BoundOutput(label, Number, primary, x_axis, val, handle)
(name, OutputEntry(bound))
def percent_output[a](
name: String,
label: String,
val: a,
primary: Bool
) -> (String, OutputEntry):
handle = OutputHandle(name)
bound = BoundOutput(label, Percent, primary, XAxisDisallowed, val, handle)
(name, OutputEntry(bound))
def percent_output_chartable[a](
name: String,
label: String,
val: a,
primary: Bool,
x_axis: XAxisMode
) -> (String, OutputEntry):
handle = OutputHandle(name)
bound = BoundOutput(label, Percent, primary, x_axis, val, handle)
(name, OutputEntry(bound))
# ============================================================================
# sim — assembles the top-level SimConfig from a SimBuilder and metadata.
# `sweeps` parameter is gone; chartable outputs subsume the use case.
# ============================================================================
def sim(
name: String,
description: String,
package_name: String,
function_name: String,
builder: SimBuilder,
assumptions: List[AssumptionConfig],
snippet_max_lines: Int
) -> SimConfig:
match builder:
case SimBuilder(input_list, output_list):
SimConfig(
name,
description,
package_name,
function_name,
input_list,
output_list,
assumptions,
snippet_max_lines
)
# ============================================================================
# Graph output — multi-curve charts with auto and explicit marks.
# Auto helpers (`intersection`, `area_between_marks`) accept only Line-backed
# sides (`LineCurve` / `HLineCurve`), so sampled curves cannot be passed to a
# solvable mark (unrepresentable). Explicit helpers (`point_mark`, `region`)
# carry an author derivation; the simapp checker verifies that derivation's
# Matchless dataflow reaches the referenced curves.
# ============================================================================
struct LineCurve(
id: String,
label: String,
line: Line
)
struct SampledCurve(
id: String,
label: String,
samples: List[Point]
)
struct IntersectionMark(
id: String,
label: String,
a: LineCurve,
b: LineCurve
)
struct HLineCurve(
id: String,
label: String,
mark: IntersectionMark
)
enum GraphXBound:
XAt(x: Float64)
XOf(mark: IntersectionMark)
# Area sides are Line-backed only: a compute Line, or a horizontal through
# an auto intersection's y. SampledCurve cannot inhabit this type.
enum AreaSide:
OfLine(c: LineCurve)
OfHLine(c: HLineCurve)
struct AreaBetweenMark(
id: String,
label: String,
upper: AreaSide,
lower: AreaSide,
from_x: GraphXBound,
to_x: GraphXBound
)
struct PointMark(
id: String,
label: String,
point: Point,
along: SampledCurve
)
struct RegionMark(
id: String,
label: String,
area: Float64,
along: SampledCurve
)
# A named point series from the compute Result, drawn as scatter dots.
# Layers are functions of a Dataset plus optional slider parameters.
struct Dataset(
id: String,
label: String,
points: List[Point]
)
enum SeriesLayer:
Smoothing(id: String, label: String, data: Dataset, window: InputHandle[Int])
Regression(id: String, label: String, data: Dataset)
# A dashed overlay of `data`. The points are whatever the compute
# program produced — a vintage forecast, an exponential path, OLS
# samples from project_linear, etc. The layer does not pick a form.
Projection(id: String, label: String, data: Dataset)
struct Graph(
graph_id: String,
title: String,
x_label: String,
y_label: String,
x_min: Float64,
x_max: Float64,
line_curves: List[LineCurve],
hline_curves: List[HLineCurve],
sampled_curves: List[SampledCurve],
intersections: List[IntersectionMark],
areas: List[AreaBetweenMark],
point_marks: List[PointMark],
regions: List[RegionMark],
datasets: List[Dataset],
layers: List[SeriesLayer]
)
def curve(id: String, label: String, line: Line) -> LineCurve:
LineCurve(id, label, line)
def sampled_curve(id: String, label: String, samples: List[Point]) -> SampledCurve:
SampledCurve(id, label, samples)
def hline_at(id: String, label: String, mark: IntersectionMark) -> HLineCurve:
HLineCurve(id, label, mark)
def intersection(id: String, label: String, a: LineCurve, b: LineCurve) -> IntersectionMark:
IntersectionMark(id, label, a, b)
def x_at(x: Int) -> GraphXBound:
XAt(int_to_Float64(x))
def x_of(mark: IntersectionMark) -> GraphXBound:
XOf(mark)
def on_line(c: LineCurve) -> AreaSide:
OfLine(c)
def on_hline(c: HLineCurve) -> AreaSide:
OfHLine(c)
def area_between_marks(
id: String,
label: String,
upper: AreaSide,
lower: AreaSide,
from_x: GraphXBound,
to_x: GraphXBound
) -> AreaBetweenMark:
AreaBetweenMark(id, label, upper, lower, from_x, to_x)
def point_mark(
id: String,
label: String,
point: Point,
along: SampledCurve
) -> PointMark:
PointMark(id, label, point, along)
def region(
id: String,
label: String,
area: Float64,
along: SampledCurve
) -> RegionMark:
RegionMark(id, label, area, along)
def dataset(id: String, label: String, points: List[Point]) -> Dataset:
Dataset(id, label, points)
def smoothing(
id: String,
label: String,
data: Dataset,
window: InputHandle[Int]
) -> SeriesLayer:
Smoothing(id, label, data, window)
def regression(id: String, label: String, data: Dataset) -> SeriesLayer:
Regression(id, label, data)
def projection(id: String, label: String, data: Dataset) -> SeriesLayer:
Projection(id, label, data)
def graph(
graph_id: String,
title: String,
x_label: String,
y_label: String,
x_min: Int,
x_max: Int,
line_curves: List[LineCurve],
hline_curves: List[HLineCurve],
sampled_curves: List[SampledCurve],
intersections: List[IntersectionMark],
areas: List[AreaBetweenMark],
point_marks: List[PointMark],
regions: List[RegionMark],
datasets: List[Dataset],
layers: List[SeriesLayer]
) -> Graph:
Graph(
graph_id,
title,
x_label,
y_label,
int_to_Float64(x_min),
int_to_Float64(x_max),
line_curves,
hline_curves,
sampled_curves,
intersections,
areas,
point_marks,
regions,
datasets,
layers
)
def graph_output(
name: String,
label: String,
g: Graph,
primary: Bool
) -> (String, OutputEntry):
handle = OutputHandle(name)
bound = BoundOutput(label, GraphKind, primary, XAxisDisallowed, g, handle)
(name, OutputEntry(bound))