02 - Configuration and Coordinates

Purpose

This tutorial explains two key components within SMPy:

  1. Configuration controls reproducibility.

  2. Coordinate system choice controls physical interpretation.

The notebook is intentionally short and conceptual.

1) Configuration Anatomy

Think of the config as a scientific contract:

  • general: data source, coordinate system, output behavior

  • methods: algorithm-specific parameters

  • plotting: map rendering choices

  • snr: null-realization controls

Every single tunable parameter in the model is defined in the configuration yaml file.

[1]:
from pathlib import Path
import random
import numpy as np
import pandas as pd
import yaml

import smpy
from IPython.display import Image, display
from smpy.config import Config
from smpy.run import run

SEED = 42
random.seed(SEED)
np.random.seed(SEED)


def find_repo_root(start: Path) -> Path:
    for candidate in [start, *start.parents]:
        if (candidate / "setup.py").exists() and (candidate / "smpy").is_dir():
            return candidate
    raise RuntimeError("Could not locate SMPy repository root.")


repo_root = find_repo_root(Path.cwd().resolve())
data_file = repo_root / "examples" / "data" / "forecast_lum_annular.fits"
artifacts_dir = repo_root / "examples" / "outputs" / "tutorials"
artifacts_dir.mkdir(parents=True, exist_ok=True)

print(f"Python package version: {smpy.__version__}")
print(f"Random seed: {SEED}")
print(f"Data file: {data_file}")
print(f"Artifacts: {artifacts_dir}")
Python package version: 0.5.0
Random seed: 42
Data file: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/data/forecast_lum_annular.fits
Artifacts: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials
[2]:
config = Config.from_defaults("kaiser_squires")
config.update_from_kwargs(
    data=str(data_file),
    coord_system="radec",
    pixel_scale=0.4,
    g1_col="g1_Rinv",
    g2_col="g2_Rinv",
    weight_col="weight",
    mode=["E"],
    save_plots=False,
    save_fits=False,
    output_dir=str(artifacts_dir),
    output_base_name="tutorial02_minimal",
)
config.validate()

cfg = config.to_dict()
print("Validated config sections:", list(cfg.keys()))
print("Coordinate system:", cfg["general"]["coordinate_system"])
print("Method:", cfg["general"]["method"])

# Print the config in a readable format
print("\nConfiguration:")
for section, options in cfg.items():
    print(f"\n{section.capitalize()}:")
    for key, value in options.items():
        print(f"  {key}: {value}")

Validated config sections: ['general', 'methods', 'plotting', 'snr']
Coordinate system: radec
Method: kaiser_squires

Configuration:

General:
  input_path: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/data/forecast_lum_annular.fits
  input_hdu: 1
  output_directory: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials
  output_base_name: tutorial02_minimal
  coordinate_system: radec
  radec: {'resolution': 0.4, 'coord1': 'ra', 'coord2': 'dec'}
  pixel: {'downsample_factor': 1, 'coord1': 'X_IMAGE', 'coord2': 'Y_IMAGE', 'pixel_axis_reference': 'catalog'}
  g1_col: g1_Rinv
  g2_col: g2_Rinv
  weight_col: weight
  method: kaiser_squires
  create_snr: False
  create_counts_map: False
  overlay_counts_map: False
  mode: ['E']
  print_timing: False
  save_fits: False
  save_plots: False
  _coord_system_set_by_user: True
  _pixel_scale_set_by_user: True

Methods:
  kaiser_squires: {'smoothing': {'type': 'gaussian', 'sigma': 2.0}}
  aperture_mass: {'filter': {'type': 'schirmer', 'scale': 60, 'truncation': 1.0, 'l': 3}}
  ks_plus: {'inpainting_iterations': 100, 'reduced_shear_iterations': 3, 'nscales': None, 'extension_size': 'double', 'use_wavelet_constraints': True, 'constrain_B': False, 'threshold_schedule': 'exp', 'threshold_tau': None, 'smoothing': {'type': 'gaussian', 'sigma': 2.0}}

Plotting:
  figsize: [12, 8]
  fontsize: 15
  cmap: viridis
  xlabel: auto
  ylabel: auto
  plot_title: Mass Map
  gridlines: True
  vmax: None
  vmin: None
  threshold: None
  verbose: None
  cluster_center: None
  xray_contours: {'ctr_file': None, 'show_on_convergence': False, 'show_on_snr': False, 'color': 'cyan', 'linewidth': 0.8, 'alpha': 0.7}
  scaling: {'type': 'linear', 'gamma': 2, 'percentile': None, 'convergence': {'linthresh': 0.1, 'linscale': 1.0}, 'snr': {'linthresh': 5, 'linscale': 0.5}}

Snr:
  shuffle_type: spatial
  num_shuffles: 100
  seed: 0
  smoothing: {'type': 'gaussian', 'sigma': 2.0}
  plot_title: Signal-to-Noise Map

2) Reproducibility Essentials

Good baseline practice:

  • Save the exact config used for a run.

  • Keep output names stable and explicit.

  • Log package versions and seed.

[3]:
minimal_config_path = artifacts_dir / "config_minimal.yaml"
config.save_config(minimal_config_path)

reloaded = Config.from_file(minimal_config_path)
reloaded.validate()

print(f"Saved and reloaded: {minimal_config_path}")
print("Reloaded coordinate system:", reloaded.to_dict()["general"]["coordinate_system"])
Configuration saved to: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/config_minimal.yaml
Saved and reloaded: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/config_minimal.yaml
Reloaded coordinate system: radec

3) Coordinate Systems: How to Choose

Use this quick rule:

Data columns look like

Use

Main scale knob

ra, dec in sky coordinates

radec

pixel_scale (arcmin/pixel)

