"""Shared helpers for 6-String Optimizer HF Space (mystery shell + hf_space core).""" from __future__ import annotations import os import sys import tempfile from dataclasses import dataclass, field from pathlib import Path from typing import Any, Generator import numpy as np import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import pandas as pd import torch _BUNDLE = Path(__file__).resolve().parent if str(_BUNDLE) not in sys.path: sys.path.insert(0, str(_BUNDLE)) if str(_BUNDLE / "src") not in sys.path: sys.path.insert(0, str(_BUNDLE / "src")) from optimizer import GeooptBurstOptimizer, HierarchicalGeooptBurstOptimizer from optimizer.models import SphereRosenbrockModel from optimizer.utils import stereographic_projection from optimizer.losses import rosenbrock_3d GITHUB_URL = "https://github.com/kinaar8340/6-string-optimizer" HF_SPACE_URL = "https://huggingface.co/spaces/kinaar111/6-string-optimizer" QVPIC_URL = "https://huggingface.co/spaces/kinaar111/qvpic" PHYSICS_AUDIO_URL = f"{GITHUB_URL}/tree/main/physics_audio" BOOT_QUOTE_STRING = "HARMONY OF THE SPHERES · SIX-STRING BURST · S³" STARTUP_STRING = "HARMONY OF THE SPHERES · SIX STRING BURST · S THREE" SAMPLE_GUITAR_FILENAME = "demo_guitar_g3.wav" SAMPLE_GUITAR_NOTE = "G3 open string (196 Hz) · 2.5s" STRING_NAMES = ["High E", "B", "G", "D", "A", "Low E"] STRING_COLORS = ["#FF6B6B", "#FF9F43", "#FFD93D", "#6BCB77", "#4D96FF", "#9B59B6"] WINDOWS = [400, 580, 780, 1100, 1600, 2400] BURST_BOOSTS = [1.02, 1.04, 1.06, 1.08, 1.10, 1.15] THETA_BOOSTS = [1.01, 1.02, 1.03, 1.04, 1.05, 1.06] WALLPAPER_URL = f"{GITHUB_URL}/raw/main/physics_audio/overview_composite.png" SIMULATION_BANNER_MD = """ > **Research showcase** on Hugging Face **cpu-basic** (free tier). Default **Quick Demo** mode (~20s optimize). > For full 8k–12k step runs, clone [GitHub](https://github.com/kinaar8340/6-string-optimizer) locally. """ KNOWN_LIMITATIONS_BODY = """ - **CPU only** — optimization + Plotly streaming can feel slow; use **Quick Demo** first. - **WebGL required for 3D** — embedded views, privacy browsers, or older GPUs may block WebGL; use **2D (WebGL-safe)** trajectory view. - **Cold starts** — first load after sleep may take 30–60s to build. - **ETA is approximate** — actual time varies with server load and concurrent users. - **Compare vs RiemannianAdam** doubles wall time; capped at 3000 steps on HF. - **Research mode (12k steps)** — best run [locally](https://github.com/kinaar8340/6-string-optimizer) or upgrade Space hardware. - **Best experience:** desktop Chrome/Firefox · let Quick Demo finish before switching tabs. """.strip() WEBGL_PROBE_JS = """() => { let ok = false; try { if (localStorage.getItem('6string-webgl-failed') === '1') ok = false; else { const c = document.createElement('canvas'); ok = !!(window.WebGLRenderingContext && (c.getContext('webgl') || c.getContext('experimental-webgl'))); } } catch (e) { ok = false; } return ok ? "1" : "0"; }""" TRAJECTORY_VIEW_CHOICES: list[tuple[str, str]] = [ ("Auto (3D if WebGL available)", "auto"), ("3D interactive", "3d"), ("2D projection (WebGL-safe)", "2d"), ] KNOWN_LIMITATIONS_MD = f"### Known limitations (HF free tier)\n{KNOWN_LIMITATIONS_BODY}" LOCAL_RUN_MD = """ **Run locally (recommended for 12k+ steps):** ```bash git clone https://github.com/kinaar8340/6-string-optimizer.git cd 6-string-optimizer/hf_staging && pip install -r requirements.txt && python app.py ``` [View on GitHub](https://github.com/kinaar8340/6-string-optimizer) · [Upgrade Space hardware](https://huggingface.co/spaces/kinaar111/6-string-optimizer/settings) """ INTRO_WALKTHROUGH_HTML = """
Open Space · Quick Demo selected
▶ DEMO GUITAR — hear G3 partials
▶ RUN OPTIMIZATION — loss drops · VU bursts
Theory tab — S³ + burst mechanism
""" WHAT_IS_THIS_MD = """ ### What is this project? An **artistic research demo** for [GeooptBurstOptimizer](https://github.com/kinaar8340/6-string-optimizer) — a novel optimizer on the **3-sphere (S³)** that uses **punctuated-equilibrium bursts** (slow tension buildup → productive escape) inspired by Pythagorean harmonics, musical consonance, and natural avalanche dynamics. **You are not** tuning a real guitar here. **You are** watching how six virtual "strings" monitor stagnation and fire macro-bursts to escape Rosenbrock plateaus where Riemannian Adam often stalls. | Try this first | Action | |----------------|--------| | Hear it | **SPECTRUM** → ▶ DEMO GUITAR (G3) | | See it | **OPTIMIZE** → ▶ RUN OPTIMIZATION | | Understand it | Open **Theory** tab (nav bar) | | Full tour | Keypad **11** — skippable anytime | [GitHub repo](https://github.com/kinaar8340/6-string-optimizer) · [Physics audio](https://github.com/kinaar8340/6-string-optimizer/tree/main/physics_audio) """ ONBOARDING_MD = """ ### 60-second guided tour *(skippable — dismiss welcome card or press CLEAR)* **1 — The metaphor** Six virtual guitar strings wrap the base optimizer. Each watches a **stagnation window** (400 → 2400 steps, Pythagorean ratios). Tension rises → **burst** fires → plateau escape. **2 — Optimize (~30s on HF)** **OPTIMIZE** tab → choose a preset (Standard / Fisher-Rao / Aggressive) → ▶ RUN OPTIMIZATION. Watch S³→R³ trajectory, loss envelope, per-string VU meters (High E → Low E), twist accumulation. **3 — Listen** **SPECTRUM** → ▶ DEMO GUITAR (G3) — bundled 2.5s open G string. Waveform · STFT · partial tracks · string energy. **4 — Physics connection** **PHYSICS** tab — Smith chart (Γ reflection / mismatch analogy) + reconstruction pyramid overview. Full Stiefel-manifold training runs locally via `physics_audio/run_real_audio.py`. **5 — Keypad** PROG 1–6 = string layers · 7 = optimize · 8 = demo guitar · 11 = replay this tour. Press **Theory** in the nav bar for equations and burst mechanism diagrams. """ THEORY_MD = """ ## Theory — How it works ### S³ optimization in plain language Parameters live on the **unit 3-sphere S³** (quaternion q, |q| = 1). We optimize a loss on **stereographic projection** u = π(q) ∈ ℝ³ — a compact view of ℝ³ plus a point at infinity. The demo target is Rosenbrock in 3D; the global minimum sits at u = (1, 1, 1). ### Burst mechanism — tension → escape Standard Riemannian SGD/Adam can **stall on plateaus**. GeooptBurstOptimizer tracks stagnation: when loss improvement drops below threshold over a sliding window, it applies a **macro-burst** — a controlled geodesic jump with damped momentum — to escape the plateau. ``` loss │ ╭── plateau ──╮ │ ╱ ╲___ burst lands lower │───╱ tension ↑ ╲ └──────────────────────── step └── window fills → BURST ``` Six **hierarchical layers** (virtual strings) use staggered windows and boost factors — like coupled oscillators at Pythagorean ratios (400 : 580 : 780 : …). ### Key equations - **Stereographic map:** u = (q₁/(1+q₀), q₂/(1+q₀), q₃/(1+q₀)) - **Stagnation trigger:** |Δloss| < ε over window Wᵢ → burst on layer i - **Burst factor:** scales geodesic step magnitude (cap = burst_factor_max) - **Twist accumulation:** tracks cumulative rotation from burst jumps on S³ - **Fisher-Rao modulation (optional):** uses information geometry to shape burst severity ### Why guitar / harmonics? - **Harmony of the Spheres** — optimization layers echo consonant frequency ratios. - **VU meters** — each bar is a real string name (High E … Low E), showing stagnation *tension*. - **Spectrum tab** — real audio partials foreshadow the **physics-audio** inverse problem: recover damped, inharmonic modal parameters on the **Stiefel manifold** from a recording. ### Smith chart (PHYSICS tab) In the physics-audio trainer, **Γ (reflection coefficient)** measures how far candidate modal parameters are from matching the observed spectrum. The Smith chart plots Γ in the complex plane — like impedance matching in RF engineering, but here "matching" means **reconstructing string vibration modes**. ### vs. Riemannian Adam | | Burst optimizer | Riemannian Adam | |--|-----------------|-----------------| | Plateau escape | Macro-bursts on stagnation | Smooth small steps only | | Hierarchy | Six staggered windows | Single optimizer | | Geometry | S³ + optional Fisher-Rao | Manifold-aware steps | Full benchmarks and derivations: [GitHub](https://github.com/kinaar8340/6-string-optimizer). """ KATEX_CDN = """ """ THEORY_KATEX_HTML = """

Key equations (KaTeX)

Stereographic projection from unit quaternion $q=(q_0,q_1,q_2,q_3)$:

$$u_i = \\frac{q_i}{1+q_0}, \\quad i=1,2,3$$

Stagnation burst trigger when loss improvement falls below $\\varepsilon$ over window $W_i$:

$$|\\mathcal{L}_t - \\mathcal{L}_{t-W_i}| < \\varepsilon \\;\\Rightarrow\\; \\text{BURST}_i$$

Ideal string tension (frequency–length relation):

$$f = \\frac{1}{2L}\\sqrt{\\frac{T}{\\mu}} \\quad\\Rightarrow\\quad T = 4\\mu L^2 f^2$$

Inharmonic partial (physics-audio modal model):

$$f_k = k\\,f_0\\sqrt{1 + B k^2}$$

Smith chart reflection coefficient (modal mismatch):

$$\\Gamma = \\frac{Z - Z_0}{Z + Z_0}$$
""" CLAIMS_MD = """ | What you see | What it demonstrates | |--------------|----------------------| | **Six-string VU meters** | High E, B, G, D, A, Low E — real-time stagnation tension per layer | | **S³ trajectory** | Stereographic path toward Rosenbrock minimum [1,1,1] | | **Burst presets** | Standard · Fisher-Rao enhanced · Aggressive plateau escape | | **Demo guitar G3** | Waveform, STFT, partial tracks, per-string spectral energy | | **Smith chart** | Γ-mismatch visualization from physics-audio training | | **Terminal UI** | Research-console aesthetic — keypad, phosphor scan, streaming readouts | *Not included in-browser:* full modal synthesis, multi-string upload batch, tension/gauge calculator. Those live in the [physics_audio](https://github.com/kinaar8340/6-string-optimizer/tree/main/physics_audio) repo. """ RUN_MODES: dict[str, dict[str, Any]] = { "quick": { "label": "Quick Demo", "total_steps": 2500, "steps_per_update": 125, "learning_rate": 0.03, "burst_factor_max": 8.0, "use_fisher_modulation": False, "compare_adam": False, "audio_duration": 1.5, "eta_hint": "~15–25s optimize on HF cpu-basic", "description": "Recommended first visit — fewer Plotly updates, faster finish.", }, "standard": { "label": "Standard HF", "total_steps": 4000, "steps_per_update": 100, "learning_rate": 0.03, "burst_factor_max": 8.0, "use_fisher_modulation": False, "compare_adam": False, "audio_duration": 2.0, "eta_hint": "~35–50s optimize on HF cpu-basic", "description": "Balanced public demo — still tuned for free-tier CPU.", }, "research": { "label": "Full Research Run", "total_steps": 12000, "steps_per_update": 80, "learning_rate": 0.03, "burst_factor_max": 8.0, "use_fisher_modulation": False, "compare_adam": False, "audio_duration": 3.0, "eta_hint": "~2–4 min on HF — may timeout; prefer local clone", "description": "Long run for plateau study — heavy on free tier.", }, } OPTIMIZATION_PRESETS: dict[str, dict[str, Any]] = { "standard": { "label": "Standard Burst", "total_steps": 4000, "steps_per_update": 100, "learning_rate": 0.03, "burst_factor_max": 8.0, "use_fisher_modulation": False, "description": "Balanced — matches Standard HF run mode.", }, "fisher_rao": { "label": "Fisher-Rao Enhanced", "total_steps": 4000, "steps_per_update": 100, "learning_rate": 0.025, "burst_factor_max": 8.0, "use_fisher_modulation": True, "description": "Information-geometric burst shaping — smoother escapes.", }, "aggressive": { "label": "Aggressive Escape", "total_steps": 6000, "steps_per_update": 80, "learning_rate": 0.05, "burst_factor_max": 12.0, "use_fisher_modulation": False, "description": "Larger bursts — use Research mode locally for 12k steps.", }, } SLIDER_TOOLTIPS = { "total_steps": "Optimizer steps. Quick Demo uses 2500 · Standard HF 4000 · Research 12000.", "steps_per_update": "Steps between UI refreshes — higher = fewer Plotly redraws (faster on HF).", "learning_rate": "Base Riemannian step size before burst scaling.", "burst_factor_max": "Maximum burst jump magnitude — higher = more aggressive plateau escape.", "use_fisher": "Enable Fisher-Rao information geometry to modulate burst severity.", "audio_duration": "Seconds of audio to analyze (trimmed from start).", } FIGURES_INTRO_MD = ( "Physics-audio reference figures — bundled overview composite and HF-safe Smith chart preview. " "Regenerate full pyramid plots locally with `run_real_audio.py`." ) FIGURE_URLS = ( str(_BUNDLE / "assets" / "overview_composite.png"), "", # filled at runtime by render_smith_chart_preview f"{GITHUB_URL}/raw/main/physics_audio/overview_composite.png", f"{GITHUB_URL}/raw/main/physics_audio/training_evaluation/viz.py", ) PROG_ACTIONS: dict[int, tuple[str, str]] = { 1: ("high_e", "High E string — window 400 · burst 1.02×"), 2: ("b_string", "B string — window 580 · burst 1.04×"), 3: ("g_string", "G string — window 780 · burst 1.06×"), 4: ("d_string", "D string — window 1100 · burst 1.08×"), 5: ("a_string", "A string — window 1600 · burst 1.10×"), 6: ("low_e", "Low E string — window 2400 · burst 1.15×"), 7: ("run_optimize", "Run Rosenbrock S³ optimization"), 8: ("demo_guitar", "Analyze bundled demo guitar (G3)"), 9: ("guided_tour", "60-second guided onboarding tour"), 10: ("string_table", "Six-string hierarchy reference"), 11: ("settings", "Open tuning & parameter dials"), 12: ("about", "About / credits / build stamp"), } TERM_KEY_ACTIONS: dict[int, tuple[str, str]] = { 1: ("home", "Return to selection menu"), 2: ("status", "Live optimizer & environment status"), 3: ("scope", "What this Space runs vs local pipeline"), 4: ("directory", "Repo layout & paths"), 5: ("results", "Six-string hierarchy snapshot"), 6: ("build", "Build stamp & deploy info"), 7: ("help", "D-pad / keypad navigation"), 8: ("scan", "Phosphor signal scan — CSS only, any key exits"), 9: ("strings", "Six-string layer catalog"), 10: ("physics", "Physics-audio pipeline overview"), 11: ("tour", "Guided onboarding tour"), 12: ("figures", "Smith chart + pyramid figure index"), } def is_hf_space() -> bool: return bool(os.environ.get("SPACE_ID")) def get_build_label() -> str: try: from build_info import BUILD_COMMIT, BUILD_UPDATED_UTC # noqa: WPS433 return f"Build {BUILD_UPDATED_UTC} UTC · `{BUILD_COMMIT}`" except ImportError: return "Local dev build" def get_sample_guitar_path() -> Path: candidates = ( _BUNDLE / "assets" / SAMPLE_GUITAR_FILENAME, Path(__file__).resolve().parent / "assets" / SAMPLE_GUITAR_FILENAME, ) for path in candidates: if path.is_file(): return path raise FileNotFoundError( f"Demo guitar not found — expected assets/{SAMPLE_GUITAR_FILENAME} in Space bundle." ) def get_overview_composite_path() -> Path: path = _BUNDLE / "assets" / "overview_composite.png" if path.is_file(): return path return Path(FIGURE_URLS[2]) def default_run_params() -> dict[str, Any]: on_hf = is_hf_space() if on_hf: return dict(RUN_MODES["quick"]) research = dict(RUN_MODES["research"]) research["total_steps"] = 20000 research["audio_duration"] = 4.0 return research def get_run_mode_params(mode: str) -> dict[str, Any]: """Return slider values for quick / standard / research run mode.""" base = default_run_params() spec = RUN_MODES.get(mode, RUN_MODES["quick"]) base.update({k: v for k, v in spec.items() if k not in ("label", "description", "eta_hint")}) return base def estimate_runtime_sec(total_steps: int, steps_per_update: int, *, compare: bool = False) -> float: """Heuristic seconds for HF cpu-basic (empirical ~0.008s per step + plot overhead).""" updates = max(1, total_steps // max(1, steps_per_update)) base = total_steps * 0.009 + updates * 0.35 return base * (1.9 if compare else 1.0) def format_eta_status(step: int, total: int, elapsed_s: float, *, compare: bool = False) -> str: approx = " (approx., server load varies)" if step <= 0 or elapsed_s <= 0: est = estimate_runtime_sec(total, max(1, total // max(1, 20)), compare=compare) return f"Estimated runtime: ~{est:.0f}s on HF cpu-basic{approx}" rate = step / elapsed_s remaining = max(0, total - step) eta = remaining / rate if rate > 0 else 0 return f"Elapsed {elapsed_s:.0f}s · ETA ~{eta:.0f}s{approx} · {100 * step / total:.0f}%" @dataclass class PlotStreamConfig: trajectory_every: int = 1 trajectory_max_points: int = 300 loss_max_points: int = 800 live_3d: bool = True @classmethod def for_run_mode(cls, mode: str) -> "PlotStreamConfig": if mode == "quick": return cls(trajectory_every=3, trajectory_max_points=100, loss_max_points=350, live_3d=False) if mode == "standard": return cls(trajectory_every=2, trajectory_max_points=180, loss_max_points=500, live_3d=True) return cls(trajectory_every=1, trajectory_max_points=400, loss_max_points=1500, live_3d=True) def resolve_trajectory_mode(view_pref: str, webgl_ok: bool) -> str: """Map UI preference + client WebGL probe to '3d' or '2d'.""" pref = (view_pref or "auto").strip().lower() if pref == "2d": return "2d" if pref == "3d": return "3d" if webgl_ok else "2d" return "2d" if not webgl_ok else "3d" def default_trajectory_view() -> str: """HF free tier defaults to 2D for maximum browser compatibility.""" return "2d" if is_hf_space() else "auto" def webgl_status_markdown(webgl_ok: bool, view_pref: str) -> str: if is_hf_space() and view_pref == "2d": lead = ( "**HF free tier:** **2D (WebGL-safe)** trajectory is the default for maximum compatibility. " "Switch to **3D interactive** if your browser supports WebGL." ) if not webgl_ok: return f"⚠ {lead} (WebGL probe failed — staying in 2D.)" return lead if not webgl_ok: return ( "⚠ **WebGL unavailable** — trajectory uses **2D stereographic projections**. " "Select **2D (WebGL-safe)** below, or try desktop Chrome/Firefox." ) if view_pref == "2d": return "Showing **2D projections** (WebGL-safe mode)." if view_pref == "3d": return "✓ WebGL detected — **interactive 3D** trajectory." return "✓ WebGL detected — **Auto** will use 3D trajectory." def _subsample_df(df: pd.DataFrame, max_points: int) -> pd.DataFrame: if len(df) <= max_points: return df idx = np.linspace(0, len(df) - 1, max_points, dtype=int) return df.iloc[idx].reset_index(drop=True) def friendly_error(context: str, exc: Exception) -> str: """User-facing error text with actionable hints.""" msg = str(exc).strip() or type(exc).__name__ hints: dict[str, str] = { "optimize": ( "Optimization hit an error. Try **Quick Demo** mode, fewer steps, or press ■ CANCEL and retry. " "If this persists, run locally from GitHub." ), "spectrum": ( "Audio analysis failed. Try **▶ DEMO GUITAR (G3)** (bundled WAV) instead of upload, " "or shorten **Audio duration** to 1.5s." ), "synthesis": ( "Modal synthesis failed. Analyze demo guitar first, or reduce duration / inharmonicity B." ), "physics": "Physics preview failed. Smith chart will regenerate on retry — overview image is bundled.", "asset": ( f"Missing bundled asset ({msg}). Re-deploy the Space or use the GitHub repo copy." ), "webgl": ( "3D trajectory needs WebGL. Switch **Trajectory view** to **2D (WebGL-safe)** " "in the OPTIMIZE tab, or open in desktop Chrome/Firefox." ), } lead = hints.get(context, "Something went wrong.") return f"⚠ {lead}\n\nDetails: {msg[:300]}" def _configure_cpu_threads() -> None: threads = 4 if is_hf_space() else min(8, (os.cpu_count() or 8) // 2) torch.set_num_threads(threads) torch.set_num_interop_threads(2) @dataclass class OptimizerMonitor: layer_names: list[str] history: dict[str, list] = field(default_factory=dict) layer_tension: dict[str, float] = field(default_factory=dict) burst_events: list[tuple[int, str, str]] = field(default_factory=list) def __post_init__(self) -> None: self.history = { "step": [], "loss": [], "u_x": [], "u_y": [], "u_z": [], "twist": [], } self.layer_tension = {name: 0.0 for name in self.layer_names} def record_step(self, step: int, loss_val: float, u: np.ndarray, twist: float = 0.0) -> None: self.history["step"].append(step) self.history["loss"].append(loss_val) self.history["u_x"].append(float(u[0])) self.history["u_y"].append(float(u[1])) self.history["u_z"].append(float(u[2])) self.history["twist"].append(twist) def record_burst(self, step: int, layer_name: str, severity: float = 0.0) -> None: self.burst_events.append((step, layer_name, "BURST")) self.layer_tension[layer_name] = severity for name in self.layer_names: if name != layer_name: self.layer_tension[name] *= 0.85 def _build_hierarchical_optimizer( model: SphereRosenbrockModel, *, lr: float, burst_factor_max: float, use_fisher_modulation: bool, ) -> HierarchicalGeooptBurstOptimizer: base_opt = GeooptBurstOptimizer( model.parameters(), lr=lr, burst_factor_max=burst_factor_max, damping_min=0.98, damping=0.995, max_theta=torch.pi / 20, verbose=False, use_fisher_modulation=use_fisher_modulation, ) opt: Any = base_opt for name, win, bb, tb in zip(STRING_NAMES, WINDOWS, BURST_BOOSTS, THETA_BOOSTS): opt = HierarchicalGeooptBurstOptimizer( opt, stagnation_window=win, stagnation_thresh=5e-5, burst_boost=bb, theta_boost=tb, name=name, verbose=False, ) return opt def _make_trajectory_2d_figure( df: pd.DataFrame, *, max_points: int | None = None, lite: bool = False, webgl_fallback: bool = False, ) -> go.Figure: """Canvas/SVG 2D projections — no WebGL required.""" if df.empty or not {"u_x", "u_y", "u_z"}.issubset(df.columns): return _trajectory_placeholder("Waiting for trajectory data…") plot_df = _subsample_df(df, max_points) if max_points else df marker_size = 3 if lite else 4 fallback_note = " · 2D fallback (WebGL unavailable)" if webgl_fallback else "" fig = make_subplots( rows=1, cols=2, subplot_titles=("u_x vs u_y", "u_y vs u_z"), horizontal_spacing=0.1, ) marker_kw = dict( size=marker_size, color=plot_df["loss"], colorscale="Viridis", showscale=not lite, colorbar=dict(title="loss", len=0.55, y=0.5) if not lite else None, ) line_kw = dict(width=1 if lite else 2, color="rgba(255,170,0,0.65)") for col, xcol, ycol in ((1, "u_x", "u_y"), (2, "u_y", "u_z")): fig.add_trace( go.Scatter( x=plot_df[xcol], y=plot_df[ycol], mode="lines+markers", marker=marker_kw, line=line_kw, name=f"{xcol}/{ycol}", showlegend=False, ), row=1, col=col, ) fig.add_trace( go.Scatter( x=[1], y=[1], mode="markers", marker=dict(size=9, color="#FF4444", symbol="diamond"), name="min [1,1,1]", showlegend=False, ), row=1, col=col, ) fig.update_layout( title=f"S³ → R³ Stereographic — 2D projections{fallback_note}", paper_bgcolor="#0a0a0a", font=dict(color="#FFB000"), height=380, margin=dict(l=40, r=20, t=50, b=30), ) for col, xlab, ylab in ((1, "u_x", "u_y"), (2, "u_y", "u_z")): fig.update_xaxes(title_text=xlab, gridcolor="#333", row=1, col=col) fig.update_yaxes(title_text=ylab, gridcolor="#333", row=1, col=col) return fig def _make_trajectory_figure( df: pd.DataFrame, *, max_points: int | None = None, lite: bool = False, ) -> go.Figure: plot_df = _subsample_df(df, max_points) if max_points else df marker_size = 1 if lite else 2 fig = go.Figure() fig.add_trace( go.Scatter3d( x=plot_df["u_x"], y=plot_df["u_y"], z=plot_df["u_z"], mode="lines+markers", marker=dict( size=marker_size, color=plot_df["loss"], colorscale="Viridis", showscale=not lite, ), line=dict(width=1 if lite else 2, color="rgba(255,170,0,0.6)"), name="Trajectory", ) ) fig.add_trace( go.Scatter3d( x=[1], y=[1], z=[1], mode="markers", marker=dict(size=10, color="#FF4444", symbol="diamond"), name="Global min [1,1,1]", ) ) fig.update_layout( title="S³ → R³ Stereographic Trajectory (drag to rotate)", scene=dict( xaxis_title="u_x", yaxis_title="u_y", zaxis_title="u_z", aspectmode="cube", bgcolor="rgba(10,10,10,0.9)", camera=dict(eye=dict(x=1.6, y=1.4, z=1.1)), ), paper_bgcolor="#0a0a0a", font=dict(color="#FFB000"), margin=dict(l=0, r=0, t=40, b=0), height=380, ) return fig def make_trajectory_figure( df: pd.DataFrame, *, mode: str = "3d", max_points: int | None = None, lite: bool = False, webgl_fallback: bool = False, ) -> go.Figure: """Dispatch 3D WebGL or 2D canvas trajectory.""" if mode == "2d": return _make_trajectory_2d_figure( df, max_points=max_points, lite=lite, webgl_fallback=webgl_fallback, ) if df.empty: return _trajectory_placeholder("Waiting for trajectory data…") return _make_trajectory_figure(df, max_points=max_points, lite=lite) def _make_loss_figure( df: pd.DataFrame, *, max_points: int | None = None, monitor: OptimizerMonitor | None = None, step: int | None = None, best_loss: float | None = None, ) -> go.Figure: if df.empty or "loss" not in df.columns: fig = go.Figure() fig.add_annotation( text="Loss curve will appear after the first optimizer update", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=13, color="#FFB000"), ) fig.update_layout( title="Loss Envelope (log scale)", paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), height=280, xaxis=dict(visible=False), yaxis=dict(visible=False), ) return fig plot_df = _subsample_df(df, max_points) if max_points else df current_loss = float(plot_df["loss"].iloc[-1]) best = best_loss if best_loss is not None else float(plot_df["loss"].min()) cur_step = step if step is not None else int(plot_df["step"].iloc[-1]) burst_n = len(monitor.burst_events) if monitor else 0 title = ( f"Loss Envelope — step {cur_step:,} · current {current_loss:.4g} · " f"best {best:.4g} · {burst_n} burst{'s' if burst_n != 1 else ''}" ) fig = px.line(plot_df, x="step", y="loss", title=title, log_y=True) fig.update_traces(line=dict(color="#FFB000", width=2)) if monitor: color_cycle = STRING_COLORS for i, (burst_step, layer, _) in enumerate(monitor.burst_events[-12:]): color = color_cycle[i % len(color_cycle)] fig.add_vline( x=burst_step, line_width=1, line_dash="dot", line_color=color, ) if i == len(monitor.burst_events[-12:]) - 1: fig.add_annotation( x=burst_step, y=current_loss, text=f"{layer} burst", showarrow=False, font=dict(size=9, color=color), yshift=12, ) losses = plot_df["loss"].astype(float) y_min = max(losses.min() * 0.5, 1e-12) y_max = max(losses.max() * 2.0, y_min * 10) fig.update_layout( paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), xaxis=dict(gridcolor="#333", title="Step"), yaxis=dict(gridcolor="#333", title="Loss", type="log", range=[np.log10(y_min), np.log10(y_max)]), height=280, margin=dict(l=40, r=20, t=50, b=30), ) return fig def _make_string_vu_figure(monitor: OptimizerMonitor) -> go.Figure: tensions = [monitor.layer_tension[name] for name in monitor.layer_names] fig = go.Figure() for name, tension, color in zip(monitor.layer_names, tensions, STRING_COLORS): fig.add_trace( go.Bar( y=[name], x=[tension], orientation="h", marker=dict(color=color, line=dict(color="#222", width=1)), showlegend=False, ) ) max_t = max(tensions) if tensions else 1.0 fig.update_layout( title="Six-String VU — High E · B · G · D · A · Low E (stagnation tension)", barmode="overlay", xaxis=dict(range=[0, max(max_t * 1.3, 0.001)], title="Tension → burst trigger"), paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000", family="monospace"), height=320, margin=dict(l=60, r=20, t=40, b=30), ) return fig def _make_twist_figure(df: pd.DataFrame, *, max_points: int | None = None) -> go.Figure: plot_df = _subsample_df(df, max_points) if max_points else df fig = px.area(plot_df, x="step", y="twist", title="Twist Accumulation") fig.update_traces(fillcolor="rgba(255,170,0,0.3)", line=dict(color="#FF8800")) fig.update_layout( paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), height=220, margin=dict(l=40, r=20, t=40, b=30), ) return fig def _trajectory_placeholder(message: str = "3D trajectory paused in Quick mode — see loss & VU") -> go.Figure: fig = go.Figure() fig.add_annotation( text=message, xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="#FFB000"), ) fig.update_layout( title="S³ Trajectory (lite stream)", paper_bgcolor="#0a0a0a", font=dict(color="#FFB000"), height=280, xaxis=dict(visible=False), yaxis=dict(visible=False), ) return fig def _build_live_figures( df: pd.DataFrame, monitor: OptimizerMonitor, *, plot_cfg: PlotStreamConfig, update_idx: int, step: int, total_steps: int, last_traj: go.Figure | None, loss_builder, trajectory_mode: str = "3d", trajectory_webgl_fallback: bool = False, best_loss: float | None = None, ) -> tuple[go.Figure, go.Figure, go.Figure, go.Figure]: """Throttle expensive 3D redraws; subsample points for 2D plots.""" final = step >= total_steps lite = not final loss_fig = _make_loss_figure( df, max_points=None if final else plot_cfg.loss_max_points, monitor=monitor, step=step, best_loss=best_loss, ) vu_fig = _make_string_vu_figure(monitor) twist_fig = _make_twist_figure(df, max_points=None if final else plot_cfg.loss_max_points) use_2d = trajectory_mode == "2d" refresh_traj = final or (update_idx % plot_cfg.trajectory_every == 0) or use_2d if not plot_cfg.live_3d and not final and not use_2d: traj_fig = last_traj or _trajectory_placeholder() elif refresh_traj: traj_fig = make_trajectory_figure( df, mode=trajectory_mode, max_points=None if final else plot_cfg.trajectory_max_points, lite=lite and not final, webgl_fallback=trajectory_webgl_fallback, ) else: traj_fig = last_traj or make_trajectory_figure( df, mode=trajectory_mode, max_points=plot_cfg.trajectory_max_points, lite=True, webgl_fallback=trajectory_webgl_fallback, ) if loss_builder is not None: loss_fig = loss_builder(df if final else _subsample_df(df, plot_cfg.loss_max_points)) return traj_fig, loss_fig, vu_fig, twist_fig def run_optimization_stream( *, total_steps: int, steps_per_update: int, learning_rate: float, burst_factor_max: float, use_fisher_modulation: bool, plot_cfg: PlotStreamConfig | None = None, trajectory_mode: str = "3d", trajectory_webgl_fallback: bool = False, progress_cb=None, ) -> Generator[tuple, None, None]: import time _configure_cpu_threads() np.random.seed(42) torch.manual_seed(42) t0 = time.monotonic() model = SphereRosenbrockModel(num_instances=1) monitor = OptimizerMonitor(STRING_NAMES) def closure(): model.zero_grad() q = model() u = stereographic_projection(q) loss = rosenbrock_3d(u).mean() loss.backward() return loss opt = _build_hierarchical_optimizer( model, lr=learning_rate, burst_factor_max=burst_factor_max, use_fisher_modulation=use_fisher_modulation, ) plot_cfg = plot_cfg or PlotStreamConfig.for_run_mode("standard") step = 0 best_loss = float("inf") prev_loss = float("inf") loss_val = float("inf") update_idx = 0 last_traj: go.Figure | None = None while step < total_steps: for _ in range(steps_per_update): loss = opt.step(closure) loss_val = loss.item() if loss_val < best_loss: best_loss = loss_val if abs(prev_loss - loss_val) < 5e-5 and step > 100: layer_idx = min(step // 500, len(STRING_NAMES) - 1) monitor.record_burst( step, STRING_NAMES[layer_idx], severity=abs(prev_loss - loss_val) * 1e4 ) prev_loss = loss_val with torch.no_grad(): u = stereographic_projection(model.q).squeeze(0).cpu().numpy() twist = float(getattr(opt, "twist_accum", 0.0)) if hasattr(opt, "twist_accum") else 0.0 monitor.record_step(step, loss_val, u, twist=twist) step += 1 if step >= total_steps: break if progress_cb is not None: progress_cb(step / total_steps, desc=f"Optimizing step {step}/{total_steps}") df = pd.DataFrame(monitor.history) elapsed = time.monotonic() - t0 eta = format_eta_status(step, total_steps, elapsed) status = ( f"STEP {step:,} / {total_steps:,} │ LOSS {loss_val:.6f} │ " f"BEST {best_loss:.6f} │ BURSTS {len(monitor.burst_events)} │ {eta}" ) update_idx += 1 traj_fig, loss_fig, vu_fig, twist_fig = _build_live_figures( df, monitor, plot_cfg=plot_cfg, update_idx=update_idx, step=step, total_steps=total_steps, last_traj=last_traj, loss_builder=None, trajectory_mode=trajectory_mode, trajectory_webgl_fallback=trajectory_webgl_fallback, best_loss=best_loss, ) last_traj = traj_fig yield (traj_fig, loss_fig, vu_fig, twist_fig, status, dict(monitor.history)) if progress_cb is not None: progress_cb(1.0, desc="Optimization complete") def _subsample_series(steps: list, values: list, max_points: int) -> tuple[list, list]: if len(steps) <= max_points: return steps, values idx = np.linspace(0, len(steps) - 1, max_points, dtype=int) return [steps[i] for i in idx], [values[i] for i in idx] def _make_dual_loss_figure( hist_burst: dict, hist_adam: dict, *, max_points: int | None = None, ) -> go.Figure: hb, ha = hist_burst, hist_adam if max_points and len(hb["step"]) > max_points: s, l = _subsample_series(hb["step"], hb["loss"], max_points) hb = {"step": s, "loss": l} s2, l2 = _subsample_series(ha["step"], ha["loss"], max_points) ha = {"step": s2, "loss": l2} fig = go.Figure() fig.add_trace( go.Scatter( x=hb["step"], y=hb["loss"], mode="lines", name="6-String Burst", line=dict(color="#FFB000", width=2), ) ) fig.add_trace( go.Scatter( x=ha["step"], y=ha["loss"], mode="lines", name="RiemannianAdam", line=dict(color="#4D96FF", width=2, dash="dot"), ) ) fig.update_layout( title="Burst vs RiemannianAdam (same init, log scale)", yaxis_type="log", paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), xaxis=dict(gridcolor="#333", title="Step"), yaxis=dict(gridcolor="#333", title="Loss"), height=300, legend=dict(orientation="h", y=1.12), margin=dict(l=50, r=20, t=50, b=40), ) return fig def run_comparison_stream( *, total_steps: int, steps_per_update: int, learning_rate: float, burst_factor_max: float, use_fisher_modulation: bool, plot_cfg: PlotStreamConfig | None = None, trajectory_mode: str = "3d", trajectory_webgl_fallback: bool = False, progress_cb=None, ) -> Generator[tuple, None, None]: """Side-by-side burst optimizer vs geoopt RiemannianAdam on identical Rosenbrock S³ init.""" import time from geoopt.optim import RiemannianAdam _configure_cpu_threads() np.random.seed(42) torch.manual_seed(42) t0 = time.monotonic() cap = min(int(total_steps), 3000 if is_hf_space() else int(total_steps)) steps_per_update = int(steps_per_update) model_burst = SphereRosenbrockModel(num_instances=1) init_q = model_burst.q.data.clone() model_adam = SphereRosenbrockModel(num_instances=1) model_adam.q.data.copy_(init_q) monitor = OptimizerMonitor(STRING_NAMES) hist_burst: dict[str, list] = {"step": [], "loss": []} hist_adam: dict[str, list] = {"step": [], "loss": []} def closure_burst(): model_burst.zero_grad() u = stereographic_projection(model_burst()) loss = rosenbrock_3d(u).mean() loss.backward() return loss def closure_adam(): model_adam.zero_grad() u = stereographic_projection(model_adam()) loss = rosenbrock_3d(u).mean() loss.backward() return loss opt_burst = _build_hierarchical_optimizer( model_burst, lr=learning_rate, burst_factor_max=burst_factor_max, use_fisher_modulation=use_fisher_modulation, ) opt_adam = RiemannianAdam([model_adam.q], lr=learning_rate) plot_cfg = plot_cfg or PlotStreamConfig.for_run_mode("standard") step = 0 best_b = best_a = float("inf") loss_b = loss_a = float("inf") update_idx = 0 last_traj: go.Figure | None = None while step < cap: for _ in range(steps_per_update): loss_b = opt_burst.step(closure_burst).item() loss_a = opt_adam.step(closure_adam).item() best_b = min(best_b, loss_b) best_a = min(best_a, loss_a) hist_burst["step"].append(step) hist_burst["loss"].append(loss_b) hist_adam["step"].append(step) hist_adam["loss"].append(loss_a) with torch.no_grad(): u = stereographic_projection(model_burst.q).squeeze(0).cpu().numpy() twist = float(getattr(opt_burst, "twist_accum", 0.0)) if hasattr(opt_burst, "twist_accum") else 0.0 monitor.record_step(step, loss_b, u, twist=twist) step += 1 if step >= cap: break if progress_cb is not None: progress_cb(step / cap, desc=f"Compare step {step}/{cap}") df = pd.DataFrame(monitor.history) elapsed = time.monotonic() - t0 eta = format_eta_status(step, cap, elapsed, compare=True) status = ( f"COMPARE {step:,}/{cap:,} │ BURST {loss_b:.6f} (best {best_b:.6f}) │ " f"ADAM {loss_a:.6f} (best {best_a:.6f}) │ Δ {loss_a - loss_b:+.4f} │ {eta}" ) update_idx += 1 traj_fig, loss_fig, vu_fig, twist_fig = _build_live_figures( df, monitor, plot_cfg=plot_cfg, update_idx=update_idx, step=step, total_steps=cap, last_traj=last_traj, loss_builder=lambda d: _make_dual_loss_figure( hist_burst, hist_adam, max_points=None if step >= cap else plot_cfg.loss_max_points, ), trajectory_mode=trajectory_mode, trajectory_webgl_fallback=trajectory_webgl_fallback, best_loss=best_b, ) last_traj = traj_fig yield (traj_fig, loss_fig, vu_fig, twist_fig, status, dict(monitor.history)) if progress_cb is not None: progress_cb(1.0, desc="Comparison complete") def save_plotly_png(fig: go.Figure, filename: str) -> str | None: """Export plotly figure to PNG (kaleido) or HTML fallback.""" out_dir = Path(tempfile.gettempdir()) / "string_opt_exports" out_dir.mkdir(parents=True, exist_ok=True) png_path = out_dir / filename try: fig.write_image(str(png_path), width=1100, height=700, scale=2) return str(png_path) except Exception: html_path = png_path.with_suffix(".html") fig.write_html(str(html_path)) return str(html_path) def export_optimization_plots(history: dict | None) -> tuple[str | None, str | None, str | None]: """Rebuild figures from stored history and export trajectory, loss, VU PNGs.""" if not history or not history.get("step"): return None, None, None df = pd.DataFrame(history) monitor = OptimizerMonitor(STRING_NAMES) for i, step in enumerate(history["step"]): u = np.array([history["u_x"][i], history["u_y"][i], history["u_z"][i]]) monitor.record_step(int(step), history["loss"][i], u, twist=history.get("twist", [0])[i]) return ( save_plotly_png(make_trajectory_figure(df, mode="2d"), "trajectory.png"), save_plotly_png(_make_loss_figure(df), "loss.png"), save_plotly_png(_make_string_vu_figure(monitor), "vu.png"), ) # --- Guitar string tension calculator (complementary simple tool) --- STANDARD_TUNING: dict[str, dict[str, float]] = { "High E": {"freq_hz": 329.63, "gauge_in": 0.010}, "B": {"freq_hz": 246.94, "gauge_in": 0.013}, "G": {"freq_hz": 196.00, "gauge_in": 0.017}, "D": {"freq_hz": 146.83, "gauge_in": 0.026}, "A": {"freq_hz": 110.00, "gauge_in": 0.036}, "Low E": {"freq_hz": 82.41, "gauge_in": 0.046}, } DEFAULT_SCALE_LENGTH_IN = 25.5 def calculate_tension_lbs(freq_hz: float, scale_length_in: float, gauge_in: float) -> float: """D'Addario-style unit-weight approximation: tension in pounds.""" unit_weight = 0.000121547 * (gauge_in ** 2) return float((unit_weight * (2.0 * scale_length_in * freq_hz) ** 2) / 386.088) def tension_calculator_table(scale_length_in: float = DEFAULT_SCALE_LENGTH_IN) -> str: lines = [ f"### String tension reference (scale {scale_length_in:.1f}\")", "", "| String | Freq (Hz) | Gauge (in) | Tension (lb) |", "|--------|-----------|------------|--------------|", ] for name, spec in STANDARD_TUNING.items(): t = calculate_tension_lbs(spec["freq_hz"], scale_length_in, spec["gauge_in"]) lines.append( f"| **{name}** | {spec['freq_hz']:.2f} | {spec['gauge_in']:.3f} | **{t:.1f}** |" ) lines.extend([ "", "Formula: $T = UW \\cdot (2 L f)^2 / 386.088$ with unit weight $UW \\propto d^2$.", "Adjust scale length below for baritone/short-scale guitars.", ]) return "\n".join(lines) def tension_single_string( string_name: str, scale_length_in: float, gauge_in: float | None = None, freq_hz: float | None = None, ) -> str: spec = STANDARD_TUNING.get(string_name, STANDARD_TUNING["G"]) g = gauge_in if gauge_in and gauge_in > 0 else spec["gauge_in"] f = freq_hz if freq_hz and freq_hz > 0 else spec["freq_hz"] t = calculate_tension_lbs(f, scale_length_in, g) return "\n".join([ f"String: {string_name}", f"Frequency: {f:.2f} Hz", f"Gauge: {g:.3f} in", f"Scale length: {scale_length_in:.2f} in", f"Estimated tension: {t:.2f} lb ({t * 4.448:.1f} N)", "", "Approximation for steel acoustic strings — not a setup prescription.", ]) def _lite_modal_synthesis( freqs: np.ndarray, damping: np.ndarray, sr: int, duration: float, amps: np.ndarray | None = None, ) -> np.ndarray: """HF-safe damped harmonic modal sum (no torch / Stiefel deps).""" n_samples = int(sr * duration) t = np.arange(n_samples, dtype=np.float64) / sr if amps is None: amps = 1.0 / np.arange(1, len(freqs) + 1) y = np.zeros(n_samples, dtype=np.float64) for k, (freq, damp, amp) in enumerate(zip(freqs, damping, amps)): y += amp * np.exp(-damp * t) * np.sin(2.0 * np.pi * freq * t) peak = np.max(np.abs(y)) return (y / peak if peak > 1e-8 else y).astype(np.float32) def synthesize_modal_comparison( audio_path: str | None, *, duration: float = 2.0, use_sample: bool = False, inharmonicity_b: float = 0.0005, progress_cb=None, ) -> tuple[str | None, str | None, go.Figure, str]: """Before/after: original clip vs lite modal resynthesis from estimated partials.""" import librosa import soundfile as sf if progress_cb is not None: progress_cb(0.15, desc="Loading audio…") if use_sample: sample = get_sample_guitar_path() y, sr = _load_audio_array(str(sample), duration=duration) source = sample.name elif audio_path and Path(audio_path).is_file(): y, sr = _load_audio_array(audio_path, duration=duration) source = Path(audio_path).name else: y = _synthetic_guitar_tone(duration=duration) sr = 22050 source = "synthetic" hop = 512 f0, voiced, _ = librosa.pyin(y, fmin=80, fmax=600, sr=sr, hop_length=hop) f0_mean = float(np.nanmean(f0)) if f0 is not None and np.any(np.isfinite(f0)) else 196.0 if progress_cb is not None: progress_cb(0.45, desc="Fitting modal parameters…") n_modes = 6 freqs = np.array([ (k + 1) * f0_mean * np.sqrt(1.0 + inharmonicity_b * (k + 1) ** 2) for k in range(n_modes) ]) damping = np.linspace(2.0, 8.0, n_modes) rms_env = librosa.feature.rms(y=y, hop_length=hop)[0] decay_rate = float(np.median(np.diff(rms_env)[rms_env[:-1] > 0.01])) if len(rms_env) > 2 else -0.05 damping = np.clip(damping * (1.0 + abs(decay_rate) * 10), 1.5, 12.0) amps = 1.0 / np.arange(1, n_modes + 1) y_synth = _lite_modal_synthesis(freqs, damping, sr, len(y) / sr, amps) if progress_cb is not None: progress_cb(0.75, desc="Writing audio files…") out_dir = Path(tempfile.mkdtemp(prefix="string_modal_")) orig_path = out_dir / "original.wav" synth_path = out_dir / "modal_synthesis.wav" sf.write(str(orig_path), y, sr) sf.write(str(synth_path), y_synth, sr) t = np.arange(len(y)) / sr fig = go.Figure() fig.add_trace(go.Scatter(x=t, y=y, mode="lines", name="Original", line=dict(color="#4D96FF", width=1))) fig.add_trace(go.Scatter(x=t, y=y_synth, mode="lines", name="Modal synth", line=dict(color="#FFB000", width=1))) fig.update_layout( title=f"Before / After — {source}", xaxis_title="Time (s)", yaxis_title="Amplitude", paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), height=260, legend=dict(orientation="h"), ) mse = float(np.mean((y - y_synth[: len(y)]) ** 2)) metrics = "\n".join([ "=== Modal Synthesis (lite preview) ===", "", f"source : {source}", f"f0 estimate : {f0_mean:.2f} Hz", f"inharmonicity B : {inharmonicity_b:.6f}", f"modes : {n_modes}", "damping (1/s) : " + ", ".join(f"{d:.2f}" for d in damping), f"recon MSE : {mse:.6f}", "", "Full physics-audio inversion (Stiefel + coupling) runs locally.", f"Pipeline: {PHYSICS_AUDIO_URL}", ]) if progress_cb is not None: progress_cb(1.0, desc="Synthesis complete") return str(orig_path), str(synth_path), fig, metrics def _load_audio_array(audio_path: str, duration: float = 3.0) -> tuple[np.ndarray, int]: import librosa path = Path(audio_path) if not path.is_file(): raise FileNotFoundError(f"Audio file not found: {audio_path}") y, sr = librosa.load(str(path), sr=22050, duration=duration, mono=True) y, _ = librosa.effects.trim(y, top_db=25) y = librosa.util.normalize(y) return y, sr def get_preset_params(preset_key: str) -> dict[str, Any]: """Return slider values for a named optimization preset.""" base = default_run_params() preset = OPTIMIZATION_PRESETS.get(preset_key, OPTIMIZATION_PRESETS["standard"]) return { "total_steps": preset.get("total_steps", base["total_steps"]), "steps_per_update": preset.get("steps_per_update", base["steps_per_update"]), "learning_rate": preset.get("learning_rate", base["learning_rate"]), "burst_factor_max": preset.get("burst_factor_max", base["burst_factor_max"]), "use_fisher_modulation": preset.get("use_fisher_modulation", base["use_fisher_modulation"]), } def _make_string_energy_figure(y: np.ndarray, sr: int, f0_hz: float | None) -> go.Figure: """Per-string spectral energy from harmonic partials 1–6.""" import librosa n_fft, hop = 2048, 512 S = np.abs(librosa.stft(y, n_fft=n_fft, hop_length=hop)) ** 2 freqs = librosa.fft_frequencies(sr=sr, n_fft=n_fft) f0 = f0_hz if f0_hz and f0_hz > 0 else 196.0 energies = [] for k in range(6): target = (k + 1) * f0 * np.sqrt(1 + 0.0005 * (k + 1) ** 2) band = (freqs >= target * 0.92) & (freqs <= target * 1.08) energies.append(float(S[band, :].sum()) if band.any() else 0.0) total = sum(energies) or 1.0 pct = [100 * e / total for e in energies] fig = go.Figure() fig.add_trace( go.Bar( x=STRING_NAMES, y=pct, marker=dict(color=STRING_COLORS, line=dict(color="#222", width=1)), text=[f"{p:.1f}%" for p in pct], textposition="outside", ) ) fig.update_layout( title=f"Partial Energy by String (f0 ≈ {f0:.1f} Hz)", yaxis_title="% of harmonic energy", paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), height=280, margin=dict(l=40, r=20, t=40, b=60), ) return fig def _synthetic_guitar_tone(duration: float = 2.0, sr: int = 22050) -> np.ndarray: t = np.linspace(0, duration, int(sr * duration), endpoint=False) f0 = 196.0 y = np.zeros_like(t) for k in range(1, 7): freq = f0 * k * (1 + 0.0003 * k ** 2) amp = 1.0 / k decay = np.exp(-2.5 * k * t) y += amp * decay * np.sin(2 * np.pi * freq * t) y += 0.02 * np.random.randn(len(t)) return y / (np.max(np.abs(y)) + 1e-8) def analyze_audio_signal( audio_path: str | None, *, duration: float = 2.0, use_sample: bool = False, progress_cb=None, ) -> tuple[go.Figure, go.Figure, go.Figure, go.Figure, str]: import librosa _configure_cpu_threads() if progress_cb is not None: progress_cb(0.1, desc="Loading audio…") if use_sample: sample = get_sample_guitar_path() y, sr = _load_audio_array(str(sample), duration=duration) source = f"{sample.name} ({SAMPLE_GUITAR_NOTE})" elif audio_path and Path(audio_path).is_file(): y, sr = _load_audio_array(audio_path, duration=duration) source = Path(audio_path).name else: y = _synthetic_guitar_tone(duration=duration) sr = 22050 source = "synthetic G3 harmonic (fallback)" if progress_cb is not None: progress_cb(0.35, desc="Computing spectrogram…") t = np.arange(len(y)) / sr fig_wave = go.Figure() fig_wave.add_trace(go.Scatter(x=t, y=y, mode="lines", line=dict(color="#4D96FF", width=1))) fig_wave.update_layout( title=f"Waveform — {source}", xaxis_title="Time (s)", yaxis_title="Amplitude", paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), height=220, margin=dict(l=40, r=20, t=40, b=30), ) n_fft, hop = 2048, 512 S = np.abs(librosa.stft(y, n_fft=n_fft, hop_length=hop)) S_db = librosa.amplitude_to_db(S, ref=np.max) times = librosa.frames_to_time(np.arange(S_db.shape[1]), sr=sr, hop_length=hop) freqs = librosa.fft_frequencies(sr=sr, n_fft=n_fft) fig_spec = go.Figure( data=go.Heatmap(z=S_db, x=times, y=freqs, colorscale="Viridis", colorbar=dict(title="dB")) ) fig_spec.update_layout( title="STFT Spectrogram", xaxis_title="Time (s)", yaxis_title="Frequency (Hz)", yaxis=dict(range=[0, min(4000, sr // 2)]), paper_bgcolor="#0a0a0a", font=dict(color="#FFB000"), height=320, margin=dict(l=50, r=20, t=40, b=30), ) if progress_cb is not None: progress_cb(0.65, desc="Tracking partials…") f0, voiced, _ = librosa.pyin(y, fmin=80, fmax=600, sr=sr, hop_length=hop) fig_partials = go.Figure() for k in range(6): if k == 0 and f0 is not None and np.any(np.isfinite(f0)): fig_partials.add_trace( go.Scatter( y=f0, mode="lines", name="f0 (pYIN)", line=dict(color=STRING_COLORS[k], width=1.5), ) ) elif k > 0 and f0 is not None: harmonic = np.where( (f0 > 0) & np.isfinite(f0), (k + 1) * f0 * np.sqrt(1 + 0.0005 * (k + 1) ** 2), np.nan, ) fig_partials.add_trace( go.Scatter( y=harmonic, mode="lines", name=f"Partial {k + 1}", line=dict(color=STRING_COLORS[k], width=1, dash="dot"), ) ) fig_partials.update_layout( title="Harmonic Partial Tracks (6 strings)", xaxis_title="Frame", yaxis_title="Frequency (Hz)", paper_bgcolor="#0a0a0a", plot_bgcolor="#111318", font=dict(color="#FFB000"), height=280, margin=dict(l=50, r=20, t=40, b=30), legend=dict(orientation="h", y=-0.25), ) f0_mean = float(np.nanmean(f0)) if f0 is not None and np.any(np.isfinite(f0)) else 196.0 fig_energy = _make_string_energy_figure(y, sr, f0_mean) rms = float(np.sqrt(np.mean(y ** 2))) peak = float(np.max(np.abs(y))) centroid = float(librosa.feature.spectral_centroid(y=y, sr=sr).mean()) decay = float(librosa.feature.rms(y=y, hop_length=hop).mean()) metrics = "\n".join([ "=== 6-String Spectrum Analysis ===", "", f"source : {source}", f"sample_rate : {sr} Hz", f"duration : {len(y) / sr:.2f} s", f"f0 (mean) : {f0_mean:.1f} Hz", f"rms : {rms:.4f}", f"peak : {peak:.4f}", f"spectral_centroid: {centroid:.1f} Hz", f"mean_frame_rms : {decay:.4f}", f"voiced_frames : {int(np.sum(voiced)) if voiced is not None else 0}", "", "Per-string bars = energy in partial bands 1–6 (harmonic model).", "Full physics-audio: damped inharmonic modes on Stiefel manifold.", f"Pipeline: {PHYSICS_AUDIO_URL}", ]) if progress_cb is not None: progress_cb(1.0, desc="Analysis complete") return fig_wave, fig_spec, fig_partials, fig_energy, metrics def _draw_classic_smith_background(ax) -> None: import matplotlib.pyplot as plt from matplotlib.patches import Arc, Circle ax.add_patch(Circle((0, 0), 1.0, fill=False, color="black", linewidth=1.5)) ax.plot([-1.1, 1.1], [0, 0], color="black", linewidth=1) for swr in (1.5, 2.0, 3.0, 5.0, 10.0): rho = (swr - 1.0) / (swr + 1.0) ax.add_patch(Circle((0, 0), rho, fill=False, color="gray", linewidth=0.8)) ax.text(rho + 0.02, 0.02, f"{swr}", fontsize=9, color="gray", ha="left", va="bottom") ax.text(0, 0.02, "SWR=1.0\nPerfect\nMatch", ha="center", va="bottom", fontsize=10, color="black") for r in (0.2, 0.5, 1.0, 2.0, 3.0, 5.0, 10.0, 20.0): center_x = r / (r + 1.0) radius = 1.0 / (r + 1.0) ax.add_patch(Circle((center_x, 0), radius, fill=False, color="blue", linewidth=0.8, ls="--")) for base_x in (0.2, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0): for sign in (1.0, -1.0): x = sign * base_x center = (1.0, 1.0 / x) radius = abs(1.0 / x) theta1 = 0 if x > 0 else 180 theta2 = 180 if x > 0 else 360 ax.add_patch( Arc(center, 2 * radius, 2 * radius, angle=0.0, theta1=theta1, theta2=theta2, color="blue", linewidth=0.8, ls="--") ) ax.set_xlim(-1.1, 1.1) ax.set_ylim(-1.1, 1.1) ax.set_aspect("equal") ax.axis("off") def render_smith_chart_preview(seed: int = 42) -> str: """HF-safe Smith chart preview using synthetic Γ candidates (physics_audio viz style).""" import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt rng = np.random.default_rng(seed) n = 24 angles = rng.uniform(0, 2 * np.pi, n) mags = rng.uniform(0.05, 0.85, n) mags = np.sort(mags) gamma = mags * np.exp(1j * angles) re, im = np.real(gamma), np.imag(gamma) mag_norm = mags / (mags.max() + 1e-8) colors = plt.cm.viridis_r(1.0 - mag_norm) pre_idx, sel_idx = 8, 3 fig = plt.figure(figsize=(8, 8)) ax = fig.add_subplot(111) _draw_classic_smith_background(ax) ax.scatter(re, im, c=colors, s=90, marker="o", edgecolors="black", linewidth=1.0, alpha=0.92, zorder=5) ax.scatter(re[pre_idx], im[pre_idx], s=120, marker="o", facecolors="black", edgecolors="white", zorder=10) ax.scatter(re[sel_idx], im[sel_idx], s=120, marker="o", facecolors="lime", edgecolors="black", zorder=11) ax.arrow(re[pre_idx], im[pre_idx], re[sel_idx] - re[pre_idx], im[sel_idx] - im[pre_idx], head_width=0.06, head_length=0.08, fc="black", ec="black", lw=1, length_includes_head=True) ax.set_title(f"Smith Chart Preview — Seed {seed} (synthetic Γ jump)", fontsize=14, pad=20) sm = plt.cm.ScalarMappable(cmap="viridis_r", norm=plt.Normalize(mags.min(), mags.max())) sm.set_array([]) fig.colorbar(sm, ax=ax, shrink=0.65, pad=0.02).set_label("|Γ| mismatch — lower = better") out_dir = Path(tempfile.mkdtemp(prefix="string_smith_")) path = out_dir / f"smith_preview_seed_{seed}.png" fig.savefig(path, dpi=150, bbox_inches="tight") plt.close(fig) return str(path) def run_analysis(_kappa: float = 0.85) -> tuple[str, str | None, str | None]: """Mystery-shell compatibility: demo guitar spectrum + Smith chart + overview.""" _fig_wave, _fig_spec, _fig_partials, _fig_energy, metrics = analyze_audio_signal( None, duration=default_run_params()["audio_duration"], use_sample=True ) smith_path = render_smith_chart_preview() overview = get_overview_composite_path() overview_path = str(overview) if overview.is_file() else smith_path return metrics, smith_path, overview_path def terminal_results_snapshot() -> str: lines = ["Six-string hierarchy (Pythagorean stagger):", ""] for name, win, boost in zip(STRING_NAMES, WINDOWS, BURST_BOOSTS): lines.append(f" {name:8s} window {win:4d} burst {boost:.2f}×") lines.extend([ "", f"Default HF steps: {default_run_params()['total_steps']:,}", f"Demo guitar: {SAMPLE_GUITAR_FILENAME}", f"Physics audio: {PHYSICS_AUDIO_URL}", ]) return "\n".join(lines) def terminal_directory_help() -> str: return "\n".join([ "6-string-optimizer/ (github.com/kinaar8340/6-string-optimizer)", "├── src/optimizer/ burst optimizer + manifolds", "├── physics_audio/ real-audio Stiefel inverse problem", "│ ├── run_real_audio.py", "│ └── training_evaluation/viz.py (pyramid + Smith chart)", "├── hf_staging/ Space bundle source (mystery shell)", "│ ├── app.py terminal + keypad + optimize/spectrum", "│ ├── demo_core.py optimizer + audio + Smith preview", "│ └── assets/ demo_guitar_g3.wav · overview_composite.png", "", "Local: python physics_audio/run_real_audio.py", "HF: OPTIMIZE · SPECTRUM → DEMO GUITAR", ]) def terminal_probe_scope() -> str: return "\n".join([ "THIS SPACE — production demo (browser):", " · Rosenbrock S³ optimization with six-string burst hierarchy", " · Real-audio spectrum: waveform · STFT · harmonic partials", " · Demo guitar G3 one-click analysis", " · Smith chart + pyramid overview figures", " · CLI terminal + 24-key prog keypad", "", "GITHUB REPO — full depth:", " · physics_audio/run_real_audio.py — Stiefel training on real WAV", " · training_evaluation/viz.py — full pyramid + Smith chart jumps", " · Fisher-Rao burst modulation extensions", ]) def terminal_string_catalog() -> str: lines = ["Six virtual strings (outermost → innermost):", ""] for idx, (name, win, boost) in enumerate(zip(STRING_NAMES, WINDOWS, BURST_BOOSTS), start=1): lines.append(f" PROG {idx:02d} {name}") lines.append(f" stagnation window {win} · burst boost {boost:.2f}×") lines.extend(["", "PROG 07 → run optimization · PROG 08 → demo guitar · PROG 09 → tour"]) return "\n".join(lines) def terminal_physics_overview() -> str: return "\n".join([ "Physics-audio pipeline (local training):", "", " 1. Load real guitar WAV → streaming STFT features", " 2. Stiefel manifold parameterization of coupled string modes", " 3. Fisher-Rao / invariant losses + burst jumps on plateau", " 4. Viz: reconstruction pyramid + Smith chart (Γ mismatch)", "", f"Entry: physics_audio/run_real_audio.py", f"Viz: physics_audio/training_evaluation/viz.py", "", "This Space renders an HF-safe Smith chart preview + bundled overview composite.", f"Repo: {PHYSICS_AUDIO_URL}", ]) def terminal_figures_index() -> str: overview = get_overview_composite_path() smith = render_smith_chart_preview() lines = ["Bundled / generated figures:", ""] lines.append(f" 1. overview_composite.png") lines.append(f" {overview}") lines.append(f" 2. smith_chart_preview.png") lines.append(f" {smith}") lines.append("") lines.append("Open Figures tab in the UI for full grid.") lines.append(f"Regenerate locally: python physics_audio/run_real_audio.py") return "\n".join(lines) def terminal_keypad_map() -> str: lines = ["Assigned prog keys (01–12):", ""] for index in sorted(TERM_KEY_ACTIONS): _action, desc = TERM_KEY_ACTIONS[index] tag = "01 Home" if index == 1 else f"{index:02d}" lines.append(f" [{tag}] {desc}") lines.extend([ "", "D-pad: ▲▼◀▶ move menu · enter confirm · clear blank", "Keys 13–24: reserved (latch only)", "PROG 1–6 mirror string layers · 7=optimize · 8=demo guitar", ]) return "\n".join(lines) def terminal_optimizer_help() -> str: return "\n".join([ "6-STRING SIGNAL ANALYZER — keypad / terminal help", "", "WORKSPACE TABS:", " OPTIMIZE Rosenbrock S³ + VU meters + twist", " SPECTRUM Upload WAV or ▶ DEMO GUITAR (G3)", " SETTINGS Steps · LR · burst factor · Fisher-Rao", "", "KEYPAD:", " 01 Home 02 Status 03 Scope 04 Directory", " 05 Results 06 Build 07 Help 08 Scan", " 09 Strings 10 Physics 11 Tour 12 Figures", "", "Commands: optimize · spectrum · demo · tour · help · about", f"Repo: {GITHUB_URL}", ]) def terminal_guided_onboarding() -> str: return "\n".join([ "╔══════════════════════════════════════════════════════╗", "║ 6-STRING SIGNAL ANALYZER — GUIDED TOUR (SKIPPABLE) ║", "╚══════════════════════════════════════════════════════╝", "", "Press CLEAR or dismiss welcome card to skip anytime.", "", "STEP 1 — Theory tab: S³, bursts, guitar metaphor explained", "STEP 2 — OPTIMIZE: pick preset → ▶ RUN (~30s HF)", "STEP 3 — SPECTRUM: ▶ DEMO GUITAR (G3) + string energy bars", "STEP 4 — STRINGS tab: High E→Low E hierarchy table", "STEP 5 — PHYSICS: Smith chart Γ + pyramid overview", "", "Keypad 1–6 = string layers · 7 = optimize · 8 = demo · 11 = this tour", "", f"Repo: {GITHUB_URL}", f"Physics: {PHYSICS_AUDIO_URL}", ]) def terminal_about() -> str: return "\n".join([ "6-STRING OPTIMIZER — production", "Riemannian burst optimizer on S³ · six virtual guitar strings", "Real-audio spectrum analysis · physics-audio Smith chart viz", f"GitHub: {GITHUB_URL}", f"Space: {HF_SPACE_URL}", f"Shell: mystery terminal + qvpic signal-analyzer style", get_build_label(), ])