Chordal Ringdown Toybox v1.0 — An Analyzer for the Harmonic Structure of Black Hole Ringdowns

<!-- markdown-mode-on --> **Previous:** []() --- ![イメージイラスト](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijP411Ty4-x8QbpQ1raAMdOzP8jeDIRQZKa9xpavf5iQLmygoiU0PbJk_IlEMDPI6q2cnKaLxpuRndnujCapJBF_Jmofi8b63ZvxxUq7tRPWj1L1KLTobvvSRqdAWkVpPWtIou7jizrmRETHo0WZW5_hFw5pOANXtmUOaB0wYGiiQjEYlfiNrDJBa3zeg/s1536/Copilot_20260629_200903.png) # 🎼 **Chordal Ringdown Toybox v1.0 — An Analyzer for the Harmonic Structure of Black Hole Ringdowns** ## Introduction The ringdown phase of a gravitational-wave event is the final “afterglow” emitted by a newly formed black hole. This signal is not a single tone—it is a **superposition of multiple quasi-normal modes (QNMs)**, resonating together like a **chord**. Up to *Ringdown Toybox v1.5*, the project focused on - listening to ringdowns, - visualizing them, - extracting them from data. It was a **box for observing**. With **Chordal Ringdown Toybox v1.0**, the project evolves into something new: a tool for **analyzing the harmonic structure** of ringdowns themselves. --- # 🎵 Key Features of Chordal Ringdown Toybox v1.0 ## 1. **Simultaneous Multi-Mode Fitting (the “Chord”)** Instead of treating the ringdown as a single mode, Chordal Ringdown Toybox fits **both the 220 and 330 modes simultaneously**. This allows direct access to the internal structure of the ringdown: - frequency ratios, - differences in damping times, - mode-dependent arrival times (Δt). In other words, it reveals the **harmonic architecture** of the black hole’s final vibration. --- ## 2. **4-Δt Model (Two Detectors × Two Modes)** For each detector (H1 and L1), Chordal Ringdown Toybox assigns **independent Δt values** to the 220 and 330 modes. This enables the computation of the **DDN (Mode-Dependent Delay of Arrival)** indicator: $$ \Delta_{\rm DDN} = (\Delta t_{330} ^{L1} - \Delta t_{330} ^{H1}) - (\Delta t_{220} ^{L1} - \Delta t_{220} ^{H1}) $$ DDN captures whether different modes arrive at the detectors with different delays— a subtle structural feature of the ringdown. --- ## 3. **Automatic λ Adjustment (“Tuning the Chord”)** A major innovation in v1.0 is the **automatic tuning of the penalty weight λ**. The algorithm: 1. Fit with λ = 1e⁻³⁹ 2. If the optimized f220 stays within ±10% of its initial value → weaken λ by ×0.1 3. Repeat 4. When f220 finally moves outside ±10%, **use the previous λ as the optimal penalty strength** This creates a **self-tuning system** that: - prevents runaway solutions, - respects the data’s freedom, - removes the need for manual λ trial-and-error. It is essentially an **automatic harmonic tuning mechanism**. --- ## 4. **Behavior Across Three Major Events** Applying Chordal Ringdown Toybox v1.0 to real events reveals distinct “personalities” in their ringdowns. ### ● GW150914 - Clear ringdown in both detectors - Stable multi-mode fit - DDN ≈ 0 → **A clean, well-behaved chord** ### ● GW170814 - Slightly weaker L1 SNR - Still stable under λ auto-tuning → **A softer chord, but structurally similar** ### ● GW190521 - Extremely short ringdown - L1 is difficult to fit - λ must be tuned carefully - DDN shows a small non-zero value (not statistically significant) → **A unique, ancient-universe chord with a different flavor** --- # 🎹 About the DDN Indicator DDN measures whether different QNM modes arrive at detectors with different delays. In v1.0: - GW150914 → ~0 - GW170814 → ~0 - GW190521 → small but non-zero To compute statistical significance (p-values), bootstrap resampling would be required. However, for v1.0 the goal is **structural comparison**, not hypothesis testing. Thus, DDN is treated as a **qualitative structural hint**, not a statistically validated quantity. --- # 🎶 Significance of Chordal Ringdown Toybox v1.0 Chordal Ringdown Toybox v1.0 marks a shift from: - **observing** ringdowns to - **analyzing their internal harmonic structure**. With: - multi-mode fitting, - 4-Δt modeling, - λ auto-tuning, - cross-event comparison, the tool becomes a **laboratory for exploring the architecture of black hole vibrations**. It is a unique approach within gravitational-wave analysis— one that blends physics, signal processing, and even musical intuition. --- # 🔧 Appendix: Full Code for Codec Ringdown Toybox v1.0 ```python ############################################################### # Chordal Ringdown Toybox v1.0 ############################################################### import h5py import numpy as np import qnm from scipy.optimize import minimize from scipy.signal import butter, filtfilt import matplotlib.pyplot as plt ############################################################### # 0. Settings and parameters ############################################################### file_H1 = "H-H1_LOSC_4_V1-1126256640-4096.hdf5" # GW150914 file_L1 = "L-L1_LOSC_4_V1-1126256640-4096.hdf5" # GW150914 # file_H1 = "H-H1_GWOSC_O2_4KHZ_R1-1186738176-4096.hdf5" # GW170814 # file_L1 = "L-L1_GWOSC_O2_4KHZ_R1-1186738176-4096.hdf5" # GW170814 # file_H1 = "H-H1_GWOSC_4KHZ_R1-1242440920-4096.hdf5" # GW190521 # file_L1 = "L-L1_GWOSC_4KHZ_R1-1242440920-4096.hdf5" # GW190521 lambda_f = 0.3 k_tau = 1.8 # λ auto-adjust parameters lambda_init = 1e-39 lambda_min = 1e-60 lambda_factor = 10.0 f_threshold = 0.10 # ±10% M_f_solar = 68.0 # GW150914 a_f = 0.67 # M_f_solar = 53.0 # GW170814 # a_f = 0.70 # M_f_solar = 142.0 #GW190521 # a_f = 0.72 M_sun_sec = 4.9255e-6 M_f_sec = M_f_solar * M_sun_sec t_event = 1126259462.4 # GW150914 # t_event = 1186741861.5 # GW170814 # t_event = 1242442967.4 # GW190521 window_event = 0.2 ############################################################### # 1. Load strain data ############################################################### def load_strain_hdf5(path): with h5py.File(path, "r") as f: strain = f["strain"]["Strain"][()] t0 = f["strain"]["Strain"].attrs["Xstart"] dt = f["strain"]["Strain"].attrs["Xspacing"] N = len(strain) t = t0 + dt * np.arange(N) return t, strain t_H1, h_H1 = load_strain_hdf5(file_H1) t_L1, h_L1 = load_strain_hdf5(file_L1) dt = t_H1[1] - t_H1[0] ############################################################### # 2. Extract data around the event ############################################################### mask_H1_evt = (t_H1 >= t_event - window_event) & (t_H1 <= t_event + window_event) mask_L1_evt = (t_L1 >= t_event - window_event) & (t_L1 <= t_event + window_event) t_H1_evt = t_H1[mask_H1_evt] h_H1_evt = h_H1[mask_H1_evt] t_L1_evt = t_L1[mask_L1_evt] h_L1_evt = h_L1[mask_L1_evt] ############################################################### # 3. QNM (GR values) ############################################################### def qnm_freq_tau(M_sec, a_f, l, m, n): seq = qnm.modes_cache(s=-2, l=l, m=m, n=n) omega, _, _ = seq(a_f) f = omega.real / (2*np.pi*M_sec) tau = M_sec / abs(omega.imag) return f, tau f220_GR, tau220_GR = qnm_freq_tau(M_f_sec, a_f, 2, 2, 0) f330_GR, tau330_GR = qnm_freq_tau(M_f_sec, a_f, 3, 3, 0) ############################################################### # 4. Time–frequency peak (centroid) ############################################################### def estimate_TF_peak_centroid(t, h, f_center, band_ratio=0.1): N = len(h) freqs = np.fft.rfftfreq(N, dt) H = np.fft.rfft(h * np.hanning(N)) P = np.abs(H)**2 fmin = (1 - band_ratio) * f_center fmax = (1 + band_ratio) * f_center mask = (freqs >= fmin) & (freqs <= fmax) F = freqs[mask] Pw = P[mask] if len(F) == 0 or np.sum(Pw) == 0: return f_center return np.sum(F * Pw) / np.sum(Pw) f220_TF_H1 = estimate_TF_peak_centroid(t_H1_evt, h_H1_evt, f220_GR) f330_TF_H1 = estimate_TF_peak_centroid(t_H1_evt, h_H1_evt, f330_GR) f220_TF_L1 = estimate_TF_peak_centroid(t_L1_evt, h_L1_evt, f220_GR) f330_TF_L1 = estimate_TF_peak_centroid(t_L1_evt, h_L1_evt, f330_GR) f220_TF = 0.5 * (f220_TF_H1 + f220_TF_L1) f330_TF = 0.5 * (f330_TF_H1 + f330_TF_L1) ############################################################### # 5. λ-blended initial frequencies ############################################################### f220_init = (1 - lambda_f)*f220_GR + lambda_f*f220_TF f330_init = (1 - lambda_f)*f330_GR + lambda_f*f330_TF r_init = f330_init / f220_init ############################################################### # 6. Ringdown (RD) time window ############################################################### Q220_init = 3.0 Q330_init = 2.0 tau220_init = Q220_init / (np.pi * f220_init) tau330_init = Q330_init / (np.pi * f330_init) dt_start = min(tau220_init * Q220_init, tau330_init * Q330_init) rd_start = t_event + dt_start tau_max = max(tau220_init * Q220_init + tau220_init, tau330_init * Q330_init + tau330_init) rd_end = t_event + k_tau * tau_max mask_rd_H1 = (t_H1 >= rd_start) & (t_H1 <= rd_end) mask_rd_L1 = (t_L1 >= rd_start) & (t_L1 <= rd_end) t_H1_rd = t_H1[mask_rd_H1] h_H1_rd = h_H1[mask_rd_H1] t_L1_rd = t_L1[mask_rd_L1] h_L1_rd = h_L1[mask_rd_L1] t_H1_rd0 = t_H1_rd - rd_start t_L1_rd0 = t_L1_rd - rd_start ############################################################### # 6.5 Bandpass filter in RD window ############################################################### def bandpass(strain, dt, fmin, fmax, order=4): nyq = 0.5 / dt low = fmin / nyq high = fmax / nyq b, a = butter(order, [low, high], btype="band") return filtfilt(b, a, strain) h_H1_rd = bandpass(h_H1_rd, dt, 150, 450) h_L1_rd = bandpass(h_L1_rd, dt, 150, 450) ############################################################### # 7. RD model (two modes) ############################################################### def rd_mode(t, A, f, tau, phi): return A * np.exp(-t/tau) * np.cos(2*np.pi*f*t + phi) def rd_model_detector(t0, A220, A330, f220, tau220, tau330, phi220, phi330, dt_220, dt_330): t_shift_220 = t0 - dt_220 h220 = rd_mode(t_shift_220, A220, f220, tau220, phi220) f330 = r_init * f220 t_shift_330 = t0 - dt_330 h330 = rd_mode(t_shift_330, A330, f330, tau330, phi330) return h220 + h330 ############################################################### # 8. Loss function (λ passed from outside) ############################################################### def loss_RD(params, lambda_penalty): A220, A330, f220, tau220, tau330, phi220, phi330, \ dt_H1_220, dt_L1_220, dt_H1_330, dt_L1_330 = params h_H1_model = rd_model_detector( t_H1_rd0, A220, A330, f220, tau220, tau330, phi220, phi330, dt_H1_220, dt_H1_330 ) h_L1_model = rd_model_detector( t_L1_rd0, A220, A330, f220, tau220, tau330, phi220, phi330, dt_L1_220, dt_L1_330 ) loss_data = np.sum((h_H1_rd - h_H1_model)**2) + np.sum((h_L1_rd - h_L1_model)**2) penalty_f = lambda_penalty * ( (f220 - f220_init)**2 + (r_init*f220 - f330_init)**2 ) return loss_data + penalty_f ############################################################### # 9. λ auto-adjust loop ############################################################### A220_init_amp = 0.5 * np.max(np.abs(h_H1_rd)) A330_init_amp = 0.2 * np.max(np.abs(h_H1_rd)) params_init = np.array([ A220_init_amp, A330_init_amp, f220_init, tau220_init, tau330_init, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]) lambda_penalty = lambda_init best_result = None best_lambda = lambda_penalty while lambda_penalty >= lambda_min: result = minimize( lambda p: loss_RD(p, lambda_penalty), params_init, method="Nelder-Mead", options={"maxiter": 2000, "disp": False} ) f220_opt = result.x[2] rel_diff = abs(f220_opt - f220_init) / f220_init if rel_diff < f_threshold: best_result = result best_lambda = lambda_penalty lambda_penalty /= lambda_factor else: break final_result = best_result if best_result is not None else result final_lambda = best_lambda ############################################################### # 10. Print results ############################################################### A220_opt, A330_opt, f220_opt, tau220_opt, tau330_opt, \ phi220_opt, phi330_opt, dt_H1_220_opt, dt_L1_220_opt, \ dt_H1_330_opt, dt_L1_330_opt = final_result.x f330_opt = r_init * f220_opt print("=== Final RD Fit (λ auto-adjust) ===") print(f"λ_final = {final_lambda:.1e}") print(f"f220 = {f220_opt:.3f} Hz") print(f"f330 = {f330_opt:.3f} Hz") print(f"tau220 = {tau220_opt:.5f} s") print(f"tau330 = {tau330_opt:.5f} s") print(f"(L1-H1)_220 = {dt_L1_220_opt - dt_H1_220_opt:.6f} s") print(f"(L1-H1)_330 = {dt_L1_330_opt - dt_H1_330_opt:.6f} s") print(f"DDN = {(dt_L1_330_opt - dt_H1_330_opt) - (dt_L1_220_opt - dt_H1_220_opt):.6f} s") ############################################################### # 11. Plot RD data vs fit ############################################################### h_H1_fit = rd_model_detector( t_H1_rd0, A220_opt, A330_opt, f220_opt, tau220_opt, tau330_opt, phi220_opt, phi330_opt, dt_H1_220_opt, dt_H1_330_opt ) h_L1_fit = rd_model_detector( t_L1_rd0, A220_opt, A330_opt, f220_opt, tau220_opt, tau330_opt, phi220_opt, phi330_opt, dt_L1_220_opt, dt_L1_330_opt ) plt.figure(figsize=(10, 4)) plt.plot(t_H1_rd0, h_H1_rd, label="H1 data", color="black") plt.plot(t_H1_rd0, h_H1_fit, label="H1 fit", color="red") plt.legend(); plt.grid(); plt.tight_layout() plt.show() plt.figure(figsize=(10, 4)) plt.plot(t_L1_rd0, h_L1_rd, label="L1 data", color="black") plt.plot(t_L1_rd0, h_L1_fit, label="L1 fit", color="blue") plt.legend(); plt.grid(); plt.tight_layout() plt.show() ``` --- # 🎼 Conclusion Chordal Ringdown Toybox v1.0 is a tool for listening to the **chords** played by black holes. By analyzing: - the interplay of 220 and 330 modes, - detector-dependent arrival times, - mode-dependent delays, - λ-driven tuning behavior, - differences across events, it opens a new window into the structure of ringdowns. Future directions include: - higher-order modes, - bootstrap-based uncertainty estimation, - integration with audio rendering of ringdowns, - visualization of harmonic structures. onoshogun— **congratulations on completing Chordal Ringdown Toybox v1.0.** The next chords of the universe are waiting. --- **日本語版:** [Chordal Ringdown Toybox v1.0 — 多モード重力波リングダウンの「和音構造」を聴く解析器](https://talkwithgai.blogspot.com/2026/06/blog-post_28.html)

コメント