For nearly a century, astronomers have observed a quiet but persistent contradiction: galaxies spin too fast. The outer stars move as if held by invisible hands—far beyond what visible matter should allow. According to Newton’s law of gravity, these stars should slow down with distance. But they don’t.
Instead, the curves flatten. The velocity remains high, long after the luminous matter ends. This mismatch is one of modern astronomy’s most famous puzzles, and it has a name: the galactic rotation problem.
Most models solve this by adding something we can’t see: dark matter. But what if the stars are not lying—and nothing is missing? What if the problem is not the mass… but the math?
In 1933, Swiss astrophysicist Fritz Zwicky studied the Coma Cluster and made a shocking discovery: galaxies within the cluster were moving far too fast to be held together by the visible matter. To account for this, he proposed the existence of “dunkle Materie”—dark matter—an invisible substance whose gravity could explain the motions.
Zwicky’s proposal was bold, and largely overlooked. But in the 1970s, another scientist brought the mystery back into the spotlight—this time, within individual galaxies. Vera Rubin, working with W. Kent Ford, carefully measured how stars moved in spiral galaxies like Andromeda. What she found stunned the astrophysics community: the rotation curves didn’t fall. They flattened.
Rubin’s data was meticulous and unambiguous. It showed that Newtonian expectations failed—consistently and dramatically. The case for dark matter was no longer speculative; it was empirical. And yet, Rubin herself remained open-minded about the nature of the cause. In her words:
“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 (SCG) enters not as a rejection of Rubin and Zwicky, but as a possible resolution to the very problem they saw so clearly. It offers a third path: not added matter, not tweaked force—but a change in perspective. What if the stars aren’t misbehaving? What if the space around them is telling the truth?
Most models of galactic motion begin with force: how much mass is there, and how strongly does it pull? But Spatial-Causal Geometry (SCG) starts elsewhere. It begins with a different question:
What if motion isn’t caused by mass at all?
What if motion emerges from how space itself curves?
In SCG, space is not empty. It has structure—a causal-density field, written as \( \rho(x) \). This field tells us how “tightly woven” space is at every point. And where this field curves, things move. No invisible matter, no pulling forces—just local imbalances in geometry, like a ball rolling down a slope.
Instead of force causing acceleration, SCG says acceleration arises from the gradient of this curvature:
\( a(x) = c^2 \cdot \frac{d}{dx} \ln \rho(x) \)
Here, \( c \) is the causal speed limit—analogous to the speed of light—and \( \ln \rho(x) \) is the natural logarithm of the density field. When \( \rho(x) \) is flat, there’s no motion. When it curves, structures respond by accelerating. That’s all it takes.
This is the heart of SCG: movement is not commanded; it is permitted by curvature. Galaxies spin not because something is pulling on them, but because their space is curved in a very specific, very consistent way.
If acceleration comes from the curvature of \( \ln \rho(x) \), then rotation—circular motion—emerges naturally from that geometry. In Spatial-Causal Geometry, the orbital velocity of a star at radius \( r \) is given by:
\( v(r) = A \cdot r^{(1 - B)/2} \)
This deceptively simple equation has only two free parameters:
To evaluate how well a curve fits a galaxy, we calculate the root-mean-square deviation (RMSD). This is a measure of the average error between the predicted velocities and the observed ones. The smaller the RMSD, the better the fit.
In SCG, causal phase structure refers to how the spatial curvature evolves as you move outward from the center of a galaxy. It's the pattern or “tempo” of how tightly or loosely space is curved, and how that pattern influences motion. When \( B \) is higher, curvature drops off more sharply—leading to falling rotation curves. When \( B \) is lower, curvature is shallower and more extended—resulting in flat or rising curves.
This structure isn’t imposed by external forces—it emerges from how space itself wants to bend. And \( B \) lets us read that curvature’s character directly from the stars.
Unlike Newtonian gravity, where velocity falls off as \( v \sim r^{-1/2} \), or dark matter models that require complex halo profiles, SCG gives you the full rotation curve from geometry alone. It doesn’t need to be patched with invisible mass. It just asks:
How does space curve here?
When you fit this curve to real galactic data, you’re not tuning a theory—you’re tracing the shape of space.
To see SCG in action, let’s look at a relatively quiet galaxy: UGC 05829. It’s a late-type spiral with a smooth rotation profile and just enough data points to resolve its causal structure clearly. This makes it a perfect first example for a single-phase SCG fit.
Below, the blue points show real observations of stellar velocity at different radii. The orange line is the SCG prediction, generated from the galaxy’s own geometric curvature:
\( v(r) = A \cdot r^{(1 - B)/2} \)
For UGC 05829, the SCG model found:
Despite the simplicity—just a single phase and two parameters—the fit is remarkably precise. The low value of \( B \) means the causal gradient is nearly flat, which translates into a slow and steady rise in velocity across the disk.
This is the elegance of SCG: no added mass, no special forces. Just the curve of space—and how the stars respond to it.
Every galaxy fit starts the same way: with a table of observations. For each radius \( r \), we’re given the orbital velocity \( v \)—how fast the stars are moving at that distance. This is the raw rotation curve.
SCG doesn’t simulate particles or halos. It simply asks: what geometric curve best describes these points, based on the equation:
\( v(r) = A \cdot r^{(1 - B)/2} \)
To find the best-fitting values of \( A \) and \( B \), we use a standard method from numerical analysis: nonlinear least squares optimization. Specifically, we minimize the root-mean-square deviation (RMSD) between the observed velocities and the predicted curve.
This means we’re solving:
\( \text{RMSD} = \sqrt{ \frac{1}{N} \sum_{i=1}^{N} (v_{\text{obs},i} - v_{\text{SCG}}(r_i; A, B))^2 } \)
We scan through candidate values of \( A \) and \( B \), adjust them automatically, and stop when the RMSD is as low as possible. The lower the RMSD, the tighter the match. For most of the 175 galaxies studied, SCG fits converge in milliseconds—fast, stable, and accurate.
When the galaxy is simple, like UGC 05829, a single curve is enough. When it’s more complex, we test for a breakpoint and consider a two-region model. But the principle is always the same: match the observed motion by finding the shape that space wants to be.
Not all galaxies follow a single smooth pattern. Some change character—gently or suddenly—as you move outward from the core. In SCG, these transitions are called causal phase shifts, and they show up as changes in the curvature of space itself.
To model these galaxies, we extend the basic SCG equation into two regions, each with its own parameters:
The point where the galaxy transitions from one curvature profile to another is called the breakpoint. We don’t choose it by eye—we find it by scanning every possible location and computing the total RMSD across both regions.
If the dual-fit RMSD improves by at least 20% compared to the single fit, we accept the dual model as a meaningful phase separation.
This isn’t just an algorithmic trick—it’s a geometric insight. A galaxy that changes causal structure is telling us something about how its inner and outer densities evolved. SCG listens carefully, and models both regions with precision.
NGC 2903 is a bright barred spiral galaxy, rich in structure and star formation. When we first attempt to fit it with a single SCG curve, the result is decent—but something feels off. The inner velocities rise too steeply, while the outer disk flattens more gently. A single causal phase can’t capture both behaviors at once.
So we let SCG ask a deeper question: is there a natural place to divide the curve? Using a breakpoint scan, the model splits the galaxy into two regions and fits each independently. The result is strikingly better.
SCG identifies the breakpoint at \( r = 2.88 \, \text{kpc} \), and returns these parameters:
That’s an 84% improvement in RMSD—well beyond the 20% threshold. More importantly, the fit aligns beautifully with observation. The inner region reflects a gently curved basin with a lower causal-phase exponent \( B_1 = -0.337 \), while the outer region sharpens into a steeper curvature phase with \( B_2 = 1.184 \). This dual-profile structure captures the shift from central coherence to an extended outer gradient with remarkable fidelity.
This is the power of SCG’s dual-region modeling: it lets space speak with two voices, and listens to both.
Click any “View” link to display the SCG rotation curve fit for that galaxy. Click column headers to sort.
Galaxy | Datapoints | A1 | B1 | Single RMSD | Break Radius | A2 | B2 | Dual RMSD | View |
---|---|---|---|---|---|---|---|---|---|
CamB | 9 | 11.863 | -0.859 | 0.271 | View | ||||
D512-2 | 4 | 25.348 | 0.382 | 2.298 | View | ||||
D564-8 | 6 | 14.605 | -0.046 | 1.127 | View | ||||
D631-7 | 16 | 20.057 | -0.293 | 2.874 | 4.940 | 46.843 | 0.777 | 1.286 | View |
DDO064 | 14 | 27.554 | -0.624 | 3.787 | 1.680 | 43.322 | 0.841 | 1.597 | View |
DDO154 | 12 | 22.185 | -0.207 | 3.054 | 3.460 | 42.601 | 0.884 | 0.889 | View |
DDO161 | 31 | 21.375 | -0.036 | 2.789 | 8.160 | 46.321 | 0.709 | 0.993 | View |
DDO168 | 10 | 28.320 | -0.044 | 3.775 | View | ||||
DDO170 | 8 | 27.603 | 0.315 | 3.603 | View | ||||
ESO079-G014 | 15 | 41.854 | -0.203 | 9.307 | 8.340 | 100.515 | 0.583 | 4.402 | View |
ESO116-G012 | 15 | 45.409 | -0.090 | 7.358 | 4.130 | 89.278 | 0.801 | 1.557 | View |
ESO444-G084 | 7 | 39.992 | 0.277 | 4.072 | View | ||||
ESO563-G021 | 30 | 43.083 | -0.808 | 41.644 | 10.230 | 287.520 | 0.946 | 6.866 | View |
F561-1 | 6 | 24.407 | 0.298 | 3.466 | View | ||||
F563-1 | 17 | 20.090 | -1.500 | 10.692 | 6.950 | 65.062 | 0.630 | 2.948 | View |
F563-V1 | 6 | 14.667 | 0.272 | 3.159 | View | ||||
F563-V2 | 10 | 53.099 | 0.253 | 13.111 | View | ||||
F565-V2 | 7 | 24.423 | -0.172 | 4.060 | View | ||||
F567-2 | 5 | 26.040 | 0.357 | 4.065 | View | ||||
F568-1 | 12 | 35.850 | -0.595 | 10.287 | 4.840 | 79.363 | 0.553 | 1.191 | View |
F568-3 | 18 | 18.592 | -1.062 | 9.699 | 4.190 | 53.602 | 0.459 | 2.339 | View |
F568-V1 | 15 | 42.727 | -0.235 | 11.957 | 5.090 | 101.517 | 0.905 | 2.760 | View |
F571-8 | 13 | 42.833 | -0.129 | 8.024 | 5.440 | 75.903 | 0.512 | 3.366 | View |
F571-V1 | 7 | 31.824 | 0.196 | 5.576 | View | ||||
F574-1 | 14 | 32.078 | -0.374 | 7.625 | 5.170 | 67.511 | 0.683 | 1.463 | View |
F574-2 | 5 | 8.385 | -0.334 | 0.991 | View | ||||
F579-V1 | 14 | 64.503 | 0.223 | 8.805 | 4.740 | 93.434 | 0.841 | 3.580 | View |
F583-1 | 25 | 21.593 | -0.491 | 7.847 | 5.400 | 53.658 | 0.645 | 2.609 | View |
F583-4 | 12 | 29.934 | 0.048 | 2.521 | 4.350 | 33.599 | 0.274 | 1.960 | View |
IC2574 | 34 | 13.938 | -0.368 | 1.907 | View | ||||
IC4202 | 32 | 63.040 | -0.443 | 27.298 | 7.080 | 231.936 | 0.967 | 7.047 | View |
KK98-251 | 15 | 16.166 | -0.845 | 2.140 | 1.720 | 24.448 | 0.352 | 0.611 | View |
NGC0024 | 29 | 70.263 | -0.173 | 14.415 | 2.020 | 101.736 | 0.947 | 4.007 | View |
NGC0055 | 21 | 26.763 | -0.145 | 5.290 | 7.980 | 77.399 | 0.912 | 1.098 | View |
NGC0100 | 21 | 28.752 | -0.432 | 5.553 | 3.880 | 53.341 | 0.535 | 1.269 | View |
NGC0247 | 26 | 40.573 | 0.232 | 2.844 | 10.230 | 70.847 | 0.687 | 2.113 | View |
NGC0289 | 28 | 180.875 | 1.034 | 9.123 | 37.840 | 414.187 | 1.442 | 7.238 | View |
NGC0300 | 25 | 36.899 | -0.183 | 5.483 | 4.540 | 62.702 | 0.656 | 2.073 | View |
NGC0801 | 13 | 121.196 | -0.160 | 27.724 | 10.590 | 249.447 | 1.073 | 7.126 | View |
NGC0891 | 18 | 216.873 | 0.978 | 9.228 | 9.730 | 325.765 | 1.309 | 6.381 | View |
NGC1003 | 36 | 44.727 | 0.183 | 5.300 | 8.700 | 67.026 | 0.691 | 2.219 | View |
NGC1090 | 24 | 56.245 | -0.251 | 21.201 | 6.610 | 182.103 | 1.065 | 3.753 | View |
NGC1705 | 14 | 67.213 | 0.649 | 3.739 | 2.450 | 72.385 | 1.016 | 1.063 | View |
NGC2366 | 26 | 22.794 | -0.527 | 5.175 | 2.970 | 51.569 | 1.026 | 1.402 | View |
NGC2403 | 73 | 72.945 | 0.342 | 8.282 | 3.900 | 106.809 | 0.839 | 3.343 | View |
NGC2683 | 11 | 208.008 | 1.060 | 14.257 | 17.310 | 398.496 | 1.558 | 10.693 | View |
NGC2841 | 50 | 269.827 | 0.844 | 11.839 | 14.350 | 362.310 | 1.128 | 5.670 | View |
NGC2903 | 34 | 114.661 | -0.337 | 31.900 | 2.880 | 243.154 | 1.184 | 5.085 | View |
NGC2915 | 30 | 39.105 | -0.503 | 9.629 | 3.350 | 78.049 | 0.947 | 2.316 | View |
NGC2955 | 24 | 203.730 | 0.767 | 19.563 | 19.490 | 677.946 | 1.601 | 10.924 | View |
NGC2976 | 27 | 52.160 | -0.769 | 3.733 | 1.560 | 69.333 | 0.419 | 1.939 | View |
NGC2998 | 13 | 125.688 | 0.303 | 19.019 | 7.590 | 210.119 | 0.997 | 4.706 | View |
NGC3109 | 25 | 19.467 | -0.865 | 2.758 | 2.580 | 28.520 | 0.052 | 0.837 | View |
NGC3198 | 43 | 53.224 | -0.072 | 19.197 | 8.040 | 158.336 | 1.033 | 2.895 | View |
NGC3521 | 41 | 178.322 | 0.500 | 12.145 | 2.230 | 222.212 | 1.047 | 3.627 | View |
NGC3726 | 12 | 60.979 | 0.130 | 11.557 | 12.190 | 120.738 | 0.812 | 5.252 | View |
NGC3741 | 21 | 24.678 | 0.252 | 1.455 | 2.800 | 28.889 | 0.391 | 1.074 | View |
NGC3769 | 12 | 78.626 | 0.542 | 8.321 | 10.470 | 127.101 | 1.046 | 2.682 | View |
NGC3877 | 13 | 53.005 | -0.514 | 16.513 | 5.240 | 141.562 | 0.839 | 4.342 | View |
NGC3893 | 10 | 163.098 | 0.914 | 12.439 | View | ||||
NGC3917 | 17 | 36.512 | -0.406 | 12.895 | 6.980 | 119.260 | 0.889 | 3.627 | View |
NGC3949 | 7 | 115.493 | 0.597 | 3.421 | View | ||||
NGC3953 | 8 | 167.271 | 0.774 | 7.898 | View | ||||
NGC3972 | 10 | 56.388 | 0.149 | 5.737 | View | ||||
NGC3992 | 9 | 296.584 | 1.098 | 11.436 | View | ||||
NGC4010 | 12 | 40.019 | -0.151 | 7.791 | 6.110 | 108.494 | 0.873 | 3.212 | View |
NGC4013 | 36 | 230.538 | 1.171 | 7.529 | 14.980 | 113.485 | 0.734 | 2.634 | View |
NGC4051 | 7 | 106.325 | 0.668 | 8.984 | View | ||||
NGC4068 | 6 | 20.746 | -0.707 | 0.904 | View | ||||
NGC4085 | 7 | 62.208 | 0.055 | 10.353 | View | ||||
NGC4088 | 12 | 75.005 | 0.132 | 17.104 | 10.470 | 199.053 | 1.108 | 5.548 | View |
NGC4100 | 24 | 81.846 | 0.074 | 17.503 | 7.850 | 307.659 | 1.410 | 4.879 | View |
NGC4138 | 7 | 218.170 | 1.255 | 11.231 | View | ||||
NGC4157 | 17 | 89.225 | 0.184 | 18.778 | 10.470 | 204.408 | 1.066 | 5.710 | View |
NGC4183 | 23 | 52.934 | 0.249 | 7.924 | 7.850 | 116.098 | 1.035 | 1.972 | View |
NGC4214 | 14 | 67.813 | 0.722 | 2.105 | 2.300 | 78.186 | 0.963 | 0.658 | View |
NGC4217 | 19 | 67.131 | -0.378 | 21.409 | 5.240 | 192.073 | 1.038 | 6.778 | View |
NGC4389 | 6 | 33.771 | -0.414 | 1.851 | View | ||||
NGC4559 | 32 | 54.286 | 0.066 | 7.207 | 4.580 | 91.957 | 0.797 | 2.705 | View |
NGC5005 | 18 | 225.423 | 0.849 | 5.219 | 3.440 | 251.652 | 0.956 | 2.422 | View |
NGC5033 | 22 | 214.255 | 0.981 | 10.368 | 30.870 | 319.512 | 1.270 | 3.960 | View |
NGC5055 | 28 | 150.469 | 0.676 | 16.836 | 11.490 | 283.082 | 1.246 | 4.757 | View |
NGC5371 | 19 | 205.449 | 0.913 | 12.158 | 31.760 | 211.379 | 1.005 | 5.420 | View |
NGC5585 | 24 | 39.329 | 0.106 | 5.155 | 6.850 | 94.075 | 1.034 | 2.479 | View |
NGC5907 | 19 | 188.899 | 0.837 | 7.065 | 17.610 | 292.097 | 1.164 | 3.840 | View |
NGC5985 | 33 | 154.854 | 0.426 | 14.201 | 11.080 | 333.900 | 1.087 | 3.777 | View |
NGC6015 | 44 | 80.243 | -0.016 | 15.624 | 3.280 | 143.205 | 0.940 | 5.257 | View |
NGC6195 | 23 | 183.752 | 0.613 | 11.777 | 6.340 | 226.309 | 0.935 | 7.930 | View |
NGC6503 | 31 | 86.418 | 0.457 | 6.159 | 4.550 | 120.007 | 1.026 | 1.486 | View |
NGC6674 | 15 | 206.736 | 0.787 | 20.759 | 27.480 | 284.164 | 1.082 | 10.492 | View |
NGC6789 | 4 | 78.674 | -0.559 | 0.614 | View | ||||
NGC6946 | 58 | 139.274 | 0.921 | 12.476 | 4.770 | 216.602 | 1.201 | 6.010 | View |
NGC7331 | 36 | 181.221 | 0.556 | 7.296 | 5.350 | 275.599 | 1.091 | 3.898 | View |
NGC7793 | 46 | 63.446 | 0.147 | 11.055 | 4.200 | 195.144 | 1.706 | 4.534 | View |
NGC7814 | 18 | 255.631 | 1.130 | 2.609 | 12.820 | 214.000 | 1.000 | 1.803 | View |
PGC51017 | 6 | 19.576 | 1.101 | 0.222 | View | ||||
UGC00128 | 22 | 42.289 | 0.186 | 10.367 | 16.220 | 114.631 | 0.933 | 4.093 | View |
UGC00191 | 9 | 44.891 | 0.396 | 6.868 | View | ||||
UGC00634 | 4 | 47.491 | 0.400 | 5.179 | View | ||||
UGC00731 | 12 | 33.325 | 0.199 | 2.628 | 6.360 | 53.570 | 0.718 | 1.292 | View |
UGC00891 | 5 | 23.062 | -0.062 | 2.893 | View | ||||
UGC01230 | 11 | 32.845 | -0.289 | 17.838 | 8.600 | 113.922 | 1.049 | 5.400 | View |
UGC01281 | 25 | 20.393 | -0.793 | 3.846 | 3.070 | 45.135 | 0.709 | 0.535 | View |
UGC02023 | 5 | 19.244 | -0.668 | 0.575 | View | ||||
UGC02259 | 8 | 60.601 | 0.602 | 2.805 | View | ||||
UGC02455 | 8 | 21.773 | -0.390 | 2.545 | View | ||||
UGC02487 | 17 | 380.640 | 1.013 | 11.658 | 45.210 | 448.167 | 1.143 | 5.405 | View |
UGC02885 | 19 | 278.681 | 1.004 | 11.742 | 31.220 | 200.764 | 0.811 | 7.658 | View |
UGC02916 | 43 | 201.639 | 0.945 | 9.810 | 22.140 | 666.962 | 1.728 | 4.468 | View |
UGC02953 | 115 | 241.413 | 0.870 | 25.692 | 5.250 | 362.633 | 1.161 | 9.147 | View |
UGC03205 | 48 | 146.343 | 0.393 | 22.561 | 4.970 | 259.690 | 1.116 | 3.599 | View |
UGC03546 | 30 | 240.794 | 1.221 | 7.127 | 9.730 | 243.169 | 1.148 | 4.126 | View |
UGC03580 | 47 | 70.108 | 0.865 | 5.102 | 2.180 | 76.107 | 0.671 | 3.100 | View |
UGC04278 | 25 | 28.900 | -0.030 | 3.167 | 4.010 | 24.769 | -0.404 | 1.815 | View |
UGC04305 | 22 | 22.928 | -1.324 | 5.299 | 1.500 | 35.840 | 1.088 | 1.432 | View |
UGC04325 | 8 | 60.033 | 0.412 | 7.041 | View | ||||
UGC04483 | 8 | 23.867 | 0.057 | 1.572 | View | ||||
UGC04499 | 9 | 35.732 | 0.240 | 4.197 | View | ||||
UGC05005 | 11 | 19.040 | -0.298 | 6.134 | 8.600 | 42.136 | 0.457 | 2.662 | View |
UGC05253 | 73 | 240.492 | 0.970 | 9.035 | 18.340 | 408.160 | 1.335 | 2.880 | View |
UGC05414 | 6 | 28.905 | -0.108 | 1.921 | View | ||||
UGC05716 | 12 | 39.021 | 0.395 | 2.312 | 7.230 | 65.881 | 0.908 | 0.538 | View |
UGC05721 | 23 | 64.383 | 0.085 | 9.209 | 1.530 | 83.631 | 1.074 | 1.727 | View |
UGC05750 | 11 | 11.003 | -0.963 | 8.966 | 4.820 | 39.829 | 0.521 | 3.113 | View |
UGC05764 | 10 | 38.512 | 0.382 | 4.891 | View | ||||
UGC05829 | 11 | 22.489 | -0.154 | 1.120 | View | ||||
UGC05918 | 8 | 25.595 | 0.213 | 1.770 | View | ||||
UGC05986 | 15 | 43.552 | -0.369 | 11.730 | 4.390 | 127.614 | 1.128 | 2.183 | View |
UGC05999 | 5 | 31.812 | 0.144 | 5.924 | View | ||||
UGC06399 | 9 | 36.772 | 0.090 | 5.107 | View | ||||
UGC06446 | 17 | 46.969 | 0.325 | 4.607 | 5.230 | 76.172 | 0.924 | 2.572 | View |
UGC06614 | 13 | 211.328 | 1.166 | 10.794 | 24.940 | 159.324 | 0.875 | 2.780 | View |
UGC06628 | 7 | 30.268 | 0.620 | 2.331 | View | ||||
UGC06667 | 9 | 36.435 | 0.095 | 4.667 | View | ||||
UGC06786 | 45 | 186.279 | 0.833 | 7.929 | 14.930 | 287.494 | 1.163 | 3.366 | View |
UGC06787 | 71 | 262.091 | 0.711 | 18.487 | 1.660 | 224.124 | 0.946 | 11.700 | View |
UGC06818 | 8 | 22.017 | -0.296 | 4.295 | View | ||||
UGC06917 | 11 | 46.321 | 0.075 | 3.830 | 6.110 | 67.261 | 0.576 | 1.327 | View |
UGC06923 | 6 | 43.637 | 0.185 | 3.697 | View | ||||
UGC06930 | 10 | 60.195 | 0.542 | 6.169 | View | ||||
UGC06973 | 9 | 165.912 | 0.939 | 2.581 | View | ||||
UGC06983 | 17 | 58.009 | 0.377 | 6.417 | 7.850 | 109.179 | 1.001 | 3.631 | View |
UGC07089 | 12 | 24.436 | -0.207 | 2.374 | 5.240 | 36.398 | 0.303 | 1.258 | View |
UGC07125 | 13 | 26.907 | 0.261 | 3.414 | 11.470 | 64.667 | 0.994 | 1.502 | View |
UGC07151 | 11 | 42.076 | -0.132 | 5.294 | 3.000 | 53.442 | 0.590 | 2.022 | View |
UGC07232 | 4 | 50.533 | -0.464 | 0.295 | View | ||||
UGC07261 | 7 | 51.501 | 0.560 | 2.398 | View | ||||
UGC07323 | 10 | 31.965 | -0.156 | 1.754 | View | ||||
UGC07399 | 10 | 65.230 | 0.440 | 4.275 | View | ||||
UGC07524 | 31 | 27.974 | -0.112 | 3.557 | 3.790 | 44.687 | 0.477 | 1.111 | View |
UGC07559 | 7 | 19.879 | -0.161 | 1.333 | View | ||||
UGC07577 | 9 | 11.455 | -0.587 | 0.305 | View | ||||
UGC07603 | 12 | 40.940 | -0.281 | 4.628 | 2.050 | 56.234 | 0.835 | 0.667 | View |
UGC07608 | 8 | 28.607 | -0.183 | 3.168 | View | ||||
UGC07690 | 7 | 53.642 | 0.854 | 4.009 | View | ||||
UGC07866 | 7 | 22.620 | 0.087 | 0.809 | View | ||||
UGC08286 | 17 | 44.064 | -0.298 | 6.692 | 2.840 | 66.177 | 0.758 | 1.655 | View |
UGC08490 | 30 | 59.460 | 0.196 | 5.360 | 2.030 | 77.226 | 0.980 | 1.104 | View |
UGC08550 | 11 | 38.651 | 0.268 | 3.203 | 2.920 | 42.257 | 0.614 | 2.043 | View |
UGC08699 | 41 | 188.303 | 0.748 | 9.908 | 2.390 | 190.154 | 1.037 | 5.082 | View |
UGC08837 | 8 | 14.868 | -0.722 | 1.751 | View | ||||
UGC09037 | 22 | 56.240 | 0.257 | 10.876 | 18.230 | 292.368 | 1.410 | 5.004 | View |
UGC09133 | 68 | 279.619 | 0.983 | 13.915 | 16.610 | 382.530 | 1.234 | 3.858 | View |
UGC09992 | 5 | 29.337 | 0.759 | 0.361 | View | ||||
UGC10310 | 7 | 40.056 | 0.347 | 4.801 | View | ||||
UGC11455 | 36 | 70.456 | -0.112 | 35.085 | 14.560 | 367.683 | 1.174 | 8.545 | View |
UGC11557 | 12 | 19.106 | -0.765 | 8.119 | 4.620 | 57.328 | 0.664 | 3.078 | View |
UGC11820 | 10 | 34.030 | 0.322 | 4.613 | View | ||||
UGC11914 | 65 | 266.143 | 0.304 | 17.321 | 1.300 | 290.892 | 1.012 | 3.709 | View |
UGC12506 | 31 | 128.633 | 0.523 | 18.446 | 18.850 | 351.051 | 1.216 | 5.657 | View |
UGC12632 | 15 | 28.833 | -0.130 | 3.611 | 4.260 | 46.573 | 0.605 | 0.885 | View |
UGC12732 | 16 | 36.800 | 0.153 | 2.515 | 5.760 | 44.131 | 0.438 | 1.647 | View |
UGCA281 | 7 | 30.619 | 0.021 | 1.731 | View | ||||
UGCA442 | 8 | 28.001 | 0.129 | 3.797 | View | ||||
UGCA444 | 36 | 23.388 | -0.244 | 1.199 | 0.690 | 24.686 | 0.126 | 0.650 | View |
While Spatial-Causal Geometry approaches galactic motion from geometric principles—not through conventional force models—it’s still fair to ask: how well do its predictions match real data?
Below is a summary of RMSD (root-mean-square deviation) values reported by various models across similar galactic datasets. Lower RMSD indicates a closer match between model and observation.
Study / Model | Typical RMSD (km/s) |
---|---|
McGaugh et al. (2016, 2020) | 6–13 |
Li et al. (2018) | ~13 |
NFW / Burkert / MOND fits | 6–15 |
D. J. Hallman (2025, SCG) | 0.22–13.1 (most between 2–5) |
These results speak for themselves. SCG isn’t just a different way of seeing—it’s also surprisingly precise. And all it needs is geometry.
This script applies the SCG causal-density velocity model to galactic rotation data. It evaluates both single and dual-region fits, selecting the model with the lowest RMSD. The equation used is \( v(r) = A \cdot r^{(1-B)/2} \), derived from curvature in \( \ln \rho(r) \).
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import os
import shutil
import pandas as pd
# --- SCG MODEL AND METRICS ---
def scg_velocity(x, A, B):
"""
SCG rotation velocity model:
v(r) = A * r^((1 - B)/2)
This follows directly from SCG's causal-density curvature relation.
"""
return A * x**((1 - B) / 2)
def rmsd(observed, predicted):
"""
Computes root mean square deviation (RMSD) between observed and predicted values.
Used to assess model fit quality.
"""
return np.sqrt(np.mean((observed - predicted) ** 2))
# --- SINGLE-REGION SCG FIT ---
def fit_region(radius, velocity):
"""
Fits the SCG model to a region of (radius, velocity) data.
Returns best-fit parameters, predicted values, and RMSD.
"""
if len(radius) < 4:
return None # Too few data points for a stable fit
try:
params, _ = curve_fit(scg_velocity, radius, velocity, p0=[100, 1.7])
pred = scg_velocity(radius, *params)
return params, pred, rmsd(velocity, pred)
except RuntimeError:
return None # Fitting failed (e.g., numerical instability)
# --- DUAL-REGION SPLIT FIT SEARCH ---
def find_best_break(radius, velocity, min_gap=5):
"""
Finds the best radius to split the dataset into two zones,
minimizing combined RMSD of two SCG fits.
"""
best_rmsd = float('inf')
best_split = None
best_fits = None
for i in range(min_gap, len(radius) - min_gap):
r_break = radius[i]
left_mask = radius < r_break
right_mask = radius >= r_break
fit_left = fit_region(radius[left_mask], velocity[left_mask])
fit_right = fit_region(radius[right_mask], velocity[right_mask])
if fit_left and fit_right:
total_rmsd = (
fit_left[2] * np.sum(left_mask) +
fit_right[2] * np.sum(right_mask)
) / len(radius)
if total_rmsd < best_rmsd:
best_rmsd = total_rmsd
best_split = r_break
best_fits = (fit_left, fit_right)
return best_split, best_fits, best_rmsd
# --- PER-FILE (PER-GALAXY) FITTING LOGIC ---
def process_file(filepath, output_dir="graphs_final", processed_dir="processed_final", min_improvement=0.20):
"""
Processes a single galaxy data file:
- Performs global (single) SCG fit
- Optionally applies dual-region split if significantly better
- Generates a plot
- Returns summary fit metrics
"""
galaxy_name = os.path.basename(filepath).split('_')[0]
with open(filepath, 'r') as file:
lines = file.readlines()
data = [line.strip().split() for line in lines if not line.startswith("#")]
radius = np.array([float(row[0]) for row in data])
v_obs = np.array([float(row[1]) for row in data])
num_points = len(radius)
global_fit = fit_region(radius, v_obs)
if not global_fit:
return {
"Galaxy": galaxy_name,
"Best Fit": "Failed",
"Global RMSD": None,
"Dual RMSD": None,
"Break Radius": None,
"A1": None, "B1": None, "A2": None, "B2": None,
}
global_rmsd = global_fit[2]
use_dual = False
break_radius, split_fits, dual_rmsd = None, (None, None), None
# Try dual-region fit if there are enough data points
if num_points >= 10:
break_radius, split_fits, dual_rmsd = find_best_break(radius, v_obs)
# Only switch to dual if improvement over single is meaningful
if break_radius and global_rmsd > 0 and ((global_rmsd - dual_rmsd) / global_rmsd) >= min_improvement:
use_dual = True
# --- Visualization ---
os.makedirs(output_dir, exist_ok=True)
fig, ax = plt.subplots(figsize=(9, 5))
ax.plot(radius, v_obs, 'o', label='Observed', markersize=4)
if use_dual:
fit_left, fit_right = split_fits
ax.plot(radius[radius < break_radius], fit_left[1], '-', label=f'Inner Fit B={fit_left[0][1]:.3f}')
ax.plot(radius[radius >= break_radius], fit_right[1], '-', label=f'Outer Fit B={fit_right[0][1]:.3f}')
ax.axvline(break_radius, color='gray', linestyle='--', alpha=0.6, label=f'Break @ {break_radius:.2f} kpc')
title = f'{galaxy_name} | Best Fit: Dual | RMSD: {dual_rmsd:.2f}'
else:
ax.plot(radius, global_fit[1], '-', label=f'Single Fit B={global_fit[0][1]:.3f}')
title = f'{galaxy_name} | Best Fit: Single | RMSD: {global_rmsd:.2f}'
ax.set_title(title)
ax.set_xlabel('Radius (kpc)')
ax.set_ylabel('Velocity (km/s)')
ax.grid(True)
ax.legend()
plt.tight_layout()
plt.savefig(f"{output_dir}/{galaxy_name}_best_fit.png")
plt.close()
# Move file to processed directory
os.makedirs(processed_dir, exist_ok=True)
shutil.move(filepath, os.path.join(processed_dir, os.path.basename(filepath)))
print(filepath)
return {
"Galaxy": galaxy_name,
"Datapoints": num_points,
"Best Fit": "Dual" if use_dual else "Single",
"Global RMSD": global_rmsd,
"Dual RMSD": dual_rmsd if use_dual else None,
"Break Radius": break_radius if use_dual else None,
"A1": split_fits[0][0][0] if use_dual else global_fit[0][0],
"B1": split_fits[0][0][1] if use_dual else global_fit[0][1],
"A2": split_fits[1][0][0] if use_dual else None,
"B2": split_fits[1][0][1] if use_dual else None,
}
# --- BATCH PROCESSING LOOP ---
def batch_process():
"""
Runs SCG fitting on all galaxy data files in the current directory
(expects filenames ending with '_rotmod.dat').
Outputs results to CSV.
"""
results = []
for filename in os.listdir():
if filename.endswith("_rotmod.dat"):
result = process_file(filename)
if result:
results.append(result)
df = pd.DataFrame(results)
df.to_csv("SCG_best_fit_summary.csv", index=False)
print("✅ SCG model selection complete. Summary saved to SCG_best_fit_summary.csv.")
# --- ENTRY POINT ---
if __name__ == "__main__":
batch_process()
For updates, examples, or implementation help, contact D. J. Hallman SCG@azfn.com.