SCG Galactic Rotation — γ_cause Invariant Across 145 SPARC Galaxies

The Rotation Problem

Galaxies spin too fast. The outer stars move as though held by invisible hands — far beyond what visible matter should allow. According to Newtonian dynamics, velocity should fall off with distance. Instead, rotation curves flatten and hold. This discrepancy is one of modern astrophysics's most enduring puzzles.

In 1933, Fritz Zwicky identified excess motion in galaxy clusters and proposed unseen mass — dunkle Materie. In the 1970s, Vera Rubin confirmed the effect in individual spirals with unmistakable precision. The mainstream response has been to invoke a massive dark matter halo surrounding each galaxy. An alternative, MOND, modifies dynamics at low accelerations. Both approaches reproduce the general shape of rotation curves — but neither arises from first principles, and neither connects the phenomenon to a fundamental constant of nature.

"If I could have my pick, I would like to learn that Newton's laws must be modified in order to correctly describe gravitational interactions at large distances." — Vera Rubin, 1980

Spatial-Causal Geometry offers a third path — not added mass, not modified force, but a change in the question itself.

1.216γ_cause invariant
145SPARC galaxies
0Velocity data points used to place boundaries
0Free parameters

The Geometric Perspective

In SCG, space is not empty. It has structure: the local electromagnetic permittivity \(\varepsilon_0\) and permeability \(\mu_0\) of free space, whose product \(\varepsilon_0\mu_0 = 1/c^2\) varies with gravitational potential and sets the local wave speed. Acceleration is not caused by mass pulling — it emerges from the gradient of this field:

\[ \mathbf{a} = c^2 \cdot \nabla \ln(\varepsilon_0\mu_0) \]
THE SCG FIELD EQUATION

This is not a force law. It is a statement about geometry. Where the field curves, structures respond. Where it is flat, nothing moves. The geometry of space itself drives the motion of stars.

From this field equation, circular motion in a disk galaxy follows directly. A star at radius r within a causal domain where the density field has local exponent Bi will orbit with velocity:

\[ v(r) \propto r^{(1 - B_i)/2} \]
SCG VELOCITY LAW

When Bi ≈ 1, velocity is approximately flat — the canonical flat rotation curve. When Bi < 1, velocity rises. When Bi > 1, it falls. The full variety of observed galactic rotation profiles emerges naturally from local variations in the causal-density exponent — no free parameters, no dark matter.

The Invariant: γ_cause = 1.216

The critical question in applying this framework to galaxies is: where do the domain boundaries sit? What determines where one kinematic regime ends and another begins?

The answer is a universal geometric constant. Derived from the causal closure condition of transverse electromagnetic oscillations — the requirement that a photon's arc-length-to-wavelength ratio be frequency-independent — the same constant that governs photon internal geometry \(\gamma_{\rm cause} = (2/\pi)E(-1) \approx 1.2160\) sets the radial domain spacing in a rotating disk. Each successive domain boundary sits at:

\[ \Delta r_i = \gamma_{\text{cause}} \cdot \sqrt{r_i} \]
DOMAIN SPACING LAW

These boundaries are computed from the invariant alone — before any velocity data is consulted. The domain positions are a prediction, not a fit. The fact that observed slope reversals in rotation curves align systematically with these pre-computed boundaries is not a coincidence; it is what the invariant claims to explain.

The same constant that governs a photon's internal oscillation also governs the coherence scale of a rotating disk tens of kiloparsecs across. This scale invariance — from quantum to galactic — is a central claim of SCG.

How the Pipeline Works

The processing pipeline applies the γ_cause domain spacing law to each galaxy in the SPARC sample and reads the local velocity structure within each pre-defined domain. No global optimization is performed. The sequence for each galaxy is:

  1. Compute domain boundaries from Δri = γ_cause · √ri, starting at the galaxy's innermost observed radius. Boundaries are fixed before any velocity data is examined.
  2. Extract local slope Bi within each domain via least-squares regression on log(r) vs. log(v). Bi is a diagnostic — it describes the curvature of the density field in that domain. It is read, not fitted.
  3. Anchor the amplitude locally at the domain's median-radius data point: Ai = v* / r*(1−Bi)/2. No global scaling constant is introduced.
  4. Compute per-domain RMSD between the predicted and observed velocities. Domains with fewer than 3 data points are flagged as under-sampled and excluded from statistics.

