%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.12%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22httpx%22%2C%0A%23%20%20%20%20%20%22marimo%22%2C%0A%23%20%20%20%20%20%22openai%22%2C%0A%23%20%20%20%20%20%22python-dotenv%22%2C%0A%23%20%20%20%20%20%22torch%22%2C%0A%23%20%20%20%20%20%22transformers%22%2C%0A%23%20%5D%0A%23%0A%23%20%5Btool.marimo.runtime%5D%0A%23%20watcher_on_save%20%3D%20%22autorun%22%0A%23%0A%23%20%5Btool.marimo.save%5D%0A%23%20autosave%20%3D%20%22off%22%0A%23%20%2F%2F%2F%0A%22%22%22BYOJ%20talk%3A%20live%20demo.%20Turn%20any%20LLM%20into%20a%20Jev%2C%20then%20do%20it%20offline%20on%20a%20tiny%20model.%22%22%22%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.1%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20math%0A%20%20%20%20import%20os%0A%20%20%20%20import%20random%0A%20%20%20%20import%20time%0A%20%20%20%20from%20dataclasses%20import%20dataclass%0A%0A%20%20%20%20import%20httpx%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20torch%0A%20%20%20%20from%20dotenv%20import%20load_dotenv%0A%20%20%20%20from%20openai%20import%20OpenAI%0A%20%20%20%20from%20transformers%20import%20AutoModelForCausalLM%2C%20AutoTokenizer%0A%0A%20%20%20%20load_dotenv(mo.notebook_dir()%20%2F%20%22.env%22)%20%20%23%20OPENROUTER_API_KEY%2C%20gitignored%0A%20%20%20%20autorun%20%3D%20os.environ.get(%22DEMO_AUTORUN%22)%20%3D%3D%20%221%22%20%20%23%20run%20everything%20without%20pressing%20buttons%20(for%20exports)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20AutoModelForCausalLM%2C%0A%20%20%20%20%20%20%20%20AutoTokenizer%2C%0A%20%20%20%20%20%20%20%20OpenAI%2C%0A%20%20%20%20%20%20%20%20autorun%2C%0A%20%20%20%20%20%20%20%20dataclass%2C%0A%20%20%20%20%20%20%20%20httpx%2C%0A%20%20%20%20%20%20%20%20math%2C%0A%20%20%20%20%20%20%20%20mo%2C%0A%20%20%20%20%20%20%20%20os%2C%0A%20%20%20%20%20%20%20%20random%2C%0A%20%20%20%20%20%20%20%20time%2C%0A%20%20%20%20%20%20%20%20torch%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Bring%20Your%20Own%20Jev%3A%20live%20demo%0A%0A%20%20%20%20Ask%20any%20question%20about%20any%20text.%20Three%20models%20answer%20with%20a%20**probability%20for%20every%20option**%2C%20read%20from%20a%20**single%20token**%3A%0A%20%20%20%20an%20LLM%20through%20OpenRouter%2C%20the%20real%20Jev%2C%20and%20a%20tiny%20model%20running%20offline%20on%20this%20laptop.%0A%0A%20%20%20%20The%20code%20comes%20first%20(click%20a%20cell%20to%20open%20it)%2C%20then%20three%20experiments.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(dataclass%2C%20math)%3A%0A%20%20%20%20%40dataclass%0A%20%20%20%20class%20Choice%3A%0A%20%20%20%20%20%20%20%20instructions%3A%20str%0A%20%20%20%20%20%20%20%20options%3A%20list%5Bstr%5D%0A%0A%0A%20%20%20%20def%20prompt(state%3A%20str%2C%20choice%3A%20Choice)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20options%20%3D%20%22%5Cn%22.join(f%22%7Bi%7D)%20%7Boption%7D%22%20for%20i%2C%20option%20in%20enumerate(choice.options))%0A%20%20%20%20%20%20%20%20return%20f'Message%3A%5Cn%22%22%22%5Cn%7Bstate%7D%5Cn%22%22%22%5Cn%5Cn%7Bchoice.instructions%7D%5Cn%7Boptions%7D%5Cn%5CnAnswer%20with%20a%20single%20digit.'%0A%0A%0A%20%20%20%20def%20softmax(options%3A%20list%5Bstr%5D%2C%20logits%3A%20list%5Bfloat%5D)%20-%3E%20dict%5Bstr%2C%20float%5D%3A%0A%20%20%20%20%20%20%20%20exps%20%3D%20%5Bmath.exp(x)%20for%20x%20in%20logits%5D%0A%20%20%20%20%20%20%20%20return%20%7Boption%3A%20e%20%2F%20sum(exps)%20for%20option%2C%20e%20in%20zip(options%2C%20exps)%7D%0A%0A%20%20%20%20return%20Choice%2C%20prompt%2C%20softmax%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(OpenAI%2C%20math%2C%20os%2C%20prompt%2C%20softmax)%3A%0A%20%20%20%20client%20%3D%20OpenAI(base_url%3D%22https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1%22%2C%20api_key%3Dos.environ.get(%22OPENROUTER_API_KEY%22))%0A%0A%0A%20%20%20%20def%20jev_api(state%2C%20choice%2C%20model%3D%22openai%2Fgpt-4o-mini%22)%3A%0A%20%20%20%20%20%20%20%20response%20%3D%20client.chat.completions.create(%0A%20%20%20%20%20%20%20%20%20%20%20%20model%3Dmodel%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20messages%3D%5B%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20prompt(state%2C%20choice)%7D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_tokens%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20logprobs%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20top_logprobs%3D20%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20extra_body%3D%7B%22provider%22%3A%20%7B%22require_parameters%22%3A%20True%7D%7D%2C%20%20%23%20only%20providers%20that%20return%20logprobs%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20top%20%3D%20%7Bt.token%3A%20t.logprob%20for%20t%20in%20response.choices%5B0%5D.logprobs.content%5B0%5D.top_logprobs%7D%0A%20%20%20%20%20%20%20%20logits%20%3D%20%5Btop.get(str(i)%2C%20-math.inf)%20for%20i%20in%20range(len(choice.options))%5D%0A%20%20%20%20%20%20%20%20return%20softmax(choice.options%2C%20logits)%0A%0A%20%20%20%20return%20(jev_api%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(httpx%2C%20os)%3A%0A%20%20%20%20def%20jev_typesafe(state%2C%20choice)%3A%0A%20%20%20%20%20%20%20%20response%20%3D%20httpx.post(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fopenrouter.ai%2Fapi%2Falpha%2Fdecisions%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20headers%3D%7B%22Authorization%22%3A%20f%22Bearer%20%7Bos.environ.get('OPENROUTER_API_KEY')%7D%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20json%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20%22~typesafe%2Fjev-latest%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22state%22%3A%20state%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22questions%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22answer%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22choice%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22instructions%22%3A%20choice.instructions%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22criteria%22%3A%20%7Boption%3A%20option%20for%20option%20in%20choice.options%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20timeout%3D30%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20response.raise_for_status()%0A%20%20%20%20%20%20%20%20return%20response.json()%5B%22answers%22%5D%5B%22answer%22%5D%5B%22probabilities%22%5D%0A%0A%20%20%20%20return%20(jev_typesafe%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(AutoModelForCausalLM%2C%20AutoTokenizer%2C%20torch)%3A%0A%20%20%20%20device%20%3D%20%22mps%22%20if%20torch.backends.mps.is_available()%20else%20%22cpu%22%0A%20%20%20%20tokenizer%20%3D%20AutoTokenizer.from_pretrained(%22Qwen%2FQwen3-1.7B%22)%0A%20%20%20%20model%20%3D%20AutoModelForCausalLM.from_pretrained(%22Qwen%2FQwen3-1.7B%22%2C%20dtype%3Dtorch.float16).to(device).eval()%0A%20%20%20%20return%20device%2C%20model%2C%20tokenizer%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(device%2C%20model%2C%20prompt%2C%20softmax%2C%20tokenizer%2C%20torch)%3A%0A%20%20%20%20%40torch.no_grad()%0A%20%20%20%20def%20jev_local(state%2C%20choice)%3A%0A%20%20%20%20%20%20%20%20messages%20%3D%20%5B%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20prompt(state%2C%20choice)%7D%5D%0A%20%20%20%20%20%20%20%20text%20%3D%20tokenizer.apply_chat_template(messages%2C%20tokenize%3DFalse%2C%20add_generation_prompt%3DTrue%2C%20enable_thinking%3DFalse)%0A%20%20%20%20%20%20%20%20inputs%20%3D%20tokenizer(text%2C%20return_tensors%3D%22pt%22).to(device)%0A%0A%20%20%20%20%20%20%20%20next_token_logits%20%3D%20model(**inputs).logits%5B0%2C%20-1%5D%0A%20%20%20%20%20%20%20%20digits%20%3D%20%5Btokenizer.convert_tokens_to_ids(str(i))%20for%20i%20in%20range(len(choice.options))%5D%0A%20%20%20%20%20%20%20%20return%20softmax(choice.options%2C%20next_token_logits%5Bdigits%5D.tolist())%0A%0A%20%20%20%20return%20(jev_local%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(jev_api%2C%20jev_local%2C%20jev_typesafe%2C%20mo%2C%20time)%3A%0A%20%20%20%20def%20run_all(state%2C%20choice%2C%20api_model)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Run%20every%20backend%20on%20one%20decision%3A%20%7Bname%3A%20(probabilities%20or%20error%2C%20ms)%7D.%22%22%22%0A%20%20%20%20%20%20%20%20backends%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Qwen3-1.7B%20(offline)%22%3A%20lambda%3A%20jev_local(state%2C%20choice)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20api_model%3A%20lambda%3A%20jev_api(state%2C%20choice%2C%20api_model)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Jev%22%3A%20lambda%3A%20jev_typesafe(state%2C%20choice)%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20results%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20for%20name%2C%20run%20in%20backends.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20start%20%3D%20time.perf_counter()%0A%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20results%5Bname%5D%20%3D%20(run()%2C%20(time.perf_counter()%20-%20start)%20*%201000)%0A%20%20%20%20%20%20%20%20%20%20%20%20except%20Exception%20as%20e%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20results%5Bname%5D%20%3D%20(f%22error%3A%20%7Be%7D%22%2C%20None)%0A%20%20%20%20%20%20%20%20return%20results%0A%0A%0A%20%20%20%20def%20bars(probs)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Probabilities%20as%20a%20small%20horizontal%20bar%20chart.%22%22%22%0A%20%20%20%20%20%20%20%20top%20%3D%20max(probs%2C%20key%3Dprobs.get)%0A%20%20%20%20%20%20%20%20rows%20%3D%20%22%22.join(%0A%20%20%20%20%20%20%20%20%20%20%20%20f'''%3Cdiv%20style%3D%22display%3Aflex%3Balign-items%3Acenter%3Bgap%3A0.6em%3Bmargin%3A0.2em%200%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D%22width%3A8em%3Btext-align%3Aright%22%3E%7Boption%7D%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D%22flex%3A1%3Bbackground%3A%23f3f4f6%3Bborder-radius%3A4px%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D%22width%3A%7Bp%20*%20100%3A.1f%7D%25%3Bheight%3A1.2em%3Bborder-radius%3A4px%3Bbackground%3A%7B%22%23a626a4%22%20if%20option%20%3D%3D%20top%20else%20%22%23d8b4d8%22%7D%22%3E%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D%22width%3A3em%3Bfont-weight%3A700%22%3E%7Bp%3A.0%25%7D%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E'''%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20option%2C%20p%20in%20probs.items()%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20return%20mo.Html(rows)%0A%0A%20%20%20%20return%20bars%2C%20run_all%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%201.%20Try%20it%0A%0A%20%20%20%20Type%20a%20message%2C%20a%20question%2C%20and%20the%20options.%20Press%20**Run**.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20state%20%3D%20mo.ui.text_area(value%3D%22My%20manager%20keeps%20stressing%20me%20out.%22%2C%20label%3D%22State%22%2C%20full_width%3DTrue)%0A%20%20%20%20question%20%3D%20mo.ui.text(value%3D%22Is%20this%20about%20health%20and%20wellness%3F%22%2C%20label%3D%22Question%22%2C%20full_width%3DTrue)%0A%20%20%20%20options%20%3D%20mo.ui.text(value%3D%22no%2C%20yes%22%2C%20label%3D%22Options%22%2C%20full_width%3DTrue)%0A%20%20%20%20api_model%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20%5B%22openai%2Fgpt-4o-mini%22%2C%20%22google%2Fgemma-4-31b-it%22%5D%2C%0A%20%20%20%20%20%20%20%20value%3D%22openai%2Fgpt-4o-mini%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22LLM%22%2C%0A%20%20%20%20)%0A%20%20%20%20run%20%3D%20mo.ui.run_button(label%3D%22Run%22)%0A%20%20%20%20mo.vstack(%5Bstate%2C%20question%2C%20options%2C%20mo.hstack(%5Bapi_model%2C%20run%5D%2C%20justify%3D%22start%22)%5D)%0A%20%20%20%20return%20api_model%2C%20options%2C%20question%2C%20run%2C%20state%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20Choice%2C%0A%20%20%20%20api_model%2C%0A%20%20%20%20autorun%2C%0A%20%20%20%20bars%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20options%2C%0A%20%20%20%20question%2C%0A%20%20%20%20run%2C%0A%20%20%20%20run_all%2C%0A%20%20%20%20state%2C%0A)%3A%0A%20%20%20%20mo.stop(not%20(run.value%20or%20autorun)%2C%20mo.md(%22*Press%20Run.*%22))%0A%0A%20%20%20%20_choice%20%3D%20Choice(question.value%2C%20%5Bo.strip()%20for%20o%20in%20options.value.split(%22%2C%22)%5D)%0A%20%20%20%20_panels%20%3D%20%5B%5D%0A%20%20%20%20for%20_name%2C%20(_result%2C%20_ms)%20in%20run_all(state.value%2C%20_choice%2C%20api_model.value).items()%3A%0A%20%20%20%20%20%20%20%20_title%20%3D%20mo.md(f%22**%7B_name%7D**%22%20%2B%20(f%22%20%C2%B7%20%7B_ms%3A.0f%7D%20ms%22%20if%20_ms%20else%20%22%22))%0A%20%20%20%20%20%20%20%20_panels.append(mo.vstack(%5B_title%2C%20bars(_result)%20if%20isinstance(_result%2C%20dict)%20else%20mo.md(_result)%5D))%0A%20%20%20%20mo.hstack(_panels%2C%20widths%3D%22equal%22%2C%20gap%3D2)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%202.%20Heads%20or%20tails%3F%0A%0A%20%20%20%20A%20fair%20coin%20is%20**50%20%2F%2050**.%20We%20ask%20each%20model%20*%22I%20flip%20a%20fair%20coin.%20How%20does%20it%20land%3F%22*%20twice%3A%0A%20%20%20%20once%20with%20**heads**%20listed%20first%2C%20once%20with%20**tails**%20first.%0A%0A%20%20%20%20The%20first%20row%20is%20a%20real%20fair%20coin%2C%20%60random.random()%20%3C%200.5%60%2C%20for%20comparison.%20The%20slider%20simulates%20N%20flips%20from%20each%20answer%20(no%20extra%20calls).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20flip%20%3D%20mo.ui.run_button(label%3D%22Ask%20the%20models%22)%0A%20%20%20%20n_flips%20%3D%20mo.ui.slider(10%2C%201000%2C%20step%3D10%2C%20value%3D100%2C%20label%3D%22N%20flips%22%2C%20show_value%3DTrue)%0A%20%20%20%20mo.hstack(%5Bflip%2C%20n_flips%5D%2C%20justify%3D%22start%22%2C%20gap%3D2)%0A%20%20%20%20return%20flip%2C%20n_flips%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(Choice%2C%20api_model%2C%20autorun%2C%20flip%2C%20mo%2C%20run_all)%3A%0A%20%20%20%20mo.stop(not%20(flip.value%20or%20autorun)%2C%20mo.md(%22*Press%20Ask%20the%20models.*%22))%0A%0A%20%20%20%20ORDERS%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22heads%20listed%20first%22%3A%20Choice(%22How%20does%20it%20land%3F%22%2C%20%5B%22heads%22%2C%20%22tails%22%5D)%2C%0A%20%20%20%20%20%20%20%20%22tails%20listed%20first%22%3A%20Choice(%22How%20does%20it%20land%3F%22%2C%20%5B%22tails%22%2C%20%22heads%22%5D)%2C%0A%20%20%20%20%7D%0A%20%20%20%20p_heads%20%3D%20%7B%22random.random()%20%3C%200.5%22%3A%20%7Border%3A%200.5%20for%20order%20in%20ORDERS%7D%7D%20%20%23%20a%20real%20fair%20coin%2C%20for%20comparison%0A%20%20%20%20%23%20then%20%7Bmodel%3A%20%7Border%3A%20P(heads)%2C%20or%20an%20error%20message%7D%7D%0A%20%20%20%20for%20_order%2C%20_choice%20in%20ORDERS.items()%3A%0A%20%20%20%20%20%20%20%20for%20_name%2C%20(_result%2C%20_ms)%20in%20run_all(%22I%20flip%20a%20fair%20coin.%22%2C%20_choice%2C%20api_model.value).items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20p_heads.setdefault(_name%2C%20%7B%7D)%5B_order%5D%20%3D%20_result%5B%22heads%22%5D%20if%20isinstance(_result%2C%20dict)%20else%20_result%0A%20%20%20%20return%20ORDERS%2C%20p_heads%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20random)%3A%0A%20%20%20%20def%20split_bar(p)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Heads%20(purple)%20vs%20tails%20(grey)%2C%20with%20a%20dashed%20line%20at%2050%25.%22%22%22%0A%20%20%20%20%20%20%20%20heads%20%3D%20f%22heads%20%7Bp%3A.0%25%7D%22%20if%20p%20%3E%3D%200.15%20else%20%22%22%0A%20%20%20%20%20%20%20%20tails%20%3D%20f%22tails%20%7B1%20-%20p%3A.0%25%7D%22%20if%201%20-%20p%20%3E%3D%200.15%20else%20%22%22%0A%20%20%20%20%20%20%20%20return%20mo.Html(%0A%20%20%20%20%20%20%20%20%20%20%20%20'%3Cdiv%20style%3D%22position%3Arelative%3Bdisplay%3Aflex%3Bheight%3A1.8em%3Bborder-radius%3A6px%3Boverflow%3Ahidden%3Bcolor%3Awhite%3Bfont-weight%3A600%22%3E'%0A%20%20%20%20%20%20%20%20%20%20%20%20f'%3Cdiv%20style%3D%22width%3A%7Bp%20*%20100%3A.1f%7D%25%3Bbackground%3A%23a626a4%3Bdisplay%3Aflex%3Balign-items%3Acenter%3Bpadding-left%3A0.5em%22%3E%7Bheads%7D%3C%2Fdiv%3E'%0A%20%20%20%20%20%20%20%20%20%20%20%20f'%3Cdiv%20style%3D%22flex%3A1%3Bbackground%3A%239ca3af%3Bdisplay%3Aflex%3Balign-items%3Acenter%3Bjustify-content%3Aflex-end%3Bpadding-right%3A0.5em%22%3E%7Btails%7D%3C%2Fdiv%3E'%0A%20%20%20%20%20%20%20%20%20%20%20%20'%3Cdiv%20style%3D%22position%3Aabsolute%3Bleft%3A50%25%3Btop%3A0%3Bbottom%3A0%3Bborder-left%3A2px%20dashed%20white%22%3E%3C%2Fdiv%3E'%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%3C%2Fdiv%3E%22%0A%20%20%20%20%20%20%20%20)%0A%0A%0A%20%20%20%20def%20simulate_flips(p%2C%20n)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Flip%20a%20coin%20that%20lands%20heads%20with%20probability%20p%2C%20n%20times%3B%20return%20the%20number%20of%20heads.%22%22%22%0A%20%20%20%20%20%20%20%20return%20sum(random.random()%20%3C%20p%20for%20_%20in%20range(n))%0A%0A%20%20%20%20return%20simulate_flips%2C%20split_bar%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(ORDERS%2C%20mo%2C%20n_flips%2C%20p_heads%2C%20simulate_flips%2C%20split_bar)%3A%0A%20%20%20%20def%20_cell(p)%3A%0A%20%20%20%20%20%20%20%20if%20isinstance(p%2C%20str)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20mo.md(p)%0A%20%20%20%20%20%20%20%20heads%20%3D%20simulate_flips(p%2C%20n_flips.value)%0A%20%20%20%20%20%20%20%20return%20mo.vstack(%5Bsplit_bar(p)%2C%20mo.md(f%22%7Bn_flips.value%7D%20flips%20%E2%86%92%20**%7Bheads%7D**%20heads%2C%20**%7Bn_flips.value%20-%20heads%7D**%20tails%22)%5D)%0A%0A%20%20%20%20_header%20%3D%20mo.hstack(%5Bmo.md(%22**Model**%22)%2C%20*%5Bmo.md(f%22**%7Border%7D**%22)%20for%20order%20in%20ORDERS%5D%5D%2C%20widths%3D%5B1%2C%202%2C%202%5D%2C%20gap%3D2)%0A%20%20%20%20_rows%20%3D%20%5B%0A%20%20%20%20%20%20%20%20mo.hstack(%5Bmo.md(f%22**%7Bname%7D**%22)%2C%20*%5B_cell(orders%5Bo%5D)%20for%20o%20in%20ORDERS%5D%5D%2C%20widths%3D%5B1%2C%202%2C%202%5D%2C%20gap%3D2%2C%20align%3D%22center%22)%0A%20%20%20%20%20%20%20%20for%20name%2C%20orders%20in%20p_heads.items()%0A%20%20%20%20%5D%0A%20%20%20%20mo.vstack(%5B_header%2C%20*_rows%5D%2C%20gap%3D1.5)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%203.%20Can%20a%20decision%20model%20be%20random%3F%20Approximating%20%CF%80%0A%0A%20%20%20%20Did%20you%20know%20you%20could%20approximate%20pi%20by%20just%20drawing%20points%20at%20random%20on%20a%20grid%3F%20This%20is%20how%20it%20works%3A%0A%0A%20%20%20%20Draw%20an%20(x%2Cy)%20point%20at%20random%20inside%20a%20square%2C%20and%20check%20if%20it%20falls%20within%20a%20circle%20inscribed%20in%20that%20square.%20Count%20the%20number%20of%20circle%20hits%20and%20divide%20by%20the%20number%20of%20total%20hits.%20This%20should%20approximate%20the%20area%20of%20the%20circle%20to%20the%20square.%20From%20that%2C%20we%20can%20approximate%20pi.%20Check%20out%20the%20figure%20below%20for%20more%20intuition.%0A%0A%20%20%20%20!%5Bapproximate_pi%5D(https%3A%2F%2Fi.stack.imgur.com%2Fg0Ctz.png)%0A%0A%20%20%20%20Since%0A%0A%20%20%20%20!%5Bapprox_pi1%5D(https%3A%2F%2Fuser-images.githubusercontent.com%2F18450628%2F111053532-8d5f5b00-8432-11eb-9be4-2de7ec8cc3e2.jpg)%0A%0A%20%20%20%20we%20can%20approximate%20pi%3A%0A%0A%20%20%20%20!%5Bapprox_pi2%5D(https%3A%2F%2Fuser-images.githubusercontent.com%2F18450628%2F111053571-d1526000-8432-11eb-9e23-27867c99a54b.png)%0A%0A%20%20%20%20**The%20twist%3A**%20instead%20of%20%60random.random()%60%2C%20we%20ask%20each%20model%20for%20a%20random%20digit%20**once**%2C%20then%20draw%20points%20from%20its%20distribution%20(%60x%20%3D%200.d%E2%82%81d%E2%82%82%60%2C%20%60y%20%3D%200.d%E2%82%83d%E2%82%84%60).%20A%20fair%20model%20gives%2010%25%20per%20digit%2C%20and%20%CF%80%20%E2%89%88%203.14.%20Play%20with%20N%2C%20the%20number%20of%20points%20to%20draw%2C%20to%20see%20how%20it%20affects%20the%20approximation.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20pi_state%20%3D%20mo.ui.text(value%3D%22Pick%20a%20random%20digit%20between%200%20and%209.%22%2C%20label%3D%22State%22%2C%20full_width%3DTrue)%0A%20%20%20%20run_pi%20%3D%20mo.ui.run_button(label%3D%22Ask%20the%20models%22)%0A%20%20%20%20n_points%20%3D%20mo.ui.slider(100%2C%2010000%2C%20step%3D100%2C%20value%3D2000%2C%20label%3D%22N%20points%22%2C%20show_value%3DTrue)%0A%20%20%20%20mo.vstack(%5Bpi_state%2C%20mo.hstack(%5Brun_pi%2C%20n_points%5D%2C%20justify%3D%22start%22%2C%20gap%3D2)%5D)%0A%20%20%20%20return%20n_points%2C%20pi_state%2C%20run_pi%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(Choice%2C%20api_model%2C%20autorun%2C%20mo%2C%20pi_state%2C%20run_all%2C%20run_pi)%3A%0A%20%20%20%20mo.stop(not%20(run_pi.value%20or%20autorun)%2C%20mo.md(%22*Press%20Ask%20the%20models.*%22))%0A%0A%20%20%20%20digit%20%3D%20Choice(%22Pick%20a%20random%20digit.%22%2C%20%5Bstr(i)%20for%20i%20in%20range(10)%5D)%0A%20%20%20%20digit_probs%20%3D%20%7B%7D%0A%20%20%20%20for%20_name%2C%20(_result%2C%20_ms)%20in%20run_all(pi_state.value%2C%20digit%2C%20api_model.value).items()%3A%0A%20%20%20%20%20%20%20%20if%20isinstance(_result%2C%20dict)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20digit_probs%5B_name%5D%20%3D%20_result%0A%20%20%20%20return%20(digit_probs%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20random)%3A%0A%20%20%20%20def%20estimate_pi(draw_digit%2C%20n%2C%20seed%3D0)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Draw%20n%20points%20from%20a%20digit%20generator%3B%20return%20(pi%20estimate%2C%20points%2C%20digits%20drawn).%22%22%22%0A%20%20%20%20%20%20%20%20rng%20%3D%20random.Random(seed)%0A%20%20%20%20%20%20%20%20digits%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20def%20coordinate()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20d1%2C%20d2%20%3D%20draw_digit(rng)%2C%20draw_digit(rng)%0A%20%20%20%20%20%20%20%20%20%20%20%20digits.extend(%5Bd1%2C%20d2%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20(d1%20*%2010%20%2B%20d2%20%2B%200.5)%20%2F%20100%0A%0A%20%20%20%20%20%20%20%20points%20%3D%20%5B(coordinate()%2C%20coordinate())%20for%20_%20in%20range(n)%5D%0A%20%20%20%20%20%20%20%20inside%20%3D%20sum((x%20-%200.5)%20**%202%20%2B%20(y%20-%200.5)%20**%202%20%3C%200.25%20for%20x%2C%20y%20in%20points)%0A%20%20%20%20%20%20%20%20return%204%20*%20inside%20%2F%20n%2C%20points%2C%20digits%0A%0A%0A%20%20%20%20def%20from_model(probs)%3A%0A%20%20%20%20%20%20%20%20%22%22%22A%20digit%20generator%20that%20samples%20from%20a%20model's%20distribution.%22%22%22%0A%20%20%20%20%20%20%20%20return%20lambda%20rng%3A%20int(rng.choices(list(probs)%2C%20weights%3Dlist(probs.values()))%5B0%5D)%0A%0A%0A%20%20%20%20def%20scatter(points%2C%20size%3D180)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Points%20in%20the%20unit%20square%3A%20inside%20the%20circle%20in%20purple%2C%20outside%20in%20grey.%22%22%22%0A%20%20%20%20%20%20%20%20dots%20%3D%20%22%22.join(%0A%20%20%20%20%20%20%20%20%20%20%20%20f'%3Ccircle%20cx%3D%22%7Bx%20*%20size%3A.1f%7D%22%20cy%3D%22%7B(1%20-%20y)%20*%20size%3A.1f%7D%22%20r%3D%221.3%22%20'%0A%20%20%20%20%20%20%20%20%20%20%20%20f'fill%3D%22%7B%22%23a626a4%22%20if%20(x%20-%200.5)%20**%202%20%2B%20(y%20-%200.5)%20**%202%20%3C%200.25%20else%20%22%239ca3af%22%7D%22%2F%3E'%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20x%2C%20y%20in%20points%5B%3A3000%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20return%20mo.Html(%0A%20%20%20%20%20%20%20%20%20%20%20%20f'%3Csvg%20width%3D%22%7Bsize%7D%22%20height%3D%22%7Bsize%7D%22%20style%3D%22border%3A1px%20solid%20%23e5e7eb%22%3E'%0A%20%20%20%20%20%20%20%20%20%20%20%20f'%3Ccircle%20cx%3D%22%7Bsize%20%2F%202%7D%22%20cy%3D%22%7Bsize%20%2F%202%7D%22%20r%3D%22%7Bsize%20%2F%202%7D%22%20fill%3D%22none%22%20stroke%3D%22%23383a42%22%2F%3E%7Bdots%7D%3C%2Fsvg%3E'%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20return%20estimate_pi%2C%20from_model%2C%20scatter%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(bars%2C%20digit_probs%2C%20estimate_pi%2C%20from_model%2C%20mo%2C%20n_points%2C%20scatter)%3A%0A%20%20%20%20_generators%20%3D%20%7B%22random.randint(0%2C%209)%22%3A%20lambda%20rng%3A%20rng.randint(0%2C%209)%7D%0A%20%20%20%20_generators.update(%7Bname%3A%20from_model(probs)%20for%20name%2C%20probs%20in%20digit_probs.items()%7D)%0A%0A%20%20%20%20_panels%20%3D%20%5B%5D%0A%20%20%20%20for%20_name%2C%20_draw%20in%20_generators.items()%3A%0A%20%20%20%20%20%20%20%20_pi%2C%20_points%2C%20_digits%20%3D%20estimate_pi(_draw%2C%20n_points.value)%0A%20%20%20%20%20%20%20%20_counts%20%3D%20%7Bstr(d)%3A%20_digits.count(d)%20%2F%20len(_digits)%20for%20d%20in%20range(10)%7D%20%20%23%20what%20was%20actually%20drawn%0A%20%20%20%20%20%20%20%20_panels.append(mo.vstack(%5Bmo.md(f%22**%7B_name%7D**%20%20%5Cn%CF%80%20%E2%89%88%20**%7B_pi%3A.3f%7D**%22)%2C%20scatter(_points)%2C%20bars(_counts)%5D))%0A%20%20%20%20mo.hstack(_panels%2C%20widths%3D%22equal%22%2C%20gap%3D1.5)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
3e64d39d24000d95425b546d51d4f785