detector/image coordinates (X_*, Y_*)

pixel

downsample_factor

Important distinction:

  • pixel_scale sets angular map resolution in sky coordinates.

  • downsample_factor coarsens a native pixel-coordinate grid.

[4]:
def make_base_config(output_base_name: str) -> Config:
    c = Config.from_defaults("kaiser_squires")
    c.update_from_kwargs(
        data=str(data_file),
        g1_col="g1_Rinv",
        g2_col="g2_Rinv",
        weight_col="weight",
        mode=["E"],
        save_plots=True,
        save_fits=False,
        output_dir=str(artifacts_dir),
        output_base_name=output_base_name,
    )
    return c


# RA/Dec configuration
radec_config = make_base_config("tutorial02_radec")
radec_config.update_from_kwargs(coord_system="radec", pixel_scale=0.4)
radec_config_path = artifacts_dir / "config_radec.yaml"
radec_config.save_config(radec_config_path)
radec_result = run(radec_config)

# Pixel configuration (explicitly map coordinate column names)
pixel_config = make_base_config("tutorial02_pixel")
pixel_config.update_from_kwargs(coord_system="pixel", downsample_factor=170)
pixel_dict = pixel_config.to_dict()
pixel_dict["general"]["pixel"]["coord1"] = "X_IMAGE_se"
pixel_dict["general"]["pixel"]["coord2"] = "Y_IMAGE_se"
pixel_config = Config(pixel_dict)
pixel_config.validate()
pixel_config_path = artifacts_dir / "config_pixel.yaml"
pixel_config.save_config(pixel_config_path)
pixel_result = run(pixel_config)

print("RA/Dec map shape:", radec_result["maps"]["E"].shape)
print("Pixel map shape:", pixel_result["maps"]["E"].shape)
Configuration saved to: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/config_radec.yaml
Convergence map saved as PNG file: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/kaiser_squires/tutorial02_radec_kaiser_squires_e_mode.png
Configuration saved to: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/config_pixel.yaml
Convergence map saved as PNG file: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/kaiser_squires/tutorial02_pixel_kaiser_squires_e_mode.png
RA/Dec map shape: (38, 57)
Pixel map shape: (38, 57)
[5]:
rows = []
for label, res, cfg in [
    ("radec", radec_result, radec_config.to_dict()),
    ("pixel", pixel_result, pixel_config.to_dict()),
]:
    e = res["maps"]["E"]
    if label == "radec":
        scale_value = cfg["general"]["radec"]["resolution"]
        scale_label = "pixel_scale_arcmin"
    else:
        scale_value = cfg["general"]["pixel"]["downsample_factor"]
        scale_label = "downsample_factor"

    rows.append(
        {
            "coordinate_system": label,
            "map_shape": f"{e.shape[0]}x{e.shape[1]}",
            scale_label: scale_value,
            "mean_E": float(np.nanmean(e)),
            "std_E": float(np.nanstd(e)),
            "min_E": float(np.nanmin(e)),
            "max_E": float(np.nanmax(e)),
        }
    )

summary = pd.DataFrame(rows)
summary_path = artifacts_dir / "geometry_comparison.csv"
summary.to_csv(summary_path, index=False)
summary
[5]:
coordinate_system map_shape pixel_scale_arcmin mean_E std_E min_E max_E downsample_factor
0 radec 38x57 0.4 2.050273e-19 0.042876 -0.104254 0.165443 NaN
1 pixel 38x57 NaN -6.150820e-19 0.043971 -0.108428 0.165440 170.0
[6]:
radec_png = (
    artifacts_dir
    / "kaiser_squires"
    / f"{radec_config.to_dict()['general']['output_base_name']}_kaiser_squires_e_mode.png"
)
pixel_png = (
    artifacts_dir
    / "kaiser_squires"
    / f"{pixel_config.to_dict()['general']['output_base_name']}_kaiser_squires_e_mode.png"
)

print("SMPy plotter output files:")
print(f"- RA/Dec map: {radec_png}")
print(f"- Pixel map: {pixel_png}")
print(f"- Summary table: {summary_path}")

if radec_png.exists() and pixel_png.exists():
    display(Image(filename=str(radec_png)))
    display(Image(filename=str(pixel_png)))
else:
    print("One or more expected PNG files were not found. Re-run the map cell above.")
SMPy plotter output files:
- RA/Dec map: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/kaiser_squires/tutorial02_radec_kaiser_squires_e_mode.png
- Pixel map: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/kaiser_squires/tutorial02_pixel_kaiser_squires_e_mode.png
- Summary table: /home/docs/checkouts/readthedocs.org/user_builds/smpy-docs/checkouts/latest/examples/outputs/tutorials/geometry_comparison.csv
../_images/tutorials_02_configuration_9_1.png
../_images/tutorials_02_configuration_9_2.png

4) Errors and Misconfigurations

When configuration and coordinate parameters disagree, SMPy fails fast. This is intentional!

[7]:
bad_config = Config.from_defaults("kaiser_squires")
bad_config.update_from_kwargs(
    data=str(data_file),
    coord_system="radec",
    g1_col="g1_Rinv",
    g2_col="g2_Rinv",
    weight_col="weight",
)

try:
    bad_config.validate()
except ValueError as exc:
    print("Expected validation error:")
    print(exc)
Expected validation error:
Missing required parameter for coordinate_system='radec'. Provide 'pixel_scale' (API: pixel_scale=..., YAML: general.radec.resolution).

5) Practical Checklist

Before running:

  1. Confirm your coordinate columns match your chosen coordinate system (and data/file format).

  2. For radec, set pixel_scale in arcmin/pixel.

  3. For pixel, set downsample_factor and pixel coordinate column names.

  4. Save your config with the run outputs.