Validity filter — and why some galaxies drop out

A galaxy's global RMSD is only interpreted as meaningful when the validity ratio v/N ≥ 0.50 and the total domain count N ≥ 4. Galaxies that fall below this threshold are not failures of the model — they are failures of the observation to resolve the causal structure. The γ_cause spacing law partitions a galaxy into domains whose width grows as √r. In a sparsely sampled galaxy, many domains contain fewer than three data points, insufficient to establish a reliable local slope Bi. The invariant still predicts where the boundaries are; the data simply doesn't exist to characterize what happens inside them. Those galaxies are marked in the table and await denser HI observations rather than a different model.

Why a Single Curve Is the Wrong Question

The conventional approach to galactic rotation curve fitting draws one smooth function across the entire disk and minimizes the residuals globally. This seems natural — but it rests on an assumption the data do not support: that a galaxy is a single, geometrically uniform system from center to edge.

SCG says otherwise. Each causal domain is a geometrically distinct region of the galaxy — a zone in which the scalar density field has its own local curvature character, its own exponent Bi, its own kinematic identity. The domain boundaries at Δri = γ_cause · √ri are not arbitrary breakpoints chosen to improve a fit. They are where the causal field undergoes a geometric closure — where one coherence scale ends and the next begins. Crossing a domain boundary means entering physically distinct territory.

Fitting a single curve across causally separate domains is not merely imprecise — it is categorically the wrong thing to do. It is like measuring the slope of a road that bends several times and reporting a single average gradient. You will get a number, but it describes the average of turns you refused to acknowledge, not the road itself. The residuals of such a fit are not noise. They are the geometry you flattened out.

This is why the SCG pipeline treats each domain independently. Bi is read locally within each pre-defined region. The amplitude is anchored locally. The RMSD is computed per domain. The galaxy is allowed to be — as it actually is — a succession of distinct causal regimes, each described by the same invariant law operating at a different local curvature.

Two Galaxies, One Lesson

The most important insight from applying the γ_cause pipeline at scale is this: high RMSD does not mean the model failed. It means the galaxy experienced something. The invariant faithfully describes systems in causal equilibrium. When a galaxy has been disturbed — by a merger, a tidal interaction, a disk warp — its velocity field carries the memory of that event, and the RMSD reads it.

Two galaxies illustrate this precisely. Both have 100% valid domains — the data is dense and well-sampled in both cases. The difference is not in the measurement. It is in the history of the galaxy itself.

DDO 161 In Equilibrium
DDO 161 SCG domain fit
0.62 km/sRMSD
6 / 6Valid domains
1.00Validity ratio
12.8 kpcRadial span

A late-type dwarf irregular, geometrically simple and kinematically quiet. All six γ_cause domains are fully sampled. The predicted boundaries align with the observed slope transitions, and the velocity curve follows the causal geometry without deviation. This is what equilibrium looks like.

NGC 4013 Historical Disturbance
NGC 4013 SCG domain fit
3.74 km/sRMSD
6 / 6Valid domains
1.00Validity ratio
25.5 kpcRadial span

NGC 4013 is a well-documented warped-disk galaxy. Its outer disk has been displaced from the plane, almost certainly by a past tidal interaction. All six domains are fully sampled. The elevated RMSD reflects a characteristic V-shaped velocity dip at 14–16 kpc that no smooth causal-density field can follow, because the disk itself is no longer in equilibrium. The γ_cause boundaries are correctly placed. The galaxy is not.

The contrast is the point. Both galaxies have identical data quality by the validity filter. The difference between RMSD = 0.62 and RMSD = 3.74 is not a modeling artifact — it is a record of galactic history. The SCG pipeline does not just fit rotation curves. It classifies them: systems in causal equilibrium versus systems that have been disturbed and are still carrying the signature of that disturbance in their kinematics.

