diff --git a/llms-full.txt b/llms-full.txt index 0f8aa070a..c1d0ac4fe 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -459,6 +459,8 @@ AUTO-GENERATED by PyAutoBuild — do not edit by hand; regenerate with generate. - Contents: Illustration, Numerics, Images, Adaptive Over Sampling, Multiple Lens Galaxies, Ray Tracing, Default Ray Tracing, Dataset & Modeling, Pixelization - [Chaining: Over Sample](scripts/guides/advanced/over_sampling_chaining.py): Over sampling is a numerical technique where the images of light profiles and galaxies are evaluated on a higher resolution grid than the image data to ensure the calculation is accurate. - Contents: Start Here Notebook, Dataset + Masking, Paths, Redshifts, Model (Search 1), Search + Analysis + Model-Fit (Search 1), Result (Search 1), Over Sampling (Search 2) +- [COOLEST](scripts/guides/coolest_interop.py): COOLEST (COde-independent Organized LEns STandard, https://github.com/aymgal/COOLEST) is a standard for storing and exchanging strong lens models between different lens modeling software, for example lenstronomy, herculens and GLEE. + - Contents: Conventions, Lens Model, Export, Import, Round Trip, NFW Profiles - [Data Structures](scripts/guides/data_structures.py): This tutorial illustrates the data structure objects which data and results quantities are stored using, which are extensions of NumPy arrays. - Contents: Units, API, Grids, Native, Slim, Masked Data Structures, Data, Tracer, Irregular Structures, Vector Quantities - [Galaxies](scripts/guides/galaxies.py): In the guide `tracer.py`, we inspected the results of a `Tracer` and computed the overall properties of the lens model's image, convergence and other quantities. diff --git a/notebooks/guides/README.md b/notebooks/guides/README.md index ee8db6bc4..702e4040e 100644 --- a/notebooks/guides/README.md +++ b/notebooks/guides/README.md @@ -10,6 +10,7 @@ most calculations. - `data_structures`: How the NumPy arrays containing results are structured and the API for using them. - `tracer` Performing ray-tracing and lensing calculations. - `galaxies` Creating and using galaxies and their mass and light profiles. +- `coolest_interop` Exporting / importing lens models via the COOLEST standard for exchange with other lens modeling codes. # Folders diff --git a/notebooks/guides/coolest_interop.ipynb b/notebooks/guides/coolest_interop.ipynb new file mode 100644 index 000000000..7dcd57918 --- /dev/null +++ b/notebooks/guides/coolest_interop.ipynb @@ -0,0 +1,269 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "COOLEST\n", + "=======\n", + "\n", + "COOLEST (COde-independent Organized LEns STandard, https://github.com/aymgal/COOLEST) is a standard for storing\n", + "and exchanging strong lens models between different lens modeling software, for example lenstronomy, herculens\n", + "and GLEE.\n", + "\n", + "**PyAutoLens** can export a lens model's analytic profile parameters to a COOLEST JSON template file and import\n", + "a template (including one produced by another code) back as a `Tracer`.\n", + "\n", + "This requires the optional `coolest` package:\n", + "\n", + "`pip install autolens[coolest]`\n", + "\n", + "__Contents__\n", + "\n", + "- **Conventions:** How PyAutoLens conventions map to COOLEST conventions.\n", + "- **Lens Model:** A simple lens model (power-law + shear lens, Sersic source) to export.\n", + "- **Export:** Writing the model to a COOLEST `.json` template.\n", + "- **Import:** Reading a COOLEST template back as a `Tracer`.\n", + "- **Round Trip:** Verifying the exported and imported models are numerically identical.\n", + "- **NFW Profiles:** The critical surface density COOLEST's NFW normalization requires.\n", + "\n", + "__Conventions__\n", + "\n", + "The conversion handles the following differences automatically, so you do not need to apply any factors yourself:\n", + "\n", + "- Position angles: PyAutoLens measures counter-clockwise from the positive x-axis; COOLEST measures\n", + " counter-clockwise from the positive y-axis (\"East-of-North\") in the interval (-90, +90] degrees.\n", + "\n", + "- Ellipticity: PyAutoLens profiles use elliptical components `ell_comps`; COOLEST uses the axis ratio `q` and\n", + " position angle `phi`.\n", + "\n", + "- Radii: COOLEST characteristic radii (e.g. the Einstein radius `theta_E`) use the intermediate axis\n", + " r = sqrt(a * b) of the elliptical contours. For a power-law profile this means\n", + " `theta_E = sqrt(q) * (2 / (1 + q))^(1 / (gamma - 1)) * einstein_radius`, which for an isothermal profile\n", + " reduces to the familiar `2 sqrt(q) / (1 + q)` factor. The Sersic `effective_radius` is already an\n", + " intermediate-axis radius and converts with no factor.\n", + "\n", + "The exported `theta_E` is the mass profile's parameter in the COOLEST convention. It is not a curve-based\n", + "Einstein radius (e.g. the effective radius of the area within the tangential critical curve, used by the\n", + "Euclid DR1 catalogue), which depends on all profiles in the model including external shear." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Google Colab Setup__\n", + "\n", + "This cell sets up the environment when the notebook is run on Google Colab: it installs the\n", + "required PyAuto packages, clones the workspace (configuration files and example datasets) and\n", + "points the configuration at it. If you are running the notebook elsewhere (e.g. locally via\n", + "your own installation) it does nothing, and you can run it safely.\n", + "\n", + "Colab tip: model-fits run much faster on a GPU \u2014 enable one via \"Runtime\" -> \"Change runtime\n", + "type\" -> \"Hardware accelerator\" before running the notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " import subprocess\n", + " import sys\n", + "\n", + " subprocess.check_call(\n", + " [sys.executable, \"-m\", \"pip\", \"install\", \"autoconf\", \"--no-deps\"]\n", + " )\n", + "except ImportError:\n", + " pass\n", + "\n", + "from autoconf import setup_colab\n", + "\n", + "setup_colab.setup(\"autolens\")" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "\n", + "from autoconf import jax_wrapper # Sets JAX environment before other imports\n", + "\n", + "from autoconf import setup_notebook; setup_notebook()\n", + "\n", + "from os import path\n", + "\n", + "import autolens as al" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Lens Model__\n", + "\n", + "A simple lens model: a power-law mass profile with external shear lensing a Sersic source." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "lens = al.Galaxy(\n", + " redshift=0.5,\n", + " mass=al.mp.PowerLaw(\n", + " centre=(0.0, 0.0),\n", + " ell_comps=al.convert.ell_comps_from(axis_ratio=0.7, angle=45.0),\n", + " einstein_radius=1.6,\n", + " slope=2.1,\n", + " ),\n", + " shear=al.mp.ExternalShear(gamma_1=0.02, gamma_2=-0.03),\n", + ")\n", + "\n", + "source = al.Galaxy(\n", + " redshift=1.0,\n", + " bulge=al.lp.Sersic(\n", + " centre=(0.1, 0.1),\n", + " ell_comps=al.convert.ell_comps_from(axis_ratio=0.8, angle=60.0),\n", + " intensity=0.3,\n", + " effective_radius=0.5,\n", + " sersic_index=2.0,\n", + " ),\n", + ")\n", + "\n", + "tracer = al.Tracer(galaxies=[lens, source])" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Export__\n", + "\n", + "`to_coolest` writes the model to a COOLEST `.json` template file and returns the written path.\n", + "\n", + "Each galaxy becomes a COOLEST `Galaxy` lensing entity; `ExternalShear` and `MassSheet` profiles are written as\n", + "COOLEST `MassField` entities, as the standard requires. The model cosmology's H0 and Om0 are stored in the\n", + "template." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "file_path = al.interop.coolest.to_coolest(\n", + " galaxies=tracer, file_path=path.join(\"output\", \"coolest_template\")\n", + ")\n", + "\n", + "print(f\"COOLEST template written to: {file_path}\")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Import__\n", + "\n", + "`from_coolest` reads a COOLEST template \u2014 one written by **PyAutoLens** or by any other code \u2014 and returns a\n", + "`Tracer` built from its profiles, with all parameters converted back to PyAutoLens conventions." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "tracer_via_coolest = al.interop.coolest.from_coolest(file_path=file_path)\n", + "\n", + "print(tracer_via_coolest.galaxies)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Round Trip__\n", + "\n", + "The imported model is numerically identical to the exported one, which we verify by comparing deflection angles\n", + "on a grid." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "grid = al.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.1)\n", + "\n", + "deflections = tracer.deflections_yx_2d_from(grid=grid)\n", + "deflections_via_coolest = tracer_via_coolest.deflections_yx_2d_from(grid=grid)\n", + "\n", + "print(\n", + " \"Max deflection difference: \"\n", + " f\"{abs(deflections.array - deflections_via_coolest.array).max()}\"\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__NFW Profiles__\n", + "\n", + "COOLEST parameterises the NFW profile by a physical characteristic density `rho_c`, whereas the PyAutoLens\n", + "`NFW` uses the dimensionless `kappa_s`. The conversion therefore uses the critical surface mass density between\n", + "the galaxy's redshift and the model's highest redshift, computed automatically from the model's cosmology\n", + "(pass `cosmology=` to `to_coolest` / `from_coolest` to control it; a template only stores H0 and Om0, so supply\n", + "the same cosmology to both directions for an exact round trip).\n", + "\n", + "__Supported Profiles__\n", + "\n", + "Light: `Sersic` / `SersicSph`. Mass: `Isothermal` / `IsothermalSph` (SIE), `PowerLaw` / `PowerLawSph` (PEMD),\n", + "`NFW` / `NFWSph`, `ExternalShear` and `MassSheet` (ConvergenceSheet). Converting an unsupported profile raises\n", + "an error naming the profile.\n", + "\n", + "Fin." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [], + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "anaconda-cloud": {}, + "kernelspec": { + "display_name": "Python 3", + "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.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/scripts/guides/README.md b/scripts/guides/README.md index ee8db6bc4..702e4040e 100644 --- a/scripts/guides/README.md +++ b/scripts/guides/README.md @@ -10,6 +10,7 @@ most calculations. - `data_structures`: How the NumPy arrays containing results are structured and the API for using them. - `tracer` Performing ray-tracing and lensing calculations. - `galaxies` Creating and using galaxies and their mass and light profiles. +- `coolest_interop` Exporting / importing lens models via the COOLEST standard for exchange with other lens modeling codes. # Folders diff --git a/scripts/guides/coolest_interop.py b/scripts/guides/coolest_interop.py new file mode 100644 index 000000000..abf6c07f8 --- /dev/null +++ b/scripts/guides/coolest_interop.py @@ -0,0 +1,140 @@ +""" +COOLEST +======= + +COOLEST (COde-independent Organized LEns STandard, https://github.com/aymgal/COOLEST) is a standard for storing +and exchanging strong lens models between different lens modeling software, for example lenstronomy, herculens +and GLEE. + +**PyAutoLens** can export a lens model's analytic profile parameters to a COOLEST JSON template file and import +a template (including one produced by another code) back as a `Tracer`. + +This requires the optional `coolest` package: + +`pip install autolens[coolest]` + +__Contents__ + +- **Conventions:** How PyAutoLens conventions map to COOLEST conventions. +- **Lens Model:** A simple lens model (power-law + shear lens, Sersic source) to export. +- **Export:** Writing the model to a COOLEST `.json` template. +- **Import:** Reading a COOLEST template back as a `Tracer`. +- **Round Trip:** Verifying the exported and imported models are numerically identical. +- **NFW Profiles:** The critical surface density COOLEST's NFW normalization requires. + +__Conventions__ + +The conversion handles the following differences automatically, so you do not need to apply any factors yourself: + +- Position angles: PyAutoLens measures counter-clockwise from the positive x-axis; COOLEST measures + counter-clockwise from the positive y-axis ("East-of-North") in the interval (-90, +90] degrees. + +- Ellipticity: PyAutoLens profiles use elliptical components `ell_comps`; COOLEST uses the axis ratio `q` and + position angle `phi`. + +- Radii: COOLEST characteristic radii (e.g. the Einstein radius `theta_E`) use the intermediate axis + r = sqrt(a * b) of the elliptical contours. For a power-law profile this means + `theta_E = sqrt(q) * (2 / (1 + q))^(1 / (gamma - 1)) * einstein_radius`, which for an isothermal profile + reduces to the familiar `2 sqrt(q) / (1 + q)` factor. The Sersic `effective_radius` is already an + intermediate-axis radius and converts with no factor. + +The exported `theta_E` is the mass profile's parameter in the COOLEST convention. It is not a curve-based +Einstein radius (e.g. the effective radius of the area within the tangential critical curve, used by the +Euclid DR1 catalogue), which depends on all profiles in the model including external shear. +""" + +from autoconf import jax_wrapper # Sets JAX environment before other imports + +# from autoconf import setup_notebook; setup_notebook() + +from os import path + +import autolens as al + +""" +__Lens Model__ + +A simple lens model: a power-law mass profile with external shear lensing a Sersic source. +""" +lens = al.Galaxy( + redshift=0.5, + mass=al.mp.PowerLaw( + centre=(0.0, 0.0), + ell_comps=al.convert.ell_comps_from(axis_ratio=0.7, angle=45.0), + einstein_radius=1.6, + slope=2.1, + ), + shear=al.mp.ExternalShear(gamma_1=0.02, gamma_2=-0.03), +) + +source = al.Galaxy( + redshift=1.0, + bulge=al.lp.Sersic( + centre=(0.1, 0.1), + ell_comps=al.convert.ell_comps_from(axis_ratio=0.8, angle=60.0), + intensity=0.3, + effective_radius=0.5, + sersic_index=2.0, + ), +) + +tracer = al.Tracer(galaxies=[lens, source]) + +""" +__Export__ + +`to_coolest` writes the model to a COOLEST `.json` template file and returns the written path. + +Each galaxy becomes a COOLEST `Galaxy` lensing entity; `ExternalShear` and `MassSheet` profiles are written as +COOLEST `MassField` entities, as the standard requires. The model cosmology's H0 and Om0 are stored in the +template. +""" +file_path = al.interop.coolest.to_coolest( + galaxies=tracer, file_path=path.join("output", "coolest_template") +) + +print(f"COOLEST template written to: {file_path}") + +""" +__Import__ + +`from_coolest` reads a COOLEST template — one written by **PyAutoLens** or by any other code — and returns a +`Tracer` built from its profiles, with all parameters converted back to PyAutoLens conventions. +""" +tracer_via_coolest = al.interop.coolest.from_coolest(file_path=file_path) + +print(tracer_via_coolest.galaxies) + +""" +__Round Trip__ + +The imported model is numerically identical to the exported one, which we verify by comparing deflection angles +on a grid. +""" +grid = al.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.1) + +deflections = tracer.deflections_yx_2d_from(grid=grid) +deflections_via_coolest = tracer_via_coolest.deflections_yx_2d_from(grid=grid) + +print( + "Max deflection difference: " + f"{abs(deflections.array - deflections_via_coolest.array).max()}" +) + +""" +__NFW Profiles__ + +COOLEST parameterises the NFW profile by a physical characteristic density `rho_c`, whereas the PyAutoLens +`NFW` uses the dimensionless `kappa_s`. The conversion therefore uses the critical surface mass density between +the galaxy's redshift and the model's highest redshift, computed automatically from the model's cosmology +(pass `cosmology=` to `to_coolest` / `from_coolest` to control it; a template only stores H0 and Om0, so supply +the same cosmology to both directions for an exact round trip). + +__Supported Profiles__ + +Light: `Sersic` / `SersicSph`. Mass: `Isothermal` / `IsothermalSph` (SIE), `PowerLaw` / `PowerLawSph` (PEMD), +`NFW` / `NFWSph`, `ExternalShear` and `MassSheet` (ConvergenceSheet). Converting an unsupported profile raises +an error naming the profile. + +Fin. +""" diff --git a/workspace_index.json b/workspace_index.json index 02c751bdc..9f75acef9 100644 --- a/workspace_index.json +++ b/workspace_index.json @@ -1471,6 +1471,21 @@ "summary": "Over sampling is a numerical technique where the images of light profiles and galaxies are evaluated on a higher resolution grid than the image data to ensure the calculation is accurate.", "title": "Chaining: Over Sample" }, + { + "contents": [ + "Conventions", + "Lens Model", + "Export", + "Import", + "Round Trip", + "NFW Profiles" + ], + "cross_refs": [], + "notebook": "notebooks/guides/coolest_interop.ipynb", + "path": "scripts/guides/coolest_interop.py", + "summary": "COOLEST (COde-independent Organized LEns STandard, https://github.com/aymgal/COOLEST) is a standard for storing and exchanging strong lens models between different lens modeling software, for example lenstronomy, herculens and GLEE.", + "title": "COOLEST" + }, { "contents": [ "Units",