| |
| |
|
|
| import io |
| import datetime |
| from typing import Dict, Any, List, Tuple |
|
|
| import numpy as np |
|
|
| import matplotlib |
| matplotlib.use("Agg") |
| import matplotlib.pyplot as plt |
|
|
| import gradio as gr |
|
|
| from reportlab.lib.pagesizes import A4 |
| from reportlab.pdfgen import canvas |
| from reportlab.lib.units import cm |
| from reportlab.lib.utils import ImageReader |
| from reportlab.pdfbase import pdfmetrics |
| from reportlab.pdfbase.ttfonts import TTFont |
| from reportlab.lib import colors |
|
|
| from engine.validation import validate_inputs |
| from engine.thresholds import compute_diagnostics, interpret_governance, euler_simulation |
|
|
|
|
| RIGHTS_HOLDER_LINE = "Rights holder: Abdessamad Bourkibate (Morocco) — Apache-2.0" |
|
|
|
|
| |
| |
| |
| def _register_pdf_font() -> str: |
| candidates = [ |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", |
| "/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed.ttf", |
| "/usr/share/fonts/truetype/freefont/FreeSans.ttf", |
| ] |
| for p in candidates: |
| try: |
| pdfmetrics.registerFont(TTFont("UFont", p)) |
| return "UFont" |
| except Exception: |
| continue |
| return "Helvetica" |
|
|
|
|
| def _fig_to_png_bytes(fig, dpi=420) -> io.BytesIO: |
| buf = io.BytesIO() |
| fig.savefig(buf, format="png", dpi=dpi, bbox_inches="tight") |
| buf.seek(0) |
| return buf |
|
|
|
|
| def _wrap_lines(font_name: str, font_size: int, text: str, max_width_pt: float) -> List[str]: |
| words = (text or "").replace("\r", "").split() |
| if not words: |
| return [""] |
| lines, cur = [], words[0] |
| for w in words[1:]: |
| trial = cur + " " + w |
| if pdfmetrics.stringWidth(trial, font_name, font_size) <= max_width_pt: |
| cur = trial |
| else: |
| lines.append(cur) |
| cur = w |
| lines.append(cur) |
| return lines |
|
|
|
|
| def _draw_wrapped(c: canvas.Canvas, x: float, y: float, font: str, size: int, |
| text: str, max_w: float, leading: float = 13.0) -> float: |
| c.setFont(font, size) |
| for para in (text or "").split("\n"): |
| if para.strip() == "": |
| y -= leading |
| continue |
| for ln in _wrap_lines(font, size, para, max_w): |
| c.drawString(x, y, ln) |
| y -= leading |
| return y |
|
|
|
|
| def _section_bar(c: canvas.Canvas, x: float, y: float, w: float, title: str, |
| font: str, bg=(0.07, 0.19, 0.22), fg=(0.97, 0.99, 0.99)) -> float: |
| c.setFillColorRGB(*bg) |
| c.roundRect(x, y - 16, w, 20, 6, fill=1, stroke=0) |
| c.setFillColorRGB(*fg) |
| c.setFont(font, 11) |
| c.drawString(x + 10, y - 3, title) |
| return y - 28 |
|
|
|
|
| def _kpi_box(c: canvas.Canvas, x: float, y: float, w: float, h: float, |
| label: str, value: str, font: str): |
| c.setFillColorRGB(0.96, 0.98, 0.99) |
| c.setStrokeColorRGB(0.85, 0.90, 0.93) |
| c.roundRect(x, y - h, w, h, 10, fill=1, stroke=1) |
| c.setFillColorRGB(0.12, 0.12, 0.12) |
| c.setFont(font, 9) |
| c.drawString(x + 10, y - 16, label) |
| c.setFont(font, 12) |
| c.drawString(x + 10, y - 36, value) |
|
|
|
|
| def _draw_table(c: canvas.Canvas, x: float, y: float, col_w: List[float], rows: List[List[str]], |
| font: str, header: bool = True, row_h: float = 18.0) -> float: |
| |
| total_w = sum(col_w) |
| nrows = len(rows) |
| cur_y = y |
|
|
| |
| for r in range(nrows): |
| is_header = header and (r == 0) |
| bg = (0.92, 0.96, 0.97) if is_header else (1, 1, 1) |
| c.setFillColorRGB(*bg) |
| c.setStrokeColorRGB(0.84, 0.88, 0.90) |
| c.rect(x, cur_y - row_h, total_w, row_h, fill=1, stroke=1) |
|
|
| cx = x |
| for j, cell in enumerate(rows[r]): |
| c.setStrokeColorRGB(0.84, 0.88, 0.90) |
| c.rect(cx, cur_y - row_h, col_w[j], row_h, fill=0, stroke=1) |
|
|
| c.setFillColorRGB(0.10, 0.10, 0.10) |
| c.setFont(font, 9 if not is_header else 9.5) |
| max_w = col_w[j] - 10 |
| lines = _wrap_lines(font, 9 if not is_header else 9.5, str(cell), max_w) |
| |
| c.drawString(cx + 6, cur_y - 13, lines[0][:140]) |
| cx += col_w[j] |
|
|
| cur_y -= row_h |
|
|
| return cur_y - 8 |
|
|
|
|
| |
| |
| |
| def _parameter_sweep(alpha: float, beta: float, D: float, k: float, tol: float, eps: float, |
| D_span=(0, 10), k_span=(0, 10), n=90) -> Dict[str, Any]: |
| Dg = np.linspace(D_span[0], D_span[1], n) |
| kg = np.linspace(k_span[0], k_span[1], n) |
| Lam = np.zeros((n, n), dtype=float) |
| F = np.zeros((n, n), dtype=float) |
|
|
| for i, Di in enumerate(Dg): |
| for j, kj in enumerate(kg): |
| lam = (alpha * Di) - (beta * kj) |
| Delta = (beta * kj) - (alpha * Di) |
| denom = abs(alpha * Di) + abs(beta * kj) + eps |
| frag = abs(Delta) / denom |
| Lam[i, j] = lam |
| F[i, j] = frag |
|
|
| |
| stable = Lam < -tol |
| near = np.abs(Lam) <= tol |
| unstable = Lam > tol |
|
|
| return {"Dg": Dg, "kg": kg, "Lam": Lam, "F": F, "stable": stable, "near": near, "unstable": unstable} |
|
|
|
|
| def _make_figures(diag: Dict[str, Any], params: Dict[str, Any], t: np.ndarray, R: np.ndarray) -> Dict[str, ImageReader]: |
| alpha = float(params["alpha"]); beta = float(params["beta"]) |
| D = float(params["D"]); k = float(params["k"]) |
| tol = float(params["tol"]); eps = float(params["eps"]) |
|
|
| |
| plt.rcParams.update({ |
| "font.size": 10, |
| "axes.titlesize": 11, |
| "axes.labelsize": 10, |
| "axes.spines.top": False, |
| "axes.spines.right": False, |
| "grid.alpha": 0.25, |
| }) |
|
|
| |
| fig1 = plt.figure(figsize=(6.6, 4.2)) |
| plt.plot(t, R, linewidth=2.6) |
| plt.xlabel("Time t") |
| plt.ylabel("Risk R(t)") |
| plt.title("Risk trajectory (Euler integration)") |
| plt.grid(True) |
| img1 = ImageReader(_fig_to_png_bytes(fig1, dpi=440)) |
| plt.close(fig1) |
|
|
| |
| fig2 = plt.figure(figsize=(6.6, 4.2)) |
| Dmax = max(10.0, D * 2.0 + 1.0) |
| Dg = np.linspace(0, Dmax, 420) |
| k_line = (alpha / beta) * Dg if beta != 0 else np.full_like(Dg, np.nan) |
| plt.plot(Dg, k_line, linestyle="--", linewidth=2.2, label="Boundary: k = (α/β)·D") |
| plt.scatter([D], [k], s=95, label="Current point (D,k)") |
| plt.xlabel("Dependency intensity D") |
| plt.ylabel("Authority gain k") |
| plt.title("Stability map (stable region above boundary)") |
| plt.grid(True) |
| plt.legend() |
| img2 = ImageReader(_fig_to_png_bytes(fig2, dpi=440)) |
| plt.close(fig2) |
|
|
| |
| fig3 = plt.figure(figsize=(6.6, 4.2)) |
| ranked = diag["sens_ranked"] |
| labels = [a for a, _ in ranked][::-1] |
| vals = [abs(v) for _, v in ranked][::-1] |
| plt.barh(labels, vals) |
| plt.xlabel("|sensitivity|") |
| plt.title("Local sensitivity ranking of λ") |
| plt.grid(True, axis="x") |
| img3 = ImageReader(_fig_to_png_bytes(fig3, dpi=440)) |
| plt.close(fig3) |
|
|
| |
| sweep = _parameter_sweep(alpha, beta, D, k, tol, eps, n=95) |
| fig4 = plt.figure(figsize=(6.6, 4.8)) |
| |
| Z = np.zeros_like(sweep["Lam"]) |
| Z[sweep["near"]] = 1 |
| Z[sweep["unstable"]] = 2 |
| plt.imshow( |
| Z.T, |
| origin="lower", |
| aspect="auto", |
| extent=[sweep["Dg"][0], sweep["Dg"][-1], sweep["kg"][0], sweep["kg"][-1]] |
| ) |
| plt.plot(sweep["Dg"], (alpha / beta) * sweep["Dg"] if beta != 0 else np.nan, linestyle="--", linewidth=1.8) |
| plt.scatter([D], [k], s=70) |
| plt.xlabel("D") |
| plt.ylabel("k") |
| plt.title("Regime map over (D,k): stable / near-boundary / unstable") |
| img4 = ImageReader(_fig_to_png_bytes(fig4, dpi=440)) |
| plt.close(fig4) |
|
|
| |
| fig5 = plt.figure(figsize=(6.6, 4.8)) |
| plt.imshow( |
| sweep["F"].T, |
| origin="lower", |
| aspect="auto", |
| extent=[sweep["Dg"][0], sweep["Dg"][-1], sweep["kg"][0], sweep["kg"][-1]] |
| ) |
| plt.plot(sweep["Dg"], (alpha / beta) * sweep["Dg"] if beta != 0 else np.nan, linestyle="--", linewidth=1.8) |
| plt.scatter([D], [k], s=70) |
| plt.xlabel("D") |
| plt.ylabel("k") |
| plt.title("Fragility proximity surface F over (D,k)") |
| img5 = ImageReader(_fig_to_png_bytes(fig5, dpi=440)) |
| plt.close(fig5) |
|
|
| return {"traj": img1, "map": img2, "sens": img3, "regmap": img4, "fragmap": img5} |
|
|
|
|
| |
| |
| |
| def make_pdf( |
| title: str, |
| params: Dict[str, Any], |
| diag: Dict[str, Any], |
| gov: Dict[str, str], |
| t: np.ndarray, |
| R: np.ndarray, |
| figures: Dict[str, ImageReader], |
| ) -> str: |
| font = _register_pdf_font() |
| out_path = "/tmp/FDRSM4_Threshold_Report_Extended.pdf" |
| c = canvas.Canvas(out_path, pagesize=A4) |
| W, H = A4 |
| mx = 2.0 * cm |
| max_w = W - 4.0 * cm |
|
|
| def header(subtitle: str = ""): |
| |
| c.setFillColorRGB(0.07, 0.19, 0.22) |
| c.rect(0, H - 2.2 * cm, W, 2.2 * cm, fill=1, stroke=0) |
| c.setFillColorRGB(0.97, 0.99, 0.99) |
| c.setFont(font, 14) |
| c.drawString(mx, H - 1.25 * cm, (title or "FDRSM-4 Report")[:110]) |
| c.setFont(font, 9.5) |
| c.drawString(mx, H - 1.85 * cm, f"Generated: {datetime.date.today().isoformat()}") |
| if subtitle: |
| c.setFont(font, 9.5) |
| c.drawRightString(W - mx, H - 1.85 * cm, subtitle[:90]) |
|
|
| def footer(page_no: int): |
| c.setFillColorRGB(0.35, 0.40, 0.48) |
| c.setFont(font, 9) |
| c.drawString(mx, 1.1 * cm, RIGHTS_HOLDER_LINE) |
| c.drawRightString(W - mx, 1.1 * cm, f"Page {page_no}") |
|
|
| page = 1 |
|
|
| |
| header("Extended Report") |
| y = H - 3.2 * cm |
|
|
| c.setFillColorRGB(0.10, 0.10, 0.10) |
| c.setFont(font, 20) |
| c.drawString(mx, y, "FDRSM-4 — Threshold Engine") |
| y -= 1.1 * cm |
|
|
| c.setFont(font, 11) |
| y = _draw_wrapped( |
| c, mx, y, font, 11, |
| "A decision-facing diagnostic report for authority–dependency stability in family digital risk systems.\n" |
| "This report is structural and analytical: no content moderation, no behavioral profiling, no surveillance tooling.", |
| max_w, leading=15 |
| ) |
| y -= 0.2 * cm |
|
|
| |
| _kpi_box(c, mx, y, (max_w/3)-8, 55, "Regime", str(diag["regime"]), font) |
| _kpi_box(c, mx + (max_w/3), y, (max_w/3)-8, 55, "Fragility", str(diag["fragility"]), font) |
| _kpi_box(c, mx + 2*(max_w/3), y, (max_w/3)-8, 55, "Margin to boundary", f"{diag['margin_to_boundary']:.6f}", font) |
| y -= 2.0 * cm |
|
|
| y = _section_bar(c, mx, y, max_w, "Parameter snapshot", font) |
| snap = ( |
| f"α={float(params['alpha']):g}, β={float(params['beta']):g}, D={float(params['D']):g}, k={float(params['k']):g}\n" |
| f"R(0)={float(params['R0']):g}, T={float(params['T']):g}, n={int(params['n'])}, tol={float(params['tol']):g}, ε={float(params['eps']):g}\n" |
| f"λ={diag['lambda']:.6f}, Δ={diag['Delta']:.6f}, F={diag['F']:.6f}" |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, snap, max_w, leading=13) |
|
|
| footer(page) |
| c.showPage() |
| page += 1 |
|
|
| |
| header("Executive Summary") |
| y = H - 3.0 * cm |
|
|
| y = _section_bar(c, mx, y, max_w, "Summary", font) |
| summary = ( |
| f"Regime classification: {diag['regime']}\n" |
| f"Fragility class: {diag['fragility']}\n" |
| f"Boundary point: k* = (α/β)·D = {diag['boundary_k']:.6f}\n" |
| f"Distance to boundary: k − k* = {diag['margin_to_boundary']:.6f}\n\n" |
| "Interpretation (decision-facing):\n" |
| f"- Posture: {gov['posture']}\n" |
| f"- Decision: {gov['decision']}\n" |
| f"- Risk note: {gov['risk']}\n" |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, summary, max_w, leading=13) |
|
|
| y -= 0.3 * cm |
| y = _section_bar(c, mx, y, max_w, "What this report is / is not", font) |
| scope = ( |
| "This report provides a structural diagnostic of stability conditions in a formal model.\n" |
| "It does NOT infer individual behavior, does NOT profile users, and does NOT implement monitoring or surveillance.\n" |
| "Its purpose is reproducible interpretation for governance, policy, and research discussion." |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, scope, max_w, leading=13) |
|
|
| footer(page) |
| c.showPage() |
| page += 1 |
|
|
| |
| header("Diagnostics") |
| y = H - 3.0 * cm |
|
|
| y = _section_bar(c, mx, y, max_w, "Formal diagnostics table", font) |
| rows = [ |
| ["Metric", "Value", "Meaning"], |
| ["λ = αD − βk", f"{diag['lambda']:.6f}", "Stability rate (negative → decay; positive → escalation)"], |
| ["Δ = βk − αD", f"{diag['Delta']:.6f}", "Signed stability margin (positive → stable margin)"], |
| ["F", f"{diag['F']:.6f}", "Normalized proximity (smaller → closer to boundary)"], |
| ["k* = (α/β)D", f"{diag['boundary_k']:.6f}", "Boundary authority required for stability"], |
| ["k − k*", f"{diag['margin_to_boundary']:.6f}", "Distance to boundary (robustness indicator)"], |
| ["Regime", str(diag["regime"]), "Stable / Near-boundary / Unstable"], |
| ["Fragility", str(diag["fragility"]), "Low / Moderate / High (based on F)"], |
| ] |
| col_w = [4.0*cm, 4.0*cm, max_w - 8.0*cm] |
| y = _draw_table(c, mx, y, col_w, rows, font, header=True, row_h=19) |
|
|
| y = _section_bar(c, mx, y, max_w, "Local sensitivities of λ", font) |
| sens_rows = [["Sensitivity term", "Value (signed)", "Impact intuition"]] |
| for name, val in diag["sens_ranked"]: |
| sens_rows.append([name, f"{val:+.6f}", "Higher magnitude → stronger local influence on regime switching"]) |
| y = _draw_table(c, mx, y, [7.0*cm, 4.0*cm, max_w - 11.0*cm], sens_rows, font, header=True, row_h=18) |
|
|
| footer(page) |
| c.showPage() |
| page += 1 |
|
|
| |
| header("Figures") |
| y = H - 3.0 * cm |
| y = _section_bar(c, mx, y, max_w, "Figure 1 — Risk trajectory", font) |
|
|
| img_w = max_w |
| img_h = 13.2 * cm |
| c.drawImage(figures["traj"], mx, y - img_h, width=img_w, height=img_h, mask="auto") |
|
|
| y -= img_h + 0.6 * cm |
| caption = ( |
| "Caption: Euler integration of the baseline dynamic. A decaying trajectory indicates stability; " |
| "growth indicates escalation. The shape reflects the sign and magnitude of λ." |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, caption, max_w, leading=13) |
|
|
| footer(page) |
| c.showPage() |
| page += 1 |
|
|
| |
| header("Figures") |
| y = H - 3.0 * cm |
| y = _section_bar(c, mx, y, max_w, "Figure 2 — Stability map", font) |
|
|
| img_h2 = 13.2 * cm |
| c.drawImage(figures["map"], mx, y - img_h2, width=img_w, height=img_h2, mask="auto") |
|
|
| y -= img_h2 + 0.6 * cm |
| caption2 = ( |
| "Caption: Stability boundary k*=(α/β)D. Points above the line (higher k for given D) are stable. " |
| "Near the line: small drift can flip the regime." |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, caption2, max_w, leading=13) |
|
|
| footer(page) |
| c.showPage() |
| page += 1 |
|
|
| |
| header("Figures") |
| y = H - 3.0 * cm |
| y = _section_bar(c, mx, y, max_w, "Figure 3 — Sensitivity ranking", font) |
|
|
| img_h3 = 13.2 * cm |
| c.drawImage(figures["sens"], mx, y - img_h3, width=img_w, height=img_h3, mask="auto") |
|
|
| y -= img_h3 + 0.6 * cm |
| caption3 = ( |
| "Caption: Local derivatives indicate which parameter perturbations most strongly shift λ. " |
| "This is a local diagnostic: it explains directionally which knobs are most influential around the current point." |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, caption3, max_w, leading=13) |
|
|
| footer(page) |
| c.showPage() |
| page += 1 |
|
|
| |
| header("Stress view") |
| y = H - 3.0 * cm |
| y = _section_bar(c, mx, y, max_w, "Figure 4 — Regime map over (D,k)", font) |
|
|
| img_h4 = 11.0 * cm |
| c.drawImage(figures["regmap"], mx, y - img_h4, width=img_w, height=img_h4, mask="auto") |
| y -= img_h4 + 0.5 * cm |
|
|
| y = _section_bar(c, mx, y, max_w, "Figure 5 — Fragility surface F over (D,k)", font) |
| img_h5 = 11.0 * cm |
| c.drawImage(figures["fragmap"], mx, y - img_h5, width=img_w, height=img_h5, mask="auto") |
|
|
| footer(page) |
| c.showPage() |
| page += 1 |
|
|
| |
| header("Appendix") |
| y = H - 3.0 * cm |
|
|
| y = _section_bar(c, mx, y, max_w, "Appendix A — Model definitions", font) |
| appA = ( |
| "Baseline:\n" |
| " dR/dt = (αD − βk)R\n\n" |
| "Definitions:\n" |
| " λ = αD − βk\n" |
| " Δ = βk − αD\n" |
| " F = |Δ| / (|αD| + |βk| + ε)\n\n" |
| "Regime rules:\n" |
| " Stable if λ < −tol\n" |
| " Near-boundary if |λ| ≤ tol\n" |
| " Unstable if λ > tol\n" |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, appA, max_w, leading=13) |
|
|
| y -= 0.2 * cm |
| y = _section_bar(c, mx, y, max_w, "Appendix B — Governance interpretation output", font) |
| appB = ( |
| f"Posture: {gov['posture']}\n" |
| f"Decision: {gov['decision']}\n" |
| f"Risk note: {gov['risk']}\n\n" |
| "Note: The interpretation is structural. It maps regime/fragility to governance stance without behavioral inference." |
| ) |
| y = _draw_wrapped(c, mx, y, font, 10, appB, max_w, leading=13) |
|
|
| footer(page) |
| c.save() |
| return out_path |
|
|
|
|
| |
| |
| |
| def run_engine(alpha, beta, D, k, R0, T, n, tol, eps): |
| v = validate_inputs(alpha, beta, D, k, R0, T, n, tol, eps) |
| if not v["ok"]: |
| msg = "Input errors:\n- " + "\n- ".join(v["errors"]) |
| return None, None, None, msg, None |
|
|
| diag = compute_diagnostics(alpha, beta, D, k, tol, eps) |
| gov = interpret_governance(diag["regime"], diag["fragility"], diag["Delta"], diag["margin_to_boundary"]) |
| t, R = euler_simulation(alpha, beta, D, k, R0, T, int(n)) |
|
|
| params = { |
| "alpha": float(alpha), "beta": float(beta), "D": float(D), "k": float(k), |
| "R0": float(R0), "T": float(T), "n": int(n), "tol": float(tol), "eps": float(eps) |
| } |
|
|
| figures = _make_figures(diag, params, t, R) |
| pdf_path = make_pdf("FDRSM-4 — Threshold Engine Report", params, diag, gov, t, R, figures) |
|
|
| |
| fig1 = plt.figure(figsize=(6.2, 4.0)) |
| plt.plot(t, R, linewidth=2.4) |
| plt.xlabel("Time t") |
| plt.ylabel("Risk R(t)") |
| plt.title("Risk trajectory (Euler integration)") |
| plt.grid(True, alpha=0.25) |
|
|
| fig2 = plt.figure(figsize=(6.2, 4.0)) |
| Dmax = max(10.0, float(D) * 2.0 + 1.0) |
| Dg = np.linspace(0, Dmax, 360) |
| k_line = (float(alpha) / float(beta)) * Dg |
| plt.plot(Dg, k_line, linestyle="--", linewidth=2.0, label="Boundary k = (α/β)·D") |
| plt.scatter([float(D)], [float(k)], s=80, label="Current (D,k)") |
| plt.xlabel("Dependency intensity D") |
| plt.ylabel("Authority gain k") |
| plt.title("Stability map (stable region above boundary)") |
| plt.grid(True, alpha=0.25) |
| plt.legend() |
|
|
| |
| sens_lines = ["Local sensitivity ranking of λ:"] |
| for name, val in diag["sens_ranked"]: |
| sens_lines.append(f"- {name}: {val:+.6f}") |
|
|
| report = ( |
| f"Regime: {diag['regime']}\n" |
| f"Fragility: {diag['fragility']}\n\n" |
| f"λ = {diag['lambda']:.6f}\n" |
| f"Δ = {diag['Delta']:.6f}\n" |
| f"F = {diag['F']:.6f}\n" |
| f"k* = (α/β)D = {diag['boundary_k']:.6f}\n" |
| f"k − k* = {diag['margin_to_boundary']:.6f}\n\n" |
| f"Posture: {gov['posture']}\n" |
| f"Decision: {gov['decision']}\n" |
| f"Risk note: {gov['risk']}\n\n" |
| + "\n".join(sens_lines) |
| ) |
|
|
| return fig1, fig2, pdf_path, report, diag |
|
|
|
|
| |
| |
| |
| THEME = gr.themes.Soft( |
| primary_hue="teal", |
| secondary_hue="blue", |
| neutral_hue="slate", |
| radius_size=gr.themes.sizes.radius_lg, |
| font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"], |
| ) |
|
|
| CSS = """ |
| .gradio-container {max-width: 1180px !important;} |
| .small {font-size: 12px; opacity: 0.85;} |
| """ |
|
|
| HEADER_MD = r""" |
| # FDRSM-4 — Threshold Engine |
| |
| This Space implements a **decision-facing threshold layer** on top of the FDRSM series. |
| |
| ## Model |
| |
| $$ |
| \dot{R}(t) = (\alpha D - \beta k)R(t) |
| $$ |
| |
| $$ |
| \lambda = \alpha D - \beta k,\quad |
| \Delta = \beta k - \alpha D,\quad |
| F = \frac{|\Delta|}{|\alpha D| + |\beta k| + \varepsilon} |
| $$ |
| |
| **Outputs:** stability regime, fragility class, stability map, trajectory plot, and an extended PDF report (≈ 8 pages). |
| """ |
|
|
| with gr.Blocks(theme=THEME, css=CSS, title="FDRSM-4 — Threshold Engine") as demo: |
| |
| gr.Markdown( |
| HEADER_MD, |
| latex_delimiters=[ |
| {"left": "$$", "right": "$$", "display": True}, |
| {"left": "$", "right": "$", "display": False}, |
| ], |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| alpha = gr.Slider(0.1, 5.0, value=1.0, step=0.1, label="α (dependency amplification)") |
| beta = gr.Slider(0.1, 5.0, value=1.0, step=0.1, label="β (authority damping)") |
| D = gr.Slider(0.0, 10.0, value=3.0, step=0.1, label="D (dependency intensity)") |
| k = gr.Slider(0.0, 10.0, value=3.2, step=0.1, label="k (authority gain)") |
|
|
| R0 = gr.Slider(0.01, 10.0, value=1.0, step=0.01, label="R(0)") |
| T = gr.Slider(1.0, 80.0, value=25.0, step=1.0, label="T (horizon)") |
| n = gr.Slider(200, 6000, value=1400, step=50, label="n (steps)") |
| tol = gr.Slider(0.0, 0.5, value=0.006, step=0.0005, label="tol (near-boundary)") |
| eps = gr.Number(value=1e-12, label="ε", precision=12) |
|
|
| run = gr.Button("Run Threshold Engine", variant="primary") |
|
|
| with gr.Column(scale=1): |
| p1 = gr.Plot(label="Risk trajectory") |
| p2 = gr.Plot(label="Stability map") |
| pdf = gr.File(label="Extended PDF report (≈ 8 pages)") |
| txt = gr.Textbox(label="Decision-facing report", lines=18) |
|
|
| diag_state = gr.State({}) |
|
|
| def _run(alpha, beta, D, k, R0, T, n, tol, eps): |
| fig1, fig2, pdf_path, report, diag = run_engine(alpha, beta, D, k, R0, T, n, tol, eps) |
| return fig1, fig2, pdf_path, report, diag |
|
|
| run.click( |
| fn=_run, |
| inputs=[alpha, beta, D, k, R0, T, n, tol, eps], |
| outputs=[p1, p2, pdf, txt, diag_state] |
| ) |
|
|
| demo.launch() |