| """Draw the PIVOT overview with editable vector paths and embedded fonts.""" |
|
|
| from pathlib import Path |
| import numpy as np |
| import matplotlib |
|
|
| matplotlib.use("Agg") |
| import matplotlib.pyplot as plt |
| from matplotlib import font_manager as fm |
| from matplotlib.patches import Ellipse, FancyArrowPatch, PathPatch, FancyBboxPatch |
| from matplotlib.path import Path as MPath |
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| for f in (ROOT / "assets/fonts").glob("*.ttf"): |
| fm.fontManager.addfont(str(f)) |
| plt.rcParams.update( |
| { |
| "font.family": "Ubuntu", |
| "mathtext.fontset": "cm", |
| "pdf.fonttype": 42, |
| "ps.fonttype": 42, |
| "font.size": 13, |
| } |
| ) |
| blue = "#62AEDD" |
| navy = "#245E84" |
| red = "#9D2944" |
| ink = "#243746" |
| pale = "#E9F4FB" |
| gray = "#9EB0BD" |
| fig, ax = plt.subplots(figsize=(13.6, 4.8)) |
| ax.set(xlim=(0, 13.6), ylim=(0, 4.8)) |
| ax.axis("off") |
|
|
|
|
| def text(x, y, s, size=13, color=ink, **kw): |
| ax.text( |
| x, |
| y, |
| s, |
| fontsize=size, |
| color=color, |
| ha=kw.pop("ha", "center"), |
| va="center", |
| **kw, |
| ) |
|
|
|
|
| def arrow(start, end, color=blue, rad=0, lw=2.2): |
| ax.add_patch( |
| FancyArrowPatch( |
| start, |
| end, |
| arrowstyle="-|>", |
| mutation_scale=16, |
| connectionstyle=f"arc3,rad={rad}", |
| color=color, |
| lw=lw, |
| ) |
| ) |
|
|
|
|
| def cloud(x, y, w, h, color, seed): |
| rng = np.random.default_rng(seed) |
| ax.add_patch(Ellipse((x, y), w, h, facecolor=color, alpha=0.10, edgecolor="none")) |
| z = rng.normal(size=(36, 2)) |
| z = z[np.linalg.norm(z, axis=1) < 2] |
| ax.scatter( |
| x + z[:, 0] * w / 5, |
| y + z[:, 1] * h / 5, |
| s=rng.uniform(9, 24, len(z)), |
| color=color, |
| alpha=0.7, |
| linewidth=0, |
| ) |
|
|
|
|
| cloud(1.45, 3.08, 2.1, 1.72, blue, 2) |
| text(1.45, 4.19, "Control population", 18, navy, weight="bold") |
| text(1.45, 2.02, r"$c_0\sim\rho_0$", 18) |
| |
| for off in [-0.32, -0.12, 0.12, 0.32]: |
| verts = [(2.55, 3.1 + off), (4.3, 3.8 + off), (6.7, 3.9 + off), (8.75, 3.15 + off)] |
| ax.add_patch( |
| PathPatch( |
| MPath(verts, [MPath.MOVETO, MPath.CURVE4, MPath.CURVE4, MPath.CURVE4]), |
| fill=False, |
| edgecolor=blue, |
| alpha=0.20, |
| lw=10, |
| ) |
| ) |
| arrow((2.6, 3.1), (8.8, 3.14), blue, 0.20, 3) |
| text(5.6, 3.72, r"$X_\theta(0,1,c_0,e_u)$", 23) |
| text(5.6, 3.17, "Predict the response", 18, navy, weight="bold") |
| text(5.6, 4.38, r"$u=\{(g_j,o_j)\}_{j=1}^{M}\quad\longmapsto\quad e_u$", 17) |
| arrow((5.6, 4.12), (5.6, 3.92), gray, 0, 1.2) |
| cloud(9.4, 3.18, 1.75, 1.5, blue, 9) |
| text(9.4, 4.19, "Predicted cells", 18, navy, weight="bold") |
| cloud(11.97, 3.7, 1.65, 1.18, red, 3) |
| text(11.97, 4.56, "Target cells", 18, red, weight="bold") |
| text(12, 2.88, r"$c^\star\sim\rho^\star$", 18) |
| arrow((10.2, 3.23), (11.22, 3.63), red, -0.08, 1.6) |
| text(11, 2.57, "Endpoint reward", 17, red) |
| |
| arrow((11.3, 2.31), (5.22, 1.89), red, -0.17, 2.6) |
| text(7.95, 1.36, "Optimize intervention embeddings", 17, red) |
| text(3.05, 1.72, "Rank admissible interventions", 17, navy, weight="bold") |
| arrow((5.01, 2.03), (3.13, 2.25), navy, -0.10, 1.5) |
| text(3.45, 2.48, r"$e^{(L)}\;\longrightarrow\;u_{1:K}$", 18) |
| ax.add_patch( |
| FancyBboxPatch( |
| (0.18, 0.12), |
| 13.22, |
| 1.03, |
| boxstyle="round,pad=0.02,rounding_size=.08", |
| facecolor=pale, |
| edgecolor="none", |
| ) |
| ) |
| text(0.51, 0.78, "PIVOT", 18, navy, ha="left", weight="bold") |
| text( |
| 7.8, |
| 0.74, |
| r"$\hat c_1=X_\theta(0,1,c_0,e)\qquad g_e=J_eX_\theta^{\mathsf{T}}\nabla_{\hat c_1}r\qquad e^+=e+\gamma\,\dfrac{g_e}{\|g_e\|_2+\epsilon}$", |
| 20, |
| ) |
| text( |
| 7.6, |
| 0.29, |
| "Endpoint prediction, reward gradients, and admissible interventions.", |
| 16, |
| ) |
| fig.subplots_adjust(left=0, right=1, bottom=0, top=1) |
| for ext in ["pdf", "png", "svg"]: |
| fig.savefig(ROOT / "assets" / f"figure1.{ext}", dpi=250, facecolor="white") |
|
|