Tutorial 0: Data Setup and Environment Verification

brutus relies on pre-computed stellar evolutionary tracks, isochrones, bolometric-correction neural networks, model grids, photometric offsets, and 3D dust maps hosted on Harvard Dataverse and cached locally with Pooch.

Topics Covered

  1. Environment check: Python version, brutus installation, and scientific dependencies

  2. Filter system: photometric filter groups defined in brutus.data.filters

  3. Data download functions: fetching model grids, tracks, isochrones, and dust maps

  4. Loading models and offsets: array shapes and dtypes returned by load_models and load_offsets

  5. Data status check: local availability of data files for Tutorials 0 through 11

Running Without Downloaded Data

Every cell in this notebook runs without pre-downloaded data files; cells that inspect data files check availability first and print the expected array layout when a file is absent.

# Setup: imports and plot style
import sys
import warnings
from pathlib import Path

import numpy as np

warnings.filterwarnings("ignore")

# Tutorial utilities
from tutorial_utils import (
    setup_tutorial,
    find_brutus_data_file,
    check_data_requirements,
    missing_note,
)

# Run the standardized tutorial bootstrap
info = setup_tutorial(
    0, title="Tutorial 00: Data Setup and Environment Verification", verbose=True
)
Checking data requirements for Tutorial 0
=========================================
  Found: grid_mist_v9.h5 (optional)
  Missing: grid_bayestar_v5.h5 (optional)
  Found: offsets_mist_v9.txt (optional)

  All required files available

1. Environment Check

Let’s verify that Python, brutus, and the key scientific dependencies are installed and importable.

import brutus

print(f"Python version : {sys.version.split()[0]}")
print(f"brutus version : {brutus.__version__}")
print()
print("Key Dependencies:")
deps = [
    "numpy", "scipy", "matplotlib", "h5py",
    "numba", "pooch", "astropy", "healpy", "tqdm",
]
for dep in deps:
    try:
        mod = __import__(dep)
        version = getattr(mod, "__version__", "unknown")
        print(f"  {dep:12s} : {version}")
    except ImportError:
        print(f"  {dep:12s} : NOT INSTALLED")
Python version : 3.13.16
brutus version : 2.0.0

Key Dependencies:
  numpy        : 2.5.3
  scipy        : 1.18.1
  matplotlib   : 3.11.2
  h5py         : 3.16.0
  numba        : 0.68.0
  pooch        : v1.9.0
  astropy      : 8.0.2
  healpy       : 1.20.1
  tqdm         : 4.70.1

2. Filter System

brutus supports a wide range of photometric filter systems. The full list of available filters is defined in brutus.data.filters. Filters are organized into groups by survey or instrument.

The MIST grids support all filters below, while the Bayestar grids only support Pan-STARRS grizy and 2MASS JHKs.

from brutus.data.filters import FILTERS
from brutus.data import filters

filter_groups = {
    "Gaia (DR3)": filters.gaia,
    "SDSS": filters.sdss,
    "Pan-STARRS": filters.ps,
    "DECam": filters.decam,
    "Bessell": filters.bessell,
    "2MASS": filters.tmass,
    "VISTA": filters.vista,
    "UKIDSS": filters.ukidss,
    "WISE": filters.wise,
    "Tycho": filters.tycho,
    "Hipparcos": filters.hipp,
    "Kepler": filters.kepler,
    "TESS": filters.tess,
}

print(f"{'Group':<16s} {'Count':>5s}   Filters")
print(f"{'-'*16} {'-'*5}   {'-'*50}")
total = 0
for group_name, group_filters in filter_groups.items():
    n = len(group_filters)
    total += n
    filt_str = ", ".join(group_filters)
    print(f"{group_name:<16s} {n:>5d}   {filt_str}")

print(f"{'-'*16} {'-'*5}")
print(f"{'TOTAL':<16s} {total:>5d}   (len(FILTERS) = {len(FILTERS)})")
Group            Count   Filters
---------------- -----   --------------------------------------------------
Gaia (DR3)           3   Gaia_G_MAW, Gaia_BP_MAWf, Gaia_RP_MAW
SDSS                 5   SDSS_u, SDSS_g, SDSS_r, SDSS_i, SDSS_z
Pan-STARRS           7   PS_g, PS_r, PS_i, PS_z, PS_y, PS_w, PS_open
DECam                6   DECam_u, DECam_g, DECam_r, DECam_i, DECam_z, DECam_Y
Bessell              5   Bessell_U, Bessell_B, Bessell_V, Bessell_R, Bessell_I
2MASS                3   2MASS_J, 2MASS_H, 2MASS_Ks
VISTA                5   VISTA_Z, VISTA_Y, VISTA_J, VISTA_H, VISTA_Ks
UKIDSS               5   UKIDSS_Z, UKIDSS_Y, UKIDSS_J, UKIDSS_H, UKIDSS_K
WISE                 4   WISE_W1, WISE_W2, WISE_W3, WISE_W4
Tycho                2   Tycho_B, Tycho_V
Hipparcos            1   Hipparcos_Hp
Kepler               2   Kepler_D51, Kepler_Kp
TESS                 1   TESS
---------------- -----
TOTAL               49   (len(FILTERS) = 49)

