{ "cells": [ { "cell_type": "markdown", "id": "4453f447", "metadata": {}, "source": [ "# 01 - SMPy Quickstart\n", "\n", "## Learning Objectives\n", "By the end of this tutorial, you will understand SMPy's three API styles (aside from CLI runner):\n", "1. **High-level API**: One-line functions for quick results\n", "2. **Config API**: YAML/dictionary-based for reproducible workflows \n", "3. **Class API**: Direct mapper access for maximum control\n", "\n", "## Prerequisites\n", "- SMPy installed (`pip install .` from the repository root)\n", "\n", "## API Overview\n", "\n", "SMPy provides three levels of API access to match different use cases:\n", "\n", "1. **High-level functional API (Quick)** — Simple one-line functions like `map_mass()` and method-specific variants (`map_kaiser_squires()`, `map_aperture_mass()`, `map_ks_plus()`) that take your data path and parameters directly. These functions handle everything internally: loading data, creating grids, running the mass mapping, and saving outputs. Perfect for quick analysis and exploration.\n", "\n", "2. **Configuration-based API (Power Users)** — A more structured approach using `Config` objects and the `run()` function, which separates all analysis parameters from your code. This enables reproducible workflows through YAML files and is ideal for production pipelines where you need to track exactly what parameters produced which results.\n", "\n", "3. **Class-based API (Prototyping/Testing)** — Direct access to the coordinate systems, grid creation, and mapper classes for users who need fine-grained control. This low-level API is available when you need to customize the data flow, integrate with existing pipelines, or extend SMPy's functionality. Not necessary for most users.\n", "\n", "The full configuration based API is recommended for scientific use to ensure control over full analysis pipeline. " ] }, { "cell_type": "markdown", "id": "560813a3", "metadata": {}, "source": [ "## Quick Reference\n", "\n", "```python\n", "# Simple API (uses defaults)\n", "from smpy import map_mass\n", "result = map_mass(data='catalog.fits', method='kaiser_squires', \n", " coord_system='radec', pixel_scale=0.4)\n", "\n", "# Config API (for power users, full configuration)\n", "from smpy.config import Config\n", "from smpy.run import run\n", "config = Config.from_defaults('kaiser_squires')\n", "config.update_from_kwargs(data='catalog.fits', pixel_scale=0.4)\n", "result = run(config)\n", "\n", "# Class API (prototyping/testing)\n", "from smpy.mapping_methods import KaiserSquiresMapper\n", "mapper = KaiserSquiresMapper(config_dict)\n", "kappa_e, kappa_b = mapper.create_maps(g1_grid, g2_grid)\n", "```\n" ] }, { "cell_type": "code", "execution_count": null, "id": "643e131c", "metadata": {}, "outputs": [], "source": [ "# Cell 2: Environment setup\n", "import sys\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from pathlib import Path\n", "\n", "# SMPy imports - we'll use all API levels\n", "import smpy\n", "from smpy import map_mass, map_kaiser_squires, map_aperture_mass, map_ks_plus\n", "from smpy.config import Config\n", "from smpy.run import run\n", "\n", "# Set deterministic seed\n", "SEED = 42\n", "np.random.seed(SEED)\n", "\n", "print(f\"Python: {sys.version.split()[0]}\")\n", "print(f\"SMPy: {smpy.__version__}\")\n", "print(f\"Random seed: {SEED}\")\n", "\n", "def _find_repo_root(start: Path) -> Path:\n", " \"\"\"Find the SMPy repository root by walking upward from a starting directory.\"\"\"\n", " for candidate in [start, *start.parents]:\n", " if (candidate / 'setup.py').exists() and (candidate / 'smpy').is_dir():\n", " return candidate\n", " raise RuntimeError('Could not find SMPy repository root (expected setup.py and smpy/).')\n", "\n", "repo_root = _find_repo_root(Path.cwd().resolve())\n", "data_file = repo_root / 'examples' / 'data' / 'forecast_lum_annular.fits'\n", "assert data_file.exists(), f\"Data file not found: {data_file}\"\n", "\n", "data_file\n" ] }, { "cell_type": "markdown", "id": "e035b6c7", "metadata": {}, "source": [ "## Part 1: Simple API - Get Results Fast\n", "\n", "SMPy's simple API is perfect for interactive analysis and notebooks. Each mass mapping method has its own function with sensible defaults." ] }, { "cell_type": "code", "execution_count": null, "id": "0deaf0a0", "metadata": {}, "outputs": [], "source": [ "# Cell 3: Simple API demonstration\n", "# Method 1: The unified map_mass() function - specify method as parameter\n", "result_ks = map_mass(\n", " data=str(data_file),\n", " method='kaiser_squires', # Choose your method\n", " coord_system='radec',\n", " pixel_scale=0.4,\n", " smoothing=2.0,\n", " g1_col='g1_Rinv',\n", " g2_col='g2_Rinv',\n", " weight_col='weight',\n", " save_plots=False,\n", ")\n", "\n", "print(\"✓ Kaiser-Squires complete\")\n", "print(f\" E-mode shape: {result_ks['maps']['E'].shape}\")\n", "print(f\" Peak convergence: {result_ks['maps']['E'].max():.3f}\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "80d30760", "metadata": {}, "outputs": [], "source": [ "# Cell 4: Method-specific functions\n", "# Method 2: Direct method functions for common use cases\n", "result_am = map_aperture_mass(\n", " data=str(data_file),\n", " coord_system='radec',\n", " pixel_scale=0.4,\n", " filter_type='schirmer',\n", " filter_scale=60,\n", " g1_col='g1_Rinv',\n", " g2_col='g2_Rinv',\n", " weight_col='weight',\n", " save_plots=False,\n", ")\n", "\n", "print(\"✓ Aperture Mass complete\")\n", "print(f\" Filter type: Schirmer\")\n" ] }, { "cell_type": "markdown", "id": "cfece6dc", "metadata": {}, "source": [ "## Part 2: Config API - Reproducible Workflows for Power Users (Recommended)\n", "\n", "For production work, SMPy's Config API provides:\n", "- **YAML files** for single-file master configurations\n", "- **Validation** to catch errors before running" ] }, { "cell_type": "code", "execution_count": null, "id": "359b8a98", "metadata": {}, "outputs": [], "source": [ "# Cell 5: Config-based workflow\n", "# Create a configuration programmatically\n", "config = Config.from_defaults('kaiser_squires')\n", "\n", "# Update with our specific parameters\n", "config.update_from_kwargs(\n", " data=str(data_file),\n", " coord_system='radec',\n", " pixel_scale=0.4,\n", " g1_col='g1_Rinv',\n", " g2_col='g2_Rinv',\n", " weight_col='weight',\n", " smoothing=2.0,\n", " mode=['E', 'B'],\n", " create_snr=False,\n", " save_plots=False,\n", ")\n", "\n", "# Show what we've configured (general section)\n", "print(\"Configuration preview:\")\n", "config.show_config(section='general')\n" ] }, { "cell_type": "markdown", "id": "38e4ef20", "metadata": {}, "source": [ "Configurations can be saved as YAML files to serve as both a record of the parameters used to create the map and a reusable configuration file for future runs." ] }, { "cell_type": "code", "execution_count": null, "id": "e463b7dd", "metadata": {}, "outputs": [], "source": [ "# Cell 6: Run with config\n", "# Execute using the configuration\n", "result_config = run(config)\n", "\n", "print(\"✓ Config-based run complete\")\n" ] }, { "cell_type": "markdown", "id": "40ac3e8b", "metadata": {}, "source": [ "## Part 3 (Optional/Prototyping): Class API - Maximum Control\n", "\n", "For advanced users who need fine-grained control, SMPy exposes the mapper classes directly." ] }, { "cell_type": "code", "execution_count": null, "id": "63080692", "metadata": {}, "outputs": [], "source": [ "# Cell 7: Direct mapper class usage\n", "from smpy.mapping_methods import KaiserSquiresMapper, KSPlusMapper\n", "from smpy.utils import load_shear_data\n", "from smpy.coordinates import get_coordinate_system\n", "\n", "# Load and prepare data manually\n", "shear_df = load_shear_data(\n", " str(data_file),\n", " coord1_col='ra',\n", " coord2_col='dec', \n", " g1_col='g1_Rinv',\n", " g2_col='g2_Rinv',\n", " weight_col='weight',\n", " hdu=1\n", ")\n", "\n", "# Set up coordinate system\n", "coord_system = get_coordinate_system('radec')\n", "shear_df = coord_system.transform_coordinates(shear_df)\n", "scaled_bounds, true_bounds = coord_system.calculate_boundaries(\n", " shear_df['coord1'].values,\n", " shear_df['coord2'].values\n", ")\n", "\n", "# Create shear grid\n", "g1_grid, g2_grid = coord_system.create_grid(\n", " shear_df, \n", " scaled_bounds,\n", " {'general': {'radec': {'resolution': 0.4}}}\n", ")\n", "\n", "print(f\"✓ Manual data preparation complete\")\n", "print(f\" Grid shape: {g1_grid.shape}\")\n", "print(f\" Number of galaxies: {len(shear_df)}\")" ] }, { "cell_type": "code", "execution_count": null, "id": "b304d03e", "metadata": {}, "outputs": [], "source": [ "# Cell 8: Use mapper class directly\n", "# Create mapper with custom config\n", "mapper_config = {\n", " 'general': {'method': 'kaiser_squires', 'mode': ['E']},\n", " 'methods': {\n", " 'kaiser_squires': {\n", " 'smoothing': {'type': 'gaussian', 'sigma': 2.0}\n", " }\n", " },\n", " 'plotting': {}\n", "}\n", "\n", "# Initialize and run mapper\n", "mapper = KaiserSquiresMapper(mapper_config)\n", "kappa_e, kappa_b = mapper.create_maps(g1_grid, -g2_grid)\n", "# Note: -g2 for RA/Dec\n", "# high-level API handles this automatically, but the low-level API expects the user to manage it\n", "\n", "print(\"✓ Direct mapper complete\")\n", "print(f\" Convergence range: [{kappa_e.min():.3f}, {kappa_e.max():.3f}]\")" ] }, { "cell_type": "markdown", "id": "906056da", "metadata": {}, "source": [ "## Comparing the Results\n", "\n", "Let's visualize all three methods to confirm they produce equivalent results:" ] }, { "cell_type": "code", "execution_count": null, "id": "4f78b7ba", "metadata": {}, "outputs": [], "source": [ "# Cell 9: Comparison visualization\n", "fig, axes = plt.subplots(2, 2, figsize=(15, 10))\n", "\n", "# Simple API results\n", "axes[0, 0].imshow(result_ks['maps']['E'], cmap='magma')\n", "axes[0, 0].set_title('Simple API: map_mass()')\n", "\n", "axes[0, 1].imshow(result_am['maps']['E'], cmap='magma')\n", "axes[0, 1].set_title('Simple API: map_aperture_mass()')\n", "\n", "# Config API result\n", "axes[1, 0].imshow(result_config['maps']['E'], cmap='magma')\n", "axes[1, 0].set_title('Config API: run(config)')\n", "\n", "# Class API result\n", "axes[1, 1].imshow(kappa_e, cmap='magma')\n", "axes[1, 1].set_title('Class API: KaiserSquiresMapper')\n", "\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "1ebfd0bb", "metadata": {}, "source": [ "## Full Configuration File Example\n" ] }, { "cell_type": "code", "execution_count": null, "id": "0265d307", "metadata": {}, "outputs": [], "source": [ "config.show_config()" ] }, { "cell_type": "code", "execution_count": null, "id": "1ecb8c72", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "SMPy", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 5 }