CTD One Parameter Model Family and Sparc Rotation Curves
One Parameter Model Family and Sparc Rotation Curves CamB — CTD-1P (Single Parameter Model) Testj Abstract This study analyzes Camelopardalis B (CamB), the first galaxy in the SPARC database, under Curvature Tension Dynamics (CTD) using the CTD-1P single parameter model. CTD proposes that flat rotation curves can arise from stored geometric tension in spacetime, a memory of resisted cosmic expansion and collapse. This tension manifests as oscillatory breathing phases (inhale, exhale, equilibrium), which naturally explain the diversity of observed galactic rotation curves. In this framework, warps, wiggles, and ripples in velocity structure are signatures of spacetime oscillations and harmonic echoes from companion galaxies. The reaction of the U field (Gaussian balls of stored tension and release) to this and pulls from nearby attractors, contributes to the behaviour observed in rotation curves. The purpose of this test is to establish a baseline pass rate for a one-parameter CTD model across the SPARC sample. If successful, additional parameters will be introduced only for failures, building step by step toward universal coverage and ultimately show why more parameters are needed in more complex galaxies. --- Galaxy Overview: CamB - Name: Camelopardalis B (CamB) - Type: Faint dwarf irregular galaxy - Distance: ~3.4 Mpc - Properties: Gas-dominated, very low surface brightness, minimal stellar disk contribution. - Significance: CamB is the first SPARC entry and an important benchmark dwarf. Its rising, low-amplitude curve is an ideal test case for CTD’s simplest baseline. --- The Model: CTD-1P The CTD-1P model is a reduced, one-parameter implementation of the CTD v1 profile: \[ v(r) = V_0 \left(1 - e^{-r / r_0}\right) + V_c \, e^{-(r / r_c)^2} \] Where: - \( V_0 \) = **free parameter** (overall velocity amplitude, fitted by algorithm). - \( r_0 \) = **fixed** = 1.10 kpc (sets the rise scale; chosen as a modest dwarf baseline). - \( r_c \) = **fixed** = 1.80 kpc (taper scale; avoids runaway growth). - \( V_c \) = **fixed** = 0.00 km/s (neutral core; prevents artificial boosting). Thus, only one degree of freedom is allowed. The fixed constants represent — a slow rise without external power terms. --- ## Method 1. Data Input - SPARC `CamB_rotmod.dat` uploaded into Google Colab. - Columns: radius \(r\), observed velocity \(v\), error \(e\), gas/disk/bulge contributions. 2. Fitting - Implement CTD-1P in Python. - Use **SciPy’s `curve_fit`** with observational errors as weights. - Fit only \( V_0 \); all shape constants remain fixed. 3. Outputs - Best-fit \( V_0 \) and uncertainty. - \(\chi^2\), reduced \(\chi^2\), RMSE, and max residual. - Residuals table (exact values, not just plots). - Automatic PNG + CSV for full reproducibility. --- Results (CamB) - Best-fit parameter: \[ V_0 = 21.52 \pm 0.89 \ \text{km/s} \] - **Goodness of fit**: \[ \chi^2 = 9.19, \quad \chi^2_r = 1.15 \quad (N = 9) \] - Error metrics: - RMSE = 1.52 km/s - Max \(|\text{residual}|\) = 2.80 km/s - Exact residuals: | r_kpc | v_obs_kms | e_obs_kms | v_model_kms | residual_kms | std_residual | |-------:|----------:|----------:|------------:|-------------:|-------------:| | 0.160 | 1.99 | 1.50 | 2.91 | -0.92 | -0.62 | | 0.410 | 4.84 | 1.50 | 6.70 | -1.86 | -1.24 | | 0.570 | 6.79 | 1.50 | 8.70 | -1.91 | -1.28 | | 0.730 | 8.87 | 1.50 | 10.44 | -1.57 | -1.05 | | 0.900 | 10.90 | 1.50 | 12.03 | -1.13 | -0.75 | | 1.060 | 12.90 | 1.50 | 13.31 | -0.41 | -0.28 | | 1.220 | 14.70 | 1.50 | 14.42 | 0.28 | 0.18 | | 1.470 | 16.80 | 1.50 | 15.87 | 0.93 | 0.62 | | 1.790 | 20.10 | 1.50 | 17.30 | 2.80 | 1.87 | --- Interpretation - The CTD-1P model, with just one free parameter, achieves an excellent match to the CamB curve. - Residuals are small, balanced, and within measurement errors — confirming this is not an overfit. - Physically: CamB’s slow-rising curve is consistent with a galaxy in an exhale-equilibrium breathing phase, where the spacetime field relaxes after prior compression. - CTD predicts that the diversity of SPARC rotation curves reflects different galaxies caught at different phases (inhale → compression, exhale → relaxation, equilibrium → flat plateau) and that the Radial Acceleration Relation (RAR) emerges naturally from the exhale and equilibrium cycle. --- Conclusion This first test demonstrates that CamB is explained well by CTD-1P, with the residuals and reduced chi-squared confirming the robustness of the fit. The next step is to extend CTD-1P to the entire SPARC catalog, recording a global pass rate. Failures will be re-fitted with additional first-principles parameters, introduced systematically. Through this staged approach, CTD aims to show that spacetime breathing oscillations are the root cause of galactic rotation curve diversity and the empirical RAR. Here is the colab code for CTD-1p model # CTD 1P — ONE-parameter fitter (upload any *_rotmod.dat) # Shape fixed: r0=1.10 kpc, rc=1.80 kpc, Vc=0.00 km/s ; fit V0 only. import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy.optimize import curve_fit from google.colab import files import io, os, re, textwrap # ---------- 0) Upload ---------- print("Upload a SPARC-style rotmod file (e.g., CamB_rotmod.dat)…") up = files.upload() assert len(up) >= 1, "No file uploaded." fname = list(up.keys())[0] print("Saved:", fname) # ---------- 1) Load ---------- df = pd.read_csv(io.BytesIO(up[fname]), sep=r"\s+", comment="#", header=None, engine="python") df.columns = ["Radius_kpc","Velocity_kms","Error_kms","Vgas","Vdisk","Vbul","SBdisk","SBbul"] obs = df[(df["Radius_kpc"] > 0) & (df["Velocity_kms"] > 0)] r_obs = obs["Radius_kpc"].values v_obs = obs["Velocity_kms"].values e_obs = np.clip(obs["Error_kms"].values, 1e-3, None) # guard tiny/zero errors # ---------- 2) Model: CTD-1P (v1 shape fixed) ---------- # v(r) = V0 * (1 - exp(-r/r0)) + Vc * exp(-(r/rc)^2) r0_fixed = 1.10 # kpc (modest dwarf rise) rc_fixed = 1.80 # kpc (gentle core scale) Vc_fixed = 0.00 # km/s (neutral core for baseline) def ctd_1p_model(r, V0): rise = V0 * (1.0 - np.exp(-r / r0_fixed)) core = Vc_fixed * np.exp(-(r / rc_fixed)**2) return rise + core # ---------- 3) Fit V0 ---------- p0 = [np.nanmax(v_obs)] popt, pcov = curve_fit(ctd_1p_model, r_obs, v_obs, sigma=e_obs, p0=p0, absolute_sigma=True, maxfev=20000) V0_fit = float(popt[0]) V0_err = float(np.sqrt(np.diag(pcov))[0]) if pcov.size else np.nan # ---------- 4) Goodness-of-fit + residuals ---------- v_mod = ctd_1p_model(r_obs, V0_fit) resid = v_obs - v_mod stdres = resid / e_obs chi2 = float(np.sum((resid / e_obs)**2)) nu = max(len(v_obs) - 1, 1) # one free parameter chi2_r = chi2 / nu rmse = float(np.sqrt(np.mean(resid**2))) max_abs_res = float(np.max(np.abs(resid))) # ---------- 5) Smooth curve ---------- r_fit = np.linspace(0, max(r_obs) + 0.5, 600) v_fit = ctd_1p_model(r_fit, V0_fit) # ---------- 6) Plot ---------- gal_name = re.sub(r"_rotmod.*$", "", os.path.basename(fname)) plt.figure(figsize=(9.5, 7.0)) ax1 = plt.subplot(2,1,1) ax1.errorbar(r_obs, v_obs, yerr=e_obs, fmt='o', color='k', ms=5, ecolor='k', elinewidth=1, capsize=2, label='Observed') ax1.plot(r_fit, v_fit, lw=2.2, color='crimson', label=(f'CTD 1P (shape fixed): V0 = {V0_fit:.2f} ± {V0_err:.2f} km/s' f' χ²ᵣ = {chi2_r:.2f}')) ax1.set_ylabel("Velocity [km/s]") ax1.set_title(f"{gal_name} — CTD 1P (single parameter model)") ax1.legend(loc='upper left') ax1.grid(True, ls='--', alpha=0.35) ax2 = plt.subplot(2,1,2, sharex=ax1) ax2.axhline(0, color='gray', lw=1, ls='--') ax2.errorbar(r_obs, resid, yerr=e_obs, fmt='o', color='crimson', ms=5, ecolor='crimson', elinewidth=1, capsize=2, label='Residuals (Obs − Model)') ax2.set_xlabel("Radius [kpc]") ax2.set_ylabel("Residual [km/s]") ax2.legend(loc='upper left') ax2.grid(True, ls='--', alpha=0.35) plt.tight_layout() # ---------- 7) Save outputs ---------- out_dir = "ctd_1p_outputs" os.makedirs(out_dir, exist_ok=True) png_name = os.path.join(out_dir, f"{gal_name}_CTD-1P.png") plt.savefig(png_name, dpi=300) plt.show() # Table (no rounding of inputs in CSV) table = pd.DataFrame({ "r_kpc": r_obs, "v_obs_kms": v_obs, "e_obs_kms": e_obs, "v_model_kms": v_mod, "residual_kms": resid, "std_residual": stdres }) meta = textwrap.dedent( f"""\ # {gal_name} — CTD 1P (single parameter) # Fixed shape: r0={r0_fixed:.2f} kpc, rc={rc_fixed:.2f} kpc, Vc={Vc_fixed:.2f} km/s # Fit: V0 = {V0_fit:.6f} ± {V0_err:.6f} km/s | chi2 = {chi2:.6f} | chi2_red = {chi2_r:.6f} | N = {len(v_obs)} # RMSE = {rmse:.6f} km/s | max|residual| = {max_abs_res:.6f} km/s """ ) csv_name = os.path.join(out_dir, f"{gal_name}_CTD-1P.csv") with open(csv_name, "w") as f: f.write(meta) table.to_csv(f, index=False) # ---------- 8) ALSO print residual table to screen (auditable) ---------- pd.set_option("display.max_rows", None) pd.set_option("display.width", 120) pd.set_option("display.float_format", lambda x: f"{x:.6f}") print("\n=== Residuals table (exact) ===") print(table) print("\n=== Summary ===") print(f"PNG: {png_name}") print(f"CSV: {csv_name}") print(f"V0 = {V0_fit:.6f} ± {V0_err:.6f} km/s | chi2 = {chi2:.6f} | chi2_red = {chi2_r:.6f} | " f"RMSE = {rmse:.6f} km/s | max|residual| = {max_abs_res:.6f} km/s | N = {len(v_obs)}") The One Parameter Family models — SPARC Database Rotation Curves --- Canonical One Parameter Model — Global Results After testing across the full SPA
Authors
- Paul Cartwright
Publication Details
- Journal
- Zenodo (CERN European Organization for Nuclear Research)
- Published
- 2026-04-27
- DOI
- https://doi.org/10.5281/zenodo.19826991
- Primary Topic
- Galaxies: Formation, Evolution, Phenomena
- Type
- article
- Field-Weighted Citation Impact
- 0.00