cloudposterior: Monitoring

import marimo as mo

Monitor MCMC sampling remotely with a live dashboard or push notifications.

Run this notebook locally (Jupyter or marimo) to see the dashboard links and QR codes, plus the in-cell live progress.

import numpy as np
import pandas as pd
import pymc as pm
import arviz as az

Start fresh

Clear any cached results and this project’s Modal volume so the notebook runs cold and reproducibly. (Shown in marimo; hidden in the rendered notebook.)

import shutil
from pathlib import Path

import cloudposterior as cp

# Wipe the local result cache + this project's Modal volume so the example
# starts cold. The sampling cells below use `cp`, so marimo runs this first.
shutil.rmtree(Path(".cloudposterior"), ignore_errors=True)
cp.cleanup_volumes()
/Users/spencerboucher/Projects/cloudposterior-map-dashboard/cloudposterior/backends/modal_backend.py:790: AsyncUsageWarning: A blocking Modal interface is being used in an async context.

This may cause performance issues or bugs. Consider rewriting to use Modal's async interfaces:
https://modal.com/docs/guide/async

Suggested rewrite:
  await modal.Volume.objects.delete.aio(volume_name)

Original line:
  modal.Volume.objects.delete(volume_name)
df = pd.read_csv(pm.get_data("radon.csv"))

with pm.Model(name="radon_intercepts", coords={"county": df.county.unique()}) as radon:
    mu_a = pm.Normal("mu_a", mu=0, sigma=5)
    sigma_a = pm.HalfNormal("sigma_a", sigma=2)
    a_raw = pm.Normal("a_raw", mu=0, sigma=1, dims="county")
    a = pm.Deterministic("a", mu_a + sigma_a * a_raw, dims="county")
    b_floor = pm.Normal("b_floor", mu=0, sigma=5)
    mu = a[df.county_code.values] + b_floor * df.floor.values
    sigma_y = pm.HalfNormal("sigma_y", sigma=2)
    pm.Normal("obs", mu=mu, sigma=sigma_y, observed=df.log_radon.values)

Live dashboard

The dashboard is on by default for remote runs. Scan the QR code or open the URL to see:

  • Per-chain progress bars with speed, divergences, and ETA
  • Live convergence diagnostics (R-hat, ESS) with color-coded status
  • Live trace plots and posterior KDE per parameter
  • A stop button to end sampling early if convergence looks good
with cp.cloud(radon, remote=True, cache=False):
    idata = pm.sample(draws=10000, tune=1000, chains=4)
az.summary(idata, filter_vars="like", var_names=["mu_a", "sigma_a", "b_floor", "sigma_y"])
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
radon_intercepts::mu_a 1.492 0.051 1.398 1.589 0.001 0.0 9767.0 15649.0 1.0
radon_intercepts::b_floor -0.663 0.068 -0.788 -0.532 0.000 0.0 35031.0 29989.0 1.0
radon_intercepts::sigma_a 0.321 0.045 0.240 0.408 0.000 0.0 9285.0 14580.0 1.0
radon_intercepts::sigma_y 0.727 0.018 0.694 0.760 0.000 0.0 40116.0 27917.0 1.0
az.plot_trace(idata, filter_vars="like", var_names=["mu_a", "sigma_a", "b_floor", "sigma_y"])[0, 0].figure

Push notifications

Pass notify=True to get push notifications when sampling starts and completes. Works for both local and remote runs. Scan the QR code with the ntfy app to subscribe.

For private notifications, point to your own ntfy server with notify={"server": "https://ntfy.example.com"}.

with cp.cloud(radon, cache=False, notify=True):
    idata_1 = pm.sample(draws=2000, tune=1000, chains=4)

Sampler Progress

Total Chains: 4

Active Chains: 0

Finished Chains: 4

Sampling for now

Estimated Time to Completion: now

Progress Draws Divergences Step Size Gradients/Draw
3000 0 0.52 7
3000 0 0.48 7
3000 0 0.45 7
3000 0 0.48 7

Both

Use both together to get the live dashboard AND push notifications.

with cp.cloud(radon, remote=True, cache=False, notify=True):
    idata_2 = pm.sample(draws=1000, tune=1000, chains=4)
az.summary(idata_2, filter_vars='like', var_names=['mu_a', 'sigma_a', 'b_floor', 'sigma_y'])
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
radon_intercepts::mu_a 1.491 0.052 1.393 1.586 0.002 0.001 852.0 1900.0 1.0
radon_intercepts::b_floor -0.663 0.068 -0.794 -0.541 0.001 0.001 3537.0 3034.0 1.0
radon_intercepts::sigma_a 0.322 0.045 0.238 0.405 0.002 0.001 786.0 1174.0 1.0
radon_intercepts::sigma_y 0.727 0.018 0.695 0.762 0.000 0.000 3523.0 2458.0 1.0