Download modules/common.py from antaris/Waves: direct link, hf CLI and curl.
- Browser
- Download file 7.17 kB
-
https://huggingface.co/spaces/antaris/Waves/resolve/main/modules/common.py
- Command line
-
hf download hf://spaces/antaris/Waves/modules/common.py
-
curl -L -o common.py https://huggingface.co/spaces/antaris/Waves/resolve/main/modules/common.py
7.17 kB
| """Shared utilities for the CNNA Laola HuggingFace app. | |
| This file keeps the didactic modules comparable: | |
| - same base defaults, | |
| - same response profile, | |
| - same reference schedule, | |
| - same diagnostic plot contract. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Tuple | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| class BaseDefaults: | |
| n_people: int = 80 | |
| tau: float = 0.20 | |
| activation_time: float = 1.20 | |
| refractory_time: float = 2.00 | |
| seat_spacing: float = 1.00 | |
| launch_count: int = 5 | |
| launch_interval: float = 4.00 | |
| dt: float = 0.03 | |
| t_max: float = 22.0 | |
| snapshot_time: float = 18.0 | |
| BASE = BaseDefaults() | |
| def response_profile(s: np.ndarray, activation_time: float) -> np.ndarray: | |
| """Compact-support stand-up/plateau/sit-down profile p(s).""" | |
| y = np.zeros_like(s, dtype=float) | |
| if activation_time <= 0: | |
| return y | |
| rise_end = 0.25 * activation_time | |
| fall_start = 0.75 * activation_time | |
| rising = (0.0 <= s) & (s < rise_end) | |
| plateau = (rise_end <= s) & (s < fall_start) | |
| falling = (fall_start <= s) & (s <= activation_time) | |
| y[rising] = s[rising] / rise_end | |
| y[plateau] = 1.0 | |
| y[falling] = 1.0 - (s[falling] - fall_start) / (activation_time - fall_start) | |
| return np.clip(y, 0.0, 1.0) | |
| def exact_regular_schedule(n_people: int, tau: float) -> np.ndarray: | |
| """Reference schedule t_i = i τ. | |
| This deliberately avoids recursive cumsum. Recursive accumulation of 0.2 in | |
| binary float creates harmless ~1e-14 round-off drift. For the exact regular | |
| reference this would be a diagnostic artefact, not a model effect. | |
| """ | |
| n = int(n_people) | |
| return float(tau) * np.arange(n, dtype=float) | |
| def launch_offsets(launch_count: int, launch_interval: float) -> np.ndarray: | |
| count = max(1, int(launch_count)) | |
| interval = max(float(launch_interval), 1e-12) | |
| return interval * np.arange(count, dtype=float) | |
| def time_grid(t_max: float, dt: float) -> np.ndarray: | |
| # Include the right endpoint robustly without relying on exact binary divisibility. | |
| step = max(float(dt), 1e-9) | |
| return np.arange(0.0, float(t_max) + 0.5 * step, step, dtype=float) | |
| def activity_from_schedule( | |
| times: np.ndarray, | |
| activation_times: np.ndarray, | |
| launches: np.ndarray, | |
| activation_time: float, | |
| *, | |
| ring_period: float | None = None, | |
| ) -> np.ndarray: | |
| """Build H[t, i] from activation times and launch offsets.""" | |
| heights = np.zeros((len(times), len(activation_times)), dtype=float) | |
| for offset in launches: | |
| local_times = times[:, None] - activation_times[None, :] - float(offset) | |
| if ring_period is not None: | |
| local_times = local_times % ring_period | |
| heights = np.maximum(heights, response_profile(local_times, activation_time)) | |
| return heights | |
| def clean_near_zero(values: np.ndarray, scale: float = 1.0) -> np.ndarray: | |
| """Set numerical round-off at the float-noise scale to exact zero.""" | |
| arr = np.array(values, dtype=float, copy=True) | |
| tol = max(1e-12, 1000.0 * np.finfo(float).eps * max(1.0, float(scale), float(arr.size))) | |
| arr[np.abs(arr) <= tol] = 0.0 | |
| return arr | |
| def timing_error(actual_activation: np.ndarray, reference_activation: np.ndarray) -> np.ndarray: | |
| scale = max( | |
| 1.0, | |
| float(np.max(np.abs(actual_activation))) if actual_activation.size else 1.0, | |
| float(np.max(np.abs(reference_activation))) if reference_activation.size else 1.0, | |
| ) | |
| return clean_near_zero(actual_activation - reference_activation, scale=scale) | |
| def finite_difference_residual(times: np.ndarray, positions: np.ndarray, heights: np.ndarray, c: float) -> float: | |
| """Normalized residual for ∂t h + c∂x h = 0 as coarse-grained diagnostic.""" | |
| if len(times) < 3 or len(positions) < 3: | |
| return float("nan") | |
| dt = float(times[1] - times[0]) | |
| dx = float(positions[1] - positions[0]) | |
| dh_dt = (heights[2:, 1:-1] - heights[:-2, 1:-1]) / (2.0 * dt) | |
| dh_dx = (heights[1:-1, 2:] - heights[1:-1, :-2]) / (2.0 * dx) | |
| residual = dh_dt + c * dh_dx | |
| scale = np.mean(np.abs(dh_dt)) + np.mean(np.abs(c * dh_dx)) + 1e-12 | |
| return float(np.mean(np.abs(residual)) / scale) | |
| def transport_diffusion_residuals( | |
| times: np.ndarray, | |
| positions: np.ndarray, | |
| heights: np.ndarray, | |
| c: float, | |
| d_eff: float, | |
| ) -> Tuple[float, float]: | |
| """Normalized residuals for transport and transport-diffusion diagnostics.""" | |
| if len(times) < 3 or len(positions) < 3: | |
| return float("nan"), float("nan") | |
| dt = float(times[1] - times[0]) | |
| dx = float(positions[1] - positions[0]) | |
| h_center = heights[1:-1, 1:-1] | |
| dh_dt = (heights[2:, 1:-1] - heights[:-2, 1:-1]) / (2.0 * dt) | |
| dh_dx = (heights[1:-1, 2:] - heights[1:-1, :-2]) / (2.0 * dx) | |
| d2h_dx2 = (heights[1:-1, 2:] - 2.0 * h_center + heights[1:-1, :-2]) / (dx**2) | |
| r_transport = dh_dt + c * dh_dx | |
| r_diffusion = dh_dt + c * dh_dx - float(d_eff) * d2h_dx2 | |
| scale = np.mean(np.abs(dh_dt)) + np.mean(np.abs(c * dh_dx)) + 1e-12 | |
| return float(np.mean(np.abs(r_transport)) / scale), float(np.mean(np.abs(r_diffusion)) / scale) | |
| def count_visible_packets(values: np.ndarray, threshold: float = 0.05) -> int: | |
| active = np.asarray(values) > threshold | |
| if active.size == 0: | |
| return 0 | |
| starts = active & np.concatenate(([True], ~active[:-1])) | |
| return int(np.sum(starts)) | |
| def make_heatmap( | |
| times: np.ndarray, | |
| positions: np.ndarray, | |
| heights: np.ndarray, | |
| title: str, | |
| ): | |
| fig, ax = plt.subplots(figsize=(10, 4.8)) | |
| im = ax.imshow( | |
| heights, | |
| aspect="auto", | |
| origin="lower", | |
| extent=[positions[0], positions[-1], times[0], times[-1]], | |
| ) | |
| fig.colorbar(im, ax=ax, label="lokale Höhe / Aktivität hᵢ(t)") | |
| ax.set_xlabel("Knoten / Sitzposition xᵢ") | |
| ax.set_ylabel("Zeit t [s]") | |
| ax.set_title(title) | |
| fig.tight_layout() | |
| return fig | |
| def make_snapshot_comparison( | |
| times: np.ndarray, | |
| positions: np.ndarray, | |
| simulated: np.ndarray, | |
| reference: np.ndarray, | |
| snapshot_time: float, | |
| sim_label: str, | |
| ): | |
| idx = int(np.argmin(np.abs(times - float(snapshot_time)))) | |
| fig, ax = plt.subplots(figsize=(10, 3.8)) | |
| ax.plot(positions, reference[idx, :], linestyle="--", linewidth=1.4, label="idealer Script-1-Wellenzug") | |
| ax.plot(positions, simulated[idx, :], marker="o", linewidth=1.5, markersize=3, label=sim_label) | |
| ax.set_ylim(-0.05, 1.08) | |
| ax.set_xlabel("Knoten / Sitzposition xᵢ") | |
| ax.set_ylabel("hᵢ(t)") | |
| ax.set_title(f"Momentaufnahme bei t = {times[idx]:.2f} s") | |
| ax.grid(True, alpha=0.25) | |
| ax.legend() | |
| fig.tight_layout() | |
| return fig | |
| def make_timing_error_plot(positions: np.ndarray, errors: np.ndarray, title: str): | |
| fig, ax = plt.subplots(figsize=(10, 3.8)) | |
| ax.plot(positions, errors, marker="o", linewidth=1.4, markersize=3) | |
| ax.axhline(0.0, linewidth=1.0) | |
| ax.set_xlabel("Knoten / Sitzposition xᵢ") | |
| ax.set_ylabel("kumulativer Timing-Fehler [s]") | |
| ax.set_title(title) | |
| ax.grid(True, alpha=0.25) | |
| fig.tight_layout() | |
| return fig | |