|
| 1 | +--- |
| 2 | +title: "GSoC Report for DoodleBUGS: a Browser-Based Graphical Interface for Drawing Probabilistic Graphical Models" |
| 3 | +description: "Shravan Goswami's GSoC 2025 final report: goals, architecture, progress vs proposal, and how to try it." |
| 4 | +toc: true |
| 5 | +categories: |
| 6 | + - GSoC |
| 7 | + - Blog |
| 8 | +author: |
| 9 | + - name: Shravan Goswami |
| 10 | + url: https://shravangoswami.com/ |
| 11 | +date: 2025-09-01 |
| 12 | +aliases: |
| 13 | + - /GSoC-2025-Report-DoodleBUGS # submitted this to GSoC so please remember to update incase moving this page somewhere else |
| 14 | +bibliography: references.bib |
| 15 | +csl: university-of-york-ieee.csl |
| 16 | +link-citations: true |
| 17 | +nocite: | |
| 18 | + @* |
| 19 | +--- |
| 20 | + |
| 21 | +## TL;DR |
| 22 | + |
| 23 | +- BUGS (Bayesian Inference Using Gibbs Sampling) is a probabilistic programming language for Bayesian models, and JuliaBUGS is its modern implementation in Julia. DoodleBUGS is a browser-based graphical interface for JuliaBUGS, allowing users to draw probabilistic graphical models and generate BUGS code. |
| 24 | +- Implemented: visual editor (nodes, edges, nested plates), legacy BUGS code generation that compiles with [JuliaBUGS](https://github.com/TuringLang/JuliaBUGS.jl) [@JuliaBUGS; @bugs-book], local execution via a Julia backend, unified standalone script generation (frontend), timeouts, multiple layouts, and extensive cleanup/typing. |
| 25 | +- Changed from proposal: frontend implemented in Vue 3 (instead of React); backend simplified (frontend is the single source of truth for standalone scripts). |
| 26 | +- Status: Working application. Try it here (static UI): [https://turinglang.org/JuliaBUGS.jl/DoodleBUGS/](https://turinglang.org/JuliaBUGS.jl/DoodleBUGS/). For local inference, run the backend server. |
| 27 | + |
| 28 | +## DoodleBUGS UI |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +## DoodleBUGS Project Structure |
| 33 | + |
| 34 | +```{.bash} |
| 35 | +DoodleBUGS/ # Vite + Vue 3 app (UI editor) |
| 36 | +├── README.md # project documentation |
| 37 | +├── public/ # static assets served by Vite |
| 38 | +│ └── examples/ # example projects |
| 39 | +├── experiments/ # prototypes and exploratory work |
| 40 | +├── runtime/ # Julia HTTP backend (API endpoints & dependencies) |
| 41 | +├── src/ # application source |
| 42 | +│ ├── assets/ # styles and static assets |
| 43 | +│ ├── components/ # Vue components composing the UI |
| 44 | +│ │ ├── canvas/ # graph canvas and toolbars |
| 45 | +│ │ ├── common/ # shared UI primitives |
| 46 | +│ │ ├── layouts/ # app layout and modals |
| 47 | +│ │ │ └── MainLayout.vue # main application layout |
| 48 | +│ │ ├── left-sidebar/ # palette, project manager, execution settings |
| 49 | +│ │ ├── panels/ # code preview and data input panels |
| 50 | +│ │ ├── right-sidebar/ # execution, JSON editor, node properties |
| 51 | +│ │ └── ui/ # base UI elements (buttons, inputs, selects) |
| 52 | +│ ├── composables/ # reusable logic (codegen, drag & drop, graph, validator, grid) |
| 53 | +│ ├── config/ # configuration and node definitions |
| 54 | +│ ├── stores/ # Pinia state stores (graph, data, execution, project, UI) |
| 55 | +│ └── types/ # TypeScript types and ambient declarations |
| 56 | +├── tmp/ # local temporary outputs (ignored in builds) |
| 57 | +└── ztest/ # scratch/test artifacts |
| 58 | +``` |
| 59 | + |
| 60 | +## Motivation |
| 61 | + |
| 62 | +[JuliaBUGS](https://github.com/TuringLang/JuliaBUGS.jl) is a modern Julia implementation of the BUGS language [@bugs-rjournal; @bugs-book; @bugs-project]. DoodleBUGS revives the original visual modelling concept with a modern browser-based stack so users can: |
| 63 | + |
| 64 | +- Construct probabilistic graphical models visually (nodes, edges, plates). |
| 65 | +- Export readable legacy BUGS code that compiles with JuliaBUGS [@JuliaBUGS; @bugs-rjournal; @bugs-book]. |
| 66 | +- Run inference and inspect results from the UI. Common BUGS applications include parallel MCMC [@multibugs], survival analysis [@bugs-survival], and Gibbs-style samplers [@albert-chib-1993; @informs-gibbs]. |
| 67 | + |
| 68 | +## What Was Built |
| 69 | + |
| 70 | +- Visual editor |
| 71 | + - Node types: stochastic, observed, deterministic |
| 72 | + - Plates with arbitrary nesting; robust drag-in/out and creation inside plates |
| 73 | + - Graph layouts: Dagre (default), fCoSE (Force-Directed), Cola (Physics Simulation), KLay (Layered); stable interactions |
| 74 | +- Legacy BUGS code generation [@bugs-rjournal; @bugs-book] |
| 75 | + - Topological ordering and plate-aware traversal |
| 76 | + - Parameter formatting and safe index expansion |
| 77 | + - Implemented in `DoodleBUGS/src/composables/useBugsCodeGenerator.ts` |
| 78 | +- Execution flow |
| 79 | + - Frontend POSTs to `/api/run` with body: `model_code` (BUGS), `data` and `inits` (JSON), `data_string` and `inits_string` (Julia NamedTuple literals), and `settings` `{ n_samples, n_adapts, n_chains, seed, timeout_s }`. If `/api/run` returns 404, it falls back to `/api/run_model`. |
| 80 | + - Backend creates a temp dir, writes `model.bugs` and `payload.json`, generates an ephemeral `run_script.jl`, compiles with `JuliaBUGS.@bugs`, wraps with `ADgradient(:ReverseDiff)`, and samples via `AdvancedHMC.NUTS` through `AbstractMCMC` (Threads or Serial). It writes summaries (incl. ESS, R-hat) and quantiles to JSON and returns `{ success, summary, quantiles, logs, files[] }`, where `files` includes `model.bugs`, `payload.json`, `run_script.jl`, and `results.json`. |
| 81 | + - Frontend also generates a `standalone.jl` script locally (mirrors backend execution) and shows it alongside the backend files; the backend does not attach a standalone script. |
| 82 | +- Timeouts/resilience |
| 83 | + - Configurable timeout (frontend); enforced in backend worker |
| 84 | + - Safe temp directory cleanup on Windows with retries |
| 85 | +- Cleanup/typing |
| 86 | + - Strong, project-wide TypeScript typing across stores, components, and composables |
| 87 | + - Removal of unused backend code; consistent naming and logs |
| 88 | + |
| 89 | +## Architecture Overview |
| 90 | + |
| 91 | +- Frontend: [Vue 3](https://vuejs.org/), [Pinia](https://pinia.vuejs.org/), [Cytoscape.js](https://js.cytoscape.org/) [@cytoscapejs], [CodeMirror](https://codemirror.net/) |
| 92 | + - Code generation: `DoodleBUGS/src/composables/useBugsCodeGenerator.ts` |
| 93 | + - Execution panel: `DoodleBUGS/src/components/right-sidebar/ExecutionPanel.vue` |
| 94 | +- Backend (Julia) HTTP server |
| 95 | + - Server: `DoodleBUGS/runtime/server.jl` |
| 96 | + - Project deps: `DoodleBUGS/runtime/Project.toml` (HTTP, JSON3, JuliaBUGS, AbstractMCMC, AdvancedHMC, ReverseDiff, MCMCChains, DataFrames, StatsBase, Statistics) |
| 97 | + - Endpoints: GET `/api/health`; POST `/api/run` and `/api/run_model` |
| 98 | + - Execution: creates temp dir, writes `model.bugs` and `payload.json`, generates `run_script.jl`, enforces optional timeout |
| 99 | + |
| 100 | +## Design Principles and Architecture |
| 101 | + |
| 102 | +**Design principles** |
| 103 | + |
| 104 | +- Visual-first modeling with deterministic export to legacy BUGS [@bugs-rjournal; @bugs-book]. |
| 105 | +- Separation of concerns: editing (graph), generation (BUGS), execution (backend), and results (summary/quantiles) are modular. |
| 106 | +- Deterministic ordering: topological sort + plate-aware traversal ensures readable, stable code output. |
| 107 | +- Robustness: cancellable frontend fetch, backend-enforced timeout, and resilient temp cleanup on Windows (`safe_rmdir()`). |
| 108 | + |
| 109 | +**Frontend architecture (Vue 3 + Cytoscape.js)** |
| 110 | + |
| 111 | +- Core graph state is managed in Vue; [Cytoscape.js](https://js.cytoscape.org/) handles layout, hit-testing, and interaction semantics (including compound nodes for plates) [@cytoscapejs]. |
| 112 | +- Code generation lives in `DoodleBUGS/src/composables/useBugsCodeGenerator.ts` and maps `GraphNode`/`GraphEdge` to BUGS: |
| 113 | + - Kahn topological sort for definition order |
| 114 | + - Plate-aware recursion for `for (...) { ... }` blocks |
| 115 | + - Parameter canonicalization (indices, numeric/expr passthrough) |
| 116 | +- Standalone Julia script generation uses `generateStandaloneScript()` in the same composable, mirroring backend execution. |
| 117 | + |
| 118 | +**Backend architecture (Julia)** |
| 119 | + |
| 120 | +- `run_model_handler()` in `DoodleBUGS/runtime/server.jl` materializes `model.bugs`, `payload.json`, and a transient `run_script.jl` that: |
| 121 | + - Builds `NamedTuple`s from JSON or string-literal data/inits |
| 122 | + - Compiles via `JuliaBUGS.@bugs`, wraps with `ADgradient(:ReverseDiff)` [@ReverseDiff] |
| 123 | + - Samples with `AdvancedHMC.NUTS` through `AbstractMCMC` (Threads or Serial) [@AdvancedHMC; @AbstractMCMC; @HoffmanGelman2014] |
| 124 | + - Emits summaries (incl. ESS and R-hat) via `MCMCChains`/`DataFrames` and quantiles to JSON |
| 125 | + [@MCMCChains; @DataFrames] |
| 126 | + - Timeout: worker process is killed if exceeding `timeout_s`. |
| 127 | + - Cleanup: `safe_rmdir()` retries with GC to avoid EBUSY on Windows. |
| 128 | + |
| 129 | +## Why Vue (not React)? |
| 130 | + |
| 131 | +The proposal planned React; we chose Vue 3 after evaluating the graph layer and developer velocity for this app. |
| 132 | + |
| 133 | +- Tried Konva (canvas) for custom graph editing: powerful drawing primitives, but required bespoke graph semantics (hit testing, edge routing, compound nodes) that [Cytoscape.js](https://js.cytoscape.org/) provides out of the box. |
| 134 | +- Tried D3 force/layouts: flexible, but compound nodes (plates), nesting, and drag constraints became a significant amount of custom code to maintain. |
| 135 | +- [Cytoscape.js](https://js.cytoscape.org/) offered: |
| 136 | + - Native graph model with compound nodes (great for plates) |
| 137 | + - Integrated layouts (Dagre, fCoSE, Cola, KLay) and rich interaction APIs [@webcola; @elk] |
| 138 | + - Mature ecosystem and performance characteristics for medium-sized graphs |
| 139 | +- [Vue 3](https://vuejs.org/) (vs React) for this project: |
| 140 | + - Composition API made integrating an imperative graph library (Cytoscape) straightforward via composables and lifecycle hooks |
| 141 | + - SFC ergonomics and Pinia stores enabled quick iteration with strong TypeScript support |
| 142 | + - Template reactivity + refs reduced reconciliation overhead when bridging to Cytoscape’s imperative API |
| 143 | + - Minimal glue code for state management (Pinia) vs setting up reducers/selectors; enabled rapid iteration |
| 144 | + - Vite + Vue tooling yielded fast HMR for UI-heavy iterations |
| 145 | +- Design inspirations: draw.io for interaction affordances; Stan Playground for model/run UX [@drawio; @stan-playground]. |
| 146 | + |
| 147 | +## Comparison to Legacy DoodleBUGS |
| 148 | + |
| 149 | +The legacy tool was a windows desktop application driving WinBUGS [@winbugs]; the new DoodleBUGS is a browser-based editor targeting JuliaBUGS [@JuliaBUGS]. |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | + |
| 154 | + |
| 155 | +Key differences: |
| 156 | + |
| 157 | +- Platform and backend |
| 158 | + - Legacy: Desktop UI, WinBUGS execution pipeline |
| 159 | + - New: Web UI, Julia backend via `JuliaBUGS.@bugs`, sampling with `AdvancedHMC.NUTS` through `AbstractMCMC` |
| 160 | +- Graph engine and plates |
| 161 | + - Legacy: Bespoke graph handling with limited nesting semantics |
| 162 | + - New: [Cytoscape.js](https://js.cytoscape.org/) with compound nodes for robust nested plates; custom drag-and-drop for drag-in/out and creating inside plates |
| 163 | +- Layouts and interactions |
| 164 | + - Legacy: Limited auto-layout support |
| 165 | + - New: Multiple layout engines (Dagre, fCoSE, Cola, KLay) and stable interactions; positions updated after `layoutstop` [@webcola; @elk] |
| 166 | +- Code generation |
| 167 | + - Legacy: Export to BUGS without strong ordering guarantees |
| 168 | + - New: Deterministic topological + plate-aware traversal; parameter canonicalization and safe index expansion |
| 169 | +- Execution and tooling |
| 170 | + - Legacy: WinBUGS-managed runs |
| 171 | + - New: Lightweight Julia HTTP backend, configurable timeouts, resilient temp cleanup, JSON summaries via `MCMCChains` |
| 172 | +- DevX and maintainability |
| 173 | + - New: Vue 3 + TypeScript + Pinia; unified standalone script generation on the frontend; leaner backend responses |
| 174 | + |
| 175 | +## Progress vs Proposal |
| 176 | + |
| 177 | +- Implemented |
| 178 | + - Visual editor with nested plates and robust drag-and-drop |
| 179 | + - BUGS code generator (topological + plate-aware) |
| 180 | + - Local execution + summaries/quantiles |
| 181 | + - Unified standalone script generation (frontend) |
| 182 | + - Timeouts/resilience |
| 183 | + - Multiple layouts and interactions |
| 184 | + - Extensive cleanup/typing |
| 185 | + - Execution timeout (end-to-end) |
| 186 | + - Layout options (Dagre (default, layered), fCoSE (force-directed), Cola (physics simulation), KLay (layered)) and interactions |
| 187 | + - Cleanup and stronger typing |
| 188 | +- Changed |
| 189 | + - Vue 3 instead of React |
| 190 | + - Backend responses smaller; no standalone script attachment |
| 191 | +- Deferred/Partial |
| 192 | + - Visualizations: integrate with MCMCChains.jl for plots (trace, density, PPC, diagnostics). ESS and R-hat already included in summary statistics. |
| 193 | + - WebKit/Safari support |
| 194 | + - UX polish for large graphs |
| 195 | + |
| 196 | +## How to Run Locally |
| 197 | + |
| 198 | +Frontend (Vite): |
| 199 | + |
| 200 | +```bash |
| 201 | +# from repo root |
| 202 | +cd DoodleBUGS |
| 203 | +npm install |
| 204 | +npm run dev |
| 205 | +``` |
| 206 | + |
| 207 | +Backend (Julia): |
| 208 | + |
| 209 | +```bash |
| 210 | +# from repo root |
| 211 | +julia --project=DoodleBUGS/runtime DoodleBUGS/runtime/server.jl |
| 212 | +# server listens on http://localhost:8081 |
| 213 | +``` |
| 214 | + |
| 215 | +Notes: |
| 216 | + |
| 217 | +- CORS is enabled in the backend so the dev UI can call `http://localhost:8081`. |
| 218 | +- Try it here (static UI): [https://turinglang.org/JuliaBUGS.jl/DoodleBUGS/](https://turinglang.org/JuliaBUGS.jl/DoodleBUGS/) |
| 219 | + |
| 220 | +## API Summary for Backend Server |
| 221 | + |
| 222 | +- GET `/api/health` → `{ "status": "ok" }` |
| 223 | +- POST `/api/run` (alias: `/api/run_model`) |
| 224 | + - Body: `model_code`, `data` (JSON), `inits` (JSON), `data_string` (Julia literal), `inits_string` (Julia literal), `settings` `{ n_samples, n_adapts, n_chains, seed, timeout_s }` |
| 225 | + - Response: `{ success, summary, quantiles, logs, files[] }` where `files[]` contains `model.bugs`, `payload.json`, `run_script.jl`, `results.json` |
| 226 | + - Note: Frontend falls back to `/api/run_model` if `/api/run` is unavailable (404) |
| 227 | + |
| 228 | +See `DoodleBUGS/runtime/server.jl`. |
| 229 | + |
| 230 | +## Current Limitations |
| 231 | + |
| 232 | +- WebKit/Safari/iOS: unsupported at this time (see `DoodleBUGS/README.md`). |
| 233 | +- Limited visualization beyond summary/quantiles. |
| 234 | +- Overlapped plates (nodes with multiple parent plates) are currently not supported; see [#362](https://github.com/TuringLang/JuliaBUGS.jl/issues/362). |
| 235 | + |
| 236 | +## Future Work |
| 237 | + |
| 238 | +- Backend: Add Pluto.jl as a backend for supporting compound documents and QuartoNotebookRunner.jl for running notebooks. |
| 239 | +- Diagnostics/visualization: integrate with MCMCChains.jl for plots (trace, density, PPC, diagnostics). ESS and R-hat already available in summary stats. |
| 240 | +- UX: richer node templates, validation, distribution hints |
| 241 | +- Sharing: shareable links/cloud sync (projects already persisted locally) |
| 242 | +- Browser compatibility: WebKit/Safari and iOS/iPadOS |
| 243 | +- Performance: virtualization for large graphs |
| 244 | + |
| 245 | +## Acknowledgements |
| 246 | + |
| 247 | +Much appreciation goes to my mentors Xianda Sun and Hong Ge. The work is impossible without your help and support. |
| 248 | + |
| 249 | +- Mentors: Xianda Sun ([\@sunxd3](https://github.com/sunxd3)) and Hong Ge ([\@yebai](https://github.com/yebai)) |
| 250 | +- TuringLang/JuliaBUGS community and contributors |
| 251 | + |
| 252 | +## Appendix: Project Links |
| 253 | + |
| 254 | +- Repo: [https://github.com/TuringLang/JuliaBUGS.jl](https://github.com/TuringLang/JuliaBUGS.jl) |
| 255 | +- Try it here: [https://turinglang.org/JuliaBUGS.jl/DoodleBUGS/](https://turinglang.org/JuliaBUGS.jl/DoodleBUGS/) |
| 256 | + |
| 257 | +## PRs during GSoC: |
| 258 | + |
| 259 | +- [#321 - Add ISSUE template for DoodleBUGS](https://github.com/TuringLang/JuliaBUGS.jl/pull/321) |
| 260 | +- [#339 - DoodleBUGS Project: Phase 1](https://github.com/TuringLang/JuliaBUGS.jl/pull/339) |
| 261 | +- [#340 - DoodleBUGS: update all workflows to run on relevent project only](https://github.com/TuringLang/JuliaBUGS.jl/pull/340) |
| 262 | +- [#341 - Exclude navigation bar from DoodleBUGS project](https://github.com/TuringLang/JuliaBUGS.jl/pull/341) |
| 263 | +- [#347 - DoodleBUGS: Basic Code Generation, Advanced Exports, and State Persistence](https://github.com/TuringLang/JuliaBUGS.jl/pull/347) |
| 264 | +- [#357 - DoodleBUGS: Allow Nested Plates, add new layouts and fix lot of linting issues](https://github.com/TuringLang/JuliaBUGS.jl/pull/357) |
| 265 | +- [#368 - New Folder Structure](https://github.com/TuringLang/JuliaBUGS.jl/pull/368) |
| 266 | +- [#388 - DoodleBUGS Project: Phase 2 (Backend) ](https://github.com/TuringLang/JuliaBUGS.jl/pull/388) |
0 commit comments