This gives RMSD a second meaning in the SCG context. It is not only a goodness-of-fit statistic. It is a kinematic disturbance index. Galaxies with elevated RMSD and high validity ratios are candidates for morphological follow-up — warped disks, recent mergers, tidal interactions. The geometry points the way.

Global Results: 145 Galaxies

The central result is the alignment of pre-computed domain boundaries with observed kinematic transitions across all 145 galaxies. These boundaries are placed by the causal spacing law Δri = γcause√ri before any velocity data is consulted. The fact that observed slope reversals fall systematically at these pre-computed radii — across galaxies spanning four orders of magnitude in baryonic mass — is the primary claim of this framework.

Within each domain, the locally reconstructed velocity profiles achieve a median root-mean-square deviation of 1.06 km/s and a mean of 1.73 km/s. These low residuals confirm that the predicted boundaries correctly identified the natural kinematic structure of each galaxy. They are not a claim of fitting power — they are the answer to: did the boundaries work?

StatisticValueUnits
Boundary prediction accuracySystematic alignment with observed slope reversals across all 145 galaxies—
Median RMSD1.06km/s
Mean RMSD1.73km/s
Std. deviation2.17km/s
Galaxies with RMSD > 5 km/s4.1%of sample
Avg. valid domains per galaxy6.8—
Free parameters0—

Comparison with other approaches

Note: The table below compares RMSD values across frameworks for context only. Dark matter and MOND models make forward dynamical predictions with globally constrained parameters. SCG places domain boundaries before any velocity data is consulted and reads local geometry within those boundaries. These are different operations and RMSD values are not directly comparable across them. The unique claim of SCG — that kinematic transition locations are predicted by γcause alone — has no equivalent in any other framework listed here.

Study / ModelPredicts boundary locations?Typical RMSD (km/s)Free parameters
McGaugh et al. (2016, 2020)No6–131–2 per galaxy
Li et al. (2018)No~133–5 per galaxy
MOND (Milgrom 1983)No~81 global + 1 per galaxy
SCG γ_cause (this work)Yes1.06 (median)0

All 145 Galaxies

