From 1d79bd896710053161a2201385aaec5d73d00a04 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 13 Nov 2023 12:59:29 -0800 Subject: [PATCH 01/55] edit the name of python-app.yml --- .github/workflows/{python-app.yml => ci.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{python-app.yml => ci.yml} (98%) diff --git a/.github/workflows/python-app.yml b/.github/workflows/ci.yml similarity index 98% rename from .github/workflows/python-app.yml rename to .github/workflows/ci.yml index 13f22dee..f6bb9722 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python -name: Unit Tests +name: CI on: push: From 0be46cda2c14d51eb596d488138f66c9754bf964 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 13 Nov 2023 13:33:43 -0800 Subject: [PATCH 02/55] add get_start.ipynb (from basic introduction) --- docs/get_start.ipynb | 458 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 docs/get_start.ipynb diff --git a/docs/get_start.ipynb b/docs/get_start.ipynb new file mode 100644 index 00000000..0b4462c0 --- /dev/null +++ b/docs/get_start.ipynb @@ -0,0 +1,458 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Welcome to Caustic!\n", + "\n", + "In need of a differentiable strong gravitational lensing simulation package? Look no further! We have all your lensing simulator needs. In this tutorial we will cover the basics of caustic and how to get going making your own lensing configurations. Caustic is easy to use and very powerful, you will get to see some of that power here, but there will be more notebooks which demo specific use cases." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import torch\n", + "from torch.nn.functional import avg_pool2d\n", + "import matplotlib.pyplot as plt\n", + "from astropy.io import fits\n", + "import numpy as np\n", + "\n", + "import caustic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FlatLambdaCDM(\n", + " name='cosmo',\n", + " static=[h0, critical_density_0, Om0],\n", + " dynamic=[],\n", + " x keys=[]\n", + ")" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Specify the image/cosmology parameters\n", + "n_pix = 100\n", + "res = 0.05\n", + "upsample_factor = 2\n", + "fov = res * n_pix\n", + "thx, thy = caustic.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "z_l = torch.tensor(0.5, dtype=torch.float32)\n", + "z_s = torch.tensor(1.5, dtype=torch.float32)\n", + "cosmology = caustic.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology.to(dtype=torch.float32)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simulating an SIE lens\n", + "\n", + "Here we will demo the very basics of lensing with a classic `SIE` lens model. We will see what it takes to make an `SIE` model, lens a backgorund `Sersic` source, and sample some examples in a simulator. Caustic simulators can generalize to very complex scenarios. In these cases there can be a lot of parameters moving through the simulator, and the order/number of parameters may change depending on what lens or source is being used. To streamline this process, caustic impliments a class called `Parametrized` which has some knowledge of the parameters moving through it, this way it can keep track of everything for you. For this to work, you must put the parameters into a `Packed` object which it can recognize, each sub function can then unpack the parameters it needs. Below we will show some examples of what this looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# demo simulator with sersic source, SIE lens. then sample some examples. demo the model graph\n", + "\n", + "class Simple_Sim(caustic.Simulator):\n", + " def __init__(\n", + " self,\n", + " lens,\n", + " src,\n", + " z_s=None,\n", + " name: str = \"sim\",\n", + " ):\n", + " super().__init__(name) # need this so `Parametrized` can do its magic\n", + " \n", + " # These are the lens and source objects to keep track of\n", + " self.lens = lens\n", + " self.src = src\n", + " \n", + " # Here we can add a parameter to the simulator, in this case it is `z_s` which we will need later\n", + " self.add_param(\"z_s\", z_s)\n", + "\n", + " def forward(self, params):# define the forward model\n", + " # Here the simulator unpacks the parameter it needs\n", + " z_s = self.unpack(params)\n", + "\n", + " # Note this is very similar to before, except the packed up `x` is all the raytrace function needs to work\n", + " bx, by = self.lens.raytrace(thx, thy, z_s, params)\n", + " mu_fine = self.src.brightness(bx, by, params)\n", + " \n", + " # We return the sampled brightness at each pixel location\n", + " return avg_pool2d(mu_fine.squeeze()[None, None], upsample_factor)[0, 0]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "sim\n", + "\n", + "Simple_Sim('sim')\n", + "\n", + "\n", + "\n", + "sim/z_s\n", + "\n", + "z_s\n", + "\n", + "\n", + "\n", + "sim->sim/z_s\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie\n", + "\n", + "SIE('sie')\n", + "\n", + "\n", + "\n", + "sim->sie\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src\n", + "\n", + "Sersic('src')\n", + "\n", + "\n", + "\n", + "sim->src\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/z_l\n", + "\n", + "z_l\n", + "\n", + "\n", + "\n", + "sie->sie/z_l\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/x0\n", + "\n", + "x0\n", + "\n", + "\n", + "\n", + "sie->sie/x0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/y0\n", + "\n", + "y0\n", + "\n", + "\n", + "\n", + "sie->sie/y0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/q\n", + "\n", + "q\n", + "\n", + "\n", + "\n", + "sie->sie/q\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/phi\n", + "\n", + "phi\n", + "\n", + "\n", + "\n", + "sie->sie/phi\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/b\n", + "\n", + "b\n", + "\n", + "\n", + "\n", + "sie->sie/b\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/x0\n", + "\n", + "x0\n", + "\n", + "\n", + "\n", + "src->src/x0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/y0\n", + "\n", + "y0\n", + "\n", + "\n", + "\n", + "src->src/y0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/q\n", + "\n", + "q\n", + "\n", + "\n", + "\n", + "src->src/q\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/phi\n", + "\n", + "phi\n", + "\n", + "\n", + "\n", + "src->src/phi\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/n\n", + "\n", + "n\n", + "\n", + "\n", + "\n", + "src->src/n\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/Re\n", + "\n", + "Re\n", + "\n", + "\n", + "\n", + "src->src/Re\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/Ie\n", + "\n", + "Ie\n", + "\n", + "\n", + "\n", + "src->src/Ie\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sie = caustic.lenses.SIE(cosmology, name = \"sie\")\n", + "src = caustic.sources.Sersic(name = \"src\")\n", + "\n", + "sim = Simple_Sim(sie, src, torch.tensor(0.8))\n", + "\n", + "sim.get_graph(True, True)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simple_Sim(\n", + " name='sim',\n", + " static=[z_s],\n", + " dynamic=[],\n", + " x keys=[('sie': ['z_l', 'x0', 'y0', 'q', 'phi', 'b']), ('src': ['x0', 'y0', 'q', 'phi', 'n', 'Re', 'Ie'])]\n", + ")\n", + "SIE(\n", + " name='sie',\n", + " static=[],\n", + " dynamic=[z_l, x0, y0, q, phi, b],\n", + " x keys=[('sie': ['z_l', 'x0', 'y0', 'q', 'phi', 'b'])]\n", + ")\n" + ] + } + ], + "source": [ + "print(sim)\n", + "print(sie)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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dToiLsgo1QD4dncVExRIf+6XYxC7WnFBocAZV5+QcRuBktyuTVyn4hHbXlr4LLdtatuZrugrNSElDjg2fjknM6W/ofnI7VDaIRVPZewrS5l78Uah9DpWHFBAE/LmpASWfNEnWCbBbVPpXOxaDu59V6U0LLFGxtEIDaXa43yWv3RBDShnglNH8je9+2zH0Ln6pC4fka+NTU0jP2WO/B30rrn8/qr8QPnCmrqXMtanZHyZGiD6DehnP/sZPvWpT2Hx4sXo7+/HaaedhmeffXZ6ClmG66+/HkuXLkV/fz/WrFmDV155JXpihBBCjmyiFqBf/vKXOP/889HT04N/+Zd/wUsvvYS/+Iu/wAc+8IFan2984xu49dZbcfvtt+Opp57CvHnzsHbtWhw+fLjwyRNCCJm9RJngvv71r2P58uW46667an8bGhqqfc6yDLfccgu+/OUv4+KLLwYAfPe738Xg4CAefPBBXHbZZY0frJpNv/fHmNm8131hy4hxy56pTNmhKqcl41Va2kWMSqXaLTvfjVyZ3FRqIcespkxuYttzLxau1cbrvPJAFa/30iTnJQ2PccOWXU037Nis4e6+jfdV5jnLZdtys66zr3uvdQZoaf5FLoYnsjbXBbJJe5mdAi7C7r2VrtWuyU1uSxOcekbkfVfpg9y++WYpmXZIbVesNvs6uSZG9R213LJleizDtBedisf97fDm0AY37B/+8IdYtWoVPvnJT+K4447DGWecgTvvvLPW/tprr2F4eBhr1qyp/W1gYADnnHMOdu3aFXMoQgghRzhRC9Crr76K2267DSeddBIeffRRfP7zn8dVV12Fu+++GwAwPDwMABgcHPT2GxwcrLVJxsbGMDo66v0jhBBy5BNlgqtWq1i1ahVuuukmAMAZZ5yBF198Ebfffjs2bNjQ1AS2b9+Or371q03tSwghZPYStQAtXboUH/nIR7y/nXzyyfinf/onAMCSJUsAACMjI1i6dGmtz8jICE4//fS6Y27duhVbtmypbY+OjmL58uX5k5A+wgW6ZbuVQZWGYmGVapgabPpjTKmGqUlN72uUapDtssqp6ZatdJx8l+2p9vxxreqpMqWMkrQ813CYSE3IO11po495JIy0/EGXc0sDUtqf6JsZbTFu2KFyDNb5mNplfhNgSop6KKvch9RqpOTgujF3+41SA6r0Om294nkJaEDl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3AC8To3B69QYvE6NweuUj/Xm8z5tWYD+4A/+AD//+c9x/fXXY3h4GKeffjoeeeQR5ZhACCHk6KVtTgibNm1qyORGCCHk6KRjk5H29fXhK1/5iuegQDS8To3B69QYvE6NwetUDG3JhEAIIYSE6Ng3IEIIIUc2XIAIIYQkgQsQIYSQJHABIoQQkoSOXYBY0G6a7du346yzzsKCBQtw3HHH4ZJLLsGePXu8PocPH8bGjRuxePFizJ8/H+vWrVPpkI42br75ZpRKJWzevLn2N16nKX72s5/hU5/6FBYvXoz+/n6cdtppePbZZ2vtWZbh+uuvx9KlS9Hf3481a9bglVdeSTjjmadSqWDbtm0YGhpCf38/PvzhD+NrX/ual9+M16lFsg7kvvvuy3p7e7O/+7u/y/7rv/4r+6M/+qNs0aJF2cjISOqpJWHt2rXZXXfdlb344ovZ888/n/3Wb/1WtmLFiuydd96p9fnc5z6XLV++PNuxY0f27LPPZueee2523nnnJZx1Wp5++unsxBNPzD760Y9mV199de3vvE5Z9vbbb2cnnHBC9ulPfzp76qmnsldffTV79NFHs//5n/+p9bn55puzgYGB7MEHH8xeeOGF7Hd+53eyoaGh7NChQwlnPrPceOON2eLFi7OHH344e+2117L7778/mz9/fvZXf/VXtT68Tq3RkQvQ2WefnW3cuLG2XalUsmXLlmXbt29POKvOYe/evRmAbOfOnVmWZdm+ffuynp6e7P7776/1+e///u8MQLZr165U00zGgQMHspNOOil77LHHsl//9V+vLUC8TlN86Utfyi644ILc9mq1mi1ZsiT78z//89rf9u3bl/X19WXf+973ZmKKHcFFF12UfeYzn/H+dumll2br16/PsozXqQg6zgQXW9DuaGT//v0AgGOOOQYAsHv3bkxMTHjXbOXKlVixYsVRec02btyIiy66yLseAK/T+/zwhz/EqlWr8MlPfhLHHXcczjjjDNx555219tdeew3Dw8PedRoYGMA555xzVF2n8847Dzt27MDLL78MAHjhhRfwxBNP4BOf+AQAXqciSF4PSGIVtPvJT36SaFadQ7VaxebNm3H++efj1FNPBQAMDw+jt7cXixYt8vrmFQE8krnvvvvw3HPP4ZlnnlFtvE5TvPrqq7jtttuwZcsW/Mmf/AmeeeYZXHXVVejt7cWGDRtq16LRopJHKtdddx1GR0excuVKdHV1oVKp4MYbb8T69esBgNepADpuASI2GzduxIsvvognnngi9VQ6jjfeeANXX301HnvsMcyZMyf1dDqWarWKVatW4aabbgIAnHHGGXjxxRdx++23Y8OGDYln1zl8//vfxz333IN7770Xp5xyCp5//nls3rwZy5Yt43UqiI4zwcUWtDua2LRpEx5++GH867/+K44//vja35csWYLx8XHs27fP63+0XbPdu3dj7969+NjHPobu7m50d3dj586duPXWW9Hd3Y3BwUFeJwBLly7FRz7yEe9vJ598Ml5//XUAqF2Lo/07+MUvfhHXXXcdLrvsMpx22mn4wz/8Q1xzzTXYvn07AF6nIui4BcgtaPc+7xe0W716dcKZpSPLMmzatAkPPPAAHn/8cQwNDXntZ555Jnp6erxrtmfPHrz++utH1TW78MIL8eMf/xjPP/987d+qVauwfv362mdeJ+D8889Xbvwvv/wyTjjhBADA0NAQlixZ4l2n0dFRPPXUU0fVdXr33XdRLvs/kV1dXahWp0oI8zoVQGoviHrcd999WV9fX/b3f//32UsvvZRdccUV2aJFi7Lh4eHUU0vC5z//+WxgYCD7t3/7t+ytt96q/Xv33XdrfT73uc9lK1asyB5//PHs2WefzVavXp2tXr064aw7A9cLLst4nbJsykW9u7s7u/HGG7NXXnklu+eee7K5c+dm//AP/1Drc/PNN2eLFi3KfvCDH2T/+Z//mV188cVHnXvxhg0bsg996EM1N+x//ud/zo499tjs2muvrfXhdWqNjlyAsizLvvWtb2UrVqzIent7s7PPPjt78sknU08pGQDq/rvrrrtqfQ4dOpR94QtfyD7wgQ9kc+fOzX73d383e+utt9JNukOQCxCv0xQPPfRQduqpp2Z9fX3ZypUrszvuuMNrr1ar2bZt27LBwcGsr68vu/DCC7M9e/Ykmm0aRkdHs6uvvjpbsWJFNmfOnOxXfuVXsj/90z/NxsbGan14nVqD5RgIIYQkoeM0IEIIIUcHXIAIIYQkgQsQIYSQJHABIoQQkgQuQIQQQpLABYgQQkgSuAARQghJAhcgQgghSeACRAghJAlcgAghhCSBCxAhhJAkcAEihBCShP8f21qsljUV7uYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Reading the x_keys above we can input the parameters that we would like the simulator to evaluate\n", + "x = torch.tensor([\n", + " z_l.item(), # sie z_l\n", + " 0.7, # sie x0\n", + " 0.13, # sie y0\n", + " 0.4, # sie q\n", + " np.pi/5, # sie phi\n", + " 1., # sie b\n", + " 0.2, # src x0\n", + " 0.5, # src y0\n", + " 0.5, # src q\n", + " -np.pi/4, # src phi\n", + " 1.5, # src n\n", + " 2.5, # src Re\n", + " 1., # src Ie\n", + "])\n", + "plt.imshow(sim(x), origin=\"lower\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Where to go next?\n", + "\n", + "The caustic tutorials are generally short and to the point, that way you can idenfity what you want and jump right to some useful code that demo's the particular problem you face. Below is a list of caustic tutorials and a quick description of what you will learn in each one::\n", + "\n", + "- `LensZoo`: here you can see all the built-in lens mass distributions in `caustic` and how they distort the same background Seric source.\n", + "- `Playground`: here we demo the main visualizations of a lensing system (deflection angles, convergence, potential, time delay, magnification) in an interactive display so you can change the parameters by hand and see how the visuals change!\n", + "- `VisualizeCaustics`: here you can see how to find and display caustics, a must when using `caustic`!\n", + "- `Simulators`: here we describe the powerful simulator framework and how it can be used to quickly swap models, parameters, and other features and turn a complex forward model into a simple function.\n", + "- `InvertLensEquation`: here we demo forward ray tracing in `caustic` the process of mapping from the source plane to the image plane." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PY39", + "language": "python", + "name": "py39" + }, + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From fba09541e6f38398892bc2e124cf37ca406af09d Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 15 Nov 2023 16:37:50 -0800 Subject: [PATCH 03/55] setup jupyter book --- docs/jupyter_book/_config.yml | 32 +++++++ docs/jupyter_book/_toc.yml | 9 ++ docs/{ => jupyter_book}/get_start.ipynb | 12 +-- docs/jupyter_book/intro.md | 11 +++ docs/jupyter_book/logo.png | Bin 0 -> 9854 bytes docs/jupyter_book/markdown-notebooks.md | 53 ++++++++++ docs/jupyter_book/markdown.md | 55 +++++++++++ docs/jupyter_book/notebooks.ipynb | 122 ++++++++++++++++++++++++ docs/jupyter_book/references.bib | 56 +++++++++++ docs/jupyter_book/requirements.txt | 3 + 10 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 docs/jupyter_book/_config.yml create mode 100644 docs/jupyter_book/_toc.yml rename docs/{ => jupyter_book}/get_start.ipynb (99%) create mode 100644 docs/jupyter_book/intro.md create mode 100644 docs/jupyter_book/logo.png create mode 100644 docs/jupyter_book/markdown-notebooks.md create mode 100644 docs/jupyter_book/markdown.md create mode 100644 docs/jupyter_book/notebooks.ipynb create mode 100644 docs/jupyter_book/references.bib create mode 100644 docs/jupyter_book/requirements.txt diff --git a/docs/jupyter_book/_config.yml b/docs/jupyter_book/_config.yml new file mode 100644 index 00000000..5f534f80 --- /dev/null +++ b/docs/jupyter_book/_config.yml @@ -0,0 +1,32 @@ +# Book settings +# Learn more at https://jupyterbook.org/customize/config.html + +title: My sample book +author: The Jupyter Book Community +logo: logo.png + +# Force re-execution of notebooks on each build. +# See https://jupyterbook.org/content/execute.html +execute: + execute_notebooks: force + +# Define the name of the latex output file for PDF builds +latex: + latex_documents: + targetname: book.tex + +# Add a bibtex file so that we can create citations +bibtex_bibfiles: + - references.bib + +# Information about where the book exists on the web +repository: + url: https://github.com/executablebooks/jupyter-book # Online location of your book + path_to_book: docs # Optional path to your book, relative to the repository root + branch: master # Which branch of the repository should be used when creating links (optional) + +# Add GitHub buttons to your book +# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository +html: + use_issues_button: true + use_repository_button: true diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml new file mode 100644 index 00000000..74d5c710 --- /dev/null +++ b/docs/jupyter_book/_toc.yml @@ -0,0 +1,9 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: intro +chapters: +- file: markdown +- file: notebooks +- file: markdown-notebooks diff --git a/docs/get_start.ipynb b/docs/jupyter_book/get_start.ipynb similarity index 99% rename from docs/get_start.ipynb rename to docs/jupyter_book/get_start.ipynb index 0b4462c0..21a64c2f 100644 --- a/docs/get_start.ipynb +++ b/docs/jupyter_book/get_start.ipynb @@ -87,11 +87,11 @@ " name: str = \"sim\",\n", " ):\n", " super().__init__(name) # need this so `Parametrized` can do its magic\n", - " \n", + "\n", " # These are the lens and source objects to keep track of\n", " self.lens = lens\n", " self.src = src\n", - " \n", + "\n", " # Here we can add a parameter to the simulator, in this case it is `z_s` which we will need later\n", " self.add_param(\"z_s\", z_s)\n", "\n", @@ -102,7 +102,7 @@ " # Note this is very similar to before, except the packed up `x` is all the raytrace function needs to work\n", " bx, by = self.lens.raytrace(thx, thy, z_s, params)\n", " mu_fine = self.src.brightness(bx, by, params)\n", - " \n", + "\n", " # We return the sampled brightness at each pixel location\n", " return avg_pool2d(mu_fine.squeeze()[None, None], upsample_factor)[0, 0]" ] @@ -436,9 +436,9 @@ ], "metadata": { "kernelspec": { - "display_name": "PY39", + "display_name": "base", "language": "python", - "name": "py39" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -450,7 +450,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.5" + "version": "3.9.12" } }, "nbformat": 4, diff --git a/docs/jupyter_book/intro.md b/docs/jupyter_book/intro.md new file mode 100644 index 00000000..f8cdc73c --- /dev/null +++ b/docs/jupyter_book/intro.md @@ -0,0 +1,11 @@ +# Welcome to your Jupyter Book + +This is a small sample book to give you a feel for how book content is +structured. +It shows off a few of the major file types, as well as some sample content. +It does not go in-depth into any particular topic - check out [the Jupyter Book documentation](https://jupyterbook.org) for more information. + +Check out the content pages bundled with this sample book to see more. + +```{tableofcontents} +``` diff --git a/docs/jupyter_book/logo.png b/docs/jupyter_book/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..06d56f40c838b64eb048a63e036125964a069a3a GIT binary patch literal 9854 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 L>8MsHTOs~GC;Fmw literal 0 HcmV?d00001 diff --git a/docs/jupyter_book/markdown-notebooks.md b/docs/jupyter_book/markdown-notebooks.md new file mode 100644 index 00000000..a057a320 --- /dev/null +++ b/docs/jupyter_book/markdown-notebooks.md @@ -0,0 +1,53 @@ +--- +jupytext: + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.5 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Notebooks with MyST Markdown + +Jupyter Book also lets you write text-based notebooks using MyST Markdown. +See [the Notebooks with MyST Markdown documentation](https://jupyterbook.org/file-types/myst-notebooks.html) for more detailed instructions. +This page shows off a notebook written in MyST Markdown. + +## An example cell + +With MyST Markdown, you can define code cells with a directive like so: + +```{code-cell} +print(2 + 2) +``` + +When your book is built, the contents of any `{code-cell}` blocks will be +executed with your default Jupyter kernel, and their outputs will be displayed +in-line with the rest of your content. + +```{seealso} +Jupyter Book uses [Jupytext](https://jupytext.readthedocs.io/en/latest/) to convert text-based files to notebooks, and can support [many other text-based notebook files](https://jupyterbook.org/file-types/jupytext.html). +``` + +## Create a notebook with MyST Markdown + +MyST Markdown notebooks are defined by two things: + +1. YAML metadata that is needed to understand if / how it should convert text files to notebooks (including information about the kernel needed). + See the YAML at the top of this page for example. +2. The presence of `{code-cell}` directives, which will be executed with your book. + +That's all that is needed to get started! + +## Quickly add YAML metadata for MyST Notebooks + +If you have a markdown file and you'd like to quickly add YAML metadata to it, so that Jupyter Book will treat it as a MyST Markdown Notebook, run the following command: + +``` +jupyter-book myst init path/to/markdownfile.md +``` diff --git a/docs/jupyter_book/markdown.md b/docs/jupyter_book/markdown.md new file mode 100644 index 00000000..0ddaab3f --- /dev/null +++ b/docs/jupyter_book/markdown.md @@ -0,0 +1,55 @@ +# Markdown Files + +Whether you write your book's content in Jupyter Notebooks (`.ipynb`) or +in regular markdown files (`.md`), you'll write in the same flavor of markdown +called **MyST Markdown**. +This is a simple file to help you get started and show off some syntax. + +## What is MyST? + +MyST stands for "Markedly Structured Text". It +is a slight variation on a flavor of markdown called "CommonMark" markdown, +with small syntax extensions to allow you to write **roles** and **directives** +in the Sphinx ecosystem. + +For more about MyST, see [the MyST Markdown Overview](https://jupyterbook.org/content/myst.html). + +## Sample Roles and Directives + +Roles and directives are two of the most powerful tools in Jupyter Book. They +are kind of like functions, but written in a markup language. They both +serve a similar purpose, but **roles are written in one line**, whereas +**directives span many lines**. They both accept different kinds of inputs, +and what they do with those inputs depends on the specific role or directive +that is being called. + +Here is a "note" directive: + +```{note} +Here is a note +``` + +It will be rendered in a special box when you build your book. + +Here is an inline directive to refer to a document: {doc}`markdown-notebooks`. + + +## Citations + +You can also cite references that are stored in a `bibtex` file. For example, +the following syntax: `` {cite}`holdgraf_evidence_2014` `` will render like +this: {cite}`holdgraf_evidence_2014`. + +Moreover, you can insert a bibliography into your page with this syntax: +The `{bibliography}` directive must be used for all the `{cite}` roles to +render properly. +For example, if the references for your book are stored in `references.bib`, +then the bibliography is inserted with: + +```{bibliography} +``` + +## Learn more + +This is just a simple starter to get you started. +You can learn a lot more at [jupyterbook.org](https://jupyterbook.org). diff --git a/docs/jupyter_book/notebooks.ipynb b/docs/jupyter_book/notebooks.ipynb new file mode 100644 index 00000000..fdb7176c --- /dev/null +++ b/docs/jupyter_book/notebooks.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Content with notebooks\n", + "\n", + "You can also create content with Jupyter Notebooks. This means that you can include\n", + "code blocks and their outputs in your book.\n", + "\n", + "## Markdown + notebooks\n", + "\n", + "As it is markdown, you can embed images, HTML, etc into your posts!\n", + "\n", + "![](https://myst-parser.readthedocs.io/en/latest/_static/logo-wide.svg)\n", + "\n", + "You can also $add_{math}$ and\n", + "\n", + "$$\n", + "math^{blocks}\n", + "$$\n", + "\n", + "or\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "\\mbox{mean} la_{tex} \\\\ \\\\\n", + "math blocks\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "But make sure you \\$Escape \\$your \\$dollar signs \\$you want to keep!\n", + "\n", + "## MyST markdown\n", + "\n", + "MyST markdown works in Jupyter Notebooks as well. For more information about MyST markdown, check\n", + "out [the MyST guide in Jupyter Book](https://jupyterbook.org/content/myst.html),\n", + "or see [the MyST markdown documentation](https://myst-parser.readthedocs.io/en/latest/).\n", + "\n", + "## Code blocks and outputs\n", + "\n", + "Jupyter Book will also embed your code blocks and output in your book.\n", + "For example, here's some sample Matplotlib code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import rcParams, cycler\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "plt.ion()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fixing random state for reproducibility\n", + "np.random.seed(19680801)\n", + "\n", + "N = 10\n", + "data = [np.logspace(0, 1, 100) + np.random.randn(100) + ii for ii in range(N)]\n", + "data = np.array(data).T\n", + "cmap = plt.cm.coolwarm\n", + "rcParams['axes.prop_cycle'] = cycler(color=cmap(np.linspace(0, 1, N)))\n", + "\n", + "\n", + "from matplotlib.lines import Line2D\n", + "custom_lines = [Line2D([0], [0], color=cmap(0.), lw=4),\n", + " Line2D([0], [0], color=cmap(.5), lw=4),\n", + " Line2D([0], [0], color=cmap(1.), lw=4)]\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 5))\n", + "lines = ax.plot(data)\n", + "ax.legend(custom_lines, ['Cold', 'Medium', 'Hot']);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a lot more that you can do with outputs (such as including interactive outputs)\n", + "with your book. For more information about this, see [the Jupyter Book documentation](https://jupyterbook.org)" + ] + } + ], + "metadata": { + "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.8.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/jupyter_book/references.bib b/docs/jupyter_book/references.bib new file mode 100644 index 00000000..783ec6aa --- /dev/null +++ b/docs/jupyter_book/references.bib @@ -0,0 +1,56 @@ +--- +--- + +@inproceedings{holdgraf_evidence_2014, + address = {Brisbane, Australia, Australia}, + title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}}, + booktitle = {International {Conference} on {Cognitive} {Neuroscience}}, + publisher = {Frontiers in Neuroscience}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.}, + year = {2014} +} + +@article{holdgraf_rapid_2016, + title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility}, + volume = {7}, + issn = {2041-1723}, + url = {http://www.nature.com/doifinder/10.1038/ncomms13654}, + doi = {10.1038/ncomms13654}, + number = {May}, + journal = {Nature Communications}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.}, + year = {2016}, + pages = {13654}, + file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf} +} + +@inproceedings{holdgraf_portable_2017, + title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science}, + volume = {Part F1287}, + isbn = {978-1-4503-5272-7}, + doi = {10.1145/3093338.3093370}, + abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.}, + booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}}, + author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.}, + year = {2017}, + keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy} +} + +@article{holdgraf_encoding_2017, + title = {Encoding and decoding models in cognitive electrophysiology}, + volume = {11}, + issn = {16625137}, + doi = {10.3389/fnsys.2017.00061}, + abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.}, + journal = {Frontiers in Systems Neuroscience}, + author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.}, + year = {2017}, + keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials} +} + +@book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} +} diff --git a/docs/jupyter_book/requirements.txt b/docs/jupyter_book/requirements.txt new file mode 100644 index 00000000..7e821e45 --- /dev/null +++ b/docs/jupyter_book/requirements.txt @@ -0,0 +1,3 @@ +jupyter-book +matplotlib +numpy From 5304bfc777977c44756d3b7e74b680f418bcc73c Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:12:47 -0800 Subject: [PATCH 04/55] update to docsting for utils.py --- caustic/utils.py | 299 ++++++++++++++++++++++++++++++----------------- 1 file changed, 190 insertions(+), 109 deletions(-) diff --git a/caustic/utils.py b/caustic/utils.py index 6101c12d..60f41158 100644 --- a/caustic/utils.py +++ b/caustic/utils.py @@ -11,12 +11,17 @@ def flip_axis_ratio(q, phi): """ Makes the value of 'q' positive, then swaps x and y axes if 'q' is larger than 1. - Args: - q (Tensor): Tensor containing values to be processed. - phi (Tensor): Tensor containing the phi values for the orientation of the axes. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the processed 'q' and 'phi' Tensors. + Parameters + ---------- + q: Tensor + Tensor containing values to be processed. + phi: Tensor + Tensor containing the phi values for the orientation of the axes. + + Returns + ------- + Tuple[Tensor, Tensor] + Tuple containing the processed 'q' and 'phi' Tensors. """ q = q.abs() return torch.where(q > 1, 1 / q, q), torch.where(q > 1, phi + pi / 2, phi) @@ -26,15 +31,23 @@ def translate_rotate(x, y, x0, y0, phi: Optional[Tensor] = None): """ Translates and rotates the points (x, y) by subtracting (x0, y0) and applying rotation angle phi. - Args: - x (Tensor): Tensor containing the x-coordinates. - y (Tensor): Tensor containing the y-coordinates. - x0 (Tensor): Tensor containing the x-coordinate translation values. - y0 (Tensor): Tensor containing the y-coordinate translation values. - phi (Optional[Tensor], optional): Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the translated and rotated x and y coordinates. + Parameters + ---------- + x: Tensor + Tensor containing the x-coordinates. + y: Tensor + Tensor containing the y-coordinates. + x0: Tensor + Tensor containing the x-coordinate translation values. + y0: Tensor + Tensor containing the y-coordinate translation values. + phi: Optional[Tensor], optional) + Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. + + Returns + ------- + Tuple: [Tensor, Tensor] + Tuple containing the translated and rotated x and y coordinates. """ xt = x - x0 yt = y - y0 @@ -53,13 +66,19 @@ def derotate(vx, vy, phi: Optional[Tensor] = None): """ Applies inverse rotation to the velocity components (vx, vy) using the rotation angle phi. - Args: - vx (Tensor): Tensor containing the x-component of velocity. - vy (Tensor): Tensor containing the y-component of velocity. - phi (Optional[Tensor], optional): Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the derotated x and y components of velocity. + Parameters + ---------- + vx: Tensor + Tensor containing the x-component of velocity. + vy: Tensor + Tensor containing the y-component of velocity. + phi: Optional[Tensor], optional) + Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. + + Returns + ------- + Tuple: [Tensor, Tensor] + Tuple containing the derotated x and y components of velocity. """ if phi is None: return vx, vy @@ -73,13 +92,19 @@ def to_elliptical(x, y, q: Tensor): """ Converts Cartesian coordinates to elliptical coordinates. - Args: - x (Tensor): Tensor containing the x-coordinates. - y (Tensor): Tensor containing the y-coordinates. - q (Tensor): Tensor containing the elliptical parameters. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the x and y coordinates in elliptical form. + Parameters + ---------- + x: Tensor + Tensor containing the x-coordinates. + y: Tensor + Tensor containing the y-coordinates. + q: Tensor + Tensor containing the elliptical parameters. + + Returns + ------- + Tuple: Tensor, Tensor + Tuple containing the x and y coordinates in elliptical form. """ return x, y / q @@ -90,15 +115,23 @@ def get_meshgrid( """ Generates a 2D meshgrid based on the provided pixelscale and dimensions. - Args: - pixelscale (float): The scale of the meshgrid in each dimension. - nx (int): The number of grid points along the x-axis. - ny (int): The number of grid points along the y-axis. - device (torch.device, optional): The device on which to create the tensor. Defaults to None. - dtype (torch.dtype, optional): The desired data type of the tensor. Defaults to torch.float32. - - Returns: - Tuple[Tensor, Tensor]: The generated meshgrid as a tuple of Tensors. + Parameters + ---------- + pixelscale: float + The scale of the meshgrid in each dimension. + nx: int + The number of grid points along the x-axis. + ny: int + The number of grid points along the y-axis. + device: torch.device, optional + The device on which to create the tensor. Defaults to None. + dtype: torch.dtype, optional + The desired data type of the tensor. Defaults to torch.float32. + + Returns + ------- + Tuple: [Tensor, Tensor] + The generated meshgrid as a tuple of Tensors. """ xs = torch.linspace(-1, 1, nx, device=device, dtype=dtype) * pixelscale * (nx - 1) / 2 ys = torch.linspace(-1, 1, ny, device=device, dtype=dtype) * pixelscale * (ny - 1) / 2 @@ -109,12 +142,17 @@ def safe_divide(num, denom, places = 7): """ Safely divides two tensors, returning zero where the denominator is zero. - Args: - num (Tensor): The numerator tensor. - denom (Tensor): The denominator tensor. - - Returns: - Tensor: The result of the division, with zero where the denominator was zero. + Parameters + ---------- + num: Tensor + The numerator tensor. + denom: Tensor + The denominator tensor. + + Returns + ------- + Tensor + The result of the division, with zero where the denominator was zero. """ out = torch.zeros_like(num) where = denom != 0 @@ -126,11 +164,15 @@ def safe_log(x): """ Safely applies the logarithm to a tensor, returning zero where the tensor is zero. - Args: - x (Tensor): The input tensor. + Parameters + ---------- + x: Tensor + The input tensor. - Returns: - Tensor: The result of applying the logarithm, with zero where the input was zero. + Returns + ------- + Tensor + The result of applying the logarithm, with zero where the input was zero. """ out = torch.zeros_like(x) where = x != 0 @@ -142,11 +184,15 @@ def _h_poly(t): """Helper function to compute the 'h' polynomial matrix used in the cubic spline. - Args: - t (Tensor): A 1D tensor representing the normalized x values. + Parameters + ---------- + t: Tensor + A 1D tensor representing the normalized x values. - Returns: - Tensor: A 2D tensor of size (4, len(t)) representing the 'h' polynomial matrix. + Returns + ------- + Tensor + A 2D tensor of size (4, len(t)) representing the 'h' polynomial matrix. """ @@ -163,19 +209,26 @@ def interp1d(x: Tensor, y: Tensor, xs: Tensor, extend: str = "extrapolate") -> T """Compute the 1D cubic spline interpolation for the given data points using PyTorch. - Args: - x (Tensor): A 1D tensor representing the x-coordinates of the known data points. - y (Tensor): A 1D tensor representing the y-coordinates of the known data points. - xs (Tensor): A 1D tensor representing the x-coordinates of the positions where - the cubic spline function should be evaluated. - extend (str, optional): The method for handling extrapolation, either "const", "extrapolate", or "linear". - Default is "extrapolate". - "const": Use the value of the last known data point for extrapolation. - "linear": Use linear extrapolation based on the last two known data points. - "extrapolate": Use cubic extrapolation of data. - - Returns: - Tensor: A 1D tensor representing the interpolated values at the specified positions (xs). + Parameters + ---------- + x: Tensor + A 1D tensor representing the x-coordinates of the known data points. + y: Tensor + A 1D tensor representing the y-coordinates of the known data points. + xs: Tensor + A 1D tensor representing the x-coordinates of the positions where + the cubic spline function should be evaluated. + extend: (str, optional) + The method for handling extrapolation, either "const", "extrapolate", or "linear". + Default is "extrapolate". + "const": Use the value of the last known data point for extrapolation. + "linear": Use linear extrapolation based on the last two known data points. + "extrapolate": Use cubic extrapolation of data. + + Returns + ------- + Tensor + A 1D tensor representing the interpolated values at the specified positions (xs). """ m = (y[1:] - y[:-1]) / (x[1:] - x[:-1]) @@ -208,23 +261,37 @@ def interp2d( Interpolates a 2D image at specified coordinates. Similar to `torch.nn.functional.grid_sample` with `align_corners=False`. - Args: - im (Tensor): A 2D tensor representing the image. - x (Tensor): A 0D or 1D tensor of x coordinates at which to interpolate. - y (Tensor): A 0D or 1D tensor of y coordinates at which to interpolate. - method (str, optional): Interpolation method. Either 'nearest' or 'linear'. Defaults to 'linear'. - padding_mode (str, optional): Defines the padding mode when out-of-bound indices are encountered. - Either 'zeros' or 'extrapolate'. Defaults to 'zeros'. - - Raises: - ValueError: If `im` is not a 2D tensor. - ValueError: If `x` is not a 0D or 1D tensor. - ValueError: If `y` is not a 0D or 1D tensor. - ValueError: If `padding_mode` is not 'extrapolate' or 'zeros'. - ValueError: If `method` is not 'nearest' or 'linear'. - - Returns: - Tensor: Tensor with the same shape as `x` and `y` containing the interpolated values. + Parameters + ---------- + im: Tensor + A 2D tensor representing the image. + x: Tensor + A 0D or 1D tensor of x coordinates at which to interpolate. + y: Tensor + A 0D or 1D tensor of y coordinates at which to interpolate. + method: (str, optional) + Interpolation method. Either 'nearest' or 'linear'. Defaults to 'linear'. + padding_mode: (str, optional) + Defines the padding mode when out-of-bound indices are encountered. + Either 'zeros' or 'extrapolate'. Defaults to 'zeros'. + + Raises + ------ + ValueError + If `im` is not a 2D tensor. + ValueError + If `x` is not a 0D or 1D tensor. + ValueError + If `y` is not a 0D or 1D tensor. + ValueError + If `padding_mode` is not 'extrapolate' or 'zeros'. + ValueError + If `method` is not 'nearest' or 'linear'. + + Returns + ------- + Tensor + Tensor with the same shape as `x` and `y` containing the interpolated values. """ if im.ndim != 2: raise ValueError(f"im must be 2D (received {im.ndim}D tensor)") @@ -285,18 +352,27 @@ def vmap_n( Returns `func` transformed `depth` times by `vmap`, with the same arguments passed to `vmap` each time. - Args: - func (Callable): The function to transform. - depth (int, optional): The number of times to apply `torch.vmap`. Defaults to 1. - in_dims (Union[int, Tuple], optional): The dimensions to vectorize over in the input. Defaults to 0. - out_dims (Union[int, Tuple[int, ...]], optional): The dimensions to vectorize over in the output. Defaults to 0. - randomness (str, optional): How to handle randomness. Defaults to 'error'. - - Raises: - ValueError: If `depth` is less than 1. - - Returns: - Callable: The transformed function. + Parameters: + func: Callable + The function to transform. + depth: (int, optional) + The number of times to apply `torch.vmap`. Defaults to 1. + in_dims: (Union[int, Tuple], optional) + The dimensions to vectorize over in the input. Defaults to 0. + out_dims: (Union[int, Tuple[int, ...]], optional): + The dimensions to vectorize over in the output. Defaults to 0. + randomness: (str, optional) + How to handle randomness. Defaults to 'error'. + + Raises + ------ + ValueError + If `depth` is less than 1. + + Returns + ------- + Callable + The transformed function. TODO: test. """ @@ -314,12 +390,17 @@ def get_cluster_means(xs: Tensor, k: int): """ Computes cluster means using the k-means++ initialization algorithm. - Args: - xs (Tensor): A tensor of data points. - k (int): The number of clusters. - - Returns: - Tensor: A tensor of cluster means. + Parameters + ---------- + xs: Tensor + A tensor of data points. + k: int + The number of clusters. + + Returns + ------- + Tensor + A tensor of cluster means. """ b = len(xs) mean_idxs = [int(torch.randint(high=b, size=(), device=xs.device).item())] @@ -347,15 +428,15 @@ def _lm_step(f, X, Y, Cinv, L, Lup, Ldn, epsilon): # Forward fY = f(X) dY = Y - fY - + # Jacobian J = jacfwd(f)(X) J = J.to(dtype = X.dtype) chi2 = (dY @ Cinv @ dY).sum(-1) - + # Gradient grad = J.T @ Cinv @ dY - + # Hessian hess = J.T @ Cinv @ J hess_perturb = L * (torch.diag(hess) + 0.1*torch.eye(hess.shape[0])) @@ -363,7 +444,7 @@ def _lm_step(f, X, Y, Cinv, L, Lup, Ldn, epsilon): # Step h = torch.linalg.solve(hess, grad) - + # New chi^2 fYnew = f(X + h) dYnew = Y - fYnew @@ -397,14 +478,14 @@ def batch_lm( ): B, Din = X.shape B, Dout = Y.shape - + if len(X) != len(Y): raise ValueError("x and y must having matching batch dimension") if C is None: C = torch.eye(Dout).repeat(B, 1, 1) Cinv = torch.linalg.inv(C) - + v_lm_step = torch.vmap(partial(_lm_step, lambda x: f(x, *f_args, **f_kwargs))) L = L * torch.ones(B) Lup = L_up * torch.ones(B) @@ -415,11 +496,11 @@ def batch_lm( if torch.all((Xnew - X).abs() < stopping) and torch.sum(L < 1e-2).item() > B/3: break X = Xnew - + return X, L, C def gaussian(pixelscale, nx, ny, sigma, upsample = 1, dtype = torch.float32, device = None): - + X, Y = np.meshgrid( np.linspace(-(nx*upsample - 1) * pixelscale / 2, (nx*upsample - 1) * pixelscale / 2, nx*upsample), np.linspace(-(ny*upsample - 1) * pixelscale / 2, (ny*upsample - 1) * pixelscale / 2, ny*upsample), @@ -427,7 +508,7 @@ def gaussian(pixelscale, nx, ny, sigma, upsample = 1, dtype = torch.float32, dev ) Z = np.exp(- 0.5 * (X**2 + Y**2) / sigma**2) - + Z = Z.reshape(ny, upsample, nx, upsample).sum(axis=(1, 3)) return torch.tensor(Z / np.sum(Z), dtype = dtype, device = device) From db7dc4319475e3ed8ad49a537c3bb079c28ad663 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:18:44 -0800 Subject: [PATCH 05/55] change to numpy docstring for parametrized.py --- caustic/parametrized.py | 137 ++++++++++++++++++++++++---------------- 1 file changed, 83 insertions(+), 54 deletions(-) diff --git a/caustic/parametrized.py b/caustic/parametrized.py index d0940c6a..904996b2 100644 --- a/caustic/parametrized.py +++ b/caustic/parametrized.py @@ -35,14 +35,22 @@ class Parametrized: - Attributes can be Params, Parametrized, tensor buffers or just normal attributes. - Need to make sure an attribute of one of those types isn't rebound to be of a different type. - Attributes: - name (str): The name of the Parametrized object. Default to class name. - parents (NestedNamespaceDict): Nested dictionary of parent Parametrized objects (higher level, more abstract modules). - params (OrderedDict[str, Parameter]): Dictionary of parameters. - childs NestedNamespaceDict: Nested dictionary of childs Parametrized objects (lower level, more specialized modules). - dynamic_size (int): Size of dynamic parameters. - n_dynamic (int): Number of dynamic parameters. - n_static (int): Number of static parameters. + Attributes + ---------- + name: str + The name of the Parametrized object. Default to class name. + parents: NestedNamespaceDict + Nested dictionary of parent Parametrized objects (higher level, more abstract modules). + params: OrderedDict[str, Parameter] + Dictionary of parameters. + childs: NestedNamespaceDict + Nested dictionary of childs Parametrized objects (lower level, more specialized modules). + dynamic_size: int + Size of dynamic parameters. + n_dynamic: int + Number of dynamic parameters. + n_static: int + Number of static parameters. """ def __init__(self, name: str = None): @@ -56,10 +64,10 @@ def __init__(self, name: str = None): self._params: OrderedDict[str, Parameter] = NamespaceDict() self._childs: OrderedDict[str, Parametrized] = NamespaceDict() self._module_key_map = {} - + def _default_name(self): return re.search("([A-Z])\w+", str(self.__class__)).group() - + def __getattribute__(self, key): try: return super().__getattribute__(key) @@ -82,7 +90,7 @@ def __setattr__(self, key, value): elif isinstance(value, Parametrized): # Update map from attribute key to module name for __getattribute__ method self._module_key_map[value.name] = key - self.add_parametrized(value, set_attr=False) + self.add_parametrized(value, set_attr=False) # set attr only to user defined key, not module name (self.{module.name} is still accessible, see __getattribute__ method) super().__setattr__(key, value) else: @@ -93,7 +101,7 @@ def __setattr__(self, key, value): @property def name(self) -> str: return self._name - + @name.setter def name(self, new_name: str): check_valid_name(new_name) @@ -122,7 +130,7 @@ def _generate_unique_name(name, module_names): while f"{name}_{i}" in module_names: i += 1 return f"{name}_{i}" - + def add_parametrized(self, p: "Parametrized", set_attr=True): """ Add a child to this module, and create edges for the DAG @@ -149,14 +157,18 @@ def add_param( """ Stores a parameter in the _params dictionary and records its size. - Args: - name (str): The name of the parameter. - value (Optional[Tensor], optional): The value of the parameter. Defaults to None. - shape (Optional[tuple[int, ...]], optional): The shape of the parameter. Defaults to an empty tuple. + Parameters + ---------- + name: str + The name of the parameter. + value: (Optional[Tensor], optional) + The value of the parameter. Defaults to None. + shape: (Optional[tuple[int, ...]], optional) + The shape of the parameter. Defaults to an empty tuple. """ self._params[name] = Parameter(value, shape) # __setattr__ inside add_param to catch all uses of this method - super().__setattr__(name, self._params[name]) + super().__setattr__(name, self._params[name]) @property def n_dynamic(self) -> int: @@ -180,21 +192,28 @@ def pack( ) -> Packed: """ Converts a list or tensor into a dict that can subsequently be unpacked - into arguments to this component and its childs. Also, add a batch dimension + into arguments to this component and its childs. Also, add a batch dimension to each Tensor without such a dimension. - Args: - x (Union[list[Tensor], dict[str, Union[list[Tensor], Tensor, dict[str, Tensor]]], Tensor): - The input to be packed. Can be a list of tensors, a dictionary of tensors, or a single tensor. - - Returns: - Packed: The packed input, and whether or not the input was batched. - - Raises: - ValueError: If the input is not a list, dictionary, or tensor. - ValueError: If the input is a dictionary and some keys are missing. - ValueError: If the number of dynamic arguments does not match the expected number. - ValueError: If the input is a tensor and the shape does not match the expected shape. + Parameters: + x: (Union[list[Tensor], dict[str, Union[list[Tensor], Tensor, dict[str, Tensor]]], Tensor) + The input to be packed. Can be a list of tensors, a dictionary of tensors, or a single tensor. + + Returns + ------- + Packed + The packed input, and whether or not the input was batched. + + Raises + ------ + ValueError + If the input is not a list, dictionary, or tensor. + ValueError + If the input is a dictionary and some keys are missing. + ValueError + If the number of dynamic arguments does not match the expected number. + ValueError + If the input is a tensor and the shape does not match the expected shape. """ if isinstance(x, (dict, Packed)): missing_names = [name for name in self.params.dynamic.keys() if name not in x] @@ -203,8 +222,8 @@ def pack( # TODO: check structure! return Packed(x) - - + + elif isinstance(x, (list, tuple)): n_passed = len(x) n_dynamic_params = len(self.params.dynamic.flatten()) @@ -217,17 +236,17 @@ def pack( cur_offset += module.n_dynamic elif n_passed == n_dynamic_modules: for i, name in enumerate(self.dynamic_modules.keys()): - x_repacked[name] = x[i] + x_repacked[name] = x[i] else: raise ValueError( f"{n_passed} dynamic args were passed, but {n_dynamic_params} parameters or " f"{n_dynamic_modules} Tensor (1 per dynamic module) are required" ) return Packed(x_repacked) - + elif isinstance(x, Tensor): n_passed = x.shape[-1] - n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) + n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) if n_passed != n_expected: # TODO: give component and arg names raise ValueError( @@ -250,20 +269,25 @@ def unpack( ) -> list[Tensor]: """ Unpacks a dict of kwargs, list of args or flattened vector of args to retrieve - this object's static and dynamic parameters. + this object's static and dynamic parameters. - Args: - x (Optional[dict[str, Union[list[Tensor], dict[str, Tensor], Tensor]]]): - The packed object to be unpacked. + Parameters: + x: (Optional[dict[str, Union[list[Tensor], dict[str, Tensor], Tensor]]]) + The packed object to be unpacked. - Returns: - list[Tensor]: Unpacked static and dynamic parameters of the object. Note that + Returns + ------- + list[Tensor] + Unpacked static and dynamic parameters of the object. Note that parameters will have an added batch dimension from the pack method. - Raises: - ValueError: If the input is not a dict, list, tuple or tensor. - ValueError: If the argument type is invalid. It must be a dict containing key {self.name} - and value containing args as list or flattened tensor, or kwargs. + Raises + ------ + ValueError + If the input is not a dict, list, tuple or tensor. + ValueError + If the argument type is invalid. It must be a dict containing key {self.name} + and value containing args as list or flattened tensor, or kwargs. """ # Check if module has dynamic parameters if self.module_params.dynamic: @@ -308,7 +332,7 @@ def module_params(self) -> NestedNamespaceDict: else: dynamic[name] = param return NestedNamespaceDict([("static", static), ("dynamic", dynamic)]) - + @property def params(self) -> NestedNamespaceDict: # todo make this an ordinary dict and reorder at the end. @@ -374,12 +398,17 @@ def get_graph( """ Returns a graph representation of the object and its parameters. - Args: - show_dynamic_params (bool, optional): If true, the dynamic parameters are shown in the graph. Defaults to False. - show_static_params (bool, optional): If true, the static parameters are shown in the graph. Defaults to False. - - Returns: - graphviz.Digraph: The graph representation of the object. + Parameters + ---------- + show_dynamic_params: (bool, optional) + If true, the dynamic parameters are shown in the graph. Defaults to False. + show_static_params: (bool, optional) + If true, the static parameters are shown in the graph. Defaults to False. + + Returns + ------- + graphviz.Digraph + The graph representation of the object. """ import graphviz @@ -436,7 +465,7 @@ def wrapped(self, *args, **kwargs): leading_args.append(kwargs.pop(param)) elif args: leading_args.append(args.pop(0)) - + # Collect module parameters passed in argument (dynamic or otherwise) if args and isinstance(args[0], Packed): # Case 1: Params is already Packed (or no params were passed) From d71221db9c6212b64a7d39e0bf8f355f48569427 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:34:33 -0800 Subject: [PATCH 06/55] changes to numpy docstring --- caustic/cosmology.py | 265 +++++++++++++++++++++++++------------- caustic/namespace_dict.py | 34 ++--- caustic/parameter.py | 23 ++-- 3 files changed, 205 insertions(+), 117 deletions(-) diff --git a/caustic/cosmology.py b/caustic/cosmology.py index 502218ec..80dc6825 100644 --- a/caustic/cosmology.py +++ b/caustic/cosmology.py @@ -42,20 +42,27 @@ class Cosmology(Parametrized): This class provides an interface for cosmological computations used in lensing such as comoving distance and critical surface density. - Units: - - Distance: Mpc - - Mass: solar mass - - Attributes: - name (str): Name of the cosmological model. + Units + ----- + Distance + Mpc + Mass + solar mass + + Attributes + ---------- + name: str + Name of the cosmological model. """ def __init__(self, name: str = None): """ Initialize the Cosmology. - Args: - name (str): Name of the cosmological model. + Parameters + ---------- + name: str + Name of the cosmological model. """ super().__init__(name) @@ -64,12 +71,17 @@ def critical_density(self, z: Tensor, params: Optional["Packed"] = None) -> Tens """ Compute the critical density at redshift z. - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The critical density at each redshift. + Parameters + ---------- + z: Tensor + The redshifts. + params: Packed, optional + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The critical density at each redshift. """ ... @@ -79,12 +91,17 @@ def comoving_distance(self, z: Tensor, *args, params: Optional["Packed"] = None) """ Compute the comoving distance to redshift z. - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The comoving distance to each redshift. + Parameters + ---------- + z: Tensor + The redshifts. + params: (Packed, optional0 + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The comoving distance to each redshift. """ ... @@ -94,12 +111,17 @@ def transverse_comoving_distance(self, z: Tensor, *args, params: Optional["Packe """ Compute the transverse comoving distance to redshift z (Mpc). - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The transverse comoving distance to each redshift in Mpc. + Parameters + ---------- + z: Tensor + The redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The transverse comoving distance to each redshift in Mpc. """ ... @@ -110,13 +132,19 @@ def comoving_distance_z1z2( """ Compute the comoving distance between two redshifts. - Args: - z1 (Tensor): The starting redshifts. - z2 (Tensor): The ending redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The comoving distance between each pair of redshifts. + Parameters + ---------- + z1: Tensor + The starting redshifts. + z2: Tensor + The ending redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The comoving distance between each pair of redshifts. """ return self.comoving_distance(z2, params) - self.comoving_distance(z1, params) @@ -127,13 +155,18 @@ def transverse_comoving_distance_z1z2( """ Compute the transverse comoving distance between two redshifts (Mpc). - Args: - z1 (Tensor): The starting redshifts. - z2 (Tensor): The ending redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The transverse comoving distance between each pair of redshifts in Mpc. + Parameters: + z1: Tensor + The starting redshifts. + z2: Tensor + The ending redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The transverse comoving distance between each pair of redshifts in Mpc. """ return self.transverse_comoving_distance(z2, params) - self.transverse_comoving_distance(z1, params) @@ -142,12 +175,17 @@ def angular_diameter_distance(self, z: Tensor, *args, params: Optional["Packed"] """ Compute the angular diameter distance to redshift z. - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The angular diameter distance to each redshift. + Parameters: + ----------- + z: Tensor + The redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The angular diameter distance to each redshift. """ return self.comoving_distance(z, params) / (1 + z) @@ -158,13 +196,19 @@ def angular_diameter_distance_z1z2( """ Compute the angular diameter distance between two redshifts. - Args: - z1 (Tensor): The starting redshifts. - z2 (Tensor): The ending redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The angular diameter distance between each pair of redshifts. + Parameters + ---------- + z1: Tensor + The starting redshifts. + z2: Tensor + The ending redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The angular diameter distance between each pair of redshifts. """ return self.comoving_distance_z1z2(z1, z2, params) / (1 + z2) @@ -175,13 +219,19 @@ def time_delay_distance( """ Compute the time delay distance between lens and source planes. - Args: - z_l (Tensor): The lens redshifts. - z_s (Tensor): The source redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The time delay distance for each pair of lens and source redshifts. + Parameters + ---------- + z_l: Tensor + The lens redshifts. + z_s: Tensor + The source redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The time delay distance for each pair of lens and source redshifts. """ d_l = self.angular_diameter_distance(z_l, params) d_s = self.angular_diameter_distance(z_s, params) @@ -195,13 +245,19 @@ def critical_surface_density( """ Compute the critical surface density between lens and source planes. - Args: - z_l (Tensor): The lens redshifts. - z_s (Tensor): The source redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The critical surface density for each pair of lens and source redshifts. + Parameters + ---------- + z_l: Tensor + The lens redshifts. + z_s: Tensor + The source redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The critical surface density for each pair of lens and source redshifts. """ d_l = self.angular_diameter_distance(z_l, params) d_s = self.angular_diameter_distance(z_s, params) @@ -224,11 +280,16 @@ def __init__( """ Initialize a new instance of the FlatLambdaCDM class. - Args: - name (str): Name of the cosmology. - h0 (Optional[Tensor]): Hubble constant over 100. Default is h0_default. - critical_density_0 (Optional[Tensor]): Critical density at z=0. Default is critical_density_0_default. - Om0 (Optional[Tensor]): Matter density parameter at z=0. Default is Om0_default. + Parameters + ---------- + name: str + Name of the cosmology. + h0: Optional[Tensor] + Hubble constant over 100. Default is h0_default. + critical_density_0: (Optional[Tensor]) + Critical density at z=0. Default is critical_density_0_default. + Om0: Optional[Tensor] + Matter density parameter at z=0. Default is Om0_default. """ super().__init__(name) @@ -242,7 +303,7 @@ def __init__( self._comoving_distance_helper_y_grid = _comoving_distance_helper_y_grid.to( dtype=torch.float32 ) - + def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None): super().to(device, dtype) self._comoving_distance_helper_y_grid = self._comoving_distance_helper_y_grid.to(device, dtype) @@ -252,11 +313,15 @@ def hubble_distance(self, h0): """ Calculate the Hubble distance. - Args: - h0 (Tensor): Hubble constant. + Parameters + ---------- + h0: Tensor + Hubble constant. - Returns: - Tensor: Hubble distance. + Returns + ------- + Tensor + Hubble distance. """ return c_Mpc_s / (100 * km_to_Mpc) / h0 @@ -265,12 +330,17 @@ def critical_density(self, z: Tensor, h0, central_critical_density, Om0, *args, """ Calculate the critical density at redshift z. - Args: - z (Tensor): Redshift. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - torch.Tensor: Critical density at redshift z. + Parameters + ---------- + z: Tensor + Redshift. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + torch.Tensor + Critical density at redshift z. """ Ode0 = 1 - Om0 return central_critical_density * (Om0 * (1 + z) ** 3 + Ode0) @@ -280,11 +350,15 @@ def _comoving_distance_helper(self, x: Tensor, *args, params: Optional["Packed"] """ Helper method for computing comoving distances. - Args: - x (Tensor): Input tensor. + Parameters + ---------- + x: Tensor + Input tensor. - Returns: - Tensor: Computed comoving distances. + Returns + ------- + Tensor + Computed comoving distances. """ return interp1d( self._comoving_distance_helper_x_grid, @@ -297,12 +371,17 @@ def comoving_distance(self, z: Tensor, h0, central_critical_density, Om0, *args, """ Calculate the comoving distance to redshift z. - Args: - z (Tensor): Redshift. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: Comoving distance to redshift z. + Parameters + ---------- + z: Tensor + Redshift. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + Comoving distance to redshift z. """ Ode0 = 1 - Om0 ratio = (Om0 / Ode0) ** (1 / 3) diff --git a/caustic/namespace_dict.py b/caustic/namespace_dict.py index 44e10738..3a77b71b 100644 --- a/caustic/namespace_dict.py +++ b/caustic/namespace_dict.py @@ -35,9 +35,11 @@ class _NestedNamespaceDict(NamespaceDict): def flatten(self) -> NamespaceDict: """ Flatten the nested dictionary into a NamespaceDict - - Returns: - NamespaceDict: Flattened dictionary as a NamespaceDict + + Returns + ------- + NamespaceDict + Flattened dictionary as a NamespaceDict """ flattened_dict = NamespaceDict() def _flatten_dict(dictionary, parent_key=""): @@ -52,11 +54,13 @@ def _flatten_dict(dictionary, parent_key=""): def collapse(self) -> NamespaceDict: """ - Flatten the nested dictionary and collapse keys into the first level + Flatten the nested dictionary and collapse keys into the first level of the NamespaceDict - - Returns: - NamespaceDict: Flattened dictionary as a NamespaceDict + + Returns + ------- + NamespaceDict + Flattened dictionary as a NamespaceDict """ flattened_dict = NamespaceDict() def _flatten_dict(dictionary): @@ -91,7 +95,7 @@ def __setattr__(self, key, value): # Hide the private keys from common usage def keys(self): return [key for key in super().keys() if not key.startswith("_")] - + def items(self): for key, value in super().items(): if not key.startswith('_'): @@ -99,7 +103,7 @@ def items(self): def values(self): return [v for k, v in super().items() if not k.startswith("_")] - + def __len__(self): # make sure hidden keys don't count in the length of the object return len(self.keys()) @@ -108,7 +112,7 @@ def __len__(self): class NestedNamespaceDict(_NestedNamespaceDict): """ Example usage - ```python + ```python nested_namespace = NestedNamespaceDict() nested_namespace.foo = 'Hello' nested_namespace.bar = {'baz': 'World'} @@ -119,30 +123,30 @@ class NestedNamespaceDict(_NestedNamespaceDict): print(nested_namespace) # Output: # {'foo': 'Hello', 'bar': {'baz': 'World', 'qux': 42 }} - + #============================== # Flattened key access #============================== print(nested_dict['foo']) # Output: Hello print(nested_dict['bar.baz']) # Output: World print(nested_dict['bar.qux']) # Output: 42 - + #============================== # Nested namespace access #============================== print(nested_dict.bar.qux) # Output: 42 - + #============================== # Flatten and collapse method #============================== print(nested_dict.flatten()) # Output: # {'foo': 'Hello', 'bar.baz': 'World', 'bar.qux': 42} - + print(nested_dict.collapse() # Output: # {'foo': 'Hello', 'baz': 'World', 'qux': 42} - + """ def __getattr__(self, key): if key in self: diff --git a/caustic/parameter.py b/caustic/parameter.py index 4d3acbd0..c42a8310 100644 --- a/caustic/parameter.py +++ b/caustic/parameter.py @@ -13,9 +13,12 @@ class Parameter: A static parameter has a fixed value, while a dynamic parameter must be passed in each time it's required. - Attributes: - value (Optional[Tensor]): The value of the parameter. - shape (tuple[int, ...]): The shape of the parameter. + Attributes + ---------- + value: (Optional[Tensor]) + The value of the parameter. + shape: (tuple[int, ...]) + The shape of the parameter. """ def __init__( @@ -45,7 +48,7 @@ def dynamic(self) -> bool: @property def value(self) -> Optional[Tensor]: return self._value - + @value.setter def value(self, value: Union[None, Tensor, float]): if value is not None: @@ -54,7 +57,7 @@ def value(self, value: Union[None, Tensor, float]): raise ValueError(f"Cannot set Parameter value with a different shape. Received {value.shape}, expected {self.shape}") self._value = value self._dtype = None if value is None else value.dtype - + @property def dtype(self): return self._dtype @@ -62,7 +65,7 @@ def dtype(self): @property def shape(self) -> tuple[int, ...]: return self._shape - + def set_static(self): self.value = None @@ -70,9 +73,11 @@ def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] """ Moves and/or casts the values of the parameter. - Args: - device (Optional[torch.device], optional): The device to move the values to. Defaults to None. - dtype (Optional[torch.dtype], optional): The desired data type. Defaults to None. + Parameters: + device: (Optional[torch.device], optional) + The device to move the values to. Defaults to None. + dtype: (Optional[torch.dtype], optional) + The desired data type. Defaults to None. """ if self.static: self.value = self._value.to(device=device, dtype=dtype) From c218ad3caf9b530be5357f0ebbc20bd41d00e7ca Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:39:06 -0800 Subject: [PATCH 07/55] change to numpy docstings for sims --- caustic/sims/lens_source.py | 79 ++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/caustic/sims/lens_source.py b/caustic/sims/lens_source.py index aa57af60..93c251c9 100644 --- a/caustic/sims/lens_source.py +++ b/caustic/sims/lens_source.py @@ -11,7 +11,7 @@ class Lens_Source(Simulator): """Lens image of a source. - + Striaghtforward simulator to sample a lensed image of a source object. Constructs a sampling grid internally based on the pixelscale and gridding parameters. It can automatically upscale @@ -28,23 +28,35 @@ class Lens_Source(Simulator): lens = caustic.lenses.SIS(cosmology = cosmo, x0 = 0., y0 = 0., th_ein = 1.) source = caustic.sources.Sersic(x0 = 0., y0 = 0., q = 0.5, phi = 0.4, n = 2., Re = 1., Ie = 1.) sim = caustic.sims.Lens_Source(lens, source, pixelscale = 0.05, gridx = 100, gridy = 100, upsample_factor = 2, z_s = 1.) - + img = sim() plt.imshow(img, origin = "lower") plt.show() - Attributes: - lens: caustic lens mass model object - source: caustic light object which defines the background source - pixelscale: pixelscale of the sampling grid. - pixels_x: number of pixels on the x-axis for the sampling grid - lens_light (optional): caustic light object which defines the lensing object's light - psf (optional): An image to convolve with the scene. Note that if ``upsample_factor > 1`` the psf must also be at the higher resolution. - pixels_y (optional): number of pixels on the y-axis for the sampling grid. If left as ``None`` then this will simply be equal to ``gridx`` - upsample_factor (default 1): Amount of upsampling to model the image. For example ``upsample_factor = 2`` indicates that the image will be sampled at double the resolution then summed back to the original resolution (given by pixelscale and gridx/y). - psf_pad (default True): If convolving the PSF it is important to sample the model in a larger FOV equal to half the PSF size in order to account for light that scatters from outside the requested FOV inwards. Internally this padding will be added before sampling, then cropped off before returning the final image to the user. - z_s (optional): redshift of the source - name (default "sim"): a name for this simulator in the parameter DAG. + Attributes + ---------- + lens + caustic lens mass model object + source + caustic light object which defines the background source + pixelscale: float + pixelscale of the sampling grid. + pixels_x: int + number of pixels on the x-axis for the sampling grid + lens_light: (optional) + caustic light object which defines the lensing object's light + psf: (optional) + An image to convolve with the scene. Note that if ``upsample_factor > 1`` the psf must also be at the higher resolution. + pixels_y: Optional[int] + number of pixels on the y-axis for the sampling grid. If left as ``None`` then this will simply be equal to ``gridx`` + upsample_factor (default 1) + Amount of upsampling to model the image. For example ``upsample_factor = 2`` indicates that the image will be sampled at double the resolution then summed back to the original resolution (given by pixelscale and gridx/y). + psf_pad: Boolean(default True) + If convolving the PSF it is important to sample the model in a larger FOV equal to half the PSF size in order to account for light that scatters from outside the requested FOV inwards. Internally this padding will be added before sampling, then cropped off before returning the final image to the user. + z_s: optional + redshift of the source + name: string (default "sim") + a name for this simulator in the parameter DAG. """ def __init__( @@ -95,8 +107,8 @@ def __init__( self.grid = torch.meshgrid(tx, ty, indexing = "xy") if self.psf is not None: - self.psf_fft = self._fft2_padded(self.psf) - + self.psf_fft = self._fft2_padded(self.psf) + def _fft2_padded(self, x): """ Compute the 2D Fast Fourier Transform (FFT) of a tensor with zero-padding. @@ -110,28 +122,41 @@ def _fft2_padded(self, x): npix = copy(self.n_pix) npix = (next_fast_len(npix[0]), next_fast_len(npix[1])) self._s = npix - + return torch.fft.rfft2(x, self._s) - + def _unpad_fft(self, x): """ Remove padding from the result of a 2D FFT. - Args: - x (Tensor): The input tensor with padding. + Parameters + --------- + x: Tensor + The input tensor with padding. - Returns: - Tensor: The input tensor without padding. + Returns + ------- + Tensor + The input tensor without padding. """ return torch.roll(x, (-self.psf_pad[0],-self.psf_pad[1]), dims = (-2,-1))[..., :self.n_pix[0], :self.n_pix[1]] def forward(self, params, source_light=True, lens_light=True, lens_source=True, psf_convolve=True): """ - params: Packed object - source_light: when true the source light will be sampled - lens_light: when true the lens light will be sampled - lens_source: when true, the source light model will be lensed by the lens mass distribution - psf_convolve: when true the image will be convolved with the psf + forward function + + Parameters + ---------- + params: + Packed object + source_light: boolean + when true the source light will be sampled + lens_light: boolean + when true the lens light will be sampled + lens_source: boolean + when true, the source light model will be lensed by the lens mass distribution + psf_convolve: boolean + when true the image will be convolved with the psf """ z_s, = self.unpack(params) From 7c3905f5a08bf2d7c92805b2fbb882c6c7e58eb3 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:50:09 -0800 Subject: [PATCH 08/55] change to numpy docstring for data dir --- caustic/data/hdf5dataset.py | 16 ++++-- caustic/data/probes.py | 5 +- caustic/light/base.py | 43 ++++++++------ caustic/light/sersic.py | 110 ++++++++++++++++++++++-------------- 4 files changed, 109 insertions(+), 65 deletions(-) diff --git a/caustic/data/hdf5dataset.py b/caustic/data/hdf5dataset.py index 02236973..11c43311 100644 --- a/caustic/data/hdf5dataset.py +++ b/caustic/data/hdf5dataset.py @@ -22,11 +22,17 @@ def __init__( dtypes: Union[Dict[str, torch.dtype], torch.dtype] = torch.float32, ): """ - Args: - path: location of dataset. - keys: dataset keys to read. - dtypes: either a numpy datatype to which the items will be converted - or a dictionary specifying the datatype corresponding to each key. + Parameters + ---------- + path: string + location of dataset. + keys: List[str] + dataset keys to read. + device: torch.deviec + the device for torch + dtypes: torch.dtype + either a numpy datatype to which the items will be converted + or a dictionary specifying the datatype corresponding to each key. """ super().__init__() self.keys = keys diff --git a/caustic/data/probes.py b/caustic/data/probes.py index def03ef9..05df1304 100644 --- a/caustic/data/probes.py +++ b/caustic/data/probes.py @@ -24,6 +24,9 @@ def __len__(self): def __getitem__(self, i: Union[int, slice]) -> Tensor: """ - Returns image `i` with channel as first dimension. + Returns + ------- + Tensor + image `i` with channel as first dimension. """ return self.ds[i][self.key].movedim(-1, 0) diff --git a/caustic/light/base.py b/caustic/light/base.py index 40e9da3e..0e93c215 100644 --- a/caustic/light/base.py +++ b/caustic/light/base.py @@ -10,12 +10,12 @@ class Source(Parametrized): """ - This is an abstract base class used to represent a source in a strong gravitational lensing system. - It provides the basic structure and required methods that any derived source class should implement. - The Source class inherits from the Parametrized class, implying that it contains parameters that can + This is an abstract base class used to represent a source in a strong gravitational lensing system. + It provides the basic structure and required methods that any derived source class should implement. + The Source class inherits from the Parametrized class, implying that it contains parameters that can be optimized or manipulated. - - The class introduces one abstract method, `brightness`, that must be implemented in any concrete + + The class introduces one abstract method, `brightness`, that must be implemented in any concrete subclass. This method calculates the brightness of the source at given coordinates. """ @abstractmethod @@ -24,24 +24,31 @@ def brightness( self, x: Tensor, y: Tensor, *args, params: Optional["Packed"] = None, **kwargs ) -> Tensor: """ - Abstract method that calculates the brightness of the source at the given coordinates. + Abstract method that calculates the brightness of the source at the given coordinates. This method is expected to be implemented in any class that derives from Source. - - Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. + + Parameters + ---------- + x: Tensor + The x-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. + + y: Tensor + The y-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - - params (Packed, optional): Dynamic parameter container that might be required to calculate - the brightness. The exact contents will depend on the specific implementation in derived classes. - Returns: - Tensor: The brightness of the source at the given coordinate(s). The exact form of the output + params: (Packed, optional) + Dynamic parameter container that might be required to calculate + the brightness. The exact contents will depend on the specific implementation in derived classes. + + Returns + ------- + Tensor + The brightness of the source at the given coordinate(s). The exact form of the output will depend on the specific implementation in the derived class. - - Note: + + Note + ----- This method must be overridden in any class that inherits from `Source`. """ ... diff --git a/caustic/light/sersic.py b/caustic/light/sersic.py index e35ea8f1..05956188 100644 --- a/caustic/light/sersic.py +++ b/caustic/light/sersic.py @@ -11,22 +11,32 @@ class Sersic(Source): """ - `Sersic` is a subclass of the abstract class `Source`. It represents a source in a strong - gravitational lensing system that follows a Sersic profile, a mathematical function that describes + `Sersic` is a subclass of the abstract class `Source`. It represents a source in a strong + gravitational lensing system that follows a Sersic profile, a mathematical function that describes how the intensity I of a galaxy varies with distance r from its center. - + The Sersic profile is often used to describe elliptical galaxies and spiral galaxies' bulges. - Attributes: - x0 (Optional[Tensor]): The x-coordinate of the Sersic source's center. - y0 (Optional[Tensor]): The y-coordinate of the Sersic source's center. - q (Optional[Tensor]): The axis ratio of the Sersic source. - phi (Optional[Tensor]): The orientation of the Sersic source (position angle). - n (Optional[Tensor]): The Sersic index, which describes the degree of concentration of the source. - Re (Optional[Tensor]): The scale length of the Sersic source. - Ie (Optional[Tensor]): The intensity at the effective radius. - s (float): A small constant for numerical stability. - lenstronomy_k_mode (bool): A flag indicating whether to use lenstronomy to compute the value of k. + Attributes + ----------- + x0: Optional[Tensor] + The x-coordinate of the Sersic source's center. + y0: Optional[Tensor] + The y-coordinate of the Sersic source's center. + q: Optional[Tensor] + The axis ratio of the Sersic source. + phi: Optional[Tensor] + The orientation of the Sersic source (position angle). + n: Optional[Tensor] + The Sersic index, which describes the degree of concentration of the source. + Re: Optional[Tensor] + The scale length of the Sersic source. + Ie: Optional[Tensor] + The intensity at the effective radius. + s: float + A small constant for numerical stability. + lenstronomy_k_mode: bool + A flag indicating whether to use lenstronomy to compute the value of k. """ def __init__( self, @@ -42,19 +52,30 @@ def __init__( name: str = None, ): """ - Constructs the `Sersic` object with the given parameters. + Constructs the `Sersic` object with the given parameters. - Args: - name (str): The name of the source. - x0 (Optional[Tensor]): The x-coordinate of the Sersic source's center. - y0 (Optional[Tensor]): The y-coordinate of the Sersic source's center. - q (Optional[Tensor]): The axis ratio of the Sersic source. - phi (Optional[Tensor]): The orientation of the Sersic source. - n (Optional[Tensor]): The Sersic index, which describes the degree of concentration of the source. - Re (Optional[Tensor]): The scale length of the Sersic source. - Ie (Optional[Tensor]): The intensity at the effective radius. - s (float): A small constant for numerical stability. - use_lenstronomy_k (bool): A flag indicating whether to use lenstronomy to compute the value of k. + Parameters + ---------- + name: str + The name of the source. + x0: Optional[Tensor] + The x-coordinate of the Sersic source's center. + y0: Optional[Tensor] + The y-coordinate of the Sersic source's center. + q: Optional[Tensor]) + The axis ratio of the Sersic source. + phi: Optional[Tensor] + The orientation of the Sersic source. + n: Optional[Tensor] + The Sersic index, which describes the degree of concentration of the source. + Re: Optional[Tensor] + The scale length of the Sersic source. + Ie: Optional[Tensor] + The intensity at the effective radius. + s: float + A small constant for numerical stability. + use_lenstronomy_k: bool + A flag indicating whether to use lenstronomy to compute the value of k. """ super().__init__(name=name) self.add_param("x0", x0) @@ -71,27 +92,34 @@ def __init__( @unpack(2) def brightness(self, x, y, x0, y0, q, phi, n, Re, Ie, *args, params: Optional["Packed"] = None, **kwargs): """ - Implements the `brightness` method for `Sersic`. The brightness at a given point is + Implements the `brightness` method for `Sersic`. The brightness at a given point is determined by the Sersic profile formula. - Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + x: Tensor + The x-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + y: Tensor + The y-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tensor: The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. + Returns + ------- + Tensor + The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. - Notes: - The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), - where Ie is the intensity at the effective radius r_e, n is the Sersic index - that describes the concentration of the source, and k is a parameter that - depends on n. In this implementation, we use elliptical coordinates ex and ey, + Notes + ----- + The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), + where Ie is the intensity at the effective radius r_e, n is the Sersic index + that describes the concentration of the source, and k is a parameter that + depends on n. In this implementation, we use elliptical coordinates ex and ey, and the transformation from Cartesian coordinates is handled by `to_elliptical`. - The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. - If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, + The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. + If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, otherwise, we use the approximation from Ciotti & Bertin (1999). """ x, y = translate_rotate(x, y, x0, y0, phi) From d79e9f4cf104fa47792b2774e53a612405aa93f3 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:06:27 -0800 Subject: [PATCH 09/55] change to numpy doc string for base,py in lensors --- caustic/lenses/base.py | 390 ++++++++++++++++++++++++++--------------- 1 file changed, 251 insertions(+), 139 deletions(-) diff --git a/caustic/lenses/base.py b/caustic/lenses/base.py index 1a349192..fd6d3ad2 100644 --- a/caustic/lenses/base.py +++ b/caustic/lenses/base.py @@ -8,7 +8,7 @@ from ..constants import arcsec_to_rad, c_Mpc_s from ..cosmology import Cosmology -from ..parametrized import Parametrized, unpack +from ..parametrized import Parametrized, unpack from .utils import get_magnification from ..utils import batch_lm @@ -22,9 +22,12 @@ def __init__(self, cosmology: Cosmology, name: str = None): """ Initializes a new instance of the Lens class. - Args: - name (str): The name of the lens model. - cosmology (Cosmology): An instance of a Cosmology class that describes the cosmological parameters of the model. + Parameters + ---------- + name: string + The name of the lens model. + cosmology: Cosmology + An instance of a Cosmology class that describes the cosmological parameters of the model. """ super().__init__(name) self.cosmology = cosmology @@ -53,14 +56,21 @@ def magnification(self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Option """ Compute the gravitational magnification at the given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Gravitational magnification at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Gravitational magnification at the given coordinates. """ return get_magnification(partial(self.raytrace, params = params), x, y, z_s) @@ -71,19 +81,29 @@ def forward_raytrace( """ Perform a forward ray-tracing operation which maps from the source plane to the image plane. - Args: - bx (Tensor): Tensor of x coordinate in the source plane (scalar). - by (Tensor): Tensor of y coordinate in the source plane (scalar). - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - epsilon (Tensor): maximum distance between two images (arcsec) before they are considered the same image. - n_init (int): number of random initialization points used to try and find image plane points. - fov (float): the field of view in which the initial random samples are taken. - - Returns: - tuple[Tensor, Tensor]: Ray-traced coordinates in the x and y directions. + Parameters + ---------- + bx: Tensor + Tensor of x coordinate in the source plane (scalar). + by: Tensor + Tensor of y coordinate in the source plane (scalar). + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + epsilon: Tensor + maximum distance between two images (arcsec) before they are considered the same image. + n_init: int + number of random initialization points used to try and find image plane points. + fov: float + the field of view in which the initial random samples are taken. + + Returns + ------- + tuple[Tensor, Tensor] + Ray-traced coordinates in the x and y directions. """ - + bxy = torch.stack((bx, by)).repeat(n_init,1) # has shape (n_init, Dout:2) # TODO make FOV more general so that it doesnt have to be centered on zero,zero @@ -114,14 +134,16 @@ def forward_raytrace( res = torch.stack(res,dim = 0) return res[...,0], res[...,1] - + class ThickLens(Lens): """ Base class for modeling gravitational lenses that cannot be treated using the thin lens approximation. It is an abstract class and should be subclassed for different types of lens models. - Attributes: - cosmology (Cosmology): An instance of a Cosmology class that describes the cosmological parameters of the model. + Attributes + ---------- + cosmology: Cosmology + An instance of a Cosmology class that describes the cosmological parameters of the model. """ @unpack(3) @@ -130,15 +152,19 @@ def reduced_deflection_angle( ) -> tuple[Tensor, Tensor]: """ ThickLens objects do not have a reduced deflection angle since the distance D_ls is undefined - - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - Raises: - NotImplementedError + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Raises:: NotImplementedError """ warnings.warn("ThickLens objects do not have a reduced deflection angle since they have no unique lens redshift. The distance D_{ls} is undefined in the equation $\alpha_{reduced} = \frac{D_{ls}}{D_s}\alpha_{physical}$. See `effective_reduced_deflection_angle`. Now using effective_reduced_deflection_angle, please switch functions to remove this warning") return self.effective_reduced_deflection_angle(x, y, z_s, params) @@ -154,12 +180,17 @@ def effective_reduced_deflection_angle( effective reduced deflection angle, $\theta$ are the observed angular coordinates, and $\beta$ are the angular coordinates to the source plane. - - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. + + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. """ bx, by = self.raytrace(x, y, z_s, params) @@ -173,14 +204,21 @@ def physical_deflection_angle( plane. ThickLens objects have no unique definition of a lens plane and so cannot compute a physical_deflection_angle - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. - Returns: - tuple[Tensor, Tensor]: Tuple of Tensors representing the x and y components of the deflection angle, respectively. + Returns + ------- + tuple[Tensor, Tensor] + Tuple of Tensors representing the x and y components of the deflection angle, respectively. """ raise NotImplementedError("Physical deflection angles are computed with respect to a lensing plane. ThickLens objects have no unique definition of a lens plane and so cannot compute a physical_deflection_angle") @@ -194,13 +232,19 @@ def raytrace( source plance associated with a given input observed angular coordinate x,y. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- tuple[Tensor, Tensor]: Tuple of Tensors representing the x and y coordinates of the ray-traced light rays, respectively. """ @@ -214,14 +258,21 @@ def surface_density( """ Computes the projected mass density at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: The projected mass density at the given coordinates in units of solar masses per square Megaparsec. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + The projected mass density at the given coordinates in units of solar masses per square Megaparsec. """ ... @@ -233,14 +284,21 @@ def time_delay( """ Computes the gravitational time delay at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor ofsource redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: The gravitational time delay at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor ofsource redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + The gravitational time delay at the given coordinates. """ ... @@ -259,7 +317,7 @@ def _jacobian_effective_deflection_angle_finitediff( J[...,0,1], J[...,0,0] = torch.gradient(ax, spacing = pixelscale) J[...,1,1], J[...,1,0] = torch.gradient(ay, spacing = pixelscale) return J - + @unpack(3) def _jacobian_effective_deflection_angle_autograd( self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs @@ -281,7 +339,7 @@ def _jacobian_effective_deflection_angle_autograd( J[...,1,0], = torch.autograd.grad(ay, x, grad_outputs = torch.ones_like(ay), create_graph = True) J[...,1,1], = torch.autograd.grad(ay, y, grad_outputs = torch.ones_like(ay), create_graph = True) return J.detach() - + @unpack(3) def jacobian_effective_deflection_angle( self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, method = "autograd", pixelscale = None, **kwargs @@ -311,7 +369,7 @@ def _jacobian_lens_equation_finitediff( # Build Jacobian J = self._jacobian_effective_deflection_angle_finitediff(x, y, z_s, pixelscale, params, **kwargs) return torch.eye(2) - J - + @unpack(3) def _jacobian_lens_equation_autograd( self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs @@ -322,7 +380,7 @@ def _jacobian_lens_equation_autograd( # Build Jacobian J = self._jacobian_effective_deflection_angle_autograd(x, y, z_s, params, **kwargs) return torch.eye(2) - J.detach() - + @unpack(3) def effective_convergence_div( self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs @@ -353,10 +411,14 @@ class ThinLens(Lens): lensing quantities such as the deflection angle, convergence, potential, surface mass density, and gravitational time delay. - Args: - name (str): Name of the lens model. - cosmology (Cosmology): Cosmology object that encapsulates cosmological parameters and distances. - z_l (Optional[Tensor], optional): Redshift of the lens. Defaults to None. + Attributes + ---------- + name: string + Name of the lens model. + cosmology: Cosmology + Cosmology object that encapsulates cosmological parameters and distances. + z_l: (Optional[Tensor], optional) + Redshift of the lens. Defaults to None. """ @@ -371,14 +433,21 @@ def reduced_deflection_angle( """ Computes the reduced deflection angle of the lens at given coordinates [arcsec]. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - tuple[Tensor, Tensor]: Reduced deflection angle in x and y directions. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + -------- + tuple[Tensor, Tensor] + Reduced deflection angle in x and y directions. """ d_s = self.cosmology.angular_diameter_distance(z_s, params) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s, params) @@ -392,14 +461,21 @@ def physical_deflection_angle( """ Computes the physical deflection angle immediately after passing through this lens's plane. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - tuple[Tensor, Tensor]: Physical deflection angle in x and y directions in arcseconds. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + tuple[Tensor, Tensor] + Physical deflection angle in x and y directions in arcseconds. """ d_s = self.cosmology.angular_diameter_distance(z_s, params) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s, params) @@ -414,14 +490,21 @@ def convergence( """ Computes the convergence of the lens at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Convergence at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Convergence at the given coordinates. """ ... @@ -433,13 +516,21 @@ def potential( """ Computes the gravitational lensing potential at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: Tensor: Gravitational lensing potential at the given coordinates in arcsec^2. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Gravitational lensing potential at the given coordinates in arcsec^2. """ ... @@ -450,14 +541,21 @@ def surface_density( """ Computes the surface mass density of the lens at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Surface mass density at the given coordinates in solar masses per Mpc^2. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Surface mass density at the given coordinates in solar masses per Mpc^2. """ critical_surface_density = self.cosmology.critical_surface_density(z_l, z_s, params) return self.convergence(x, y, z_s, params) * critical_surface_density @@ -469,14 +567,21 @@ def raytrace( """ Perform a ray-tracing operation by subtracting the deflection angles from the input coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - tuple[Tensor, Tensor]: Ray-traced coordinates in the x and y directions. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + tuple[Tensor, Tensor] + Ray-traced coordinates in the x and y directions. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) return x - ax, y - ay @@ -488,14 +593,21 @@ def time_delay( """ Compute the gravitational time delay for light passing through the lens at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Time delay at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Time delay at the given coordinates. """ d_l = self.cosmology.angular_diameter_distance(z_l, params) d_s = self.cosmology.angular_diameter_distance(z_s, params) @@ -521,7 +633,7 @@ def _jacobian_deflection_angle_finitediff( J[...,0,1], J[...,0,0] = torch.gradient(ax, spacing = pixelscale) J[...,1,1], J[...,1,0] = torch.gradient(ay, spacing = pixelscale) return J - + @unpack(3) def _jacobian_deflection_angle_autograd( self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs @@ -543,7 +655,7 @@ def _jacobian_deflection_angle_autograd( J[...,1,0], = torch.autograd.grad(ay, x, grad_outputs = torch.ones_like(ay), create_graph = True) J[...,1,1], = torch.autograd.grad(ay, y, grad_outputs = torch.ones_like(ay), create_graph = True) return J.detach() - + @unpack(3) def jacobian_deflection_angle( self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, method = "autograd", pixelscale = None, **kwargs @@ -562,7 +674,7 @@ def jacobian_deflection_angle( return self._jacobian_deflection_angle_finitediff(x, y, z_s, pixelscale, params) else: raise ValueError("method should be one of: autograd, finitediff") - + @unpack(4) def _jacobian_lens_equation_finitediff( self, x: Tensor, y: Tensor, z_s: Tensor, pixelscale: Tensor, *args, params: Optional["Packed"] = None, **kwargs @@ -573,7 +685,7 @@ def _jacobian_lens_equation_finitediff( # Build Jacobian J = self._jacobian_deflection_angle_finitediff(x, y, z_s, pixelscale, params, **kwargs) return torch.eye(2) - J - + @unpack(3) def _jacobian_lens_equation_autograd( self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs @@ -584,4 +696,4 @@ def _jacobian_lens_equation_autograd( # Build Jacobian J = self._jacobian_deflection_angle_autograd(x, y, z_s, params, **kwargs) return torch.eye(2) - J.detach() - + From cfca1ac4e8a212467cb248181570eb5a8d4fb8f7 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:39:30 -0800 Subject: [PATCH 10/55] change to numpy docstring for files in lenses --- caustic/lenses/epl.py | 20 +++-- caustic/lenses/external_shear.py | 99 ++++++++++++++++--------- caustic/lenses/mass_sheet.py | 49 ++++++++----- caustic/lenses/multiplane.py | 121 ++++++++++++++++++++----------- caustic/lenses/point.py | 117 ++++++++++++++++++++---------- caustic/lenses/singleplane.py | 79 +++++++++++++------- caustic/lenses/sis.py | 92 +++++++++++++++-------- caustic/lenses/utils.py | 66 +++++++++++------ 8 files changed, 418 insertions(+), 225 deletions(-) diff --git a/caustic/lenses/epl.py b/caustic/lenses/epl.py index 789bd8c9..d6e43b89 100644 --- a/caustic/lenses/epl.py +++ b/caustic/lenses/epl.py @@ -15,16 +15,20 @@ class EPL(ThinLens): """ Elliptical power law (EPL, aka singular power-law ellipsoid) profile. - This class represents a thin gravitational lens model with an elliptical power law profile. The lensing equations are solved + This class represents a thin gravitational lens model with an elliptical power law profile. The lensing equations are solved iteratively using an approach based on Tessore et al. 2015. - Attributes: - n_iter (int): Number of iterations for the iterative solver. - s (float): Softening length for the elliptical power-law profile. - - Parameters: - z_l (Optional[Union[Tensor, float]]): This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. - x0 and y0 (Optional[Union[Tensor, float]]): These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. + Attributes + ---------- + n_iter: int + Number of iterations for the iterative solver. + s: float + Softening length for the elliptical power-law profile. + + Parameters + ---------- + z_l (Optional[Union[Tensor, float]]): This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. + x0 and y0 (Optional[Union[Tensor, float]]): These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. q (Optional[Union[Tensor, float]]): This is the axis ratio of the lens, i.e., the ratio of the minor axis to the major axis of the elliptical lens. phi (Optional[Union[Tensor, float]]): This is the orientation of the lens on the sky, typically given as an angle measured counter-clockwise from some reference direction. b (Optional[Union[Tensor, float]]): This is the scale length of the lens, which sets the overall scale of the lensing effect. In some contexts, this is referred to as the Einstein radius. diff --git a/caustic/lenses/external_shear.py b/caustic/lenses/external_shear.py index 88f4cb98..4862f2b5 100644 --- a/caustic/lenses/external_shear.py +++ b/caustic/lenses/external_shear.py @@ -14,15 +14,23 @@ class ExternalShear(ThinLens): """ Represents an external shear effect in a gravitational lensing system. - Attributes: - name (str): Identifier for the lens instance. - cosmology (Cosmology): The cosmological model used for lensing calculations. - z_l (Optional[Union[Tensor, float]]): The redshift of the lens. - x0, y0 (Optional[Union[Tensor, float]]): Coordinates of the shear center in the lens plane. - gamma_1, gamma_2 (Optional[Union[Tensor, float]]): Shear components. - - Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational - distortion that can be caused by nearby structures outside of the main lens galaxy. + Attributes + ---------- + name: str + Identifier for the lens instance. + cosmology: Cosmology + The cosmological model used for lensing calculations. + z_l: Optional[Union[Tensor, float]] + The redshift of the lens. + x0, y0: Optional[Union[Tensor, float]] + Coordinates of the shear center in the lens plane. + gamma_1, gamma_2: Optional[Union[Tensor, float]] + Shear components. + + Notes + ------ + The shear components gamma_1 and gamma_2 represent an external shear, a gravitational + distortion that can be caused by nearby structures outside of the main lens galaxy. """ def __init__( self, @@ -35,7 +43,7 @@ def __init__( s: float = 0.0, name: str = None, ): - + super().__init__(cosmology, z_l, name=name) self.add_param("x0", x0) @@ -51,14 +59,21 @@ def reduced_deflection_angle( """ Calculates the reduced deflection angle. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The reduced deflection angles in the x and y directions. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) # Meneghetti eq 3.83 @@ -68,7 +83,7 @@ def reduced_deflection_angle( #a2 = y/th + x * gamma_2 - y * gamma_1 a1 = x * gamma_1 + y * gamma_2 a2 = x * gamma_2 - y * gamma_1 - + return a1, a2 # I'm not sure but I think no derotation necessary @unpack(3) @@ -78,14 +93,21 @@ def potential( """ Calculates the lensing potential. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) x, y = translate_rotate(x, y, x0, y0) @@ -98,14 +120,21 @@ def convergence( """ The convergence is undefined for an external shear. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Raises: - NotImplementedError: This method is not implemented as the convergence is not defined + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Raises + ------ + NotImplementedError + This method is not implemented as the convergence is not defined for an external shear. """ raise NotImplementedError("convergence undefined for external shear") diff --git a/caustic/lenses/mass_sheet.py b/caustic/lenses/mass_sheet.py index c386c52a..d6844022 100644 --- a/caustic/lenses/mass_sheet.py +++ b/caustic/lenses/mass_sheet.py @@ -15,15 +15,23 @@ class MassSheet(ThinLens): """ Represents an external shear effect in a gravitational lensing system. - Attributes: - name (str): Identifier for the lens instance. - cosmology (Cosmology): The cosmological model used for lensing calculations. - z_l (Optional[Union[Tensor, float]]): The redshift of the lens. - x0, y0 (Optional[Union[Tensor, float]]): Coordinates of the shear center in the lens plane. - gamma_1, gamma_2 (Optional[Union[Tensor, float]]): Shear components. - - Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational - distortion that can be caused by nearby structures outside of the main lens galaxy. + Attributes + ---------- + name: string + Identifier for the lens instance. + cosmology: Cosmology + The cosmological model used for lensing calculations. + z_l: Optional[Union[Tensor, float]] + The redshift of the lens. + x0, y0: Optional[Union[Tensor, float]] + Coordinates of the shear center in the lens plane. + gamma_1, gamma_2: Optional[Union[Tensor, float]] + Shear components. + + Notes + ------ + The shear components gamma_1 and gamma_2 represent an external shear, a gravitational + distortion that can be caused by nearby structures outside of the main lens galaxy. """ def __init__( self, @@ -34,7 +42,7 @@ def __init__( surface_density: Optional[Union[Tensor, float]] = None, name: str = None, ): - + super().__init__(cosmology, z_l, name=name) self.add_param("x0", x0) @@ -48,14 +56,21 @@ def reduced_deflection_angle( """ Calculates the reduced deflection angle. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. - Returns: - tuple[Tensor, Tensor]: The reduced deflection angles in the x and y directions. + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) # Meneghetti eq 3.84 diff --git a/caustic/lenses/multiplane.py b/caustic/lenses/multiplane.py index 7c0e471f..fabb423d 100644 --- a/caustic/lenses/multiplane.py +++ b/caustic/lenses/multiplane.py @@ -16,13 +16,19 @@ class Multiplane(ThickLens): """ Class for handling gravitational lensing with multiple lens planes. - Attributes: - lenses (list[ThinLens]): List of thin lenses. - - Args: - name (str): Name of the lens. - cosmology (Cosmology): Cosmological parameters used for calculations. - lenses (list[ThinLens]): List of thin lenses. + Attributes + ---------- + lenses (list[ThinLens]) + List of thin lenses. + + Parameters + ---------- + name: string + Name of the lens. + cosmology: Cosmology + Cosmological parameters used for calculations. + lenses: list[ThinLens] + List of thin lenses. """ def __init__(self, cosmology: Cosmology, lenses: list[ThinLens], name: str = None): super().__init__(cosmology, name=name) @@ -35,11 +41,15 @@ def get_z_ls(self, *args, params: Optional["Packed"] = None, **kwargs) -> list[T """ Get the redshifts of each lens in the multiplane. - Args: - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + params: (Packed, optional) + Dynamic parameter container. - Returns: - List[Tensor]: Redshifts of the lenses. + Returns + -------- + List[Tensor] + Redshifts of the lenses. """ # Relies on z_l being the first element to be unpacked, which should always # be the case for a ThinLens @@ -68,15 +78,22 @@ def raytrace( \vec{\theta}^{i+1} = \vec{\theta}^{i} - \alpha^i(\vec{x}^{i+1}) Here we set as initialization :math:`\vec{\theta}^0 = theta` the observation angular coordinates and :math:`\vec{x}^0 = 0` the initial physical coordinates (i.e. the observation rays come from a point at the observer). The indexing of :math:`\vec{x}^i` and :math:`\vec{\theta}^i` indicates the properties at the plane :math:`i`, and 0 means the observer, 1 is the first lensing plane (infinitesimally after the plane since the deflection has been applied), and so on. Note that in the actual implementation we start at :math:`\vec{x}^1` and :math:`\vec{\theta}^0` and begin at the second step in the recursion formula. - - Args: - x (Tensor): angular x-coordinates from the observer perspective. - y (Tensor): angular y-coordinates from the observer perspective. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - Returns: - tuple[Tensor, Tensor]: The reduced deflection angle. + Parameters + ---------- + x: Tensor + angular x-coordinates from the observer perspective. + y: Tensor + angular y-coordinates from the observer perspective. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angle. """ return self.raytrace_z1z2(x, y, torch.zeros_like(z_s), z_s, params) @@ -86,7 +103,7 @@ def raytrace_z1z2( self, x: Tensor, y: Tensor, z_start: Tensor, z_end: Tensor, *args, params: Optional["Packed"] = None, **kwargs ) -> tuple[Tensor, Tensor]: """ - Method to do multiplane ray tracing from arbitrary start/end redshift. + Method to do multiplane ray tracing from arbitrary start/end redshift. """ # Collect lens redshifts and ensure proper order @@ -99,7 +116,7 @@ def raytrace_z1z2( # Initial angles are observation angles (negative needed because of negative in propogation term) theta_x, theta_y = x, y - + for i in lens_planes: # Compute deflection angle at current ray positions D_l = self.cosmology.transverse_comoving_distance_z1z2(z_start, z_ls[i], params) @@ -138,17 +155,26 @@ def surface_density( """ Calculate the projected mass density. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: Projected mass density [solMass / Mpc^2]. - - Raises: - NotImplementedError: This method is not yet implemented. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + Projected mass density [solMass / Mpc^2]. + + Raises + ------- + NotImplementedError + This method is not yet implemented. """ # TODO: rescale mass densities of each lens and sum raise NotImplementedError() @@ -160,17 +186,26 @@ def time_delay( """ Compute the time delay of light caused by the lensing. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: Time delay caused by the lensing. - - Raises: - NotImplementedError: This method is not yet implemented. + Parameters: + ----- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + Time delay caused by the lensing. + + Raises + ------ + NotImplementedError + This method is not yet implemented. """ # TODO: figure out how to compute this raise NotImplementedError() diff --git a/caustic/lenses/point.py b/caustic/lenses/point.py index 41967021..c3dbebe2 100644 --- a/caustic/lenses/point.py +++ b/caustic/lenses/point.py @@ -14,14 +14,22 @@ class Point(ThinLens): """ Class representing a point mass lens in strong gravitational lensing. - Attributes: - name (str): The name of the point lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Union[Tensor, float]]): Redshift of the lens. - x0 (Optional[Union[Tensor, float]]): x-coordinate of the center of the lens. - y0 (Optional[Union[Tensor, float]]): y-coordinate of the center of the lens. - th_ein (Optional[Union[Tensor, float]]): Einstein radius of the lens. - s (float): Softening parameter to prevent numerical instabilities. + Attributes + ---------- + name: str + The name of the point lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Union[Tensor, float]] + Redshift of the lens. + x0: Optional[Union[Tensor, float]] + x-coordinate of the center of the lens. + y0: Optional[Union[Tensor, float]] + y-coordinate of the center of the lens. + th_ein: Optional[Union[Tensor, float]] + Einstein radius of the lens. + s: float + Softening parameter to prevent numerical instabilities. """ def __init__( self, @@ -36,14 +44,22 @@ def __init__( """ Initialize the Point class. - Args: - name (str): The name of the point lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Tensor]): Redshift of the lens. - x0 (Optional[Tensor]): x-coordinate of the center of the lens. - y0 (Optional[Tensor]): y-coordinate of the center of the lens. - th_ein (Optional[Tensor]): Einstein radius of the lens. - s (float): Softening parameter to prevent numerical instabilities. + Parameters + ---------- + name: string + The name of the point lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Tensor] + Redshift of the lens. + x0: Optional[Tensor] + x-coordinate of the center of the lens. + y0: Optional[Tensor] + y-coordinate of the center of the lens. + th_ein: Optional[Tensor] + Einstein radius of the lens. + s: float + Softening parameter to prevent numerical instabilities. """ super().__init__(cosmology, z_l, name=name) @@ -59,14 +75,21 @@ def reduced_deflection_angle( """ Compute the deflection angles. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The deflection angles in the x and y directions. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -81,14 +104,21 @@ def potential( """ Compute the lensing potential. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -101,14 +131,21 @@ def convergence( """ Compute the convergence (dimensionless surface mass density). - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The convergence (dimensionless surface mass density). + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + -------- + Tensor + The convergence (dimensionless surface mass density). """ x, y = translate_rotate(x, y, x0, y0) return torch.where((x == 0) & (y == 0), torch.inf, 0.0) diff --git a/caustic/lenses/singleplane.py b/caustic/lenses/singleplane.py index f9cad5f4..6ca18743 100644 --- a/caustic/lenses/singleplane.py +++ b/caustic/lenses/singleplane.py @@ -12,13 +12,17 @@ class SinglePlane(ThinLens): """ - A class for combining multiple thin lenses into a single lensing plane. + A class for combining multiple thin lenses into a single lensing plane. This model inherits from the base `ThinLens` class. - - Attributes: - name (str): The name of the single plane lens. - cosmology (Cosmology): An instance of the Cosmology class. - lenses (List[ThinLens]): A list of ThinLens objects that are being combined into a single lensing plane. + + Attributes + ---------- + name: str + The name of the single plane lens. + cosmology: Cosmology + An instance of the Cosmology class. + lenses: List[ThinLens] + A list of ThinLens objects that are being combined into a single lensing plane. """ def __init__(self, cosmology: Cosmology, lenses: list[ThinLens], name: str = None, **kwargs): @@ -38,14 +42,21 @@ def reduced_deflection_angle( """ Calculate the total deflection angle by summing the deflection angles of all individual lenses. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tuple[Tensor, Tensor]: The total deflection angle in the x and y directions. + Returns + ------- + Tuple[Tensor, Tensor] + The total deflection angle in the x and y directions. """ ax = torch.zeros_like(x) ay = torch.zeros_like(x) @@ -62,14 +73,21 @@ def convergence( """ Calculate the total projected mass density by summing the mass densities of all individual lenses. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tensor: The total projected mass density. + Returns + ------- + Tensor + The total projected mass density. """ convergence = torch.zeros_like(x) for lens in self.lenses: @@ -84,14 +102,21 @@ def potential( """ Compute the total lensing potential by summing the lensing potentials of all individual lenses. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + ----------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tensor: The total lensing potential. + Returns + ------- + Tensor + The total lensing potential. """ potential = torch.zeros_like(x) for lens in self.lenses: diff --git a/caustic/lenses/sis.py b/caustic/lenses/sis.py index f6ac43e4..2408820d 100644 --- a/caustic/lenses/sis.py +++ b/caustic/lenses/sis.py @@ -13,17 +13,24 @@ class SIS(ThinLens): """ - A class representing the Singular Isothermal Sphere (SIS) model. + A class representing the Singular Isothermal Sphere (SIS) model. This model inherits from the base `ThinLens` class. - Attributes: - name (str): The name of the SIS lens. - cosmology (Cosmology): An instance of the Cosmology class. - z_l (Optional[Union[Tensor, float]]): The lens redshift. - x0 (Optional[Union[Tensor, float]]): The x-coordinate of the lens center. - y0 (Optional[Union[Tensor, float]]): The y-coordinate of the lens center. + Attributes + ---------- + name: str + The name of the SIS lens. + cosmology: Cosmology + An instance of the Cosmology class. + z_l: Optional[Union[Tensor, float]] + The lens redshift. + x0: Optional[Union[Tensor, float]] + The x-coordinate of the lens center. + y0: Optional[Union[Tensor, float]] + The y-coordinate of the lens center. th_ein (Optional[Union[Tensor, float]]): The Einstein radius of the lens. - s (float): A smoothing factor, default is 0.0. + s: float + A smoothing factor, default is 0.0. """ def __init__( self, @@ -52,14 +59,21 @@ def reduced_deflection_angle( """ Calculate the deflection angle of the SIS lens. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tuple[Tensor, Tensor]: The deflection angle in the x and y directions. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tuple[Tensor, Tensor] + The deflection angle in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) R = (x**2 + y**2).sqrt() + self.s @@ -74,14 +88,21 @@ def potential( """ Compute the lensing potential of the SIS lens. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -94,14 +115,21 @@ def convergence( """ Calculate the projected mass density of the SIS lens. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The projected mass density. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The projected mass density. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s diff --git a/caustic/lenses/utils.py b/caustic/lenses/utils.py index 4912a6c8..d59ff3d3 100644 --- a/caustic/lenses/utils.py +++ b/caustic/lenses/utils.py @@ -16,14 +16,20 @@ def get_pix_jacobian( (:math:`\\partial \beta / \\partial \theta`). This is done at a single point on the lensing plane. - Args: - raytrace: A function that maps the lensing plane coordinates to the source plane coordinates. - x (Tensor): The x-coordinate on the lensing plane. - y (Tensor): The y-coordinate on the lensing plane. - z_s (Tensor): The redshift of the source. - - Returns: - The Jacobian matrix of the image position with respect to the source position at the given point. + Parameters + ----------- + raytrace: function + A function that maps the lensing plane coordinates to the source plane coordinates. + x: Tensor + The x-coordinate on the lensing plane. + y: Tensor + The y-coordinate on the lensing plane. + z_s: Tensor + The redshift of the source. + + Returns + -------- + The Jacobian matrix of the image position with respect to the source position at the given point. """ jac = torch.func.jacfwd(raytrace, (0, 1))(x, y, z_s) # type: ignore @@ -35,13 +41,20 @@ def get_pix_magnification(raytrace, x, y, z_s) -> Tensor: Computes the magnification at a single point on the lensing plane. The magnification is derived from the determinant of the Jacobian matrix of the image position with respect to the source position. - Args: - raytrace: A function that maps the lensing plane coordinates to the source plane coordinates. - x (Tensor): The x-coordinate on the lensing plane. - y (Tensor): The y-coordinate on the lensing plane. - z_s (Tensor): The redshift of the source. - - Returns: + Parameters + ---------- + raytrace: function + A function that maps the lensing plane coordinates to the source plane coordinates. + x: Tensor + The x-coordinate on the lensing plane. + y: Tensor + The y-coordinate on the lensing plane. + z_s: Tensor + The redshift of the source. + + Returns + ------- + Tensor The magnification at the given point on the lensing plane. """ jac = get_pix_jacobian(raytrace, x, y, z_s) @@ -50,16 +63,23 @@ def get_pix_magnification(raytrace, x, y, z_s) -> Tensor: def get_magnification(raytrace, x, y, z_s) -> Tensor: """ - Computes the magnification over a grid on the lensing plane. This is done by calling `get_pix_magnification` + Computes the magnification over a grid on the lensing plane. This is done by calling `get_pix_magnification` for each point on the grid. - Args: - raytrace: A function that maps the lensing plane coordinates to the source plane coordinates. - x (Tensor): The x-coordinates on the lensing plane. - y (Tensor): The y-coordinates on the lensing plane. - z_s (Tensor): The redshift of the source. - - Returns: + Parameters + ---------- + raytrace: function + A function that maps the lensing plane coordinates to the source plane coordinates. + x: Tensor + The x-coordinates on the lensing plane. + y: Tensor + The y-coordinates on the lensing plane. + z_s: Tensor + The redshift of the source. + + Returns + -------- + Tensor A tensor representing the magnification at each point on the grid. """ return vmap_n(get_pix_magnification, 2, (None, 0, 0, None))( From 19cc3c18fb89879f7999865e0c02ff0c2389fe23 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:45:38 -0800 Subject: [PATCH 11/55] update epl.py --- caustic/lenses/epl.py | 143 +++++++++++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 49 deletions(-) diff --git a/caustic/lenses/epl.py b/caustic/lenses/epl.py index d6e43b89..03011354 100644 --- a/caustic/lenses/epl.py +++ b/caustic/lenses/epl.py @@ -27,12 +27,18 @@ class EPL(ThinLens): Parameters ---------- - z_l (Optional[Union[Tensor, float]]): This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. - x0 and y0 (Optional[Union[Tensor, float]]): These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. - q (Optional[Union[Tensor, float]]): This is the axis ratio of the lens, i.e., the ratio of the minor axis to the major axis of the elliptical lens. - phi (Optional[Union[Tensor, float]]): This is the orientation of the lens on the sky, typically given as an angle measured counter-clockwise from some reference direction. - b (Optional[Union[Tensor, float]]): This is the scale length of the lens, which sets the overall scale of the lensing effect. In some contexts, this is referred to as the Einstein radius. - t (Optional[Union[Tensor, float]]): This is the power-law slope parameter of the lens model. In the context of the EPL model, t is equivalent to the gamma parameter minus one, where gamma is the power-law index of the radial mass distribution of the lens. + z_l: Optional[Union[Tensor, float]] + This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. + x0 and y0: Optional[Union[Tensor, float]] + These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. + q: Optional[Union[Tensor, float]] + This is the axis ratio of the lens, i.e., the ratio of the minor axis to the major axis of the elliptical lens. + phi: Optional[Union[Tensor, float]] + This is the orientation of the lens on the sky, typically given as an angle measured counter-clockwise from some reference direction. + b: Optional[Union[Tensor, float]] + This is the scale length of the lens, which sets the overall scale of the lensing effect. In some contexts, this is referred to as the Einstein radius. + t: Optional[Union[Tensor, float]] + This is the power-law slope parameter of the lens model. In the context of the EPL model, t is equivalent to the gamma parameter minus one, where gamma is the power-law index of the radial mass distribution of the lens. """ @@ -53,18 +59,30 @@ def __init__( """ Initialize an EPL lens model. - Args: - name (str): Name of the lens model. - cosmology (Cosmology): Cosmology object that provides cosmological distance calculations. - z_l (Optional[Tensor]): Redshift of the lens. If not provided, it is considered as a free parameter. - x0 (Optional[Tensor]): X coordinate of the lens center. If not provided, it is considered as a free parameter. - y0 (Optional[Tensor]): Y coordinate of the lens center. If not provided, it is considered as a free parameter. - q (Optional[Tensor]): Axis ratio of the lens. If not provided, it is considered as a free parameter. - phi (Optional[Tensor]): Position angle of the lens. If not provided, it is considered as a free parameter. - b (Optional[Tensor]): Scale length of the lens. If not provided, it is considered as a free parameter. - t (Optional[Tensor]): Power law slope (`gamma-1`) of the lens. If not provided, it is considered as a free parameter. - s (float): Softening length for the elliptical power-law profile. - n_iter (int): Number of iterations for the iterative solver. + Parameters + ----------- + name: string + Name of the lens model. + cosmology: Cosmology + Cosmology object that provides cosmological distance calculations. + z_l: Optional[Tensor] + Redshift of the lens. If not provided, it is considered as a free parameter. + x0: Optional[Tensor] + X coordinate of the lens center. If not provided, it is considered as a free parameter. + y0: Optional[Tensor] + Y coordinate of the lens center. If not provided, it is considered as a free parameter. + q: Optional[Tensor] + Axis ratio of the lens. If not provided, it is considered as a free parameter. + phi: Optional[Tensor] + Position angle of the lens. If not provided, it is considered as a free parameter. + b: Optional[Tensor] + Scale length of the lens. If not provided, it is considered as a free parameter. + t: Optional[Tensor] + Power law slope (`gamma-1`) of the lens. If not provided, it is considered as a free parameter. + s: float + Softening length for the elliptical power-law profile. + n_iter: int + Number of iterations for the iterative solver. """ super().__init__(cosmology, z_l, name=name) @@ -85,14 +103,21 @@ def reduced_deflection_angle( """ Compute the reduced deflection angles of the lens. - Args: - x (Tensor): X coordinates in the lens plane. - y (Tensor): Y coordinates in the lens plane. - z_s (Tensor): Source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. - - Returns: - tuple[Tensor, Tensor]: Reduced deflection angles in the x and y directions. + Parameters + ---------- + x: Tensor + X coordinates in the lens plane. + y: Tensor + Y coordinates in the lens plane. + z_s: Tensor + Source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. + + Returns + -------- + tuple[Tensor, Tensor] + Reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0, phi) @@ -112,13 +137,19 @@ def _r_omega(self, z, t, q): """ Iteratively computes `R * omega(phi)` (eq. 23 in Tessore et al 2015). - Args: - z (Tensor): `R * e^(i * phi)`, position vector in the lens plane. - t (Tensor): Power law slow (`gamma-1`). - q (Tensor): Axis ratio. - - Returns: - Tensor: The value of `R * omega(phi)`. + Parameters + ---------- + z: Tensor + `R * e^(i * phi)`, position vector in the lens plane. + t: Tensor + Power law slow (`gamma-1`). + q: Tensor + Axis ratio. + + Returns + -------- + Tensor + The value of `R * omega(phi)`. """ # constants f = (1.0 - q) / (1.0 + q) @@ -142,14 +173,21 @@ def potential( """ Compute the lensing potential of the lens. - Args: - x (Tensor): X coordinates in the lens plane. - y (Tensor): Y coordinates in the lens plane. - z_s (Tensor): Source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + X coordinates in the lens plane. + y: Tensor + Y coordinates in the lens plane. + z_s: Tensor + Source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. + + Returns + ------- + Tensor + The lensing potential. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) ax, ay = derotate(ax, ay, -phi) @@ -163,14 +201,21 @@ def convergence( """ Compute the convergence of the lens, which describes the local density of the lens. - Args: - x (Tensor): X coordinates in the lens plane. - y (Tensor): Y coordinates in the lens plane. - z_s (Tensor): Source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. - - Returns: - Tensor: The convergence of the lens. + Parameters + ---------- + x: Tensor + X coordinates in the lens plane. + y: Tensor + Y coordinates in the lens plane. + z_s: Tensor + Source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. + + Returns + ------- + Tensor + The convergence of the lens. """ x, y = translate_rotate(x, y, x0, y0, phi) psi = (q**2 * (x**2 + self.s**2) + y**2).sqrt() From a5bb4d358b615cd9c9958527f34ba3ce4e3450c2 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:51:04 -0800 Subject: [PATCH 12/55] update sie.py --- caustic/lenses/sie.py | 125 +++++++++++++++++++++++++++--------------- 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/caustic/lenses/sie.py b/caustic/lenses/sie.py index c187a27a..de337de4 100644 --- a/caustic/lenses/sie.py +++ b/caustic/lenses/sie.py @@ -12,19 +12,29 @@ class SIE(ThinLens): """ - A class representing a Singular Isothermal Ellipsoid (SIE) strong gravitational lens model. + A class representing a Singular Isothermal Ellipsoid (SIE) strong gravitational lens model. This model is based on Keeton 2001, which can be found at https://arxiv.org/abs/astro-ph/0102341. - - Attributes: - name (str): The name of the lens. - cosmology (Cosmology): An instance of the Cosmology class. - z_l (Optional[Union[Tensor, float]]): The redshift of the lens. - x0 (Optional[Union[Tensor, float]]): The x-coordinate of the lens center. - y0 (Optional[Union[Tensor, float]]): The y-coordinate of the lens center. - q (Optional[Union[Tensor, float]]): The axis ratio of the lens. - phi (Optional[Union[Tensor, float]]): The orientation angle of the lens (position angle). - b (Optional[Union[Tensor, float]]): The Einstein radius of the lens. - s (float): The core radius of the lens (defaults to 0.0). + + Attributes + ---------- + name: str + The name of the lens. + cosmology: Cosmology + An instance of the Cosmology class. + z_l: Optional[Union[Tensor, float]] + The redshift of the lens. + x0: Optional[Union[Tensor, float]] + The x-coordinate of the lens center. + y0: Optional[Union[Tensor, float]] + The y-coordinate of the lens center. + q: Optional[Union[Tensor, float]] + The axis ratio of the lens. + phi: Optional[Union[Tensor, float]] + The orientation angle of the lens (position angle). + b: Optional[Union[Tensor, float]] + The Einstein radius of the lens. + s: float + The core radius of the lens (defaults to 0.0). """ def __init__( @@ -55,13 +65,19 @@ def _get_potential(self, x, y, q): """ Compute the radial coordinate in the lens plane. - Args: - x (Tensor): The x-coordinate in the lens plane. - y (Tensor): The y-coordinate in the lens plane. - q (Tensor): The axis ratio of the lens. - - Returns: - Tensor: The radial coordinate in the lens plane. + Parameters + ---------- + x: Tensor + The x-coordinate in the lens plane. + y: Tensor + The y-coordinate in the lens plane. + q: Tensor + The axis ratio of the lens. + + Returns + -------- + Tensor + The radial coordinate in the lens plane. """ return (q**2 * (x**2 + self.s**2) + y**2).sqrt() @@ -72,14 +88,21 @@ def reduced_deflection_angle( """ Calculate the physical deflection angle. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tuple[Tensor, Tensor]: The deflection angle in the x and y directions. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + -------- + Tuple[Tensor, Tensor] + The deflection angle in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0, phi) psi = self._get_potential(x, y, q) @@ -90,20 +113,27 @@ def reduced_deflection_angle( return derotate(ax, ay, phi) @unpack(3) - def potential( + def potential( self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, q, phi, b, *args, params: Optional["Packed"] = None, **kwargs ) -> Tensor: """ Compute the lensing potential. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) ax, ay = derotate(ax, ay, -phi) @@ -117,14 +147,21 @@ def convergence( """ Calculate the projected mass density. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The projected mass. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The projected mass. """ x, y = translate_rotate(x, y, x0, y0, phi) psi = self._get_potential(x, y, q) From a84a79910cbe4590651ace1d682995a38048e4cb Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 14:02:58 -0800 Subject: [PATCH 13/55] update tnfw.py --- caustic/lenses/tnfw.py | 327 ++++++++++++++++++++++++++--------------- 1 file changed, 210 insertions(+), 117 deletions(-) diff --git a/caustic/lenses/tnfw.py b/caustic/lenses/tnfw.py index 5f41f61f..b236900c 100644 --- a/caustic/lenses/tnfw.py +++ b/caustic/lenses/tnfw.py @@ -16,7 +16,7 @@ class TNFW(ThinLens): """Truncated Navaro-Frenk-White profile - + TNFW lens class. This class models a lens using the truncated Navarro-Frenk-White (NFW) profile. The NFW profile is a spatial density profile of dark matter halo that arises in cosmological @@ -25,10 +25,11 @@ class TNFW(ThinLens): infinity. This is based off the paper by Baltz et al. 2009: https://arxiv.org/abs/0705.0682 - + https://ui.adsabs.harvard.edu/abs/2009JCAP...01..015B/abstract - Note: + Notes + ------ The mass `m` in the TNFW profile corresponds to the total mass of the lens. This is different from the NFW profile where the mass `m` parameter corresponds to the mass within R200. If you @@ -38,25 +39,37 @@ class TNFW(ThinLens): NFW profile, not a TNFW profile. This is in line with how lenstronomy inteprets the mass parameter. - Args: - name (str): Name of the lens instance. - cosmology (Cosmology): An instance of the Cosmology class which contains - information about the cosmological model and parameters. - z_l (Optional[Tensor]): Redshift of the lens. - x0 (Optional[Tensor]): Center of lens position on x-axis (arcsec). - y0 (Optional[Tensor]): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - s (float): Softening parameter to avoid singularities at the center of the lens. - Default is 0.0. - interpret_m_total_mass (bool): Indicates how to interpret the mass variable "m". If true - the mass is intepreted as the total mass of the halo (good because it makes sense). If - false it is intepreted as what the mass would have been within R200 of a an NFW that - isn't truncated (good because it is easily compared with an NFW). - use_case (str): Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile - specifically cant be both batchable and differentiable. You may select which version - you wish to use by setting this parameter to one of: batchable, differentiable. + Parameters + ----- + name: string + Name of the lens instance. + cosmology: Cosmology + An instance of the Cosmology class which contains + information about the cosmological model and parameters. + z_l: Optional[Tensor] + Redshift of the lens. + x0: Optional[Tensor] + Center of lens position on x-axis (arcsec). + y0: Optional[Tensor] + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + s: float + Softening parameter to avoid singularities at the center of the lens. + Default is 0.0. + interpret_m_total_mass: boolean + Indicates how to interpret the mass variable "m". If true + the mass is intepreted as the total mass of the halo (good because it makes sense). If + false it is intepreted as what the mass would have been within R200 of a an NFW that + isn't truncated (good because it is easily compared with an NFW). + use_case: str + Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile + specifically cant be both batchable and differentiable. You may select which version + you wish to use by setting this parameter to one of: batchable, differentiable. """ def __init__( @@ -92,7 +105,7 @@ def __init__( self._F = self._F_differentiable else: raise ValueError("use case should be one of: batchable, differentiable") - + @staticmethod def _F_batchable(x): """ @@ -106,7 +119,7 @@ def _F_differentiable(x): f[x < 1] = torch.arctanh((1. - x[x < 1]**2).sqrt()) / (1. - x[x < 1]**2).sqrt() f[x > 1] = torch.arctan((x[x > 1]**2 - 1.).sqrt()) / (x[x > 1]**2 - 1.).sqrt() return f - + @staticmethod def _L(x, tau): """ @@ -119,20 +132,30 @@ def get_concentration(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: """ Calculate the scale radius of the lens. This is the same formula used for the classic NFW profile. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The scale radius of the lens in Mpc. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + ------- + Tensor + The scale radius of the lens in Mpc. """ critical_density = self.cosmology.critical_density(z_l, params) - d_l = self.cosmology.angular_diameter_distance(z_l, params) + d_l = self.cosmology.angular_diameter_distance(z_l, params) r_delta = (3 * mass / (4 * pi * DELTA * critical_density)) ** (1 / 3) return r_delta / (scale_radius * d_l * arcsec_to_rad) @@ -141,17 +164,27 @@ def get_truncation_radius(self, z_l, x0, y0, mass, scale_radius, tau, *args, par """ Calculate the truncation radius of the TNFW lens. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The truncation radius of the lens in arcsec. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dictionary + Dynamic parameter container. + + Returns + ------- + Tensor + The truncation radius of the lens in arcsec. """ return tau * scale_radius @@ -160,17 +193,27 @@ def get_M0(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional[" """ Calculate the reference mass. This is an abstract reference mass used internally in the equations from Baltz et al. 2009. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The reference mass of the lens in Msol. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dictionary + Dynamic parameter container. + + Returns + ------- + Tensor + The reference mass of the lens in Msol. """ if self.interpret_m_total_mass: return mass * (tau**2 + 1)**2 / (tau**2 * ((tau**2 - 1) * tau.log() + torch.pi * tau - (tau**2 + 1))) @@ -184,17 +227,27 @@ def get_scale_density(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: """ Calculate the scale density of the lens. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The scale density of the lens in solar masses per Mpc cubed. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + -------- + Tensor + The scale density of the lens in solar masses per Mpc cubed. """ c = self.get_concentration(params) return ( @@ -212,18 +265,28 @@ def convergence( """ TNFW convergence as given in Baltz et al. 2009. This is unitless since it is Sigma(x) / Sigma_crit. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: unitless convergence at requested position - + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + --------- + Tensor + unitless convergence at requested position + """ x, y = translate_rotate(x, y, x0, y0) r = (x**2 + y**2).sqrt() + self.s @@ -234,7 +297,7 @@ def convergence( critical_density = self.cosmology.critical_surface_density(z_l, z_s, params) S = self.get_M0(params) / (2 * torch.pi * (scale_radius * d_l * arcsec_to_rad)**2) - + t2 = tau**2 a1 = t2 / (t2 + 1)**2 a2 = torch.where(g == 1, (t2 + 1) / 3., (t2 + 1) * (1 - F) / (g**2 - 1)) @@ -250,17 +313,27 @@ def mass_enclosed_2d( """ Total projected mass (Msol) within a radius r (arcsec). - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: Integrated mass projected in infinite cylinder within radius r. + Parameters + ----------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + ------- + Tensor + Integrated mass projected in infinite cylinder within radius r. """ g = r / scale_radius t2 = tau**2 @@ -273,7 +346,7 @@ def mass_enclosed_2d( a5 = (t2 + g**2).sqrt() * (-torch.pi + (t2 - 1) * L / tau) S = self.get_M0(params) return S * a1 * (a2 + a3 + a4 + a5) - + @unpack(3) def physical_deflection_angle( self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs @@ -283,22 +356,32 @@ def physical_deflection_angle( naturally represented as a physical deflection angle, this is easily internally converted to a reduced deflection angle. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The physical deflection angles in the x and y directions (arcsec). + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + -------- + tuple[Tensor, Tensor] + The physical deflection angles in the x and y directions (arcsec). """ d_l = self.cosmology.angular_diameter_distance(z_l, params) x, y = translate_rotate(x, y, x0, y0) - r = ((x**2 + y**2).sqrt() + self.s) + r = ((x**2 + y**2).sqrt() + self.s) theta = torch.arctan2(y,x) # The below actually equally comes from eq 2.13 in Meneghetti notes @@ -315,17 +398,27 @@ def potential( TODO: convert to dimensionless potential. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ----------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) r = (x**2 + y**2).sqrt() + self.s From be03b43521858aca8740432241c2a1ed90499738 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 14:09:58 -0800 Subject: [PATCH 14/55] update pseudo_jaffe.py --- caustic/lenses/pseudo_jaffe.py | 184 +++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 64 deletions(-) diff --git a/caustic/lenses/pseudo_jaffe.py b/caustic/lenses/pseudo_jaffe.py index d1490537..0aa88a10 100644 --- a/caustic/lenses/pseudo_jaffe.py +++ b/caustic/lenses/pseudo_jaffe.py @@ -15,20 +15,30 @@ class PseudoJaffe(ThinLens): """ - Class representing a Pseudo Jaffe lens in strong gravitational lensing, - based on `Eliasdottir et al 2007 `_ and + Class representing a Pseudo Jaffe lens in strong gravitational lensing, + based on `Eliasdottir et al 2007 `_ and the `lenstronomy` source code. - Attributes: - name (str): The name of the Pseudo Jaffe lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Union[Tensor, float]]): Redshift of the lens. - x0 (Optional[Union[Tensor, float]]): x-coordinate of the center of the lens (arcsec). - y0 (Optional[Union[Tensor, float]]): y-coordinate of the center of the lens (arcsec). - mass (Optional[Union[Tensor, float]]): Total mass of the lens (Msol). - core_radius (Optional[Union[Tensor, float]]): Core radius of the lens (arcsec). - scale_radius (Optional[Union[Tensor, float]]): Scaling radius of the lens (arcsec). - s (float): Softening parameter to prevent numerical instabilities. + Attributes + ---------- + name: string + The name of the Pseudo Jaffe lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Union[Tensor, float]] + Redshift of the lens. + x0: Optional[Union[Tensor, float]] + x-coordinate of the center of the lens (arcsec). + y0: Optional[Union[Tensor, float]] + y-coordinate of the center of the lens (arcsec). + mass: Optional[Union[Tensor, float]] + Total mass of the lens (Msol). + core_radius: Optional[Union[Tensor, float]] + Core radius of the lens (arcsec). + scale_radius: Optional[Union[Tensor, float]] + Scaling radius of the lens (arcsec). + s: float + Softening parameter to prevent numerical instabilities. """ def __init__( @@ -46,16 +56,26 @@ def __init__( """ Initialize the PseudoJaffe class. - Args: - name (str): The name of the Pseudo Jaffe lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Tensor]): Redshift of the lens. - x0 (Optional[Tensor]): x-coordinate of the center of the lens. - y0 (Optional[Tensor]): y-coordinate of the center of the lens. - mass (Optional[Tensor]): Total mass of the lens (Msol). - core_radius (Optional[Tensor]): Core radius of the lens. - scale_radius (Optional[Tensor]): Scaling radius of the lens. - s (float): Softening parameter to prevent numerical instabilities. + Parameters + ---------- + name: string + The name of the Pseudo Jaffe lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Tensor] + Redshift of the lens. + x0: Optional[Tensor] + x-coordinate of the center of the lens. + y0: Optional[Tensor] + y-coordinate of the center of the lens. + mass: Optional[Tensor] + Total mass of the lens (Msol). + core_radius: Optional[Tensor] + Core radius of the lens. + scale_radius: Optional[Tensor] + Scaling radius of the lens. + s: float + Softening parameter to prevent numerical instabilities. """ super().__init__(cosmology, z_l, name=name) @@ -71,19 +91,25 @@ def get_convergence_0(self, z_s, z_l, x0, y0, mass, core_radius, scale_radius, * d_l = self.cosmology.angular_diameter_distance(z_l, params) sigma_crit = self.cosmology.critical_surface_density(z_l, z_s, params) return mass / (2 * torch.pi * sigma_crit * core_radius * scale_radius * (d_l * arcsec_to_rad)**2) - + @unpack(2) def mass_enclosed_2d(self, theta, z_s, z_l, x0, y0, mass, core_radius, scale_radius, *args, params: Optional["Packed"] = None, **kwargs): """ Calculate the mass enclosed within a two-dimensional radius. - Args: - theta (Tensor): Radius at which to calculate enclosed mass (arcsec). - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + theta: Tensor + Radius at which to calculate enclosed mass (arcsec). + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tensor: The mass enclosed within the given radius. + Returns + ------- + Tensor + The mass enclosed within the given radius. """ theta = theta + self.s d_l = self.cosmology.angular_diameter_distance(z_l, params) @@ -116,16 +142,25 @@ def central_convergence( """ Compute the central convergence. - Args: - z_l (Tensor): Lens redshift. - z_s (Tensor): Source redshift. - rho_0 (Tensor): Central mass density. - core_radius (Tensor): Core radius of the lens (must be in Mpc). - scale_radius (Tensor): Scaling radius of the lens (must be in Mpc). - cosmology (Cosmology): The cosmology used for calculations. + Parameters + ----------- + z_l: Tensor + Lens redshift. + z_s: Tensor + Source redshift. + rho_0: Tensor + Central mass density. + core_radius: Tensor + Core radius of the lens (must be in Mpc). + scale_radius: Tensor + Scaling radius of the lens (must be in Mpc). + cosmology: Cosmology + The cosmology used for calculations. - Returns: - Tensor: The central convergence. + Returns + -------- + Tensor + The central convergence. """ return ( pi @@ -142,14 +177,21 @@ def reduced_deflection_angle( ) -> tuple[Tensor, Tensor]: """ Calculate the deflection angle. - Args: - x (Tensor): x-coordinate of the lens. - y (Tensor): y-coordinate of the lens. - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tuple[Tensor, Tensor]: The deflection angle in the x and y directions. + Parameters + ---------- + x: Tensor + x-coordinate of the lens. + y: Tensor + y-coordinate of the lens. + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + -------- + Tuple[Tensor, Tensor] + The deflection angle in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) R = (x**2 + y**2).sqrt() + self.s @@ -167,15 +209,22 @@ def potential( ) -> Tensor: """ Compute the lensing potential. This calculation is based on equation A18. - - Args: - x (Tensor): x-coordinate of the lens. - y (Tensor): y-coordinate of the lens. - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + + Parameters + -------- + x: Tensor + x-coordinate of the lens. + y: Tensor + y-coordinate of the lens. + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + -------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) R_squared = x**2 + y**2 + self.s @@ -193,15 +242,22 @@ def convergence( ) -> Tensor: """ Calculate the projected mass density, based on equation A6. - - Args: - x (Tensor): x-coordinate of the lens. - y (Tensor): y-coordinate of the lens. - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The projected mass density. + + Parameters + ----------- + x: Tensor + x-coordinate of the lens. + y: Tensor + y-coordinate of the lens. + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The projected mass density. """ x, y = translate_rotate(x, y, x0, y0) R_squared = x**2 + y**2 + self.s From 2a501f33c5ab295bc8dacab0206a431eda4f6143 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 15:41:52 -0800 Subject: [PATCH 15/55] update nfw.py --- caustic/lenses/nfw.py | 311 +++++++++++++++++++++++++++--------------- 1 file changed, 201 insertions(+), 110 deletions(-) diff --git a/caustic/lenses/nfw.py b/caustic/lenses/nfw.py index 05357159..07c4c501 100644 --- a/caustic/lenses/nfw.py +++ b/caustic/lenses/nfw.py @@ -17,34 +17,50 @@ class NFW(ThinLens): """ NFW lens class. This class models a lens using the Navarro-Frenk-White (NFW) profile. - The NFW profile is a spatial density profile of dark matter halo that arises in + The NFW profile is a spatial density profile of dark matter halo that arises in cosmological simulations. - Attributes: - z_l (Optional[Tensor]): Redshift of the lens. Default is None. - x0 (Optional[Tensor]): x-coordinate of the lens center in the lens plane. - Default is None. - y0 (Optional[Tensor]): y-coordinate of the lens center in the lens plane. - Default is None. - m (Optional[Tensor]): Mass of the lens. Default is None. - c (Optional[Tensor]): Concentration parameter of the lens. Default is None. - s (float): Softening parameter to avoid singularities at the center of the lens. - Default is 0.0. - use_case (str): Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile - specifically cant be both batchable and differentiable. You may select which version - you wish to use by setting this parameter to one of: batchable, differentiable. - - Methods: - get_scale_radius: Returns the scale radius of the lens. - get_scale_density: Returns the scale density of the lens. - get_convergence_s: Returns the dimensionless surface mass density of the lens. - _f: Helper method for computing deflection angles. - _g: Helper method for computing lensing potential. - _h: Helper method for computing reduced deflection angles. - deflection_angle_hat: Computes the reduced deflection angle. - deflection_angle: Computes the deflection angle. - convergence: Computes the convergence (dimensionless surface mass density). - potential: Computes the lensing potential. + Attributes + ----------- + z_l: Optional[Tensor] + Redshift of the lens. Default is None. + x0: Optional[Tensor] + x-coordinate of the lens center in the lens plane. Default is None. + y0: Optional[Tensor] + y-coordinate of the lens center in the lens plane. Default is None. + m: Optional[Tensor] + Mass of the lens. Default is None. + c: Optional[Tensor] + Concentration parameter of the lens. Default is None. + s: float + Softening parameter to avoid singularities at the center of the lens. Default is 0.0. + use_case: str + Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile + specifically cant be both batchable and differentiable. You may select which version + you wish to use by setting this parameter to one of: batchable, differentiable. + + Methods + ------- + get_scale_radius + Returns the scale radius of the lens. + get_scale_density + Returns the scale density of the lens. + get_convergence_s + Returns the dimensionless surface mass density of the lens. + _f + Helper method for computing deflection angles. + _g + Helper method for computing lensing potential. + _h + Helper method for computing reduced deflection angles. + deflection_angle_hat + Computes the reduced deflection angle. + deflection_angle + Computes the deflection angle. + convergence + Computes the convergence (dimensionless surface mass density). + potential + Computes the lensing potential. """ def __init__( self, @@ -61,19 +77,28 @@ def __init__( """ Initialize an instance of the NFW lens class. - Args: - name (str): Name of the lens instance. - cosmology (Cosmology): An instance of the Cosmology class which contains - information about the cosmological model and parameters. - z_l (Optional[Union[Tensor, float]]): Redshift of the lens. Default is None. - x0 (Optional[Union[Tensor, float]]): x-coordinate of the lens center in the lens plane. + Parameters + ---------- + name: str + Name of the lens instance. + cosmology: Cosmology + An instance of the Cosmology class which contains + information about the cosmological model and parameters. + z_l: Optional[Union[Tensor, float]] + Redshift of the lens. Default is None. + x0: Optional[Union[Tensor, float]] + x-coordinate of the lens center in the lens plane. Default is None. - y0 (Optional[Union[Tensor, float]]): y-coordinate of the lens center in the lens plane. + y0: Optional[Union[Tensor, float]] + y-coordinate of the lens center in the lens plane. Default is None. - m (Optional[Union[Tensor, float]]): Mass of the lens. Default is None. - c (Optional[Union[Tensor, float]]): Concentration parameter of the lens. Default is None. - s (float): Softening parameter to avoid singularities at the center of the lens. - Default is 0.0. + m: Optional[Union[Tensor, float]] + Mass of the lens. Default is None. + c: Optional[Union[Tensor, float]] + Concentration parameter of the lens. Default is None. + s: float + Softening parameter to avoid singularities at the center of the lens. + Default is 0.0. """ super().__init__(cosmology, z_l, name=name) @@ -98,14 +123,21 @@ def get_scale_radius(self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] """ Calculate the scale radius of the lens. - Args: - z_l (Tensor): Redshift of the lens. - m (Tensor): Mass of the lens. - c (Tensor): Concentration parameter of the lens. - x (dict): Dynamic parameter container. - - Returns: - Tensor: The scale radius of the lens in Mpc. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + m: Tensor + Mass of the lens. + c: Tensor + Concentration parameter of the lens. + x: dict + Dynamic parameter container. + + Returns + ------- + Tensor + The scale radius of the lens in Mpc. """ critical_density = self.cosmology.critical_density(z_l, params) r_delta = (3 * m / (4 * pi * DELTA * critical_density)) ** (1 / 3) @@ -116,13 +148,19 @@ def get_scale_density(self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] """ Calculate the scale density of the lens. - Args: - z_l (Tensor): Redshift of the lens. - c (Tensor): Concentration parameter of the lens. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The scale density of the lens in solar masses per Mpc cubed. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + c: Tensor + Concentration parameter of the lens. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The scale density of the lens in solar masses per Mpc cubed. """ return ( DELTA @@ -137,15 +175,23 @@ def get_convergence_s(self, z_s, z_l, x0, y0, m, c, *args, params: Optional["Pac """ Calculate the dimensionless surface mass density of the lens. - Args: - z_l (Tensor): Redshift of the lens. - z_s (Tensor): Redshift of the source. - m (Tensor): Mass of the lens. - c (Tensor): Concentration parameter of the lens. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The dimensionless surface mass density of the lens. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + z_s: Tensor + Redshift of the source. + m: Tensor + Mass of the lens. + c: Tensor + Concentration parameter of the lens. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The dimensionless surface mass density of the lens. """ critical_surface_density = self.cosmology.critical_surface_density(z_l, z_s, params) return self.get_scale_density(params) * self.get_scale_radius(params) / critical_surface_density @@ -155,11 +201,15 @@ def _f_differentiable(x: Tensor) -> Tensor: """ Helper method for computing deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the deflection angle computation. + Returns + ------- + Tensor + Result of the deflection angle computation. """ # TODO: generalize beyond torch, or patch Tensor f = torch.zeros_like(x) @@ -171,11 +221,15 @@ def _f_batchable(x: Tensor) -> Tensor: """ Helper method for computing deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the deflection angle computation. + Returns + ------- + Tensor + Result of the deflection angle computation. """ # TODO: generalize beyond torch, or patch Tensor return torch.where( @@ -193,11 +247,15 @@ def _g_differentiable(x: Tensor) -> Tensor: """ Helper method for computing lensing potential. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the lensing potential computation. + Returns + ------- + Tensor + Result of the lensing potential computation. """ # TODO: generalize beyond torch, or patch Tensor term_1 = (x / 2).log() ** 2 @@ -210,11 +268,15 @@ def _g_batchable(x: Tensor) -> Tensor: """ Helper method for computing lensing potential. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the lensing potential computation. + Returns + ------- + Tensor + Result of the lensing potential computation. """ # TODO: generalize beyond torch, or patch Tensor term_1 = (x / 2).log() ** 2 @@ -234,11 +296,15 @@ def _h_differentiable(x: Tensor) -> Tensor: """ Helper method for computing reduced deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the reduced deflection angle computation. + Returns + ------- + Tensor + Result of the reduced deflection angle computation. """ term_1 = (x / 2).log() term_2 = torch.ones_like(x) @@ -250,11 +316,15 @@ def _h_batchable(x: Tensor) -> Tensor: """ Helper method for computing reduced deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the reduced deflection angle computation. + Returns + ------- + Tensor + Result of the reduced deflection angle computation. """ term_1 = (x / 2).log() term_2 = torch.where( @@ -267,7 +337,7 @@ def _h_batchable(x: Tensor) -> Tensor: ) ) return term_1 + term_2 - + @unpack(3) def reduced_deflection_angle( @@ -276,14 +346,21 @@ def reduced_deflection_angle( """ Compute the reduced deflection angle. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The reduced deflection angles in the x and y directions. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -316,14 +393,21 @@ def convergence( """ Compute the convergence (dimensionless surface mass density). - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The convergence (dimensionless surface mass density). + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The convergence (dimensionless surface mass density). """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -341,14 +425,21 @@ def potential( """ Compute the lensing potential. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s From 2397ad19a04856b2d3c028468df56765221299cd Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 15:54:00 -0800 Subject: [PATCH 16/55] change to numpy docstring for pixelated.py --- caustic/lenses/pixelated_convergence.py | 252 +++++++++++++++--------- docs/jupyter_book/notebooks.ipynb | 122 ------------ 2 files changed, 161 insertions(+), 213 deletions(-) delete mode 100644 docs/jupyter_book/notebooks.ipynb diff --git a/caustic/lenses/pixelated_convergence.py b/caustic/lenses/pixelated_convergence.py index 320cacea..fa25063f 100644 --- a/caustic/lenses/pixelated_convergence.py +++ b/caustic/lenses/pixelated_convergence.py @@ -38,28 +38,41 @@ def __init__( grid using either Fast Fourier Transform (FFT) or a 2D convolution. - Attributes: - name (str): The name of the PixelatedConvergence object. - fov (float): The field of view in arcseconds. - n_pix (int): The number of pixels on each side of the grid. - cosmology (Cosmology): An instance of the cosmological parameters. - z_l (Optional[Tensor]): The redshift of the lens. - x0 (Optional[Tensor]): The x-coordinate of the center of the grid. - y0 (Optional[Tensor]): The y-coordinate of the center of the grid. - convergence_map (Optional[Tensor]): A 2D tensor representing the convergence map. - shape (Optional[tuple[int, ...]]): The shape of the convergence map. - convolution_mode (str, optional): The convolution mode for calculating deflection angles and lensing potential. - It can be either "fft" (Fast Fourier Transform) or "conv2d" (2D convolution). Default is "fft". - use_next_fast_len (bool, optional): If True, adds additional padding to speed up the FFT by calling - `scipy.fft.next_fast_len`. The speed boost can be substantial when `n_pix` is a multiple of a - small prime number. Default is True. - padding (str): Specifies the type of padding to use. "zero" will do zero padding, "circular" will do - cyclic boundaries. "reflect" will do reflection padding. "tile" will tile the image at 2x2 which - basically identical to circular padding, but is easier. Generally you should use either "zero" - or "tile". + Attributes + ---------- + name: string + The name of the PixelatedConvergence object. + fov: float + The field of view in arcseconds. + n_pix: int + The number of pixels on each side of the grid. + cosmology: Cosmology + An instance of the cosmological parameters. + z_l: Optional[Tensor] + The redshift of the lens. + x0: Optional[Tensor] + The x-coordinate of the center of the grid. + y0: Optional[Tensor] + The y-coordinate of the center of the grid. + convergence_map: Optional[Tensor] + A 2D tensor representing the convergence map. + shape: Optional[tuple[int, ...]] + The shape of the convergence map. + convolution_mode: (str, optional) + The convolution mode for calculating deflection angles and lensing potential. + It can be either "fft" (Fast Fourier Transform) or "conv2d" (2D convolution). Default is "fft". + use_next_fast_len: (bool, optional) + If True, adds additional padding to speed up the FFT by calling + `scipy.fft.next_fast_len`. The speed boost can be substantial when `n_pix` is a multiple of a + small prime number. Default is True. + padding: string + Specifies the type of padding to use. "zero" will do zero padding, "circular" will do + cyclic boundaries. "reflect" will do reflection padding. "tile" will tile the image at 2x2 which + basically identical to circular padding, but is easier. Generally you should use either "zero" + or "tile". """ - + super().__init__(cosmology, z_l, name=name) if convergence_map is not None and convergence_map.ndim != 2: @@ -109,10 +122,13 @@ def to( """ Move the ConvergenceGrid object and all its tensors to the specified device and dtype. - Args: - device (Optional[torch.device]): The target device to move the tensors to. - dtype (Optional[torch.dtype]): The target data type to cast the tensors to. - """ + Parameters + ---------- + device: Optional[torch.device] + The target device to move the tensors to. + dtype: Optional[torch.dtype] + The target data type to cast the tensors to. + """ super().to(device, dtype) self.potential_kernel = self.potential_kernel.to(device=device, dtype=dtype) self.ax_kernel = self.ax_kernel.to(device=device, dtype=dtype) @@ -128,17 +144,20 @@ def _fft2_padded(self, x: Tensor) -> Tensor: """ Compute the 2D Fast Fourier Transform (FFT) of a tensor with zero-padding. - Args: - x (Tensor): The input tensor to be transformed. + Parameters + x: Tensor + The input tensor to be transformed. - Returns: - Tensor: The 2D FFT of the input tensor with zero-padding. + Returns + ------- + Tensor + The 2D FFT of the input tensor with zero-padding. """ pad = 2 * self.n_pix if self.use_next_fast_len: pad = next_fast_len(pad) self._s = (pad, pad) - + if self.padding == "zero": pass elif self.padding in ["reflect", "circular"]: @@ -147,16 +166,20 @@ def _fft2_padded(self, x: Tensor) -> Tensor: x = torch.tile(x, (2,2)) return torch.fft.rfft2(x, self._s) - + def _unpad_fft(self, x: Tensor) -> Tensor: """ Remove padding from the result of a 2D FFT. - Args: - x (Tensor): The input tensor with padding. + Parameters + ---------- + x: Tensor + The input tensor with padding. - Returns: - Tensor: The input tensor without padding. + Returns + ------- + Tensor + The input tensor without padding. """ return torch.roll(x, (-self._s[0]//2,-self._s[1]//2), dims = (-2,-1))[..., :self.n_pix, :self.n_pix] @@ -164,11 +187,15 @@ def _unpad_conv2d(self, x: Tensor) -> Tensor: """ Remove padding from the result of a 2D convolution. - Args: - x (Tensor): The input tensor with padding. + Parameters + ---------- + x: Tensor + The input tensor with padding. - Returns: - Tensor: The input tensor without padding. + Returns + ------- + Tensor + The input tensor without padding. """ return x # torch.roll(x, (-self.padding_range * self.ax_kernel.shape[0]//4,-self.padding_range * self.ax_kernel.shape[1]//4), dims = (-2,-1))[..., :self.n_pix, :self.n_pix] #[..., 1:, 1:] @@ -177,8 +204,10 @@ def convolution_mode(self): """ Get the convolution mode of the ConvergenceGrid object. - Returns: - str: The convolution mode, either "fft" or "conv2d". + Returns + ------- + string + The convolution mode, either "fft" or "conv2d". """ return self._convolution_mode @@ -187,8 +216,10 @@ def convolution_mode(self, convolution_mode: str): """ Set the convolution mode of the ConvergenceGrid object. - Args: - mode (str): The convolution mode to be set, either "fft" or "conv2d". + Parameters + ---------- + mode: string + The convolution mode to be set, either "fft" or "conv2d". """ if convolution_mode == "fft": # Create FFTs of kernels @@ -212,14 +243,21 @@ def reduced_deflection_angle( """ Compute the deflection angles at the specified positions using the given convergence map. - Args: - x (Tensor): The x-coordinates of the positions to compute the deflection angles for. - y (Tensor): The y-coordinates of the positions to compute the deflection angles for. - z_s (Tensor): The source redshift. - params (Packed, optional): A dictionary containing additional parameters. - - Returns: - tuple[Tensor, Tensor]: The x and y components of the deflection angles at the specified positions. + Parameters + ---------- + x: Tensor + The x-coordinates of the positions to compute the deflection angles for. + y: Tensor + The y-coordinates of the positions to compute the deflection angles for. + z_s: Tensor + The source redshift. + params: (Packed, optional) + A dictionary containing additional parameters. + + Returns + ------- + tuple[Tensor, Tensor] + The x and y components of the deflection angles at the specified positions. """ if self.convolution_mode == "fft": deflection_angle_x_map, deflection_angle_y_map = self._deflection_angle_fft(convergence_map) @@ -239,11 +277,15 @@ def _deflection_angle_fft(self, convergence_map: Tensor) -> tuple[Tensor, Tensor """ Compute the deflection angles using the Fast Fourier Transform (FFT) method. - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. - Returns: - tuple[Tensor, Tensor]: The x and y components of the deflection angles. + Returns + ------- + tuple[Tensor, Tensor] + The x and y components of the deflection angles. """ convergence_tilde = self._fft2_padded(convergence_map) deflection_angle_x = torch.fft.irfft2(convergence_tilde * self.ax_kernel_tilde, self._s).real * ( @@ -258,15 +300,19 @@ def _deflection_angle_conv2d(self, convergence_map: Tensor) -> tuple[Tensor, Ten """ Compute the deflection angles using the 2D convolution method. - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. - Returns: - tuple[Tensor, Tensor]: The x and y components of the deflection angles. + Returns + ------- + tuple[Tensor, Tensor] + The x and y components of the deflection angles. """ # Use convergence_map as kernel since the kernel is twice as large. Flip since # we actually want the cross-correlation. - + pad = 2 * self.n_pix convergence_map_flipped = convergence_map.flip((-1, -2))[None, None] # F.pad(, ((pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2), mode = self.padding_mode) deflection_angle_x = F.conv2d(self.ax_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( @@ -284,14 +330,21 @@ def potential( """ Compute the lensing potential at the specified positions using the given convergence map. - Args: - x (Tensor): The x-coordinates of the positions to compute the lensing potential for. - y (Tensor): The y-coordinates of the positions to compute the lensing potential for. - z_s (Tensor): The source redshift. - params (Packed, optional): A dictionary containing additional parameters. - - Returns: - Tensor: The lensing potential at the specified positions. + Parameters + ---------- + x: Tensor + The x-coordinates of the positions to compute the lensing potential for. + y: Tensor + The y-coordinates of the positions to compute the lensing potential for. + z_s: Tensor + The source redshift. + params: (Packed, optional) + A dictionary containing additional parameters. + + Returns + ------- + Tensor + The lensing potential at the specified positions. """ if self.convolution_mode == "fft": potential_map = self._potential_fft(convergence_map) @@ -307,12 +360,16 @@ def potential( def _potential_fft(self, convergence_map: Tensor) -> Tensor: """ Compute the lensing potential using the Fast Fourier Transform (FFT) method. - - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. - - Returns: - Tensor: The lensing potential. + + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. + + Returns + ------- + Tensor + The lensing potential. """ convergence_tilde = self._fft2_padded(convergence_map) potential = torch.fft.irfft2(convergence_tilde * self.potential_kernel_tilde, self._s) * ( @@ -323,12 +380,16 @@ def _potential_fft(self, convergence_map: Tensor) -> Tensor: def _potential_conv2d(self, convergence_map: Tensor) -> Tensor: """ Compute the lensing potential using the 2D convolution method. - - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. - - Returns: - Tensor: The lensing potential. + + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. + + Returns + ------- + Tensor + The lensing potential. """ # Use convergence_map as kernel since the kernel is twice as large. Flip since # we actually want the cross-correlation. @@ -344,18 +405,27 @@ def convergence( ) -> Tensor: """ Compute the convergence at the specified positions. This method is not implemented. - - Args: - x (Tensor): The x-coordinates of the positions to compute the convergence for. - y (Tensor): The y-coordinates of the positions to compute the convergence for. - z_s (Tensor): The source redshift. - params (Packed, optional): A dictionary containing additional parameters. - - Returns: - Tensor: The convergence at the specified positions. - - Raises: - NotImplementedError: This method is not implemented. + + Parameters + ---------- + x: Tensor + The x-coordinates of the positions to compute the convergence for. + y: Tensor + The y-coordinates of the positions to compute the convergence for. + z_s: Tensor + The source redshift. + params: (Packed, optional) + A dictionary containing additional parameters. + + Returns + ------- + Tensor + The convergence at the specified positions. + + Raises + ------ + NotImplementedError + This method is not implemented. """ return interp2d( convergence_map, (x - x0).view(-1) / self.fov*2, (y - y0).view(-1) / self.fov*2 diff --git a/docs/jupyter_book/notebooks.ipynb b/docs/jupyter_book/notebooks.ipynb deleted file mode 100644 index fdb7176c..00000000 --- a/docs/jupyter_book/notebooks.ipynb +++ /dev/null @@ -1,122 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Content with notebooks\n", - "\n", - "You can also create content with Jupyter Notebooks. This means that you can include\n", - "code blocks and their outputs in your book.\n", - "\n", - "## Markdown + notebooks\n", - "\n", - "As it is markdown, you can embed images, HTML, etc into your posts!\n", - "\n", - "![](https://myst-parser.readthedocs.io/en/latest/_static/logo-wide.svg)\n", - "\n", - "You can also $add_{math}$ and\n", - "\n", - "$$\n", - "math^{blocks}\n", - "$$\n", - "\n", - "or\n", - "\n", - "$$\n", - "\\begin{aligned}\n", - "\\mbox{mean} la_{tex} \\\\ \\\\\n", - "math blocks\n", - "\\end{aligned}\n", - "$$\n", - "\n", - "But make sure you \\$Escape \\$your \\$dollar signs \\$you want to keep!\n", - "\n", - "## MyST markdown\n", - "\n", - "MyST markdown works in Jupyter Notebooks as well. For more information about MyST markdown, check\n", - "out [the MyST guide in Jupyter Book](https://jupyterbook.org/content/myst.html),\n", - "or see [the MyST markdown documentation](https://myst-parser.readthedocs.io/en/latest/).\n", - "\n", - "## Code blocks and outputs\n", - "\n", - "Jupyter Book will also embed your code blocks and output in your book.\n", - "For example, here's some sample Matplotlib code:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import rcParams, cycler\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "plt.ion()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Fixing random state for reproducibility\n", - "np.random.seed(19680801)\n", - "\n", - "N = 10\n", - "data = [np.logspace(0, 1, 100) + np.random.randn(100) + ii for ii in range(N)]\n", - "data = np.array(data).T\n", - "cmap = plt.cm.coolwarm\n", - "rcParams['axes.prop_cycle'] = cycler(color=cmap(np.linspace(0, 1, N)))\n", - "\n", - "\n", - "from matplotlib.lines import Line2D\n", - "custom_lines = [Line2D([0], [0], color=cmap(0.), lw=4),\n", - " Line2D([0], [0], color=cmap(.5), lw=4),\n", - " Line2D([0], [0], color=cmap(1.), lw=4)]\n", - "\n", - "fig, ax = plt.subplots(figsize=(10, 5))\n", - "lines = ax.plot(data)\n", - "ax.legend(custom_lines, ['Cold', 'Medium', 'Hot']);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is a lot more that you can do with outputs (such as including interactive outputs)\n", - "with your book. For more information about this, see [the Jupyter Book documentation](https://jupyterbook.org)" - ] - } - ], - "metadata": { - "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.8.0" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": {}, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From 023b6af9b46bf9f377fcfccc09a69e01f1aded43 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 16:00:15 -0800 Subject: [PATCH 17/55] modify the config.yml and toc.yml --- docs/jupyter_book/_config.yml | 4 ++-- docs/jupyter_book/_toc.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/jupyter_book/_config.yml b/docs/jupyter_book/_config.yml index 5f534f80..04579d5b 100644 --- a/docs/jupyter_book/_config.yml +++ b/docs/jupyter_book/_config.yml @@ -1,8 +1,8 @@ # Book settings # Learn more at https://jupyterbook.org/customize/config.html -title: My sample book -author: The Jupyter Book Community +title: caustic +author: Ciela Institute logo: logo.png # Force re-execution of notebooks on each build. diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index 74d5c710..258e8f89 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -4,6 +4,6 @@ format: jb-book root: intro chapters: -- file: markdown +- file: get_started - file: notebooks - file: markdown-notebooks From ed3153ec551a8a8d90e448891e7ea751455072a3 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 22:44:20 -0800 Subject: [PATCH 18/55] modify ci.yml, _toc.yml --- .github/workflows/ci.yml | 2 +- docs/jupyter_book/_toc.yml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6bb9722..5216df74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - python-version: ["3.9", "3.10"] + python-version: ["3.9", "3.10", "3.11"] os: [ubuntu-latest, windows-latest, macOS-latest] steps: diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index 258e8f89..1fdcfe2f 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -5,5 +5,8 @@ format: jb-book root: intro chapters: - file: get_started -- file: notebooks +- file: notebook +- file: markdown-notebooks +- file: markdown-notebooks +- file: markdown-notebooks - file: markdown-notebooks From 20cd4ed00b06065ddf87fb65692703d6427439c3 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 22 Nov 2023 12:08:35 -0800 Subject: [PATCH 19/55] update table of content --- docs/jupyter_book/_toc.yml | 12 +++--- docs/jupyter_book/intro.md | 10 ++--- docs/jupyter_book/markdown-notebooks.md | 53 ------------------------ docs/jupyter_book/markdown.md | 55 ------------------------- 4 files changed, 10 insertions(+), 120 deletions(-) delete mode 100644 docs/jupyter_book/markdown-notebooks.md delete mode 100644 docs/jupyter_book/markdown.md diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index 1fdcfe2f..e9f8a5c4 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -4,9 +4,9 @@ format: jb-book root: intro chapters: -- file: get_started -- file: notebook -- file: markdown-notebooks -- file: markdown-notebooks -- file: markdown-notebooks -- file: markdown-notebooks +- file: docs/getting_started # Getting Started +- file: docs/install # Installation +- file: docs/tutorials # Tutorials +- file: docs/contributing # Contributing +- file: docs/citation # Citation +- file: docs/license # Caustics \ No newline at end of file diff --git a/docs/jupyter_book/intro.md b/docs/jupyter_book/intro.md index f8cdc73c..13bb357d 100644 --- a/docs/jupyter_book/intro.md +++ b/docs/jupyter_book/intro.md @@ -1,11 +1,9 @@ -# Welcome to your Jupyter Book +# Welcome to Caustics’ documentation! -This is a small sample book to give you a feel for how book content is -structured. -It shows off a few of the major file types, as well as some sample content. -It does not go in-depth into any particular topic - check out [the Jupyter Book documentation](https://jupyterbook.org) for more information. +The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. -Check out the content pages bundled with this sample book to see more. +# Intallation +The easiest way to install is to make a new virtual environment then run: ```{tableofcontents} ``` diff --git a/docs/jupyter_book/markdown-notebooks.md b/docs/jupyter_book/markdown-notebooks.md deleted file mode 100644 index a057a320..00000000 --- a/docs/jupyter_book/markdown-notebooks.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -jupytext: - formats: md:myst - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.11.5 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- - -# Notebooks with MyST Markdown - -Jupyter Book also lets you write text-based notebooks using MyST Markdown. -See [the Notebooks with MyST Markdown documentation](https://jupyterbook.org/file-types/myst-notebooks.html) for more detailed instructions. -This page shows off a notebook written in MyST Markdown. - -## An example cell - -With MyST Markdown, you can define code cells with a directive like so: - -```{code-cell} -print(2 + 2) -``` - -When your book is built, the contents of any `{code-cell}` blocks will be -executed with your default Jupyter kernel, and their outputs will be displayed -in-line with the rest of your content. - -```{seealso} -Jupyter Book uses [Jupytext](https://jupytext.readthedocs.io/en/latest/) to convert text-based files to notebooks, and can support [many other text-based notebook files](https://jupyterbook.org/file-types/jupytext.html). -``` - -## Create a notebook with MyST Markdown - -MyST Markdown notebooks are defined by two things: - -1. YAML metadata that is needed to understand if / how it should convert text files to notebooks (including information about the kernel needed). - See the YAML at the top of this page for example. -2. The presence of `{code-cell}` directives, which will be executed with your book. - -That's all that is needed to get started! - -## Quickly add YAML metadata for MyST Notebooks - -If you have a markdown file and you'd like to quickly add YAML metadata to it, so that Jupyter Book will treat it as a MyST Markdown Notebook, run the following command: - -``` -jupyter-book myst init path/to/markdownfile.md -``` diff --git a/docs/jupyter_book/markdown.md b/docs/jupyter_book/markdown.md deleted file mode 100644 index 0ddaab3f..00000000 --- a/docs/jupyter_book/markdown.md +++ /dev/null @@ -1,55 +0,0 @@ -# Markdown Files - -Whether you write your book's content in Jupyter Notebooks (`.ipynb`) or -in regular markdown files (`.md`), you'll write in the same flavor of markdown -called **MyST Markdown**. -This is a simple file to help you get started and show off some syntax. - -## What is MyST? - -MyST stands for "Markedly Structured Text". It -is a slight variation on a flavor of markdown called "CommonMark" markdown, -with small syntax extensions to allow you to write **roles** and **directives** -in the Sphinx ecosystem. - -For more about MyST, see [the MyST Markdown Overview](https://jupyterbook.org/content/myst.html). - -## Sample Roles and Directives - -Roles and directives are two of the most powerful tools in Jupyter Book. They -are kind of like functions, but written in a markup language. They both -serve a similar purpose, but **roles are written in one line**, whereas -**directives span many lines**. They both accept different kinds of inputs, -and what they do with those inputs depends on the specific role or directive -that is being called. - -Here is a "note" directive: - -```{note} -Here is a note -``` - -It will be rendered in a special box when you build your book. - -Here is an inline directive to refer to a document: {doc}`markdown-notebooks`. - - -## Citations - -You can also cite references that are stored in a `bibtex` file. For example, -the following syntax: `` {cite}`holdgraf_evidence_2014` `` will render like -this: {cite}`holdgraf_evidence_2014`. - -Moreover, you can insert a bibliography into your page with this syntax: -The `{bibliography}` directive must be used for all the `{cite}` roles to -render properly. -For example, if the references for your book are stored in `references.bib`, -then the bibliography is inserted with: - -```{bibliography} -``` - -## Learn more - -This is just a simple starter to get you started. -You can learn a lot more at [jupyterbook.org](https://jupyterbook.org). From f3a6ee2d6dada1b9b282a0c9154083d0199b95a3 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 22 Nov 2023 12:45:36 -0800 Subject: [PATCH 20/55] add basic table --- docs/jupyter_book/_config.yml | 4 +-- docs/jupyter_book/_toc.yml | 3 +- docs/jupyter_book/intro.md | 23 +++++++++++++ docs/jupyter_book/references.bib | 56 -------------------------------- 4 files changed, 27 insertions(+), 59 deletions(-) delete mode 100644 docs/jupyter_book/references.bib diff --git a/docs/jupyter_book/_config.yml b/docs/jupyter_book/_config.yml index 04579d5b..0a23c1de 100644 --- a/docs/jupyter_book/_config.yml +++ b/docs/jupyter_book/_config.yml @@ -16,8 +16,8 @@ latex: targetname: book.tex # Add a bibtex file so that we can create citations -bibtex_bibfiles: - - references.bib +# bibtex_bibfiles: +# - references.bib # Information about where the book exists on the web repository: diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index e9f8a5c4..f4c44443 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -9,4 +9,5 @@ chapters: - file: docs/tutorials # Tutorials - file: docs/contributing # Contributing - file: docs/citation # Citation -- file: docs/license # Caustics \ No newline at end of file +- file: docs/license # Caustics +- file: docs/modules # modules \ No newline at end of file diff --git a/docs/jupyter_book/intro.md b/docs/jupyter_book/intro.md index 13bb357d..397b5649 100644 --- a/docs/jupyter_book/intro.md +++ b/docs/jupyter_book/intro.md @@ -1,9 +1,32 @@ # Welcome to Caustics’ documentation! The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. +```{note} +Caustic is in its early development phase. This means the API will change with time. These changes are a good thing, but they can be annoying. Watch the version numbers, when we get to 1.0.0 that will be the first stable release! +``` # Intallation The easiest way to install is to make a new virtual environment then run: +```console +pip install caustic +``` + +this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. If you want to help out with building the caustic code base check out the developer installation instructions instead. + + +# Read The Docs +## Contents +```{tableofcontents} +docs/getting_started.rst +docs/install.rst +docs/tutorials.rst +docs/contributing.rst +docs/citation.rst +docs/license.rst +``` + +# Indices and tables ```{tableofcontents} +docs/modules.rst ``` diff --git a/docs/jupyter_book/references.bib b/docs/jupyter_book/references.bib deleted file mode 100644 index 783ec6aa..00000000 --- a/docs/jupyter_book/references.bib +++ /dev/null @@ -1,56 +0,0 @@ ---- ---- - -@inproceedings{holdgraf_evidence_2014, - address = {Brisbane, Australia, Australia}, - title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}}, - booktitle = {International {Conference} on {Cognitive} {Neuroscience}}, - publisher = {Frontiers in Neuroscience}, - author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.}, - year = {2014} -} - -@article{holdgraf_rapid_2016, - title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility}, - volume = {7}, - issn = {2041-1723}, - url = {http://www.nature.com/doifinder/10.1038/ncomms13654}, - doi = {10.1038/ncomms13654}, - number = {May}, - journal = {Nature Communications}, - author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.}, - year = {2016}, - pages = {13654}, - file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf} -} - -@inproceedings{holdgraf_portable_2017, - title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science}, - volume = {Part F1287}, - isbn = {978-1-4503-5272-7}, - doi = {10.1145/3093338.3093370}, - abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.}, - booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}}, - author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.}, - year = {2017}, - keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy} -} - -@article{holdgraf_encoding_2017, - title = {Encoding and decoding models in cognitive electrophysiology}, - volume = {11}, - issn = {16625137}, - doi = {10.3389/fnsys.2017.00061}, - abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.}, - journal = {Frontiers in Systems Neuroscience}, - author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.}, - year = {2017}, - keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials} -} - -@book{ruby, - title = {The Ruby Programming Language}, - author = {Flanagan, David and Matsumoto, Yukihiro}, - year = {2008}, - publisher = {O'Reilly Media} -} From c2a1f35ea49793fc30aae96342b51793a503b2ac Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 22 Nov 2023 13:04:25 -0800 Subject: [PATCH 21/55] change the path of jupyter book --- docs/{jupyter_book => }/get_start.ipynb | 0 docs/jupyter_book/_toc.yml | 13 ---- .../jupyter_book => jupyter_book}/_config.yml | 0 jupyter_book/_toc.yml | 13 ++++ jupyter_book/citation.rst | 6 ++ jupyter_book/contributing.rst | 64 ++++++++++++++++++ jupyter_book/getting_started.rst | 20 ++++++ jupyter_book/install.rst | 30 ++++++++ {docs/jupyter_book => jupyter_book}/intro.md | 12 ---- jupyter_book/license.rst | 25 +++++++ {docs/jupyter_book => jupyter_book}/logo.png | Bin jupyter_book/modules.rst | 7 ++ .../requirements.txt | 0 jupyter_book/tutorials.rst | 23 +++++++ 14 files changed, 188 insertions(+), 25 deletions(-) rename docs/{jupyter_book => }/get_start.ipynb (100%) delete mode 100644 docs/jupyter_book/_toc.yml rename {docs/jupyter_book => jupyter_book}/_config.yml (100%) create mode 100644 jupyter_book/_toc.yml create mode 100644 jupyter_book/citation.rst create mode 100644 jupyter_book/contributing.rst create mode 100644 jupyter_book/getting_started.rst create mode 100644 jupyter_book/install.rst rename {docs/jupyter_book => jupyter_book}/intro.md (80%) create mode 100644 jupyter_book/license.rst rename {docs/jupyter_book => jupyter_book}/logo.png (100%) create mode 100644 jupyter_book/modules.rst rename {docs/jupyter_book => jupyter_book}/requirements.txt (100%) create mode 100644 jupyter_book/tutorials.rst diff --git a/docs/jupyter_book/get_start.ipynb b/docs/get_start.ipynb similarity index 100% rename from docs/jupyter_book/get_start.ipynb rename to docs/get_start.ipynb diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml deleted file mode 100644 index f4c44443..00000000 --- a/docs/jupyter_book/_toc.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Table of contents -# Learn more at https://jupyterbook.org/customize/toc.html - -format: jb-book -root: intro -chapters: -- file: docs/getting_started # Getting Started -- file: docs/install # Installation -- file: docs/tutorials # Tutorials -- file: docs/contributing # Contributing -- file: docs/citation # Citation -- file: docs/license # Caustics -- file: docs/modules # modules \ No newline at end of file diff --git a/docs/jupyter_book/_config.yml b/jupyter_book/_config.yml similarity index 100% rename from docs/jupyter_book/_config.yml rename to jupyter_book/_config.yml diff --git a/jupyter_book/_toc.yml b/jupyter_book/_toc.yml new file mode 100644 index 00000000..23b8900a --- /dev/null +++ b/jupyter_book/_toc.yml @@ -0,0 +1,13 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: intro +chapters: +- file: docs/getting_started.rst # Getting Started +- file: docs/install.rst # Installation +- file: docs/tutorials.rst # Tutorials +- file: docs/contributing.rst # Contributing +- file: docs/citation.rst # Citation +- file: docs/license.rst # Caustics +- file: docs/modules.rst # modules \ No newline at end of file diff --git a/jupyter_book/citation.rst b/jupyter_book/citation.rst new file mode 100644 index 00000000..0d200348 --- /dev/null +++ b/jupyter_book/citation.rst @@ -0,0 +1,6 @@ + + +Citation +======== + +Paper comming soon! We will put all the citation information here when its ready. diff --git a/jupyter_book/contributing.rst b/jupyter_book/contributing.rst new file mode 100644 index 00000000..b384222c --- /dev/null +++ b/jupyter_book/contributing.rst @@ -0,0 +1,64 @@ +Contributing +============ + +.. contents:: Table of Contents + :local: + +Thanks for helping make caustic better! Here you will learn the full process needed to contribute to caustic. Following these steps will make the process as painless as possible for everyone. + +Create An Issue +--------------- + +Before actually writing any code, its best to create an issue on the GitHub. Describe the issue in detail and let us know the desired solution. Here it will be possible to address concerns (maybe its aready solved and just not yet documented) and plan out the best solution. We may also assign someone to work on it if that seems better. Note that submitting an issue is a contribution to caustic, we appreciate your ideas! Still, if after discussion it seems that the problem does need some work and you're the person to do it, then we can move on to the next steps. + +1. Navigate to the **Issues** tab of the GitHub repository. +2. Click on **New Issue**. +3. Specify a concise and descriptive title. +4. In the issue body, elaborate on the problem or feature request, employing adequate code snippets or references as necessary. +5. Submit the issue by clicking **Submit new issue**. + +Install +------- + +Please fork the caustic repo, then follow the developer install instructions at the :doc:`install` page. This will ensure you have a version of caustic that you can tinker with and see the results. + +The reason you should fork the repo is so that you have full control while making your edits. You will still be able to make a Pull Request later when it is time to merge your code with the main caustic branch. Note that you should keep your fork up to date with the caustic repo to make the merge as smooth as possible. + +Notebooks +--------- + +You will likely want to see how your changes affect various features of caustic. A good way to quickly see this is to run the tutorial notebooks which can be found `here `_. Any change that breaks one of these must be addressed, either by changing the nature of your updates to the code, or by forking and updating the caustic-tutorials repo as well (this is usually pretty easy). + +Resolving the Issue +------------------- + +As you modify the code, make sure to regularly commit changes and push them to your fork. This makes it easier for you to fix something if you make a mistake, and easier for us to see what changes were made along the way. Feel free to return to the issue on the main GitHub page for advice as to proceed. + +1. Make the necessary code modifications to address the issue. +2. Use ``git status`` to inspect the changes. +3. Execute ``git add .`` to stage the changes. +4. Commit the changes with ``git commit -m ""``. +5. Push the changes to your fork by executing ``git push origin ``. + +Unit Tests +---------- + +When you think you've solved an issue, please make unit tests related to any code you have added. Any new code added to caustic must have unit tests which match the level of completion of the rest of the code. Generally you should test all cases for the newly added code. Also ensure the previous unit tests run correctly. + +Submitting a Pull Request +------------------------- + +Once you think your updates are ready to merge with the rest of caustic you can submit a PR! This should provide a description of what you have changed and if it isn't straightforward, why you made those changes. + +1. Navigate to the **Pull Requests** tab of the original repository. +2. Click on **New Pull Request**. +3. Choose the appropriate base and compare branches. +4. Provide a concise and descriptive title and elaborate on the pull request body. +5. Click **Create Pull Request**. + +Finalizing a Pull Request +------------------------- + +Once the PR is submitted, we will look through it and request any changes necessary before merging it into the main branch. You can make those changes just like any other edits on your fork. Then when you push them, they will be joined in to the PR automatically and any unit tests will run again. + +Once the PR has been merged, you may delete your fork if you aren't using it any more, or take on a new issue, it's up to you! diff --git a/jupyter_book/getting_started.rst b/jupyter_book/getting_started.rst new file mode 100644 index 00000000..1bd0d949 --- /dev/null +++ b/jupyter_book/getting_started.rst @@ -0,0 +1,20 @@ + +Getting Started +=============== + +Install +------- + +Please follow the instructions on the :doc:`install` page. For most users, the basic pip install is all that's needed. + + +Tutorials +--------- + +We have created a repository of tutorial Jupyter notebooks that can help initiate you on the main features of caustic. Please checkout the `tutorials here `_ to see what caustic can do! + + +Read The Docs +------------- + +Docs for all the main functions in caustic are avaialble at :doc:`caustic` at varying degrees of completeness. Further development of the docs is always ongoing. diff --git a/jupyter_book/install.rst b/jupyter_book/install.rst new file mode 100644 index 00000000..077356f2 --- /dev/null +++ b/jupyter_book/install.rst @@ -0,0 +1,30 @@ + +Installation +============ + +Regular Install +--------------- + +The easiest way to install is to make a new virtual environment then run:: + + pip install caustic + +this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. + + +Developer Install +----------------- + +First clone the repo with:: + + git clone git@github.com:Ciela-Institute/caustic.git + +this will create a directory `caustic` wherever you ran the command. Next go into the directory and install in developer mode:: + + pip install -e ".[dev]" + +this will install all relevant libraries and then install caustic in an editable format so any changes you make to the code will be included next time you import the package. To start making changes you should immediately create a new branch:: + + git checkout -b + +you can edit this branch however you like. If you are happy with the results and want to share with the rest of the community, then follow the contributors guide to create a pull request! diff --git a/docs/jupyter_book/intro.md b/jupyter_book/intro.md similarity index 80% rename from docs/jupyter_book/intro.md rename to jupyter_book/intro.md index 397b5649..35018a1f 100644 --- a/docs/jupyter_book/intro.md +++ b/jupyter_book/intro.md @@ -17,16 +17,4 @@ this will install all the required libraries and then install caustic and you ar # Read The Docs ## Contents -```{tableofcontents} -docs/getting_started.rst -docs/install.rst -docs/tutorials.rst -docs/contributing.rst -docs/citation.rst -docs/license.rst -``` -# Indices and tables -```{tableofcontents} -docs/modules.rst -``` diff --git a/jupyter_book/license.rst b/jupyter_book/license.rst new file mode 100644 index 00000000..54abbeda --- /dev/null +++ b/jupyter_book/license.rst @@ -0,0 +1,25 @@ + +License +======= + +MIT License + +Copyright (c) [2023] [caustic authors] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/jupyter_book/logo.png b/jupyter_book/logo.png similarity index 100% rename from docs/jupyter_book/logo.png rename to jupyter_book/logo.png diff --git a/jupyter_book/modules.rst b/jupyter_book/modules.rst new file mode 100644 index 00000000..a922b553 --- /dev/null +++ b/jupyter_book/modules.rst @@ -0,0 +1,7 @@ +caustic +======= + +.. toctree:: + :maxdepth: 4 + + caustic diff --git a/docs/jupyter_book/requirements.txt b/jupyter_book/requirements.txt similarity index 100% rename from docs/jupyter_book/requirements.txt rename to jupyter_book/requirements.txt diff --git a/jupyter_book/tutorials.rst b/jupyter_book/tutorials.rst new file mode 100644 index 00000000..a5997413 --- /dev/null +++ b/jupyter_book/tutorials.rst @@ -0,0 +1,23 @@ +========= +Tutorials +========= + +Here you will find the jupyter notebook tutorials. It is recommended +that you go through the tutorials yourself, but for quick reference a +version of each tutorial is available here. + +.. toctree:: + :maxdepth: 1 + + BasicIntroduction + LensZoo + VisualizeCaustics + MultiplaneDemo + InvertLensEquation + + + + + + + From ac023ce59c87fe8c266eea390194e218a72f37ba Mon Sep 17 00:00:00 2001 From: Don Setiawan Date: Mon, 27 Nov 2023 14:20:19 -0800 Subject: [PATCH 22/55] feat: Setup packaging and Github Codespaces dev environment (#26) * Update README.md Add SSEC badge * Move project to src/ directory * Update Codespaces link * Update README.md * Update README.md Update Codespaces link * Update devcontainer * ci: Create cd.yml for building and pushing package * Update caustic name to caustics * Add PyLint and Black formatting features to devcon * Closes issue uw-ssec/caustics#16 * Updated test/ -> tests/ * Move project to src/ directory * Update README.md * Update README.md Update Codespaces link * Create noxfile.py * Add .readthedocs.yaml * Create .git_archival.txt * Create .gitattributes * Create .pre-commit-config.yaml * Add sphinx and myst-parser support * style: pre-commit fixes * Update docs * style: pre-commit fixes * Update .github Actions * Update environment.yml - Add jupyter-book * Create CONTRIBUTING.md * Update README.md * chore: resolve conflicts * chore: Remove sources * test: remove test lenses * test: fix test_simulator * chore: Set min python to 3.9 * fix: change pytest to point to tests/ * build: update dep list to use reqs.txt * ci: Don't fail fast * chore(deps): Pin astropy < 6 * fix: change instances of caustic to caustics * fix: Perform pre-commit * fix: update path to conf.py * update README * style: pre-commit fixes * docs: Add contributor graph * chore: Setup dynamic versioning * ci: Update fetch depth to 0 to get correct version * Update README.md Update Codespaces button to new repo name --------- Co-authored-by: Cordero Core <127983572+uwcdc@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .devcontainer/apt.txt | 4 + .devcontainer/devcontainer.json | 21 + .devcontainer/environment.yml | 15 + .devcontainer/postBuild.sh | 4 + .devcontainer/start | 12 + .git_archival.txt | 4 + .gitattributes | 1 + .github/CONTRIBUTING.md | 101 +++++ .github/dependabot.yml | 7 + .github/matchers/pylint.json | 32 ++ .github/workflows/cd.yml | 99 ++++ .github/workflows/coverage.yaml | 86 ++-- .github/workflows/documentation.yaml | 23 +- .github/workflows/python-app.yml | 73 +-- .gitignore | 3 + .pre-commit-config.yaml | 85 ++++ .readthedocs.yaml => .readthedocs.yml | 24 +- README.md | 45 +- docs/Makefile | 2 +- docs/requirements.txt | 4 +- docs/{ => source}/citation.rst | 0 docs/{ => source}/conf.py | 72 ++- docs/{ => source}/contributing.rst | 0 docs/{ => source}/getting_started.rst | 0 docs/{ => source}/illustrative_examples.rst | 0 docs/{ => source}/index.rst | 6 +- docs/{ => source}/install.rst | 0 docs/{ => source}/license.rst | 0 docs/{ => source}/modules.rst | 0 docs/{ => source}/pull_notebooks.py | 0 docs/{ => source}/tutorials.rst | 7 - noxfile.py | 116 +++++ pyproject.toml | 63 +++ requirements.txt | 2 +- setup.py | 15 +- {caustics => src/caustics}/__init__.py | 7 +- {caustics => src/caustics}/constants.py | 14 +- {caustics => src/caustics}/cosmology.py | 77 +++- {caustics => src/caustics}/data/__init__.py | 0 .../caustics}/data/hdf5dataset.py | 0 .../caustics}/data/illustris_kappa.py | 0 {caustics => src/caustics}/data/probes.py | 0 {caustics => src/caustics}/lenses/__init__.py | 0 {caustics => src/caustics}/lenses/base.py | 427 ++++++++++++++---- {caustics => src/caustics}/lenses/epl.py | 51 ++- .../caustics}/lenses/external_shear.py | 57 ++- .../caustics}/lenses/mass_sheet.py | 46 +- .../caustics}/lenses/multiplane.py | 68 ++- {caustics => src/caustics}/lenses/nfw.py | 127 ++++-- .../caustics}/lenses/pixelated_convergence.py | 142 ++++-- {caustics => src/caustics}/lenses/point.py | 40 +- .../caustics}/lenses/pseudo_jaffe.py | 142 ++++-- {caustics => src/caustics}/lenses/sie.py | 52 ++- .../caustics}/lenses/singleplane.py | 34 +- {caustics => src/caustics}/lenses/sis.py | 44 +- {caustics => src/caustics}/lenses/tnfw.py | 246 +++++++--- {caustics => src/caustics}/lenses/utils.py | 6 +- {caustics => src/caustics}/light/__init__.py | 0 {caustics => src/caustics}/light/base.py | 37 +- {caustics => src/caustics}/light/pixelated.py | 45 +- {caustics => src/caustics}/light/probes.py | 0 {caustics => src/caustics}/light/sersic.py | 45 +- {caustics => src/caustics}/namespace_dict.py | 43 +- {caustics => src/caustics}/packed.py | 0 {caustics => src/caustics}/parameter.py | 19 +- {caustics => src/caustics}/parametrized.py | 104 +++-- {caustics => src/caustics}/sims/__init__.py | 0 .../caustics}/sims/lens_source.py | 89 ++-- {caustics => src/caustics}/sims/simulator.py | 4 +- {caustics => src/caustics}/utils.py | 95 ++-- test/test_simulator.py | 28 -- {test => tests}/test_base.py | 26 +- {test => tests}/test_batching.py | 52 ++- {test => tests}/test_cosmology.py | 0 {test => tests}/test_epl.py | 0 {test => tests}/test_external_shear.py | 0 {test => tests}/test_interpolate_image.py | 8 +- .../test_jacobian_lens_equation.py | 56 ++- {test => tests}/test_kappa_grid.py | 35 +- {test => tests}/test_masssheet.py | 16 +- {test => tests}/test_multiplane.py | 48 +- {test => tests}/test_namespace_dict.py | 2 - {test => tests}/test_nfw.py | 13 +- {test => tests}/test_parameter.py | 33 +- {test => tests}/test_parametrized.py | 88 ++-- {test => tests}/test_pixel_grid.py | 0 {test => tests}/test_point.py | 0 {test => tests}/test_pseudo_jaffe.py | 45 +- {test => tests}/test_sersic.py | 22 +- {test => tests}/test_sie.py | 3 +- tests/test_simulator.py | 89 ++++ {test => tests}/test_sis.py | 0 {test => tests}/test_tnfw.py | 34 +- {test => tests}/utils.py | 60 ++- 94 files changed, 2702 insertions(+), 943 deletions(-) create mode 100644 .devcontainer/apt.txt create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/environment.yml create mode 100644 .devcontainer/postBuild.sh create mode 100644 .devcontainer/start create mode 100644 .git_archival.txt create mode 100644 .gitattributes create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/dependabot.yml create mode 100644 .github/matchers/pylint.json create mode 100644 .github/workflows/cd.yml create mode 100644 .pre-commit-config.yaml rename .readthedocs.yaml => .readthedocs.yml (53%) rename docs/{ => source}/citation.rst (100%) rename docs/{ => source}/conf.py (82%) rename docs/{ => source}/contributing.rst (100%) rename docs/{ => source}/getting_started.rst (100%) rename docs/{ => source}/illustrative_examples.rst (100%) rename docs/{ => source}/index.rst (99%) rename docs/{ => source}/install.rst (100%) rename docs/{ => source}/license.rst (100%) rename docs/{ => source}/modules.rst (100%) rename docs/{ => source}/pull_notebooks.py (100%) rename docs/{ => source}/tutorials.rst (90%) create mode 100644 noxfile.py create mode 100644 pyproject.toml rename {caustics => src/caustics}/__init__.py (75%) rename {caustics => src/caustics}/constants.py (60%) rename {caustics => src/caustics}/cosmology.py (82%) rename {caustics => src/caustics}/data/__init__.py (100%) rename {caustics => src/caustics}/data/hdf5dataset.py (100%) rename {caustics => src/caustics}/data/illustris_kappa.py (100%) rename {caustics => src/caustics}/data/probes.py (100%) rename {caustics => src/caustics}/lenses/__init__.py (100%) rename {caustics => src/caustics}/lenses/base.py (66%) rename {caustics => src/caustics}/lenses/epl.py (90%) rename {caustics => src/caustics}/lenses/external_shear.py (78%) rename {caustics => src/caustics}/lenses/mass_sheet.py (76%) rename {caustics => src/caustics}/lenses/multiplane.py (83%) rename {caustics => src/caustics}/lenses/nfw.py (83%) rename {caustics => src/caustics}/lenses/pixelated_convergence.py (81%) rename {caustics => src/caustics}/lenses/point.py (85%) rename {caustics => src/caustics}/lenses/pseudo_jaffe.py (70%) rename {caustics => src/caustics}/lenses/sie.py (83%) rename {caustics => src/caustics}/lenses/singleplane.py (83%) rename {caustics => src/caustics}/lenses/sis.py (82%) rename {caustics => src/caustics}/lenses/tnfw.py (76%) rename {caustics => src/caustics}/lenses/utils.py (96%) rename {caustics => src/caustics}/light/__init__.py (100%) rename {caustics => src/caustics}/light/base.py (71%) rename {caustics => src/caustics}/light/pixelated.py (81%) rename {caustics => src/caustics}/light/probes.py (100%) rename {caustics => src/caustics}/light/sersic.py (89%) rename {caustics => src/caustics}/namespace_dict.py (93%) rename {caustics => src/caustics}/packed.py (100%) rename {caustics => src/caustics}/parameter.py (85%) rename {caustics => src/caustics}/parametrized.py (86%) rename {caustics => src/caustics}/sims/__init__.py (100%) rename {caustics => src/caustics}/sims/lens_source.py (72%) rename {caustics => src/caustics}/sims/simulator.py (97%) rename {caustics => src/caustics}/utils.py (90%) delete mode 100644 test/test_simulator.py rename {test => tests}/test_base.py (69%) rename {test => tests}/test_batching.py (77%) rename {test => tests}/test_cosmology.py (100%) rename {test => tests}/test_epl.py (100%) rename {test => tests}/test_external_shear.py (100%) rename {test => tests}/test_interpolate_image.py (89%) rename {test => tests}/test_jacobian_lens_equation.py (70%) rename {test => tests}/test_kappa_grid.py (88%) rename {test => tests}/test_masssheet.py (56%) rename {test => tests}/test_multiplane.py (81%) rename {test => tests}/test_namespace_dict.py (99%) rename {test => tests}/test_nfw.py (98%) rename {test => tests}/test_parameter.py (53%) rename {test => tests}/test_parametrized.py (81%) rename {test => tests}/test_pixel_grid.py (100%) rename {test => tests}/test_point.py (100%) rename {test => tests}/test_pseudo_jaffe.py (61%) rename {test => tests}/test_sersic.py (81%) rename {test => tests}/test_sie.py (95%) create mode 100644 tests/test_simulator.py rename {test => tests}/test_sis.py (100%) rename {test => tests}/test_tnfw.py (82%) rename {test => tests}/utils.py (82%) diff --git a/.devcontainer/apt.txt b/.devcontainer/apt.txt new file mode 100644 index 00000000..6c1009bf --- /dev/null +++ b/.devcontainer/apt.txt @@ -0,0 +1,4 @@ +git +ncdu +wget +curl diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..1ca5f58e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +{ + "image": "quay.io/pangeo/pytorch-notebook:latest", + + "customizations": { + "vscode": { + "extensions": [ + "ms-toolsai.jupyter", + "ms-python.python", + "ms-vsliveshare.vsliveshare", + "DavidAnson.vscode-markdownlint", + "GitHub.copilot" + ] + } + }, + "postCreateCommand": "sh .devcontainer/postBuild.sh", + "features": { + "ghcr.io/devcontainers-contrib/features/black:2": {}, + "ghcr.io/devcontainers-contrib/features/pylint:2": {} + } +} diff --git a/.devcontainer/environment.yml b/.devcontainer/environment.yml new file mode 100644 index 00000000..bdff2af9 --- /dev/null +++ b/.devcontainer/environment.yml @@ -0,0 +1,15 @@ +channels: + - conda-forge +dependencies: + - python>=3.10 + - astropy + - jupyterlab + - matplotlib + - numpy + - pandas + - scipy + - h5py + - sphinx + - myst-parser + - jupyter-book + - pip diff --git a/.devcontainer/postBuild.sh b/.devcontainer/postBuild.sh new file mode 100644 index 00000000..345a70c6 --- /dev/null +++ b/.devcontainer/postBuild.sh @@ -0,0 +1,4 @@ +# For writing commands that will be executed after the container is created + +# Installs `caustic` as local library without resolving dependencies (--no-deps) +python3 -m pip install -e /workspaces/caustics --no-deps diff --git a/.devcontainer/start b/.devcontainer/start new file mode 100644 index 00000000..5b46c1ac --- /dev/null +++ b/.devcontainer/start @@ -0,0 +1,12 @@ +#!/bin/bash -l + +# ==== ONLY EDIT WITHIN THIS BLOCK ===== + +export CAUSTICS_ENV="caustics" +if ! [[ -z "${CAUSTICS_SCRATCH_PREFIX}" ]] && ! [[ -z "${JUPYTERHUB_USER}" ]]; then + export CAUSTICS_SCRATCH="${CAUSTICS_SCRATCH_PREFIX}/${JUPYTERHUB_USER}/" +fi + +# ==== ONLY EDIT WITHIN THIS BLOCK ===== + +exec "$@" diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 00000000..8fb235d7 --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..00a7b00c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..97d5ed09 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,101 @@ +See the [Scientific Python Developer Guide][spc-dev-intro] for a detailed +description of best practices for developing scientific packages. + +[spc-dev-intro]: https://scientific-python-cookie.readthedocs.io/guide/intro + +# Quick development + +The fastest way to start with development is to use nox. If you don't have nox, +you can use `pipx run nox` to run it without installing, or `pipx install nox`. +If you don't have pipx (pip for applications), then you can install with with +`pip install pipx` (the only case were installing an application with regular +pip is reasonable). If you use macOS, then pipx and nox are both in brew, use +`brew install pipx nox`. + +To use, run `nox`. This will lint and test using every installed version of +Python on your system, skipping ones that are not installed. You can also run +specific jobs: + +```console +$ nox -s lint # Lint only +$ nox -s tests # Python tests +$ nox -s docs -- serve # Build and serve the docs +$ nox -s build # Make an SDist and wheel +``` + +Nox handles everything for you, including setting up an temporary virtual +environment for each run. + +# Setting up a development environment manually + +You can set up a development environment by running: + +```bash +python3 -m venv .venv +source ./.venv/bin/activate +pip install -v -e .[dev] +``` + +If you have the +[Python Launcher for Unix](https://github.com/brettcannon/python-launcher), you +can instead do: + +```bash +py -m venv .venv +py -m install -v -e .[dev] +``` + +# Post setup + +You should prepare pre-commit, which will help you by checking that commits pass +required checks: + +```bash +pip install pre-commit # or brew install pre-commit on macOS +pre-commit install # Will install a pre-commit hook into the git repo +``` + +You can also/alternatively run `pre-commit run` (changes only) or +`pre-commit run --all-files` to check even without installing the hook. + +# Testing + +Use pytest to run the unit checks: + +```bash +pytest +``` + +# Coverage + +Use pytest-cov to generate coverage reports: + +```bash +pytest --cov=caustics +``` + +# Building docs + +You can build the docs using: + +```bash +nox -s docs +``` + +You can see a preview with: + +```bash +nox -s docs -- serve +``` + +# Pre-commit + +This project uses pre-commit for all style checking. While you can run it with +nox, this is such an important tool that it deserves to be installed on its own. +Install pre-commit and run: + +```bash +pre-commit run -a +``` + +to check all files. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6fddca0d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/matchers/pylint.json b/.github/matchers/pylint.json new file mode 100644 index 00000000..e3a6bd16 --- /dev/null +++ b/.github/matchers/pylint.json @@ -0,0 +1,32 @@ +{ + "problemMatcher": [ + { + "severity": "warning", + "pattern": [ + { + "regexp": "^([^:]+):(\\d+):(\\d+): ([A-DF-Z]\\d+): \\033\\[[\\d;]+m([^\\033]+).*$", + "file": 1, + "line": 2, + "column": 3, + "code": 4, + "message": 5 + } + ], + "owner": "pylint-warning" + }, + { + "severity": "error", + "pattern": [ + { + "regexp": "^([^:]+):(\\d+):(\\d+): (E\\d+): \\033\\[[\\d;]+m([^\\033]+).*$", + "file": 1, + "line": 2, + "column": 3, + "code": 4, + "message": 5 + } + ], + "owner": "pylint-error" + } + ] +} diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..67c32853 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,99 @@ +name: CD + +on: + workflow_dispatch: + push: + branches: + - main + - dev + release: + types: + - published + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: 3 + +jobs: + dist: + name: Distribution build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build sdist and wheel + run: pipx run build + + - uses: actions/upload-artifact@v3 + with: + path: dist + + - name: Check products + run: pipx run twine check dist/* + + test-built-dist: + needs: [dist] + name: Test built distribution + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/setup-python@v4.7.0 + name: Install Python + with: + python-version: "3.10" + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + - name: List contents of built dist + run: | + ls -ltrh + ls -ltrh dist + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@v1.8.10 + with: + repository-url: https://test.pypi.org/legacy/ + verbose: true + skip-existing: true + - name: Check pypi packages + run: | + sleep 3 + python -m pip install --upgrade pip + + echo "=== Testing wheel file ===" + # Install wheel to get dependencies and check import + python -m pip install --extra-index-url https://test.pypi.org/simple --upgrade --pre caustic + python -c "import caustic; print(caustics.__version__)" + echo "=== Done testing wheel file ===" + + echo "=== Testing source tar file ===" + # Install tar gz and check import + python -m pip uninstall --yes caustic + python -m pip install --extra-index-url https://test.pypi.org/simple --upgrade --pre --no-binary=:all: caustic + python -c "import caustic; print(caustics.__version__)" + echo "=== Done testing source tar file ===" + + publish: + needs: [dist, test-built-dist] + name: Publish to PyPI + environment: pypi + permissions: + id-token: write + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + + steps: + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@v1.8.10 + if: startsWith(github.ref, 'refs/tags') diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 13c8bd44..c568c12d 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -15,46 +15,48 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - name: Checkout caustics - uses: actions/checkout@v3 + - name: Checkout caustics + uses: actions/checkout@v3 + with: + fetch-depth: 0 - - name: Set up Python 3.10 - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: Record State - run: | - pwd - echo github.ref is: ${{ github.ref }} - echo GITHUB_SHA is: $GITHUB_SHA - echo github.event_name is: ${{ github.event_name }} - echo github workspace: ${{ github.workspace }} - pip --version - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest pytest-cov torch wheel - # Install deps - cd $GITHUB_WORKSPACE - pip install -r requirements.txt - shell: bash - - - name: Install Caustics - run: | - cd $GITHUB_WORKSPACE - pip install -e .[dev] - pip show caustics - shell: bash - - name: Test with pytest - run: | - cd $GITHUB_WORKSPACE - pwd - pytest --cov-report=xml --cov=caustics test/ - cat coverage.xml - shell: bash - - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v3 - with: - files: ${{ github.workspace }}/coverage.xml - fail_ci_if_error: false + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Record State + run: | + pwd + echo github.ref is: ${{ github.ref }} + echo GITHUB_SHA is: $GITHUB_SHA + echo github.event_name is: ${{ github.event_name }} + echo github workspace: ${{ github.workspace }} + pip --version + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov torch wheel + # Install deps + cd $GITHUB_WORKSPACE + pip install -r requirements.txt + shell: bash + + - name: Install Caustics + run: | + cd $GITHUB_WORKSPACE + pip install -e .[dev] + pip show caustics + shell: bash + - name: Test with pytest + run: | + cd $GITHUB_WORKSPACE + pwd + pytest --cov-report=xml --cov=caustics tests/ + cat coverage.xml + shell: bash + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@v3 + with: + files: ${{ github.workspace }}/coverage.xml + fail_ci_if_error: false diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 6498c184..cd2adec2 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -4,7 +4,7 @@ on: branches: - main workflow_dispatch: - + jobs: docs: runs-on: ubuntu-latest @@ -12,38 +12,39 @@ jobs: - uses: actions/checkout@master - uses: actions/setup-python@v4 with: - python-version: '3.9' - cache: 'pip' - + python-version: "3.9" + cache: "pip" + - run: | - pip install torch wheel + pip install torch wheel pip install -r requirements.txt - + - name: Install dependencies run: | sudo apt-get install -y pandoc pip install sphinx sphinx_rtd_theme nbsphinx - + - name: Install Caustics run: | cd $GITHUB_WORKSPACE pip install -e .[dev] pip show caustics shell: bash - + - name: Clone external Jupyter Notebook repository run: | cd $GITHUB_WORKSPACE/docs/ python pull_notebooks.py - + - name: Sphinx build run: | sphinx-apidoc -f -o docs/ caustics/ sphinx-build docs _build - + - name: Deploy uses: peaceiris/actions-gh-pages@v3 - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + if: + ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} with: publish_branch: gh-pages github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 0f9cec17..a5078b30 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -16,49 +16,50 @@ on: jobs: build: - runs-on: ${{matrix.os}} strategy: + fail-fast: false matrix: - python-version: ["3.9", "3.10"] + python-version: ["3.9", "3.10", "3.11"] os: [ubuntu-latest, windows-latest, macOS-latest] steps: - - name: Checkout caustics - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Record State - run: | - pwd - echo github.ref is: ${{ github.ref }} - echo GITHUB_SHA is: $GITHUB_SHA - echo github.event_name is: ${{ github.event_name }} - echo github workspace: ${{ github.workspace }} - pip --version + - name: Checkout caustics + uses: actions/checkout@v3 + with: + fetch-depth: 0 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest pytest-cov torch wheel - # Install deps - cd $GITHUB_WORKSPACE - pip install -r requirements.txt - shell: bash + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Record State + run: | + pwd + echo github.ref is: ${{ github.ref }} + echo GITHUB_SHA is: $GITHUB_SHA + echo github.event_name is: ${{ github.event_name }} + echo github workspace: ${{ github.workspace }} + pip --version - - name: Install Caustics - run: | - cd $GITHUB_WORKSPACE - pip install -e .[dev] - pip show caustics - shell: bash + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov torch wheel + # Install deps + cd $GITHUB_WORKSPACE + pip install -r requirements.txt + shell: bash - - name: Test with pytest - run: | - cd $GITHUB_WORKSPACE - pytest test - shell: bash + - name: Install Caustics + run: | + cd $GITHUB_WORKSPACE + pip install -e .[dev] + pip show caustics + shell: bash + - name: Test with pytest + run: | + cd $GITHUB_WORKSPACE + pytest -vvv tests/ + shell: bash diff --git a/.gitignore b/.gitignore index 0d07421a..12601fd5 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,6 @@ package-lock.json package.json .idea/ + +# version +_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9b8e5dfd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,85 @@ +exclude: | + (?x)^( + tests/utils.py | + .devcontainer/ | + docs/source/ + ) + +ci: + autoupdate_commit_msg: "chore: update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + +repos: + - repo: https://github.com/psf/black + rev: "23.7.0" + hooks: + - id: black-jupyter + + - repo: https://github.com/asottile/blacken-docs + rev: "1.15.0" + hooks: + - id: blacken-docs + additional_dependencies: [black==23.7.0] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v4.4.0" + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: name-tests-test + args: ["--pytest-test-first"] + - id: requirements-txt-fixer + - id: trailing-whitespace + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: "v1.10.0" + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.0" + hooks: + - id: prettier + types_or: [yaml, markdown, html, css, scss, javascript, json] + args: [--prose-wrap=always] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.0.277" + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.4.1" + hooks: + - id: mypy + files: src|tests + args: [] + additional_dependencies: + - pytest + + - repo: https://github.com/codespell-project/codespell + rev: "v2.2.5" + hooks: + - id: codespell + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: "v0.9.0.5" + hooks: + - id: shellcheck + + - repo: local + hooks: + - id: disallow-caps + name: Disallow improper capitalization + language: pygrep + entry: PyBind|Numpy|Cmake|CCache|Github|PyTest + exclude: .pre-commit-config.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yml similarity index 53% rename from .readthedocs.yaml rename to .readthedocs.yml index fe6a07a9..05041f5a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yml @@ -5,19 +5,9 @@ # Required version: 2 -# Set the OS, Python version and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "3.12" - # You can also specify other tool versions: - # nodejs: "19" - # rust: "1.64" - # golang: "1.19" - # Build documentation in the "docs/" directory with Sphinx sphinx: - configuration: docs/conf.py + configuration: docs/source/conf.py # Optionally build your docs in additional formats such as PDF and ePub # formats: @@ -34,15 +24,11 @@ build: tools: python: "3.9" apt_packages: - - pandoc # Specify pandoc to be installed via apt-get + - pandoc # Specify pandoc to be installed via apt-get python: install: - - requirements: requirements.txt # Path to your requirements.txt file - - requirements: docs/requirements.txt # Path to your requirements.txt file + - requirements: requirements.txt # Path to your requirements.txt file + - requirements: docs/requirements.txt # Path to your requirements.txt file - method: pip - path: . # Install the package itself - -# python: -# install: -# - requirements: docs/requirements.txt \ No newline at end of file + path: . # Install the package itself diff --git a/README.md b/README.md index 7af68fb7..1b325182 100644 --- a/README.md +++ b/README.md @@ -4,41 +4,56 @@ caustics logo - +[![ssec](https://img.shields.io/badge/SSEC-Project-purple?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAQAAABedl5ZAAAACXBIWXMAAAHKAAABygHMtnUxAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMNJREFUGBltwcEqwwEcAOAfc1F2sNsOTqSlNUopSv5jW1YzHHYY/6YtLa1Jy4mbl3Bz8QIeyKM4fMaUxr4vZnEpjWnmLMSYCysxTcddhF25+EvJia5hhCudULAePyRalvUteXIfBgYxJufRuaKuprKsbDjVUrUj40FNQ11PTzEmrCmrevPhRcVQai8m1PRVvOPZgX2JttWYsGhD3atbHWcyUqX4oqDtJkJiJHUYv+R1JbaNHJmP/+Q1HLu2GbNoSm3Ft0+Y1YMdPSTSwQAAAABJRU5ErkJggg==&style=plastic)](https://escience.washington.edu/wetai/) [![tests](https://github.com/Ciela-Institute/caustics/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/Ciela-Institute/caustics/actions) [![Docs](https://github.com/Ciela-Institute/caustics/actions/workflows/documentation.yaml/badge.svg)](https://github.com/Ciela-Institute/caustics/actions/workflows/documentation.yaml) [![PyPI version](https://badge.fury.io/py/caustics.svg)](https://pypi.org/project/caustics/) -[![coverage](https://img.shields.io/codecov/c/github/Ciela-Institute/caustics)](https://app.codecov.io/gh/Ciela-Institute/caustics) +[![coverage](https://img.shields.io/codecov/c/github/Ciela-Institute/caustic)](https://app.codecov.io/gh/Ciela-Institute/caustic) + # caustics -The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, -highly modular. Currently under heavy development: expect interface changes and -some imprecise/untested calculations. +The lensing pipeline of the future: GPU-accelerated, +automatically-differentiable, highly modular. Currently under heavy development: +expect interface changes and some imprecise/untested calculations. -## Installation +## Installation Simply install caustics from PyPI: + ```bash pip install caustics ``` ## Documentation -Please see our [documentation page](Ciela-Institute.github.io/caustics/) for more detailed information. +Please see our [documentation page](Ciela-Institute.github.io/caustics/) for +more detailed information. -## Contributing +## Contribution -Please reach out to one of us if you're interested in contributing! +We welcome contributions from collaborators and researchers interested in our +work. If you have improvements, suggestions, or new findings to share, please +submit a pull request. Your contributions help advance our research and analysis +efforts. -To start, follow the installation instructions, replacing the last line with -```bash -pip install -e ".[dev]" -``` -This creates an editable install and installs the dev dependencies. +To get started with your development (or fork), click the "Open with GitHub +Codespaces" button below to launch a fully configured development environment +with all the necessary tools and extensions. + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/uw-ssec/caustics?quickstart=1) + +Instruction on how to contribute to this project can be found in the +CONTRIBUTION.md Some guidelines: + - Please use `isort` and `black` to format your code. -- Use `CamelCase` for class names and `snake_case` for variable and method names. +- Use `CamelCase` for class names and `snake_case` for variable and method + names. - Open up issues for bugs/missing features. - Use pull requests for additions to the code. - Write tests that can be run by [`pytest`](https://docs.pytest.org/). + +Thanks to our contributors so far! + +[![Contributors](https://contrib.rocks/image?repo=Ciela-Institute/caustics)](https://github.com/Ciela-Institute/caustics/graphs/contributors) diff --git a/docs/Makefile b/docs/Makefile index 298ea9e2..51285967 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -16,4 +16,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/requirements.txt b/docs/requirements.txt index a25ab063..80e683f8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ -wheel +nbsphinx sphinx sphinx_rtd_theme -nbsphinx +wheel diff --git a/docs/citation.rst b/docs/source/citation.rst similarity index 100% rename from docs/citation.rst rename to docs/source/citation.rst diff --git a/docs/conf.py b/docs/source/conf.py similarity index 82% rename from docs/conf.py rename to docs/source/conf.py index 4cf235a0..92bb9f92 100644 --- a/docs/conf.py +++ b/docs/source/conf.py @@ -12,21 +12,20 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import os -import sys -#sys.path.insert(0, os.path.abspath('../src')) + +# sys.path.insert(0, os.path.abspath('../src')) # -- Project information ----------------------------------------------------- -project = 'caustics' -copyright = '2023, Ciela Institute' -author = 'Ciela Institute' +project = "caustics" +copyright = "2023, Ciela Institute" +author = "Ciela Institute" # The short X.Y version -version = '0.5' +version = "0.5" # The full version, including alpha/beta/rc tags -release = 'v0.5.0' +release = "v0.5.0" # -- General configuration --------------------------------------------------- @@ -39,40 +38,40 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'nbsphinx', - 'sphinx.ext.autodoc', + "nbsphinx", + "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.napoleon", - 'sphinx.ext.doctest', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', + "sphinx.ext.doctest", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" @@ -84,7 +83,7 @@ # a list of builtin themes. # html_theme = "sphinx_rtd_theme" -#html_theme = 'alabaster' +# html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -95,7 +94,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -112,7 +111,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'causticsdoc' +htmlhelp_basename = "causticsdoc" # -- Options for LaTeX output ------------------------------------------------ @@ -121,15 +120,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -139,8 +135,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'caustics.tex', 'caustics Documentation', - 'Ciela Institute', 'manual'), + (master_doc, "caustics.tex", "caustics Documentation", "Ciela Institute", "manual"), ] @@ -148,10 +143,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'caustics', 'caustics Documentation', - [author], 1) -] +man_pages = [(master_doc, "caustics", "caustics Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -160,9 +152,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'caustics', 'caustics Documentation', - author, 'caustics', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "caustics", + "caustics Documentation", + author, + "caustics", + "One line description of project.", + "Miscellaneous", + ), ] @@ -181,9 +179,9 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- # -- Options for nbsphinx -------------------------------------------------- -nbsphinx_execute = 'never' +nbsphinx_execute = "never" diff --git a/docs/contributing.rst b/docs/source/contributing.rst similarity index 100% rename from docs/contributing.rst rename to docs/source/contributing.rst diff --git a/docs/getting_started.rst b/docs/source/getting_started.rst similarity index 100% rename from docs/getting_started.rst rename to docs/source/getting_started.rst diff --git a/docs/illustrative_examples.rst b/docs/source/illustrative_examples.rst similarity index 100% rename from docs/illustrative_examples.rst rename to docs/source/illustrative_examples.rst diff --git a/docs/index.rst b/docs/source/index.rst similarity index 99% rename from docs/index.rst rename to docs/source/index.rst index 0b4ca666..10c6ab50 100644 --- a/docs/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,7 @@ .. image:: https://github.com/Ciela-Institute/caustics/blob/main/media/AP_logo.png?raw=true :width: 100 % :target: https://github.com/Ciela-Institute/caustics - + |br| Welcome to Caustics' documentation! @@ -40,7 +40,7 @@ Read The Docs contributing.rst citation.rst license.rst - + Indices and tables ================== @@ -48,7 +48,7 @@ Indices and tables :maxdepth: 1 modules.rst - + * :ref:`genindex` * :ref:`modindex` * :ref:`search` diff --git a/docs/install.rst b/docs/source/install.rst similarity index 100% rename from docs/install.rst rename to docs/source/install.rst diff --git a/docs/license.rst b/docs/source/license.rst similarity index 100% rename from docs/license.rst rename to docs/source/license.rst diff --git a/docs/modules.rst b/docs/source/modules.rst similarity index 100% rename from docs/modules.rst rename to docs/source/modules.rst diff --git a/docs/pull_notebooks.py b/docs/source/pull_notebooks.py similarity index 100% rename from docs/pull_notebooks.py rename to docs/source/pull_notebooks.py diff --git a/docs/tutorials.rst b/docs/source/tutorials.rst similarity index 90% rename from docs/tutorials.rst rename to docs/source/tutorials.rst index a5997413..e9dea14b 100644 --- a/docs/tutorials.rst +++ b/docs/source/tutorials.rst @@ -14,10 +14,3 @@ version of each tutorial is available here. VisualizeCaustics MultiplaneDemo InvertLensEquation - - - - - - - diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..3532266b --- /dev/null +++ b/noxfile.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +import argparse +import shutil +from pathlib import Path + +import nox + +DIR = Path(__file__).parent.resolve() + +nox.options.sessions = ["lint", "pylint", "tests"] + + +@nox.session +def lint(session: nox.Session) -> None: + """ + Run the linter. + """ + session.install("pre-commit") + session.run("pre-commit", "run", "--all-files", *session.posargs) + + +@nox.session +def pylint(session: nox.Session) -> None: + """ + Run PyLint. + """ + # This needs to be installed into the package environment, and is slower + # than a pre-commit check + session.install(".", "pylint") + session.run("pylint", "src", *session.posargs) + + +@nox.session +def tests(session: nox.Session) -> None: + """ + Run the unit and regular tests. Use --cov to activate coverage. + """ + session.install(".[test]") + session.run("pytest", *session.posargs) + + +@nox.session +def docs(session: nox.Session) -> None: + """ + Build the docs. Pass "--serve" to serve. + """ + + parser = argparse.ArgumentParser() + parser.add_argument("--serve", action="store_true", help="Serve after building") + parser.add_argument( + "-b", dest="builder", default="html", help="Build target (default: html)" + ) + args, posargs = parser.parse_known_args(session.posargs) + + if args.builder != "html" and args.serve: + session.error("Must not specify non-HTML builder with --serve") + + session.install(".[docs]") + session.chdir("docs") + + if args.builder == "linkcheck": + session.run( + "sphinx-build", "-b", "linkcheck", ".", "_build/linkcheck", *posargs + ) + return + + session.run( + "sphinx-build", + "-n", # nitpicky mode + "-T", # full tracebacks + "-W", # Warnings as errors + "--keep-going", # See all errors + "-b", + args.builder, + ".", + f"_build/{args.builder}", + *posargs, + ) + + if args.serve: + session.log("Launching docs at http://localhost:8000/ - use Ctrl-C to quit") + session.run("python", "-m", "http.server", "8000", "-d", "_build/html") + + +@nox.session +def build_api_docs(session: nox.Session) -> None: + """ + Build (regenerate) API docs. + """ + + session.install("sphinx") + session.chdir("docs") + session.run( + "sphinx-apidoc", + "-o", + "api/", + "--module-first", + "--no-toc", + "--force", + "../src/braingeneers", + ) + + +@nox.session +def build(session: nox.Session) -> None: + """ + Build an SDist and wheel. + """ + + build_p = DIR.joinpath("build") + if build_p.exists(): + shutil.rmtree(build_p) + + session.install("build") + session.run("python", "-m", "build") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..6b6e41e7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[build-system] +requires = ["hatchling", "hatch-requirements-txt", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "caustics" +dynamic = [ + "dependencies", + "version" +] +authors = [ + { name="Connor Stone", email="connor.stone@mila.quebec" }, + { name="Alexandre Adam", email="alexandre.adam@mila.quebec" }, + { name="UW SSEC", email="ssec@uw.edu" } +] +description = "The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular. Currently under heavy development: expect interface changes and some imprecise/untested calculations." +readme = "README.md" +requires-python = ">=3.9" +license = {file = "LICENSE"} +keywords = [ + "caustics", + "lensing", + "astronomy", + "strong lensing", + "gravitational lensing", + "astrophysics", + "differentiable programming", + "pytorch" +] +classifiers=[ + "Development Status :: 1 - Planning", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3" +] + +[project.urls] +Homepage = "https://mila.quebec/en/" +Documentation = "" +Repository = "https://github.com/Ciela-Institute/caustics.git" +Changelog = "" +Issues = "https://github.com/Ciela-Institute/caustics/issues" + +[project.optional-dependencies] +dev = [ + "lenstronomy==1.11.1" +] + +[tool.hatch.metadata.hooks.requirements_txt] +files = ["requirements.txt"] + +[tool.hatch.build] +sources = ["src"] + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.hooks.vcs] +version-file = "src/caustics/_version.py" + +[tool.hatch.version.raw-options] +local_scheme = "no-local-version" diff --git a/requirements.txt b/requirements.txt index 79afaf1d..85e4c715 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ +astropy>=5.2.1,<6.0.0 graphviz==0.20.1 h5py>=3.8.0 levmarq_torch==0.0.1 numpy>=1.23.5 scipy>=1.8.0 torch>=2.0.0 -astropy>=5.2.1 diff --git a/setup.py b/setup.py index 22606b40..2bfa1336 100644 --- a/setup.py +++ b/setup.py @@ -2,20 +2,23 @@ from setuptools import setup, find_packages import caustics.__init__ as caustics + def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() + + def read_lines(fname): - ret = list(open(os.path.join(os.path.dirname(__file__), fname)).readlines()) print(ret) return ret + setup( - name = "caustics", + name="caustics", version=caustics.__version__, description="A gravitational lensing simulator for the future", long_description=read("README.md"), - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", url="https://github.com/Ciela-Institute/caustics", author=caustics.__author__, license="MIT license", @@ -27,7 +30,7 @@ def read_lines(fname): "setuptools>=67.2.0", ], }, - keywords = [ + keywords=[ "gravitational lensing", "astrophysics", "differentiable programming", @@ -36,8 +39,8 @@ def read_lines(fname): classifiers=[ "Development Status :: 1 - Planning", "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - ], + ], ) diff --git a/caustics/__init__.py b/src/caustics/__init__.py similarity index 75% rename from caustics/__init__.py rename to src/caustics/__init__.py index b83a1af1..5bc6f757 100644 --- a/caustics/__init__.py +++ b/src/caustics/__init__.py @@ -1,5 +1,4 @@ -__version__ = '0.5.0' -__author__ = "Ciela" +from ._version import version as VERSION # noqa from .constants import * from .lenses import * @@ -9,4 +8,8 @@ from .light import * from .utils import * from .sims import * + # from .demo import * + +__version__ = VERSION +__author__ = "Ciela" diff --git a/caustics/constants.py b/src/caustics/constants.py similarity index 60% rename from caustics/constants.py rename to src/caustics/constants.py index c40b5268..c95a96ba 100644 --- a/caustics/constants.py +++ b/src/caustics/constants.py @@ -3,12 +3,20 @@ from astropy.constants.codata2018 import G as _G_astropy from astropy.constants.codata2018 import c as _c_astropy -__all__ = ("rad_to_arcsec", "arcsec_to_rad", "c_km_s", "G", "G_over_c2", "c_Mpc_s", "km_to_Mpc") +__all__ = ( + "rad_to_arcsec", + "arcsec_to_rad", + "c_km_s", + "G", + "G_over_c2", + "c_Mpc_s", + "km_to_Mpc", +) -rad_to_arcsec = 180 / pi * 60 ** 2 +rad_to_arcsec = 180 / pi * 60**2 arcsec_to_rad = 1 / rad_to_arcsec c_km_s = float(_c_astropy.to("km/s").value) G = float(_G_astropy.to("pc * km^2 / (s^2 * solMass)").value) -G_over_c2 = float((_G_astropy / _c_astropy ** 2).to("Mpc/solMass").value) # type: ignore +G_over_c2 = float((_G_astropy / _c_astropy**2).to("Mpc/solMass").value) # type: ignore c_Mpc_s = float(_c_astropy.to("Mpc/s").value) km_to_Mpc = 3.2407792896664e-20 # TODO: use astropy diff --git a/caustics/cosmology.py b/src/caustics/cosmology.py similarity index 82% rename from caustics/cosmology.py rename to src/caustics/cosmology.py index 502218ec..6b55112d 100644 --- a/caustics/cosmology.py +++ b/src/caustics/cosmology.py @@ -1,6 +1,6 @@ from abc import abstractmethod from math import pi -from typing import Any, Optional +from typing import Optional import torch from astropy.cosmology import default_cosmology @@ -30,7 +30,7 @@ _comoving_distance_helper_x_grid = 10 ** torch.linspace(-3, 1, 500, dtype=torch.float64) _comoving_distance_helper_y_grid = torch.as_tensor( _comoving_distance_helper_x_grid - * hyp2f1(1 / 3, 1 / 2, 4 / 3, -(_comoving_distance_helper_x_grid ** 3)), + * hyp2f1(1 / 3, 1 / 2, 4 / 3, -(_comoving_distance_helper_x_grid**3)), dtype=torch.float64, ) @@ -75,7 +75,9 @@ def critical_density(self, z: Tensor, params: Optional["Packed"] = None) -> Tens @abstractmethod @unpack(1) - def comoving_distance(self, z: Tensor, *args, params: Optional["Packed"] = None) -> Tensor: + def comoving_distance( + self, z: Tensor, *args, params: Optional["Packed"] = None + ) -> Tensor: """ Compute the comoving distance to redshift z. @@ -90,7 +92,9 @@ def comoving_distance(self, z: Tensor, *args, params: Optional["Packed"] = None) @abstractmethod @unpack(1) - def transverse_comoving_distance(self, z: Tensor, *args, params: Optional["Packed"] = None) -> Tensor: + def transverse_comoving_distance( + self, z: Tensor, *args, params: Optional["Packed"] = None + ) -> Tensor: """ Compute the transverse comoving distance to redshift z (Mpc). @@ -105,7 +109,7 @@ def transverse_comoving_distance(self, z: Tensor, *args, params: Optional["Packe @unpack(2) def comoving_distance_z1z2( - self, z1: Tensor, z2: Tensor, *args, params: Optional["Packed"] = None + self, z1: Tensor, z2: Tensor, *args, params: Optional["Packed"] = None ) -> Tensor: """ Compute the comoving distance between two redshifts. @@ -122,7 +126,7 @@ def comoving_distance_z1z2( @unpack(2) def transverse_comoving_distance_z1z2( - self, z1: Tensor, z2: Tensor, *args, params: Optional["Packed"] = None + self, z1: Tensor, z2: Tensor, *args, params: Optional["Packed"] = None ) -> Tensor: """ Compute the transverse comoving distance between two redshifts (Mpc). @@ -135,10 +139,14 @@ def transverse_comoving_distance_z1z2( Returns: Tensor: The transverse comoving distance between each pair of redshifts in Mpc. """ - return self.transverse_comoving_distance(z2, params) - self.transverse_comoving_distance(z1, params) + return self.transverse_comoving_distance( + z2, params + ) - self.transverse_comoving_distance(z1, params) @unpack(1) - def angular_diameter_distance(self, z: Tensor, *args, params: Optional["Packed"] = None) -> Tensor: + def angular_diameter_distance( + self, z: Tensor, *args, params: Optional["Packed"] = None + ) -> Tensor: """ Compute the angular diameter distance to redshift z. @@ -153,7 +161,7 @@ def angular_diameter_distance(self, z: Tensor, *args, params: Optional["Packed"] @unpack(2) def angular_diameter_distance_z1z2( - self, z1: Tensor, z2: Tensor, *args, params: Optional["Packed"] = None + self, z1: Tensor, z2: Tensor, *args, params: Optional["Packed"] = None ) -> Tensor: """ Compute the angular diameter distance between two redshifts. @@ -170,7 +178,7 @@ def angular_diameter_distance_z1z2( @unpack(2) def time_delay_distance( - self, z_l: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None + self, z_l: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None ) -> Tensor: """ Compute the time delay distance between lens and source planes. @@ -190,7 +198,7 @@ def time_delay_distance( @unpack(2) def critical_surface_density( - self, z_l: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None + self, z_l: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None ) -> Tensor: """ Compute the critical surface density between lens and source planes. @@ -242,11 +250,17 @@ def __init__( self._comoving_distance_helper_y_grid = _comoving_distance_helper_y_grid.to( dtype=torch.float32 ) - - def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None): + + def to( + self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None + ): super().to(device, dtype) - self._comoving_distance_helper_y_grid = self._comoving_distance_helper_y_grid.to(device, dtype) - self._comoving_distance_helper_x_grid = self._comoving_distance_helper_x_grid.to(device, dtype) + self._comoving_distance_helper_y_grid = ( + self._comoving_distance_helper_y_grid.to(device, dtype) + ) + self._comoving_distance_helper_x_grid = ( + self._comoving_distance_helper_x_grid.to(device, dtype) + ) def hubble_distance(self, h0): """ @@ -261,7 +275,15 @@ def hubble_distance(self, h0): return c_Mpc_s / (100 * km_to_Mpc) / h0 @unpack(1) - def critical_density(self, z: Tensor, h0, central_critical_density, Om0, *args, params: Optional["Packed"] = None) -> torch.Tensor: + def critical_density( + self, + z: Tensor, + h0, + central_critical_density, + Om0, + *args, + params: Optional["Packed"] = None, + ) -> torch.Tensor: """ Calculate the critical density at redshift z. @@ -276,7 +298,9 @@ def critical_density(self, z: Tensor, h0, central_critical_density, Om0, *args, return central_critical_density * (Om0 * (1 + z) ** 3 + Ode0) @unpack(1) - def _comoving_distance_helper(self, x: Tensor, *args, params: Optional["Packed"] = None) -> Tensor: + def _comoving_distance_helper( + self, x: Tensor, *args, params: Optional["Packed"] = None + ) -> Tensor: """ Helper method for computing comoving distances. @@ -293,7 +317,15 @@ def _comoving_distance_helper(self, x: Tensor, *args, params: Optional["Packed"] ).reshape(x.shape) @unpack(1) - def comoving_distance(self, z: Tensor, h0, central_critical_density, Om0, *args, params: Optional["Packed"] = None) -> Tensor: + def comoving_distance( + self, + z: Tensor, + h0, + central_critical_density, + Om0, + *args, + params: Optional["Packed"] = None, + ) -> Tensor: """ Calculate the comoving distance to redshift z. @@ -317,7 +349,12 @@ def comoving_distance(self, z: Tensor, h0, central_critical_density, Om0, *args, @unpack(1) def transverse_comoving_distance( - self, z: Tensor, h0, central_critical_density, Om0, *args, params: Optional["Packed"] = None + self, + z: Tensor, + h0, + central_critical_density, + Om0, + *args, + params: Optional["Packed"] = None, ) -> Tensor: - return self.comoving_distance(z, params) diff --git a/caustics/data/__init__.py b/src/caustics/data/__init__.py similarity index 100% rename from caustics/data/__init__.py rename to src/caustics/data/__init__.py diff --git a/caustics/data/hdf5dataset.py b/src/caustics/data/hdf5dataset.py similarity index 100% rename from caustics/data/hdf5dataset.py rename to src/caustics/data/hdf5dataset.py diff --git a/caustics/data/illustris_kappa.py b/src/caustics/data/illustris_kappa.py similarity index 100% rename from caustics/data/illustris_kappa.py rename to src/caustics/data/illustris_kappa.py diff --git a/caustics/data/probes.py b/src/caustics/data/probes.py similarity index 100% rename from caustics/data/probes.py rename to src/caustics/data/probes.py diff --git a/caustics/lenses/__init__.py b/src/caustics/lenses/__init__.py similarity index 100% rename from caustics/lenses/__init__.py rename to src/caustics/lenses/__init__.py diff --git a/caustics/lenses/base.py b/src/caustics/lenses/base.py similarity index 66% rename from caustics/lenses/base.py rename to src/caustics/lenses/base.py index 1a349192..768205f1 100644 --- a/caustics/lenses/base.py +++ b/src/caustics/lenses/base.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Any, Optional, Union +from typing import Optional, Union from functools import partial import warnings @@ -8,16 +8,18 @@ from ..constants import arcsec_to_rad, c_Mpc_s from ..cosmology import Cosmology -from ..parametrized import Parametrized, unpack +from ..parametrized import Parametrized, unpack from .utils import get_magnification from ..utils import batch_lm __all__ = ("ThinLens", "ThickLens") + class Lens(Parametrized): """ Base class for all lenses """ + def __init__(self, cosmology: Cosmology, name: str = None): """ Initializes a new instance of the Lens class. @@ -31,8 +33,16 @@ def __init__(self, cosmology: Cosmology, name: str = None): @unpack(3) def jacobian_lens_equation( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, method = "autograd", pixelscale = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + method="autograd", + pixelscale=None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. @@ -43,13 +53,25 @@ def jacobian_lens_equation( return self._jacobian_lens_equation_autograd(x, y, z_s, params, **kwargs) elif method == "finitediff": if pixelscale is None: - raise ValueError("Finite differences lensing jacobian requires regular grid and known pixelscale. Please include the pixelscale argument") - return self._jacobian_lens_equation_finitediff(x, y, z_s, pixelscale, params, **kwargs) + raise ValueError( + "Finite differences lensing jacobian requires regular grid and known pixelscale. Please include the pixelscale argument" + ) + return self._jacobian_lens_equation_finitediff( + x, y, z_s, pixelscale, params, **kwargs + ) else: raise ValueError("method should be one of: autograd, finitediff") @unpack(3) - def magnification(self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def magnification( + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> Tensor: """ Compute the gravitational magnification at the given coordinates. @@ -62,11 +84,20 @@ def magnification(self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Option Returns: Tensor: Gravitational magnification at the given coordinates. """ - return get_magnification(partial(self.raytrace, params = params), x, y, z_s) + return get_magnification(partial(self.raytrace, params=params), x, y, z_s) @unpack(3) def forward_raytrace( - self, bx: Tensor, by: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, epsilon = 1e-2, n_init = 50, fov = 5., **kwargs + self, + bx: Tensor, + by: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + epsilon=1e-2, + n_init=50, + fov=5.0, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Perform a forward ray-tracing operation which maps from the source plane to the image plane. @@ -83,38 +114,42 @@ def forward_raytrace( Returns: tuple[Tensor, Tensor]: Ray-traced coordinates in the x and y directions. """ - - bxy = torch.stack((bx, by)).repeat(n_init,1) # has shape (n_init, Dout:2) + + bxy = torch.stack((bx, by)).repeat(n_init, 1) # has shape (n_init, Dout:2) # TODO make FOV more general so that it doesnt have to be centered on zero,zero if fov is None: raise ValueError("fov must be given to generate initial guesses") # Random starting points in image plane - guesses = torch.as_tensor(fov) * (torch.rand(n_init, 2) - 0.5) # Has shape (n_init, Din:2) + guesses = torch.as_tensor(fov) * ( + torch.rand(n_init, 2) - 0.5 + ) # Has shape (n_init, Din:2) # Optimize guesses in image plane x, l, c = batch_lm( guesses, bxy, - lambda *a, **k: torch.stack(self.raytrace(a[0][...,0], a[0][...,1], *a[1:], **k), dim = -1), - f_args = (z_s, params) + lambda *a, **k: torch.stack( + self.raytrace(a[0][..., 0], a[0][..., 1], *a[1:], **k), dim=-1 + ), + f_args=(z_s, params), ) # Clip points that didn't converge - x = x[c < 1e-2*epsilon**2] + x = x[c < 1e-2 * epsilon**2] # Cluster results into n-images res = [] while len(x) > 0: res.append(x[0]) - d = torch.linalg.norm(x - x[0], dim = -1) + d = torch.linalg.norm(x - x[0], dim=-1) x = x[d > epsilon] - res = torch.stack(res,dim = 0) - return res[...,0], res[...,1] + res = torch.stack(res, dim=0) + return res[..., 0], res[..., 1] + - class ThickLens(Lens): """ Base class for modeling gravitational lenses that cannot be treated using the thin lens approximation. @@ -126,11 +161,17 @@ class ThickLens(Lens): @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ ThickLens objects do not have a reduced deflection angle since the distance D_ls is undefined - + Args: x (Tensor): Tensor of x coordinates in the lens plane. y (Tensor): Tensor of y coordinates in the lens plane. @@ -140,12 +181,20 @@ def reduced_deflection_angle( Raises: NotImplementedError """ - warnings.warn("ThickLens objects do not have a reduced deflection angle since they have no unique lens redshift. The distance D_{ls} is undefined in the equation $\alpha_{reduced} = \frac{D_{ls}}{D_s}\alpha_{physical}$. See `effective_reduced_deflection_angle`. Now using effective_reduced_deflection_angle, please switch functions to remove this warning") + warnings.warn( + "ThickLens objects do not have a reduced deflection angle since they have no unique lens redshift. The distance D_{ls} is undefined in the equation $\alpha_{reduced} = \frac{D_{ls}}{D_s}\alpha_{physical}$. See `effective_reduced_deflection_angle`. Now using effective_reduced_deflection_angle, please switch functions to remove this warning" + ) return self.effective_reduced_deflection_angle(x, y, z_s, params) @unpack(3) def effective_reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ThickLens objects do not have a reduced deflection angle since the distance D_ls is undefined. Instead we define an effective @@ -154,7 +203,7 @@ def effective_reduced_deflection_angle( effective reduced deflection angle, $\theta$ are the observed angular coordinates, and $\beta$ are the angular coordinates to the source plane. - + Args: x (Tensor): Tensor of x coordinates in the lens plane. y (Tensor): Tensor of y coordinates in the lens plane. @@ -167,7 +216,13 @@ def effective_reduced_deflection_angle( @unpack(3) def physical_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """Physical deflection angles are computed with respect to a lensing plane. ThickLens objects have no unique definition of a lens @@ -183,12 +238,20 @@ def physical_deflection_angle( tuple[Tensor, Tensor]: Tuple of Tensors representing the x and y components of the deflection angle, respectively. """ - raise NotImplementedError("Physical deflection angles are computed with respect to a lensing plane. ThickLens objects have no unique definition of a lens plane and so cannot compute a physical_deflection_angle") + raise NotImplementedError( + "Physical deflection angles are computed with respect to a lensing plane. ThickLens objects have no unique definition of a lens plane and so cannot compute a physical_deflection_angle" + ) @abstractmethod @unpack(3) def raytrace( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """Performs ray tracing by computing the angular position on the source plance associated with a given input observed angular @@ -209,7 +272,13 @@ def raytrace( @abstractmethod @unpack(3) def surface_density( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Computes the projected mass density at given coordinates. @@ -228,7 +297,13 @@ def surface_density( @abstractmethod @unpack(3) def time_delay( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Computes the gravitational time delay at given coordinates. @@ -246,8 +321,15 @@ def time_delay( @unpack(4) def _jacobian_effective_deflection_angle_finitediff( - self, x: Tensor, y: Tensor, z_s: Tensor, pixelscale: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + pixelscale: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the effective reduced deflection angle vector field. This equates to a (2,2) matrix at each (x,y) point. """ @@ -256,14 +338,20 @@ def _jacobian_effective_deflection_angle_finitediff( # Build Jacobian J = torch.zeros((*ax.shape, 2, 2), device=ax.device, dtype=ax.dtype) - J[...,0,1], J[...,0,0] = torch.gradient(ax, spacing = pixelscale) - J[...,1,1], J[...,1,0] = torch.gradient(ay, spacing = pixelscale) + J[..., 0, 1], J[..., 0, 0] = torch.gradient(ax, spacing=pixelscale) + J[..., 1, 1], J[..., 1, 0] = torch.gradient(ay, spacing=pixelscale) return J - + @unpack(3) def _jacobian_effective_deflection_angle_autograd( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the effective reduced deflection angle vector field. This equates to a (2,2) matrix at each (x,y) point. """ @@ -276,16 +364,32 @@ def _jacobian_effective_deflection_angle_autograd( # Build Jacobian J = torch.zeros((*ax.shape, 2, 2), device=ax.device, dtype=ax.dtype) - J[...,0,0], = torch.autograd.grad(ax, x, grad_outputs = torch.ones_like(ax), create_graph = True) - J[...,0,1], = torch.autograd.grad(ax, y, grad_outputs = torch.ones_like(ax), create_graph = True) - J[...,1,0], = torch.autograd.grad(ay, x, grad_outputs = torch.ones_like(ay), create_graph = True) - J[...,1,1], = torch.autograd.grad(ay, y, grad_outputs = torch.ones_like(ay), create_graph = True) + (J[..., 0, 0],) = torch.autograd.grad( + ax, x, grad_outputs=torch.ones_like(ax), create_graph=True + ) + (J[..., 0, 1],) = torch.autograd.grad( + ax, y, grad_outputs=torch.ones_like(ax), create_graph=True + ) + (J[..., 1, 0],) = torch.autograd.grad( + ay, x, grad_outputs=torch.ones_like(ay), create_graph=True + ) + (J[..., 1, 1],) = torch.autograd.grad( + ay, y, grad_outputs=torch.ones_like(ay), create_graph=True + ) return J.detach() - + @unpack(3) def jacobian_effective_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, method = "autograd", pixelscale = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + method="autograd", + pixelscale=None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the effective reduced deflection angle vector field. This equates to a (2,2) matrix at each (x,y) point. @@ -296,52 +400,85 @@ def jacobian_effective_deflection_angle( return self._jacobian_effective_deflection_angle_autograd(x, y, z_s, params) elif method == "finitediff": if pixelscale is None: - raise ValueError("Finite differences lensing jacobian requires regular grid and known pixelscale. Please include the pixelscale argument") - return self._jacobian_effective_deflection_angle_finitediff(x, y, z_s, pixelscale, params) + raise ValueError( + "Finite differences lensing jacobian requires regular grid and known pixelscale. Please include the pixelscale argument" + ) + return self._jacobian_effective_deflection_angle_finitediff( + x, y, z_s, pixelscale, params + ) else: raise ValueError("method should be one of: autograd, finitediff") @unpack(4) def _jacobian_lens_equation_finitediff( - self, x: Tensor, y: Tensor, z_s: Tensor, pixelscale: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + pixelscale: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian - J = self._jacobian_effective_deflection_angle_finitediff(x, y, z_s, pixelscale, params, **kwargs) + J = self._jacobian_effective_deflection_angle_finitediff( + x, y, z_s, pixelscale, params, **kwargs + ) return torch.eye(2) - J - + @unpack(3) def _jacobian_lens_equation_autograd( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian - J = self._jacobian_effective_deflection_angle_autograd(x, y, z_s, params, **kwargs) + J = self._jacobian_effective_deflection_angle_autograd( + x, y, z_s, params, **kwargs + ) return torch.eye(2) - J.detach() - + @unpack(3) def effective_convergence_div( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Using the divergence of the effective reduced delfection angle we can compute the divergence component of the effective convergence field. This field produces a single plane convergence field which reproduces as much of the deflection field as possible for a single plane. See: https://arxiv.org/pdf/2006.07383.pdf see also the `effective_convergence_curl` method. """ J = self.jacobian_effective_deflection_angle(x, y, z_s, params, **kwargs) - return 0.5*(J[...,0,0] + J[...,1,1]) + return 0.5 * (J[..., 0, 0] + J[..., 1, 1]) @unpack(3) def effective_convergence_curl( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Use the curl of the effective reduced deflection angle vector field to compute an effective convergence which derrives specifically from the curl of the deflection field. This field is purely a result of multiplane lensing and cannot occur in single plane lensing. See: https://arxiv.org/pdf/2006.07383.pdf """ J = self.jacobian_effective_deflection_angle(x, y, z_s, params, **kwargs) - return 0.5 * (J[...,1,0] - J[...,0,1]) + return 0.5 * (J[..., 1, 0] - J[..., 0, 1]) class ThinLens(Lens): @@ -360,13 +497,25 @@ class ThinLens(Lens): """ - def __init__(self, cosmology: Cosmology, z_l: Optional[Union[Tensor, float]] = None, name: str = None): - super().__init__(cosmology = cosmology, name = name) + def __init__( + self, + cosmology: Cosmology, + z_l: Optional[Union[Tensor, float]] = None, + name: str = None, + ): + super().__init__(cosmology=cosmology, name=name) self.add_param("z_l", z_l) @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Computes the reduced deflection angle of the lens at given coordinates [arcsec]. @@ -382,12 +531,21 @@ def reduced_deflection_angle( """ d_s = self.cosmology.angular_diameter_distance(z_s, params) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s, params) - deflection_angle_x, deflection_angle_y = self.physical_deflection_angle(x, y, z_s, params) + deflection_angle_x, deflection_angle_y = self.physical_deflection_angle( + x, y, z_s, params + ) return (d_ls / d_s) * deflection_angle_x, (d_ls / d_s) * deflection_angle_y @unpack(3) def physical_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Computes the physical deflection angle immediately after passing through this lens's plane. @@ -403,13 +561,21 @@ def physical_deflection_angle( """ d_s = self.cosmology.angular_diameter_distance(z_s, params) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s, params) - deflection_angle_x, deflection_angle_y = self.reduced_deflection_angle(x, y, z_s, params) + deflection_angle_x, deflection_angle_y = self.reduced_deflection_angle( + x, y, z_s, params + ) return (d_s / d_ls) * deflection_angle_x, (d_s / d_ls) * deflection_angle_y @abstractmethod @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Computes the convergence of the lens at given coordinates. @@ -428,7 +594,13 @@ def convergence( @abstractmethod @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Computes the gravitational lensing potential at given coordinates. @@ -445,7 +617,14 @@ def potential( @unpack(3) def surface_density( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Computes the surface mass density of the lens at given coordinates. @@ -459,12 +638,20 @@ def surface_density( Returns: Tensor: Surface mass density at the given coordinates in solar masses per Mpc^2. """ - critical_surface_density = self.cosmology.critical_surface_density(z_l, z_s, params) + critical_surface_density = self.cosmology.critical_surface_density( + z_l, z_s, params + ) return self.convergence(x, y, z_s, params) * critical_surface_density @unpack(3) def raytrace( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Perform a ray-tracing operation by subtracting the deflection angles from the input coordinates. @@ -483,7 +670,14 @@ def raytrace( @unpack(3) def time_delay( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + *args, + params: Optional["Packed"] = None, + **kwargs, ): """ Compute the gravitational time delay for light passing through the lens at given coordinates. @@ -508,8 +702,15 @@ def time_delay( @unpack(4) def _jacobian_deflection_angle_finitediff( - self, x: Tensor, y: Tensor, z_s: Tensor, pixelscale: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + pixelscale: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the deflection angle vector. This equates to a (2,2) matrix at each (x,y) point. """ @@ -518,14 +719,20 @@ def _jacobian_deflection_angle_finitediff( # Build Jacobian J = torch.zeros((*ax.shape, 2, 2), device=ax.device, dtype=ax.dtype) - J[...,0,1], J[...,0,0] = torch.gradient(ax, spacing = pixelscale) - J[...,1,1], J[...,1,0] = torch.gradient(ay, spacing = pixelscale) + J[..., 0, 1], J[..., 0, 0] = torch.gradient(ax, spacing=pixelscale) + J[..., 1, 1], J[..., 1, 0] = torch.gradient(ay, spacing=pixelscale) return J - + @unpack(3) def _jacobian_deflection_angle_autograd( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the deflection angle vector. This equates to a (2,2) matrix at each (x,y) point. """ @@ -538,16 +745,32 @@ def _jacobian_deflection_angle_autograd( # Build Jacobian J = torch.zeros((*ax.shape, 2, 2), device=ax.device, dtype=ax.dtype) - J[...,0,0], = torch.autograd.grad(ax, x, grad_outputs = torch.ones_like(ax), create_graph = True) - J[...,0,1], = torch.autograd.grad(ax, y, grad_outputs = torch.ones_like(ax), create_graph = True) - J[...,1,0], = torch.autograd.grad(ay, x, grad_outputs = torch.ones_like(ay), create_graph = True) - J[...,1,1], = torch.autograd.grad(ay, y, grad_outputs = torch.ones_like(ay), create_graph = True) + (J[..., 0, 0],) = torch.autograd.grad( + ax, x, grad_outputs=torch.ones_like(ax), create_graph=True + ) + (J[..., 0, 1],) = torch.autograd.grad( + ax, y, grad_outputs=torch.ones_like(ax), create_graph=True + ) + (J[..., 1, 0],) = torch.autograd.grad( + ay, x, grad_outputs=torch.ones_like(ay), create_graph=True + ) + (J[..., 1, 1],) = torch.autograd.grad( + ay, y, grad_outputs=torch.ones_like(ay), create_graph=True + ) return J.detach() - + @unpack(3) def jacobian_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, method = "autograd", pixelscale = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + method="autograd", + pixelscale=None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the deflection angle vector. This equates to a (2,2) matrix at each (x,y) point. @@ -558,30 +781,48 @@ def jacobian_deflection_angle( return self._jacobian_deflection_angle_autograd(x, y, z_s, params) elif method == "finitediff": if pixelscale is None: - raise ValueError("Finite differences lensing jacobian requires regular grid and known pixelscale. Please include the pixelscale argument") - return self._jacobian_deflection_angle_finitediff(x, y, z_s, pixelscale, params) + raise ValueError( + "Finite differences lensing jacobian requires regular grid and known pixelscale. Please include the pixelscale argument" + ) + return self._jacobian_deflection_angle_finitediff( + x, y, z_s, pixelscale, params + ) else: raise ValueError("method should be one of: autograd, finitediff") - + @unpack(4) def _jacobian_lens_equation_finitediff( - self, x: Tensor, y: Tensor, z_s: Tensor, pixelscale: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + pixelscale: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian - J = self._jacobian_deflection_angle_finitediff(x, y, z_s, pixelscale, params, **kwargs) + J = self._jacobian_deflection_angle_finitediff( + x, y, z_s, pixelscale, params, **kwargs + ) return torch.eye(2) - J - + @unpack(3) def _jacobian_lens_equation_autograd( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs - ) -> tuple[tuple[Tensor, Tensor],tuple[Tensor, Tensor]]: + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> tuple[tuple[Tensor, Tensor], tuple[Tensor, Tensor]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian J = self._jacobian_deflection_angle_autograd(x, y, z_s, params, **kwargs) return torch.eye(2) - J.detach() - diff --git a/caustics/lenses/epl.py b/src/caustics/lenses/epl.py similarity index 90% rename from caustics/lenses/epl.py rename to src/caustics/lenses/epl.py index 789bd8c9..4945527e 100644 --- a/caustics/lenses/epl.py +++ b/src/caustics/lenses/epl.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Optional, Union import torch from torch import Tensor @@ -15,13 +15,13 @@ class EPL(ThinLens): """ Elliptical power law (EPL, aka singular power-law ellipsoid) profile. - This class represents a thin gravitational lens model with an elliptical power law profile. The lensing equations are solved + This class represents a thin gravitational lens model with an elliptical power law profile. The lensing equations are solved iteratively using an approach based on Tessore et al. 2015. Attributes: n_iter (int): Number of iterations for the iterative solver. s (float): Softening length for the elliptical power-law profile. - + Parameters: z_l (Optional[Union[Tensor, float]]): This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. x0 and y0 (Optional[Union[Tensor, float]]): These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. @@ -76,7 +76,20 @@ def __init__( @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, q, phi, b, t, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + q, + phi, + b, + t, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Compute the reduced deflection angles of the lens. @@ -133,7 +146,20 @@ def _r_omega(self, z, t, q): @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, q, phi, b, t, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + q, + phi, + b, + t, + *args, + params: Optional["Packed"] = None, + **kwargs, ): """ Compute the lensing potential of the lens. @@ -154,7 +180,20 @@ def potential( @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, q, phi, b, t, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + q, + phi, + b, + t, + *args, + params: Optional["Packed"] = None, + **kwargs, ): """ Compute the convergence of the lens, which describes the local density of the lens. diff --git a/caustics/lenses/external_shear.py b/src/caustics/lenses/external_shear.py similarity index 78% rename from caustics/lenses/external_shear.py rename to src/caustics/lenses/external_shear.py index 88f4cb98..b1f41450 100644 --- a/caustics/lenses/external_shear.py +++ b/src/caustics/lenses/external_shear.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Optional, Union from torch import Tensor @@ -21,9 +21,10 @@ class ExternalShear(ThinLens): x0, y0 (Optional[Union[Tensor, float]]): Coordinates of the shear center in the lens plane. gamma_1, gamma_2 (Optional[Union[Tensor, float]]): Shear components. - Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational - distortion that can be caused by nearby structures outside of the main lens galaxy. + Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational + distortion that can be caused by nearby structures outside of the main lens galaxy. """ + def __init__( self, cosmology: Cosmology, @@ -35,7 +36,6 @@ def __init__( s: float = 0.0, name: str = None, ): - super().__init__(cosmology, z_l, name=name) self.add_param("x0", x0) @@ -46,7 +46,18 @@ def __init__( @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, gamma_1, gamma_2, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + gamma_1, + gamma_2, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Calculates the reduced deflection angle. @@ -63,17 +74,28 @@ def reduced_deflection_angle( x, y = translate_rotate(x, y, x0, y0) # Meneghetti eq 3.83 # TODO, why is it not: - #th = (x**2 + y**2).sqrt() + self.s - #a1 = x/th + x * gamma_1 + y * gamma_2 - #a2 = y/th + x * gamma_2 - y * gamma_1 + # th = (x**2 + y**2).sqrt() + self.s + # a1 = x/th + x * gamma_1 + y * gamma_2 + # a2 = y/th + x * gamma_2 - y * gamma_1 a1 = x * gamma_1 + y * gamma_2 a2 = x * gamma_2 - y * gamma_1 - + return a1, a2 # I'm not sure but I think no derotation necessary @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, gamma_1, gamma_2, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + gamma_1, + gamma_2, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Calculates the lensing potential. @@ -93,7 +115,18 @@ def potential( @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, gamma_1, gamma_2, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + gamma_1, + gamma_2, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ The convergence is undefined for an external shear. @@ -105,7 +138,7 @@ def convergence( params (Packed, optional): Dynamic parameter container. Raises: - NotImplementedError: This method is not implemented as the convergence is not defined + NotImplementedError: This method is not implemented as the convergence is not defined for an external shear. """ raise NotImplementedError("convergence undefined for external shear") diff --git a/caustics/lenses/mass_sheet.py b/src/caustics/lenses/mass_sheet.py similarity index 76% rename from caustics/lenses/mass_sheet.py rename to src/caustics/lenses/mass_sheet.py index c386c52a..4e8e5a58 100644 --- a/caustics/lenses/mass_sheet.py +++ b/src/caustics/lenses/mass_sheet.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Optional, Union import torch from torch import Tensor @@ -22,9 +22,10 @@ class MassSheet(ThinLens): x0, y0 (Optional[Union[Tensor, float]]): Coordinates of the shear center in the lens plane. gamma_1, gamma_2 (Optional[Union[Tensor, float]]): Shear components. - Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational - distortion that can be caused by nearby structures outside of the main lens galaxy. + Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational + distortion that can be caused by nearby structures outside of the main lens galaxy. """ + def __init__( self, cosmology: Cosmology, @@ -34,7 +35,6 @@ def __init__( surface_density: Optional[Union[Tensor, float]] = None, name: str = None, ): - super().__init__(cosmology, z_l, name=name) self.add_param("x0", x0) @@ -43,7 +43,17 @@ def __init__( @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, surface_density, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + surface_density, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Calculates the reduced deflection angle. @@ -65,16 +75,34 @@ def reduced_deflection_angle( @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, surface_density, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + surface_density, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: - # Meneghetti eq 3.81 return surface_density * 0.5 * (x**2 + y**2) @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, surface_density, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + surface_density, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: - # Essentially by definition return surface_density * torch.ones_like(x) diff --git a/caustics/lenses/multiplane.py b/src/caustics/lenses/multiplane.py similarity index 83% rename from caustics/lenses/multiplane.py rename to src/caustics/lenses/multiplane.py index 7c0e471f..e24ae2da 100644 --- a/caustics/lenses/multiplane.py +++ b/src/caustics/lenses/multiplane.py @@ -1,5 +1,5 @@ from operator import itemgetter -from typing import Any, Optional +from typing import Optional import torch from torch import Tensor @@ -24,6 +24,7 @@ class Multiplane(ThickLens): cosmology (Cosmology): Cosmological parameters used for calculations. lenses (list[ThinLens]): List of thin lenses. """ + def __init__(self, cosmology: Cosmology, lenses: list[ThinLens], name: str = None): super().__init__(cosmology, name=name) self.lenses = lenses @@ -31,7 +32,9 @@ def __init__(self, cosmology: Cosmology, lenses: list[ThinLens], name: str = Non self.add_parametrized(lens) @unpack(0) - def get_z_ls(self, *args, params: Optional["Packed"] = None, **kwargs) -> list[Tensor]: + def get_z_ls( + self, *args, params: Optional["Packed"] = None, **kwargs + ) -> list[Tensor]: """ Get the redshifts of each lens in the multiplane. @@ -47,7 +50,13 @@ def get_z_ls(self, *args, params: Optional["Packed"] = None, **kwargs) -> list[T @unpack(3) def raytrace( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """Calculate the angular source positions corresponding to the observer positions x,y. See Margarita et al. 2013 for the @@ -68,7 +77,7 @@ def raytrace( \vec{\theta}^{i+1} = \vec{\theta}^{i} - \alpha^i(\vec{x}^{i+1}) Here we set as initialization :math:`\vec{\theta}^0 = theta` the observation angular coordinates and :math:`\vec{x}^0 = 0` the initial physical coordinates (i.e. the observation rays come from a point at the observer). The indexing of :math:`\vec{x}^i` and :math:`\vec{\theta}^i` indicates the properties at the plane :math:`i`, and 0 means the observer, 1 is the first lensing plane (infinitesimally after the plane since the deflection has been applied), and so on. Note that in the actual implementation we start at :math:`\vec{x}^1` and :math:`\vec{\theta}^0` and begin at the second step in the recursion formula. - + Args: x (Tensor): angular x-coordinates from the observer perspective. y (Tensor): angular y-coordinates from the observer perspective. @@ -83,10 +92,17 @@ def raytrace( @unpack(4) def raytrace_z1z2( - self, x: Tensor, y: Tensor, z_start: Tensor, z_end: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_start: Tensor, + z_end: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ - Method to do multiplane ray tracing from arbitrary start/end redshift. + Method to do multiplane ray tracing from arbitrary start/end redshift. """ # Collect lens redshifts and ensure proper order @@ -94,15 +110,19 @@ def raytrace_z1z2( lens_planes = [i for i, _ in sorted(enumerate(z_ls), key=itemgetter(1))] # Compute physical position on first lens plane - D = self.cosmology.transverse_comoving_distance_z1z2(z_start, z_ls[lens_planes[0]], params) + D = self.cosmology.transverse_comoving_distance_z1z2( + z_start, z_ls[lens_planes[0]], params + ) X, Y = x * arcsec_to_rad * D, y * arcsec_to_rad * D # Initial angles are observation angles (negative needed because of negative in propogation term) theta_x, theta_y = x, y - + for i in lens_planes: # Compute deflection angle at current ray positions - D_l = self.cosmology.transverse_comoving_distance_z1z2(z_start, z_ls[i], params) + D_l = self.cosmology.transverse_comoving_distance_z1z2( + z_start, z_ls[i], params + ) alpha_x, alpha_y = self.lenses[i].physical_deflection_angle( X * rad_to_arcsec / D_l, Y * rad_to_arcsec / D_l, @@ -115,8 +135,10 @@ def raytrace_z1z2( theta_y = theta_y - alpha_y # Propogate rays to next plane (basically eq 18) - z_next = z_ls[i+1] if i != lens_planes[-1] else z_end - D = self.cosmology.transverse_comoving_distance_z1z2(z_ls[i], z_next, params) + z_next = z_ls[i + 1] if i != lens_planes[-1] else z_end + D = self.cosmology.transverse_comoving_distance_z1z2( + z_ls[i], z_next, params + ) X = X + D * theta_x * arcsec_to_rad Y = Y + D * theta_y * arcsec_to_rad @@ -126,14 +148,26 @@ def raytrace_z1z2( @unpack(3) def effective_reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: bx, by = self.raytrace(x, y, z_s, params) return x - bx, y - by @unpack(3) def surface_density( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Calculate the projected mass density. @@ -155,7 +189,13 @@ def surface_density( @unpack(3) def time_delay( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the time delay of light caused by the lensing. diff --git a/caustics/lenses/nfw.py b/src/caustics/lenses/nfw.py similarity index 83% rename from caustics/lenses/nfw.py rename to src/caustics/lenses/nfw.py index 05357159..2a1d0297 100644 --- a/caustics/lenses/nfw.py +++ b/src/caustics/lenses/nfw.py @@ -1,5 +1,5 @@ from math import pi -from typing import Any, Optional, Union +from typing import Optional, Union import torch from torch import Tensor @@ -14,21 +14,22 @@ __all__ = ("NFW",) + class NFW(ThinLens): """ NFW lens class. This class models a lens using the Navarro-Frenk-White (NFW) profile. - The NFW profile is a spatial density profile of dark matter halo that arises in + The NFW profile is a spatial density profile of dark matter halo that arises in cosmological simulations. Attributes: z_l (Optional[Tensor]): Redshift of the lens. Default is None. - x0 (Optional[Tensor]): x-coordinate of the lens center in the lens plane. + x0 (Optional[Tensor]): x-coordinate of the lens center in the lens plane. Default is None. - y0 (Optional[Tensor]): y-coordinate of the lens center in the lens plane. + y0 (Optional[Tensor]): y-coordinate of the lens center in the lens plane. Default is None. m (Optional[Tensor]): Mass of the lens. Default is None. c (Optional[Tensor]): Concentration parameter of the lens. Default is None. - s (float): Softening parameter to avoid singularities at the center of the lens. + s (float): Softening parameter to avoid singularities at the center of the lens. Default is 0.0. use_case (str): Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile specifically cant be both batchable and differentiable. You may select which version @@ -46,6 +47,7 @@ class NFW(ThinLens): convergence: Computes the convergence (dimensionless surface mass density). potential: Computes the lensing potential. """ + def __init__( self, cosmology: Cosmology, @@ -55,7 +57,7 @@ def __init__( m: Optional[Union[Tensor, float]] = None, c: Optional[Union[Tensor, float]] = None, s: float = 0.0, - use_case = "batchable", + use_case="batchable", name: str = None, ): """ @@ -63,16 +65,16 @@ def __init__( Args: name (str): Name of the lens instance. - cosmology (Cosmology): An instance of the Cosmology class which contains + cosmology (Cosmology): An instance of the Cosmology class which contains information about the cosmological model and parameters. z_l (Optional[Union[Tensor, float]]): Redshift of the lens. Default is None. - x0 (Optional[Union[Tensor, float]]): x-coordinate of the lens center in the lens plane. + x0 (Optional[Union[Tensor, float]]): x-coordinate of the lens center in the lens plane. Default is None. - y0 (Optional[Union[Tensor, float]]): y-coordinate of the lens center in the lens plane. + y0 (Optional[Union[Tensor, float]]): y-coordinate of the lens center in the lens plane. Default is None. m (Optional[Union[Tensor, float]]): Mass of the lens. Default is None. c (Optional[Union[Tensor, float]]): Concentration parameter of the lens. Default is None. - s (float): Softening parameter to avoid singularities at the center of the lens. + s (float): Softening parameter to avoid singularities at the center of the lens. Default is 0.0. """ super().__init__(cosmology, z_l, name=name) @@ -94,7 +96,9 @@ def __init__( raise ValueError("use case should be one of: batchable, differentiable") @unpack(0) - def get_scale_radius(self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def get_scale_radius( + self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs + ) -> Tensor: """ Calculate the scale radius of the lens. @@ -112,7 +116,9 @@ def get_scale_radius(self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] return 1 / c * r_delta @unpack(0) - def get_scale_density(self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def get_scale_density( + self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs + ) -> Tensor: """ Calculate the scale density of the lens. @@ -133,7 +139,9 @@ def get_scale_density(self, z_l, x0, y0, m, c, *args, params: Optional["Packed"] ) @unpack(1) - def get_convergence_s(self, z_s, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def get_convergence_s( + self, z_s, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs + ) -> Tensor: """ Calculate the dimensionless surface mass density of the lens. @@ -147,8 +155,14 @@ def get_convergence_s(self, z_s, z_l, x0, y0, m, c, *args, params: Optional["Pac Returns: Tensor: The dimensionless surface mass density of the lens. """ - critical_surface_density = self.cosmology.critical_surface_density(z_l, z_s, params) - return self.get_scale_density(params) * self.get_scale_radius(params) / critical_surface_density + critical_surface_density = self.cosmology.critical_surface_density( + z_l, z_s, params + ) + return ( + self.get_scale_density(params) + * self.get_scale_radius(params) + / critical_surface_density + ) @staticmethod def _f_differentiable(x: Tensor) -> Tensor: @@ -163,9 +177,20 @@ def _f_differentiable(x: Tensor) -> Tensor: """ # TODO: generalize beyond torch, or patch Tensor f = torch.zeros_like(x) - f[x > 1] = 1 - 2 / (x[x > 1]**2 - 1).sqrt() * ((x[x > 1] - 1) / (x[x > 1] + 1)).sqrt().arctan() - f[x < 1] = 1 - 2 / (1 - x[x < 1]**2).sqrt() * ((1 - x[x < 1]) / (1 + x[x < 1])).sqrt().arctanh() + f[x > 1] = ( + 1 + - 2 + / (x[x > 1] ** 2 - 1).sqrt() + * ((x[x > 1] - 1) / (x[x > 1] + 1)).sqrt().arctan() + ) + f[x < 1] = ( + 1 + - 2 + / (1 - x[x < 1] ** 2).sqrt() + * ((1 - x[x < 1]) / (1 + x[x < 1])).sqrt().arctanh() + ) return f + @staticmethod def _f_batchable(x: Tensor) -> Tensor: """ @@ -184,8 +209,8 @@ def _f_batchable(x: Tensor) -> Tensor: torch.where( x < 1, 1 - 2 / (1 - x**2).sqrt() * ((1 - x) / (1 + x)).sqrt().arctanh(), - torch.zeros_like(x), # x == 1 - ) + torch.zeros_like(x), # x == 1 + ), ) @staticmethod @@ -205,6 +230,7 @@ def _g_differentiable(x: Tensor) -> Tensor: term_2[x > 1] = (1 / x[x > 1]).arccos() ** 2 term_2[x < 1] = -(1 / x[x < 1]).arccosh() ** 2 return term_1 + term_2 + @staticmethod def _g_batchable(x: Tensor) -> Tensor: """ @@ -224,8 +250,8 @@ def _g_batchable(x: Tensor) -> Tensor: torch.where( x < 1, -(1 / x).arccosh() ** 2, - torch.zeros_like(x), # x == 1 - ) + torch.zeros_like(x), # x == 1 + ), ) return term_1 + term_2 @@ -242,9 +268,10 @@ def _h_differentiable(x: Tensor) -> Tensor: """ term_1 = (x / 2).log() term_2 = torch.ones_like(x) - term_2[x > 1] = (1 / x[x > 1]).arccos() * 1 / (x[x > 1]**2 - 1).sqrt() - term_2[x < 1] = (1 / x[x < 1]).arccosh() * 1 / (1 - x[x < 1]**2).sqrt() + term_2[x > 1] = (1 / x[x > 1]).arccos() * 1 / (x[x > 1] ** 2 - 1).sqrt() + term_2[x < 1] = (1 / x[x < 1]).arccosh() * 1 / (1 - x[x < 1] ** 2).sqrt() return term_1 + term_2 + @staticmethod def _h_batchable(x: Tensor) -> Tensor: """ @@ -261,17 +288,25 @@ def _h_batchable(x: Tensor) -> Tensor: x > 1, (1 / x).arccos() * 1 / (x**2 - 1).sqrt(), torch.where( - x < 1, - (1 / x).arccosh() * 1 / (1 - x**2).sqrt(), - torch.ones_like(x) - ) + x < 1, (1 / x).arccosh() * 1 / (1 - x**2).sqrt(), torch.ones_like(x) + ), ) return term_1 + term_2 - @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + m, + c, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Compute the reduced deflection angle. @@ -311,7 +346,18 @@ def reduced_deflection_angle( @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + m, + c, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the convergence (dimensionless surface mass density). @@ -336,7 +382,18 @@ def convergence( @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, m, c, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + m, + c, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the lensing potential. @@ -357,4 +414,10 @@ def potential( xi = d_l * th * arcsec_to_rad r = xi / scale_radius # xi / xi_0 convergence_s = self.get_convergence_s(z_s, params) - return 2 * convergence_s * self._g(r) * scale_radius**2 / (d_l**2 * arcsec_to_rad**2) + return ( + 2 + * convergence_s + * self._g(r) + * scale_radius**2 + / (d_l**2 * arcsec_to_rad**2) + ) diff --git a/caustics/lenses/pixelated_convergence.py b/src/caustics/lenses/pixelated_convergence.py similarity index 81% rename from caustics/lenses/pixelated_convergence.py rename to src/caustics/lenses/pixelated_convergence.py index 320cacea..dd4d88b0 100644 --- a/caustics/lenses/pixelated_convergence.py +++ b/src/caustics/lenses/pixelated_convergence.py @@ -1,5 +1,5 @@ from math import pi -from typing import Any, Optional +from typing import Optional import torch import torch.nn.functional as F @@ -13,6 +13,7 @@ __all__ = ("PixelatedConvergence",) + class PixelatedConvergence(ThinLens): def __init__( self, @@ -26,7 +27,7 @@ def __init__( shape: Optional[tuple[int, ...]] = None, convolution_mode: str = "fft", use_next_fast_len: bool = True, - padding = "zero", + padding="zero", name: str = None, ): """Strong lensing with user provided kappa map @@ -59,7 +60,7 @@ def __init__( or "tile". """ - + super().__init__(cosmology, z_l, name=name) if convergence_map is not None and convergence_map.ndim != 2: @@ -67,9 +68,7 @@ def __init__( f"convergence_map must be 2D. Received a {convergence_map.ndim}D tensor)" ) elif shape is not None and len(shape) != 2: - raise ValueError( - f"shape must specify a 2D tensor. Received shape={shape}" - ) + raise ValueError(f"shape must specify a 2D tensor. Received shape={shape}") self.add_param("x0", x0) self.add_param("y0", y0) @@ -112,7 +111,7 @@ def to( Args: device (Optional[torch.device]): The target device to move the tensors to. dtype (Optional[torch.dtype]): The target data type to cast the tensors to. - """ + """ super().to(device, dtype) self.potential_kernel = self.potential_kernel.to(device=device, dtype=dtype) self.ax_kernel = self.ax_kernel.to(device=device, dtype=dtype) @@ -138,16 +137,18 @@ def _fft2_padded(self, x: Tensor) -> Tensor: if self.use_next_fast_len: pad = next_fast_len(pad) self._s = (pad, pad) - + if self.padding == "zero": pass elif self.padding in ["reflect", "circular"]: - x = F.pad(x[None,None], (0, self.n_pix-1, 0, self.n_pix-1), mode = self.padding).squeeze() + x = F.pad( + x[None, None], (0, self.n_pix - 1, 0, self.n_pix - 1), mode=self.padding + ).squeeze() elif self.padding == "tile": - x = torch.tile(x, (2,2)) + x = torch.tile(x, (2, 2)) return torch.fft.rfft2(x, self._s) - + def _unpad_fft(self, x: Tensor) -> Tensor: """ Remove padding from the result of a 2D FFT. @@ -158,7 +159,9 @@ def _unpad_fft(self, x: Tensor) -> Tensor: Returns: Tensor: The input tensor without padding. """ - return torch.roll(x, (-self._s[0]//2,-self._s[1]//2), dims = (-2,-1))[..., :self.n_pix, :self.n_pix] + return torch.roll(x, (-self._s[0] // 2, -self._s[1] // 2), dims=(-2, -1))[ + ..., : self.n_pix, : self.n_pix + ] def _unpad_conv2d(self, x: Tensor) -> Tensor: """ @@ -170,7 +173,7 @@ def _unpad_conv2d(self, x: Tensor) -> Tensor: Returns: Tensor: The input tensor without padding. """ - return x # torch.roll(x, (-self.padding_range * self.ax_kernel.shape[0]//4,-self.padding_range * self.ax_kernel.shape[1]//4), dims = (-2,-1))[..., :self.n_pix, :self.n_pix] #[..., 1:, 1:] + return x # torch.roll(x, (-self.padding_range * self.ax_kernel.shape[0]//4,-self.padding_range * self.ax_kernel.shape[1]//4), dims = (-2,-1))[..., :self.n_pix, :self.n_pix] #[..., 1:, 1:] @property def convolution_mode(self): @@ -207,7 +210,17 @@ def convolution_mode(self, convolution_mode: str): @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, convergence_map, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + convergence_map, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Compute the deflection angles at the specified positions using the given convergence map. @@ -222,9 +235,14 @@ def reduced_deflection_angle( tuple[Tensor, Tensor]: The x and y components of the deflection angles at the specified positions. """ if self.convolution_mode == "fft": - deflection_angle_x_map, deflection_angle_y_map = self._deflection_angle_fft(convergence_map) + deflection_angle_x_map, deflection_angle_y_map = self._deflection_angle_fft( + convergence_map + ) else: - deflection_angle_x_map, deflection_angle_y_map = self._deflection_angle_conv2d(convergence_map) + ( + deflection_angle_x_map, + deflection_angle_y_map, + ) = self._deflection_angle_conv2d(convergence_map) # Scale is distance from center of image to center of pixel on the edge scale = self.fov / 2 deflection_angle_x = interp2d( @@ -246,15 +264,17 @@ def _deflection_angle_fft(self, convergence_map: Tensor) -> tuple[Tensor, Tensor tuple[Tensor, Tensor]: The x and y components of the deflection angles. """ convergence_tilde = self._fft2_padded(convergence_map) - deflection_angle_x = torch.fft.irfft2(convergence_tilde * self.ax_kernel_tilde, self._s).real * ( - self.pixelscale**2 / pi - ) - deflection_angle_y = torch.fft.irfft2(convergence_tilde * self.ay_kernel_tilde, self._s).real * ( - self.pixelscale**2 / pi - ) + deflection_angle_x = torch.fft.irfft2( + convergence_tilde * self.ax_kernel_tilde, self._s + ).real * (self.pixelscale**2 / pi) + deflection_angle_y = torch.fft.irfft2( + convergence_tilde * self.ay_kernel_tilde, self._s + ).real * (self.pixelscale**2 / pi) return self._unpad_fft(deflection_angle_x), self._unpad_fft(deflection_angle_y) - def _deflection_angle_conv2d(self, convergence_map: Tensor) -> tuple[Tensor, Tensor]: + def _deflection_angle_conv2d( + self, convergence_map: Tensor + ) -> tuple[Tensor, Tensor]: """ Compute the deflection angles using the 2D convolution method. @@ -266,20 +286,34 @@ def _deflection_angle_conv2d(self, convergence_map: Tensor) -> tuple[Tensor, Ten """ # Use convergence_map as kernel since the kernel is twice as large. Flip since # we actually want the cross-correlation. - - pad = 2 * self.n_pix - convergence_map_flipped = convergence_map.flip((-1, -2))[None, None] # F.pad(, ((pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2), mode = self.padding_mode) - deflection_angle_x = F.conv2d(self.ax_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( - self.pixelscale**2 / pi - ) - deflection_angle_y = F.conv2d(self.ay_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( - self.pixelscale**2 / pi + + 2 * self.n_pix + convergence_map_flipped = convergence_map.flip((-1, -2))[ + None, None + ] # F.pad(, ((pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2), mode = self.padding_mode) + deflection_angle_x = F.conv2d( + self.ax_kernel[None, None], convergence_map_flipped, padding="same" + ).squeeze() * (self.pixelscale**2 / pi) + deflection_angle_y = F.conv2d( + self.ay_kernel[None, None], convergence_map_flipped, padding="same" + ).squeeze() * (self.pixelscale**2 / pi) + return self._unpad_conv2d(deflection_angle_x), self._unpad_conv2d( + deflection_angle_y ) - return self._unpad_conv2d(deflection_angle_x), self._unpad_conv2d(deflection_angle_y) @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, convergence_map, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + convergence_map, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the lensing potential at the specified positions using the given convergence map. @@ -307,56 +341,68 @@ def potential( def _potential_fft(self, convergence_map: Tensor) -> Tensor: """ Compute the lensing potential using the Fast Fourier Transform (FFT) method. - + Args: convergence_map (Tensor): The 2D tensor representing the convergence map. - + Returns: Tensor: The lensing potential. """ convergence_tilde = self._fft2_padded(convergence_map) - potential = torch.fft.irfft2(convergence_tilde * self.potential_kernel_tilde, self._s) * ( - self.pixelscale**2 / pi - ) + potential = torch.fft.irfft2( + convergence_tilde * self.potential_kernel_tilde, self._s + ) * (self.pixelscale**2 / pi) return self._unpad_fft(potential) def _potential_conv2d(self, convergence_map: Tensor) -> Tensor: """ Compute the lensing potential using the 2D convolution method. - + Args: convergence_map (Tensor): The 2D tensor representing the convergence map. - + Returns: Tensor: The lensing potential. """ # Use convergence_map as kernel since the kernel is twice as large. Flip since # we actually want the cross-correlation. convergence_map_flipped = convergence_map.flip((-1, -2))[None, None] - potential = F.conv2d(self.potential_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( - self.pixelscale**2 / pi - ) + potential = F.conv2d( + self.potential_kernel[None, None], convergence_map_flipped, padding="same" + ).squeeze() * (self.pixelscale**2 / pi) return self._unpad_conv2d(potential) @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, convergence_map, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + convergence_map, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the convergence at the specified positions. This method is not implemented. - + Args: x (Tensor): The x-coordinates of the positions to compute the convergence for. y (Tensor): The y-coordinates of the positions to compute the convergence for. z_s (Tensor): The source redshift. params (Packed, optional): A dictionary containing additional parameters. - + Returns: Tensor: The convergence at the specified positions. - + Raises: NotImplementedError: This method is not implemented. """ return interp2d( - convergence_map, (x - x0).view(-1) / self.fov*2, (y - y0).view(-1) / self.fov*2 + convergence_map, + (x - x0).view(-1) / self.fov * 2, + (y - y0).view(-1) / self.fov * 2, ).reshape(x.shape) diff --git a/caustics/lenses/point.py b/src/caustics/lenses/point.py similarity index 85% rename from caustics/lenses/point.py rename to src/caustics/lenses/point.py index 41967021..a0efca85 100644 --- a/caustics/lenses/point.py +++ b/src/caustics/lenses/point.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Optional, Union import torch from torch import Tensor @@ -10,6 +10,7 @@ __all__ = ("Point",) + class Point(ThinLens): """ Class representing a point mass lens in strong gravitational lensing. @@ -23,6 +24,7 @@ class Point(ThinLens): th_ein (Optional[Union[Tensor, float]]): Einstein radius of the lens. s (float): Softening parameter to prevent numerical instabilities. """ + def __init__( self, cosmology: Cosmology, @@ -54,7 +56,17 @@ def __init__( @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, th_ein, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + th_ein, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Compute the deflection angles. @@ -76,7 +88,17 @@ def reduced_deflection_angle( @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, th_ein, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + th_ein, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the lensing potential. @@ -96,7 +118,17 @@ def potential( @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, th_ein, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + th_ein, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the convergence (dimensionless surface mass density). diff --git a/caustics/lenses/pseudo_jaffe.py b/src/caustics/lenses/pseudo_jaffe.py similarity index 70% rename from caustics/lenses/pseudo_jaffe.py rename to src/caustics/lenses/pseudo_jaffe.py index d1490537..cca8ab09 100644 --- a/caustics/lenses/pseudo_jaffe.py +++ b/src/caustics/lenses/pseudo_jaffe.py @@ -1,11 +1,11 @@ from math import pi -from typing import Any, Optional, Union +from typing import Optional, Union import torch from torch import Tensor from ..cosmology import Cosmology -from ..constants import arcsec_to_rad, rad_to_arcsec +from ..constants import arcsec_to_rad from ..utils import translate_rotate from .base import ThinLens from ..parametrized import unpack @@ -15,8 +15,8 @@ class PseudoJaffe(ThinLens): """ - Class representing a Pseudo Jaffe lens in strong gravitational lensing, - based on `Eliasdottir et al 2007 `_ and + Class representing a Pseudo Jaffe lens in strong gravitational lensing, + based on `Eliasdottir et al 2007 `_ and the `lenstronomy` source code. Attributes: @@ -67,13 +67,45 @@ def __init__( self.s = s @unpack(1) - def get_convergence_0(self, z_s, z_l, x0, y0, mass, core_radius, scale_radius, *args, params: Optional["Packed"] = None, **kwargs): + def get_convergence_0( + self, + z_s, + z_l, + x0, + y0, + mass, + core_radius, + scale_radius, + *args, + params: Optional["Packed"] = None, + **kwargs, + ): d_l = self.cosmology.angular_diameter_distance(z_l, params) sigma_crit = self.cosmology.critical_surface_density(z_l, z_s, params) - return mass / (2 * torch.pi * sigma_crit * core_radius * scale_radius * (d_l * arcsec_to_rad)**2) - + return mass / ( + 2 + * torch.pi + * sigma_crit + * core_radius + * scale_radius + * (d_l * arcsec_to_rad) ** 2 + ) + @unpack(2) - def mass_enclosed_2d(self, theta, z_s, z_l, x0, y0, mass, core_radius, scale_radius, *args, params: Optional["Packed"] = None, **kwargs): + def mass_enclosed_2d( + self, + theta, + z_s, + z_l, + x0, + y0, + mass, + core_radius, + scale_radius, + *args, + params: Optional["Packed"] = None, + **kwargs, + ): """ Calculate the mass enclosed within a two-dimensional radius. @@ -87,14 +119,16 @@ def mass_enclosed_2d(self, theta, z_s, z_l, x0, y0, mass, core_radius, scale_rad """ theta = theta + self.s d_l = self.cosmology.angular_diameter_distance(z_l, params) - surface_density_0 = self.get_convergence_0(z_s, params) * self.cosmology.critical_surface_density(z_l, z_s, params) + surface_density_0 = self.get_convergence_0( + z_s, params + ) * self.cosmology.critical_surface_density(z_l, z_s, params) return ( 2 * pi * surface_density_0 * core_radius * scale_radius - * (d_l * arcsec_to_rad)**2 + * (d_l * arcsec_to_rad) ** 2 / (scale_radius - core_radius) * ( (core_radius**2 + theta**2).sqrt() @@ -138,74 +172,130 @@ def central_convergence( @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, mass, core_radius, scale_radius, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + mass, + core_radius, + scale_radius, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: - """ Calculate the deflection angle. + """Calculate the deflection angle. Args: x (Tensor): x-coordinate of the lens. y (Tensor): y-coordinate of the lens. z_s (Tensor): Source redshift. params (Packed, optional): Dynamic parameter container. - + Returns: Tuple[Tensor, Tensor]: The deflection angle in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) R = (x**2 + y**2).sqrt() + self.s - f = R / core_radius / (1 + (1 + (R / core_radius) ** 2).sqrt()) - R / scale_radius / ( - 1 + (1 + (R / scale_radius) ** 2).sqrt() + f = R / core_radius / ( + 1 + (1 + (R / core_radius) ** 2).sqrt() + ) - R / scale_radius / (1 + (1 + (R / scale_radius) ** 2).sqrt()) + alpha = ( + 2 + * self.get_convergence_0(z_s, params) + * core_radius + * scale_radius + / (scale_radius - core_radius) + * f ) - alpha = 2 * self.get_convergence_0(z_s, params) * core_radius * scale_radius / (scale_radius - core_radius) * f ax = alpha * x / R ay = alpha * y / R return ax, ay @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, mass, core_radius, scale_radius, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + mass, + core_radius, + scale_radius, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the lensing potential. This calculation is based on equation A18. - + Args: x (Tensor): x-coordinate of the lens. y (Tensor): y-coordinate of the lens. z_s (Tensor): Source redshift. params (Packed, optional): Dynamic parameter container. - + Returns: Tensor: The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) R_squared = x**2 + y**2 + self.s - coeff = -2 * self.get_convergence_0(z_s, params) * core_radius * scale_radius / (scale_radius - core_radius) + coeff = ( + -2 + * self.get_convergence_0(z_s, params) + * core_radius + * scale_radius + / (scale_radius - core_radius) + ) return coeff * ( (scale_radius**2 + R_squared).sqrt() - (core_radius**2 + R_squared).sqrt() + core_radius * (core_radius + (core_radius**2 + R_squared).sqrt()).log() - - scale_radius * (scale_radius + (scale_radius**2 + R_squared).sqrt()).log() + - scale_radius + * (scale_radius + (scale_radius**2 + R_squared).sqrt()).log() ) @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, mass, core_radius, scale_radius, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + mass, + core_radius, + scale_radius, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Calculate the projected mass density, based on equation A6. - + Args: x (Tensor): x-coordinate of the lens. y (Tensor): y-coordinate of the lens. z_s (Tensor): Source redshift. params (Packed, optional): Dynamic parameter container. - + Returns: Tensor: The projected mass density. """ x, y = translate_rotate(x, y, x0, y0) R_squared = x**2 + y**2 + self.s - coeff = self.get_convergence_0(z_s, params) * core_radius * scale_radius / (scale_radius - core_radius) + coeff = ( + self.get_convergence_0(z_s, params) + * core_radius + * scale_radius + / (scale_radius - core_radius) + ) return coeff * ( - 1 / (core_radius**2 + R_squared).sqrt() - 1 / (scale_radius**2 + R_squared).sqrt() + 1 / (core_radius**2 + R_squared).sqrt() + - 1 / (scale_radius**2 + R_squared).sqrt() ) diff --git a/caustics/lenses/sie.py b/src/caustics/lenses/sie.py similarity index 83% rename from caustics/lenses/sie.py rename to src/caustics/lenses/sie.py index c187a27a..f5bcd917 100644 --- a/caustics/lenses/sie.py +++ b/src/caustics/lenses/sie.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Optional, Union from torch import Tensor @@ -12,9 +12,9 @@ class SIE(ThinLens): """ - A class representing a Singular Isothermal Ellipsoid (SIE) strong gravitational lens model. + A class representing a Singular Isothermal Ellipsoid (SIE) strong gravitational lens model. This model is based on Keeton 2001, which can be found at https://arxiv.org/abs/astro-ph/0102341. - + Attributes: name (str): The name of the lens. cosmology (Cosmology): An instance of the Cosmology class. @@ -33,7 +33,7 @@ def __init__( z_l: Optional[Union[Tensor, float]] = None, x0: Optional[Union[Tensor, float]] = None, y0: Optional[Union[Tensor, float]] = None, - q: Optional[Union[Tensor, float]] = None,# TODO change to true axis ratio + q: Optional[Union[Tensor, float]] = None, # TODO change to true axis ratio phi: Optional[Union[Tensor, float]] = None, b: Optional[Union[Tensor, float]] = None, s: float = 0.0, @@ -67,7 +67,19 @@ def _get_potential(self, x, y, q): @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, q, phi, b, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + q, + phi, + b, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Calculate the physical deflection angle. @@ -90,8 +102,20 @@ def reduced_deflection_angle( return derotate(ax, ay, phi) @unpack(3) - def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, q, phi, b, *args, params: Optional["Packed"] = None, **kwargs + def potential( + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + q, + phi, + b, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the lensing potential. @@ -112,7 +136,19 @@ def potential( @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, q, phi, b, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + q, + phi, + b, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Calculate the projected mass density. diff --git a/caustics/lenses/singleplane.py b/src/caustics/lenses/singleplane.py similarity index 83% rename from caustics/lenses/singleplane.py rename to src/caustics/lenses/singleplane.py index f9cad5f4..7c6ff11a 100644 --- a/caustics/lenses/singleplane.py +++ b/src/caustics/lenses/singleplane.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Optional import torch from torch import Tensor @@ -12,16 +12,18 @@ class SinglePlane(ThinLens): """ - A class for combining multiple thin lenses into a single lensing plane. + A class for combining multiple thin lenses into a single lensing plane. This model inherits from the base `ThinLens` class. - + Attributes: name (str): The name of the single plane lens. cosmology (Cosmology): An instance of the Cosmology class. lenses (List[ThinLens]): A list of ThinLens objects that are being combined into a single lensing plane. """ - def __init__(self, cosmology: Cosmology, lenses: list[ThinLens], name: str = None, **kwargs): + def __init__( + self, cosmology: Cosmology, lenses: list[ThinLens], name: str = None, **kwargs + ): """ Initialize the SinglePlane lens model. """ @@ -33,7 +35,13 @@ def __init__(self, cosmology: Cosmology, lenses: list[ThinLens], name: str = Non @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Calculate the total deflection angle by summing the deflection angles of all individual lenses. @@ -57,7 +65,13 @@ def reduced_deflection_angle( @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Calculate the total projected mass density by summing the mass densities of all individual lenses. @@ -79,7 +93,13 @@ def convergence( @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the total lensing potential by summing the lensing potentials of all individual lenses. diff --git a/caustics/lenses/sis.py b/src/caustics/lenses/sis.py similarity index 82% rename from caustics/lenses/sis.py rename to src/caustics/lenses/sis.py index f6ac43e4..244fe6ea 100644 --- a/caustics/lenses/sis.py +++ b/src/caustics/lenses/sis.py @@ -1,6 +1,5 @@ -from typing import Any, Optional, Union +from typing import Optional, Union -import torch from torch import Tensor from ..cosmology import Cosmology @@ -13,7 +12,7 @@ class SIS(ThinLens): """ - A class representing the Singular Isothermal Sphere (SIS) model. + A class representing the Singular Isothermal Sphere (SIS) model. This model inherits from the base `ThinLens` class. Attributes: @@ -25,6 +24,7 @@ class SIS(ThinLens): th_ein (Optional[Union[Tensor, float]]): The Einstein radius of the lens. s (float): A smoothing factor, default is 0.0. """ + def __init__( self, cosmology: Cosmology, @@ -33,7 +33,7 @@ def __init__( y0: Optional[Union[Tensor, float]] = None, th_ein: Optional[Union[Tensor, float]] = None, s: float = 0.0, - name: str = None + name: str = None, ): """ Initialize the SIS lens model. @@ -47,7 +47,17 @@ def __init__( @unpack(3) def reduced_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, th_ein, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + th_ein, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """ Calculate the deflection angle of the SIS lens. @@ -69,7 +79,17 @@ def reduced_deflection_angle( @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, th_ein, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + th_ein, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the lensing potential of the SIS lens. @@ -89,7 +109,17 @@ def potential( @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, th_ein, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + th_ein, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Calculate the projected mass density of the SIS lens. diff --git a/caustics/lenses/tnfw.py b/src/caustics/lenses/tnfw.py similarity index 76% rename from caustics/lenses/tnfw.py rename to src/caustics/lenses/tnfw.py index 5f41f61f..093ee53f 100644 --- a/caustics/lenses/tnfw.py +++ b/src/caustics/lenses/tnfw.py @@ -1,5 +1,5 @@ from math import pi -from typing import Any, Optional, Union +from typing import Optional, Union import torch from torch import Tensor @@ -14,9 +14,10 @@ __all__ = ("TNFW",) + class TNFW(ThinLens): """Truncated Navaro-Frenk-White profile - + TNFW lens class. This class models a lens using the truncated Navarro-Frenk-White (NFW) profile. The NFW profile is a spatial density profile of dark matter halo that arises in cosmological @@ -25,7 +26,7 @@ class TNFW(ThinLens): infinity. This is based off the paper by Baltz et al. 2009: https://arxiv.org/abs/0705.0682 - + https://ui.adsabs.harvard.edu/abs/2009JCAP...01..015B/abstract Note: @@ -40,15 +41,15 @@ class TNFW(ThinLens): Args: name (str): Name of the lens instance. - cosmology (Cosmology): An instance of the Cosmology class which contains + cosmology (Cosmology): An instance of the Cosmology class which contains information about the cosmological model and parameters. z_l (Optional[Tensor]): Redshift of the lens. - x0 (Optional[Tensor]): Center of lens position on x-axis (arcsec). - y0 (Optional[Tensor]): Center of lens position on y-axis (arcsec). + x0 (Optional[Tensor]): Center of lens position on x-axis (arcsec). + y0 (Optional[Tensor]): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - s (float): Softening parameter to avoid singularities at the center of the lens. + s (float): Softening parameter to avoid singularities at the center of the lens. Default is 0.0. interpret_m_total_mass (bool): Indicates how to interpret the mass variable "m". If true the mass is intepreted as the total mass of the halo (good because it makes sense). If @@ -59,6 +60,7 @@ class TNFW(ThinLens): you wish to use by setting this parameter to one of: batchable, differentiable. """ + def __init__( self, cosmology: Cosmology, @@ -70,7 +72,7 @@ def __init__( tau: Optional[Union[Tensor, float]] = None, s: float = 0.0, interpret_m_total_mass: bool = True, - use_case = "batchable", + use_case="batchable", name: str = None, ): """ @@ -92,21 +94,32 @@ def __init__( self._F = self._F_differentiable else: raise ValueError("use case should be one of: batchable, differentiable") - + @staticmethod def _F_batchable(x): """ Helper method from Baltz et al. 2009 equation A.5 """ - return torch.where(x == 1, torch.ones_like(x), ((1 / x.to(dtype=torch.cdouble)).arccos() / (x.to(dtype=torch.cdouble)**2 - 1).sqrt()).abs()) + return torch.where( + x == 1, + torch.ones_like(x), + ( + (1 / x.to(dtype=torch.cdouble)).arccos() + / (x.to(dtype=torch.cdouble) ** 2 - 1).sqrt() + ).abs(), + ) @staticmethod def _F_differentiable(x): f = torch.ones_like(x) - f[x < 1] = torch.arctanh((1. - x[x < 1]**2).sqrt()) / (1. - x[x < 1]**2).sqrt() - f[x > 1] = torch.arctan((x[x > 1]**2 - 1.).sqrt()) / (x[x > 1]**2 - 1.).sqrt() + f[x < 1] = ( + torch.arctanh((1.0 - x[x < 1] ** 2).sqrt()) / (1.0 - x[x < 1] ** 2).sqrt() + ) + f[x > 1] = ( + torch.arctan((x[x > 1] ** 2 - 1.0).sqrt()) / (x[x > 1] ** 2 - 1.0).sqrt() + ) return f - + @staticmethod def _L(x, tau): """ @@ -115,14 +128,25 @@ def _L(x, tau): return (x / (tau + (tau**2 + x**2).sqrt())).log() @unpack(0) - def get_concentration(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def get_concentration( + self, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> Tensor: """ Calculate the scale radius of the lens. This is the same formula used for the classic NFW profile. Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -132,19 +156,30 @@ def get_concentration(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: Tensor: The scale radius of the lens in Mpc. """ critical_density = self.cosmology.critical_density(z_l, params) - d_l = self.cosmology.angular_diameter_distance(z_l, params) + d_l = self.cosmology.angular_diameter_distance(z_l, params) r_delta = (3 * mass / (4 * pi * DELTA * critical_density)) ** (1 / 3) return r_delta / (scale_radius * d_l * arcsec_to_rad) @unpack(0) - def get_truncation_radius(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def get_truncation_radius( + self, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> Tensor: """ Calculate the truncation radius of the TNFW lens. Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -156,14 +191,25 @@ def get_truncation_radius(self, z_l, x0, y0, mass, scale_radius, tau, *args, par return tau * scale_radius @unpack(0) - def get_M0(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def get_M0( + self, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> Tensor: """ Calculate the reference mass. This is an abstract reference mass used internally in the equations from Baltz et al. 2009. Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -173,21 +219,43 @@ def get_M0(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional[" Tensor: The reference mass of the lens in Msol. """ if self.interpret_m_total_mass: - return mass * (tau**2 + 1)**2 / (tau**2 * ((tau**2 - 1) * tau.log() + torch.pi * tau - (tau**2 + 1))) + return ( + mass + * (tau**2 + 1) ** 2 + / ( + tau**2 + * ((tau**2 - 1) * tau.log() + torch.pi * tau - (tau**2 + 1)) + ) + ) else: d_l = self.cosmology.angular_diameter_distance(z_l, params) - return 4 * torch.pi * (scale_radius * d_l * arcsec_to_rad)**3 * self.get_scale_density(params) - + return ( + 4 + * torch.pi + * (scale_radius * d_l * arcsec_to_rad) ** 3 + * self.get_scale_density(params) + ) @unpack(0) - def get_scale_density(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs) -> Tensor: + def get_scale_density( + self, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, + ) -> Tensor: """ Calculate the scale density of the lens. Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -207,15 +275,27 @@ def get_scale_density(self, z_l, x0, y0, mass, scale_radius, tau, *args, params: @unpack(3) def convergence( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ TNFW convergence as given in Baltz et al. 2009. This is unitless since it is Sigma(x) / Sigma_crit. Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -223,7 +303,7 @@ def convergence( Returns: Tensor: unitless convergence at requested position - + """ x, y = translate_rotate(x, y, x0, y0) r = (x**2 + y**2).sqrt() + self.s @@ -233,27 +313,40 @@ def convergence( L = self._L(g, tau) critical_density = self.cosmology.critical_surface_density(z_l, z_s, params) - S = self.get_M0(params) / (2 * torch.pi * (scale_radius * d_l * arcsec_to_rad)**2) - + S = self.get_M0(params) / ( + 2 * torch.pi * (scale_radius * d_l * arcsec_to_rad) ** 2 + ) + t2 = tau**2 - a1 = t2 / (t2 + 1)**2 - a2 = torch.where(g == 1, (t2 + 1) / 3., (t2 + 1) * (1 - F) / (g**2 - 1)) + a1 = t2 / (t2 + 1) ** 2 + a2 = torch.where(g == 1, (t2 + 1) / 3.0, (t2 + 1) * (1 - F) / (g**2 - 1)) a3 = 2 * F - a4 = - torch.pi / (t2 + g**2).sqrt() + a4 = -torch.pi / (t2 + g**2).sqrt() a5 = (t2 - 1) * L / (tau * (t2 + g**2).sqrt()) return a1 * (a2 + a3 + a4 + a5) * S / critical_density @unpack(2) def mass_enclosed_2d( - self, r: Tensor, z_s: Tensor, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs + self, + r: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Total projected mass (Msol) within a radius r (arcsec). Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -266,17 +359,29 @@ def mass_enclosed_2d( t2 = tau**2 F = self._F(g) L = self._L(g, tau) - a1 = t2 / (t2 + 1)**2 - a2 = (t2 + 1 + 2*(g**2 - 1)) * F + a1 = t2 / (t2 + 1) ** 2 + a2 = (t2 + 1 + 2 * (g**2 - 1)) * F a3 = tau * torch.pi a4 = (t2 - 1) * tau.log() a5 = (t2 + g**2).sqrt() * (-torch.pi + (t2 - 1) * L / tau) S = self.get_M0(params) return S * a1 * (a2 + a3 + a4 + a5) - + @unpack(3) def physical_deflection_angle( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> tuple[Tensor, Tensor]: """Compute the physical deflection angle (arcsec) for this lens at the requested position. Note that the NFW/TNFW profile is more @@ -285,8 +390,8 @@ def physical_deflection_angle( Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -298,17 +403,31 @@ def physical_deflection_angle( """ d_l = self.cosmology.angular_diameter_distance(z_l, params) x, y = translate_rotate(x, y, x0, y0) - r = ((x**2 + y**2).sqrt() + self.s) - theta = torch.arctan2(y,x) + r = (x**2 + y**2).sqrt() + self.s + theta = torch.arctan2(y, x) # The below actually equally comes from eq 2.13 in Meneghetti notes - dr = self.mass_enclosed_2d(r, z_s, params) / (r * d_l * arcsec_to_rad) # note dpsi(u)/du = 2x*dpsi(x)/dx when u = x^2 + dr = self.mass_enclosed_2d(r, z_s, params) / ( + r * d_l * arcsec_to_rad + ) # note dpsi(u)/du = 2x*dpsi(x)/dx when u = x^2 S = 4 * G_over_c2 * rad_to_arcsec return S * dr * theta.cos(), S * dr * theta.sin() @unpack(3) def potential( - self, x: Tensor, y: Tensor, z_s: Tensor, z_l, x0, y0, mass, scale_radius, tau, *args, params: Optional["Packed"] = None, **kwargs + self, + x: Tensor, + y: Tensor, + z_s: Tensor, + z_l, + x0, + y0, + mass, + scale_radius, + tau, + *args, + params: Optional["Packed"] = None, + **kwargs, ) -> Tensor: """ Compute the lensing potential. Note that this is not a unitless potential! This is the potential as given in Baltz et al. 2009. @@ -317,8 +436,8 @@ def potential( Args: z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). + x0 (Tensor): Center of lens position on x-axis (arcsec). + y0 (Tensor): Center of lens position on y-axis (arcsec). mass (Optional[Tensor]): Mass of the lens (Msol). scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). @@ -335,15 +454,24 @@ def potential( F = self._F(g) L = self._L(g, tau) - #d_l = self.cosmology.angular_diameter_distance(z_l, params) - S = 2 * self.get_M0(params) * G_over_c2 # * rad_to_arcsec * d_l**2 - a1 = 1 / (t2 + 1)**2 - a2 = 2 * torch.pi * t2 * (tau - (t2 + u).sqrt() + tau * (tau + (t2 + u).sqrt()).log()) + # d_l = self.cosmology.angular_diameter_distance(z_l, params) + S = 2 * self.get_M0(params) * G_over_c2 # * rad_to_arcsec * d_l**2 + a1 = 1 / (t2 + 1) ** 2 + a2 = ( + 2 + * torch.pi + * t2 + * (tau - (t2 + u).sqrt() + tau * (tau + (t2 + u).sqrt()).log()) + ) a3 = 2 * (t2 - 1) * tau * (t2 + u).sqrt() * L a4 = t2 * (t2 - 1) * L**2 a5 = 4 * t2 * (u - 1) * F - a6 = t2 * (t2 - 1) * (1 / g.to(dtype=torch.cdouble)).arccos().abs()**2 + a6 = t2 * (t2 - 1) * (1 / g.to(dtype=torch.cdouble)).arccos().abs() ** 2 a7 = t2 * ((t2 - 1) * tau.log() - t2 - 1) * u.log() - a8 = t2 * ((t2 - 1) * tau.log() * (4*tau).log() + 2 * (tau/2).log() - 2 * tau * (tau - torch.pi) * (2*tau).log()) + a8 = t2 * ( + (t2 - 1) * tau.log() * (4 * tau).log() + + 2 * (tau / 2).log() + - 2 * tau * (tau - torch.pi) * (2 * tau).log() + ) return S * a1 * (a2 + a3 + a4 + a5 + a6 + a7 - a8) diff --git a/caustics/lenses/utils.py b/src/caustics/lenses/utils.py similarity index 96% rename from caustics/lenses/utils.py rename to src/caustics/lenses/utils.py index 4912a6c8..4ccb2b54 100644 --- a/caustics/lenses/utils.py +++ b/src/caustics/lenses/utils.py @@ -50,7 +50,7 @@ def get_pix_magnification(raytrace, x, y, z_s) -> Tensor: def get_magnification(raytrace, x, y, z_s) -> Tensor: """ - Computes the magnification over a grid on the lensing plane. This is done by calling `get_pix_magnification` + Computes the magnification over a grid on the lensing plane. This is done by calling `get_pix_magnification` for each point on the grid. Args: @@ -62,6 +62,4 @@ def get_magnification(raytrace, x, y, z_s) -> Tensor: Returns: A tensor representing the magnification at each point on the grid. """ - return vmap_n(get_pix_magnification, 2, (None, 0, 0, None))( - raytrace, x, y, z_s - ) + return vmap_n(get_pix_magnification, 2, (None, 0, 0, None))(raytrace, x, y, z_s) diff --git a/caustics/light/__init__.py b/src/caustics/light/__init__.py similarity index 100% rename from caustics/light/__init__.py rename to src/caustics/light/__init__.py diff --git a/caustics/light/base.py b/src/caustics/light/base.py similarity index 71% rename from caustics/light/base.py rename to src/caustics/light/base.py index 40e9da3e..f6a0e915 100644 --- a/caustics/light/base.py +++ b/src/caustics/light/base.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Any, Optional +from typing import Optional from torch import Tensor @@ -10,38 +10,39 @@ class Source(Parametrized): """ - This is an abstract base class used to represent a source in a strong gravitational lensing system. - It provides the basic structure and required methods that any derived source class should implement. - The Source class inherits from the Parametrized class, implying that it contains parameters that can + This is an abstract base class used to represent a source in a strong gravitational lensing system. + It provides the basic structure and required methods that any derived source class should implement. + The Source class inherits from the Parametrized class, implying that it contains parameters that can be optimized or manipulated. - - The class introduces one abstract method, `brightness`, that must be implemented in any concrete + + The class introduces one abstract method, `brightness`, that must be implemented in any concrete subclass. This method calculates the brightness of the source at given coordinates. """ + @abstractmethod @unpack(2) def brightness( - self, x: Tensor, y: Tensor, *args, params: Optional["Packed"] = None, **kwargs + self, x: Tensor, y: Tensor, *args, params: Optional["Packed"] = None, **kwargs ) -> Tensor: """ - Abstract method that calculates the brightness of the source at the given coordinates. + Abstract method that calculates the brightness of the source at the given coordinates. This method is expected to be implemented in any class that derives from Source. - + Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. + x (Tensor): The x-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. + + y (Tensor): The y-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - - params (Packed, optional): Dynamic parameter container that might be required to calculate - the brightness. The exact contents will depend on the specific implementation in derived classes. + + params (Packed, optional): Dynamic parameter container that might be required to calculate + the brightness. The exact contents will depend on the specific implementation in derived classes. Returns: - Tensor: The brightness of the source at the given coordinate(s). The exact form of the output + Tensor: The brightness of the source at the given coordinate(s). The exact form of the output will depend on the specific implementation in the derived class. - - Note: + + Note: This method must be overridden in any class that inherits from `Source`. """ ... diff --git a/caustics/light/pixelated.py b/src/caustics/light/pixelated.py similarity index 81% rename from caustics/light/pixelated.py rename to src/caustics/light/pixelated.py index 72a82790..142e53b0 100644 --- a/caustics/light/pixelated.py +++ b/src/caustics/light/pixelated.py @@ -1,7 +1,6 @@ from typing import Optional, Union from torch import Tensor -from torch import vmap from ..utils import interp2d from .base import Source @@ -12,23 +11,24 @@ class Pixelated(Source): """ - `Pixelated` is a subclass of the abstract class `Source`. It representes the brightness profile of + `Pixelated` is a subclass of the abstract class `Source`. It representes the brightness profile of source with a pixelated grid of intensity values. - - This class provides a concrete implementation of the `brightness` method required by the `Source` - superclass. In this implementation, brightness is determined by interpolating values from the + + This class provides a concrete implementation of the `brightness` method required by the `Source` + superclass. In this implementation, brightness is determined by interpolating values from the provided source image. Attributes: - x0 (Optional[Tensor]): The x-coordinate of the source image's center. + x0 (Optional[Tensor]): The x-coordinate of the source image's center. y0 (Optional[Tensor]): The y-coordinate of the source image's center. image (Optional[Tensor]): The source image from which brightness values will be interpolated. pixelscale (Optional[Tensor]): The pixelscale of the source image in the lens plane in units of arcsec/pixel. shape (Optional[tuple[int, ...]]): The shape of the source image. """ + def __init__( self, - image: Optional[Tensor] = None, + image: Optional[Tensor] = None, x0: Optional[Union[Tensor, float]] = None, y0: Optional[Union[Tensor, float]] = None, pixelscale: Optional[Union[Tensor, float]] = None, @@ -36,7 +36,7 @@ def __init__( name: str = None, ): """ - Constructs the `Pixelated` object with the given parameters. + Constructs the `Pixelated` object with the given parameters. Args: name (str): The name of the source. @@ -61,25 +61,38 @@ def __init__( self.add_param("pixelscale", pixelscale) @unpack(2) - def brightness(self, x, y, x0, y0, image, pixelscale, *args, params: Optional["Packed"] = None, **kwargs): + def brightness( + self, + x, + y, + x0, + y0, + image, + pixelscale, + *args, + params: Optional["Packed"] = None, + **kwargs, + ): """ - Implements the `brightness` method for `Pixelated`. The brightness at a given point is + Implements the `brightness` method for `Pixelated`. The brightness at a given point is determined by interpolating values from the source image. Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. + x (Tensor): The x-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. + y (Tensor): The y-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - params (Optional[Packed]): A dictionary containing additional parameters that might be required to - calculate the brightness. + params (Optional[Packed]): A dictionary containing additional parameters that might be required to + calculate the brightness. Returns: - Tensor: The brightness of the source at the given coordinate(s). The brightness is + Tensor: The brightness of the source at the given coordinate(s). The brightness is determined by interpolating values from the source image. """ fov_x = pixelscale * image.shape[0] fov_y = pixelscale * image.shape[1] return interp2d( - image, (x - x0).view(-1) / fov_x*2, (y - y0).view(-1) / fov_y*2 # make coordinates bounds at half the fov + image, + (x - x0).view(-1) / fov_x * 2, + (y - y0).view(-1) / fov_y * 2, # make coordinates bounds at half the fov ).reshape(x.shape) diff --git a/caustics/light/probes.py b/src/caustics/light/probes.py similarity index 100% rename from caustics/light/probes.py rename to src/caustics/light/probes.py diff --git a/caustics/light/sersic.py b/src/caustics/light/sersic.py similarity index 89% rename from caustics/light/sersic.py rename to src/caustics/light/sersic.py index e35ea8f1..4e37263a 100644 --- a/caustics/light/sersic.py +++ b/src/caustics/light/sersic.py @@ -11,14 +11,14 @@ class Sersic(Source): """ - `Sersic` is a subclass of the abstract class `Source`. It represents a source in a strong - gravitational lensing system that follows a Sersic profile, a mathematical function that describes + `Sersic` is a subclass of the abstract class `Source`. It represents a source in a strong + gravitational lensing system that follows a Sersic profile, a mathematical function that describes how the intensity I of a galaxy varies with distance r from its center. - + The Sersic profile is often used to describe elliptical galaxies and spiral galaxies' bulges. Attributes: - x0 (Optional[Tensor]): The x-coordinate of the Sersic source's center. + x0 (Optional[Tensor]): The x-coordinate of the Sersic source's center. y0 (Optional[Tensor]): The y-coordinate of the Sersic source's center. q (Optional[Tensor]): The axis ratio of the Sersic source. phi (Optional[Tensor]): The orientation of the Sersic source (position angle). @@ -28,6 +28,7 @@ class Sersic(Source): s (float): A small constant for numerical stability. lenstronomy_k_mode (bool): A flag indicating whether to use lenstronomy to compute the value of k. """ + def __init__( self, x0: Optional[Union[Tensor, float]] = None, @@ -42,7 +43,7 @@ def __init__( name: str = None, ): """ - Constructs the `Sersic` object with the given parameters. + Constructs the `Sersic` object with the given parameters. Args: name (str): The name of the source. @@ -69,15 +70,29 @@ def __init__( self.lenstronomy_k_mode = use_lenstronomy_k @unpack(2) - def brightness(self, x, y, x0, y0, q, phi, n, Re, Ie, *args, params: Optional["Packed"] = None, **kwargs): + def brightness( + self, + x, + y, + x0, + y0, + q, + phi, + n, + Re, + Ie, + *args, + params: Optional["Packed"] = None, + **kwargs, + ): """ - Implements the `brightness` method for `Sersic`. The brightness at a given point is + Implements the `brightness` method for `Sersic`. The brightness at a given point is determined by the Sersic profile formula. Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. + x (Tensor): The x-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. + y (Tensor): The y-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. params (Packed, optional): Dynamic parameter container. @@ -85,13 +100,13 @@ def brightness(self, x, y, x0, y0, q, phi, n, Re, Ie, *args, params: Optional["P Tensor: The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. Notes: - The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), - where Ie is the intensity at the effective radius r_e, n is the Sersic index - that describes the concentration of the source, and k is a parameter that - depends on n. In this implementation, we use elliptical coordinates ex and ey, + The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), + where Ie is the intensity at the effective radius r_e, n is the Sersic index + that describes the concentration of the source, and k is a parameter that + depends on n. In this implementation, we use elliptical coordinates ex and ey, and the transformation from Cartesian coordinates is handled by `to_elliptical`. - The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. - If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, + The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. + If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, otherwise, we use the approximation from Ciotti & Bertin (1999). """ x, y = translate_rotate(x, y, x0, y0, phi) diff --git a/caustics/namespace_dict.py b/src/caustics/namespace_dict.py similarity index 93% rename from caustics/namespace_dict.py rename to src/caustics/namespace_dict.py index 44e10738..6e0ac77d 100644 --- a/caustics/namespace_dict.py +++ b/src/caustics/namespace_dict.py @@ -6,6 +6,7 @@ class NamespaceDict(OrderedDict): """ Add support for attributes on top of an OrderedDict """ + def __getattr__(self, key): if key in self: return self[key] @@ -32,14 +33,16 @@ class _NestedNamespaceDict(NamespaceDict): """ Abstract method for NestedNamespaceDict and its Proxy """ + def flatten(self) -> NamespaceDict: """ Flatten the nested dictionary into a NamespaceDict - + Returns: NamespaceDict: Flattened dictionary as a NamespaceDict """ flattened_dict = NamespaceDict() + def _flatten_dict(dictionary, parent_key=""): for key, value in dictionary.items(): new_key = f"{parent_key}.{key}" if parent_key else key @@ -47,24 +50,27 @@ def _flatten_dict(dictionary, parent_key=""): _flatten_dict(value, new_key) else: flattened_dict[new_key] = value + _flatten_dict(self) return flattened_dict def collapse(self) -> NamespaceDict: """ - Flatten the nested dictionary and collapse keys into the first level + Flatten the nested dictionary and collapse keys into the first level of the NamespaceDict - + Returns: NamespaceDict: Flattened dictionary as a NamespaceDict """ flattened_dict = NamespaceDict() + def _flatten_dict(dictionary): for key, value in dictionary.items(): if isinstance(value, dict): _flatten_dict(value) else: flattened_dict[key] = value + _flatten_dict(self) return flattened_dict @@ -74,6 +80,7 @@ class _NestedNamespaceProxy(_NestedNamespaceDict): Proxy for NestedNamespaceDict in order to allow recursion in the class attributes """ + def __init__(self, parent, key_path): # Add new private keys to give us a ladder back to root node self._parent = parent @@ -81,7 +88,7 @@ def __init__(self, parent, key_path): super().__init__(parent[key_path]) def __setattr__(self, key, value): - if key.startswith('_'): + if key.startswith("_"): # We are in a child node, we need to recurse up super().__setattr__(key, value) else: @@ -91,15 +98,15 @@ def __setattr__(self, key, value): # Hide the private keys from common usage def keys(self): return [key for key in super().keys() if not key.startswith("_")] - + def items(self): for key, value in super().items(): - if not key.startswith('_'): + if not key.startswith("_"): yield (key, value) def values(self): return [v for k, v in super().items() if not k.startswith("_")] - + def __len__(self): # make sure hidden keys don't count in the length of the object return len(self.keys()) @@ -108,7 +115,7 @@ def __len__(self): class NestedNamespaceDict(_NestedNamespaceDict): """ Example usage - ```python + ```python nested_namespace = NestedNamespaceDict() nested_namespace.foo = 'Hello' nested_namespace.bar = {'baz': 'World'} @@ -119,31 +126,32 @@ class NestedNamespaceDict(_NestedNamespaceDict): print(nested_namespace) # Output: # {'foo': 'Hello', 'bar': {'baz': 'World', 'qux': 42 }} - + #============================== # Flattened key access #============================== print(nested_dict['foo']) # Output: Hello print(nested_dict['bar.baz']) # Output: World print(nested_dict['bar.qux']) # Output: 42 - + #============================== # Nested namespace access #============================== print(nested_dict.bar.qux) # Output: 42 - + #============================== # Flatten and collapse method #============================== print(nested_dict.flatten()) # Output: # {'foo': 'Hello', 'bar.baz': 'World', 'bar.qux': 42} - + print(nested_dict.collapse() # Output: # {'foo': 'Hello', 'baz': 'World', 'qux': 42} - + """ + def __getattr__(self, key): if key in self: value = super().__getitem__(key) @@ -152,7 +160,9 @@ def __getattr__(self, key): else: return value else: - raise AttributeError(f"'NestedNamespaceDict' object has no attribute '{key}'") + raise AttributeError( + f"'NestedNamespaceDict' object has no attribute '{key}'" + ) def __getitem__(self, key): if "." in key: @@ -171,8 +181,9 @@ def __setitem__(self, key, value): if root not in self: self[root] = NestedNamespaceDict() elif not isinstance(self[root], dict): - raise ValueError("Can't assign a NestedNamespaceDict to a non-dict entry") + raise ValueError( + "Can't assign a NestedNamespaceDict to a non-dict entry" + ) self[root].__setitem__(childs, value) else: super().__setitem__(key, value) - diff --git a/caustics/packed.py b/src/caustics/packed.py similarity index 100% rename from caustics/packed.py rename to src/caustics/packed.py diff --git a/caustics/parameter.py b/src/caustics/parameter.py similarity index 85% rename from caustics/parameter.py rename to src/caustics/parameter.py index c87d9be8..d49a490d 100644 --- a/caustics/parameter.py +++ b/src/caustics/parameter.py @@ -2,7 +2,6 @@ import torch from torch import Tensor -from .namespace_dict import NamespaceDict __all__ = ("Parameter",) @@ -19,7 +18,9 @@ class Parameter: """ def __init__( - self, value: Optional[Union[Tensor, float]] = None, shape: Optional[tuple[int, ...]] = () + self, + value: Optional[Union[Tensor, float]] = None, + shape: Optional[tuple[int, ...]] = (), ): # Must assign one of value or shape if value is None: @@ -45,16 +46,18 @@ def dynamic(self) -> bool: @property def value(self) -> Optional[Tensor]: return self._value - + @value.setter def value(self, value: Union[None, Tensor, float]): if value is not None: value = torch.as_tensor(value) if value.shape != self.shape: - raise ValueError(f"Cannot set Parameter value with a different shape. Received {value.shape}, expected {self.shape}") + raise ValueError( + f"Cannot set Parameter value with a different shape. Received {value.shape}, expected {self.shape}" + ) self._value = value self._dtype = None if value is None else value.dtype - + @property def dtype(self): return self._dtype @@ -62,11 +65,13 @@ def dtype(self): @property def shape(self) -> tuple[int, ...]: return self._shape - + def set_static(self): self.value = None - def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None): + def to( + self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None + ): """ Moves and/or casts the values of the parameter. diff --git a/caustics/parametrized.py b/src/caustics/parametrized.py similarity index 86% rename from caustics/parametrized.py rename to src/caustics/parametrized.py index d0940c6a..753207e5 100644 --- a/caustics/parametrized.py +++ b/src/caustics/parametrized.py @@ -1,4 +1,4 @@ -from collections import OrderedDict, defaultdict +from collections import OrderedDict from math import prod from typing import Optional, Union import functools @@ -13,13 +13,15 @@ from .namespace_dict import NamespaceDict, NestedNamespaceDict from .parameter import Parameter -__all__ = ("Parametrized","unpack") +__all__ = ("Parametrized", "unpack") def check_valid_name(name): if keyword.iskeyword(name) or not bool(re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", name)): - raise NameError(f"The string {name} contains illegal characters (like space or '-'). "\ - "Please use snake case or another valid python variable naming style.") + raise NameError( + f"The string {name} contains illegal characters (like space or '-'). " + "Please use snake case or another valid python variable naming style." + ) class Parametrized: @@ -56,16 +58,18 @@ def __init__(self, name: str = None): self._params: OrderedDict[str, Parameter] = NamespaceDict() self._childs: OrderedDict[str, Parametrized] = NamespaceDict() self._module_key_map = {} - + def _default_name(self): return re.search("([A-Z])\w+", str(self.__class__)).group() - + def __getattribute__(self, key): try: return super().__getattribute__(key) except AttributeError as e: # Check if key refers to a parametrized module name (different from its attribute key) - _map = super().__getattribute__("_module_key_map") # use super to avoid recursion error + _map = super().__getattribute__( + "_module_key_map" + ) # use super to avoid recursion error if key in _map.keys(): return super().__getattribute__(_map[key]) else: @@ -82,18 +86,18 @@ def __setattr__(self, key, value): elif isinstance(value, Parametrized): # Update map from attribute key to module name for __getattribute__ method self._module_key_map[value.name] = key - self.add_parametrized(value, set_attr=False) + self.add_parametrized(value, set_attr=False) # set attr only to user defined key, not module name (self.{module.name} is still accessible, see __getattribute__ method) super().__setattr__(key, value) else: super().__setattr__(key, value) - except AttributeError: # _params or another attribute in here do not exist yet - super().__setattr__(key, value) + except AttributeError: # _params or another attribute in here do not exist yet + super().__setattr__(key, value) @property def name(self) -> str: return self._name - + @name.setter def name(self, new_name: str): check_valid_name(new_name) @@ -106,7 +110,9 @@ def name(self, new_name: str): child._parents[new_name] = self self._name = new_name - def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None): + def to( + self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None + ): """ Moves static Params for this component and its childs to the specified device and casts them to the specified data type. """ @@ -122,7 +128,7 @@ def _generate_unique_name(name, module_names): while f"{name}_{i}" in module_names: i += 1 return f"{name}_{i}" - + def add_parametrized(self, p: "Parametrized", set_attr=True): """ Add a child to this module, and create edges for the DAG @@ -130,7 +136,7 @@ def add_parametrized(self, p: "Parametrized", set_attr=True): # If self.name is already in the module parents, we need to update self.name if self.name in p._parents.keys(): new_name = self._generate_unique_name(self.name, p._parents.keys()) - self.name = new_name # from name.setter, this updates the DAG edges as well + self.name = new_name # from name.setter, this updates the DAG edges as well p._parents[self.name] = self # If the module name is already in self._childs, we need to update module name if p.name is self._childs.keys(): @@ -156,7 +162,7 @@ def add_param( """ self._params[name] = Parameter(value, shape) # __setattr__ inside add_param to catch all uses of this method - super().__setattr__(name, self._params[name]) + super().__setattr__(name, self._params[name]) @property def n_dynamic(self) -> int: @@ -180,7 +186,7 @@ def pack( ) -> Packed: """ Converts a list or tensor into a dict that can subsequently be unpacked - into arguments to this component and its childs. Also, add a batch dimension + into arguments to this component and its childs. Also, add a batch dimension to each Tensor without such a dimension. Args: @@ -197,14 +203,15 @@ def pack( ValueError: If the input is a tensor and the shape does not match the expected shape. """ if isinstance(x, (dict, Packed)): - missing_names = [name for name in self.params.dynamic.keys() if name not in x] + missing_names = [ + name for name in self.params.dynamic.keys() if name not in x + ] if len(missing_names) > 0: raise ValueError(f"missing x keys for {missing_names}") # TODO: check structure! return Packed(x) - - + elif isinstance(x, (list, tuple)): n_passed = len(x) n_dynamic_params = len(self.params.dynamic.flatten()) @@ -217,17 +224,19 @@ def pack( cur_offset += module.n_dynamic elif n_passed == n_dynamic_modules: for i, name in enumerate(self.dynamic_modules.keys()): - x_repacked[name] = x[i] + x_repacked[name] = x[i] else: raise ValueError( f"{n_passed} dynamic args were passed, but {n_dynamic_params} parameters or " f"{n_dynamic_modules} Tensor (1 per dynamic module) are required" ) return Packed(x_repacked) - + elif isinstance(x, Tensor): n_passed = x.shape[-1] - n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) + n_expected = sum( + [module.dynamic_size for module in self.dynamic_modules.values()] + ) if n_passed != n_expected: # TODO: give component and arg names raise ValueError( @@ -250,7 +259,7 @@ def unpack( ) -> list[Tensor]: """ Unpacks a dict of kwargs, list of args or flattened vector of args to retrieve - this object's static and dynamic parameters. + this object's static and dynamic parameters. Args: x (Optional[dict[str, Union[list[Tensor], dict[str, Tensor], Tensor]]]): @@ -268,11 +277,13 @@ def unpack( # Check if module has dynamic parameters if self.module_params.dynamic: dynamic_x = x[self.name] - else: # all parameters are static and module is not present in x + else: # all parameters are static and module is not present in x dynamic_x = [] if isinstance(x, dict): if self.name in x.keys() and x.get(self.name, {}): - print(f"Module {self.name} is static, the parameters {' '.join(x[self.name].keys())} passed dynamically will be ignored ignored") + print( + f"Module {self.name} is static, the parameters {' '.join(x[self.name].keys())} passed dynamically will be ignored ignored" + ) unpacked_x = [] offset = 0 for name, param in self._params.items(): @@ -284,17 +295,23 @@ def unpack( offset += 1 elif isinstance(dynamic_x, Tensor): size = prod(param.shape) - param_value = dynamic_x[..., offset: offset + size].reshape(param.shape) + param_value = dynamic_x[..., offset : offset + size].reshape( + param.shape + ) offset += size else: - raise ValueError(f"Invalid data type found when unpacking parameters for {self.name}." - f"Expected argument of unpack to be a list/tuple/dict of Tensor, or simply a flattened tensor" - f"but found {type(dynamic_x)}.") - else: # param is static + raise ValueError( + f"Invalid data type found when unpacking parameters for {self.name}." + f"Expected argument of unpack to be a list/tuple/dict of Tensor, or simply a flattened tensor" + f"but found {type(dynamic_x)}." + ) + else: # param is static param_value = param.value if not isinstance(param_value, Tensor): - raise ValueError(f"Invalid data type found when unpacking parameters for {self.name}." - f"Argument of unpack must contain Tensor, but found {type(param_value)}") + raise ValueError( + f"Invalid data type found when unpacking parameters for {self.name}." + f"Argument of unpack must contain Tensor, but found {type(param_value)}" + ) unpacked_x.append(param_value) return unpacked_x @@ -308,12 +325,13 @@ def module_params(self) -> NestedNamespaceDict: else: dynamic[name] = param return NestedNamespaceDict([("static", static), ("dynamic", dynamic)]) - + @property def params(self) -> NestedNamespaceDict: # todo make this an ordinary dict and reorder at the end. static = NestedNamespaceDict() dynamic = NestedNamespaceDict() + def _get_params(module): if module.module_params.static: static[module.name] = module.module_params.static @@ -321,6 +339,7 @@ def _get_params(module): dynamic[module.name] = module.module_params.dynamic for child in module._childs.values(): _get_params(child) + _get_params(self) # TODO reorder return NestedNamespaceDict([("static", static), ("dynamic", dynamic)]) @@ -328,7 +347,10 @@ def _get_params(module): @property def dynamic_modules(self) -> NamespaceDict[str, "Parametrized"]: # Only catch modules with dynamic parameters - modules = NamespaceDict() # todo make this an ordinary dict and reorder at the end. + modules = ( + NamespaceDict() + ) # todo make this an ordinary dict and reorder at the end. + def _get_childs(module): # Start from root, and move down the DAG if module.module_params.dynamic: @@ -336,6 +358,7 @@ def _get_childs(module): if module._childs != {}: for child in module._childs.values(): _get_childs(child) + _get_childs(self) # TODO reorder return modules @@ -355,7 +378,9 @@ def __str__(self) -> str: for n, d in self._childs.items(): if d.n_dynamic > 0: - desc_dynamic_strs.append(f"('{n}': {list(d.module_params.dynamic.keys())})") + desc_dynamic_strs.append( + f"('{n}': {list(d.module_params.dynamic.keys())})" + ) desc_dynamic_str = ", ".join(desc_dynamic_strs) @@ -419,6 +444,7 @@ def add_params(p: Parametrized, dot): return dot + def unpack(n_leading_args=0): def decorator(method): sig = inspect.signature(method) @@ -436,7 +462,7 @@ def wrapped(self, *args, **kwargs): leading_args.append(kwargs.pop(param)) elif args: leading_args.append(args.pop(0)) - + # Collect module parameters passed in argument (dynamic or otherwise) if args and isinstance(args[0], Packed): # Case 1: Params is already Packed (or no params were passed) @@ -453,7 +479,9 @@ def wrapped(self, *args, **kwargs): trailing_args.append(kwargs.pop(param)) elif args: trailing_args.append(args.pop(0)) - if not trailing_args or (len(trailing_args) == 1 and trailing_args[0] is None): + if not trailing_args or ( + len(trailing_args) == 1 and trailing_args[0] is None + ): # No params were passed, module is static and was expecting no params x = Packed() elif isinstance(trailing_args[0], (list, dict)): @@ -463,7 +491,7 @@ def wrapped(self, *args, **kwargs): # all parameters were passed individually in args or kwargs x = self.pack(trailing_args) unpacked_args = self.unpack(x) - kwargs['params'] = x + kwargs["params"] = x return method(self, *leading_args, *unpacked_args, **kwargs) return wrapped diff --git a/caustics/sims/__init__.py b/src/caustics/sims/__init__.py similarity index 100% rename from caustics/sims/__init__.py rename to src/caustics/sims/__init__.py diff --git a/caustics/sims/lens_source.py b/src/caustics/sims/lens_source.py similarity index 72% rename from caustics/sims/lens_source.py rename to src/caustics/sims/lens_source.py index d17b6e42..e6238be8 100644 --- a/caustics/sims/lens_source.py +++ b/src/caustics/sims/lens_source.py @@ -2,16 +2,17 @@ from scipy.fft import next_fast_len from torch.nn.functional import avg_pool2d, conv2d -from typing import Tuple, Optional +from typing import Optional import torch from .simulator import Simulator __all__ = ("Lens_Source",) + class Lens_Source(Simulator): """Lens image of a source. - + Striaghtforward simulator to sample a lensed image of a source object. Constructs a sampling grid internally based on the pixelscale and gridding parameters. It can automatically upscale @@ -28,7 +29,7 @@ class Lens_Source(Simulator): lens = caustics.lenses.SIS(cosmology = cosmo, x0 = 0., y0 = 0., th_ein = 1.) source = caustics.sources.Sersic(x0 = 0., y0 = 0., q = 0.5, phi = 0.4, n = 2., Re = 1., Ie = 1.) sim = caustics.sims.Lens_Source(lens, source, pixelscale = 0.05, gridx = 100, gridy = 100, upsample_factor = 2, z_s = 1.) - + img = sim() plt.imshow(img, origin = "lower") plt.show() @@ -47,19 +48,20 @@ class Lens_Source(Simulator): name (default "sim"): a name for this simulator in the parameter DAG. """ + def __init__( self, lens, source, pixelscale: float, pixels_x: int, - lens_light = None, - psf = None, + lens_light=None, + psf=None, pixels_y: Optional[int] = None, upsample_factor: int = 1, - psf_pad = True, - psf_mode = "fft", - z_s = None, + psf_pad=True, + psf_mode="fft", + z_s=None, name: str = "sim", ): super().__init__(name) @@ -72,7 +74,7 @@ def __init__( self.psf = None else: self.psf = torch.as_tensor(psf) - self.psf /= psf.sum() # ensure normalized + self.psf /= psf.sum() # ensure normalized self.add_param("z_s", z_s) # Image grid @@ -83,20 +85,31 @@ def __init__( # PSF padding if needed self.psf_mode = psf_mode if psf_pad and self.psf is not None: - self.psf_pad = (self.psf.shape[1]//2 + 1, self.psf.shape[0]//2 + 1) + self.psf_pad = (self.psf.shape[1] // 2 + 1, self.psf.shape[0] // 2 + 1) else: - self.psf_pad = (0,0) + self.psf_pad = (0, 0) # Build the imaging grid self.upsample_factor = upsample_factor - self.n_pix = (self.gridding[0] + self.psf_pad[0]*2, self.gridding[1] + self.psf_pad[1]*2) - tx = torch.linspace(-0.5 * (pixelscale * self.n_pix[0]), 0.5 * (pixelscale * self.n_pix[0]), self.n_pix[0]*upsample_factor) - ty = torch.linspace(-0.5 * (pixelscale * self.n_pix[1]), 0.5 * (pixelscale * self.n_pix[1]), self.n_pix[1]*upsample_factor) - self.grid = torch.meshgrid(tx, ty, indexing = "xy") + self.n_pix = ( + self.gridding[0] + self.psf_pad[0] * 2, + self.gridding[1] + self.psf_pad[1] * 2, + ) + tx = torch.linspace( + -0.5 * (pixelscale * self.n_pix[0]), + 0.5 * (pixelscale * self.n_pix[0]), + self.n_pix[0] * upsample_factor, + ) + ty = torch.linspace( + -0.5 * (pixelscale * self.n_pix[1]), + 0.5 * (pixelscale * self.n_pix[1]), + self.n_pix[1] * upsample_factor, + ) + self.grid = torch.meshgrid(tx, ty, indexing="xy") if self.psf is not None: - self.psf_fft = self._fft2_padded(self.psf) - + self.psf_fft = self._fft2_padded(self.psf) + def _fft2_padded(self, x): """ Compute the 2D Fast Fourier Transform (FFT) of a tensor with zero-padding. @@ -110,9 +123,9 @@ def _fft2_padded(self, x): npix = copy(self.n_pix) npix = (next_fast_len(npix[0]), next_fast_len(npix[1])) self._s = npix - + return torch.fft.rfft2(x, self._s) - + def _unpad_fft(self, x): """ Remove padding from the result of a 2D FFT. @@ -123,17 +136,26 @@ def _unpad_fft(self, x): Returns: Tensor: The input tensor without padding. """ - return torch.roll(x, (-self.psf_pad[0],-self.psf_pad[1]), dims = (-2,-1))[..., :self.n_pix[0], :self.n_pix[1]] + return torch.roll(x, (-self.psf_pad[0], -self.psf_pad[1]), dims=(-2, -1))[ + ..., : self.n_pix[0], : self.n_pix[1] + ] - def forward(self, params, source_light=True, lens_light=True, lens_source=True, psf_convolve=True): + def forward( + self, + params, + source_light=True, + lens_light=True, + lens_source=True, + psf_convolve=True, + ): """ params: Packed object source_light: when true the source light will be sampled lens_light: when true the lens light will be sampled - lens_source: when true, the source light model will be lensed by the lens mass distribution + lens_source: when true, the source light model will be lensed by the lens mass distribution psf_convolve: when true the image will be convolved with the psf """ - z_s, = self.unpack(params) + (z_s,) = self.unpack(params) # Sample the source light if source_light: @@ -156,13 +178,24 @@ def forward(self, params, source_light=True, lens_light=True, lens_source=True, if psf_convolve and self.psf is not None: if self.psf_mode == "fft": mu_fft = self._fft2_padded(mu) - mu = self._unpad_fft(torch.fft.irfft2(mu_fft * self.psf_fft, self._s).real) + mu = self._unpad_fft( + torch.fft.irfft2(mu_fft * self.psf_fft, self._s).real + ) elif self.psf_mode == "conv2d": - mu = conv2d(mu[None, None], self.psf[None, None], padding = "same").squeeze() + mu = conv2d( + mu[None, None], self.psf[None, None], padding="same" + ).squeeze() else: - raise ValueError(f"psf_mode should be one of 'fft' or 'conv2d', not {self.psf_mode}") + raise ValueError( + f"psf_mode should be one of 'fft' or 'conv2d', not {self.psf_mode}" + ) # Return to the desired image - mu_native_resolution = avg_pool2d(mu[None, None], self.upsample_factor, divisor_override = 1).squeeze() - mu_clipped = mu_native_resolution[self.psf_pad[1]:self.gridding[1] + self.psf_pad[1], self.psf_pad[0]:self.gridding[0] + self.psf_pad[0]] + mu_native_resolution = avg_pool2d( + mu[None, None], self.upsample_factor, divisor_override=1 + ).squeeze() + mu_clipped = mu_native_resolution[ + self.psf_pad[1] : self.gridding[1] + self.psf_pad[1], + self.psf_pad[0] : self.gridding[0] + self.psf_pad[0], + ] return mu_clipped diff --git a/caustics/sims/simulator.py b/src/caustics/sims/simulator.py similarity index 97% rename from caustics/sims/simulator.py rename to src/caustics/sims/simulator.py index ae3ecd9d..d24f5260 100644 --- a/caustics/sims/simulator.py +++ b/src/caustics/sims/simulator.py @@ -23,7 +23,5 @@ def __call__(self, *args, **kwargs): else: packed_args = self.pack() rest_args = tuple() - - - return self.forward(packed_args, *rest_args, **kwargs) + return self.forward(packed_args, *rest_args, **kwargs) diff --git a/caustics/utils.py b/src/caustics/utils.py similarity index 90% rename from caustics/utils.py rename to src/caustics/utils.py index 6101c12d..f3fb94c7 100644 --- a/caustics/utils.py +++ b/src/caustics/utils.py @@ -7,6 +7,7 @@ from torch.func import jacfwd import numpy as np + def flip_axis_ratio(q, phi): """ Makes the value of 'q' positive, then swaps x and y axes if 'q' is larger than 1. @@ -100,12 +101,22 @@ def get_meshgrid( Returns: Tuple[Tensor, Tensor]: The generated meshgrid as a tuple of Tensors. """ - xs = torch.linspace(-1, 1, nx, device=device, dtype=dtype) * pixelscale * (nx - 1) / 2 - ys = torch.linspace(-1, 1, ny, device=device, dtype=dtype) * pixelscale * (ny - 1) / 2 + xs = ( + torch.linspace(-1, 1, nx, device=device, dtype=dtype) + * pixelscale + * (nx - 1) + / 2 + ) + ys = ( + torch.linspace(-1, 1, ny, device=device, dtype=dtype) + * pixelscale + * (ny - 1) + / 2 + ) return torch.meshgrid([xs, ys], indexing="xy") -def safe_divide(num, denom, places = 7): +def safe_divide(num, denom, places=7): """ Safely divides two tensors, returning zero where the denominator is zero. @@ -342,28 +353,28 @@ def get_cluster_means(xs: Tensor, k: int): return torch.stack(means) -def _lm_step(f, X, Y, Cinv, L, Lup, Ldn, epsilon): +def _lm_step(f, X, Y, Cinv, L, Lup, Ldn, epsilon): # Forward fY = f(X) dY = Y - fY - + # Jacobian J = jacfwd(f)(X) - J = J.to(dtype = X.dtype) + J = J.to(dtype=X.dtype) chi2 = (dY @ Cinv @ dY).sum(-1) - + # Gradient grad = J.T @ Cinv @ dY - + # Hessian hess = J.T @ Cinv @ J - hess_perturb = L * (torch.diag(hess) + 0.1*torch.eye(hess.shape[0])) + hess_perturb = L * (torch.diag(hess) + 0.1 * torch.eye(hess.shape[0])) hess = hess + hess_perturb # Step h = torch.linalg.solve(hess, grad) - + # New chi^2 fYnew = f(X + h) dYnew = Y - fYnew @@ -379,32 +390,33 @@ def _lm_step(f, X, Y, Cinv, L, Lup, Ldn, epsilon): return X, L, chi2 + def batch_lm( - X, # B, Din - Y, # B, Dout - f, # Din -> Dout - C = None, # B, Dout, Dout - epsilon = 1e-1, - L = 1e0, - L_dn = 11., - L_up = 9., - max_iter = 50, - L_min = 1e-9, - L_max = 1e9, - stopping = 1e-4, - f_args = (), - f_kwargs = {}, + X, # B, Din + Y, # B, Dout + f, # Din -> Dout + C=None, # B, Dout, Dout + epsilon=1e-1, + L=1e0, + L_dn=11.0, + L_up=9.0, + max_iter=50, + L_min=1e-9, + L_max=1e9, + stopping=1e-4, + f_args=(), + f_kwargs={}, ): B, Din = X.shape B, Dout = Y.shape - + if len(X) != len(Y): raise ValueError("x and y must having matching batch dimension") if C is None: C = torch.eye(Dout).repeat(B, 1, 1) Cinv = torch.linalg.inv(C) - + v_lm_step = torch.vmap(partial(_lm_step, lambda x: f(x, *f_args, **f_kwargs))) L = L * torch.ones(B) Lup = L_up * torch.ones(B) @@ -412,22 +424,33 @@ def batch_lm( e = epsilon * torch.ones(B) for _ in range(max_iter): Xnew, L, C = v_lm_step(X, Y, Cinv, L, Lup, Ldn, e) - if torch.all((Xnew - X).abs() < stopping) and torch.sum(L < 1e-2).item() > B/3: + if ( + torch.all((Xnew - X).abs() < stopping) + and torch.sum(L < 1e-2).item() > B / 3 + ): break X = Xnew - + return X, L, C -def gaussian(pixelscale, nx, ny, sigma, upsample = 1, dtype = torch.float32, device = None): - + +def gaussian(pixelscale, nx, ny, sigma, upsample=1, dtype=torch.float32, device=None): X, Y = np.meshgrid( - np.linspace(-(nx*upsample - 1) * pixelscale / 2, (nx*upsample - 1) * pixelscale / 2, nx*upsample), - np.linspace(-(ny*upsample - 1) * pixelscale / 2, (ny*upsample - 1) * pixelscale / 2, ny*upsample), - indexing = "xy", + np.linspace( + -(nx * upsample - 1) * pixelscale / 2, + (nx * upsample - 1) * pixelscale / 2, + nx * upsample, + ), + np.linspace( + -(ny * upsample - 1) * pixelscale / 2, + (ny * upsample - 1) * pixelscale / 2, + ny * upsample, + ), + indexing="xy", ) - Z = np.exp(- 0.5 * (X**2 + Y**2) / sigma**2) - + Z = np.exp(-0.5 * (X**2 + Y**2) / sigma**2) + Z = Z.reshape(ny, upsample, nx, upsample).sum(axis=(1, 3)) - return torch.tensor(Z / np.sum(Z), dtype = dtype, device = device) + return torch.tensor(Z / np.sum(Z), dtype=dtype, device=device) diff --git a/test/test_simulator.py b/test/test_simulator.py deleted file mode 100644 index 562306ee..00000000 --- a/test/test_simulator.py +++ /dev/null @@ -1,28 +0,0 @@ -from math import pi - -import torch - -from caustics.sims import Lens_Source -from caustics.cosmology import FlatLambdaCDM -from caustics.lenses import SIE -from caustics.light import Sersic -from caustics.utils import get_meshgrid, gaussian - -def test_simulator_runs(): - - # Model - cosmology = FlatLambdaCDM(name="cosmo") - lensmass = SIE(name="lens", cosmology=cosmology, z_l = 1., x0 = 0., y0 = 0.01, q = 0.5, phi = pi/3., b = 1.) - - source = Sersic(name="source", x0 = 0.01, y0 = -0.03, q = 0.6, phi = -pi/4, n = 2., Re = 0.5, Ie = 1.) - lenslight = Sersic(name="lenslight", x0 = 0.0, y0 = 0.01, q = 0.7, phi = pi/4, n = 3., Re = 0.7, Ie = 1.) - - psf = gaussian(0.05, 11, 11, 0.2, upsample = 2) - - sim = Lens_Source(lens = lensmass, source = source, pixelscale = 0.05, pixels_x = 50, lens_light = lenslight, psf = psf, z_s = 2.) - - assert torch.all(torch.isfinite(sim())) - assert torch.all(torch.isfinite(sim({}, source_light=True, lens_light=True, lens_source=True, psf_convolve=False))) - assert torch.all(torch.isfinite(sim({}, source_light=True, lens_light=True, lens_source=False, psf_convolve=True))) - assert torch.all(torch.isfinite(sim({}, source_light=True, lens_light=False, lens_source=True, psf_convolve=True))) - assert torch.all(torch.isfinite(sim({}, source_light=False, lens_light=True, lens_source=True, psf_convolve=True))) diff --git a/test/test_base.py b/tests/test_base.py similarity index 69% rename from test/test_base.py rename to tests/test_base.py index 60068931..320ea69e 100644 --- a/test/test_base.py +++ b/tests/test_base.py @@ -1,38 +1,36 @@ -from math import pi - import torch import numpy as np from caustics.cosmology import FlatLambdaCDM from caustics.lenses import SIE -def test(): +def test(): z_l = torch.tensor(0.5, dtype=torch.float32) z_s = torch.tensor(1.5, dtype=torch.float32) - + # Model cosmology = FlatLambdaCDM(name="cosmo") lens = SIE( name="sie", cosmology=cosmology, - z_l = z_l, - x0 = torch.tensor(0.), - y0 = torch.tensor(0.), - q = torch.tensor(0.4), - phi = torch.tensor(np.pi/5), - b = torch.tensor(1.), + z_l=z_l, + x0=torch.tensor(0.0), + y0=torch.tensor(0.0), + q=torch.tensor(0.4), + phi=torch.tensor(np.pi / 5), + b=torch.tensor(1.0), ) - + # Point in the source plane sp_x = torch.tensor(0.2) sp_y = torch.tensor(0.2) - + # Points in image plane x, y = lens.forward_raytrace(sp_x, sp_y, z_s) - + # Raytrace to check bx, by = lens.raytrace(x, y, z_s) assert torch.all((sp_x - bx).abs() < 1e-3) - assert torch.all((sp_y - by).abs() < 1e-3) + assert torch.all((sp_y - by).abs() < 1e-3) diff --git a/test/test_batching.py b/tests/test_batching.py similarity index 77% rename from test/test_batching.py rename to tests/test_batching.py index 7671b569..78004aea 100644 --- a/test/test_batching.py +++ b/tests/test_batching.py @@ -1,59 +1,77 @@ import torch from torch import vmap from utils import setup_image_simulator, setup_simulator -import pytest + def test_vmapped_simulator(): - sim, (sim_params, cosmo_params, lens_params, source_params) = setup_simulator(batched_params=True) + sim, (sim_params, cosmo_params, lens_params, source_params) = setup_simulator( + batched_params=True + ) n_pix = sim.n_pix print(sim.params) - + # test list input x = sim_params + cosmo_params + lens_params + source_params print(x[0].shape) assert vmap(sim)(x).shape == torch.Size([2, n_pix, n_pix]) - + # test tensor input x_tensor = torch.stack(x, dim=1) print(x_tensor.shape) assert vmap(sim)(x_tensor).shape == torch.Size([2, n_pix, n_pix]) - + # Test dictionary input: Only module with dynamic parameters are required - x_dict = {"simulator": sim_params, "cosmo": cosmo_params, "source": source_params, "lens": lens_params} + x_dict = { + "simulator": sim_params, + "cosmo": cosmo_params, + "source": source_params, + "lens": lens_params, + } print(x_dict) assert vmap(sim)(x_dict).shape == torch.Size([2, n_pix, n_pix]) - + # Test semantic list (one tensor per module) x_semantic = [sim_params, cosmo_params, lens_params, source_params] assert vmap(sim)(x_semantic).shape == torch.Size([2, n_pix, n_pix]) def test_vmapped_simulator_with_pixelated_modules(): - sim, (cosmo_params, lens_params, kappa, source) = setup_image_simulator(batched_params=True) + sim, (cosmo_params, lens_params, kappa, source) = setup_image_simulator( + batched_params=True + ) n_pix = sim.n_pix print(sim.params) - + # test list input - x = cosmo_params + lens_params + kappa + source + x = cosmo_params + lens_params + kappa + source print(x[2].shape) assert vmap(sim)(x).shape == torch.Size([2, n_pix, n_pix]) - + # test tensor input: Does not work well with images since it would require unflattening the images in caustics # x_tensor = torch.concat([_x.view(2, -1) for _x in x], dim=1) # print(x_tensor.shape) # assert vmap(sim)(x_tensor).shape == torch.Size([2, n_pix, n_pix]) - + # Test dictionary input: Only module with dynamic parameters are required - x_dict = {"cosmo": cosmo_params, "source": source, "lens": lens_params, "kappa": kappa} + x_dict = { + "cosmo": cosmo_params, + "source": source, + "lens": lens_params, + "kappa": kappa, + } print(x_dict) assert vmap(sim)(x_dict).shape == torch.Size([2, n_pix, n_pix]) - + # Test passing tensor in source and kappa instead of list - x_dict = {"cosmo": cosmo_params, "source": source[0], "lens": lens_params, "kappa": kappa[0]} + x_dict = { + "cosmo": cosmo_params, + "source": source[0], + "lens": lens_params, + "kappa": kappa[0], + } print(x_dict) assert vmap(sim)(x_dict).shape == torch.Size([2, n_pix, n_pix]) - + # Test semantic list (one tensor per module) x_semantic = [cosmo_params, lens_params, kappa, source] assert vmap(sim)(x_semantic).shape == torch.Size([2, n_pix, n_pix]) - diff --git a/test/test_cosmology.py b/tests/test_cosmology.py similarity index 100% rename from test/test_cosmology.py rename to tests/test_cosmology.py diff --git a/test/test_epl.py b/tests/test_epl.py similarity index 100% rename from test/test_epl.py rename to tests/test_epl.py diff --git a/test/test_external_shear.py b/tests/test_external_shear.py similarity index 100% rename from test/test_external_shear.py rename to tests/test_external_shear.py diff --git a/test/test_interpolate_image.py b/tests/test_interpolate_image.py similarity index 89% rename from test/test_interpolate_image.py rename to tests/test_interpolate_image.py index 06056e46..c292878f 100644 --- a/test/test_interpolate_image.py +++ b/tests/test_interpolate_image.py @@ -59,14 +59,14 @@ def test_pixelated_source(): n = 32 x, y = get_meshgrid(res, n, n) image = torch.ones(n, n) - source = Pixelated(image=image, x0=0., y0=0., pixelscale=res) + source = Pixelated(image=image, x0=0.0, y0=0.0, pixelscale=res) im = source.brightness(x, y) print(im) assert torch.all(im == image) - + # Check smaller res - source = Pixelated(image=image, x0=0., y0=0., pixelscale=res/2) + source = Pixelated(image=image, x0=0.0, y0=0.0, pixelscale=res / 2) im = source.brightness(x, y) - expected_im = torch.nn.functional.pad(torch.ones(n//2, n//2), pad=[n//4]*4) + expected_im = torch.nn.functional.pad(torch.ones(n // 2, n // 2), pad=[n // 4] * 4) print(im) assert torch.all(im == expected_im) diff --git a/test/test_jacobian_lens_equation.py b/tests/test_jacobian_lens_equation.py similarity index 70% rename from test/test_jacobian_lens_equation.py rename to tests/test_jacobian_lens_equation.py index 9b0bd94e..ef126551 100644 --- a/test/test_jacobian_lens_equation.py +++ b/tests/test_jacobian_lens_equation.py @@ -1,29 +1,32 @@ from math import pi -import lenstronomy.Util.param_util as param_util import torch -from lenstronomy.LensModel.lens_model import LensModel -from utils import lens_test_helper from caustics.cosmology import FlatLambdaCDM from caustics.lenses import SIE, Multiplane from caustics.utils import get_meshgrid + def test_jacobian_autograd_vs_finitediff(): # Models cosmology = FlatLambdaCDM(name="cosmo") lens = SIE(name="sie", cosmology=cosmology) thx, thy = get_meshgrid(0.01, 20, 20) - + # Parameters z_s = torch.tensor(1.2) x = torch.tensor([0.5, 0.912, -0.442, 0.7, pi / 3, 1.4]) # Evaluate Jacobian J_autograd = lens.jacobian_lens_equation(thx, thy, z_s, lens.pack(x)) - J_finitediff = lens.jacobian_lens_equation(thx, thy, z_s, lens.pack(x), method = "finitediff", pixelscale = torch.tensor(0.01)) - - assert torch.sum(((J_autograd - J_finitediff)/J_autograd).abs() < 1e-3) > 0.8 * J_autograd.numel() + J_finitediff = lens.jacobian_lens_equation( + thx, thy, z_s, lens.pack(x), method="finitediff", pixelscale=torch.tensor(0.01) + ) + + assert ( + torch.sum(((J_autograd - J_finitediff) / J_autograd).abs() < 1e-3) + > 0.8 * J_autograd.numel() + ) def test_multiplane_jacobian(): @@ -41,16 +44,19 @@ def test_multiplane_jacobian(): x = torch.tensor([p for _xs in xs for p in _xs], dtype=torch.float32) lens = Multiplane( - name="multiplane", cosmology=cosmology, lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))] + name="multiplane", + cosmology=cosmology, + lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))], ) thx, thy = get_meshgrid(0.1, 10, 10) - + # Parameters z_s = torch.tensor(1.2) x = torch.tensor(xs).flatten() A = lens.jacobian_lens_equation(thx, thy, z_s, lens.pack(x)) - assert A.shape == (10,10,2,2) - + assert A.shape == (10, 10, 2, 2) + + def test_multiplane_jacobian_autograd_vs_finitediff(): # Setup z_s = torch.tensor(1.5, dtype=torch.float32) @@ -66,21 +72,28 @@ def test_multiplane_jacobian_autograd_vs_finitediff(): x = torch.tensor([p for _xs in xs for p in _xs], dtype=torch.float32) lens = Multiplane( - name="multiplane", cosmology=cosmology, lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))] + name="multiplane", + cosmology=cosmology, + lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))], ) thx, thy = get_meshgrid(0.01, 10, 10) - + # Parameters z_s = torch.tensor(1.2) x = torch.tensor(xs).flatten() # Evaluate Jacobian J_autograd = lens.jacobian_lens_equation(thx, thy, z_s, lens.pack(x)) - J_finitediff = lens.jacobian_lens_equation(thx, thy, z_s, lens.pack(x), method = "finitediff", pixelscale = torch.tensor(0.01)) - - assert torch.sum(((J_autograd - J_finitediff)/J_autograd).abs() < 1e-2) > 0.5 * J_autograd.numel() + J_finitediff = lens.jacobian_lens_equation( + thx, thy, z_s, lens.pack(x), method="finitediff", pixelscale=torch.tensor(0.01) + ) + + assert ( + torch.sum(((J_autograd - J_finitediff) / J_autograd).abs() < 1e-2) + > 0.5 * J_autograd.numel() + ) + - def test_multiplane_effective_convergence(): # Setup z_s = torch.tensor(1.5, dtype=torch.float32) @@ -96,7 +109,9 @@ def test_multiplane_effective_convergence(): x = torch.tensor([p for _xs in xs for p in _xs], dtype=torch.float32) lens = Multiplane( - name="multiplane", cosmology=cosmology, lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))] + name="multiplane", + cosmology=cosmology, + lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))], ) thx, thy = get_meshgrid(0.1, 10, 10) @@ -104,9 +119,10 @@ def test_multiplane_effective_convergence(): z_s = torch.tensor(1.2) x = torch.tensor(xs).flatten() C = lens.effective_convergence_div(thx, thy, z_s, lens.pack(x)) - assert C.shape == (10,10) + assert C.shape == (10, 10) curl = lens.effective_convergence_curl(thx, thy, z_s, lens.pack(x)) - assert curl.shape == (10,10) + assert curl.shape == (10, 10) + if __name__ == "__main__": test() diff --git a/test/test_kappa_grid.py b/tests/test_kappa_grid.py similarity index 88% rename from test/test_kappa_grid.py rename to tests/test_kappa_grid.py index be5b1d61..f1a2c4b2 100644 --- a/test/test_kappa_grid.py +++ b/tests/test_kappa_grid.py @@ -5,9 +5,9 @@ from caustics.utils import get_meshgrid -def _setup(n_pix, mode, use_next_fast_len, padding = "zero"): +def _setup(n_pix, mode, use_next_fast_len, padding="zero"): # TODO understand why this test fails for resolutions != 0.025 - res = 0.025 + res = 0.025 thx, thy = get_meshgrid(res, n_pix, n_pix) z_l = torch.tensor(0.5) @@ -25,16 +25,25 @@ def _setup(n_pix, mode, use_next_fast_len, padding = "zero"): rho_0 = torch.tensor(1.0) d_l = cosmology.angular_diameter_distance(z_l) - arcsec_to_rad = 1 / (180 / torch.pi * 60 ** 2) - - kappa_0 = lens_pj.central_convergence(z_l, z_s, rho_0, th_core * d_l * arcsec_to_rad, th_s * d_l * arcsec_to_rad, cosmology.critical_surface_density(z_l, z_s)) + arcsec_to_rad = 1 / (180 / torch.pi * 60**2) + + kappa_0 = lens_pj.central_convergence( + z_l, + z_s, + rho_0, + th_core * d_l * arcsec_to_rad, + th_s * d_l * arcsec_to_rad, + cosmology.critical_surface_density(z_l, z_s), + ) # z_l, thx0, thy0, kappa_0, th_core, th_s x_pj = torch.tensor([z_l, thx0, thy0, kappa_0, th_core, th_s]) # Exact calculations Psi = lens_pj.potential(thx, thy, z_l, lens_pj.pack(x_pj)) Psi -= Psi.min() - alpha_x, alpha_y = lens_pj.reduced_deflection_angle(thx, thy, z_l, lens_pj.pack(x_pj)) + alpha_x, alpha_y = lens_pj.reduced_deflection_angle( + thx, thy, z_l, lens_pj.pack(x_pj) + ) # Approximate calculations lens_kap = PixelatedConvergence( @@ -92,20 +101,27 @@ def test_consistency(): assert torch.allclose(alpha_x_fft, alpha_x_conv2d, atol=1e-20, rtol=0) assert torch.allclose(alpha_y_fft, alpha_y_conv2d, atol=1e-20, rtol=0) + def test_padoptions(): """ Checks whether using fft and conv2d give the same results. """ _, Psi_fft_circ, _, alpha_x_fft_circ, _, alpha_y_fft_circ = _setup( - 100, "fft", True, "circular", + 100, + "fft", + True, + "circular", ) _, Psi_fft_tile, _, alpha_x_fft_tile, _, alpha_y_fft_tile = _setup( - 100, "fft", True, "tile", + 100, + "fft", + True, + "tile", ) assert torch.allclose(Psi_fft_circ, Psi_fft_tile, atol=1e-20, rtol=0) assert torch.allclose(alpha_x_fft_circ, alpha_x_fft_tile, atol=1e-20, rtol=0) assert torch.allclose(alpha_y_fft_circ, alpha_y_fft_tile, atol=1e-20, rtol=0) - + def _check_center( x, x_approx, center_c, center_r, rtol=1e-5, atol=1e-8, half_buffer=20 @@ -128,4 +144,3 @@ def _check_center( rtol, atol, ) - diff --git a/test/test_masssheet.py b/tests/test_masssheet.py similarity index 56% rename from test/test_masssheet.py rename to tests/test_masssheet.py index 539cbf9e..d6167b33 100644 --- a/test/test_masssheet.py +++ b/tests/test_masssheet.py @@ -1,9 +1,4 @@ -from math import pi - -import lenstronomy.Util.param_util as param_util import torch -from lenstronomy.LensModel.lens_model import LensModel -from utils import lens_test_helper from caustics.cosmology import FlatLambdaCDM from caustics.lenses import MassSheet @@ -11,21 +6,18 @@ def test(): - atol = 1e-5 - rtol = 1e-5 - # Models cosmology = FlatLambdaCDM(name="cosmo") lens = MassSheet(name="sheet", cosmology=cosmology) # Parameters z_s = torch.tensor(1.2) - x = torch.tensor([0.5, 0., 0., 0.7]) + x = torch.tensor([0.5, 0.0, 0.0, 0.7]) thx, thy = get_meshgrid(0.01, 10, 10) - + ax, ay = lens.reduced_deflection_angle(thx, thy, z_s, *x) - p = lens.potential(thx, thy, z_s, *x) + lens.potential(thx, thy, z_s, *x) - c = lens.convergence(thx, thy, z_s, *x) + lens.convergence(thx, thy, z_s, *x) diff --git a/test/test_multiplane.py b/tests/test_multiplane.py similarity index 81% rename from test/test_multiplane.py rename to tests/test_multiplane.py index f00d4a2a..ce015697 100644 --- a/test/test_multiplane.py +++ b/tests/test_multiplane.py @@ -1,5 +1,4 @@ from math import pi -from operator import mul import lenstronomy.Util.param_util as param_util import torch @@ -12,6 +11,7 @@ from caustics.lenses import SIE, Multiplane, PixelatedConvergence from caustics.utils import get_meshgrid + def test(): rtol = 0 atol = 5e-3 @@ -30,9 +30,11 @@ def test(): x = torch.tensor([p for _xs in xs for p in _xs], dtype=torch.float32) lens = Multiplane( - name="multiplane", cosmology=cosmology, lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))] + name="multiplane", + cosmology=cosmology, + lenses=[SIE(name=f"sie_{i}", cosmology=cosmology) for i in range(len(xs))], ) - #lens.effective_reduced_deflection_angle = lens.raytrace + # lens.effective_reduced_deflection_angle = lens.raytrace # lenstronomy kwargs_ls = [] @@ -73,40 +75,46 @@ def test_params(): planes = [] for p in range(n_planes): lens = PixelatedConvergence( - name=f"plane_{p}", - pixelscale=pixel_size, - n_pix=pixels, - cosmology=cosmology, - z_l=z[p], - x0=0., - y0=0., - shape=(pixels, pixels), - padding="tile" - ) - planes.append(lens) + name=f"plane_{p}", + pixelscale=pixel_size, + n_pix=pixels, + cosmology=cosmology, + z_l=z[p], + x0=0.0, + y0=0.0, + shape=(pixels, pixels), + padding="tile", + ) + planes.append(lens) multiplane_lens = Multiplane(cosmology=cosmology, lenses=planes) z_s = torch.tensor(z_s) x, y = get_meshgrid(pixel_size, 32, 32) params = [torch.randn(pixels, pixels) for i in range(10)] # Test out the computation of a few quantities to make sure params are passed correctly - + # First case, params as list of tensors kappa_eff = multiplane_lens.effective_convergence_div(x, y, z_s, params) assert kappa_eff.shape == torch.Size([32, 32]) - alphax, alphay = multiplane_lens.effective_reduced_deflection_angle(x, y, z_s, params) + alphax, alphay = multiplane_lens.effective_reduced_deflection_angle( + x, y, z_s, params + ) - # Second case, params given as a kwargs + # Second case, params given as a kwargs kappa_eff = multiplane_lens.effective_convergence_div(x, y, z_s, params=params) assert kappa_eff.shape == torch.Size([32, 32]) - alphax, alphay = multiplane_lens.effective_reduced_deflection_angle(x, y, z_s, params=params) + alphax, alphay = multiplane_lens.effective_reduced_deflection_angle( + x, y, z_s, params=params + ) # Test that we can pass a dictionary params = {f"plane_{p}": torch.randn(pixels, pixels) for p in range(n_planes)} kappa_eff = multiplane_lens.effective_convergence_div(x, y, z_s, params) assert kappa_eff.shape == torch.Size([32, 32]) - alphax, alphay = multiplane_lens.effective_reduced_deflection_angle(x, y, z_s, params) + alphax, alphay = multiplane_lens.effective_reduced_deflection_angle( + x, y, z_s, params + ) + - if __name__ == "__main__": test() diff --git a/test/test_namespace_dict.py b/tests/test_namespace_dict.py similarity index 99% rename from test/test_namespace_dict.py rename to tests/test_namespace_dict.py index c5854e74..0ce3075e 100644 --- a/test/test_namespace_dict.py +++ b/tests/test_namespace_dict.py @@ -78,7 +78,6 @@ def test_nested_namespace_dict_collapse_shared_node(): nested_namespace["bar.qux.corge"] = 98 - def test_nested_namespace_errors(): nested_namespace = NestedNamespaceDict() with raises(AttributeError): @@ -95,4 +94,3 @@ def test_nested_namespace_errors(): with raises(KeyError): nested_namespace.foo = {"bar": 42} print(nested_namespace["qux.baz"]) - diff --git a/test/test_nfw.py b/tests/test_nfw.py similarity index 98% rename from test/test_nfw.py rename to tests/test_nfw.py index 9b2c0ea1..b2a8093a 100644 --- a/test/test_nfw.py +++ b/tests/test_nfw.py @@ -39,7 +39,7 @@ def test(): m = 1e12 c = 8.0 x = torch.tensor([thx0, thy0, m, c]) - + # Lenstronomy cosmo = FlatLambdaCDM_AP(H0=h0_default * 100, Om0=Om0_default, Ob0=Ob0_default) lens_cosmo = LensCosmo(z_lens=z_l.item(), z_source=z_s.item(), cosmo=cosmo) @@ -52,11 +52,12 @@ def test(): lens_test_helper(lens, lens_ls, z_s, x, kwargs_ls, atol, rtol) + def test_runs(): cosmology = CausticFlatLambdaCDM(name="cosmo") z_l = torch.tensor(0.1) - lens = NFW(name="nfw", cosmology=cosmology, z_l=z_l, use_case = "differentiable") - + lens = NFW(name="nfw", cosmology=cosmology, z_l=z_l, use_case="differentiable") + # Parameters z_s = torch.tensor(0.5) @@ -65,9 +66,9 @@ def test_runs(): m = 1e12 rs = 8.0 x = torch.tensor([thx0, thy0, m, rs]) - + thx, thy, thx_ls, thy_ls = setup_grids() - + Psi = lens.potential(thx, thy, z_s, lens.pack(x)) assert torch.all(torch.isfinite(Psi)) alpha = lens.reduced_deflection_angle(thx, thy, z_s, lens.pack(x)) @@ -75,7 +76,7 @@ def test_runs(): assert torch.all(torch.isfinite(alpha[1])) kappa = lens.convergence(thx, thy, z_s, lens.pack(x)) assert torch.all(torch.isfinite(kappa)) - + if __name__ == "__main__": test() diff --git a/test/test_parameter.py b/tests/test_parameter.py similarity index 53% rename from test/test_parameter.py rename to tests/test_parameter.py index c879988b..5c6f90f1 100644 --- a/test/test_parameter.py +++ b/tests/test_parameter.py @@ -1,33 +1,31 @@ -import torch -from caustics.lenses import EPL, PixelatedConvergence -from caustics.sims import Simulator +from caustics.lenses import PixelatedConvergence from caustics.cosmology import FlatLambdaCDM -from caustics.light import Pixelated import pytest # For future PR currently this test fails # def test_static_parameter_init(): - # module = EPL(FlatLambdaCDM(h0=0.7, Om0=0.3)) - # print(module.params) - # module.to(dtype=torch.float16) - # assert module.params.static.FlatLambdaCDM.h0.value.dtype == torch.float16 +# module = EPL(FlatLambdaCDM(h0=0.7, Om0=0.3)) +# print(module.params) +# module.to(dtype=torch.float16) +# assert module.params.static.FlatLambdaCDM.h0.value.dtype == torch.float16 + def test_shape_error_messages(): # with pytest.raises(TypeError): - # # User cannot enter a list, only a tuple for type checking and consistency with torch - # module = Pixelated(shape=[8, 8]) - + # # User cannot enter a list, only a tuple for type checking and consistency with torch + # module = Pixelated(shape=[8, 8]) + # with pytest.raises(ValueError): - # module = Pixelated(shape=(8,)) - + # module = Pixelated(shape=(8,)) + fov = 7.8 n_pix = 20 cosmo = FlatLambdaCDM() with pytest.raises(TypeError): # User cannot enter a list, only a tuple (because of type checking and consistency with torch) PixelatedConvergence(fov, n_pix, cosmo, shape=[8, 8]) - - with pytest.raises(ValueError): + + with pytest.raises(ValueError): # wrong number of dimensions PixelatedConvergence(fov, n_pix, cosmo, shape=(8,)) @@ -35,6 +33,9 @@ def test_shape_error_messages(): def test_repr(): cosmo = FlatLambdaCDM() print(cosmo.h0) - assert cosmo.h0.__repr__() == f"Param(value={cosmo.h0.value}, dtype={str(cosmo.h0.dtype)})" + assert ( + cosmo.h0.__repr__() + == f"Param(value={cosmo.h0.value}, dtype={str(cosmo.h0.dtype)})" + ) cosmo = FlatLambdaCDM(h0=None) assert cosmo.h0.__repr__() == f"Param(shape={cosmo.h0.shape})" diff --git a/test/test_parametrized.py b/tests/test_parametrized.py similarity index 81% rename from test/test_parametrized.py rename to tests/test_parametrized.py index fa2592a0..660321c3 100644 --- a/test/test_parametrized.py +++ b/tests/test_parametrized.py @@ -1,5 +1,4 @@ import torch -from torch import vmap import pytest import numpy as np from caustics.sims import Simulator @@ -19,15 +18,15 @@ def __init__(self): self.epl = EPL(self.cosmo) self.sersic = Sersic() self.add_param("z_s", 1.0) - + sim = Sim() - assert len(sim.module_params) == 2 # dynamic and static - assert len(sim.module_params.dynamic) == 0 # simulator has no dynmaic params - assert len(sim.module_params.static) == 1 # and 1 static param (z_s) - assert len(sim.params) == 2 # dynamic and static - assert len(sim.params.dynamic) == 3 # cosmo, epl and sersic - assert len(sim.params.static) == 2 # simulator and cosmo have static params - assert len(sim.params.dynamic.flatten()) == 15 # total number of params + assert len(sim.module_params) == 2 # dynamic and static + assert len(sim.module_params.dynamic) == 0 # simulator has no dynmaic params + assert len(sim.module_params.static) == 1 # and 1 static param (z_s) + assert len(sim.params) == 2 # dynamic and static + assert len(sim.params.dynamic) == 3 # cosmo, epl and sersic + assert len(sim.params.static) == 2 # simulator and cosmo have static params + assert len(sim.params.dynamic.flatten()) == 15 # total number of params def test_graph(): @@ -39,43 +38,53 @@ def test_graph(): def test_unpack_all_modules_dynamic(): sim, (sim_params, cosmo_params, lens_params, source_params) = setup_simulator() n_pix = sim.n_pix - + # test list input x = sim_params + cosmo_params + lens_params + source_params assert sim(x).shape == torch.Size([n_pix, n_pix]) - + # test tensor input x_tensor = torch.stack(x) assert sim(x_tensor).shape == torch.Size([n_pix, n_pix]) - + # Test dictionary input: Only module with dynamic parameters are required - x_dict = {"simulator": sim_params, "cosmo": cosmo_params, "source": source_params, "lens": lens_params} - + x_dict = { + "simulator": sim_params, + "cosmo": cosmo_params, + "source": source_params, + "lens": lens_params, + } + assert sim(x_dict).shape == torch.Size([n_pix, n_pix]) - + # Test semantic list (one tensor per module) - x_semantic = [torch.stack(module) for module in [sim_params, cosmo_params, lens_params, source_params]] + x_semantic = [ + torch.stack(module) + for module in [sim_params, cosmo_params, lens_params, source_params] + ] assert sim(x_semantic).shape == torch.Size([n_pix, n_pix]) def test_unpack_some_modules_static(): # same test as above but cosmo is completely static so not fed in the forward method - sim, (_, _, lens_params, source_params) = setup_simulator(cosmo_static=True, simulator_static=True) + sim, (_, _, lens_params, source_params) = setup_simulator( + cosmo_static=True, simulator_static=True + ) n_pix = sim.n_pix - + # test list input x = lens_params + source_params assert sim(x).shape == torch.Size([n_pix, n_pix]) - + # test tensor input x_tensor = torch.stack(x) assert sim(x_tensor).shape == torch.Size([n_pix, n_pix]) - + # Test dictionary input: Only module with dynamic parameters are required x_dict = {"source": source_params, "lens": lens_params} - + assert sim(x_dict).shape == torch.Size([n_pix, n_pix]) - + # Test semantic list (one tensor per module) x_semantic = [torch.stack(module) for module in [lens_params, source_params]] assert sim(x_semantic).shape == torch.Size([n_pix, n_pix]) @@ -97,7 +106,7 @@ def __init__(self): self.cosmo = FlatLambdaCDM() self.lens = EPL(self.cosmo, name="lens") self.source = Sersic(name="source") - + sim = Sim() assert sim.name == "Sim" sim.name = "Test" @@ -114,12 +123,13 @@ def test_parametrized_name_setter_bad_names(): # Make sure bad names are catched by our added method. Bad names are name which cannot be used as class attributes. good_names = ["variable", "_variable", "var_iable2"] for name in good_names: - module = Sersic(name=name) + Sersic(name=name) bad_names = ["for", "2variable", "variable!", "var-iable", "var iable", "def"] for name in bad_names: print(name) with pytest.raises(NameError): - module = Sersic(name=name) + Sersic(name=name) + def test_parametrized_name_collision(): # Case 1: Name collision in children of simulator @@ -130,7 +140,7 @@ def __init__(self): # These two module are identical and will create a name collision self.lens1 = EPL(self.cosmo) self.lens2 = EPL(self.cosmo) - + sim = Sim() # Current way names are updated. Could be chnaged so that all params in collision # Get a number @@ -140,11 +150,12 @@ def __init__(self): # Case 2: name collision in parents of a module cosmo = FlatLambdaCDM(h0=None) lens = EPL(cosmo) + class Sim(Simulator): def __init__(self): super().__init__() self.lens = lens - + sim1 = Sim() sim2 = Sim() assert sim1.name == "Sim" @@ -153,13 +164,13 @@ def __init__(self): assert "Sim" in lens._parents.keys() - - # TODO make the params attribute -> parameters and make it more intuitive def test_to_method(): - sim, (sim_params, cosmo_params, lens_params, source_params) = setup_simulator(batched_params=True) - - # Check that static params have correct type + sim, (sim_params, cosmo_params, lens_params, source_params) = setup_simulator( + batched_params=True + ) + + # Check that static params have correct type module = Sersic(x0=0.5) assert module.x0.dtype == torch.float32 @@ -167,18 +178,20 @@ def test_to_method(): assert module.x0.dtype == torch.float32 module = Sersic(x0=np.array(0.5)) - assert module.x0.dtype == torch.float64 # Decided against default type, so gets numpy type here - + assert ( + module.x0.dtype == torch.float64 + ) # Decided against default type, so gets numpy type here + # Check that all parameters are converted to correct type sim.to(dtype=torch.float16) - assert sim.z_s.dtype is None # dynamic parameter + assert sim.z_s.dtype is None # dynamic parameter assert sim.lens.cosmo.Om0.dtype == torch.float16 assert sim.cosmo.Om0.dtype == torch.float16 - + def test_parameter_redefinition(): sim, _ = setup_simulator() - + # Make sure the __getattribute__ method works as intended assert isinstance(sim.z_s, Parameter) # Now test __setattr__ method, we need to catch the redefinition here and keep z_s a parameter @@ -189,4 +202,3 @@ def test_parameter_redefinition(): sim.z_s = None assert sim.z_s.value is None assert sim.z_s.dynamic is True - diff --git a/test/test_pixel_grid.py b/tests/test_pixel_grid.py similarity index 100% rename from test/test_pixel_grid.py rename to tests/test_pixel_grid.py diff --git a/test/test_point.py b/tests/test_point.py similarity index 100% rename from test/test_point.py rename to tests/test_point.py diff --git a/test/test_pseudo_jaffe.py b/tests/test_pseudo_jaffe.py similarity index 61% rename from test/test_pseudo_jaffe.py rename to tests/test_pseudo_jaffe.py index 89c64f95..1cf408dd 100644 --- a/test/test_pseudo_jaffe.py +++ b/tests/test_pseudo_jaffe.py @@ -1,5 +1,3 @@ -from collections import defaultdict - import torch from lenstronomy.LensModel.lens_model import LensModel from utils import lens_test_helper @@ -22,11 +20,24 @@ def test(): z_s = torch.tensor(2.1) x = torch.tensor([0.5, 0.071, 0.023, -1e100, 0.5, 1.5]) d_l = cosmology.angular_diameter_distance(x[0]) - arcsec_to_rad = 1 / (180 / torch.pi * 60 ** 2) + arcsec_to_rad = 1 / (180 / torch.pi * 60**2) kappa_0 = lens.central_convergence( - x[0], z_s, torch.tensor(2e11), x[4] * d_l * arcsec_to_rad, x[5] * d_l * arcsec_to_rad, cosmology.critical_surface_density(x[0], z_s) + x[0], + z_s, + torch.tensor(2e11), + x[4] * d_l * arcsec_to_rad, + x[5] * d_l * arcsec_to_rad, + cosmology.critical_surface_density(x[0], z_s), + ) + x[3] = ( + 2 + * torch.pi + * kappa_0 + * cosmology.critical_surface_density(x[0], z_s) + * x[4] + * x[5] + * (d_l * arcsec_to_rad) ** 2 ) - x[3] = 2 * torch.pi * kappa_0 * cosmology.critical_surface_density(x[0], z_s) * x[4] * x[5] * (d_l * arcsec_to_rad)**2 kwargs_ls = [ { "sigma0": kappa_0.item(), @@ -39,22 +50,36 @@ def test(): lens_test_helper(lens, lens_ls, z_s, x, kwargs_ls, rtol, atol) + def test_massenclosed(): cosmology = FlatLambdaCDM(name="cosmo") lens = PseudoJaffe(name="pj", cosmology=cosmology) z_s = torch.tensor(2.1) x = torch.tensor([0.5, 0.071, 0.023, -1e100, 0.5, 1.5]) d_l = cosmology.angular_diameter_distance(x[0]) - arcsec_to_rad = 1 / (180 / torch.pi * 60 ** 2) + arcsec_to_rad = 1 / (180 / torch.pi * 60**2) kappa_0 = lens.central_convergence( - x[0], z_s, torch.tensor(2e11), x[4] * d_l * arcsec_to_rad, x[5] * d_l * arcsec_to_rad, cosmology.critical_surface_density(x[0], z_s) + x[0], + z_s, + torch.tensor(2e11), + x[4] * d_l * arcsec_to_rad, + x[5] * d_l * arcsec_to_rad, + cosmology.critical_surface_density(x[0], z_s), ) - x[3] = 2 * torch.pi * kappa_0 * cosmology.critical_surface_density(x[0], z_s) * x[4] * x[5] * (d_l * arcsec_to_rad)**2 - xx = torch.linspace(0,10,10) + x[3] = ( + 2 + * torch.pi + * kappa_0 + * cosmology.critical_surface_density(x[0], z_s) + * x[4] + * x[5] + * (d_l * arcsec_to_rad) ** 2 + ) + xx = torch.linspace(0, 10, 10) masses = lens.mass_enclosed_2d(xx, z_s, lens.pack(x)) assert torch.all(masses < x[3]) - + if __name__ == "__main__": test() diff --git a/test/test_sersic.py b/tests/test_sersic.py similarity index 81% rename from test/test_sersic.py rename to tests/test_sersic.py index cecdfe67..e4c5e9a8 100644 --- a/test/test_sersic.py +++ b/tests/test_sersic.py @@ -1,5 +1,3 @@ -from math import pi - import lenstronomy.Util.param_util as param_util import numpy as np import torch @@ -35,7 +33,7 @@ def test(): # Parameters thx0_src = 0.05 thy0_src = 0.01 - phi_src = 0. + phi_src = 0.0 q_src = 0.5 index_src = 1.5 th_e_src = 0.1 @@ -44,7 +42,17 @@ def test(): # the definition used by lenstronomy. This only works when phi = 0. # any other angle will not give the same results between the # two codes. - x = torch.tensor([thx0_src * np.sqrt(q_src), thy0_src, np.sqrt(q_src), phi_src, index_src, th_e_src, I_e_src]) + x = torch.tensor( + [ + thx0_src * np.sqrt(q_src), + thy0_src, + np.sqrt(q_src), + phi_src, + index_src, + th_e_src, + I_e_src, + ] + ) e1, e2 = param_util.phi_q2_ellipticity(phi=phi_src, q=q_src) kwargs_light_source = [ { @@ -58,11 +66,9 @@ def test(): } ] - brightness = sersic.brightness(thx*np.sqrt(q_src), thy, sersic.pack(x)) + brightness = sersic.brightness(thx * np.sqrt(q_src), thy, sersic.pack(x)) x_ls, y_ls = pixel_grid.coordinate_grid(nx, ny) - brightness_ls = sersic_ls.surface_brightness( - x_ls, y_ls, kwargs_light_source - ) + brightness_ls = sersic_ls.surface_brightness(x_ls, y_ls, kwargs_light_source) assert np.allclose(brightness.numpy(), brightness_ls) diff --git a/test/test_sie.py b/tests/test_sie.py similarity index 95% rename from test/test_sie.py rename to tests/test_sie.py index c9827bb3..51860001 100644 --- a/test/test_sie.py +++ b/tests/test_sie.py @@ -7,7 +7,6 @@ from caustics.cosmology import FlatLambdaCDM from caustics.lenses import SIE -from caustics.utils import get_meshgrid def test(): @@ -36,6 +35,6 @@ def test(): lens_test_helper(lens, lens_ls, z_s, x, kwargs_ls, rtol, atol) - + if __name__ == "__main__": test() diff --git a/tests/test_simulator.py b/tests/test_simulator.py new file mode 100644 index 00000000..8fbf5720 --- /dev/null +++ b/tests/test_simulator.py @@ -0,0 +1,89 @@ +from math import pi + +import torch + +from caustics.sims import Lens_Source +from caustics.cosmology import FlatLambdaCDM +from caustics.lenses import SIE +from caustics.light import Sersic +from caustics.utils import gaussian + + +def test_simulator_runs(): + # Model + cosmology = FlatLambdaCDM(name="cosmo") + lensmass = SIE( + name="lens", + cosmology=cosmology, + z_l=1.0, + x0=0.0, + y0=0.01, + q=0.5, + phi=pi / 3.0, + b=1.0, + ) + + source = Sersic( + name="source", x0=0.01, y0=-0.03, q=0.6, phi=-pi / 4, n=2.0, Re=0.5, Ie=1.0 + ) + lenslight = Sersic( + name="lenslight", x0=0.0, y0=0.01, q=0.7, phi=pi / 4, n=3.0, Re=0.7, Ie=1.0 + ) + + psf = gaussian(0.05, 11, 11, 0.2, upsample=2) + + sim = Lens_Source( + lens=lensmass, + source=source, + pixelscale=0.05, + pixels_x=50, + lens_light=lenslight, + psf=psf, + z_s=2.0, + ) + + assert torch.all(torch.isfinite(sim())) + assert torch.all( + torch.isfinite( + sim( + {}, + source_light=True, + lens_light=True, + lens_source=True, + psf_convolve=False, + ) + ) + ) + assert torch.all( + torch.isfinite( + sim( + {}, + source_light=True, + lens_light=True, + lens_source=False, + psf_convolve=True, + ) + ) + ) + assert torch.all( + torch.isfinite( + sim( + {}, + source_light=True, + lens_light=False, + lens_source=True, + psf_convolve=True, + ) + ) + ) + assert torch.all( + torch.isfinite( + sim( + {}, + source_light=False, + lens_light=True, + lens_source=True, + psf_convolve=True, + ) + ) + ) diff --git a/test/test_sis.py b/tests/test_sis.py similarity index 100% rename from test/test_sis.py rename to tests/test_sis.py diff --git a/test/test_tnfw.py b/tests/test_tnfw.py similarity index 82% rename from test/test_tnfw.py rename to tests/test_tnfw.py index c721aafa..bfb5138f 100644 --- a/test/test_tnfw.py +++ b/tests/test_tnfw.py @@ -25,7 +25,7 @@ def test(): # Models cosmology = CausticFlatLambdaCDM(name="cosmo") z_l = torch.tensor(0.1) - lens = TNFW(name="tnfw", cosmology=cosmology, z_l=z_l, interpret_m_total_mass = False) + lens = TNFW(name="tnfw", cosmology=cosmology, z_l=z_l, interpret_m_total_mass=False) lens_model_list = ["TNFW"] lens_ls = LensModel(lens_model_list=lens_model_list) @@ -40,7 +40,7 @@ def test(): c = 8.0 t = 3.0 x = torch.tensor([thx0, thy0, m, c, t]) - + # Lenstronomy cosmo = FlatLambdaCDM_AP(H0=h0_default * 100, Om0=Om0_default, Ob0=Ob0_default) lens_cosmo = LensCosmo(z_lens=z_l.item(), z_source=z_s.item(), cosmo=cosmo) @@ -49,16 +49,34 @@ def test(): # lenstronomy params ['Rs', 'alpha_Rs', 'center_x', 'center_y'] kwargs_ls = [ - {"Rs": Rs_angle, "alpha_Rs": alpha_Rs, "r_trunc": Rs_angle * t, "center_x": thx0, "center_y": thy0} + { + "Rs": Rs_angle, + "alpha_Rs": alpha_Rs, + "r_trunc": Rs_angle * t, + "center_x": thx0, + "center_y": thy0, + } ] - lens_test_helper(lens, lens_ls, z_s, x, kwargs_ls, atol, rtol, test_alpha = True, test_Psi = False, test_kappa = True) + lens_test_helper( + lens, + lens_ls, + z_s, + x, + kwargs_ls, + atol, + rtol, + test_alpha=True, + test_Psi=False, + test_kappa=True, + ) + def test_runs(): cosmology = CausticFlatLambdaCDM(name="cosmo") z_l = torch.tensor(0.1) - lens = TNFW(name="tnfw", cosmology=cosmology, z_l=z_l, use_case = "differentiable") - + lens = TNFW(name="tnfw", cosmology=cosmology, z_l=z_l, use_case="differentiable") + # Parameters z_s = torch.tensor(0.5) @@ -68,9 +86,9 @@ def test_runs(): rs = 8.0 t = 3.0 x = torch.tensor([thx0, thy0, m, rs, t]) - + thx, thy, thx_ls, thy_ls = setup_grids() - + Psi = lens.potential(thx, thy, z_s, lens.pack(x)) assert torch.all(torch.isfinite(Psi)) diff --git a/test/utils.py b/tests/utils.py similarity index 82% rename from test/utils.py rename to tests/utils.py index 72a9ef68..6ab48e0b 100644 --- a/test/utils.py +++ b/tests/utils.py @@ -12,8 +12,12 @@ from caustics.sims import Simulator from caustics.cosmology import FlatLambdaCDM -def setup_simulator(cosmo_static=False, use_nfw=True, simulator_static=False, batched_params=False): + +def setup_simulator( + cosmo_static=False, use_nfw=True, simulator_static=False, batched_params=False +): n_pix = 20 + class Sim(Simulator): def __init__(self, name="simulator"): super().__init__(name) @@ -24,7 +28,9 @@ def __init__(self, name="simulator"): z_l = 0.5 self.cosmo = FlatLambdaCDM(h0=0.7 if cosmo_static else None, name="cosmo") if use_nfw: - self.lens = NFW(self.cosmo, z_l=z_l, name="lens") # NFW wactually depend on cosmology, so a better test for Parametrized + self.lens = NFW( + self.cosmo, z_l=z_l, name="lens" + ) # NFW wactually depend on cosmology, so a better test for Parametrized else: self.lens = EPL(self.cosmo, z_l=z_l, name="lens") self.sersic = Sersic(name="source") @@ -32,8 +38,10 @@ def __init__(self, name="simulator"): self.n_pix = n_pix def forward(self, params): - z_s, = self.unpack(params) - alphax, alphay = self.lens.reduced_deflection_angle(x=self.thx, y=self.thy, z_s=z_s, params=params) + (z_s,) = self.unpack(params) + alphax, alphay = self.lens.reduced_deflection_angle( + x=self.thx, y=self.thy, z_s=z_s, params=params + ) bx = self.thx - alphax by = self.thy - alphay return self.sersic.brightness(bx, by, params) @@ -44,10 +52,10 @@ def forward(self, params): # default cosmo params h0 = torch.tensor([0.68, 0.75]) cosmo_params = [h0] - # default lens params + # default lens params if use_nfw: - x0 = torch.tensor([0., 0.1]) - y0 = torch.tensor([0., 0.1]) + x0 = torch.tensor([0.0, 0.1]) + y0 = torch.tensor([0.0, 0.1]) m = torch.tensor([1e12, 1e13]) c = torch.tensor([10, 5]) lens_params = [x0, y0, m, c] @@ -59,16 +67,16 @@ def forward(self, params): b = torch.tensor([1.5, 1.2]) t = torch.tensor([1.2, 1.0]) lens_params = [x0, y0, q, phi, b, t] - # default source params + # default source params x0s = torch.tensor([0, 0.1]) y0s = torch.tensor([0, 0.1]) qs = torch.tensor([0.9, 0.8]) phis = torch.tensor([-0.56, 0.8]) - n = torch.tensor([1., 4.]) - Re = torch.tensor([.2, .5]) - Ie = torch.tensor([1.2, 10.]) + n = torch.tensor([1.0, 4.0]) + Re = torch.tensor([0.2, 0.5]) + Ie = torch.tensor([1.2, 10.0]) source_params = [x0s, y0s, qs, phis, n, Re, Ie] - + if not batched_params: sim_params = [_x[0] for _x in sim_params] cosmo_params = [_x[0] for _x in cosmo_params] @@ -79,6 +87,7 @@ def forward(self, params): def setup_image_simulator(cosmo_static=False, batched_params=False): n_pix = 20 + class Sim(Simulator): def __init__(self, name="test"): super().__init__(name) @@ -87,21 +96,38 @@ def __init__(self, name="test"): self.z_s = torch.tensor(1.0) self.cosmo = FlatLambdaCDM(h0=0.7 if cosmo_static else None, name="cosmo") self.epl = EPL(self.cosmo, z_l=z_l, name="lens") - self.kappa = PixelatedConvergence(pixel_scale, n_pix, self.cosmo, z_l=z_l, shape=(n_pix, n_pix), name="kappa") - self.source = Pixelated(x0=0., y0=0., pixelscale=pixel_scale/2, shape=(n_pix, n_pix), name="source") + self.kappa = PixelatedConvergence( + pixel_scale, + n_pix, + self.cosmo, + z_l=z_l, + shape=(n_pix, n_pix), + name="kappa", + ) + self.source = Pixelated( + x0=0.0, + y0=0.0, + pixelscale=pixel_scale / 2, + shape=(n_pix, n_pix), + name="source", + ) self.thx, self.thy = get_meshgrid(pixel_scale, n_pix, n_pix) self.n_pix = n_pix def forward(self, params): - alphax, alphay = self.epl.reduced_deflection_angle(x=self.thx, y=self.thy, z_s=self.z_s, params=params) - alphax_h, alphay_h = self.kappa.reduced_deflection_angle(x=self.thx, y=self.thy, z_s=self.z_s, params=params) + alphax, alphay = self.epl.reduced_deflection_angle( + x=self.thx, y=self.thy, z_s=self.z_s, params=params + ) + alphax_h, alphay_h = self.kappa.reduced_deflection_angle( + x=self.thx, y=self.thy, z_s=self.z_s, params=params + ) bx = self.thx - alphax - alphax_h by = self.thy - alphay - alphay_h return self.source.brightness(bx, by, params) # default cosmo params h0 = torch.tensor([0.68, 0.75]) - # default lens params + # default lens params x0 = torch.tensor([0, 0.1]) y0 = torch.tensor([0, 0.1]) q = torch.tensor([0.9, 0.8]) From e60390b3f3c3851d247aeffaa479d41862be3d74 Mon Sep 17 00:00:00 2001 From: Don Setiawan Date: Mon, 27 Nov 2023 14:31:09 -0800 Subject: [PATCH 23/55] build: remove dir sources (#27) --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6b6e41e7..4fdc6476 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,9 +50,6 @@ dev = [ [tool.hatch.metadata.hooks.requirements_txt] files = ["requirements.txt"] -[tool.hatch.build] -sources = ["src"] - [tool.hatch.version] source = "vcs" From de64cb39f995fe5c1a1e0f6490ab4404b5f667ec Mon Sep 17 00:00:00 2001 From: Landung 'Don' Setiawan Date: Mon, 27 Nov 2023 14:41:06 -0800 Subject: [PATCH 24/55] fix: Add project urls to empty slots --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4fdc6476..b3216a9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,8 @@ classifiers=[ [project.urls] Homepage = "https://mila.quebec/en/" -Documentation = "" -Repository = "https://github.com/Ciela-Institute/caustics.git" -Changelog = "" +Documentation = "https://caustics.readthedocs.io/en/latest/" +Repository = "https://github.com/Ciela-Institute/caustics" Issues = "https://github.com/Ciela-Institute/caustics/issues" [project.optional-dependencies] From a230dd76eecb989dff35c17eb535b055223e4d44 Mon Sep 17 00:00:00 2001 From: Don Setiawan Date: Mon, 27 Nov 2023 15:36:26 -0800 Subject: [PATCH 25/55] ci: update yaml configuration for CI (#28) * ci: update yaml configuration for CI * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/{python-app.yml => ci.yml} | 43 ++++++++------ .github/workflows/coverage.yaml | 62 -------------------- 2 files changed, 26 insertions(+), 79 deletions(-) rename .github/workflows/{python-app.yml => ci.yml} (63%) delete mode 100644 .github/workflows/coverage.yaml diff --git a/.github/workflows/python-app.yml b/.github/workflows/ci.yml similarity index 63% rename from .github/workflows/python-app.yml rename to .github/workflows/ci.yml index a5078b30..27441588 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,23 @@ # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python -name: Unit Tests +name: CI on: - push: - branches: - - main - - dev + workflow_dispatch: pull_request: + push: branches: - main - dev - workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: 3 + PROJECT_NAME: "caustics" jobs: build: @@ -33,6 +38,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true + - name: Record State run: | pwd @@ -46,20 +53,22 @@ jobs: run: | python -m pip install --upgrade pip pip install pytest pytest-cov torch wheel - # Install deps - cd $GITHUB_WORKSPACE - pip install -r requirements.txt - shell: bash + + # We only want to install this on one run, because otherwise we'll have + # duplicate annotations. + - name: Install error reporter + if: ${{ matrix.python-version == '3.10' }} + run: | + python -m pip install pytest-github-actions-annotate-failures - name: Install Caustics run: | - cd $GITHUB_WORKSPACE - pip install -e .[dev] - pip show caustics - shell: bash + pip install -e ".[dev]" + pip show ${{ env.PROJECT_NAME }} - name: Test with pytest run: | - cd $GITHUB_WORKSPACE - pytest -vvv tests/ - shell: bash + pytest -vvv --cov=${{ env.PROJECT_NAME }} --cov-report=xml --cov-report=term tests/ + + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v3 diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml deleted file mode 100644 index c568c12d..00000000 --- a/.github/workflows/coverage.yaml +++ /dev/null @@ -1,62 +0,0 @@ -name: Code Coverage - -on: - push: - branches: - - main - - dev - pull_request: - branches: - - main - - dev - workflow_dispatch: - -jobs: - coverage: - runs-on: ubuntu-latest - steps: - - name: Checkout caustics - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Python 3.10 - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Record State - run: | - pwd - echo github.ref is: ${{ github.ref }} - echo GITHUB_SHA is: $GITHUB_SHA - echo github.event_name is: ${{ github.event_name }} - echo github workspace: ${{ github.workspace }} - pip --version - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest pytest-cov torch wheel - # Install deps - cd $GITHUB_WORKSPACE - pip install -r requirements.txt - shell: bash - - - name: Install Caustics - run: | - cd $GITHUB_WORKSPACE - pip install -e .[dev] - pip show caustics - shell: bash - - name: Test with pytest - run: | - cd $GITHUB_WORKSPACE - pwd - pytest --cov-report=xml --cov=caustics tests/ - cat coverage.xml - shell: bash - - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v3 - with: - files: ${{ github.workspace }}/coverage.xml - fail_ci_if_error: false From 68d52484b7c9c810deb92a4ec558863ce7c189a1 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 13 Nov 2023 13:33:43 -0800 Subject: [PATCH 26/55] add get_start.ipynb (from basic introduction) --- docs/get_start.ipynb | 458 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 docs/get_start.ipynb diff --git a/docs/get_start.ipynb b/docs/get_start.ipynb new file mode 100644 index 00000000..0b4462c0 --- /dev/null +++ b/docs/get_start.ipynb @@ -0,0 +1,458 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Welcome to Caustic!\n", + "\n", + "In need of a differentiable strong gravitational lensing simulation package? Look no further! We have all your lensing simulator needs. In this tutorial we will cover the basics of caustic and how to get going making your own lensing configurations. Caustic is easy to use and very powerful, you will get to see some of that power here, but there will be more notebooks which demo specific use cases." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import torch\n", + "from torch.nn.functional import avg_pool2d\n", + "import matplotlib.pyplot as plt\n", + "from astropy.io import fits\n", + "import numpy as np\n", + "\n", + "import caustic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FlatLambdaCDM(\n", + " name='cosmo',\n", + " static=[h0, critical_density_0, Om0],\n", + " dynamic=[],\n", + " x keys=[]\n", + ")" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Specify the image/cosmology parameters\n", + "n_pix = 100\n", + "res = 0.05\n", + "upsample_factor = 2\n", + "fov = res * n_pix\n", + "thx, thy = caustic.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "z_l = torch.tensor(0.5, dtype=torch.float32)\n", + "z_s = torch.tensor(1.5, dtype=torch.float32)\n", + "cosmology = caustic.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology.to(dtype=torch.float32)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simulating an SIE lens\n", + "\n", + "Here we will demo the very basics of lensing with a classic `SIE` lens model. We will see what it takes to make an `SIE` model, lens a backgorund `Sersic` source, and sample some examples in a simulator. Caustic simulators can generalize to very complex scenarios. In these cases there can be a lot of parameters moving through the simulator, and the order/number of parameters may change depending on what lens or source is being used. To streamline this process, caustic impliments a class called `Parametrized` which has some knowledge of the parameters moving through it, this way it can keep track of everything for you. For this to work, you must put the parameters into a `Packed` object which it can recognize, each sub function can then unpack the parameters it needs. Below we will show some examples of what this looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# demo simulator with sersic source, SIE lens. then sample some examples. demo the model graph\n", + "\n", + "class Simple_Sim(caustic.Simulator):\n", + " def __init__(\n", + " self,\n", + " lens,\n", + " src,\n", + " z_s=None,\n", + " name: str = \"sim\",\n", + " ):\n", + " super().__init__(name) # need this so `Parametrized` can do its magic\n", + " \n", + " # These are the lens and source objects to keep track of\n", + " self.lens = lens\n", + " self.src = src\n", + " \n", + " # Here we can add a parameter to the simulator, in this case it is `z_s` which we will need later\n", + " self.add_param(\"z_s\", z_s)\n", + "\n", + " def forward(self, params):# define the forward model\n", + " # Here the simulator unpacks the parameter it needs\n", + " z_s = self.unpack(params)\n", + "\n", + " # Note this is very similar to before, except the packed up `x` is all the raytrace function needs to work\n", + " bx, by = self.lens.raytrace(thx, thy, z_s, params)\n", + " mu_fine = self.src.brightness(bx, by, params)\n", + " \n", + " # We return the sampled brightness at each pixel location\n", + " return avg_pool2d(mu_fine.squeeze()[None, None], upsample_factor)[0, 0]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "sim\n", + "\n", + "Simple_Sim('sim')\n", + "\n", + "\n", + "\n", + "sim/z_s\n", + "\n", + "z_s\n", + "\n", + "\n", + "\n", + "sim->sim/z_s\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie\n", + "\n", + "SIE('sie')\n", + "\n", + "\n", + "\n", + "sim->sie\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src\n", + "\n", + "Sersic('src')\n", + "\n", + "\n", + "\n", + "sim->src\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/z_l\n", + "\n", + "z_l\n", + "\n", + "\n", + "\n", + "sie->sie/z_l\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/x0\n", + "\n", + "x0\n", + "\n", + "\n", + "\n", + "sie->sie/x0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/y0\n", + "\n", + "y0\n", + "\n", + "\n", + "\n", + "sie->sie/y0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/q\n", + "\n", + "q\n", + "\n", + "\n", + "\n", + "sie->sie/q\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/phi\n", + "\n", + "phi\n", + "\n", + "\n", + "\n", + "sie->sie/phi\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sie/b\n", + "\n", + "b\n", + "\n", + "\n", + "\n", + "sie->sie/b\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/x0\n", + "\n", + "x0\n", + "\n", + "\n", + "\n", + "src->src/x0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/y0\n", + "\n", + "y0\n", + "\n", + "\n", + "\n", + "src->src/y0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/q\n", + "\n", + "q\n", + "\n", + "\n", + "\n", + "src->src/q\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/phi\n", + "\n", + "phi\n", + "\n", + "\n", + "\n", + "src->src/phi\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/n\n", + "\n", + "n\n", + "\n", + "\n", + "\n", + "src->src/n\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/Re\n", + "\n", + "Re\n", + "\n", + "\n", + "\n", + "src->src/Re\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "src/Ie\n", + "\n", + "Ie\n", + "\n", + "\n", + "\n", + "src->src/Ie\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sie = caustic.lenses.SIE(cosmology, name = \"sie\")\n", + "src = caustic.sources.Sersic(name = \"src\")\n", + "\n", + "sim = Simple_Sim(sie, src, torch.tensor(0.8))\n", + "\n", + "sim.get_graph(True, True)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simple_Sim(\n", + " name='sim',\n", + " static=[z_s],\n", + " dynamic=[],\n", + " x keys=[('sie': ['z_l', 'x0', 'y0', 'q', 'phi', 'b']), ('src': ['x0', 'y0', 'q', 'phi', 'n', 'Re', 'Ie'])]\n", + ")\n", + "SIE(\n", + " name='sie',\n", + " static=[],\n", + " dynamic=[z_l, x0, y0, q, phi, b],\n", + " x keys=[('sie': ['z_l', 'x0', 'y0', 'q', 'phi', 'b'])]\n", + ")\n" + ] + } + ], + "source": [ + "print(sim)\n", + "print(sie)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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dToiLsgo1QD4dncVExRIf+6XYxC7WnFBocAZV5+QcRuBktyuTVyn4hHbXlr4LLdtatuZrugrNSElDjg2fjknM6W/ofnI7VDaIRVPZewrS5l78Uah9DpWHFBAE/LmpASWfNEnWCbBbVPpXOxaDu59V6U0LLFGxtEIDaXa43yWv3RBDShnglNH8je9+2zH0Ln6pC4fka+NTU0jP2WO/B30rrn8/qr8QPnCmrqXMtanZHyZGiD6DehnP/sZPvWpT2Hx4sXo7+/HaaedhmeffXZ6ClmG66+/HkuXLkV/fz/WrFmDV155JXpihBBCjmyiFqBf/vKXOP/889HT04N/+Zd/wUsvvYS/+Iu/wAc+8IFan2984xu49dZbcfvtt+Opp57CvHnzsHbtWhw+fLjwyRNCCJm9RJngvv71r2P58uW46667an8bGhqqfc6yDLfccgu+/OUv4+KLLwYAfPe738Xg4CAefPBBXHbZZY0frJpNv/fHmNm8131hy4hxy56pTNmhKqcl41Va2kWMSqXaLTvfjVyZ3FRqIcespkxuYttzLxau1cbrvPJAFa/30iTnJQ2PccOWXU037Nis4e6+jfdV5jnLZdtys66zr3uvdQZoaf5FLoYnsjbXBbJJe5mdAi7C7r2VrtWuyU1uSxOcekbkfVfpg9y++WYpmXZIbVesNvs6uSZG9R213LJleizDtBedisf97fDm0AY37B/+8IdYtWoVPvnJT+K4447DGWecgTvvvLPW/tprr2F4eBhr1qyp/W1gYADnnHMOdu3aFXMoQgghRzhRC9Crr76K2267DSeddBIeffRRfP7zn8dVV12Fu+++GwAwPDwMABgcHPT2GxwcrLVJxsbGMDo66v0jhBBy5BNlgqtWq1i1ahVuuukmAMAZZ5yBF198Ebfffjs2bNjQ1AS2b9+Or371q03tSwghZPYStQAtXboUH/nIR7y/nXzyyfinf/onAMCSJUsAACMjI1i6dGmtz8jICE4//fS6Y27duhVbtmypbY+OjmL58uX5k5A+wgW6ZbuVQZWGYmGVapgabPpjTKmGqUlN72uUapDtssqp6ZatdJx8l+2p9vxxreqpMqWMkrQ813CYSE3IO11po495JIy0/EGXc0sDUtqf6JsZbTFu2KFyDNb5mNplfhNgSop6KKvch9RqpOTgujF3+41SA6r0Om294nkJaEDl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3AC8To3B69QYvE6NweuUj/Xm8z5tWYD+4A/+AD//+c9x/fXXY3h4GKeffjoeeeQR5ZhACCHk6KVtTgibNm1qyORGCCHk6KRjk5H29fXhK1/5iuegQDS8To3B69QYvE6NwetUDG3JhEAIIYSE6Ng3IEIIIUc2XIAIIYQkgQsQIYSQJHABIoQQkoSOXYBY0G6a7du346yzzsKCBQtw3HHH4ZJLLsGePXu8PocPH8bGjRuxePFizJ8/H+vWrVPpkI42br75ZpRKJWzevLn2N16nKX72s5/hU5/6FBYvXoz+/n6cdtppePbZZ2vtWZbh+uuvx9KlS9Hf3481a9bglVdeSTjjmadSqWDbtm0YGhpCf38/PvzhD+NrX/ual9+M16lFsg7kvvvuy3p7e7O/+7u/y/7rv/4r+6M/+qNs0aJF2cjISOqpJWHt2rXZXXfdlb344ovZ888/n/3Wb/1WtmLFiuydd96p9fnc5z6XLV++PNuxY0f27LPPZueee2523nnnJZx1Wp5++unsxBNPzD760Y9mV199de3vvE5Z9vbbb2cnnHBC9ulPfzp76qmnsldffTV79NFHs//5n/+p9bn55puzgYGB7MEHH8xeeOGF7Hd+53eyoaGh7NChQwlnPrPceOON2eLFi7OHH344e+2117L7778/mz9/fvZXf/VXtT68Tq3RkQvQ2WefnW3cuLG2XalUsmXLlmXbt29POKvOYe/evRmAbOfOnVmWZdm+ffuynp6e7P7776/1+e///u8MQLZr165U00zGgQMHspNOOil77LHHsl//9V+vLUC8TlN86Utfyi644ILc9mq1mi1ZsiT78z//89rf9u3bl/X19WXf+973ZmKKHcFFF12UfeYzn/H+dumll2br16/PsozXqQg6zgQXW9DuaGT//v0AgGOOOQYAsHv3bkxMTHjXbOXKlVixYsVRec02btyIiy66yLseAK/T+/zwhz/EqlWr8MlPfhLHHXcczjjjDNx555219tdeew3Dw8PedRoYGMA555xzVF2n8847Dzt27MDLL78MAHjhhRfwxBNP4BOf+AQAXqciSF4PSGIVtPvJT36SaFadQ7VaxebNm3H++efj1FNPBQAMDw+jt7cXixYt8vrmFQE8krnvvvvw3HPP4ZlnnlFtvE5TvPrqq7jtttuwZcsW/Mmf/AmeeeYZXHXVVejt7cWGDRtq16LRopJHKtdddx1GR0excuVKdHV1oVKp4MYbb8T69esBgNepADpuASI2GzduxIsvvognnngi9VQ6jjfeeANXX301HnvsMcyZMyf1dDqWarWKVatW4aabbgIAnHHGGXjxxRdx++23Y8OGDYln1zl8//vfxz333IN7770Xp5xyCp5//nls3rwZy5Yt43UqiI4zwcUWtDua2LRpEx5++GH867/+K44//vja35csWYLx8XHs27fP63+0XbPdu3dj7969+NjHPobu7m50d3dj586duPXWW9Hd3Y3BwUFeJwBLly7FRz7yEe9vJ598Ml5//XUAqF2Lo/07+MUvfhHXXXcdLrvsMpx22mn4wz/8Q1xzzTXYvn07AF6nIui4BcgtaPc+7xe0W716dcKZpSPLMmzatAkPPPAAHn/8cQwNDXntZ555Jnp6erxrtmfPHrz++utH1TW78MIL8eMf/xjPP/987d+qVauwfv362mdeJ+D8889Xbvwvv/wyTjjhBADA0NAQlixZ4l2n0dFRPPXUU0fVdXr33XdRLvs/kV1dXahWp0oI8zoVQGoviHrcd999WV9fX/b3f//32UsvvZRdccUV2aJFi7Lh4eHUU0vC5z//+WxgYCD7t3/7t+ytt96q/Xv33XdrfT73uc9lK1asyB5//PHs2WefzVavXp2tXr064aw7A9cLLst4nbJsykW9u7s7u/HGG7NXXnklu+eee7K5c+dm//AP/1Drc/PNN2eLFi3KfvCDH2T/+Z//mV188cVHnXvxhg0bsg996EM1N+x//ud/zo499tjs2muvrfXhdWqNjlyAsizLvvWtb2UrVqzIent7s7PPPjt78sknU08pGQDq/rvrrrtqfQ4dOpR94QtfyD7wgQ9kc+fOzX73d383e+utt9JNukOQCxCv0xQPPfRQduqpp2Z9fX3ZypUrszvuuMNrr1ar2bZt27LBwcGsr68vu/DCC7M9e/Ykmm0aRkdHs6uvvjpbsWJFNmfOnOxXfuVXsj/90z/NxsbGan14nVqD5RgIIYQkoeM0IEIIIUcHXIAIIYQkgQsQIYSQJHABIoQQkgQuQIQQQpLABYgQQkgSuAARQghJAhcgQgghSeACRAghJAlcgAghhCSBCxAhhJAkcAEihBCShP8f21qsljUV7uYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Reading the x_keys above we can input the parameters that we would like the simulator to evaluate\n", + "x = torch.tensor([\n", + " z_l.item(), # sie z_l\n", + " 0.7, # sie x0\n", + " 0.13, # sie y0\n", + " 0.4, # sie q\n", + " np.pi/5, # sie phi\n", + " 1., # sie b\n", + " 0.2, # src x0\n", + " 0.5, # src y0\n", + " 0.5, # src q\n", + " -np.pi/4, # src phi\n", + " 1.5, # src n\n", + " 2.5, # src Re\n", + " 1., # src Ie\n", + "])\n", + "plt.imshow(sim(x), origin=\"lower\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Where to go next?\n", + "\n", + "The caustic tutorials are generally short and to the point, that way you can idenfity what you want and jump right to some useful code that demo's the particular problem you face. Below is a list of caustic tutorials and a quick description of what you will learn in each one::\n", + "\n", + "- `LensZoo`: here you can see all the built-in lens mass distributions in `caustic` and how they distort the same background Seric source.\n", + "- `Playground`: here we demo the main visualizations of a lensing system (deflection angles, convergence, potential, time delay, magnification) in an interactive display so you can change the parameters by hand and see how the visuals change!\n", + "- `VisualizeCaustics`: here you can see how to find and display caustics, a must when using `caustic`!\n", + "- `Simulators`: here we describe the powerful simulator framework and how it can be used to quickly swap models, parameters, and other features and turn a complex forward model into a simple function.\n", + "- `InvertLensEquation`: here we demo forward ray tracing in `caustic` the process of mapping from the source plane to the image plane." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PY39", + "language": "python", + "name": "py39" + }, + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 62e303683383e9c82f919058f32e42fa2351a4f7 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 15 Nov 2023 16:37:50 -0800 Subject: [PATCH 27/55] setup jupyter book --- docs/jupyter_book/_config.yml | 32 +++++++ docs/jupyter_book/_toc.yml | 9 ++ docs/{ => jupyter_book}/get_start.ipynb | 12 +-- docs/jupyter_book/intro.md | 11 +++ docs/jupyter_book/logo.png | Bin 0 -> 9854 bytes docs/jupyter_book/markdown-notebooks.md | 53 ++++++++++ docs/jupyter_book/markdown.md | 55 +++++++++++ docs/jupyter_book/notebooks.ipynb | 122 ++++++++++++++++++++++++ docs/jupyter_book/references.bib | 56 +++++++++++ docs/jupyter_book/requirements.txt | 3 + 10 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 docs/jupyter_book/_config.yml create mode 100644 docs/jupyter_book/_toc.yml rename docs/{ => jupyter_book}/get_start.ipynb (99%) create mode 100644 docs/jupyter_book/intro.md create mode 100644 docs/jupyter_book/logo.png create mode 100644 docs/jupyter_book/markdown-notebooks.md create mode 100644 docs/jupyter_book/markdown.md create mode 100644 docs/jupyter_book/notebooks.ipynb create mode 100644 docs/jupyter_book/references.bib create mode 100644 docs/jupyter_book/requirements.txt diff --git a/docs/jupyter_book/_config.yml b/docs/jupyter_book/_config.yml new file mode 100644 index 00000000..5f534f80 --- /dev/null +++ b/docs/jupyter_book/_config.yml @@ -0,0 +1,32 @@ +# Book settings +# Learn more at https://jupyterbook.org/customize/config.html + +title: My sample book +author: The Jupyter Book Community +logo: logo.png + +# Force re-execution of notebooks on each build. +# See https://jupyterbook.org/content/execute.html +execute: + execute_notebooks: force + +# Define the name of the latex output file for PDF builds +latex: + latex_documents: + targetname: book.tex + +# Add a bibtex file so that we can create citations +bibtex_bibfiles: + - references.bib + +# Information about where the book exists on the web +repository: + url: https://github.com/executablebooks/jupyter-book # Online location of your book + path_to_book: docs # Optional path to your book, relative to the repository root + branch: master # Which branch of the repository should be used when creating links (optional) + +# Add GitHub buttons to your book +# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository +html: + use_issues_button: true + use_repository_button: true diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml new file mode 100644 index 00000000..74d5c710 --- /dev/null +++ b/docs/jupyter_book/_toc.yml @@ -0,0 +1,9 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: intro +chapters: +- file: markdown +- file: notebooks +- file: markdown-notebooks diff --git a/docs/get_start.ipynb b/docs/jupyter_book/get_start.ipynb similarity index 99% rename from docs/get_start.ipynb rename to docs/jupyter_book/get_start.ipynb index 0b4462c0..21a64c2f 100644 --- a/docs/get_start.ipynb +++ b/docs/jupyter_book/get_start.ipynb @@ -87,11 +87,11 @@ " name: str = \"sim\",\n", " ):\n", " super().__init__(name) # need this so `Parametrized` can do its magic\n", - " \n", + "\n", " # These are the lens and source objects to keep track of\n", " self.lens = lens\n", " self.src = src\n", - " \n", + "\n", " # Here we can add a parameter to the simulator, in this case it is `z_s` which we will need later\n", " self.add_param(\"z_s\", z_s)\n", "\n", @@ -102,7 +102,7 @@ " # Note this is very similar to before, except the packed up `x` is all the raytrace function needs to work\n", " bx, by = self.lens.raytrace(thx, thy, z_s, params)\n", " mu_fine = self.src.brightness(bx, by, params)\n", - " \n", + "\n", " # We return the sampled brightness at each pixel location\n", " return avg_pool2d(mu_fine.squeeze()[None, None], upsample_factor)[0, 0]" ] @@ -436,9 +436,9 @@ ], "metadata": { "kernelspec": { - "display_name": "PY39", + "display_name": "base", "language": "python", - "name": "py39" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -450,7 +450,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.5" + "version": "3.9.12" } }, "nbformat": 4, diff --git a/docs/jupyter_book/intro.md b/docs/jupyter_book/intro.md new file mode 100644 index 00000000..f8cdc73c --- /dev/null +++ b/docs/jupyter_book/intro.md @@ -0,0 +1,11 @@ +# Welcome to your Jupyter Book + +This is a small sample book to give you a feel for how book content is +structured. +It shows off a few of the major file types, as well as some sample content. +It does not go in-depth into any particular topic - check out [the Jupyter Book documentation](https://jupyterbook.org) for more information. + +Check out the content pages bundled with this sample book to see more. + +```{tableofcontents} +``` diff --git a/docs/jupyter_book/logo.png b/docs/jupyter_book/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..06d56f40c838b64eb048a63e036125964a069a3a GIT binary patch literal 9854 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 L>8MsHTOs~GC;Fmw literal 0 HcmV?d00001 diff --git a/docs/jupyter_book/markdown-notebooks.md b/docs/jupyter_book/markdown-notebooks.md new file mode 100644 index 00000000..a057a320 --- /dev/null +++ b/docs/jupyter_book/markdown-notebooks.md @@ -0,0 +1,53 @@ +--- +jupytext: + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.5 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Notebooks with MyST Markdown + +Jupyter Book also lets you write text-based notebooks using MyST Markdown. +See [the Notebooks with MyST Markdown documentation](https://jupyterbook.org/file-types/myst-notebooks.html) for more detailed instructions. +This page shows off a notebook written in MyST Markdown. + +## An example cell + +With MyST Markdown, you can define code cells with a directive like so: + +```{code-cell} +print(2 + 2) +``` + +When your book is built, the contents of any `{code-cell}` blocks will be +executed with your default Jupyter kernel, and their outputs will be displayed +in-line with the rest of your content. + +```{seealso} +Jupyter Book uses [Jupytext](https://jupytext.readthedocs.io/en/latest/) to convert text-based files to notebooks, and can support [many other text-based notebook files](https://jupyterbook.org/file-types/jupytext.html). +``` + +## Create a notebook with MyST Markdown + +MyST Markdown notebooks are defined by two things: + +1. YAML metadata that is needed to understand if / how it should convert text files to notebooks (including information about the kernel needed). + See the YAML at the top of this page for example. +2. The presence of `{code-cell}` directives, which will be executed with your book. + +That's all that is needed to get started! + +## Quickly add YAML metadata for MyST Notebooks + +If you have a markdown file and you'd like to quickly add YAML metadata to it, so that Jupyter Book will treat it as a MyST Markdown Notebook, run the following command: + +``` +jupyter-book myst init path/to/markdownfile.md +``` diff --git a/docs/jupyter_book/markdown.md b/docs/jupyter_book/markdown.md new file mode 100644 index 00000000..0ddaab3f --- /dev/null +++ b/docs/jupyter_book/markdown.md @@ -0,0 +1,55 @@ +# Markdown Files + +Whether you write your book's content in Jupyter Notebooks (`.ipynb`) or +in regular markdown files (`.md`), you'll write in the same flavor of markdown +called **MyST Markdown**. +This is a simple file to help you get started and show off some syntax. + +## What is MyST? + +MyST stands for "Markedly Structured Text". It +is a slight variation on a flavor of markdown called "CommonMark" markdown, +with small syntax extensions to allow you to write **roles** and **directives** +in the Sphinx ecosystem. + +For more about MyST, see [the MyST Markdown Overview](https://jupyterbook.org/content/myst.html). + +## Sample Roles and Directives + +Roles and directives are two of the most powerful tools in Jupyter Book. They +are kind of like functions, but written in a markup language. They both +serve a similar purpose, but **roles are written in one line**, whereas +**directives span many lines**. They both accept different kinds of inputs, +and what they do with those inputs depends on the specific role or directive +that is being called. + +Here is a "note" directive: + +```{note} +Here is a note +``` + +It will be rendered in a special box when you build your book. + +Here is an inline directive to refer to a document: {doc}`markdown-notebooks`. + + +## Citations + +You can also cite references that are stored in a `bibtex` file. For example, +the following syntax: `` {cite}`holdgraf_evidence_2014` `` will render like +this: {cite}`holdgraf_evidence_2014`. + +Moreover, you can insert a bibliography into your page with this syntax: +The `{bibliography}` directive must be used for all the `{cite}` roles to +render properly. +For example, if the references for your book are stored in `references.bib`, +then the bibliography is inserted with: + +```{bibliography} +``` + +## Learn more + +This is just a simple starter to get you started. +You can learn a lot more at [jupyterbook.org](https://jupyterbook.org). diff --git a/docs/jupyter_book/notebooks.ipynb b/docs/jupyter_book/notebooks.ipynb new file mode 100644 index 00000000..fdb7176c --- /dev/null +++ b/docs/jupyter_book/notebooks.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Content with notebooks\n", + "\n", + "You can also create content with Jupyter Notebooks. This means that you can include\n", + "code blocks and their outputs in your book.\n", + "\n", + "## Markdown + notebooks\n", + "\n", + "As it is markdown, you can embed images, HTML, etc into your posts!\n", + "\n", + "![](https://myst-parser.readthedocs.io/en/latest/_static/logo-wide.svg)\n", + "\n", + "You can also $add_{math}$ and\n", + "\n", + "$$\n", + "math^{blocks}\n", + "$$\n", + "\n", + "or\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "\\mbox{mean} la_{tex} \\\\ \\\\\n", + "math blocks\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "But make sure you \\$Escape \\$your \\$dollar signs \\$you want to keep!\n", + "\n", + "## MyST markdown\n", + "\n", + "MyST markdown works in Jupyter Notebooks as well. For more information about MyST markdown, check\n", + "out [the MyST guide in Jupyter Book](https://jupyterbook.org/content/myst.html),\n", + "or see [the MyST markdown documentation](https://myst-parser.readthedocs.io/en/latest/).\n", + "\n", + "## Code blocks and outputs\n", + "\n", + "Jupyter Book will also embed your code blocks and output in your book.\n", + "For example, here's some sample Matplotlib code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import rcParams, cycler\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "plt.ion()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fixing random state for reproducibility\n", + "np.random.seed(19680801)\n", + "\n", + "N = 10\n", + "data = [np.logspace(0, 1, 100) + np.random.randn(100) + ii for ii in range(N)]\n", + "data = np.array(data).T\n", + "cmap = plt.cm.coolwarm\n", + "rcParams['axes.prop_cycle'] = cycler(color=cmap(np.linspace(0, 1, N)))\n", + "\n", + "\n", + "from matplotlib.lines import Line2D\n", + "custom_lines = [Line2D([0], [0], color=cmap(0.), lw=4),\n", + " Line2D([0], [0], color=cmap(.5), lw=4),\n", + " Line2D([0], [0], color=cmap(1.), lw=4)]\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 5))\n", + "lines = ax.plot(data)\n", + "ax.legend(custom_lines, ['Cold', 'Medium', 'Hot']);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a lot more that you can do with outputs (such as including interactive outputs)\n", + "with your book. For more information about this, see [the Jupyter Book documentation](https://jupyterbook.org)" + ] + } + ], + "metadata": { + "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.8.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/jupyter_book/references.bib b/docs/jupyter_book/references.bib new file mode 100644 index 00000000..783ec6aa --- /dev/null +++ b/docs/jupyter_book/references.bib @@ -0,0 +1,56 @@ +--- +--- + +@inproceedings{holdgraf_evidence_2014, + address = {Brisbane, Australia, Australia}, + title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}}, + booktitle = {International {Conference} on {Cognitive} {Neuroscience}}, + publisher = {Frontiers in Neuroscience}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.}, + year = {2014} +} + +@article{holdgraf_rapid_2016, + title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility}, + volume = {7}, + issn = {2041-1723}, + url = {http://www.nature.com/doifinder/10.1038/ncomms13654}, + doi = {10.1038/ncomms13654}, + number = {May}, + journal = {Nature Communications}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.}, + year = {2016}, + pages = {13654}, + file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf} +} + +@inproceedings{holdgraf_portable_2017, + title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science}, + volume = {Part F1287}, + isbn = {978-1-4503-5272-7}, + doi = {10.1145/3093338.3093370}, + abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.}, + booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}}, + author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.}, + year = {2017}, + keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy} +} + +@article{holdgraf_encoding_2017, + title = {Encoding and decoding models in cognitive electrophysiology}, + volume = {11}, + issn = {16625137}, + doi = {10.3389/fnsys.2017.00061}, + abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.}, + journal = {Frontiers in Systems Neuroscience}, + author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.}, + year = {2017}, + keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials} +} + +@book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} +} diff --git a/docs/jupyter_book/requirements.txt b/docs/jupyter_book/requirements.txt new file mode 100644 index 00000000..7e821e45 --- /dev/null +++ b/docs/jupyter_book/requirements.txt @@ -0,0 +1,3 @@ +jupyter-book +matplotlib +numpy From 195b8419b313426658d64bd465b73e9e710ccb90 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:12:47 -0800 Subject: [PATCH 28/55] update to docsting for utils.py --- src/caustics/utils.py | 281 +++++++++++++++++++++++++++--------------- 1 file changed, 181 insertions(+), 100 deletions(-) diff --git a/src/caustics/utils.py b/src/caustics/utils.py index f3fb94c7..87ac579c 100644 --- a/src/caustics/utils.py +++ b/src/caustics/utils.py @@ -12,12 +12,17 @@ def flip_axis_ratio(q, phi): """ Makes the value of 'q' positive, then swaps x and y axes if 'q' is larger than 1. - Args: - q (Tensor): Tensor containing values to be processed. - phi (Tensor): Tensor containing the phi values for the orientation of the axes. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the processed 'q' and 'phi' Tensors. + Parameters + ---------- + q: Tensor + Tensor containing values to be processed. + phi: Tensor + Tensor containing the phi values for the orientation of the axes. + + Returns + ------- + Tuple[Tensor, Tensor] + Tuple containing the processed 'q' and 'phi' Tensors. """ q = q.abs() return torch.where(q > 1, 1 / q, q), torch.where(q > 1, phi + pi / 2, phi) @@ -27,15 +32,23 @@ def translate_rotate(x, y, x0, y0, phi: Optional[Tensor] = None): """ Translates and rotates the points (x, y) by subtracting (x0, y0) and applying rotation angle phi. - Args: - x (Tensor): Tensor containing the x-coordinates. - y (Tensor): Tensor containing the y-coordinates. - x0 (Tensor): Tensor containing the x-coordinate translation values. - y0 (Tensor): Tensor containing the y-coordinate translation values. - phi (Optional[Tensor], optional): Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the translated and rotated x and y coordinates. + Parameters + ---------- + x: Tensor + Tensor containing the x-coordinates. + y: Tensor + Tensor containing the y-coordinates. + x0: Tensor + Tensor containing the x-coordinate translation values. + y0: Tensor + Tensor containing the y-coordinate translation values. + phi: Optional[Tensor], optional) + Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. + + Returns + ------- + Tuple: [Tensor, Tensor] + Tuple containing the translated and rotated x and y coordinates. """ xt = x - x0 yt = y - y0 @@ -54,13 +67,19 @@ def derotate(vx, vy, phi: Optional[Tensor] = None): """ Applies inverse rotation to the velocity components (vx, vy) using the rotation angle phi. - Args: - vx (Tensor): Tensor containing the x-component of velocity. - vy (Tensor): Tensor containing the y-component of velocity. - phi (Optional[Tensor], optional): Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the derotated x and y components of velocity. + Parameters + ---------- + vx: Tensor + Tensor containing the x-component of velocity. + vy: Tensor + Tensor containing the y-component of velocity. + phi: Optional[Tensor], optional) + Tensor containing the rotation angles. If None, no rotation is applied. Defaults to None. + + Returns + ------- + Tuple: [Tensor, Tensor] + Tuple containing the derotated x and y components of velocity. """ if phi is None: return vx, vy @@ -74,13 +93,19 @@ def to_elliptical(x, y, q: Tensor): """ Converts Cartesian coordinates to elliptical coordinates. - Args: - x (Tensor): Tensor containing the x-coordinates. - y (Tensor): Tensor containing the y-coordinates. - q (Tensor): Tensor containing the elliptical parameters. - - Returns: - Tuple[Tensor, Tensor]: Tuple containing the x and y coordinates in elliptical form. + Parameters + ---------- + x: Tensor + Tensor containing the x-coordinates. + y: Tensor + Tensor containing the y-coordinates. + q: Tensor + Tensor containing the elliptical parameters. + + Returns + ------- + Tuple: Tensor, Tensor + Tuple containing the x and y coordinates in elliptical form. """ return x, y / q @@ -91,15 +116,23 @@ def get_meshgrid( """ Generates a 2D meshgrid based on the provided pixelscale and dimensions. - Args: - pixelscale (float): The scale of the meshgrid in each dimension. - nx (int): The number of grid points along the x-axis. - ny (int): The number of grid points along the y-axis. - device (torch.device, optional): The device on which to create the tensor. Defaults to None. - dtype (torch.dtype, optional): The desired data type of the tensor. Defaults to torch.float32. - - Returns: - Tuple[Tensor, Tensor]: The generated meshgrid as a tuple of Tensors. + Parameters + ---------- + pixelscale: float + The scale of the meshgrid in each dimension. + nx: int + The number of grid points along the x-axis. + ny: int + The number of grid points along the y-axis. + device: torch.device, optional + The device on which to create the tensor. Defaults to None. + dtype: torch.dtype, optional + The desired data type of the tensor. Defaults to torch.float32. + + Returns + ------- + Tuple: [Tensor, Tensor] + The generated meshgrid as a tuple of Tensors. """ xs = ( torch.linspace(-1, 1, nx, device=device, dtype=dtype) @@ -120,12 +153,17 @@ def safe_divide(num, denom, places=7): """ Safely divides two tensors, returning zero where the denominator is zero. - Args: - num (Tensor): The numerator tensor. - denom (Tensor): The denominator tensor. - - Returns: - Tensor: The result of the division, with zero where the denominator was zero. + Parameters + ---------- + num: Tensor + The numerator tensor. + denom: Tensor + The denominator tensor. + + Returns + ------- + Tensor + The result of the division, with zero where the denominator was zero. """ out = torch.zeros_like(num) where = denom != 0 @@ -137,11 +175,15 @@ def safe_log(x): """ Safely applies the logarithm to a tensor, returning zero where the tensor is zero. - Args: - x (Tensor): The input tensor. + Parameters + ---------- + x: Tensor + The input tensor. - Returns: - Tensor: The result of applying the logarithm, with zero where the input was zero. + Returns + ------- + Tensor + The result of applying the logarithm, with zero where the input was zero. """ out = torch.zeros_like(x) where = x != 0 @@ -153,11 +195,15 @@ def _h_poly(t): """Helper function to compute the 'h' polynomial matrix used in the cubic spline. - Args: - t (Tensor): A 1D tensor representing the normalized x values. + Parameters + ---------- + t: Tensor + A 1D tensor representing the normalized x values. - Returns: - Tensor: A 2D tensor of size (4, len(t)) representing the 'h' polynomial matrix. + Returns + ------- + Tensor + A 2D tensor of size (4, len(t)) representing the 'h' polynomial matrix. """ @@ -174,19 +220,26 @@ def interp1d(x: Tensor, y: Tensor, xs: Tensor, extend: str = "extrapolate") -> T """Compute the 1D cubic spline interpolation for the given data points using PyTorch. - Args: - x (Tensor): A 1D tensor representing the x-coordinates of the known data points. - y (Tensor): A 1D tensor representing the y-coordinates of the known data points. - xs (Tensor): A 1D tensor representing the x-coordinates of the positions where - the cubic spline function should be evaluated. - extend (str, optional): The method for handling extrapolation, either "const", "extrapolate", or "linear". - Default is "extrapolate". - "const": Use the value of the last known data point for extrapolation. - "linear": Use linear extrapolation based on the last two known data points. - "extrapolate": Use cubic extrapolation of data. - - Returns: - Tensor: A 1D tensor representing the interpolated values at the specified positions (xs). + Parameters + ---------- + x: Tensor + A 1D tensor representing the x-coordinates of the known data points. + y: Tensor + A 1D tensor representing the y-coordinates of the known data points. + xs: Tensor + A 1D tensor representing the x-coordinates of the positions where + the cubic spline function should be evaluated. + extend: (str, optional) + The method for handling extrapolation, either "const", "extrapolate", or "linear". + Default is "extrapolate". + "const": Use the value of the last known data point for extrapolation. + "linear": Use linear extrapolation based on the last two known data points. + "extrapolate": Use cubic extrapolation of data. + + Returns + ------- + Tensor + A 1D tensor representing the interpolated values at the specified positions (xs). """ m = (y[1:] - y[:-1]) / (x[1:] - x[:-1]) @@ -219,23 +272,37 @@ def interp2d( Interpolates a 2D image at specified coordinates. Similar to `torch.nn.functional.grid_sample` with `align_corners=False`. - Args: - im (Tensor): A 2D tensor representing the image. - x (Tensor): A 0D or 1D tensor of x coordinates at which to interpolate. - y (Tensor): A 0D or 1D tensor of y coordinates at which to interpolate. - method (str, optional): Interpolation method. Either 'nearest' or 'linear'. Defaults to 'linear'. - padding_mode (str, optional): Defines the padding mode when out-of-bound indices are encountered. - Either 'zeros' or 'extrapolate'. Defaults to 'zeros'. - - Raises: - ValueError: If `im` is not a 2D tensor. - ValueError: If `x` is not a 0D or 1D tensor. - ValueError: If `y` is not a 0D or 1D tensor. - ValueError: If `padding_mode` is not 'extrapolate' or 'zeros'. - ValueError: If `method` is not 'nearest' or 'linear'. - - Returns: - Tensor: Tensor with the same shape as `x` and `y` containing the interpolated values. + Parameters + ---------- + im: Tensor + A 2D tensor representing the image. + x: Tensor + A 0D or 1D tensor of x coordinates at which to interpolate. + y: Tensor + A 0D or 1D tensor of y coordinates at which to interpolate. + method: (str, optional) + Interpolation method. Either 'nearest' or 'linear'. Defaults to 'linear'. + padding_mode: (str, optional) + Defines the padding mode when out-of-bound indices are encountered. + Either 'zeros' or 'extrapolate'. Defaults to 'zeros'. + + Raises + ------ + ValueError + If `im` is not a 2D tensor. + ValueError + If `x` is not a 0D or 1D tensor. + ValueError + If `y` is not a 0D or 1D tensor. + ValueError + If `padding_mode` is not 'extrapolate' or 'zeros'. + ValueError + If `method` is not 'nearest' or 'linear'. + + Returns + ------- + Tensor + Tensor with the same shape as `x` and `y` containing the interpolated values. """ if im.ndim != 2: raise ValueError(f"im must be 2D (received {im.ndim}D tensor)") @@ -296,18 +363,27 @@ def vmap_n( Returns `func` transformed `depth` times by `vmap`, with the same arguments passed to `vmap` each time. - Args: - func (Callable): The function to transform. - depth (int, optional): The number of times to apply `torch.vmap`. Defaults to 1. - in_dims (Union[int, Tuple], optional): The dimensions to vectorize over in the input. Defaults to 0. - out_dims (Union[int, Tuple[int, ...]], optional): The dimensions to vectorize over in the output. Defaults to 0. - randomness (str, optional): How to handle randomness. Defaults to 'error'. - - Raises: - ValueError: If `depth` is less than 1. - - Returns: - Callable: The transformed function. + Parameters: + func: Callable + The function to transform. + depth: (int, optional) + The number of times to apply `torch.vmap`. Defaults to 1. + in_dims: (Union[int, Tuple], optional) + The dimensions to vectorize over in the input. Defaults to 0. + out_dims: (Union[int, Tuple[int, ...]], optional): + The dimensions to vectorize over in the output. Defaults to 0. + randomness: (str, optional) + How to handle randomness. Defaults to 'error'. + + Raises + ------ + ValueError + If `depth` is less than 1. + + Returns + ------- + Callable + The transformed function. TODO: test. """ @@ -325,12 +401,17 @@ def get_cluster_means(xs: Tensor, k: int): """ Computes cluster means using the k-means++ initialization algorithm. - Args: - xs (Tensor): A tensor of data points. - k (int): The number of clusters. - - Returns: - Tensor: A tensor of cluster means. + Parameters + ---------- + xs: Tensor + A tensor of data points. + k: int + The number of clusters. + + Returns + ------- + Tensor + A tensor of cluster means. """ b = len(xs) mean_idxs = [int(torch.randint(high=b, size=(), device=xs.device).item())] From c2772580c73e28a3dda91b742fe6b4f62616cb9e Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:18:44 -0800 Subject: [PATCH 29/55] change to numpy docstring for parametrized.py --- src/caustics/parametrized.py | 107 ++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 39 deletions(-) diff --git a/src/caustics/parametrized.py b/src/caustics/parametrized.py index 753207e5..298b570a 100644 --- a/src/caustics/parametrized.py +++ b/src/caustics/parametrized.py @@ -37,14 +37,22 @@ class Parametrized: - Attributes can be Params, Parametrized, tensor buffers or just normal attributes. - Need to make sure an attribute of one of those types isn't rebound to be of a different type. - Attributes: - name (str): The name of the Parametrized object. Default to class name. - parents (NestedNamespaceDict): Nested dictionary of parent Parametrized objects (higher level, more abstract modules). - params (OrderedDict[str, Parameter]): Dictionary of parameters. - childs NestedNamespaceDict: Nested dictionary of childs Parametrized objects (lower level, more specialized modules). - dynamic_size (int): Size of dynamic parameters. - n_dynamic (int): Number of dynamic parameters. - n_static (int): Number of static parameters. + Attributes + ---------- + name: str + The name of the Parametrized object. Default to class name. + parents: NestedNamespaceDict + Nested dictionary of parent Parametrized objects (higher level, more abstract modules). + params: OrderedDict[str, Parameter] + Dictionary of parameters. + childs: NestedNamespaceDict + Nested dictionary of childs Parametrized objects (lower level, more specialized modules). + dynamic_size: int + Size of dynamic parameters. + n_dynamic: int + Number of dynamic parameters. + n_static: int + Number of static parameters. """ def __init__(self, name: str = None): @@ -155,10 +163,14 @@ def add_param( """ Stores a parameter in the _params dictionary and records its size. - Args: - name (str): The name of the parameter. - value (Optional[Tensor], optional): The value of the parameter. Defaults to None. - shape (Optional[tuple[int, ...]], optional): The shape of the parameter. Defaults to an empty tuple. + Parameters + ---------- + name: str + The name of the parameter. + value: (Optional[Tensor], optional) + The value of the parameter. Defaults to None. + shape: (Optional[tuple[int, ...]], optional) + The shape of the parameter. Defaults to an empty tuple. """ self._params[name] = Parameter(value, shape) # __setattr__ inside add_param to catch all uses of this method @@ -189,18 +201,25 @@ def pack( into arguments to this component and its childs. Also, add a batch dimension to each Tensor without such a dimension. - Args: - x (Union[list[Tensor], dict[str, Union[list[Tensor], Tensor, dict[str, Tensor]]], Tensor): - The input to be packed. Can be a list of tensors, a dictionary of tensors, or a single tensor. - - Returns: - Packed: The packed input, and whether or not the input was batched. - - Raises: - ValueError: If the input is not a list, dictionary, or tensor. - ValueError: If the input is a dictionary and some keys are missing. - ValueError: If the number of dynamic arguments does not match the expected number. - ValueError: If the input is a tensor and the shape does not match the expected shape. + Parameters: + x: (Union[list[Tensor], dict[str, Union[list[Tensor], Tensor, dict[str, Tensor]]], Tensor) + The input to be packed. Can be a list of tensors, a dictionary of tensors, or a single tensor. + + Returns + ------- + Packed + The packed input, and whether or not the input was batched. + + Raises + ------ + ValueError + If the input is not a list, dictionary, or tensor. + ValueError + If the input is a dictionary and some keys are missing. + ValueError + If the number of dynamic arguments does not match the expected number. + ValueError + If the input is a tensor and the shape does not match the expected shape. """ if isinstance(x, (dict, Packed)): missing_names = [ @@ -261,18 +280,23 @@ def unpack( Unpacks a dict of kwargs, list of args or flattened vector of args to retrieve this object's static and dynamic parameters. - Args: - x (Optional[dict[str, Union[list[Tensor], dict[str, Tensor], Tensor]]]): - The packed object to be unpacked. + Parameters: + x: (Optional[dict[str, Union[list[Tensor], dict[str, Tensor], Tensor]]]) + The packed object to be unpacked. - Returns: - list[Tensor]: Unpacked static and dynamic parameters of the object. Note that + Returns + ------- + list[Tensor] + Unpacked static and dynamic parameters of the object. Note that parameters will have an added batch dimension from the pack method. - Raises: - ValueError: If the input is not a dict, list, tuple or tensor. - ValueError: If the argument type is invalid. It must be a dict containing key {self.name} - and value containing args as list or flattened tensor, or kwargs. + Raises + ------ + ValueError + If the input is not a dict, list, tuple or tensor. + ValueError + If the argument type is invalid. It must be a dict containing key {self.name} + and value containing args as list or flattened tensor, or kwargs. """ # Check if module has dynamic parameters if self.module_params.dynamic: @@ -399,12 +423,17 @@ def get_graph( """ Returns a graph representation of the object and its parameters. - Args: - show_dynamic_params (bool, optional): If true, the dynamic parameters are shown in the graph. Defaults to False. - show_static_params (bool, optional): If true, the static parameters are shown in the graph. Defaults to False. - - Returns: - graphviz.Digraph: The graph representation of the object. + Parameters + ---------- + show_dynamic_params: (bool, optional) + If true, the dynamic parameters are shown in the graph. Defaults to False. + show_static_params: (bool, optional) + If true, the static parameters are shown in the graph. Defaults to False. + + Returns + ------- + graphviz.Digraph + The graph representation of the object. """ import graphviz From a0cee3bccab39b554f55efb03343a176f0fa893d Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:34:33 -0800 Subject: [PATCH 30/55] changes to numpy docstring --- src/caustics/cosmology.py | 263 +++++++++++++++++++++------------ src/caustics/namespace_dict.py | 12 +- src/caustics/parameter.py | 17 ++- 3 files changed, 190 insertions(+), 102 deletions(-) diff --git a/src/caustics/cosmology.py b/src/caustics/cosmology.py index 6b55112d..78f042dd 100644 --- a/src/caustics/cosmology.py +++ b/src/caustics/cosmology.py @@ -42,20 +42,27 @@ class Cosmology(Parametrized): This class provides an interface for cosmological computations used in lensing such as comoving distance and critical surface density. - Units: - - Distance: Mpc - - Mass: solar mass - - Attributes: - name (str): Name of the cosmological model. + Units + ----- + Distance + Mpc + Mass + solar mass + + Attributes + ---------- + name: str + Name of the cosmological model. """ def __init__(self, name: str = None): """ Initialize the Cosmology. - Args: - name (str): Name of the cosmological model. + Parameters + ---------- + name: str + Name of the cosmological model. """ super().__init__(name) @@ -64,12 +71,17 @@ def critical_density(self, z: Tensor, params: Optional["Packed"] = None) -> Tens """ Compute the critical density at redshift z. - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The critical density at each redshift. + Parameters + ---------- + z: Tensor + The redshifts. + params: Packed, optional + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The critical density at each redshift. """ ... @@ -81,12 +93,17 @@ def comoving_distance( """ Compute the comoving distance to redshift z. - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The comoving distance to each redshift. + Parameters + ---------- + z: Tensor + The redshifts. + params: (Packed, optional0 + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The comoving distance to each redshift. """ ... @@ -98,12 +115,17 @@ def transverse_comoving_distance( """ Compute the transverse comoving distance to redshift z (Mpc). - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The transverse comoving distance to each redshift in Mpc. + Parameters + ---------- + z: Tensor + The redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The transverse comoving distance to each redshift in Mpc. """ ... @@ -114,13 +136,19 @@ def comoving_distance_z1z2( """ Compute the comoving distance between two redshifts. - Args: - z1 (Tensor): The starting redshifts. - z2 (Tensor): The ending redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The comoving distance between each pair of redshifts. + Parameters + ---------- + z1: Tensor + The starting redshifts. + z2: Tensor + The ending redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The comoving distance between each pair of redshifts. """ return self.comoving_distance(z2, params) - self.comoving_distance(z1, params) @@ -131,13 +159,18 @@ def transverse_comoving_distance_z1z2( """ Compute the transverse comoving distance between two redshifts (Mpc). - Args: - z1 (Tensor): The starting redshifts. - z2 (Tensor): The ending redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The transverse comoving distance between each pair of redshifts in Mpc. + Parameters: + z1: Tensor + The starting redshifts. + z2: Tensor + The ending redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The transverse comoving distance between each pair of redshifts in Mpc. """ return self.transverse_comoving_distance( z2, params @@ -150,12 +183,17 @@ def angular_diameter_distance( """ Compute the angular diameter distance to redshift z. - Args: - z (Tensor): The redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The angular diameter distance to each redshift. + Parameters: + ----------- + z: Tensor + The redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The angular diameter distance to each redshift. """ return self.comoving_distance(z, params) / (1 + z) @@ -166,13 +204,19 @@ def angular_diameter_distance_z1z2( """ Compute the angular diameter distance between two redshifts. - Args: - z1 (Tensor): The starting redshifts. - z2 (Tensor): The ending redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The angular diameter distance between each pair of redshifts. + Parameters + ---------- + z1: Tensor + The starting redshifts. + z2: Tensor + The ending redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The angular diameter distance between each pair of redshifts. """ return self.comoving_distance_z1z2(z1, z2, params) / (1 + z2) @@ -183,13 +227,19 @@ def time_delay_distance( """ Compute the time delay distance between lens and source planes. - Args: - z_l (Tensor): The lens redshifts. - z_s (Tensor): The source redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The time delay distance for each pair of lens and source redshifts. + Parameters + ---------- + z_l: Tensor + The lens redshifts. + z_s: Tensor + The source redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The time delay distance for each pair of lens and source redshifts. """ d_l = self.angular_diameter_distance(z_l, params) d_s = self.angular_diameter_distance(z_s, params) @@ -203,13 +253,19 @@ def critical_surface_density( """ Compute the critical surface density between lens and source planes. - Args: - z_l (Tensor): The lens redshifts. - z_s (Tensor): The source redshifts. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: The critical surface density for each pair of lens and source redshifts. + Parameters + ---------- + z_l: Tensor + The lens redshifts. + z_s: Tensor + The source redshifts. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + The critical surface density for each pair of lens and source redshifts. """ d_l = self.angular_diameter_distance(z_l, params) d_s = self.angular_diameter_distance(z_s, params) @@ -232,11 +288,16 @@ def __init__( """ Initialize a new instance of the FlatLambdaCDM class. - Args: - name (str): Name of the cosmology. - h0 (Optional[Tensor]): Hubble constant over 100. Default is h0_default. - critical_density_0 (Optional[Tensor]): Critical density at z=0. Default is critical_density_0_default. - Om0 (Optional[Tensor]): Matter density parameter at z=0. Default is Om0_default. + Parameters + ---------- + name: str + Name of the cosmology. + h0: Optional[Tensor] + Hubble constant over 100. Default is h0_default. + critical_density_0: (Optional[Tensor]) + Critical density at z=0. Default is critical_density_0_default. + Om0: Optional[Tensor] + Matter density parameter at z=0. Default is Om0_default. """ super().__init__(name) @@ -266,11 +327,15 @@ def hubble_distance(self, h0): """ Calculate the Hubble distance. - Args: - h0 (Tensor): Hubble constant. + Parameters + ---------- + h0: Tensor + Hubble constant. - Returns: - Tensor: Hubble distance. + Returns + ------- + Tensor + Hubble distance. """ return c_Mpc_s / (100 * km_to_Mpc) / h0 @@ -287,12 +352,17 @@ def critical_density( """ Calculate the critical density at redshift z. - Args: - z (Tensor): Redshift. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - torch.Tensor: Critical density at redshift z. + Parameters + ---------- + z: Tensor + Redshift. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + torch.Tensor + Critical density at redshift z. """ Ode0 = 1 - Om0 return central_critical_density * (Om0 * (1 + z) ** 3 + Ode0) @@ -304,11 +374,15 @@ def _comoving_distance_helper( """ Helper method for computing comoving distances. - Args: - x (Tensor): Input tensor. + Parameters + ---------- + x: Tensor + Input tensor. - Returns: - Tensor: Computed comoving distances. + Returns + ------- + Tensor + Computed comoving distances. """ return interp1d( self._comoving_distance_helper_x_grid, @@ -329,12 +403,17 @@ def comoving_distance( """ Calculate the comoving distance to redshift z. - Args: - z (Tensor): Redshift. - params (Packed, optional): Dynamic parameter container for the computation. - - Returns: - Tensor: Comoving distance to redshift z. + Parameters + ---------- + z: Tensor + Redshift. + params: (Packed, optional) + Dynamic parameter container for the computation. + + Returns + ------- + Tensor + Comoving distance to redshift z. """ Ode0 = 1 - Om0 ratio = (Om0 / Ode0) ** (1 / 3) diff --git a/src/caustics/namespace_dict.py b/src/caustics/namespace_dict.py index 6e0ac77d..7106f5df 100644 --- a/src/caustics/namespace_dict.py +++ b/src/caustics/namespace_dict.py @@ -38,8 +38,10 @@ def flatten(self) -> NamespaceDict: """ Flatten the nested dictionary into a NamespaceDict - Returns: - NamespaceDict: Flattened dictionary as a NamespaceDict + Returns + ------- + NamespaceDict + Flattened dictionary as a NamespaceDict """ flattened_dict = NamespaceDict() @@ -59,8 +61,10 @@ def collapse(self) -> NamespaceDict: Flatten the nested dictionary and collapse keys into the first level of the NamespaceDict - Returns: - NamespaceDict: Flattened dictionary as a NamespaceDict + Returns + ------- + NamespaceDict + Flattened dictionary as a NamespaceDict """ flattened_dict = NamespaceDict() diff --git a/src/caustics/parameter.py b/src/caustics/parameter.py index d49a490d..73a9ee57 100644 --- a/src/caustics/parameter.py +++ b/src/caustics/parameter.py @@ -12,9 +12,12 @@ class Parameter: A static parameter has a fixed value, while a dynamic parameter must be passed in each time it's required. - Attributes: - value (Optional[Tensor]): The value of the parameter. - shape (tuple[int, ...]): The shape of the parameter. + Attributes + ---------- + value: (Optional[Tensor]) + The value of the parameter. + shape: (tuple[int, ...]) + The shape of the parameter. """ def __init__( @@ -75,9 +78,11 @@ def to( """ Moves and/or casts the values of the parameter. - Args: - device (Optional[torch.device], optional): The device to move the values to. Defaults to None. - dtype (Optional[torch.dtype], optional): The desired data type. Defaults to None. + Parameters: + device: (Optional[torch.device], optional) + The device to move the values to. Defaults to None. + dtype: (Optional[torch.dtype], optional) + The desired data type. Defaults to None. """ if self.static: self.value = self._value.to(device=device, dtype=dtype) From a5bc43ad11fa939815e2bffd46b5ece48b89e3ab Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:39:06 -0800 Subject: [PATCH 31/55] change to numpy docstings for sims --- src/caustics/sims/lens_source.py | 67 ++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/src/caustics/sims/lens_source.py b/src/caustics/sims/lens_source.py index e6238be8..68139a22 100644 --- a/src/caustics/sims/lens_source.py +++ b/src/caustics/sims/lens_source.py @@ -34,18 +34,30 @@ class Lens_Source(Simulator): plt.imshow(img, origin = "lower") plt.show() - Attributes: - lens: caustics lens mass model object - source: caustics light object which defines the background source - pixelscale: pixelscale of the sampling grid. - pixels_x: number of pixels on the x-axis for the sampling grid - lens_light (optional): caustics light object which defines the lensing object's light - psf (optional): An image to convolve with the scene. Note that if ``upsample_factor > 1`` the psf must also be at the higher resolution. - pixels_y (optional): number of pixels on the y-axis for the sampling grid. If left as ``None`` then this will simply be equal to ``gridx`` - upsample_factor (default 1): Amount of upsampling to model the image. For example ``upsample_factor = 2`` indicates that the image will be sampled at double the resolution then summed back to the original resolution (given by pixelscale and gridx/y). - psf_pad (default True): If convolving the PSF it is important to sample the model in a larger FOV equal to half the PSF size in order to account for light that scatters from outside the requested FOV inwards. Internally this padding will be added before sampling, then cropped off before returning the final image to the user. - z_s (optional): redshift of the source - name (default "sim"): a name for this simulator in the parameter DAG. + Attributes + ---------- + lens + caustic lens mass model object + source + caustic light object which defines the background source + pixelscale: float + pixelscale of the sampling grid. + pixels_x: int + number of pixels on the x-axis for the sampling grid + lens_light: (optional) + caustic light object which defines the lensing object's light + psf: (optional) + An image to convolve with the scene. Note that if ``upsample_factor > 1`` the psf must also be at the higher resolution. + pixels_y: Optional[int] + number of pixels on the y-axis for the sampling grid. If left as ``None`` then this will simply be equal to ``gridx`` + upsample_factor (default 1) + Amount of upsampling to model the image. For example ``upsample_factor = 2`` indicates that the image will be sampled at double the resolution then summed back to the original resolution (given by pixelscale and gridx/y). + psf_pad: Boolean(default True) + If convolving the PSF it is important to sample the model in a larger FOV equal to half the PSF size in order to account for light that scatters from outside the requested FOV inwards. Internally this padding will be added before sampling, then cropped off before returning the final image to the user. + z_s: optional + redshift of the source + name: string (default "sim") + a name for this simulator in the parameter DAG. """ @@ -130,11 +142,15 @@ def _unpad_fft(self, x): """ Remove padding from the result of a 2D FFT. - Args: - x (Tensor): The input tensor with padding. + Parameters + --------- + x: Tensor + The input tensor with padding. - Returns: - Tensor: The input tensor without padding. + Returns + ------- + Tensor + The input tensor without padding. """ return torch.roll(x, (-self.psf_pad[0], -self.psf_pad[1]), dims=(-2, -1))[ ..., : self.n_pix[0], : self.n_pix[1] @@ -149,11 +165,20 @@ def forward( psf_convolve=True, ): """ - params: Packed object - source_light: when true the source light will be sampled - lens_light: when true the lens light will be sampled - lens_source: when true, the source light model will be lensed by the lens mass distribution - psf_convolve: when true the image will be convolved with the psf + forward function + + Parameters + ---------- + params: + Packed object + source_light: boolean + when true the source light will be sampled + lens_light: boolean + when true the lens light will be sampled + lens_source: boolean + when true, the source light model will be lensed by the lens mass distribution + psf_convolve: boolean + when true the image will be convolved with the psf """ (z_s,) = self.unpack(params) From 694c1f46bdd1c1a1c1a1b1df16f36ceb40772ea7 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 12:50:09 -0800 Subject: [PATCH 32/55] change to numpy docstring for data dir --- src/caustics/data/hdf5dataset.py | 16 ++++-- src/caustics/data/probes.py | 5 +- src/caustics/light/base.py | 23 +++++--- src/caustics/light/sersic.py | 91 +++++++++++++++++++++++--------- 4 files changed, 96 insertions(+), 39 deletions(-) diff --git a/src/caustics/data/hdf5dataset.py b/src/caustics/data/hdf5dataset.py index 02236973..11c43311 100644 --- a/src/caustics/data/hdf5dataset.py +++ b/src/caustics/data/hdf5dataset.py @@ -22,11 +22,17 @@ def __init__( dtypes: Union[Dict[str, torch.dtype], torch.dtype] = torch.float32, ): """ - Args: - path: location of dataset. - keys: dataset keys to read. - dtypes: either a numpy datatype to which the items will be converted - or a dictionary specifying the datatype corresponding to each key. + Parameters + ---------- + path: string + location of dataset. + keys: List[str] + dataset keys to read. + device: torch.deviec + the device for torch + dtypes: torch.dtype + either a numpy datatype to which the items will be converted + or a dictionary specifying the datatype corresponding to each key. """ super().__init__() self.keys = keys diff --git a/src/caustics/data/probes.py b/src/caustics/data/probes.py index def03ef9..05df1304 100644 --- a/src/caustics/data/probes.py +++ b/src/caustics/data/probes.py @@ -24,6 +24,9 @@ def __len__(self): def __getitem__(self, i: Union[int, slice]) -> Tensor: """ - Returns image `i` with channel as first dimension. + Returns + ------- + Tensor + image `i` with channel as first dimension. """ return self.ds[i][self.key].movedim(-1, 0) diff --git a/src/caustics/light/base.py b/src/caustics/light/base.py index f6a0e915..e457a58f 100644 --- a/src/caustics/light/base.py +++ b/src/caustics/light/base.py @@ -28,21 +28,28 @@ def brightness( Abstract method that calculates the brightness of the source at the given coordinates. This method is expected to be implemented in any class that derives from Source. - Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. + Parameters + ---------- + x: Tensor + The x-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. + y: Tensor + The y-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. - params (Packed, optional): Dynamic parameter container that might be required to calculate - the brightness. The exact contents will depend on the specific implementation in derived classes. + params: (Packed, optional) + Dynamic parameter container that might be required to calculate + the brightness. The exact contents will depend on the specific implementation in derived classes. - Returns: - Tensor: The brightness of the source at the given coordinate(s). The exact form of the output + Returns + ------- + Tensor + The brightness of the source at the given coordinate(s). The exact form of the output will depend on the specific implementation in the derived class. - Note: + Note + ----- This method must be overridden in any class that inherits from `Source`. """ ... diff --git a/src/caustics/light/sersic.py b/src/caustics/light/sersic.py index 4e37263a..daf2d430 100644 --- a/src/caustics/light/sersic.py +++ b/src/caustics/light/sersic.py @@ -17,16 +17,26 @@ class Sersic(Source): The Sersic profile is often used to describe elliptical galaxies and spiral galaxies' bulges. - Attributes: - x0 (Optional[Tensor]): The x-coordinate of the Sersic source's center. - y0 (Optional[Tensor]): The y-coordinate of the Sersic source's center. - q (Optional[Tensor]): The axis ratio of the Sersic source. - phi (Optional[Tensor]): The orientation of the Sersic source (position angle). - n (Optional[Tensor]): The Sersic index, which describes the degree of concentration of the source. - Re (Optional[Tensor]): The scale length of the Sersic source. - Ie (Optional[Tensor]): The intensity at the effective radius. - s (float): A small constant for numerical stability. - lenstronomy_k_mode (bool): A flag indicating whether to use lenstronomy to compute the value of k. + Attributes + ----------- + x0: Optional[Tensor] + The x-coordinate of the Sersic source's center. + y0: Optional[Tensor] + The y-coordinate of the Sersic source's center. + q: Optional[Tensor] + The axis ratio of the Sersic source. + phi: Optional[Tensor] + The orientation of the Sersic source (position angle). + n: Optional[Tensor] + The Sersic index, which describes the degree of concentration of the source. + Re: Optional[Tensor] + The scale length of the Sersic source. + Ie: Optional[Tensor] + The intensity at the effective radius. + s: float + A small constant for numerical stability. + lenstronomy_k_mode: bool + A flag indicating whether to use lenstronomy to compute the value of k. """ def __init__( @@ -45,17 +55,28 @@ def __init__( """ Constructs the `Sersic` object with the given parameters. - Args: - name (str): The name of the source. - x0 (Optional[Tensor]): The x-coordinate of the Sersic source's center. - y0 (Optional[Tensor]): The y-coordinate of the Sersic source's center. - q (Optional[Tensor]): The axis ratio of the Sersic source. - phi (Optional[Tensor]): The orientation of the Sersic source. - n (Optional[Tensor]): The Sersic index, which describes the degree of concentration of the source. - Re (Optional[Tensor]): The scale length of the Sersic source. - Ie (Optional[Tensor]): The intensity at the effective radius. - s (float): A small constant for numerical stability. - use_lenstronomy_k (bool): A flag indicating whether to use lenstronomy to compute the value of k. + Parameters + ---------- + name: str + The name of the source. + x0: Optional[Tensor] + The x-coordinate of the Sersic source's center. + y0: Optional[Tensor] + The y-coordinate of the Sersic source's center. + q: Optional[Tensor]) + The axis ratio of the Sersic source. + phi: Optional[Tensor] + The orientation of the Sersic source. + n: Optional[Tensor] + The Sersic index, which describes the degree of concentration of the source. + Re: Optional[Tensor] + The scale length of the Sersic source. + Ie: Optional[Tensor] + The intensity at the effective radius. + s: float + A small constant for numerical stability. + use_lenstronomy_k: bool + A flag indicating whether to use lenstronomy to compute the value of k. """ super().__init__(name=name) self.add_param("x0", x0) @@ -89,17 +110,37 @@ def brightness( Implements the `brightness` method for `Sersic`. The brightness at a given point is determined by the Sersic profile formula. +<<<<<<< HEAD:src/caustics/light/sersic.py Args: x (Tensor): The x-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. y (Tensor): The y-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. - +======= + Parameters + ---------- + x: Tensor + The x-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + y: Tensor + The y-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + params: (Packed, optional) + Dynamic parameter container. +>>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py + + Returns + ------- + Tensor + The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. + +<<<<<<< HEAD:src/caustics/light/sersic.py Notes: +======= + Notes + ----- +>>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), where Ie is the intensity at the effective radius r_e, n is the Sersic index that describes the concentration of the source, and k is a parameter that From 7a308db736d11c5f17dcc0b02b9c557a4a473552 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:06:27 -0800 Subject: [PATCH 33/55] change to numpy doc string for base,py in lensors --- src/caustics/lenses/base.py | 377 +++++++++++++++++++++++++----------- 1 file changed, 259 insertions(+), 118 deletions(-) diff --git a/src/caustics/lenses/base.py b/src/caustics/lenses/base.py index 768205f1..d951e1d9 100644 --- a/src/caustics/lenses/base.py +++ b/src/caustics/lenses/base.py @@ -24,9 +24,12 @@ def __init__(self, cosmology: Cosmology, name: str = None): """ Initializes a new instance of the Lens class. - Args: - name (str): The name of the lens model. - cosmology (Cosmology): An instance of a Cosmology class that describes the cosmological parameters of the model. + Parameters + ---------- + name: string + The name of the lens model. + cosmology: Cosmology + An instance of a Cosmology class that describes the cosmological parameters of the model. """ super().__init__(name) self.cosmology = cosmology @@ -75,14 +78,21 @@ def magnification( """ Compute the gravitational magnification at the given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Gravitational magnification at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Gravitational magnification at the given coordinates. """ return get_magnification(partial(self.raytrace, params=params), x, y, z_s) @@ -102,20 +112,34 @@ def forward_raytrace( """ Perform a forward ray-tracing operation which maps from the source plane to the image plane. - Args: - bx (Tensor): Tensor of x coordinate in the source plane (scalar). - by (Tensor): Tensor of y coordinate in the source plane (scalar). - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - epsilon (Tensor): maximum distance between two images (arcsec) before they are considered the same image. - n_init (int): number of random initialization points used to try and find image plane points. - fov (float): the field of view in which the initial random samples are taken. - - Returns: - tuple[Tensor, Tensor]: Ray-traced coordinates in the x and y directions. - """ - + Parameters + ---------- + bx: Tensor + Tensor of x coordinate in the source plane (scalar). + by: Tensor + Tensor of y coordinate in the source plane (scalar). + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + epsilon: Tensor + maximum distance between two images (arcsec) before they are considered the same image. + n_init: int + number of random initialization points used to try and find image plane points. + fov: float + the field of view in which the initial random samples are taken. + + Returns + ------- + tuple[Tensor, Tensor] + Ray-traced coordinates in the x and y directions. + """ + +<<<<<<< HEAD:src/caustics/lenses/base.py bxy = torch.stack((bx, by)).repeat(n_init, 1) # has shape (n_init, Dout:2) +======= + bxy = torch.stack((bx, by)).repeat(n_init,1) # has shape (n_init, Dout:2) +>>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py # TODO make FOV more general so that it doesnt have to be centered on zero,zero if fov is None: @@ -150,13 +174,19 @@ def forward_raytrace( return res[..., 0], res[..., 1] +<<<<<<< HEAD:src/caustics/lenses/base.py +======= + +>>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py class ThickLens(Lens): """ Base class for modeling gravitational lenses that cannot be treated using the thin lens approximation. It is an abstract class and should be subclassed for different types of lens models. - Attributes: - cosmology (Cosmology): An instance of a Cosmology class that describes the cosmological parameters of the model. + Attributes + ---------- + cosmology: Cosmology + An instance of a Cosmology class that describes the cosmological parameters of the model. """ @unpack(3) @@ -171,15 +201,28 @@ def reduced_deflection_angle( ) -> tuple[Tensor, Tensor]: """ ThickLens objects do not have a reduced deflection angle since the distance D_ls is undefined +<<<<<<< HEAD:src/caustics/lenses/base.py Args: x (Tensor): Tensor of x coordinates in the lens plane. y (Tensor): Tensor of y coordinates in the lens plane. z_s (Tensor): Tensor of source redshifts. params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Raises: - NotImplementedError +======= +>>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py + + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Raises:: NotImplementedError """ warnings.warn( "ThickLens objects do not have a reduced deflection angle since they have no unique lens redshift. The distance D_{ls} is undefined in the equation $\alpha_{reduced} = \frac{D_{ls}}{D_s}\alpha_{physical}$. See `effective_reduced_deflection_angle`. Now using effective_reduced_deflection_angle, please switch functions to remove this warning" @@ -204,11 +247,24 @@ def effective_reduced_deflection_angle( angular coordinates, and $\beta$ are the angular coordinates to the source plane. +<<<<<<< HEAD:src/caustics/lenses/base.py Args: x (Tensor): Tensor of x coordinates in the lens plane. y (Tensor): Tensor of y coordinates in the lens plane. z_s (Tensor): Tensor of source redshifts. params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. +======= + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. +>>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py """ bx, by = self.raytrace(x, y, z_s, params) @@ -228,14 +284,21 @@ def physical_deflection_angle( plane. ThickLens objects have no unique definition of a lens plane and so cannot compute a physical_deflection_angle - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. - Returns: - tuple[Tensor, Tensor]: Tuple of Tensors representing the x and y components of the deflection angle, respectively. + Returns + ------- + tuple[Tensor, Tensor] + Tuple of Tensors representing the x and y components of the deflection angle, respectively. """ raise NotImplementedError( @@ -257,13 +320,19 @@ def raytrace( source plance associated with a given input observed angular coordinate x,y. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- tuple[Tensor, Tensor]: Tuple of Tensors representing the x and y coordinates of the ray-traced light rays, respectively. """ @@ -283,14 +352,21 @@ def surface_density( """ Computes the projected mass density at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: The projected mass density at the given coordinates in units of solar masses per square Megaparsec. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + The projected mass density at the given coordinates in units of solar masses per square Megaparsec. """ ... @@ -308,14 +384,21 @@ def time_delay( """ Computes the gravitational time delay at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor ofsource redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: The gravitational time delay at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor ofsource redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + The gravitational time delay at the given coordinates. """ ... @@ -490,10 +573,14 @@ class ThinLens(Lens): lensing quantities such as the deflection angle, convergence, potential, surface mass density, and gravitational time delay. - Args: - name (str): Name of the lens model. - cosmology (Cosmology): Cosmology object that encapsulates cosmological parameters and distances. - z_l (Optional[Tensor], optional): Redshift of the lens. Defaults to None. + Attributes + ---------- + name: string + Name of the lens model. + cosmology: Cosmology + Cosmology object that encapsulates cosmological parameters and distances. + z_l: (Optional[Tensor], optional) + Redshift of the lens. Defaults to None. """ @@ -520,14 +607,21 @@ def reduced_deflection_angle( """ Computes the reduced deflection angle of the lens at given coordinates [arcsec]. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - tuple[Tensor, Tensor]: Reduced deflection angle in x and y directions. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + -------- + tuple[Tensor, Tensor] + Reduced deflection angle in x and y directions. """ d_s = self.cosmology.angular_diameter_distance(z_s, params) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s, params) @@ -550,14 +644,21 @@ def physical_deflection_angle( """ Computes the physical deflection angle immediately after passing through this lens's plane. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - tuple[Tensor, Tensor]: Physical deflection angle in x and y directions in arcseconds. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + tuple[Tensor, Tensor] + Physical deflection angle in x and y directions in arcseconds. """ d_s = self.cosmology.angular_diameter_distance(z_s, params) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s, params) @@ -580,14 +681,21 @@ def convergence( """ Computes the convergence of the lens at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Convergence at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Convergence at the given coordinates. """ ... @@ -605,13 +713,21 @@ def potential( """ Computes the gravitational lensing potential at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: Tensor: Gravitational lensing potential at the given coordinates in arcsec^2. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Gravitational lensing potential at the given coordinates in arcsec^2. """ ... @@ -629,14 +745,21 @@ def surface_density( """ Computes the surface mass density of the lens at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Surface mass density at the given coordinates in solar masses per Mpc^2. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Surface mass density at the given coordinates in solar masses per Mpc^2. """ critical_surface_density = self.cosmology.critical_surface_density( z_l, z_s, params @@ -656,14 +779,21 @@ def raytrace( """ Perform a ray-tracing operation by subtracting the deflection angles from the input coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - tuple[Tensor, Tensor]: Ray-traced coordinates in the x and y directions. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + tuple[Tensor, Tensor] + Ray-traced coordinates in the x and y directions. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) return x - ax, y - ay @@ -682,14 +812,21 @@ def time_delay( """ Compute the gravitational time delay for light passing through the lens at given coordinates. - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. - - Returns: - Tensor: Time delay at the given coordinates. + Parameters + ---------- + x: Tensor + Tensor of x coordinates in the lens plane. + y: Tensor + Tensor of y coordinates in the lens plane. + z_s: Tensor + Tensor of source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. Defaults to None. + + Returns + ------- + Tensor + Time delay at the given coordinates. """ d_l = self.cosmology.angular_diameter_distance(z_l, params) d_s = self.cosmology.angular_diameter_distance(z_s, params) @@ -826,3 +963,7 @@ def _jacobian_lens_equation_autograd( # Build Jacobian J = self._jacobian_deflection_angle_autograd(x, y, z_s, params, **kwargs) return torch.eye(2) - J.detach() +<<<<<<< HEAD:src/caustics/lenses/base.py +======= + +>>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py From 56055530b57589e6de048f2e8eca36e0e146e94d Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:39:30 -0800 Subject: [PATCH 34/55] change to numpy docstring for files in lenses --- src/caustics/lenses/epl.py | 18 ++-- src/caustics/lenses/external_shear.py | 93 +++++++++++++------- src/caustics/lenses/mass_sheet.py | 43 +++++++--- src/caustics/lenses/multiplane.py | 117 +++++++++++++++++--------- src/caustics/lenses/point.py | 117 +++++++++++++++++--------- src/caustics/lenses/singleplane.py | 81 ++++++++++++------ src/caustics/lenses/sis.py | 90 +++++++++++++------- src/caustics/lenses/utils.py | 64 +++++++++----- 8 files changed, 408 insertions(+), 215 deletions(-) diff --git a/src/caustics/lenses/epl.py b/src/caustics/lenses/epl.py index 4945527e..eccfbf6d 100644 --- a/src/caustics/lenses/epl.py +++ b/src/caustics/lenses/epl.py @@ -18,13 +18,17 @@ class EPL(ThinLens): This class represents a thin gravitational lens model with an elliptical power law profile. The lensing equations are solved iteratively using an approach based on Tessore et al. 2015. - Attributes: - n_iter (int): Number of iterations for the iterative solver. - s (float): Softening length for the elliptical power-law profile. - - Parameters: - z_l (Optional[Union[Tensor, float]]): This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. - x0 and y0 (Optional[Union[Tensor, float]]): These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. + Attributes + ---------- + n_iter: int + Number of iterations for the iterative solver. + s: float + Softening length for the elliptical power-law profile. + + Parameters + ---------- + z_l (Optional[Union[Tensor, float]]): This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. + x0 and y0 (Optional[Union[Tensor, float]]): These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. q (Optional[Union[Tensor, float]]): This is the axis ratio of the lens, i.e., the ratio of the minor axis to the major axis of the elliptical lens. phi (Optional[Union[Tensor, float]]): This is the orientation of the lens on the sky, typically given as an angle measured counter-clockwise from some reference direction. b (Optional[Union[Tensor, float]]): This is the scale length of the lens, which sets the overall scale of the lensing effect. In some contexts, this is referred to as the Einstein radius. diff --git a/src/caustics/lenses/external_shear.py b/src/caustics/lenses/external_shear.py index b1f41450..a2276b14 100644 --- a/src/caustics/lenses/external_shear.py +++ b/src/caustics/lenses/external_shear.py @@ -14,14 +14,22 @@ class ExternalShear(ThinLens): """ Represents an external shear effect in a gravitational lensing system. - Attributes: - name (str): Identifier for the lens instance. - cosmology (Cosmology): The cosmological model used for lensing calculations. - z_l (Optional[Union[Tensor, float]]): The redshift of the lens. - x0, y0 (Optional[Union[Tensor, float]]): Coordinates of the shear center in the lens plane. - gamma_1, gamma_2 (Optional[Union[Tensor, float]]): Shear components. - - Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational + Attributes + ---------- + name: str + Identifier for the lens instance. + cosmology: Cosmology + The cosmological model used for lensing calculations. + z_l: Optional[Union[Tensor, float]] + The redshift of the lens. + x0, y0: Optional[Union[Tensor, float]] + Coordinates of the shear center in the lens plane. + gamma_1, gamma_2: Optional[Union[Tensor, float]] + Shear components. + + Notes + ------ + The shear components gamma_1 and gamma_2 represent an external shear, a gravitational distortion that can be caused by nearby structures outside of the main lens galaxy. """ @@ -62,14 +70,21 @@ def reduced_deflection_angle( """ Calculates the reduced deflection angle. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The reduced deflection angles in the x and y directions. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) # Meneghetti eq 3.83 @@ -100,14 +115,21 @@ def potential( """ Calculates the lensing potential. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) x, y = translate_rotate(x, y, x0, y0) @@ -131,14 +153,21 @@ def convergence( """ The convergence is undefined for an external shear. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Raises: - NotImplementedError: This method is not implemented as the convergence is not defined + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Raises + ------ + NotImplementedError + This method is not implemented as the convergence is not defined for an external shear. """ raise NotImplementedError("convergence undefined for external shear") diff --git a/src/caustics/lenses/mass_sheet.py b/src/caustics/lenses/mass_sheet.py index 4e8e5a58..1e7a79ea 100644 --- a/src/caustics/lenses/mass_sheet.py +++ b/src/caustics/lenses/mass_sheet.py @@ -15,14 +15,22 @@ class MassSheet(ThinLens): """ Represents an external shear effect in a gravitational lensing system. - Attributes: - name (str): Identifier for the lens instance. - cosmology (Cosmology): The cosmological model used for lensing calculations. - z_l (Optional[Union[Tensor, float]]): The redshift of the lens. - x0, y0 (Optional[Union[Tensor, float]]): Coordinates of the shear center in the lens plane. - gamma_1, gamma_2 (Optional[Union[Tensor, float]]): Shear components. + Attributes + ---------- + name: string + Identifier for the lens instance. + cosmology: Cosmology + The cosmological model used for lensing calculations. + z_l: Optional[Union[Tensor, float]] + The redshift of the lens. + x0, y0: Optional[Union[Tensor, float]] + Coordinates of the shear center in the lens plane. + gamma_1, gamma_2: Optional[Union[Tensor, float]] + Shear components. - Note: The shear components gamma_1 and gamma_2 represent an external shear, a gravitational + Notes + ------ + The shear components gamma_1 and gamma_2 represent an external shear, a gravitational distortion that can be caused by nearby structures outside of the main lens galaxy. """ @@ -58,14 +66,21 @@ def reduced_deflection_angle( """ Calculates the reduced deflection angle. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. - Returns: - tuple[Tensor, Tensor]: The reduced deflection angles in the x and y directions. + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) # Meneghetti eq 3.84 diff --git a/src/caustics/lenses/multiplane.py b/src/caustics/lenses/multiplane.py index e24ae2da..7c6b3491 100644 --- a/src/caustics/lenses/multiplane.py +++ b/src/caustics/lenses/multiplane.py @@ -16,13 +16,19 @@ class Multiplane(ThickLens): """ Class for handling gravitational lensing with multiple lens planes. - Attributes: - lenses (list[ThinLens]): List of thin lenses. - - Args: - name (str): Name of the lens. - cosmology (Cosmology): Cosmological parameters used for calculations. - lenses (list[ThinLens]): List of thin lenses. + Attributes + ---------- + lenses (list[ThinLens]) + List of thin lenses. + + Parameters + ---------- + name: string + Name of the lens. + cosmology: Cosmology + Cosmological parameters used for calculations. + lenses: list[ThinLens] + List of thin lenses. """ def __init__(self, cosmology: Cosmology, lenses: list[ThinLens], name: str = None): @@ -38,11 +44,15 @@ def get_z_ls( """ Get the redshifts of each lens in the multiplane. - Args: - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + params: (Packed, optional) + Dynamic parameter container. - Returns: - List[Tensor]: Redshifts of the lenses. + Returns + -------- + List[Tensor] + Redshifts of the lenses. """ # Relies on z_l being the first element to be unpacked, which should always # be the case for a ThinLens @@ -78,14 +88,21 @@ def raytrace( Here we set as initialization :math:`\vec{\theta}^0 = theta` the observation angular coordinates and :math:`\vec{x}^0 = 0` the initial physical coordinates (i.e. the observation rays come from a point at the observer). The indexing of :math:`\vec{x}^i` and :math:`\vec{\theta}^i` indicates the properties at the plane :math:`i`, and 0 means the observer, 1 is the first lensing plane (infinitesimally after the plane since the deflection has been applied), and so on. Note that in the actual implementation we start at :math:`\vec{x}^1` and :math:`\vec{\theta}^0` and begin at the second step in the recursion formula. - Args: - x (Tensor): angular x-coordinates from the observer perspective. - y (Tensor): angular y-coordinates from the observer perspective. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The reduced deflection angle. + Parameters + ---------- + x: Tensor + angular x-coordinates from the observer perspective. + y: Tensor + angular y-coordinates from the observer perspective. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angle. """ return self.raytrace_z1z2(x, y, torch.zeros_like(z_s), z_s, params) @@ -172,17 +189,26 @@ def surface_density( """ Calculate the projected mass density. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: Projected mass density [solMass / Mpc^2]. - - Raises: - NotImplementedError: This method is not yet implemented. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + Projected mass density [solMass / Mpc^2]. + + Raises + ------- + NotImplementedError + This method is not yet implemented. """ # TODO: rescale mass densities of each lens and sum raise NotImplementedError() @@ -200,17 +226,26 @@ def time_delay( """ Compute the time delay of light caused by the lensing. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: Time delay caused by the lensing. - - Raises: - NotImplementedError: This method is not yet implemented. + Parameters: + ----- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + Time delay caused by the lensing. + + Raises + ------ + NotImplementedError + This method is not yet implemented. """ # TODO: figure out how to compute this raise NotImplementedError() diff --git a/src/caustics/lenses/point.py b/src/caustics/lenses/point.py index a0efca85..0822c1ff 100644 --- a/src/caustics/lenses/point.py +++ b/src/caustics/lenses/point.py @@ -15,14 +15,22 @@ class Point(ThinLens): """ Class representing a point mass lens in strong gravitational lensing. - Attributes: - name (str): The name of the point lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Union[Tensor, float]]): Redshift of the lens. - x0 (Optional[Union[Tensor, float]]): x-coordinate of the center of the lens. - y0 (Optional[Union[Tensor, float]]): y-coordinate of the center of the lens. - th_ein (Optional[Union[Tensor, float]]): Einstein radius of the lens. - s (float): Softening parameter to prevent numerical instabilities. + Attributes + ---------- + name: str + The name of the point lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Union[Tensor, float]] + Redshift of the lens. + x0: Optional[Union[Tensor, float]] + x-coordinate of the center of the lens. + y0: Optional[Union[Tensor, float]] + y-coordinate of the center of the lens. + th_ein: Optional[Union[Tensor, float]] + Einstein radius of the lens. + s: float + Softening parameter to prevent numerical instabilities. """ def __init__( @@ -38,14 +46,22 @@ def __init__( """ Initialize the Point class. - Args: - name (str): The name of the point lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Tensor]): Redshift of the lens. - x0 (Optional[Tensor]): x-coordinate of the center of the lens. - y0 (Optional[Tensor]): y-coordinate of the center of the lens. - th_ein (Optional[Tensor]): Einstein radius of the lens. - s (float): Softening parameter to prevent numerical instabilities. + Parameters + ---------- + name: string + The name of the point lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Tensor] + Redshift of the lens. + x0: Optional[Tensor] + x-coordinate of the center of the lens. + y0: Optional[Tensor] + y-coordinate of the center of the lens. + th_ein: Optional[Tensor] + Einstein radius of the lens. + s: float + Softening parameter to prevent numerical instabilities. """ super().__init__(cosmology, z_l, name=name) @@ -71,14 +87,21 @@ def reduced_deflection_angle( """ Compute the deflection angles. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The deflection angles in the x and y directions. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -103,14 +126,21 @@ def potential( """ Compute the lensing potential. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -133,14 +163,21 @@ def convergence( """ Compute the convergence (dimensionless surface mass density). - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The convergence (dimensionless surface mass density). + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + -------- + Tensor + The convergence (dimensionless surface mass density). """ x, y = translate_rotate(x, y, x0, y0) return torch.where((x == 0) & (y == 0), torch.inf, 0.0) diff --git a/src/caustics/lenses/singleplane.py b/src/caustics/lenses/singleplane.py index 7c6ff11a..577d336e 100644 --- a/src/caustics/lenses/singleplane.py +++ b/src/caustics/lenses/singleplane.py @@ -15,10 +15,14 @@ class SinglePlane(ThinLens): A class for combining multiple thin lenses into a single lensing plane. This model inherits from the base `ThinLens` class. - Attributes: - name (str): The name of the single plane lens. - cosmology (Cosmology): An instance of the Cosmology class. - lenses (List[ThinLens]): A list of ThinLens objects that are being combined into a single lensing plane. + Attributes + ---------- + name: str + The name of the single plane lens. + cosmology: Cosmology + An instance of the Cosmology class. + lenses: List[ThinLens] + A list of ThinLens objects that are being combined into a single lensing plane. """ def __init__( @@ -46,14 +50,21 @@ def reduced_deflection_angle( """ Calculate the total deflection angle by summing the deflection angles of all individual lenses. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tuple[Tensor, Tensor]: The total deflection angle in the x and y directions. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tuple[Tensor, Tensor] + The total deflection angle in the x and y directions. """ ax = torch.zeros_like(x) ay = torch.zeros_like(x) @@ -76,14 +87,21 @@ def convergence( """ Calculate the total projected mass density by summing the mass densities of all individual lenses. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The total projected mass density. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The total projected mass density. """ convergence = torch.zeros_like(x) for lens in self.lenses: @@ -104,14 +122,21 @@ def potential( """ Compute the total lensing potential by summing the lensing potentials of all individual lenses. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The total lensing potential. + Parameters + ----------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The total lensing potential. """ potential = torch.zeros_like(x) for lens in self.lenses: diff --git a/src/caustics/lenses/sis.py b/src/caustics/lenses/sis.py index 244fe6ea..bb72118f 100644 --- a/src/caustics/lenses/sis.py +++ b/src/caustics/lenses/sis.py @@ -15,14 +15,21 @@ class SIS(ThinLens): A class representing the Singular Isothermal Sphere (SIS) model. This model inherits from the base `ThinLens` class. - Attributes: - name (str): The name of the SIS lens. - cosmology (Cosmology): An instance of the Cosmology class. - z_l (Optional[Union[Tensor, float]]): The lens redshift. - x0 (Optional[Union[Tensor, float]]): The x-coordinate of the lens center. - y0 (Optional[Union[Tensor, float]]): The y-coordinate of the lens center. + Attributes + ---------- + name: str + The name of the SIS lens. + cosmology: Cosmology + An instance of the Cosmology class. + z_l: Optional[Union[Tensor, float]] + The lens redshift. + x0: Optional[Union[Tensor, float]] + The x-coordinate of the lens center. + y0: Optional[Union[Tensor, float]] + The y-coordinate of the lens center. th_ein (Optional[Union[Tensor, float]]): The Einstein radius of the lens. - s (float): A smoothing factor, default is 0.0. + s: float + A smoothing factor, default is 0.0. """ def __init__( @@ -62,14 +69,21 @@ def reduced_deflection_angle( """ Calculate the deflection angle of the SIS lens. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tuple[Tensor, Tensor]: The deflection angle in the x and y directions. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tuple[Tensor, Tensor] + The deflection angle in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) R = (x**2 + y**2).sqrt() + self.s @@ -94,14 +108,21 @@ def potential( """ Compute the lensing potential of the SIS lens. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -124,14 +145,21 @@ def convergence( """ Calculate the projected mass density of the SIS lens. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The projected mass density. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The projected mass density. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s diff --git a/src/caustics/lenses/utils.py b/src/caustics/lenses/utils.py index 4ccb2b54..3d4d52ff 100644 --- a/src/caustics/lenses/utils.py +++ b/src/caustics/lenses/utils.py @@ -16,14 +16,20 @@ def get_pix_jacobian( (:math:`\\partial \beta / \\partial \theta`). This is done at a single point on the lensing plane. - Args: - raytrace: A function that maps the lensing plane coordinates to the source plane coordinates. - x (Tensor): The x-coordinate on the lensing plane. - y (Tensor): The y-coordinate on the lensing plane. - z_s (Tensor): The redshift of the source. - - Returns: - The Jacobian matrix of the image position with respect to the source position at the given point. + Parameters + ----------- + raytrace: function + A function that maps the lensing plane coordinates to the source plane coordinates. + x: Tensor + The x-coordinate on the lensing plane. + y: Tensor + The y-coordinate on the lensing plane. + z_s: Tensor + The redshift of the source. + + Returns + -------- + The Jacobian matrix of the image position with respect to the source position at the given point. """ jac = torch.func.jacfwd(raytrace, (0, 1))(x, y, z_s) # type: ignore @@ -35,13 +41,20 @@ def get_pix_magnification(raytrace, x, y, z_s) -> Tensor: Computes the magnification at a single point on the lensing plane. The magnification is derived from the determinant of the Jacobian matrix of the image position with respect to the source position. - Args: - raytrace: A function that maps the lensing plane coordinates to the source plane coordinates. - x (Tensor): The x-coordinate on the lensing plane. - y (Tensor): The y-coordinate on the lensing plane. - z_s (Tensor): The redshift of the source. - - Returns: + Parameters + ---------- + raytrace: function + A function that maps the lensing plane coordinates to the source plane coordinates. + x: Tensor + The x-coordinate on the lensing plane. + y: Tensor + The y-coordinate on the lensing plane. + z_s: Tensor + The redshift of the source. + + Returns + ------- + Tensor The magnification at the given point on the lensing plane. """ jac = get_pix_jacobian(raytrace, x, y, z_s) @@ -53,13 +66,20 @@ def get_magnification(raytrace, x, y, z_s) -> Tensor: Computes the magnification over a grid on the lensing plane. This is done by calling `get_pix_magnification` for each point on the grid. - Args: - raytrace: A function that maps the lensing plane coordinates to the source plane coordinates. - x (Tensor): The x-coordinates on the lensing plane. - y (Tensor): The y-coordinates on the lensing plane. - z_s (Tensor): The redshift of the source. - - Returns: + Parameters + ---------- + raytrace: function + A function that maps the lensing plane coordinates to the source plane coordinates. + x: Tensor + The x-coordinates on the lensing plane. + y: Tensor + The y-coordinates on the lensing plane. + z_s: Tensor + The redshift of the source. + + Returns + -------- + Tensor A tensor representing the magnification at each point on the grid. """ return vmap_n(get_pix_magnification, 2, (None, 0, 0, None))(raytrace, x, y, z_s) From 65fb9aa0eb075a0ae83b2c341318b70bdef4431a Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:45:38 -0800 Subject: [PATCH 35/55] update epl.py --- src/caustics/lenses/epl.py | 135 ++++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 45 deletions(-) diff --git a/src/caustics/lenses/epl.py b/src/caustics/lenses/epl.py index eccfbf6d..378bf97f 100644 --- a/src/caustics/lenses/epl.py +++ b/src/caustics/lenses/epl.py @@ -27,12 +27,18 @@ class EPL(ThinLens): Parameters ---------- - z_l (Optional[Union[Tensor, float]]): This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. - x0 and y0 (Optional[Union[Tensor, float]]): These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. - q (Optional[Union[Tensor, float]]): This is the axis ratio of the lens, i.e., the ratio of the minor axis to the major axis of the elliptical lens. - phi (Optional[Union[Tensor, float]]): This is the orientation of the lens on the sky, typically given as an angle measured counter-clockwise from some reference direction. - b (Optional[Union[Tensor, float]]): This is the scale length of the lens, which sets the overall scale of the lensing effect. In some contexts, this is referred to as the Einstein radius. - t (Optional[Union[Tensor, float]]): This is the power-law slope parameter of the lens model. In the context of the EPL model, t is equivalent to the gamma parameter minus one, where gamma is the power-law index of the radial mass distribution of the lens. + z_l: Optional[Union[Tensor, float]] + This is the redshift of the lens. In the context of gravitational lensing, the lens is the galaxy or other mass distribution that is bending the light from a more distant source. + x0 and y0: Optional[Union[Tensor, float]] + These are the coordinates of the lens center in the lens plane. The lens plane is the plane perpendicular to the line of sight in which the deflection of light by the lens is considered. + q: Optional[Union[Tensor, float]] + This is the axis ratio of the lens, i.e., the ratio of the minor axis to the major axis of the elliptical lens. + phi: Optional[Union[Tensor, float]] + This is the orientation of the lens on the sky, typically given as an angle measured counter-clockwise from some reference direction. + b: Optional[Union[Tensor, float]] + This is the scale length of the lens, which sets the overall scale of the lensing effect. In some contexts, this is referred to as the Einstein radius. + t: Optional[Union[Tensor, float]] + This is the power-law slope parameter of the lens model. In the context of the EPL model, t is equivalent to the gamma parameter minus one, where gamma is the power-law index of the radial mass distribution of the lens. """ @@ -53,18 +59,30 @@ def __init__( """ Initialize an EPL lens model. - Args: - name (str): Name of the lens model. - cosmology (Cosmology): Cosmology object that provides cosmological distance calculations. - z_l (Optional[Tensor]): Redshift of the lens. If not provided, it is considered as a free parameter. - x0 (Optional[Tensor]): X coordinate of the lens center. If not provided, it is considered as a free parameter. - y0 (Optional[Tensor]): Y coordinate of the lens center. If not provided, it is considered as a free parameter. - q (Optional[Tensor]): Axis ratio of the lens. If not provided, it is considered as a free parameter. - phi (Optional[Tensor]): Position angle of the lens. If not provided, it is considered as a free parameter. - b (Optional[Tensor]): Scale length of the lens. If not provided, it is considered as a free parameter. - t (Optional[Tensor]): Power law slope (`gamma-1`) of the lens. If not provided, it is considered as a free parameter. - s (float): Softening length for the elliptical power-law profile. - n_iter (int): Number of iterations for the iterative solver. + Parameters + ----------- + name: string + Name of the lens model. + cosmology: Cosmology + Cosmology object that provides cosmological distance calculations. + z_l: Optional[Tensor] + Redshift of the lens. If not provided, it is considered as a free parameter. + x0: Optional[Tensor] + X coordinate of the lens center. If not provided, it is considered as a free parameter. + y0: Optional[Tensor] + Y coordinate of the lens center. If not provided, it is considered as a free parameter. + q: Optional[Tensor] + Axis ratio of the lens. If not provided, it is considered as a free parameter. + phi: Optional[Tensor] + Position angle of the lens. If not provided, it is considered as a free parameter. + b: Optional[Tensor] + Scale length of the lens. If not provided, it is considered as a free parameter. + t: Optional[Tensor] + Power law slope (`gamma-1`) of the lens. If not provided, it is considered as a free parameter. + s: float + Softening length for the elliptical power-law profile. + n_iter: int + Number of iterations for the iterative solver. """ super().__init__(cosmology, z_l, name=name) @@ -98,14 +116,21 @@ def reduced_deflection_angle( """ Compute the reduced deflection angles of the lens. - Args: - x (Tensor): X coordinates in the lens plane. - y (Tensor): Y coordinates in the lens plane. - z_s (Tensor): Source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. + Parameters + ---------- + x: Tensor + X coordinates in the lens plane. + y: Tensor + Y coordinates in the lens plane. + z_s: Tensor + Source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. - Returns: - tuple[Tensor, Tensor]: Reduced deflection angles in the x and y directions. + Returns + -------- + tuple[Tensor, Tensor] + Reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0, phi) @@ -125,13 +150,19 @@ def _r_omega(self, z, t, q): """ Iteratively computes `R * omega(phi)` (eq. 23 in Tessore et al 2015). - Args: - z (Tensor): `R * e^(i * phi)`, position vector in the lens plane. - t (Tensor): Power law slow (`gamma-1`). - q (Tensor): Axis ratio. + Parameters + ---------- + z: Tensor + `R * e^(i * phi)`, position vector in the lens plane. + t: Tensor + Power law slow (`gamma-1`). + q: Tensor + Axis ratio. - Returns: - Tensor: The value of `R * omega(phi)`. + Returns + -------- + Tensor + The value of `R * omega(phi)`. """ # constants f = (1.0 - q) / (1.0 + q) @@ -168,14 +199,21 @@ def potential( """ Compute the lensing potential of the lens. - Args: - x (Tensor): X coordinates in the lens plane. - y (Tensor): Y coordinates in the lens plane. - z_s (Tensor): Source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. + Parameters + ---------- + x: Tensor + X coordinates in the lens plane. + y: Tensor + Y coordinates in the lens plane. + z_s: Tensor + Source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. - Returns: - Tensor: The lensing potential. + Returns + ------- + Tensor + The lensing potential. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) ax, ay = derotate(ax, ay, -phi) @@ -202,14 +240,21 @@ def convergence( """ Compute the convergence of the lens, which describes the local density of the lens. - Args: - x (Tensor): X coordinates in the lens plane. - y (Tensor): Y coordinates in the lens plane. - z_s (Tensor): Source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. + Parameters + ---------- + x: Tensor + X coordinates in the lens plane. + y: Tensor + Y coordinates in the lens plane. + z_s: Tensor + Source redshifts. + params: (Packed, optional) + Dynamic parameter container for the lens model. - Returns: - Tensor: The convergence of the lens. + Returns + ------- + Tensor + The convergence of the lens. """ x, y = translate_rotate(x, y, x0, y0, phi) psi = (q**2 * (x**2 + self.s**2) + y**2).sqrt() From eb9a6976a3886c88f8599660d675580d363efa90 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 13:51:04 -0800 Subject: [PATCH 36/55] update sie.py --- src/caustics/lenses/sie.py | 119 ++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 41 deletions(-) diff --git a/src/caustics/lenses/sie.py b/src/caustics/lenses/sie.py index f5bcd917..5e9e50b5 100644 --- a/src/caustics/lenses/sie.py +++ b/src/caustics/lenses/sie.py @@ -15,16 +15,26 @@ class SIE(ThinLens): A class representing a Singular Isothermal Ellipsoid (SIE) strong gravitational lens model. This model is based on Keeton 2001, which can be found at https://arxiv.org/abs/astro-ph/0102341. - Attributes: - name (str): The name of the lens. - cosmology (Cosmology): An instance of the Cosmology class. - z_l (Optional[Union[Tensor, float]]): The redshift of the lens. - x0 (Optional[Union[Tensor, float]]): The x-coordinate of the lens center. - y0 (Optional[Union[Tensor, float]]): The y-coordinate of the lens center. - q (Optional[Union[Tensor, float]]): The axis ratio of the lens. - phi (Optional[Union[Tensor, float]]): The orientation angle of the lens (position angle). - b (Optional[Union[Tensor, float]]): The Einstein radius of the lens. - s (float): The core radius of the lens (defaults to 0.0). + Attributes + ---------- + name: str + The name of the lens. + cosmology: Cosmology + An instance of the Cosmology class. + z_l: Optional[Union[Tensor, float]] + The redshift of the lens. + x0: Optional[Union[Tensor, float]] + The x-coordinate of the lens center. + y0: Optional[Union[Tensor, float]] + The y-coordinate of the lens center. + q: Optional[Union[Tensor, float]] + The axis ratio of the lens. + phi: Optional[Union[Tensor, float]] + The orientation angle of the lens (position angle). + b: Optional[Union[Tensor, float]] + The Einstein radius of the lens. + s: float + The core radius of the lens (defaults to 0.0). """ def __init__( @@ -55,13 +65,19 @@ def _get_potential(self, x, y, q): """ Compute the radial coordinate in the lens plane. - Args: - x (Tensor): The x-coordinate in the lens plane. - y (Tensor): The y-coordinate in the lens plane. - q (Tensor): The axis ratio of the lens. - - Returns: - Tensor: The radial coordinate in the lens plane. + Parameters + ---------- + x: Tensor + The x-coordinate in the lens plane. + y: Tensor + The y-coordinate in the lens plane. + q: Tensor + The axis ratio of the lens. + + Returns + -------- + Tensor + The radial coordinate in the lens plane. """ return (q**2 * (x**2 + self.s**2) + y**2).sqrt() @@ -84,14 +100,21 @@ def reduced_deflection_angle( """ Calculate the physical deflection angle. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tuple[Tensor, Tensor]: The deflection angle in the x and y directions. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + -------- + Tuple[Tensor, Tensor] + The deflection angle in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0, phi) psi = self._get_potential(x, y, q) @@ -120,14 +143,21 @@ def potential( """ Compute the lensing potential. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ ax, ay = self.reduced_deflection_angle(x, y, z_s, params) ax, ay = derotate(ax, ay, -phi) @@ -153,14 +183,21 @@ def convergence( """ Calculate the projected mass density. - Args: - x (Tensor): The x-coordinate of the lens. - y (Tensor): The y-coordinate of the lens. - z_s (Tensor): The source redshift. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The projected mass. + Parameters + ---------- + x: Tensor + The x-coordinate of the lens. + y: Tensor + The y-coordinate of the lens. + z_s: Tensor + The source redshift. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The projected mass. """ x, y = translate_rotate(x, y, x0, y0, phi) psi = self._get_potential(x, y, q) From 06557cb0c28dad4c2ecc84e1b6447e53595c768f Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 14:02:58 -0800 Subject: [PATCH 37/55] update tnfw.py --- src/caustics/lenses/tnfw.py | 309 +++++++++++++++++++++++------------- 1 file changed, 201 insertions(+), 108 deletions(-) diff --git a/src/caustics/lenses/tnfw.py b/src/caustics/lenses/tnfw.py index 093ee53f..1132448d 100644 --- a/src/caustics/lenses/tnfw.py +++ b/src/caustics/lenses/tnfw.py @@ -29,7 +29,8 @@ class TNFW(ThinLens): https://ui.adsabs.harvard.edu/abs/2009JCAP...01..015B/abstract - Note: + Notes + ------ The mass `m` in the TNFW profile corresponds to the total mass of the lens. This is different from the NFW profile where the mass `m` parameter corresponds to the mass within R200. If you @@ -39,25 +40,37 @@ class TNFW(ThinLens): NFW profile, not a TNFW profile. This is in line with how lenstronomy inteprets the mass parameter. - Args: - name (str): Name of the lens instance. - cosmology (Cosmology): An instance of the Cosmology class which contains - information about the cosmological model and parameters. - z_l (Optional[Tensor]): Redshift of the lens. - x0 (Optional[Tensor]): Center of lens position on x-axis (arcsec). - y0 (Optional[Tensor]): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - s (float): Softening parameter to avoid singularities at the center of the lens. - Default is 0.0. - interpret_m_total_mass (bool): Indicates how to interpret the mass variable "m". If true - the mass is intepreted as the total mass of the halo (good because it makes sense). If - false it is intepreted as what the mass would have been within R200 of a an NFW that - isn't truncated (good because it is easily compared with an NFW). - use_case (str): Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile - specifically cant be both batchable and differentiable. You may select which version - you wish to use by setting this parameter to one of: batchable, differentiable. + Parameters + ----- + name: string + Name of the lens instance. + cosmology: Cosmology + An instance of the Cosmology class which contains + information about the cosmological model and parameters. + z_l: Optional[Tensor] + Redshift of the lens. + x0: Optional[Tensor] + Center of lens position on x-axis (arcsec). + y0: Optional[Tensor] + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + s: float + Softening parameter to avoid singularities at the center of the lens. + Default is 0.0. + interpret_m_total_mass: boolean + Indicates how to interpret the mass variable "m". If true + the mass is intepreted as the total mass of the halo (good because it makes sense). If + false it is intepreted as what the mass would have been within R200 of a an NFW that + isn't truncated (good because it is easily compared with an NFW). + use_case: str + Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile + specifically cant be both batchable and differentiable. You may select which version + you wish to use by setting this parameter to one of: batchable, differentiable. """ @@ -143,17 +156,27 @@ def get_concentration( """ Calculate the scale radius of the lens. This is the same formula used for the classic NFW profile. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The scale radius of the lens in Mpc. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + ------- + Tensor + The scale radius of the lens in Mpc. """ critical_density = self.cosmology.critical_density(z_l, params) d_l = self.cosmology.angular_diameter_distance(z_l, params) @@ -176,17 +199,27 @@ def get_truncation_radius( """ Calculate the truncation radius of the TNFW lens. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The truncation radius of the lens in arcsec. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dictionary + Dynamic parameter container. + + Returns + ------- + Tensor + The truncation radius of the lens in arcsec. """ return tau * scale_radius @@ -206,17 +239,27 @@ def get_M0( """ Calculate the reference mass. This is an abstract reference mass used internally in the equations from Baltz et al. 2009. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The reference mass of the lens in Msol. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dictionary + Dynamic parameter container. + + Returns + ------- + Tensor + The reference mass of the lens in Msol. """ if self.interpret_m_total_mass: return ( @@ -252,17 +295,27 @@ def get_scale_density( """ Calculate the scale density of the lens. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The scale density of the lens in solar masses per Mpc cubed. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + -------- + Tensor + The scale density of the lens in solar masses per Mpc cubed. """ c = self.get_concentration(params) return ( @@ -292,17 +345,27 @@ def convergence( """ TNFW convergence as given in Baltz et al. 2009. This is unitless since it is Sigma(x) / Sigma_crit. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: unitless convergence at requested position + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + --------- + Tensor + unitless convergence at requested position """ x, y = translate_rotate(x, y, x0, y0) @@ -343,17 +406,27 @@ def mass_enclosed_2d( """ Total projected mass (Msol) within a radius r (arcsec). - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: Integrated mass projected in infinite cylinder within radius r. + Parameters + ----------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + ------- + Tensor + Integrated mass projected in infinite cylinder within radius r. """ g = r / scale_radius t2 = tau**2 @@ -388,17 +461,27 @@ def physical_deflection_angle( naturally represented as a physical deflection angle, this is easily internally converted to a reduced deflection angle. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The physical deflection angles in the x and y directions (arcsec). + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + -------- + tuple[Tensor, Tensor] + The physical deflection angles in the x and y directions (arcsec). """ d_l = self.cosmology.angular_diameter_distance(z_l, params) @@ -434,17 +517,27 @@ def potential( TODO: convert to dimensionless potential. - Args: - z_l (Tensor): Redshift of the lens. - x0 (Tensor): Center of lens position on x-axis (arcsec). - y0 (Tensor): Center of lens position on y-axis (arcsec). - mass (Optional[Tensor]): Mass of the lens (Msol). - scale_radius (Optional[Tensor]): Scale radius of the TNFW lens (arcsec). - tau (Optional[Tensor]): Truncation scale. Ratio of truncation radius to scale radius (rt/rs). - params (dict): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ----------- + z_l: Tensor + Redshift of the lens. + x0: Tensor + Center of lens position on x-axis (arcsec). + y0: Tensor + Center of lens position on y-axis (arcsec). + mass: Optional[Tensor] + Mass of the lens (Msol). + scale_radius: Optional[Tensor] + Scale radius of the TNFW lens (arcsec). + tau: Optional[Tensor] + Truncation scale. Ratio of truncation radius to scale radius (rt/rs). + params: dict + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) r = (x**2 + y**2).sqrt() + self.s From d00b9dd7b06560270da864c67399b4beb38344fc Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 14:09:58 -0800 Subject: [PATCH 38/55] update pseudo_jaffe.py --- src/caustics/lenses/pseudo_jaffe.py | 168 ++++++++++++++++++---------- 1 file changed, 112 insertions(+), 56 deletions(-) diff --git a/src/caustics/lenses/pseudo_jaffe.py b/src/caustics/lenses/pseudo_jaffe.py index cca8ab09..f6990841 100644 --- a/src/caustics/lenses/pseudo_jaffe.py +++ b/src/caustics/lenses/pseudo_jaffe.py @@ -19,16 +19,26 @@ class PseudoJaffe(ThinLens): based on `Eliasdottir et al 2007 `_ and the `lenstronomy` source code. - Attributes: - name (str): The name of the Pseudo Jaffe lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Union[Tensor, float]]): Redshift of the lens. - x0 (Optional[Union[Tensor, float]]): x-coordinate of the center of the lens (arcsec). - y0 (Optional[Union[Tensor, float]]): y-coordinate of the center of the lens (arcsec). - mass (Optional[Union[Tensor, float]]): Total mass of the lens (Msol). - core_radius (Optional[Union[Tensor, float]]): Core radius of the lens (arcsec). - scale_radius (Optional[Union[Tensor, float]]): Scaling radius of the lens (arcsec). - s (float): Softening parameter to prevent numerical instabilities. + Attributes + ---------- + name: string + The name of the Pseudo Jaffe lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Union[Tensor, float]] + Redshift of the lens. + x0: Optional[Union[Tensor, float]] + x-coordinate of the center of the lens (arcsec). + y0: Optional[Union[Tensor, float]] + y-coordinate of the center of the lens (arcsec). + mass: Optional[Union[Tensor, float]] + Total mass of the lens (Msol). + core_radius: Optional[Union[Tensor, float]] + Core radius of the lens (arcsec). + scale_radius: Optional[Union[Tensor, float]] + Scaling radius of the lens (arcsec). + s: float + Softening parameter to prevent numerical instabilities. """ def __init__( @@ -46,16 +56,26 @@ def __init__( """ Initialize the PseudoJaffe class. - Args: - name (str): The name of the Pseudo Jaffe lens. - cosmology (Cosmology): The cosmology used for calculations. - z_l (Optional[Tensor]): Redshift of the lens. - x0 (Optional[Tensor]): x-coordinate of the center of the lens. - y0 (Optional[Tensor]): y-coordinate of the center of the lens. - mass (Optional[Tensor]): Total mass of the lens (Msol). - core_radius (Optional[Tensor]): Core radius of the lens. - scale_radius (Optional[Tensor]): Scaling radius of the lens. - s (float): Softening parameter to prevent numerical instabilities. + Parameters + ---------- + name: string + The name of the Pseudo Jaffe lens. + cosmology: Cosmology + The cosmology used for calculations. + z_l: Optional[Tensor] + Redshift of the lens. + x0: Optional[Tensor] + x-coordinate of the center of the lens. + y0: Optional[Tensor] + y-coordinate of the center of the lens. + mass: Optional[Tensor] + Total mass of the lens (Msol). + core_radius: Optional[Tensor] + Core radius of the lens. + scale_radius: Optional[Tensor] + Scaling radius of the lens. + s: float + Softening parameter to prevent numerical instabilities. """ super().__init__(cosmology, z_l, name=name) @@ -109,13 +129,19 @@ def mass_enclosed_2d( """ Calculate the mass enclosed within a two-dimensional radius. - Args: - theta (Tensor): Radius at which to calculate enclosed mass (arcsec). - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + theta: Tensor + Radius at which to calculate enclosed mass (arcsec). + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tensor: The mass enclosed within the given radius. + Returns + ------- + Tensor + The mass enclosed within the given radius. """ theta = theta + self.s d_l = self.cosmology.angular_diameter_distance(z_l, params) @@ -150,16 +176,25 @@ def central_convergence( """ Compute the central convergence. - Args: - z_l (Tensor): Lens redshift. - z_s (Tensor): Source redshift. - rho_0 (Tensor): Central mass density. - core_radius (Tensor): Core radius of the lens (must be in Mpc). - scale_radius (Tensor): Scaling radius of the lens (must be in Mpc). - cosmology (Cosmology): The cosmology used for calculations. + Parameters + ----------- + z_l: Tensor + Lens redshift. + z_s: Tensor + Source redshift. + rho_0: Tensor + Central mass density. + core_radius: Tensor + Core radius of the lens (must be in Mpc). + scale_radius: Tensor + Scaling radius of the lens (must be in Mpc). + cosmology: Cosmology + The cosmology used for calculations. - Returns: - Tensor: The central convergence. + Returns + -------- + Tensor + The central convergence. """ return ( pi @@ -188,14 +223,21 @@ def reduced_deflection_angle( ) -> tuple[Tensor, Tensor]: """Calculate the deflection angle. - Args: - x (Tensor): x-coordinate of the lens. - y (Tensor): y-coordinate of the lens. - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + ---------- + x: Tensor + x-coordinate of the lens. + y: Tensor + y-coordinate of the lens. + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tuple[Tensor, Tensor]: The deflection angle in the x and y directions. + Returns + -------- + Tuple[Tensor, Tensor] + The deflection angle in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) R = (x**2 + y**2).sqrt() + self.s @@ -233,14 +275,21 @@ def potential( """ Compute the lensing potential. This calculation is based on equation A18. - Args: - x (Tensor): x-coordinate of the lens. - y (Tensor): y-coordinate of the lens. - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + -------- + x: Tensor + x-coordinate of the lens. + y: Tensor + y-coordinate of the lens. + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tensor: The lensing potential. + Returns + -------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) R_squared = x**2 + y**2 + self.s @@ -278,14 +327,21 @@ def convergence( """ Calculate the projected mass density, based on equation A6. - Args: - x (Tensor): x-coordinate of the lens. - y (Tensor): y-coordinate of the lens. - z_s (Tensor): Source redshift. - params (Packed, optional): Dynamic parameter container. + Parameters + ----------- + x: Tensor + x-coordinate of the lens. + y: Tensor + y-coordinate of the lens. + z_s: Tensor + Source redshift. + params: (Packed, optional) + Dynamic parameter container. - Returns: - Tensor: The projected mass density. + Returns + ------- + Tensor + The projected mass density. """ x, y = translate_rotate(x, y, x0, y0) R_squared = x**2 + y**2 + self.s From 087ce12d7d7ee2d2958722c8a78fedf9d245b201 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 15:41:52 -0800 Subject: [PATCH 39/55] update nfw.py --- src/caustics/lenses/nfw.py | 311 ++++++++++++++++++++++++------------- 1 file changed, 203 insertions(+), 108 deletions(-) diff --git a/src/caustics/lenses/nfw.py b/src/caustics/lenses/nfw.py index 2a1d0297..1870fbdf 100644 --- a/src/caustics/lenses/nfw.py +++ b/src/caustics/lenses/nfw.py @@ -21,31 +21,47 @@ class NFW(ThinLens): The NFW profile is a spatial density profile of dark matter halo that arises in cosmological simulations. - Attributes: - z_l (Optional[Tensor]): Redshift of the lens. Default is None. - x0 (Optional[Tensor]): x-coordinate of the lens center in the lens plane. - Default is None. - y0 (Optional[Tensor]): y-coordinate of the lens center in the lens plane. - Default is None. - m (Optional[Tensor]): Mass of the lens. Default is None. - c (Optional[Tensor]): Concentration parameter of the lens. Default is None. - s (float): Softening parameter to avoid singularities at the center of the lens. - Default is 0.0. - use_case (str): Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile - specifically cant be both batchable and differentiable. You may select which version - you wish to use by setting this parameter to one of: batchable, differentiable. - - Methods: - get_scale_radius: Returns the scale radius of the lens. - get_scale_density: Returns the scale density of the lens. - get_convergence_s: Returns the dimensionless surface mass density of the lens. - _f: Helper method for computing deflection angles. - _g: Helper method for computing lensing potential. - _h: Helper method for computing reduced deflection angles. - deflection_angle_hat: Computes the reduced deflection angle. - deflection_angle: Computes the deflection angle. - convergence: Computes the convergence (dimensionless surface mass density). - potential: Computes the lensing potential. + Attributes + ----------- + z_l: Optional[Tensor] + Redshift of the lens. Default is None. + x0: Optional[Tensor] + x-coordinate of the lens center in the lens plane. Default is None. + y0: Optional[Tensor] + y-coordinate of the lens center in the lens plane. Default is None. + m: Optional[Tensor] + Mass of the lens. Default is None. + c: Optional[Tensor] + Concentration parameter of the lens. Default is None. + s: float + Softening parameter to avoid singularities at the center of the lens. Default is 0.0. + use_case: str + Due to an idyosyncratic behaviour of PyTorch, the NFW/TNFW profile + specifically cant be both batchable and differentiable. You may select which version + you wish to use by setting this parameter to one of: batchable, differentiable. + + Methods + ------- + get_scale_radius + Returns the scale radius of the lens. + get_scale_density + Returns the scale density of the lens. + get_convergence_s + Returns the dimensionless surface mass density of the lens. + _f + Helper method for computing deflection angles. + _g + Helper method for computing lensing potential. + _h + Helper method for computing reduced deflection angles. + deflection_angle_hat + Computes the reduced deflection angle. + deflection_angle + Computes the deflection angle. + convergence + Computes the convergence (dimensionless surface mass density). + potential + Computes the lensing potential. """ def __init__( @@ -63,19 +79,28 @@ def __init__( """ Initialize an instance of the NFW lens class. - Args: - name (str): Name of the lens instance. - cosmology (Cosmology): An instance of the Cosmology class which contains - information about the cosmological model and parameters. - z_l (Optional[Union[Tensor, float]]): Redshift of the lens. Default is None. - x0 (Optional[Union[Tensor, float]]): x-coordinate of the lens center in the lens plane. + Parameters + ---------- + name: str + Name of the lens instance. + cosmology: Cosmology + An instance of the Cosmology class which contains + information about the cosmological model and parameters. + z_l: Optional[Union[Tensor, float]] + Redshift of the lens. Default is None. + x0: Optional[Union[Tensor, float]] + x-coordinate of the lens center in the lens plane. Default is None. - y0 (Optional[Union[Tensor, float]]): y-coordinate of the lens center in the lens plane. + y0: Optional[Union[Tensor, float]] + y-coordinate of the lens center in the lens plane. Default is None. - m (Optional[Union[Tensor, float]]): Mass of the lens. Default is None. - c (Optional[Union[Tensor, float]]): Concentration parameter of the lens. Default is None. - s (float): Softening parameter to avoid singularities at the center of the lens. - Default is 0.0. + m: Optional[Union[Tensor, float]] + Mass of the lens. Default is None. + c: Optional[Union[Tensor, float]] + Concentration parameter of the lens. Default is None. + s: float + Softening parameter to avoid singularities at the center of the lens. + Default is 0.0. """ super().__init__(cosmology, z_l, name=name) @@ -102,14 +127,21 @@ def get_scale_radius( """ Calculate the scale radius of the lens. - Args: - z_l (Tensor): Redshift of the lens. - m (Tensor): Mass of the lens. - c (Tensor): Concentration parameter of the lens. - x (dict): Dynamic parameter container. - - Returns: - Tensor: The scale radius of the lens in Mpc. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + m: Tensor + Mass of the lens. + c: Tensor + Concentration parameter of the lens. + x: dict + Dynamic parameter container. + + Returns + ------- + Tensor + The scale radius of the lens in Mpc. """ critical_density = self.cosmology.critical_density(z_l, params) r_delta = (3 * m / (4 * pi * DELTA * critical_density)) ** (1 / 3) @@ -122,13 +154,19 @@ def get_scale_density( """ Calculate the scale density of the lens. - Args: - z_l (Tensor): Redshift of the lens. - c (Tensor): Concentration parameter of the lens. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The scale density of the lens in solar masses per Mpc cubed. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + c: Tensor + Concentration parameter of the lens. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The scale density of the lens in solar masses per Mpc cubed. """ return ( DELTA @@ -145,15 +183,23 @@ def get_convergence_s( """ Calculate the dimensionless surface mass density of the lens. - Args: - z_l (Tensor): Redshift of the lens. - z_s (Tensor): Redshift of the source. - m (Tensor): Mass of the lens. - c (Tensor): Concentration parameter of the lens. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The dimensionless surface mass density of the lens. + Parameters + ---------- + z_l: Tensor + Redshift of the lens. + z_s: Tensor + Redshift of the source. + m: Tensor + Mass of the lens. + c: Tensor + Concentration parameter of the lens. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The dimensionless surface mass density of the lens. """ critical_surface_density = self.cosmology.critical_surface_density( z_l, z_s, params @@ -169,11 +215,15 @@ def _f_differentiable(x: Tensor) -> Tensor: """ Helper method for computing deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the deflection angle computation. + Returns + ------- + Tensor + Result of the deflection angle computation. """ # TODO: generalize beyond torch, or patch Tensor f = torch.zeros_like(x) @@ -196,11 +246,15 @@ def _f_batchable(x: Tensor) -> Tensor: """ Helper method for computing deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the deflection angle computation. + Returns + ------- + Tensor + Result of the deflection angle computation. """ # TODO: generalize beyond torch, or patch Tensor return torch.where( @@ -218,11 +272,15 @@ def _g_differentiable(x: Tensor) -> Tensor: """ Helper method for computing lensing potential. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the lensing potential computation. + Returns + ------- + Tensor + Result of the lensing potential computation. """ # TODO: generalize beyond torch, or patch Tensor term_1 = (x / 2).log() ** 2 @@ -236,11 +294,15 @@ def _g_batchable(x: Tensor) -> Tensor: """ Helper method for computing lensing potential. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the lensing potential computation. + Returns + ------- + Tensor + Result of the lensing potential computation. """ # TODO: generalize beyond torch, or patch Tensor term_1 = (x / 2).log() ** 2 @@ -260,11 +322,15 @@ def _h_differentiable(x: Tensor) -> Tensor: """ Helper method for computing reduced deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the reduced deflection angle computation. + Returns + ------- + Tensor + Result of the reduced deflection angle computation. """ term_1 = (x / 2).log() term_2 = torch.ones_like(x) @@ -277,11 +343,15 @@ def _h_batchable(x: Tensor) -> Tensor: """ Helper method for computing reduced deflection angles. - Args: - x (Tensor): The scaled radius (xi / xi_0). + Parameters + ---------- + x: Tensor + The scaled radius (xi / xi_0). - Returns: - Tensor: Result of the reduced deflection angle computation. + Returns + ------- + Tensor + Result of the reduced deflection angle computation. """ term_1 = (x / 2).log() term_2 = torch.where( @@ -292,6 +362,10 @@ def _h_batchable(x: Tensor) -> Tensor: ), ) return term_1 + term_2 +<<<<<<< HEAD:src/caustics/lenses/nfw.py +======= + +>>>>>>> 2a501f3 (update nfw.py):caustic/lenses/nfw.py @unpack(3) def reduced_deflection_angle( @@ -311,14 +385,21 @@ def reduced_deflection_angle( """ Compute the reduced deflection angle. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - tuple[Tensor, Tensor]: The reduced deflection angles in the x and y directions. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + tuple[Tensor, Tensor] + The reduced deflection angles in the x and y directions. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -362,14 +443,21 @@ def convergence( """ Compute the convergence (dimensionless surface mass density). - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The convergence (dimensionless surface mass density). + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The convergence (dimensionless surface mass density). """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s @@ -398,14 +486,21 @@ def potential( """ Compute the lensing potential. - Args: - x (Tensor): x-coordinates in the lens plane. - y (Tensor): y-coordinates in the lens plane. - z_s (Tensor): Redshifts of the sources. - params (Packed, optional): Dynamic parameter container. - - Returns: - Tensor: The lensing potential. + Parameters + ---------- + x: Tensor + x-coordinates in the lens plane. + y: Tensor + y-coordinates in the lens plane. + z_s: Tensor + Redshifts of the sources. + params: (Packed, optional) + Dynamic parameter container. + + Returns + ------- + Tensor + The lensing potential. """ x, y = translate_rotate(x, y, x0, y0) th = (x**2 + y**2).sqrt() + self.s From 2e1e655dec6d290894608b741ec84168cec4c103 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 15:54:00 -0800 Subject: [PATCH 40/55] change to numpy docstring for pixelated.py --- docs/jupyter_book/notebooks.ipynb | 122 ---------- src/caustics/lenses/pixelated_convergence.py | 232 ++++++++++++------- 2 files changed, 151 insertions(+), 203 deletions(-) delete mode 100644 docs/jupyter_book/notebooks.ipynb diff --git a/docs/jupyter_book/notebooks.ipynb b/docs/jupyter_book/notebooks.ipynb deleted file mode 100644 index fdb7176c..00000000 --- a/docs/jupyter_book/notebooks.ipynb +++ /dev/null @@ -1,122 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Content with notebooks\n", - "\n", - "You can also create content with Jupyter Notebooks. This means that you can include\n", - "code blocks and their outputs in your book.\n", - "\n", - "## Markdown + notebooks\n", - "\n", - "As it is markdown, you can embed images, HTML, etc into your posts!\n", - "\n", - "![](https://myst-parser.readthedocs.io/en/latest/_static/logo-wide.svg)\n", - "\n", - "You can also $add_{math}$ and\n", - "\n", - "$$\n", - "math^{blocks}\n", - "$$\n", - "\n", - "or\n", - "\n", - "$$\n", - "\\begin{aligned}\n", - "\\mbox{mean} la_{tex} \\\\ \\\\\n", - "math blocks\n", - "\\end{aligned}\n", - "$$\n", - "\n", - "But make sure you \\$Escape \\$your \\$dollar signs \\$you want to keep!\n", - "\n", - "## MyST markdown\n", - "\n", - "MyST markdown works in Jupyter Notebooks as well. For more information about MyST markdown, check\n", - "out [the MyST guide in Jupyter Book](https://jupyterbook.org/content/myst.html),\n", - "or see [the MyST markdown documentation](https://myst-parser.readthedocs.io/en/latest/).\n", - "\n", - "## Code blocks and outputs\n", - "\n", - "Jupyter Book will also embed your code blocks and output in your book.\n", - "For example, here's some sample Matplotlib code:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import rcParams, cycler\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "plt.ion()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Fixing random state for reproducibility\n", - "np.random.seed(19680801)\n", - "\n", - "N = 10\n", - "data = [np.logspace(0, 1, 100) + np.random.randn(100) + ii for ii in range(N)]\n", - "data = np.array(data).T\n", - "cmap = plt.cm.coolwarm\n", - "rcParams['axes.prop_cycle'] = cycler(color=cmap(np.linspace(0, 1, N)))\n", - "\n", - "\n", - "from matplotlib.lines import Line2D\n", - "custom_lines = [Line2D([0], [0], color=cmap(0.), lw=4),\n", - " Line2D([0], [0], color=cmap(.5), lw=4),\n", - " Line2D([0], [0], color=cmap(1.), lw=4)]\n", - "\n", - "fig, ax = plt.subplots(figsize=(10, 5))\n", - "lines = ax.plot(data)\n", - "ax.legend(custom_lines, ['Cold', 'Medium', 'Hot']);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is a lot more that you can do with outputs (such as including interactive outputs)\n", - "with your book. For more information about this, see [the Jupyter Book documentation](https://jupyterbook.org)" - ] - } - ], - "metadata": { - "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.8.0" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": {}, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/src/caustics/lenses/pixelated_convergence.py b/src/caustics/lenses/pixelated_convergence.py index dd4d88b0..b0cb2d7c 100644 --- a/src/caustics/lenses/pixelated_convergence.py +++ b/src/caustics/lenses/pixelated_convergence.py @@ -39,25 +39,38 @@ def __init__( grid using either Fast Fourier Transform (FFT) or a 2D convolution. - Attributes: - name (str): The name of the PixelatedConvergence object. - fov (float): The field of view in arcseconds. - n_pix (int): The number of pixels on each side of the grid. - cosmology (Cosmology): An instance of the cosmological parameters. - z_l (Optional[Tensor]): The redshift of the lens. - x0 (Optional[Tensor]): The x-coordinate of the center of the grid. - y0 (Optional[Tensor]): The y-coordinate of the center of the grid. - convergence_map (Optional[Tensor]): A 2D tensor representing the convergence map. - shape (Optional[tuple[int, ...]]): The shape of the convergence map. - convolution_mode (str, optional): The convolution mode for calculating deflection angles and lensing potential. - It can be either "fft" (Fast Fourier Transform) or "conv2d" (2D convolution). Default is "fft". - use_next_fast_len (bool, optional): If True, adds additional padding to speed up the FFT by calling - `scipy.fft.next_fast_len`. The speed boost can be substantial when `n_pix` is a multiple of a - small prime number. Default is True. - padding (str): Specifies the type of padding to use. "zero" will do zero padding, "circular" will do - cyclic boundaries. "reflect" will do reflection padding. "tile" will tile the image at 2x2 which - basically identical to circular padding, but is easier. Generally you should use either "zero" - or "tile". + Attributes + ---------- + name: string + The name of the PixelatedConvergence object. + fov: float + The field of view in arcseconds. + n_pix: int + The number of pixels on each side of the grid. + cosmology: Cosmology + An instance of the cosmological parameters. + z_l: Optional[Tensor] + The redshift of the lens. + x0: Optional[Tensor] + The x-coordinate of the center of the grid. + y0: Optional[Tensor] + The y-coordinate of the center of the grid. + convergence_map: Optional[Tensor] + A 2D tensor representing the convergence map. + shape: Optional[tuple[int, ...]] + The shape of the convergence map. + convolution_mode: (str, optional) + The convolution mode for calculating deflection angles and lensing potential. + It can be either "fft" (Fast Fourier Transform) or "conv2d" (2D convolution). Default is "fft". + use_next_fast_len: (bool, optional) + If True, adds additional padding to speed up the FFT by calling + `scipy.fft.next_fast_len`. The speed boost can be substantial when `n_pix` is a multiple of a + small prime number. Default is True. + padding: string + Specifies the type of padding to use. "zero" will do zero padding, "circular" will do + cyclic boundaries. "reflect" will do reflection padding. "tile" will tile the image at 2x2 which + basically identical to circular padding, but is easier. Generally you should use either "zero" + or "tile". """ @@ -108,9 +121,12 @@ def to( """ Move the ConvergenceGrid object and all its tensors to the specified device and dtype. - Args: - device (Optional[torch.device]): The target device to move the tensors to. - dtype (Optional[torch.dtype]): The target data type to cast the tensors to. + Parameters + ---------- + device: Optional[torch.device] + The target device to move the tensors to. + dtype: Optional[torch.dtype] + The target data type to cast the tensors to. """ super().to(device, dtype) self.potential_kernel = self.potential_kernel.to(device=device, dtype=dtype) @@ -127,11 +143,14 @@ def _fft2_padded(self, x: Tensor) -> Tensor: """ Compute the 2D Fast Fourier Transform (FFT) of a tensor with zero-padding. - Args: - x (Tensor): The input tensor to be transformed. + Parameters + x: Tensor + The input tensor to be transformed. - Returns: - Tensor: The 2D FFT of the input tensor with zero-padding. + Returns + ------- + Tensor + The 2D FFT of the input tensor with zero-padding. """ pad = 2 * self.n_pix if self.use_next_fast_len: @@ -153,11 +172,15 @@ def _unpad_fft(self, x: Tensor) -> Tensor: """ Remove padding from the result of a 2D FFT. - Args: - x (Tensor): The input tensor with padding. + Parameters + ---------- + x: Tensor + The input tensor with padding. - Returns: - Tensor: The input tensor without padding. + Returns + ------- + Tensor + The input tensor without padding. """ return torch.roll(x, (-self._s[0] // 2, -self._s[1] // 2), dims=(-2, -1))[ ..., : self.n_pix, : self.n_pix @@ -167,11 +190,15 @@ def _unpad_conv2d(self, x: Tensor) -> Tensor: """ Remove padding from the result of a 2D convolution. - Args: - x (Tensor): The input tensor with padding. + Parameters + ---------- + x: Tensor + The input tensor with padding. - Returns: - Tensor: The input tensor without padding. + Returns + ------- + Tensor + The input tensor without padding. """ return x # torch.roll(x, (-self.padding_range * self.ax_kernel.shape[0]//4,-self.padding_range * self.ax_kernel.shape[1]//4), dims = (-2,-1))[..., :self.n_pix, :self.n_pix] #[..., 1:, 1:] @@ -180,8 +207,10 @@ def convolution_mode(self): """ Get the convolution mode of the ConvergenceGrid object. - Returns: - str: The convolution mode, either "fft" or "conv2d". + Returns + ------- + string + The convolution mode, either "fft" or "conv2d". """ return self._convolution_mode @@ -190,8 +219,10 @@ def convolution_mode(self, convolution_mode: str): """ Set the convolution mode of the ConvergenceGrid object. - Args: - mode (str): The convolution mode to be set, either "fft" or "conv2d". + Parameters + ---------- + mode: string + The convolution mode to be set, either "fft" or "conv2d". """ if convolution_mode == "fft": # Create FFTs of kernels @@ -225,14 +256,21 @@ def reduced_deflection_angle( """ Compute the deflection angles at the specified positions using the given convergence map. - Args: - x (Tensor): The x-coordinates of the positions to compute the deflection angles for. - y (Tensor): The y-coordinates of the positions to compute the deflection angles for. - z_s (Tensor): The source redshift. - params (Packed, optional): A dictionary containing additional parameters. - - Returns: - tuple[Tensor, Tensor]: The x and y components of the deflection angles at the specified positions. + Parameters + ---------- + x: Tensor + The x-coordinates of the positions to compute the deflection angles for. + y: Tensor + The y-coordinates of the positions to compute the deflection angles for. + z_s: Tensor + The source redshift. + params: (Packed, optional) + A dictionary containing additional parameters. + + Returns + ------- + tuple[Tensor, Tensor] + The x and y components of the deflection angles at the specified positions. """ if self.convolution_mode == "fft": deflection_angle_x_map, deflection_angle_y_map = self._deflection_angle_fft( @@ -257,11 +295,15 @@ def _deflection_angle_fft(self, convergence_map: Tensor) -> tuple[Tensor, Tensor """ Compute the deflection angles using the Fast Fourier Transform (FFT) method. - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. - Returns: - tuple[Tensor, Tensor]: The x and y components of the deflection angles. + Returns + ------- + tuple[Tensor, Tensor] + The x and y components of the deflection angles. """ convergence_tilde = self._fft2_padded(convergence_map) deflection_angle_x = torch.fft.irfft2( @@ -278,11 +320,15 @@ def _deflection_angle_conv2d( """ Compute the deflection angles using the 2D convolution method. - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. - Returns: - tuple[Tensor, Tensor]: The x and y components of the deflection angles. + Returns + ------- + tuple[Tensor, Tensor] + The x and y components of the deflection angles. """ # Use convergence_map as kernel since the kernel is twice as large. Flip since # we actually want the cross-correlation. @@ -318,14 +364,21 @@ def potential( """ Compute the lensing potential at the specified positions using the given convergence map. - Args: - x (Tensor): The x-coordinates of the positions to compute the lensing potential for. - y (Tensor): The y-coordinates of the positions to compute the lensing potential for. - z_s (Tensor): The source redshift. - params (Packed, optional): A dictionary containing additional parameters. - - Returns: - Tensor: The lensing potential at the specified positions. + Parameters + ---------- + x: Tensor + The x-coordinates of the positions to compute the lensing potential for. + y: Tensor + The y-coordinates of the positions to compute the lensing potential for. + z_s: Tensor + The source redshift. + params: (Packed, optional) + A dictionary containing additional parameters. + + Returns + ------- + Tensor + The lensing potential at the specified positions. """ if self.convolution_mode == "fft": potential_map = self._potential_fft(convergence_map) @@ -342,11 +395,15 @@ def _potential_fft(self, convergence_map: Tensor) -> Tensor: """ Compute the lensing potential using the Fast Fourier Transform (FFT) method. - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. - Returns: - Tensor: The lensing potential. + Returns + ------- + Tensor + The lensing potential. """ convergence_tilde = self._fft2_padded(convergence_map) potential = torch.fft.irfft2( @@ -358,11 +415,15 @@ def _potential_conv2d(self, convergence_map: Tensor) -> Tensor: """ Compute the lensing potential using the 2D convolution method. - Args: - convergence_map (Tensor): The 2D tensor representing the convergence map. + Parameters + ---------- + convergence_map: Tensor + The 2D tensor representing the convergence map. - Returns: - Tensor: The lensing potential. + Returns + ------- + Tensor + The lensing potential. """ # Use convergence_map as kernel since the kernel is twice as large. Flip since # we actually want the cross-correlation. @@ -389,17 +450,26 @@ def convergence( """ Compute the convergence at the specified positions. This method is not implemented. - Args: - x (Tensor): The x-coordinates of the positions to compute the convergence for. - y (Tensor): The y-coordinates of the positions to compute the convergence for. - z_s (Tensor): The source redshift. - params (Packed, optional): A dictionary containing additional parameters. - - Returns: - Tensor: The convergence at the specified positions. - - Raises: - NotImplementedError: This method is not implemented. + Parameters + ---------- + x: Tensor + The x-coordinates of the positions to compute the convergence for. + y: Tensor + The y-coordinates of the positions to compute the convergence for. + z_s: Tensor + The source redshift. + params: (Packed, optional) + A dictionary containing additional parameters. + + Returns + ------- + Tensor + The convergence at the specified positions. + + Raises + ------ + NotImplementedError + This method is not implemented. """ return interp2d( convergence_map, From 02c523130dbab92f8f0956302e4c452412b52ebb Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 16:00:15 -0800 Subject: [PATCH 41/55] modify the config.yml and toc.yml --- docs/jupyter_book/_config.yml | 4 ++-- docs/jupyter_book/_toc.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/jupyter_book/_config.yml b/docs/jupyter_book/_config.yml index 5f534f80..04579d5b 100644 --- a/docs/jupyter_book/_config.yml +++ b/docs/jupyter_book/_config.yml @@ -1,8 +1,8 @@ # Book settings # Learn more at https://jupyterbook.org/customize/config.html -title: My sample book -author: The Jupyter Book Community +title: caustic +author: Ciela Institute logo: logo.png # Force re-execution of notebooks on each build. diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index 74d5c710..258e8f89 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -4,6 +4,6 @@ format: jb-book root: intro chapters: -- file: markdown +- file: get_started - file: notebooks - file: markdown-notebooks From 2d578a3d782bf63a460dc88a04fe29da233d9ad9 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 20 Nov 2023 22:44:20 -0800 Subject: [PATCH 42/55] modify ci.yml, _toc.yml --- docs/jupyter_book/_toc.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index 258e8f89..1fdcfe2f 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -5,5 +5,8 @@ format: jb-book root: intro chapters: - file: get_started -- file: notebooks +- file: notebook +- file: markdown-notebooks +- file: markdown-notebooks +- file: markdown-notebooks - file: markdown-notebooks From 411226d21a6fd04a2a5528a37f409e89f3eb4801 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 22 Nov 2023 12:08:35 -0800 Subject: [PATCH 43/55] update table of content --- docs/jupyter_book/_toc.yml | 12 +++--- docs/jupyter_book/intro.md | 10 ++--- docs/jupyter_book/markdown-notebooks.md | 53 ------------------------ docs/jupyter_book/markdown.md | 55 ------------------------- 4 files changed, 10 insertions(+), 120 deletions(-) delete mode 100644 docs/jupyter_book/markdown-notebooks.md delete mode 100644 docs/jupyter_book/markdown.md diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index 1fdcfe2f..e9f8a5c4 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -4,9 +4,9 @@ format: jb-book root: intro chapters: -- file: get_started -- file: notebook -- file: markdown-notebooks -- file: markdown-notebooks -- file: markdown-notebooks -- file: markdown-notebooks +- file: docs/getting_started # Getting Started +- file: docs/install # Installation +- file: docs/tutorials # Tutorials +- file: docs/contributing # Contributing +- file: docs/citation # Citation +- file: docs/license # Caustics \ No newline at end of file diff --git a/docs/jupyter_book/intro.md b/docs/jupyter_book/intro.md index f8cdc73c..13bb357d 100644 --- a/docs/jupyter_book/intro.md +++ b/docs/jupyter_book/intro.md @@ -1,11 +1,9 @@ -# Welcome to your Jupyter Book +# Welcome to Caustics’ documentation! -This is a small sample book to give you a feel for how book content is -structured. -It shows off a few of the major file types, as well as some sample content. -It does not go in-depth into any particular topic - check out [the Jupyter Book documentation](https://jupyterbook.org) for more information. +The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. -Check out the content pages bundled with this sample book to see more. +# Intallation +The easiest way to install is to make a new virtual environment then run: ```{tableofcontents} ``` diff --git a/docs/jupyter_book/markdown-notebooks.md b/docs/jupyter_book/markdown-notebooks.md deleted file mode 100644 index a057a320..00000000 --- a/docs/jupyter_book/markdown-notebooks.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -jupytext: - formats: md:myst - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.11.5 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- - -# Notebooks with MyST Markdown - -Jupyter Book also lets you write text-based notebooks using MyST Markdown. -See [the Notebooks with MyST Markdown documentation](https://jupyterbook.org/file-types/myst-notebooks.html) for more detailed instructions. -This page shows off a notebook written in MyST Markdown. - -## An example cell - -With MyST Markdown, you can define code cells with a directive like so: - -```{code-cell} -print(2 + 2) -``` - -When your book is built, the contents of any `{code-cell}` blocks will be -executed with your default Jupyter kernel, and their outputs will be displayed -in-line with the rest of your content. - -```{seealso} -Jupyter Book uses [Jupytext](https://jupytext.readthedocs.io/en/latest/) to convert text-based files to notebooks, and can support [many other text-based notebook files](https://jupyterbook.org/file-types/jupytext.html). -``` - -## Create a notebook with MyST Markdown - -MyST Markdown notebooks are defined by two things: - -1. YAML metadata that is needed to understand if / how it should convert text files to notebooks (including information about the kernel needed). - See the YAML at the top of this page for example. -2. The presence of `{code-cell}` directives, which will be executed with your book. - -That's all that is needed to get started! - -## Quickly add YAML metadata for MyST Notebooks - -If you have a markdown file and you'd like to quickly add YAML metadata to it, so that Jupyter Book will treat it as a MyST Markdown Notebook, run the following command: - -``` -jupyter-book myst init path/to/markdownfile.md -``` diff --git a/docs/jupyter_book/markdown.md b/docs/jupyter_book/markdown.md deleted file mode 100644 index 0ddaab3f..00000000 --- a/docs/jupyter_book/markdown.md +++ /dev/null @@ -1,55 +0,0 @@ -# Markdown Files - -Whether you write your book's content in Jupyter Notebooks (`.ipynb`) or -in regular markdown files (`.md`), you'll write in the same flavor of markdown -called **MyST Markdown**. -This is a simple file to help you get started and show off some syntax. - -## What is MyST? - -MyST stands for "Markedly Structured Text". It -is a slight variation on a flavor of markdown called "CommonMark" markdown, -with small syntax extensions to allow you to write **roles** and **directives** -in the Sphinx ecosystem. - -For more about MyST, see [the MyST Markdown Overview](https://jupyterbook.org/content/myst.html). - -## Sample Roles and Directives - -Roles and directives are two of the most powerful tools in Jupyter Book. They -are kind of like functions, but written in a markup language. They both -serve a similar purpose, but **roles are written in one line**, whereas -**directives span many lines**. They both accept different kinds of inputs, -and what they do with those inputs depends on the specific role or directive -that is being called. - -Here is a "note" directive: - -```{note} -Here is a note -``` - -It will be rendered in a special box when you build your book. - -Here is an inline directive to refer to a document: {doc}`markdown-notebooks`. - - -## Citations - -You can also cite references that are stored in a `bibtex` file. For example, -the following syntax: `` {cite}`holdgraf_evidence_2014` `` will render like -this: {cite}`holdgraf_evidence_2014`. - -Moreover, you can insert a bibliography into your page with this syntax: -The `{bibliography}` directive must be used for all the `{cite}` roles to -render properly. -For example, if the references for your book are stored in `references.bib`, -then the bibliography is inserted with: - -```{bibliography} -``` - -## Learn more - -This is just a simple starter to get you started. -You can learn a lot more at [jupyterbook.org](https://jupyterbook.org). From 45faef87ba8f5f996d8dd8d15f84826b7d19ca1f Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 22 Nov 2023 12:45:36 -0800 Subject: [PATCH 44/55] add basic table --- docs/jupyter_book/_config.yml | 4 +-- docs/jupyter_book/_toc.yml | 3 +- docs/jupyter_book/intro.md | 23 +++++++++++++ docs/jupyter_book/references.bib | 56 -------------------------------- 4 files changed, 27 insertions(+), 59 deletions(-) delete mode 100644 docs/jupyter_book/references.bib diff --git a/docs/jupyter_book/_config.yml b/docs/jupyter_book/_config.yml index 04579d5b..0a23c1de 100644 --- a/docs/jupyter_book/_config.yml +++ b/docs/jupyter_book/_config.yml @@ -16,8 +16,8 @@ latex: targetname: book.tex # Add a bibtex file so that we can create citations -bibtex_bibfiles: - - references.bib +# bibtex_bibfiles: +# - references.bib # Information about where the book exists on the web repository: diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml index e9f8a5c4..f4c44443 100644 --- a/docs/jupyter_book/_toc.yml +++ b/docs/jupyter_book/_toc.yml @@ -9,4 +9,5 @@ chapters: - file: docs/tutorials # Tutorials - file: docs/contributing # Contributing - file: docs/citation # Citation -- file: docs/license # Caustics \ No newline at end of file +- file: docs/license # Caustics +- file: docs/modules # modules \ No newline at end of file diff --git a/docs/jupyter_book/intro.md b/docs/jupyter_book/intro.md index 13bb357d..397b5649 100644 --- a/docs/jupyter_book/intro.md +++ b/docs/jupyter_book/intro.md @@ -1,9 +1,32 @@ # Welcome to Caustics’ documentation! The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. +```{note} +Caustic is in its early development phase. This means the API will change with time. These changes are a good thing, but they can be annoying. Watch the version numbers, when we get to 1.0.0 that will be the first stable release! +``` # Intallation The easiest way to install is to make a new virtual environment then run: +```console +pip install caustic +``` + +this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. If you want to help out with building the caustic code base check out the developer installation instructions instead. + + +# Read The Docs +## Contents +```{tableofcontents} +docs/getting_started.rst +docs/install.rst +docs/tutorials.rst +docs/contributing.rst +docs/citation.rst +docs/license.rst +``` + +# Indices and tables ```{tableofcontents} +docs/modules.rst ``` diff --git a/docs/jupyter_book/references.bib b/docs/jupyter_book/references.bib deleted file mode 100644 index 783ec6aa..00000000 --- a/docs/jupyter_book/references.bib +++ /dev/null @@ -1,56 +0,0 @@ ---- ---- - -@inproceedings{holdgraf_evidence_2014, - address = {Brisbane, Australia, Australia}, - title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}}, - booktitle = {International {Conference} on {Cognitive} {Neuroscience}}, - publisher = {Frontiers in Neuroscience}, - author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.}, - year = {2014} -} - -@article{holdgraf_rapid_2016, - title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility}, - volume = {7}, - issn = {2041-1723}, - url = {http://www.nature.com/doifinder/10.1038/ncomms13654}, - doi = {10.1038/ncomms13654}, - number = {May}, - journal = {Nature Communications}, - author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.}, - year = {2016}, - pages = {13654}, - file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf} -} - -@inproceedings{holdgraf_portable_2017, - title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science}, - volume = {Part F1287}, - isbn = {978-1-4503-5272-7}, - doi = {10.1145/3093338.3093370}, - abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.}, - booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}}, - author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.}, - year = {2017}, - keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy} -} - -@article{holdgraf_encoding_2017, - title = {Encoding and decoding models in cognitive electrophysiology}, - volume = {11}, - issn = {16625137}, - doi = {10.3389/fnsys.2017.00061}, - abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.}, - journal = {Frontiers in Systems Neuroscience}, - author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.}, - year = {2017}, - keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials} -} - -@book{ruby, - title = {The Ruby Programming Language}, - author = {Flanagan, David and Matsumoto, Yukihiro}, - year = {2008}, - publisher = {O'Reilly Media} -} From 6c9f90b14f239de63137422b30ec9a0d349019ea Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 22 Nov 2023 13:04:25 -0800 Subject: [PATCH 45/55] change the path of jupyter book --- docs/{jupyter_book => }/get_start.ipynb | 0 docs/jupyter_book/_toc.yml | 13 ---- .../jupyter_book => jupyter_book}/_config.yml | 0 jupyter_book/_toc.yml | 13 ++++ jupyter_book/citation.rst | 6 ++ jupyter_book/contributing.rst | 64 ++++++++++++++++++ jupyter_book/getting_started.rst | 20 ++++++ jupyter_book/install.rst | 30 ++++++++ {docs/jupyter_book => jupyter_book}/intro.md | 12 ---- jupyter_book/license.rst | 25 +++++++ {docs/jupyter_book => jupyter_book}/logo.png | Bin jupyter_book/modules.rst | 7 ++ .../requirements.txt | 0 jupyter_book/tutorials.rst | 23 +++++++ 14 files changed, 188 insertions(+), 25 deletions(-) rename docs/{jupyter_book => }/get_start.ipynb (100%) delete mode 100644 docs/jupyter_book/_toc.yml rename {docs/jupyter_book => jupyter_book}/_config.yml (100%) create mode 100644 jupyter_book/_toc.yml create mode 100644 jupyter_book/citation.rst create mode 100644 jupyter_book/contributing.rst create mode 100644 jupyter_book/getting_started.rst create mode 100644 jupyter_book/install.rst rename {docs/jupyter_book => jupyter_book}/intro.md (80%) create mode 100644 jupyter_book/license.rst rename {docs/jupyter_book => jupyter_book}/logo.png (100%) create mode 100644 jupyter_book/modules.rst rename {docs/jupyter_book => jupyter_book}/requirements.txt (100%) create mode 100644 jupyter_book/tutorials.rst diff --git a/docs/jupyter_book/get_start.ipynb b/docs/get_start.ipynb similarity index 100% rename from docs/jupyter_book/get_start.ipynb rename to docs/get_start.ipynb diff --git a/docs/jupyter_book/_toc.yml b/docs/jupyter_book/_toc.yml deleted file mode 100644 index f4c44443..00000000 --- a/docs/jupyter_book/_toc.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Table of contents -# Learn more at https://jupyterbook.org/customize/toc.html - -format: jb-book -root: intro -chapters: -- file: docs/getting_started # Getting Started -- file: docs/install # Installation -- file: docs/tutorials # Tutorials -- file: docs/contributing # Contributing -- file: docs/citation # Citation -- file: docs/license # Caustics -- file: docs/modules # modules \ No newline at end of file diff --git a/docs/jupyter_book/_config.yml b/jupyter_book/_config.yml similarity index 100% rename from docs/jupyter_book/_config.yml rename to jupyter_book/_config.yml diff --git a/jupyter_book/_toc.yml b/jupyter_book/_toc.yml new file mode 100644 index 00000000..23b8900a --- /dev/null +++ b/jupyter_book/_toc.yml @@ -0,0 +1,13 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: intro +chapters: +- file: docs/getting_started.rst # Getting Started +- file: docs/install.rst # Installation +- file: docs/tutorials.rst # Tutorials +- file: docs/contributing.rst # Contributing +- file: docs/citation.rst # Citation +- file: docs/license.rst # Caustics +- file: docs/modules.rst # modules \ No newline at end of file diff --git a/jupyter_book/citation.rst b/jupyter_book/citation.rst new file mode 100644 index 00000000..0d200348 --- /dev/null +++ b/jupyter_book/citation.rst @@ -0,0 +1,6 @@ + + +Citation +======== + +Paper comming soon! We will put all the citation information here when its ready. diff --git a/jupyter_book/contributing.rst b/jupyter_book/contributing.rst new file mode 100644 index 00000000..b384222c --- /dev/null +++ b/jupyter_book/contributing.rst @@ -0,0 +1,64 @@ +Contributing +============ + +.. contents:: Table of Contents + :local: + +Thanks for helping make caustic better! Here you will learn the full process needed to contribute to caustic. Following these steps will make the process as painless as possible for everyone. + +Create An Issue +--------------- + +Before actually writing any code, its best to create an issue on the GitHub. Describe the issue in detail and let us know the desired solution. Here it will be possible to address concerns (maybe its aready solved and just not yet documented) and plan out the best solution. We may also assign someone to work on it if that seems better. Note that submitting an issue is a contribution to caustic, we appreciate your ideas! Still, if after discussion it seems that the problem does need some work and you're the person to do it, then we can move on to the next steps. + +1. Navigate to the **Issues** tab of the GitHub repository. +2. Click on **New Issue**. +3. Specify a concise and descriptive title. +4. In the issue body, elaborate on the problem or feature request, employing adequate code snippets or references as necessary. +5. Submit the issue by clicking **Submit new issue**. + +Install +------- + +Please fork the caustic repo, then follow the developer install instructions at the :doc:`install` page. This will ensure you have a version of caustic that you can tinker with and see the results. + +The reason you should fork the repo is so that you have full control while making your edits. You will still be able to make a Pull Request later when it is time to merge your code with the main caustic branch. Note that you should keep your fork up to date with the caustic repo to make the merge as smooth as possible. + +Notebooks +--------- + +You will likely want to see how your changes affect various features of caustic. A good way to quickly see this is to run the tutorial notebooks which can be found `here `_. Any change that breaks one of these must be addressed, either by changing the nature of your updates to the code, or by forking and updating the caustic-tutorials repo as well (this is usually pretty easy). + +Resolving the Issue +------------------- + +As you modify the code, make sure to regularly commit changes and push them to your fork. This makes it easier for you to fix something if you make a mistake, and easier for us to see what changes were made along the way. Feel free to return to the issue on the main GitHub page for advice as to proceed. + +1. Make the necessary code modifications to address the issue. +2. Use ``git status`` to inspect the changes. +3. Execute ``git add .`` to stage the changes. +4. Commit the changes with ``git commit -m ""``. +5. Push the changes to your fork by executing ``git push origin ``. + +Unit Tests +---------- + +When you think you've solved an issue, please make unit tests related to any code you have added. Any new code added to caustic must have unit tests which match the level of completion of the rest of the code. Generally you should test all cases for the newly added code. Also ensure the previous unit tests run correctly. + +Submitting a Pull Request +------------------------- + +Once you think your updates are ready to merge with the rest of caustic you can submit a PR! This should provide a description of what you have changed and if it isn't straightforward, why you made those changes. + +1. Navigate to the **Pull Requests** tab of the original repository. +2. Click on **New Pull Request**. +3. Choose the appropriate base and compare branches. +4. Provide a concise and descriptive title and elaborate on the pull request body. +5. Click **Create Pull Request**. + +Finalizing a Pull Request +------------------------- + +Once the PR is submitted, we will look through it and request any changes necessary before merging it into the main branch. You can make those changes just like any other edits on your fork. Then when you push them, they will be joined in to the PR automatically and any unit tests will run again. + +Once the PR has been merged, you may delete your fork if you aren't using it any more, or take on a new issue, it's up to you! diff --git a/jupyter_book/getting_started.rst b/jupyter_book/getting_started.rst new file mode 100644 index 00000000..1bd0d949 --- /dev/null +++ b/jupyter_book/getting_started.rst @@ -0,0 +1,20 @@ + +Getting Started +=============== + +Install +------- + +Please follow the instructions on the :doc:`install` page. For most users, the basic pip install is all that's needed. + + +Tutorials +--------- + +We have created a repository of tutorial Jupyter notebooks that can help initiate you on the main features of caustic. Please checkout the `tutorials here `_ to see what caustic can do! + + +Read The Docs +------------- + +Docs for all the main functions in caustic are avaialble at :doc:`caustic` at varying degrees of completeness. Further development of the docs is always ongoing. diff --git a/jupyter_book/install.rst b/jupyter_book/install.rst new file mode 100644 index 00000000..077356f2 --- /dev/null +++ b/jupyter_book/install.rst @@ -0,0 +1,30 @@ + +Installation +============ + +Regular Install +--------------- + +The easiest way to install is to make a new virtual environment then run:: + + pip install caustic + +this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. + + +Developer Install +----------------- + +First clone the repo with:: + + git clone git@github.com:Ciela-Institute/caustic.git + +this will create a directory `caustic` wherever you ran the command. Next go into the directory and install in developer mode:: + + pip install -e ".[dev]" + +this will install all relevant libraries and then install caustic in an editable format so any changes you make to the code will be included next time you import the package. To start making changes you should immediately create a new branch:: + + git checkout -b + +you can edit this branch however you like. If you are happy with the results and want to share with the rest of the community, then follow the contributors guide to create a pull request! diff --git a/docs/jupyter_book/intro.md b/jupyter_book/intro.md similarity index 80% rename from docs/jupyter_book/intro.md rename to jupyter_book/intro.md index 397b5649..35018a1f 100644 --- a/docs/jupyter_book/intro.md +++ b/jupyter_book/intro.md @@ -17,16 +17,4 @@ this will install all the required libraries and then install caustic and you ar # Read The Docs ## Contents -```{tableofcontents} -docs/getting_started.rst -docs/install.rst -docs/tutorials.rst -docs/contributing.rst -docs/citation.rst -docs/license.rst -``` -# Indices and tables -```{tableofcontents} -docs/modules.rst -``` diff --git a/jupyter_book/license.rst b/jupyter_book/license.rst new file mode 100644 index 00000000..54abbeda --- /dev/null +++ b/jupyter_book/license.rst @@ -0,0 +1,25 @@ + +License +======= + +MIT License + +Copyright (c) [2023] [caustic authors] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/jupyter_book/logo.png b/jupyter_book/logo.png similarity index 100% rename from docs/jupyter_book/logo.png rename to jupyter_book/logo.png diff --git a/jupyter_book/modules.rst b/jupyter_book/modules.rst new file mode 100644 index 00000000..a922b553 --- /dev/null +++ b/jupyter_book/modules.rst @@ -0,0 +1,7 @@ +caustic +======= + +.. toctree:: + :maxdepth: 4 + + caustic diff --git a/docs/jupyter_book/requirements.txt b/jupyter_book/requirements.txt similarity index 100% rename from docs/jupyter_book/requirements.txt rename to jupyter_book/requirements.txt diff --git a/jupyter_book/tutorials.rst b/jupyter_book/tutorials.rst new file mode 100644 index 00000000..a5997413 --- /dev/null +++ b/jupyter_book/tutorials.rst @@ -0,0 +1,23 @@ +========= +Tutorials +========= + +Here you will find the jupyter notebook tutorials. It is recommended +that you go through the tutorials yourself, but for quick reference a +version of each tutorial is available here. + +.. toctree:: + :maxdepth: 1 + + BasicIntroduction + LensZoo + VisualizeCaustics + MultiplaneDemo + InvertLensEquation + + + + + + + From 5c70383d3e527c8b50b1d436d2eea1449911084f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 00:06:22 +0000 Subject: [PATCH 46/55] style: pre-commit fixes --- docs/get_start.ipynb | 50 ++++++++++++--------- jupyter_book/_config.yml | 6 +-- jupyter_book/_toc.yml | 14 +++--- jupyter_book/intro.md | 13 ++++-- jupyter_book/tutorials.rst | 14 +++--- src/caustics/light/sersic.py | 84 ++++++++++++++++++------------------ 6 files changed, 97 insertions(+), 84 deletions(-) diff --git a/docs/get_start.ipynb b/docs/get_start.ipynb index 21a64c2f..65cbe0cb 100644 --- a/docs/get_start.ipynb +++ b/docs/get_start.ipynb @@ -54,10 +54,15 @@ "res = 0.05\n", "upsample_factor = 2\n", "fov = res * n_pix\n", - "thx, thy = caustic.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "thx, thy = caustic.get_meshgrid(\n", + " res / upsample_factor,\n", + " upsample_factor * n_pix,\n", + " upsample_factor * n_pix,\n", + " dtype=torch.float32,\n", + ")\n", "z_l = torch.tensor(0.5, dtype=torch.float32)\n", "z_s = torch.tensor(1.5, dtype=torch.float32)\n", - "cosmology = caustic.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology = caustic.FlatLambdaCDM(name=\"cosmo\")\n", "cosmology.to(dtype=torch.float32)" ] }, @@ -78,6 +83,7 @@ "source": [ "# demo simulator with sersic source, SIE lens. then sample some examples. demo the model graph\n", "\n", + "\n", "class Simple_Sim(caustic.Simulator):\n", " def __init__(\n", " self,\n", @@ -86,7 +92,7 @@ " z_s=None,\n", " name: str = \"sim\",\n", " ):\n", - " super().__init__(name) # need this so `Parametrized` can do its magic\n", + " super().__init__(name) # need this so `Parametrized` can do its magic\n", "\n", " # These are the lens and source objects to keep track of\n", " self.lens = lens\n", @@ -95,7 +101,7 @@ " # Here we can add a parameter to the simulator, in this case it is `z_s` which we will need later\n", " self.add_param(\"z_s\", z_s)\n", "\n", - " def forward(self, params):# define the forward model\n", + " def forward(self, params): # define the forward model\n", " # Here the simulator unpacks the parameter it needs\n", " z_s = self.unpack(params)\n", "\n", @@ -337,8 +343,8 @@ } ], "source": [ - "sie = caustic.lenses.SIE(cosmology, name = \"sie\")\n", - "src = caustic.sources.Sersic(name = \"src\")\n", + "sie = caustic.lenses.SIE(cosmology, name=\"sie\")\n", + "src = caustic.sources.Sersic(name=\"src\")\n", "\n", "sim = Simple_Sim(sie, src, torch.tensor(0.8))\n", "\n", @@ -392,21 +398,23 @@ ], "source": [ "# Reading the x_keys above we can input the parameters that we would like the simulator to evaluate\n", - "x = torch.tensor([\n", - " z_l.item(), # sie z_l\n", - " 0.7, # sie x0\n", - " 0.13, # sie y0\n", - " 0.4, # sie q\n", - " np.pi/5, # sie phi\n", - " 1., # sie b\n", - " 0.2, # src x0\n", - " 0.5, # src y0\n", - " 0.5, # src q\n", - " -np.pi/4, # src phi\n", - " 1.5, # src n\n", - " 2.5, # src Re\n", - " 1., # src Ie\n", - "])\n", + "x = torch.tensor(\n", + " [\n", + " z_l.item(), # sie z_l\n", + " 0.7, # sie x0\n", + " 0.13, # sie y0\n", + " 0.4, # sie q\n", + " np.pi / 5, # sie phi\n", + " 1.0, # sie b\n", + " 0.2, # src x0\n", + " 0.5, # src y0\n", + " 0.5, # src q\n", + " -np.pi / 4, # src phi\n", + " 1.5, # src n\n", + " 2.5, # src Re\n", + " 1.0, # src Ie\n", + " ]\n", + ")\n", "plt.imshow(sim(x), origin=\"lower\")\n", "plt.show()" ] diff --git a/jupyter_book/_config.yml b/jupyter_book/_config.yml index 0a23c1de..bea0c2ae 100644 --- a/jupyter_book/_config.yml +++ b/jupyter_book/_config.yml @@ -21,9 +21,9 @@ latex: # Information about where the book exists on the web repository: - url: https://github.com/executablebooks/jupyter-book # Online location of your book - path_to_book: docs # Optional path to your book, relative to the repository root - branch: master # Which branch of the repository should be used when creating links (optional) + url: https://github.com/executablebooks/jupyter-book # Online location of your book + path_to_book: docs # Optional path to your book, relative to the repository root + branch: master # Which branch of the repository should be used when creating links (optional) # Add GitHub buttons to your book # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository diff --git a/jupyter_book/_toc.yml b/jupyter_book/_toc.yml index 23b8900a..d0569ec0 100644 --- a/jupyter_book/_toc.yml +++ b/jupyter_book/_toc.yml @@ -4,10 +4,10 @@ format: jb-book root: intro chapters: -- file: docs/getting_started.rst # Getting Started -- file: docs/install.rst # Installation -- file: docs/tutorials.rst # Tutorials -- file: docs/contributing.rst # Contributing -- file: docs/citation.rst # Citation -- file: docs/license.rst # Caustics -- file: docs/modules.rst # modules \ No newline at end of file + - file: docs/getting_started.rst # Getting Started + - file: docs/install.rst # Installation + - file: docs/tutorials.rst # Tutorials + - file: docs/contributing.rst # Contributing + - file: docs/citation.rst # Citation + - file: docs/license.rst # Caustics + - file: docs/modules.rst # modules diff --git a/jupyter_book/intro.md b/jupyter_book/intro.md index 35018a1f..033114d2 100644 --- a/jupyter_book/intro.md +++ b/jupyter_book/intro.md @@ -1,20 +1,25 @@ # Welcome to Caustics’ documentation! -The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. +The lensing pipeline of the future: GPU-accelerated, +automatically-differentiable, highly modular and extensible. + ```{note} Caustic is in its early development phase. This means the API will change with time. These changes are a good thing, but they can be annoying. Watch the version numbers, when we get to 1.0.0 that will be the first stable release! ``` # Intallation + The easiest way to install is to make a new virtual environment then run: ```console pip install caustic ``` -this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. If you want to help out with building the caustic code base check out the developer installation instructions instead. - +this will install all the required libraries and then install caustic and you +are ready to go! You can check out the tutorials afterwards to see some of +caustic's capabilities. If you want to help out with building the caustic code +base check out the developer installation instructions instead. # Read The Docs -## Contents +## Contents diff --git a/jupyter_book/tutorials.rst b/jupyter_book/tutorials.rst index a5997413..59f7ebfc 100644 --- a/jupyter_book/tutorials.rst +++ b/jupyter_book/tutorials.rst @@ -14,10 +14,10 @@ version of each tutorial is available here. VisualizeCaustics MultiplaneDemo InvertLensEquation - - - - - - - + + + + + + + diff --git a/src/caustics/light/sersic.py b/src/caustics/light/sersic.py index daf2d430..e0abc63b 100644 --- a/src/caustics/light/sersic.py +++ b/src/caustics/light/sersic.py @@ -107,48 +107,48 @@ def brightness( **kwargs, ): """ - Implements the `brightness` method for `Sersic`. The brightness at a given point is - determined by the Sersic profile formula. - -<<<<<<< HEAD:src/caustics/light/sersic.py - Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - params (Packed, optional): Dynamic parameter container. -======= - Parameters - ---------- - x: Tensor - The x-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - y: Tensor - The y-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - params: (Packed, optional) - Dynamic parameter container. ->>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py - - Returns - ------- - Tensor - The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. - -<<<<<<< HEAD:src/caustics/light/sersic.py - Notes: -======= - Notes - ----- ->>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py - The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), - where Ie is the intensity at the effective radius r_e, n is the Sersic index - that describes the concentration of the source, and k is a parameter that - depends on n. In this implementation, we use elliptical coordinates ex and ey, - and the transformation from Cartesian coordinates is handled by `to_elliptical`. - The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. - If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, - otherwise, we use the approximation from Ciotti & Bertin (1999). + Implements the `brightness` method for `Sersic`. The brightness at a given point is + determined by the Sersic profile formula. + + <<<<<<< HEAD:src/caustics/light/sersic.py + Args: + x (Tensor): The x-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + y (Tensor): The y-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + params (Packed, optional): Dynamic parameter container. + ======= + Parameters + ---------- + x: Tensor + The x-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + y: Tensor + The y-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + params: (Packed, optional) + Dynamic parameter container. + >>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py + + Returns + ------- + Tensor + The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. + + <<<<<<< HEAD:src/caustics/light/sersic.py + Notes: + ======= + Notes + ----- + >>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py + The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), + where Ie is the intensity at the effective radius r_e, n is the Sersic index + that describes the concentration of the source, and k is a parameter that + depends on n. In this implementation, we use elliptical coordinates ex and ey, + and the transformation from Cartesian coordinates is handled by `to_elliptical`. + The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. + If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, + otherwise, we use the approximation from Ciotti & Bertin (1999). """ x, y = translate_rotate(x, y, x0, y0, phi) ex, ey = to_elliptical(x, y, q) From 50a25dceb946df6738e3656dadb2a2740768cb28 Mon Sep 17 00:00:00 2001 From: Landung 'Don' Setiawan Date: Mon, 27 Nov 2023 16:08:22 -0800 Subject: [PATCH 47/55] fix: Fix some leftover merge conflicts --- src/caustics/lenses/base.py | 29 ------------------ src/caustics/lenses/nfw.py | 4 --- src/caustics/light/sersic.py | 59 ++++++++++++++---------------------- 3 files changed, 23 insertions(+), 69 deletions(-) diff --git a/src/caustics/lenses/base.py b/src/caustics/lenses/base.py index d951e1d9..c6a26d2c 100644 --- a/src/caustics/lenses/base.py +++ b/src/caustics/lenses/base.py @@ -135,11 +135,7 @@ def forward_raytrace( Ray-traced coordinates in the x and y directions. """ -<<<<<<< HEAD:src/caustics/lenses/base.py bxy = torch.stack((bx, by)).repeat(n_init, 1) # has shape (n_init, Dout:2) -======= - bxy = torch.stack((bx, by)).repeat(n_init,1) # has shape (n_init, Dout:2) ->>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py # TODO make FOV more general so that it doesnt have to be centered on zero,zero if fov is None: @@ -174,10 +170,6 @@ def forward_raytrace( return res[..., 0], res[..., 1] -<<<<<<< HEAD:src/caustics/lenses/base.py -======= - ->>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py class ThickLens(Lens): """ Base class for modeling gravitational lenses that cannot be treated using the thin lens approximation. @@ -201,15 +193,6 @@ def reduced_deflection_angle( ) -> tuple[Tensor, Tensor]: """ ThickLens objects do not have a reduced deflection angle since the distance D_ls is undefined -<<<<<<< HEAD:src/caustics/lenses/base.py - - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. -======= ->>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py Parameters ---------- @@ -247,13 +230,6 @@ def effective_reduced_deflection_angle( angular coordinates, and $\beta$ are the angular coordinates to the source plane. -<<<<<<< HEAD:src/caustics/lenses/base.py - Args: - x (Tensor): Tensor of x coordinates in the lens plane. - y (Tensor): Tensor of y coordinates in the lens plane. - z_s (Tensor): Tensor of source redshifts. - params (Packed, optional): Dynamic parameter container for the lens model. Defaults to None. -======= Parameters ---------- x: Tensor @@ -264,7 +240,6 @@ def effective_reduced_deflection_angle( Tensor of source redshifts. params: (Packed, optional) Dynamic parameter container for the lens model. Defaults to None. ->>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py """ bx, by = self.raytrace(x, y, z_s, params) @@ -963,7 +938,3 @@ def _jacobian_lens_equation_autograd( # Build Jacobian J = self._jacobian_deflection_angle_autograd(x, y, z_s, params, **kwargs) return torch.eye(2) - J.detach() -<<<<<<< HEAD:src/caustics/lenses/base.py -======= - ->>>>>>> d79e9f4 (change to numpy doc string for base,py in lensors):caustic/lenses/base.py diff --git a/src/caustics/lenses/nfw.py b/src/caustics/lenses/nfw.py index 1870fbdf..4f70ed27 100644 --- a/src/caustics/lenses/nfw.py +++ b/src/caustics/lenses/nfw.py @@ -362,10 +362,6 @@ def _h_batchable(x: Tensor) -> Tensor: ), ) return term_1 + term_2 -<<<<<<< HEAD:src/caustics/lenses/nfw.py -======= - ->>>>>>> 2a501f3 (update nfw.py):caustic/lenses/nfw.py @unpack(3) def reduced_deflection_angle( diff --git a/src/caustics/light/sersic.py b/src/caustics/light/sersic.py index e0abc63b..c913534d 100644 --- a/src/caustics/light/sersic.py +++ b/src/caustics/light/sersic.py @@ -107,48 +107,35 @@ def brightness( **kwargs, ): """ - Implements the `brightness` method for `Sersic`. The brightness at a given point is - determined by the Sersic profile formula. - - <<<<<<< HEAD:src/caustics/light/sersic.py - Args: - x (Tensor): The x-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - y (Tensor): The y-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - params (Packed, optional): Dynamic parameter container. - ======= - Parameters - ---------- - x: Tensor - The x-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - y: Tensor - The y-coordinate(s) at which to calculate the source brightness. - This could be a single value or a tensor of values. - params: (Packed, optional) - Dynamic parameter container. - >>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py + Implements the `brightness` method for `Sersic`. The brightness at a given point is + determined by the Sersic profile formula. + + Parameters + ---------- + x: Tensor + The x-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + y: Tensor + The y-coordinate(s) at which to calculate the source brightness. + This could be a single value or a tensor of values. + params: (Packed, optional) + Dynamic parameter container. Returns ------- Tensor The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. - <<<<<<< HEAD:src/caustics/light/sersic.py - Notes: - ======= - Notes - ----- - >>>>>>> 7c3905f (change to numpy docstring for data dir):caustic/light/sersic.py - The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), - where Ie is the intensity at the effective radius r_e, n is the Sersic index - that describes the concentration of the source, and k is a parameter that - depends on n. In this implementation, we use elliptical coordinates ex and ey, - and the transformation from Cartesian coordinates is handled by `to_elliptical`. - The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. - If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, - otherwise, we use the approximation from Ciotti & Bertin (1999). + Notes + ----- + The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), + where Ie is the intensity at the effective radius r_e, n is the Sersic index + that describes the concentration of the source, and k is a parameter that + depends on n. In this implementation, we use elliptical coordinates ex and ey, + and the transformation from Cartesian coordinates is handled by `to_elliptical`. + The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. + If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, + otherwise, we use the approximation from Ciotti & Bertin (1999). """ x, y = translate_rotate(x, y, x0, y0, phi) ex, ey = to_elliptical(x, y, q) From c9616623a2664eaff0a8f62f3d10e0b4d060af91 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 00:10:27 +0000 Subject: [PATCH 48/55] style: pre-commit fixes --- jupyter_book/tutorials.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/jupyter_book/tutorials.rst b/jupyter_book/tutorials.rst index 59f7ebfc..e9dea14b 100644 --- a/jupyter_book/tutorials.rst +++ b/jupyter_book/tutorials.rst @@ -14,10 +14,3 @@ version of each tutorial is available here. VisualizeCaustics MultiplaneDemo InvertLensEquation - - - - - - - From 4bfd736dc75b49f5284ca1bbde4382e0032413d9 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Mon, 27 Nov 2023 20:11:01 -0800 Subject: [PATCH 49/55] create a book directory --- docs/index.rst | 4 +- docs/tutorials.rst | 8 +-- jupyter_book/_toc.yml | 13 ----- jupyter_book/getting_started.rst | 20 ------- jupyter_book/modules.rst | 7 --- {jupyter_book => testbook}/_config.yml | 6 +-- testbook/_toc.yml | 12 +++++ {jupyter_book => testbook}/citation.rst | 0 {jupyter_book => testbook}/contributing.rst | 0 {docs => testbook}/get_start.ipynb | 2 +- {jupyter_book => testbook}/install.rst | 0 {jupyter_book => testbook}/intro.md | 11 ++-- {jupyter_book => testbook}/license.rst | 0 {jupyter_book => testbook}/logo.png | Bin testbook/references.bib | 56 ++++++++++++++++++++ {jupyter_book => testbook}/requirements.txt | 0 {jupyter_book => testbook}/tutorials.rst | 22 +++----- 17 files changed, 89 insertions(+), 72 deletions(-) delete mode 100644 jupyter_book/_toc.yml delete mode 100644 jupyter_book/getting_started.rst delete mode 100644 jupyter_book/modules.rst rename {jupyter_book => testbook}/_config.yml (94%) create mode 100644 testbook/_toc.yml rename {jupyter_book => testbook}/citation.rst (100%) rename {jupyter_book => testbook}/contributing.rst (100%) rename {docs => testbook}/get_start.ipynb (99%) rename {jupyter_book => testbook}/install.rst (100%) rename {jupyter_book => testbook}/intro.md (87%) rename {jupyter_book => testbook}/license.rst (100%) rename {jupyter_book => testbook}/logo.png (100%) create mode 100644 testbook/references.bib rename {jupyter_book => testbook}/requirements.txt (100%) rename {jupyter_book => testbook}/tutorials.rst (56%) diff --git a/docs/index.rst b/docs/index.rst index a0702a5e..ba8acc33 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,7 +34,7 @@ Read The Docs contributing.rst citation.rst license.rst - + Indices and tables ================== @@ -42,7 +42,7 @@ Indices and tables :maxdepth: 1 modules.rst - + * :ref:`genindex` * :ref:`modindex` * :ref:`search` diff --git a/docs/tutorials.rst b/docs/tutorials.rst index a5997413..e15d0731 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -14,10 +14,4 @@ version of each tutorial is available here. VisualizeCaustics MultiplaneDemo InvertLensEquation - - - - - - - + diff --git a/jupyter_book/_toc.yml b/jupyter_book/_toc.yml deleted file mode 100644 index 23b8900a..00000000 --- a/jupyter_book/_toc.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Table of contents -# Learn more at https://jupyterbook.org/customize/toc.html - -format: jb-book -root: intro -chapters: -- file: docs/getting_started.rst # Getting Started -- file: docs/install.rst # Installation -- file: docs/tutorials.rst # Tutorials -- file: docs/contributing.rst # Contributing -- file: docs/citation.rst # Citation -- file: docs/license.rst # Caustics -- file: docs/modules.rst # modules \ No newline at end of file diff --git a/jupyter_book/getting_started.rst b/jupyter_book/getting_started.rst deleted file mode 100644 index 1bd0d949..00000000 --- a/jupyter_book/getting_started.rst +++ /dev/null @@ -1,20 +0,0 @@ - -Getting Started -=============== - -Install -------- - -Please follow the instructions on the :doc:`install` page. For most users, the basic pip install is all that's needed. - - -Tutorials ---------- - -We have created a repository of tutorial Jupyter notebooks that can help initiate you on the main features of caustic. Please checkout the `tutorials here `_ to see what caustic can do! - - -Read The Docs -------------- - -Docs for all the main functions in caustic are avaialble at :doc:`caustic` at varying degrees of completeness. Further development of the docs is always ongoing. diff --git a/jupyter_book/modules.rst b/jupyter_book/modules.rst deleted file mode 100644 index a922b553..00000000 --- a/jupyter_book/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -caustic -======= - -.. toctree:: - :maxdepth: 4 - - caustic diff --git a/jupyter_book/_config.yml b/testbook/_config.yml similarity index 94% rename from jupyter_book/_config.yml rename to testbook/_config.yml index 0a23c1de..bdd57892 100644 --- a/jupyter_book/_config.yml +++ b/testbook/_config.yml @@ -1,7 +1,7 @@ # Book settings # Learn more at https://jupyterbook.org/customize/config.html -title: caustic +title: Caustic author: Ciela Institute logo: logo.png @@ -16,8 +16,8 @@ latex: targetname: book.tex # Add a bibtex file so that we can create citations -# bibtex_bibfiles: -# - references.bib +bibtex_bibfiles: + - references.bib # Information about where the book exists on the web repository: diff --git a/testbook/_toc.yml b/testbook/_toc.yml new file mode 100644 index 00000000..66d7e60d --- /dev/null +++ b/testbook/_toc.yml @@ -0,0 +1,12 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: intro +chapters: +- file: get_start +- file: install +- file: tutorials +- file: contributing +- file: citation +- file: license \ No newline at end of file diff --git a/jupyter_book/citation.rst b/testbook/citation.rst similarity index 100% rename from jupyter_book/citation.rst rename to testbook/citation.rst diff --git a/jupyter_book/contributing.rst b/testbook/contributing.rst similarity index 100% rename from jupyter_book/contributing.rst rename to testbook/contributing.rst diff --git a/docs/get_start.ipynb b/testbook/get_start.ipynb similarity index 99% rename from docs/get_start.ipynb rename to testbook/get_start.ipynb index 21a64c2f..ede97429 100644 --- a/docs/get_start.ipynb +++ b/testbook/get_start.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Welcome to Caustic!\n", + "# Getting started!\n", "\n", "In need of a differentiable strong gravitational lensing simulation package? Look no further! We have all your lensing simulator needs. In this tutorial we will cover the basics of caustic and how to get going making your own lensing configurations. Caustic is easy to use and very powerful, you will get to see some of that power here, but there will be more notebooks which demo specific use cases." ] diff --git a/jupyter_book/install.rst b/testbook/install.rst similarity index 100% rename from jupyter_book/install.rst rename to testbook/install.rst diff --git a/jupyter_book/intro.md b/testbook/intro.md similarity index 87% rename from jupyter_book/intro.md rename to testbook/intro.md index 35018a1f..d71f5cd0 100644 --- a/jupyter_book/intro.md +++ b/testbook/intro.md @@ -1,11 +1,11 @@ -# Welcome to Caustics’ documentation! +# Welcome to Caustics’ documentation! The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. ```{note} Caustic is in its early development phase. This means the API will change with time. These changes are a good thing, but they can be annoying. Watch the version numbers, when we get to 1.0.0 that will be the first stable release! ``` -# Intallation +## Intallation The easiest way to install is to make a new virtual environment then run: ```console @@ -15,6 +15,7 @@ pip install caustic this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. If you want to help out with building the caustic code base check out the developer installation instructions instead. -# Read The Docs -## Contents - +## Read The Docs +### Contents +```{tableofcontents} +``` diff --git a/jupyter_book/license.rst b/testbook/license.rst similarity index 100% rename from jupyter_book/license.rst rename to testbook/license.rst diff --git a/jupyter_book/logo.png b/testbook/logo.png similarity index 100% rename from jupyter_book/logo.png rename to testbook/logo.png diff --git a/testbook/references.bib b/testbook/references.bib new file mode 100644 index 00000000..783ec6aa --- /dev/null +++ b/testbook/references.bib @@ -0,0 +1,56 @@ +--- +--- + +@inproceedings{holdgraf_evidence_2014, + address = {Brisbane, Australia, Australia}, + title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}}, + booktitle = {International {Conference} on {Cognitive} {Neuroscience}}, + publisher = {Frontiers in Neuroscience}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.}, + year = {2014} +} + +@article{holdgraf_rapid_2016, + title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility}, + volume = {7}, + issn = {2041-1723}, + url = {http://www.nature.com/doifinder/10.1038/ncomms13654}, + doi = {10.1038/ncomms13654}, + number = {May}, + journal = {Nature Communications}, + author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.}, + year = {2016}, + pages = {13654}, + file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf} +} + +@inproceedings{holdgraf_portable_2017, + title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science}, + volume = {Part F1287}, + isbn = {978-1-4503-5272-7}, + doi = {10.1145/3093338.3093370}, + abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.}, + booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}}, + author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.}, + year = {2017}, + keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy} +} + +@article{holdgraf_encoding_2017, + title = {Encoding and decoding models in cognitive electrophysiology}, + volume = {11}, + issn = {16625137}, + doi = {10.3389/fnsys.2017.00061}, + abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.}, + journal = {Frontiers in Systems Neuroscience}, + author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.}, + year = {2017}, + keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials} +} + +@book{ruby, + title = {The Ruby Programming Language}, + author = {Flanagan, David and Matsumoto, Yukihiro}, + year = {2008}, + publisher = {O'Reilly Media} +} diff --git a/jupyter_book/requirements.txt b/testbook/requirements.txt similarity index 100% rename from jupyter_book/requirements.txt rename to testbook/requirements.txt diff --git a/jupyter_book/tutorials.rst b/testbook/tutorials.rst similarity index 56% rename from jupyter_book/tutorials.rst rename to testbook/tutorials.rst index a5997413..1e903704 100644 --- a/jupyter_book/tutorials.rst +++ b/testbook/tutorials.rst @@ -6,18 +6,12 @@ Here you will find the jupyter notebook tutorials. It is recommended that you go through the tutorials yourself, but for quick reference a version of each tutorial is available here. -.. toctree:: - :maxdepth: 1 +.. .. toctree:: +.. :maxdepth: 1 + +.. BasicIntroduction +.. LensZoo +.. VisualizeCaustics +.. MultiplaneDemo +.. InvertLensEquation - BasicIntroduction - LensZoo - VisualizeCaustics - MultiplaneDemo - InvertLensEquation - - - - - - - From 15aae0c5079f4cf05224976df5cb38706091a379 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 04:15:37 +0000 Subject: [PATCH 50/55] style: pre-commit fixes --- src/caustics/lenses/pixelated_convergence.py | 2 +- src/caustics/parametrized.py | 6 +-- src/caustics/utils.py | 4 +- testbook/_config.yml | 6 +-- testbook/_toc.yml | 12 ++--- testbook/get_start.ipynb | 50 ++++++++++++-------- testbook/intro.md | 14 ++++-- testbook/tutorials.rst | 1 - 8 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/caustics/lenses/pixelated_convergence.py b/src/caustics/lenses/pixelated_convergence.py index a4f8813c..fd837609 100644 --- a/src/caustics/lenses/pixelated_convergence.py +++ b/src/caustics/lenses/pixelated_convergence.py @@ -339,7 +339,7 @@ def _deflection_angle_conv2d( deflection_angle_x = F.conv2d(self.ax_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( self.pixelscale**2 / pi ||||||| c8d51f7:caustic/lenses/pixelated_convergence.py - + pad = 2 * self.n_pix convergence_map_flipped = convergence_map.flip((-1, -2))[None, None] # F.pad(, ((pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2), mode = self.padding_mode) deflection_angle_x = F.conv2d(self.ax_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( diff --git a/src/caustics/parametrized.py b/src/caustics/parametrized.py index 3087f429..7bd1a71c 100644 --- a/src/caustics/parametrized.py +++ b/src/caustics/parametrized.py @@ -234,8 +234,8 @@ def pack( ||||||| c8d51f7:caustic/parametrized.py - - + + ======= >>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91:src/caustics/parametrized.py @@ -264,7 +264,7 @@ def pack( <<<<<<< HEAD:caustic/parametrized.py n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) ||||||| c8d51f7:caustic/parametrized.py - n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) + n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) ======= n_expected = sum( [module.dynamic_size for module in self.dynamic_modules.values()] diff --git a/src/caustics/utils.py b/src/caustics/utils.py index 768d4375..470fada2 100644 --- a/src/caustics/utils.py +++ b/src/caustics/utils.py @@ -519,7 +519,7 @@ def gaussian(pixelscale, nx, ny, sigma, upsample = 1, dtype = torch.float32, dev ||||||| c8d51f7:caustic/utils.py def gaussian(pixelscale, nx, ny, sigma, upsample = 1, dtype = torch.float32, device = None): - + ======= def gaussian(pixelscale, nx, ny, sigma, upsample=1, dtype=torch.float32, device=None): @@ -543,7 +543,7 @@ def gaussian(pixelscale, nx, ny, sigma, upsample=1, dtype=torch.float32, device= ||||||| c8d51f7:caustic/utils.py Z = np.exp(- 0.5 * (X**2 + Y**2) / sigma**2) - + ======= Z = np.exp(-0.5 * (X**2 + Y**2) / sigma**2) diff --git a/testbook/_config.yml b/testbook/_config.yml index bdd57892..29706f85 100644 --- a/testbook/_config.yml +++ b/testbook/_config.yml @@ -21,9 +21,9 @@ bibtex_bibfiles: # Information about where the book exists on the web repository: - url: https://github.com/executablebooks/jupyter-book # Online location of your book - path_to_book: docs # Optional path to your book, relative to the repository root - branch: master # Which branch of the repository should be used when creating links (optional) + url: https://github.com/executablebooks/jupyter-book # Online location of your book + path_to_book: docs # Optional path to your book, relative to the repository root + branch: master # Which branch of the repository should be used when creating links (optional) # Add GitHub buttons to your book # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository diff --git a/testbook/_toc.yml b/testbook/_toc.yml index 66d7e60d..8eef7643 100644 --- a/testbook/_toc.yml +++ b/testbook/_toc.yml @@ -4,9 +4,9 @@ format: jb-book root: intro chapters: -- file: get_start -- file: install -- file: tutorials -- file: contributing -- file: citation -- file: license \ No newline at end of file + - file: get_start + - file: install + - file: tutorials + - file: contributing + - file: citation + - file: license diff --git a/testbook/get_start.ipynb b/testbook/get_start.ipynb index ede97429..a649406e 100644 --- a/testbook/get_start.ipynb +++ b/testbook/get_start.ipynb @@ -54,10 +54,15 @@ "res = 0.05\n", "upsample_factor = 2\n", "fov = res * n_pix\n", - "thx, thy = caustic.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "thx, thy = caustic.get_meshgrid(\n", + " res / upsample_factor,\n", + " upsample_factor * n_pix,\n", + " upsample_factor * n_pix,\n", + " dtype=torch.float32,\n", + ")\n", "z_l = torch.tensor(0.5, dtype=torch.float32)\n", "z_s = torch.tensor(1.5, dtype=torch.float32)\n", - "cosmology = caustic.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology = caustic.FlatLambdaCDM(name=\"cosmo\")\n", "cosmology.to(dtype=torch.float32)" ] }, @@ -78,6 +83,7 @@ "source": [ "# demo simulator with sersic source, SIE lens. then sample some examples. demo the model graph\n", "\n", + "\n", "class Simple_Sim(caustic.Simulator):\n", " def __init__(\n", " self,\n", @@ -86,7 +92,7 @@ " z_s=None,\n", " name: str = \"sim\",\n", " ):\n", - " super().__init__(name) # need this so `Parametrized` can do its magic\n", + " super().__init__(name) # need this so `Parametrized` can do its magic\n", "\n", " # These are the lens and source objects to keep track of\n", " self.lens = lens\n", @@ -95,7 +101,7 @@ " # Here we can add a parameter to the simulator, in this case it is `z_s` which we will need later\n", " self.add_param(\"z_s\", z_s)\n", "\n", - " def forward(self, params):# define the forward model\n", + " def forward(self, params): # define the forward model\n", " # Here the simulator unpacks the parameter it needs\n", " z_s = self.unpack(params)\n", "\n", @@ -337,8 +343,8 @@ } ], "source": [ - "sie = caustic.lenses.SIE(cosmology, name = \"sie\")\n", - "src = caustic.sources.Sersic(name = \"src\")\n", + "sie = caustic.lenses.SIE(cosmology, name=\"sie\")\n", + "src = caustic.sources.Sersic(name=\"src\")\n", "\n", "sim = Simple_Sim(sie, src, torch.tensor(0.8))\n", "\n", @@ -392,21 +398,23 @@ ], "source": [ "# Reading the x_keys above we can input the parameters that we would like the simulator to evaluate\n", - "x = torch.tensor([\n", - " z_l.item(), # sie z_l\n", - " 0.7, # sie x0\n", - " 0.13, # sie y0\n", - " 0.4, # sie q\n", - " np.pi/5, # sie phi\n", - " 1., # sie b\n", - " 0.2, # src x0\n", - " 0.5, # src y0\n", - " 0.5, # src q\n", - " -np.pi/4, # src phi\n", - " 1.5, # src n\n", - " 2.5, # src Re\n", - " 1., # src Ie\n", - "])\n", + "x = torch.tensor(\n", + " [\n", + " z_l.item(), # sie z_l\n", + " 0.7, # sie x0\n", + " 0.13, # sie y0\n", + " 0.4, # sie q\n", + " np.pi / 5, # sie phi\n", + " 1.0, # sie b\n", + " 0.2, # src x0\n", + " 0.5, # src y0\n", + " 0.5, # src q\n", + " -np.pi / 4, # src phi\n", + " 1.5, # src n\n", + " 2.5, # src Re\n", + " 1.0, # src Ie\n", + " ]\n", + ")\n", "plt.imshow(sim(x), origin=\"lower\")\n", "plt.show()" ] diff --git a/testbook/intro.md b/testbook/intro.md index d71f5cd0..f2e296e4 100644 --- a/testbook/intro.md +++ b/testbook/intro.md @@ -1,21 +1,29 @@ # Welcome to Caustics’ documentation! -The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. +The lensing pipeline of the future: GPU-accelerated, +automatically-differentiable, highly modular and extensible. + ```{note} Caustic is in its early development phase. This means the API will change with time. These changes are a good thing, but they can be annoying. Watch the version numbers, when we get to 1.0.0 that will be the first stable release! ``` ## Intallation + The easiest way to install is to make a new virtual environment then run: ```console pip install caustic ``` -this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. If you want to help out with building the caustic code base check out the developer installation instructions instead. - +this will install all the required libraries and then install caustic and you +are ready to go! You can check out the tutorials afterwards to see some of +caustic's capabilities. If you want to help out with building the caustic code +base check out the developer installation instructions instead. ## Read The Docs + ### Contents + ```{tableofcontents} + ``` diff --git a/testbook/tutorials.rst b/testbook/tutorials.rst index 1e903704..d8cfc745 100644 --- a/testbook/tutorials.rst +++ b/testbook/tutorials.rst @@ -14,4 +14,3 @@ version of each tutorial is available here. .. VisualizeCaustics .. MultiplaneDemo .. InvertLensEquation - From 526432d1789d0b5b6e34ebfbc8f97dd1174d1511 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Tue, 28 Nov 2023 14:29:58 -0800 Subject: [PATCH 51/55] delete origin book --- jupyter_book/_config.yml | 32 ---------------- jupyter_book/_toc.yml | 13 ------- jupyter_book/citation.rst | 6 --- jupyter_book/contributing.rst | 64 ------------------------------- jupyter_book/getting_started.rst | 20 ---------- jupyter_book/install.rst | 30 --------------- jupyter_book/intro.md | 25 ------------ jupyter_book/license.rst | 25 ------------ jupyter_book/logo.png | Bin 9854 -> 0 bytes jupyter_book/modules.rst | 7 ---- jupyter_book/requirements.txt | 3 -- jupyter_book/tutorials.rst | 16 -------- 12 files changed, 241 deletions(-) delete mode 100644 jupyter_book/_config.yml delete mode 100644 jupyter_book/_toc.yml delete mode 100644 jupyter_book/citation.rst delete mode 100644 jupyter_book/contributing.rst delete mode 100644 jupyter_book/getting_started.rst delete mode 100644 jupyter_book/install.rst delete mode 100644 jupyter_book/intro.md delete mode 100644 jupyter_book/license.rst delete mode 100644 jupyter_book/logo.png delete mode 100644 jupyter_book/modules.rst delete mode 100644 jupyter_book/requirements.txt delete mode 100644 jupyter_book/tutorials.rst diff --git a/jupyter_book/_config.yml b/jupyter_book/_config.yml deleted file mode 100644 index bea0c2ae..00000000 --- a/jupyter_book/_config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Book settings -# Learn more at https://jupyterbook.org/customize/config.html - -title: caustic -author: Ciela Institute -logo: logo.png - -# Force re-execution of notebooks on each build. -# See https://jupyterbook.org/content/execute.html -execute: - execute_notebooks: force - -# Define the name of the latex output file for PDF builds -latex: - latex_documents: - targetname: book.tex - -# Add a bibtex file so that we can create citations -# bibtex_bibfiles: -# - references.bib - -# Information about where the book exists on the web -repository: - url: https://github.com/executablebooks/jupyter-book # Online location of your book - path_to_book: docs # Optional path to your book, relative to the repository root - branch: master # Which branch of the repository should be used when creating links (optional) - -# Add GitHub buttons to your book -# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository -html: - use_issues_button: true - use_repository_button: true diff --git a/jupyter_book/_toc.yml b/jupyter_book/_toc.yml deleted file mode 100644 index d0569ec0..00000000 --- a/jupyter_book/_toc.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Table of contents -# Learn more at https://jupyterbook.org/customize/toc.html - -format: jb-book -root: intro -chapters: - - file: docs/getting_started.rst # Getting Started - - file: docs/install.rst # Installation - - file: docs/tutorials.rst # Tutorials - - file: docs/contributing.rst # Contributing - - file: docs/citation.rst # Citation - - file: docs/license.rst # Caustics - - file: docs/modules.rst # modules diff --git a/jupyter_book/citation.rst b/jupyter_book/citation.rst deleted file mode 100644 index 0d200348..00000000 --- a/jupyter_book/citation.rst +++ /dev/null @@ -1,6 +0,0 @@ - - -Citation -======== - -Paper comming soon! We will put all the citation information here when its ready. diff --git a/jupyter_book/contributing.rst b/jupyter_book/contributing.rst deleted file mode 100644 index b384222c..00000000 --- a/jupyter_book/contributing.rst +++ /dev/null @@ -1,64 +0,0 @@ -Contributing -============ - -.. contents:: Table of Contents - :local: - -Thanks for helping make caustic better! Here you will learn the full process needed to contribute to caustic. Following these steps will make the process as painless as possible for everyone. - -Create An Issue ---------------- - -Before actually writing any code, its best to create an issue on the GitHub. Describe the issue in detail and let us know the desired solution. Here it will be possible to address concerns (maybe its aready solved and just not yet documented) and plan out the best solution. We may also assign someone to work on it if that seems better. Note that submitting an issue is a contribution to caustic, we appreciate your ideas! Still, if after discussion it seems that the problem does need some work and you're the person to do it, then we can move on to the next steps. - -1. Navigate to the **Issues** tab of the GitHub repository. -2. Click on **New Issue**. -3. Specify a concise and descriptive title. -4. In the issue body, elaborate on the problem or feature request, employing adequate code snippets or references as necessary. -5. Submit the issue by clicking **Submit new issue**. - -Install -------- - -Please fork the caustic repo, then follow the developer install instructions at the :doc:`install` page. This will ensure you have a version of caustic that you can tinker with and see the results. - -The reason you should fork the repo is so that you have full control while making your edits. You will still be able to make a Pull Request later when it is time to merge your code with the main caustic branch. Note that you should keep your fork up to date with the caustic repo to make the merge as smooth as possible. - -Notebooks ---------- - -You will likely want to see how your changes affect various features of caustic. A good way to quickly see this is to run the tutorial notebooks which can be found `here `_. Any change that breaks one of these must be addressed, either by changing the nature of your updates to the code, or by forking and updating the caustic-tutorials repo as well (this is usually pretty easy). - -Resolving the Issue -------------------- - -As you modify the code, make sure to regularly commit changes and push them to your fork. This makes it easier for you to fix something if you make a mistake, and easier for us to see what changes were made along the way. Feel free to return to the issue on the main GitHub page for advice as to proceed. - -1. Make the necessary code modifications to address the issue. -2. Use ``git status`` to inspect the changes. -3. Execute ``git add .`` to stage the changes. -4. Commit the changes with ``git commit -m ""``. -5. Push the changes to your fork by executing ``git push origin ``. - -Unit Tests ----------- - -When you think you've solved an issue, please make unit tests related to any code you have added. Any new code added to caustic must have unit tests which match the level of completion of the rest of the code. Generally you should test all cases for the newly added code. Also ensure the previous unit tests run correctly. - -Submitting a Pull Request -------------------------- - -Once you think your updates are ready to merge with the rest of caustic you can submit a PR! This should provide a description of what you have changed and if it isn't straightforward, why you made those changes. - -1. Navigate to the **Pull Requests** tab of the original repository. -2. Click on **New Pull Request**. -3. Choose the appropriate base and compare branches. -4. Provide a concise and descriptive title and elaborate on the pull request body. -5. Click **Create Pull Request**. - -Finalizing a Pull Request -------------------------- - -Once the PR is submitted, we will look through it and request any changes necessary before merging it into the main branch. You can make those changes just like any other edits on your fork. Then when you push them, they will be joined in to the PR automatically and any unit tests will run again. - -Once the PR has been merged, you may delete your fork if you aren't using it any more, or take on a new issue, it's up to you! diff --git a/jupyter_book/getting_started.rst b/jupyter_book/getting_started.rst deleted file mode 100644 index 1bd0d949..00000000 --- a/jupyter_book/getting_started.rst +++ /dev/null @@ -1,20 +0,0 @@ - -Getting Started -=============== - -Install -------- - -Please follow the instructions on the :doc:`install` page. For most users, the basic pip install is all that's needed. - - -Tutorials ---------- - -We have created a repository of tutorial Jupyter notebooks that can help initiate you on the main features of caustic. Please checkout the `tutorials here `_ to see what caustic can do! - - -Read The Docs -------------- - -Docs for all the main functions in caustic are avaialble at :doc:`caustic` at varying degrees of completeness. Further development of the docs is always ongoing. diff --git a/jupyter_book/install.rst b/jupyter_book/install.rst deleted file mode 100644 index 077356f2..00000000 --- a/jupyter_book/install.rst +++ /dev/null @@ -1,30 +0,0 @@ - -Installation -============ - -Regular Install ---------------- - -The easiest way to install is to make a new virtual environment then run:: - - pip install caustic - -this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. - - -Developer Install ------------------ - -First clone the repo with:: - - git clone git@github.com:Ciela-Institute/caustic.git - -this will create a directory `caustic` wherever you ran the command. Next go into the directory and install in developer mode:: - - pip install -e ".[dev]" - -this will install all relevant libraries and then install caustic in an editable format so any changes you make to the code will be included next time you import the package. To start making changes you should immediately create a new branch:: - - git checkout -b - -you can edit this branch however you like. If you are happy with the results and want to share with the rest of the community, then follow the contributors guide to create a pull request! diff --git a/jupyter_book/intro.md b/jupyter_book/intro.md deleted file mode 100644 index 033114d2..00000000 --- a/jupyter_book/intro.md +++ /dev/null @@ -1,25 +0,0 @@ -# Welcome to Caustics’ documentation! - -The lensing pipeline of the future: GPU-accelerated, -automatically-differentiable, highly modular and extensible. - -```{note} -Caustic is in its early development phase. This means the API will change with time. These changes are a good thing, but they can be annoying. Watch the version numbers, when we get to 1.0.0 that will be the first stable release! -``` - -# Intallation - -The easiest way to install is to make a new virtual environment then run: - -```console -pip install caustic -``` - -this will install all the required libraries and then install caustic and you -are ready to go! You can check out the tutorials afterwards to see some of -caustic's capabilities. If you want to help out with building the caustic code -base check out the developer installation instructions instead. - -# Read The Docs - -## Contents diff --git a/jupyter_book/license.rst b/jupyter_book/license.rst deleted file mode 100644 index 54abbeda..00000000 --- a/jupyter_book/license.rst +++ /dev/null @@ -1,25 +0,0 @@ - -License -======= - -MIT License - -Copyright (c) [2023] [caustic authors] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/jupyter_book/logo.png b/jupyter_book/logo.png deleted file mode 100644 index 06d56f40c838b64eb048a63e036125964a069a3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9854 zcmcJ#_ghoX7cGn*K?4W`P^xr9dX-2=s)7_L0g)Pd2}GoYu8}Goq=uqY=^(vJ3q7D9 zgx&ncihTY58>yQhyHVAq6+lGO~MVagOauq5m9v<`6Yyea8LU7g^33d5#6JIpIaLG z-1|gCJhU3BN``QY-K@=)`@JW9M^t~b7uLWQYei|mDOJQ5UUg0~vIqbGt~C8wn@$P% z5pl~zRjG%ihlB(HjNnDEe-dDz2JixSk(`6?==a`q;3sz5kB=vYkB^Usv)VI9k21rD zJiTz9qguh+nKE8m#+pE4C16PIb7Iqf7uN3q_3Quydk+ycREf|Kaf=g!AT$7Pt5%T^ z8aVDmSdkMNlHc+OU`GfMo(G6M`@b>}|7lC2WNZAX;dG{O$?=D%iOq{^-7HrB zcK+ZB)!$jy15VseoVKDTyWDkjAxOOZ*QX8d#=@20sP;i@Xow95<_tP$?zwjiu96e z>w=n(W1oxV>vfZL+?;M$rHrbr-j~Q4Z0#f{{?B_kL)V~b;Ypj(r`bl+t51=jl6us% zisP0c%+gd*@NjHx-9P?#e$1%)t<{43ZZ7psNeO=)Y*C@keuU`+V-r`LF5ytZC}IBu zbG$h|;({qNsYw+4y*-`g9}w-)-1o;4J-XQ1@Hi(x-*u)|Ne#p@^B%N%Ah2Q;LCG(ZHBQFL?Li-e*gAW=zAB)SnqFmSqA*R7HO(vfNpkV`XWrI=Kemp{ z`XTgmXPPHd1fbknj1MsBI};8fOdfpI;lh4^A@)#qeVu zJb2)IeR*!gAy`Wti~X5b#3bXH#-tDsvNcs{`2~3O>45+@w=lsB@yx}N+7A*AT1iWo zCLk(xWbfP7ppKMjUV$FTMNv+WKG*ZuS~AGj-P2i^ah`gNkqv6jA-Y4>M+bYJ1ouAM zhio_#B1U3u6!)N$?mJ2ZbANVV>Xl|5+3DVVOU$d89?>$dF>ix#X2Zv>SuCqqWS!S> zomY&g)au`g*ER&}`dKnw-|KyL(Xv>>BAvCz#~c7<2z4i2S3Ea{s$tl4;Fmfrw5uQ6 zdZdFouB9Zn$Ox^;m&3&jBs=B z%AGu-+-3>0hu*7*Go%ig0F*a+}bQ z8Cc)R_4Xa}T)gvvu4H@GAS#AA)o|_JsNW8zdTY`Y2EMw$8M6iKf6zLo2}vX5#E`E8 zLfTXySbqo;)cm*vz`Y<&q8-QUpyBxlw(wEG~e8ar+}fF-S+sb& zDz})rHK~=qsT-W;2Plhi{U0Y!6S$rmj%Lf#_6Q{}o9ON=pa2rAPpn&9&e(qYe-t*V z@vGCn-F!I$@0)jPw(#1-v_r^JT~5YZW{`r1+085#GB%6IJC?RRv%5q~F>%c&OxynZ z%xYjt7MVY0BPNAf>4{^p{XbrcwEclTApV;6FC515Ns#UfLZ z2YrA=|5>ly8F0Bp+l*6!<>1heHsIR01D`C08YNKz!~*JpVLU<@0QpHnTUW{;>sYp= z#eU02VX*}f3vp`~7iJXDzx9yXd^Up*0-yHrbarsvW*UH%P49|4$4@)tJgR$?6o6f5 z(}}v|BxI|mgea@2>qg^b#ozLf17HU@5Fa-Fraz@QN%7lZ5lq)Vn7Q=hZ-RdZ+wFlD zJdw!7J1*GND#_)ys*=%^U*Zr0;^&s<^_q%ds6d3-IC~_amnNV&0xM^1;`=@1xFn zORQ(tnI35sf7lD%W&%N9E6Y~6qoNr#BAw2aiDiR!7CS8KoW|9)GoJAAS##ZIrQTUl zBW}q?kb$Tk4JP=7j@W0(Ea^R`$D>gU%nfa^3Dwub5~JS+2Q@c7Z9!yGJ7`P^5kIj$ zg3O{je@?Kt&v;;R&~$K48WR=L6GcxNIc4yw)BgJ8Bb7oLJ2X_3X0F)>>o%A^0m7n(dq+!+P%cBi)cl|I6CzcZF{kC1jgSv|j(+zAw~tn#IxO6lUd zj3V;e_C=~at8H=>ZGE#9<8QfP>BH9b3(Dp1zuXnN-uc&csJ#%8Q4@!2to4XneWRff zIaA{h=OO9fyAt`B20gSGZE0+5EGtCJ!AS6zFb)b4RvV0{cMY(`Y<6f+_ign{;I9w2 z?`CxPvQXRE34SVHT0>{c&%(Dx6)wu&)H)_KU+lGLizO9h`wd3$Z?JRA2VVyyEvYkH zj67X5JX#+y_;{BJ&ZZ+qCX?k*~w*V_4;9w2D`5E~7T0 zi3~~~jxu(te?CA<_w_{5#uzKO&O9+lj}F{x+F-4r%K9((FwF&kFVse6mP!vDt_>x9 zUx>VaMt_HzSdkN>rs`F|pR*{TM8rR(unZk0EXqrLLqyw#%|A#KhSQVT6e&4@$gbX?Lg3SMXEj8wDG)D;FDQ8q8%^qqz=<=X1rcVpN?A{w?-*WMkRlJWw z3+-+`iUdC0c&rucqhLSGU;|L-v$MoCUgN^3b8#YnlqTL^wOuSldYIkFOhc9*s!|7C zZCfJ4{p-Vci9_o*vV1IliLv_qg!ONlaCFU*O(&by>`lq|I z4hpOFuCt)IyVtJs&2^hgfhWI>P3B?isG8c+o4E?=CTZWp{P7tyGpzM%&=GR+HEzQq z;QD++XUMPpY$fV5j+c40`CQ?TIK@slTaYNrn;_-|+;Pj|71}f4JSG4)@AI{Y``0;x zLIAw$!n>UCW{q*q4}sT5IX7vGytw4;e6H4@D|~;@%gt~92mAUO5uvgxOB5{Ep(ELz z2y^2geQ@Ae;wJm&>(%ce!yCWuih%7rn$vb`hZwIICxbe)vwUsJ`2COHc=-h!)ol1J zae_fdeqQ#!4Z#;G@7#18om~t^Dkw@;k|8CYnl4`WYjT=O>;V$I*8CVeKahu}oThzI zby8mM|V!o$r$h~2x@YYgecvv)44 zVEmn@-71;kO#&Fe!dj}OTkMCigz^2|hDFfuhsUY!IpqW^Rup!q8D*X-)f`dZYB%V( z+J*fl9B5WrGvpO-E^E$NZT%lAFfaIUD6e?hS2V7Wc__#^DX?+UE*yzRce_CIV*Ig- z{@Au>EMGnM(+{WpNRX7+Z+dydzM}QZc1KP7jO|yav-WKD37#31CiIdmppsvasoZjJ zhjMmpSbHGVq~5PpJY6V>>3^|%q!?r@6j>j<0Q>N?Q0ng{%+Hv@V2buU<19&o4fYJ3 zRGPrfikVAIeWWMdn<`28#Px;wwVB2fJsK(!YG{yS2zy&s*m4tR82lID$;!4LN{)!y zpw)6_si`@A8LBejn^g}GjhqlZDAg{@exDRYNd-JHnKP1SG{R>+AL4dGabfD5bgVHmK*ej73ye~XLd@pb%2zq%8KX$Hu7^Z0-YJ;>P` zMz#ko5|<$pp_sI$-oYj5Rh=9)S^tdB2NesZ#!D?}dqn52D&U`j+g9hxWVkkYBdn4% zc1N30W|e88l8+P(9tA)ET&$81!y6FlDYdS0di~KK=i%a0-3?AToyPe^X?C+|Opi&` zbYC5`m0#x7y;R^{@3?t`@KHC#yOZy27qg$X^7DX*k*Y3=r*l?48H^0m@Zwspa2w!= z8Rzo~D@*^~I(w;37S?CAu65^aOXttu1t0tLL~jVQR1W5BCjS95x7_>(Zn95tLXtC* zAm8pA%*Ozxz$w46`Mo9f*vB&x+b$=u+Rs;z6TfMxU!%gWE+ByCzxygTL4BDhyv1d} zD{w^)?0bgm#ph9M!q4%-kFW6k$paVLINgXgd}#yNe44b#J%%(m=NxxGP{<*vw)MiW z6(l~^rYnHK%O&5WXN!98Ny<2ab6T^H9CrHXik+$sMot2o2Lo_hnsKuJwz^8h{%eED zr2qYWAmxXK|7vS?)YGY#yL&+^&tb?CV%7@vu~e0v#UWvx>Telu)*eDs26wvKAAXce ztkMG*S4qdZc!ua^$*k4(E6U{?$oIskaZkqURK+y3u8q`gKrVl;*Kr~!{`;%OR=SeB ztg)-z=sVw9%gUM?9nbAWb9|%m;w8yNvf{LmJDY1OcB@=q+=4Avtsm1NlJkLj{9Zl{ zRC#RkkoY@`MSlwW&pUEARg2XK0BFF<0#Y;GEnlJE-CR#m7hqrV%3DU|i@%R^k^PBt zL9=sbeO)J@Xjbkq>qG>iL5LcW_dHMcNq-6n&mRKy6!O?ZPQK4yWR5ho z{xPlMGn^?DBvWZmb@A?AY_C}N(gWxoy$S=QS5eTZZNYtHt5=3oKWhj+ zaI1UQC{CL^LFo3hB0Yv-r9m2lrMlI5bVANzvQRAEKf+Mm4kO*IX;k1Odq94dXD2Rs zWX}=%8D2#S>f=WSng80ZNRQ|oV9VtCLzT;8yEMCSo9+)@Kf$MS9rA)R#TWwx9jD+W zi=Qw0Y3I9G%tn(aT>or~nGrp+uA%epd$M7pH7C*qpS6v-koOaxCl@1mMC(q!bC(s) zUgY#LBuJW)rT0r8sRU(ABiG>{p%6yPkp?S?iJpV=r}PnKq7z9&)n=Xca+&dPn@b(& ze@Ry7r&SX0G7$e$1tfQ_eZVwx$s~3PWZ`c=&{$USD7g>1-9MIsOBgfi&|QFmfGN66 z9#c7bCn;+>Nxde;IuKZxgr+*ZjS~=78Slptsx0B zC(yjsMZl}YK{pqR$m-cI5O-qwWr{63uC0&=YTvT9y zqo$r(5f9TobpUqSOOL1pntof&8#PdLxxmJ+JLjGv#)W(sdt|m&Pg~Ei>X{9WRM-FL z1ug=$A>CFfx;j-KXvS4L_(V+6QyE%^N?!-xBP2BBQ&_ebDIcw^RMMR1W|7&hYIg>2|Km|AZ``=`r~-Lr_^!%2k1B)uvrP6qZ4d`6mPBN>!xZ zJk**bYPy$w%2j60-W`={AU!!oHcBpiucFvTp~*34H)rMJxvaCFizQ$>@9ORE9?hrY z_T-JG%a{$#O{{Y(Q@I#^@Irxli=@R7a-z_}8Dgl&lu4==oQh=QPkJP(*KtS8U5075dF7 zk<6-UO^#Ci_8qvBD}L=^EXWWqC7Ki;}V;^B#BGlT;7cAwRaC& zbWyAQj{@gNy2Gix);R!v_O^)R%4Ip-S@)aIy3x`iPB(2_!mn0a%vs>k4@ENe8|dXK zHIjH9)!DsyQ_armEk(s_CL(LDUW*)7qrAO%P<3Cqijj$(X$*6}#_C8^v1VmCWJ3)T z|NZ4>M2bedT9pKY4Q7bvkLx|;@_%6ztsBvH1rl^a`}A}Jw($PS)0VjqRNGVbvSsj1 zeq5c?zFG;a$eXVSb{-Qhrt$Ln4+G5@1M_i1Fd>I!(Z%Ryk{|<_Z0^nWo_yDc#qZRN zW*Uzoe6ISr;?i&$?@WdN7*w?_e&Bt%7FO_$#Pp-o%+Bmb?YO52TFJ_X=Y` zB79{;AGE8w(H+`M-ReL&@+8qpOiubk(5AfNWE$Iqg?mQ0-sS zlV5}N6=QWM-DmG5T$?m-d0zL9tvkD61+==-ZvvE}&!_HEa);T%|5iUNQgr??Arj2v zYeVDHiT2)^j`CqWEdiHi8rN_or|xDgsM^V2=a8S%L1RY)zkF1Bo+rlV-5C~88P38p z>}G^G6k1xQ5BXO3n!~$(MW8-5Y&VdjIRXZ``{U*C+40wek?4&Psi$FSNhg8N zU>DZ?ITJ2d!p5txmKpAB-_hjqgY3%%Myhw3#rRoHotWKDxD&LqP=@Yh^miCTC#uU( z=E!Jmu<%zp1u}I+JV)@$hXbDq!nQdddw1iD+p{!H2R#miIqW_TAeZvSG>?CwQDl<= zq&r!EMjYv>v=zr(%WfQ4B|0sk7UEOk!}O@D@vX9{>t{X+!7w~tYw!I{;N8C%TN>!M z>7xX?(GH%vZg|!Xp4X8E(FQ-T=0g3Wlt`Tf|0;>yF&gVyM`s}om7?7plxL!q;@a!V z{l4{qolEEroMw2oJNmp@)G2ljpK|T#-8cW*{bS2K$Q!%h%5Qx>Yp}*|3=`1IrGYA_ z#3pFJc%b*?vw(G9JA{OJs6Iu?03Z#!9|dMV4WKd?L9VWXkJ|UY=bg3AH;sIrwQD;I zc&6=zrtXy=AOQHjS}Aa=9}Kkvkysnn7oOmLzpHuu&^6N3?IQXpetcqqXIvVbh9q;e zZ*y5xQ0j@_UR5}&q#}Q}_z?g~1NZ0~DoWtg{a2c3{5#i|(VB{zs7lh{ns-0}39+eZ zmuodiGS}9p!Cll;j;+GMld@E%{}vT(vQ^7~sZyUydd{}xs*Gvp`d6JIiVu(*_EN9q z$Qn5T>zL^jWs2J*dS)WbaTymtJNWt7SCtZNBxs!#HlJZ0Xj4IzF#0FmKaa9WFj*t^ z4$IrEQwQer_l3e3LFZ0uR?w~Qj8tBUW5aL8_8yvFiQH_QgmCjr9apD6&DIQX(SNWv zb|F@%eNq*cn1*MdhziznN^XqbD54Rd`f42e z%dkRxE0pw|k;g*Kxz=~~Q^d%iQS|mdZfV%PFuTgKOoQQ2B*Db7CQ{U+%(vqjr2@+)eLf{cZscW-ddJS@qnyU6i*!6DF%fms`AZv@>Z`K>M^jl7)Jy^-; z(0WW!4OGD=qRpx%OsLesm*9)M|LH&G?IjJgE9N?vI~25Tc)^B;`$p7s5P+z)rqsu8 z#LN*-*k4E7rlzJtJp0n5I7ds<0+d9DE{E_46|Ejr244-$k>h0krj6YhP4oX0jS7JghDO3@cFKC#AZ`8L30t0U8)M^2*m&M6}$Qp~Q_wmx(G zn(!n3Y)O0=4MK=5P0>QQfvJ@cAL_pnWzfF4g)K|wu=I~FYX(3W-2 zf|@^YU!Ut&*_K+D;p+qF5BVv9?k(CLxvwrM?*$1Y8Q1rO%po-&x{`*9F<>gKc8C(qtBR&?*4F>(;9=xmQap z#=6YaIOw962LhIK=$)s(7ibVg-6j|qt}Gf?spXuC?|60jPfUv_x01+;B7RU=)jPnT zh|?|SxQAbf65$EmPTvdZzt8N+PABxnwrkm)Y?Tga)nYIMgoc7w4XzRV2$`%ex6z9bNU zTv2t^8?QF3hskl_jm7(RNCWicsx?yw57HN%DNW%~m{)QN=KZ8m6{!i#T2h!Xg3@O2 zaAK4htobm}2YP9pB2afR<%OFoY%oDA4Bs?^mtDV=I(pY}zRp|(4qBFfrFG}vZP7x$ zMB$pC$#-tpQDWYIddI0^+71N$Q1U1_4^iys=?2}s!KPO2!JcD*HVg#F{oEWIe*nU-%yV<;YJz}09_`Dz?AWLlKA$!=5B7tkp z9_Ihe&3$NL+wtF@TpDvLR)ihayRpLvH0}o-Zvte|uU@(+0lWT*aU3ZK?GKTLYcJCO zQ~U7QT6{r3c?3TzU|gX^V}%M%XI;xd_qK-&M9KdV1Sos|8}%17JACDbM!iDforMt* z7Auxhgv1PQq~6FDWuVifhd=4`Vl2Xr#vI%}S{cQRAwGNOF9zF0RTH&w_q#Z%t4 zGx*92|7e3Cd$W~Y2_l4SU!I)$Ol%$mL(dkfgnhfk3#n<-t!kX(JFLhS_|%>=Fst7u zheXTT)Vo^bsf*`klEo@*>S0f;%3gz^+m_^rcv(QLan(?cKx9RG{g^Ez;QtBl6Ph+saou0`c!| zI8XcO^OnR(X{|3YvOxJr%@#4@tTR!0O7_qzZS`-=iZ3mwL3@LwASi z(QvV}`KN5>8$=N+@p9IR8VfQdvSZ+Lf{Fv;$%v%_0s%+RBoafg; zB^upVY;ewq1QLHeD4y<6Oa4d6hgbOmM;(hw8Y(3@UM)XV_nC91+I{svghGcwL9DQC zyM&5PhT=%Y7C{j)JyC3slsF1h)J!2ju6cNc?HhXJLHl25entk$v!XYOzK=(rP~Rh! z*p(T?yl4h)l~Mkkoa1>4%y{DERdSd$UE=vGXLs>#A3q)Cuz$F)ey2S&b!HYf=Mm@i z$vBfjX|diF=}~}Se`0d%u`^s!Jiz*S>KK$b5mKnTyL{tReI0e>|9%tuAFB^Xu1EqI z2*~6xoM8t-esZMcNE3x1OqyN-Lp+FtX23bZdIhv1I_L3poeEE12w+x`r3BtK?UgS_ zgjv%6!;CN9dDzM}v3-DxU~SIgW9;-IvqR%OnBm4Ejqg0G}YnQC~*J|RZ=pB5-~vu$W~ z)Un>6^zlydKLzhIZ?b;=zuG68MC1PzKM`{<{lBe>Vh5EBTCLDuB`X-584ml0{G L>8MsHTOs~GC;Fmw diff --git a/jupyter_book/modules.rst b/jupyter_book/modules.rst deleted file mode 100644 index a922b553..00000000 --- a/jupyter_book/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -caustic -======= - -.. toctree:: - :maxdepth: 4 - - caustic diff --git a/jupyter_book/requirements.txt b/jupyter_book/requirements.txt deleted file mode 100644 index 7e821e45..00000000 --- a/jupyter_book/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -jupyter-book -matplotlib -numpy diff --git a/jupyter_book/tutorials.rst b/jupyter_book/tutorials.rst deleted file mode 100644 index e9dea14b..00000000 --- a/jupyter_book/tutorials.rst +++ /dev/null @@ -1,16 +0,0 @@ -========= -Tutorials -========= - -Here you will find the jupyter notebook tutorials. It is recommended -that you go through the tutorials yourself, but for quick reference a -version of each tutorial is available here. - -.. toctree:: - :maxdepth: 1 - - BasicIntroduction - LensZoo - VisualizeCaustics - MultiplaneDemo - InvertLensEquation From edc5068673d66cf70e9d12c66fb91024baa93553 Mon Sep 17 00:00:00 2001 From: Landung 'Don' Setiawan Date: Wed, 29 Nov 2023 10:34:17 -0800 Subject: [PATCH 52/55] fix: Cleanup HEAD merge conflicts --- .github/workflows/ci.yml | 68 -------------------- docs/source/tutorials.rst | 12 ---- src/caustics/lenses/pixelated_convergence.py | 14 ---- src/caustics/parametrized.py | 15 ----- src/caustics/utils.py | 16 ----- 5 files changed, 125 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f921734a..27441588 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,70 +1,3 @@ -<<<<<<< HEAD -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: CI - -on: - push: - branches: - - main - - dev - pull_request: - branches: - - main - - dev - workflow_dispatch: - -jobs: - build: - - runs-on: ${{matrix.os}} - strategy: - matrix: - python-version: ["3.9", "3.10", "3.11"] - os: [ubuntu-latest, windows-latest, macOS-latest] - - steps: - - name: Checkout caustic - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Record State - run: | - pwd - echo github.ref is: ${{ github.ref }} - echo GITHUB_SHA is: $GITHUB_SHA - echo github.event_name is: ${{ github.event_name }} - echo github workspace: ${{ github.workspace }} - pip --version - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest pytest-cov torch wheel - # Install deps - cd $GITHUB_WORKSPACE - pip install -r requirements.txt - shell: bash - - - name: Install Caustic - run: | - cd $GITHUB_WORKSPACE - pip install -e .[dev] - pip show caustic - shell: bash - - - name: Test with pytest - run: | - cd $GITHUB_WORKSPACE - pytest test - shell: bash - -||||||| c8d51f7 -======= # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python @@ -139,4 +72,3 @@ jobs: - name: Upload coverage reports to Codecov with GitHub Action uses: codecov/codecov-action@v3 ->>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91 diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index 9c0c6f71..e9dea14b 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -14,15 +14,3 @@ version of each tutorial is available here. VisualizeCaustics MultiplaneDemo InvertLensEquation -<<<<<<< HEAD:docs/tutorials.rst - -||||||| c8d51f7:docs/tutorials.rst - - - - - - - -======= ->>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91:docs/source/tutorials.rst diff --git a/src/caustics/lenses/pixelated_convergence.py b/src/caustics/lenses/pixelated_convergence.py index fd837609..b0cb2d7c 100644 --- a/src/caustics/lenses/pixelated_convergence.py +++ b/src/caustics/lenses/pixelated_convergence.py @@ -332,19 +332,6 @@ def _deflection_angle_conv2d( """ # Use convergence_map as kernel since the kernel is twice as large. Flip since # we actually want the cross-correlation. -<<<<<<< HEAD:caustic/lenses/pixelated_convergence.py - - pad = 2 * self.n_pix - convergence_map_flipped = convergence_map.flip((-1, -2))[None, None] # F.pad(, ((pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2), mode = self.padding_mode) - deflection_angle_x = F.conv2d(self.ax_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( - self.pixelscale**2 / pi -||||||| c8d51f7:caustic/lenses/pixelated_convergence.py - - pad = 2 * self.n_pix - convergence_map_flipped = convergence_map.flip((-1, -2))[None, None] # F.pad(, ((pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2, (pad - self.n_pix)//2), mode = self.padding_mode) - deflection_angle_x = F.conv2d(self.ax_kernel[None, None], convergence_map_flipped, padding = "same").squeeze() * ( - self.pixelscale**2 / pi -======= 2 * self.n_pix convergence_map_flipped = convergence_map.flip((-1, -2))[ @@ -358,7 +345,6 @@ def _deflection_angle_conv2d( ).squeeze() * (self.pixelscale**2 / pi) return self._unpad_conv2d(deflection_angle_x), self._unpad_conv2d( deflection_angle_y ->>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91:src/caustics/lenses/pixelated_convergence.py ) @unpack(3) diff --git a/src/caustics/parametrized.py b/src/caustics/parametrized.py index 7bd1a71c..c7183484 100644 --- a/src/caustics/parametrized.py +++ b/src/caustics/parametrized.py @@ -230,15 +230,6 @@ def pack( # TODO: check structure! return Packed(x) -<<<<<<< HEAD:caustic/parametrized.py - - -||||||| c8d51f7:caustic/parametrized.py - - -======= - ->>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91:src/caustics/parametrized.py elif isinstance(x, (list, tuple)): n_passed = len(x) n_dynamic_params = len(self.params.dynamic.flatten()) @@ -261,15 +252,9 @@ def pack( elif isinstance(x, Tensor): n_passed = x.shape[-1] -<<<<<<< HEAD:caustic/parametrized.py - n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) -||||||| c8d51f7:caustic/parametrized.py - n_expected = sum([module.dynamic_size for module in self.dynamic_modules.values()]) -======= n_expected = sum( [module.dynamic_size for module in self.dynamic_modules.values()] ) ->>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91:src/caustics/parametrized.py if n_passed != n_expected: # TODO: give component and arg names raise ValueError( diff --git a/src/caustics/utils.py b/src/caustics/utils.py index 470fada2..87ac579c 100644 --- a/src/caustics/utils.py +++ b/src/caustics/utils.py @@ -514,16 +514,8 @@ def batch_lm( return X, L, C -<<<<<<< HEAD:caustic/utils.py -def gaussian(pixelscale, nx, ny, sigma, upsample = 1, dtype = torch.float32, device = None): - -||||||| c8d51f7:caustic/utils.py -def gaussian(pixelscale, nx, ny, sigma, upsample = 1, dtype = torch.float32, device = None): - -======= def gaussian(pixelscale, nx, ny, sigma, upsample=1, dtype=torch.float32, device=None): ->>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91:src/caustics/utils.py X, Y = np.meshgrid( np.linspace( -(nx * upsample - 1) * pixelscale / 2, @@ -538,16 +530,8 @@ def gaussian(pixelscale, nx, ny, sigma, upsample=1, dtype=torch.float32, device= indexing="xy", ) -<<<<<<< HEAD:caustic/utils.py - Z = np.exp(- 0.5 * (X**2 + Y**2) / sigma**2) - -||||||| c8d51f7:caustic/utils.py - Z = np.exp(- 0.5 * (X**2 + Y**2) / sigma**2) - -======= Z = np.exp(-0.5 * (X**2 + Y**2) / sigma**2) ->>>>>>> c9616623a2664eaff0a8f62f3d10e0b4d060af91:src/caustics/utils.py Z = Z.reshape(ny, upsample, nx, upsample).sum(axis=(1, 3)) return torch.tensor(Z / np.sum(Z), dtype=dtype, device=device) From 239a94c86ea67c479417764d9ccd50a84ca8b420 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 29 Nov 2023 10:35:47 -0800 Subject: [PATCH 53/55] edit book --- testbook/_toc.yml | 1 + testbook/intro.md | 2 ++ testbook/modules.rst | 7 +++++++ testbook/tutorials.md | 12 ++++++++++++ testbook/tutorials.rst | 16 ---------------- 5 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 testbook/modules.rst create mode 100644 testbook/tutorials.md delete mode 100644 testbook/tutorials.rst diff --git a/testbook/_toc.yml b/testbook/_toc.yml index 8eef7643..527686a9 100644 --- a/testbook/_toc.yml +++ b/testbook/_toc.yml @@ -10,3 +10,4 @@ chapters: - file: contributing - file: citation - file: license + - file: modules diff --git a/testbook/intro.md b/testbook/intro.md index f2e296e4..84d99516 100644 --- a/testbook/intro.md +++ b/testbook/intro.md @@ -1,5 +1,7 @@ # Welcome to Caustics’ documentation! +!["caustics_logo"](https://github.com/Ciela-Institute/caustics/blob/main/media/caustics_logo.png?raw=true) + The lensing pipeline of the future: GPU-accelerated, automatically-differentiable, highly modular and extensible. diff --git a/testbook/modules.rst b/testbook/modules.rst new file mode 100644 index 00000000..446b67c7 --- /dev/null +++ b/testbook/modules.rst @@ -0,0 +1,7 @@ +caustics +======= + +.. toctree:: + :maxdepth: 4 + + caustics diff --git a/testbook/tutorials.md b/testbook/tutorials.md new file mode 100644 index 00000000..6d3c6f60 --- /dev/null +++ b/testbook/tutorials.md @@ -0,0 +1,12 @@ +# Tutorials + +Here you will find the jupyter notebook tutorials. It is recommended +that you go through the tutorials yourself, but for quick reference a +version of each tutorial is available here. + +[Basic Introduction](get_start.ipynb) \ +[LensZoo](https://website-name.com) \ +[Visualize Caustics](https://website-name.com) \ +[Multiplane Demo](https://website-name.com) \ +[InvertLens Equation](https://website-name.com) + diff --git a/testbook/tutorials.rst b/testbook/tutorials.rst deleted file mode 100644 index d8cfc745..00000000 --- a/testbook/tutorials.rst +++ /dev/null @@ -1,16 +0,0 @@ -========= -Tutorials -========= - -Here you will find the jupyter notebook tutorials. It is recommended -that you go through the tutorials yourself, but for quick reference a -version of each tutorial is available here. - -.. .. toctree:: -.. :maxdepth: 1 - -.. BasicIntroduction -.. LensZoo -.. VisualizeCaustics -.. MultiplaneDemo -.. InvertLensEquation From 5a61a74c3ddebd80a7022d9e645ad028e42c7295 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Wed, 29 Nov 2023 15:46:19 -0800 Subject: [PATCH 54/55] change tutorials --- docs/get_start.ipynb | 466 -------------- docs/testbook/InvertLensEquation.ipynb | 157 +++++ docs/testbook/LensZoo.ipynb | 572 ++++++++++++++++++ docs/testbook/MultiplaneDemo.ipynb | 296 +++++++++ docs/testbook/VisualizeCaustics.ipynb | 183 ++++++ .../_autosummary/caustics.constants.rst | 23 + .../_autosummary/caustics.cosmology.rst | 30 + .../caustics.data.hdf5dataset.rst | 29 + .../caustics.data.illustris_kappa.rst | 29 + .../_autosummary/caustics.data.probes.rst | 29 + docs/testbook/_autosummary/caustics.data.rst | 33 + .../_autosummary/caustics.lenses.base.rst | 31 + .../_autosummary/caustics.lenses.epl.rst | 29 + .../caustics.lenses.external_shear.rst | 29 + .../caustics.lenses.mass_sheet.rst | 29 + .../caustics.lenses.multiplane.rst | 29 + .../_autosummary/caustics.lenses.nfw.rst | 29 + .../caustics.lenses.pixelated_convergence.rst | 29 + .../_autosummary/caustics.lenses.point.rst | 29 + .../caustics.lenses.pseudo_jaffe.rst | 29 + .../testbook/_autosummary/caustics.lenses.rst | 44 ++ .../_autosummary/caustics.lenses.sie.rst | 29 + .../caustics.lenses.singleplane.rst | 29 + .../_autosummary/caustics.lenses.sis.rst | 29 + .../_autosummary/caustics.lenses.tnfw.rst | 29 + .../_autosummary/caustics.lenses.utils.rst | 31 + .../_autosummary/caustics.light.base.rst | 29 + .../_autosummary/caustics.light.pixelated.rst | 29 + .../_autosummary/caustics.light.probes.rst | 29 + docs/testbook/_autosummary/caustics.light.rst | 34 ++ .../_autosummary/caustics.light.sersic.rst | 29 + .../_autosummary/caustics.namespace_dict.rst | 30 + .../testbook/_autosummary/caustics.packed.rst | 29 + .../_autosummary/caustics.parameter.rst | 29 + .../_autosummary/caustics.parametrized.rst | 36 ++ docs/testbook/_autosummary/caustics.rst | 41 ++ .../caustics.sims.lens_source.rst | 29 + docs/testbook/_autosummary/caustics.sims.rst | 32 + .../_autosummary/caustics.sims.simulator.rst | 29 + docs/testbook/_autosummary/caustics.utils.rst | 31 + {testbook => docs/testbook}/_config.yml | 13 + {testbook => docs/testbook}/_toc.yml | 0 {testbook => docs/testbook}/citation.rst | 0 {testbook => docs/testbook}/contributing.rst | 0 {testbook => docs/testbook}/get_start.ipynb | 20 +- {testbook => docs/testbook}/install.rst | 4 +- {testbook => docs/testbook}/intro.md | 0 {testbook => docs/testbook}/license.rst | 0 {testbook => docs/testbook}/logo.png | Bin docs/testbook/modules.rst | 8 + {testbook => docs/testbook}/references.bib | 0 {testbook => docs/testbook}/requirements.txt | 0 {testbook => docs/testbook}/tutorials.md | 8 +- testbook/modules.rst | 7 - 54 files changed, 2308 insertions(+), 489 deletions(-) delete mode 100644 docs/get_start.ipynb create mode 100644 docs/testbook/InvertLensEquation.ipynb create mode 100644 docs/testbook/LensZoo.ipynb create mode 100644 docs/testbook/MultiplaneDemo.ipynb create mode 100644 docs/testbook/VisualizeCaustics.ipynb create mode 100644 docs/testbook/_autosummary/caustics.constants.rst create mode 100644 docs/testbook/_autosummary/caustics.cosmology.rst create mode 100644 docs/testbook/_autosummary/caustics.data.hdf5dataset.rst create mode 100644 docs/testbook/_autosummary/caustics.data.illustris_kappa.rst create mode 100644 docs/testbook/_autosummary/caustics.data.probes.rst create mode 100644 docs/testbook/_autosummary/caustics.data.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.base.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.epl.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.external_shear.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.mass_sheet.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.multiplane.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.nfw.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.pixelated_convergence.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.point.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.pseudo_jaffe.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.sie.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.singleplane.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.sis.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.tnfw.rst create mode 100644 docs/testbook/_autosummary/caustics.lenses.utils.rst create mode 100644 docs/testbook/_autosummary/caustics.light.base.rst create mode 100644 docs/testbook/_autosummary/caustics.light.pixelated.rst create mode 100644 docs/testbook/_autosummary/caustics.light.probes.rst create mode 100644 docs/testbook/_autosummary/caustics.light.rst create mode 100644 docs/testbook/_autosummary/caustics.light.sersic.rst create mode 100644 docs/testbook/_autosummary/caustics.namespace_dict.rst create mode 100644 docs/testbook/_autosummary/caustics.packed.rst create mode 100644 docs/testbook/_autosummary/caustics.parameter.rst create mode 100644 docs/testbook/_autosummary/caustics.parametrized.rst create mode 100644 docs/testbook/_autosummary/caustics.rst create mode 100644 docs/testbook/_autosummary/caustics.sims.lens_source.rst create mode 100644 docs/testbook/_autosummary/caustics.sims.rst create mode 100644 docs/testbook/_autosummary/caustics.sims.simulator.rst create mode 100644 docs/testbook/_autosummary/caustics.utils.rst rename {testbook => docs/testbook}/_config.yml (77%) rename {testbook => docs/testbook}/_toc.yml (100%) rename {testbook => docs/testbook}/citation.rst (100%) rename {testbook => docs/testbook}/contributing.rst (100%) rename {testbook => docs/testbook}/get_start.ipynb (98%) rename {testbook => docs/testbook}/install.rst (92%) rename {testbook => docs/testbook}/intro.md (100%) rename {testbook => docs/testbook}/license.rst (100%) rename {testbook => docs/testbook}/logo.png (100%) create mode 100644 docs/testbook/modules.rst rename {testbook => docs/testbook}/references.bib (100%) rename {testbook => docs/testbook}/requirements.txt (100%) rename {testbook => docs/testbook}/tutorials.md (56%) delete mode 100644 testbook/modules.rst diff --git a/docs/get_start.ipynb b/docs/get_start.ipynb deleted file mode 100644 index 65cbe0cb..00000000 --- a/docs/get_start.ipynb +++ /dev/null @@ -1,466 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Welcome to Caustic!\n", - "\n", - "In need of a differentiable strong gravitational lensing simulation package? Look no further! We have all your lensing simulator needs. In this tutorial we will cover the basics of caustic and how to get going making your own lensing configurations. Caustic is easy to use and very powerful, you will get to see some of that power here, but there will be more notebooks which demo specific use cases." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "import torch\n", - "from torch.nn.functional import avg_pool2d\n", - "import matplotlib.pyplot as plt\n", - "from astropy.io import fits\n", - "import numpy as np\n", - "\n", - "import caustic" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "FlatLambdaCDM(\n", - " name='cosmo',\n", - " static=[h0, critical_density_0, Om0],\n", - " dynamic=[],\n", - " x keys=[]\n", - ")" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Specify the image/cosmology parameters\n", - "n_pix = 100\n", - "res = 0.05\n", - "upsample_factor = 2\n", - "fov = res * n_pix\n", - "thx, thy = caustic.get_meshgrid(\n", - " res / upsample_factor,\n", - " upsample_factor * n_pix,\n", - " upsample_factor * n_pix,\n", - " dtype=torch.float32,\n", - ")\n", - "z_l = torch.tensor(0.5, dtype=torch.float32)\n", - "z_s = torch.tensor(1.5, dtype=torch.float32)\n", - "cosmology = caustic.FlatLambdaCDM(name=\"cosmo\")\n", - "cosmology.to(dtype=torch.float32)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simulating an SIE lens\n", - "\n", - "Here we will demo the very basics of lensing with a classic `SIE` lens model. We will see what it takes to make an `SIE` model, lens a backgorund `Sersic` source, and sample some examples in a simulator. Caustic simulators can generalize to very complex scenarios. In these cases there can be a lot of parameters moving through the simulator, and the order/number of parameters may change depending on what lens or source is being used. To streamline this process, caustic impliments a class called `Parametrized` which has some knowledge of the parameters moving through it, this way it can keep track of everything for you. For this to work, you must put the parameters into a `Packed` object which it can recognize, each sub function can then unpack the parameters it needs. Below we will show some examples of what this looks like." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# demo simulator with sersic source, SIE lens. then sample some examples. demo the model graph\n", - "\n", - "\n", - "class Simple_Sim(caustic.Simulator):\n", - " def __init__(\n", - " self,\n", - " lens,\n", - " src,\n", - " z_s=None,\n", - " name: str = \"sim\",\n", - " ):\n", - " super().__init__(name) # need this so `Parametrized` can do its magic\n", - "\n", - " # These are the lens and source objects to keep track of\n", - " self.lens = lens\n", - " self.src = src\n", - "\n", - " # Here we can add a parameter to the simulator, in this case it is `z_s` which we will need later\n", - " self.add_param(\"z_s\", z_s)\n", - "\n", - " def forward(self, params): # define the forward model\n", - " # Here the simulator unpacks the parameter it needs\n", - " z_s = self.unpack(params)\n", - "\n", - " # Note this is very similar to before, except the packed up `x` is all the raytrace function needs to work\n", - " bx, by = self.lens.raytrace(thx, thy, z_s, params)\n", - " mu_fine = self.src.brightness(bx, by, params)\n", - "\n", - " # We return the sampled brightness at each pixel location\n", - " return avg_pool2d(mu_fine.squeeze()[None, None], upsample_factor)[0, 0]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "sim\n", - "\n", - "Simple_Sim('sim')\n", - "\n", - "\n", - "\n", - "sim/z_s\n", - "\n", - "z_s\n", - "\n", - "\n", - "\n", - "sim->sim/z_s\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sie\n", - "\n", - "SIE('sie')\n", - "\n", - "\n", - "\n", - "sim->sie\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src\n", - "\n", - "Sersic('src')\n", - "\n", - "\n", - "\n", - "sim->src\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sie/z_l\n", - "\n", - "z_l\n", - "\n", - "\n", - "\n", - "sie->sie/z_l\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sie/x0\n", - "\n", - "x0\n", - "\n", - "\n", - "\n", - "sie->sie/x0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sie/y0\n", - "\n", - "y0\n", - "\n", - "\n", - "\n", - "sie->sie/y0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sie/q\n", - "\n", - "q\n", - "\n", - "\n", - "\n", - "sie->sie/q\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sie/phi\n", - "\n", - "phi\n", - "\n", - "\n", - "\n", - "sie->sie/phi\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sie/b\n", - "\n", - "b\n", - "\n", - "\n", - "\n", - "sie->sie/b\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src/x0\n", - "\n", - "x0\n", - "\n", - "\n", - "\n", - "src->src/x0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src/y0\n", - "\n", - "y0\n", - "\n", - "\n", - "\n", - "src->src/y0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src/q\n", - "\n", - "q\n", - "\n", - "\n", - "\n", - "src->src/q\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src/phi\n", - "\n", - "phi\n", - "\n", - "\n", - "\n", - "src->src/phi\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src/n\n", - "\n", - "n\n", - "\n", - "\n", - "\n", - "src->src/n\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src/Re\n", - "\n", - "Re\n", - "\n", - "\n", - "\n", - "src->src/Re\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "src/Ie\n", - "\n", - "Ie\n", - "\n", - "\n", - "\n", - "src->src/Ie\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sie = caustic.lenses.SIE(cosmology, name=\"sie\")\n", - "src = caustic.sources.Sersic(name=\"src\")\n", - "\n", - "sim = Simple_Sim(sie, src, torch.tensor(0.8))\n", - "\n", - "sim.get_graph(True, True)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simple_Sim(\n", - " name='sim',\n", - " static=[z_s],\n", - " dynamic=[],\n", - " x keys=[('sie': ['z_l', 'x0', 'y0', 'q', 'phi', 'b']), ('src': ['x0', 'y0', 'q', 'phi', 'n', 'Re', 'Ie'])]\n", - ")\n", - "SIE(\n", - " name='sie',\n", - " static=[],\n", - " dynamic=[z_l, x0, y0, q, phi, b],\n", - " x keys=[('sie': ['z_l', 'x0', 'y0', 'q', 'phi', 'b'])]\n", - ")\n" - ] - } - ], - "source": [ - "print(sim)\n", - "print(sie)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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dToiLsgo1QD4dncVExRIf+6XYxC7WnFBocAZV5+QcRuBktyuTVyn4hHbXlr4LLdtatuZrugrNSElDjg2fjknM6W/ofnI7VDaIRVPZewrS5l78Uah9DpWHFBAE/LmpASWfNEnWCbBbVPpXOxaDu59V6U0LLFGxtEIDaXa43yWv3RBDShnglNH8je9+2zH0Ln6pC4fka+NTU0jP2WO/B30rrn8/qr8QPnCmrqXMtanZHyZGiD6DehnP/sZPvWpT2Hx4sXo7+/HaaedhmeffXZ6ClmG66+/HkuXLkV/fz/WrFmDV155JXpihBBCjmyiFqBf/vKXOP/889HT04N/+Zd/wUsvvYS/+Iu/wAc+8IFan2984xu49dZbcfvtt+Opp57CvHnzsHbtWhw+fLjwyRNCCJm9RJngvv71r2P58uW46667an8bGhqqfc6yDLfccgu+/OUv4+KLLwYAfPe738Xg4CAefPBBXHbZZY0frJpNv/fHmNm8131hy4hxy56pTNmhKqcl41Va2kWMSqXaLTvfjVyZ3FRqIcespkxuYttzLxau1cbrvPJAFa/30iTnJQ2PccOWXU037Nis4e6+jfdV5jnLZdtys66zr3uvdQZoaf5FLoYnsjbXBbJJe5mdAi7C7r2VrtWuyU1uSxOcekbkfVfpg9y++WYpmXZIbVesNvs6uSZG9R213LJleizDtBedisf97fDm0AY37B/+8IdYtWoVPvnJT+K4447DGWecgTvvvLPW/tprr2F4eBhr1qyp/W1gYADnnHMOdu3aFXMoQgghRzhRC9Crr76K2267DSeddBIeffRRfP7zn8dVV12Fu+++GwAwPDwMABgcHPT2GxwcrLVJxsbGMDo66v0jhBBy5BNlgqtWq1i1ahVuuukmAMAZZ5yBF198Ebfffjs2bNjQ1AS2b9+Or371q03tSwghZPYStQAtXboUH/nIR7y/nXzyyfinf/onAMCSJUsAACMjI1i6dGmtz8jICE4//fS6Y27duhVbtmypbY+OjmL58uX5k5A+wgW6ZbuVQZWGYmGVapgabPpjTKmGqUlN72uUapDtssqp6ZatdJx8l+2p9vxxreqpMqWMkrQ813CYSE3IO11po495JIy0/EGXc0sDUtqf6JsZbTFu2KFyDNb5mNplfhNgSop6KKvch9RqpOTgujF3+41SA6r0Om294nkJaEDl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3AC8To3B69QYvE6NweuUj/Xm8z5tWYD+4A/+AD//+c9x/fXXY3h4GKeffjoeeeQR5ZhACCHk6KVtTgibNm1qyORGCCHk6KRjk5H29fXhK1/5iuegQDS8To3B69QYvE6NwetUDG3JhEAIIYSE6Ng3IEIIIUc2XIAIIYQkgQsQIYSQJHABIoQQkoSOXYBY0G6a7du346yzzsKCBQtw3HHH4ZJLLsGePXu8PocPH8bGjRuxePFizJ8/H+vWrVPpkI42br75ZpRKJWzevLn2N16nKX72s5/hU5/6FBYvXoz+/n6cdtppePbZZ2vtWZbh+uuvx9KlS9Hf3481a9bglVdeSTjjmadSqWDbtm0YGhpCf38/PvzhD+NrX/ual9+M16lFsg7kvvvuy3p7e7O/+7u/y/7rv/4r+6M/+qNs0aJF2cjISOqpJWHt2rXZXXfdlb344ovZ888/n/3Wb/1WtmLFiuydd96p9fnc5z6XLV++PNuxY0f27LPPZueee2523nnnJZx1Wp5++unsxBNPzD760Y9mV199de3vvE5Z9vbbb2cnnHBC9ulPfzp76qmnsldffTV79NFHs//5n/+p9bn55puzgYGB7MEHH8xeeOGF7Hd+53eyoaGh7NChQwlnPrPceOON2eLFi7OHH344e+2117L7778/mz9/fvZXf/VXtT68Tq3RkQvQ2WefnW3cuLG2XalUsmXLlmXbt29POKvOYe/evRmAbOfOnVmWZdm+ffuynp6e7P7776/1+e///u8MQLZr165U00zGgQMHspNOOil77LHHsl//9V+vLUC8TlN86Utfyi644ILc9mq1mi1ZsiT78z//89rf9u3bl/X19WXf+973ZmKKHcFFF12UfeYzn/H+dumll2br16/PsozXqQg6zgQXW9DuaGT//v0AgGOOOQYAsHv3bkxMTHjXbOXKlVixYsVRec02btyIiy66yLseAK/T+/zwhz/EqlWr8MlPfhLHHXcczjjjDNx555219tdeew3Dw8PedRoYGMA555xzVF2n8847Dzt27MDLL78MAHjhhRfwxBNP4BOf+AQAXqciSF4PSGIVtPvJT36SaFadQ7VaxebNm3H++efj1FNPBQAMDw+jt7cXixYt8vrmFQE8krnvvvvw3HPP4ZlnnlFtvE5TvPrqq7jtttuwZcsW/Mmf/AmeeeYZXHXVVejt7cWGDRtq16LRopJHKtdddx1GR0excuVKdHV1oVKp4MYbb8T69esBgNepADpuASI2GzduxIsvvognnngi9VQ6jjfeeANXX301HnvsMcyZMyf1dDqWarWKVatW4aabbgIAnHHGGXjxxRdx++23Y8OGDYln1zl8//vfxz333IN7770Xp5xyCp5//nls3rwZy5Yt43UqiI4zwcUWtDua2LRpEx5++GH867/+K44//vja35csWYLx8XHs27fP63+0XbPdu3dj7969+NjHPobu7m50d3dj586duPXWW9Hd3Y3BwUFeJwBLly7FRz7yEe9vJ598Ml5//XUAqF2Lo/07+MUvfhHXXXcdLrvsMpx22mn4wz/8Q1xzzTXYvn07AF6nIui4BcgtaPc+7xe0W716dcKZpSPLMmzatAkPPPAAHn/8cQwNDXntZ555Jnp6erxrtmfPHrz++utH1TW78MIL8eMf/xjPP/987d+qVauwfv362mdeJ+D8889Xbvwvv/wyTjjhBADA0NAQlixZ4l2n0dFRPPXUU0fVdXr33XdRLvs/kV1dXahWp0oI8zoVQGoviHrcd999WV9fX/b3f//32UsvvZRdccUV2aJFi7Lh4eHUU0vC5z//+WxgYCD7t3/7t+ytt96q/Xv33XdrfT73uc9lK1asyB5//PHs2WefzVavXp2tXr064aw7A9cLLst4nbJsykW9u7s7u/HGG7NXXnklu+eee7K5c+dm//AP/1Drc/PNN2eLFi3KfvCDH2T/+Z//mV188cVHnXvxhg0bsg996EM1N+x//ud/zo499tjs2muvrfXhdWqNjlyAsizLvvWtb2UrVqzIent7s7PPPjt78sknU08pGQDq/rvrrrtqfQ4dOpR94QtfyD7wgQ9kc+fOzX73d383e+utt9JNukOQCxCv0xQPPfRQduqpp2Z9fX3ZypUrszvuuMNrr1ar2bZt27LBwcGsr68vu/DCC7M9e/Ykmm0aRkdHs6uvvjpbsWJFNmfOnOxXfuVXsj/90z/NxsbGan14nVqD5RgIIYQkoeM0IEIIIUcHXIAIIYQkgQsQIYSQJHABIoQQkgQuQIQQQpLABYgQQkgSuAARQghJAhcgQgghSeACRAghJAlcgAghhCSBCxAhhJAkcAEihBCShP8f21qsljUV7uYAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Reading the x_keys above we can input the parameters that we would like the simulator to evaluate\n", - "x = torch.tensor(\n", - " [\n", - " z_l.item(), # sie z_l\n", - " 0.7, # sie x0\n", - " 0.13, # sie y0\n", - " 0.4, # sie q\n", - " np.pi / 5, # sie phi\n", - " 1.0, # sie b\n", - " 0.2, # src x0\n", - " 0.5, # src y0\n", - " 0.5, # src q\n", - " -np.pi / 4, # src phi\n", - " 1.5, # src n\n", - " 2.5, # src Re\n", - " 1.0, # src Ie\n", - " ]\n", - ")\n", - "plt.imshow(sim(x), origin=\"lower\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Where to go next?\n", - "\n", - "The caustic tutorials are generally short and to the point, that way you can idenfity what you want and jump right to some useful code that demo's the particular problem you face. Below is a list of caustic tutorials and a quick description of what you will learn in each one::\n", - "\n", - "- `LensZoo`: here you can see all the built-in lens mass distributions in `caustic` and how they distort the same background Seric source.\n", - "- `Playground`: here we demo the main visualizations of a lensing system (deflection angles, convergence, potential, time delay, magnification) in an interactive display so you can change the parameters by hand and see how the visuals change!\n", - "- `VisualizeCaustics`: here you can see how to find and display caustics, a must when using `caustic`!\n", - "- `Simulators`: here we describe the powerful simulator framework and how it can be used to quickly swap models, parameters, and other features and turn a complex forward model into a simple function.\n", - "- `InvertLensEquation`: here we demo forward ray tracing in `caustic` the process of mapping from the source plane to the image plane." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "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.9.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/testbook/InvertLensEquation.ipynb b/docs/testbook/InvertLensEquation.ipynb new file mode 100644 index 00000000..75386a3f --- /dev/null +++ b/docs/testbook/InvertLensEquation.ipynb @@ -0,0 +1,157 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b7d08f1d", + "metadata": {}, + "source": [ + "# Inverting the Lens Equation\n", + "\n", + "The lens equation $\\vec{\\beta} = \\vec{\\theta} - \\vec{\\alpha}(\\vec{\\theta})$ allows us to find a point in the source plane given a point in the image plane. However, sometimes we know a point in the source plane and would like to see where it ends up in the image plane. This is not easy to do since a point in the source plane may map to multiple locations in the image plane. There is no closed form function to invert the lens equation, in large part because the deflection angle $\\vec{\\alpha}$ depends on the position in the image plane $\\vec{\\theta}$. To invert the lens equation, we will need to rely on optimization and a little luck to find all the images for a given source plane point. Below we will demonstrate how this is done in caustic!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4027aaf9", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "from functools import partial\n", + "\n", + "import torch\n", + "from torch.nn.functional import avg_pool2d\n", + "import matplotlib.pyplot as plt\n", + "from ipywidgets import interact\n", + "from astropy.io import fits\n", + "import numpy as np\n", + "from time import process_time as time\n", + "\n", + "import caustics" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2118e1c1", + "metadata": {}, + "outputs": [], + "source": [ + "# initialization stuff for an SIE lens\n", + "\n", + "cosmology = caustics.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology.to(dtype=torch.float32)\n", + "n_pix = 100\n", + "res = 0.05\n", + "upsample_factor = 2\n", + "fov = res * n_pix\n", + "thx, thy = caustics.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "z_l = torch.tensor(0.5, dtype=torch.float32)\n", + "z_s = torch.tensor(1.5, dtype=torch.float32)\n", + "lens = caustics.SIE(\n", + " cosmology = cosmology,\n", + " name = \"sie\",\n", + " z_l = z_l,\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " q = torch.tensor(0.4),\n", + " phi = torch.tensor(np.pi/5),\n", + " b = torch.tensor(1.),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "98e46aa1", + "metadata": {}, + "outputs": [], + "source": [ + "# Point in the source plane\n", + "sp_x = torch.tensor(0.2)\n", + "sp_y = torch.tensor(0.2)\n", + "\n", + "# Points in image plane\n", + "x, y = lens.forward_raytrace(sp_x, sp_y, z_s)\n", + "\n", + "# Raytrace to check\n", + "bx, by = lens.raytrace(x, y, z_s)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "eb73147c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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GDdObr7vvy9hZMAAWYm1QgXVVRYsTZPxHCEhchw9Ksfdu49eW60a0Lw58PLLMs6DO9oSkSUwqyASGBiIcePGqS6DFLK3BwICZP+SuXOBmTOBy5dlNdYvvpAbcJcussy4t7fiYvPo/n0gLEwC1smTErpOnABu33702iJFgAYNgCZNJHi89BIXFSMiy5SnrpmRI0di0qRJT7wmLCwM1atXz3qel66ZnFpEvLy82DVjxVJSZNzIggXA3r2G7/PxkWDy8sty037+efUb6aWlAdHRQESEdK9cuCCLhp07Jy0fOf1rs7cH6tQBXngBaNhQjjp1OLOFiAqvvHTN5CmI3Lx5E7dz+vPtIVWqVIHDQ/+DcowIFZSoKGDNGllzZO9eQK83fH/RokDNmkD16tKNUbmyjKPw8JDWBDe3Z585kpoq3SW3bwM3bwLXr8vCYLGxcsTESACJiXm0roeVLi0ho3Zt2bHW11fednR8trqIiMyR0caIuLu7w93dPV/FET2rypVl07zhw4F79ySMBAcDBw5IN0dSUvZYi5zY2QGurhJInJyk+8PBQWbm2NjI8ufp6dKqkZwsR2IikJAgb+eWoyNQpYqEIR8fOWrUkIDEfz5ERIaM1pAdHR2NO3fuIDo6Gnq9HqGhoQCAqlWrwtnZ2VgvS1aieHGgY0c5AGmFCA+XMRjnzkmXSHS0HNeuAfHxEjLu3JHjWeh08rplyshRtqwMnvXwkJaXihWBSpVk9oqNTUF9pUREls1o03f79OmDxYsXP3J+586d8MvlWt7smqGC8uCBrD4aFydHcrKcS0mRcRt6vbSM2NnJUbSotJg4O0srSmZLCtc1ISJ6OqONETE1BhEiIqLCJy/3bzYgExERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIMIkRERKQMgwgREREpwyBCREREyjCIEBERkTIM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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "A = lens.jacobian_lens_equation(thx, thy, z_s)\n", + "detA = torch.linalg.det(A)\n", + "CS = ax.contour(thx, thy, detA, levels = [0.], colors = \"b\", zorder = 1)\n", + "# Get the path from the matplotlib contour plot of the critical line\n", + "paths = CS.collections[0].get_paths()\n", + "caustic_paths = []\n", + "for path in paths:\n", + " # Collect the path into a descrete set of points\n", + " vertices = path.interpolated(5).vertices\n", + " x1 = torch.tensor(list(float(vs[0]) for vs in vertices))\n", + " x2 = torch.tensor(list(float(vs[1]) for vs in vertices))\n", + " # raytrace the points to the source plane\n", + " y1,y2 = lens.raytrace(x1, x2, z_s)\n", + "\n", + " # Plot the caustic\n", + " ax.plot(y1,y2, color = \"r\", zorder = 1)\n", + "ax.scatter(x, y, color = \"b\", label = \"forward raytrace\", zorder = 10)\n", + "ax.scatter(bx, by, color = \"r\", marker = \"x\", label = \"source plane\", zorder = 9)\n", + "ax.scatter([sp_x.item()], [sp_y.item()], color = \"g\", label = \"true pos\", zorder = 8)\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0aa515f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PY39", + "language": "python", + "name": "py39" + }, + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/testbook/LensZoo.ipynb b/docs/testbook/LensZoo.ipynb new file mode 100644 index 00000000..846ddc8e --- /dev/null +++ b/docs/testbook/LensZoo.ipynb @@ -0,0 +1,572 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e4054d16", + "metadata": {}, + "source": [ + "# A Menagerie of Lenses\n", + "\n", + "Here we have a quick visual demo of every type of lens in caustic. This is a good way to pick out what you need and quickly copy paste. For all of these lenses we have placed a Sersic source with the same parameters as the background, that way you can visualize the differences between them." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "beeb58fa", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import torch\n", + "from torch.nn.functional import avg_pool2d\n", + "import matplotlib.pyplot as plt\n", + "from ipywidgets import interact\n", + "from astropy.io import fits\n", + "import numpy as np\n", + "from time import process_time as time\n", + "\n", + "import caustics" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "72cbde6d", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYUAAAGbCAYAAAAr/4yjAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAC50ElEQVR4nO29bax121UXPtY+97m3L9J/EVItgohgoCJNoSQYQJq21CAtyBcw4EuCIWoUxS8alSiBRBMiAQkBNMS0jdIURIMppLRXvYSXGEJ8l0CARP1gCBWBGmjpfe7Z8/9h77nWmGP+xttcc59znts9knvPWnOOMeZY+9l7/OYYY865llJKoStd6UpXutKViOhw3wZc6UpXutKVHg5dQeFKV7rSla600hUUrnSlK13pSitdQeFKV7rSla600hUUrnSlK13pSitdQeFKV7rSla600hUUrnSlK13pSitdQeFKV7rSla600hUUrnSlK13pSitdQeFKV3qA9OM//uO0LAv9+I//+H2bcqWPMrqCwpWm0jve8Q5alqX571WvehW98Y1vpPe+9733bZ5J73nPe+gNb3gDvepVr6KXvexl9Af/4B+kr/qqr6If+7Efu2/TrnSlO6On7tuAK7046Vu+5VvoUz7lU6iUQr/6q79K73jHO+hLv/RL6T3veQ+97W1vu2/zOvq2b/s2+ht/42/QG97wBvrbf/tv08te9jL65V/+Zfo3/+bf0Lvf/W76ki/5kju154u+6Ivowx/+MD399NN3Ou6VrrRcD8S70kx6xzveQV/7tV9LP/uzP0uf+7mfu7b/xm/8Bv2e3/N76Cu/8ivp+7//++/Rwp5eeOEF+riP+zj6vM/7PHr/+9/f9X/gAx+gV73qVbvHOR6P9Pzzz9NLXvKS3bqudKVL0TV9dKU7oVe+8pX00pe+lJ56qg1Ov+3bvo0+//M/nz7u4z6OXvrSl9LrX/96+qEf+qFO/tlnn6Uv/MIvpFe+8pX0u37X76JP//RPp7/zd/5Ow/ORj3yEvumbvok+7dM+jZ555hn6pE/6JPqbf/Nv0kc+8hHTtl/7tV+j//f//h99wRd8AeyXgBAdZ1kW+vqv/3r6/u//fvrMz/xMeuaZZ9ZU1Lvf/W56/etfTx/zMR9Dr3jFK+izPuuz6Du/8ztXWa2m8DM/8zP0pV/6pfSxH/ux9PKXv5xe+9rXNnJXutJeuqaPrnQR+uAHP0i/9mu/RqUU+sAHPkDf9V3fRb/1W79Ff+bP/JmG7zu/8zvpy7/8y+lP/+k/Tc8//zy9+93vpq/8yq+kH/mRH6G3vvWtRET0cz/3c/S2t72NXvva19K3fMu30DPPPEO//Mu/TD/90z+96jkej/TlX/7l9FM/9VP0F/7CX6DXvOY19N/+23+j7/iO76Bf/MVfpB/+4R9WbX3Vq15FL33pS+k973kP/dW/+lfpd//u363yZsf5d//u39EP/uAP0td//dfTx3/8x9Mf+AN/gJ599ln66q/+anrzm99M3/qt30pERD//8z9PP/3TP03f8A3foI797LPP0tve9jZ69atfTd/wDd9Av/f3/l76+Z//efqRH/kRU+5KV0pRudKVJtLb3/72QkTdf88880x5xzve0fF/6EMfau6ff/758kf+yB8pb3rTm9a27/iO7yhEVP7P//k/6rj/7J/9s3I4HMpP/uRPNu3/+B//40JE5ad/+qdNu//e3/t7hYjKy1/+8vIn/sSfKH//7//98h/+w3/YNQ4RlcPhUH7u536u4f2Gb/iG8opXvKK88MILqj3PPfdcIaLy3HPPlVJKeeGFF8qnfMqnlE/+5E8uv/Ebv9HwHo9H89mudKUMXdNHV7oIffd3fzc9++yz9Oyzz9I//+f/nN74xjfS133d19G/+lf/quF76Utful7/xm/8Bn3wgx+kP/bH/hj9x//4H9f2V77ylURE9K//9b+m4/EIx/sX/+Jf0Gte8xr6jM/4DPq1X/u19b83velNRET03HPPmfZ+8zd/M73rXe+iz/7sz6b3ve999I3f+I30+te/nj7ncz6Hfv7nf354nDe84Q30h//wH27aXvnKV9Jv//Zv07PPPmvaxOk//af/RP/jf/wP+ut//a+vn0elZVnCeq50JZfuG5Wu9OKiGin87M/+bNN+e3tbXvva15ZXv/rV5SMf+cja/p73vKd83ud9XnnmmWeayGJZlpXnQx/6UPmCL/iCQkTl4z/+48uf+lN/qvzAD/xAub29XXle85rXwAil/vfX/tpfCz/DBz/4wfL+97+/fM3XfE0hovKpn/qp5cMf/nB6HCIqf/7P//lO/6/+6q+uen7f7/t95Wu/9mvLe9/73oZHRgrvfve7CxGVZ599NvwcV7rSCF1rCle6EzocDvTGN76RvvM7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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cosmology = caustics.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology.to(dtype=torch.float32)\n", + "z_s = torch.tensor(1.)\n", + "base_sersic = caustics.sources.Sersic(\n", + " name = \"sersic\",\n", + " x0 = torch.tensor(0.1),\n", + " y0 = torch.tensor(0.1),\n", + " q = torch.tensor(0.6),\n", + " phi = torch.tensor(np.pi/3),\n", + " n = torch.tensor(2.),\n", + " Re = torch.tensor(1.),\n", + " Ie = torch.tensor(1.),\n", + ")\n", + "n_pix = 100\n", + "res = 0.05\n", + "upsample_factor = 2\n", + "fov = res * n_pix\n", + "thx, thy = caustics.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "z_l = torch.tensor(0.5, dtype=torch.float32)\n", + "\n", + "class Zoo_Sim(caustics.Simulator):\n", + " def __init__(\n", + " self,\n", + " lens,\n", + " name: str = \"sim\",\n", + " ):\n", + " super().__init__(name) # need this so `Parametrized` can do its magic\n", + "\n", + " # These are the lens and source objects to keep track of\n", + " self.lens = lens\n", + " self.src = base_sersic\n", + "\n", + " def forward(self, params):# define the forward model\n", + " # Note this is very similar to before, except the packed up `x` is all the raytrace function needs to work\n", + " bx, by = self.lens.raytrace(thx, thy, z_s, params)\n", + " mu_fine = self.src.brightness(bx, by, params)\n", + "\n", + " # We return the sampled brightness at each pixel location\n", + " return avg_pool2d(mu_fine.squeeze()[None, None], upsample_factor)[0, 0]\n", + "\n", + "plt.imshow(np.log10(base_sersic.brightness(thx,thy).numpy()), origin = \"lower\")\n", + "plt.gca().axis(\"off\")\n", + "plt.title(\"Base Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "aa183e3d", + "metadata": {}, + "source": [ + "## Point (Point)\n", + "\n", + "The simplest lens, an infinitely small point of mass (did someone say black holes?)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4b4b2faa", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAJFCAYAAAD6TAMuAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABis0lEQVR4nO39ebRlZX3g/3/uOXVvDVQVkwUYQESGoIjDQtGAUKgIjWIWDgFEDZCgxIi2A7rECZmCs/g1dNS2Ay5ncCQqiLak46xENGirXSKgQBoQA6hF1b11zv79wY/buWd/qvZTZ587VPF6reVaYbP3fp69zz6H1HPPfddYVVVVAAAAALBRnfmeAAAAAMBCZwEFAAAAoIEFFAAAAIAGFlAAAAAAGlhAAQAAAGhgAQUAAACggQUUAAAAgAYWUAAAAAAaWEABAAAAaGABhVlx+OGHx+GHHz7f04CReOhDHxonn3zyUMf+4Q9/iFNPPTV22WWXGBsbi1e84hVx4403xtjYWFxyySUjnWepNtcDAAAPVBZQiIiISy65JMbGxqb/t2TJkth3333j9NNPj9tuu21O5/Lxj388Lrzwws06ptfrxcUXXxyHH3547LDDDrF48eJ46EMfGqecckpcc801szNRFrwvf/nL8Za3vGVe5/B3f/d3cckll8RLXvKS+MhHPhIvfOEL53U+AADAcBbN9wRYWM4555zYc889Y926dfHNb34z/uEf/iG+/OUvx09+8pNYtmxZ8Xmuuuqqoefw8Y9/PH7yk5/EK17xiqL977333nj2s58dV155ZRx22GHx+te/PnbYYYe48cYb49JLL40Pf/jD8etf/zp22223oefElunLX/5yXHTRRfO6iPL1r389nvjEJ8ZZZ501va2qqrj33ntjfHx83uYFAABsHgsozHD00UfH4x73uIiIOPXUU2PHHXeMd7/73fGFL3whnve85xWfZ2JiYramWPOa17wmrrzyynjPe95TW3Q566yz4j3vec+czWW2rF27drMWsFg4br/99njEIx4xY9v93/ICAAC2HH6Fh016ylOeEhERN9xwQ0REbNiwIc4999zYa6+9pn9N5vWvf32sX79+xnGDDZR//ud/jrGxsbj00kvj/PPPj9122y2WLFkST33qU+OXv/zljOO+9KUvxU033TT960QPfehDNzq/m2++OT7wgQ/E0572tPQbK91uN84444wZ3z659tpr4+ijj46VK1fG8uXL46lPfWp897vfnXHc/b/S9K1vfSte9apXxapVq2KbbbaJZz3rWXHHHXdM73fMMcfEwx72sHRuf/Znfza9GHW/j370o3HggQfG0qVLY4cddogTTjghfvOb39Tu3SMf+cj413/91zjssMNi2bJl8frXvz4iIu6888544QtfGCtXroztttsuTjrppPjxj3+c9jR+/vOfx3Of+9zYYYcdYsmSJfG4xz0uLr/88qGu835XXHFFrF69OlasWBErV66Mxz/+8fHxj398xj7f+9734r/8l/8S2267bSxbtixWr14d3/rWt9J79J+VPiP3u+yyy6bv5YMe9KB4wQteELfccsv0vz/55JPjoosuioiY8etpm1JVVZx33nmx2267xbJly+LJT35y/PSnP033veuuu+IVr3hF7L777rF48eLYe++9421ve1v0+/0Z13PDDTfEl770penxb7zxxrSBcvLJJ8fy5cvjlltuiWOPPTaWL18eq1atijPOOCN6vd6Msfv9flx44YWx//77x5IlS2LnnXeO0047Lf7jP/5j6OsBAAA2zTdQ2KTrr78+IiJ23HHHiLjvWykf/vCH47nPfW68+tWvju9973txwQUXxM9+9rP43Oc+13i+t771rdHpdOKMM86Iu+++O97+9rfH85///Pje974XERFveMMb4u67746bb755+psjy5cv3+j5rrjiitiwYUNxV+KnP/1pHHroobFy5cp47WtfG+Pj4/GBD3wgDj/88Phf/+t/xROe8IQZ+7/sZS+L7bffPs4666y48cYb48ILL4zTTz89PvWpT0VExPHHHx9/+Zd/GT/4wQ/i8Y9//PRxN910U3z3u9+Nd7zjHdPbzj///HjTm94Uxx13XJx66qlxxx13xPve97447LDD4tprr43ttttuet8777wzjj766DjhhBPiBS94Qey8887R7/fjmc98Znz/+9+Pl7zkJbHffvvFF77whTjppJPS6zzkkENi1113jde97nWxzTbbxKWXXhrHHntsfOYzn4lnPetZm3WdEfcttvzVX/1V7L///nHmmWfGdtttF9dee21ceeWVceKJJ0bEfb+ucvTRR8eBBx4YZ511VnQ6nbj44ovjKU95SnzjG9+Igw46qPE1anpG7p/LKaecEo9//OPjggsuiNtuuy3e+973xre+9a3pe3naaafFrbfeGl/96lfjIx/5SOO4ERFvfvOb47zzzounP/3p8fSnPz1++MMfxpFHHhmTk5Mz9lu7dm2sXr06brnlljjttNPiIQ95SHz729+OM888M/793/89Lrzwwnj4wx8eH/nIR+KVr3xl7LbbbvHqV786IiJWrVqVLk5F3NfyOeqoo+IJT3hCvPOd74yvfe1r8a53vSv22muveMlLXjK932mnnTZ9D17+8pfHDTfcEH//938f1157bXzrW9+a/tWg0usBAAAKVFBV1cUXX1xFRPW1r32tuuOOO6rf/OY31Sc/+clqxx13rJYuXVrdfPPN1Y9+9KMqIqpTTz11xrFnnHFGFRHV17/+9eltq1evrlavXj39z1dffXUVEdXDH/7wav369dPb3/ve91YRUV133XXT257xjGdUe+yxR9G8X/nKV1YRUV177bVF+x977LHVxMREdf31109vu/XWW6sVK1ZUhx12WO1+HHHEEVW/358xXrfbre66666qqqrq7rvvrhYvXly9+tWvnjHO29/+9mpsbKy66aabqqqqqhtvvLHqdrvV+eefP2O/6667rlq0aNGM7atXr64ionr/+98/Y9/PfOYzVURUF1544fS2Xq9XPeUpT6kiorr44ountz/1qU+tDjjggGrdunXT2/r9fnXwwQdX++yzz2Zf51133VWtWLGiesITnlDde++9M+Z1/3H9fr/aZ599qqOOOmrGudauXVvtueee1dOe9rRqU0qfkcnJyWqnnXaqHvnIR86Yyxe/+MUqIqo3v/nN09te+tKXVqUfc7fffns1MTFRPeMZz5gx/9e//vVVRFQnnXTS9LZzzz232mabbar/83/+z4xzvO51r6u63W7161//enrbHnvsUT3jGc+Ysd8NN9xQe81OOumkKiKqc845Z8a+j33sY6sDDzxw+p+/8Y1vVBFRfexjH5ux35VXXjlj++ZcDwAA0Myv8DDDEUccEatWrYrdd989TjjhhFi+fHl87nOfi1133TW+/OUvR0TEq171qhnH3P+T9S996UuN5z/llFNm9FEOPfTQiIj41a9+NdR877nnnoiIWLFiReO+vV4vrrrqqjj22GNn/NrNgx/84DjxxBPjm9/85vT57vfiF794xq99HHroodHr9eKmm26KiIiVK1fG0UcfHZdeemlUVTW936c+9al44hOfGA95yEMiIuKzn/1s9Pv9OO644+K3v/3t9P922WWX2GeffeLqq6+eMe7ixYvjlFNOmbHtyiuvjPHx8XjRi140va3T6cRLX/rSGfv97ne/i69//etx3HHHxe9///vpse6888446qijYs2aNTN+1aXkOr/61a/G73//+3jd615Xa3fcf9yPfvSjWLNmTZx44olx5513To/7xz/+MZ761KfGv/zLv0z/esumND0j11xzTdx+++3xt3/7tzPm8oxnPCP222+/oucw87WvfS0mJyfjZS972Yx7kf1q2GWXXRaHHnpobL/99jNezyOOOCJ6vV78y7/8y1BziIj4m7/5mxn/fOihh854f1x22WWx7bbbxtOe9rQZYx944IGxfPny6Wdpc64HAABo5ld4mOGiiy6KfffdNxYtWhQ777xz/Omf/ml0Ovets910003R6XRi7733nnHMLrvsEtttt930H7Y35f4Fhfttv/32ERG1dkOplStXRkTE73//+8Z977jjjli7dm386Z/+ae3fPfzhD49+vx+/+c1vYv/999+s+R5//PHx+c9/Pr7zne/EwQcfHNdff33867/+64y/innNmjVRVVXss88+6dwG/zaWXXfdtRbivemmm+LBD35wLSY7+Hr88pe/jKqq4k1velO86U1vSse7/fbbY9dddy2+zvt/leuRj3xker6I+64xItJfKbrf3XffPX3ujWmay/3PWfY67rfffvHNb35zk+ffmPvPO/garVq1qjbnNWvWxL/927/FqlWr0nPdfvvtQ81hyZIltXNuv/32M563NWvWxN133x077bTTJsfenOsBAACaWUBhhoMOOqgWPh3UFOLclG63m27/z9/e2Bz77bdfRERcd9118ZjHPGbYaW1UyXyf+cxnxrJly+LSSy+Ngw8+OC699NLodDrxF3/xF9P79Pv9GBsbiyuuuCI952DnZenSpUPP+f5veZxxxhlx1FFHpfsMLrqM4nW5f9x3vOMdG30tNtWzGeVcZlu/34+nPe1p8drXvjb99/vuu+9Q593YtQ+OvdNOO8XHPvax9N9vbFEHAABoxwIKxfbYY4/o9/uxZs2aePjDHz69/bbbbou77ror9thjj5GMszkLNEcffXR0u9346Ec/2hiSXbVqVSxbtix+8Ytf1P7dz3/+8+h0OrH77rtv9ny32WabOOaYY+Kyyy6Ld7/73fGpT30qDj300PiTP/mT6X322muvqKoq9txzz6H/cL3HHnvE1VdfXfsrjQf/hpr7fz1pfHw8jjjiiKHGGrTXXntFRMRPfvKT2uLL4D4rV64c2biZ+5+zX/ziF9N/S9T9fvGLX8x4DjfnWbr/uDVr1sz4Fa877rij9g2pvfbaK/7whz/M6nVuzF577RVf+9rX4pBDDtnkQtvmXA8AANBMA4ViT3/60yMiZvxqSkTEu9/97oi4r0ExCttss03cfffdRfvuvvvu8aIXvSiuuuqqeN/73lf79/1+P971rnfFzTffHN1uN4488sj4whe+EDfeeOP0Prfddlt8/OMfjyc96UnTvxK0uY4//vi49dZb40Mf+lD8+Mc/juOPP37Gv3/2s58d3W43zj777No3KaqqijvvvLNxjKOOOiqmpqbiv//3/z7j+u7/q3rvt9NOO8Xhhx8eH/jAB+Lf//3fa+fZ2N8AsylHHnlkrFixIi644IJYt25dbf4REQceeGDstdde8c53vjP+8Ic/jGTczOMe97jYaaed4v3vf/+Mvz77iiuuiJ/97GcznsNtttkmIu77K4ebHHHEETE+Ph7ve9/7ZrxGg897RMRxxx0X3/nOd+IrX/lK7d/dddddsWHDhs24os1z3HHHRa/Xi3PPPbf27zZs2DB9rZtzPQAAQDPfQKHYox/96DjppJPigx/8YNx1112xevXq+P73vx8f/vCH49hjj40nP/nJIxnnwAMPjE996lPxqle9Kh7/+MfH8uXL45nPfOZG93/Xu94V119/fbz85S+Pz372s3HMMcfE9ttvH7/+9a/jsssui5///OdxwgknRETEeeedF1/96lfjSU96Uvzt3/5tLFq0KD7wgQ/E+vXr4+1vf/vQc376058eK1asiDPOOCO63W485znPmfHv99prrzjvvPPizDPPjBtvvDGOPfbYWLFiRdxwww3xuc99Ll784hfHGWecsckxjj322DjooIPi1a9+dfzyl7+M/fbbLy6//PL43e9+FxEzv21x0UUXxZOe9KQ44IAD4kUvelE87GEPi9tuuy2+853vxM033xw//vGPN+v6Vq5cGe95z3vi1FNPjcc//vFx4oknxvbbbx8//vGPY+3atfHhD384Op1OfOhDH4qjjz469t9//zjllFNi1113jVtuuSWuvvrqWLlyZfzTP/3TZo2bGR8fj7e97W1xyimnxOrVq+N5z3ve9F9j/NCHPjRe+cpXTu974IEHRkTEy1/+8jjqqKOi2+1OPwuDVq1aFWeccUZccMEFccwxx8TTn/70uPbaa+OKK66IBz3oQTP2fc1rXhOXX355HHPMMXHyySfHgQceGH/84x/juuuui09/+tNx44031o4ZldWrV8dpp50WF1xwQfzoRz+KI488MsbHx2PNmjVx2WWXxXvf+9547nOfu1nXAwAAFJiXv/uHBef+v872Bz/4wSb3m5qaqs4+++xqzz33rMbHx6vdd9+9OvPMM2f8dblVtfG/xviyyy6bsV/217n+4Q9/qE488cRqu+22qyKi6K803rBhQ/WhD32oOvTQQ6ttt922Gh8fr/bYY4/qlFNOqf0Vxz/84Q+ro446qlq+fHm1bNmy6slPfnL17W9/u+h+3H8dV199dW0Oz3/+86f/SuCN+cxnPlM96UlPqrbZZptqm222qfbbb7/qpS99afWLX/xiep/Vq1dX+++/f3r8HXfcUZ144onVihUrqm233bY6+eSTq29961tVRFSf/OQnZ+x7/fXXV3/5l39Z7bLLLtX4+Hi16667Vsccc0z16U9/eujrvPzyy6uDDz64Wrp0abVy5crqoIMOqj7xiU/M2Ofaa6+tnv3sZ1c77rhjtXjx4mqPPfaojjvuuOp//s//udH78p/HLHlGqqqqPvWpT1WPfexjq8WLF1c77LBD9fznP7+6+eabZ+yzYcOG6mUve1m1atWqamxsrPGvNO71etXZZ59dPfjBD66WLl1aHX744dVPfvKTao899qj9tb+///3vqzPPPLPae++9q4mJiepBD3pQdfDBB1fvfOc7q8nJyen9NuevMd5mm21qczrrrLPSeX/wgx+sDjzwwGrp0qXVihUrqgMOOKB67WtfW916661DXQ8AALBpY1W1gMqMwGb7/Oc/H8961rPim9/8ZhxyyCHzPR0AAICtkgUU2ILce++9M8KhvV4vjjzyyLjmmmvi//7f/9vqb+8BAABg4zRQYAvyspe9LO699974sz/7s1i/fn189rOfjW9/+9vxd3/3dxZPAAAAZpFvoMAW5OMf/3i8613vil/+8pexbt262HvvveMlL3lJnH766fM9NQAAgK2aBRQAAACABp35ngAAAADAQmcBBQAAAKCBBRQAAACABhZQAAAAABpYQAEAAChw+OGHx+GHHz7f09hsb3nLW2JsbGy+pwFbPAsoAADAyFxyySUxNjYW11xzzXxPZd5MTk7Ge9/73njsYx8bK1eujO222y7233//ePGLXxw///nP53t6wJAWzfcEAAAAtibPec5z4oorrojnPe958aIXvSimpqbi5z//eXzxi1+Mgw8+OPbbb785nc8b3/jGeN3rXjenY8LWyAIKAADAiPzgBz+IL37xi3H++efH61//+hn/7u///u/jrrvuGsk469ati4mJieh0mn+pYNGiRbFokT/6QVt+hQcAAJhzt9xyS/zVX/1V7LzzzrF48eLYf//94x//8R9n7PPP//zPMTY2Fpdeemmcf/75sdtuu8WSJUviqU99avzyl7+cse+aNWviOc95Tuyyyy6xZMmS2G233eKEE06Iu+++e8Z+H/3oR+PAAw+MpUuXxg477BAnnHBC/OY3v6nN74Mf/GDstddesXTp0jjooIPiG9/4RtF1XX/99RERccghh9T+XbfbjR133HHo+/DJT34y3vjGN8auu+4ay5Yti3vuuSempqbi7LPPjn322SeWLFkSO+64YzzpSU+Kr371q9PHb6yB8tGPfjQOOuigWLZsWWy//fZx2GGHxVVXXVV0nfBAZBkSAACYU7fddls88YlPjLGxsTj99NNj1apVccUVV8Rf//Vfxz333BOveMUrZuz/1re+NTqdTpxxxhlx9913x9vf/vZ4/vOfH9/73vci4r7myFFHHRXr16+Pl73sZbHLLrvELbfcEl/84hfjrrvuim233TYiIs4///x405veFMcdd1yceuqpcccdd8T73ve+OOyww+Laa6+N7bbbLiIi/sf/+B9x2mmnxcEHHxyveMUr4le/+lX8+Z//eeywww6x++67b/La9thjj4iI+NjHPhaHHHLIJr/5sbn34dxzz42JiYk444wzYv369TExMRFvectb4oILLohTTz01DjrooLjnnnvimmuuiR/+8IfxtKc9baNjn3322fGWt7wlDj744DjnnHNiYmIivve978XXv/71OPLIIzd5jfCAVQEAAIzIxRdfXEVE9YMf/GCj+/z1X/919eAHP7j67W9/O2P7CSecUG277bbV2rVrq6qqqquvvrqKiOrhD394tX79+un93vve91YRUV133XVVVVXVtddeW0VEddlll210zBtvvLHqdrvV+eefP2P7ddddVy1atGh6++TkZLXTTjtVj3nMY2aM+cEPfrCKiGr16tWbvP5+v1+tXr26iohq5513rp73vOdVF110UXXTTTe1vg8Pe9jDprfd79GPfnT1jGc8Y5NzOuuss6r//Ee/NWvWVJ1Op3rWs55V9Xq92vyBnF/hAQAA5kxVVfGZz3wmnvnMZ0ZVVfHb3/52+n9HHXVU3H333fHDH/5wxjGnnHJKTExMTP/zoYceGhERv/rVryIipr9h8pWvfCXWrl2bjvvZz342+v1+HHfccTPG3GWXXWKfffaJq6++OiIirrnmmrj99tvjb/7mb2aMefLJJ0+PsyljY2Pxla98Jc4777zYfvvt4xOf+ES89KUvjT322COOP/746QbKMPfhpJNOiqVLl87Ytt1228VPf/rTWLNmTePc7vf5z38++v1+vPnNb641VPx1x7BxFlAAAIA5c8cdd8Rdd90VH/zgB2PVqlUz/nfKKadERMTtt98+45iHPOQhM/55++23j4iI//iP/4iIiD333DNe9apXxYc+9KF40IMeFEcddVRcdNFFM/ona9asiaqqYp999qmN+7Of/Wx6zJtuuikiIvbZZ58ZY46Pj8fDHvawomtcvHhxvOENb4if/exnceutt8YnPvGJeOITnxiXXnppnH766UPfhz333LM21jnnnBN33XVX7LvvvnHAAQfEa17zmvi3f/u3Tc7v+uuvj06nE494xCOKrge4jwYKAAAwZ/r9fkREvOAFL4iTTjop3edRj3rUjH/udrvpflVVTf/f73rXu+Lkk0+OL3zhC3HVVVfFy1/+8rjgggviu9/9buy2227R7/djbGwsrrjiivR8y5cvH/aSNunBD35wnHDCCfGc5zwn9t9//7j00kvjkksuGeo+DH77JCLisMMOi+uvv376uj/0oQ/Fe97znnj/+98fp5566ugvCB7ALKAAAABzZtWqVbFixYro9XpxxBFHjPTcBxxwQBxwwAHxxje+Mb797W/HIYccEu9///vjvPPOi7322iuqqoo999wz9t13342e4/4I7Jo1a+IpT3nK9Papqam44YYb4tGPfvRQcxsfH49HPepRsWbNmvjtb3870vuwww47xCmnnBKnnHJK/OEPf4jDDjss3vKWt2x0AWWvvfaKfr8f//t//+94zGMe02pseCDxKzwAAMCc6Xa78ZznPCc+85nPxE9+8pPav7/jjjs2+5z33HNPbNiwYca2Aw44IDqdTqxfvz4iIp797GdHt9uNs88+e8Y3VyLu+ybLnXfeGRERj3vc42LVqlXx/ve/PyYnJ6f3ueSSS6b7JZuyZs2a+PWvf13bftddd8V3vvOd2H777WPVqlUjuw/3z/t+y5cvj7333nv6ujPHHntsdDqdOOecc6a/CXO/wXsD/D++gQIAAIzcP/7jP8aVV15Z2/5f/+t/jbe+9a1x9dVXxxOe8IR40YteFI94xCPid7/7Xfzwhz+Mr33ta/G73/1us8b6+te/Hqeffnr8xV/8Rey7776xYcOG+MhHPjK9SBFx37cuzjvvvDjzzDPjxhtvjGOPPTZWrFgRN9xwQ3zuc5+LF7/4xXHGGWfE+Ph4nHfeeXHaaafFU57ylDj++OPjhhtuiIsvvriogfLjH/84TjzxxDj66KPj0EMPjR122CFuueWW+PCHPxy33nprXHjhhdO/QjSK+/CIRzwiDj/88DjwwANjhx12iGuuuSY+/elPT7dWMnvvvXe84Q1viHPPPTcOPfTQePaznx2LFy+OH/zgB/Enf/InccEFFxTeeXhgsYACAACM3D/8wz+k208++eTYbbfd4vvf/36cc8458dnPfjb+23/7b7HjjjvG/vvvH29729s2e6xHP/rRcdRRR8U//dM/xS233BLLli2LRz/60XHFFVfEE5/4xOn9Xve618W+++4b73nPe+Lss8+OiIjdd989jjzyyPjzP//z6f1e/OIXR6/Xi3e84x3xmte8Jg444IC4/PLL401velPjXA477LA499xz44orroh3v/vdcccdd8SKFSvisY99bLztbW+bXtCJiNh5551b34eXv/zlcfnll8dVV10V69evjz322CPOO++8eM1rXrPJ484555zYc889433ve1+84Q1viGXLlsWjHvWoeOELX1g0LjwQjVW+owUAAACwSRooAAAAAA0soAAAAAA0sIACAAAA0MACCgAAAEADCygAAAAADSygAAAAADSwgAIAAADQYNF8TwAAYGv18M+9pWi/sbFsW1V4bH2/5HTRKThfeq7CeXQKr6FkHptzbHatmezYUc6l9FydKLvHxedrc2wyl85Yf/bHLdgvm1v5PMquoVs433TcFvMrHqPwOjJtrm1r1atKPy2a9avhv4fQL5xHv/DTrfS6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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.Point(\n", + " cosmology = cosmology,\n", + " name = \"point\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " th_ein = torch.tensor(1.),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "#convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "#axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"Point Convergence not defined\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "2ca96b29", + "metadata": {}, + "source": [ + "## Singular Isothermal Sphere (SIS)\n", + "\n", + "An SIS is a mass distribution represented by a constant temperature velocity dispersion of masses. Alternatively, a constant temperature gas in a spherical gravitational potential." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6d563d58", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAIXCAYAAAC7CqrSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACSIUlEQVR4nO3de7RtZ0Gf/3ettfc+JycJkECAAIKQACkBxRGgCEKQi+CNchPQMgpBMVpEqaKVSiWBpFBqLdSISuVSK0UCqHVYkYvi0FEQYRAsiGDkpgMsSCXXk3P23mvN3x8p5+ee77Mzv2e9c1/OyfMZwz/y+s7rusy5J/s8e9J1XVckSZIkSZK0rele74AkSZIkSdJ+5wMUSZIkSZKkAT5AkSRJkiRJGuADFEmSJEmSpAE+QJEkSZIkSRrgAxRJkiRJkqQBPkCRJEmSJEka4AMUSZIkSZKkAT5AkSRJkiRJGuADFEmSJEkKPOpRjyqPetSj9no3jtsll1xSJpPJXu+GdMLzAYpuVT72sY+Vpz3taeUe97hHOXjwYLnrXe9aHve4x5Vf+IVf2DLv67/+68t3fdd3bRm74YYbyktf+tJy//vfv5x66qnl9re/fXngAx9YfuzHfqx88YtfjLb/pS99qbzoRS8q5513Xjl06FA59dRTywUXXFAuu+yycs0114x1mJIkSXvmTW96U5lMJuXDH/7wXu/KnllfXy+vec1ryjd90zeV29zmNuV2t7tdOf/888sP/uAPlk9+8pN7vXuSlrSy1zsg7Zb3v//95Vu/9VvL3e9+9/K85z2v3PnOdy5/+7d/W/70T/+0vOY1rykveMELtl12Y2OjPPKRjyyf/OQny7Of/ezyghe8oNxwww3lL/7iL8p//+//vTz5yU8ud7nLXW5x+x/60IfKd3zHd5QbbrihPOtZzyoXXHBBKaWUD3/4w+WVr3xl+eM//uPy7ne/e9RjliRJ0u576lOfWt75zneW7/3e7y3Pe97zysbGRvnkJz9Zfvd3f7c87GEPK+edd96u7s9LXvKS8tM//dO7uk3pZOQDFN1qXH755eW2t71t+dCHPlRud7vbbfn/ffnLX77FZX/7t3+7XHXVVeXNb35z+b7v+74t/78jR46U9fX1W1z+mmuuKU9+8pPLbDYrV111VXXRvPzyy8t/+S//JT+YfWhzc7MsFouytra217siSZK0Zz70oQ+V3/3d3y2XX355+Tf/5t9s+f9dccUVo/3W8ZEjR8ra2lqZTof/UcHKykpZWfFHP6mV/4RHtxqf/vSny/nnn189PCmllDve8Y6Dy5ZSysMf/vDq/3fw4MFym9vc5haX/5Vf+ZXyhS98ofz8z/88/i8Od7rTncpLXvKSLWOvfe1ry/nnn18OHDhQ7nKXu5TnP//51QX3UY96VLn//e9fPvGJT5Rv/dZvLYcOHSp3vetdy6te9apjc770pS+VlZWVcumll1bb/dSnPlUmk0m54oorjo1dc8015YUvfGH5uq/7unLgwIFy7rnnln//7/99WSwWx+Z87nOfK5PJpPzcz/1cefWrX13OOeeccuDAgfKJT3yilFLKH/3RH5UHPehB5eDBg+Wcc84pv/Irv7Ltv7399V//9XLBBReUU045pZx55pnlmc98Zvnbv/3b4z7Orzly5Ei55JJLyn3uc59y8ODBcvbZZ5enPOUpx17DUkpZLBbl1a9+dTn//PPLwYMHy53udKdy8cUXl69+9avV+iRJ0s74whe+UJ773OeWO93pTuXAgQPl/PPPL294wxu2zPmjP/qjMplMypVXXlkuv/zycre73a0cPHiwPOYxjyl//dd/vWXu1VdfXZ761KeWO9/5zuXgwYPlbne7W3nmM59Zrr322i3zknuPUkp53eteV84555xyyimnlIc85CHlT/7kT6LjuqX7xtlsVm5/+9svfR5+4zd+o7zkJS8pd73rXcuhQ4fKddddVzY2Nsqll15a7n3ve5eDBw+W29/+9uVbvuVbynve855jy9/SfdhDHvKQcujQoXLGGWeURz7ykf5GtHQLfAypW4173OMe5QMf+ED5+Mc/Xu5///sf97KllPJrv/Zr5SUveclxR7h+53d+p5xyyinlaU97WjT/kksuKZdeeml57GMfW374h3+4fOpTnyq/9Eu/VD70oQ+V//W//ldZXV09NverX/1qecITnlCe8pSnlKc//enl7W9/e/nX//pflwc84AHl27/928ud7nSncuGFF5Yrr7yyvPSlL92ynbe+9a1lNpuV7/me7ymllHL48OFy4YUXli984Qvl4osvLne/+93L+9///vLiF7+4/N3f/V159atfvWX5N77xjeXIkSPlB3/wB8uBAwfKmWeeWa666qryhCc8oZx99tnl0ksvLfP5vLzsZS8rZ511VnWcl19+efm3//bflqc//enlB37gB8rf//3fl1/4hV8oj3zkI8tVV1215WHX0HGWUsp8Pi/f9V3fVf7gD/6gPPOZzyw/9mM/Vq6//vrynve8p3z84x8v55xzTimllIsvvri86U1vKhdddFH50R/90fLZz362XHHFFeWqq66qzq8kSRrfl770pfLQhz60TCaT8iM/8iPlrLPOKu985zvL93//95frrruuvPCFL9wy/5WvfGWZTqflRS96Ubn22mvLq171qvLP//k/Lx/84AdLKTc3Rx7/+MeXo0ePlhe84AXlzne+c/nCF75Qfvd3f7dcc8015ba3vW0pJb/3eP3rX18uvvji8rCHPay88IUvLJ/5zGfKE5/4xHLmmWeWr/u6r7vFY/vafeOb3/zm8vCHP/wWf/PjeM/Dy1/+8rK2tlZe9KIXlaNHj5a1tbVyySWXlFe84hXlB37gB8pDHvKQct1115UPf/jD5SMf+Uh53OMet+22L7300nLJJZeUhz3sYeVlL3tZWVtbKx/84AfLH/7hH5Zv+7Zvu8VjlG61OulW4t3vfnc3m8262WzWffM3f3P3Uz/1U9273vWubn19vZp7j3vco/vO7/zOY/99+PDh7r73vW9XSunucY97dM95znO617/+9d2XvvSlaNtnnHFG943f+I3R3C9/+cvd2tpa923f9m3dfD4/Nn7FFVd0pZTuDW94w7GxCy+8sCuldL/2a792bOzo0aPdne985+6pT33qsbFf+ZVf6Uop3cc+9rEt27rf/e7XPfrRjz723y9/+cu7U089tfurv/qrLfN++qd/upvNZt3f/M3fdF3XdZ/97Ge7Ukp3m9vcpvvyl7+8Ze53f/d3d4cOHeq+8IUvHBu7+uqru5WVle4ff+V87nOf62azWXf55ZdvWf5jH/tYt7KysmU8Pc43vOENXSml+/mf//mub7FYdF3XdX/yJ3/SlVK6N7/5zVv+/7//+7+P45Ik6fi88Y1v7Eop3Yc+9KFt53z/939/d/bZZ3df+cpXtow/85nP7G5729t2hw8f7rqu6973vvd1pZTun/yTf9IdPXr02LzXvOY1W+5trrrqqq6U0r3tbW/bdpvpvcf6+np3xzvesXvgAx+4ZZuve93rulJKd+GFF97i8S8Wi2P3Lne605267/3e7+1+8Rd/sfv85z/ffB7uda97HRv7mm/8xm/cct9KXvrSl265D7v66qu76XTaPfnJT95yv/m1/ZfE/Cc8utV43OMeVz7wgQ+UJz7xieXP//zPy6te9ary+Mc/vtz1rnctv/M7v3OLy55yyinlgx/8YPnJn/zJUsrNdfnv//7vL2effXZ5wQteUI4ePXqLy1933XXl9NNPj/bzve99b1lfXy8vfOELt/yb1uc973nlNre5Tfmf//N/bpl/2mmnlWc961nH/nttba085CEPKZ/5zGeOjT3lKU8pKysr5a1vfeuxsY9//OPlE5/4RHnGM55xbOxtb3tbecQjHlHOOOOM8pWvfOXY/z32sY8t8/m8/PEf//GWbT/1qU/d8psl8/m8vPe97y1PetKTtkR1zz333GO/JfI1v/mbv1kWi0V5+tOfvmVbd77zncu9733v8r73ve+4j/Md73hHucMd7oBB4K/91tDb3va2ctvb3rY87nGP27LdCy64oJx22mnVdiVJ0ri6rivveMc7ynd/93eXruu2XI8f//jHl2uvvbZ85CMf2bLMRRddtKWz9ohHPKKUUo7dB3ztN0ze9a53lcOHD+N203uPD3/4w+XLX/5y+aEf+qEt23zOc55zbDu3ZDKZlHe9613lsssuK2eccUZ5y1veUp7//OeXe9zjHuUZz3jGsX+Svcx5ePazn11OOeWULWO3u93tyl/8xV+Uq6++enDfvua3f/u3y2KxKD/7sz9bNVT8c8fS9nyAoluVBz/4weU3f/M3y1e/+tXyZ3/2Z+XFL35xuf7668vTnva0Y/2O7dz2trctr3rVq8rnPve58rnPfa68/vWvL/e9733LFVdcUV7+8pff4rK3uc1tyvXXXx/t4+c///lSSin3ve99t4yvra2Ve93rXsf+/19zt7vdrbrQnXHGGVt6Hne4wx3KYx7zmHLllVceG3vrW99aVlZWylOe8pRjY1dffXX5/d///XLWWWdt+b/HPvaxpZQ6tnvPe95zy39/+ctfLjfddFM599xzq+Pqj1199dWl67py73vfu9reX/7lX1bbSo7z05/+dLnvfe97i78qe/XVV5drr7223PGOd6y2e8MNNwwGhSVJUpu///u/L9dcc0153eteV12LL7roolJKfc9x97vffct/n3HGGaWUcuw+4J73vGf58R//8fKrv/qr5Q53uEN5/OMfX37xF39xS/8kvff42r3Wve997y3bXF1dLfe6172iYzxw4ED5mZ/5mfKXf/mX5Ytf/GJ5y1veUh760IeWK6+8svzIj/zI0uehf+9VSikve9nLyjXXXFPuc5/7lAc84AHlJ3/yJ8v//t//+xb379Of/nSZTqflfve7X3Q8km5mA0W3Smtra+XBD35wefCDH1zuc5/7lIsuuqi87W1vqxoh27nHPe5Rnvvc55YnP/nJ5V73uld585vfXC677LJt55933nnlox/9aFlfXx/9r9TMZjMc77puy38/85nPLBdddFH56Ec/Wh74wAeWK6+8sjzmMY8pd7jDHY7NWSwW5XGPe1z5qZ/6KVznfe5zny3/3f9fQI7HYrEok8mkvPOd78RjOO2007b8d3qcyXbveMc7lje/+c34/6dWiyRJGs/XwvTPetazyrOf/Wyc8w3f8A1b/ju5D/iP//E/luc85znlf/yP/1He/e53lx/90R8tr3jFK8qf/umflrvd7W7Hfe8xlrPPPrs885nPLE996lPL+eefX6688srypje9aanzQPdej3zkI8unP/3pY8f9q7/6q+U//af/VH75l3+5/MAP/MD4ByTdivkARbd6D3rQg0oppfzd3/3dcS97xhlnlHPOOad8/OMfv8V53/3d310+8IEPlHe84x3le7/3e29x7tfCY5/61Ke2/K8c6+vr5bOf/eyx3wY5Xk960pPKxRdffOyf8fzVX/1VefGLX7xlzjnnnFNuuOGGpbdxxzvesRw8eLCq4pdSqrFzzjmndF1X7nnPe1YPZpZ1zjnnlA9+8INlY2Nj2xDsOeecU9773veWhz/84U0PgCRJ0nLOOuuscvrpp5f5fL70Pcd2HvCAB5QHPOAB5SUveUl5//vfXx7+8IeXX/7lXy6XXXZZfO/xtXuxq6++ujz60Y8+Nr6xsVE++9nPlm/8xm9cat9WV1fLN3zDN5Srr766fOUrXxn1PJx55pnloosuKhdddFG54YYbyiMf+chyySWXbPsA5ZxzzimLxaJ84hOfKA984AObti3dmvhPeHSr8b73vQ9/W+H3fu/3Sin1P5n5x/78z/+8fOUrX6nGP//5z5dPfOITt7hsKaX80A/9UDn77LPLT/zET5S/+qu/qv7/X/7yl4/9BstjH/vYsra2Vv7zf/7PW/b39a9/fbn22mvLd37nd97itrZzu9vdrjz+8Y8vV155ZfmN3/iNsra2Vp70pCdtmfP0pz+9fOADHyjvete7quWvueaasrm5eYvbmM1m5bGPfWz57d/+7fLFL37x2Phf//Vfl3e+851b5j7lKU8ps9msXHrppdXr0nVd+b//9/8e5xHe3GT5yle+suXPMv/jdZZy8zHO53P8Z1ebm5vVn4qWJEnjms1m5alPfWp5xzvegf8j1N///d8f9zqvu+666j7lAQ94QJlOp8dadem9x4Me9KBy1llnlV/+5V8u6+vrx+a86U1viu4Trr766vI3f/M31fg111xTPvCBD5QzzjijnHXWWaOdh/4902mnnVbOPffcW2z0PelJTyrT6bS87GUvO/abMF9zvL/dK92a+BsoutV4wQteUA4fPlye/OQnl/POO6+sr6+X97///eWtb31r+fqv//pj/9aUvOc97ykvfelLyxOf+MTy0Ic+tJx22mnlM5/5THnDG95Qjh49Wi655JJb3PYZZ5xRfuu3fqt8x3d8R3ngAx9YnvWsZ5ULLriglFLKRz7ykfKWt7ylfPM3f3Mp5eb/VebFL35xufTSS8sTnvCE8sQnPrF86lOfKq997WvLgx/84C0h1eP1jGc8ozzrWc8qr33ta8vjH//4LX8muJRSfvInf7L8zu/8Tvmu7/qu8pznPKdccMEF5cYbbywf+9jHytvf/vbyuc99bss/+SGXXHJJefe7310e/vCHlx/+4R8u8/m8XHHFFeX+979/+ehHP3ps3jnnnFMuu+yy8uIXv7h87nOfK0960pPK6aefXj772c+W3/qt3yo/+IM/WF70ohcd1/H9i3/xL8qv/dqvlR//8R8vf/Znf1Ye8YhHlBtvvLG8973vLf/yX/7L8s/+2T8rF154Ybn44ovLK17xivLRj360fNu3fVtZXV0tV199dXnb295WXvOa18R/blqSJG3vDW94Q/n93//9avzHfuzHyitf+cryvve9r/zTf/pPy/Oe97xyv/vdr/zDP/xD+chHPlLe+973ln/4h384rm394R/+YfmRH/mR8j3f8z3lPve5T9nc3Cz/7b/9t2MPKUrJ7z1WV1fLZZddVi6++OLy6Ec/ujzjGc8on/3sZ8sb3/jGqIHy53/+5+X7vu/7yrd/+7eXRzziEeXMM88sX/jCF8p//a//tXzxi18sr371q4/9E6IxzsP97ne/8qhHPapccMEF5cwzzywf/vCHy9vf/vZjrRVy7rnnlp/5mZ8pL3/5y8sjHvGI8pSnPKUcOHCgfOhDHyp3uctdyite8YrwzEu3Mrv8V3+kPfPOd76ze+5zn9udd9553Wmnndatra115557bveCF7yg+nPE/T9j/JnPfKb72Z/92e6hD31od8c73rFbWVnpzjrrrO47v/M7uz/8wz+M9+GLX/xi96/+1b/q7nOf+3QHDx7sDh061F1wwQXd5Zdf3l177bVb5l5xxRXdeeed162urnZ3utOduh/+4R/uvvrVr26Zc+GFF3bnn39+tZ1nP/vZ3T3ucY9q/LrrrutOOeWUrpTS/fqv/zru4/XXX9+9+MUv7s4999xubW2tu8Md7tA97GEP637u537u2J98/tqfMf4P/+E/4Dr+4A/+oPumb/qmbm1trTvnnHO6X/3VX+1+4id+ojt48GA19x3veEf3Ld/yLd2pp57anXrqqd15553XPf/5z+8+9alPLXWchw8f7n7mZ36mu+c979mtrq52d77znbunPe1p3ac//ekt8173utd1F1xwQXfKKad0p59+eveABzyg+6mf+qnui1/8Ih6TJEnKfO3PGG/3f3/7t3/bdV3XfelLX+qe//znd1/3dV937Jr9mMc8pnvd6153bF1f+/O9/T9P/LV7kTe+8Y1d1918r/bc5z63O+ecc7qDBw92Z555Zvet3/qt3Xvf+95q/5J7j67rute+9rXdPe95z+7AgQPdgx70oO6P//iPuwsvvHDwzxh/6Utf6l75yld2F154YXf22Wd3Kysr3RlnnNE9+tGP7t7+9rfj/GXPQ9d13WWXXdY95CEP6W53u9t1p5xySnfeeed1l19++bH7tq6r/4zx17zhDW/ovumbvqk7cOBAd8YZZ3QXXnhh9573vOcWj0+6NZt0nb+jJWnnPelJTzruP7EnSZIkSfuFDRRJo7vpppu2/PfVV19dfu/3fq886lGP2psdkiRJkqRG/gaKpNGdffbZ5TnPeU65173uVT7/+c+XX/qlXypHjx4tV111Vbn3ve+917snSZIkScfNiKyk0T3hCU8ob3nLW8r/+T//pxw4cKB88zd/c/l3/+7f+fBEkiRJ0gnL30CRJEmSJEkaYANFkiRJkiRpgA9QJEmSJEmSBvgARZIkSZIkaUAckX3c9HuyiZNJOC97djOZjri+cF0TOoZp+KwpPf5wfbgv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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.SIS(\n", + " cosmology = cosmology,\n", + " name = \"sis\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " th_ein = torch.tensor(1.),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"SIS Convergence\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "e09f874d", + "metadata": {}, + "source": [ + "## Singular Isothermal Ellipsoid (SIE)\n", + "\n", + "The SIE is just like the SIS except it has been compressed along one axis." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "63c78c9a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAIXCAYAAAC7CqrSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACNd0lEQVR4nO39e7Bvd0Hf/78/n8++nNwvGBIgmEAAE5KINkorEYIXpK0W1ADBFoFwEWcAtYpMvVRBoFBnqma0HSZVBIUqREApFgEHVFC0MCqDVmoMSBH8BmLIPefsvT+f9fsjv5zhrPdzZ73O5732Jec8HzP+kbfvdf1c1tqLfZ570nVdVyRJkiRJkrSt6V7vgCRJkiRJ0n7nAxRJkiRJkqQBPkCRJEmSJEka4AMUSZIkSZKkAT5AkSRJkiRJGuADFEmSJEmSpAE+QJEkSZIkSRrgAxRJkiRJkqQBPkCRJEmSJEka4AMUSZIkSQo88YlPLE984hP3ejeO2ite8YoymUz2ejek+z0foOi48olPfKI87WlPK+edd145cOBAechDHlKe9KQnlV/8xV88Yt75559fvuM7vuOIsclksu3/ff/3f3+0/RtuuKG86EUvKg9/+MPLgQMHyqmnnlouv/zycs0115S77757tOOUJEnaK2984xvLZDIpH/vYx/Z6V/bMxsZGueaaa8rXfu3XllNPPbWcfvrp5eKLLy7f933fVz75yU/u9e5JWtLKXu+AtFv+5E/+pHzTN31T+cqv/Mrywhe+sJxzzjnls5/9bPnTP/3Tcs0115SXvvSlg+t40pOeVJ797GdX44961KMGl/3d3/3d8vSnP72sr6+XZz/72eWSSy4pGxsb5cMf/nD50R/90fLXf/3X5dprr13q2CRJkrR/XHnlleU973lP+Z7v+Z7ywhe+sGxubpZPfvKT5d3vfnd53OMeVy688MJd3Z+f/MmfLP/hP/yHXd2mdCzyAYqOG695zWvKaaedVj760Y+W008//Yj/3xe+8IVoHY961KPKs571rKPe9qc//enyzGc+s5x33nnlAx/4QHnQgx50+P/34he/uPzd3/1d+d3f/d2jXu9+cvDgwbK2tlamU3+xTZIkHb8++tGPlne/+93lNa95TfnxH//xI/5/v/RLv1RuueWWUbZzNPdeKysrZWXFH/2kVv6ko+PGDTfcUC6++OLq4UkppTzwgQ/c0W3/7M/+bLnjjjvKr/zKrxzx8ORej3jEI8oP/uAPHv7vra2t8qpXvapccMEFZX19vZx//vnlx3/8x8uhQ4eOWO7ef2r04Q9/uDz2sY8tBw4cKA9/+MPLr/3arx2e87GPfaxMJpPypje9qdrue9/73jKZTMq73/3uw2Of+9znyvOe97xy9tlnl/X19XLxxReXN7zhDUcs9wd/8AdlMpmU3/zN3yw/+ZM/WR7ykIeUE088sdx2222llFKuu+668uhHP7ocOHCgXHLJJeWd73xnee5zn1vOP//8I9azWCzKL/zCL5SLL764HDhwoJx99tnlRS96UfnSl7501Md5r1tuuaX8+3//78v5559f1tfXy7nnnlue/exnl5tuuunwnEOHDpWf/umfLo94xCPK+vp6eehDH1pe/vKXV+dXkiTtnKO553jb295WXvOa15Rzzz23HDhwoHzLt3xL+bu/+7sj5l5//fXlyiuvLOecc045cOBAOffcc8szn/nMcuuttx4x781vfnO57LLLygknnFDOPPPM8sxnPrN89rOfrfbv2muvLRdccEE54YQTymMf+9jyoQ99KDquG264oZRSyuWXX179/2azWXnAAx6w9Hmge6/Nzc3yyle+sjzykY8sBw4cKA94wAPKN37jN5b3v//9h5ffroHy5je/uTz2sY8tJ554YjnjjDPKE57whPK+970vOk7peORjSB03zjvvvPKRj3yk/NVf/VW55JJLllrHwYMHj/hB/F6nnnpqWVtb23a5//k//2d5+MMfXh73uMdF23nBC15Q3vSmN5WnPe1p5Ud+5EfKn/3Zn5XXvva15W/+5m/KO9/5ziPm/t3f/V152tOeVp7//OeX5zznOeUNb3hDee5zn1suu+yycvHFF5ev+7qvKw9/+MPL2972tvKc5zzniGXf+ta3ljPOOKM8+clPLqWUcuONN5Z/8S/+RZlMJuUlL3lJOeuss8p73vOe8vznP7/cdttt5Yd+6IeOWP5Vr3pVWVtbKy972cvKoUOHytraWvnd3/3dctVVV5VLL720vPa1ry1f+tKXyvOf//zykIc8pDrOF73oReWNb3xjufrqq8sP/MAPlE9/+tPll37pl8pf/MVflD/+4z8uq6ur8XGWUsodd9xRHv/4x5e/+Zu/Kc973vPKP/tn/6zcdNNN5V3velf5h3/4h/IVX/EVZbFYlKc85Snlwx/+cPm+7/u+ctFFF5VPfOIT5ed//ufL3/7t35bf/u3fjl4jSZK0vKO953jd615XptNpednLXlZuvfXW8rM/+7Pl3/27f1f+7M/+rJRyT3PkyU9+cjl06FB56UtfWs4555zyuc99rrz73e8ut9xySznttNNKKff8RvJ//I//sTzjGc8oL3jBC8oXv/jF8ou/+IvlCU94QvmLv/iLw/9D26/8yq+UF73oReVxj3tc+aEf+qHyqU99qjzlKU8pZ555ZnnoQx96n8d23nnnlVJKectb3lIuv/zy+/zNjzHuvV7xileU1772teUFL3hBeexjH1tuu+228rGPfaz8+Z//eXnSk5607bZf+cpXlle84hXlcY97XPmZn/mZsra2Vv7sz/6sfOADHyjf9m3fdp/HKB23Ouk48b73va+bzWbdbDbrvuEbvqF7+ctf3r33ve/tNjY2qrnnnXde9+3f/u1HjJVStv2/3/iN39h2u7feemtXSume+tSnRvv5l3/5l10ppXvBC15wxPjLXvayrpTSfeADHzhiP0sp3R/90R8dHvvCF77Qra+vdz/yIz9yeOzHfuzHutXV1e7mm28+PHbo0KHu9NNP7573vOcdHnv+85/fPehBD+puuummI7b9zGc+szvttNO6u+66q+u6rvvgBz/YlVK6hz/84YfH7nXppZd25557bnf77bcfHvuDP/iDrpTSnXfeeYfHPvShD3WllO4tb3nLEcv/3u/9XjWeHudP/dRPdaWU7h3veEfXt1gsuq7rul//9V/vptNp96EPfeiI///rX//6rpTS/fEf/3G1rCRJyv3qr/5qV0rpPvrRj24752jvOS666KLu0KFDh+ddc801XSml+8QnPtF1Xdf9xV/8RVdK6a677rptt/n3f//33Ww2617zmtccMf6JT3yiW1lZOTy+sbHRPfCBD+y+5mu+5ohtXnvttV0ppbviiivu8/gXi0V3xRVXdKWU7uyzz+6+53u+p/uv//W/dp/5zGeazwPdez3mMY+p7lv7fvqnf7r78h/9rr/++m46nXbf9V3f1c3n82r/JTH/CY+OG0960pPKRz7ykfKUpzylfPzjHy8/+7M/W5785CeXhzzkIeVd73pXtI6nPvWp5f3vf3/1f9/0Td+07TL3/rOWU045JdrG//pf/6uUUsoP//APHzH+Iz/yI6WUUrVSHv3oR5fHP/7xh//7rLPOKl/1VV9VPvWpTx0eu+qqq8rm5mZ5xzvecXjsfe97X7nlllvKVVddVUoppeu68va3v738m3/zb0rXdeWmm246/H9PfvKTy6233lr+/M///IhtP+c5zyknnHDC4f/+/Oc/Xz7xiU+UZz/72eXkk08+PH7FFVeUSy+99Ihlr7vuunLaaaeVJz3pSUds67LLLisnn3xy+eAHP3jUx/n2t7+9POYxjynf9V3fVZ3Xe39t9brrrisXXXRRufDCC4/Y7jd/8zeXUkq1XUmSNK5l7jmuvvrqI37b9957gnvvA+79DZP3vve95a677sLtvuMd7yiLxaI84xnPOGKb55xzTnnkIx95+B7gYx/7WPnCF75Qvv/7v/+IbT73uc89vJ37MplMynvf+97y6le/upxxxhnlN37jN8qLX/zict5555WrrrrqcANljHuvUko5/fTTy1//9V+X66+/fnDf7vXbv/3bZbFYlJ/6qZ+qGir+uWNpe/4THh1Xvv7rv7684x3vKBsbG+XjH/94eec731l+/ud/vjztaU8rf/mXf1ke/ehH3+fy5557bvnWb/3Wo9rmqaeeWkop5fbbb4/mf+YznynT6bQ84hGPOGL8nHPOKaeffnr5zGc+c8T4V37lV1brOOOMM47oiDzmMY8pF154YXnrW99anv/855dS7vnnO1/xFV9x+MHBF7/4xXLLLbeUa6+9dtu/BtSP7T7sYQ+r9r2UUu37vWNffhNw/fXXl1tvvXXb/kx/W8lx3nDDDeXKK6/E9X35dv/mb/6mnHXWWdF2JUnSuJa55+jfB5xxxhmllHL4PuBhD3tY+eEf/uHycz/3c+Utb3lLefzjH1+e8pSnlGc961mHH3pcf/31peu68shHPhK3ee8/Hb73fqY/b3V1tTz84Q+PjnF9fb38xE/8RPmJn/iJ8o//+I/lD//wD8s111xT3va2t5XV1dXy5je/eZR7r1JK+Zmf+Zny1Kc+tTzqUY8ql1xySfmX//Jflu/93u8tX/3VX73t/t1www1lOp0O3vtKOpIPUHRcWltbK1//9V9fvv7rv7486lGPKldffXW57rrryk//9E+Pvq1TTz21PPjBDy5/9Vd/dVTLpU//Z7MZjnddd8R/X3XVVeU1r3lNuemmm8opp5xS3vWud5Xv+Z7vOfzvcheLRSmllGc961lVK+Ve/Qtx/38BORqLxaI88IEPLG95y1vw/99/wJEeZ7LdSy+9tPzcz/0c/v+H/l2zJElqs8w9R3If8F/+y38pz33uc8vv/M7vlPe9733lB37gB8prX/va8qd/+qfl3HPPLYvFokwmk/Ke97wH1/flvz07pgc96EHlmc98ZrnyyivLxRdfXN72treVN77xjaPdez3hCU8oN9xww+Hj/uVf/uXy8z//8+X1r399ecELXjD+AUnHMR+g6Lj3dV/3daWUUv7xH/9xx7bxHd/xHeXaa68tH/nIR8o3fMM33Ofc8847rywWi3L99deXiy666PD4jTfeWG655ZbDYbKjddVVV5VXvvKV5e1vf3s5++yzy2233Vae+cxnHv7/n3XWWeWUU04p8/n8qH/L5sv3vZRSVfFp7IILLii///u/Xy6//PKmBzH9dQ49qLrgggvKxz/+8fIt3/It/oqqJEl7YIx7ju1ceuml5dJLLy0/+ZM/Wf7kT/6kXH755eX1r399efWrX10uuOCC0nVdedjDHlYe9ahHbbuOe+9nrr/++sO/qVtKKZubm+XTn/50ecxjHrPUvq2urpav/uqvLtdff3256aabRj0PZ555Zrn66qvL1VdfXe64447yhCc8obziFa/Y9gHKBRdcUBaLRfk//+f/lK/5mq9p2rZ0PLGBouPGBz/4QfxthXubI1/1VV+1Y9t++ctfXk466aTyghe8oNx4443V//+GG24o11xzTSmllH/9r/91KaWUX/iFXzhizr2/MfHt3/7tS+3DRRddVC699NLy1re+tbz1rW8tD3rQg8oTnvCEw///2WxWrrzyyvL2t78dH0J88YtfHNzGgx/84HLJJZeUX/u1Xyt33HHH4fE//MM/LJ/4xCeOmPuMZzyjzOfz8qpXvapaz9bW1uF/H3w0rrzyysP/NKvv3tf+Gc94Rvnc5z5X/vt//+/VnLvvvrvceeedR71dSZKUG+Oeo++2224rW1tbR4xdeumlZTqdlkOHDpVSSvnu7/7uMpvNyitf+crqnrDruvJP//RPpZR7/se1s846q7z+9a8vGxsbh+e88Y1vjO5Prr/++vL//t//q8ZvueWW8pGPfKScccYZ5ayzzhrtPNy73/c6+eSTyyMe8YjDx02+8zu/s0yn0/IzP/Mzh38T5l5H+9u90vHE30DRceOlL31pueuuu8p3fdd3lQsvvLBsbGyUP/mTPylvfetby/nnn1+uvvrqwXX87d/+bXnzm99cjZ999tn3+WfiLrjggvI//sf/KFdddVW56KKLyrOf/exyySWXHN6H6667rjz3uc8tpdzTK3nOc55Trr322nLLLbeUK664ovzv//2/y5ve9Kbynd/5nfcZrB1y1VVXlZ/6qZ8qBw4cKM9//vOraNjrXve68sEPfrD883/+z8sLX/jC8uhHP7rcfPPN5c///M/L7//+75ebb755cBv/6T/9p/LUpz61XH755eXqq68uX/rSl8ov/dIvlUsuueSIhypXXHFFedGLXlRe+9rXlr/8y78s3/Zt31ZWV1fL9ddfX6677rpyzTXXlKc97WlHdXw/+qM/Wn7rt36rPP3pTy/Pe97zymWXXVZuvvnm8q53vau8/vWvL495zGPK937v95a3ve1t5fu///vLBz/4wXL55ZeX+XxePvnJT5a3ve1t5b3vfe/h30qSJEnLe8Mb3lB+7/d+rxr/wR/8wVHuOb7cBz7wgfKSl7ykPP3pTy+PetSjytbWVvn1X//1ww8pSrnnfuzVr351+bEf+7Hy93//9+U7v/M7yymnnFI+/elPl3e+853l+77v+8rLXvaysrq6Wl796leXF73oReWbv/mby1VXXVU+/elPl1/91V+NGigf//jHy7/9t/+2/Kt/9a/K4x//+HLmmWeWz33uc+VNb3pT+fznP19+4Rd+4fA/IRrjPDz60Y8uT3ziE8tll11WzjzzzPKxj32s/NZv/VZ5yUtesu0yj3jEI8pP/MRPlFe96lXl8Y9/fPnu7/7usr6+Xj760Y+WBz/4weW1r31teOal48zu/+EfaW+85z3v6Z73vOd1F154YXfyySd3a2tr3SMe8YjupS99aXfjjTceMfdo/4zx0J+zu9ff/u3fdi984Qu7888/v1tbW+tOOeWU7vLLL+9+8Rd/sTt48ODheZubm90rX/nK7mEPe1i3urraPfShD+1+7Md+7Ig52+1n13XdFVdcgft0/fXXH97nD3/4w7iPN954Y/fiF7+4e+hDH9qtrq5255xzTvct3/It3bXXXnt4zr1/Sm+7PxX4m7/5m92FF17Yra+vd5dcckn3rne9q7vyyiu7Cy+8sJp77bXXdpdddll3wgkndKecckp36aWXdi9/+cu7z3/+80sd5z/90z91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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.SIE(\n", + " cosmology = cosmology,\n", + " name = \"sie\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " q = torch.tensor(0.6),\n", + " phi = torch.tensor(np.pi/2),\n", + " b = torch.tensor(1.),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"SIE Convergence\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "794060c3", + "metadata": {}, + "source": [ + "## Elliptical Power Law (EPL)\n", + "\n", + "This is a power law mass distribution with an elliptical isodensity contour." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "557bad9f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAIXCAYAAAC7CqrSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACNK0lEQVR4nO39e7RtZ0Hf/z9rrX05J8nJDcI1FEgCTUgi2jCwJUIQBEq1XIwQaJFbQBwDqlaRUYQi10IdQ4UhOhip3FRaSUAthSLiABRQKQyVgRZqCJFa6BcIkBsn5+y915q/P/LLKXvO9878nPXMvfY657xfY/DHeXjmda0919wze7/3qGmapkiSJEmSJGlH473eAUmSJEmSpGXnAxRJkiRJkqQePkCRJEmSJEnq4QMUSZIkSZKkHj5AkSRJkiRJ6uEDFEmSJEmSpB4+QJEkSZIkSerhAxRJkiRJkqQePkCRJEmSJEnq4QMUSZIkSQo88pGPLI985CP3ejeO2itf+coyGo32ejekY54PUHTce8c73lFGo9GO//uLv/iLI3O/e3w8Hpd73ete5bGPfWz52Mc+tm2d97vf/cqP/MiPzL1P1113XXnBC15QzjnnnLJv375y6qmnlksvvbS86U1vKrfddtvc65UkSdprd9x7feYzn9nrXdkzGxsb5U1velP5vu/7vnLqqaeW008/vVx44YXlJ37iJ8oXvvCFvd49SXNa2esdkBbl1a9+dbn//e/fGT/vvPO2/fsxj3lMeeYzn1mapinXX399+Y3f+I3yqEc9qnzgAx8oj3/846v34wMf+EB5ylOeUtbX18szn/nMctFFF5WNjY3yiU98ovz8z/98+du//dty1VVXVW9HkiRJe+Pyyy8vH/zgB8vTn/708vznP79sbm6WL3zhC+X9739/edjDHlbOP//8he7Py1/+8vLv/t2/W+g2peORD1B0wnj84x9fHvKQh/TOe+ADH1ie8YxnHPn3k5/85PI93/M95Y1vfGP1A5Trr7++PO1pTyv3ve99y0c+8pFyz3ve88j/98IXvrB88YtfLB/4wAeqtrHXDh06VNbW1sp47A+4SZKkE8+nP/3p8v73v7+87nWvK7/wC7+w7f9785vfXG688cZBtnM091wrKytlZcVv/aRafocj9bj44ovLXe9613L99ddXr+uXfumXyq233lre+ta3bnt4cofzzjuv/PRP//SRf29tbZXXvOY15dxzzy3r6+vlfve7X/mFX/iFcvjw4W3L3fErRZ/4xCfKQx/60LJv375yzjnnlN/6rd86Muczn/lMGY1G5Z3vfGdnux/60IfKaDQq73//+4+MfeUrXynPfe5zy93vfveyvr5eLrzwwvK2t71t23If+9jHymg0Kr/7u79bXv7yl5d73/ve5aSTTio333xzKaWUa665pjzoQQ8q+/btKxdddFH5/d///fLsZz+73O9+99u2ntlsVt74xjeWCy+8sOzbt6/c/e53Ly94wQvKt7/97aM+zjvceOON5d/+239b7ne/+5X19fVy9tlnl2c+85nlhhtuODLn8OHD5Rd/8RfLeeedV9bX18t97nOf8pKXvKRzfiVJ0vCO5l7j6quvLq973evK2WefXfbt21ce/ehHly9+8Yvb5l577bXl8ssvL/e4xz3Kvn37ytlnn12e9rSnlZtuumnbvN/5nd8pl1xySdm/f38588wzy9Oe9rTyD//wD539u+qqq8q5555b9u/fXx760IeWj3/849FxXXfddaWUUi699NLO/zeZTMpd7nKXuc8D3XNtbm6WV73qVeUBD3hA2bdvX7nLXe5SfuAHfqB8+MMfPrL8Tg2U3/md3ykPfehDy0knnVTOOOOM8ohHPKL80R/9UXSc0onIx5A6Ydx0003bvnku5fbmSftDrO3b3/52+fa3v935VZ95/Lf/9t/KOeecUx72sIdF85/3vOeVd77zneXHfuzHys/93M+VT33qU+X1r399+fznP19+//d/f9vcL37xi+XHfuzHypVXXlme9axnlbe97W3l2c9+drnkkkvKhRdeWB7ykIeUc845p1x99dXlWc961rZl3/3ud5czzjijPO5xjyullPK1r32t/NN/+k/LaDQqL3rRi8pZZ51VPvjBD5Yrr7yy3HzzzeVnfuZnti3/mte8pqytrZUXv/jF5fDhw2Vtba184AMfKFdccUW5+OKLy+tf//ry7W9/u1x55ZXl3ve+d+c4X/CCF5R3vOMd5TnPeU75qZ/6qXL99deXN7/5zeWv/uqvyic/+cmyuroaH2cppdx6663l4Q9/ePn85z9fnvvc55Z/8k/+SbnhhhvK+973vvJ//s//KXe9613LbDYrT3jCE8onPvGJ8hM/8RPlggsuKJ/73OfKr/7qr5a/+7u/K3/wB38QvUaSJOnoHe29xhve8IYyHo/Li1/84nLTTTeVX/qlXyr/+l//6/KpT32qlHJ7c+Rxj3tcOXz4cPk3/+bflHvc4x7lK1/5Snn/+99fbrzxxnLaaaeVUkp53eteV/79v//35alPfWp53vOeV77xjW+UX/u1XyuPeMQjyl/91V+V008/vZRSylvf+tbyghe8oDzsYQ8rP/MzP1O+9KUvlSc84QnlzDPPLPe5z33u9Njue9/7llJKede73lUuvfTSO/3JjyHuuV75yleW17/+9eV5z3teeehDH1puvvnm8pnPfKb85V/+ZXnMYx6z47Zf9apXlVe+8pXlYQ97WHn1q19d1tbWyqc+9anykY98pDz2sY+902OUTliNdJx7+9vf3pRS8H/r6+vb5pZSmiuvvLL5xje+0Xz9619vPvWpTzWPfvSjm1JK88u//MtH5t33vvdtfviHf/io9uOmm25qSinNE5/4xGj+X//1XzellOZ5z3vetvEXv/jFTSml+chHPrJtf0opzZ/+6Z8eGfv617/erK+vNz/3cz93ZOylL31ps7q62nzrW986Mnb48OHm9NNPb5773OceGbvyyiube97zns0NN9ywbdtPe9rTmtNOO605ePBg0zRN89GPfrQppTTnnHPOkbE7XHzxxc3ZZ5/d3HLLLUfGPvaxjzWllOa+973vkbGPf/zjTSmlede73rVt+T/8wz/sjKfH+YpXvKIppTS/93u/17TNZrOmaZrmt3/7t5vxeNx8/OMf3/b/v+Utb2lKKc0nP/nJzrKSJKnfHfden/70p3ecc7T3GhdccEFz+PDhI/Pe9KY3NaWU5nOf+1zTNE3zV3/1V00ppbnmmmt23Obf//3fN5PJpHnd6163bfxzn/tcs7KycmR8Y2Ojudvd7tZ87/d+77ZtXnXVVU0ppbnsssvu9Phns1lz2WWXNaWU5u53v3vz9Kc/vfn1X//15stf/nL1eaB7rgc/+MG996W/+Iu/2Hz3t37XXnttMx6Pmyc/+cnNdDrt7L8k5q/w6ITx67/+6+XDH/7wtv998IMf7Mx761vfWs4666xyt7vdrXz/939/+eQnP1l+9md/tvNfAI7WHb/WcuDAgWj+f//v/72UUsrP/uzPbhv/uZ/7uVJK6bRSHvSgB5WHP/zhR/591llnlX/8j/9x+dKXvnRk7Iorriibm5vl937v946M/dEf/VG58cYbyxVXXFFKKaVpmvLe9763/Mt/+S9L0zTlhhtuOPK/xz3uceWmm24qf/mXf7lt28961rPK/v37j/z7q1/9avnc5z5XnvnMZ5ZTTjnlyPhll11WLr744m3LXnPNNeW0004rj3nMY7Zt65JLLimnnHJK+ehHP3rUx/ne9763PPjBDy5PfvKTO+f1jh9fveaaa8oFF1xQzj///G3bfdSjHlVKKZ3tSpKkYcxzr/Gc5zynrK2tHfn3HfcCd3z+3/ETJh/60IfKwYMHcbu/93u/V2azWXnqU5+6bZv3uMc9ygMe8IAjn/2f+cxnyte//vXykz/5k9u2+exnP/vIdu7MaDQqH/rQh8prX/vacsYZZ5T/8l/+S3nhC19Y7nvf+5YrrrjiSANliHuuUko5/fTTy9/+7d+Wa6+9tnff7vAHf/AHZTablVe84hWdhop/7ljamb/CoxPGQx/60Cgi+8QnPrG86EUvKqPRqBw4cKBceOGF5eSTT67e/qmnnlpKKeWWW26J5n/5y18u4/G486tD97jHPcrpp59evvzlL28b/0f/6B911nHGGWds64g8+MEPLueff35597vfXa688spSyu2/vnPXu971yIODb3zjG+XGG28sV1111Y5/DejrX//6tn+3/7rRHftGv/Z03nnnbbsZuPbaa8tNN91U7na3u0XbSo7zuuuuK5dffjmu77u3+/nPf76cddZZ0XYlSdIw5rnXaH/+n3HGGaWUcuTz//73v3/52Z/92fIrv/Ir5V3veld5+MMfXp7whCeUZzzjGUceelx77bWlaZrygAc8ALd5x68M33Ef0563urpazjnnnOgY19fXy8te9rLyspe9rPzf//t/y5/8yZ+UN73pTeXqq68uq6ur5Xd+53cGuecq5fa/NPnEJz6xPPCBDywXXXRR+ef//J+XH//xHy/f8z3fs+P+XXfddWU8HpcHPehB0fFIup0PUKSWs88+u/zQD/3Q4Os99dRTy73uda/yN3/zN0e1XPpfASaTCY43TbPt31dccUV53eteV2644YZy4MCB8r73va88/elPP/L7ubPZrJRSyjOe8YxOK+UO7Q/k9n8JORqz2azc7W53K+9617vw/28/4EiPM9nuxRdfXH7lV34F//++32+WJEnzmedeI/n8/+Vf/uXy7Gc/u/zX//pfyx/90R+Vn/qpnyqvf/3ry1/8xV+Us88+u8xmszIajcoHP/hBXN93/9TskO55z3uWpz3taeXyyy8vF154Ybn66qvLO97xjsHuuR7xiEeU66677shx/+Zv/mb51V/91fKWt7ylPO95zxv+gKQTmA9QpAX6kR/5kXLVVVeVP//zPy//7J/9szude9/73rfMZrNy7bXXlgsuuODI+Ne+9rVy4403HgmUHa0rrriivOpVryrvfe97y93vfvdy8803l6c97WlH/v+zzjqrHDhwoEyn07kfJN2xb+06Po2de+655Y//+I/LpZdeWvUgpr3OvgdV5557bvnsZz9bHv3oR/ujqpIkLdAQ9xo7ufjii8vFF19cXv7yl5c/+7M/K5deeml5y1veUl772teWc889tzRNU+5///uXBz7wgTuu4477mGuvvfbIT+iWUsrm5ma5/vrry4Mf/OC59m11dbV8z/d8T7n22mvLDTfcMOh5OPPMM8tznvOc8pznPKfceuut5RGPeER55StfueMDlHPPPbfMZrPyP//n/yzf+73fW7Vt6URiA0VaoJe85CXl5JNPLs973vPK1772tc7/f91115U3velNpZRS/sW/+BellFLe+MY3bptzx09M/PAP//Bc+3DBBReUiy++uLz73e8u7373u8s973nP8ohHPOLI/z+ZTMrll19e3vve9+JDiG984xu927jXve5VLrroovJbv/Vb5dZbbz0y/id/8iflc5/73La5T33qU8t0Oi2vec1rOuvZ2to68nvCR+Pyyy8vn/3sZzt/qaiU//dfqp761KeWr3zlK+U//af/1Jlz2223le985ztHvV1JktRviHuNtptvvrlsbW1tG7v44ovLeDwuhw8fLqWU8qM/+qNlMpmUV73qVZ2fXG2apnzzm98spZTykIc8pJx11lnlLW95S9nY2Dgy5x3veEd0X3LttdeW//2//3dn/MYbbyx//ud/Xs4444xy1llnDXYe7tjvO5xyyinlvPPOO3Lc5ElPelIZj8fl1a9+9ZGfhLnD0f5Ur3Qi8SdQdML44Ac/WL7whS90xh/2sIfFv8/63b74xS+W1772tZ3x7/u+79vx4ca5555b/vN//s/liiuuKBdccEF55jOfWS666KKysbFR/uzP/qxcc8015dnPfnYp5fZeybOe9axy1VVXlRtvvLFcdtll5X/8j/9R3vnOd5YnPelJ5Qd/8AePep/vcMUVV5RXvOIVZd++feXKK6/sxMPe8IY3lI9+9KPl+7//+8vzn//88qAHPah861vfKn/5l39Z/viP/7h861vf6t3Gf/gP/6E88YlPLJdeeml5znOeU7797W+XN7/5zeWiiy7a9lDlsssuKy94wQvK61//+vLXf/3X5bGPfWxZXV0t1157bbnmmmvKm970pvJjP/ZjR3V8P//zP1/e8573lKc85Snluc99brnkkkvKt771rfK+972vvOUtbykPfvCDy4//+I+Xq6++uvzkT/5k+ehHP1ouvfTSMp1Oyxe+8IVy9dVXlw996ENRM0eSJLG3ve1t5Q//8A874z/90z89yL3Gd/vIRz5SXvSiF5WnPOUp5YEPfGDZ2toqv/3bv33kIUUpt9+Hvfa1ry0vfelLy9///d+XJz3pSeXAgQPl+uuvL7//+79ffuInfqK8+MUvLqurq+W1r31tecELXlAe9ahHlSuuuKJcf/315e1vf3t0z/jZz362/Kt/9a/K4x//+PLwhz+8nHnmmeUrX/lKeec731m++tWvlje+8Y1HfoVoiPPwoAc9qDzykY8sl1xySTnzzDPLZz7zmfKe97ynvOhFL9pxmfPOO6+87GUvK695zWvKwx/+8PKjP/qjZX19vXz6058u97rXvcrrX//68MxLJ5g9+Ms/0kLd2Z8xLqU0b3/724/MLaU0L3zhC3vXecef06X/XXnllb3L/93f/V3z/Oc/v7nf/e7XrK2tNQcOHGguvfTS5td+7deaQ4cOHZm3ubnZvOpVr2ruf//7N6urq8197nOf5qUvfem2OXfsD/35ussuuwz/1N611157ZH8/8YlP4D5+7Wtfa174whc297nPfZrV1dXmHve4R/PoRz+6ueqqq47MueNP6u30JwN/93d/tzn//POb9fX15qKLLmre9773NZdffnlz/vnnd+ZeddVVzSWXXNLs37+/OXDgQHPxxRc3L3nJS5qvfvWrcx3nN7/5zeZFL3pRc+9737tZW1trzj777OZZz3rWtj8TuLGx0fzH//gfmwsvvLBZX19vzjjjjOaSSy5pXvWqVzU33XQTHpMkSbpzffde//AP/9A0Td29xvXXX7/tPu5LX/pS89znPrc599xzm3379jVnnnlm84M/+IPNH//xH3f2773vfW/zAz/wA83JJ5/cnHzyyc35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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.EPL(\n", + " cosmology = cosmology,\n", + " name = \"epl\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " q = torch.tensor(0.6),\n", + " phi = torch.tensor(np.pi/2),\n", + " b = torch.tensor(1.),\n", + " t = torch.tensor(0.5),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"EPL Convergence\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "1aa4a0b4", + "metadata": {}, + "source": [ + "## Navarro Frenk White profile (NFW)\n", + "\n", + "The NFW profile is a classic mass profile that approximates the mass distribution of halos in large dark matter simulations.\n", + "\n", + "$$\\rho(r) = \\frac{\\rho_0}{\\frac{r}{r_s}\\left(1 + \\frac{r}{r_s}\\right)^2}$$" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bb209aba", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAIXCAYAAAC7CqrSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACX70lEQVR4nO3deZRtZ0Hn/eecU8PNDQkkIRAmQwhBJDJoFNtASAAhQAsyNGOjjAJLEF22uAxDMyUtIio00o3RZmimJiA2DS0CMghEoEEQQRo6jPKmGYMhIcm9t+qc/f6R996X2vtb2b+7n32r6ibfz1r+4ZNnD2fcz91UfWvSNE1TJEmSJEmStKnpdp+AJEmSJEnSTucNFEmSJEmSpB7eQJEkSZIkSerhDRRJkiRJkqQe3kCRJEmSJEnq4Q0USZIkSZKkHt5AkSRJkiRJ6uENFEmSJEmSpB7eQJEkSZIkSerhDRRJkiRJCpx11lnlrLPO2u7TOGjPe97zymQy2e7TkA573kDRtc5rXvOaMplMyq5du8rFF1/c+e9nnXVW+cmf/MkNY7e85S3LZDLB/9uzZ0+54IILymQyKX/5l3/Z2d8d73jHMplMygc+8IHOf/uxH/uxcvrpp0fn/cEPfrA8+MEPLieccEJZWVkpN7rRjcr973//8ra3vS185JIkSdtv/1rsk5/85HafyrbZt29fednLXlZ+6qd+qhx99NHlBje4QTn11FPLk570pPKFL3xhu09P0kBL230C0qGyd+/e8qIXvai8/OUvj+bf6U53Kv/u3/27zvjKykq5613vWkop5SMf+Uh50IMedOC/XXbZZeVzn/tcWVpaKhdeeGG5+93vfuC/feMb3yjf+MY3yiMe8YjeYz/3uc8tL3jBC8opp5xSnvzkJ5cTTzyxXHLJJeWv/uqvykMe8pDyhje8oTzqUY+KHockSZK210Me8pDyrne9qzzykY8sv/qrv1rW1tbKF77whfLOd76znH766eW2t73tlp7Ps5/97PK7v/u7W3pM6drIGyi61rrTne5U/uzP/qycc8455aY3vWnv/Jvd7Gbl0Y9+NP63m970puWkk04qH/nIRzaMf/SjHy1N05SHPvShnf+2///ff/NlM29961vLC17wgvJv/s2/KW984xvL8vLygf/2jGc8o7z73e8ua2trvee/k11xxRXlyCOP3O7TkCRJOuQ+8YlPlHe+853lvPPOK8985jM3/Lc/+ZM/KZdeeukox9mzZ09ZWVkp02n/LxUsLS2VpSX/6SfV8ld4dK31zGc+s8zn8/KiF71olP3d9a53LZ/+9KfLVVdddWDswgsvLKeeemq5733vWz72sY+VxWKx4b9NJpNyl7vc5Rr3+5znPKcce+yx5VWvetWGmyf7nX322eUXf/EXD/z/3/nOd8oTnvCEcuMb37js2rWr3PGOdyyvfe1rN2zzta99rUwmk/KSl7yknH/++eXkk08uq6ur5Wd/9mfLJz7xiQPzXvKSl5TJZFK+/vWvd457zjnnlJWVlfIv//IvB8Y+/vGPl/vc5z7l+te/ftm9e3c588wzy4UXXrhhu/2/Y/v5z3++POpRjyrHHHPMgZtIi8WiPO95zys3velNy+7du8vd73738vnPf77c8pa3LI997GM37OfSSy8tv/mbv1lucYtblNXV1XLrW9+6/P7v//6G5zh9nPt94QtfKA972MPK8ccfX4444ojy4z/+4+VZz3rWhjkXX3xxefzjH19ufOMbl9XV1XLqqaeWV73qVZ19SZKkOsk194Mf/GCZTCblggsuKOedd165+c1vXnbt2lXuec97li996Usb5l500UXlIQ95SDnhhBPKrl27ys1vfvPyiEc8ovzgBz/YMO/1r399Oe2008oRRxxRjj322PKIRzyifOMb3+ic3/61xRFHHFHufOc7lw9/+MPR4/ryl79cSim4BpzNZuW4444b/Dz8t//238qzn/3scrOb3azs3r27XHbZZWVtba08//nPL6ecckrZtWtXOe6448pd73rX8t73vvfA9ps1UF7/+teXO9/5zmX37t3lmGOOKXe7293Ke97znuhxStdF3obUtdZJJ51UfuVXfqX82Z/9Wfnd3/3d3p9CWVtbK9/73vc2jO3evbvs3r27lHL1DZTXve515eMf//iBeNiFF15YTj/99HL66aeXH/zgB+Vzn/tcucMd7nDgv932trftXCR/1EUXXVS+8IUvlMc//vHlqKOO6n1MV111VTnrrLPKl770pfK0pz2tnHTSSeUtb3lLeexjH1suvfTS8hu/8Rsb5r/xjW8sl19+eXnyk59cJpNJefGLX1we/OAHl6985StleXm5POxhDyu/8zu/Uy644ILyjGc8Y8O2F1xwQbn3ve9djjnmmFJKKe9///vLfe9733LaaaeV5z73uWU6nZZXv/rV5R73uEf58Ic/XO585ztv2P6hD31oOeWUU8p/+A//oTRNU0q5+qbMi1/84nL/+9+/nH322eUzn/lMOfvss8uePXs2bHvllVeWM888s1x88cXlyU9+cvmxH/ux8nd/93flnHPOKd/85jfLS1/60oN6nKWU8o//+I/ljDPOKMvLy+VJT3pSueUtb1m+/OUvl3e84x3lvPPOK6WU8u1vf7v8q3/1r8pkMilPe9rTyvHHH1/e9a53lSc84QnlsssuK7/5m7/Z+xpJkqR+B3vNfdGLXlSm02n57d/+7fKDH/ygvPjFLy7/9t/+2/Lxj3+8lHJ1c+Tss88ue/fuLb/+679eTjjhhHLxxReXd77zneXSSy8t17/+9UsppZx33nnlOc95TnnYwx5WnvjEJ5bvfve75eUvf3m5293uVj796U+XG9zgBqWUUv7Lf/kv5clPfnI5/fTTy2/+5m+Wr3zlK+UBD3hAOfbYY8stbnGLa3xsJ554YimllDe84Q3lLne5yzX+5MfBPg8vfOELy8rKSvnt3/7tsnfv3rKyslKe97znld/7vd8rT3ziE8ud73znctlll5VPfvKT5VOf+lS5173utemxn//855fnPe955fTTTy8veMELysrKSvn4xz9e3v/+95d73/ve1/gYpeusRrqWefWrX92UUppPfOITzZe//OVmaWmpefrTn37gv5955pnNqaeeumGbE088sSmldP7vuc997oE5//RP/9SUUpoXvvCFTdM0zdraWnPkkUc2r33ta5umaZob3/jGzSte8YqmaZrmsssua2azWfOrv/qr13iub3/725tSSvPHf/zH0WN76Utf2pRSmte//vUHxvbt29f8/M//fHO9612vueyyy5qmaZqvfvWrTSmlOe6445rvf//7neO94x3vODD28z//881pp5224Tj/63/9r6aU0vzX//pfm6ZpmsVi0ZxyyinN2Wef3SwWiwPzrrzyyuakk05q7nWvex0Ye+5zn9uUUppHPvKRG/b5rW99q1laWmoe+MAHbhh/3vOe15RSmsc85jEHxl74whc2Rx55ZPN//s//2TD3d3/3d5vZbNb88z//80E/zrvd7W7NUUcd1Xz961/fsM8ffTxPeMITmpvc5CbN9773vQ1zHvGIRzTXv/71myuvvLKRJEnX7EfXYptJr7kf+MAHmlJK8xM/8RPN3r17D8x72cte1pRSms9+9rNN0zTNpz/96aaU0rzlLW/Z9Jhf+9rXmtls1px33nkbxj/72c82S0tLB8b37dvX3OhGN2rudKc7bTjm+eef35RSmjPPPPMaH/9isWjOPPPMppTS3PjGN24e+chHNq94xSs6a5Ahz8OtbnWrznrkjne8Y/Ov//W/vsZz2r8+2++iiy5qptNp86AHPaiZz+ed85fE/BUeXavd6la3Kr/8y79czj///PLNb37zGuf+3M/9XHnve9+74f9+5Vd+5cB//4mf+Ily3HHHHWibfOYznylXXHHFgb+yc/rppx/4dZaPfvSjZT6f9/ZPLrvsslJKiX76pJRS/uqv/qqccMIJ5ZGPfOSBseXl5fL0pz+9/PCHPyx/+7d/u2H+wx/+8AM/QVJKKWeccUYppZSvfOUrG+b8/d///YEfNy2llDe/+c1ldXW1/NIv/VIppZR/+Id/KBdddFF51KMeVS655JLyve99r3zve98rV1xxRbnnPe9ZPvShD2341ZpSSnnKU56y4f9/3/veV9bX18uv/dqvbRj/9V//9c7jfMtb3lLOOOOMcswxxxw41ve+973yC7/wC2U+n5cPfehDB/U4v/vd75YPfehD5fGPf3z5sR/7sQ3b7v9x1qZpyl/8xV+U+9///qVpmg3HPfvss8sPfvCD8qlPfapzrpIk6eAMueY+7nGPKysrKwf+//a1fv9PmLz73e8uV155JR73bW97W1ksFuVhD3vYhmOecMIJ5ZRTTjnwFxU/+clPlu985zvlKU95yoZjPvaxjz1wnGsymUzKu9/97nLuueeWY445przpTW8qT33qU8uJJ55YHv7whx9ooAx5Hh7zmMeUI444YsPYDW5wg/JP//RP5aKLLuo9t/3++3//72WxWJR//+//faeh4p87ljbnr/DoWu/Zz352ed3rXlde9KIXlZe97GWbzrvhDW9YfuEXfmHT/z6ZTMrpp59+4GbBhRdeWG50oxuVW9/61qWUq2+g/Mmf/EkppRy4kdJ3A+Xoo48upZRy+eWXR4/l61//ejnllFM6F7qf+ImfOPDff1T7ZsH+mww/2jV56EMfWn7rt36rvPnNby7PfOYzS9M05S1veUu5733ve+D89l+QH/OYx2x6bj/4wQ823MQ46aSTOudeSjnwfO137LHHbthu//H+8R//sRx//PF4rO985zsH9Tj3L67af776R333u98tl156aTn//PPL+eefHx1XkiQdvCHX3L5r/UknnVR+67d+q/zRH/1RecMb3lDOOOOM8oAHPKA8+tGPPnDT46KLLipN05RTTjkFj7n/1373r1na85aXl8utbnWr6DGurq6WZz3rWeVZz3pW+eY3v1n+9m//trzsZS8rF1xwQVleXi6vf/3rBz0P7fVVKaW84AUvKL/0S79UbnOb25Sf/MmfLPe5z33KL//yLx/4tXLy5S9/uUyn03K7290uejySruYNFF3r3epWtyqPfvSjy/nnn1/959vuete7lne84x3ls5/97IH+yX6nn356ecYznlEuvvji8pGPfKTc9KY37b3I7v8Tdp/97Gerzmszs9kMx5v/r0lSytV/YeiMM84oF1xwQXnmM59ZPvaxj5V//ud/Lr//+79/YM7+ny75gz/4g3KnO90J93m9611vw//f/l9HDsZisSj3ute9yu/8zu/gf7/NbW6z4f9PHmdyzFJKefSjH73pjaJrWohIkqTMkGtucq3/wz/8w/LYxz62vP3tby/vec97ytOf/vTye7/3e+VjH/tYufnNb14Wi0WZTCblXe96F+6vvZYZy01ucpPyiEc8ojzkIQ8pp556arngggvKa17zmkHPA62v7na3u5Uvf/nLBx73n//5n5c//uM/Lq985SvLE5/4xPEfkHQd5g0UXSc8+9nPLq9//es33BQYYv9PlHzkIx8pF1544Yaw12mnnVZWV1fLBz/4wfLxj3+83O9+9+vd321uc5vy4z/+4+Xtb397ednLXtZ74T7xxBPLP/7jP5bFYrHhp1C+8IUvHPjvQzz84Q8vv/Zrv1a++MUvlje/+c1l9+7d5f73v/+B/37yySeXUq7+iZlr+imdvnMvpZQvfelLG/7Xk0suuWTDT8TsP94Pf/jDwcdq238j63Of+9ymc44//vhy1FFHlfl8PtpxJUlS16G85t7+9rcvt7/97cuzn/3s8nd/93flLne5S3nlK19Zzj333HLyySeXpmnKSSed1PkfY37U/jXLRRddVO5xj3scGF9bWytf/epXyx3veMdB57a8vFzucIc7lIsuuqh873vfG/V5OPbYY8vjHve48rjHPa788Ic/LHe7293K8573vE1voJx88sllsViUz3/+85v+j2OSumyg6Drh5JNPLo9+9KPLn/7pn5Zvfetbg/fzMz/zM2XXrl3lDW94Q7n44os3/ATK6upq+emf/unyile8olxxxRW9v76z3/Of//xyySWXlCc+8YllfX2989/f8573lHe+852llFLud7/7lW9961vlzW9+84H/vr6+Xl7+8peX613veuXMM88c9Lge8pCHlNlsVt70pjeVt7zlLeUXf/EXy5FHHnngv5922mnl5JNPLi95yUvKD3/4w8723/3ud3uPcc973rMsLS2V//yf//OG8f2/9vSjHvawh5WPfvSj5d3vfnfnv1166aX4PF2T448/vtztbncrr3rVq8o///M/b/hv+/+Xq9lsVh7ykIeUv/iLv8AbLcljlCRJ/Q7FNfeyyy7rrA9uf/vbl+l0Wvbu3VtKKeXBD35wmc1m5fnPf37np1SbpimXXHJJKeXq9d7xxx9fXvnKV5Z9+/YdmPOa17zmQL/kmlx00UWd9UYpV69hPvrRj5ZjjjmmHH/88aM9D/vPe7/rXe965da3vvWBx00e+MAHlul0Wl7wghd0OnYH8xO80nWNP4Gi64xnPetZ5XWve1354he/WE499dRB+1hZWSk/+7M/Wz784Q+X1dXVctppp23476effnr5wz/8w1JKf/9kv4c//OHls5/9bDnvvPPKpz/96fLIRz6ynHjiieWSSy4pf/3Xf13e9773lTe+8Y2llFKe9KQnlT/90z8tj33sY8vf//3fl1ve8pblrW99a7nwwgvLS1/60jhG23ajG92o3P3udy9/9Ed/VC6//PLy8Ic/fMN/n06n5c///M/Lfe9733LqqaeWxz3uceVmN7tZufjii8sHPvCBcvTRR5d3vOMd13iMG9/4xuU3fuM3yh/+4R+WBzzgAeU+97lP+cxnPlPe9a53lRve8IYbgmXPeMYzyv/4H/+j/OIv/mJ57GMfW0477bRyxRVXlM9+9rPlrW99a/na175WbnjDGx7UY/yP//E/lrve9a7lp3/6p8uTnvSkctJJJ5Wvfe1r5X/+z/9Z/uEf/qGUcvWfSPzABz5Qfu7nfq786q/+arnd7W5Xvv/975dPfepT5W/+5m/K97///YM6piRJ12WvetWryl//9V93xn/jN35j9Gvu+9///vK0pz2tPPShDy23uc1tyvr6ennd61534CZFKVf/D2rnnntuOeecc8rXvva18sAHPrAcddRR5atf/Wr5y7/8y/KkJz2p/PZv/3ZZXl4u5557bnnyk59c7nGPe5SHP/zh5atf/Wp59atfHTVQPvOZz5RHPepR5b73vW8544wzyrHHHlsuvvji8trXvrb83//7f8tLX/rSA79CNMbzcLvb3a6cddZZ5bTTTivHHnts+eQnP1ne+ta3lqc97WmbbnPrW9+6POtZzyovfOELyxlnnFEe/OAHl9XV1fKJT3yi3PSmNy2/93u/Fz7z0nXM1v/hH+nQuqY/nfeYxzymKaXgnzHu+/Nv+51zzjlNKaU5/fTTO//tbW97W1NKaY466qhmfX39oM77fe97X/NLv/RLzY1udKNmaWmpOf7445v73//+zdvf/vYN87797W83j3vc45ob3vCGzcrKSnP729++efWrX71hzv4/7/sHf/AHneOU1p9n3u/P/uzPDpz7VVddhef46U9/unnwgx/cHHfccc3q6mpz4oknNg972MOa973vfQfm7P8zed/97nc726+vrzfPec5zmhNOOKE54ogjmnvc4x7N//7f/7s57rjjmqc85Skb5l5++eXNOeec09z61rduVlZWmhve8IbN6aef3rzkJS9p9u3bN+hxfu5zn2se9KAHNTe4wQ2aXbt2NT/+4z/ePOc5z9kw59vf/nbz1Kc+tbnFLW7RLC8vNyeccEJzz3veszn//PPxOZEkSRvtX4tt9n/f+MY3mqbJrrn7/3xv+88T718D7F8DfeUrX2ke//jHNyeffHKza9eu5thjj23ufve7N3/zN3/TOb+/+Iu/aO5617s2Rx55ZHPkkUc2t73tbZunPvWpzRe/+MUN8/7Tf/pPzUknndSsrq42P/MzP9N86EMfas4888zeP2P87W9/u3nRi17UnHnmmc1NbnKTZmlpqTnmmGOae9zjHs1b3/pWnD/0eWiapjn33HObO9/5zs0NbnCD5ogjjmhue9vbNuedd96B9VLTdP+M8X6vetWrmp/6qZ9qVldXm2OOOaY588wzm/e+973X+Pik67JJ0/gzWpK2z6WXXlqOOeaYcu6555ZnPetZ2306kiRJkoRsoEjaMldddVVn7KUvfWkppZSzzjpra09GkiRJkg6CDRRJW+bNb35zec1rXlPud7/7letd73rlIx/5SHnTm95U7n3ve5e73OUu2316kiRJkrQpb6BI2jJ3uMMdytLS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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.NFW(\n", + " cosmology = cosmology,\n", + " name = \"nfw\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " m = torch.tensor(1e13),\n", + " c = torch.tensor(20.),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"NFW Convergence\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d96d3992", + "metadata": {}, + "source": [ + "## Truncated NFW profile (TNFW)\n", + "\n", + "The TNFW profile is a slight modification to the classic NFW mass profile that approximates the mass distribution of halos in large dark matter simulations. The new density profile is defined as:\n", + "\n", + "$$\\rho_{\\rm tnfw}(r) = \\rho_{\\rm nfw}(r)\\frac{\\tau^2}{\\tau^2 + \\frac{r^2}{r_s^2}}$$\n", + "\n", + "where $\\tau = \\frac{r_t}{r_s}$ is the ratio of the truncation radius to the scale radius. Note that the truncation happens smoothly so there are no sharp boundaries. In the TNFW profile, the mass quantity now actually corresponds the to the total mass since it is no longer divergent. This often means the mass values are larger." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "dd5805a5", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAIXCAYAAAC7CqrSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACfEklEQVR4nO3deZRtZ0Gn//ecU3Vv5WYgiQSIgjQEMDIorjBoGBJmxAkJQrRpAQUCDSgirSI0JBAaxAFokakVcECUobVZtghBUTEiQhMQpIXI4BBmMXPuUOfs3x/53dup/T6V/b3n3beqEp7PWr1W5/Xdwxn3ezdVT026ruuKJEmSJEmSNjXd7hOQJEmSJEna6byBIkmSJEmSNMAbKJIkSZIkSQO8gSJJkiRJkjTAGyiSJEmSJEkDvIEiSZIkSZI0wBsokiRJkiRJA7yBIkmSJEmSNMAbKJIkSZIkSQO8gSIdIf/hP/yH8rjHPW67T0OSJEkjOfPMM8uZZ5653adx2M4999wymUy2+zSkGzxvoOh6TSaT6P/9+Z//+Xaf6lL++I//uJx77rnbeg4Hn8Nf/uVfrv5vb3zjG8tkMikf+tCHDo0dvADS/3vNa15T5vN5Oe6448oP/MAPVPt72cteViaTSXnsYx9b/d+e97znlclkUj71qU8NnvOXvvSl8qxnPauceuqpZc+ePeXoo48up512Wjn//PPLpZdeenhPgCRJulGh9cvXm/3795dXvOIV5Tu+4zvKcccdV44//vhypzvdqTzpSU8q//AP/7DdpydpSSvbfQLa2X77t397w3//1m/9Vrnggguq8W/91m/dytMazR//8R+XX/u1X9v2myillPKLv/iL5SlPeUrZs2dPNP/Vr351OeaYYzaM3fOe9yyz2ax853d+Z/nrv/7rapsLL7ywrKyslAsvvBD/bze72c3KHe5wh+s97gc/+MHysIc9rFx55ZXlMY95TDnttNNKKaV86EMfKi95yUvKX/7lX5Z3v/vd0WOQJEm6MTrrrLPKO9/5zvLDP/zD5YlPfGI5cOBA+Yd/+IfyR3/0R+X0008vp5566paez3Of+9zycz/3c1t6TOnGyBsoul6PecxjNvz33/zN35QLLrigGu+7+uqr4xsBKuWud71r+chHPlJe85rXlGc+85nRNo985CPLTW96U/y/3fve9y4XXHBB+b//9/9uuLl14YUXlkc96lHld3/3d8sXv/jFcotb3KKUUsr6+nr5wAc+UB784Adf7zEvvfTS8oM/+INlNpuViy66qLr4v+hFLyr/43/8j+j8d6r19fWyWCzKrl27tvtUJEnSDdAHP/jB8kd/9EflRS96Ufn5n//5Df+3V77ylaP9tO7evXvLrl27ynQ6/EsFKysrZWXFf/pJrfwVHjU788wzy53vfOfyf/7P/yn3ve99y549ew5dLCaTCf50R78PcvBHPS+88MLyzGc+s5x00knl6KOPLj/4gz9YvvKVr1Tbv/Od7yxnnHFGOfbYY8txxx1X7n73u5ff/d3fPfR/f9/73ld+6Id+qHzzN39z2b17d7nVrW5Vfuqnfqpcc801h+Y87nGPK7/2a7926DwP/r+DFotFefnLX17udKc7lbW1tXLzm9+8nHPOOeXf//3fN5xL13Xl/PPPL7e85S3Lnj17yv3ud7/y93//94f1HN7rXvcq97///ctLX/rSDee4rHvf+96llLLhJ00+85nPlC9+8YvlaU97WllbW9vwf/vIRz5SrrrqqkPbbea1r31tueSSS8qv/Mqv4P9ycvOb37w897nP3TD2qle9qtzpTncqu3fvLt/4jd9YnvrUp1YLh4PvoU984hPlfve7X9mzZ0/5pm/6pvLSl7700JwvfelLZWVlpZx33nnVcT/5yU+WyWRSXvnKVx4au/TSS8sznvGMcqtb3ars3r273O52tyu/8Au/UBaLxaE5n/vc58pkMim/9Eu/VF7+8peXU045pezevbt84hOfKKWU8ud//uflbne7W1lbWyunnHJKee1rX7vp7xD/zu/8TjnttNPKUUcdVU488cRy9tlnl3/5l3857Md50N69e8u5555b7nCHO5S1tbVy8sknl0c84hHl05/+9KE56XtUkqSd6JJLLik/9mM/Vm5+85uX3bt3lzvd6U7l9a9//YY5f/7nf14mk0l5y1veUl70oheVW97ylmVtba084AEPKP/4j/+4Ye7FF19czjrrrHKLW9yirK2tlVve8pbl7LPPLpdddtmGeck1u5RSXve615VTTjmlHHXUUeUe97hHed/73hc9roPX6nvd617V/202m5Vv+IZvWPp5+L3f+73y3Oc+t3zTN31T2bNnT7n88svLgQMHynnnnVduf/vbl7W1tfIN3/ANh/7HtIOub/1yj3vco+zZs6eccMIJ5b73va8/SSxdD29DahT/9m//Vr77u7+7nH322eUxj3lMufnNb77Ufp7+9KeXE044oTz/+c8vn/vc58rLX/7y8rSnPa38/u///qE5b3zjG8uP/diPlTvd6U7l2c9+djn++OPLRRddVP7kT/6k/MiP/EgppZS3vvWt5eqrry5PecpTyjd8wzeUv/3bvy2/+qu/Wv71X/+1vPWtby2llHLOOeeUz3/+8/grSQf/72984xvL4x//+PITP/ET5bOf/Wx55StfWS666KJy4YUXltXV1VLKte2Q888/vzzsYQ8rD3vYw8qHP/zh8uAHP7js37//sB77ueeeW+573/uWV7/61dFPoXzta1/b8N+z2ayccMIJpZRSvvM7v7OsrKyUv/qrvypPeMITSinX3kw5+uijy93vfvdyt7vdrVx44YXlrLPOOvR/K6UM3kB5xzveUY466qjyyEc+Mn5M5513XnngAx9YnvKUp5RPfvKT5dWvfnX54Ac/uOE5LKWUf//3fy8PfehDyyMe8YjyqEc9qrztbW8rP/uzP1vucpe7lO/+7u8uN7/5zcsZZ5xR3vKWt5TnP//5G47z+7//+2U2m5Uf+qEfKqVc+xNQZ5xxRrnkkkvKOeecU775m7+5/PVf/3V59rOfXb7whS+Ul7/85Ru2f8Mb3lD27t1bnvSkJ5Xdu3eXE088sVx00UXloQ99aDn55JPLeeedV+bzeXnBC15QTjrppOpxvuhFLyr/9b/+1/KoRz2qPOEJTyhf+cpXyq/+6q+W+973vuWiiy4qxx9/fPw4SyllPp+X7/3e7y1/+qd/Ws4+++zykz/5k+WKK64oF1xwQfn4xz9eTjnllFJK/h6VJGmn+dKXvlS+8zu/s0wmk/K0pz2tnHTSSeWd73xn+fEf//Fy+eWXl2c84xkb5r/kJS8p0+m0POtZzyqXXXZZeelLX1r+43/8j+UDH/hAKeXa5shDHvKQsm/fvvL0pz+93OIWtyiXXHJJ+aM/+qNy6aWXlpvc5CallPya/Ru/8RvlnHPOKaeffnp5xjOeUT7zmc+U7//+7y8nnnhiudWtbnW9j+3Wt751KaWUN73pTeVe97rX9f7kx+E+Dy984QvLrl27yrOe9ayyb9++smvXrnLuueeWF7/4xeUJT3hCucc97lEuv/zy8qEPfah8+MMfLg960IM2PfZ5551Xzj333HL66aeXF7zgBWXXrl3lAx/4QPmzP/uzwZ9Klr5uddJheOpTn9r13zZnnHFGV0rpXvOa11TzSynd85///Gr81re+dffYxz720H+/4Q1v6Eop3QMf+MBusVgcGv+pn/qpbjabdZdeemnXdV136aWXdscee2x3z3ves7vmmms27PO621199dXVMV/84hd3k8mk+6d/+qfrfTxd13Xve9/7ulJK96Y3vWnD+J/8yZ9sGP/yl7/c7dq1q/ue7/meDcf/+Z//+a6UsuExbqaU0j31qU/tuq7r7ne/+3W3uMUtDp3/weflgx/84KH5z3/+87tSSvX/bn3rW2/Y793vfvfulFNOOfTf55xzTne/+92v67qu+5mf+Znu7ne/+6H/2yMf+chuz5493YEDB673XE844YTu27/92wcfU9f9v+fmwQ9+cDefzw+Nv/KVr+xKKd3rX//6Q2MH30O/9Vu/dWhs37593S1ucYvurLPOOjT22te+tiuldB/72Mc2HOuOd7xjd//73//Qf7/whS/sjj766O5Tn/rUhnk/93M/181ms+6f//mfu67rus9+9rNdKaU77rjjui9/+csb5n7f931ft2fPnu6SSy45NHbxxRd3KysrG94zn/vc57rZbNa96EUv2rD9xz72sW5lZWXDePo4X//613ellO5XfuVXur6D77P0PSpJ0laj9Uvfj//4j3cnn3xy99WvfnXD+Nlnn93d5CY3ObQWeu9739uVUrpv/dZv7fbt23do3ite8YoNa4KLLrqoK6V0b33rWzc9ZnrN3r9/f3ezm92su+td77rhmK973eu6Ukp3xhlnXO/jXywWh675N7/5zbsf/uEf7n7t135twxp02efhtre9bbXO/fZv//bue77ne673nA6uHw+6+OKLu+l02v3gD/7ghnXawfOXxPwVHo1i9+7d5fGPf3zzfp70pCdt+PHC+9znPmU+n5d/+qd/KqWUcsEFF5Qrrrii/NzP/VxZW1vbsO11tzvqqKMO/f+vuuqq8tWvfrWcfvrppeu6ctFFFw2ex1vf+tZyk5vcpDzoQQ8qX/3qVw/9v9NOO60cc8wx5b3vfW8ppZT3vOc9Zf/+/eXpT3/6huP3/9eC1Lnnnlu++MUvlte85jWDc9/+9reXCy644ND/e9Ob3rTh/37ve9+7fPrTny5f/OIXSynX/pTJ6aefXkq59kdKL7roonL11Vcf+r/d8573HPzd2Msvv7wce+yx0WM5+Nw84xnP2PC7uU984hPLcccdV/73//7fG+Yfc8wxG9o6u3btKve4xz3KZz7zmUNjj3jEI8rKysqGn0j6+Mc/Xj7xiU+URz/60YfG3vrWt5b73Oc+5YQTTtjw+j3wgQ8s8/m8/OVf/uWGY5911lkbfrJkPp+X97znPeXhD394+cZv/MZD47e73e0O/ZTIQf/zf/7PslgsyqMe9agNx7rFLW5Rbn/72x96rxzO43z7299ebnrTm5anP/3p1fN68H2WvkclSdppuq4rb3/728v3fd/3la7rNlzHHvKQh5TLLrusfPjDH96wzeMf//gNfbL73Oc+pZRy6Pp58CdM3vWudx1a3/Sl1+wPfehD5ctf/nJ58pOfvOGYj3vc4w4d5/pMJpPyrne9q5x//vnlhBNOKG9+85vLU5/61HLrW9+6PPrRjz70q8zLPA+PfexjN6xzSynl+OOPL3//939fLr744sFzO+gP//APy2KxKM973vOqhop/7ljanL/Co1F80zd90yjRzW/+5m/e8N8HfyXlYNPh4O+U3vnOd77e/fzzP/9zed7znlfe8Y53VD2I/u/Bkosvvrhcdtll5WY3uxn+37/85S+XUsqhGzu3v/3tN/zfTzrppEPnfjjue9/7lvvd737lpS99aXnyk588OHeziGwp195AednLXlYuvPDC8oAHPKD8/d///aHWxumnn17W19fL3/7t35Zb3/rW5Qtf+MKhX/W5Pscdd1y54oorosdy8Ln5lm/5lg3ju3btKre97W0P/d8PuuUtb1ldsE844YTyd3/3d4f++6Y3vWl5wAMeUN7ylreUF77whaWUa399Z2VlpTziEY84NO/iiy8uf/d3f4e/blPK/3v9DrrNbW5T/d+vueaacrvb3a7atj928cUXl67rqvfAQf1fo0ke56c//enyLd/yLdd7Qyt9j0qStNN85StfKZdeeml53eteV173utfhnP51bGiNeJvb3KY885nPLL/yK79S3vSmN5X73Oc+5fu///vLYx7zmEM3PdJr9mbru9XV1XLb2942eoy7d+8uz3nOc8pznvOc8oUvfKH8xV/8RXnFK15R3vKWt5TV1dXyO7/zO0s9D/01SymlvOAFLyg/8AM/UO5whzuUO9/5zuWhD31o+U//6T+Vb/u2b9v0/D796U+X6XRa7njHO0aPR9K1vIGiUfTvhA+Zz+c4PpvNcLzrusPa94Me9KDyta99rfzsz/5sOfXUU8vRRx9dLrnkkvK4xz1uQ0R0M4vFotzsZjerfqrjoM3+YT6G5z//+eXMM88sr33taze0Mw7XwZ7JX/3VXx36i0jf9V3fVUq59kbE7W9/+/JXf/VXh6JpQ/2TUko59dRTy0c+8pGyf//+0f9KTfran3322eXxj398+chHPlLuete7lre85S3lAQ94wIabSYvFojzoQQ8qP/MzP4P77P+p5sN9/17XYrEok8mkvPOd78TH0P9T02O8xw8ed7veo5IktTi4FnvMYx5THvvYx+Kc/j/+k+vnL//yL5fHPe5x5X/9r/9V3v3ud5ef+ImfKC9+8YvL3/zN35Rb3vKWh33NHsvJJ59czj777HLWWWeVO93pTuUtb3lLeeMb37jU80Brlvve977l05/+9KHH/eu//uvlZS97WXnNa14T/Q9kknLeQNERdcIJJ1R/cWX//v3lC1/4wlL7OxjP/PjHP44/HVBKKR/72MfKpz71qfKbv/mb5Ud/9EcPjV+3RH7QZj+ieMopp5T3vOc95V73utf1/uP6YCTs4osv3vC/SHzlK19Z+i+hnHHGGeXMM88sv/ALv1Ce97znLbWPUkq52c1udugmydFHH13ueMc7brghc/rpp5cLL7yw/Ou//muZzWaHbq5cn+/7vu8r73//+8vb3/728sM//MPXO/fgc/PJT35yw3Ozf//+8tnPfrY88IEPXOpxPfzhDy/nnHPOoV/j+dSnPlWe/exnb5hzyimnlCuvvHLpY9zsZjcra2trVd2/lFKNnXLKKaXrunKb29ymujGzrFNOOaV84AMfKAcOHNg0BJu+RyVJ2mlOOumkcuyxx5b5fL70tXozd7nLXcpd7nKX8tznPrf89V//dbnXve5VXvOa15Tzzz8/vmZfd313//vf/9D4gQMHymc/+9ny7d/+7Uud2+rqavm2b/u2cvHFF5evfvWroz4PJ554Ynn84x9fHv/4x5crr7yy3Pe+9y3nnnvupjdQTjnllLJYLMonPvGJcte73rXp2NLXExsoOqJOOeWUqjfxute9btOfQBny4Ac/uBx77LHlxS9+cdm7d++G/9vB/wXi4P+icN3/RaLruvKKV7yi2t/RRx9dSinVTZ5HPepRZT6fH/o1ketaX18/NP+BD3xgWV1dLb/6q7+64Xj9v/JyuA62UDb7cc7Uve997/KRj3ykvPvd7z7UPzno9NNPL+9///vL+973vvJt3/ZtUdvkyU9+cjn55JPLT//0T5dPfepT1f/9y1/+cjn//PNLKdc+N7t27Sr//b//9w3PzW/8xm+Uyy67rHzP93zPUo/p+OOPLw95yEPKW97ylvJ7v/d7ZdeuXeXhD3/4hjmPetSjyvvf//7yrne9q9r+0ksvLevr69d7jNlsVh74wAeWP/zDPyyf//znD43/4z/+Y3nnO9+5Ye4jHvGIMpvNynnnnVf9FEnXdeXf/u3fDvMRXttk+epXv7rhzzJfd5+l5O9RSZJ2mtlsVs4666zy9re/vXz84x+v/u9f+cpXDnufl19+eXV9v8td7lKm02nZt29fKSW/Zt/tbncrJ510UnnNa16z4a8qvvGNb4yurxdffHH553/+52r80ksvLe9///vLCSecUE466aTRnof+WuOYY44pt7vd7Q49bvLwhz+8TKfT8oIXvKD66ezD/alY6euJP4GiI+oJT3hCefKTn1zOOuus8qAHPah89KMfLe9617uut91xfY477rjyspe9rDzhCU8od7/73cuP/MiPlBNOOKF89KMfLVdffXX5zd/8zXLqqaeWU045pTzrWc8ql1xySTnuuOPK29/+dvyJkNNOO62UUspP/MRPlIc85CFlNpuVs88+u5xxxhnlnHPOKS9+8YvLRz7ykfLgBz+4rK6ulosvvri89a1vLa94xSvKIx/5yHLSSSeVZz3rWeXFL35x+d7v/d7ysIc9rFx00UXlne9859KPsZRrfwrljDPOKH/xF3+x9D5KufYGyhve8IbywQ9+sDz1qU/d8H87/fTTy2WXXVYuu+wyjJWSE044ofzBH/xBedjDHlbuete7lsc85jGHnsMPf/jD5c1vfvOhn2Q56aSTyrOf/exy3nnnlYc+9KHl+7//+8snP/nJ8qpXvarc/e533xBSPVyPfvSjy2Me85jyqle9qjzkIQ+pftXpv/yX/1Le8Y53lO/93u8tj3vc48ppp51WrrrqqvKxj32svO1tbyuf+9znBl+fc889t7z73e8u97rXvcpTnvKUMp/Pyytf+cpy5zvfuXzkIx85NO+UU04p559/fnn2s59dPve5z5WHP/zh5dhjjy2f/exnyx/8wR+UJz3pSeVZz3rWYT2+H/3RHy2/9Vu/VZ75zGeWv/3bvy33uc99ylVXXVXe8573lP/8n/9z+YEf+IH4PSpJ0nZ5/etfX/7kT/6kGv/Jn/zJ8pKXvKS8973vLfe85z3LE5/4xHLHO96xfO1rXysf/vCHy3ve857yta997bCO9Wd/9mflaU97WvmhH/qhcoc73KGsr6+X3/7t3z50k6KU/Jq9urpazj///HLOOeeU+9///uXRj350+exnP1ve8IY3RA2Uj370o+VHfuRHynd/93eX+9znPuXEE08sl1xySfnN3/zN8vnPf768/OUvP/Q/+I3xPNzxjncsZ555ZjnttNPKiSeeWD70oQ+Vt73tbeVpT3vaptvc7na3K895znPKC1/4wnKf+9ynPOIRjyi7d+8uH/zgB8s3fuM3lhe/+MXhMy99ndm6P/ijG4PN/ozxne50J5w/n8+7n/3Zn+1uetObdnv27Oke8pCHdP/4j/+46Z8x7v+5u4N/su29733vhvF3vOMd3emnn94dddRR3XHHHdfd4x736N785jcf+r9/4hOf6B74wAd2xxxzTHfTm960e+ITn9h99KMf7Uop3Rve8IZD89bX17unP/3p3UknndRNJpPqsb3uda/rTjvttO6oo47qjj322O4ud7lL9zM/8zPd5z//+Q2P8bzzzutOPvnk7qijjurOPPPM7uMf/3j1GDdTrvNnjOmx95+Xg3+G7itf+crgvj/5yU8e2kf/T/ouFovu+OOP70op3e///u8P7uu6Pv/5z3c/9VM/1d3hDnfo1tbWuj179nSnnXZa96IXvai77LLLNsx95Stf2Z166qnd6upqd/Ob37x7ylOe0v37v//7hjmbvYce+9jHVn+eueu67vLLL++OOuqorpTS/c7v/A6e4xVXXNE9+9nP7m53u9t1u3bt6m5605t2p59+evdLv/RL3f79+7uu+39/xvgXf/EXcR9/+qd/2n3Hd3xHt2vXru6UU07pfv3Xf7376Z/+6W5tba2a+/a3v727973v3R199NHd0Ucf3Z166qndU5/61O6Tn/zkUo/z6quv7p7znOd0t7nNbbrV1dXuFre4RffIRz6y+/SnP71hXvIelSRpKx1c1232//7lX/6l67qu+9KXvtQ99alP7W51q1sdutY94AEP6F73utcd2tfB9VD/zxMfvIYf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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.TNFW(\n", + " cosmology = cosmology,\n", + " name = \"tnfw\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " mass = torch.tensor(1e12),\n", + " scale_radius = torch.tensor(1.),\n", + " tau = torch.tensor(3.),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"Truncated NFW Convergence\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f6f19942", + "metadata": {}, + "source": [ + "## Pseudo Jaffe (PseudoJaffe)\n", + "\n", + "The Pseudo Jaffe closely approximates an isothermal mass distribution except that it is easier to compute and has finite mass.\n", + "\n", + "$$ \\rho(r) = \\frac{\\rho_0}{\\left(1 + \\frac{r^2}{r_c^2}\\right)\\left(1 + \\frac{r^2}{r_s^2}\\right)} $$\n", + "\n", + "where $\\rho_0$ is the central density limit, $r_c$ is the core radius, $r_s$ is the scale radius." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0c8985d8", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAIXCAYAAAC7CqrSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACjpElEQVR4nO3deZhld0Hn/++9t6qr00l3FgwQAxND2CSCKBg0IYsgxCDIJhDQEMLiMoCDEudBkSEiiIJsDyLLoMADbkEgMIwsQeKCogMSM4CDEwNhUwMEE5ZOd1fde35/9HT/Uue8K+dT93u6qpK8X8/jH3z9nuWu59snVe8aNU3TFEmSJEmSJK1pvNknIEmSJEmStNV5A0WSJEmSJKmHN1AkSZIkSZJ6eANFkiRJkiSphzdQJEmSJEmSengDRZIkSZIkqYc3UCRJkiRJknp4A0WSJEmSJKmHN1AkSZIkSZJ6eANFKqVcffXVZTQalTe/+c2bfSqllFKuueaa8hM/8RPlNre5TRmNRuWVr3xlKaWUK6+8sjz4wQ8uRx55ZBmNRuWSSy7Z1POUJEm6NTnrrLPKWWedtdmnsW4XXXRRGY1Gm30a0s2eN1A0uDe/+c1lNBod/L/t27eXu971ruUZz3hGueaaazb79DbEgRsyv/3bvz3X9r/wC79QPvCBD5Rf/uVfLm9961vLj/7oj5ZSSjn//PPLJz/5yfKiF72ovPWtby33ve99Bznf6XRa3vSmN5WzzjqrHHPMMWVpaal813d9V7ngggvKxz/+8UGOIUmSbh0OrAVvzWuIffv2lVe96lXl+77v+8quXbvKUUcdVU4++eTy0z/90+Uzn/nMZp+epDktbPYJ6JbrBS94QTnxxBPLnj17ykc+8pHy2te+tvzZn/1Z+dSnPlV27Nix2ae3pX34wx8uD3/4w8uFF154cOyGG24oH/3oR8tzn/vc8oxnPGOwY91www3lUY96VHn/+99fzjjjjPIrv/Ir5ZhjjilXX311ufjii8tb3vKW8oUvfKHc4Q53GOyYkiRJt2SPfvSjy/ve977y+Mc/vjztaU8ry8vL5TOf+Ux573vfW0499dRy97vffUPP51d/9VfLc57znA09pnRL5A0UHTLnnHPOwZ+QeOpTn1puc5vblJe//OXl3e9+d3n84x+/yWe3tX3lK18pRx111Kqxr371q6WU0hmv9Uu/9Evl/e9/f3nFK15RnvWsZ636/z3/+c8vr3jFKwY93mbYvXu3N+0kSdKG+NjHPlbe+973lhe96EXlV37lV1b9/37nd36nXHfddYMcZ8+ePWXbtm1lPO7/pYKFhYWysOA//aRa/gqPNswDHvCAUkopn/vc50oppfz7v/97ueCCC8od7nCHsrS0VI477rjy8Ic/vFx99dWrtnvf+95XTj/99HL44YeXnTt3lh/7sR8rn/70p1fNWev3UZ/0pCeV7/qu71o1dt1115UnPelJ5cgjjyxHHXVUOf/889e8kH34wx8+eOyjjjqqPPzhDy//5//8n7ke/9e//vVy4YUXlnve857liCOOKLt27SrnnHNOueKKKw7OOfAjr03TlNe85jUHfw3qoosuKieccEIpZf8Nj9FotOpxffnLXy5PfvKTy+1ud7uytLRUTj755PL7v//7vef0pS99qbz+9a8vD3rQgzo3T0opZTKZlAsvvHDVT59cfvnl5Zxzzim7du0qRxxxRHngAx9Y/u7v/m7Vdgcex9/8zd+UX/zFXyzHHntsOfzww8sjH/nIgzeCSinloQ99aLnTne6E5/ZDP/RDnV9Retvb3lbuc5/7lMMOO6wcc8wx5dxzzy1f/OIXV80566yzyvd8z/eUf/iHfyhnnHFG2bFjx8HFy7XXXlvOO++8gz9Ke/7555crrrgC+zef+cxnyk/8xE+UY445pmzfvr3c9773Le95z3vmepwHvO997ytnnnlm2blzZ9m1a1f5gR/4gfKHf/iHq+b8/d//ffnRH/3RcuSRR5YdO3aUM888s/zN3/wNPkeSJN2cJeuXv/iLvyij0ahcfPHF5UUvelG5wx3uULZv314e+MAHln/5l39ZNffKK68sj370o8vtb3/7sn379nKHO9yhnHvuueX6669fNS9ZT5RSyhve8IZy0kknlcMOO6yccsop5a//+q+jx3XVVVeVUko57bTTOv+/yWRSbnOb28z9PPzxH/9x+dVf/dVy/PHHlx07dpRvfOMbZXl5ufzar/1auctd7lK2b99ebnOb25T73//+5dJLLz24/VoNlLe97W3llFNOKTt27ChHH310OeOMM8oHP/jB6HFKt0behtSGOXAxOXDRePSjH10+/elPl2c+85nlu77ru8pXvvKVcumll5YvfOELB28OvPWtby3nn39+Ofvss8tv/dZvld27d5fXvva15f73v3+5/PLLOzdH+jRNUx7+8IeXj3zkI+Vnf/Zny3d/93eXd73rXeX888/vzP3Qhz5UzjnnnHKnO92pXHTRReWGG24or371q8tpp51WPvGJT6z72J/97GfLJZdcUh7zmMeUE088sVxzzTXl9a9/fTnzzDPLP/3TP5Xv/M7vLGeccUZ561vfWs4777zyoAc9qDzxiU8spZRyr3vdqxx11FHlF37hF8rjH//48pCHPKQcccQRpZT9wdkf/MEfLKPRqDzjGc8oxx57bHnf+95XnvKUp5RvfOMbeGPkgPe9731lZWWlnHfeedFj+PSnP11OP/30smvXrvJf/+t/LYuLi+X1r399Oeuss8pf/uVflvvd736r5j/zmc8sRx99dHn+859frr766vLKV76yPOMZzyh/8id/Ukop5XGPe1x54hOfWD72sY+VH/iBHzi43ec///nyd3/3d+WlL33pwbEXvehF5XnPe1557GMfW5761KeWr371q+XVr351OeOMM8rll1++6idzrr322nLOOeeUc889t/zUT/1Uud3tbldms1l52MMeVv7X//pf5ed+7ufK3e9+9/Lud78bX/tPf/rT5bTTTivHH398ec5znlMOP/zwcvHFF5dHPOIR5R3veEd55CMfua7HWcr+my1PfvKTy8knn1x++Zd/uRx11FHl8ssvL+9///vLE57whFLK/ht255xzTrnPfe5Tnv/855fxeFze9KY3lQc84AHlr//6r8spp5wSvU6SJG11612//OZv/mYZj8flwgsvLNdff315yUteUn7yJ3+y/P3f/30pZX9z5Oyzzy579+4tz3zmM8vtb3/78uUvf7m8973vLdddd1058sgjSyn5euL3fu/3ys/8zM+UU089tTzrWc8qn/3sZ8uP//iPl2OOOabc8Y53vMnHduA/ev3BH/xBOe20027yJz/W+zz8+q//etm2bVu58MILy969e8u2bdvKRRddVF784heXpz71qeWUU04p3/jGN8rHP/7x8olPfKI86EEPWvPYv/Zrv1Yuuuiicuqpp5YXvOAFZdu2beXv//7vy4c//OHy4Ac/+CYfo3Sr1UgDe9Ob3tSUUpoPfehDzVe/+tXmi1/8YvPHf/zHzW1uc5vmsMMOa770pS81//Ef/9GUUpqXvvSla+7nm9/8ZnPUUUc1T3va01aN//u//3tz5JFHrho/88wzmzPPPLOzj/PPP7854YQTDv7vSy65pCmlNC95yUsOjq2srDSnn356U0pp3vSmNx0cv/e9793c9ra3ba699tqDY1dccUUzHo+bJz7xiTf5HHzuc5/rPL49e/Y00+m0M29paal5wQtesGq8lNI8/elP791n0zTNU57ylOa4445rvva1r60aP/fcc5sjjzyy2b1795rn+Qu/8AtNKaW5/PLLb/LxHPCIRzyi2bZtW3PVVVcdHPvXf/3XZufOnc0ZZ5xxcOzAe+BHfuRHmtlstup4k8mkue6665qmaZrrr7++WVpaap797GevOs5LXvKSZjQaNZ///Oebpmmaq6++uplMJs2LXvSiVfM++clPNgsLC6vGzzzzzKaU0rzuda9bNfcd73hHU0ppXvnKVx4cm06nzQMe8IDOa//ABz6wuec979ns2bPn4NhsNmtOPfXU5i53ucu6H+d1113X7Ny5s7nf/e7X3HDDDavO68B2s9msuctd7tKcffbZq/a1e/fu5sQTT2we9KAHNZIk3RwcuD5+7GMfW3NOun657LLLmlJK893f/d3N3r17D8571ate1ZRSmk9+8pNN0zTN5Zdf3pRSmre//e1rHjNdT+zbt6+57W1v29z73vdedcw3vOENTSkF15w3NpvNDq5Hbne72zWPf/zjm9e85jUH1zU1z8Od7nSnztrue7/3e5sf+7Efu8lzev7zn9/c+J9+V155ZTMej5tHPvKRnfXpjdchklbzV3h0yPzIj/xIOfbYY8sd73jHcu6555YjjjiivOtd7yrHH398Oeyww8q2bdvKX/zFX5T/+I//wO0vvfTSct1115XHP/7x5Wtf+9rB/5tMJuV+97tfueyyy9Z9Tn/2Z39WFhYWys/93M8dHJtMJuWZz3zmqnn/9m//Vv7xH/+xPOlJTyrHHHPMwfF73ete5UEPelD5sz/7s3Ufe2lp6eDvqE6n03LttdeWI444otztbncrn/jEJ9a9v1L2/0TNO97xjvKwhz2sNE2z6nk6++yzy/XXX3+T+/7GN75RSill586dvceaTqflgx/8YHnEIx6x6tdujjvuuPKEJzyhfOQjHzm4vwN++qd/etWPi55++ullOp2Wz3/+86WUcvDXmC6++OLSNM3BeX/yJ39SfvAHf7D8p//0n0oppbzzne8ss9msPPaxj131GG9/+9uXu9zlLp33wtLSUrngggtWjb3//e8vi4uL5WlPe9rBsfF4XJ7+9Kevmvf1r3+9fPjDHy6Pfexjyze/+c2Dx7r22mvL2WefXa688sry5S9/eV2P89JLLy3f/OY3y3Oe85yyffv2Vdse2O4f//Efy5VXXlme8IQnlGuvvfbgcb/97W+XBz7wgeWv/uqvymw267wukiTd3MyzfrngggvKtm3bDv7v008/vZSy/yd8SykHf8LkAx/4QNm9ezceN11PfPzjHy9f+cpXys/+7M+uOuaBXwHvMxqNygc+8IHywhe+sBx99NHlj/7oj8rTn/70csIJJ5THPe5xB391fJ7n4fzzzy+HHXbYqrGjjjqqfPrTny5XXnll77kdcMkll5TZbFb+23/7b52Gin/uWFqbv8KjQ+Y1r3lNuetd71oWFhbK7W53u3K3u93t4Bf00tJS+a3f+q3y7Gc/u9zudrcrP/iDP1ge+tCHlic+8Ynl9re/fSmlHLwIHGintO3atWvd5/T5z3++HHfccQd//eWAu93tbp15NF5KKd/93d9dPvCBD5Rvf/vb5fDDD4+PPZvNyqte9aryu7/7u+Vzn/tcmU6nB/9/7d+FTX31q18t1113XXnDG95Q3vCGN+Ccr3zlK2tuf+A5/OY3vxkda/fu3Ws+J7PZrHzxi18sJ5988sHxAzdADjj66KNLKWXVTbPHPe5x5ZJLLikf/ehHy6mnnlquuuqq8g//8A/lla985cE5V155ZWmaptzlLnfBc1tcXFz1v48//vhVC55S/v/Xvh2TvfOd77zqf//Lv/xLaZqmPO95zyvPe97z8Hhf+cpXyvHHHx8/zgO/vvY93/M9uL9S/v/3O/1K0QHXX3/9wX1LknRzNc/6pe9ae+KJJ5Zf/MVfLC9/+cvLH/zBH5TTTz+9/PiP/3j5qZ/6qYM3PdL1xIF1YHve4uLimu22tqWlpfLc5z63PPe5zy3/9m//Vv7yL/+yvOpVryoXX3xxWVxcLG9729vmeh5OPPHEzpwXvOAF5eEPf3i5613vWr7ne76n/OiP/mg577zzyr3uda81z++qq64q4/G43OMe94gej6T9vIGiQ+aUU07pREBv7FnPelZ52MMeVi655JLygQ98oDzvec8rL37xi8uHP/zh8n3f930H/2v7W9/61oM3VW7sxr9PeiC82nbjmxSb7Td+4zfK8573vPLkJz+5/Pqv/3o55phjyng8Ls961rPm/smCA9v91E/91Jr/8L6pi+eBP6H3yU9+stz73vee6xxuymQywfEbv1YPe9jDyo4dO8rFF19cTj311HLxxReX8XhcHvOYxxycM5vNymg0Ku973/twn+0bYu3/MrMeB57TCy+8sJx99tk4p33TJXmc6XFf+tKXrvlatB+nJEk3R/OsX5Jr7cte9rLypCc9qbz73e8uH/zgB8vP//zPlxe/+MXl7/7u78od7nCHda8nhnLccceVc889tzz60Y8uJ598crn44ovLm9/85rmeB1rjnHHGGeWqq646+Ljf+MY3lle84hXlda97XXnqU586/AOSbsW8gaJNddJJJ5VnP/vZ5dnPfna58sory73vfe/yspe9rLztbW8rJ510UimllNve9rblR37kR25yP0cfffTBH+G8sQP/BeGAE044ofz5n/95+da3vrXqIvnP//zPnXk0Xsr+v87yHd/xHev66ZNSSvnTP/3T8sM//MPl937v91aNX3fddeU7vuM71rWvA4499tiyc+fOMp1Oe58jcs4555TJZFLe9ra39YZkjz322LJjx441n5PxeNwbVSOHH354eehDH1re/va3l5e//OXlT/7kT8rpp59evvM7v/PgnJNOOqk0TVNOPPHEcte73nXdxyhl/2t62WWXdf6kcbvgf+C/LC0uLs71nJID7+VPfepTnZsv7Tm7du0a7LiSJG1FteuXm3LPe96z3POe9yy/+qu/Wv72b/+2nHbaaeV1r3tdeeELXxivJw6sA6+88spVPwm9vLxcPve5z5Xv/d7vnevcFhcXy73uda9y5ZVXlq997WuDPg/HHHNMueCCC8oFF1xQvvWtb5UzzjijXHTRRWveQDnppJPKbDYr//RP/3RI/iOadEtlA0WbYvfu3WXPnj2rxk466aSyc+fOsnfv3lJKKWeffXbZtWtX+Y3f+I2yvLzc2ceN/0zsSSedVD7zmc+sGrviiis6f/71IQ95SFlZWSmvfe1rD45Np9Py6le/etW84447rtz73vcub3nLW1b9ieNPfepT5YMf/GB5yEMesu7HPJlMOj+R8Pa3v73T01jvPh/96EeXd7zjHeVTn/pU5/9Pf0r3xu54xzuWpz3taeWDH/xg5zkoZf9/IXrZy15WvvSlL5XJZFIe/OAHl3e/+92r/tT0NddcU/7wD/+w3P/+95/r16pK2f9rPP/6r/9a3vjGN5YrrriiPO5xj1v1/3/Uox5VJpNJ+bVf+7XOc9g0Tbn22mt7j3H22WeX5eXl8t//+39f9fhe85rXrJp329vetpx11lnl9a9/ffm3f/u3zn76nlPy4Ac/uOzcubO8+MUv7rzvDzye+9znPuWkk04qv/3bv12+9a1vDXJcSZK2otr1C/nGN75RVlZWVo3d8573LOPx+ODaMl1P3Pe+9y3HHntsed3rXlf27dt3cM6b3/zmVevCtVx55ZXlC1/4Qmf8uuuuKx/96EfL0UcfXY499tjBnof2OuiII44od77znQ8+bvKIRzyijMfj8oIXvKDzk9Dr+Qla6dbGn0DRpvi///f/lgc+8IHlsY99bLnHPe5RFhYWyrve9a5yzTXXlHPPPbeUsv+/xL/2ta8t5513Xvn+7//+cu6555Zjjz22fOELXyj/83/+z3LaaaeV3/md3ymllPLkJz+5vPzlLy9nn312ecpTnlK+8pWvlNe97nXl5JNPXhU2fdjDHlZOO+208pznPKdcffXV5R73uEd55zvfWa6//vrOOb70pS8t55xzTvmhH/qh8pSnPOXgnzE+8sgjy0UXXbTux/zQhz60vOAFLygXXHBBOfXUU8snP/nJ8gd/8Afx79Ku5Td/8zfLZZddVu53v/uVpz3taeUe97hH+frXv14+8YlPlA996EPl61//+k1u/7KXvaxcddVV5ed//ufLO9/5zvLQhz60HH300eULX/hCefvb314+85nPHHxNXvjCF5ZLL7203P/+9y//+T//57KwsFBe//rXl71795aXvOQlcz+GhzzkIWXnzp3lwgsvPLiYuLGTTjqpvPCFLyy//Mu/XK6++uryiEc8ouzcubN87nOfK+9617vKT//0T5cLL7zwJo/xiEc8opxyyinl2c9+dvmXf/mXcve737285z3vOfj83DiY9prXvKbc//73L/e85z3L0572tHKnO92pXHPNNeWjH/1o+dKXvlSuuOKKdT2+Xbt2lVe84hXlqU99avmBH/iB8oQnPKEcffTR5Yorrii7d+8ub3nLW8p4PC5vfOMbyznnnFNOPvnkcsEFF5Tjjz++fPnLXy6XXXZZ2bVrV/kf/+N/rOu4kiRtpt///d8v73//+zvj/+W//Jfq9Uvbhz/84fKMZzyjPOYxjyl3vetdy8rKSnnrW9+6al2RricWFxfLC1/4wvIzP/Mz5QEPeEB53OMeVz73uc+VN73pTdG67YorrihPeMITyjnnnFNOP/30cswxx5Qvf/nL5S1veUv513/91/LKV77y4K8QDfE83OMe9yhnnXVWuc997lOOOeaY8vGPf7z86Z/+aXnGM56x5jZ3vvOdy3Of+9zy67/+6+X0008vj3rUo8rS0lL52Mc+Vr7zO7+zvPjFLw6feelWZgP/4o9uJZI/Xfe1r32tefrTn97c/e53bw4//PDmyCOPbO53v/s1F198cWfuZZdd1px99tnNkUce2Wzfvr056aSTmic96UnNxz/+8VXz3va2tzV3utOdmm3btjX3vve9mw984AOdP2PcNE1z7bXXNuedd16za9eu5sgjj2zOO++8g3/67sZ/yrZpmuZDH/pQc9pppzWHHXZYs2vXruZhD3tY80//9E+9z8FnP/vZppTSvPzlLz84tmfPnubZz352c9xxxzWHHXZYc9pppzUf/ehH8U8wl3X8GeOmaZprrrmmefrTn97c8Y53bBYXF5vb3/72zQMf+MDmDW94Q++5Ns3+P+X8xje+sTn99NObI488sllcXGxOOOGE5oILLuj8ieNPfOITzdlnn90cccQRzY4dO5of/uEfbv72b/921Zy13gMH/gTfZZdd1jmHn/zJnzz4J4HX8o53vKO5//3v3xx++OHN4Ycf3tz97ndvnv70pzf//M//fHDOmWee2Zx88sm4/Ve/+tXmCU94QrNz587myCOPbJ70pCc1f/M3f9OUUpo//uM/XjX3qquuap74xCc2t7/97ZvFxcXm+OOPbx760Ic2f/qnfzr343zPe97TnHrqqQffT6ecckrzR3/0R6vmXH755c2jHvWo5ja3uU2ztLTUnHDCCc1jH/vY5s///M/XfF4kSdpKDlwf1/q/L37xi03TZOuXA9fU9p8nPrAuOrB2++xnP9s8+clPbk466aRm+/btzTHHHNP88A//cPOhD32oc37JeqJpmuZ3f/d3mxNPPLFZWlpq7nvf+zZ/9Vd/heu2tmuuuab5zd/8zebMM89sjjvuuGZhYaE5+uijmwc84AGr1hE3nj/v89A0TfPCF76wOeWUU5qjjjqqOeyww5q73/3uzYte9KJm3759B+e0/4zxAb//+7/ffN/3fV+ztLTUHH30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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.PseudoJaffe(\n", + " cosmology = cosmology,\n", + " name = \"pseudojaffe\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " mass = torch.tensor(1e13),\n", + " core_radius = torch.tensor(5e-1),\n", + " scale_radius = torch.tensor(15.)\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"Pseudo Jaffe Convergence\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "b232c8b0", + "metadata": {}, + "source": [ + "## External Shear (ExternalShear)\n", + "\n", + "It is often necessary to embed a lens in an external shear field to account for the fact that the lensing mass is not the only mass in the universe." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e1d7b927", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAJFCAYAAAD6TAMuAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABt/UlEQVR4nO3debgkdX3o/0+fMwv7MoBAAFkGEEE2USQgoKIS3ELAAJoYwAVNMIabqDESf6wuuIBcBQnXCIpeExQ1SsQdr0aNSxACLgkimFzUsAkIwiyn6/cHmXM53Z9Dfaarz8wAr9fz+DyZ4ltV36qu7sl8T897ek3TNAEAAADArCZW9wQAAAAA1nQWUAAAAABaWEABAAAAaGEBBQAAAKCFBRQAAACAFhZQAAAAAFpYQAEAAABoYQEFAAAAoIUFFAAAAIAWFlCYczfddFP0er24+OKLx3bM7bbbLp73vOeN7XjwcPa0pz0tnva0p4207/Lly+P1r399bLPNNjExMRGHH354RET0er049dRTxzbHldHlegAAYK5YQFkFLr744uj1erP+75//+Z9X6ng///nP49RTT42rr756bia8Gt10001x/PHHx+LFi2OttdaKLbbYIg466KA45ZRTVvfUOvvkJz8Zhx12WGy66aaxYMGC+K3f+q046qij4itf+crqnhqryTe/+c049dRT484771xtc/jABz4Q73jHO+KFL3xhfPCDH4z/8T/+x2qbCwAArMnmre4JPJqcfvrpsf322w9t33HHHVfqOD//+c/jtNNOi+222y722muvMc1u9fvJT34ST37yk2PttdeOl770pbHddtvFL37xi7jqqqvirLPOitNOO211T3EkTdPES1/60rj44otj7733jj//8z+PLbbYIn7xi1/EJz/5yTjkkEPiG9/4Ruy///6re6qsYt/85jfjtNNOi+OOOy422mij1TKHr3zlK7HVVlvFOeecM2P7fffdF/Pm+S0CAABW8P8dr0KHHXZYPOlJT1rd05jVvffeG+uuu+5qO/8555wT99xzT1x99dWx7bbbzvhvt9xyy2qaVbt+vx9Lly6NtdZaK/3v73rXu+Liiy+Ok046Kc4+++zo9XrT/+3kk0+OSy655GH/B9XV/ewwultuuSVdvJnteQYAgEcrf4VnDXLKKafExMREfPnLX56x/YQTTogFCxbENddcE1/96lfjyU9+ckREHH/88dN/DejBfZFvf/vb8Tu/8zux4YYbxjrrrBMHH3xwfOMb35hxzFNPPTV6vV788Ic/jBe/+MWx8cYbx1Of+tSI+H99kX/6p3+KfffdN9Zaa63YYYcd4kMf+tCMY9xxxx3x2te+NnbfffdYb731YoMNNojDDjssrrnmmpGu/4Ybboitt956aPEkIuIxj3lMuk/bHCMi7rzzzjjppJNim222iYULF8aOO+4YZ511VvT7/Rnj3vnOd8b+++8fm2yySay99tqxzz77xMc//vGh4/V6vXj1q18dH/nIR2K33XaLhQsXxuc+97l0fvfdd1+89a1vjV122SXe+c53zlg8WeElL3lJ7LvvvtO//ulPfxq///u/H4sWLYp11lkn9ttvv/jHf/zHGft89atfjV6vF5deemm8+c1vjq233jrWWmutOOSQQ+InP/nJ9LhXv/rVsd5668VvfvObofO+6EUvii222CKmpqamt11xxRVx4IEHxrrrrhvrr79+PPe5z40f/OAHM/Y77rjjYr311osbbrghnvOc58T6668ff/AHfzB9va95zWti0003jfXXXz9e8IIXxM0335z2NG6++eZ46UtfGptvvnksXLgwdtttt/jABz4w0nWu8O1vfzue85znxMYbbxzrrrtu7LHHHnHuuefOGPPjH/84XvjCF8aiRYtirbXWiic96Unx6U9/euhYg1a0fN75znfGhRdeGIsXL46FCxfGk5/85Pjud787NP4rX/nK9L3caKON4nd/93fjRz/60fR/P/XUU+N1r3tdRERsv/320+/lm2666SHnseLca6+9duy7777x9a9/PR23ZMmSOOWUU2LHHXeMhQsXxjbbbBOvf/3rY8mSJTOu58orr4wf/OAH0+f/6le/GhHDDZQVnxk/+clPpr8xs+GGG8bxxx+fPl8f/vCHY5999om11147Fi1aFMccc0z853/+58jXAwAAq13DnLvooouaiGi+9KUvNbfeeuuM/912223T45YuXdrsvffezbbbbtvcfffdTdM0zec+97kmIpozzjijaZqm+eUvf9mcfvrpTUQ0J5xwQnPJJZc0l1xySXPDDTc0TdM0X/7yl5sFCxY0v/3bv928613vas4555xmjz32aBYsWNB8+9vfnj7XKaec0kREs+uuuza/+7u/25x//vnNeeed1zRN02y77bbN4x73uGbzzTdv3vjGNzbvfe97myc+8YlNr9drrrvuuuljfPe7320WL17cvOENb2j+5m/+pjn99NObrbbaqtlwww2bm2++eXrcjTfe2EREc9FFFz3kfTrhhBOaycnJ5stf/nLrPa3O8d5772322GOPZpNNNmne+MY3NhdccEHzR3/0R02v12v+7M/+bMYxt9566+ZP/uRPmve+973N2Wef3ey7775NRDSXX375jHER0Tz+8Y9vNttss+a0005rzjvvvOb73/9+Os8vfOELTUQ0p59+eus1Nc0Dr+/mm2/erL/++s3JJ5/cnH322c2ee+7ZTExMNJ/4xCemx1155ZVNRDR77713s88++zTnnHNOc+qppzbrrLNOs++++06P+9rXvtZERHPppZfOOM+9997brLvuus2JJ544ve1DH/pQ0+v1mt/5nd9p3vOe9zRnnXVWs9122zUbbbRRc+ONN06PO/bYY5uFCxc2ixcvbo499tjmggsuaD70oQ81TdM0Rx11VBMRzUte8pLmvPPOa4466qhmzz33bCKiOeWUU2Zc59Zbb91ss802zemnn968733va17wghc0EdGcc845K32dK+71ggULmm233bY55ZRTmve9733Na17zmuaZz3zm9Jjrrruu2XDDDZtdd921Oeuss5r3vve9zUEHHdT0er0Z9zez4jnee++9mx133LE566yzmre//e3Npptu2my99dbN0qVLp8d+8YtfbObNm9fsvPPOzdvf/vbmtNNOazbddNNm4403nr6X11xzTfOiF71o+ppXvJfvueeeWefw/ve/v4mIZv/992/+5//8n81JJ53UbLTRRs0OO+zQHHzwwdPjpqammmc/+9nNOuus05x00knN3/zN3zSvfvWrm3nz5jW/+7u/2zRN09xzzz3NJZdc0uyyyy7N1ltvPX3+X/7yl03TNEOv2YrPjL333rs54ogjmvPPP795+ctf3kRE8/rXv37GPM8888ym1+s1Rx99dHP++edPX/92223X/OpXv1rp6wEAgDWBBZRVYMUCSva/hQsXzhh77bXXNgsWLGhe/vKXN7/61a+arbbaqnnSk57ULFu2bHrMd7/73XRBot/vNzvttFNz6KGHNv1+f3r7b37zm2b77bdvnvWsZ01vW/GHoRe96EVD8912222biGi+9rWvTW+75ZZbmoULFzZ/8Rd/Mb3t/vvvb6ampmbse+ONNzYLFy6csWBQXUC57rrrmrXXXruJiGavvfZq/uzP/qz51Kc+1dx7770jz/GMM85o1l133ebf//3fZ+z/hje8oZmcnGz+4z/+Y3rbb37zmxljli5d2jzhCU9onvGMZ8zYHhHNxMRE84Mf/OAhr6dpmubcc89tIqL55Cc/2Tq2aZrmpJNOaiKi+frXvz697de//nWz/fbbN9ttt930/V6xsPD4xz++WbJkydD5rr322qZpHngmttpqq+bII4+ccZ5LL710xv379a9/3Wy00UbNK17xihnjfvnLXzYbbrjhjO3HHntsExHNG97whhlj/+Vf/qWJiOakk06asf24444b+sP4y172smbLLbecsYDYNE1zzDHHNBtuuOH0a1G9zuXLlzfbb799s+222874A/qKe7DCIYcc0uy+++7N/fffP+O/77///s1OO+3UPJQVz/Emm2zS3HHHHdPb/+Ef/qGJiOYzn/nM9La99tqrecxjHtPcfvvt09uuueaaZmJiovmjP/qj6W3veMc7moiYsUA1m6VLlzaPecxjmr322mvGvbjwwgubiJix4HDJJZc0ExMTM56jpmmaCy64oImI5hvf+Mb0toMPPrjZbbfdhs432wLKS1/60hnjfu/3fq/ZZJNNpn990003NZOTk82b3/zmGeOuvfbaZt68edPbV+Z6AABgTeCv8KxC5513Xnzxi1+c8b8rrrhixpgnPOEJcdppp8X73//+OPTQQ+O2226LD37wg6VGxtVXXx3XX399vPjFL47bb789brvttrjtttvi3nvvjUMOOSS+9rWvDf21lVe96lXpsXbdddc48MADp3+92WabxeMe97j46U9/Or1t4cKFMTHxwCM0NTUVt99+e6y33nrxuMc9Lq666qryfVlht912i6uvvjr+8A//MG666aY499xz4/DDD4/NN988/tf/+l8jzfFjH/tYHHjggbHxxhtP34/bbrstnvnMZ8bU1FR87Wtfmx679tprT//fv/rVr+Kuu+6KAw88ML2Wgw8+OHbdddfWa7r77rsjImL99dcv3YPPfvazse+++07/daqIiPXWWy9OOOGEuOmmm+KHP/zhjPHHH398LFiwYPrXK+7HinvQ6/Xi93//9+Ozn/1s3HPPPdPj/v7v/z622mqr6fN88YtfjDvvvDNe9KIXzbhPk5OT8ZSnPCWuvPLKobn+8R//8Yxfr/hrTH/yJ38yY/uf/umfzvh10zRx2WWXxfOf//xommbG+Q499NC46667hu5523V+//vfjxtvvDFOOumkoZ7Hir82dccdd8RXvvKVOOqoo+LXv/719Dlvv/32OPTQQ+P666+Pm2++eeg6Bx199NGx8cYbzzqXX/ziF3H11VfHcccdF4sWLZoet8cee8SznvWs+OxnP9t6jsz3vve9uOWWW+JVr3rVjHtx3HHHxYYbbjhj7Mc+9rF4/OMfH7vsssuM+/uMZzwjIiJ9PasGPzMOPPDAuP3226ef9U984hPR7/fjqKOOmnHuLbbYInbaaafpc6/M9QAAwJrg4V2ufJjZd999SxHZ173udfF3f/d38Z3vfCfe8pa3lP6gHhFx/fXXR0TEscceO+uYu+66a8Yf/rJ/FSgi4rGPfezQto033jh+9atfTf+63+/HueeeG+eff37ceOONM1oam2yySWnOg3beeee45JJLYmpqKn74wx/G5ZdfHm9/+9vjhBNOiO233z6e+cxnrtQcr7/++vjXf/3X2GyzzdLzPThOe/nll8eZZ54ZV1999XQnIiLSbsls923QBhtsEBERv/71r0vjf/azn8VTnvKUoe2Pf/zjp//7E57whOntg/dgxWv74Htw9NFHx7vf/e749Kc/HS9+8Yvjnnvuic9+9rPxyle+cvraVjw7K/6APdt1rDBv3rzYeuuth+Y+MTExdG8G/5WpW2+9Ne6888648MIL48ILL0zPNxgNbrvOG264ISJixr0Z9JOf/CSapok3velN8aY3vWnW82611VazHqMyl5/97GcREfG4xz1uaN/HP/7x8fnPf36k6O6K4+60004zts+fPz922GGHGduuv/76+NGPflR67lfWQ13/BhtsENdff300TTM0zwfPN2LlrgcAANYEFlDWQD/96U+n/0B77bXXlvdb8e2Sd7zjHbP+88brrbfejF8/+FsXDzY5OZlub5pm+v9+y1veEm9605vipS99aZxxxhmxaNGimJiYiJNOOmnomy4ra3JyMnbffffYfffd47d/+7fj6U9/enzkIx+ZsYBSmWO/349nPetZ8frXvz4du/POO0dExNe//vV4wQteEAcddFCcf/75seWWW8b8+fPjoosuiv/9v//30H6z3bdBu+yyS0Q88DoefvjhpX1WRuUe7LfffrHddtvFpZdeGi9+8YvjM5/5TNx3331x9NFHT49Z8XpdcsklscUWWwwdb/AbUA/+9tHKWnGuP/zDP5x1sW+PPfaY8evKdVbP+9rXvjYOPfTQdEzlnxQfx1zmWr/fj9133z3OPvvs9L9vs802Ix+77fr7/X70er244oor0rGDn0EAAPBwYQFlDdPv9+O4446LDTbYIE466aR4y1veEi984QvjiCOOmB6TfSMiImLx4sUR8cC3BR680DBXPv7xj8fTn/70+Nu//dsZ2++8887YdNNNx3aeFd/a+cUvfrHS+y5evDjuueee1vtx2WWXxVprrRWf//znY+HChdPbL7roopU+54M99alPjY033jg++tGPxhvf+MZZ//C5wrbbbhv/9m//NrT9xz/+8fR/H8VRRx0V5557btx9993x93//97HddtvFfvvtN/3fVzw7j3nMY0Z+drbddtvo9/tx4403zvhWweC/lrPZZpvF+uuvH1NTU2N7TlfM/7rrrpv1mCu+1TB//vw5fX+seI1mex033XTT6W+fzPZefqjjXn/99TO+KbRs2bK48cYbY88995zetnjx4rjmmmvikEMOWalzjMPixYujaZrYfvvtpxcoMytzPQAAsCbQQFnDnH322fHNb34zLrzwwjjjjDNi//33jz/+4z+O2267bXrMij983XnnnTP23WeffWLx4sXxzne+c0bvYoVbb711rHOdnJwc+qn7xz72sVJHIvP1r389li1bNrR9RTMi+ysRbY466qj41re+FZ///OeH/tudd94Zy5cvj4gHrqXX6834a0g33XRTfOpTn1rpcz7YOuusE3/5l38ZP/rRj+Iv//Iv028pfPjDH47vfOc7ERHxnOc8J77zne/Et771ren/fu+998aFF14Y2223Xfmvcw06+uijY8mSJfHBD34wPve5z8VRRx01478feuihscEGG8Rb3vKW9DWoPDsrvtVx/vnnz9j+nve8Z8avJycn48gjj4zLLrssrrvuupHONeiJT3xibL/99vHud7976H2x4p4/5jGPiac97WnxN3/zN+li3LjeH1tuuWXstdde8cEPfnDGXK677rr4whe+EM95znOmt832Xs486UlPis022ywuuOCCWLp06fT2iy++eGj/o446Km6++ea0HXTffffFvffeu3IXtRKOOOKImJycjNNOO23oeW+aJm6//faIWLnrAQCANYFvoKxCV1xxxfQ3CR5s//33jx122CF+9KMfxZve9KY47rjj4vnPf35EPPCHib322iv+5E/+JC699NKIeOAnvBtttFFccMEFsf7668e6664bT3nKU2L77beP97///XHYYYfFbrvtFscff3xstdVWcfPNN8eVV14ZG2ywQXzmM58Z2/U873nPi9NPPz2OP/742H///ePaa6+Nj3zkIyP3C84666z4l3/5lzjiiCOm/wrHVVddFR/60Idi0aJFcdJJJ630MV/3utfFpz/96Xje854Xxx13XOyzzz5x7733xrXXXhsf//jH46abbopNN900nvvc58bZZ58dv/M7vxMvfvGL45Zbbonzzjsvdtxxx/jXf/3Xka7nwXP4wQ9+EO9617viyiuvjBe+8IWxxRZbxC9/+cv41Kc+Fd/5znfim9/8ZkREvOENb4iPfvSjcdhhh8VrXvOaWLRoUXzwgx+MG2+8MS677LKR/9rME5/4xNhxxx3j5JNPjiVLlsz46zsRD3xr6X3ve1+85CUviSc+8YlxzDHHxGabbRb/8R//Ef/4j/8YBxxwQLz3ve99yHPss88+ceSRR8a73/3uuP3222O//faL//N//k/8+7//e0TM/LbF2972trjyyivjKU95SrziFa+IXXfdNe6444646qqr4ktf+lLccccdK3V9ExMT8b73vS+e//znx1577RXHH398bLnllvHjH/84fvCDH0wvoJ133nnx1Kc+NXbfffd4xSteETvssEP813/9V3zrW9+K//t//29cc801K3Xe2bzjHe+Iww47LH77t387Xvayl8V9990X73nPe2LDDTeMU089dXrcPvvsExERJ598chxzzDExf/78eP7zn5/2UebPnx9nnnlmvPKVr4xnPOMZcfTRR8eNN94YF1100dB77iUveUlceuml8apXvSquvPLKOOCAA2Jqaip+/OMfx6WXXhqf//znSz2mUSxevDjOPPPM+Ku/+qu46aab4vDDD4/1118/brzxxvjkJz8ZJ5xwQrz2ta9dqesBAIA1wqr/h38efR7qnzGO//7nfZcvX948+clPbrbeeuvmzjvvnLH/in+y9e///u+nt/3DP/xDs+uuuzbz5s0b+ieCv//97zdHHHFEs8kmmzQLFy5stt122+aoo45qvvzlL0+PWfFPkt56661D8912222b5z73uUPbDz744Bn/tOj999/f/MVf/EWz5ZZbNmuvvXZzwAEHNN/61reGxlX/GeNvfOMbzYknntg84QlPaDbccMNm/vz5zWMf+9jmuOOOa2644YaR5tg0D/wTvX/1V3/V7Ljjjs2CBQuaTTfdtNl///2bd77znc3SpUunx/3t3/5ts9NOOzULFy5sdtlll+aiiy6avk8PFhHNiSee+JDXkvn4xz/ePPvZz24WLVrUzJs3r9lyyy2bo48+uvnqV786Y9wNN9zQvPCFL2w22mijZq211mr23Xff5vLLL58xZsU/7/uxj31sxvaHutcnn3xyExHNjjvuOOscr7zyyubQQw9tNtxww2attdZqFi9e3Bx33HHN9773vekxxx57bLPuuuum+997773NiSee2CxatKhZb731msMPP7z5t3/7tyYimre97W0zxv7Xf/1Xc+KJJzbbbLNNM3/+/GaLLbZoDjnkkObCCy8c+Tr/6Z/+qXnWs57VrL/++s26667b7LHHHs173vOeGWNuuOGG5o/+6I+aLbbYopk/f36z1VZbNc973vOaj3/847Pelwef8x3veMfQf4uBf/K3aZrmS1/6UnPAAQc0a6+9drPBBhs0z3/+85sf/vCHQ/ueccYZzVZbbdVMTEyU/knj888/v9l+++2bhQsXNk960pOar33ta+lzv3Tp0uass85qdtttt2bhwoXNxhtv3Oyzzz7Naaed1tx1113T41b2nzEe/MxY8fk2OO/LLruseepTn9qsu+66zbrrrtvssssuzYknntj827/920jXAwAAq1uvadag8iHwiHP11VfH3nvvHR/+8IfjD/7gD1b3dAAAAEaigQKMzX333Te07d3vfndMTEzEQQcdtBpmBAAAMB4aKMDYvP3tb49/+Zd/iac//ekxb968uOKKK+KKK66IE044odM/nQsAALC6+Ss8wNh88YtfjNNOOy1++MMfxj333BOPfexj4yUveUmcfPLJMW+e9VoAAODhywIKAAAAQAsNFAAAAIAWFlAAAAAAWlhAAQAAAGhhAQUAAACghQUUAACAgqc97WnxtKc9bXVPY6Wdeuqp0ev1Vvc04GHPAgoAADA2F198cfR6vfje9763uqey2ixdujTOPffc2HvvvWODDTaIjTbaKHbbbbc44YQT4sc//vHqnh4wonmrewIAAACPJEceeWRcccUV8aIXvShe8YpXxLJly+LHP/5xXH755bH//vvHLrvsskrn89d//dfxhje8YZWeEx6JLKAAAACMyXe/+924/PLL481vfnO88Y1vnPHf3vve98add945lvPcf//9sWDBgpiYaP9LBfPmzYt58/zRD7ryV3gAAIBV7uabb46XvvSlsfnmm8fChQtjt912iw984AMzxnz1q1+NXq8Xl156abz5zW+OrbfeOtZaa6045JBD4ic/+cmMsddff30ceeSRscUWW8Raa60VW2+9dRxzzDFx1113zRj34Q9/OPbZZ59Ye+21Y9GiRXHMMcfEf/7nfw7N78ILL4zFixfH2muvHfvuu298/etfL13XDTfcEBERBxxwwNB/m5ycjE022WTk+/B3f/d38dd//dex1VZbxTrrrBN33313LFu2LE477bTYaaedYq211opNNtkknvrUp8YXv/jF6f1na6B8+MMfjn333TfWWWed2HjjjeOggw6KL3zhC6XrhEcjy5AAAMAq9V//9V+x3377Ra/Xi1e/+tWx2WabxRVXXBEve9nL4u67746TTjppxvi3ve1tMTExEa997Wvjrrvuire//e3xB3/wB/Htb387Ih5ojhx66KGxZMmS+NM//dPYYost4uabb47LL7887rzzzthwww0jIuLNb35zvOlNb4qjjjoqXv7yl8ett94a73nPe+Kggw6K73//+7HRRhtFRMTf/u3fxitf+crYf//946STToqf/vSn8YIXvCAWLVoU22yzzUNe27bbbhsRER/5yEfigAMOeMhvfqzsfTjjjDNiwYIF8drXvjaWLFkSCxYsiFNPPTXe+ta3xstf/vLYd9994+67747vfe97cdVVV8WznvWsWc992mmnxamnnhr7779/nH766bFgwYL49re/HV/5ylfi2c9+9kNeIzxqNQAAAGNy0UUXNRHRfPe73511zMte9rJmyy23bG677bYZ24855phmww03bH7zm980TdM0V155ZRMRzeMf//hmyZIl0+POPffcJiKaa6+9tmmapvn+97/fRETzsY99bNZz3nTTTc3k5GTz5je/ecb2a6+9tpk3b9709qVLlzaPecxjmr322mvGOS+88MImIpqDDz74Ia+/3+83Bx98cBMRzeabb9686EUvas4777zmZz/7Wef7sMMOO0xvW2HPPfdsnvvc5z7knE455ZTmwX/0u/7665uJiYnm937v95qpqamh+QM5f4UHAABYZZqmicsuuyye//znR9M0cdttt03/79BDD4277rorrrrqqhn7HH/88bFgwYLpXx944IEREfHTn/40ImL6Gyaf//zn4ze/+U163k984hPR7/fjqKOOmnHOLbbYInbaaae48sorIyLie9/7Xtxyyy3xqle9asY5jzvuuOnzPJRerxef//zn48wzz4yNN944PvrRj8aJJ54Y2267bRx99NHTDZRR7sOxxx4ba6+99oxtG220UfzgBz+I66+/vnVuK3zqU5+Kfr8f/9//9/8NNVT8c8cwOwsoAADAKnPrrbfGnXfeGRdeeGFsttlmM/53/PHHR0TELbfcMmOfxz72sTN+vfHGG0dExK9+9auIiNh+++3jz//8z+P9739/bLrppnHooYfGeeedN6N/cv3110fTNLHTTjsNnfdHP/rR9Dl/9rOfRUTETjvtNOOc8+fPjx122KF0jQsXLoyTTz45fvSjH8XPf/7z+OhHPxr77bdfXHrppfHqV7965Puw/fbbD53r9NNPjzvvvDN23nnn2H333eN1r3td/Ou//utDzu+GG26IiYmJ2HXXXUvXAzxAAwUAAFhl+v1+RET84R/+YRx77LHpmD322GPGrycnJ9NxTdNM/9/vete74rjjjot/+Id/iC984Qvxmte8Jt761rfGP//zP8fWW28d/X4/er1eXHHFFenx1ltvvVEv6SFtueWWccwxx8SRRx4Zu+22W1x66aVx8cUXj3QfBr99EhFx0EEHxQ033DB93e9///vjnHPOiQsuuCBe/vKXj/+C4FHMAgoAALDKbLbZZrH++uvH1NRUPPOZzxzrsXfffffYfffd46//+q/jm9/8ZhxwwAFxwQUXxJlnnhmLFy+Opmli++23j5133nnWY6yIwF5//fXxjGc8Y3r7smXL4sYbb4w999xzpLnNnz8/9thjj7j++uvjtttuG+t9WLRoURx//PFx/PHHxz333BMHHXRQnHrqqbMuoCxevDj6/X788Ic/jL322qvTueHRxF/hAQAAVpnJyck48sgj47LLLovrrrtu6L/feuutK33Mu+++O5YvXz5j2+677x4TExOxZMmSiIg44ogjYnJyMk477bQZ31yJeOCbLLfffntERDzpSU+KzTbbLC644IJYunTp9JiLL754ul/yUK6//vr4j//4j6Htd955Z3zrW9+KjTfeODbbbLOx3YcV815hvfXWix133HH6ujOHH354TExMxOmnnz79TZgVBu8N8P/4BgoAADB2H/jAB+Jzn/vc0PY/+7M/i7e97W1x5ZVXxlOe8pR4xSteEbvuumvccccdcdVVV8WXvvSluOOOO1bqXF/5ylfi1a9+dfz+7/9+7LzzzrF8+fK45JJLphcpIh741sWZZ54Zf/VXfxU33XRTHH744bH++uvHjTfeGJ/85CfjhBNOiNe+9rUxf/78OPPMM+OVr3xlPOMZz4ijjz46brzxxrjoootKDZRrrrkmXvziF8dhhx0WBx54YCxatChuvvnm+OAHPxg///nP493vfvf0XyEax33Ydddd42lPe1rss88+sWjRovje974XH//4x6dbK5kdd9wxTj755DjjjDPiwAMPjCOOOCIWLlwY3/3ud+O3fuu34q1vfWvxzsOjiwUUAABg7N73vvel24877rjYeuut4zvf+U6cfvrp8YlPfCLOP//82GSTTWK33XaLs846a6XPteeee8ahhx4an/nMZ+Lmm2+OddZZJ/bcc8+44oorYr/99pse94Y3vCF23nnnOOecc+K0006LiIhtttkmnv3sZ8cLXvCC6XEnnHBCTE1NxTve8Y543eteF7vvvnt8+tOfjje96U2tcznooIPijDPOiCuuuCLOPvvsuPXWW2P99dePvffeO84666zpBZ2IiM0337zzfXjNa14Tn/70p+MLX/hCLFmyJLbddts488wz43Wve91D7nf66afH9ttvH+95z3vi5JNPjnXWWSf22GOPeMlLXlI6Lzwa9Rrf0QIAAAB4SBooAAAAAC0soAAAAAC0sIACAAAA0MICCgAAAEALCygAAAAALSygAAAAALSwgAIAAADQYt7qngAAwCPV7v/jnKFtvSYZmG1LpPtmiufoNYUDpvsVz5ko71s9b6bDnOvjmtYxmfHPIxs3vHHOr3+2cf0Rj7cqrr9fe9HGfk+ycel9qr6O7efNz9nl+LVryO/J6jreiPtmr8Ooxxr33CIi+smNSsY15fNmD2Ni1OMlY/K51a4rG9flWr+w9KPD4x7EN1AAAAAAWlhAAQAAAGhhAQUAAACghQYKAMBcSX5UlVYXio2FbFiXHkk0vZH2y/56ebX/UJ/bKtiWDSt2NpqBe1dvu3S4T+Vt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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.ExternalShear(\n", + " cosmology = cosmology,\n", + " name = \"externalshear\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " gamma_1 = torch.tensor(1.),\n", + " gamma_2 = torch.tensor(-1.),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "#convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "#axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"External Shear Convergence not defined\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "688d0796", + "metadata": {}, + "source": [ + "## Mass Sheet (MassSheet)\n", + "\n", + "This is a simple case of an external shear field which represents an infinite constant surface density sheet." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cdfba784", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFAAAAIXCAYAAAC7CqrSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABf60lEQVR4nO3deZRlZXno4fdU9UQzSONCQfCiAgZFTHJBVGZBRQIqoAK2GsWIw3XKNRjnOEE0JorGCY2KXhUVxatessQhcp01GgkOEYMoKhgVUSaBbrrOvn+wui519lvst88+VdXA86yVtcLnt789nFPV3V+d/vWgaZomAAAAAJjX1FJfAAAAAMDmzgYKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANDBBgoAAABABxsoAAAAAB1soAAAAAB0sIECAAAA0MEGCkzIIYccEve5z32W+jIAAFgghxxySBxyyCFLfRmb7BWveEUMBoOlvgy41bOBwoJ773vfG4PBIAaDQXzlK19p/e9N08Rd73rXGAwGcdRRRy3BFd6yyy+/PJ773OfGHnvsEVtssUXc6U53in333Tde8IIXxLXXXrvUlxcREWeeeWa88Y1v3KRjZmZm4owzzohDDjkktttuu1i5cmXc7W53ixNPPDG+/e1vL8yFAgC3eRt/73d7/v3E+vXr401velP86Z/+aWyzzTax7bbbxp577hlPfepT48ILL1zqywPGtGypL4Dbj1WrVsWZZ54ZBxxwwJzxL37xi3HppZfGypUrl+jK5ve73/0u9tlnn7j66qvjyU9+cuyxxx5xxRVXxHe/+914+9vfHs94xjNiq622WurLjDPPPDO+//3vx1/+5V+W5l9//fVx7LHHxrnnnhsHHXRQvPjFL47tttsuLrnkkjjrrLPife97X/z85z+PnXfeeWEvHADgNuhRj3pUfPrTn47HPvaxcdJJJ8WNN94YF154YZxzzjmx3377xR577LGo1/PSl740XvjCFy7qOeG2yAYKi+bP/uzP4qMf/Wj84z/+Yyxb9v/femeeeWbsvffe8dvf/nYJry737ne/O37+85/HV7/61dhvv/3m/G9XX311rFixYomurJ/nP//5ce6558Zpp53W2nR5+ctfHqeddtrSXNgEXXfddbF69eqlvgwA4HbmW9/6Vpxzzjlx6qmnxotf/OI5/9tb3vKWuPLKKydynhtuuCFWrFgRU1Pdf6lg2bJlc37/DYzHX+Fh0Tz2sY+NK664Ij73uc/Njq1fvz4+9rGPxdq1a9Nj/uEf/iH222+/uOMd7xhbbLFF7L333vGxj32sNe9zn/tcHHDAAbHtttvGVlttFX/0R3/U+gXrzW9+c+y5556xevXqWLNmTeyzzz5x5pln3uI1X3zxxTE9PR0PeMADWv/bNttsE6tWrWqN/8d//Ec86EEPitWrV8dOO+0Ur3vd61pz1q1bFy9/+ctjt912i5UrV8Zd73rX+Ou//utYt25da+4HPvCB2HvvvWOLLbaI7bbbLk444YT4xS9+Mfu/H3LIIfHP//zP8bOf/Wz2r0rd7W53m/eeLr300njHO94RD3nIQ9JPrExPT8fJJ58859Mn559/fhxxxBGxzTbbxFZbbRWHHXZYfOMb35hz3MaP6371q1+N5z3vebH99tvHlltuGcccc0xcfvnls/OOOuqouMc97pFe2wMf+MDYZ599Nun+Nz6D+9znPvFv//ZvcdBBB8Xq1atnX/8rrrginvCEJ8x+fPaJT3xiXHDBBTEYDOK9733vnHUuvPDCePSjHx3bbbddrFq1KvbZZ5/41Kc+NdZ9bvTpT386Dj744Nh6661jm222ifvd736t9903v/nNeNjDHhZ3uMMdYvXq1XHwwQfHV7/61fQZAcBtxWWXXRZPfvKT4853vnOsXLky9txzz3jPe94zZ87//b//NwaDQZx11llx6qmnxs477xyrVq2Kww47LH784x/PmXvRRRfFox71qNhhhx1i1apVsfPOO8cJJ5wQV1111Zx5ld9bRES8853vjF133TW22GKL2HfffePLX/5y6b4uvvjiiIjYf//9W//b9PR03PGOdxz7OXz4wx+Ol770pbHTTjvF6tWr4+qrr44bb7wxXvnKV8buu+8eq1atijve8Y5xwAEHzPk993wNlA984AOx7777zv7++KCDDorPfvazpfuE2yPbkCyau93tbvHABz4wPvShD8URRxwRETf94fKqq66KE044If7xH/+xdcyb3vSmeMQjHhGPe9zjYv369fHhD384HvOYx8Q555wTRx55ZERE/OAHP4ijjjoq7nvf+8arXvWqWLlyZfz4xz+e8wfQf/qnf4rnPOc58ehHPzqe+9znxg033BDf/e5345vf/Oa8mzcREbvsskvMzMzE+9///njiE5/YeY+///3v42EPe1gce+yxcdxxx8XHPvaxeMELXhB77bXX7D0Ph8N4xCMeEV/5ylfiqU99atzrXveK733ve3HaaafFf/7nf8YnPvGJ2fVOPfXUeNnLXhbHHXdcPOUpT4nLL7883vzmN8dBBx0U559/fmy77bbxkpe8JK666qq49NJLZz85ckt/rejTn/50bNiwIZ7whCd03k/ETc/3wAMPjG222Sb++q//OpYvXx7veMc74pBDDokvfvGLcf/733/O/Gc/+9mxZs2aePnLXx6XXHJJvPGNb4xnPetZ8ZGPfCQiIo4//vj48z//8/jWt74V97vf/WaP+9nPfhbf+MY34u///u836f43uuKKK+KII46IE044IR7/+MfHne985xgOh/Hwhz88/vVf/zWe8YxnxB577BGf/OQn09fyBz/4Qey///6x0047xQtf+MLYcsst46yzzoqjjz46zj777DjmmGM26T4jbtpsefKTnxx77rlnvOhFL4ptt902zj///Dj33HNn33df+MIX4ogjjoi99947Xv7yl8fU1FScccYZceihh8aXv/zl2HfffUuvEwDcmvz617+OBzzgATEYDOJZz3pWbL/99vHpT386/uIv/iKuvvrq1g95Xvva18bU1FScfPLJcdVVV8XrXve6eNzjHhff/OY3I+KmH8odfvjhsW7dunj2s58dO+ywQ1x22WVxzjnnxJVXXhl3uMMdIqL+e4t3v/vd8bSnPS3222+/+Mu//Mv4yU9+Eo94xCNiu+22i7ve9a63eG+77LJLRER88IMfjP333/8WP/mxqc/h1a9+daxYsSJOPvnkWLduXaxYsSJe8YpXxGte85p4ylOeEvvuu29cffXV8e1vfzu+853vxEMe8pB5z/3KV74yXvGKV8R+++0Xr3rVq2LFihXxzW9+M77whS/EQx/60Fu8R7jdamCBnXHGGU1ENN/61reat7zlLc3WW2/dXHfddU3TNM1jHvOY5kEPelDTNE2zyy67NEceeeScYzfO22j9+vXNfe5zn+bQQw+dHTvttNOaiGguv/zyea/hkY98ZLPnnntu8rX/6le/arbffvsmIpo99tijefrTn96ceeaZzZVXXtmae/DBBzcR0fyv//W/ZsfWrVvX7LDDDs2jHvWo2bH3v//9zdTUVPPlL395zvGnn356ExHNV7/61aZpmuaSSy5ppqenm1NPPXXOvO9973vNsmXL5owfeeSRzS677FK6p//5P/9nExHN+eefX5p/9NFHNytWrGguvvji2bFf/vKXzdZbb90cdNBBs2MbX+cHP/jBzXA4nHO+6enp2Wd21VVXNStXrmz+6q/+as55Xve61zWDwaD52c9+tsn3v/HZn3766XPmnn322U1ENG984xtnx2ZmZppDDz20iYjmjDPOmB0/7LDDmr322qu54YYbZseGw2Gz3377Nbvvvvsm3+eVV17ZbL311s3973//5vrrr59zXRuPGw6Hze67794cfvjhc9a67rrrmrvf/e7NQx7ykAYAbm1u/nu/+fzFX/xFs+OOOza//e1v54yfcMIJzR3ucIfZ3wOed955TUQ097rXvZp169bNznvTm97URETzve99r2mapjn//PObiGg++tGPznvO6u8t1q9f39zpTndq/uRP/mTOOd/5znc2EdEcfPDBt3j/w+Fw9vcmd77znZvHPvaxzVvf+tbZ3+P0eQ73uMc9Wr8//uM//uPW76FHvfzlL29u/ke/iy66qJmammqOOeaYZmZmpnX9QM5f4WFRHXfccXH99dfHOeecE9dcc02cc845t/gJkC222GL2///9738fV111VRx44IHxne98Z3Z8408KPvnJT8ZwOEzX2XbbbePSSy+Nb33rW5t0vXe+853jggsuiKc//enx+9//Pk4//fRYu3Zt3OlOd4pXv/rV0TTNnPlbbbVVPP7xj5/97xUrVsS+++4bP/nJT2bHPvrRj8a97nWv2GOPPeK3v/3t7P8deuihERFx3nnnRUTExz/+8RgOh3HcccfNmbfDDjvE7rvvPjtvU1199dUREbH11lt3zp2ZmYnPfvazcfTRR8/5azc77rhjrF27Nr7yla/MrrfRU5/61DkfET3wwANjZmYmfvazn0XETX/16YgjjoizzjprzvP7yEc+Eg94wAPiv/23/zbW/a9cuTJOPPHEOWPnnntuLF++PE466aTZsampqXjmM585Z97vfve7+MIXvhDHHXdcXHPNNbPnuuKKK+Lwww+Piy66KC677LJNus/Pfe5zcc0118QLX/jC1l/12njcv//7v8dFF10Ua9eujSuuuGL2vH/4wx/isMMOiy996UvzvqcB4NaqaZo4++yz4+EPf3g0TTPn1/nDDz88rrrqqjm/14uIOPHEE+e05w488MCIiNnfY238hMlnPvOZuO6669LzVn9v8e1vfzt+85vfxNOf/vQ553zSk540e55bMhgM4jOf+UyccsopsWbNmvjQhz4Uz3zmM2OXXXaJ448/fraBMs5zeOITnzjn98cRN/0+9wc/+EFcdNFFnde20Sc+8YkYDofxN3/zN62Gin/uGObnr/CwqLbffvt48IMfHGeeeWZcd911MTMzE49+9KPnnX/OOefEKaecEv/+7/8+pw9y82/sxx9/fLzrXe+KpzzlKfHCF74wDjvssDj22GPj0Y9+9OwvCC94wQvi85//fOy7776x2267xUMf+tBYu3Zt+ndTR+24447x9re/Pd72trfFRRddFJ/5zGfi7/7u7+Jv/uZvYscdd4ynPOUps3N33nnn1i86a9asie9+97uz/33RRRfFD3/4w9h+++3T8/3mN7+Zndc0Tey+++7pvOXLl3dee2abbbaJiIhrrrmmc+7ll18e1113XfzRH/1R63+7173uFcPhMH7xi1/EnnvuOTu+cQNkozVr1kTETRtgGx1//PHxiU98Ir7+9a/HfvvtFxdffHH827/925x/inlT73+nnXZqRX1/9rOfxY477tiKye62225z/vvHP/5xNE0TL3vZy+JlL3tZer7f/OY3sdNOO5Xvc+Pff77Pfe6TrhcRs7/RuaW/HnbVVVfNrg0AtwWXX355XHnllfHOd74z3vnOd6ZzNv5+aKOuX3fvfve7x/Oe97x4wxveEB/84AfjwAMPjEc84hHx+Mc/fnbTo/p7i40/DBmdt3z58nk7bqNWrlwZL3nJS+IlL3lJ/Nd//Vd88YtfjDe96U1x1llnxfLly+MDH/jAWM/h7ne/e2vOq171qnjkIx8Z97znPeM+97lPPOxhD4snPOEJcd/73nfe67v44otjamoq7n3ve5fuB7iJDRQW3dq1a+Okk06KX/3qV3HEEUfM6Vjc3Je//OV4xCMeEQcddFC87W1vix133DGWL18eZ5xxxpwI5xZbbBFf+tKX4rzzzot//ud/jnPPPTc+8pGPxKGHHhqf/exnY3p6Ou51r3vFj370ozjnnHPi3HPPjbPPPjve9ra3xd/8zd/EK1/5ytJ1DwaDuOc97xn3vOc948gjj4zdd989PvjBD87ZQJmenk6PvfknLYbDYey1117xhje8IZ278e/VDofDGAwG8elPfzpdd9x/PnnjP5v3ve99L/7kT/5krDVuSeUZPPzhD4/Vq1fHWWedFfvtt1+cddZZMTU1FY95zGNm52zq/Y/+NGZTbPyUx8knnxyHH354Omd006Vyn9Xz/v3f//28r8Xm8M9kA8Akbfz17/GPf/y8P0QY/cN/5dfd17/+9fGkJz0pPvnJT8ZnP/vZeM5znhOvec1r4hvf+EbsvPPOC/Z7qy477rhjnHDCCfGoRz0q9txzzzjrrLPive9971jPIfv9zkEHHRQXX3zx7H2/613vitNOOy1OP/30Ob9PBfqzgcKiO+aYY+JpT3tafOMb35gT3Bx19tlnx6pVq+Izn/lMrFy5cnb8jDPOaM2dmpqKww47LA477LB4wxveEH/7t38bL3nJS+K8886LBz/4wRERseWWW8bxxx8fxx9/fKxfvz6OPfbYOPXUU+NFL3pR+q/p3JJ73OMesWbNmviv//qvTTouImLXXXeNCy64IA477LBb/IjkrrvuGk3TxN3vfve45z3veYtrbspHLY844oiYnp6OD3zgA50h2e233z5Wr14dP/rRj1r/24UXXhhTU1OdIbXMlltuGUcddVR89KMfjTe84Q3xkY98JA488MC4y13uMjtnU+5/Prvsskucd955rX/SeLTav/GnScuXL599v/S16667RkTE97///dbmy+icbbbZZmLnBYDN3fbbbx9bb711zMzMTPzXv7322iv22muveOlLXxpf+9rXYv/994/TTz89TjnllPLvLTZGYC+66KLZv2IdEXHjjTfGT3/60/jjP/7jsa5t+fLlcd/73jcuuuii+O1vfzvR57DddtvFiSeeGCeeeGJce+21cdBBB8UrXvGKeTdQdt111xgOh/Ef//EfC/IDNbit0kBh0W211Vbx9re/PV7xilfEwx/+8HnnTU9Px2AwiJmZmdmxSy65ZM6/UhNxU79i1MZfCDb+tZ8rrrhizv++YsWKuPe97x1N08SNN9447zV885vfjD/84Q+t8X/913+NK664Iv2rLV2OO+64uOyyy+Kf/umfWv/b9ddfP3u+Y489Nqanp+OVr3xl61MNTdPMuactt9yy9U/0zeeud71rnHTSSfHZz3423vzmN7f+9+FwGK9//evj0ksvjenp6XjoQx8an/zkJ+OSSy6ZnfPrX/86zjzzzDjggANm/0rQpjr++OPjl7/8ZbzrXe+KCy64II4//vg5//um3P98Dj/88LjxxhvnPOvhcBhvfetb58y7053uFIcccki84x3vSDfFsn+euMtDH/rQ2HrrreM1r3lN3HDDDa3rj4jYe++9Y9ddd41/+Id/iGuvvXYi5wWAzd309HQ86lGPirPPPju+//3vt/73cX79u/rqq2PDhg1zxvbaa6+Ympqa/f1g9fcW++yzT2y//fZx+umnx/r162fnvPe9753tl9ySiy66KH7+85+3xq+88sr4+te/HmvWrIntt99+Ys9h9PdEW221Vey2225z/vr7qKOPPjqmpqbiVa96Vau3timfpoXbG59AYUlU/kngI488Mt7whjfEwx72sFi7dm385je/ibe+9a2x2267zWmKvOpVr4ovfelLceSRR8Yuu+wSv/nNb+Jtb3tb7LzzznHAAQdExE1/mN1hhx1i//33jzvf+c7xwx/+MN7ylrfEkUceeYsx1fe///3xwQ9+MI455pjYe++9Y8WKFfHDH/4w3vOe98SqVavixS9+8Sbf+xOe8IQ466yz4ulPf3qcd955sf/++8fMzExceOGFcdZZZ8VnPvOZ2GeffWLXXXeNU045JV70ohfFJZdcEkcffXRsvfXW8dOf/jT+9//+3/HUpz41Tj755Ii46Q/iH/nIR+J5z3te3O9+94utttrqFjenXv/618fFF18cz3nOc+LjH/94HHXUUbFmzZr4+c9/Hh/96EfjwgsvjBNOOCEiIk455ZT43Oc+FwcccED8j//xP2LZsmXxjne8I9atWxeve93rNvn+N/qzP/uz2HrrrePkk0+e/Q3EzW3K/c/n6KOPjn333Tf+6q/+Kn784x/HHnvsEZ/61KdmN91u/smdt771rXHAAQfEXnvtFSeddFLc4x73iF//+tfx9a9/PS699NK44IILNun+ttlmmzjttNPiKU95StzvfveLtWvXxpo1a+KCCy6I6667Lt73vvfF1NRUvOtd74ojjjgi9txzzzjxxBNjp512issuuyzOO++82GabbeL//J//s0nnBYDNxXve854499xzW+PPfe5z47WvfW2cd955cf/73z9OOumkuPe97x2/+93v4jvf+U58/vOfT39Adku+8IUvxLOe9ax4zGMeE/e85z1jw4YN8f73v3/O7zGqv7dYvnx5nHLKKfG0pz0tDj300Dj++OPjpz/9aZxxxhmlBsoFF1wQa9eujSOOOCIOPPDA2G677eKyyy6L973vffHLX/4y3vjGN87+FaJJPId73/veccghh8Tee+8d2223XXz729+Oj33sY/GsZz1r3mN22223eMlLXhKvfvWr48ADD4xjjz02Vq5cGd/61rfiLne5S7zmNa8pPnm4nVmsf+6H26/KP2XXNPk/Y/zud7+72X333ZuVK1c2e+yxR3PGGWe0/hm2f/mXf2ke+chHNne5y12aFStWNHe5y12axz72sc1//ud/zs55xzve0Rx00EHNHe94x2blypXNrrvu2jz/+c9vrrrqqlu8pu9+97vN85///Oa///f/3my33XbNsmXLmh133LF5zGMe03znO9+ZM/fggw9O/6nkJz7xia1/Ynj9+vXN3/3d3zV77rlns3LlymbNmjXN3nvv3bzyla9sXdPZZ5/dHHDAAc2WW27ZbLnlls0ee+zRPPOZz2x+9KMfzc659tprm7Vr1zbbbrttExGlf9J4w4YNzbve9a7mwAMPbO5whzs0y5cvb3bZZZfmxBNPbP0Tx9/5zneaww8/vNlqq62a1atXNw960IOar33ta3PmzPc6b/xn984777zWNTzucY+b/SeB51O5//mefdM0zeWXX96sXbu22XrrrZs73OEOzZOe9KTmq1/9ahMRzYc//OE5cy+++OLmz//8z5sddtihWb58ebPTTjs1Rx11VPOxj31s7Pv81Kc+1ey3337NFlts0WyzzTbNvvvu23zoQx+aM+f8889vjj322Nn35y677NIcd9xxzb/8y7/M+1wAYHO18dfK+f7vF7/4RdM0TfPrX/+6eeYzn9nc9a53bZYvX97ssMMOzWGHHda8853vnF1r46+vo/888U9/+tMmIpozzjijaZqm+clPftI8+clPbnbddddm1apVzXbbbdc86EEPaj7/+c+3rq/ye4umaZq3ve1tzd3vfvdm5cqVzT777NN86Utfag4++ODOf8b417/+dfPa1762Ofjgg5sdd9yxWbZsWbNmzZrm0EMPnfN7ipvPH/c5NE3TnHLKKc2+++7bbLvtts0WW2zR7LHHHs2pp57arF+/fnbO6O+fN3rPe97T/Omf/uns70cPPvjg5nOf+9wt3h/cng2axme0gNuXT3ziE3HMMcfEV77yldK/xAQAAGADBbhNu/766+cU62dmZuKhD31ofPvb345f/epXvf71HgAA4PZDAwW4TXv2s58d119/fTzwgQ+MdevWxcc//vH42te+Fn/7t39r8wQAACjzCRTgNu3MM8+M17/+9fHjH/84brjhhthtt93iGc94xi2G1QAAAEbZQAEAAADoMLXUFwAAAACwubOBAgAAANDBBgoAAABAh/K/wvOQqccs5HUAABP0ueFHl/oSiIiHrnhse3B6ujU0GAxq85KxmKodG4VzDKaSn62Vx5L1k3nNdHJsdm3ZOYrHNtPJetm89D7aQ/l9zF2vSe8hOWcyLZLrzdZrsmecDGX3n543PUc2LzlHdm/VYyvnTY/L1q/ea3W9PvOyiT3OUTw2U17vVmZQrXcW55XWS+YMsoxoOq+6Xp957cE+6w2GxXsbFs8xul7xnOk9ZPOS68hen/S+kmP/5YsvTk78//kECgAAAEAHGygAAAAAHWygAAAAAHQoN1AAANhEg/bPqqq9k3JnZMzeSXotm1PvJO19bB69k4ikd7GZ907K65Xn9Ti20BRJGyubU9tkwh0TDZS2rIFRvq9i7yNdb2ReOiV7TxT7JPm88ccGw+RrJZlYuNWbJF97WT8k+x4wmEnWq7xm2ffA4j1Ubyx7zQbVWM7N+AQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYIEMsohqMdSaHptEadMo67jx2jTwWoy+ptfRIxibxVyL51jwYGxE65onHYzNAo1pqLYaW82ir9U4atIpzu8jOW853jryPNPr7bN+Nm9y0dt552UWISI7seMWy4SDseViauXYasw1nVYLl/a5hexre+Jh2WqANW3rzh0cJGHd7LUeZItl3xeSwG16vVnQd4yPk/gECgAAAEAHGygAAAAAHWygAAAAAHSwgQIAAADQQUQWAGChZDHT0XBrzBN4zYKxWWw1C8amAdpCgHXc4yKiqR7bJxibRVn7BGOrYdFKqDW9tmpEtn3KxQjGZnHYNAJZPe8Ej+0XkV2EOOykQ7DFY8vnGHP9JZOWS9sGPWqr6bPL+qNN55RyfHXJwrLFKGsali2eOLuPmErWG3avlSq+huly1a+x4vvu5nwCBQAAAKCDDRQAAACADjZQAAAAADrYQAEAAADoICILALBQkphlGoxNw6rZWFL9TMOyWYA1OXZ0XnptSXy1fM5aWLYajM2vrz1UDsZm11eNno5c36IEY7Njq+HWLMqavo7ZvGQsjTQWzzt2RHYzisNOOgR7e4rIVuuo1WOL89I4anbo6LVkx018LPm1YtgjLJt9rxiNuUYels0eVPklq3z/yO41i+jWLi3/NXAmW69aoL1lPoECAAAA0MEGCgAAAEAHGygAAAAAHWygAAAAAHQQkQUAWChJzDQN3lUCr/McO0jPUYu3tiKvaQS0FpZN10/XS44tBmObbN4SBGMj2s/l9haMnXyAtnv9zSkO2+scmXJEdswa7FJFZHsEYwdNLSxaL6vWxiqx2T6nrM5Lv9/3CMum78/ivHS95ODs/dl6Hfs8qOz9X3yfpK+riCwAAADA5NlAAQAAAOhgAwUAAACggw0UAAAAgA4isgAAC2SQhUsH1cBrOyw7SMOyxYhsGv0cCaGm11sLvLaCtDFPMDaNoxaDsdm8JQjG3nTsyLPLYqnVe+gTjE36w+WgbfUcxThsPSLbXakcNz67KfMWI0Bbjr5OPDZbXG+BVYKs82nSN152kuy848dWW6cthmabYXH96nUkY9n3j0Eys1ccNvv+kT6E6noja2XfO5LXuhwRHvM6blowm3jLfAIFAAAAoIMNFAAAAIAONlAAAAAAOthAAQAAAOggIgsAsFCyYGwac23PS4OxSVg2Dbpm58iCrqPHViO11dhs9qO6ZL08KlqMuabX0h4qB2PTUG13lLVPMDa7r/QeegRj8+hr8dheEdlivHeCEdkli8NWry9Rjr5uxhHZNKxavY5yHLZ2bPr6pMdmB3deRn652Xs2a68msdlqWDZ9LyaDWVg2P7Z2jkHxxaiEb7M4bPlrJ31QxV97ZrIo7aZ/ofgECgAAAEAHGygAAAAAHWygAAAAAHSwgQIAAADQQUQWAGChpDHXpASaRkSLY9WgaxYMHT22GqRN1yrOS++hFozNr6U9lB5bDcZm0dMsDjm63mIEY4tB1l7B2HJEtvpa1NarRGQ39zhsOXKb6HNsagkislkbtH5wj3lZk7S4XiXAWg28ludl37KLYdns4w/5sbXoazVUm4d6S6dor1f+mkhem7RUnB1buI6ov09uzidQAAAAADrYQAEAAADoYAMFAAAAoIMGCgDAQik2RQZTyc+0srFqFyU5Nm9WjIxlPZVsrWpjJbuHYisle055n2T89kq1d1LpwGxOvZNefZK0WVJ8TptzA6X6TBajY9JnvaolaKBUOyalTsY866XPpNhFqbZSmtGTpGslPZFixyS9hWJ6J33ExWPT93txXn6OYlRkZCj99WMqeZ4ztesot1gyY3yd+AQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYKEM2j+rGhSjr/lYNd5aPMfoscX4ahaCTYOxaQg0OTZdrxib7bFeNRhbOXbiwdhifLV8X+XAa/FaepyjEoKsH5e917vXr17HfPMmHpatHpvZXIKx1euoBj6L0dfqKapR1sptNNmsLIRaDctm79nk2H5x2CRgnsRwy+/3auR39JeZ6kMvV3SzY2vfF2KmuuAtLwMAAADAzdhAAQAAAOhgAwUAAACggw0UAAAAgA4isgAAC2QwXQi3zjc2bgh2U8ZGrq9PCDadl6yXBl6rcdgsmFoO0GbXN35stjXv1hiMzdYrHzv+eWsR2eJrWI7j1o7tE5btFX3tEYItn3eCqjHXTNYt7VV9LcZmy+cdHcvWT+OwEw7LluOwyVCPUGv1HGOft0ccNvu1YpC+2LX1xuETKAAAAAAdbKAAAAAAdLCBAgAAANDBBgoAAABABxFZAICFkoZbk7JojxBsUw3VplHOkbEeIdj02tLQZu3a0ohoMUiaB13HD8ZWzrFUwdh6zLXPtSTn6HNs5fqKkdZxX6/51uszb+Jh2cRSBGNT1UhnMWaaxkezNmifsGwSb82MHpqeMvu2Ww3LJjebvqwTjhLncdgkyprVdstfA9mJu98s+a8LyXOa6VzqFs6RDI7x9eQTKAAAAAAdbKAAAAAAdLCBAgAAANDBBgoAAABABxFZAICFkgRY87BsbV4aVq2ul8VgR49NQ7DFYGwWKU3vqz2Uxv3SYGr1vMV51fhieo7KtSVrbU7B2OL15a9F7dj8PVtYr0+QdqnisH0islWbSUS22pAtX281BFs8cTote/8UYrPFDm6v6GseeK2dovpeTJ9d9Wug+twr61WvLdPj6658jg4+gQIAAADQwQYKAAAAQAcbKAAAAAAdbKAAAAAAdBCRBQBYKFmAtRiCzQOsxYhsNQY7ElttsuhtGu6s3ld7KI2KlqOvtRBsdawcqp3unle+jj6B13GDrJsw1mu99H2RzSusN+mIbDWYO+kQbJ/1NpNgbKoY5EzDnclYOejbIzbbDJN52XIj75XRqOx8l1GPvrZHB+lNZAfXztsnDpuHw2vXVwq19rnezYBPoAAAAAB0sIECAAAA0MEGCgAAAEAHGygAAAAAHURkAQAWSo/oax5qTX72VY3NVmKw2ZzknE0x+toncJuOZSHQ6rVUg7HFOOpo9LIafd2cgrH9IrKTvb7RY/PXofu46vrzrpepHjvp9foYXa8YfS0vX1yv6RF9rcZmy63V7D1VCMtm76c0LJu977L1s9e/WmQth2qTweJY+lr0Cb+O3nD6ppiwynVEjPVxEp9AAQAAAOhgAwUAAACggw0UAAAAgA42UAAAAAA6iMgCACyUYuC1HGAtzsvCqmkMdvTYQiw1PW6+Y7OIajH6Wg3G5qHa5NhqlHbM8OutMRhbj8iOf2/jRl57BW4XIw67CGHZzCRjs9UQbHod1WOLwdg+sdn0UrLnlAVdC2HZ9JH3eV0nfezEo6/Fc0xQ/rWT/TpWKwsPFjBU6xMoAAAAAB1soAAAAAB0sIECAAAA0MEGCgAAAEAHEVkAgIWSBl6Tn18lgdc8jppFXnvMG4my5tHX7Hprsdn0R3XVaGE1mFsNwU4X540Zfr01BmMnfS3Z651GiAvHVoOsveZVA7Q9QqD5erXA5SSDsen6yVg5LJuGO7MFk0P7BGOzeUkcNlsvfd8VwrKjUdmbJpWGyu+n/NhsYvYAatOqykHX4rW0xiZ8vYvNJ1AAAAAAOthAAQAAAOhgAwUAAACggw0UAAAAgA4isgAAC6UYeE3Hknhrk8Vm01Brsl5lXjUqmoUCp9uDeaQ1C7xWj02uZcLB2DyCWHguPeKjSxaMrR5bDbBWY7OFZ7A5xWGrIdjyOTILHIytSgOv1YkTDtBm8dY0tpo1uIth2fT1Hp1SDSFn11t9TxSvrdf3mWrQ9lYUdO1rnFCzT6AAAAAAdLCBAgAAANDBBgoAAABABxsoAAAAAB1EZAEAFkga7kwDr9V5yUmSsGwWZU3nDUb/e/zobXZsPtZerjo26ahiHjOtxmsrc8Zbq/9Yj2DsmNHX3uuNvhd7XEefqGYzlRQ0e4Rge4Vll0IxIJpGX7Nj08eZBHiThzLI3k/Zekm8tRqWLbVb+4SAFyHmWl5vib6ntoPbxZtdhIjuOMFcn0ABAAAA6GADBQAAAKCDDRQAAACADjZQAAAAADqIyAIALJQJR1n7zWtPa6ZHfpaWRk+TsSRSWw6mFo/No6fZtRTnLXDk9bYSjF2MsGwp8DjJtWIT4rCTDhonyt3KScZmq3HY6j1Ug7Hl2Gw2MVlvWPs6m2hYNvsW2x5alJhr9v1+kD5QFopPoAAAAAB0sIECAAAA0MEGCgAAAEAHGygAAAAAHURkAQAWylTys6rRcGtEXhVMg5l95nWXENMIYjHcmY6l56wVGbNYYhoprQZz+4wV4q236WBs9dhqCLOyXvG4rFLaJzab3msyLdUnLDvJYGxRGnjNJvYI0JZjs5WYa0REEgNOw7I9wq+t55LdV7JWvzhsbV6fc0w6kLwEb9nNgk+gAAAAAHSwgQIAAADQwQYKAAAAQAcbKAAAAAAdRGQBABZKMaLaTGdx0PbPucrzilHW1rWkQdbiWHJtWfS0GkdtpovzegRYy8cWQrX1+Gx17NYXjC2vV4i3NkkstBx97ROH7RXfrNZWa9OqYdnqaVvrV49LLiQN0Pa4/ey9k54jic2mgdPs9U6OrURZ02BsMrYocdjNSP5rSlYI7vjvfGiz5RMoAAAAAB1soAAAAAB0sIECAAAA0MEGCgAAAEAHEVkAgIVSDLD2m9ee1kwnPyNLI6pzx3qFYHsEFCc9thSx2TSgWg7L3oaDseWxkfhk9Zw93nf5eoUI5nznyEx6XqLUbs0mVaOvSR02DcZmsdkk3JpGWbNnXIy+ZsHhGCbXMmYgeNxI74IoB8Jr7+P0mSyBcmx3wa+km0+gAAAAAHSwgQIAAADQwQYKAAAAQAcbKAAAAAAdRGQBABZKVuhLw5V95tVKe2mkb/Qc1XBres7idWSR1univGqAtVeAdsyxari18jr0uY75zrs5B2OT9SYfjO0Rhx0zPnrTvM2kQFqs3maXmwdjk2OHSWw2+fpMz5EFY7P3TnIpWag2j6jWItytadlrnZyyHGnt9X6qLddL9ZqXQBbMHVRryBPiEygAAAAAHWygAAAAAHSwgQIAAADQwQYKAAAAQAcRWQCABZIF7/KQYXXe+OeonDdbq5nOYq7jB17zcGn1XrNz9BnrEaAtRE/Lz2TC99UrGJsGfavnyCKixfVGrqUUPZ7nnNU4cK8QbJ/Q5mIEOVuXnJVbk6E0GFsMwSaHDpIFm2GyXvbaZmHZSQdYC/PS0Gx6s+NfW6+3xFJFXzeXsGzy3knDwhPiEygAAAAAHWygAAAAAHSwgQIAAADQQQMFAGChJP2Q9C/dV1sZ08nEtDMyXsuk3Mmo/p37ahNg0v2Q9BzVbktxbLST0KcBc3vrnYz5PCM7Z7WVks4bv23SpBGM2rG95hVPW5k3SN8U2VoT7phkr2O2XvGZpE2Rcsum+3tKubtSm5Yq30Mft6MuykLyCRQAAACADjZQAAAAADrYQAEAAADoYAMFAAAAoIOILADAQskChVlYdqoWh81jlsl5sx+RVUKI6TmL15GGa2vz8murnrd4LeXY7HhjTfZaTzgEu9kHY7NzVCOvo+fo8dr0isNWj62qxmbL6yVjaYG0e6kmicNmJdQ0NptMzJYbJBeShmWT55R+r0ijtKXLK8evW1/KPV7/icdhq7HuPutN2m0sLOsTKAAAAAAdbKAAAAAAdLCBAgAAANDBBgoAAABABxFZAIAFkoVF07BsGjitBl2z9apjheuoRlrTmGnxXqvrjRvH7ble6RncloOxxfXGDsZm6xWPqwaTe8VhsxBsn5hnn7BsMejaGkpPWQw1J3XYLA6bBV6z556GZbOrG2bPPTk2Pbh06DyvbfcLmcZh+/SCJxxazb73DvI3QXJse+w21oHtxSdQAAAAADrYQAEAAADoYAMFAAAAoIMNFAAAAIAOIrIAAAslDVwmOb4koprGHIuRznIIdXowMqdYD5xwuDWP6FbXqwVo02vuE6odmdcvSFsc6xPHXapg7HQhGJuNFYOxTVbuTF+LHiHYSUdkM9k5xg3GZmPpm6e4VhZ9LcZh07BsOfA62WPL6417zkyv2HBx3u3cIHmPLSSfQAEAAADoYAMFAAAAoIMNFAAAAIAONlAAAAAAOojIAgAslKQ0mAdTi5XCbL00oloN1Y6MFa+tGnPNA4rFYG4aDC0+z4lHWbvP2ysO2+Pa0h+HLkIwNjs2C7+WI7cjsdnsestBzjRAO9mIbB4uTQusPVTDssm0VkQ2mZTFN7P1h9nBPcKyWby2+LU9SIO+40e400zv6LfFPtHXCb8lqvewOR17W+MTKAAAAAAdbKAAAAAAdLCBAgAAANDBBgoAAABABxFZAICFktUHi7HVPNyZhRarodrCWDEWWomqzjtWjYouShw2O7YazR1z/R5jaRw2fX3Gj7n2CsYWr6UUuU3fE7W1qhHZcgi2HDNN1pu0JMDaJC9a6/KyHm018JrNq4ZlswBv9v2j2IatPuP06saOBo9fUB03XLsJp2CR+QQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYIHkYdUsUppMTIKxefAwGUtDtd3rpddRDi+2h/rFZvvcf21eeo4xg67l6Gvx2qrHloOxWeB1qYKx04UAazEEm60/KEdkq8HY9lgWm5109DMLoeYvWlqI7ZySxkyH2ddEMSw7U5uWv9+ze6h9/yi/ZsXbGGvOElqMAG32pVLW59gRgzRyvLh8AgUAAACggw0UAAAAgA42UAAAAAA62EABAAAA6CAiCwCwUNJIaa14mMZm07H2cnmUNRsbdM9Jo7fda0XE5GOz1bBq8dj8WsacV10rUb6vPmOLEYwth2ULMdhqCLY81l6uGoLNY7NZlDY5uIe0l5mFVbOx0YFhslSyfDksmz2T7Bknxzbps6t9D0gfcVo4Hb+sOnre6suaXm+tjZs/90WIw27OeoVri+uNcw6fQAEAAADoYAMFAAAAoIMNFAAAAIAONlAAAAAAOojIAgAskGocNf2RVjHAmkVpxw66VsOtvcK1hevYhPPWjy3GcMe8j0muNf9YEnitPuN0bDMJxkZETM+dl4Vg+0Rkp4oFyalsvWK5M43NFmUh2Hxe+xzDJNQ6GFlvWA0aJ2Np4LRaOE2fe/WBFo+tfq/sEXSd1HEskgkHaG/OJ1AAAAAAOthAAQAAAOhgAwUAAACggw0UAAAAgA4isgAACyUNHmZh2epYe7lqMLYSUa3HZ3sEWYvH5sHcZL10LDm4R2y1cn39Yq7ZscX4anW96WqAdsLB2OS82fUNpodz/zuNwybHJaHRwdSwNZbFYfNgbC0i2ycYm8mirEkvNp2XXd9w5BEkj6Qels1uNTs2LdD2iL5Wx4qqgVx6GPPLoteX0wIGYzM+gQIAAADQwQYKAAAAQAcbKAAAAAAdbKAAAAAAdBCRBQBYIFlENf3xVTWWmMRR03OMOZZHX2tj1XOWY7M95lXPO9F5fe6/GIytP5NiMLYYeF3oYOxNY3OPzSKyUz3isPVgbDLWGomYmnBEdphFZLN5Sfm1KVRek4ZsDGaKgdfstc6ut0f0tUmfe+3g6nnHDcZm629O8dnqW3HS88a1WQVjh5u+oE+gAAAAAHSwgQIAAADQwQYKAAAAQAcbKAAAAAAdRGQBABbKIAstVsfayzVJRDYNlWbrpXHQkXlpjLG4Vo/r7TNWvtcFDtrmz3fCY+UobW0sDcamIdhiMDYZy2Kwo8HYiHbkdZAEY6fT44ph2aRcOZ0cm0Vks7dxJju2SQunbVnLMgvGZs8lC9C2s7FZfLa9VpOcM/sekJZAs3lV5a/32qG9bCaF2IWOufa20Nc36fXT2PKm8wkUAAAAgA42UAAAAAA62EABAAAA6GADBQAAAKCDiCwAwELJflRVjSVO18Ky5fWSEmYrhFqMlPYJPk465lp+xj3mlaKs5bWS0Gj1/qezsSz6ml1Ldt4sBJodWxsbNxgbETE1PTdoOj1di8NmIdh8LLm2Ylg2k0Vpq7LoaxabnUlei5lhMpiFX0eGmiSgOUiir9l9DYuR6zwsm81LxoqxXSas+jbu0V8tfalMPBg74fVuxidQAAAAADrYQAEAAADoYAMFAAAAoIMNFAAAAIAOIrIAAAukSeOL1SBjMphGWZP1xgy/VkKz844lx+Yx29q15WHV4vX1iNKm15dpPbtsrWrgtTpWWy8Ly5Zjs9m8ZGyQRF7HDcZGtKOxWUR2WRJ4nSoGY9OwbFK3zCKqfYKxmSwiO1OOqGYvWvvempGwbPY6ZGHZmexWk/vPvj1lg+XwNSXVt+IgeW0XQy0YO/61DZLGc597HedL2ydQAAAAADrYQAEAAADoYAMFAAAAoIMNFAAAAIAOIrIAAAulGlAsxlur640dg530+j2CsflzKs7rEYwdNzZbXr98ziT6OfFQbXbs4gdjs7EsGLssC8tOz7TXSsqQ2XpZWDaNyMb4kcph8mbMIrJTw/YLVG+tto8dDcQ2aQg2G2ufNbuOcvO2qvz9Lq3cTvZaFri/OuEmcV31vJMO1Y55v5OOw07qufsECgAAAEAHGygAAAAAHWygAAAAAHSwgQIAAADQQUQWAGCBNEmQMQ+GJoNp9DVZr0dYNUbWK4VmN2H96rHVZzJu4HW+89Yjv91j5ehrn8BtMQ6bhmWzgmISgs3isIMsDjvBYGxExPKRGGwWjB2dExGxvBiHXTboEZEt1iezOGx1rHqOagdzZjj3HHkwtjZWthiB1yWwkEHSiay3CMcudPh2ye5/DD6BAgAAANDBBgoAAABABxsoAAAAAB00UAAAFkra06i1Pfo0O6otk1ZTY8Itkny92rw+x5Y7I32e3ehYr9cw+Uv8ae8k66xkgYbk2KRtkq2XjeVdlMn1TiLazZNszorsuOQ6lk/V5i0btOdNuoGyYTjdHsvejO3Li2HytZc2VZLrmx55HWeS9ZOkUirvolQPrk27LZh4KyU7trreIndBNuW8S9Y7GSYHj7GeT6AAAAAAdLCBAgAAANDBBgoAAABABxsoAAAAAB1EZAEAFkg1SJqHUJN4axZCTUKTeai0OwbbJ2Zbv69krEdYt0+UNo/81sZG463lSG26Vvf6m3JteTC2PTRIzjGVHDuVBmOTcGkSjE3jrcm80UBsGpFN4rArl21or5/EYbMA7VRSkMwjsu3rHSYvWhZ4TdcbZi9a+49lTRaMTb5WZpJ5o+HX7Dqy+Gw9LNseW5RuafqN4VZm0g+quF413lqOvE5yvUkHc5uFezf6BAoAAABABxsoAAAAAB1soAAAAAB0sIECAAAA0EFEFgBgoaSB12r0tTZWDdWWoqyTXCv6BV6rx5ZDrQsddK0GXnvEYdPryAqNybwsGDsoBmOnkmOrwdjly9rx1iwQOzq2crodh81CsKumb2zPS2Kzy5OxZUkcNgvGZqoR2XXD9h+30qBrtl7y/WMqmZe2kEfOMRqV3SR9ju1jEU6b3VprrE/gtGrC57jVBWOLBj3isPlrvenr+QQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYIHUA6/twTyEWg3QVtcbb/1egdfs2hYhSrvQod60AZnEV8uR2ur1FkO1gyT6moZli8HY6SQYuyyZlwVjsxjsaDR2VRKRXbUsC8a2561MgrErk3nLknnT0b6HmeQhZ8HYDc10ayyVhGWza9mQvFmyAG02dntSCsFG/uUzUdUQ7ITjsOUQ6lIEYyMiCteX3UOx51x+/SfFJ1AAAAAAOthAAQAAAOhgAwUAAACggw0UAAAAgA4isgAAC6UcfR1/rBqqraxXCc1uynWUo6d97rV4zXm8NRtL6oNpqLUwp8czya+jNpbHYZNDkxBsGoxNxpYlIdhsbHlyjhVJMHU0GpsFY7eYro3lYdn22PJB+zqqbkyCseuGtXLlMHkTpMHYZL2ppPCZRWQHk6xopl/c1WMnO2/icdA08lopRE/2MiYeQu3x3Muh2jGfSz0+O976C72eT6AAAAAAdLCBAgAAANDBBgoAAABABxsoAAAAAB1EZAEAFkg5wJqGUNsT8/Vqodo8NjsyOPHAbe0e+sRrlyrKO9oezKKv1UhtGoxNY7M9IrJJuXEqmZeNTSfHTifzsmDs8iQsu3JZO+i6YiQim8Vht5xe3xrbIhlbnYxlwdhqRHYmeSHXNbU/Rg2TN89U8kewNA6bjE00DttD1hQtx2YnHJYtx0GX4NGV47CLEVbtE6rtEfktnaNXHDY5uBh0TuO4HXwCBQAAAKCDDRQAAACADjZQAAAAADrYQAEAAADoICILALBQimHV6rF9oqeV9coB1WIItlfMtbher2uuXl92LaNjfdYqB2OTQ9OIbDavHXjNI7Ltecuma3HYdGyqPbZiqh2RXTUSjc0islkwdqvpda2x1cnYqkH7nMuTscwNzfLW2PSw/UzS2OwgCcYuQgi2qQZdF9piBGOrigHW0ZenGkbtFYfN9Fhv7Jhrz3OU1iuvn0zsdf+T+brzCRQAAACADjZQAAAAADrYQAEAAADoYAMFAAAAoIOILADAQukTUZ1gHPamY9uDrfUmvP6k76scqq1EXyOiSSKq2TnSluNogLe4fj02W6tAphHZZF4ejG2PLUsistPJ2PJ0rB2MHY3D3jTWjreORmO3TEKwWTD2Dsuua42tnmrHZrecah87HUkINnmjTA3bz2mYvODLB+37z84xNcjGaoHLScZhs7XSbmf6RZvNq564vd6geI7SF+N886oq0dMliNnOp1e8duLB2O6JWcw1+ZIoX0evOG7xvDfnEygAAAAAHWygAAAAAHSwgQIAAADQwQYKAAAAQAcRWQCABZKGW7PYaDHAGpUQ7DxjlXhpOeY64XmTDutOOsBbiryWQ7hJybAYm02Dscn7aZAEXrNgbBaHne4Rls3mrZhuh1VXTnVHZFdPt0OwW0/f0B6bao9tO90Oy2YR2amkIHlDs7w1NpO8edYN2vOme8Rhhz3isNmxo2PV+Gwali3Oq3/hFRWPTcOi1Xl9YrCjesRh68HYrN5bO7Z+LcVzTPLe+oRwk8hzr8hvB59AAQAAAOhgAwUAAACggw0UAAAAgA42UAAAAAA6iMgCACymCQdTq+corbcY8dVFuNd6QbJ2bB7+veX/joj8R5XjRmpjnohscl9ZMLYeka3FYZdPteOwK5I4bDa2cro9tnpq/S3+d0TEVklE9o7Lrm2NbTfdHtty0F5vJnnwV86sbo2tGLTvdSoJxmbywGvtZ9jD5PrSsSz8OjKWz2mfMxvLv0CTeZlqHLRPkLTXWOHeyoHX2li/6x3/HOVgbo/7La3X671TOzgL4eZx3E2vzfoECgAAAEAHGygAAAAAHWygAAAAAHSwgQIAAADQQUQWAGChpHHUJOY44WBqdo7Sej3Wr4ZgFyNK2ydUm19fIUCbRFqbalUyjcNm15EMlSOy7ehpdmyfsOyK6XZsdXkSW90iCcSuHInNZhHZbaf/0B6buq41tkMyb+vkXq9JXuzrhitbY5ksBDuTjSU/r07DssU4bBqDTa+ve85oaPamwVpsNv1Cybq6ixBRTU0wVLsocdhMjxBsn+ur328WZe24sIjI+stp4DU7tk8cNz1v8dib8QkUAAAAgA42UAAAAAA62EABAAAA6GADBQAAAKCDiCwAwAJJmpK5NBg6fqh13LDqbSX6uiTXPOn1s7BsdSyLw6bB2Nq8ZUn1sTq2cnpDe14SoF01deOc/149ta41Z8tkbLvpdkR25+nlrbHVUytaY9fdeG1rbCZ5MbIQbDavHIJN5m0YFmOz1bDsyFgWjB0Ou4+7aTAZqoZlk7FB8Rz5WLZee9pEw68TDrJm8ustRlp7hGqr8db6eoVz9Hl2o3XkTVhvUnwCBQAAAKCDDRQAAACADjZQAAAAADrYQAEAAADoICILALBQJhxMXZT1JnjOXtfW41rykmHt2HKodjTA2isiWwvB5q3h9rypalg2iblWx5ZNzbTGlidjywbtsdFgbETEypGxbM6qQXtsy0E7Urt6aqvW2HXD9a2xPzTtPwrd0LQDtDcM22PrkrFs3oZmujV247A9tiEZm0nCstWx0RhsNRib9jjLXxTZwbWxQXJ9Ew3BRvS75sJ1VK+t1z1kFjqiuwDr1Y6rPYAshJsHeKs15FvmEygAAAAAHWygAAAAAHSwgQIAAADQwQYKAAAAQAcRWQCABdIMkkhjn2Bq8Rzl9UbH0lbk+PfQJ4TbZ6zcvJxggLapVhbTYGz3+jfNGz8Ymx7bY2xZEpadSkqQ6XrJvOmRselIYrbJcTPJg/qvDde2xq5JXuzfzWzdGvvDcGVr7LpkLA3GJjHXdTPtP25taNrzqmMzyX3MJAHW0XlpMDYLyyZjkY31irkuzdi4X6L9wrXjj/X4lrIAcdgswJrMSwxGvpTTwGt2XPXaMu1vH/Xn1MEnUAAAAAA62EABAAAA6GADBQAAAKCDDRQAAACADiKyAACLKQ2G1kKtfaKslfBref3MEkVfe4VqM+OuVz0u+/FlOSxbDMZmp82itOm88UOw2bw0BjtalYyIqWRs1PpmujV25XBFa+ya5Nr+0LTnXTlc3R6b2bI1dl1yjnXD9h+jrk/m3ZiEYNfPtO8jC9DOFMeyQOxwZN4wC8amX1DJUBbaTMOy7bFBdo5ylDZbrz1t0mHV1tgiRF/TsGqfe0j0Oke63pjnqL5ew8leb/ZGrgZtb84nUAAAAAA62EABAAAA6GADBQAAAKCDDRQAAACADiKyAAALpRgurQRee69XaT72CbL2ib5myufNioTjrzd2qHbi4dpaWDYby2KuWWy2HIctVhqzEGx2jsxwJLaaBWNvaJa3xq6caYdgh8nPiP8wXFk69tqZVa2xLCKbhmVn2n+0Wp/EZrOxG4eTDcuORmOrEdkmC7yWo6/JWNIGLodVs65wOaza45pHr2OhI7XzjFXP0ev6EhO/39JxtYvLoq95uHbT47BVPoECAAAA0MEGCgAAAEAHGygAAAAAHWygAAAAAHQQkQUAWCBjB0nnGWuSYmh5vczIvGz9zKTvq4/qtVQ7k6liCLV0XDHwmj664nWUg7Hj3tcmHDtMHvJM0/4Z7o0j0dgbmnak9Zph+5zXRTsOO1OMyGYh2GuyiOxMe971M+2g7bosGDszfhw2n5c8z0JEtknmNEmkNY3IpnHY9rxBr9hsj2N7hFVLY0sUgu0zr19sNouyZscm37fSaHAz8t89ri1TDBXn77tN/x7oEygAAAAAHWygAAAAAHSwgQIAAADQQQMFAGAx9eiM5OuNP69yjkVpmyzZObK/eF87thk9ttwnqa2fdlGK8/qorpe1TYZJ2yTrkYz2TrKxrE+SnTNzY9P+I84Nydh1M+0uyrXJ2B+SsayBcsNMct5k3o3D5P57dFFGeyfZWJM8u7x3koz16JNkXZS8k5GMJadIj82uL5mXjqXXXJjTq2NSa4z0a5uMf44+91Yay64ta5GU18+OTb5/pmPJeh18AgUAAACggw0UAAAAgA42UAAAAAA62EABAAAA6CAiCwCwUIrR00kHY7P1SudYhHBr9drK97AYsdlx5/W5jgmbdGw2k4dl22NZRHY0GjuTBGnXDdpB1kwlUhsRcd1MO1R7fTqWBWPbY+uH7T9apcHYmdrYhpksGFuLyDYj85okoJpGZNP4ajEiW4y0lo+tRl8nPDZ6jixc2yv6Wo3oTjjmOvE4bKIcry1cRx59LV5I+h6rxWa7+AQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYIGMHXON6BegHfccPa6t171O2qRjs5MMxia1xMFmFJttshBscnPVsQ1JvHVdFnMcMTMY/+e8WTB2XRJ4zaKvWTA2G1s/0z523YYkIpsFY5MQ7EwSap3JXotkXhqRHTk2D8YWx9IQantePlY8tj1t8tHTMcfS6yiHYItR1V5x2Cy2Wl2vdmx6jjEjv71ew+Jzz+TnFZEFAAAAmDgbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYDEVY6ZNUhadaDC2aFHisOXoa1YonOylTDTy2+faqmXEojQOWw3GJvM2JCHUGwftYOpUWoJsGz3HukH7jynTSUFyJvl58IZhLSK7LpmXhWVv2NCOyN6QRGTXJ+tlwdgNSVg2G5uZSWKzydgwOUczHP3v8SOyWfQ1i3n2CYFWx+qh2mRs3Bhsn/vqEcKtXm89Njv+tfQbGxlMItJ5CDcL8NbG0mMz2fPs4BMoAAAAAB1soAAAAAB0sIECAAAA0MEGCgAAAEAHEVkAgIWShGAzEw2XbsK80VBtrzhsOQTb4xxFacdwMWK4tzJpRLY4loVaN0wlRcYkypoZDdBOpbXMZPkmibQmYzemwdj22A0z7WDs+iTwui6LyCbzbkyDsUkcNgnBZsHYLAabjo0eO+EQbD3mmsxLTrF0gdPkvKP3UQ6yZjHTMc8573lr58jXywKsxXOksd3itTTdc1LFEHAe9C0GaMfgEygAAAAAHWygAAAAAHSwgQIAAADQwQYKAAAAQAcRWQCARTTpYGx1vbGDqYsRgi2eY8lCsEsRm01vrBZBbIoh2Ly92J6XBU43JDXHLKI6nKpdy9TIellENgvGptHb5AXLrm1Dk8RhNyRx2CQ2m8Vh07ENyXmLwdhhEmAdJsc2M4WwbBaRTZ5dFn1NY7PpWJ9j20P1UG0yVgyrVqKk1cBrvzjseNfWd73Jh3qTwdGga5+10nBtNd5bO7aLT6AAAAAAdLCBAgAAANDBBgoAAABABxsoAAAAAB1EZAEAFshmH4ydZBx1KUKrCyGtDy6sascwbyoWg7HFedVg7GA0DBkR2R8thk0Sg52aaY2NRmQjkiBt8R6yOGwWbr0xicNm87IAbRaM3ZCEYKvB2JkkBJsFY4eVYGxEO8qaHLdZxVz7BGj7xFYL6+X3UA2XJscuQhw2/2aRHJvdR/o6FtcrjOXR1/GfZytSGzFPzDZbT0QWAAAAYOJsoAAAAAB0sIECAAAA0MEGCgAAAEAHEVkAgMW0VLHVynmzOT2uN4vZlgO3twU9erRZ9DWfVzvtTBLpnJ5KIrLJeaeSmGk7+hpppHE4yCKvxfVG1+oTwk3Omc27MRsrBmOzeXkwtj3WJOdt0vBrcWz02DQgWl2/PZR/cWfzamPVOGp5rBi0LYVlJxik3ZRrS0PNfdbr8fqUY7iVa06Py87ZJzZbO1ZEFgAAAGAB2EABAAAA6GADBQAAAKCDDRQAAACADiKyAACboXJstU+UdcxjJx2CvV3FZtMbq4UMs7Bsn7EsopqpvhTDJEqbxWGnxqzrDpMrKUdkk7EsmLuhGIxN1ysGY4fZWBJvbbKxLCybjY3cWx6MbQ+lQdLqWDVwWg3VFqOsfSKvlfvIYq7lIO0ihHXLJelygLYar609l9H1yjHXcqS2FrQuz+vgEygAAAAAHWygAAAAAHSwgQIAAADQwQYKAAAAQAcRWQCAhVKsbzaD4sTFCMuOaVGir4txjrRoWwwcjn3O2vJ5e7EYjE3G0phn8eered+xFpEdV7Z+GpEthmVnkvsfVgO0kw7GJiHYJjk20gBrIcpajLlWo6/1YOyEx3qctxygHT22eM56pDWb1yMEWx2b8DnyoGthveozSb7hVSO61VBtOq+DT6AAAAAAdLCBAgAAANDBBgoAAABABxsoAAAAAB1EZAEAFtGixFbT83afeKmu7TahGGNMm4VZ4LU4b5gFGZPXOhvLwqpp4bH4M9dhctHjRmSzOGwmu4c0opuEUPOwbC36uijB2GReOpa9B0bmjf73fMeVgrSbMFYN1VajzBOPzRbipX2Csfm8Whx14iHY9P7HL1jXw68jY8XAax6pzea1h3rN6+ATKAAAAAAdbKAAAAAAdLCBAgAAANDBBgoAAABABxFZAIAF0ivKWj1W+HXzkL3YxbhjFjLMm4rtc0ylx2ax2eT60oZsFmVtnyPrO04V34uDpD6ZXXPlnHkwNom5Fp/JMAvLZtHXYmw2Dcb2CbWmcdBsvZGxQmj2prFkXjVIWr2v8nrJsdUAbY95o+ftF6QtXseEz9ErNpuNzYy/3ugzrj6TfF4hUttzXhefQAEAAADoYAMFAAAAoIMNFAAAAIAONlAAAAAAOojIAgAstc04BNsrhDtpm97722RZuDDtDI6OVZ9TulYWoM0upBgzTeubaTE2OUVyjqn2vOHMdPsMafVxPFn0NW9KJvPKwdjxw7JZCDYNxs4kzz17eZJjWyHYyMOvpRBqMdJaD8ZOeKxPRLXXvEIctVcINouZ1q4t/x5QPMe49zrfemnkdcxQa5/Aa/U5leeJyAIAAABMnA0UAAAAgA42UAAAAAA62EABAAAA6CAiCwBwK1ENuk563lJIY64TXi9bsHqOwcjDa6qLVSO1SUA0Lzkm62Uh1OQU2c9SB8k5miab115tOMGIbB6Hrc2rxmGzAG963uTh5RHZCQdjs/dAFgdN1mtHZJM51ZhpGoLtPmdEbEJsdfOZN/pc+kRqy/ffY6xX5Db93jP+97L0vVKI8g5mat8Yq7HZcpQ2/eZ7y3wCBQAAAKCDDRQAAACADjZQAAAAADrYQAEAAADoICILALCYNuNw621F9ojLqcBxg4zl42ph0DwOm8xLJG3UmEqOzbqNU8mxg6wYm8gCtFVZvLU9pz2WxWGzZzxM47C1sSzwms5L47DtoXIwdqY9VI23tsKyxThsNRhbP7Y2Vg7BVtdL5yWx0cqx5fhsFjOtHZsHTrNja/eQR1SL66Wx2cmFWgczWZU5++JuD/WbJyILAAAAsChsoAAAAAB0sIECAAAA0MEGCgAAAEAHEVkAAJZUFi2spv3Kx44bh43Iw6+jE6vHFeOOWaS0Txw3C8sO0thqWpBsD6XN1/ELya3TZtHXbCxdLIu+JtN6RGTzseRaJh2MTeZVrqUacy1Haqtj1QDrpNfLoqfVeO3IWJ/obfkeyrHZ8ceyYGweVi2eo/z6NJ1zsu+L1UjtuDHbecc6+AQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAgHlVI62TntfnWvITZ/NqWdb0vKOhyTR6mkUbk3Oml9E+tldYNruH7FKyOmwWkc3O0UPrDOnzrByYx2az554HY5NzVCOyWUBzpnaOPsHYbF4lhDrpsUyfAOuk540btK2EUee7jmrMtU8It75edaxHqLUSqk2Paw/1m1cM0GbzOvgECgAAAEAHGygAAAAAHWygAAAAAHTQQAEAWCg9QhFpnmPCRv/++6b/bfAFVL2YHvOyv/+fNzWSeaMtinIEJlsrGcsmTvXoomSD6Vj2oJJ+SHJoL61MQq0xUm6lVDsmaWNh/I7JUvROsnl5OyNZP23AJGPVPkm1n9JrXvFrr3gfo2NpY6TaE+nRSqn2U/Jjq12Q4rXM1PoheT+l+9qqjZVyi6XPWAefQAEAAADoYAMFAAAAoIMNFAAAAIAONlAAAAAAOojIAgCwScq91GxeWjgtnjgNi9YuJumg5qetxhxHo6dZ3DKLr6bRwiw+WpyXPOQmnZddX3ssP3YR8sIjr236mMph2WSsGpEtxzyTY2dqUdY8jtojGFs4Rz1cO976izaWhUXLx4553nGPm++cxThs+vpU1yuPZeetRXPzr7NCqDWN+WbR2+q8YoC2OK+LT6AAAAAAdLCBAgAAANDBBgoAAABABxsoAAAAAB1EZAEANkPVUOskj+1zzrJyMLY91CteW40gJqHSLLTYGsoCp1kYMom0NlM9wrLVOm42LQv6ZmPVsGy6YFHreWZzekRk09enPTQozkujr9WwaBKbza+lul42b+Qc6T0U1+8TUe11jmIwduKR17mDafQ3fY8Vg9bl+yp+wyuGVfPzFr9ZZlHWdKz7+vL7qq1fvbZeYx18AgUAAACggw0UAAAAgA42UAAAAAA62EABAAAA6CAiCwDA/HpEX6sx06ULxk5uvfJxWSyyT1i2+lCy2Gy1+ZqWIYsHV8Oy40ZkK2tFElWd7xxpfLQYfe1z7CSDsck5+sVcxx/LX4tk3hIdWwm65qHZ6vq1ayt/f8qud6YWpS2/36vXVw3Qjp6jMmfeecUAbfnaRGQBAAAAJs4GCgAAAEAHGygAAAAAHWygAAAAAHQQkQUAuJWohlXzY9szm3JFdMyTFpXvqxoWzaKCxWvOg7Htc4xGCvPMajEOWw3LpuXK5MTV2Gx6aI9gbFU1Bts6rj1UjsOmEczsdc3O0eNaqqHWNA7b4xyjIdRs/R6R1snPGz+EOul4a+vZVa83jc3W7qt6/+Wgbfo9sBhWLR6bx2YLx0742iY+1sEnUAAAAAA62EABAAAA6GADBQAAAKCDDRQAAACADiKyAAALZcKx1YmfY/TYSa4V/aK35eBjcV7aCswijdmPF5N5zdTcmGca6S3HYZNzJjcxGCQTs4ZqtZibnTe7mGrztfriFuYN0jhwca0sDlt9/fvEZqvnKI+NF4yNaEdjlyoYmz+nali1ul5xbNxQbRofLa6fxoGL3xiz85aPTcaq79n02Oxa2gsOZgrffNNnXr3X7DUsfsOvzuvgEygAAAAAHWygAAAAAHSwgQIAAADQwQYKAAAAQAcRWQCAxbS5h2ULa/WJw5aPrZ43jSBmAdIkPtijKTh6bJOFRqeKYdnkR5rZenkZMxnKYrOZahy2j3HDssXXtRrBLMVCYxPCrT3Ou9DB2Ij29U08BFseKwZjez274nnHjM1Wz1mOuVavo/xMsnhtMbaaHpvEYdNj20PpN9CZ0eeZHJicM4/3Tngsu5YOPoECAAAA0MEGCgAAAEAHGygAAAAAHWygAAAAAHQQkQUAWGpLFZad4Hnrgdfs4MmuVw18ltfLmrQj7cFqCHaQXFz6SLLAazKY9XJT6UNZIpUY7IRf6zTS2mu9ZF4xDluOklaDqZXrm3QcthBf3ZT18iBpbazPeSv3lgdeq+fsMS8LwaYx7PEjqvl67UOrx5bm9Qi8ZoHb/NgeYdkOPoECAAAA0MEGCgAAAEAHGygAAAAAHWygAAAAAHQQkQUA2BwtcPQ1M+n4aq/AazKtul5+fVlEtBZ4TCOvIz+GHI3KRswTn53KoqLZjSXHZrHd5NrSwbRKO2HV92dhXvV9kgZpi0HSXsHYYqh2SYKxyXq9oqo9oq+97rV6LX2+HxUir3ngtbZWfh3V6Gl2bDJWfJ/kx1aDrtl6Y84rnjN/nslYOSzbnteIyAIAAABMng0UAAAAgA42UAAAAAA62EABAAAA6CAiCwBwa9Yjjjka2muySulmpB6MrR0bSfSzmUpihtl5R0OTWfQ1OWUawUwOToOx6U1k69WmZapvgeql1MOyc09cfw+3h9JbqIY2y+HW5Cx9zjHBYGw2b9LB2Ep8NSLKYdn6WO281WNL76kJR7Orr2sery3GVtNj2yfJv86y74HF8GtlXnGtRRnLnl0Hn0ABAAAA6GADBQAAAKCDDRQAAACADjZQAAAAADqIyAIALJBxY643TdtMgq59Iq19oq/FS6ley2j0NSJikP0osXrsyAU2yVrVVzB7JlmUNh9M1uvx1unzrusVli0c2ysOWw2yJg9v0nHUcmx0zGBsNrYYwdjyWDm2Otlg7LjHDmb6rF8MwfYIt1ZjrnnQOAnLJtdXnlcJtaava4/oazZvpv2iNemzy74IbplPoAAAAAB0sIECAAAA0MEGCgAAAEAHGygAAAAAHURkAQA2Q/UAbTI07rGLEIctR1+TQ7OKaHpsNUqbhjuTmVNZ5Hf0uOQEyVJZ4LXYhs2fe+20/eqwxWvpddoJvhfzwGsSh+0Tm51wvHbSodpKRHZRgrGTjr6WA7TVse7z5jHX9lA9epodm4z1eI+VY6vp+67HvJlk4miAtkccN7+29jnTYGwSls1/4bplPoECAAAA0MEGCgAAAEAHGygAAAAAHWygAAAAAHQQkQUAICLyWGKTVU97xGGrAdo0PlqNqCYdwyb5sWF9XlZ+Hf3v5NlVg7HF2GymT4B2UfSJwVbW6hGHLQdjqzHXCUdkJxqWvTUGYye+XjIv6Yq25pXX6nMd2bG1KO0gCbfm78Xk2GKotTqvMpZdbz3AWw3L9pjXwSdQAAAAADrYQAEAAADoYAMFAAAAoIMNFAAAAIAOIrIAAAulR8xy0mOVwGU5XNojDlu+h0wxNjvxsGwWgx05tkmuZFCs3pZjs5nqvMXQ53UcHUseSq84bDavTwh20gHa7Nji9VWOvS0HYxf6+2f6zMtjkw2y5uu1D41hEpZNQrXZvOx91yfU2nrNsrXKYdn2vCZ9dtl9Fed18AkUAAAAgA42UAAAAAA62EABAAAA6GADBQAAAKCDiCwAwK3YRIOu1chitdzaI+aZBl6L/cjq9ZXDstlyo5HObFK1BJseWyuyVsO/k1a8vHoMtnVcbWwx4rC9zlu9lh5h1fy93Yz89/jr97mOfmO1L/hyvHamOK8S4E2CrPlaWTC1GIJN3yfFb+7lEGx7aJCFZauR18pY9biZ5AXrEbPNgrFNdl8dfAIFAAAAoIMNFAAAAIAONlAAAAAAOthAAQAAAOggIgsAsEDKYcByQLEd5OwTEW1dX/E6miwMmp2g2PYrH5sFD7MfBxbjsOWwbHKBrT5sMid9bbK10lBv7YVNA7cTDsvWg7Hjz6u9FwvHbcJ1ZMfWI60LP68aR62stxjB2EqQdf6xCR87ZjA2ItrfpIrPPI++JtdRjMMOksBpPUpb++ZbnZfHW5Prq8Rmy9HX5NeedP1kLDtHOiYiCwAAADBxNlAAAAAAOthAAQAAAOiggQIAcGvRq6mSjI22MvqslZnwsWnvo0cXJe2WZNMKz67USZnnnJlebZvqYPW1qJ53gq/3pNsmvfoki9A26dMUWegGymbfO0k7I9VuSdYemTtW765k1zZ+iyR/Hxc7JmmfpNo2KV5ztWUy2i0pXm+TvoaFxso88/L1Nv2boE+gAAAAAHSwgQIAAADQwQYKAAAAQAcbKAAAAAAdRGQBABZKGq0rlksr0dfYhNhmoZ+XxkeLIcMmOTq9tqwBmPxILws3pvOyyyuGZYuPOA+6VqKn2UtdjMOmAdrNyYSDrpNcq/q+m3hstnqOYvS0X+R1kmstRvQ1G6ueN4uSFq8lDb8WrqMaJC3GV9PAaxJWTWPYaZS1OG808DrfeXvcb2tsZqZ2XDlSm6yXvieK99rBJ1AAAAAAOthAAQAAAOhgAwUAAACggw0UAAAAgA4isgAAS6wegk3irVltdNywbCGWOt/YIBlskmJqNVSbNlknHZbNJo45VgnNRszzWmc2p4jsmNHX+VTe733iyL1isxOOyJajrNX1qgHWUkS29oXXKxhbfO55WLU2r/xMymNzB9NzpvHZ4jeyahw1fcbJYHYtk4y+RsQgi82OG37Nfh1L18+ir7VzNsVQbZPdQwefQAEAAADoYAMFAAAAoIMNFAAAAIAONlAAAAAAOojIAgAskGqkMg2QZqphzWS9yrVkPb0+0ddyfLR43mpYNj04+bFhuedaecbV17A4b8Ld1l7K4dvivNLXxSLEYcvB1PKxPaKsaWx0/GPbEdklCsZWj60GY3tFaWvh19ZYn/dEEkfN72v8EGw6L43N1kKw1eurrjd6LWm4NQvGFufl6xWfSXavHXwCBQAAAKCDDRQAAACADjZQAAAAADrYQAEAAADoICILALCY0uBdUhYtx2Fr65UirxOOvmb32iQzs9Bkk/yYrxqWzZRjs9lLUTjxoLhW/YKL8yasHIzNVOOtlfNOOg5bDJzWY7O1iGo11Nor6Fq4vn7nXPhgbK/IbzEYW35/VgK86TOpvieK15tGT2vrpXHU9LzVeGs1LJu8uKPzypHaZK30tcieUzU2mz3QW+YTKAAAAAAdbKAAAAAAdLCBAgAAANDBBgoAAABABxFZAICFUg7BFo8tdhHHPm9x/ex603lZn29q/LBsNfpaDcGWw6+VsWIbuCq9tsUwbvR1E9YrHdsjDluelwZZa2/uasx1cSKySRx0dN5mHoxNjy2eI4+SVq8vO+/IWBqHXYwQbHtokK036RBses3jr9eMXl8Wbi2+rnkItjgvidI22Xk7+AQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYIFUo5JpbLQalk3jsLWTtGZl4cXsMrK4YfJjuUmHZTPVR1edl56juuA4c+abt+ltw03X4xx9IrKTDMvWw63F0OikQ7CTjshWgrHZWJ9waznI2p63GMHYetB2vBBqr7XKIdhaqLZPCDb/GihGedOwbHssj7yOzEujt8W1khBsNXqbBmOzX8w6+AQKAAAAQAcbKAAAAAAdbKAAAAAAdLCBAgAAANBBRBYAYDGlkcokIJiVRatR2uJ5W2corpU2T6sB2my9rOOXliuT9QbtwWQov5jiWCnym8Z8k+My1djspPUJ1Y4bgq0eW47PTjgOu1lFZLOwaG29dkR28w7GVmOm5WBsMbaaX9/IWDW+moVgs8Bpdh3FEGweYK3dV7pejyhtU4y3ttbLwq0LHa6dd2zTvwn6BAoAAABABxsoAAAAAB1soAAAAAB0sIECAAAA0GHQpNUVAAAAADbyCRQAAACADjZQAAAAADrYQAEAAADoYAMFAAAAoIMNFAAAAIAONlAAAAAAOthAAQAAAOhgAwUAAACggw0UAAAAgA7/D4/V2V/1O4EtAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens = caustics.lenses.MassSheet(\n", + " cosmology = cosmology,\n", + " name = \"masssheet\",\n", + " x0 = torch.tensor(0.),\n", + " y0 = torch.tensor(0.),\n", + " surface_density = torch.tensor(1.5),\n", + ")\n", + "sim = Zoo_Sim(lens)\n", + "fig, axarr = plt.subplots(1,2, figsize = (14,7))\n", + "convergence = avg_pool2d(lens.convergence(thx, thy, z_s, z_l).squeeze()[None, None], upsample_factor).squeeze()\n", + "axarr[0].imshow(np.log10(convergence.numpy()), origin = \"lower\")\n", + "axarr[0].axis(\"off\")\n", + "axarr[0].set_title(\"Mass Sheet Convergence\")\n", + "axarr[1].imshow(np.log10(sim([z_l]).numpy()), origin = \"lower\")\n", + "axarr[1].axis(\"off\")\n", + "axarr[1].set_title(\"Lensed Sersic\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6c44b6c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PY39", + "language": "python", + "name": "py39" + }, + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/testbook/MultiplaneDemo.ipynb b/docs/testbook/MultiplaneDemo.ipynb new file mode 100644 index 00000000..ea186abd --- /dev/null +++ b/docs/testbook/MultiplaneDemo.ipynb @@ -0,0 +1,296 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2a324070-a163-4b79-8e77-819da73083f3", + "metadata": {}, + "source": [ + "# Multiplane Lensing\n", + "\n", + "The universe is three dimensional and filled with stuff. A light ray traveling to our telescope may encounter more than a single massive object on its way to our telescopes. This is handled by a multiplane lensing framework. Multiplane lensing involves tracing the path of a ray backwards from our telescope through each individual plane (which is treated similarly to typical single plane lensing, though extra factors account for the ray physically moving in 3D space) getting perturbed at each step until it finally lands on the source we'd like to image. For more mathmatical details see [Petkova et al. 2014](https://doi.org/10.1093/mnras/stu1860) for the formalism we use internally.\n", + "\n", + "The main concept to keep in mind is that a lot of quantities we are used to working with, such as \"reduced deflection angles\" don't really exist in multiplane lensing since these are normalized by the redshift of the source and lens, however there is no single \"lens redshift\" for multiplane! Instead we define everything with respect to results from full raytracing, once the raytracing is done we can define effective quantities (like effective reduced deflection angle) which behave similarly in intuition but are not quite the same in detail." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "45b6a8b4", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import torch\n", + "from torch.nn.functional import avg_pool2d\n", + "import matplotlib.pyplot as plt\n", + "from ipywidgets import interact\n", + "from astropy.io import fits\n", + "import numpy as np\n", + "\n", + "import caustics" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ab43e042", + "metadata": {}, + "outputs": [], + "source": [ + "# initialization stuff for lenses\n", + "cosmology = caustics.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology.to(dtype=torch.float32)\n", + "n_pix = 100\n", + "res = 0.05\n", + "upsample_factor = 2\n", + "fov = res * n_pix\n", + "thx, thy = caustics.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "z_s = torch.tensor(1.5, dtype=torch.float32)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ea49d25d", + "metadata": {}, + "outputs": [], + "source": [ + "N_planes = 10\n", + "N_lenses = 2 # per plane\n", + "\n", + "z_plane = np.linspace(0.1, 1.0, N_planes)\n", + "planes = []\n", + "\n", + "for p, z_p in enumerate(z_plane):\n", + " lenses = []\n", + "\n", + " for _ in range(N_lenses):\n", + " lenses.append(\n", + " caustics.PseudoJaffe(\n", + " cosmology = cosmology,\n", + " z_l = z_p,\n", + " x0 = torch.tensor(np.random.uniform(-fov/3., fov/3.)),\n", + " y0 = torch.tensor(np.random.uniform(-fov/3., fov/3.)),\n", + " mass = torch.tensor(3e10),\n", + " core_radius = 0.01,\n", + " scale_radius = torch.tensor(10**np.random.uniform(-0.5,0.5)),\n", + " s = torch.tensor(0.001),\n", + " )\n", + " )\n", + "\n", + " planes.append(\n", + " caustics.lenses.SinglePlane(z_l = z_p, cosmology = cosmology, lenses = lenses, name = f\"plane {p}\")\n", + " )\n", + "\n", + "lens = caustics.lenses.Multiplane(name = \"multiplane\", cosmology = cosmology, lenses = planes)" + ] + }, + { + "cell_type": "markdown", + "id": "4aa429c8", + "metadata": {}, + "source": [ + "## Effective Reduced Deflection Angles" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f2e0a341", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9MAAAGbCAYAAADHmcNtAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOy9edRlR1ku/tQ+Xw+EpAMxpLMSc+kEXCKXIZgmLZNGibSAQhwDekloXUGERrAvLskFMgjYwYTYDJFWMASjSO5VcQoEoaFFJBqNN16mZIEmJIDdJPjLQId0f312/f6oYb9V+61hD+d832nqSU6fs2ve+ztnVz37ed+3hJRSoqCgoKCgoKCgoKCgoKCgIBvVSg+goKCgoKCgoKCgoKCgoGDRUMh0QUFBQUFBQUFBQUFBQUFHFDJdUFBQUFBQUFBQUFBQUNARhUwXFBQUFBQUFBQUFBQUFHREIdMFBQUFBQUFBQUFBQUFBR1RyHRBQUFBQUFBQUFBQUFBQUcUMl1QUFBQUFBQUFBQUFBQ0BGFTBcUFBQUFBQUFBQUFBQUdMTSSg+goKCgoGAx8dBDD+HQoUOjtLV27VqsX79+lLYKCgoKCgoKxkGZ6+MoZLqgoKCgoDMeeughnProo7HvG9NR2jvxxBNx++23H3GTbEFBQUFBwaKizPVpFDJdsCL40pe+hFe+8pX4p3/6J9x///340Ic+hHvvvRfbtm3D7bffjk2bNs11PJs2bcJZZ52Fa665Zq79riT27t2LH/7hH8YnP/lJnHXWWSs9nIIFw6FDh7DvG1PcfvOjseGYYR5D9z9Q49QzvoJDhw4dURNsQUFBme9XA8p8X9AXZa5Po/hMF7C45pprIISwr/Xr1+Okk07C1q1b8Y53vAMPPPDAoPbPP/98fPazn8Vb3vIWXHvttdi8efNIIw/jM5/5DC655BLce++9M+/rOxHvfe97IYTA+9///lbejTfeiKqq8NrXvnYFRlYwS2w4phrlVVBQsDIo831BF1x22WUQQuCjH/0om/+85z0Pxx57LL7+9a/PeWQFs0SZ68MoynRBFL/5m7+JU089FcvLy9i3bx/27t2L17zmNbjyyivxV3/1V3jSk57Uuc1vf/vbuPHGG/H6178e27dvn8GoeXzmM5/BpZdeipe+9KV4xCMe4eTddtttqKoj80c+L/zSL/0S3v/+9+O1r30tfvzHfxzf9V3fBQBYXl7Gy172Mpxyyim49NJLV3iUBWNjKmtM5fA2CgoKVhZlvi/Iwf/8n/8TH/jAB/CKV7wCn/vc5/Cwhz3M5v2f//N/8JGPfARXXXUVTjrppBUcZcHYKHN9GOVuUhDFc5/7XPyP//E/sG3bNlx44YX46Ec/io9//OP4xje+gRe84AX49re/3bnNu+++GwBaE9xKYt26dVizZs1KD2OhIYTA7/3e7+G+++5zFOi3ve1t+NznPod3vetdePjDH76CIyyYBWrIUV5dcdVVV2HTpk1Yv349tmzZgptuuimr3gc/+EEIIXDOOed07rOg4EhGme8LcrBmzRr8/u//Pu644w686U1vsukPPPAAXvOa1+AHfuAH8PKXv3wFR1gwC6zUXL8IKGS6oDN+5Ed+BG984xvxla98BX/0R3/k5N166634mZ/5GRx33HFYv349Nm/ejL/6q7+y+Zdccgke/ehHAwB+/dd/HUKIpL/URz7yETzrWc/Cwx/+cBxzzDF4/vOfj89//vOtcrfeeit+7ud+Do961KPwsIc9DN/7vd+L17/+9bbfX//1XwcAnHrqqdac7Y477gCgfKhe+tKXOu39x3/8B372Z38Wxx13HI466ij8wA/8AK6//nqnzN69eyGEwP/+3/8bb3nLW/Dd3/3dWL9+PZ797Gfjy1/+cvJafuUrX8ErXvEKfO/3fi8e9rCH4bu+67vwsz/7s3ZcBsYM7x/+4R+wY8cOPOpRj8LDH/5w/ORP/qRdrBjUdY1LLrkEJ510Eo466ij88A//ML7whS+w58jhn/7pn/BjP/ZjOPbYY3HUUUfhh37oh/AP//APyXoA8PjHPx6//uu/jmuuuQZ/93d/h9tvvx2/+Zu/iZ/6qZ/CT/zET2S1UVCQwnXXXYcdO3bg4osvxr/+67/iyU9+MrZu3YpvfOMb0Xp33HEHXvva1+JZz3rWnEZaULDYKPN9gzLfNzCE+YorrsAXvvAFAMAb3vAGfOMb38Dv//7vF+W/4DsK5dte0AsveclLAAB/+7d/a9M+//nP4wd+4AfwxS9+Ea973evwtre9DQ9/+MNxzjnn4EMf+hAA4Kd+6qfwO7/zOwCAF7/4xbj22muxa9euYD/XXnstnv/85+Poo4/GW9/6VrzxjW/EF77wBTzzmc90JqD/9//+H7Zs2YJPfOITuOCCC/D2t78d55xzDv76r//a9vviF78YAPA7v/M7uPbaa3HttdfiUY96FNvv/v378fSnPx0f/ehH8YpXvAJvectb8NBDD+EFL3iBPReKyy67DB/60Ifw2te+FhdeeCH+8R//Eb/wC7+QvI7//M//jM985jN40YtehHe84x14+ctfjj179uCss87Cgw8+2Cr/qle9Cv/2b/+Giy++GL/yK7+Cv/7rv26Zzl144YW49NJLsXnzZlx++eX4nu/5HmzduhUHDhxIjucTn/gEfvAHfxD3338/Lr74YvzWb/0W7r33XvzIj/xItvL3hje8Aaeddhp++Zd/GS9/+cuxtLSEd7zjHVl1CxYP9Uj/dcGVV16JCy64ANu2bcPjH/947N69G0cddRSuvvrqYJ3pdIpf+IVfwKWXXorTTjtt6GkXFHzHoMz3Lsp8r7Bz50486lGPwi//8i/j5ptvxlVXXYXXvva1eOITn5isW7B4WIm5fmEgCwoYvO9975MA5D//8z8Hyxx77LHyKU95ij1+9rOfLZ/4xCfKhx56yKbVdS2f/vSny+/5nu+xabfffrsEIC+//HK2z9tvv11KKeUDDzwgH/GIR8gLLrjAKbdv3z557LHHOuk/+IM/KI855hj5la98xSlb17X9fPnllzvtUzz60Y+W559/vj1+zWteIwHIv//7v7dpDzzwgDz11FPlpk2b5HQ6lVJK+clPflICkN/3fd8nDx48aMu+/e1vlwDkZz/72VZfFA8++GAr7cYbb5QA5B/+4R/aNHNtzj77bOecfu3Xfk1OJhN577332muztLQkzznnHKfNSy65RAJwztGM/ZOf/KSUUl2r7/me75Fbt251+njwwQflqaeeKn/0R380ei4UH/3oRyUACUDu2rUru17B4uC+++6TAORdt54s7/vaKYNed916smrrrrvkfffdZ1/0XmJw8OBBOZlM5Ic+9CEn/bzzzpMveMELguO96KKL7O/i/PPPly984QvHvBwFBQuLMt+X+Z6Osct8/6d/+qcSgDzuuOPkaaedxp5jwWJjFnP9fffdt9KnNSqKMl3QG0cffbSN8vlf//Vf+MQnPoGf+7mfwwMPPIB77rkH99xzD775zW9i69at+NKXvoSvfe1rndr/2Mc+hnvvvRcvfvGLbXv33HMPJpMJtmzZgk9+8pMAlE/Wpz71KfziL/4i/tt/+29OG0KIXuf24Q9/GGeeeSae+cxnOuf7spe9DHfccYc1azLYtm0b1q5da4+NGel//Md/RPuhgTuWl5fxzW9+E4997GPxiEc8Av/6r//aKv+yl73MOadnPetZmE6n+MpXvgIA2LNnDw4fPoxXvOIVTr1XvepVqVPGLbfcgi996Uv4+Z//eXzzm9+01/vAgQN49rOfjU996lOo67yniscdd5w183rOc56TVaeg4JRTTsGxxx5rXzt37myVueeeezCdTrFx40YnfePGjdi3bx/b7qc//Wn8wR/8Ad7znvfMZNwFBUc6ynzfoMz3DX76p38az3ve8/Bf//VfuOqqq5xzLCj4TkGJ5l3QG9/61rdwwgknAAC+/OUvQ0qJN77xjXjjG9/Ilv/GN76Bk08+Obv9L33pSwCUzxaHDRs2AGgmsCc84QnZbafwla98BVu2bGmlf9/3fZ/Np/35k/ojH/lIAMD/9//9f9F+vv3tb2Pnzp143/veh6997WuQsgnOcN9997XKp/oxk+xjH/tYp9xxxx1ny4Zgrvf5558fLHPfffcl25lOp3jZy16Gk046Cd/61rfwq7/6q/jYxz4WrVOwuBgjqIipf9ddd9nfNaACBQ3FAw88gJe85CV4z3veg+OPP35wewUF34ko832Z70N46lOfig9/+MNz2fKsYOUw5lx/pKGQ6YJe+OpXv4r77rvP3sTNE8zXvva12Lp1K1vHv+GnYNq89tprceKJJ7byl5ZWz9d3Mpmw6XSy5PCqV70K73vf+/Ca17wGT3va03DsscdCCIEXvehF7FPhvv3kwPR3+eWX4/TTT2fLHH300cl23v72t+P//t//i7/4i7/A1772Nbzyla/EBz7wAfz8z//84DEWrD7UkJiONMFu2LDBIdMcjj/+eEwmE+zfv99J379/P3uf+Pd//3fccccdTgA8811fWlrCbbfdhsc85jGDxl9QcCSjzPcuynxf8J2IMef6Iw2r5+5UsFC49tprAcBOpCagz5o1a3D22WeP0odZ4J5wwgnRNk3fn/vc56LtdTEBe/SjH43bbrutlX7rrbfa/DHwp3/6pzj//PPxtre9zaY99NBDuPfee3u1Z8b15S9/GaeeeqpN/+Y3v5l8am6u94YNG3r/De+66y5cfPHFeOELX4gXvvCFqOsa73//+7Fjxw48//nPx7HHHtur3YICg7Vr1+KM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/v37B9f77/7dv8Pv/d7v4dFHH/XSL7zwQvziL/4i/sW/+BdiuZ/5mZ/BcrnEb//2bwMYvm76mTcJqKKioqJifRgd3XNV3bwrKioqKiq2GROO9RdffDEuuOCCbrvjjjvEJudcceBjH/sY7r//frznPe8ZekY6VDfvioqKiorBOI1TwEj/ptM4NU1nKioqKioqKibHlGO9pExLyF1x4MiRI3jyySf9tk6fxokTJ1T37fvvvx//5//8Hxw8eNBLv+GGG/CP/tE/wic/+cn4wRBUN++KioqKimJ8+9vfxkte8pIu4MpYHDlyBH/1V3+F8847b5L6KioqKioqKsZhF8b6Rx55BC9/+cvx+c9/HldccQUA4H/+z/+Jo0eP4m/+5m/EAGTHjx/HN77xDS/t+77v+/De974XP/ZjP1YUmb2S6YqKioqKQfj2t7+N559/fpK6zj333EqkKyoqKioqtgy7MNb/k3/yT/C1r30NH/jAB7qlsa688spuaawnnngC1157LX7/938fr3rVq8Q6hs6Zrm7eFRUVFRWDcN5551UCXFFRUVFRcQZjF8b6D3/4wzh27BiuvfZaLBYL3HDDDXjf+97X5Z86dQqPPvoonn322cnbrsp0RUVFRUVFRUVFRUVFRUUhajTvioqKioqKioqKioqKiopCVDJdUVFRUVFRUVFRUVFRUVGISqYrKioqKioqKioqKioqKgpRyXRFRUVFRUVFRUVFRUVFRSEqma6oqKioqKioqKioqKioKEQl0xUVFRUVFRUVFRUVFRUVhahkuqKioqKioqKioqKioqKiEJVMV1RUVFRUVFRUVFRUVFQUopLpioqKioqKioqKioqKiopCVDJdUVFRUVFRUVFRUVFRUVGISqYrKioqKioqKioqKioqKgpRyXRFRUVFRUVFRUVFRUVFRSH+/1dZflVD2rJCAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Effective reduced deflection angles for the multiplane lens system\n", + "ax, ay = lens.effective_reduced_deflection_angle(thx, thy, z_s)\n", + "\n", + "# Plot\n", + "fig, axarr = plt.subplots(1,2,figsize = (12,5))\n", + "im = axarr[0].imshow(ax, extent = (thx[0][0], thx[0][-1], thy[0][0], thy[-1][0]), origin = \"lower\")\n", + "axarr[0].set_title(\"Deflection angle X\")\n", + "plt.colorbar(im)\n", + "im = axarr[1].imshow(ay, extent = (thx[0][0], thx[0][-1], thy[0][0], thy[-1][0]), origin = \"lower\")\n", + "axarr[1].set_title(\"Deflection angle Y\")\n", + "plt.colorbar(im)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "c7ad98c6", + "metadata": {}, + "source": [ + "## Critical Lines" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b2e23c3e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Compute critical curves using effective difflection angle\n", + "A = lens.jacobian_lens_equation(thx, thy, z_s)\n", + "\n", + "# Here we compute detA at every point\n", + "detA = torch.linalg.det(A)\n", + "\n", + "# Plot the critical line\n", + "im = plt.imshow(detA, extent = (thx[0][0], thx[0][-1], thy[0][0], thy[-1][0]), origin = \"lower\")\n", + "plt.colorbar(im)\n", + "CS = plt.contour(thx, thy, detA, levels = [0.], colors = \"b\")\n", + "plt.title(\"Critical curves\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7cd1f948", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For completeness, here are the caustics!\n", + "paths = CS.collections[0].get_paths()\n", + "caustic_paths = []\n", + "for path in paths:\n", + " # Collect the path into a descrete set of points\n", + " vertices = path.interpolated(5).vertices\n", + " x1 = torch.tensor(list(float(vs[0]) for vs in vertices))\n", + " x2 = torch.tensor(list(float(vs[1]) for vs in vertices))\n", + " # raytrace the points to the source plane\n", + " y1,y2 = lens.raytrace(x1, x2, z_s)\n", + "\n", + " # Plot the caustic\n", + " plt.plot(y1,y2, color = \"k\", linewidth = 0.5)\n", + "plt.gca().axis(\"off\")\n", + "plt.title(\"Caustics\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "94144e25", + "metadata": {}, + "source": [ + "## Effective Convergence" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d8a84fde", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "C = lens.effective_convergence_div(thx, thy, z_s)\n", + "\n", + "plt.imshow(C.detach().cpu().numpy(), origin = \"lower\")\n", + "plt.colorbar()\n", + "plt.title(\"Effective Convergence\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "708338df", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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002ea/DpT38613KHld1h0aJFnnL98z//s2d70eocgKe8u3btwj333APGGJYsWeJJ14g6J1oI3oSceeaZ/KqrrnK/V6tVPn36dL569eoGlkrPm2++yQHwJ554wl33vve9j3/2s59tXKE0XH/99Xzu3LnKbXv37uUdHR384Ycfdte98sorHADfuHFjnUoYjc9+9rP82GOP5aZpcs6LWd8A+E9/+lP3u2mavLe3l99yyy3uur179/Kuri7+z//8z5xzzn/3u99xAPy5555z0/zbv/0bZ4zxN954oyHlVvHss89yAPy1115z182cOZN/5zvfybdwIajKftlll/EPf/jD2n2apc4//OEP8/e///2edUWoc6K5aTpLwvDwMDZt2oQFCxa46wzDwIIFC7Bx48YGlkzPvn37AAATJkzwrL///vsxadIknHzyyVi1apXnfeGN5NVXX8X06dMxe/ZsLF26FDt27AAAbNq0CSMjI566P/HEE3H00UcXqu6Hh4fxT//0T/jYxz4Gxpi7vqj17bB9+3b09/d76nfcuHHo6+tz63fjxo0YP348zjjjDDfNggULYBiG8jXrjWLfvn1gjGH8+PGe9TfffDMmTpyI0047DbfccgsqlUpjCiixYcMGTJkyBSeccAI+9alP4e2333a3NUOd7969G7/4xS+wbNky37ai1jnRHDTFq6JF/vSnP6FarfqmcJ46dSp+//vfN6hUekzTxOc+9zn81V/9FU4++WR3/Uc/+lHMnDkT06dPx4svvohrrrkGW7ZswU9+8pMGlhbo6+vDfffdhxNOOAG7du3CjTfeiL/+67/GSy+9hP7+fnR2dvoa/qlTp2b2zvYseOSRR7B3715cfvnl7rqi1reIU4eqe9vZ1t/fjylTpni2l8tlTJgwoTDXYHBwENdccw0uueQSz1sJP/OZz+D000/HhAkT8NRTT2HVqlXYtWsXbrvttgaW1nI1XHjhhZg1axa2bduGr3zlK1i8eDE2btyIUqnUFHX+ox/9CGPHjvW5/opa50Tz0HQiodm46qqr8NJLL3n8+gA8/sxTTjkF06ZNw7nnnott27bh2GOPrXcxXRYvXux+njNnDvr6+jBz5kz8+Mc/xqhRoxpWrjjcfffdWLx4sec99UWt71ZjZGQEf/d3fwfOOe666y7PtpUrV7qf58yZg87OTnziE5/A6tWrG/regYsvvtj9fMopp2DOnDk49thjsWHDBpx77rkNK1cc7rnnHixdutT3WuSi1jnRPDSdu2HSpEkolUq+iPrdu3ejt7e3QaVSs2LFCjz66KP45S9/iXe84x2Bafv6+gAAW7durUfRIjN+/Hi8853vxNatW9Hb24vh4WHs3bvXk6ZIdf/aa6/hsccew8c//vHAdEWsb6cOg+7t3t5eX4BupVLBnj17Gn4NHIHw2muvYd26dR4rgoq+vj5UKhX84Q9/qE8BIzJ79mxMmjTJvTeKXOcA8B//8R/YsmVL6D0PFLfOieLSdCKhs7MT8+bNw/r16911pmli/fr1mD9/fgNLVoNzjhUrVuCnP/0pHn/8ccyaNSt0n82bNwMApk2blnPp4nHgwAFs27YN06ZNw7x589DR0eGp+y1btmDHjh2Fqft7770XU6ZMwQc/+MHAdEWs71mzZqG3t9dTvwMDA3jmmWfc+p0/fz727t2LTZs2uWkef/xxmKbpCp9G4AiEV199FY899hgmTpwYus/mzZthGIbPlN9o/vjHP+Ltt992742i1rnD3XffjXnz5mHu3LmhaYta50SBaXTkZBIefPBB3tXVxe+77z7+u9/9jl955ZV8/PjxvL+/v9FF45xz/qlPfYqPGzeOb9iwge/atctdDh06xDnnfOvWrfymm27izz//PN++fTv/2c9+xmfPns3POuusBpec889//vN8w4YNfPv27fzXv/41X7BgAZ80aRJ/8803Oeecf/KTn+RHH300f/zxx/nzzz/P58+fz+fPn9/gUltUq1V+9NFH82uuucazvkj1vX//fv6b3/yG/+Y3v+EA+G233cZ/85vfuKMAbr75Zj5+/Hj+s5/9jL/44ov8wx/+MJ81axY/fPiwm8eiRYv4aaedxp955hn+q1/9ih9//PH8kksuaVi5h4eH+Yc+9CH+jne8g2/evNlzzw8NDXHOOX/qqaf4d77zHb5582a+bds2/k//9E988uTJ/NJLL8213GFl379/P//CF77AN27cyLdv384fe+wxfvrpp/Pjjz+eDw4OunkUrc4d9u3bx0ePHs3vuusu3/6NrHOidWhKkcA553fccQc/+uijeWdnJz/zzDP5008/3egiuQBQLvfeey/nnPMdO3bws846i0+YMIF3dXXx4447jn/xi1/k+/bta2zBOecXXXQRnzZtGu/s7ORHHXUUv+iii/jWrVvd7YcPH+b/8A//wI888kg+evRo/rd/+7d8165dDSxxjX//93/nAPiWLVs864tU37/85S+V98Zll13GObeGQX71q1/lU6dO5V1dXfzcc8/1nc/bb7/NL7nkEj5mzBje09PDr7jiCr5///6GlXv79u3ae/6Xv/wl55zzTZs28b6+Pj5u3Dje3d3NTzrpJP6Nb3zD0xE3ouyHDh3iH/jAB/jkyZN5R0cHnzlzJl++fLnvgaNode7wP//n/+SjRo3ie/fu9e3fyDonWgfGOee5mioIgiAIgmhKmi4mgSAIgiCI+kAigSAIgiAIJSQSCIIgCIJQQiKBIAiCIAglJBIIgiAIglBCIoEgCIIgCCUkEgiCIAiCUEIigSAIgiAIJSQSCIIgCIJQQiKBIAiCIAglJBIIgiAIglDy/wN19FGAzvUdMAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Curl = lens.effective_convergence_curl(thx, thy, z_s)\n", + "\n", + "plt.imshow(Curl.detach().cpu().numpy(), origin = \"lower\")\n", + "plt.colorbar()\n", + "plt.title(\"Curl\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00e9c94a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PY39", + "language": "python", + "name": "py39" + }, + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/testbook/VisualizeCaustics.ipynb b/docs/testbook/VisualizeCaustics.ipynb new file mode 100644 index 00000000..c2b92c5c --- /dev/null +++ b/docs/testbook/VisualizeCaustics.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3bb2cd70-e98d-4e8d-b195-64d7e5de6e13", + "metadata": {}, + "source": [ + "# Visualize Caustics\n", + "\n", + "Here we will demonstrate how to collect caustic lines using caustic! Since caustic (the code) uses autodiff and can get exact derivatives, it is actually very acurate at computing caustics. \n", + "\n", + "Conceptually a caustic occurs where the magnification of a lens diverges to infinity. A convenient way to measure the magnification in the image plane is by taking the determinant ($det$) of the jacobian of the lens equation ($A$), its reciprocal is the magnification. This means that anywhere that $det(A) = 0$ is a critical line in the image plane (magnification goes to infinity). If we take this line and raytrace it back to the source plane we can see the caustics which define boundaries for lensing phenomena." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f716feef", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import torch\n", + "from torch.nn.functional import avg_pool2d\n", + "import matplotlib.pyplot as plt\n", + "from ipywidgets import interact\n", + "from astropy.io import fits\n", + "import numpy as np\n", + "from time import process_time as time\n", + "\n", + "import caustics" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bdede2df", + "metadata": {}, + "outputs": [], + "source": [ + "# initialization stuff for an SIE lens\n", + "\n", + "cosmology = caustics.FlatLambdaCDM(name = \"cosmo\")\n", + "cosmology.to(dtype=torch.float32)\n", + "sie = caustics.SIE(cosmology, name = \"sie\")\n", + "n_pix = 100\n", + "res = 0.05\n", + "upsample_factor = 2\n", + "fov = res * n_pix\n", + "thx, thy = caustics.get_meshgrid(res/upsample_factor, upsample_factor*n_pix, upsample_factor*n_pix, dtype=torch.float32)\n", + "z_l = torch.tensor(0.5, dtype=torch.float32)\n", + "z_s = torch.tensor(1.5, dtype=torch.float32)\n", + "x = torch.tensor([\n", + " z_l.item(), # sie z_l\n", + " 0., # sie x0\n", + " 0., # sie y0\n", + " 0.4, # sie q\n", + " np.pi/5, # sie phi\n", + " 1., # sie b\n", + "])\n", + "packparams = sie.pack(x)" + ] + }, + { + "cell_type": "markdown", + "id": "38dd09e3", + "metadata": {}, + "source": [ + "## Critical Lines\n", + "\n", + "Before we can see the caustics, we need to find the critical lines. The critical lines are the locus of points in the lens plane (the plane of the mass causing the lensing) at which the magnification of the source becomes theoretically infinite for a point source. In simpler terms, it is where the lensing effect becomes so strong that it can create highly magnified and distorted images of the source. The shape and size of the critical curve depend on the distribution of mass in the lensing object. These lines can be found using the Jacobian of the lensing deflection, specifically $A = \\mathbb{I} - J$. When ${\\rm det}(A) = 0$, that point is on the critical line. Interestingly, $\\frac{1}{{\\rm det}(A)}$ is the magnification, which is why ${\\rm det}(A) = 0$ defines the points of infinite magnification." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "487b3030", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Conveniently caustic has a function to compute the jacobian of the lens equation\n", + "A = sie.jacobian_lens_equation(thx, thy, z_s, packparams)\n", + "# Note that if this is too slow you can set `method = \"finitediff\"` to run a faster version. You will also need to provide `pixelscale` then\n", + "\n", + "# Here we compute A's determinant at every point\n", + "detA = torch.linalg.det(A)\n", + "\n", + "# Plot the critical line\n", + "im = plt.imshow(detA, extent = (thx[0][0], thx[0][-1], thy[0][0], thy[-1][0]), origin = \"lower\")\n", + "plt.colorbar(im)\n", + "CS = plt.contour(thx, thy, detA, levels = [0.], colors = \"b\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "3b099cfd", + "metadata": {}, + "source": [ + "## Caustics\n", + "\n", + "Critical lines show us where the magnification approaches infinity, they are important structures in understanding a lensing system. These lines are also very useful when mapped into the source plane. When the critical lines are raytraced back to the source plane they are called caustics (see what we did there?). In the source plane these lines deliniate when a source will be multiply imaged. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0c1f1177", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAi8AAAGdCAYAAADaPpOnAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABeHklEQVR4nO3dd3zTdf4H8FdGk3Sle09ogTJbaKGCAiKoOBBO3ANBRO9OvIHendx56p13h9458E5+eg7cioqLU0TZs6yyoYPuvdskTZr9/f2RtlpldCT9Junr+Xjk0fbbb9o3X9Lklc+UCIIggIiIiMhDSMUugIiIiKgvGF6IiIjIozC8EBERkUdheCEiIiKPwvBCREREHoXhhYiIiDwKwwsRERF5FIYXIiIi8ihysQtwNrvdjpqaGgQGBkIikYhdDhEREfWCIAjQ6XSIjY2FVHrhthWvCy81NTVISEgQuwwiIiLqh8rKSsTHx1/wHK8LL4GBgQAc/3i1Wi1yNURERNQbWq0WCQkJ3a/jF+J14aWrq0itVjO8EBEReZjeDPnggF0iIiLyKAwvRERE5FEYXoiIiMijMLwQERGRR2F4ISIiIo/C8EJEREQeheGFiIiIPArDCxEREXkUhhciIiLyKAwvRERE5FEYXoiIiMijMLwQERGRR2F4ISIiol4pb9bjue8KUNSgE7UOr9tVmoiIiJxv7Z5S/PWrMwCAWo0Rz96cLlotDC9ERER0XlqjBVc+vxP1WlP3sVuyEkSsiOGFiIiIzmN7fgOWvHWox7E9f5iF+BA/kSpyYHghIiKiHtoMZvz5y9P43/Ga7mMjIgPw9a+mQyEXf7gswwsREREBAARBwOdHq/GHT0/AYhO6jz923WjcN324iJX1xPBCREREKGrQ4bEvTmF/SUuP4+8unYLpIyJEqurcGF6IiIiGsA6zDS9tP4tXd5X0aG0J81fg3aXZGBOrFrG6c2N4ISIiGqK25dfjiQ2nUdnS0eP4qKhAvLlkMmKDfUWq7MIYXoiIiIaYogYdnvoqDzsLG3/yvWkpYXj5rkwE+fqIUFnvMLwQERENERqDBS9uPYt3cspgtQvwkUkgk0pgtNgBAAsyYvHPm9LdYkbRhTC8EBEReTmbXcCHByvw3HcFaDVYAABzRkdCrfLBZ0erAQCLpyXj8evHQCqViFlqrzC8EBERebG9RU146qszyK9z7Ec0IjIAf75+DHYUNGLt3lIAwG/mjMCvZ4+AROL+wQVgeCEiIvJKZ2q0eHpTPnZ1jmsJ8vXBiitH4tbJCXjsi1NYn1sFAHj8+jG497JhYpbaZwwvREREXqSq1YDnNxfi86PVEATARybBndlJ+PXsEfBXyvHQh0fw7el6yKQS/HPhBCzMjBe75D5jeCEiIvICbQYz1mwvwts55TBbHQNwr58Qg99dPQpJYf4wWW34xXu52JrfAIVMiv/cMRFXj40Wuer+YXghIiLyYEaLDW/vK8Oa7UXQGq0AgKnDw/DoNWlITwjuPufn7+ViR0EjlHIpXluUhRkj3WvV3L5geCEiIvJAJqsNHx2qxJrtRajXmgAAadGB+MM1abh8ZET34NsOsw33v3sYu882QeUjxRv3TMalqeFilj5gDC9EREQexGKzY31uFV7aVoTqNsfKuHHBvvjtlSPxs4lxkP1gqrPBbMV9bx/GvuJm+ClkWLt4Mi4ZHiZW6U4zKKvQrFmzBsnJyVCpVMjOzsbBgwd7db9169ZBIpFgwYIFri2QiIjIzVk7Q8vs53Zi5WcnUd3WgSi1Ek/NH4ttj8zETZnxPYKL3mTF4jcPYV9xM/wVMrx97xSvCC7AILS8fPTRR1ixYgVeeeUVZGdnY/Xq1bj66qtRUFCAyMjI896vrKwMjzzyCKZPn+7qEomIiNyWzS7gqxM1eHHLWZQ06QEA4QEK/OLyVNyZnQiVj+wn92k3WbF47UEcLm9FgFKOt++dgsykkMEu3WUkgiAIFz+t/7KzszF58mS89NJLAAC73Y6EhAQ89NBDePTRR895H5vNhhkzZuDee+/F7t270dbWhi+++KJXv0+r1SIoKAgajQZqtfvthElERNQbXaHlpW1FONvQDgAI8fPBz2em4O6pSfBTnLv9QW+y4p7O4BKokuPdpdnI6By468768vrt0pYXs9mM3NxcrFy5svuYVCrFnDlzkJOTc977/fWvf0VkZCSWLl2K3bt3X/B3mEwmmEym7q+1Wu3ACyciIhKJ2WrH50er8PKOYpQ1GwAAapUc988YjsWXDkOA8vwv3UaLDfe9fRiHy1uhVsnx3n3ZmBAfPEiVDx6XhpempibYbDZERUX1OB4VFYX8/Pxz3mfPnj144403cOzYsV79jlWrVuEvf/nLQEslIiISldHimD30353FqNEYAThaWu69dBgWTUu+6C7PJqsN97+bi5yS5u6uIm8MLoCbzTbS6XS4++678dprryE8vHfTuFauXIkVK1Z0f63VapGQkOCqEomIiJyq3WTFe/vL8fruUjS1O3oSIgKVuH/6cNyRnQj/C7S0dDFb7Xjw/SPYVdgIXx/HrKKJid4zxuXHXBpewsPDIZPJUF9f3+N4fX09oqN/uqpfcXExysrKMG/evO5jdrtjlUC5XI6CggKkpKT0uI9SqYRSqXRB9URERK7T3G7COznleGtfGTQdjp2e44J98fPLU3BzZvw5B+Kei9Vmx28+OooteQ1QyKV4/Z4sTBkW6srSRefS8KJQKJCZmYmtW7d2T3e22+3YunUrli9f/pPz09LScPLkyR7HHnvsMeh0Orz44otsUSEiIo9X0tiON/aUYn1uFUydy/gPD/fHLy5PwYKJcfCR9X4VE5tdwCOfHMfGk3XwkUnw37szPX4But5webfRihUrcM899yArKwtTpkzB6tWrodfrsWTJEgDAokWLEBcXh1WrVkGlUmHcuHE97h8cHAwAPzlORETkSQ6XteDVXSXYnFePrnm+E+KDcP+M4bhmXEyPNVp6w24X8MfPTuKLYzWQSSV46Y5JmDXq/EuQeBOXh5dbb70VjY2NePzxx1FXV4eMjAxs2rSpexBvRUUFpNJBWSuPiIhoUNnsAjafqcOru0pwpKKt+/jstEgsmzEc2cNCu5fx7wtBEPDU12fw0eFKSCXA6lszPHaTxf5w+Tovg43rvBARkdjaTVZ8mluFtXtLUd453Vkhk+JnE+OwbMYwpEYGDujnv7TtLJ79rhAA8OzN6bgpM37ANYvNbdZ5ISIiGkpKm/R4e18Z1udWod3k2OE5yNcHd1+ShEXTkhAZqBrw73j/QHl3cHn8+jFeEVz6iuGFiIhoAOx2ATvPNuKtvWXYWdjYfXx4hD/umZqMmzLjezXduTe+PlGLx744BQBYPisV9142zCk/19MwvBAREfWD1mjB+sNVeHd/OUo79xySSIBZoyJxz7RkTE8Nh7SPg3AvZPfZRvzmo6MQBOCO7EQ8fNVIp/1sT8PwQkRE1AeF9Tq8t78cn+ZWQW+2AQAClXLcnJWARVOTkBzu7/TfeayyDQ+8mwuLTcB142Pw1Pxx/Rro6y0YXoiIiC7CaLFh48lafHCgAofLW7uPp0YG4J5pybhxYpzTuoZ+rKhBhyVvHoTBbMP0EeF4/tb0Pk+r9jYML0T9UNTQjha92etXsSQa6s7W6/D+gQp8dqQKWqNjAK5MKsHstEgsmpqMS1PDXNoCUtPWgbvfOIhWgwXpCcF45a5MKOW9W3nXmzG8EPXDvW8dQkWLAfMzYvHibRPFLoeInKirleXDgxU4VPZ9K0tcsC9un5KAm7MSEKUe+Kyhi9F0WLD4zYOo1RiRGhmAtxZPdlnrjqfhVSDqh4oWx7oNXx6rwbwJsZgzJuoi9yAid3emRouPD1fi86PV3XsNdbWy3JGdiOkjIgatu8ZkteGBdw+jsL4dUWol3r53CkL8FYPyuz0BF6kj6odWvRkTn9rc/fUrd03C3HExIlZERP3R3G7Cl8dqsD63Cmdqtd3HB7uV5YfsdgG//fgYvjxWgwClHB8/MBVjYr3/9YyL1BG5WIi/Ag/MHI7/7iwBADz4wVGsutGKW7K4eSiRu7PY7Nie34D1uVXYlt8Aq93xHl4hk+LKMVG4KSseMwaxleXH/vVdAb48VgO5VIKX75o0JIJLXzG8EPXTAzNS8MbuUljtAmx2Ab9ffwLFDe34/dy0IT8TgMjdCIKAvFod1udW4ctj1WjWm7u/lx4fhJsy4zEvPRbBfuJ2zby7vxwv7ygGADy9cAKmj4gQtR53xfBC1E+h/grckB6Lz45Wdx/7764SFNTr8OJtExHk6yNidUQEAJUtBmw4XoMvj1WjsL69+3hEoBI3TozDwsx4jIwa2D5DzrL5TD2e+NKxeu6KK0cOyWX/e4vhhWgA7pqahM+OVkMpl+LJG8biL/87jR0Fjbj2xd148bYMZCVzKjXRYGvUmfD1iRp8ebwGR3+wk7NCJsWcMZG4OTMB00eEQy6TilfkjxyrbMNDHx6BXQBum5yAh65IFbskt8bwQjQAExOCkRLhj+JGPZRyKdb/fBp++f4RVLQYcMt/c/Cr2SOwfFaqWz1JEnkjndGCb0/X48tj1dhb1ITOYSyQSoBpKeG4ISMWV4+NdssW0eq2Dtz39mEYLXbMHBmBpxYM7dVze4OzjYgGaPWWQqzechazRkXgzSVToDNa8MSXp7u7k9ITgvHMwvFIi+bjkciZtEYLtuU14JtTtdhe0Aiz1d79vfSEYMxPj8X1E2IQOcizhfqi3WTFTS/vQ36dDmnRgVj/i2kIGKJrufTl9ZvhhWiAihraMef5nZBLJch97EoE+Tne2X1xtBp//uIUdCYr5FIJfj4zBcuvSIXKh6tjEvWXxmDB5rx6fHOyFrvPNsFs+z6wDI/wx4KMONyQHuuS/YWczWYXcP87h7E1vwERgUp8+eCliA32Fbss0XCqNNEgSo0MwIjIAJxtaMfuokZcPyEWALBgYhwuGR6Gx788he/O1OOl7UXYeLIWT94wFjNGcgYBUW81t5uw+Uw9Np6qw76ipu6pzYAjsFw7LgZzx0VjbKzao7pbVm3Mw9b8BijlUry2KGtIB5e+YnghcoLLR0XgbEM7tud/H14AIDpIhVcXZWHTqVo8/uVplDTpsWjtQVw+KgJ/unY0RrjJLAcid1PVasDWvAZ8e7oO+0ua8YO8glFRgbhmfDSuHR+DEZEBHhVYunxwoAKv7ykFADx3SzoyEoLFLcjDMLwQOcHloyLx2u5S7CxshN0uQPqjdV7mjovBtNRwvLjlLN7JKcOOgkbsPtuEO6Yk4lezRyAiUClS5UTuwW4XcLyqDVvzGrAlrx75dboe3x8Xp8Y1nS0sKREBIlXpHPuKmvD4D6ZE//AND/UOx7wQOYHJakPGXzajw2LDt7+ZgVHR529RKW3SY9XGPHx3ph4AoPKRYtHUZNw/YzjCAxhiaOjoMNuwp6gJW87UY2t+A5raTd3fk0qArKRQzBkTibljY5AY5idipc5T0tiOBWv2Qmu0Yn5GLFbfmuGRLUeuwDEvRINMKZchIyEYOSXNOFzecsHwMizcH68uykJOcTOe2ZSPY5VteHVXCd7NKceiaUm4f/pwhDHEkJcqa9Jj19lG7ChoxN6iJph+MEMoQCnHzJERmDMmEpePjPS6jQg1BguWvn0YWqMVkxKD8czCCQwu/cTwQuQkWckhyClpRm5ZK+7MTrro+VNTwvD5L6dhR2EjVm8uxPEqDf67swRv7S3DjZPisfSyYUiN9OzmcSK9yYqc4mbsLGzErrONKG829Ph+fIgv5oyOwpzRUZgyLBQKuXeuiWSzC/jVuqMobdIjLtgXry7K4szDAWB4IXKSzKQQAMDh8tZe30cikWDWqEhcPjIC2wsa8OLWIhyvbMOHByvw4cEKXJEWifumD8PU4WF8h0YewW4XkFenxa7CJuwqbMTh8hZYbN+PTvCRSZCZFIIZIyNwRVokRkUFDonH9r++LcDOwkaofKR4dVEmu4gHiOGFyEkmJYVAIgEqWgxo1Jn6NAhXIpHgirQozBoViUNlrXhtdwm25NVjW34DtuU3IDUyALdPScTCSXGibxxH9EOCIKC4UY+c4ibsK27G/pJmtBosPc5JDPXDzJERmDEyAlNTwobcImwbjtfglZ2OzRb/dVM6xsYGiVyR5xtajyAiF1KrfDAs3B8ljXrk1WoREdj3tVwkEgmmDAvFlGGhKG3SY+2eUqzPrUJRQzue+uoMntmUj2vHReP2KYmYMix0SLxjJfdT2WLAvuIm5BQ3Y19xMxp0ph7f91fIkD08DDNHRmDmyAiPWDDOVU5Va/D79ccBAD+fmYJ56ZxZ5AwML0ROlBYdiJJGPQrqdANeiG5YuD+eWjAOv587Cl8eq8EHBypwplaLL47V4ItjNUgI9cUN6bGYnxHnNrvikvex2wUUNuhwuKwVh8tacKisFdVtHT3OUcilyEoKwbSUMExNCceE+CD4cD8vNLeb8MC7uTBa7Lh8VAR+d/UosUvyGgwvRE6UFq3GxpN1yKvTOu1nBqp8cNclSbgzOxEnqzX48GAFNhyrQWVLB9ZsL8aa7cVIiw7EDRmxmDchFgmh3jGllMRhtNhwokqDQ2UtOFzWgtzyVmiN1h7nyKUSZCQEd4eViYnBHHz6IxabHb98/wiq2zowLNwfL942ETIpW0qdheGFyIm6pkgX/GiBLWeQSCSYEB+MCfHBePz6sdiSV48Nx2uwo6AB+XU65G8qwD83FWB0jBpXjo7ElWOiMS7Os5ZLp8ElCAIqWzpwvKoNJ6racKSiDSerND32CwIAP4UMExODkZUUisnJoZiYGAz/ITZupa/+9tUZHChtQYBSjtcWZbrlbtaejI8+IidK6wwvZxvaz7nSrrP4KmSYlx6Leemx0Bgs+OZULb48VoMDpc3Iq9Uir1aLf28rQrRahdmjIzF7dCSyh4XxBWeIa9SZcKKqDcerNDhe6QgsPx5cCwARgUpMTg7pDiujYwIhZzdQr32aW4W3c8oBAM/fko7USHbrOhufyYicKC7YF3KpBGarHfU6I2KCXL/RWpCfD26bkojbpiSiRW/G9nzH8uo7CxtRpzXi/QMVeP9ABeRSCSYmBmNaSjguTQ1HRkKw166pMdQJgoB6rQlnajXIq9XhVLUGJ6o0PxmrAjimLo+OUSM9PhjpCcGYnByCxFA/ttj1U16tFn/64iQA4NezR+CqsdEiV+SdGF6InEgukyIuxBflzQZUNBsGJbz8UKi/Agsz47EwMx5Giw05Jc3YcqYeu842orKlA4fKWnGorBUvbj0LP4UMU4Z93w2QHs+uAE9kttpR1NDe3eJ2pvPjuVpUJBIgNSIAE+KDkZ4QhPT4YKTFBEIp53gVZ9B0WPDz9xwDdGeOjMCvZ48QuySvxWcqIidLDPVDebMB5S0GZA8PE60OlY8Ms0ZFYtaoSABARbMBe4ubsLfIMcW1WW/GjgLHMu2AYy+ZtGg1JiUFY1JiCCYmhiAp1M9lXV/UN0aLDSWNepxt0KG4oR1Fje0oamhHaZO+xyJwXWRSCVIi/DE6Ro0xMWpMiA/GuDg1AlUce+EKgiDgkU+Oo7zZgLhgX6y+NYN/Oy7E8ELkZImds30qWwwXOXNwJYb5ITEsEbdPSYTdLiC/ToeckmYcqWjF0fJW1GiMONP5zv29/RUAHOt1jIoOdLwAxqoxOkaNtOhA+Cn41OEKNruAmrYOVLQYUN5sQFmzHkUNjpBS2WrA+bbRDVTJu0PKmBjH/9OIqADOABpE/91Vgs1n6qGQSfHyXZO8bl8md8NnICIniwlSAQAatKaLnCkeqVSCMbGOQLIUwwAAdRojjlS04kh5K3IrWnG6Rgu92YYjFY5ZKF0kEiAp1A8pEQEYFu6PYRH+GBbuj+HhAYhSKzlW4gIEQUCL3oxajbE7pHQFlYoWA6paDedsRekS7OeD1IgApEb2vMUF+/K6iyinuBn/3JQPAHjyhrGYEB8sbkFDAMMLkZN1bQvQ2O6+4eVcooNUuHZ8DK4dHwMAsNrsKG3Sd7fG5NXqcKZGi6Z2E8qaDShr/mnLkq+PDMPC/REf4ovYYF/EBKkQE+yL2CAVYoN9ERmo9NpZK0aLDc16M5rbTWjQmlCr6UCNxoi6zqBSpzWiVmOE2Wq/4M9RyKSID/VFcpg/EkP9kBIZgBGdISXMX8GQ4mbqtUY89OER2AVg4aR43D4lQeyShgSGFyIn69pwrVHnWeHlx+QyKUZEBWJEVCDmZ8R1H2/UmVBQp0Npsx6ljXqUNjnGXVS2dqDDYusOO+cilQCRgSqEBSgQ6v+Dm58CoQGOjyH+CgSq5AhQyuGvdHxUyqWD8qItCAIMZhu0Rgt0Rit0Rgu0Rmv35zqjFdoOC1r0ZjS1m9GsN6G53YwWvRntJuvFf0Gn8AAlYoJUSAj1RVKYP5JC/ZAY5oekMH9Eq1VczMxDWGx2PPj+ETS1m5EWHYi/LRjHcDlIGF6InKy75cXDw8v5RAQqERGoxGUjwnsct9jsqGwxoLRJj+q2DtS0GVGr6UBtmxE1mg7UaYyw2gXUaY2o0xr79DvlUgn8lXL4+sigkEvhI5PARyaFUi6Fj8xxU8il6HrdEARAgCOMfP+1AJtdgNlqh6nzZrTYHJ93fbxIq8jF+MgkCPN3XJ+YIFV3y1NMkArR6s7WJ7WSs3u8xL++LcDh8lYEKuV45a5M+Cr4/zpYGF6InKwrvDS1m1y6UJ278ZFJMTwiAMMjAs75fbtdQFO7CbUaI1r0ZjTrzWj90ccWvQmtBgvaTVboTVYYzDYAgNUuQNNhgabjp9N/XUEulSBQJUegyqfz4/efq1U+CPNXICxAiVB/BcIDHJ+HBSgQqJTznfcQsS2/Hq/uKgEA/Ovm9CG9+aQYGF6InCy0c5aB1S5AZ7JyWfBOUqkEkWoVItWqXt/HZhegNzuCjN5khdHiaB2x2Bw3c+fnJqvjcwFAV3SQSCSQwDHAWCIBJJBAKpVAKXe02Kh8ZJ2fy6D0kXZ/HqCUQ+UzON1U5JlqNR14+GPHTtGLpyVj7jguRDfYGF6InEwpl0Ehk8Jss0PP8DIgMqkEapUP1FybhNyE1WbHrz88hlaDBePi1Fh5bZrYJQ1J3jnsn0hkfkpH37fB3PtBnETk/v699SwOljk2XHzp9kkcvyQShhciF/DvXMSt3WQTuRIicpa9RU34z/YiAMA/bhzPcS4iYnghcgH/rpaXPkyfJSL31agz4TcfHYMgALdNTsAN6bFilzSkMbwQuUDXBod9WfuDiNyT3S5gxcfH0KgzYWRUAJ6YN1bskoY8hhciF/Dt3FOmw8JuIyJP98quYuw+2wSVjxRr7pjE9VzcAMMLkQt0rZB6vo30iMgznKhqw/PfFQIA/nrDOIyIChS5IgIYXohcomuNEDvTC5HHMpit+M26Y7DaBVw7Pho3Z8WLXRJ1YnghcoGuRXVtdoYXIk/11Fd5KGnSI1qtwj9+Np4LF7oRhhciF5BK2G1E5Mm+O12HDw9WQCIBnr8lHcF+CrFLoh9geCFyga6WF3YbEXmeBp0Rj352EgCwbPpwTEsNv8g9aLAxvBC5AHuLiDyTIAj43Scn0KI3Y0yMGg9fNVLskugcGF6IXMBstQMAlD78EyPyJG/vK8POwkYo5VK8eFsGl/93U3xmJXKBrvCikPGJj8hTFNbr8I9v8gEAf7x2NKdFuzGGFyIXMNk6W17k/BMj8gRmqx2/WXcMZqsdl4+KwKKpSWKXRBfAZ1YiFzB1rqyrYHgh8ggvbS/CmVotQvx88M+bJnBatJvjMyuRC5jZ8kLkMU5WabCmc7fopxaMQ2SgSuSK6GL4zErkAiZL55gXhhcit2ay2vDwJ8dgswu4bkIMrp/A3aI9AZ9ZiVxAa7QAANS+PiJXQkQX8sLmsyisb0d4gAJPzR8ndjnUSwwvRE5mswvQGa0AALWK4YXIXR2paMWru4oBAP/42XiE+nMVXU/B8ELkZO2dwQUAgtjyQuSWjBYbHvnkOOwC8LOJcbhqbLTYJVEfMLwQOZmmw9Fl5Osj45gXIjf1r28LUNKoR5RaiSfnjRW7HOojPrMSOVlXeGGrC5F7OljagrV7SwEATy+cgCA//q16mkEJL2vWrEFycjJUKhWys7Nx8ODB85772muvYfr06QgJCUFISAjmzJlzwfOJ3A3DC5H7Mlps+P364xAE4NasBMwaFSl2SdQPLg8vH330EVasWIEnnngCR44cQXp6Oq6++mo0NDSc8/wdO3bg9ttvx/bt25GTk4OEhARcddVVqK6udnWpRE7RrDcBAEL8GV6I3M3qLWdR1mxAtFqFP10/WuxyqJ9cHl6ef/55LFu2DEuWLMGYMWPwyiuvwM/PD2vXrj3n+e+//z5++ctfIiMjA2lpaXj99ddht9uxdetWV5dK5BQNWkd4iVJzoSsid3KqWoPXdpcAAP62YBxnA3owl4YXs9mM3NxczJkz5/tfKJVizpw5yMnJ6dXPMBgMsFgsCA0NPef3TSYTtFptjxuRmOq1RgAML0TuxGqz4w+fnoDNLuD6CTGYMyZK7JJoAFwaXpqammCz2RAV1fNBEhUVhbq6ul79jD/84Q+IjY3tEYB+aNWqVQgKCuq+JSQkDLhuooGo1zlaXiIDlSJXQkRdXt9TitM1WgT5+uAJzi7yeG492+jpp5/GunXr8Pnnn0OlOve72JUrV0Kj0XTfKisrB7lKop4aOlteItnyQuQWSpv0eGFzIQDgz9ePQQTfWHg8uSt/eHh4OGQyGerr63scr6+vR3T0hRcEevbZZ/H0009jy5YtmDBhwnnPUyqVUCr5QCT30dDZ8hLFJ0gi0QmCgJWfnYDJasf0EeFYOClO7JLICVza8qJQKJCZmdljsG3X4NupU6ee937//Oc/8dRTT2HTpk3IyspyZYlETiUIAse8ELmRjw5VYn9JC3x9ZPjHz8ZDIpGIXRI5gUtbXgBgxYoVuOeee5CVlYUpU6Zg9erV0Ov1WLJkCQBg0aJFiIuLw6pVqwAAzzzzDB5//HF88MEHSE5O7h4bExAQgICAAFeXSzQgbQYLDGYbACA6iOGFSEz1WiP+vjEPAPDwVSOREOonckXkLC4PL7feeisaGxvx+OOPo66uDhkZGdi0aVP3IN6KigpIpd83AL388sswm8246aabevycJ554Ak8++aSryyUakPIWAwAgSq2EykcmcjVEQ9tf/ncaOqMV6fFBWHLpMLHLISdyeXgBgOXLl2P58uXn/N6OHTt6fF1WVub6gohcpLxZDwBICvUXuRKioW1HQQM2nqyDTCrBqhsnQCZld5E3cevZRkSepqLZ0fKSGMbmaSKxGC02PLHhNABg8bRkjIlVi1wRORvDC5ETdXUbJbJvnUg0/7ejGOXNBkSplfjtlSPFLodcgOGFyIkqOsNLElteiERR2qTHKzuKAQCPXz8WAcpBGR1Bg4zhhciJuruN2PJCNOgEQcDjX56C2WbHjJERuHb8hdcTI8/F8ELkJAazFXWda7wkh3HALtFg+/pkLXafbYJCLsVfbxjLNV28GMMLkZOcrW8HAIQHKBHirxC5GqKhRWe04K//OwMA+OXlKUgO5xsIb8bwQuQkhfU6AMDIKC6mSDTYXth8Fg06E5LD/PDzmSlil0MuxvBC5CRnGxwtLyOjAkWuhGhoKazX4e2cMgDAX+eP4wKRQwDDC5GTdLW8jGDLC9GgEQQBf/3fGdjsAq4aE4UZIyPELokGAcMLkZMU1nV1G7HlhWiwfHemHnuKHIN0H7tujNjl0CBheCFyAp3RghqNY6bRyEiGF6LBYLTY8LevHYN0l00fxpWthxCGFyInKOhsdYlWqxDk5yNyNURDwxt7SlHZ0oEotRK/vDxV7HJoEDG8EDnBiSoNAGBcHPdQIRoMdRoj1mwvAgCsvGY0/LmS7pDC8ELkBKeqHeFlfFywuIUQDRFPf5MHg9mGzKQQzM+IFbscGmQML0ROcLIrvMSz5YXI1XLLW/DFsRpIJMCT87iS7lDE8EI0QHqTFcWNjjVexsUFiVwNkXez2wU8ucExSPeWzASMj+ff3FDE8EI0QGdqtbALQJRaichAldjlEHm1L45V42S1BoFKOX43d5TY5ZBIGF6IBuhkVdd4F74DJHIlo8WGZ78tAAD8YlYKwgOUIldEYmF4IRqgrvEu7DIicq0395ahRmNEbJAK9146TOxySEQML0QDdLi8BQAwKTFE5EqIvFdzuwn/1zk1+pGrR3H/oiGO4YVoABq0RlS2dEAqASYmBotdDpHX+s+2IuhMVoyNVWNBRpzY5ZDIGF6IBuBweSsAYFS0GoEqrqxL5AqlTXq8t78cAPDHa0dDKuXU6KGO4YVoAA6XOcJLVhK7jIhc5Zlv8mG1C7h8VAQuTQ0XuxxyAwwvRAOQ2zneJSuZ4YXIFQ6XtWDT6TpIJY5tAIgAhheifusw23C6RgsAyGTLC5HTCYKAp7/JBwDckpWAUdHcsZ0cGF6I+uloZSusdgHRahXign3FLofI6+woaMTh8lYo5VL8Zs5IscshN8LwQtRP+4ubAQDZw0O5twqRk9ntAv7ZuSDd4mnJiA7i6tX0PYYXon7a2xlepqWEiVwJkff5+mQt8mq1CFDK8fOZKWKXQ26G4YWoH9pNVhyvbAMATEvh7AciZ7La7Hh+cyEAYNn04QjxV4hcEbkbhheifjhU2gKrXUBiqB8SQv3ELofIq3x6pAqlTXqE+iuwdDq3AaCfYngh6oe9RU0A2GVE5GxGiw0vbjkLAPjl5SkIUMpFrojcEcMLUT/s6xrvwgWziJzqgwMVqNEYEa1W4a5LksQuh9wUwwtRH7XozThT61jfZepwtrwQOYveZMWazs0XfzV7BDdfpPNieCHqo33Fji6jUVGBiAhUilwNkfd4b385mvVmJIX54easeLHLITfG8ELUR9vyGwAAM0dFiFwJkfcwmK14dVcJAODBWanwkfHlic6Pjw6iPrDbBewsaAQAzBoVKXI1RN7jgwMVaNabkRDqi59NjBO7HHJzDC9EfXCiWoNmvRmBSjk3YyRykg6zDa/sdLS6LGerC/UCHyFEfdDVZTR9ZDifYImc5IODFWhqNyEu2Bc/m8ixLnRxfPYl6oMdBY7wcjm7jIicwmix4ZWdxQAcY10Ucr4s0cXxUULUSw06I05UaQAAl3OwLpFTrDtYgUadCbFBKtyUyVYX6h2GF6Je6hqoOz4uCJGB3OGWaKCMFhte7mx1+QVbXagP+Egh6qXtnV1Gs9LYZUTkDOtzq1CvNSEmSIVbuK4L9QHDC1EvGC027OhseZnN8EI0YFabvXtdl/tnDIdSztV0qfcYXoh6YVdhIwxmG+KCfTEhPkjscog83sZTdahoMSDEzwe3Tk4QuxzyMAwvRL2w6VQdAODqsdGQSCQiV0Pk2QRBwCs7HGNdFk8bBj8Fd46mvmF4IboIs9WOLXn1AIC546JFrobI8+0624QztVr4+siwaCp3jqa+Y3ghuoj9Jc3QGq0ID1AiM4mr6hINVFery+1TEhHirxC5GvJEDC9EF/FNZ5fRVWOjIJOyy4hoII5VtiGnpBlyqQT3TR8mdjnkoRheiC7AZhew+YwjvFzDLiOiAetqdZmfEYfYYF+RqyFPxfBCdAGHy1rQ1G6GWiXHJcPDxC6HyKOVNunxbeebgZ/PHC5yNeTJGF6ILmDD8RoAwFVjo7kRI9EAvbm3FIIAXJEWiRFRgWKXQx6Mz8ZE52G22vH1yVoAwPyMWJGrIfJsGoMFnxyuAgAsvYxjXWhgGF6IzmNPUSPaDBaEBygxlV1GRAOy7lAFOiw2pEUHYloK/55oYBheiM5jwzFHl9H1E2IgZ5cRUb9ZbXa8va8MAHDvpcO40CMNGJ+Ric7BYLbiuzOOheluYJcR0YBsOl2HGo0RYf4K/j2RUzC8EJ3DlrwGGMw2JIT6YmJCsNjlEHm0tXtKAQB3XpIElQ83YKSBY3ghOoeuLqP56XFs4iYagKMVrThS0QaFTIq7LkkUuxzyEgwvRD/S3G7CzsIGAOwyIhqoN/eWAQDmpcciMlAlbjHkNRheiH7ki2M1sNgEjI8LwkiuRUHUb406E7455VhuYMmlyeIWQ16F4YXoBwRBwCeHKwEAN2fFi1wNkWf7+HAlLDYB6QnBGBcXJHY55EUYXoh+4HSNFvl1OijkUtyQzi4jov6y2QV8cKACAHBXNse6kHMxvBD9QFery1VjohDspxC5GiLPtbOwAdVtHQjy9cE8vhEgJxuU8LJmzRokJydDpVIhOzsbBw8evOD5n3zyCdLS0qBSqTB+/Hhs3LhxMMqkIc5kteHLzr2Mbs5KELkaIs/23n5Hq8tNmfGcHk1O5/Lw8tFHH2HFihV44okncOTIEaSnp+Pqq69GQ0PDOc/ft28fbr/9dixduhRHjx7FggULsGDBApw6dcrVpdIQt+VMA9oMFsQEqXBZarjY5RB5rKpWA7YXOJ7j72SXEbmAy8PL888/j2XLlmHJkiUYM2YMXnnlFfj5+WHt2rXnPP/FF1/E3Llz8bvf/Q6jR4/GU089hUmTJuGll15ydak0xH2S6+gyunFSHGRSru1C1F8fHqyAIACXpoZheESA2OWQF3JpeDGbzcjNzcWcOXO+/4VSKebMmYOcnJxz3icnJ6fH+QBw9dVXn/d8k8kErVbb40bUV3UaI3YVNgIAbspklxFRf5mtdnx0yPFG4K7sJJGrIW/l0vDS1NQEm82GqKioHsejoqJQV1d3zvvU1dX16fxVq1YhKCio+5aQwBce6ruPDlXCLgCTk0MwLNxf7HKIPNbmM/VoajcjMlCJOWOiLn4Hon7w+NlGK1euhEaj6b5VVlaKXRJ5GKvNjg8POgYX3sl3ikQD8nHnjL1bshLgw93YyUXkrvzh4eHhkMlkqK+v73G8vr4e0dHR57xPdHR0n85XKpVQKpXOKZiGpG35DajTGhHqr8A148/9OCOii6vTGLH7bFf3Kxd5JNdxaSxWKBTIzMzE1q1bu4/Z7XZs3boVU6dOPed9pk6d2uN8ANi8efN5zycaqPc6F9K6JSsBSjmndBL116dHqmAXgCnJoUhm9yu5kEtbXgBgxYoVuOeee5CVlYUpU6Zg9erV0Ov1WLJkCQBg0aJFiIuLw6pVqwAAv/71rzFz5kw899xzuO6667Bu3TocPnwYr776qqtLpSGovFmPXYWNkEg4pZNoIARBwPrcKgDATdxag1zM5eHl1ltvRWNjIx5//HHU1dUhIyMDmzZt6h6UW1FRAan0+wagadOm4YMPPsBjjz2GP/7xjxgxYgS++OILjBs3ztWl0hDUtXz5zJERSAj1E7kaIs91uLwVpU16+ClkuG58jNjlkJeTCIIgiF2EM2m1WgQFBUGj0UCtVotdDrkxo8WGqau2otVgweuLsjgzgmgAfr/+OD4+XIWbMuPx7M3pYpdDHqgvr98cCk5D1jenatFqsCAu2Bez0iLFLofIYxnMVnx9ohYAcDMH6tIgYHihIatr75XbpyRwRV2iAdh4sg56sw3JYX6YMixU7HJoCGB4oSHpeGUbcstb4SOT4JbJXNiQaCA+O9I5UDczHhIJ3wiQ6zG80JC0dm8pAGBeeiwiA1UiV0Pkueq1RuSUNAMA5mfEiVwNDRUMLzTk1GmM3f3z9146TORqiDzb/47XQBCAzKQQztijQcPwQkPOOzllsNoFZA8Lxbi4ILHLIfJoG47XAADmZ8SKXAkNJQwvNKR0mG34oHMfo3svY6sL0UCUNulxokoDmVSCa7m2Cw0ihhcaUj49UoU2gwWJoX6YM5rruhANxIZjjlaXS1PDER7APeZo8DC80JBhtwt4s3Og7uJpyZweTTQAgiDgy+PVAID56ewyosHF8EJDxs6zjShu1CNQKef0aKIBOl2jRUmjHkq5FFeNZSsmDS6GFxoyXt1ZAgC4ZXICApQu39aLyKttPOmYsXdFWiQCVT4iV0NDDcMLDQnHKtuQU9IMuVSCpRyoSzQggiBg06k6AMDccdEiV0NDEcMLDQmv7CgGANyQEYvYYF+RqyHybGcb2lHSpIdCJsUV3BeMRMDwQl6vuLEd355xvEv8+cwUkash8nxdrS6XjQhnlxGJguGFvN5ru0ogCMCc0ZEYGRUodjlEHu8bdhmRyBheyKvVa4347IhjOucvLmerC9FAlTfrkVerhUwqwZVcK4lEwvBCXm3tnlKYbXZMTg5BZlKo2OUQebxvTztaXS4ZHooQf4XI1dBQxfBCXkvTYcH7BxxbAXCsC5FzdHcZjWWXEYmH4YW81lt7y9BusmJUVCBmjeKMCKKBamo34VhlGwDgKoYXEhHDC3klrdGCN/Y4FqVbfkUqpNwKgGjAdhY0QhCAsbFqRKlVYpdDQxjDC3mlt/eWQWu0IjUygLvdEjnJtoIGAODaLiQ6hhfyOjqjBa/vcWzA+NAVqdyAkcgJrDY7dhU2AgAuZzcsiYzhhbzOOznl0HRYMDzCH9dP4G63RM6QW94KndGKED8fZCQEi10ODXEML+RV9CYrXt/tGOvCVhci59le4Gh1mTkygn9XJDqGF/Iq7+SUo9VgwbBwf8xjqwuR02zPd4x3mcXxLuQGGF7IaxjMVrzW2eqyfFYq5DI+vImcobqtAwX1OkgljpYXIrHx2Z28xjs55WjRm5EU5of5GWx1IXKWroG6ExNDEOzHVXVJfAwv5BW0Rgte2VkMAHjoihFsdSFyor1FTQCAy1LDRa6EyIHP8OQVXt9dijaDBamRAfjZxDixyyHyGna7gJziZgDApQwv5CYYXsjjNbeb8EbnWJeHrxzJmRBETlTYoEOz3gxfHxmnSJPbYHghj/fyjmLozTaMjwvC3HHcb4XImfYWOVpdJg8LhULOlwxyD3wkkker1XTgnf3lAIBHrh4FiYStLkTOtK9zvMulKWEiV0L0PYYX8mj/3loEs9WOKcNCMWME++OJnMlqs+NAaQsAjnch98LwQh6rrEmPjw9XAgB+x1YXIqc7Ua1Bu8mKIF8fjI5Ri10OUTeGF/JYL2wphM0uYNaoCExODhW7HCKvc6DE0epyyfBQDoQnt8LwQh7pVLUGXx6rAQA8fNUokash8k655Y7wwjcH5G4YXsjjCIKAv3+dBwCYnxGLcXFBIldE5H0EQUBueSsAIDMpRORqiHpieCGPs72gATklzVDIpXiErS5ELlHcqEerwQKlXIqxsXyDQO6F4YU8itVmx6qN+QCAJdOSkRDqJ3JFRN6pq8soPT6Y67uQ2+EjkjzKx4ercLahHcF+PvjlrFSxyyHyWofLOruMktllRO6H4YU8ht5kxfObCwEAv7piBIJ8fUSuiMh7dY13yeJ4F3JDDC/kMf67qwRN7SYkhfnhrkuSxC6HyGtpDBaUNOkBAJMSGV7I/TC8kEeo1xrx2i7H5ot/mJvGPngiFzpZrQEAJIb6IcRfIXI1RD/FVwDyCM9+W4AOiw2TEoNxDTdfJHKprvAynssQkJtieCG3d7yyDZ/kVgEAHrt+DLcBIHKxU53hhWsokbtieCG3ZrcLePJ/pwEAN06MY/870SBgywu5O4YXcmtfHKvG0Yo2+Clk+MM1aWKXQ+T1NAYLKloMAIBxcdyMkdwTwwu5Lb3Jiqe/cSxI9+CsVESpVSJXROT9TtV8P1g32I+Ddck9MbyQ21qzvQgNOhMSQ/2w9LJhYpdDNCScYpcReQCGF3JL5c16vL67FADw2HWjofKRiVwR0dBQUK8DAKRFB4pcCdH5MbyQW/rb13kw2+yYPiIcV46JErscoiHjbH07AGBEFMMLuS+GF3I7u882YvOZesikEjzOqdFEg8ZuF3C2wdHyMjIqQORqiM6P4YXcitlqx5MbHFOjF01N4rs/okFU2WqA0WKHQi5FUpi/2OUQnRfDC7mV1/eUoLhRjzB/BX4ze6TY5RANKYWdXUapEQGQSdniSe6L4YXcRmWLAf/eehYA8KfrRiPIj7tGEw2mwnp2GZFnYHght/GX/52G0WJH9rBQ/GxinNjlEA05RQ0crEuegeGF3MLmM/XYktcAuVSCvy0Yx0G6RCIoa9YDAIaHc7wLuTeGFxKdwWztHqS7bMZwvusjEklFs2NbgIRQP5ErIbowhhcS3b+3FqG6rQNxwb741RUjxC6HaEhqN1nRrDcDAJLCGF7IvTG8kKjO1uvw+u4SAMCTN4yFr4Ir6RKJobyzyyjUX4FAFQfLk3tjeCHRCIKAx744BatdwJzRUVxJl0hEXV1GiewyIg/A8EKi+ehQJQ6UtsDXR4YnbxgjdjlEQ1p5C8MLeQ6XhZeWlhbceeedUKvVCA4OxtKlS9He3n7B8x966CGMGjUKvr6+SExMxK9+9StoNBpXlUgiatAa8feNeQCAh68aifgQPmESiamiM7xwvAt5ApeFlzvvvBOnT5/G5s2b8dVXX2HXrl24//77z3t+TU0Nampq8Oyzz+LUqVN46623sGnTJixdutRVJZKIHv/yNHRGK9Ljg7Dk0mFil0M05NVpjACA2GBfkSshuji5K35oXl4eNm3ahEOHDiErKwsA8J///AfXXnstnn32WcTGxv7kPuPGjcOnn37a/XVKSgr+/ve/46677oLVaoVc7pJSSQSbTtVi0+k6yKUSPL1wApchJ3ID9VpHeIlWq0SuhOjiXNLykpOTg+Dg4O7gAgBz5syBVCrFgQMHev1zNBoN1Go1g4sX0XRY8PiXjjVdfj4zBaNj1CJXREQAUK81AQAi1UqRKyG6OJekgrq6OkRGRvb8RXI5QkNDUVdX16uf0dTUhKeeeuqCXU0AYDKZYDKZur/WarV9L5gGzaqNeWjQmTA8wh/Lr0gVuxwiAmC12dGsdzyPRrHlhTxAn1peHn30UUgkkgve8vPzB1yUVqvFddddhzFjxuDJJ5+84LmrVq1CUFBQ9y0hIWHAv59cY19xE9YdqgQAPH3jBKh8uKYLkTtoajdDEAC5VIJQP4XY5RBdVJ9aXh5++GEsXrz4gucMHz4c0dHRaGho6HHcarWipaUF0dHRF7y/TqfD3LlzERgYiM8//xw+PhdeLGnlypVYsWJF99darZYBxg0ZLTb88bOTAIA7sxMxZVioyBURUZeu8S4RgUpIOQaNPECfwktERAQiIiIuet7UqVPR1taG3NxcZGZmAgC2bdsGu92O7Ozs895Pq9Xi6quvhlKpxIYNG6BSXbz5UqlUQqlkH627e2FLIcqaDYhWq/CHa9LELoeIfqArvEQG8rmUPINLBuyOHj0ac+fOxbJly3Dw4EHs3bsXy5cvx2233dY906i6uhppaWk4ePAgAEdwueqqq6DX6/HGG29Aq9Wirq4OdXV1sNlsriiTBsnRila8tsuxBcBTC8ZBzaXHidxKq8Gxp1FYAMMLeQaXTeN5//33sXz5csyePRtSqRQLFy7Ev//97+7vWywWFBQUwGBwLIx05MiR7plIqak9B3KWlpYiOTnZVaWSCxktNjz8yXHYBWBBRiy3ACByQ5oOCwAgyJdvLMgzuCy8hIaG4oMPPjjv95OTkyEIQvfXl19+eY+vyTs8+20BShr1iAxU4skbxopdDhGdQ1d4Uau4LAV5Bu5tRC5zsLQFb+wtBQA8vXA8gjmLgcgtaTusANjyQp6D4YVcwmC24nfrj0MQgJsz43FFGruLiNxVd8sLwwt5CIYXcomnv8lHebMBsUEq/Hked4wmcmcc80KehuGFnG5fURPeySkHADxz0wTOLiJyczqjI7wE8m+VPATDCzmVzmjB79afAOBYjG76iIuvC0RE4jJZ7QAApQ9fEsgz8JFKTvWPjXmobutAfIgv/njtaLHLIaJeMHeFFzlfEsgz8JFKTvPd6Tp8eLASEgnwr5vS4a/ktEsiT2C2MbyQZ+EjlZyiQWvEHz51dBctmz4cU1PCRK6IiHrLZOkKL9wslTwDwwsNmN0u4JH1J9BqsGBMjBoPXzVS7JKIqA/Y8kKeho9UGrC3c8qwq7ARSrkUL96WwXdvRB7GZHHsH6dgeCEPwUcqDUhBnQ6rvskHAPzputEYERUockVE1F8SSMQugahXGF6o30xWG3697ijMVjtmjYrA3ZckiV0SEfWDVOIILXbuL0ceguGF+u1fmwqQX6dDmL8C/7wpHRIJ37UReaKuP10bwwt5CIYX6pc9Z5vw+h7Hpov/vGkCIgKVIldERP0lkzrSi8DwQh6C4YX6rLndhBUfHwPgWEV39mhuukjkyb7vNhK5EKJeYnihPrHbBTz8yXE06ExIifDHY9dx00UiT9fV5Wu1Mb2QZ2B4oT55bXcJdhQ4pkWvuXMSfBWcFk3k6fw6/447OqdME7k7hhfqtSMVrfjXtwUAgCfmjUVatFrkiojIGbrCi8FsFbkSot5heKFe0RgseOiDo7DaBVw/IQa3T0kQuyQicpKAzn3I9CaGF/IMDC90UYIg4PefHkd1WwcSQ/2w6sbxnBZN5EX8OsNLu4ndRuQZGF7oot7dX45vT9fDRybBS3dMRKDKR+ySiMiJApTsNiLPIhe7AHJvp6o1+NtXeQCAldeMxoT4YHELIrdlswtoN1nRbrKiw2yDyWqDyWqHyWL//nOrHSaLDRabAAECBAEQAEAQIDg+dK814iOXwkcmhUImhaLzcx+ZBAq5FEq5FH4KOQKUjpu/Us59eQbAX+F4KdAZGV7IMzC80HnpjBY89OFRmG12zBkdhSWXJotdEg0CQRCg6bCgWW9Gc7sZze0mNOnNaGk3o1lvQqvBAp3RAp3R+oOPjtAiJoVMCn+lDEG+Pgj1V3TfQvwVCPNXIMRPgbAABSICVIgJViHMX8Huz06h/goAQIveLHIlRL3D8ELnJAgCfr/+BEqb9IgNUuFfN03gE70XMFvtqNcaUasxolbTgVqNEXUaI2raOlCndXzeojfDOoDVyhQyKXwVMijlUih9pFDKHZ+rfDqPyaWQy6SQwLEsvQQSx8fOz7v2BrTa7DBb7bDYBJi7P3d8NFntMJgdgclosTv+bTY7zAY7Wg0WlDUbLl6nXIqYIBViglSIDfJFTLAKscG+SAr1x7AIf8SoVZBKh8ZjvmuF7EadSeRKiHqH4YXO6Y09pfjmVB18ZBKsuXMSQjrfmZF7s9sFNOhMKG/Wo7zFgIpmQ+dHParbjGhq7/2LU6BKjvAAJcI6WzDCApQID1Ag2E+BQJUcapUcgSofBPb4KIdSPrhr/1htduhNNrSbrdCbrGgzWNCiN6NFb0arwdF61Gowo1lvRovehAatCY3tJpitdpQ3G1B+nqCjlEsxLNy/xy0lMgBp0YHwU3jXU2d4gCO89OXxQSQm7/oLJKc4WNqCVd/kAwAev34MJiaGiFwR/ZjGYEFRow5FDe04W9+OsmY9ypsNqGgxwGS1X/C+P2xxiAny7fF5dJAK4QFKhPj7DHoI6S+5TIogPymC/Ho/kLyrBaqmzdH6VKPpQG2b4+uyZn33dcyv0yG/TtfjvhIJMCzMH6Nj1BgTq8bomECMjlEjWq3y2NZJtryQp2F4oR4adEY8+MER2OwCFmTE4q5LksQuaUhr1JlQWO8IKUUN7TjboENRg/6C75BlUgnign2RFOaHxFC/zo/+iA9xBJVQjvWAQi5FQqgfEkL9zvl9q82O6rYOlDTpUdqoR2mTHiVN7Sisb0ejzoSSJj1KmvT4+mRt931C/RWYmBCMSUkhmJgYjPT4YPgrPeMptju8sOWFPIRn/GXRoLDa7Fj+wVE06kwYGRWAf3A9l0FjtdlR0qRHXq0WZ2q1OFOjRV6t7oIhJSZIhdTIAKREBCAlwh+JYf5ICvVDXIgvfGSceTMQcpkUSWH+SArzx6xRPb/XqDMhr1bb/X+VV6tFcaMeLXoztuY3YGt+AwBHiBwVFYhJScGYnByKaSnhbrv7elSgCgDQZrDAYLZ6XbcYeR8+Qqnbv74twMHSFgQo5Xj5rkw+gbmIyWpDfq0Ox6vacKpag7xaHQrqdTCfo7tHIgGSQv2QGhmI1MiA7ltKhD/X2xFJRKASEYERmDEyovuY0WLDmVotjpS34mhFG45UtKJWY3QE0Vot3ttfAQBIiw7EtJRwXJoahuzhYd0r24otyM8HapUcWqMVlS0dGBUdKHZJRBfkHn85JLpNp2rx310lAIB/3TQBKREBIlfkHWx2ASWN7ThW2YYTVRqcqGrDmVotLOfYvddfIUNazPdjKMbEqDHKCweHeiOVjwyTEkMw6Qfjw2o1HThS3obc8lbklDQjr1bbPYZm7d5SyKUSZCQEY/boKFw5JhIpEQGitnQmhvnhVLUWFS0Ghhdye3xWJJQ0tuORT04AAJZNH4ZrxseIXJHnam434XB5K46Ut+J4VRtOVmmgN/90yfUQPx+kJwRjfFwQxnQO/EwI8RsyU3OHgpggX1w3wRfXTXD8PTW3m5BT0oy9Rc3YW9SEihYDDpe34nB5K57ZlI/kMD/MGR2FK8dEITMpBPJB7vpLCvXHqWotypv1g/p7ifqD4WWI0xktWPbOYbSbrJiSHIrfz00TuySPIQgCypoNOFTWgsNlLThc3oqSxp8+8fv6yDA+LgjpCUGYEB+MjIRgxIf4cjzREBMWoMT1E2Jx/YRYAEBliwE7Chux5Uw9coqbUdZswOt7SvH6nlKE+PngmvExmJ8ei8nJoYMSahPDHIOXS5sYXsj9MbwMYXa7gN9+dAzFjXpEq1V46c6JHOh5AVabHadrtJ1hpRWHy1vQ1P7TFUlHRgUgMykUExOCMSEhCKkRAYP+LprcX0KoH+6+JAl3X5KEdpMVuwsbsTmvHtvyG9BqsOCDAxX44EAFYoJUmJceixvSYzE2Vu2y0DsyytFVXFivu8iZROJjeBnCVm8pxJa8BijkUry6KBORnTMOyMFuF5Bfp8O+4ibkFDfjQGnLT5bAV8ikSE8IQlZyKLKSQpCZFIJgPy7oR30ToJTjmvExuGZ8DKw2O/aXtODLY9XYdKoOtRojXt1Vgld3lWBkVABun5KIGyfG92ldm95Ii1YDAPLrdBAEgS2D5NYkQtcuaF5Cq9UiKCgIGo0GarVa7HLc1qZTtfj5e0cAAM/fko4bJ8WLXJH4BEFAcaMeOcVN2FfcjP0lzWg1WHqco1bJMTk5FFnJoZicHIJxcUFQ+XjGYm7keYwWG3YUNGLD8WpsyWvonpGmlEtx3fgY3J6diKykEKcEDbPVjjGPb4LVLmDvo1cgLth3wD+TqC/68vrNlpchKL9OixUfHwcALL1s2JAOLq16M3YXNWFnQSP2FDWiXttzXRV/hQxThoViakoYpqWEY3SMGjIOqqVBovKRYe64aMwdFw1NhwUbjlXj/QMVyK/T4bOj1fjsaDVGRgVg6WXDMD8jbkBBWiGXIiUiAAX1OuTXahleyK2x5WWIaTOYccNLe1HRYsBlqeF4a8nkITUew2qz43hVG3YWNGLn2SacqGrDD/8CFHIpspJCMC0lDFNTwjEhPojjgMitCIKA41UafHigAhuO16DD4pjNFh6gxD1Tk3DXJUn93ovstx8dw+dHq/Hr2SPw2ytHOrNsootiywudU9cKuhUtBiSE+uI/t08cEsGlQWfE9vwG7CxsxJ6zTdAae45bSYsOxMyRjkXHMpNC2A1Ebk0icawPk5EQjD9dPxrrDlbgzb1lqNUY8dzmQqzZUYTbJifil5enIFLdt3Fsk5JC8PnRauSWt7qoeiLnYHgZQv6xMR97iprg6yPDq3dnee1O0YLgGGi75Uw9tuQ34HhlW4/vB/n6YPqIcMwYGYEZIyIQHcSByuSZ1Cof3D8jBUsuHYaNJ2vx6q4SnK7R4q19ZfjwYAXuzE7Czy8f3uvB+FlJjkX2jlS0wmqzD4k3N+SZGF6GiPcPlGPt3lIAwHO3pGN0jHd1qZmsNhwoacHWvHpsyWtAdVtHj++nxwfh8lGRuHxUBCbEB3PcCnkVH5kU8zPicEN6LPYUNWH1lrPILW/F2r2l+OBgOe6ZmoxfzkpFkO+FZyiNjApEoFIOncmK/DodxsUFDdK/gKhvGF6GgD1nm/D4l6cBAA9fORLXeskKulqjBVvz6rH5TD12FjT2WMlWKZdi+ohwzB4dhdlpkX1uPifyRBKJBNNHROCy1HDsOtuEFzYX4lhlG/67qwQfH67Eb68ciTumJJ63RUUmlWBiUgh2FTbiQGkLwwu5LYYXL1fU0I5fvJ8Lm13AgoxYLL8iVeySBqTNYMZ3Z+rxzcla7Clq6rFHUESgEnNGR2J2WhQuTQ2Hr4JjV2hokkgkjnFcI8KxvaAB/9iYj6KGdjz+5Wm8k1OOx68f02NjyR+6LDUMuwobsauwEUsvGzbIlRP1DmcbebFWvRkL/m8vypsNyEwKwfv3ZXvkYNSmdhO+O12Pb07VIqe4GVb79w/Z1MgAzB0bjSvHRGF8XBD3BiI6B6vNjg8PVuD5zYXdaxfNz4jFn68fg/AAZY9zC+t1uOqFXVDKpTj2+FV8E0CDhrONCGarHQ+8l4vyZgPiQ3zx37szPSq4NOiM2HSqDhtP1uJgaQt+kFeQFh2Ia8fH4Jpx0RgRxd1viS5GLpPi7qnJuCEjDi9uOYu39pXiy2M12FnYiD9dOxo3ZcZ3L3Q3IjIAsUEq1GiM2F/SjFlpkSJXT/RTDC9eSBAE/OnzkzhY2oIApRxrF0/+ybsrd6TpsODb03XYcKwG+4qbegSWCfFBmDsuGteMi8GwcH/xiiTyYEG+Pnh83hgsmBiLRz89iTO1Wvxu/Ql8fbIW/7opHRGBSkgkEsxKi8T7Byqw6VQdwwu5JXYbeaFXdhbj6W/yIZUAaxdPxuWj3PfJx2ixYXt+A744Vo3t+Y0w2+zd38tICMZ142Mwd1w0EkL9RKySyPtYbHas3VOK5zcXwmS1IzxAgWdvTsfloyKRU9yM21/bD7VKjsOPXQmFnFOmyfXYbTSEfXWiBk9/kw8AeGLeWLcMLlabHTklzfjyWA2+PVUH3Q82OxwRGYAFE+Mwb0IsEsMYWIhcxUcmxQMzUzArLRK/+vAo8ut0WPzmIdx32TD8fm4aIgOVaNCZsPtsI2aPjhK7XKIe2PLiRQ6UNOPuNw7CbLNj8bRkPHnDWLFL6qGgTof1uZX4/GgNmtq/30MoLtgX89JjMT8jFmnRgdzNlmiQGS02rNqYh7dzygEAl6WGI1KtxGdHqjF3bDReuTtT5AppKGDLyxBU1KDDsncOw2yz4+qxUfjz9WPELgmAY8bThuM1WJ9bhZPVmu7jIX4+uG5CDOZnxCEzMYSzhIhEpPKR4S/zx2FqShh++9Fx7Clq6v7e5rx61Go6EBPEjRrJfTC8eIEGrRH3rD0ErdGKSYnBePG2iaKuIGu12bGzsBHrc6uwJa++ey0WH5kEs9OicFNmPGaOiuCGh0RuZu64GCSG+mPZO4e7V6m22QV8eKACK64aJXJ1RN9jt5GH05usuPXVHJyq1mJYuD8+/cU0hIq0Z1FRQzs+PlyJz45U9+gWGhurxk2Z8ZifESdabUTUe83tJtz71iEcr/q+tfTkk1chUHXh7QWIBoLdRkOExWbHL98/glPVWoT5K/DWksmDHg6MFhu+PV2HDw5U4EBpS/fxMH8FFkyMw8JJ8RgT6/0hksibhAUo8d592bj3rUM4VObYYXrlZyfx0h2TRK6MyIHhxUMJgoA/f3EKOwsbofKR4o3Fk5EUNnjrnxQ1tGPdwQp8eqSqe8VOqQS4Ii0St2QlYFZaJLuFiDxYoMoH7y7NRtqfNwEAvjpRi0evMSA+hLMASXwMLx7q+c2FWHeoElIJ8NLtk5CREOzy33m+VpaYIBVunZyAWycncFAfkRdR+chw9M9XYuJTmwEAFS0ML+QeGF480No9pfjPtiIAwFMLxmHOGNeuwVDRbMC7+8uwPvenrSx3ZCdi5shIUQcIE5HrhPgrcPLJq3Cssg1Th4eJXQ4RAIYXj/P50Sr89aszAIBHrhqJO7OTXPJ7BEHAnqImvL2vDFvzG9A1rJutLERDT6DKB9NHnHsXaiIxMLx4kG359XjkkxMAgHsvHYYHZ6U6/XfoTVZ8dqQKb+eUo6ihvfv4jJERWHRJEmalsZWFiIjExfDiIQ6VteAX7x2BzS7gxolxeOy60U5dibasSY+3c8qw/nBV93L9/goZbsqMx6JpyUiJCHDa7yIiIhoIhhcPkFerxb1vHYLJascVaZF45qYJTlmRtqtraO2eUuwobOzuGhoe7o9FU5OwMDOe6zoQEZHbYXhxcxXNBixaexA6oxWTk0Ow5o5JA56CbLba8dWJGry6qwT5dbru47NGReCeacmYMSKCy/UTEZHbYnhxY/VaI+564wAadSakRQfi9Xsmw1ch6/fP0xotWHewAmv3lKFOawQA+ClkuCUrAfdMS8aw8MFbJ4aIiKi/GF7cVFO7CXe8th8VLQYkhvrhnaVTEOTbvy6cmrYOvLm3FB8erER753iWiEAlFk9Lxl3ZSQjyY9cQERF5DoYXN9SqN+Ou1w+guFGP2CAV3r8vG5GBqj7/nPw6Lf67swT/O14Dq90xoGVEZACWzRiO+RmxUMr734pDREQkFpet397S0oI777wTarUawcHBWLp0Kdrb2y9+RzgGkl5zzTWQSCT44osvXFWiW9J0WHD32gPIr9MhMlCJD5ZdgoTQvq1oeayyDfe9fRhzV+/G50erYbULmDo8DG8unoxvfzMDt2QlMLgQEZHHclnLy5133ona2lps3rwZFosFS5Yswf33348PPvjgovddvXq1U6cBe4p2kxWL3zzYvdHiB8uykdyHcSgHSprx0vYi7D7bBACQSIBrx8XggZnDMSE+2EVVExERDS6XhJe8vDxs2rQJhw4dQlZWFgDgP//5D6699lo8++yziI2NPe99jx07hueeew6HDx9GTEyMK8pzSx1mG+596xCOVrQh2M8H792XjdTIwIveTxAE7CxsxJrtRd27v8qkEizIiMMvLk9BaiTXZyEiIu/ikvCSk5OD4ODg7uACAHPmzIFUKsWBAwfws5/97Jz3MxgMuOOOO7BmzRpER0f36neZTCaYTKbur7Va7cCKF4HRYsOydw7jYGkLApVyvHtvNkbHqC94H7tdwHdn6rFmexFOVmsAAAq5FLdkxeOBGSl97moiIiLyFC4JL3V1dYiMjOz5i+RyhIaGoq6u7rz3++1vf4tp06Zh/vz5vf5dq1atwl/+8pd+1yo2o8WGX7yXiz1FTfBTyPDWvVMwPj7ovOcLgoAteQ14YXMhztQ6gpqvjwx3XZKIZdOHI1Ld94G9REREnqRP4eXRRx/FM888c8Fz8vLy+lXIhg0bsG3bNhw9erRP91u5ciVWrFjR/bVWq0VCQkK/ahhsRosND7ybi52FjVD5SLF28WRkJoWc89yu7qEXNhfieJWjpSVAKcfiacm497JhCPVXDGbpREREoulTeHn44YexePHiC54zfPhwREdHo6Ghocdxq9WKlpaW83YHbdu2DcXFxQgODu5xfOHChZg+fTp27NhxzvsplUoolcre/hPcRofZ0VW0p6gJvj4yvLE4C5ecZ7v5fUVNeG5zIXLLHWNafH1kWHxpMu6fPhwhDC1ERDTE9Cm8REREICLi4tuiT506FW1tbcjNzUVmZiYARzix2+3Izs4+530effRR3HfffT2OjR8/Hi+88ALmzZvXlzLdnsFsxdK3DiOnpBl+ChneXDwZ2ecILgdLW/D85gLsL2kBACjlUtx9SRJ+fnkKwgM8L7ARERE5g0vGvIwePRpz587FsmXL8Morr8BisWD58uW47bbbumcaVVdXY/bs2XjnnXcwZcoUREdHn7NVJjExEcOGDXNFmaLQm6xY8tYhHCxtQYBSjreWTEZWcmiPc07XaPDMpgLsKmwEAChkUtw+JQG/nJWKKI5pISKiIc5l67y8//77WL58OWbPng2pVIqFCxfi3//+d/f3LRYLCgoKYDAYXFWC22k3WbF47UEcLm9FoFKOt5dOwaTE78e4VLYY8PzmQnx+tBoAIJdKcMvkBCyflYrYYF+xyiYiInIrEkEQBLGLcCatVougoCBoNBqo1ReebjyYqts6cOnT2wAAapUc7y7NRnpCMADHdgBrthfhnZxymG12AMC89Fg8ctVIJIVxs0QiIvJ+fXn95t5Gg6Cp3dQdXADg7XunID0hGEaLDW/uLcP/7SiCzujYMHFaShgevSaNK+ISERGdB8OLi1W3deDu1w90f/3szelIjw/Gp7lVePa7AtRqjACAtOhAPHpNGmaOjBiSWyMQERH1FsOLCxU3tuPu1w+gRmNEXLAv3rsvG60GM3728j4cr2wDAMQGqfDwVaOwYGIcZFKGFiIiootheHGRU9UaLFp7EC16M1Ii/PHszen499az3YNx/RUyLL9iBJZcmgyVD3d4JiIi6i2GFxc4WNqCpW8dgs5kxYjIAFyaGo47XjuADosNEglwc2Y8Hrl6FCIDOe2ZiIiorxhenOy703V46MOjMFkds4YadCa8ta8MAJCVFIIn5o294N5FREREdGEML0709r4yPPm/0/jh5HNNhwUxQSqsvHY05k2I4WBcIiKiAWJ4cQK7XcDTm/Lx6q6SHsd9ZBI8MCMFD85Kha+C41qIiIicgeFlgIwWGx755Di+OlHb4/jU4WF4asE4pEYGiFQZERGRd2J4GYA2gxm3/nc/Cup13cfCAxR47LoxmJ8Ryy4iIiIiF2B46afixnbMfm5nj2N3XZKI312VhiA/H5GqIiIi8n4ML/3waW4VHv7keM9jv5iGzKSQ89yDiIiInIXhpQ8EQcDP/m8fjnWujgsAt2TF46/zx3GhOSIiokHC8NJLdruA4X/c2OMYW1uIiIgGH8NLL/1wUG6gSo7cx66EQi4VsSIiIqKhieGll0ZGBeKfN01AmL8Cs0dHiV0OERHRkMXw0ksyqQS3ZCWIXQYREdGQx34PIiIi8igML0RERORRGF6IiIjIozC8EBERkUdheCEiIiKPwvBCREREHoXhhYiIiDwKwwsRERF5FIYXIiIi8igML0RERORRGF6IiIjIozC8EBERkUdheCEiIiKP4nW7SguCAADQarUiV0JERES91fW63fU6fiFeF150Oh0AICEhQeRKiIiIqK90Oh2CgoIueI5E6E3E8SB2ux01NTUIDAyERCIRrQ6tVouEhARUVlZCrVaLVoc74rW5MF6f8+O1uTBen/PjtTk/d7k2giBAp9MhNjYWUumFR7V4XcuLVCpFfHy82GV0U6vV/EM5D16bC+P1OT9emwvj9Tk/Xpvzc4drc7EWly4csEtEREQeheGFiIiIPArDi4solUo88cQTUCqVYpfidnhtLozX5/x4bS6M1+f8eG3OzxOvjdcN2CUiIiLvxpYXIiIi8igML0RERORRGF6IiIjIozC8EBERkUdheHGilpYW3HnnnVCr1QgODsbSpUvR3t5+0fvl5OTgiiuugL+/P9RqNWbMmIGOjo5BqHjw9PfaAI5VF6+55hpIJBJ88cUXri1UJH29Pi0tLXjooYcwatQo+Pr6IjExEb/61a+g0WgGsWrXWLNmDZKTk6FSqZCdnY2DBw9e8PxPPvkEaWlpUKlUGD9+PDZu3DhIlYqjL9fntddew/Tp0xESEoKQkBDMmTPnotfTk/X1sdNl3bp1kEgkWLBggWsLFFFfr01bWxsefPBBxMTEQKlUYuTIke71tyWQ08ydO1dIT08X9u/fL+zevVtITU0Vbr/99gveZ9++fYJarRZWrVolnDp1SsjPzxc++ugjwWg0DlLVg6M/16bL888/L1xzzTUCAOHzzz93baEi6ev1OXnypHDjjTcKGzZsEIqKioStW7cKI0aMEBYuXDiIVTvfunXrBIVCIaxdu1Y4ffq0sGzZMiE4OFior68/5/l79+4VZDKZ8M9//lM4c+aM8Nhjjwk+Pj7CyZMnB7nywdHX63PHHXcIa9asEY4ePSrk5eUJixcvFoKCgoSqqqpBrtz1+nptupSWlgpxcXHC9OnThfnz5w9OsYOsr9fGZDIJWVlZwrXXXivs2bNHKC0tFXbs2CEcO3ZskCs/P4YXJzlz5owAQDh06FD3sW+++UaQSCRCdXX1ee+XnZ0tPPbYY4NRomj6e20EQRCOHj0qxMXFCbW1tV4bXgZyfX7o448/FhQKhWCxWFxR5qCYMmWK8OCDD3Z/bbPZhNjYWGHVqlXnPP+WW24Rrrvuuh7HsrOzhQceeMCldYqlr9fnx6xWqxAYGCi8/fbbripRNP25NlarVZg2bZrw+uuvC/fcc4/Xhpe+XpuXX35ZGD58uGA2mwerxD5jt5GT5OTkIDg4GFlZWd3H5syZA6lUigMHDpzzPg0NDThw4AAiIyMxbdo0REVFYebMmdizZ89glT0o+nNtAMBgMOCOO+7AmjVrEB0dPRiliqK/1+fHNBoN1Go15HLP3LLMbDYjNzcXc+bM6T4mlUoxZ84c5OTknPM+OTk5Pc4HgKuvvvq853uy/lyfHzMYDLBYLAgNDXVVmaLo77X561//isjISCxdunQwyhRFf67Nhg0bMHXqVDz44IOIiorCuHHj8I9//AM2m22wyr4ohhcnqaurQ2RkZI9jcrkcoaGhqKurO+d9SkpKAABPPvkkli1bhk2bNmHSpEmYPXs2zp496/KaB0t/rg0A/Pa3v8W0adMwf/58V5coqv5enx9qamrCU089hfvvv98VJQ6KpqYm2Gw2REVF9TgeFRV13utQV1fXp/M9WX+uz4/94Q9/QGxs7E8Cn6frz7XZs2cP3njjDbz22muDUaJo+nNtSkpKsH79ethsNmzcuBF//vOf8dxzz+Fvf/vbYJTcKwwvF/Hoo49CIpFc8Jafn9+vn2232wEADzzwAJYsWYKJEyfihRdewKhRo7B27Vpn/jNcwpXXZsOGDdi2bRtWr17t3KIHkSuvzw9ptVpcd911GDNmDJ588smBF05e6emnn8a6devw+eefQ6VSiV2OqHQ6He6++2689tprCA8PF7sct2O32xEZGYlXX30VmZmZuPXWW/GnP/0Jr7zyitildfPM9uVB9PDDD2Px4sUXPGf48OGIjo5GQ0NDj+NWqxUtLS3n7fKIiYkBAIwZM6bH8dGjR6OioqL/RQ8SV16bbdu2obi4GMHBwT2OL1y4ENOnT8eOHTsGUPngcOX16aLT6TB37lwEBgbi888/h4+Pz0DLFk14eDhkMhnq6+t7HK+vrz/vdYiOju7T+Z6sP9eny7PPPounn34aW7ZswYQJE1xZpij6em2Ki4tRVlaGefPmdR/rejMpl8tRUFCAlJQU1xY9SPrzuImJiYGPjw9kMln3sdGjR6Ourg5msxkKhcKlNfeK2INuvEXXoMvDhw93H/v2228vOOjSbrcLsbGxPxmwm5GRIaxcudKl9Q6m/lyb2tpa4eTJkz1uAIQXX3xRKCkpGazSB0V/ro8gCIJGoxEuueQSYebMmYJerx+MUl1uypQpwvLly7u/ttlsQlxc3AUH7F5//fU9jk2dOtWrB+z25foIgiA888wzglqtFnJycgajRNH05dp0dHT85Pll/vz5whVXXCGcPHlSMJlMg1m6y/X1cbNy5UohKSlJsNls3cdWr14txMTEuLzW3mJ4caK5c+cKEydOFA4cOCDs2bNHGDFiRI/prlVVVcKoUaOEAwcOdB974YUXBLVaLXzyySfC2bNnhccee0xQqVRCUVGRGP8El+nPtfkxeOlsI0Ho+/XRaDRCdna2MH78eKGoqEiora3tvlmtVrH+GQO2bt06QalUCm+99ZZw5swZ4f777xeCg4OFuro6QRAE4e677xYeffTR7vP37t0ryOVy4dlnnxXy8vKEJ554wuunSvfl+jz99NOCQqEQ1q9f3+MxotPpxPonuExfr82PefNso75em4qKCiEwMFBYvny5UFBQIHz11VdCZGSk8Le//U2sf8JPMLw4UXNzs3D77bcLAQEBglqtFpYsWdLjSaK0tFQAIGzfvr3H/VatWiXEx8cLfn5+wtSpU4Xdu3cPcuWu199r80PeHF76en22b98uADjnrbS0VJx/hJP85z//ERITEwWFQiFMmTJF2L9/f/f3Zs6cKdxzzz09zv/444+FkSNHCgqFQhg7dqzw9ddfD3LFg6sv1ycpKemcj5Ennnhi8AsfBH197PyQN4cXQej7tdm3b5+QnZ0tKJVKYfjw4cLf//53t3pjJBEEQRjcjioiIiKi/uNsIyIiIvIoDC9ERETkURheiIiIyKMwvBAREZFHYXghIiIij8LwQkRERB6F4YWIiIg8CsMLEREReRSGFyIiIvIoDC9ERETkURheiIiIyKMwvBAREZFH+X+NH3w+3BGTDQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Get the path from the matplotlib contour plot of the critical line\n", + "paths = CS.collections[0].get_paths()\n", + "caustic_paths = []\n", + "for path in paths:\n", + " # Collect the path into a descrete set of points\n", + " vertices = path.interpolated(5).vertices\n", + " x1 = torch.tensor(list(float(vs[0]) for vs in vertices))\n", + " x2 = torch.tensor(list(float(vs[1]) for vs in vertices))\n", + " # raytrace the points to the source plane\n", + " y1,y2 = sie.raytrace(x1, x2, z_s, packparams)\n", + "\n", + " # Plot the caustic\n", + " plt.plot(y1,y2)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e77da2e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PY39", + "language": "python", + "name": "py39" + }, + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/testbook/_autosummary/caustics.constants.rst b/docs/testbook/_autosummary/caustics.constants.rst new file mode 100644 index 00000000..db9ccc1f --- /dev/null +++ b/docs/testbook/_autosummary/caustics.constants.rst @@ -0,0 +1,23 @@ +caustics.constants +================== + +.. automodule:: caustics.constants + + + + + + + + + + + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.cosmology.rst b/docs/testbook/_autosummary/caustics.cosmology.rst new file mode 100644 index 00000000..e7188431 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.cosmology.rst @@ -0,0 +1,30 @@ +caustics.cosmology +================== + +.. automodule:: caustics.cosmology + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Cosmology + FlatLambdaCDM + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.data.hdf5dataset.rst b/docs/testbook/_autosummary/caustics.data.hdf5dataset.rst new file mode 100644 index 00000000..f181fc95 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.data.hdf5dataset.rst @@ -0,0 +1,29 @@ +caustics.data.hdf5dataset +========================= + +.. automodule:: caustics.data.hdf5dataset + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + HDF5Dataset + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.data.illustris_kappa.rst b/docs/testbook/_autosummary/caustics.data.illustris_kappa.rst new file mode 100644 index 00000000..b2a46200 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.data.illustris_kappa.rst @@ -0,0 +1,29 @@ +caustics.data.illustris\_kappa +============================== + +.. automodule:: caustics.data.illustris_kappa + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + IllustrisKappaDataset + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.data.probes.rst b/docs/testbook/_autosummary/caustics.data.probes.rst new file mode 100644 index 00000000..4a74f615 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.data.probes.rst @@ -0,0 +1,29 @@ +caustics.data.probes +==================== + +.. automodule:: caustics.data.probes + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + PROBESDataset + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.data.rst b/docs/testbook/_autosummary/caustics.data.rst new file mode 100644 index 00000000..63edce36 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.data.rst @@ -0,0 +1,33 @@ +caustics.data +============= + +.. automodule:: caustics.data + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + caustics.data.hdf5dataset + caustics.data.illustris_kappa + caustics.data.probes + diff --git a/docs/testbook/_autosummary/caustics.lenses.base.rst b/docs/testbook/_autosummary/caustics.lenses.base.rst new file mode 100644 index 00000000..f87a1ac9 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.base.rst @@ -0,0 +1,31 @@ +caustics.lenses.base +==================== + +.. automodule:: caustics.lenses.base + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Lens + ThickLens + ThinLens + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.epl.rst b/docs/testbook/_autosummary/caustics.lenses.epl.rst new file mode 100644 index 00000000..eecc55b2 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.epl.rst @@ -0,0 +1,29 @@ +caustics.lenses.epl +=================== + +.. automodule:: caustics.lenses.epl + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + EPL + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.external_shear.rst b/docs/testbook/_autosummary/caustics.lenses.external_shear.rst new file mode 100644 index 00000000..170f1e90 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.external_shear.rst @@ -0,0 +1,29 @@ +caustics.lenses.external\_shear +=============================== + +.. automodule:: caustics.lenses.external_shear + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + ExternalShear + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.mass_sheet.rst b/docs/testbook/_autosummary/caustics.lenses.mass_sheet.rst new file mode 100644 index 00000000..573ea10a --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.mass_sheet.rst @@ -0,0 +1,29 @@ +caustics.lenses.mass\_sheet +=========================== + +.. automodule:: caustics.lenses.mass_sheet + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + MassSheet + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.multiplane.rst b/docs/testbook/_autosummary/caustics.lenses.multiplane.rst new file mode 100644 index 00000000..c5bdd40e --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.multiplane.rst @@ -0,0 +1,29 @@ +caustics.lenses.multiplane +========================== + +.. automodule:: caustics.lenses.multiplane + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Multiplane + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.nfw.rst b/docs/testbook/_autosummary/caustics.lenses.nfw.rst new file mode 100644 index 00000000..0e609b7e --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.nfw.rst @@ -0,0 +1,29 @@ +caustics.lenses.nfw +=================== + +.. automodule:: caustics.lenses.nfw + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + NFW + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.pixelated_convergence.rst b/docs/testbook/_autosummary/caustics.lenses.pixelated_convergence.rst new file mode 100644 index 00000000..2aaa20f6 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.pixelated_convergence.rst @@ -0,0 +1,29 @@ +caustics.lenses.pixelated\_convergence +====================================== + +.. automodule:: caustics.lenses.pixelated_convergence + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + PixelatedConvergence + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.point.rst b/docs/testbook/_autosummary/caustics.lenses.point.rst new file mode 100644 index 00000000..8fcd8826 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.point.rst @@ -0,0 +1,29 @@ +caustics.lenses.point +===================== + +.. automodule:: caustics.lenses.point + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Point + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.pseudo_jaffe.rst b/docs/testbook/_autosummary/caustics.lenses.pseudo_jaffe.rst new file mode 100644 index 00000000..9d4bbc9e --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.pseudo_jaffe.rst @@ -0,0 +1,29 @@ +caustics.lenses.pseudo\_jaffe +============================= + +.. automodule:: caustics.lenses.pseudo_jaffe + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + PseudoJaffe + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.rst b/docs/testbook/_autosummary/caustics.lenses.rst new file mode 100644 index 00000000..ab8481e7 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.rst @@ -0,0 +1,44 @@ +caustics.lenses +=============== + +.. automodule:: caustics.lenses + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + caustics.lenses.base + caustics.lenses.epl + caustics.lenses.external_shear + caustics.lenses.mass_sheet + caustics.lenses.multiplane + caustics.lenses.nfw + caustics.lenses.pixelated_convergence + caustics.lenses.point + caustics.lenses.pseudo_jaffe + caustics.lenses.sie + caustics.lenses.singleplane + caustics.lenses.sis + caustics.lenses.tnfw + caustics.lenses.utils + diff --git a/docs/testbook/_autosummary/caustics.lenses.sie.rst b/docs/testbook/_autosummary/caustics.lenses.sie.rst new file mode 100644 index 00000000..d721451e --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.sie.rst @@ -0,0 +1,29 @@ +caustics.lenses.sie +=================== + +.. automodule:: caustics.lenses.sie + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + SIE + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.singleplane.rst b/docs/testbook/_autosummary/caustics.lenses.singleplane.rst new file mode 100644 index 00000000..78dcf533 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.singleplane.rst @@ -0,0 +1,29 @@ +caustics.lenses.singleplane +=========================== + +.. automodule:: caustics.lenses.singleplane + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + SinglePlane + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.sis.rst b/docs/testbook/_autosummary/caustics.lenses.sis.rst new file mode 100644 index 00000000..8240f489 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.sis.rst @@ -0,0 +1,29 @@ +caustics.lenses.sis +=================== + +.. automodule:: caustics.lenses.sis + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + SIS + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.tnfw.rst b/docs/testbook/_autosummary/caustics.lenses.tnfw.rst new file mode 100644 index 00000000..92afa7f4 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.tnfw.rst @@ -0,0 +1,29 @@ +caustics.lenses.tnfw +==================== + +.. automodule:: caustics.lenses.tnfw + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + TNFW + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.lenses.utils.rst b/docs/testbook/_autosummary/caustics.lenses.utils.rst new file mode 100644 index 00000000..58b8e1f5 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.lenses.utils.rst @@ -0,0 +1,31 @@ +caustics.lenses.utils +===================== + +.. automodule:: caustics.lenses.utils + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_magnification + get_pix_jacobian + get_pix_magnification + + + + + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.light.base.rst b/docs/testbook/_autosummary/caustics.light.base.rst new file mode 100644 index 00000000..005b78bb --- /dev/null +++ b/docs/testbook/_autosummary/caustics.light.base.rst @@ -0,0 +1,29 @@ +caustics.light.base +=================== + +.. automodule:: caustics.light.base + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Source + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.light.pixelated.rst b/docs/testbook/_autosummary/caustics.light.pixelated.rst new file mode 100644 index 00000000..51141fb2 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.light.pixelated.rst @@ -0,0 +1,29 @@ +caustics.light.pixelated +======================== + +.. automodule:: caustics.light.pixelated + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Pixelated + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.light.probes.rst b/docs/testbook/_autosummary/caustics.light.probes.rst new file mode 100644 index 00000000..8d2b2a1a --- /dev/null +++ b/docs/testbook/_autosummary/caustics.light.probes.rst @@ -0,0 +1,29 @@ +caustics.light.probes +===================== + +.. automodule:: caustics.light.probes + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + PROBESDataset + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.light.rst b/docs/testbook/_autosummary/caustics.light.rst new file mode 100644 index 00000000..c4294658 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.light.rst @@ -0,0 +1,34 @@ +caustics.light +============== + +.. automodule:: caustics.light + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + caustics.light.base + caustics.light.pixelated + caustics.light.probes + caustics.light.sersic + diff --git a/docs/testbook/_autosummary/caustics.light.sersic.rst b/docs/testbook/_autosummary/caustics.light.sersic.rst new file mode 100644 index 00000000..26ffc87d --- /dev/null +++ b/docs/testbook/_autosummary/caustics.light.sersic.rst @@ -0,0 +1,29 @@ +caustics.light.sersic +===================== + +.. automodule:: caustics.light.sersic + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Sersic + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.namespace_dict.rst b/docs/testbook/_autosummary/caustics.namespace_dict.rst new file mode 100644 index 00000000..d9d3f9b2 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.namespace_dict.rst @@ -0,0 +1,30 @@ +caustics.namespace\_dict +======================== + +.. automodule:: caustics.namespace_dict + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + NamespaceDict + NestedNamespaceDict + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.packed.rst b/docs/testbook/_autosummary/caustics.packed.rst new file mode 100644 index 00000000..ad17a27b --- /dev/null +++ b/docs/testbook/_autosummary/caustics.packed.rst @@ -0,0 +1,29 @@ +caustics.packed +=============== + +.. automodule:: caustics.packed + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Packed + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.parameter.rst b/docs/testbook/_autosummary/caustics.parameter.rst new file mode 100644 index 00000000..e550adca --- /dev/null +++ b/docs/testbook/_autosummary/caustics.parameter.rst @@ -0,0 +1,29 @@ +caustics.parameter +================== + +.. automodule:: caustics.parameter + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Parameter + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.parametrized.rst b/docs/testbook/_autosummary/caustics.parametrized.rst new file mode 100644 index 00000000..0d79ea16 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.parametrized.rst @@ -0,0 +1,36 @@ +caustics.parametrized +===================== + +.. automodule:: caustics.parametrized + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + check_valid_name + unpack + + + + + + .. rubric:: Classes + + .. autosummary:: + + Parametrized + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.rst b/docs/testbook/_autosummary/caustics.rst new file mode 100644 index 00000000..7cee0f52 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.rst @@ -0,0 +1,41 @@ +caustics +======== + +.. automodule:: caustics + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + caustics.constants + caustics.cosmology + caustics.data + caustics.lenses + caustics.light + caustics.namespace_dict + caustics.packed + caustics.parameter + caustics.parametrized + caustics.sims + caustics.utils + diff --git a/docs/testbook/_autosummary/caustics.sims.lens_source.rst b/docs/testbook/_autosummary/caustics.sims.lens_source.rst new file mode 100644 index 00000000..bf43ecf6 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.sims.lens_source.rst @@ -0,0 +1,29 @@ +caustics.sims.lens\_source +========================== + +.. automodule:: caustics.sims.lens_source + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Lens_Source + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.sims.rst b/docs/testbook/_autosummary/caustics.sims.rst new file mode 100644 index 00000000..478d8d37 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.sims.rst @@ -0,0 +1,32 @@ +caustics.sims +============= + +.. automodule:: caustics.sims + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + caustics.sims.lens_source + caustics.sims.simulator + diff --git a/docs/testbook/_autosummary/caustics.sims.simulator.rst b/docs/testbook/_autosummary/caustics.sims.simulator.rst new file mode 100644 index 00000000..8adc9481 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.sims.simulator.rst @@ -0,0 +1,29 @@ +caustics.sims.simulator +======================= + +.. automodule:: caustics.sims.simulator + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Simulator + + + + + + + + + diff --git a/docs/testbook/_autosummary/caustics.utils.rst b/docs/testbook/_autosummary/caustics.utils.rst new file mode 100644 index 00000000..64d42bd1 --- /dev/null +++ b/docs/testbook/_autosummary/caustics.utils.rst @@ -0,0 +1,31 @@ +caustics.utils +============== + +.. automodule:: caustics.utils + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_magnification + get_pix_jacobian + get_pix_magnification + + + + + + + + + + + + + diff --git a/testbook/_config.yml b/docs/testbook/_config.yml similarity index 77% rename from testbook/_config.yml rename to docs/testbook/_config.yml index 29706f85..e5c2767a 100644 --- a/testbook/_config.yml +++ b/docs/testbook/_config.yml @@ -30,3 +30,16 @@ repository: html: use_issues_button: true use_repository_button: true + +sphinx: + extra_extensions: + - 'sphinx.ext.autodoc' + - 'sphinx.ext.autosummary' + - 'sphinx.ext.napoleon' + - 'sphinx.ext.doctest' + - 'sphinx.ext.coverage' + - 'sphinx.ext.mathjax' + - 'sphinx.ext.ifconfig' + - 'sphinx.ext.viewcode' + config: + autosummary_generate: True \ No newline at end of file diff --git a/testbook/_toc.yml b/docs/testbook/_toc.yml similarity index 100% rename from testbook/_toc.yml rename to docs/testbook/_toc.yml diff --git a/testbook/citation.rst b/docs/testbook/citation.rst similarity index 100% rename from testbook/citation.rst rename to docs/testbook/citation.rst diff --git a/testbook/contributing.rst b/docs/testbook/contributing.rst similarity index 100% rename from testbook/contributing.rst rename to docs/testbook/contributing.rst diff --git a/testbook/get_start.ipynb b/docs/testbook/get_start.ipynb similarity index 98% rename from testbook/get_start.ipynb rename to docs/testbook/get_start.ipynb index a649406e..15043fe4 100644 --- a/testbook/get_start.ipynb +++ b/docs/testbook/get_start.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -24,7 +24,7 @@ "from astropy.io import fits\n", "import numpy as np\n", "\n", - "import caustic" + "import caustics" ] }, { @@ -54,7 +54,7 @@ "res = 0.05\n", "upsample_factor = 2\n", "fov = res * n_pix\n", - "thx, thy = caustic.get_meshgrid(\n", + "thx, thy = caustics.get_meshgrid(\n", " res / upsample_factor,\n", " upsample_factor * n_pix,\n", " upsample_factor * n_pix,\n", @@ -62,7 +62,7 @@ ")\n", "z_l = torch.tensor(0.5, dtype=torch.float32)\n", "z_s = torch.tensor(1.5, dtype=torch.float32)\n", - "cosmology = caustic.FlatLambdaCDM(name=\"cosmo\")\n", + "cosmology = caustics.FlatLambdaCDM(name=\"cosmo\")\n", "cosmology.to(dtype=torch.float32)" ] }, @@ -84,7 +84,7 @@ "# demo simulator with sersic source, SIE lens. then sample some examples. demo the model graph\n", "\n", "\n", - "class Simple_Sim(caustic.Simulator):\n", + "class Simple_Sim(caustics.Simulator):\n", " def __init__(\n", " self,\n", " lens,\n", @@ -343,8 +343,8 @@ } ], "source": [ - "sie = caustic.lenses.SIE(cosmology, name=\"sie\")\n", - "src = caustic.sources.Sersic(name=\"src\")\n", + "sie = caustics.lenses.SIE(cosmology, name=\"sie\")\n", + "src = caustics.sources.Sersic(name=\"src\")\n", "\n", "sim = Simple_Sim(sie, src, torch.tensor(0.8))\n", "\n", @@ -427,11 +427,11 @@ "\n", "The caustic tutorials are generally short and to the point, that way you can idenfity what you want and jump right to some useful code that demo's the particular problem you face. Below is a list of caustic tutorials and a quick description of what you will learn in each one::\n", "\n", - "- `LensZoo`: here you can see all the built-in lens mass distributions in `caustic` and how they distort the same background Seric source.\n", + "- `LensZoo`: here you can see all the built-in lens mass distributions in `caustics` and how they distort the same background Seric source.\n", "- `Playground`: here we demo the main visualizations of a lensing system (deflection angles, convergence, potential, time delay, magnification) in an interactive display so you can change the parameters by hand and see how the visuals change!\n", - "- `VisualizeCaustics`: here you can see how to find and display caustics, a must when using `caustic`!\n", + "- `VisualizeCaustics`: here you can see how to find and display caustics, a must when using `caustics`!\n", "- `Simulators`: here we describe the powerful simulator framework and how it can be used to quickly swap models, parameters, and other features and turn a complex forward model into a simple function.\n", - "- `InvertLensEquation`: here we demo forward ray tracing in `caustic` the process of mapping from the source plane to the image plane." + "- `InvertLensEquation`: here we demo forward ray tracing in `caustics` the process of mapping from the source plane to the image plane." ] }, { diff --git a/testbook/install.rst b/docs/testbook/install.rst similarity index 92% rename from testbook/install.rst rename to docs/testbook/install.rst index 077356f2..227cfc57 100644 --- a/testbook/install.rst +++ b/docs/testbook/install.rst @@ -7,7 +7,7 @@ Regular Install The easiest way to install is to make a new virtual environment then run:: - pip install caustic + pip install caustics this will install all the required libraries and then install caustic and you are ready to go! You can check out the tutorials afterwards to see some of caustic's capabilities. @@ -17,7 +17,7 @@ Developer Install First clone the repo with:: - git clone git@github.com:Ciela-Institute/caustic.git + git clone git@github.com:Ciela-Institute/caustics.git this will create a directory `caustic` wherever you ran the command. Next go into the directory and install in developer mode:: diff --git a/testbook/intro.md b/docs/testbook/intro.md similarity index 100% rename from testbook/intro.md rename to docs/testbook/intro.md diff --git a/testbook/license.rst b/docs/testbook/license.rst similarity index 100% rename from testbook/license.rst rename to docs/testbook/license.rst diff --git a/testbook/logo.png b/docs/testbook/logo.png similarity index 100% rename from testbook/logo.png rename to docs/testbook/logo.png diff --git a/docs/testbook/modules.rst b/docs/testbook/modules.rst new file mode 100644 index 00000000..e08069e3 --- /dev/null +++ b/docs/testbook/modules.rst @@ -0,0 +1,8 @@ +caustics +======= + +.. autosummary:: + :toctree: _autosummary + :recursive: + + caustics diff --git a/testbook/references.bib b/docs/testbook/references.bib similarity index 100% rename from testbook/references.bib rename to docs/testbook/references.bib diff --git a/testbook/requirements.txt b/docs/testbook/requirements.txt similarity index 100% rename from testbook/requirements.txt rename to docs/testbook/requirements.txt diff --git a/testbook/tutorials.md b/docs/testbook/tutorials.md similarity index 56% rename from testbook/tutorials.md rename to docs/testbook/tutorials.md index 6d3c6f60..f5b5ccea 100644 --- a/testbook/tutorials.md +++ b/docs/testbook/tutorials.md @@ -5,8 +5,8 @@ that you go through the tutorials yourself, but for quick reference a version of each tutorial is available here. [Basic Introduction](get_start.ipynb) \ -[LensZoo](https://website-name.com) \ -[Visualize Caustics](https://website-name.com) \ -[Multiplane Demo](https://website-name.com) \ -[InvertLens Equation](https://website-name.com) +[LensZoo](LensZoo.ipynb) \ +[Visualize Caustics](VisualizeCaustics.ipynb) \ +[Multiplane Demo](MultiplaneDemo.ipynb) \ +[InvertLens Equation](InvertLensEquation.ipynb) diff --git a/testbook/modules.rst b/testbook/modules.rst deleted file mode 100644 index 446b67c7..00000000 --- a/testbook/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -caustics -======= - -.. toctree:: - :maxdepth: 4 - - caustics From ece705d7349fe530ebedf878c0cedea776cf14c7 Mon Sep 17 00:00:00 2001 From: ztao2 Date: Tue, 5 Dec 2023 12:58:28 -0800 Subject: [PATCH 55/55] add pre_build --- .readthedocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 05041f5a..8071ed58 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -25,6 +25,10 @@ build: python: "3.9" apt_packages: - pandoc # Specify pandoc to be installed via apt-get + jobs: + pre_build: + # Generate the Sphinx configuration for this Jupyter Book so it builds. + - "jupyter-book config sphinx docs/" python: install: