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.
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) \]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} \]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 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} \]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.
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:
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.
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.
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.
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 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.
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?
| Statistic | Value | Units |
|---|---|---|
| Boundary prediction accuracy | Systematic alignment with observed slope reversals across all 145 galaxies | — |
| Median RMSD | 1.06 | km/s |
| Mean RMSD | 1.73 | km/s |
| Std. deviation | 2.17 | km/s |
| Galaxies with RMSD > 5 km/s | 4.1% | of sample |
| Avg. valid domains per galaxy | 6.8 | — |
| Free parameters | 0 | — |
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 / Model | Predicts boundary locations? | Typical RMSD (km/s) | Free parameters |
|---|---|---|---|
| McGaugh et al. (2016, 2020) | No | 6–13 | 1–2 per galaxy |
| Li et al. (2018) | No | ~13 | 3–5 per galaxy |
| MOND (Milgrom 1983) | No | ~8 | 1 global + 1 per galaxy |
| SCG γ_cause (this work) | Yes | 1.06 (median) | 0 |
| Galaxy | Domains N | Valid | Ratio | Span (kpc) | RMSD (km/s) | Status | Chart |
|---|---|---|---|---|---|---|---|
| CamB | 2 | 2 | 1.00 | 1.6 | 0.19 | — | View |
| D512-2 | 2 | 0 | 0.00 | 2.9 | — | — | View |
| D564-8 | 3 | 1 | 0.33 | 2.6 | 0.12 | — | View |
| D631-7 | 4 | 3 | 0.75 | 6.7 | 0.91 | ✓ | View |
| DDO064 | 3 | 3 | 1.00 | 2.9 | 2.26 | — | View |
| DDO154 | 4 | 3 | 0.75 | 5.4 | 0.44 | ✓ | View |
| DDO161 | 6 | 6 | 1.00 | 12.8 | 0.62 | ✓ | View |
| DDO168 | 3 | 3 | 1.00 | 3.7 | 1.25 | — | View |
| DDO170 | 4 | 1 | 0.25 | 10.5 | 0.56 | — | View |
| ESO079-G014 | 7 | 2 | 0.29 | 16.3 | 2.43 | — | View |
| ESO116-G012 | 5 | 2 | 0.40 | 9.6 | 1.96 | — | View |
| ESO444-G084 | 4 | 1 | 0.25 | 4.2 | 0.52 | — | View |
| ESO563-G021 | 11 | 7 | 0.64 | 42.0 | 2.88 | ~ | View |
| F561-1 | 4 | 0 | 0.00 | 8.1 | — | — | View |
| F563-1 | 7 | 4 | 0.57 | 19.0 | 2.83 | ~ | View |
| F563-V1 | 4 | 0 | 0.00 | 6.6 | — | — | View |
| F563-V2 | 6 | 1 | 0.17 | 10.2 | 0.65 | — | View |
| F565-V2 | 4 | 0 | 0.00 | 7.5 | — | — | View |
| F567-2 | 4 | 0 | 0.00 | 7.7 | — | — | View |
| F568-1 | 6 | 1 | 0.17 | 12.8 | 0.15 | — | View |
| F568-3 | 7 | 2 | 0.29 | 17.3 | 2.12 | — | View |
| F568-V1 | 7 | 3 | 0.43 | 17.2 | 0.48 | — | View |
| F571-8 | 7 | 1 | 0.14 | 15.3 | 1.06 | — | View |
| F571-V1 | 5 | 0 | 0.00 | 11.6 | — | — | View |
| F574-1 | 6 | 3 | 0.50 | 12.1 | 0.22 | ✓ | View |
| F574-2 | 4 | 0 | 0.00 | 8.7 | — | — | View |
| F579-V1 | 7 | 2 | 0.29 | 14.7 | 0.59 | — | View |
| F583-1 | 7 | 4 | 0.57 | 16.0 | 2.22 | ✓ | View |
| F583-4 | 5 | 1 | 0.20 | 7.1 | 2.35 | — | View |
| IC2574 | 5 | 5 | 1.00 | 9.4 | 2.16 | ✓ | View |
| IC4202 | 8 | 6 | 0.75 | 25.1 | 4.13 | ~ | View |
| KK98-251 | 3 | 3 | 1.00 | 2.9 | 0.36 | — | View |
| NGC0024 | 6 | 3 | 0.50 | 11.1 | 2.58 | ~ | View |
| NGC0055 | 5 | 5 | 1.00 | 12.3 | 0.80 | ✓ | View |
| NGC0100 | 5 | 4 | 0.80 | 9.4 | 0.59 | ✓ | View |
| NGC0247 | 6 | 5 | 0.83 | 13.5 | 0.76 | ✓ | View |
| NGC0289 | 13 | 5 | 0.38 | 69.6 | 3.73 | — | View |
| NGC0300 | 5 | 5 | 1.00 | 10.9 | 1.92 | ✓ | View |
| NGC0801 | 12 | 1 | 0.08 | 58.6 | 18.41 | — | View |
| NGC0891 | 6 | 4 | 0.67 | 16.2 | 1.89 | ✓ | View |
| NGC1003 | 8 | 7 | 0.88 | 29.0 | 1.43 | ✓ | View |
| NGC1090 | 10 | 4 | 0.40 | 29.7 | 3.23 | — | View |
| NGC1705 | 4 | 3 | 0.75 | 5.8 | 0.38 | ✓ | View |
| NGC2366 | 4 | 4 | 1.00 | 5.9 | 0.79 | ✓ | View |
| NGC2403 | 8 | 8 | 1.00 | 20.7 | 3.21 | ~ | View |
| NGC2683 | 8 | 0 | 0.00 | 31.7 | — | — | View |
| NGC2841 | 11 | 10 | 0.91 | 60.2 | 2.89 | ~ | View |
| NGC2903 | 9 | 8 | 0.89 | 24.6 | 7.40 | ⚠ | View |
| NGC2915 | 5 | 5 | 1.00 | 9.7 | 2.70 | ~ | View |
| NGC2955 | 10 | 3 | 0.30 | 34.7 | 8.67 | — | View |
| NGC2976 | 3 | 2 | 0.67 | 2.2 | 3.25 | — | View |
| NGC2998 | 11 | 0 | 0.00 | 42.0 | — | — | View |
| NGC3109 | 4 | 4 | 1.00 | 6.2 | 0.91 | ✓ | View |
| NGC3198 | 12 | 9 | 0.75 | 43.8 | 2.10 | ✓ | View |
| NGC3521 | 7 | 4 | 0.57 | 17.2 | 4.86 | ~ | View |
| NGC3726 | 7 | 2 | 0.29 | 29.0 | 0.89 | — | View |
| NGC3741 | 5 | 4 | 0.80 | 6.8 | 1.05 | ✓ | View |
| NGC3769 | 9 | 0 | 0.00 | 35.4 | — | — | View |
| NGC3877 | 5 | 2 | 0.40 | 10.5 | 0.53 | — | View |
| NGC3893 | 6 | 0 | 0.00 | 17.3 | — | — | View |
| NGC3917 | 6 | 3 | 0.50 | 14.0 | 0.98 | ✓ | View |
| NGC3949 | 3 | 1 | 0.33 | 5.3 | 0.09 | — | View |
| NGC3953 | 4 | 1 | 0.25 | 12.2 | 0.81 | — | View |
| NGC3972 | 4 | 1 | 0.25 | 7.8 | 2.30 | — | View |
| NGC3992 | 7 | 0 | 0.00 | 36.8 | — | — | View |
| NGC4010 | 5 | 1 | 0.20 | 9.6 | 4.10 | — | View |
| NGC4013 | 6 | 6 | 1.00 | 25.5 | 3.74 | ~ | View |
| NGC4051 | 5 | 0 | 0.00 | 10.4 | — | — | View |
| NGC4068 | 3 | 1 | 0.33 | 2.1 | 0.71 | — | View |
| NGC4085 | 4 | 0 | 0.00 | 5.3 | — | — | View |
| NGC4088 | 7 | 1 | 0.14 | 19.7 | 0.18 | — | View |
| NGC4100 | 6 | 6 | 1.00 | 20.1 | 2.59 | ~ | View |
| NGC4138 | 5 | 0 | 0.00 | 16.0 | — | — | View |
| NGC4157 | 8 | 3 | 0.38 | 27.9 | 0.84 | — | View |
| NGC4183 | 7 | 4 | 0.57 | 20.1 | 0.94 | ✓ | View |
| NGC4214 | 4 | 4 | 1.00 | 5.4 | 0.44 | ✓ | View |
| NGC4217 | 6 | 3 | 0.50 | 15.8 | 1.55 | ✓ | View |
| NGC4389 | 3 | 0 | 0.00 | 4.5 | — | — | View |
| NGC4559 | 8 | 5 | 0.62 | 20.3 | 0.61 | ✓ | View |
| NGC5005 | 6 | 4 | 0.67 | 11.0 | 2.80 | ~ | View |
| NGC5033 | 11 | 3 | 0.27 | 43.8 | 1.61 | — | View |
| NGC5055 | 12 | 4 | 0.33 | 53.9 | 1.59 | — | View |
| NGC5371 | 11 | 1 | 0.09 | 44.8 | 2.72 | — | View |
| NGC5585 | 6 | 5 | 0.83 | 10.9 | 2.15 | ✓ | View |
| NGC5907 | 9 | 3 | 0.33 | 45.3 | 1.32 | — | View |
| NGC5985 | 8 | 6 | 0.75 | 31.2 | 3.62 | ~ | View |
| NGC6015 | 9 | 8 | 0.89 | 28.9 | 3.00 | ~ | View |
| NGC6195 | 9 | 4 | 0.44 | 35.1 | 3.09 | — | View |
| NGC6503 | 8 | 6 | 0.75 | 22.7 | 1.32 | ✓ | View |
| NGC6674 | 13 | 0 | 0.00 | 69.9 | — | — | View |
| NGC6789 | 1 | 1 | 1.00 | 0.6 | 0.79 | — | View |
| NGC6946 | 8 | 8 | 1.00 | 20.2 | 2.45 | ✓ | View |
| NGC7331 | 8 | 8 | 1.00 | 33.6 | 2.15 | ✓ | View |
| NGC7793 | 5 | 5 | 1.00 | 7.8 | 7.04 | ⚠ | View |
| NGC7814 | 7 | 3 | 0.43 | 18.9 | 0.45 | — | View |
| PGC51017 | 3 | 0 | 0.00 | 3.3 | — | — | View |
| UGC00128 | 12 | 3 | 0.25 | 52.5 | 2.16 | — | View |
| UGC00191 | 5 | 1 | 0.20 | 9.4 | 2.85 | — | View |
| UGC00634 | 4 | 0 | 0.00 | 13.5 | — | — | View |
| UGC00731 | 5 | 1 | 0.20 | 10.0 | 0.83 | — | View |
| UGC00891 | 3 | 0 | 0.00 | 5.9 | — | — | View |
| UGC01230 | 10 | 0 | 0.00 | 35.8 | — | — | View |
| UGC01281 | 4 | 4 | 1.00 | 4.9 | 0.86 | ✓ | View |
| UGC02023 | 3 | 0 | 0.00 | 3.0 | — | — | View |
| UGC02259 | 4 | 0 | 0.00 | 7.1 | — | — | View |
| UGC02455 | 3 | 2 | 0.67 | 3.5 | 0.72 | — | View |
| UGC02487 | 11 | 1 | 0.09 | 70.4 | 0.23 | — | View |
| UGC02885 | 13 | 1 | 0.08 | 72.4 | 0.00 | — | View |
| UGC02916 | 11 | 6 | 0.55 | 37.6 | 0.70 | ✓ | View |
| UGC02953 | 14 | 13 | 0.93 | 62.3 | 1.16 | ✓ | View |
| UGC03205 | 11 | 6 | 0.55 | 39.8 | 1.03 | ✓ | View |
| UGC03546 | 9 | 6 | 0.67 | 28.6 | 1.42 | ✓ | View |
| UGC03580 | 9 | 7 | 0.78 | 26.9 | 1.98 | ✓ | View |
| UGC04278 | 5 | 4 | 0.80 | 6.6 | 1.97 | ✓ | View |
| UGC04305 | 4 | 4 | 1.00 | 5.3 | 1.49 | ✓ | View |
| UGC04325 | 4 | 1 | 0.25 | 4.9 | 0.08 | — | View |
| UGC04483 | 2 | 1 | 0.50 | 1.1 | 2.52 | — | View |
| UGC04499 | 4 | 1 | 0.25 | 7.3 | 0.24 | — | View |
| UGC05005 | 9 | 0 | 0.00 | 27.8 | — | — | View |
| UGC05253 | 13 | 8 | 0.62 | 53.2 | 0.43 | ✓ | View |
| UGC05414 | 3 | 0 | 0.00 | 3.4 | — | — | View |
| UGC05716 | 5 | 3 | 0.60 | 11.3 | 0.59 | ✓ | View |
| UGC05721 | 5 | 4 | 0.80 | 6.7 | 4.01 | ~ | View |
| UGC05750 | 8 | 1 | 0.12 | 22.5 | 0.27 | — | View |
| UGC05764 | 3 | 3 | 1.00 | 3.3 | 1.20 | — | View |
| UGC05829 | 4 | 2 | 0.50 | 6.3 | 0.74 | ✓ | View |
| UGC05918 | 3 | 2 | 0.67 | 3.9 | 0.36 | — | View |
| UGC05986 | 5 | 4 | 0.80 | 8.8 | 1.06 | ✓ | View |
| UGC05999 | 4 | 0 | 0.00 | 12.7 | — | — | View |
| UGC06399 | 4 | 1 | 0.25 | 7.0 | 0.28 | — | View |
| UGC06446 | 5 | 4 | 0.80 | 9.6 | 0.69 | ✓ | View |
| UGC06614 | 12 | 2 | 0.17 | 61.4 | 0.41 | — | View |
| UGC06628 | 4 | 1 | 0.25 | 6.6 | 0.33 | — | View |
| UGC06667 | 4 | 1 | 0.25 | 7.0 | 0.08 | — | View |
| UGC06786 | 10 | 8 | 0.80 | 33.9 | 1.84 | ✓ | View |
| UGC06787 | 11 | 10 | 0.91 | 37.1 | 11.11 | ⚠ | View |
| UGC06818 | 4 | 0 | 0.00 | 6.1 | — | — | View |
| UGC06917 | 4 | 3 | 0.75 | 8.7 | 1.35 | ✓ | View |
| UGC06923 | 3 | 0 | 0.00 | 4.2 | — | — | View |
| UGC06930 | 6 | 1 | 0.17 | 14.9 | 0.38 | — | View |
| UGC06973 | 3 | 2 | 0.67 | 6.1 | 0.45 | — | View |
| UGC06983 | 5 | 4 | 0.80 | 13.9 | 1.68 | ✓ | View |
| UGC07089 | 5 | 1 | 0.20 | 8.3 | 2.03 | — | View |
| UGC07125 | 6 | 2 | 0.33 | 17.2 | 0.33 | — | View |
| UGC07151 | 4 | 2 | 0.50 | 5.0 | 0.55 | ✓ | View |
| UGC07232 | 1 | 1 | 1.00 | 0.6 | 0.49 | — | View |
| UGC07261 | 4 | 0 | 0.00 | 5.7 | — | — | View |
| UGC07323 | 4 | 2 | 0.50 | 5.2 | 1.15 | ✓ | View |
| UGC07399 | 4 | 2 | 0.50 | 5.5 | 0.75 | ✓ | View |
| UGC07524 | 6 | 5 | 0.83 | 10.3 | 0.98 | ✓ | View |
| UGC07559 | 2 | 2 | 1.00 | 2.2 | 0.49 | — | View |
| UGC07577 | 2 | 2 | 1.00 | 1.5 | 0.22 | — | View |
| UGC07603 | 3 | 3 | 1.00 | 3.8 | 1.27 | — | View |
| UGC07608 | 3 | 2 | 0.67 | 4.2 | 0.31 | — | View |
| UGC07690 | 3 | 1 | 0.33 | 3.5 | 0.25 | — | View |
| UGC07866 | 2 | 2 | 1.00 | 2.0 | 0.52 | — | View |
| UGC08286 | 5 | 3 | 0.60 | 7.6 | 1.14 | ✓ | View |
| UGC08490 | 5 | 5 | 1.00 | 9.8 | 0.61 | ✓ | View |
| UGC08550 | 4 | 2 | 0.50 | 4.9 | 0.41 | ✓ | View |
| UGC08699 | 9 | 7 | 0.78 | 25.5 | 1.49 | ✓ | View |
| UGC08837 | 3 | 2 | 0.67 | 3.7 | 0.56 | — | View |
| UGC09037 | 7 | 5 | 0.71 | 25.5 | 4.31 | ~ | View |
| UGC09133 | 18 | 13 | 0.72 | 108.0 | 0.49 | ✓ | View |
| UGC09992 | 3 | 0 | 0.00 | 3.1 | — | — | View |
| UGC10310 | 4 | 1 | 0.25 | 6.6 | 0.02 | — | View |
| UGC11455 | 11 | 8 | 0.73 | 41.3 | 5.52 | ⚠ | View |
| UGC11557 | 6 | 1 | 0.17 | 10.2 | 0.40 | — | View |
| UGC11820 | 7 | 1 | 0.14 | 15.6 | 3.52 | — | View |
| UGC11914 | 5 | 5 | 1.00 | 9.6 | 2.02 | ✓ | View |
| UGC12506 | 12 | 6 | 0.50 | 49.2 | 3.11 | ~ | View |
| UGC12632 | 5 | 3 | 0.60 | 9.9 | 0.42 | ✓ | View |
| UGC12732 | 6 | 3 | 0.50 | 14.4 | 0.65 | ✓ | View |
| UGCA281 | 2 | 1 | 0.50 | 1.0 | 2.57 | — | View |
| UGCA442 | 4 | 1 | 0.25 | 5.9 | 1.10 | — | View |
| UGCA444 | 3 | 3 | 1.00 | 2.5 | 1.12 | — | View |
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")
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