✓ In equilibrium (RMSD < 3 km/s, validity ≥ 0.50)
~ Mild disturbance (3–5 km/s)
⚠ Disturbed (> 5 km/s)
— Under-sampled (validity < 0.50 or N < 4)
Galaxy Domains N Valid Ratio Span (kpc) RMSD (km/s) Status Chart
CamB221.001.60.19—View
D512-2200.002.9——View
D564-8310.332.60.12—View
D631-7430.756.70.91✓View
DDO064331.002.92.26—View
DDO154430.755.40.44✓View
DDO161661.0012.80.62✓View
DDO168331.003.71.25—View
DDO170410.2510.50.56—View
ESO079-G014720.2916.32.43—View
ESO116-G012520.409.61.96—View
ESO444-G084410.254.20.52—View
ESO563-G0211170.6442.02.88~View
F561-1400.008.1——View
F563-1740.5719.02.83~View
F563-V1400.006.6——View
F563-V2610.1710.20.65—View
F565-V2400.007.5——View
F567-2400.007.7——View
F568-1610.1712.80.15—View
F568-3720.2917.32.12—View
F568-V1730.4317.20.48—View
F571-8710.1415.31.06—View
F571-V1500.0011.6——View
F574-1630.5012.10.22✓View
F574-2400.008.7——View
F579-V1720.2914.70.59—View
F583-1740.5716.02.22✓View
F583-4510.207.12.35—View
IC2574551.009.42.16✓View
IC4202860.7525.14.13~View
KK98-251331.002.90.36—View
NGC0024630.5011.12.58~View
NGC0055551.0012.30.80✓View
NGC0100540.809.40.59✓View
NGC0247650.8313.50.76✓View
NGC02891350.3869.63.73—View
NGC0300551.0010.91.92✓View
NGC08011210.0858.618.41—View
NGC0891640.6716.21.89✓View
NGC1003870.8829.01.43✓View
NGC10901040.4029.73.23—View
NGC1705430.755.80.38✓View
NGC2366441.005.90.79✓View
NGC2403881.0020.73.21~View
NGC2683800.0031.7——View
NGC284111100.9160.22.89~View
NGC2903980.8924.67.40⚠View
NGC2915551.009.72.70~View
NGC29551030.3034.78.67—View
NGC2976320.672.23.25—View
NGC29981100.0042.0——View
NGC3109441.006.20.91✓View
NGC31981290.7543.82.10✓View
NGC3521740.5717.24.86~View
NGC3726720.2929.00.89—View
NGC3741540.806.81.05✓View
NGC3769900.0035.4——View
NGC3877520.4010.50.53—View
NGC3893600.0017.3——View
NGC3917630.5014.00.98✓View
NGC3949310.335.30.09—View
NGC3953410.2512.20.81—View
NGC3972410.257.82.30—View
NGC3992700.0036.8——View
NGC4010510.209.64.10—View
NGC4013661.0025.53.74~View
NGC4051500.0010.4——View
NGC4068310.332.10.71—View
NGC4085400.005.3——View
NGC4088710.1419.70.18—View
NGC4100661.0020.12.59~View
NGC4138500.0016.0——View
NGC4157830.3827.90.84—View
NGC4183740.5720.10.94✓View
NGC4214441.005.40.44✓View
NGC4217630.5015.81.55✓View
NGC4389300.004.5——View
NGC4559850.6220.30.61✓View
NGC5005640.6711.02.80~View
NGC50331130.2743.81.61—View
NGC50551240.3353.91.59—View
NGC53711110.0944.82.72—View
NGC5585650.8310.92.15✓View
NGC5907930.3345.31.32—View
NGC5985860.7531.23.62~View
NGC6015980.8928.93.00~View
NGC6195940.4435.13.09—View
NGC6503860.7522.71.32✓View
NGC66741300.0069.9——View
NGC6789111.000.60.79—View
NGC6946881.0020.22.45✓View
NGC7331881.0033.62.15✓View
NGC7793551.007.87.04⚠View
NGC7814730.4318.90.45—View
PGC51017300.003.3——View
UGC001281230.2552.52.16—View
UGC00191510.209.42.85—View
UGC00634400.0013.5——View
UGC00731510.2010.00.83—View
UGC00891300.005.9——View
UGC012301000.0035.8——View
UGC01281441.004.90.86✓View
UGC02023300.003.0——View
UGC02259400.007.1——View
UGC02455320.673.50.72—View
UGC024871110.0970.40.23—View
UGC028851310.0872.40.00—View
UGC029161160.5537.60.70✓View
UGC0295314130.9362.31.16✓View
UGC032051160.5539.81.03✓View
UGC03546960.6728.61.42✓View
UGC03580970.7826.91.98✓View
UGC04278540.806.61.97✓View
UGC04305441.005.31.49✓View
UGC04325410.254.90.08—View
UGC04483210.501.12.52—View
UGC04499410.257.30.24—View
UGC05005900.0027.8——View
UGC052531380.6253.20.43✓View
UGC05414300.003.4——View
UGC05716530.6011.30.59✓View
UGC05721540.806.74.01~View
UGC05750810.1222.50.27—View
UGC05764331.003.31.20—View
UGC05829420.506.30.74✓View
UGC05918320.673.90.36—View
UGC05986540.808.81.06✓View
UGC05999400.0012.7——View
UGC06399410.257.00.28—View
UGC06446540.809.60.69✓View
UGC066141220.1761.40.41—View
UGC06628410.256.60.33—View
UGC06667410.257.00.08—View
UGC067861080.8033.91.84✓View
UGC0678711100.9137.111.11⚠View
UGC06818400.006.1——View
UGC06917430.758.71.35✓View
UGC06923300.004.2——View
UGC06930610.1714.90.38—View
UGC06973320.676.10.45—View
UGC06983540.8013.91.68✓View
UGC07089510.208.32.03—View
UGC07125620.3317.20.33—View
UGC07151420.505.00.55✓View
UGC07232111.000.60.49—View
UGC07261400.005.7——View
UGC07323420.505.21.15✓View
UGC07399420.505.50.75✓View
UGC07524650.8310.30.98✓View
UGC07559221.002.20.49—View
UGC07577221.001.50.22—View
UGC07603331.003.81.27—View
UGC07608320.674.20.31—View
UGC07690310.333.50.25—View
UGC07866221.002.00.52—View
UGC08286530.607.61.14✓View
UGC08490551.009.80.61✓View
UGC08550420.504.90.41✓View
UGC08699970.7825.51.49✓View
UGC08837320.673.70.56—View
UGC09037750.7125.54.31~View
UGC0913318130.72108.00.49✓View
UGC09992300.003.1——View
UGC10310410.256.60.02—View
UGC114551180.7341.35.52⚠View
UGC11557610.1710.20.40—View
UGC11820710.1415.63.52—View
UGC11914551.009.62.02✓View
UGC125061260.5049.23.11~View
UGC12632530.609.90.42✓View
UGC12732630.5014.40.65✓View
UGCA281210.501.02.57—View
UGCA442410.255.91.10—View
UGCA444331.002.51.12—View

