cheatah
Source

tests/purrc/app_montecarlo_test.cpp

1// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).
2// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.
3// Sophisticated multi-module system-level test: a small "Monte Carlo
4// simulation" app that only passes if `random`, `math`, `statistics`, and `io`
5// all cooperate end to end (purrc + the C++ backend + the runtime + the linked
6// stdlib).
7//
8// The program:
9// - estimates pi by sampling points in the unit square and counting those in
10// the quarter unit circle (random.random + math.sqrt),
11// - asserts reproducibility by running the same seeded estimate twice and
12// comparing for equality,
13// - summarizes seeded gaussian draws with statistics.mean/stdev (math.round
14// keeps the printed values stable),
15// - exercises random.randint / random.uniform as well.
16//
17// Everything is seeded, so the printed numbers are reproducible byte-for-byte.
18// The expected stdout below was verified by compiling and running the program
19// three times (twice from one build, once from a fresh compile) and confirming
20// identical output before hardcoding.
22#include "e2e_harness.hpp"
25TEST(SystemApps, MonteCarlo) {
26 e2e::expect_e2e("app_montecarlo", R"PURR(import io
27import math
28import random
29import statistics
31# Monte Carlo estimate of pi: sample points in the unit square and count
32# the fraction that land inside the quarter unit circle. Seeded RNG makes
33# the estimate fully reproducible, so the printed result is deterministic.
34fn estimate_pi(seed, n) {
35 random.seed(seed)
36 let inside = 0
37 let i = 0
38 for i in range(0, n) {
39 let x = random.random()
40 let y = random.random()
41 if math.sqrt(x * x + y * y) <= 1.0 {
42 inside = inside + 1
43 }
44 }
45 return 4.0 * inside / n
48let n = 50000
50# (1) Deterministic pi estimate (rounded so the printed value is stable).
51let pi_hat = estimate_pi(2024, n)
52let pi_rounded = math.round(pi_hat * 1000.0) / 1000.0
54# (2) Reproducibility: same seed twice => byte-identical estimate.
55let pi_again = estimate_pi(2024, n)
56let reproducible = pi_hat == pi_again
58# (3) Summarize 10 seeded gauss draws with `statistics`.
59let samples = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
60random.seed(99)
61let k = 0
62for k in range(0,10) {
63 samples[k] = random.gauss(100.0, 15.0)
65let mu = math.round(statistics.mean(samples) * 100.0) / 100.0
66let sd = math.round(statistics.stdev(samples) * 100.0) / 100.0
68# (4) randint / uniform sanity, also seeded.
69random.seed(7)
70let d1 = random.randint(1, 6)
71let d2 = random.randint(1, 6)
72let u = math.round(random.uniform(0.0, 1.0) * 1000.0) / 1000.0
74io.print("pi ~=", pi_rounded)
75io.print("reproducible:", reproducible)
76io.print("abs err <= 0.05:", math.fabs(pi_hat -3.14159265) <= 0.05)
77io.print("gauss mean:", mu, "stdev:", sd)
78io.print("dice:", d1, d2, "uniform:", u)
79)PURR",
80 "pi ~= 3.141\n"
81 "reproducible: True\n"
82 "abs err <= 0.05: True\n"
83 "gauss mean: 94.14 stdev: 14.84\n"
84 "dice: 4 1 uniform: 0.117\n");