3. Data Download Functions

brutus data files are hosted on Harvard Dataverse and managed locally via Pooch. Each fetch_* function downloads a specific category of data into the Pooch cache (~/.cache/astro-brutus/) and links it into a target directory (defaulting to the current working directory). Files are verified by SHA256 hash and only downloaded once.

The available download functions are:

Function

What it downloads

Typical file

fetch_nns()

Neural network for bolometric corrections

nn_c3k.h5

fetch_tracks()

EEP evolutionary tracks

MIST_1.2_EEPtrk.h5

fetch_isos()

Isochrone tables

MIST_1.2_iso_vvcrit0.0.h5

fetch_grids()

Pre-computed model grids for fitting

grid_mist_v9.h5

fetch_offsets()

Photometric offset calibrations

offsets_mist_v9.txt

fetch_dustmaps()

3D dust extinction maps

bayestar2019_v1.h5

import inspect
from brutus.data import (
    fetch_nns,
    fetch_tracks,
    fetch_isos,
    fetch_grids,
    fetch_offsets,
    fetch_dustmaps,
)

fetch_funcs = [
    ("fetch_nns", fetch_nns),
    ("fetch_tracks", fetch_tracks),
    ("fetch_isos", fetch_isos),
    ("fetch_grids", fetch_grids),
    ("fetch_offsets", fetch_offsets),
    ("fetch_dustmaps", fetch_dustmaps),
]

for name, func in fetch_funcs:
    sig = inspect.signature(func)
    print(f"brutus.data.{name}{sig}")

# Uncomment the lines below to download data files (cached via Pooch):
# fetch_nns()
# fetch_tracks()
# fetch_isos()
# fetch_grids()
# fetch_offsets()
# fetch_dustmaps()
brutus.data.fetch_nns(model='c3k', *, target_dir='.')
brutus.data.fetch_tracks(track='MIST_1.2_vvcrit0.0', *, target_dir='.')
brutus.data.fetch_isos(iso='MIST_1.2_vvcrit0.0', *, target_dir='.')
brutus.data.fetch_grids(grid='mist_v9', *, target_dir='.')
brutus.data.fetch_offsets(grid='mist_v9', *, target_dir='.')
brutus.data.fetch_dustmaps(dustmap='bayestar19', *, target_dir='.')

4. Loading Models

Pre-computed stellar model grids are loaded with load_models(), which returns (models, labels, label_mask):

  • models: float array of shape (Nmodel, Nfilt, 3) containing the photometric polynomial coefficients (unreddened magnitude at 1 kpc, reddening vector at \(R_V = 0\), and \(R_V\) derivative) for each model and filter.

  • labels: structured array of shape (Nmodel,) containing the stellar parameters for each model (mini, feh, eep, smf, afe, loga, logl, logt, logg, agewt).

  • label_mask: structured boolean array of shape (1,) flagging grid input parameters (True) versus derived predictions (False).

from brutus.data import load_models

grid_file = None
for fname in ["grid_mist_v9.h5", "grid_bayestar_v5.h5"]:
    try:
        grid_file = find_brutus_data_file(fname)
        print(f"Found grid file: {fname}")
        break
    except FileNotFoundError:
        continue

if grid_file is not None:
    demo_filters = ["PS_g", "PS_r", "PS_i", "2MASS_J", "2MASS_Ks"]
    models, labels, label_mask = load_models(
        grid_file, filters=demo_filters, verbose=False
    )

    grid_inputs = [n for n in label_mask.dtype.names if label_mask[0][n]]
    derived_params = [n for n in label_mask.dtype.names if not label_mask[0][n]]
    print(f"  models shape   : {models.shape}  (Nmodel, Nfilt, Ncoef)")
    print(f"  grid inputs    : {grid_inputs}")
    print(f"  derived labels : {derived_params}")
    print()
    print(f"  {'Label':8s}  {'Min':>10s}  {'Max':>14s}")
    print(f"  {'-'*8}  {'-'*10}  {'-'*14}")
    for name in labels.dtype.names:
        vals = labels[name]
        finite = vals[np.isfinite(vals)]
        if len(finite) > 0:
            print(f"  {name:8s}  {finite.min():10.4f}  {finite.max():14.4f}")
Found grid file: grid_mist_v9.h5
  models shape   : (613530, 5, 3)  (Nmodel, Nfilt, Ncoef)
  grid inputs    : ['mini', 'feh', 'eep', 'smf', 'afe']
  derived labels : ['loga', 'logl', 'logt', 'logg', 'agewt']

  Label            Min             Max
  --------  ----------  --------------
  mini          0.5000          2.0000
  feh          -3.0000          0.4500
  eep         202.0000        808.0000
  smf           0.0000          0.0000
  afe           0.0000          0.0000
  loga          6.4619         10.1400
  logl         -1.4623          3.4528
  logt          3.4458          4.1537
  logg         -0.2019          4.9059
  agewt         0.0096  514949633.5270