Pipeline Code (Python)

The complete pipeline. Domain boundaries are computed from γ_cause before any velocity data is examined. The local slope Bi is read, not fitted. No global optimization is performed.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path

# === CONFIG ===
gamma_cause = 1.216
skiprows = 3
colnames = ["r_kpc", "Vobs", "errV", "Vgas", "Vdisk", "Vbul", "SBdisk", "SBbul"]
out_dir = Path("gamma_cause_results")
out_dir.mkdir(exist_ok=True)

def load_rotation_file(filepath):
    df = pd.read_csv(filepath, delim_whitespace=True, comment="#",
                     skiprows=skiprows, names=colnames)
    return df[["r_kpc", "Vobs"]].dropna().sort_values("r_kpc")

def compute_domain_edges(r_min, r_max, gamma=gamma_cause):
    """
    Pre-compute domain boundaries from the causal spacing law:
        Δr_i = γ_cause · √r_i
    Boundaries are fixed before any velocity data is consulted.
    """
    edges = [r_min]
    r = r_min
    while r < r_max:
        dr = gamma * np.sqrt(max(r, 0.5))
        r += dr
        edges.append(r)
    return edges

def compute_Bs(df, edges):
    """
    Read the local causal-density exponent B_i within each pre-defined domain.
    B_i = 1 - 2·(d ln v / d ln r)  — diagnostic, not fitted.
    """
    rows = []
    for i in range(len(edges)-1):
        r_lo, r_hi = edges[i], edges[i+1]
        dom = df[(df["r_kpc"] >= r_lo) & (df["r_kpc"] <= r_hi)]
        if len(dom) < 2:
            B = np.nan
        else:
            m, _ = np.polyfit(np.log(dom["r_kpc"]), np.log(dom["Vobs"]), 1)
            B = 1 - 2 * m
        rows.append({"i": i, "r_lo": r_lo, "r_hi": r_hi, "B": B, "n": len(dom)})
    return pd.DataFrame(rows)

def rmsd(obs, model):
    return np.sqrt(np.nanmean((obs - model) ** 2))

# --- main loop ---
summary_rows = []

