---
title: "Recompute a named SPARC galaxy from saved observations"
description: "Run saved-data fits, six starts, stellar-mass profiles and shared distance/inclination scenarios for own synthetic data and local NGC 2403 files."
canonical_url: "https://sandboxphysics.com/stories/galaxy-rotation-curves/recomputing-a-named-sparc-galaxy/"
language: "en"
page_type: "story"
alternate_languages:
  zh-CN: "https://sandboxphysics.com/zh/stories/galaxy-rotation-curves/recomputing-a-named-sparc-galaxy/"
last_modified: "2026-11-05"
---

[Galaxy rotation curves and mass models](https://sandboxphysics.com/stories/galaxy-rotation-curves/)

Galaxy rotation curves · L3

# Recompute a named SPARC galaxy from saved observations

Sandbox Physics · 2026-11-05

First run the complete saved-data analysis on our synthetic observations. Then acquire the fixed NGC 2403 files on your own computer and repeat the calculation. Every optimum is computed from a saved observation; the public package contains no original SPARC rows.

## Before you begin

Read the preceding three chapters and be able to run Python in a terminal. The calculation needs NumPy and SciPy, but no Lab code, browser connection, original PDF or private author software.

## Unpack the independent scientific materials

Download the scientific ZIP, compare its SHA-256, extract into a new directory and open a terminal there. The README records the actual tested environment: Python 3.9.6, NumPy 2.0.2 and SciPy 1.13.1. Record your own compatible versions too; final optimizer digits can change.

The package contains independent Python, own synthetic input, units and source identities. Its default workflow has no network access. It excludes the browser Engine, original papers, SPARC observations and measured-derived plots. Begin with the fully offline calculation below.

Reading: Independent saved-observation rotation-curve calculation

## Save the observation before starting the fitter

The first command generates 48 radii from thin exponential stellar/gas disks and a prescribed NFW field, adds the declared random noise and writes JSON. The generator reports its injection for provenance. The saved observation passed to the fitter has no injection parameters.

The second command opens that file and fits its observed speeds, errors and component columns. It does not read the generator’s truth. The NFW conditional optimum has mass-to-light ratio near $0.607$, rather than the injected $0.55$: noise has moved the optimum.

Each halo receives six starting points. Every stop status, objective value and function-evaluation count is saved. Several synthetic cored fits stop near $\chi^2=167.54$, while another reaches $126.43$. Both satisfy stopping conditions, so their actual objective values still need comparison.

```
python science/generate_observation.py --output results/own-observation.json
python science/analyze.py --input results/own-observation.json --output results/own-analysis
```

Reading: Independent saved-observation rotation-curve calculation

## Recompute checks and figures from those saved results

The validator recalculates positive-density halo integrals, an independent ring-potential thin-disk reference, numerical distance/inclination derivatives and both likelihood-coordinate Jacobians. It also recomputes every saved predicted field and checks that the fit refers to the same input.

The output retains wrong-algorithm controls: treating the exponential disk as a sphere, subtracting nearly equal floating-point numbers and averaging a common error as though it were independent. Correct-method tolerances and failed controls are reported separately.

The plotter reads the saved observation and analysis to draw total speed, components, errors and conditional profiles. The published examples use our own synthetic data. Its poorer cored fit is a deliberate model-mismatch control for an NFW-generated fixture, not a test of real galaxies.

![Own synthetic speeds with errors and a newly fitted NFW component decomposition](https://sandboxphysics.com/_astro/curve.DOUhBg3w.svg)

Own48-point synthetic observation. Independent Python fits the saved speeds, errors and component basis; points and curves are not a measured galaxy image. Equivalent component speeds encode forces whose signed squares add. [Open the full figure](https://sandboxphysics.com/_astro/curve.DOUhBg3w.svg)

```
python science/validate.py --input results/own-observation.json --analysis results/own-analysis --output results/own-validation.json
python science/plot.py --input results/own-observation.json --analysis results/own-analysis --output results/own-figures
```

Reading: Independent saved-observation rotation-curve calculation

## Acquire only the two fixed upstream products

The explicit online command below reads the official SPARC Table 1 and small Rotmod archive, a total of 138,996 body bytes. Exact sizes, SHA-256 and member CRC must pass before it extracts only NGC 2403. The other 174 members remain unextracted and unused. A changed identity stops acquisition without an automatic retry or mirror.

The author actually obtained both 200 responses into an empty directory on 5 November 2026. That fresh-acquisition receipt is separate from the subsequent offline cold-package replay. Future availability is established only by the reader’s own requests.

The fixed Table 1 body is 28,259 bytes and the archive 110,737 bytes. Complete hashes are in the package’s source metadata and downloader. The public material supplies the acquisition procedure; original products remain on the reader’s computer.

```
python science/fetch_named_source.py --online --http-budget-bytes 138996 --output local-sparc
```

Reading: Lelli, McGaugh & Schombert (2016), SPARC, AJ152,157 · Independent saved-observation rotation-curve calculation

## Convert native columns into a traceable saved observation

NGC 2403 is the fixed quality 1, zero-bulge selection. Table 1 supplies $D_0=3.16\pm0.16\,\mathrm{Mpc}$ and $i_0=63^{\circ}\pm3^{\circ}$. The native component file has $73$ radii. Conversion checks hashes, the named 19-field metadata row and the no-bulge conditions before saving the observation.

The eight columns are radius, observed circular speed, speed error, gas equivalent speed, unit-ratio stellar speed, bulge speed, disk surface brightness and bulge surface brightness. Radii are in kiloparsecs; speeds are kilometres per second. The native error excludes inclination systematics and is not a complete covariance matrix.

A negative gas column marks the radial-force direction. Stellar columns retain the native finite-thickness model. This processed rotation-curve record is not the original two-dimensional velocity field; the procedure does not remeasure warps, noncircular motions or photometric deprojection.

```
python science/save_native.py --source local-sparc --output results/native-observation.json
```

Reading: Lelli, McGaugh & Schombert (2016), SPARC, AJ152,157 · Independent saved-observation rotation-curve calculation

## Run the same optimizer and shared-geometry scenarios

The same analysis reads the saved real observation. Bounds are 0.05–1.5 for stellar mass-to-light ratio, 1000–10 billion solar masses per cubic kiloparsec for halo density, and 0.05–200 kiloparsecs for scale radius. This offline search is wider than the interactive demonstration, and those bounds remain part of the analysis.

Ten fixed stellar ratios each receive a new halo optimization. The nine common-geometry scenarios combine central and plus/minus one metadata-error distance and inclination. Every scenario is refitted. These are sensitivity calculations, not posterior samples or an assumed measured joint prior.

This actual rerun gives NFW stellar ratio about $0.288$ and scale $7.147\,\mathrm{kpc}$; the cored solution gives about $0.758$ and $3.029\,\mathrm{kpc}$. Working reduced chi-square values are $9.138$ and $10.696$. Both descriptions are poor at the supplied point-error weights. No selected point or original error is altered.

```
python science/analyze.py --input results/native-observation.json --output results/native-analysis
python science/validate.py --input results/native-observation.json --analysis results/native-analysis --output results/native-validation.json
```

Reading: Independent saved-observation rotation-curve calculation · Lelli, McGaugh & Schombert (2016), SPARC, AJ152,157

## Generate measured-derived figures locally

The explicit option below acknowledges that the output contains local measured-derived figures. Use them on your computer to inspect radial residuals, parameter boundaries and coherent departures. Figures published with these chapters continue to use own synthetic observations.

Early development retained three failures: fixed-byte metadata slicing disagreed with the actual whitespace layout; NumPy booleans failed JSON serialization; and small-core subtraction plus a likelihood-coordinate auditor missed their original numerical gates. Corrected parsing/equivalent arithmetic and the earlier failures are documented separately. The package recomputes mathematical negative controls; a clean exit is not a certificate of every physical inference.

```
python science/plot.py --allow-local-observations --input results/native-observation.json --analysis results/native-analysis --output results/local-measured-figures
```

Reading: Independent saved-observation rotation-curve calculation

## Carry the checks to the next dataset

Record source identities and selection first. Check column units and signs, save observations, then fit. Compare starts and bounds; handle common geometry separately from point errors. Finally read the residuals alongside the physical and error assumptions that produced the score.

This workflow actually recalculates the fixed object’s conditional mass models. It supplies no particle identification, calibrated probability interval or complete original-velocity-field analysis. The next guide uses these habits to examine a newer paper’s changes of observations, assumptions and model comparisons.

Reading: Independent saved-observation rotation-curve calculation · Lelli, McGaugh & Schombert (2016), SPARC, AJ152,157

## Exercises and explained answers

1. Why save the synthetic observation and launch a separate fitter instead of passing the generator’s parameters directly?

   Read the explanation

   The fit then receives the actual saved speeds and errors, without injection parameters. File hashes, repeat analyses and starting-point comparisons become inspectable. The validator also inserts an unusable extra truth field and confirms that fitting ignores it.

2. How can you test acquisition/conversion with no network if you already hold the exact upstream bodies?

   Read the explanation

   Use the downloader’s --from-cache option with the verified source directory and a fresh output directory. Hash and CRC checks still run, followed by saving and analysis. This proves cache replay; a fresh download requires a separate actual network receipt.

3. May the two large real-object scores be fixed by inflating errors until reduced chi-square approaches one?

   Read the explanation

   That changes the statistical question and needs an independently justified error model. Preserve the present mismatch first, then examine residuals, geometry sensitivity and missing physics. Adjusting errors only to improve the displayed score would conceal the original discrepancy.

## Try the prediction

### [Compare the stellar disk and conditional halo](https://sandboxphysics.com/labs/galaxy-rotation-curves/)

1. Export the own synthetic default’s observations and forward prediction.
2. Compare source identities, units and the full likelihood with the currently accepted model.
3. For a local fixed-file import, distinguish the manual forward comparison from this offline six-start fit and inspect residuals in your own output directory.

## Standalone scientific materials

[Download the scientific ZIP](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/galaxy-rotation-science-v1.zip) · [Read setup and calculation scope](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/README.md)

The offline calculation needs Python, NumPy and SciPy. Default synthetic data are fully generated and fitted. Optional named SPARC files are fetched only on your computer; the ZIP has no original rows or browser Engine.

Individual scripts, sizes and SHA-256

- [README.md](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/README.md) (6785 B)
  `5fbf0d29e006fc06f35d2290fd8fee7d5abc2301b601814165495331f9c37a84`
- [LICENSE.txt](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/LICENSE.txt) (1325 B)
  `4f15facc82fd0218a74907ae83f1716569277d9c40f55b249451dd8d89191b2f`
- [requirements.txt](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/requirements.txt) (185 B)
  `359f4c650ca6dd5a2705535e32377aa86db27d8d6ebd9b47333070850b9c5980`
- [source-identities.json](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/source-identities.json) (724 B)
  `409cf4931c19dc97049f943eb005165a59f88d1842a3dd2596eaa1dabcfbd458`
- [own-observation.json](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/own-observation.json) (9009 B)
  `48cd32150a8eff02b4c3e870cb82f8df008d90c2b5aed3f9bca743506852a5e1`
- [analyze.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/analyze.py) (2750 B)
  `5baed281117d3980af0f0e19e428c4163f79a97a84d1db71955ecbee7993ac3a`
- [conditional\_fit.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/conditional_fit.py) (3618 B)
  `37700d28edc852a10d09f949a7270a652b61f1c9d30cc2d8d0e99d4347e92d70`
- [fetch\_named\_source.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/fetch_named_source.py) (5014 B)
  `240b29a1c6e929895b5ead31269b495b1223be0615da62c703e2962028e6de5a`
- [generate\_observation.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/generate_observation.py) (849 B)
  `c5442424f9cecdcd81022ec31cae0b81bdc21400f06eea6dde4108c7062de743`
- [observation\_record.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/observation_record.py) (2693 B)
  `2d791013b5c8b289e6bfdc5ba9ed408e18b9fc4334d44ba32091546ed297e752`
- [physical\_units.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/physical_units.py) (255 B)
  `2db6448e5f4e657ef48ffd9d2a58c7487c6f7655a8033dc001327fe43192507c`
- [plot.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/plot.py) (5734 B)
  `c30e8e160f1f73f6451ebb903402a316ecba858814e6b1d5059ad31926abb4e1`
- [rotation\_model.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/rotation_model.py) (2003 B)
  `97c2c5ca56373f20996b26ef179267549ad3a341817b29666ce368029cfa96a1`
- [save\_native.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/save_native.py) (655 B)
  `bbf0881e28cf1dd4d13105af656cc4072e3a42a62a5f7013b9f48075b764c082`
- [spherical\_halos.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/spherical_halos.py) (1459 B)
  `4678e06bf172167f3a8a3a5d199447658eb51860507405b80110807165c5ef83`
- [synthetic\_record.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/synthetic_record.py) (1538 B)
  `6285cda07f2b9324806d25ecc89712eb6b94479e3caaa1f24a88c004d449a063`
- [thin\_disk\_reference.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/thin_disk_reference.py) (1298 B)
  `67ba0512e47c10d10c5b8d17f435d808812eec25f0edce296b6c80eaf72e0bc0`
- [validate.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/validate.py) (5709 B)
  `7cfbf116c5e51e9a3e1aa0ecd113a24f7a069706336ae01221de67dbf331cb6d`
- [verify\_analytic\_limits.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/verify_analytic_limits.py) (3073 B)
  `68bca45f2d3d8a1f8cda804bb45c3e165025effa0221c2f45d7c1416009bae6a`
- [verify\_nuisance\_mapping.py](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/verify_nuisance_mapping.py) (4184 B)
  `04b8587eb21a257b809fe930c33d43ddb2e008e4ebf2026134ee0a1d35b4015e`
- [SHA256SUMS.json](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/SHA256SUMS.json) (3306 B)
  `2fb52c937f2026d872f053e17343e55595fb3568862c20573d59123f3d897b49`
- [galaxy-rotation-science-v1.zip](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/galaxy-rotation-science-v1.zip) (29081 B)
  `f389268ed651be3a66ad7d87ee6760ebe39d87f6bd2498b2fb8904c9f10893a3`

[Previous: When distance and inclination move a whole curve](https://sandboxphysics.com/stories/galaxy-rotation-curves/moving-a-whole-rotation-curve/) [Next: Reading feedback, halo and MOND comparisons](https://sandboxphysics.com/stories/galaxy-rotation-curves/reading-feedback-and-gravity-comparisons/)

## Sources and calculation scope

1. [Lelli, McGaugh & Schombert (2016), SPARC, AJ152,157](https://astroweb.cwru.edu/SPARC/)

   Official fixed Table1 and Rotmod\_LTG.zip; named NGC2403 member only. The master paper sections3.1–3.3/5.1–5.3 and original Table2 notes establish signed gas, unit stellar mass-to-light, finite thickness and error meaning. Fresh source acquisition is separate from cached replay. Original rows and measured-derived plots stay local.

2. [Independent saved-observation rotation-curve calculation](https://sandboxphysics.com/stories/galaxy-rotation-curves/materials/galaxy-rotation-science-v1.zip)

   Own MIT Python produces synthetic observations, actual six-start fits, profiles, commonD/i scenarios, positive-density/ring-potential/Jacobian references and SVGs. Optional exact named-source acquisition and73-row conversion/fits were actually run. Default is offline; ZIP contains no App Engine, original SPARC rows, paper or measured-derived graph.
