A collection of Python tools for the physical characterization of young stellar clusters from Gaia-like astrometry and photometry: structural (radial density) profiles, velocity distributions and expansion kinematics, isochrone-based age/extinction fitting, CMD-based mass estimation, and combined physical/dynamical parameters (size, virial mass, virial state).
The name is a nod to a workshop where raw astrometric and photometric measurements get shaped, cluster by cluster, into finished physical parameters.
This is not a pip-installable package. Clone (or download) the whole
repository and run scripts from its root directory — the code relies on the
input/, output/, and Figs/ folders being where it expects them, next
to packages/.
Mass completeness / missing-member estimation (building a mock catalog of observed + statistically-inferred-missing stars from a mass function fit) is a different scope of problem and lives in its own repository: ClusterForge-MassCompleteness (link to be added).
That tool's output — a completeness-corrected total cluster mass and its
uncertainty — feeds back into this repo's physical_params.py, which can
recompute the cluster's dynamical/virial parameters using either the mass it
estimates directly from the observed CMD, or an externally supplied
(mass, mass_error) from the other tool:
# Mass estimated from the observed CMD alone:
cluster_main.get_cluster_parameters(my_cluster, iso_path, king, eff, vel, mass='None')
# Or, after running the mass-completeness tool separately:
cluster_main.get_cluster_parameters(my_cluster, iso_path, king, eff, vel, mass=(total_mass, total_mass_error))example.py Worked example / tutorial script - start here
make_mock_catalog.py Generates a small synthetic cluster catalog for example.py
requirements.txt
input/
mock_cluster.csv Synthetic catalog (generated, see below)
EFF_data.csv Lookup table used by the EFF density-profile fit
King_Model_Table.txt Lookup table used by physical_params.py (King W0)
isochrones/
iso_test/ Small bundled PARSEC isochrone grid (see below)
output/ CSV outputs land here (posteriors, mass tables, ...)
Figs/ Plots land here
packages/ All the characterization code
cluster_main.py Thin orchestrator used by example.py
general_tools.py Shared small utilities
density_profiles.py King (1966) + EFF (1987) radial density profile fits
velocity_analysis.py Gaussian fits to RV, V_RA, V_DEC
kinematics.py Galactic XYZ/UVW, expansion pattern, kinematic age
estimate_age_isochrones.py Isochrone grid search for age + A_V
cmd_mass_analysis.py CMD overlay + per-star mass from the observed sample
mass_estimation.py Low-level color/magnitude -> mass routines
physical_params.py Combines the above into size/mass/virial parameters
iso_query/
isochrone_grid_parsec.py Build your own PARSEC isochrone grid (needs `ezpadova`)
git clone <this-repo>
cd ClusterForge
pip install -r requirements.txtpython make_mock_catalog.py # writes input/mock_cluster.csv
python example.py # runs the full pipeline on itexample.py is meant to be read top to bottom (it's split into #%% cells
if you're using an editor that supports them) — each section calls one
stage of the pipeline and briefly explains what it needs.
Point data_path in example.py at your own catalog and update the column
names passed to cluster_main.cluster_data(...) to match your file. At
minimum you need: RA, DEC, proper motions + errors, parallax + error, radial
velocity + error, and G/BP/RP magnitudes + errors (Gaia-style photometry).
A small PARSEC grid (input/isochrones/iso_test/) is bundled so the example
runs out of the box. For real science, build your own grid with:
from packages.iso_query.isochrone_grid_parsec import build_iso_grid
build_iso_grid(age_range=(1e6, 30e6, 0.5e6), M_range=(0.0, 0.0, 0.02),
av_range=(0.0, 1.0, 0.05), folder_out='my_cluster_grid')This downloads isochrones via ezpadova and splits them into one CSV per
(age, A_V) into input/isochrones/my_cluster_grid/, ready for
estimate_cluster_age(..., iso_folder='my_cluster_grid'). Isochrone grids
get large fast (thousands of files) — keep M_range to a single value
(fixed metallicity) unless you've addressed the limitation below.
- Isochrone grids are keyed only by
(log_age, A_V).iso_test(used for the bundled example) actually varies metallicity too, and the current loader silently keeps only one metallicity's file per(log_age, A_V)pair — whichever the filesystem happens to list last. Foriso_testspecifically this discards about 80% of the files. This is harmless for the demo (the fit still runs and returns some consistent isochrone), but don't rely oniso_test— or any other grid with more than one metallicity — for a real metallicity-sensitive fit until this is addressed. Fixing it properly means adding metallicity as a third grid dimension throughestimate_age_isochrones.py(load_isochrones,grid_search,calculate_age_errors/calculate_extinction_error,plot_cmd,select_uncertainty_isochrones) — worth its own pass rather than a quick patch. - Several
input/outputpaths (e.g.input/EFF_data.csv,input/King_Model_Table.txt) are relative and assume scripts run from the repository root.
If you use ClusterForge in your research, please cite:
Sánchez-Sanjuán, S., Pérez-Villegas, Á., Hernández, J., & Aguilar, L. 2026, Dynamical evolution and dissolution time-scale of young stellar clusters in the Orion star-forming complex, Monthly Notices of the Royal Astronomical Society, 549, 4, stag1098, https://doi.org/10.1093/mnras/stag1098
@article{SanchezSanjuan2026,
author = {S{\'a}nchez-Sanju{\'a}n, Sergio and P{\'e}rez-Villegas, {\'A}ngeles and Hern{\'a}ndez, Jes{\'u}s and Aguilar, Luis},
title = {Dynamical evolution and dissolution time-scale of young stellar clusters in the Orion star-forming complex},
journal = {Monthly Notices of the Royal Astronomical Society},
year = {2026},
volume = {549},
number = {4},
pages = {stag1098},
doi = {10.1093/mnras/stag1098}
}