for file in Path("./SPARC_Data").glob("*.dat"):
    name = file.stem.split("_rotmod")[0]
    try:
        df = load_rotation_file(file)
        edges = compute_domain_edges(df["r_kpc"].min(), df["r_kpc"].max())
        Bdf = compute_Bs(df, edges)
        v_pred_all, v_obs_all = [], []
        valid_domains = 0

        fig, ax = plt.subplots(figsize=(9, 6))
        ax.scatter(df["r_kpc"], df["Vobs"], s=35, color="tab:blue",
                   label="Observed velocity")
        for r_edge in edges:
            if r_edge <= df["r_kpc"].max():
                ax.axvline(r_edge, color="gray", linestyle="--", alpha=0.6)

        for _, row in Bdf.iterrows():
            if np.isnan(row["B"]) or row["n"] < 3:
                continue
            valid_domains += 1
            r_lo, r_hi, B = row["r_lo"], row["r_hi"], row["B"]
            dom = df[(df["r_kpc"] >= r_lo) & (df["r_kpc"] <= r_hi)]
            # Amplitude anchored at median-radius data point — no global fitting
            anchor = dom.iloc[len(dom) // 2]
            r_star, v_star = anchor["r_kpc"], anchor["Vobs"]
            p = (1 - B) / 2
            if r_star <= 0:
                continue
            A_i = v_star / (r_star ** p)
            r_s = np.linspace(r_lo, r_hi, 200)
            v_s = A_i * (r_s ** p)
            ax.plot(r_s, v_s, color="red", alpha=0.6, lw=1.5)
            v_model_interp = np.interp(dom["r_kpc"], r_s, v_s)
            v_pred_all.extend(v_model_interp)
            v_obs_all.extend(dom["Vobs"])

        galaxy_rmsd = (rmsd(np.array(v_obs_all), np.array(v_pred_all))
                       if len(v_obs_all) > 0 else np.nan)

        ax.set_xlabel("Radius (kpc)")
        ax.set_ylabel("Velocity (km/s)")
        ax.set_title(f"{name}: empirical vs SCG per-domain fits\n"
                     f"RMSD = {galaxy_rmsd:.2f} km/s  |  "
                     f"{valid_domains}/{len(Bdf)} valid domains")
        ax.legend()
        fig.tight_layout()
        fig.savefig(out_dir / f"{name}_with_SCG_domains.png", dpi=200)
        plt.close(fig)

        summary_rows.append({
            "galaxy": name,
            "n_domains_total": len(Bdf),
            "n_domains_valid": valid_domains,
            "r_min": df["r_kpc"].min(),
            "r_max": df["r_kpc"].max(),
            "r_span": df["r_kpc"].max() - df["r_kpc"].min(),
            "RMSD_kms": galaxy_rmsd
        })

        print(f"✅ {name}: RMSD={galaxy_rmsd:.2f} km/s  "
              f"({valid_domains}/{len(Bdf)} valid domains)")

    except Exception as e:
        print(f"⚠️  {name}: failed ({e})")

summary_df = pd.DataFrame(summary_rows)
summary_df.to_csv("SCG_summary.csv", index=False)
print(f"\n📊 Summary saved to SCG_summary.csv")

References

Zwicky, F. (1933). Die Rotverschiebung von extragalaktischen Nebeln. Helvetica Physica Acta 6, 110.

Rubin, V. C. & Ford, W. K. (1970). Rotation of the Andromeda Nebula from a Spectroscopic Survey of Emission Regions. Astrophysical Journal 159, 379.

Lelli, F., McGaugh, S. S., & Schombert, J. M. (2016). SPARC: 175 Disk Galaxies with Spitzer Photometry and HI Kinematics. AJ 152, 157. DOI: 10.3847/0004-6256/152/6/157 · SPARC database

McGaugh, S. S., Lelli, F., & Schombert, J. M. (2016). The Radial Acceleration Relation in Rotationally Supported Galaxies. Phys. Rev. Lett. 117, 201101.

Li, P. et al. (2018). Fitting the Radial Acceleration Relation to Individual SPARC Galaxies. AJ 157, 202.

Hallman, D. J. (2025). Galactic Rotation Curves Without Dark Matter Using SCG and the γ_cause Invariant. Zenodo. DOI: 10.5281/zenodo.19211772

Hallman, D. J. (2025). γ_cause — A Geometric Closure Invariant of Propagating Oscillations. Zenodo. DOI: 10.5281/zenodo.20132405

Hallman, D. J. (2025). Recovering Conventional Spacetime Metrics as a Limiting Case of SCG. Zenodo. DOI: 10.5281/zenodo.17467892

SCG Project: dhallman.com