5. Loading Photometric Offsets

Photometric offsets correct for systematic differences between observed and synthetic photometry. They are loaded with load_offsets(), which returns a 1D array of multiplicative flux corrections (values close to 1.0).

from brutus.data import load_offsets

offset_file = None
for fname in ["offsets_mist_v9.txt", "offsets_bs_v9.txt"]:
    try:
        offset_file = find_brutus_data_file(fname)
        print(f"Found offset file: {fname}")
        break
    except FileNotFoundError:
        continue

if offset_file is not None:
    demo_filters = ["PS_g", "PS_r", "PS_i", "PS_z", "PS_y",
                    "2MASS_J", "2MASS_H", "2MASS_Ks"]
    offsets = load_offsets(offset_file, filters=demo_filters, verbose=False)

    print(f"  {'Filter':<12s} {'Offset':>8s} {'Correction':>12s}")
    print(f"  {'-'*12} {'-'*8} {'-'*12}")
    for filt, off in zip(demo_filters, offsets):
        pct = 100.0 * (off - 1.0)
        print(f"  {filt:<12s} {off:8.5f} {pct:+11.2f}%")
Found offset file: offsets_mist_v9.txt
  Filter         Offset   Correction
  ------------ -------- ------------
  PS_g          1.00000       +0.00%
  PS_r          0.95000       -5.00%
  PS_i          0.96000       -4.00%
  PS_z          0.94000       -6.00%
  PS_y          0.95000       -5.00%
  2MASS_J       0.98000       -2.00%
  2MASS_H       1.04000       +4.00%
  2MASS_Ks      1.03000       +3.00%

6. Data Status Check

The following cell checks which data files are available locally for each tutorial in the series. This helps you plan which files to download depending on which tutorials you want to run.

tutorial_titles = {
    0: "Data Setup",
    1: "Individual Stars",
    2: "Populations",
    3: "Grids & Performance",
    4: "Galactic Priors",
    5: "Fitting Individual",
    6: "Cluster Analysis",
    7: "Dust Mapping",
    8: "Photometric Calibration",
    9: "Utilities",
    10: "Plotting",
    11: "Results",
}

all_ok = True
for tut_num in range(12):
    ok, missing = check_data_requirements(tut_num, verbose=False)
    title = tutorial_titles.get(tut_num, "")
    status = "READY" if ok else f"MISSING {len(missing)} file(s)"
    marker = "[+]" if ok else "[-]"
    print(f"  {marker} Tutorial {tut_num:2d}: {title:<28s} {status}")
    if not ok:
        all_ok = False
        for f in missing:
            print(f"        -> {f}{missing_note(f)}")
  [+] Tutorial  0: Data Setup                   READY
  [+] Tutorial  1: Individual Stars             READY
  [+] Tutorial  2: Populations                  READY
  [+] Tutorial  3: Grids & Performance          READY
  [+] Tutorial  4: Galactic Priors              READY
  [+] Tutorial  5: Fitting Individual           READY
  [+] Tutorial  6: Cluster Analysis             READY
  [+] Tutorial  7: Dust Mapping                 READY
  [+] Tutorial  8: Photometric Calibration      READY
  [+] Tutorial  9: Utilities                    READY
  [+] Tutorial 10: Plotting                     READY
  [+] Tutorial 11: Results                      READY

Summary

  1. Environment verification: confirmed the Python version, brutus installation, and core scientific dependencies (numpy, scipy, numba, h5py, pooch, astropy, healpy, tqdm).

  2. Filter system: brutus.data.filters defines 49 filters across 13 survey and instrument groups (Gaia, Pan-STARRS, SDSS, DECam, Bessell, 2MASS, VISTA, UKIDSS, WISE, Tycho, Hipparcos, Kepler, and TESS). MIST grids include all 49 filters, whereas Bayestar grids cover Pan-STARRS grizy and 2MASS JHKs.

  3. Data download functions: fetch_nns, fetch_tracks, fetch_isos, fetch_grids, fetch_offsets, and fetch_dustmaps retrieve data files from Harvard Dataverse via Pooch with SHA256 verification.

  4. Loading models: load_models() returns the (Nmodel, Nfilt, 3) photometric coefficient array alongside the structured labels and label_mask arrays.

  5. Loading offsets: load_offsets() returns multiplicative flux corrections (Nfilt,) for calibrating systematic model-data offsets.

Next Steps

  • Tutorial 1: Individual star models with EEPTracks and StarEvolTrack

  • Tutorial 2: Stellar population models with Isochrone and StellarPop

  • Tutorial 3: Grid generation and performance optimization

  • Tutorial 4: Galactic priors (IMF, distance, extinction, metallicity)

  • Tutorial 5: Fitting individual sources with BruteForce

  • Tutorial 6: Cluster analysis with isochrone fitting

  • Tutorial 7: 3D dust mapping

  • Tutorial 8: Photometric calibration and offsets

  • Tutorial 9: Utility functions (photometry, likelihoods, sampling)

  • Tutorial 10: Plotting and visualization

  • Tutorial 11: Loading and interpreting BruteForce results